Browse Source

pasture: 饲料分类

Yi 1 year ago
parent
commit
331128d23d

+ 10 - 0
backend/operation/enum.proto

@@ -23,4 +23,14 @@ message CattleCategoryParent {
     PERINATAL_CAW  = 5;    // 泌乳牛
     OTHER_CAW = 6;         // 其他
   }
+}
+
+message ForageCategoryParent {
+  enum Kind {
+    INVALID = 0;           // 无效
+    ROUGHAGE = 1;          // 粗料
+    CONCENTRATE  = 2;      // 精料(浓缩料)
+    HALF_ROUGHAGE_HALF_CONCENTRATE = 3;   // 粗料精料各半
+    OTHER = 4;        // 其他
+  }
 }

+ 33 - 0
backend/operation/pasture.proto

@@ -74,4 +74,37 @@ message SearchCattleCategoryResponse {
   int32 page = 1;
   int32 total = 2;
   repeated AddCattleCategoryRequest list = 3;
+}
+
+
+// 添加饲料分类
+message AddForageCategoryRequest {
+  int64 id = 1;
+  ForageCategoryParent.Kind parent_id = 2;  // 父类id
+  string parent_name = 3;        // 父类名称
+  string name = 4;               // 牧畜分类名称
+  string number = 5;            // 畜牧类别编号
+  IsShow.Kind is_show = 6;      // 是否启用
+  int64 created_at = 7;         // 创建时间
+  string created_at_format = 8;   // 创建时间格式
+}
+
+// 是否启用
+message IsShowForageCategory {
+  int64 forage_category_id = 1;
+  IsShow.Kind is_show = 2;
+}
+
+// 饲料分类查询列表
+message SearchForageCategoryRequest {
+  string parent_name = 1;
+  IsShow.Kind is_show = 2;
+  string name = 3;
+  PaginationModel pagination = 4; // 分页
+}
+
+message SearchForageCategoryResponse {
+  int32 page = 1;
+  int32 total = 2;
+  repeated AddForageCategoryRequest list = 3;
 }

+ 116 - 0
http/handler/pasture/cattle_category.go

@@ -127,3 +127,119 @@ func SearchCattleCategory(c *gin.Context) {
 
 	c.JSON(http.StatusOK, apiok.CommonResponse(res.ToPB()))
 }
+
+// ParentForageCategoryList 饲料父类列表
+func ParentForageCategoryList(c *gin.Context) {
+	res := middleware.BackendOperation(c).OpsService.ParentForageCategoryList(c)
+	c.JSON(http.StatusOK, apiok.CommonResponse(res))
+}
+
+func AddForageCategory(c *gin.Context) {
+	var req operationPb.AddForageCategoryRequest
+	if err := c.BindJSON(&req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.ParentId, valid.Required, valid.Min(1), valid.Max(6)),
+		valid.Field(&req.ParentName, valid.Required),
+		valid.Field(&req.Number, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.AddForageCategory(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+}
+
+func EditForageCategory(c *gin.Context) {
+	var req operationPb.AddForageCategoryRequest
+	if err := c.BindJSON(&req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Id, valid.Required),
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.ParentId, valid.Required, valid.Min(1), valid.Max(6)),
+		valid.Field(&req.ParentName, valid.Required),
+		valid.Field(&req.Number, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.EditForageCategory(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+}
+
+func IsShowForageCategory(c *gin.Context) {
+	var req operationPb.IsShowForageCategory
+	if err := c.BindJSON(&req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ForageCategoryId, valid.Required, valid.Min(1)),
+		valid.Field(&req.IsShow, valid.Required, valid.Min(1), valid.Max(2)),
+	); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.IsShowForageCategory(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+}
+
+func DeleteForageCategory(c *gin.Context) {
+	forageCategoryIdStr := c.Param("forage_category_id")
+	forageCategoryId, _ := strconv.Atoi(forageCategoryIdStr)
+
+	if err := valid.Validate(forageCategoryId, valid.Required, valid.Min(1)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.DeleteForageCategory(c, int64(forageCategoryId)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+}
+
+func SearchForageCategory(c *gin.Context) {
+	req := &operationPb.SearchForageCategoryRequest{}
+	if err := c.BindJSON(req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	req.Pagination = &operationPb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SearchForageCategoryList(c, req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, apiok.CommonResponse(res.ToPB()))
+}

+ 3 - 3
http/middleware/pagination.go

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

+ 9 - 2
http/middleware/sso.go

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

+ 12 - 3
http/route/app_api.go

@@ -47,8 +47,6 @@ func AppAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 
 		// 牧场管理
 		opsRoute := authRouteGroup(s, "/api/v1/ops/")
-
-		// 牧场列表
 		opsRoute.POST("/pasture/add", pasture.AddGroupPasture)
 		opsRoute.POST("/pasture/edit", pasture.EditGroupPasture)
 		opsRoute.POST("/pasture/list", pasture.SearchGroupPastureList)
@@ -60,12 +58,23 @@ func AppAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		opsRoute.GET("/cattle/category/parent_list", pasture.ParentCattleCategoryList)
 		opsRoute.POST("/cattle/category/add", pasture.AddCattleCategory)
 		opsRoute.POST("/cattle/category/edit", pasture.EditCattleCategory)
+		opsRoute.POST("/cattle/category/is_show", pasture.IsShowCattleCategory)
+		opsRoute.DELETE("/cattle/category/:cattle_category_id", pasture.DeleteCattleCategory)
+		opsRoute.POST("/cattle/category/list", pasture.SearchCattleCategory)
+
+		// 饲料类别
+		opsRoute.GET("/forage/category/parent_list", pasture.ParentForageCategoryList)
+		opsRoute.POST("/forage/category/add", pasture.AddForageCategory)
+		opsRoute.POST("/forage/category/edit", pasture.EditForageCategory)
+		opsRoute.POST("/forage/category/is_show", pasture.IsShowForageCategory)
+		opsRoute.DELETE("/forage/category/:forage_category_id", pasture.DeleteForageCategory)
+		opsRoute.POST("/forage/category/list", pasture.SearchForageCategory)
 	}
 }
 
 func authRouteGroup(s *gin.Engine, relativePath string) *gin.RouterGroup {
 	group := s.Group(relativePath)
 	// 中间件鉴权
-	group.Use(middleware.RequireAdmin(), middleware.Pagination(), middleware.CORS())
+	group.Use(middleware.RequireAdmin(), middleware.CORS())
 	return group
 }

+ 78 - 0
model/forage_category.go

@@ -0,0 +1,78 @@
+package model
+
+import (
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"time"
+)
+
+type ForageCategory struct {
+	Id         int64                                 `json:"id"`
+	ParentId   operationPb.ForageCategoryParent_Kind `json:"parent_id"`
+	ParentName string                                `json:"parent_name"`
+	Name       string                                `json:"name"`
+	Number     string                                `json:"number"`
+	IsShow     operationPb.IsShow_Kind               `json:"is_show"`
+	IsDelete   operationPb.IsShow_Kind               `json:"is_delete"`
+	CreatedAt  int64                                 `json:"created_at"`
+	UpdatedAt  int64                                 `json:"updated_at"`
+}
+
+func (s *ForageCategory) TableName() string {
+	return "forage_category"
+}
+
+func NewForageCategory(req *operationPb.AddForageCategoryRequest) *ForageCategory {
+	return &ForageCategory{
+		ParentId:   req.ParentId,
+		ParentName: req.ParentName,
+		Name:       req.Name,
+		Number:     req.Number,
+		IsShow:     operationPb.IsShow_OK,
+		IsDelete:   operationPb.IsShow_OK,
+	}
+}
+
+type ForageCategorySlice []*ForageCategory
+
+func (f ForageCategorySlice) ToPB() []*operationPb.AddForageCategoryRequest {
+	res := make([]*operationPb.AddForageCategoryRequest, len(f))
+	for i, v := range f {
+		res[i] = &operationPb.AddForageCategoryRequest{
+			Id:              v.Id,
+			Name:            v.Name,
+			ParentId:        v.ParentId,
+			ParentName:      v.ParentName,
+			Number:          v.Number,
+			IsShow:          v.IsShow,
+			CreatedAt:       v.CreatedAt,
+			CreatedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),
+		}
+	}
+	return res
+}
+
+func (c *ForageCategory) ToPb() *operationPb.AddForageCategoryRequest {
+	return &operationPb.AddForageCategoryRequest{
+		Id:         c.Id,
+		Name:       c.Name,
+		Number:     c.Number,
+		ParentId:   c.ParentId,
+		ParentName: c.ParentName,
+		IsShow:     c.IsShow,
+		CreatedAt:  c.CreatedAt,
+	}
+}
+
+type ForageCategoryResponse struct {
+	Page  int32                                   `json:"page"`
+	Total int32                                   `json:"total"`
+	List  []*operationPb.AddForageCategoryRequest `json:"list"`
+}
+
+func (c *ForageCategoryResponse) ToPB() *operationPb.SearchForageCategoryResponse {
+	return &operationPb.SearchForageCategoryResponse{
+		Page:  c.Page,
+		Total: c.Total,
+		List:  c.List,
+	}
+}

+ 53 - 17
model/system_menu.go

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

+ 8 - 0
module/backend/interface.go

@@ -53,6 +53,14 @@ type Operation interface {
 	IsShowCattleCategory(ctx context.Context, req *operationPb.IsShowCattleCategory) error
 	DeleteCattleCategory(ctx context.Context, cattleCategoryId int64) error
 	SearchCattleCategoryList(ctx context.Context, req *operationPb.SearchCattleCategoryRequest) (*model.CattleCategoryResponse, error)
+
+	// ParentForageCategoryList 饲料类别
+	ParentForageCategoryList(ctx context.Context) map[operationPb.ForageCategoryParent_Kind]string
+	AddForageCategory(ctx context.Context, req *operationPb.AddForageCategoryRequest) error
+	EditForageCategory(ctx context.Context, req *operationPb.AddForageCategoryRequest) error
+	IsShowForageCategory(ctx context.Context, req *operationPb.IsShowForageCategory) error
+	DeleteForageCategory(ctx context.Context, cattleCategoryId int64) error
+	SearchForageCategoryList(ctx context.Context, req *operationPb.SearchForageCategoryRequest) (*model.ForageCategoryResponse, error)
 }
 
 type SystemOperation interface {

+ 116 - 8
module/backend/ops_service.go → module/backend/pasture_service.go

@@ -11,14 +11,22 @@ import (
 	"gorm.io/gorm"
 )
 
-var CattleCategoryMap = map[operationPb.CattleCategoryParent_Kind]string{
-	operationPb.CattleCategoryParent_LACTATION_CAW: "泌乳牛",
-	operationPb.CattleCategoryParent_FATTEN_CAW:    "育肥牛",
-	operationPb.CattleCategoryParent_RESERVE_CAW:   "后备牛",
-	operationPb.CattleCategoryParent_DRY_CAW:       "干奶牛",
-	operationPb.CattleCategoryParent_PERINATAL_CAW: "围产牛",
-	operationPb.CattleCategoryParent_OTHER_CAW:     "其他",
-}
+var (
+	CattleCategoryMap = map[operationPb.CattleCategoryParent_Kind]string{
+		operationPb.CattleCategoryParent_LACTATION_CAW: "泌乳牛",
+		operationPb.CattleCategoryParent_FATTEN_CAW:    "育肥牛",
+		operationPb.CattleCategoryParent_RESERVE_CAW:   "后备牛",
+		operationPb.CattleCategoryParent_DRY_CAW:       "干奶牛",
+		operationPb.CattleCategoryParent_PERINATAL_CAW: "围产牛",
+		operationPb.CattleCategoryParent_OTHER_CAW:     "其他",
+	}
+	ForageCategoryMap = map[operationPb.ForageCategoryParent_Kind]string{
+		operationPb.ForageCategoryParent_ROUGHAGE:                       "粗料",
+		operationPb.ForageCategoryParent_CONCENTRATE:                    "精料",
+		operationPb.ForageCategoryParent_HALF_ROUGHAGE_HALF_CONCENTRATE: "粗料精料各半",
+		operationPb.ForageCategoryParent_OTHER:                          "其他",
+	}
+)
 
 // CreateGroupPasture 创建集团牧场
 func (s *StoreEntry) CreateGroupPasture(ctx context.Context, req *operationPb.AddPastureRequest) error {
@@ -238,3 +246,103 @@ func (s *StoreEntry) SearchCattleCategoryList(ctx context.Context, req *operatio
 		List:  model.CattleCategorySlice(cattleCategory).ToPB(),
 	}, nil
 }
+
+// ParentForageCategoryList 饲料类别父类列表
+func (s *StoreEntry) ParentForageCategoryList(ctx context.Context) map[operationPb.ForageCategoryParent_Kind]string {
+	return ForageCategoryMap
+}
+
+// AddForageCategory 添加饲料分类
+func (s *StoreEntry) AddForageCategory(ctx context.Context, req *operationPb.AddForageCategoryRequest) error {
+	forageCategory := model.NewForageCategory(req)
+	if err := s.DB.Create(forageCategory).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// EditForageCategory 编辑饲料分类
+func (s *StoreEntry) EditForageCategory(ctx context.Context, req *operationPb.AddForageCategoryRequest) error {
+	forageCategory := &model.ForageCategory{Id: req.Id}
+	if err := s.DB.First(forageCategory).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+	updateData := &model.ForageCategory{
+		ParentName: req.ParentName,
+		Name:       req.Name,
+		Number:     req.Number,
+		ParentId:   req.ParentId,
+	}
+
+	if err := s.DB.Model(new(model.ForageCategory)).Omit("is_show", "is_delete").
+		Where("id = ?", req.Id).
+		Updates(updateData).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// IsShowForageCategory 是否启用
+func (s *StoreEntry) IsShowForageCategory(ctx context.Context, req *operationPb.IsShowForageCategory) error {
+	forageCategory := &model.ForageCategory{Id: req.ForageCategoryId}
+	if err := s.DB.First(forageCategory).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(new(model.CattleCategory)).Where("id = ?", req.ForageCategoryId).Update("is_show", req.IsShow).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// DeleteForageCategory 是否删除
+func (s *StoreEntry) DeleteForageCategory(ctx context.Context, forageCategoryId int64) error {
+	forageCategory := &model.ForageCategory{Id: forageCategoryId}
+	if err := s.DB.First(forageCategory).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(new(model.CattleCategory)).Where("id = ?", forageCategoryId).Update("is_delete", operationPb.IsShow_NO).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// SearchForageCategoryList 饲料分类类别列表
+func (s *StoreEntry) SearchForageCategoryList(ctx context.Context, req *operationPb.SearchForageCategoryRequest) (*model.ForageCategoryResponse, error) {
+	forageCategory := make([]*model.ForageCategory, 0)
+	var count int64 = 0
+
+	pref := s.DB.Model(new(model.CattleCategory)).Where("is_delete = ?", operationPb.IsShow_OK)
+	if req.Name != "" {
+		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
+	}
+
+	if req.ParentName != "" {
+		pref.Where("parent_name like ?", fmt.Sprintf("%s%s%s", "%", req.ParentName, "%"))
+	}
+
+	if req.IsShow > 0 {
+		pref.Where("is_show = ?", req.IsShow)
+	}
+
+	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
+		Find(&forageCategory).Debug().Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &model.ForageCategoryResponse{
+		Page:  req.Pagination.Page,
+		Total: int32(count),
+		List:  model.ForageCategorySlice(forageCategory).ToPB(),
+	}, nil
+}

+ 5 - 12
module/backend/system_permissions.go

@@ -18,12 +18,6 @@ type SystemAllPermissionsList struct {
 	MobileList  []*model.SystemMobilePermissions
 }
 
-const (
-	Level1 = iota + 1
-	Level2
-	Level3
-)
-
 // SystemUserMenuPermissionsUnDuplicate 角色权限去重
 func (s *SystemAllPermissionsList) SystemUserMenuPermissionsUnDuplicate() {
 	newMenuList := make([]*model.SystemMenuPermissions, 0)
@@ -60,7 +54,6 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 
 	// TODO 后面优化成递归算法
 	go func() {
-
 		level := make(map[int32][]*operationPb.AddMenuRequest, 0)
 		for _, menu := range menuList {
 			if _, ok := level[menu.Level]; !ok {
@@ -87,8 +80,8 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 			})
 		}
 
-		for _, leve3Data := range level[Level3] {
-			for _, leve2Data := range level[Level2] {
+		for _, leve3Data := range level[model.Level3] {
+			for _, leve2Data := range level[model.Level2] {
 				if leve3Data.ParentId == leve2Data.Id {
 					if leve2Data.Children == nil {
 						leve2Data.Children = make([]*operationPb.AddMenuRequest, 0)
@@ -98,8 +91,8 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 			}
 		}
 
-		for _, leve2Data := range level[Level2] {
-			for _, leve1Data := range level[Level1] {
+		for _, leve2Data := range level[model.Level2] {
+			for _, leve1Data := range level[model.Level1] {
 				if leve2Data.ParentId == leve1Data.Id {
 					if leve1Data.Children == nil {
 						leve1Data.Children = make([]*operationPb.AddMenuRequest, 0)
@@ -109,7 +102,7 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 			}
 		}
 
-		systemUserMenuPermissions.MenuList = level[Level1]
+		systemUserMenuPermissions.MenuList = level[model.Level1]
 		wg.Done()
 	}()
 

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

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

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

@@ -130,6 +130,61 @@ func (CattleCategoryParent_Kind) EnumDescriptor() ([]byte, []int) {
 	return file_backend_operation_enum_proto_rawDescGZIP(), []int{1, 0}
 }
 
+type ForageCategoryParent_Kind int32
+
+const (
+	ForageCategoryParent_INVALID                        ForageCategoryParent_Kind = 0 // 无效
+	ForageCategoryParent_ROUGHAGE                       ForageCategoryParent_Kind = 1 // 粗料
+	ForageCategoryParent_CONCENTRATE                    ForageCategoryParent_Kind = 2 // 精料(浓缩料)
+	ForageCategoryParent_HALF_ROUGHAGE_HALF_CONCENTRATE ForageCategoryParent_Kind = 3 // 粗料精料各半
+	ForageCategoryParent_OTHER                          ForageCategoryParent_Kind = 4 // 其他
+)
+
+// Enum value maps for ForageCategoryParent_Kind.
+var (
+	ForageCategoryParent_Kind_name = map[int32]string{
+		0: "INVALID",
+		1: "ROUGHAGE",
+		2: "CONCENTRATE",
+		3: "HALF_ROUGHAGE_HALF_CONCENTRATE",
+		4: "OTHER",
+	}
+	ForageCategoryParent_Kind_value = map[string]int32{
+		"INVALID":                        0,
+		"ROUGHAGE":                       1,
+		"CONCENTRATE":                    2,
+		"HALF_ROUGHAGE_HALF_CONCENTRATE": 3,
+		"OTHER":                          4,
+	}
+)
+
+func (x ForageCategoryParent_Kind) Enum() *ForageCategoryParent_Kind {
+	p := new(ForageCategoryParent_Kind)
+	*p = x
+	return p
+}
+
+func (x ForageCategoryParent_Kind) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ForageCategoryParent_Kind) Descriptor() protoreflect.EnumDescriptor {
+	return file_backend_operation_enum_proto_enumTypes[2].Descriptor()
+}
+
+func (ForageCategoryParent_Kind) Type() protoreflect.EnumType {
+	return &file_backend_operation_enum_proto_enumTypes[2]
+}
+
+func (x ForageCategoryParent_Kind) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use ForageCategoryParent_Kind.Descriptor instead.
+func (ForageCategoryParent_Kind) EnumDescriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{2, 0}
+}
+
 // 字段类型
 type IsShow struct {
 	state         protoimpl.MessageState
@@ -207,6 +262,44 @@ func (*CattleCategoryParent) Descriptor() ([]byte, []int) {
 	return file_backend_operation_enum_proto_rawDescGZIP(), []int{1}
 }
 
+type ForageCategoryParent struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *ForageCategoryParent) Reset() {
+	*x = ForageCategoryParent{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_enum_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ForageCategoryParent) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ForageCategoryParent) ProtoMessage() {}
+
+func (x *ForageCategoryParent) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_enum_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ForageCategoryParent.ProtoReflect.Descriptor instead.
+func (*ForageCategoryParent) Descriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{2}
+}
+
 var File_backend_operation_enum_proto protoreflect.FileDescriptor
 
 var file_backend_operation_enum_proto_rawDesc = []byte{
@@ -225,8 +318,16 @@ var file_backend_operation_enum_proto_rawDesc = []byte{
 	0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x52, 0x59, 0x5f, 0x43, 0x41, 0x57, 0x10, 0x04, 0x12,
 	0x11, 0x0a, 0x0d, 0x50, 0x45, 0x52, 0x49, 0x4e, 0x41, 0x54, 0x41, 0x4c, 0x5f, 0x43, 0x41, 0x57,
 	0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x5f, 0x43, 0x41, 0x57, 0x10,
-	0x06, 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,
+	0x06, 0x22, 0x79, 0x0a, 0x14, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67,
+	0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x61, 0x0a, 0x04, 0x4b, 0x69, 0x6e,
+	0x64, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x0c,
+	0x0a, 0x08, 0x52, 0x4f, 0x55, 0x47, 0x48, 0x41, 0x47, 0x45, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b,
+	0x43, 0x4f, 0x4e, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x22, 0x0a,
+	0x1e, 0x48, 0x41, 0x4c, 0x46, 0x5f, 0x52, 0x4f, 0x55, 0x47, 0x48, 0x41, 0x47, 0x45, 0x5f, 0x48,
+	0x41, 0x4c, 0x46, 0x5f, 0x43, 0x4f, 0x4e, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x54, 0x45, 0x10,
+	0x03, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x10, 0x04, 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 (
@@ -241,13 +342,15 @@ func file_backend_operation_enum_proto_rawDescGZIP() []byte {
 	return file_backend_operation_enum_proto_rawDescData
 }
 
-var file_backend_operation_enum_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
-var file_backend_operation_enum_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_backend_operation_enum_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
+var file_backend_operation_enum_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
 var file_backend_operation_enum_proto_goTypes = []interface{}{
 	(IsShow_Kind)(0),               // 0: backend.operation.IsShow.Kind
 	(CattleCategoryParent_Kind)(0), // 1: backend.operation.CattleCategoryParent.Kind
-	(*IsShow)(nil),                 // 2: backend.operation.IsShow
-	(*CattleCategoryParent)(nil),   // 3: backend.operation.CattleCategoryParent
+	(ForageCategoryParent_Kind)(0), // 2: backend.operation.ForageCategoryParent.Kind
+	(*IsShow)(nil),                 // 3: backend.operation.IsShow
+	(*CattleCategoryParent)(nil),   // 4: backend.operation.CattleCategoryParent
+	(*ForageCategoryParent)(nil),   // 5: backend.operation.ForageCategoryParent
 }
 var file_backend_operation_enum_proto_depIdxs = []int32{
 	0, // [0:0] is the sub-list for method output_type
@@ -287,14 +390,26 @@ func file_backend_operation_enum_proto_init() {
 				return nil
 			}
 		}
+		file_backend_operation_enum_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ForageCategoryParent); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_backend_operation_enum_proto_rawDesc,
-			NumEnums:      2,
-			NumMessages:   2,
+			NumEnums:      3,
+			NumMessages:   3,
 			NumExtensions: 0,
 			NumServices:   0,
 		},

+ 425 - 21
proto/go/backend/operation/pasture.pb.go

@@ -687,6 +687,301 @@ func (x *SearchCattleCategoryResponse) GetList() []*AddCattleCategoryRequest {
 	return nil
 }
 
+// 添加饲料分类
+type AddForageCategoryRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Id              int64                     `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+	ParentId        ForageCategoryParent_Kind `protobuf:"varint,2,opt,name=parent_id,json=parentId,proto3,enum=backend.operation.ForageCategoryParent_Kind" json:"parent_id,omitempty"` // 父类id
+	ParentName      string                    `protobuf:"bytes,3,opt,name=parent_name,json=parentName,proto3" json:"parent_name,omitempty"`                                             // 父类名称
+	Name            string                    `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"`                                                                           // 牧畜分类名称
+	Number          string                    `protobuf:"bytes,5,opt,name=number,proto3" json:"number,omitempty"`                                                                       // 畜牧类别编号
+	IsShow          IsShow_Kind               `protobuf:"varint,6,opt,name=is_show,json=isShow,proto3,enum=backend.operation.IsShow_Kind" json:"is_show,omitempty"`                     // 是否启用
+	CreatedAt       int64                     `protobuf:"varint,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`                                               // 创建时间
+	CreatedAtFormat string                    `protobuf:"bytes,8,opt,name=created_at_format,json=createdAtFormat,proto3" json:"created_at_format,omitempty"`                            // 创建时间格式
+}
+
+func (x *AddForageCategoryRequest) Reset() {
+	*x = AddForageCategoryRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_pasture_proto_msgTypes[9]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AddForageCategoryRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AddForageCategoryRequest) ProtoMessage() {}
+
+func (x *AddForageCategoryRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_pasture_proto_msgTypes[9]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use AddForageCategoryRequest.ProtoReflect.Descriptor instead.
+func (*AddForageCategoryRequest) Descriptor() ([]byte, []int) {
+	return file_backend_operation_pasture_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *AddForageCategoryRequest) GetId() int64 {
+	if x != nil {
+		return x.Id
+	}
+	return 0
+}
+
+func (x *AddForageCategoryRequest) GetParentId() ForageCategoryParent_Kind {
+	if x != nil {
+		return x.ParentId
+	}
+	return ForageCategoryParent_INVALID
+}
+
+func (x *AddForageCategoryRequest) GetParentName() string {
+	if x != nil {
+		return x.ParentName
+	}
+	return ""
+}
+
+func (x *AddForageCategoryRequest) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *AddForageCategoryRequest) GetNumber() string {
+	if x != nil {
+		return x.Number
+	}
+	return ""
+}
+
+func (x *AddForageCategoryRequest) GetIsShow() IsShow_Kind {
+	if x != nil {
+		return x.IsShow
+	}
+	return IsShow_INVALID
+}
+
+func (x *AddForageCategoryRequest) GetCreatedAt() int64 {
+	if x != nil {
+		return x.CreatedAt
+	}
+	return 0
+}
+
+func (x *AddForageCategoryRequest) GetCreatedAtFormat() string {
+	if x != nil {
+		return x.CreatedAtFormat
+	}
+	return ""
+}
+
+// 是否启用
+type IsShowForageCategory struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	ForageCategoryId int64       `protobuf:"varint,1,opt,name=forage_category_id,json=forageCategoryId,proto3" json:"forage_category_id,omitempty"`
+	IsShow           IsShow_Kind `protobuf:"varint,2,opt,name=is_show,json=isShow,proto3,enum=backend.operation.IsShow_Kind" json:"is_show,omitempty"`
+}
+
+func (x *IsShowForageCategory) Reset() {
+	*x = IsShowForageCategory{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_pasture_proto_msgTypes[10]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *IsShowForageCategory) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*IsShowForageCategory) ProtoMessage() {}
+
+func (x *IsShowForageCategory) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_pasture_proto_msgTypes[10]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use IsShowForageCategory.ProtoReflect.Descriptor instead.
+func (*IsShowForageCategory) Descriptor() ([]byte, []int) {
+	return file_backend_operation_pasture_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *IsShowForageCategory) GetForageCategoryId() int64 {
+	if x != nil {
+		return x.ForageCategoryId
+	}
+	return 0
+}
+
+func (x *IsShowForageCategory) GetIsShow() IsShow_Kind {
+	if x != nil {
+		return x.IsShow
+	}
+	return IsShow_INVALID
+}
+
+// 饲料分类查询列表
+type SearchForageCategoryRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	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"` // 分页
+}
+
+func (x *SearchForageCategoryRequest) Reset() {
+	*x = SearchForageCategoryRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_pasture_proto_msgTypes[11]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchForageCategoryRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchForageCategoryRequest) ProtoMessage() {}
+
+func (x *SearchForageCategoryRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_pasture_proto_msgTypes[11]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SearchForageCategoryRequest.ProtoReflect.Descriptor instead.
+func (*SearchForageCategoryRequest) Descriptor() ([]byte, []int) {
+	return file_backend_operation_pasture_proto_rawDescGZIP(), []int{11}
+}
+
+func (x *SearchForageCategoryRequest) GetParentName() string {
+	if x != nil {
+		return x.ParentName
+	}
+	return ""
+}
+
+func (x *SearchForageCategoryRequest) GetIsShow() IsShow_Kind {
+	if x != nil {
+		return x.IsShow
+	}
+	return IsShow_INVALID
+}
+
+func (x *SearchForageCategoryRequest) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *SearchForageCategoryRequest) GetPagination() *PaginationModel {
+	if x != nil {
+		return x.Pagination
+	}
+	return nil
+}
+
+type SearchForageCategoryResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Page  int32                       `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"`
+	Total int32                       `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"`
+	List  []*AddForageCategoryRequest `protobuf:"bytes,3,rep,name=list,proto3" json:"list,omitempty"`
+}
+
+func (x *SearchForageCategoryResponse) Reset() {
+	*x = SearchForageCategoryResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_pasture_proto_msgTypes[12]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchForageCategoryResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchForageCategoryResponse) ProtoMessage() {}
+
+func (x *SearchForageCategoryResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_pasture_proto_msgTypes[12]
+	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 SearchForageCategoryResponse.ProtoReflect.Descriptor instead.
+func (*SearchForageCategoryResponse) Descriptor() ([]byte, []int) {
+	return file_backend_operation_pasture_proto_rawDescGZIP(), []int{12}
+}
+
+func (x *SearchForageCategoryResponse) GetPage() int32 {
+	if x != nil {
+		return x.Page
+	}
+	return 0
+}
+
+func (x *SearchForageCategoryResponse) GetTotal() int32 {
+	if x != nil {
+		return x.Total
+	}
+	return 0
+}
+
+func (x *SearchForageCategoryResponse) GetList() []*AddForageCategoryRequest {
+	if x != nil {
+		return x.List
+	}
+	return nil
+}
+
 var File_backend_operation_pasture_proto protoreflect.FileDescriptor
 
 var file_backend_operation_pasture_proto_rawDesc = []byte{
@@ -802,9 +1097,59 @@ var file_backend_operation_pasture_proto_rawDesc = []byte{
 	0x73, 0x74, 0x18, 0x03, 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,
 	0x43, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 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,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0xc6, 0x02, 0x0a, 0x18,
+	0x41, 0x64, 0x64, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72,
+	0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x49, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65,
+	0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 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, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e,
+	0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74,
+	0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62,
+	0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72,
+	0x12, 0x37, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x73, 0x68, 0x6f, 0x77, 0x18, 0x06, 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, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65,
+	0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 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, 0x08, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x46, 0x6f,
+	0x72, 0x6d, 0x61, 0x74, 0x22, 0x7d, 0x0a, 0x14, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x46, 0x6f,
+	0x72, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x2c, 0x0a, 0x12,
+	0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f,
+	0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65,
+	0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 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, 0xcf, 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,
+	0x4e, 0x61, 0x6d, 0x65, 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, 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, 0x89, 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, 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, 0x3f, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x03, 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, 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 (
@@ -819,7 +1164,7 @@ func file_backend_operation_pasture_proto_rawDescGZIP() []byte {
 	return file_backend_operation_pasture_proto_rawDescData
 }
 
-var file_backend_operation_pasture_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
+var file_backend_operation_pasture_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
 var file_backend_operation_pasture_proto_goTypes = []interface{}{
 	(*AddPastureRequest)(nil),            // 0: backend.operation.AddPastureRequest
 	(*SearchPastureRequest)(nil),         // 1: backend.operation.SearchPastureRequest
@@ -830,26 +1175,37 @@ var file_backend_operation_pasture_proto_goTypes = []interface{}{
 	(*IsShowCattleCategory)(nil),         // 6: backend.operation.IsShowCattleCategory
 	(*SearchCattleCategoryRequest)(nil),  // 7: backend.operation.SearchCattleCategoryRequest
 	(*SearchCattleCategoryResponse)(nil), // 8: backend.operation.SearchCattleCategoryResponse
-	(IsShow_Kind)(0),                     // 9: backend.operation.IsShow.Kind
-	(*PaginationModel)(nil),              // 10: backend.operation.PaginationModel
-	(CattleCategoryParent_Kind)(0),       // 11: backend.operation.CattleCategoryParent.Kind
+	(*AddForageCategoryRequest)(nil),     // 9: backend.operation.AddForageCategoryRequest
+	(*IsShowForageCategory)(nil),         // 10: backend.operation.IsShowForageCategory
+	(*SearchForageCategoryRequest)(nil),  // 11: backend.operation.SearchForageCategoryRequest
+	(*SearchForageCategoryResponse)(nil), // 12: backend.operation.SearchForageCategoryResponse
+	(IsShow_Kind)(0),                     // 13: backend.operation.IsShow.Kind
+	(*PaginationModel)(nil),              // 14: backend.operation.PaginationModel
+	(CattleCategoryParent_Kind)(0),       // 15: backend.operation.CattleCategoryParent.Kind
+	(ForageCategoryParent_Kind)(0),       // 16: backend.operation.ForageCategoryParent.Kind
 }
 var file_backend_operation_pasture_proto_depIdxs = []int32{
-	9,  // 0: backend.operation.AddPastureRequest.is_show:type_name -> backend.operation.IsShow.Kind
-	10, // 1: backend.operation.SearchPastureRequest.pagination:type_name -> backend.operation.PaginationModel
+	13, // 0: backend.operation.AddPastureRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	14, // 1: backend.operation.SearchPastureRequest.pagination:type_name -> backend.operation.PaginationModel
 	0,  // 2: backend.operation.SearchPastureResponse.list:type_name -> backend.operation.AddPastureRequest
-	9,  // 3: backend.operation.IsShowGroupPasture.is_show:type_name -> backend.operation.IsShow.Kind
-	11, // 4: backend.operation.AddCattleCategoryRequest.parent_id:type_name -> backend.operation.CattleCategoryParent.Kind
-	9,  // 5: backend.operation.AddCattleCategoryRequest.is_show:type_name -> backend.operation.IsShow.Kind
-	9,  // 6: backend.operation.IsShowCattleCategory.is_show:type_name -> backend.operation.IsShow.Kind
-	9,  // 7: backend.operation.SearchCattleCategoryRequest.is_show:type_name -> backend.operation.IsShow.Kind
-	10, // 8: backend.operation.SearchCattleCategoryRequest.pagination:type_name -> backend.operation.PaginationModel
+	13, // 3: backend.operation.IsShowGroupPasture.is_show:type_name -> backend.operation.IsShow.Kind
+	15, // 4: backend.operation.AddCattleCategoryRequest.parent_id:type_name -> backend.operation.CattleCategoryParent.Kind
+	13, // 5: backend.operation.AddCattleCategoryRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	13, // 6: backend.operation.IsShowCattleCategory.is_show:type_name -> backend.operation.IsShow.Kind
+	13, // 7: backend.operation.SearchCattleCategoryRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	14, // 8: backend.operation.SearchCattleCategoryRequest.pagination:type_name -> backend.operation.PaginationModel
 	5,  // 9: backend.operation.SearchCattleCategoryResponse.list:type_name -> backend.operation.AddCattleCategoryRequest
-	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
+	16, // 10: backend.operation.AddForageCategoryRequest.parent_id:type_name -> backend.operation.ForageCategoryParent.Kind
+	13, // 11: backend.operation.AddForageCategoryRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	13, // 12: backend.operation.IsShowForageCategory.is_show:type_name -> backend.operation.IsShow.Kind
+	13, // 13: backend.operation.SearchForageCategoryRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	14, // 14: backend.operation.SearchForageCategoryRequest.pagination:type_name -> backend.operation.PaginationModel
+	9,  // 15: backend.operation.SearchForageCategoryResponse.list:type_name -> backend.operation.AddForageCategoryRequest
+	16, // [16:16] is the sub-list for method output_type
+	16, // [16:16] is the sub-list for method input_type
+	16, // [16:16] is the sub-list for extension type_name
+	16, // [16:16] is the sub-list for extension extendee
+	0,  // [0:16] is the sub-list for field type_name
 }
 
 func init() { file_backend_operation_pasture_proto_init() }
@@ -968,6 +1324,54 @@ func file_backend_operation_pasture_proto_init() {
 				return nil
 			}
 		}
+		file_backend_operation_pasture_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AddForageCategoryRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_pasture_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*IsShowForageCategory); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_pasture_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchForageCategoryRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_pasture_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchForageCategoryResponse); 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{
@@ -975,7 +1379,7 @@ func file_backend_operation_pasture_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_backend_operation_pasture_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   9,
+			NumMessages:   13,
 			NumExtensions: 0,
 			NumServices:   0,
 		},