Ver Fonte

Merge branch 'master' into yhg_240730

jimmyyem há 6 meses atrás
pai
commit
8f3faeda26
45 ficheiros alterados com 4454 adições e 18 exclusões
  1. 2 1
      desc/all.api
  2. 74 0
      desc/wechat/category.api
  3. 6 0
      desc/wechat/employee.api
  4. 150 0
      ent/category.go
  5. 102 0
      ent/category/category.go
  6. 330 0
      ent/category/where.go
  7. 757 0
      ent/category_create.go
  8. 88 0
      ent/category_delete.go
  9. 526 0
      ent/category_query.go
  10. 400 0
      ent/category_update.go
  11. 155 12
      ent/client.go
  12. 12 1
      ent/employee.go
  13. 10 0
      ent/employee/employee.go
  14. 45 0
      ent/employee/where.go
  15. 78 0
      ent/employee_create.go
  16. 64 0
      ent/employee_update.go
  17. 2 0
      ent/ent.go
  18. 12 0
      ent/hook/hook.go
  19. 30 0
      ent/intercept/intercept.go
  20. 27 0
      ent/migrate/schema.go
  21. 696 1
      ent/mutation.go
  22. 82 0
      ent/pagination.go
  23. 3 0
      ent/predicate/predicate.go
  24. 32 0
      ent/runtime/runtime.go
  25. 47 0
      ent/schema/category.go
  26. 1 0
      ent/schema/employee.go
  27. 120 0
      ent/set_not_nil.go
  28. 3 0
      ent/tx.go
  29. 44 0
      internal/handler/category/create_category_handler.go
  30. 44 0
      internal/handler/category/delete_category_handler.go
  31. 44 0
      internal/handler/category/get_category_by_id_handler.go
  32. 44 0
      internal/handler/category/get_category_list_handler.go
  33. 44 0
      internal/handler/category/update_category_handler.go
  34. 35 0
      internal/handler/routes.go
  35. 62 0
      internal/logic/base/init_api_data.go
  36. 40 0
      internal/logic/category/create_category_logic.go
  37. 37 0
      internal/logic/category/delete_category_logic.go
  38. 52 0
      internal/logic/category/get_category_by_id_logic.go
  39. 61 0
      internal/logic/category/get_category_list_logic.go
  40. 40 0
      internal/logic/category/update_category_logic.go
  41. 1 0
      internal/logic/employee/create_employee_logic.go
  42. 1 0
      internal/logic/employee/get_employee_by_id_logic.go
  43. 4 3
      internal/logic/employee/get_employee_list_logic.go
  44. 1 0
      internal/logic/employee/update_employee_logic.go
  45. 46 0
      internal/types/types.go

+ 2 - 1
desc/all.api

@@ -20,4 +20,5 @@ import "./wechat/employee.api"
 import "./wechat/work_experience.api"
 import "./wechat/tutorial.api"
 import "./wechat/token.api"
-import "./wechat/employee_config.api"
+import "./wechat/employee_config.api"
+import "./wechat/category.api"

+ 74 - 0
desc/wechat/category.api

@@ -0,0 +1,74 @@
+import "../base.api"
+
+type (
+    // The data of category information | Category信息
+    CategoryInfo {
+        BaseIDInfo
+
+        // name | 角色名称 
+        Name  *string `json:"name,optional"`
+
+        // organization_id | 租户ID 
+        OrganizationId  *uint64 `json:"organizationId,optional"`
+    }
+
+    // The response data of category list | Category列表数据
+    CategoryListResp {
+        BaseDataInfo
+
+        // Category list data | Category列表数据
+        Data CategoryListInfo `json:"data"`
+    }
+
+    // Category list data | Category列表数据
+    CategoryListInfo {
+        BaseListInfo
+
+        // The API list data | Category列表数据
+        Data  []CategoryInfo  `json:"data"`
+    }
+
+    // Get category list request params | Category列表请求参数
+    CategoryListReq {
+        PageInfo
+
+        // name | 角色名称 
+        Name  *string `json:"name,optional"`
+    }
+
+    // Category information response | Category信息返回体
+    CategoryInfoResp {
+        BaseDataInfo
+
+        // Category information | Category数据
+        Data CategoryInfo `json:"data"`
+    }
+)
+
+@server(
+    jwt: Auth
+    group: category
+    middleware: Authority
+)
+
+service Wechat {
+    // Create category information | 创建Category
+    @handler createCategory
+    post /category/create (CategoryInfo) returns (BaseMsgResp)
+
+    // Update category information | 更新Category
+    @handler updateCategory
+    post /category/update (CategoryInfo) returns (BaseMsgResp)
+
+    // Delete category information | 删除Category信息
+    @handler deleteCategory
+    post /category/delete (IDsReq) returns (BaseMsgResp)
+
+    // Get category list | 获取Category列表
+    @handler getCategoryList
+    post /category/list (CategoryListReq) returns (CategoryListResp)
+
+    // Get category by ID | 通过ID获取Category
+    @handler getCategoryById
+    post /category (IDReq) returns (CategoryInfoResp)
+}

+ 6 - 0
desc/wechat/employee.api

@@ -26,6 +26,9 @@ type (
         // achievement_count | 业绩单数 
         AchievementCount  *int `json:"achievementCount,optional"`
 
+		// category_id | 分类ID
+		CategoryId  *uint64 `json:"categoryId,optional"`
+
         // intro | 个人介绍 
         Intro  *string `json:"intro,optional"`
 
@@ -80,6 +83,9 @@ type (
 
         // tags | 个人标签 
         Tags  *string `json:"tags,optional"`
+
+		// category_id | 分类ID
+		CategoryId  *uint64 `json:"categoryId,optional"`
     }
 
     // Employee information response | Employee信息返回体

+ 150 - 0
ent/category.go

@@ -0,0 +1,150 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+	"fmt"
+	"strings"
+	"time"
+	"wechat-api/ent/category"
+
+	"entgo.io/ent"
+	"entgo.io/ent/dialect/sql"
+)
+
+// Category is the model entity for the Category schema.
+type Category struct {
+	config `json:"-"`
+	// ID of the ent.
+	ID uint64 `json:"id,omitempty"`
+	// Create Time | 创建日期
+	CreatedAt time.Time `json:"created_at,omitempty"`
+	// Update Time | 修改日期
+	UpdatedAt time.Time `json:"updated_at,omitempty"`
+	// Delete Time | 删除日期
+	DeletedAt time.Time `json:"deleted_at,omitempty"`
+	// name | 角色名称
+	Name string `json:"name,omitempty"`
+	// organization_id | 租户ID
+	OrganizationID uint64 `json:"organization_id,omitempty"`
+	selectValues   sql.SelectValues
+}
+
+// scanValues returns the types for scanning values from sql.Rows.
+func (*Category) scanValues(columns []string) ([]any, error) {
+	values := make([]any, len(columns))
+	for i := range columns {
+		switch columns[i] {
+		case category.FieldID, category.FieldOrganizationID:
+			values[i] = new(sql.NullInt64)
+		case category.FieldName:
+			values[i] = new(sql.NullString)
+		case category.FieldCreatedAt, category.FieldUpdatedAt, category.FieldDeletedAt:
+			values[i] = new(sql.NullTime)
+		default:
+			values[i] = new(sql.UnknownType)
+		}
+	}
+	return values, nil
+}
+
+// assignValues assigns the values that were returned from sql.Rows (after scanning)
+// to the Category fields.
+func (c *Category) assignValues(columns []string, values []any) error {
+	if m, n := len(values), len(columns); m < n {
+		return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
+	}
+	for i := range columns {
+		switch columns[i] {
+		case category.FieldID:
+			value, ok := values[i].(*sql.NullInt64)
+			if !ok {
+				return fmt.Errorf("unexpected type %T for field id", value)
+			}
+			c.ID = uint64(value.Int64)
+		case category.FieldCreatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field created_at", values[i])
+			} else if value.Valid {
+				c.CreatedAt = value.Time
+			}
+		case category.FieldUpdatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field updated_at", values[i])
+			} else if value.Valid {
+				c.UpdatedAt = value.Time
+			}
+		case category.FieldDeletedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field deleted_at", values[i])
+			} else if value.Valid {
+				c.DeletedAt = value.Time
+			}
+		case category.FieldName:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field name", values[i])
+			} else if value.Valid {
+				c.Name = value.String
+			}
+		case category.FieldOrganizationID:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field organization_id", values[i])
+			} else if value.Valid {
+				c.OrganizationID = uint64(value.Int64)
+			}
+		default:
+			c.selectValues.Set(columns[i], values[i])
+		}
+	}
+	return nil
+}
+
+// Value returns the ent.Value that was dynamically selected and assigned to the Category.
+// This includes values selected through modifiers, order, etc.
+func (c *Category) Value(name string) (ent.Value, error) {
+	return c.selectValues.Get(name)
+}
+
+// Update returns a builder for updating this Category.
+// Note that you need to call Category.Unwrap() before calling this method if this Category
+// was returned from a transaction, and the transaction was committed or rolled back.
+func (c *Category) Update() *CategoryUpdateOne {
+	return NewCategoryClient(c.config).UpdateOne(c)
+}
+
+// Unwrap unwraps the Category entity that was returned from a transaction after it was closed,
+// so that all future queries will be executed through the driver which created the transaction.
+func (c *Category) Unwrap() *Category {
+	_tx, ok := c.config.driver.(*txDriver)
+	if !ok {
+		panic("ent: Category is not a transactional entity")
+	}
+	c.config.driver = _tx.drv
+	return c
+}
+
+// String implements the fmt.Stringer.
+func (c *Category) String() string {
+	var builder strings.Builder
+	builder.WriteString("Category(")
+	builder.WriteString(fmt.Sprintf("id=%v, ", c.ID))
+	builder.WriteString("created_at=")
+	builder.WriteString(c.CreatedAt.Format(time.ANSIC))
+	builder.WriteString(", ")
+	builder.WriteString("updated_at=")
+	builder.WriteString(c.UpdatedAt.Format(time.ANSIC))
+	builder.WriteString(", ")
+	builder.WriteString("deleted_at=")
+	builder.WriteString(c.DeletedAt.Format(time.ANSIC))
+	builder.WriteString(", ")
+	builder.WriteString("name=")
+	builder.WriteString(c.Name)
+	builder.WriteString(", ")
+	builder.WriteString("organization_id=")
+	builder.WriteString(fmt.Sprintf("%v", c.OrganizationID))
+	builder.WriteByte(')')
+	return builder.String()
+}
+
+// Categories is a parsable slice of Category.
+type Categories []*Category

+ 102 - 0
ent/category/category.go

@@ -0,0 +1,102 @@
+// Code generated by ent, DO NOT EDIT.
+
+package category
+
+import (
+	"time"
+
+	"entgo.io/ent"
+	"entgo.io/ent/dialect/sql"
+)
+
+const (
+	// Label holds the string label denoting the category type in the database.
+	Label = "category"
+	// FieldID holds the string denoting the id field in the database.
+	FieldID = "id"
+	// FieldCreatedAt holds the string denoting the created_at field in the database.
+	FieldCreatedAt = "created_at"
+	// FieldUpdatedAt holds the string denoting the updated_at field in the database.
+	FieldUpdatedAt = "updated_at"
+	// FieldDeletedAt holds the string denoting the deleted_at field in the database.
+	FieldDeletedAt = "deleted_at"
+	// FieldName holds the string denoting the name field in the database.
+	FieldName = "name"
+	// FieldOrganizationID holds the string denoting the organization_id field in the database.
+	FieldOrganizationID = "organization_id"
+	// Table holds the table name of the category in the database.
+	Table = "category"
+)
+
+// Columns holds all SQL columns for category fields.
+var Columns = []string{
+	FieldID,
+	FieldCreatedAt,
+	FieldUpdatedAt,
+	FieldDeletedAt,
+	FieldName,
+	FieldOrganizationID,
+}
+
+// ValidColumn reports if the column name is valid (part of the table columns).
+func ValidColumn(column string) bool {
+	for i := range Columns {
+		if column == Columns[i] {
+			return true
+		}
+	}
+	return false
+}
+
+// Note that the variables below are initialized by the runtime
+// package on the initialization of the application. Therefore,
+// it should be imported in the main as follows:
+//
+//	import _ "wechat-api/ent/runtime"
+var (
+	Hooks        [1]ent.Hook
+	Interceptors [1]ent.Interceptor
+	// DefaultCreatedAt holds the default value on creation for the "created_at" field.
+	DefaultCreatedAt func() time.Time
+	// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
+	DefaultUpdatedAt func() time.Time
+	// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
+	UpdateDefaultUpdatedAt func() time.Time
+	// NameValidator is a validator for the "name" field. It is called by the builders before save.
+	NameValidator func(string) error
+	// OrganizationIDValidator is a validator for the "organization_id" field. It is called by the builders before save.
+	OrganizationIDValidator func(uint64) error
+)
+
+// OrderOption defines the ordering options for the Category queries.
+type OrderOption func(*sql.Selector)
+
+// ByID orders the results by the id field.
+func ByID(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldID, opts...).ToFunc()
+}
+
+// ByCreatedAt orders the results by the created_at field.
+func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
+}
+
+// ByUpdatedAt orders the results by the updated_at field.
+func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
+}
+
+// ByDeletedAt orders the results by the deleted_at field.
+func ByDeletedAt(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldDeletedAt, opts...).ToFunc()
+}
+
+// ByName orders the results by the name field.
+func ByName(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldName, opts...).ToFunc()
+}
+
+// ByOrganizationID orders the results by the organization_id field.
+func ByOrganizationID(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldOrganizationID, opts...).ToFunc()
+}

+ 330 - 0
ent/category/where.go

@@ -0,0 +1,330 @@
+// Code generated by ent, DO NOT EDIT.
+
+package category
+
+import (
+	"time"
+	"wechat-api/ent/predicate"
+
+	"entgo.io/ent/dialect/sql"
+)
+
+// ID filters vertices based on their ID field.
+func ID(id uint64) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldID, id))
+}
+
+// IDEQ applies the EQ predicate on the ID field.
+func IDEQ(id uint64) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldID, id))
+}
+
+// IDNEQ applies the NEQ predicate on the ID field.
+func IDNEQ(id uint64) predicate.Category {
+	return predicate.Category(sql.FieldNEQ(FieldID, id))
+}
+
+// IDIn applies the In predicate on the ID field.
+func IDIn(ids ...uint64) predicate.Category {
+	return predicate.Category(sql.FieldIn(FieldID, ids...))
+}
+
+// IDNotIn applies the NotIn predicate on the ID field.
+func IDNotIn(ids ...uint64) predicate.Category {
+	return predicate.Category(sql.FieldNotIn(FieldID, ids...))
+}
+
+// IDGT applies the GT predicate on the ID field.
+func IDGT(id uint64) predicate.Category {
+	return predicate.Category(sql.FieldGT(FieldID, id))
+}
+
+// IDGTE applies the GTE predicate on the ID field.
+func IDGTE(id uint64) predicate.Category {
+	return predicate.Category(sql.FieldGTE(FieldID, id))
+}
+
+// IDLT applies the LT predicate on the ID field.
+func IDLT(id uint64) predicate.Category {
+	return predicate.Category(sql.FieldLT(FieldID, id))
+}
+
+// IDLTE applies the LTE predicate on the ID field.
+func IDLTE(id uint64) predicate.Category {
+	return predicate.Category(sql.FieldLTE(FieldID, id))
+}
+
+// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
+func CreatedAt(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldCreatedAt, v))
+}
+
+// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
+func UpdatedAt(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldUpdatedAt, v))
+}
+
+// DeletedAt applies equality check predicate on the "deleted_at" field. It's identical to DeletedAtEQ.
+func DeletedAt(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldDeletedAt, v))
+}
+
+// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
+func Name(v string) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldName, v))
+}
+
+// OrganizationID applies equality check predicate on the "organization_id" field. It's identical to OrganizationIDEQ.
+func OrganizationID(v uint64) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldOrganizationID, v))
+}
+
+// CreatedAtEQ applies the EQ predicate on the "created_at" field.
+func CreatedAtEQ(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldCreatedAt, v))
+}
+
+// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
+func CreatedAtNEQ(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldNEQ(FieldCreatedAt, v))
+}
+
+// CreatedAtIn applies the In predicate on the "created_at" field.
+func CreatedAtIn(vs ...time.Time) predicate.Category {
+	return predicate.Category(sql.FieldIn(FieldCreatedAt, vs...))
+}
+
+// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
+func CreatedAtNotIn(vs ...time.Time) predicate.Category {
+	return predicate.Category(sql.FieldNotIn(FieldCreatedAt, vs...))
+}
+
+// CreatedAtGT applies the GT predicate on the "created_at" field.
+func CreatedAtGT(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldGT(FieldCreatedAt, v))
+}
+
+// CreatedAtGTE applies the GTE predicate on the "created_at" field.
+func CreatedAtGTE(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldGTE(FieldCreatedAt, v))
+}
+
+// CreatedAtLT applies the LT predicate on the "created_at" field.
+func CreatedAtLT(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldLT(FieldCreatedAt, v))
+}
+
+// CreatedAtLTE applies the LTE predicate on the "created_at" field.
+func CreatedAtLTE(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldLTE(FieldCreatedAt, v))
+}
+
+// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
+func UpdatedAtEQ(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldUpdatedAt, v))
+}
+
+// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
+func UpdatedAtNEQ(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldNEQ(FieldUpdatedAt, v))
+}
+
+// UpdatedAtIn applies the In predicate on the "updated_at" field.
+func UpdatedAtIn(vs ...time.Time) predicate.Category {
+	return predicate.Category(sql.FieldIn(FieldUpdatedAt, vs...))
+}
+
+// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
+func UpdatedAtNotIn(vs ...time.Time) predicate.Category {
+	return predicate.Category(sql.FieldNotIn(FieldUpdatedAt, vs...))
+}
+
+// UpdatedAtGT applies the GT predicate on the "updated_at" field.
+func UpdatedAtGT(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldGT(FieldUpdatedAt, v))
+}
+
+// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
+func UpdatedAtGTE(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldGTE(FieldUpdatedAt, v))
+}
+
+// UpdatedAtLT applies the LT predicate on the "updated_at" field.
+func UpdatedAtLT(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldLT(FieldUpdatedAt, v))
+}
+
+// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
+func UpdatedAtLTE(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldLTE(FieldUpdatedAt, v))
+}
+
+// DeletedAtEQ applies the EQ predicate on the "deleted_at" field.
+func DeletedAtEQ(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldDeletedAt, v))
+}
+
+// DeletedAtNEQ applies the NEQ predicate on the "deleted_at" field.
+func DeletedAtNEQ(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldNEQ(FieldDeletedAt, v))
+}
+
+// DeletedAtIn applies the In predicate on the "deleted_at" field.
+func DeletedAtIn(vs ...time.Time) predicate.Category {
+	return predicate.Category(sql.FieldIn(FieldDeletedAt, vs...))
+}
+
+// DeletedAtNotIn applies the NotIn predicate on the "deleted_at" field.
+func DeletedAtNotIn(vs ...time.Time) predicate.Category {
+	return predicate.Category(sql.FieldNotIn(FieldDeletedAt, vs...))
+}
+
+// DeletedAtGT applies the GT predicate on the "deleted_at" field.
+func DeletedAtGT(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldGT(FieldDeletedAt, v))
+}
+
+// DeletedAtGTE applies the GTE predicate on the "deleted_at" field.
+func DeletedAtGTE(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldGTE(FieldDeletedAt, v))
+}
+
+// DeletedAtLT applies the LT predicate on the "deleted_at" field.
+func DeletedAtLT(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldLT(FieldDeletedAt, v))
+}
+
+// DeletedAtLTE applies the LTE predicate on the "deleted_at" field.
+func DeletedAtLTE(v time.Time) predicate.Category {
+	return predicate.Category(sql.FieldLTE(FieldDeletedAt, v))
+}
+
+// DeletedAtIsNil applies the IsNil predicate on the "deleted_at" field.
+func DeletedAtIsNil() predicate.Category {
+	return predicate.Category(sql.FieldIsNull(FieldDeletedAt))
+}
+
+// DeletedAtNotNil applies the NotNil predicate on the "deleted_at" field.
+func DeletedAtNotNil() predicate.Category {
+	return predicate.Category(sql.FieldNotNull(FieldDeletedAt))
+}
+
+// NameEQ applies the EQ predicate on the "name" field.
+func NameEQ(v string) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldName, v))
+}
+
+// NameNEQ applies the NEQ predicate on the "name" field.
+func NameNEQ(v string) predicate.Category {
+	return predicate.Category(sql.FieldNEQ(FieldName, v))
+}
+
+// NameIn applies the In predicate on the "name" field.
+func NameIn(vs ...string) predicate.Category {
+	return predicate.Category(sql.FieldIn(FieldName, vs...))
+}
+
+// NameNotIn applies the NotIn predicate on the "name" field.
+func NameNotIn(vs ...string) predicate.Category {
+	return predicate.Category(sql.FieldNotIn(FieldName, vs...))
+}
+
+// NameGT applies the GT predicate on the "name" field.
+func NameGT(v string) predicate.Category {
+	return predicate.Category(sql.FieldGT(FieldName, v))
+}
+
+// NameGTE applies the GTE predicate on the "name" field.
+func NameGTE(v string) predicate.Category {
+	return predicate.Category(sql.FieldGTE(FieldName, v))
+}
+
+// NameLT applies the LT predicate on the "name" field.
+func NameLT(v string) predicate.Category {
+	return predicate.Category(sql.FieldLT(FieldName, v))
+}
+
+// NameLTE applies the LTE predicate on the "name" field.
+func NameLTE(v string) predicate.Category {
+	return predicate.Category(sql.FieldLTE(FieldName, v))
+}
+
+// NameContains applies the Contains predicate on the "name" field.
+func NameContains(v string) predicate.Category {
+	return predicate.Category(sql.FieldContains(FieldName, v))
+}
+
+// NameHasPrefix applies the HasPrefix predicate on the "name" field.
+func NameHasPrefix(v string) predicate.Category {
+	return predicate.Category(sql.FieldHasPrefix(FieldName, v))
+}
+
+// NameHasSuffix applies the HasSuffix predicate on the "name" field.
+func NameHasSuffix(v string) predicate.Category {
+	return predicate.Category(sql.FieldHasSuffix(FieldName, v))
+}
+
+// NameEqualFold applies the EqualFold predicate on the "name" field.
+func NameEqualFold(v string) predicate.Category {
+	return predicate.Category(sql.FieldEqualFold(FieldName, v))
+}
+
+// NameContainsFold applies the ContainsFold predicate on the "name" field.
+func NameContainsFold(v string) predicate.Category {
+	return predicate.Category(sql.FieldContainsFold(FieldName, v))
+}
+
+// OrganizationIDEQ applies the EQ predicate on the "organization_id" field.
+func OrganizationIDEQ(v uint64) predicate.Category {
+	return predicate.Category(sql.FieldEQ(FieldOrganizationID, v))
+}
+
+// OrganizationIDNEQ applies the NEQ predicate on the "organization_id" field.
+func OrganizationIDNEQ(v uint64) predicate.Category {
+	return predicate.Category(sql.FieldNEQ(FieldOrganizationID, v))
+}
+
+// OrganizationIDIn applies the In predicate on the "organization_id" field.
+func OrganizationIDIn(vs ...uint64) predicate.Category {
+	return predicate.Category(sql.FieldIn(FieldOrganizationID, vs...))
+}
+
+// OrganizationIDNotIn applies the NotIn predicate on the "organization_id" field.
+func OrganizationIDNotIn(vs ...uint64) predicate.Category {
+	return predicate.Category(sql.FieldNotIn(FieldOrganizationID, vs...))
+}
+
+// OrganizationIDGT applies the GT predicate on the "organization_id" field.
+func OrganizationIDGT(v uint64) predicate.Category {
+	return predicate.Category(sql.FieldGT(FieldOrganizationID, v))
+}
+
+// OrganizationIDGTE applies the GTE predicate on the "organization_id" field.
+func OrganizationIDGTE(v uint64) predicate.Category {
+	return predicate.Category(sql.FieldGTE(FieldOrganizationID, v))
+}
+
+// OrganizationIDLT applies the LT predicate on the "organization_id" field.
+func OrganizationIDLT(v uint64) predicate.Category {
+	return predicate.Category(sql.FieldLT(FieldOrganizationID, v))
+}
+
+// OrganizationIDLTE applies the LTE predicate on the "organization_id" field.
+func OrganizationIDLTE(v uint64) predicate.Category {
+	return predicate.Category(sql.FieldLTE(FieldOrganizationID, v))
+}
+
+// And groups predicates with the AND operator between them.
+func And(predicates ...predicate.Category) predicate.Category {
+	return predicate.Category(sql.AndPredicates(predicates...))
+}
+
+// Or groups predicates with the OR operator between them.
+func Or(predicates ...predicate.Category) predicate.Category {
+	return predicate.Category(sql.OrPredicates(predicates...))
+}
+
+// Not applies the not operator on the given predicate.
+func Not(p predicate.Category) predicate.Category {
+	return predicate.Category(sql.NotPredicates(p))
+}

+ 757 - 0
ent/category_create.go

@@ -0,0 +1,757 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"time"
+	"wechat-api/ent/category"
+
+	"entgo.io/ent/dialect/sql"
+	"entgo.io/ent/dialect/sql/sqlgraph"
+	"entgo.io/ent/schema/field"
+)
+
+// CategoryCreate is the builder for creating a Category entity.
+type CategoryCreate struct {
+	config
+	mutation *CategoryMutation
+	hooks    []Hook
+	conflict []sql.ConflictOption
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (cc *CategoryCreate) SetCreatedAt(t time.Time) *CategoryCreate {
+	cc.mutation.SetCreatedAt(t)
+	return cc
+}
+
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
+func (cc *CategoryCreate) SetNillableCreatedAt(t *time.Time) *CategoryCreate {
+	if t != nil {
+		cc.SetCreatedAt(*t)
+	}
+	return cc
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (cc *CategoryCreate) SetUpdatedAt(t time.Time) *CategoryCreate {
+	cc.mutation.SetUpdatedAt(t)
+	return cc
+}
+
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
+func (cc *CategoryCreate) SetNillableUpdatedAt(t *time.Time) *CategoryCreate {
+	if t != nil {
+		cc.SetUpdatedAt(*t)
+	}
+	return cc
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (cc *CategoryCreate) SetDeletedAt(t time.Time) *CategoryCreate {
+	cc.mutation.SetDeletedAt(t)
+	return cc
+}
+
+// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
+func (cc *CategoryCreate) SetNillableDeletedAt(t *time.Time) *CategoryCreate {
+	if t != nil {
+		cc.SetDeletedAt(*t)
+	}
+	return cc
+}
+
+// SetName sets the "name" field.
+func (cc *CategoryCreate) SetName(s string) *CategoryCreate {
+	cc.mutation.SetName(s)
+	return cc
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (cc *CategoryCreate) SetOrganizationID(u uint64) *CategoryCreate {
+	cc.mutation.SetOrganizationID(u)
+	return cc
+}
+
+// SetID sets the "id" field.
+func (cc *CategoryCreate) SetID(u uint64) *CategoryCreate {
+	cc.mutation.SetID(u)
+	return cc
+}
+
+// Mutation returns the CategoryMutation object of the builder.
+func (cc *CategoryCreate) Mutation() *CategoryMutation {
+	return cc.mutation
+}
+
+// Save creates the Category in the database.
+func (cc *CategoryCreate) Save(ctx context.Context) (*Category, error) {
+	if err := cc.defaults(); err != nil {
+		return nil, err
+	}
+	return withHooks(ctx, cc.sqlSave, cc.mutation, cc.hooks)
+}
+
+// SaveX calls Save and panics if Save returns an error.
+func (cc *CategoryCreate) SaveX(ctx context.Context) *Category {
+	v, err := cc.Save(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return v
+}
+
+// Exec executes the query.
+func (cc *CategoryCreate) Exec(ctx context.Context) error {
+	_, err := cc.Save(ctx)
+	return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (cc *CategoryCreate) ExecX(ctx context.Context) {
+	if err := cc.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+// defaults sets the default values of the builder before save.
+func (cc *CategoryCreate) defaults() error {
+	if _, ok := cc.mutation.CreatedAt(); !ok {
+		if category.DefaultCreatedAt == nil {
+			return fmt.Errorf("ent: uninitialized category.DefaultCreatedAt (forgotten import ent/runtime?)")
+		}
+		v := category.DefaultCreatedAt()
+		cc.mutation.SetCreatedAt(v)
+	}
+	if _, ok := cc.mutation.UpdatedAt(); !ok {
+		if category.DefaultUpdatedAt == nil {
+			return fmt.Errorf("ent: uninitialized category.DefaultUpdatedAt (forgotten import ent/runtime?)")
+		}
+		v := category.DefaultUpdatedAt()
+		cc.mutation.SetUpdatedAt(v)
+	}
+	return nil
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (cc *CategoryCreate) check() error {
+	if _, ok := cc.mutation.CreatedAt(); !ok {
+		return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "Category.created_at"`)}
+	}
+	if _, ok := cc.mutation.UpdatedAt(); !ok {
+		return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "Category.updated_at"`)}
+	}
+	if _, ok := cc.mutation.Name(); !ok {
+		return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "Category.name"`)}
+	}
+	if v, ok := cc.mutation.Name(); ok {
+		if err := category.NameValidator(v); err != nil {
+			return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Category.name": %w`, err)}
+		}
+	}
+	if _, ok := cc.mutation.OrganizationID(); !ok {
+		return &ValidationError{Name: "organization_id", err: errors.New(`ent: missing required field "Category.organization_id"`)}
+	}
+	if v, ok := cc.mutation.OrganizationID(); ok {
+		if err := category.OrganizationIDValidator(v); err != nil {
+			return &ValidationError{Name: "organization_id", err: fmt.Errorf(`ent: validator failed for field "Category.organization_id": %w`, err)}
+		}
+	}
+	return nil
+}
+
+func (cc *CategoryCreate) sqlSave(ctx context.Context) (*Category, error) {
+	if err := cc.check(); err != nil {
+		return nil, err
+	}
+	_node, _spec := cc.createSpec()
+	if err := sqlgraph.CreateNode(ctx, cc.driver, _spec); err != nil {
+		if sqlgraph.IsConstraintError(err) {
+			err = &ConstraintError{msg: err.Error(), wrap: err}
+		}
+		return nil, err
+	}
+	if _spec.ID.Value != _node.ID {
+		id := _spec.ID.Value.(int64)
+		_node.ID = uint64(id)
+	}
+	cc.mutation.id = &_node.ID
+	cc.mutation.done = true
+	return _node, nil
+}
+
+func (cc *CategoryCreate) createSpec() (*Category, *sqlgraph.CreateSpec) {
+	var (
+		_node = &Category{config: cc.config}
+		_spec = sqlgraph.NewCreateSpec(category.Table, sqlgraph.NewFieldSpec(category.FieldID, field.TypeUint64))
+	)
+	_spec.OnConflict = cc.conflict
+	if id, ok := cc.mutation.ID(); ok {
+		_node.ID = id
+		_spec.ID.Value = id
+	}
+	if value, ok := cc.mutation.CreatedAt(); ok {
+		_spec.SetField(category.FieldCreatedAt, field.TypeTime, value)
+		_node.CreatedAt = value
+	}
+	if value, ok := cc.mutation.UpdatedAt(); ok {
+		_spec.SetField(category.FieldUpdatedAt, field.TypeTime, value)
+		_node.UpdatedAt = value
+	}
+	if value, ok := cc.mutation.DeletedAt(); ok {
+		_spec.SetField(category.FieldDeletedAt, field.TypeTime, value)
+		_node.DeletedAt = value
+	}
+	if value, ok := cc.mutation.Name(); ok {
+		_spec.SetField(category.FieldName, field.TypeString, value)
+		_node.Name = value
+	}
+	if value, ok := cc.mutation.OrganizationID(); ok {
+		_spec.SetField(category.FieldOrganizationID, field.TypeUint64, value)
+		_node.OrganizationID = value
+	}
+	return _node, _spec
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+//	client.Category.Create().
+//		SetCreatedAt(v).
+//		OnConflict(
+//			// Update the row with the new values
+//			// the was proposed for insertion.
+//			sql.ResolveWithNewValues(),
+//		).
+//		// Override some of the fields with custom
+//		// update values.
+//		Update(func(u *ent.CategoryUpsert) {
+//			SetCreatedAt(v+v).
+//		}).
+//		Exec(ctx)
+func (cc *CategoryCreate) OnConflict(opts ...sql.ConflictOption) *CategoryUpsertOne {
+	cc.conflict = opts
+	return &CategoryUpsertOne{
+		create: cc,
+	}
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+//	client.Category.Create().
+//		OnConflict(sql.ConflictColumns(columns...)).
+//		Exec(ctx)
+func (cc *CategoryCreate) OnConflictColumns(columns ...string) *CategoryUpsertOne {
+	cc.conflict = append(cc.conflict, sql.ConflictColumns(columns...))
+	return &CategoryUpsertOne{
+		create: cc,
+	}
+}
+
+type (
+	// CategoryUpsertOne is the builder for "upsert"-ing
+	//  one Category node.
+	CategoryUpsertOne struct {
+		create *CategoryCreate
+	}
+
+	// CategoryUpsert is the "OnConflict" setter.
+	CategoryUpsert struct {
+		*sql.UpdateSet
+	}
+)
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *CategoryUpsert) SetUpdatedAt(v time.Time) *CategoryUpsert {
+	u.Set(category.FieldUpdatedAt, v)
+	return u
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *CategoryUpsert) UpdateUpdatedAt() *CategoryUpsert {
+	u.SetExcluded(category.FieldUpdatedAt)
+	return u
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (u *CategoryUpsert) SetDeletedAt(v time.Time) *CategoryUpsert {
+	u.Set(category.FieldDeletedAt, v)
+	return u
+}
+
+// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
+func (u *CategoryUpsert) UpdateDeletedAt() *CategoryUpsert {
+	u.SetExcluded(category.FieldDeletedAt)
+	return u
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (u *CategoryUpsert) ClearDeletedAt() *CategoryUpsert {
+	u.SetNull(category.FieldDeletedAt)
+	return u
+}
+
+// SetName sets the "name" field.
+func (u *CategoryUpsert) SetName(v string) *CategoryUpsert {
+	u.Set(category.FieldName, v)
+	return u
+}
+
+// UpdateName sets the "name" field to the value that was provided on create.
+func (u *CategoryUpsert) UpdateName() *CategoryUpsert {
+	u.SetExcluded(category.FieldName)
+	return u
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (u *CategoryUpsert) SetOrganizationID(v uint64) *CategoryUpsert {
+	u.Set(category.FieldOrganizationID, v)
+	return u
+}
+
+// UpdateOrganizationID sets the "organization_id" field to the value that was provided on create.
+func (u *CategoryUpsert) UpdateOrganizationID() *CategoryUpsert {
+	u.SetExcluded(category.FieldOrganizationID)
+	return u
+}
+
+// AddOrganizationID adds v to the "organization_id" field.
+func (u *CategoryUpsert) AddOrganizationID(v uint64) *CategoryUpsert {
+	u.Add(category.FieldOrganizationID, v)
+	return u
+}
+
+// UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field.
+// Using this option is equivalent to using:
+//
+//	client.Category.Create().
+//		OnConflict(
+//			sql.ResolveWithNewValues(),
+//			sql.ResolveWith(func(u *sql.UpdateSet) {
+//				u.SetIgnore(category.FieldID)
+//			}),
+//		).
+//		Exec(ctx)
+func (u *CategoryUpsertOne) UpdateNewValues() *CategoryUpsertOne {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
+	u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
+		if _, exists := u.create.mutation.ID(); exists {
+			s.SetIgnore(category.FieldID)
+		}
+		if _, exists := u.create.mutation.CreatedAt(); exists {
+			s.SetIgnore(category.FieldCreatedAt)
+		}
+	}))
+	return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+//	client.Category.Create().
+//	    OnConflict(sql.ResolveWithIgnore()).
+//	    Exec(ctx)
+func (u *CategoryUpsertOne) Ignore() *CategoryUpsertOne {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
+	return u
+}
+
+// DoNothing configures the conflict_action to `DO NOTHING`.
+// Supported only by SQLite and PostgreSQL.
+func (u *CategoryUpsertOne) DoNothing() *CategoryUpsertOne {
+	u.create.conflict = append(u.create.conflict, sql.DoNothing())
+	return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the CategoryCreate.OnConflict
+// documentation for more info.
+func (u *CategoryUpsertOne) Update(set func(*CategoryUpsert)) *CategoryUpsertOne {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+		set(&CategoryUpsert{UpdateSet: update})
+	}))
+	return u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *CategoryUpsertOne) SetUpdatedAt(v time.Time) *CategoryUpsertOne {
+	return u.Update(func(s *CategoryUpsert) {
+		s.SetUpdatedAt(v)
+	})
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *CategoryUpsertOne) UpdateUpdatedAt() *CategoryUpsertOne {
+	return u.Update(func(s *CategoryUpsert) {
+		s.UpdateUpdatedAt()
+	})
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (u *CategoryUpsertOne) SetDeletedAt(v time.Time) *CategoryUpsertOne {
+	return u.Update(func(s *CategoryUpsert) {
+		s.SetDeletedAt(v)
+	})
+}
+
+// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
+func (u *CategoryUpsertOne) UpdateDeletedAt() *CategoryUpsertOne {
+	return u.Update(func(s *CategoryUpsert) {
+		s.UpdateDeletedAt()
+	})
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (u *CategoryUpsertOne) ClearDeletedAt() *CategoryUpsertOne {
+	return u.Update(func(s *CategoryUpsert) {
+		s.ClearDeletedAt()
+	})
+}
+
+// SetName sets the "name" field.
+func (u *CategoryUpsertOne) SetName(v string) *CategoryUpsertOne {
+	return u.Update(func(s *CategoryUpsert) {
+		s.SetName(v)
+	})
+}
+
+// UpdateName sets the "name" field to the value that was provided on create.
+func (u *CategoryUpsertOne) UpdateName() *CategoryUpsertOne {
+	return u.Update(func(s *CategoryUpsert) {
+		s.UpdateName()
+	})
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (u *CategoryUpsertOne) SetOrganizationID(v uint64) *CategoryUpsertOne {
+	return u.Update(func(s *CategoryUpsert) {
+		s.SetOrganizationID(v)
+	})
+}
+
+// AddOrganizationID adds v to the "organization_id" field.
+func (u *CategoryUpsertOne) AddOrganizationID(v uint64) *CategoryUpsertOne {
+	return u.Update(func(s *CategoryUpsert) {
+		s.AddOrganizationID(v)
+	})
+}
+
+// UpdateOrganizationID sets the "organization_id" field to the value that was provided on create.
+func (u *CategoryUpsertOne) UpdateOrganizationID() *CategoryUpsertOne {
+	return u.Update(func(s *CategoryUpsert) {
+		s.UpdateOrganizationID()
+	})
+}
+
+// Exec executes the query.
+func (u *CategoryUpsertOne) Exec(ctx context.Context) error {
+	if len(u.create.conflict) == 0 {
+		return errors.New("ent: missing options for CategoryCreate.OnConflict")
+	}
+	return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *CategoryUpsertOne) ExecX(ctx context.Context) {
+	if err := u.create.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+// Exec executes the UPSERT query and returns the inserted/updated ID.
+func (u *CategoryUpsertOne) ID(ctx context.Context) (id uint64, err error) {
+	node, err := u.create.Save(ctx)
+	if err != nil {
+		return id, err
+	}
+	return node.ID, nil
+}
+
+// IDX is like ID, but panics if an error occurs.
+func (u *CategoryUpsertOne) IDX(ctx context.Context) uint64 {
+	id, err := u.ID(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return id
+}
+
+// CategoryCreateBulk is the builder for creating many Category entities in bulk.
+type CategoryCreateBulk struct {
+	config
+	err      error
+	builders []*CategoryCreate
+	conflict []sql.ConflictOption
+}
+
+// Save creates the Category entities in the database.
+func (ccb *CategoryCreateBulk) Save(ctx context.Context) ([]*Category, error) {
+	if ccb.err != nil {
+		return nil, ccb.err
+	}
+	specs := make([]*sqlgraph.CreateSpec, len(ccb.builders))
+	nodes := make([]*Category, len(ccb.builders))
+	mutators := make([]Mutator, len(ccb.builders))
+	for i := range ccb.builders {
+		func(i int, root context.Context) {
+			builder := ccb.builders[i]
+			builder.defaults()
+			var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
+				mutation, ok := m.(*CategoryMutation)
+				if !ok {
+					return nil, fmt.Errorf("unexpected mutation type %T", m)
+				}
+				if err := builder.check(); err != nil {
+					return nil, err
+				}
+				builder.mutation = mutation
+				var err error
+				nodes[i], specs[i] = builder.createSpec()
+				if i < len(mutators)-1 {
+					_, err = mutators[i+1].Mutate(root, ccb.builders[i+1].mutation)
+				} else {
+					spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
+					spec.OnConflict = ccb.conflict
+					// Invoke the actual operation on the latest mutation in the chain.
+					if err = sqlgraph.BatchCreate(ctx, ccb.driver, spec); err != nil {
+						if sqlgraph.IsConstraintError(err) {
+							err = &ConstraintError{msg: err.Error(), wrap: err}
+						}
+					}
+				}
+				if err != nil {
+					return nil, err
+				}
+				mutation.id = &nodes[i].ID
+				if specs[i].ID.Value != nil && nodes[i].ID == 0 {
+					id := specs[i].ID.Value.(int64)
+					nodes[i].ID = uint64(id)
+				}
+				mutation.done = true
+				return nodes[i], nil
+			})
+			for i := len(builder.hooks) - 1; i >= 0; i-- {
+				mut = builder.hooks[i](mut)
+			}
+			mutators[i] = mut
+		}(i, ctx)
+	}
+	if len(mutators) > 0 {
+		if _, err := mutators[0].Mutate(ctx, ccb.builders[0].mutation); err != nil {
+			return nil, err
+		}
+	}
+	return nodes, nil
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (ccb *CategoryCreateBulk) SaveX(ctx context.Context) []*Category {
+	v, err := ccb.Save(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return v
+}
+
+// Exec executes the query.
+func (ccb *CategoryCreateBulk) Exec(ctx context.Context) error {
+	_, err := ccb.Save(ctx)
+	return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (ccb *CategoryCreateBulk) ExecX(ctx context.Context) {
+	if err := ccb.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+//	client.Category.CreateBulk(builders...).
+//		OnConflict(
+//			// Update the row with the new values
+//			// the was proposed for insertion.
+//			sql.ResolveWithNewValues(),
+//		).
+//		// Override some of the fields with custom
+//		// update values.
+//		Update(func(u *ent.CategoryUpsert) {
+//			SetCreatedAt(v+v).
+//		}).
+//		Exec(ctx)
+func (ccb *CategoryCreateBulk) OnConflict(opts ...sql.ConflictOption) *CategoryUpsertBulk {
+	ccb.conflict = opts
+	return &CategoryUpsertBulk{
+		create: ccb,
+	}
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+//	client.Category.Create().
+//		OnConflict(sql.ConflictColumns(columns...)).
+//		Exec(ctx)
+func (ccb *CategoryCreateBulk) OnConflictColumns(columns ...string) *CategoryUpsertBulk {
+	ccb.conflict = append(ccb.conflict, sql.ConflictColumns(columns...))
+	return &CategoryUpsertBulk{
+		create: ccb,
+	}
+}
+
+// CategoryUpsertBulk is the builder for "upsert"-ing
+// a bulk of Category nodes.
+type CategoryUpsertBulk struct {
+	create *CategoryCreateBulk
+}
+
+// UpdateNewValues updates the mutable fields using the new values that
+// were set on create. Using this option is equivalent to using:
+//
+//	client.Category.Create().
+//		OnConflict(
+//			sql.ResolveWithNewValues(),
+//			sql.ResolveWith(func(u *sql.UpdateSet) {
+//				u.SetIgnore(category.FieldID)
+//			}),
+//		).
+//		Exec(ctx)
+func (u *CategoryUpsertBulk) UpdateNewValues() *CategoryUpsertBulk {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
+	u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
+		for _, b := range u.create.builders {
+			if _, exists := b.mutation.ID(); exists {
+				s.SetIgnore(category.FieldID)
+			}
+			if _, exists := b.mutation.CreatedAt(); exists {
+				s.SetIgnore(category.FieldCreatedAt)
+			}
+		}
+	}))
+	return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+//	client.Category.Create().
+//		OnConflict(sql.ResolveWithIgnore()).
+//		Exec(ctx)
+func (u *CategoryUpsertBulk) Ignore() *CategoryUpsertBulk {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
+	return u
+}
+
+// DoNothing configures the conflict_action to `DO NOTHING`.
+// Supported only by SQLite and PostgreSQL.
+func (u *CategoryUpsertBulk) DoNothing() *CategoryUpsertBulk {
+	u.create.conflict = append(u.create.conflict, sql.DoNothing())
+	return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the CategoryCreateBulk.OnConflict
+// documentation for more info.
+func (u *CategoryUpsertBulk) Update(set func(*CategoryUpsert)) *CategoryUpsertBulk {
+	u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+		set(&CategoryUpsert{UpdateSet: update})
+	}))
+	return u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *CategoryUpsertBulk) SetUpdatedAt(v time.Time) *CategoryUpsertBulk {
+	return u.Update(func(s *CategoryUpsert) {
+		s.SetUpdatedAt(v)
+	})
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *CategoryUpsertBulk) UpdateUpdatedAt() *CategoryUpsertBulk {
+	return u.Update(func(s *CategoryUpsert) {
+		s.UpdateUpdatedAt()
+	})
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (u *CategoryUpsertBulk) SetDeletedAt(v time.Time) *CategoryUpsertBulk {
+	return u.Update(func(s *CategoryUpsert) {
+		s.SetDeletedAt(v)
+	})
+}
+
+// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create.
+func (u *CategoryUpsertBulk) UpdateDeletedAt() *CategoryUpsertBulk {
+	return u.Update(func(s *CategoryUpsert) {
+		s.UpdateDeletedAt()
+	})
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (u *CategoryUpsertBulk) ClearDeletedAt() *CategoryUpsertBulk {
+	return u.Update(func(s *CategoryUpsert) {
+		s.ClearDeletedAt()
+	})
+}
+
+// SetName sets the "name" field.
+func (u *CategoryUpsertBulk) SetName(v string) *CategoryUpsertBulk {
+	return u.Update(func(s *CategoryUpsert) {
+		s.SetName(v)
+	})
+}
+
+// UpdateName sets the "name" field to the value that was provided on create.
+func (u *CategoryUpsertBulk) UpdateName() *CategoryUpsertBulk {
+	return u.Update(func(s *CategoryUpsert) {
+		s.UpdateName()
+	})
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (u *CategoryUpsertBulk) SetOrganizationID(v uint64) *CategoryUpsertBulk {
+	return u.Update(func(s *CategoryUpsert) {
+		s.SetOrganizationID(v)
+	})
+}
+
+// AddOrganizationID adds v to the "organization_id" field.
+func (u *CategoryUpsertBulk) AddOrganizationID(v uint64) *CategoryUpsertBulk {
+	return u.Update(func(s *CategoryUpsert) {
+		s.AddOrganizationID(v)
+	})
+}
+
+// UpdateOrganizationID sets the "organization_id" field to the value that was provided on create.
+func (u *CategoryUpsertBulk) UpdateOrganizationID() *CategoryUpsertBulk {
+	return u.Update(func(s *CategoryUpsert) {
+		s.UpdateOrganizationID()
+	})
+}
+
+// Exec executes the query.
+func (u *CategoryUpsertBulk) Exec(ctx context.Context) error {
+	if u.create.err != nil {
+		return u.create.err
+	}
+	for i, b := range u.create.builders {
+		if len(b.conflict) != 0 {
+			return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the CategoryCreateBulk instead", i)
+		}
+	}
+	if len(u.create.conflict) == 0 {
+		return errors.New("ent: missing options for CategoryCreateBulk.OnConflict")
+	}
+	return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *CategoryUpsertBulk) ExecX(ctx context.Context) {
+	if err := u.create.Exec(ctx); err != nil {
+		panic(err)
+	}
+}

+ 88 - 0
ent/category_delete.go

@@ -0,0 +1,88 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+	"wechat-api/ent/category"
+	"wechat-api/ent/predicate"
+
+	"entgo.io/ent/dialect/sql"
+	"entgo.io/ent/dialect/sql/sqlgraph"
+	"entgo.io/ent/schema/field"
+)
+
+// CategoryDelete is the builder for deleting a Category entity.
+type CategoryDelete struct {
+	config
+	hooks    []Hook
+	mutation *CategoryMutation
+}
+
+// Where appends a list predicates to the CategoryDelete builder.
+func (cd *CategoryDelete) Where(ps ...predicate.Category) *CategoryDelete {
+	cd.mutation.Where(ps...)
+	return cd
+}
+
+// Exec executes the deletion query and returns how many vertices were deleted.
+func (cd *CategoryDelete) Exec(ctx context.Context) (int, error) {
+	return withHooks(ctx, cd.sqlExec, cd.mutation, cd.hooks)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (cd *CategoryDelete) ExecX(ctx context.Context) int {
+	n, err := cd.Exec(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return n
+}
+
+func (cd *CategoryDelete) sqlExec(ctx context.Context) (int, error) {
+	_spec := sqlgraph.NewDeleteSpec(category.Table, sqlgraph.NewFieldSpec(category.FieldID, field.TypeUint64))
+	if ps := cd.mutation.predicates; len(ps) > 0 {
+		_spec.Predicate = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	affected, err := sqlgraph.DeleteNodes(ctx, cd.driver, _spec)
+	if err != nil && sqlgraph.IsConstraintError(err) {
+		err = &ConstraintError{msg: err.Error(), wrap: err}
+	}
+	cd.mutation.done = true
+	return affected, err
+}
+
+// CategoryDeleteOne is the builder for deleting a single Category entity.
+type CategoryDeleteOne struct {
+	cd *CategoryDelete
+}
+
+// Where appends a list predicates to the CategoryDelete builder.
+func (cdo *CategoryDeleteOne) Where(ps ...predicate.Category) *CategoryDeleteOne {
+	cdo.cd.mutation.Where(ps...)
+	return cdo
+}
+
+// Exec executes the deletion query.
+func (cdo *CategoryDeleteOne) Exec(ctx context.Context) error {
+	n, err := cdo.cd.Exec(ctx)
+	switch {
+	case err != nil:
+		return err
+	case n == 0:
+		return &NotFoundError{category.Label}
+	default:
+		return nil
+	}
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (cdo *CategoryDeleteOne) ExecX(ctx context.Context) {
+	if err := cdo.Exec(ctx); err != nil {
+		panic(err)
+	}
+}

+ 526 - 0
ent/category_query.go

@@ -0,0 +1,526 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+	"fmt"
+	"math"
+	"wechat-api/ent/category"
+	"wechat-api/ent/predicate"
+
+	"entgo.io/ent/dialect/sql"
+	"entgo.io/ent/dialect/sql/sqlgraph"
+	"entgo.io/ent/schema/field"
+)
+
+// CategoryQuery is the builder for querying Category entities.
+type CategoryQuery struct {
+	config
+	ctx        *QueryContext
+	order      []category.OrderOption
+	inters     []Interceptor
+	predicates []predicate.Category
+	// intermediate query (i.e. traversal path).
+	sql  *sql.Selector
+	path func(context.Context) (*sql.Selector, error)
+}
+
+// Where adds a new predicate for the CategoryQuery builder.
+func (cq *CategoryQuery) Where(ps ...predicate.Category) *CategoryQuery {
+	cq.predicates = append(cq.predicates, ps...)
+	return cq
+}
+
+// Limit the number of records to be returned by this query.
+func (cq *CategoryQuery) Limit(limit int) *CategoryQuery {
+	cq.ctx.Limit = &limit
+	return cq
+}
+
+// Offset to start from.
+func (cq *CategoryQuery) Offset(offset int) *CategoryQuery {
+	cq.ctx.Offset = &offset
+	return cq
+}
+
+// Unique configures the query builder to filter duplicate records on query.
+// By default, unique is set to true, and can be disabled using this method.
+func (cq *CategoryQuery) Unique(unique bool) *CategoryQuery {
+	cq.ctx.Unique = &unique
+	return cq
+}
+
+// Order specifies how the records should be ordered.
+func (cq *CategoryQuery) Order(o ...category.OrderOption) *CategoryQuery {
+	cq.order = append(cq.order, o...)
+	return cq
+}
+
+// First returns the first Category entity from the query.
+// Returns a *NotFoundError when no Category was found.
+func (cq *CategoryQuery) First(ctx context.Context) (*Category, error) {
+	nodes, err := cq.Limit(1).All(setContextOp(ctx, cq.ctx, "First"))
+	if err != nil {
+		return nil, err
+	}
+	if len(nodes) == 0 {
+		return nil, &NotFoundError{category.Label}
+	}
+	return nodes[0], nil
+}
+
+// FirstX is like First, but panics if an error occurs.
+func (cq *CategoryQuery) FirstX(ctx context.Context) *Category {
+	node, err := cq.First(ctx)
+	if err != nil && !IsNotFound(err) {
+		panic(err)
+	}
+	return node
+}
+
+// FirstID returns the first Category ID from the query.
+// Returns a *NotFoundError when no Category ID was found.
+func (cq *CategoryQuery) FirstID(ctx context.Context) (id uint64, err error) {
+	var ids []uint64
+	if ids, err = cq.Limit(1).IDs(setContextOp(ctx, cq.ctx, "FirstID")); err != nil {
+		return
+	}
+	if len(ids) == 0 {
+		err = &NotFoundError{category.Label}
+		return
+	}
+	return ids[0], nil
+}
+
+// FirstIDX is like FirstID, but panics if an error occurs.
+func (cq *CategoryQuery) FirstIDX(ctx context.Context) uint64 {
+	id, err := cq.FirstID(ctx)
+	if err != nil && !IsNotFound(err) {
+		panic(err)
+	}
+	return id
+}
+
+// Only returns a single Category entity found by the query, ensuring it only returns one.
+// Returns a *NotSingularError when more than one Category entity is found.
+// Returns a *NotFoundError when no Category entities are found.
+func (cq *CategoryQuery) Only(ctx context.Context) (*Category, error) {
+	nodes, err := cq.Limit(2).All(setContextOp(ctx, cq.ctx, "Only"))
+	if err != nil {
+		return nil, err
+	}
+	switch len(nodes) {
+	case 1:
+		return nodes[0], nil
+	case 0:
+		return nil, &NotFoundError{category.Label}
+	default:
+		return nil, &NotSingularError{category.Label}
+	}
+}
+
+// OnlyX is like Only, but panics if an error occurs.
+func (cq *CategoryQuery) OnlyX(ctx context.Context) *Category {
+	node, err := cq.Only(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return node
+}
+
+// OnlyID is like Only, but returns the only Category ID in the query.
+// Returns a *NotSingularError when more than one Category ID is found.
+// Returns a *NotFoundError when no entities are found.
+func (cq *CategoryQuery) OnlyID(ctx context.Context) (id uint64, err error) {
+	var ids []uint64
+	if ids, err = cq.Limit(2).IDs(setContextOp(ctx, cq.ctx, "OnlyID")); err != nil {
+		return
+	}
+	switch len(ids) {
+	case 1:
+		id = ids[0]
+	case 0:
+		err = &NotFoundError{category.Label}
+	default:
+		err = &NotSingularError{category.Label}
+	}
+	return
+}
+
+// OnlyIDX is like OnlyID, but panics if an error occurs.
+func (cq *CategoryQuery) OnlyIDX(ctx context.Context) uint64 {
+	id, err := cq.OnlyID(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return id
+}
+
+// All executes the query and returns a list of Categories.
+func (cq *CategoryQuery) All(ctx context.Context) ([]*Category, error) {
+	ctx = setContextOp(ctx, cq.ctx, "All")
+	if err := cq.prepareQuery(ctx); err != nil {
+		return nil, err
+	}
+	qr := querierAll[[]*Category, *CategoryQuery]()
+	return withInterceptors[[]*Category](ctx, cq, qr, cq.inters)
+}
+
+// AllX is like All, but panics if an error occurs.
+func (cq *CategoryQuery) AllX(ctx context.Context) []*Category {
+	nodes, err := cq.All(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return nodes
+}
+
+// IDs executes the query and returns a list of Category IDs.
+func (cq *CategoryQuery) IDs(ctx context.Context) (ids []uint64, err error) {
+	if cq.ctx.Unique == nil && cq.path != nil {
+		cq.Unique(true)
+	}
+	ctx = setContextOp(ctx, cq.ctx, "IDs")
+	if err = cq.Select(category.FieldID).Scan(ctx, &ids); err != nil {
+		return nil, err
+	}
+	return ids, nil
+}
+
+// IDsX is like IDs, but panics if an error occurs.
+func (cq *CategoryQuery) IDsX(ctx context.Context) []uint64 {
+	ids, err := cq.IDs(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return ids
+}
+
+// Count returns the count of the given query.
+func (cq *CategoryQuery) Count(ctx context.Context) (int, error) {
+	ctx = setContextOp(ctx, cq.ctx, "Count")
+	if err := cq.prepareQuery(ctx); err != nil {
+		return 0, err
+	}
+	return withInterceptors[int](ctx, cq, querierCount[*CategoryQuery](), cq.inters)
+}
+
+// CountX is like Count, but panics if an error occurs.
+func (cq *CategoryQuery) CountX(ctx context.Context) int {
+	count, err := cq.Count(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return count
+}
+
+// Exist returns true if the query has elements in the graph.
+func (cq *CategoryQuery) Exist(ctx context.Context) (bool, error) {
+	ctx = setContextOp(ctx, cq.ctx, "Exist")
+	switch _, err := cq.FirstID(ctx); {
+	case IsNotFound(err):
+		return false, nil
+	case err != nil:
+		return false, fmt.Errorf("ent: check existence: %w", err)
+	default:
+		return true, nil
+	}
+}
+
+// ExistX is like Exist, but panics if an error occurs.
+func (cq *CategoryQuery) ExistX(ctx context.Context) bool {
+	exist, err := cq.Exist(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return exist
+}
+
+// Clone returns a duplicate of the CategoryQuery builder, including all associated steps. It can be
+// used to prepare common query builders and use them differently after the clone is made.
+func (cq *CategoryQuery) Clone() *CategoryQuery {
+	if cq == nil {
+		return nil
+	}
+	return &CategoryQuery{
+		config:     cq.config,
+		ctx:        cq.ctx.Clone(),
+		order:      append([]category.OrderOption{}, cq.order...),
+		inters:     append([]Interceptor{}, cq.inters...),
+		predicates: append([]predicate.Category{}, cq.predicates...),
+		// clone intermediate query.
+		sql:  cq.sql.Clone(),
+		path: cq.path,
+	}
+}
+
+// GroupBy is used to group vertices by one or more fields/columns.
+// It is often used with aggregate functions, like: count, max, mean, min, sum.
+//
+// Example:
+//
+//	var v []struct {
+//		CreatedAt time.Time `json:"created_at,omitempty"`
+//		Count int `json:"count,omitempty"`
+//	}
+//
+//	client.Category.Query().
+//		GroupBy(category.FieldCreatedAt).
+//		Aggregate(ent.Count()).
+//		Scan(ctx, &v)
+func (cq *CategoryQuery) GroupBy(field string, fields ...string) *CategoryGroupBy {
+	cq.ctx.Fields = append([]string{field}, fields...)
+	grbuild := &CategoryGroupBy{build: cq}
+	grbuild.flds = &cq.ctx.Fields
+	grbuild.label = category.Label
+	grbuild.scan = grbuild.Scan
+	return grbuild
+}
+
+// Select allows the selection one or more fields/columns for the given query,
+// instead of selecting all fields in the entity.
+//
+// Example:
+//
+//	var v []struct {
+//		CreatedAt time.Time `json:"created_at,omitempty"`
+//	}
+//
+//	client.Category.Query().
+//		Select(category.FieldCreatedAt).
+//		Scan(ctx, &v)
+func (cq *CategoryQuery) Select(fields ...string) *CategorySelect {
+	cq.ctx.Fields = append(cq.ctx.Fields, fields...)
+	sbuild := &CategorySelect{CategoryQuery: cq}
+	sbuild.label = category.Label
+	sbuild.flds, sbuild.scan = &cq.ctx.Fields, sbuild.Scan
+	return sbuild
+}
+
+// Aggregate returns a CategorySelect configured with the given aggregations.
+func (cq *CategoryQuery) Aggregate(fns ...AggregateFunc) *CategorySelect {
+	return cq.Select().Aggregate(fns...)
+}
+
+func (cq *CategoryQuery) prepareQuery(ctx context.Context) error {
+	for _, inter := range cq.inters {
+		if inter == nil {
+			return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
+		}
+		if trv, ok := inter.(Traverser); ok {
+			if err := trv.Traverse(ctx, cq); err != nil {
+				return err
+			}
+		}
+	}
+	for _, f := range cq.ctx.Fields {
+		if !category.ValidColumn(f) {
+			return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+		}
+	}
+	if cq.path != nil {
+		prev, err := cq.path(ctx)
+		if err != nil {
+			return err
+		}
+		cq.sql = prev
+	}
+	return nil
+}
+
+func (cq *CategoryQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Category, error) {
+	var (
+		nodes = []*Category{}
+		_spec = cq.querySpec()
+	)
+	_spec.ScanValues = func(columns []string) ([]any, error) {
+		return (*Category).scanValues(nil, columns)
+	}
+	_spec.Assign = func(columns []string, values []any) error {
+		node := &Category{config: cq.config}
+		nodes = append(nodes, node)
+		return node.assignValues(columns, values)
+	}
+	for i := range hooks {
+		hooks[i](ctx, _spec)
+	}
+	if err := sqlgraph.QueryNodes(ctx, cq.driver, _spec); err != nil {
+		return nil, err
+	}
+	if len(nodes) == 0 {
+		return nodes, nil
+	}
+	return nodes, nil
+}
+
+func (cq *CategoryQuery) sqlCount(ctx context.Context) (int, error) {
+	_spec := cq.querySpec()
+	_spec.Node.Columns = cq.ctx.Fields
+	if len(cq.ctx.Fields) > 0 {
+		_spec.Unique = cq.ctx.Unique != nil && *cq.ctx.Unique
+	}
+	return sqlgraph.CountNodes(ctx, cq.driver, _spec)
+}
+
+func (cq *CategoryQuery) querySpec() *sqlgraph.QuerySpec {
+	_spec := sqlgraph.NewQuerySpec(category.Table, category.Columns, sqlgraph.NewFieldSpec(category.FieldID, field.TypeUint64))
+	_spec.From = cq.sql
+	if unique := cq.ctx.Unique; unique != nil {
+		_spec.Unique = *unique
+	} else if cq.path != nil {
+		_spec.Unique = true
+	}
+	if fields := cq.ctx.Fields; len(fields) > 0 {
+		_spec.Node.Columns = make([]string, 0, len(fields))
+		_spec.Node.Columns = append(_spec.Node.Columns, category.FieldID)
+		for i := range fields {
+			if fields[i] != category.FieldID {
+				_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
+			}
+		}
+	}
+	if ps := cq.predicates; len(ps) > 0 {
+		_spec.Predicate = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	if limit := cq.ctx.Limit; limit != nil {
+		_spec.Limit = *limit
+	}
+	if offset := cq.ctx.Offset; offset != nil {
+		_spec.Offset = *offset
+	}
+	if ps := cq.order; len(ps) > 0 {
+		_spec.Order = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	return _spec
+}
+
+func (cq *CategoryQuery) sqlQuery(ctx context.Context) *sql.Selector {
+	builder := sql.Dialect(cq.driver.Dialect())
+	t1 := builder.Table(category.Table)
+	columns := cq.ctx.Fields
+	if len(columns) == 0 {
+		columns = category.Columns
+	}
+	selector := builder.Select(t1.Columns(columns...)...).From(t1)
+	if cq.sql != nil {
+		selector = cq.sql
+		selector.Select(selector.Columns(columns...)...)
+	}
+	if cq.ctx.Unique != nil && *cq.ctx.Unique {
+		selector.Distinct()
+	}
+	for _, p := range cq.predicates {
+		p(selector)
+	}
+	for _, p := range cq.order {
+		p(selector)
+	}
+	if offset := cq.ctx.Offset; offset != nil {
+		// limit is mandatory for offset clause. We start
+		// with default value, and override it below if needed.
+		selector.Offset(*offset).Limit(math.MaxInt32)
+	}
+	if limit := cq.ctx.Limit; limit != nil {
+		selector.Limit(*limit)
+	}
+	return selector
+}
+
+// CategoryGroupBy is the group-by builder for Category entities.
+type CategoryGroupBy struct {
+	selector
+	build *CategoryQuery
+}
+
+// Aggregate adds the given aggregation functions to the group-by query.
+func (cgb *CategoryGroupBy) Aggregate(fns ...AggregateFunc) *CategoryGroupBy {
+	cgb.fns = append(cgb.fns, fns...)
+	return cgb
+}
+
+// Scan applies the selector query and scans the result into the given value.
+func (cgb *CategoryGroupBy) Scan(ctx context.Context, v any) error {
+	ctx = setContextOp(ctx, cgb.build.ctx, "GroupBy")
+	if err := cgb.build.prepareQuery(ctx); err != nil {
+		return err
+	}
+	return scanWithInterceptors[*CategoryQuery, *CategoryGroupBy](ctx, cgb.build, cgb, cgb.build.inters, v)
+}
+
+func (cgb *CategoryGroupBy) sqlScan(ctx context.Context, root *CategoryQuery, v any) error {
+	selector := root.sqlQuery(ctx).Select()
+	aggregation := make([]string, 0, len(cgb.fns))
+	for _, fn := range cgb.fns {
+		aggregation = append(aggregation, fn(selector))
+	}
+	if len(selector.SelectedColumns()) == 0 {
+		columns := make([]string, 0, len(*cgb.flds)+len(cgb.fns))
+		for _, f := range *cgb.flds {
+			columns = append(columns, selector.C(f))
+		}
+		columns = append(columns, aggregation...)
+		selector.Select(columns...)
+	}
+	selector.GroupBy(selector.Columns(*cgb.flds...)...)
+	if err := selector.Err(); err != nil {
+		return err
+	}
+	rows := &sql.Rows{}
+	query, args := selector.Query()
+	if err := cgb.build.driver.Query(ctx, query, args, rows); err != nil {
+		return err
+	}
+	defer rows.Close()
+	return sql.ScanSlice(rows, v)
+}
+
+// CategorySelect is the builder for selecting fields of Category entities.
+type CategorySelect struct {
+	*CategoryQuery
+	selector
+}
+
+// Aggregate adds the given aggregation functions to the selector query.
+func (cs *CategorySelect) Aggregate(fns ...AggregateFunc) *CategorySelect {
+	cs.fns = append(cs.fns, fns...)
+	return cs
+}
+
+// Scan applies the selector query and scans the result into the given value.
+func (cs *CategorySelect) Scan(ctx context.Context, v any) error {
+	ctx = setContextOp(ctx, cs.ctx, "Select")
+	if err := cs.prepareQuery(ctx); err != nil {
+		return err
+	}
+	return scanWithInterceptors[*CategoryQuery, *CategorySelect](ctx, cs.CategoryQuery, cs, cs.inters, v)
+}
+
+func (cs *CategorySelect) sqlScan(ctx context.Context, root *CategoryQuery, v any) error {
+	selector := root.sqlQuery(ctx)
+	aggregation := make([]string, 0, len(cs.fns))
+	for _, fn := range cs.fns {
+		aggregation = append(aggregation, fn(selector))
+	}
+	switch n := len(*cs.selector.flds); {
+	case n == 0 && len(aggregation) > 0:
+		selector.Select(aggregation...)
+	case n != 0 && len(aggregation) > 0:
+		selector.AppendSelect(aggregation...)
+	}
+	rows := &sql.Rows{}
+	query, args := selector.Query()
+	if err := cs.driver.Query(ctx, query, args, rows); err != nil {
+		return err
+	}
+	defer rows.Close()
+	return sql.ScanSlice(rows, v)
+}

+ 400 - 0
ent/category_update.go

@@ -0,0 +1,400 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"time"
+	"wechat-api/ent/category"
+	"wechat-api/ent/predicate"
+
+	"entgo.io/ent/dialect/sql"
+	"entgo.io/ent/dialect/sql/sqlgraph"
+	"entgo.io/ent/schema/field"
+)
+
+// CategoryUpdate is the builder for updating Category entities.
+type CategoryUpdate struct {
+	config
+	hooks    []Hook
+	mutation *CategoryMutation
+}
+
+// Where appends a list predicates to the CategoryUpdate builder.
+func (cu *CategoryUpdate) Where(ps ...predicate.Category) *CategoryUpdate {
+	cu.mutation.Where(ps...)
+	return cu
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (cu *CategoryUpdate) SetUpdatedAt(t time.Time) *CategoryUpdate {
+	cu.mutation.SetUpdatedAt(t)
+	return cu
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (cu *CategoryUpdate) SetDeletedAt(t time.Time) *CategoryUpdate {
+	cu.mutation.SetDeletedAt(t)
+	return cu
+}
+
+// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
+func (cu *CategoryUpdate) SetNillableDeletedAt(t *time.Time) *CategoryUpdate {
+	if t != nil {
+		cu.SetDeletedAt(*t)
+	}
+	return cu
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (cu *CategoryUpdate) ClearDeletedAt() *CategoryUpdate {
+	cu.mutation.ClearDeletedAt()
+	return cu
+}
+
+// SetName sets the "name" field.
+func (cu *CategoryUpdate) SetName(s string) *CategoryUpdate {
+	cu.mutation.SetName(s)
+	return cu
+}
+
+// SetNillableName sets the "name" field if the given value is not nil.
+func (cu *CategoryUpdate) SetNillableName(s *string) *CategoryUpdate {
+	if s != nil {
+		cu.SetName(*s)
+	}
+	return cu
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (cu *CategoryUpdate) SetOrganizationID(u uint64) *CategoryUpdate {
+	cu.mutation.ResetOrganizationID()
+	cu.mutation.SetOrganizationID(u)
+	return cu
+}
+
+// SetNillableOrganizationID sets the "organization_id" field if the given value is not nil.
+func (cu *CategoryUpdate) SetNillableOrganizationID(u *uint64) *CategoryUpdate {
+	if u != nil {
+		cu.SetOrganizationID(*u)
+	}
+	return cu
+}
+
+// AddOrganizationID adds u to the "organization_id" field.
+func (cu *CategoryUpdate) AddOrganizationID(u int64) *CategoryUpdate {
+	cu.mutation.AddOrganizationID(u)
+	return cu
+}
+
+// Mutation returns the CategoryMutation object of the builder.
+func (cu *CategoryUpdate) Mutation() *CategoryMutation {
+	return cu.mutation
+}
+
+// Save executes the query and returns the number of nodes affected by the update operation.
+func (cu *CategoryUpdate) Save(ctx context.Context) (int, error) {
+	if err := cu.defaults(); err != nil {
+		return 0, err
+	}
+	return withHooks(ctx, cu.sqlSave, cu.mutation, cu.hooks)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (cu *CategoryUpdate) SaveX(ctx context.Context) int {
+	affected, err := cu.Save(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return affected
+}
+
+// Exec executes the query.
+func (cu *CategoryUpdate) Exec(ctx context.Context) error {
+	_, err := cu.Save(ctx)
+	return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (cu *CategoryUpdate) ExecX(ctx context.Context) {
+	if err := cu.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+// defaults sets the default values of the builder before save.
+func (cu *CategoryUpdate) defaults() error {
+	if _, ok := cu.mutation.UpdatedAt(); !ok {
+		if category.UpdateDefaultUpdatedAt == nil {
+			return fmt.Errorf("ent: uninitialized category.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
+		}
+		v := category.UpdateDefaultUpdatedAt()
+		cu.mutation.SetUpdatedAt(v)
+	}
+	return nil
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (cu *CategoryUpdate) check() error {
+	if v, ok := cu.mutation.Name(); ok {
+		if err := category.NameValidator(v); err != nil {
+			return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Category.name": %w`, err)}
+		}
+	}
+	if v, ok := cu.mutation.OrganizationID(); ok {
+		if err := category.OrganizationIDValidator(v); err != nil {
+			return &ValidationError{Name: "organization_id", err: fmt.Errorf(`ent: validator failed for field "Category.organization_id": %w`, err)}
+		}
+	}
+	return nil
+}
+
+func (cu *CategoryUpdate) sqlSave(ctx context.Context) (n int, err error) {
+	if err := cu.check(); err != nil {
+		return n, err
+	}
+	_spec := sqlgraph.NewUpdateSpec(category.Table, category.Columns, sqlgraph.NewFieldSpec(category.FieldID, field.TypeUint64))
+	if ps := cu.mutation.predicates; len(ps) > 0 {
+		_spec.Predicate = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	if value, ok := cu.mutation.UpdatedAt(); ok {
+		_spec.SetField(category.FieldUpdatedAt, field.TypeTime, value)
+	}
+	if value, ok := cu.mutation.DeletedAt(); ok {
+		_spec.SetField(category.FieldDeletedAt, field.TypeTime, value)
+	}
+	if cu.mutation.DeletedAtCleared() {
+		_spec.ClearField(category.FieldDeletedAt, field.TypeTime)
+	}
+	if value, ok := cu.mutation.Name(); ok {
+		_spec.SetField(category.FieldName, field.TypeString, value)
+	}
+	if value, ok := cu.mutation.OrganizationID(); ok {
+		_spec.SetField(category.FieldOrganizationID, field.TypeUint64, value)
+	}
+	if value, ok := cu.mutation.AddedOrganizationID(); ok {
+		_spec.AddField(category.FieldOrganizationID, field.TypeUint64, value)
+	}
+	if n, err = sqlgraph.UpdateNodes(ctx, cu.driver, _spec); err != nil {
+		if _, ok := err.(*sqlgraph.NotFoundError); ok {
+			err = &NotFoundError{category.Label}
+		} else if sqlgraph.IsConstraintError(err) {
+			err = &ConstraintError{msg: err.Error(), wrap: err}
+		}
+		return 0, err
+	}
+	cu.mutation.done = true
+	return n, nil
+}
+
+// CategoryUpdateOne is the builder for updating a single Category entity.
+type CategoryUpdateOne struct {
+	config
+	fields   []string
+	hooks    []Hook
+	mutation *CategoryMutation
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (cuo *CategoryUpdateOne) SetUpdatedAt(t time.Time) *CategoryUpdateOne {
+	cuo.mutation.SetUpdatedAt(t)
+	return cuo
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (cuo *CategoryUpdateOne) SetDeletedAt(t time.Time) *CategoryUpdateOne {
+	cuo.mutation.SetDeletedAt(t)
+	return cuo
+}
+
+// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
+func (cuo *CategoryUpdateOne) SetNillableDeletedAt(t *time.Time) *CategoryUpdateOne {
+	if t != nil {
+		cuo.SetDeletedAt(*t)
+	}
+	return cuo
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (cuo *CategoryUpdateOne) ClearDeletedAt() *CategoryUpdateOne {
+	cuo.mutation.ClearDeletedAt()
+	return cuo
+}
+
+// SetName sets the "name" field.
+func (cuo *CategoryUpdateOne) SetName(s string) *CategoryUpdateOne {
+	cuo.mutation.SetName(s)
+	return cuo
+}
+
+// SetNillableName sets the "name" field if the given value is not nil.
+func (cuo *CategoryUpdateOne) SetNillableName(s *string) *CategoryUpdateOne {
+	if s != nil {
+		cuo.SetName(*s)
+	}
+	return cuo
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (cuo *CategoryUpdateOne) SetOrganizationID(u uint64) *CategoryUpdateOne {
+	cuo.mutation.ResetOrganizationID()
+	cuo.mutation.SetOrganizationID(u)
+	return cuo
+}
+
+// SetNillableOrganizationID sets the "organization_id" field if the given value is not nil.
+func (cuo *CategoryUpdateOne) SetNillableOrganizationID(u *uint64) *CategoryUpdateOne {
+	if u != nil {
+		cuo.SetOrganizationID(*u)
+	}
+	return cuo
+}
+
+// AddOrganizationID adds u to the "organization_id" field.
+func (cuo *CategoryUpdateOne) AddOrganizationID(u int64) *CategoryUpdateOne {
+	cuo.mutation.AddOrganizationID(u)
+	return cuo
+}
+
+// Mutation returns the CategoryMutation object of the builder.
+func (cuo *CategoryUpdateOne) Mutation() *CategoryMutation {
+	return cuo.mutation
+}
+
+// Where appends a list predicates to the CategoryUpdate builder.
+func (cuo *CategoryUpdateOne) Where(ps ...predicate.Category) *CategoryUpdateOne {
+	cuo.mutation.Where(ps...)
+	return cuo
+}
+
+// Select allows selecting one or more fields (columns) of the returned entity.
+// The default is selecting all fields defined in the entity schema.
+func (cuo *CategoryUpdateOne) Select(field string, fields ...string) *CategoryUpdateOne {
+	cuo.fields = append([]string{field}, fields...)
+	return cuo
+}
+
+// Save executes the query and returns the updated Category entity.
+func (cuo *CategoryUpdateOne) Save(ctx context.Context) (*Category, error) {
+	if err := cuo.defaults(); err != nil {
+		return nil, err
+	}
+	return withHooks(ctx, cuo.sqlSave, cuo.mutation, cuo.hooks)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (cuo *CategoryUpdateOne) SaveX(ctx context.Context) *Category {
+	node, err := cuo.Save(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return node
+}
+
+// Exec executes the query on the entity.
+func (cuo *CategoryUpdateOne) Exec(ctx context.Context) error {
+	_, err := cuo.Save(ctx)
+	return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (cuo *CategoryUpdateOne) ExecX(ctx context.Context) {
+	if err := cuo.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+// defaults sets the default values of the builder before save.
+func (cuo *CategoryUpdateOne) defaults() error {
+	if _, ok := cuo.mutation.UpdatedAt(); !ok {
+		if category.UpdateDefaultUpdatedAt == nil {
+			return fmt.Errorf("ent: uninitialized category.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
+		}
+		v := category.UpdateDefaultUpdatedAt()
+		cuo.mutation.SetUpdatedAt(v)
+	}
+	return nil
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (cuo *CategoryUpdateOne) check() error {
+	if v, ok := cuo.mutation.Name(); ok {
+		if err := category.NameValidator(v); err != nil {
+			return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Category.name": %w`, err)}
+		}
+	}
+	if v, ok := cuo.mutation.OrganizationID(); ok {
+		if err := category.OrganizationIDValidator(v); err != nil {
+			return &ValidationError{Name: "organization_id", err: fmt.Errorf(`ent: validator failed for field "Category.organization_id": %w`, err)}
+		}
+	}
+	return nil
+}
+
+func (cuo *CategoryUpdateOne) sqlSave(ctx context.Context) (_node *Category, err error) {
+	if err := cuo.check(); err != nil {
+		return _node, err
+	}
+	_spec := sqlgraph.NewUpdateSpec(category.Table, category.Columns, sqlgraph.NewFieldSpec(category.FieldID, field.TypeUint64))
+	id, ok := cuo.mutation.ID()
+	if !ok {
+		return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Category.id" for update`)}
+	}
+	_spec.Node.ID.Value = id
+	if fields := cuo.fields; len(fields) > 0 {
+		_spec.Node.Columns = make([]string, 0, len(fields))
+		_spec.Node.Columns = append(_spec.Node.Columns, category.FieldID)
+		for _, f := range fields {
+			if !category.ValidColumn(f) {
+				return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+			}
+			if f != category.FieldID {
+				_spec.Node.Columns = append(_spec.Node.Columns, f)
+			}
+		}
+	}
+	if ps := cuo.mutation.predicates; len(ps) > 0 {
+		_spec.Predicate = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	if value, ok := cuo.mutation.UpdatedAt(); ok {
+		_spec.SetField(category.FieldUpdatedAt, field.TypeTime, value)
+	}
+	if value, ok := cuo.mutation.DeletedAt(); ok {
+		_spec.SetField(category.FieldDeletedAt, field.TypeTime, value)
+	}
+	if cuo.mutation.DeletedAtCleared() {
+		_spec.ClearField(category.FieldDeletedAt, field.TypeTime)
+	}
+	if value, ok := cuo.mutation.Name(); ok {
+		_spec.SetField(category.FieldName, field.TypeString, value)
+	}
+	if value, ok := cuo.mutation.OrganizationID(); ok {
+		_spec.SetField(category.FieldOrganizationID, field.TypeUint64, value)
+	}
+	if value, ok := cuo.mutation.AddedOrganizationID(); ok {
+		_spec.AddField(category.FieldOrganizationID, field.TypeUint64, value)
+	}
+	_node = &Category{config: cuo.config}
+	_spec.Assign = _node.assignValues
+	_spec.ScanValues = _node.scanValues
+	if err = sqlgraph.UpdateNode(ctx, cuo.driver, _spec); err != nil {
+		if _, ok := err.(*sqlgraph.NotFoundError); ok {
+			err = &NotFoundError{category.Label}
+		} else if sqlgraph.IsConstraintError(err) {
+			err = &ConstraintError{msg: err.Error(), wrap: err}
+		}
+		return nil, err
+	}
+	cuo.mutation.done = true
+	return _node, nil
+}

+ 155 - 12
ent/client.go

@@ -13,6 +13,7 @@ import (
 
 	"wechat-api/ent/agent"
 	"wechat-api/ent/batchmsg"
+	"wechat-api/ent/category"
 	"wechat-api/ent/contact"
 	"wechat-api/ent/employee"
 	"wechat-api/ent/employeeconfig"
@@ -47,6 +48,8 @@ type Client struct {
 	Agent *AgentClient
 	// BatchMsg is the client for interacting with the BatchMsg builders.
 	BatchMsg *BatchMsgClient
+	// Category is the client for interacting with the Category builders.
+	Category *CategoryClient
 	// Contact is the client for interacting with the Contact builders.
 	Contact *ContactClient
 	// Employee is the client for interacting with the Employee builders.
@@ -92,6 +95,7 @@ func (c *Client) init() {
 	c.Schema = migrate.NewSchema(c.driver)
 	c.Agent = NewAgentClient(c.config)
 	c.BatchMsg = NewBatchMsgClient(c.config)
+	c.Category = NewCategoryClient(c.config)
 	c.Contact = NewContactClient(c.config)
 	c.Employee = NewEmployeeClient(c.config)
 	c.EmployeeConfig = NewEmployeeConfigClient(c.config)
@@ -202,6 +206,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
 		config:            cfg,
 		Agent:             NewAgentClient(cfg),
 		BatchMsg:          NewBatchMsgClient(cfg),
+		Category:          NewCategoryClient(cfg),
 		Contact:           NewContactClient(cfg),
 		Employee:          NewEmployeeClient(cfg),
 		EmployeeConfig:    NewEmployeeConfigClient(cfg),
@@ -239,6 +244,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
 		config:            cfg,
 		Agent:             NewAgentClient(cfg),
 		BatchMsg:          NewBatchMsgClient(cfg),
+		Category:          NewCategoryClient(cfg),
 		Contact:           NewContactClient(cfg),
 		Employee:          NewEmployeeClient(cfg),
 		EmployeeConfig:    NewEmployeeConfigClient(cfg),
@@ -284,9 +290,9 @@ func (c *Client) Close() error {
 // In order to add hooks to a specific client, call: `client.Node.Use(...)`.
 func (c *Client) Use(hooks ...Hook) {
 	for _, n := range []interface{ Use(...Hook) }{
-		c.Agent, c.BatchMsg, c.Contact, c.Employee, c.EmployeeConfig, c.Label,
-		c.LabelRelationship, c.Message, c.MessageRecords, c.Msg, c.Server, c.SopNode,
-		c.SopStage, c.SopTask, c.Token, c.Tutorial, c.WorkExperience, c.Wx,
+		c.Agent, c.BatchMsg, c.Category, c.Contact, c.Employee, c.EmployeeConfig,
+		c.Label, c.LabelRelationship, c.Message, c.MessageRecords, c.Msg, c.Server,
+		c.SopNode, c.SopStage, c.SopTask, c.Token, c.Tutorial, c.WorkExperience, c.Wx,
 	} {
 		n.Use(hooks...)
 	}
@@ -296,9 +302,9 @@ func (c *Client) Use(hooks ...Hook) {
 // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
 func (c *Client) Intercept(interceptors ...Interceptor) {
 	for _, n := range []interface{ Intercept(...Interceptor) }{
-		c.Agent, c.BatchMsg, c.Contact, c.Employee, c.EmployeeConfig, c.Label,
-		c.LabelRelationship, c.Message, c.MessageRecords, c.Msg, c.Server, c.SopNode,
-		c.SopStage, c.SopTask, c.Token, c.Tutorial, c.WorkExperience, c.Wx,
+		c.Agent, c.BatchMsg, c.Category, c.Contact, c.Employee, c.EmployeeConfig,
+		c.Label, c.LabelRelationship, c.Message, c.MessageRecords, c.Msg, c.Server,
+		c.SopNode, c.SopStage, c.SopTask, c.Token, c.Tutorial, c.WorkExperience, c.Wx,
 	} {
 		n.Intercept(interceptors...)
 	}
@@ -311,6 +317,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
 		return c.Agent.mutate(ctx, m)
 	case *BatchMsgMutation:
 		return c.BatchMsg.mutate(ctx, m)
+	case *CategoryMutation:
+		return c.Category.mutate(ctx, m)
 	case *ContactMutation:
 		return c.Contact.mutate(ctx, m)
 	case *EmployeeMutation:
@@ -634,6 +642,141 @@ func (c *BatchMsgClient) mutate(ctx context.Context, m *BatchMsgMutation) (Value
 	}
 }
 
+// CategoryClient is a client for the Category schema.
+type CategoryClient struct {
+	config
+}
+
+// NewCategoryClient returns a client for the Category from the given config.
+func NewCategoryClient(c config) *CategoryClient {
+	return &CategoryClient{config: c}
+}
+
+// Use adds a list of mutation hooks to the hooks stack.
+// A call to `Use(f, g, h)` equals to `category.Hooks(f(g(h())))`.
+func (c *CategoryClient) Use(hooks ...Hook) {
+	c.hooks.Category = append(c.hooks.Category, hooks...)
+}
+
+// Intercept adds a list of query interceptors to the interceptors stack.
+// A call to `Intercept(f, g, h)` equals to `category.Intercept(f(g(h())))`.
+func (c *CategoryClient) Intercept(interceptors ...Interceptor) {
+	c.inters.Category = append(c.inters.Category, interceptors...)
+}
+
+// Create returns a builder for creating a Category entity.
+func (c *CategoryClient) Create() *CategoryCreate {
+	mutation := newCategoryMutation(c.config, OpCreate)
+	return &CategoryCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// CreateBulk returns a builder for creating a bulk of Category entities.
+func (c *CategoryClient) CreateBulk(builders ...*CategoryCreate) *CategoryCreateBulk {
+	return &CategoryCreateBulk{config: c.config, builders: builders}
+}
+
+// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
+// a builder and applies setFunc on it.
+func (c *CategoryClient) MapCreateBulk(slice any, setFunc func(*CategoryCreate, int)) *CategoryCreateBulk {
+	rv := reflect.ValueOf(slice)
+	if rv.Kind() != reflect.Slice {
+		return &CategoryCreateBulk{err: fmt.Errorf("calling to CategoryClient.MapCreateBulk with wrong type %T, need slice", slice)}
+	}
+	builders := make([]*CategoryCreate, rv.Len())
+	for i := 0; i < rv.Len(); i++ {
+		builders[i] = c.Create()
+		setFunc(builders[i], i)
+	}
+	return &CategoryCreateBulk{config: c.config, builders: builders}
+}
+
+// Update returns an update builder for Category.
+func (c *CategoryClient) Update() *CategoryUpdate {
+	mutation := newCategoryMutation(c.config, OpUpdate)
+	return &CategoryUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// UpdateOne returns an update builder for the given entity.
+func (c *CategoryClient) UpdateOne(ca *Category) *CategoryUpdateOne {
+	mutation := newCategoryMutation(c.config, OpUpdateOne, withCategory(ca))
+	return &CategoryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// UpdateOneID returns an update builder for the given id.
+func (c *CategoryClient) UpdateOneID(id uint64) *CategoryUpdateOne {
+	mutation := newCategoryMutation(c.config, OpUpdateOne, withCategoryID(id))
+	return &CategoryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// Delete returns a delete builder for Category.
+func (c *CategoryClient) Delete() *CategoryDelete {
+	mutation := newCategoryMutation(c.config, OpDelete)
+	return &CategoryDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// DeleteOne returns a builder for deleting the given entity.
+func (c *CategoryClient) DeleteOne(ca *Category) *CategoryDeleteOne {
+	return c.DeleteOneID(ca.ID)
+}
+
+// DeleteOneID returns a builder for deleting the given entity by its id.
+func (c *CategoryClient) DeleteOneID(id uint64) *CategoryDeleteOne {
+	builder := c.Delete().Where(category.ID(id))
+	builder.mutation.id = &id
+	builder.mutation.op = OpDeleteOne
+	return &CategoryDeleteOne{builder}
+}
+
+// Query returns a query builder for Category.
+func (c *CategoryClient) Query() *CategoryQuery {
+	return &CategoryQuery{
+		config: c.config,
+		ctx:    &QueryContext{Type: TypeCategory},
+		inters: c.Interceptors(),
+	}
+}
+
+// Get returns a Category entity by its id.
+func (c *CategoryClient) Get(ctx context.Context, id uint64) (*Category, error) {
+	return c.Query().Where(category.ID(id)).Only(ctx)
+}
+
+// GetX is like Get, but panics if an error occurs.
+func (c *CategoryClient) GetX(ctx context.Context, id uint64) *Category {
+	obj, err := c.Get(ctx, id)
+	if err != nil {
+		panic(err)
+	}
+	return obj
+}
+
+// Hooks returns the client hooks.
+func (c *CategoryClient) Hooks() []Hook {
+	hooks := c.hooks.Category
+	return append(hooks[:len(hooks):len(hooks)], category.Hooks[:]...)
+}
+
+// Interceptors returns the client interceptors.
+func (c *CategoryClient) Interceptors() []Interceptor {
+	inters := c.inters.Category
+	return append(inters[:len(inters):len(inters)], category.Interceptors[:]...)
+}
+
+func (c *CategoryClient) mutate(ctx context.Context, m *CategoryMutation) (Value, error) {
+	switch m.Op() {
+	case OpCreate:
+		return (&CategoryCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+	case OpUpdate:
+		return (&CategoryUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+	case OpUpdateOne:
+		return (&CategoryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+	case OpDelete, OpDeleteOne:
+		return (&CategoryDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
+	default:
+		return nil, fmt.Errorf("ent: unknown Category mutation op: %q", m.Op())
+	}
+}
+
 // ContactClient is a client for the Contact schema.
 type ContactClient struct {
 	config
@@ -3129,14 +3272,14 @@ func (c *WxClient) mutate(ctx context.Context, m *WxMutation) (Value, error) {
 // hooks and interceptors per client, for fast access.
 type (
 	hooks struct {
-		Agent, BatchMsg, Contact, Employee, EmployeeConfig, Label, LabelRelationship,
-		Message, MessageRecords, Msg, Server, SopNode, SopStage, SopTask, Token,
-		Tutorial, WorkExperience, Wx []ent.Hook
+		Agent, BatchMsg, Category, Contact, Employee, EmployeeConfig, Label,
+		LabelRelationship, Message, MessageRecords, Msg, Server, SopNode, SopStage,
+		SopTask, Token, Tutorial, WorkExperience, Wx []ent.Hook
 	}
 	inters struct {
-		Agent, BatchMsg, Contact, Employee, EmployeeConfig, Label, LabelRelationship,
-		Message, MessageRecords, Msg, Server, SopNode, SopStage, SopTask, Token,
-		Tutorial, WorkExperience, Wx []ent.Interceptor
+		Agent, BatchMsg, Category, Contact, Employee, EmployeeConfig, Label,
+		LabelRelationship, Message, MessageRecords, Msg, Server, SopNode, SopStage,
+		SopTask, Token, Tutorial, WorkExperience, Wx []ent.Interceptor
 	}
 )
 

+ 12 - 1
ent/employee.go

@@ -51,6 +51,8 @@ type Employee struct {
 	VideoURL string `json:"video_url,omitempty"`
 	// organization_id | 租户ID
 	OrganizationID uint64 `json:"organization_id,omitempty"`
+	// category_id | 分类ID
+	CategoryID uint64 `json:"category_id,omitempty"`
 	// Edges holds the relations/edges for other nodes in the graph.
 	// The values are being populated by the EmployeeQuery when eager-loading is set.
 	Edges        EmployeeEdges `json:"edges"`
@@ -91,7 +93,7 @@ func (*Employee) scanValues(columns []string) ([]any, error) {
 	values := make([]any, len(columns))
 	for i := range columns {
 		switch columns[i] {
-		case employee.FieldID, employee.FieldHireCount, employee.FieldServiceCount, employee.FieldAchievementCount, employee.FieldOrganizationID:
+		case employee.FieldID, employee.FieldHireCount, employee.FieldServiceCount, employee.FieldAchievementCount, employee.FieldOrganizationID, employee.FieldCategoryID:
 			values[i] = new(sql.NullInt64)
 		case employee.FieldTitle, employee.FieldAvatar, employee.FieldTags, employee.FieldIntro, employee.FieldEstimate, employee.FieldSkill, employee.FieldAbilityType, employee.FieldScene, employee.FieldSwitchIn, employee.FieldVideoURL:
 			values[i] = new(sql.NullString)
@@ -220,6 +222,12 @@ func (e *Employee) assignValues(columns []string, values []any) error {
 			} else if value.Valid {
 				e.OrganizationID = uint64(value.Int64)
 			}
+		case employee.FieldCategoryID:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field category_id", values[i])
+			} else if value.Valid {
+				e.CategoryID = uint64(value.Int64)
+			}
 		default:
 			e.selectValues.Set(columns[i], values[i])
 		}
@@ -316,6 +324,9 @@ func (e *Employee) String() string {
 	builder.WriteString(", ")
 	builder.WriteString("organization_id=")
 	builder.WriteString(fmt.Sprintf("%v", e.OrganizationID))
+	builder.WriteString(", ")
+	builder.WriteString("category_id=")
+	builder.WriteString(fmt.Sprintf("%v", e.CategoryID))
 	builder.WriteByte(')')
 	return builder.String()
 }

+ 10 - 0
ent/employee/employee.go

@@ -49,6 +49,8 @@ const (
 	FieldVideoURL = "video_url"
 	// FieldOrganizationID holds the string denoting the organization_id field in the database.
 	FieldOrganizationID = "organization_id"
+	// FieldCategoryID holds the string denoting the category_id field in the database.
+	FieldCategoryID = "category_id"
 	// EdgeEmWorkExperiences holds the string denoting the em_work_experiences edge name in mutations.
 	EdgeEmWorkExperiences = "em_work_experiences"
 	// EdgeEmTutorial holds the string denoting the em_tutorial edge name in mutations.
@@ -91,6 +93,7 @@ var Columns = []string{
 	FieldSwitchIn,
 	FieldVideoURL,
 	FieldOrganizationID,
+	FieldCategoryID,
 }
 
 // ValidColumn reports if the column name is valid (part of the table columns).
@@ -159,6 +162,8 @@ var (
 	VideoURLValidator func(string) error
 	// OrganizationIDValidator is a validator for the "organization_id" field. It is called by the builders before save.
 	OrganizationIDValidator func(uint64) error
+	// CategoryIDValidator is a validator for the "category_id" field. It is called by the builders before save.
+	CategoryIDValidator func(uint64) error
 )
 
 // OrderOption defines the ordering options for the Employee queries.
@@ -254,6 +259,11 @@ func ByOrganizationID(opts ...sql.OrderTermOption) OrderOption {
 	return sql.OrderByField(FieldOrganizationID, opts...).ToFunc()
 }
 
+// ByCategoryID orders the results by the category_id field.
+func ByCategoryID(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldCategoryID, opts...).ToFunc()
+}
+
 // ByEmWorkExperiencesCount orders the results by em_work_experiences count.
 func ByEmWorkExperiencesCount(opts ...sql.OrderTermOption) OrderOption {
 	return func(s *sql.Selector) {

+ 45 - 0
ent/employee/where.go

@@ -140,6 +140,11 @@ func OrganizationID(v uint64) predicate.Employee {
 	return predicate.Employee(sql.FieldEQ(FieldOrganizationID, v))
 }
 
+// CategoryID applies equality check predicate on the "category_id" field. It's identical to CategoryIDEQ.
+func CategoryID(v uint64) predicate.Employee {
+	return predicate.Employee(sql.FieldEQ(FieldCategoryID, v))
+}
+
 // CreatedAtEQ applies the EQ predicate on the "created_at" field.
 func CreatedAtEQ(v time.Time) predicate.Employee {
 	return predicate.Employee(sql.FieldEQ(FieldCreatedAt, v))
@@ -1080,6 +1085,46 @@ func OrganizationIDLTE(v uint64) predicate.Employee {
 	return predicate.Employee(sql.FieldLTE(FieldOrganizationID, v))
 }
 
+// CategoryIDEQ applies the EQ predicate on the "category_id" field.
+func CategoryIDEQ(v uint64) predicate.Employee {
+	return predicate.Employee(sql.FieldEQ(FieldCategoryID, v))
+}
+
+// CategoryIDNEQ applies the NEQ predicate on the "category_id" field.
+func CategoryIDNEQ(v uint64) predicate.Employee {
+	return predicate.Employee(sql.FieldNEQ(FieldCategoryID, v))
+}
+
+// CategoryIDIn applies the In predicate on the "category_id" field.
+func CategoryIDIn(vs ...uint64) predicate.Employee {
+	return predicate.Employee(sql.FieldIn(FieldCategoryID, vs...))
+}
+
+// CategoryIDNotIn applies the NotIn predicate on the "category_id" field.
+func CategoryIDNotIn(vs ...uint64) predicate.Employee {
+	return predicate.Employee(sql.FieldNotIn(FieldCategoryID, vs...))
+}
+
+// CategoryIDGT applies the GT predicate on the "category_id" field.
+func CategoryIDGT(v uint64) predicate.Employee {
+	return predicate.Employee(sql.FieldGT(FieldCategoryID, v))
+}
+
+// CategoryIDGTE applies the GTE predicate on the "category_id" field.
+func CategoryIDGTE(v uint64) predicate.Employee {
+	return predicate.Employee(sql.FieldGTE(FieldCategoryID, v))
+}
+
+// CategoryIDLT applies the LT predicate on the "category_id" field.
+func CategoryIDLT(v uint64) predicate.Employee {
+	return predicate.Employee(sql.FieldLT(FieldCategoryID, v))
+}
+
+// CategoryIDLTE applies the LTE predicate on the "category_id" field.
+func CategoryIDLTE(v uint64) predicate.Employee {
+	return predicate.Employee(sql.FieldLTE(FieldCategoryID, v))
+}
+
 // HasEmWorkExperiences applies the HasEdge predicate on the "em_work_experiences" edge.
 func HasEmWorkExperiences() predicate.Employee {
 	return predicate.Employee(func(s *sql.Selector) {

+ 78 - 0
ent/employee_create.go

@@ -230,6 +230,12 @@ func (ec *EmployeeCreate) SetOrganizationID(u uint64) *EmployeeCreate {
 	return ec
 }
 
+// SetCategoryID sets the "category_id" field.
+func (ec *EmployeeCreate) SetCategoryID(u uint64) *EmployeeCreate {
+	ec.mutation.SetCategoryID(u)
+	return ec
+}
+
 // SetID sets the "id" field.
 func (ec *EmployeeCreate) SetID(u uint64) *EmployeeCreate {
 	ec.mutation.SetID(u)
@@ -465,6 +471,14 @@ func (ec *EmployeeCreate) check() error {
 			return &ValidationError{Name: "organization_id", err: fmt.Errorf(`ent: validator failed for field "Employee.organization_id": %w`, err)}
 		}
 	}
+	if _, ok := ec.mutation.CategoryID(); !ok {
+		return &ValidationError{Name: "category_id", err: errors.New(`ent: missing required field "Employee.category_id"`)}
+	}
+	if v, ok := ec.mutation.CategoryID(); ok {
+		if err := employee.CategoryIDValidator(v); err != nil {
+			return &ValidationError{Name: "category_id", err: fmt.Errorf(`ent: validator failed for field "Employee.category_id": %w`, err)}
+		}
+	}
 	return nil
 }
 
@@ -566,6 +580,10 @@ func (ec *EmployeeCreate) createSpec() (*Employee, *sqlgraph.CreateSpec) {
 		_spec.SetField(employee.FieldOrganizationID, field.TypeUint64, value)
 		_node.OrganizationID = value
 	}
+	if value, ok := ec.mutation.CategoryID(); ok {
+		_spec.SetField(employee.FieldCategoryID, field.TypeUint64, value)
+		_node.CategoryID = value
+	}
 	if nodes := ec.mutation.EmWorkExperiencesIDs(); len(nodes) > 0 {
 		edge := &sqlgraph.EdgeSpec{
 			Rel:     sqlgraph.O2M,
@@ -872,6 +890,24 @@ func (u *EmployeeUpsert) AddOrganizationID(v uint64) *EmployeeUpsert {
 	return u
 }
 
+// SetCategoryID sets the "category_id" field.
+func (u *EmployeeUpsert) SetCategoryID(v uint64) *EmployeeUpsert {
+	u.Set(employee.FieldCategoryID, v)
+	return u
+}
+
+// UpdateCategoryID sets the "category_id" field to the value that was provided on create.
+func (u *EmployeeUpsert) UpdateCategoryID() *EmployeeUpsert {
+	u.SetExcluded(employee.FieldCategoryID)
+	return u
+}
+
+// AddCategoryID adds v to the "category_id" field.
+func (u *EmployeeUpsert) AddCategoryID(v uint64) *EmployeeUpsert {
+	u.Add(employee.FieldCategoryID, v)
+	return u
+}
+
 // UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field.
 // Using this option is equivalent to using:
 //
@@ -1182,6 +1218,27 @@ func (u *EmployeeUpsertOne) UpdateOrganizationID() *EmployeeUpsertOne {
 	})
 }
 
+// SetCategoryID sets the "category_id" field.
+func (u *EmployeeUpsertOne) SetCategoryID(v uint64) *EmployeeUpsertOne {
+	return u.Update(func(s *EmployeeUpsert) {
+		s.SetCategoryID(v)
+	})
+}
+
+// AddCategoryID adds v to the "category_id" field.
+func (u *EmployeeUpsertOne) AddCategoryID(v uint64) *EmployeeUpsertOne {
+	return u.Update(func(s *EmployeeUpsert) {
+		s.AddCategoryID(v)
+	})
+}
+
+// UpdateCategoryID sets the "category_id" field to the value that was provided on create.
+func (u *EmployeeUpsertOne) UpdateCategoryID() *EmployeeUpsertOne {
+	return u.Update(func(s *EmployeeUpsert) {
+		s.UpdateCategoryID()
+	})
+}
+
 // Exec executes the query.
 func (u *EmployeeUpsertOne) Exec(ctx context.Context) error {
 	if len(u.create.conflict) == 0 {
@@ -1658,6 +1715,27 @@ func (u *EmployeeUpsertBulk) UpdateOrganizationID() *EmployeeUpsertBulk {
 	})
 }
 
+// SetCategoryID sets the "category_id" field.
+func (u *EmployeeUpsertBulk) SetCategoryID(v uint64) *EmployeeUpsertBulk {
+	return u.Update(func(s *EmployeeUpsert) {
+		s.SetCategoryID(v)
+	})
+}
+
+// AddCategoryID adds v to the "category_id" field.
+func (u *EmployeeUpsertBulk) AddCategoryID(v uint64) *EmployeeUpsertBulk {
+	return u.Update(func(s *EmployeeUpsert) {
+		s.AddCategoryID(v)
+	})
+}
+
+// UpdateCategoryID sets the "category_id" field to the value that was provided on create.
+func (u *EmployeeUpsertBulk) UpdateCategoryID() *EmployeeUpsertBulk {
+	return u.Update(func(s *EmployeeUpsert) {
+		s.UpdateCategoryID()
+	})
+}
+
 // Exec executes the query.
 func (u *EmployeeUpsertBulk) Exec(ctx context.Context) error {
 	if u.create.err != nil {

+ 64 - 0
ent/employee_update.go

@@ -280,6 +280,27 @@ func (eu *EmployeeUpdate) AddOrganizationID(u int64) *EmployeeUpdate {
 	return eu
 }
 
+// SetCategoryID sets the "category_id" field.
+func (eu *EmployeeUpdate) SetCategoryID(u uint64) *EmployeeUpdate {
+	eu.mutation.ResetCategoryID()
+	eu.mutation.SetCategoryID(u)
+	return eu
+}
+
+// SetNillableCategoryID sets the "category_id" field if the given value is not nil.
+func (eu *EmployeeUpdate) SetNillableCategoryID(u *uint64) *EmployeeUpdate {
+	if u != nil {
+		eu.SetCategoryID(*u)
+	}
+	return eu
+}
+
+// AddCategoryID adds u to the "category_id" field.
+func (eu *EmployeeUpdate) AddCategoryID(u int64) *EmployeeUpdate {
+	eu.mutation.AddCategoryID(u)
+	return eu
+}
+
 // AddEmWorkExperienceIDs adds the "em_work_experiences" edge to the WorkExperience entity by IDs.
 func (eu *EmployeeUpdate) AddEmWorkExperienceIDs(ids ...uint64) *EmployeeUpdate {
 	eu.mutation.AddEmWorkExperienceIDs(ids...)
@@ -456,6 +477,11 @@ func (eu *EmployeeUpdate) check() error {
 			return &ValidationError{Name: "organization_id", err: fmt.Errorf(`ent: validator failed for field "Employee.organization_id": %w`, err)}
 		}
 	}
+	if v, ok := eu.mutation.CategoryID(); ok {
+		if err := employee.CategoryIDValidator(v); err != nil {
+			return &ValidationError{Name: "category_id", err: fmt.Errorf(`ent: validator failed for field "Employee.category_id": %w`, err)}
+		}
+	}
 	return nil
 }
 
@@ -534,6 +560,12 @@ func (eu *EmployeeUpdate) sqlSave(ctx context.Context) (n int, err error) {
 	if value, ok := eu.mutation.AddedOrganizationID(); ok {
 		_spec.AddField(employee.FieldOrganizationID, field.TypeUint64, value)
 	}
+	if value, ok := eu.mutation.CategoryID(); ok {
+		_spec.SetField(employee.FieldCategoryID, field.TypeUint64, value)
+	}
+	if value, ok := eu.mutation.AddedCategoryID(); ok {
+		_spec.AddField(employee.FieldCategoryID, field.TypeUint64, value)
+	}
 	if eu.mutation.EmWorkExperiencesCleared() {
 		edge := &sqlgraph.EdgeSpec{
 			Rel:     sqlgraph.O2M,
@@ -894,6 +926,27 @@ func (euo *EmployeeUpdateOne) AddOrganizationID(u int64) *EmployeeUpdateOne {
 	return euo
 }
 
+// SetCategoryID sets the "category_id" field.
+func (euo *EmployeeUpdateOne) SetCategoryID(u uint64) *EmployeeUpdateOne {
+	euo.mutation.ResetCategoryID()
+	euo.mutation.SetCategoryID(u)
+	return euo
+}
+
+// SetNillableCategoryID sets the "category_id" field if the given value is not nil.
+func (euo *EmployeeUpdateOne) SetNillableCategoryID(u *uint64) *EmployeeUpdateOne {
+	if u != nil {
+		euo.SetCategoryID(*u)
+	}
+	return euo
+}
+
+// AddCategoryID adds u to the "category_id" field.
+func (euo *EmployeeUpdateOne) AddCategoryID(u int64) *EmployeeUpdateOne {
+	euo.mutation.AddCategoryID(u)
+	return euo
+}
+
 // AddEmWorkExperienceIDs adds the "em_work_experiences" edge to the WorkExperience entity by IDs.
 func (euo *EmployeeUpdateOne) AddEmWorkExperienceIDs(ids ...uint64) *EmployeeUpdateOne {
 	euo.mutation.AddEmWorkExperienceIDs(ids...)
@@ -1083,6 +1136,11 @@ func (euo *EmployeeUpdateOne) check() error {
 			return &ValidationError{Name: "organization_id", err: fmt.Errorf(`ent: validator failed for field "Employee.organization_id": %w`, err)}
 		}
 	}
+	if v, ok := euo.mutation.CategoryID(); ok {
+		if err := employee.CategoryIDValidator(v); err != nil {
+			return &ValidationError{Name: "category_id", err: fmt.Errorf(`ent: validator failed for field "Employee.category_id": %w`, err)}
+		}
+	}
 	return nil
 }
 
@@ -1178,6 +1236,12 @@ func (euo *EmployeeUpdateOne) sqlSave(ctx context.Context) (_node *Employee, err
 	if value, ok := euo.mutation.AddedOrganizationID(); ok {
 		_spec.AddField(employee.FieldOrganizationID, field.TypeUint64, value)
 	}
+	if value, ok := euo.mutation.CategoryID(); ok {
+		_spec.SetField(employee.FieldCategoryID, field.TypeUint64, value)
+	}
+	if value, ok := euo.mutation.AddedCategoryID(); ok {
+		_spec.AddField(employee.FieldCategoryID, field.TypeUint64, value)
+	}
 	if euo.mutation.EmWorkExperiencesCleared() {
 		edge := &sqlgraph.EdgeSpec{
 			Rel:     sqlgraph.O2M,

+ 2 - 0
ent/ent.go

@@ -10,6 +10,7 @@ import (
 	"sync"
 	"wechat-api/ent/agent"
 	"wechat-api/ent/batchmsg"
+	"wechat-api/ent/category"
 	"wechat-api/ent/contact"
 	"wechat-api/ent/employee"
 	"wechat-api/ent/employeeconfig"
@@ -92,6 +93,7 @@ func checkColumn(table, column string) error {
 		columnCheck = sql.NewColumnCheck(map[string]func(string) bool{
 			agent.Table:             agent.ValidColumn,
 			batchmsg.Table:          batchmsg.ValidColumn,
+			category.Table:          category.ValidColumn,
 			contact.Table:           contact.ValidColumn,
 			employee.Table:          employee.ValidColumn,
 			employeeconfig.Table:    employeeconfig.ValidColumn,

+ 12 - 0
ent/hook/hook.go

@@ -32,6 +32,18 @@ func (f BatchMsgFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, er
 	return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.BatchMsgMutation", m)
 }
 
+// The CategoryFunc type is an adapter to allow the use of ordinary
+// function as Category mutator.
+type CategoryFunc func(context.Context, *ent.CategoryMutation) (ent.Value, error)
+
+// Mutate calls f(ctx, m).
+func (f CategoryFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
+	if mv, ok := m.(*ent.CategoryMutation); ok {
+		return f(ctx, mv)
+	}
+	return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.CategoryMutation", m)
+}
+
 // The ContactFunc type is an adapter to allow the use of ordinary
 // function as Contact mutator.
 type ContactFunc func(context.Context, *ent.ContactMutation) (ent.Value, error)

+ 30 - 0
ent/intercept/intercept.go

@@ -8,6 +8,7 @@ import (
 	"wechat-api/ent"
 	"wechat-api/ent/agent"
 	"wechat-api/ent/batchmsg"
+	"wechat-api/ent/category"
 	"wechat-api/ent/contact"
 	"wechat-api/ent/employee"
 	"wechat-api/ent/employeeconfig"
@@ -139,6 +140,33 @@ func (f TraverseBatchMsg) Traverse(ctx context.Context, q ent.Query) error {
 	return fmt.Errorf("unexpected query type %T. expect *ent.BatchMsgQuery", q)
 }
 
+// The CategoryFunc type is an adapter to allow the use of ordinary function as a Querier.
+type CategoryFunc func(context.Context, *ent.CategoryQuery) (ent.Value, error)
+
+// Query calls f(ctx, q).
+func (f CategoryFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
+	if q, ok := q.(*ent.CategoryQuery); ok {
+		return f(ctx, q)
+	}
+	return nil, fmt.Errorf("unexpected query type %T. expect *ent.CategoryQuery", q)
+}
+
+// The TraverseCategory type is an adapter to allow the use of ordinary function as Traverser.
+type TraverseCategory func(context.Context, *ent.CategoryQuery) error
+
+// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
+func (f TraverseCategory) Intercept(next ent.Querier) ent.Querier {
+	return next
+}
+
+// Traverse calls f(ctx, q).
+func (f TraverseCategory) Traverse(ctx context.Context, q ent.Query) error {
+	if q, ok := q.(*ent.CategoryQuery); ok {
+		return f(ctx, q)
+	}
+	return fmt.Errorf("unexpected query type %T. expect *ent.CategoryQuery", q)
+}
+
 // The ContactFunc type is an adapter to allow the use of ordinary function as a Querier.
 type ContactFunc func(context.Context, *ent.ContactQuery) (ent.Value, error)
 
@@ -578,6 +606,8 @@ func NewQuery(q ent.Query) (Query, error) {
 		return &query[*ent.AgentQuery, predicate.Agent, agent.OrderOption]{typ: ent.TypeAgent, tq: q}, nil
 	case *ent.BatchMsgQuery:
 		return &query[*ent.BatchMsgQuery, predicate.BatchMsg, batchmsg.OrderOption]{typ: ent.TypeBatchMsg, tq: q}, nil
+	case *ent.CategoryQuery:
+		return &query[*ent.CategoryQuery, predicate.Category, category.OrderOption]{typ: ent.TypeCategory, tq: q}, nil
 	case *ent.ContactQuery:
 		return &query[*ent.ContactQuery, predicate.Contact, contact.OrderOption]{typ: ent.TypeContact, tq: q}, nil
 	case *ent.EmployeeQuery:

+ 27 - 0
ent/migrate/schema.go

@@ -73,6 +73,28 @@ var (
 			},
 		},
 	}
+	// CategoryColumns holds the columns for the "category" table.
+	CategoryColumns = []*schema.Column{
+		{Name: "id", Type: field.TypeUint64, Increment: true},
+		{Name: "created_at", Type: field.TypeTime, Comment: "Create Time | 创建日期"},
+		{Name: "updated_at", Type: field.TypeTime, Comment: "Update Time | 修改日期"},
+		{Name: "deleted_at", Type: field.TypeTime, Nullable: true, Comment: "Delete Time | 删除日期"},
+		{Name: "name", Type: field.TypeString, Size: 255, Comment: "name | 角色名称"},
+		{Name: "organization_id", Type: field.TypeUint64, Comment: "organization_id | 租户ID"},
+	}
+	// CategoryTable holds the schema information for the "category" table.
+	CategoryTable = &schema.Table{
+		Name:       "category",
+		Columns:    CategoryColumns,
+		PrimaryKey: []*schema.Column{CategoryColumns[0]},
+		Indexes: []*schema.Index{
+			{
+				Name:    "category_organization_id",
+				Unique:  false,
+				Columns: []*schema.Column{CategoryColumns[5]},
+			},
+		},
+	}
 	// ContactColumns holds the columns for the "contact" table.
 	ContactColumns = []*schema.Column{
 		{Name: "id", Type: field.TypeUint64, Increment: true},
@@ -145,6 +167,7 @@ var (
 		{Name: "switch_in", Type: field.TypeString, Comment: "switch_in | 支持介入", Default: ""},
 		{Name: "video_url", Type: field.TypeString, Size: 1000, Comment: "video_url | 视频地址", Default: ""},
 		{Name: "organization_id", Type: field.TypeUint64, Comment: "organization_id | 租户ID"},
+		{Name: "category_id", Type: field.TypeUint64, Comment: "category_id | 分类ID"},
 	}
 	// EmployeeTable holds the schema information for the "employee" table.
 	EmployeeTable = &schema.Table{
@@ -654,6 +677,7 @@ var (
 	Tables = []*schema.Table{
 		AgentTable,
 		BatchMsgTable,
+		CategoryTable,
 		ContactTable,
 		EmployeeTable,
 		EmployeeConfigTable,
@@ -680,6 +704,9 @@ func init() {
 	BatchMsgTable.Annotation = &entsql.Annotation{
 		Table: "batch_msg",
 	}
+	CategoryTable.Annotation = &entsql.Annotation{
+		Table: "category",
+	}
 	ContactTable.Annotation = &entsql.Annotation{
 		Table: "contact",
 	}

+ 696 - 1
ent/mutation.go

@@ -10,6 +10,7 @@ import (
 	"time"
 	"wechat-api/ent/agent"
 	"wechat-api/ent/batchmsg"
+	"wechat-api/ent/category"
 	"wechat-api/ent/contact"
 	"wechat-api/ent/custom_types"
 	"wechat-api/ent/employee"
@@ -44,6 +45,7 @@ const (
 	// Node types.
 	TypeAgent             = "Agent"
 	TypeBatchMsg          = "BatchMsg"
+	TypeCategory          = "Category"
 	TypeContact           = "Contact"
 	TypeEmployee          = "Employee"
 	TypeEmployeeConfig    = "EmployeeConfig"
@@ -2666,6 +2668,612 @@ func (m *BatchMsgMutation) ResetEdge(name string) error {
 	return fmt.Errorf("unknown BatchMsg edge %s", name)
 }
 
+// CategoryMutation represents an operation that mutates the Category nodes in the graph.
+type CategoryMutation struct {
+	config
+	op                 Op
+	typ                string
+	id                 *uint64
+	created_at         *time.Time
+	updated_at         *time.Time
+	deleted_at         *time.Time
+	name               *string
+	organization_id    *uint64
+	addorganization_id *int64
+	clearedFields      map[string]struct{}
+	done               bool
+	oldValue           func(context.Context) (*Category, error)
+	predicates         []predicate.Category
+}
+
+var _ ent.Mutation = (*CategoryMutation)(nil)
+
+// categoryOption allows management of the mutation configuration using functional options.
+type categoryOption func(*CategoryMutation)
+
+// newCategoryMutation creates new mutation for the Category entity.
+func newCategoryMutation(c config, op Op, opts ...categoryOption) *CategoryMutation {
+	m := &CategoryMutation{
+		config:        c,
+		op:            op,
+		typ:           TypeCategory,
+		clearedFields: make(map[string]struct{}),
+	}
+	for _, opt := range opts {
+		opt(m)
+	}
+	return m
+}
+
+// withCategoryID sets the ID field of the mutation.
+func withCategoryID(id uint64) categoryOption {
+	return func(m *CategoryMutation) {
+		var (
+			err   error
+			once  sync.Once
+			value *Category
+		)
+		m.oldValue = func(ctx context.Context) (*Category, error) {
+			once.Do(func() {
+				if m.done {
+					err = errors.New("querying old values post mutation is not allowed")
+				} else {
+					value, err = m.Client().Category.Get(ctx, id)
+				}
+			})
+			return value, err
+		}
+		m.id = &id
+	}
+}
+
+// withCategory sets the old Category of the mutation.
+func withCategory(node *Category) categoryOption {
+	return func(m *CategoryMutation) {
+		m.oldValue = func(context.Context) (*Category, error) {
+			return node, nil
+		}
+		m.id = &node.ID
+	}
+}
+
+// Client returns a new `ent.Client` from the mutation. If the mutation was
+// executed in a transaction (ent.Tx), a transactional client is returned.
+func (m CategoryMutation) Client() *Client {
+	client := &Client{config: m.config}
+	client.init()
+	return client
+}
+
+// Tx returns an `ent.Tx` for mutations that were executed in transactions;
+// it returns an error otherwise.
+func (m CategoryMutation) Tx() (*Tx, error) {
+	if _, ok := m.driver.(*txDriver); !ok {
+		return nil, errors.New("ent: mutation is not running in a transaction")
+	}
+	tx := &Tx{config: m.config}
+	tx.init()
+	return tx, nil
+}
+
+// SetID sets the value of the id field. Note that this
+// operation is only accepted on creation of Category entities.
+func (m *CategoryMutation) SetID(id uint64) {
+	m.id = &id
+}
+
+// ID returns the ID value in the mutation. Note that the ID is only available
+// if it was provided to the builder or after it was returned from the database.
+func (m *CategoryMutation) ID() (id uint64, exists bool) {
+	if m.id == nil {
+		return
+	}
+	return *m.id, true
+}
+
+// IDs queries the database and returns the entity ids that match the mutation's predicate.
+// That means, if the mutation is applied within a transaction with an isolation level such
+// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
+// or updated by the mutation.
+func (m *CategoryMutation) IDs(ctx context.Context) ([]uint64, error) {
+	switch {
+	case m.op.Is(OpUpdateOne | OpDeleteOne):
+		id, exists := m.ID()
+		if exists {
+			return []uint64{id}, nil
+		}
+		fallthrough
+	case m.op.Is(OpUpdate | OpDelete):
+		return m.Client().Category.Query().Where(m.predicates...).IDs(ctx)
+	default:
+		return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+	}
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (m *CategoryMutation) SetCreatedAt(t time.Time) {
+	m.created_at = &t
+}
+
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *CategoryMutation) CreatedAt() (r time.Time, exists bool) {
+	v := m.created_at
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldCreatedAt returns the old "created_at" field's value of the Category entity.
+// If the Category object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *CategoryMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldCreatedAt requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
+	}
+	return oldValue.CreatedAt, nil
+}
+
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *CategoryMutation) ResetCreatedAt() {
+	m.created_at = nil
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (m *CategoryMutation) SetUpdatedAt(t time.Time) {
+	m.updated_at = &t
+}
+
+// UpdatedAt returns the value of the "updated_at" field in the mutation.
+func (m *CategoryMutation) UpdatedAt() (r time.Time, exists bool) {
+	v := m.updated_at
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldUpdatedAt returns the old "updated_at" field's value of the Category entity.
+// If the Category object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *CategoryMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+	}
+	return oldValue.UpdatedAt, nil
+}
+
+// ResetUpdatedAt resets all changes to the "updated_at" field.
+func (m *CategoryMutation) ResetUpdatedAt() {
+	m.updated_at = nil
+}
+
+// SetDeletedAt sets the "deleted_at" field.
+func (m *CategoryMutation) SetDeletedAt(t time.Time) {
+	m.deleted_at = &t
+}
+
+// DeletedAt returns the value of the "deleted_at" field in the mutation.
+func (m *CategoryMutation) DeletedAt() (r time.Time, exists bool) {
+	v := m.deleted_at
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldDeletedAt returns the old "deleted_at" field's value of the Category entity.
+// If the Category object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *CategoryMutation) OldDeletedAt(ctx context.Context) (v time.Time, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldDeletedAt is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldDeletedAt requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldDeletedAt: %w", err)
+	}
+	return oldValue.DeletedAt, nil
+}
+
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (m *CategoryMutation) ClearDeletedAt() {
+	m.deleted_at = nil
+	m.clearedFields[category.FieldDeletedAt] = struct{}{}
+}
+
+// DeletedAtCleared returns if the "deleted_at" field was cleared in this mutation.
+func (m *CategoryMutation) DeletedAtCleared() bool {
+	_, ok := m.clearedFields[category.FieldDeletedAt]
+	return ok
+}
+
+// ResetDeletedAt resets all changes to the "deleted_at" field.
+func (m *CategoryMutation) ResetDeletedAt() {
+	m.deleted_at = nil
+	delete(m.clearedFields, category.FieldDeletedAt)
+}
+
+// SetName sets the "name" field.
+func (m *CategoryMutation) SetName(s string) {
+	m.name = &s
+}
+
+// Name returns the value of the "name" field in the mutation.
+func (m *CategoryMutation) Name() (r string, exists bool) {
+	v := m.name
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldName returns the old "name" field's value of the Category entity.
+// If the Category object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *CategoryMutation) OldName(ctx context.Context) (v string, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldName is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldName requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldName: %w", err)
+	}
+	return oldValue.Name, nil
+}
+
+// ResetName resets all changes to the "name" field.
+func (m *CategoryMutation) ResetName() {
+	m.name = nil
+}
+
+// SetOrganizationID sets the "organization_id" field.
+func (m *CategoryMutation) SetOrganizationID(u uint64) {
+	m.organization_id = &u
+	m.addorganization_id = nil
+}
+
+// OrganizationID returns the value of the "organization_id" field in the mutation.
+func (m *CategoryMutation) OrganizationID() (r uint64, exists bool) {
+	v := m.organization_id
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldOrganizationID returns the old "organization_id" field's value of the Category entity.
+// If the Category object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *CategoryMutation) OldOrganizationID(ctx context.Context) (v uint64, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldOrganizationID is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldOrganizationID requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldOrganizationID: %w", err)
+	}
+	return oldValue.OrganizationID, nil
+}
+
+// AddOrganizationID adds u to the "organization_id" field.
+func (m *CategoryMutation) AddOrganizationID(u int64) {
+	if m.addorganization_id != nil {
+		*m.addorganization_id += u
+	} else {
+		m.addorganization_id = &u
+	}
+}
+
+// AddedOrganizationID returns the value that was added to the "organization_id" field in this mutation.
+func (m *CategoryMutation) AddedOrganizationID() (r int64, exists bool) {
+	v := m.addorganization_id
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// ResetOrganizationID resets all changes to the "organization_id" field.
+func (m *CategoryMutation) ResetOrganizationID() {
+	m.organization_id = nil
+	m.addorganization_id = nil
+}
+
+// Where appends a list predicates to the CategoryMutation builder.
+func (m *CategoryMutation) Where(ps ...predicate.Category) {
+	m.predicates = append(m.predicates, ps...)
+}
+
+// WhereP appends storage-level predicates to the CategoryMutation builder. Using this method,
+// users can use type-assertion to append predicates that do not depend on any generated package.
+func (m *CategoryMutation) WhereP(ps ...func(*sql.Selector)) {
+	p := make([]predicate.Category, len(ps))
+	for i := range ps {
+		p[i] = ps[i]
+	}
+	m.Where(p...)
+}
+
+// Op returns the operation name.
+func (m *CategoryMutation) Op() Op {
+	return m.op
+}
+
+// SetOp allows setting the mutation operation.
+func (m *CategoryMutation) SetOp(op Op) {
+	m.op = op
+}
+
+// Type returns the node type of this mutation (Category).
+func (m *CategoryMutation) Type() string {
+	return m.typ
+}
+
+// Fields returns all fields that were changed during this mutation. Note that in
+// order to get all numeric fields that were incremented/decremented, call
+// AddedFields().
+func (m *CategoryMutation) Fields() []string {
+	fields := make([]string, 0, 5)
+	if m.created_at != nil {
+		fields = append(fields, category.FieldCreatedAt)
+	}
+	if m.updated_at != nil {
+		fields = append(fields, category.FieldUpdatedAt)
+	}
+	if m.deleted_at != nil {
+		fields = append(fields, category.FieldDeletedAt)
+	}
+	if m.name != nil {
+		fields = append(fields, category.FieldName)
+	}
+	if m.organization_id != nil {
+		fields = append(fields, category.FieldOrganizationID)
+	}
+	return fields
+}
+
+// Field returns the value of a field with the given name. The second boolean
+// return value indicates that this field was not set, or was not defined in the
+// schema.
+func (m *CategoryMutation) Field(name string) (ent.Value, bool) {
+	switch name {
+	case category.FieldCreatedAt:
+		return m.CreatedAt()
+	case category.FieldUpdatedAt:
+		return m.UpdatedAt()
+	case category.FieldDeletedAt:
+		return m.DeletedAt()
+	case category.FieldName:
+		return m.Name()
+	case category.FieldOrganizationID:
+		return m.OrganizationID()
+	}
+	return nil, false
+}
+
+// OldField returns the old value of the field from the database. An error is
+// returned if the mutation operation is not UpdateOne, or the query to the
+// database failed.
+func (m *CategoryMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+	switch name {
+	case category.FieldCreatedAt:
+		return m.OldCreatedAt(ctx)
+	case category.FieldUpdatedAt:
+		return m.OldUpdatedAt(ctx)
+	case category.FieldDeletedAt:
+		return m.OldDeletedAt(ctx)
+	case category.FieldName:
+		return m.OldName(ctx)
+	case category.FieldOrganizationID:
+		return m.OldOrganizationID(ctx)
+	}
+	return nil, fmt.Errorf("unknown Category field %s", name)
+}
+
+// SetField sets the value of a field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *CategoryMutation) SetField(name string, value ent.Value) error {
+	switch name {
+	case category.FieldCreatedAt:
+		v, ok := value.(time.Time)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetCreatedAt(v)
+		return nil
+	case category.FieldUpdatedAt:
+		v, ok := value.(time.Time)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetUpdatedAt(v)
+		return nil
+	case category.FieldDeletedAt:
+		v, ok := value.(time.Time)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetDeletedAt(v)
+		return nil
+	case category.FieldName:
+		v, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetName(v)
+		return nil
+	case category.FieldOrganizationID:
+		v, ok := value.(uint64)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetOrganizationID(v)
+		return nil
+	}
+	return fmt.Errorf("unknown Category field %s", name)
+}
+
+// AddedFields returns all numeric fields that were incremented/decremented during
+// this mutation.
+func (m *CategoryMutation) AddedFields() []string {
+	var fields []string
+	if m.addorganization_id != nil {
+		fields = append(fields, category.FieldOrganizationID)
+	}
+	return fields
+}
+
+// AddedField returns the numeric value that was incremented/decremented on a field
+// with the given name. The second boolean return value indicates that this field
+// was not set, or was not defined in the schema.
+func (m *CategoryMutation) AddedField(name string) (ent.Value, bool) {
+	switch name {
+	case category.FieldOrganizationID:
+		return m.AddedOrganizationID()
+	}
+	return nil, false
+}
+
+// AddField adds the value to the field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *CategoryMutation) AddField(name string, value ent.Value) error {
+	switch name {
+	case category.FieldOrganizationID:
+		v, ok := value.(int64)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.AddOrganizationID(v)
+		return nil
+	}
+	return fmt.Errorf("unknown Category numeric field %s", name)
+}
+
+// ClearedFields returns all nullable fields that were cleared during this
+// mutation.
+func (m *CategoryMutation) ClearedFields() []string {
+	var fields []string
+	if m.FieldCleared(category.FieldDeletedAt) {
+		fields = append(fields, category.FieldDeletedAt)
+	}
+	return fields
+}
+
+// FieldCleared returns a boolean indicating if a field with the given name was
+// cleared in this mutation.
+func (m *CategoryMutation) FieldCleared(name string) bool {
+	_, ok := m.clearedFields[name]
+	return ok
+}
+
+// ClearField clears the value of the field with the given name. It returns an
+// error if the field is not defined in the schema.
+func (m *CategoryMutation) ClearField(name string) error {
+	switch name {
+	case category.FieldDeletedAt:
+		m.ClearDeletedAt()
+		return nil
+	}
+	return fmt.Errorf("unknown Category nullable field %s", name)
+}
+
+// ResetField resets all changes in the mutation for the field with the given name.
+// It returns an error if the field is not defined in the schema.
+func (m *CategoryMutation) ResetField(name string) error {
+	switch name {
+	case category.FieldCreatedAt:
+		m.ResetCreatedAt()
+		return nil
+	case category.FieldUpdatedAt:
+		m.ResetUpdatedAt()
+		return nil
+	case category.FieldDeletedAt:
+		m.ResetDeletedAt()
+		return nil
+	case category.FieldName:
+		m.ResetName()
+		return nil
+	case category.FieldOrganizationID:
+		m.ResetOrganizationID()
+		return nil
+	}
+	return fmt.Errorf("unknown Category field %s", name)
+}
+
+// AddedEdges returns all edge names that were set/added in this mutation.
+func (m *CategoryMutation) AddedEdges() []string {
+	edges := make([]string, 0, 0)
+	return edges
+}
+
+// AddedIDs returns all IDs (to other nodes) that were added for the given edge
+// name in this mutation.
+func (m *CategoryMutation) AddedIDs(name string) []ent.Value {
+	return nil
+}
+
+// RemovedEdges returns all edge names that were removed in this mutation.
+func (m *CategoryMutation) RemovedEdges() []string {
+	edges := make([]string, 0, 0)
+	return edges
+}
+
+// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
+// the given name in this mutation.
+func (m *CategoryMutation) RemovedIDs(name string) []ent.Value {
+	return nil
+}
+
+// ClearedEdges returns all edge names that were cleared in this mutation.
+func (m *CategoryMutation) ClearedEdges() []string {
+	edges := make([]string, 0, 0)
+	return edges
+}
+
+// EdgeCleared returns a boolean which indicates if the edge with the given name
+// was cleared in this mutation.
+func (m *CategoryMutation) EdgeCleared(name string) bool {
+	return false
+}
+
+// ClearEdge clears the value of the edge with the given name. It returns an error
+// if that edge is not defined in the schema.
+func (m *CategoryMutation) ClearEdge(name string) error {
+	return fmt.Errorf("unknown Category unique edge %s", name)
+}
+
+// ResetEdge resets all changes to the edge with the given name in this mutation.
+// It returns an error if the edge is not defined in the schema.
+func (m *CategoryMutation) ResetEdge(name string) error {
+	return fmt.Errorf("unknown Category edge %s", name)
+}
+
 // ContactMutation represents an operation that mutates the Contact nodes in the graph.
 type ContactMutation struct {
 	config
@@ -4529,6 +5137,8 @@ type EmployeeMutation struct {
 	video_url                  *string
 	organization_id            *uint64
 	addorganization_id         *int64
+	category_id                *uint64
+	addcategory_id             *int64
 	clearedFields              map[string]struct{}
 	em_work_experiences        map[uint64]struct{}
 	removedem_work_experiences map[uint64]struct{}
@@ -5350,6 +5960,62 @@ func (m *EmployeeMutation) ResetOrganizationID() {
 	m.addorganization_id = nil
 }
 
+// SetCategoryID sets the "category_id" field.
+func (m *EmployeeMutation) SetCategoryID(u uint64) {
+	m.category_id = &u
+	m.addcategory_id = nil
+}
+
+// CategoryID returns the value of the "category_id" field in the mutation.
+func (m *EmployeeMutation) CategoryID() (r uint64, exists bool) {
+	v := m.category_id
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldCategoryID returns the old "category_id" field's value of the Employee entity.
+// If the Employee object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *EmployeeMutation) OldCategoryID(ctx context.Context) (v uint64, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldCategoryID is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldCategoryID requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldCategoryID: %w", err)
+	}
+	return oldValue.CategoryID, nil
+}
+
+// AddCategoryID adds u to the "category_id" field.
+func (m *EmployeeMutation) AddCategoryID(u int64) {
+	if m.addcategory_id != nil {
+		*m.addcategory_id += u
+	} else {
+		m.addcategory_id = &u
+	}
+}
+
+// AddedCategoryID returns the value that was added to the "category_id" field in this mutation.
+func (m *EmployeeMutation) AddedCategoryID() (r int64, exists bool) {
+	v := m.addcategory_id
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// ResetCategoryID resets all changes to the "category_id" field.
+func (m *EmployeeMutation) ResetCategoryID() {
+	m.category_id = nil
+	m.addcategory_id = nil
+}
+
 // AddEmWorkExperienceIDs adds the "em_work_experiences" edge to the WorkExperience entity by ids.
 func (m *EmployeeMutation) AddEmWorkExperienceIDs(ids ...uint64) {
 	if m.em_work_experiences == nil {
@@ -5492,7 +6158,7 @@ func (m *EmployeeMutation) Type() string {
 // order to get all numeric fields that were incremented/decremented, call
 // AddedFields().
 func (m *EmployeeMutation) Fields() []string {
-	fields := make([]string, 0, 17)
+	fields := make([]string, 0, 18)
 	if m.created_at != nil {
 		fields = append(fields, employee.FieldCreatedAt)
 	}
@@ -5544,6 +6210,9 @@ func (m *EmployeeMutation) Fields() []string {
 	if m.organization_id != nil {
 		fields = append(fields, employee.FieldOrganizationID)
 	}
+	if m.category_id != nil {
+		fields = append(fields, employee.FieldCategoryID)
+	}
 	return fields
 }
 
@@ -5586,6 +6255,8 @@ func (m *EmployeeMutation) Field(name string) (ent.Value, bool) {
 		return m.VideoURL()
 	case employee.FieldOrganizationID:
 		return m.OrganizationID()
+	case employee.FieldCategoryID:
+		return m.CategoryID()
 	}
 	return nil, false
 }
@@ -5629,6 +6300,8 @@ func (m *EmployeeMutation) OldField(ctx context.Context, name string) (ent.Value
 		return m.OldVideoURL(ctx)
 	case employee.FieldOrganizationID:
 		return m.OldOrganizationID(ctx)
+	case employee.FieldCategoryID:
+		return m.OldCategoryID(ctx)
 	}
 	return nil, fmt.Errorf("unknown Employee field %s", name)
 }
@@ -5757,6 +6430,13 @@ func (m *EmployeeMutation) SetField(name string, value ent.Value) error {
 		}
 		m.SetOrganizationID(v)
 		return nil
+	case employee.FieldCategoryID:
+		v, ok := value.(uint64)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetCategoryID(v)
+		return nil
 	}
 	return fmt.Errorf("unknown Employee field %s", name)
 }
@@ -5777,6 +6457,9 @@ func (m *EmployeeMutation) AddedFields() []string {
 	if m.addorganization_id != nil {
 		fields = append(fields, employee.FieldOrganizationID)
 	}
+	if m.addcategory_id != nil {
+		fields = append(fields, employee.FieldCategoryID)
+	}
 	return fields
 }
 
@@ -5793,6 +6476,8 @@ func (m *EmployeeMutation) AddedField(name string) (ent.Value, bool) {
 		return m.AddedAchievementCount()
 	case employee.FieldOrganizationID:
 		return m.AddedOrganizationID()
+	case employee.FieldCategoryID:
+		return m.AddedCategoryID()
 	}
 	return nil, false
 }
@@ -5830,6 +6515,13 @@ func (m *EmployeeMutation) AddField(name string, value ent.Value) error {
 		}
 		m.AddOrganizationID(v)
 		return nil
+	case employee.FieldCategoryID:
+		v, ok := value.(int64)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.AddCategoryID(v)
+		return nil
 	}
 	return fmt.Errorf("unknown Employee numeric field %s", name)
 }
@@ -5917,6 +6609,9 @@ func (m *EmployeeMutation) ResetField(name string) error {
 	case employee.FieldOrganizationID:
 		m.ResetOrganizationID()
 		return nil
+	case employee.FieldCategoryID:
+		m.ResetCategoryID()
+		return nil
 	}
 	return fmt.Errorf("unknown Employee field %s", name)
 }

+ 82 - 0
ent/pagination.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"wechat-api/ent/agent"
 	"wechat-api/ent/batchmsg"
+	"wechat-api/ent/category"
 	"wechat-api/ent/contact"
 	"wechat-api/ent/employee"
 	"wechat-api/ent/employeeconfig"
@@ -233,6 +234,87 @@ func (bm *BatchMsgQuery) Page(
 	return ret, nil
 }
 
+type CategoryPager struct {
+	Order  category.OrderOption
+	Filter func(*CategoryQuery) (*CategoryQuery, error)
+}
+
+// CategoryPaginateOption enables pagination customization.
+type CategoryPaginateOption func(*CategoryPager)
+
+// DefaultCategoryOrder is the default ordering of Category.
+var DefaultCategoryOrder = Desc(category.FieldID)
+
+func newCategoryPager(opts []CategoryPaginateOption) (*CategoryPager, error) {
+	pager := &CategoryPager{}
+	for _, opt := range opts {
+		opt(pager)
+	}
+	if pager.Order == nil {
+		pager.Order = DefaultCategoryOrder
+	}
+	return pager, nil
+}
+
+func (p *CategoryPager) ApplyFilter(query *CategoryQuery) (*CategoryQuery, error) {
+	if p.Filter != nil {
+		return p.Filter(query)
+	}
+	return query, nil
+}
+
+// CategoryPageList is Category PageList result.
+type CategoryPageList struct {
+	List        []*Category  `json:"list"`
+	PageDetails *PageDetails `json:"pageDetails"`
+}
+
+func (c *CategoryQuery) Page(
+	ctx context.Context, pageNum uint64, pageSize uint64, opts ...CategoryPaginateOption,
+) (*CategoryPageList, error) {
+
+	pager, err := newCategoryPager(opts)
+	if err != nil {
+		return nil, err
+	}
+
+	if c, err = pager.ApplyFilter(c); err != nil {
+		return nil, err
+	}
+
+	ret := &CategoryPageList{}
+
+	ret.PageDetails = &PageDetails{
+		Page: pageNum,
+		Size: pageSize,
+	}
+
+	query := c.Clone()
+	query.ctx.Fields = nil
+	count, err := query.Count(ctx)
+
+	if err != nil {
+		return nil, err
+	}
+
+	ret.PageDetails.Total = uint64(count)
+
+	if pager.Order != nil {
+		c = c.Order(pager.Order)
+	} else {
+		c = c.Order(DefaultCategoryOrder)
+	}
+
+	c = c.Offset(int((pageNum - 1) * pageSize)).Limit(int(pageSize))
+	list, err := c.All(ctx)
+	if err != nil {
+		return nil, err
+	}
+	ret.List = list
+
+	return ret, nil
+}
+
 type ContactPager struct {
 	Order  contact.OrderOption
 	Filter func(*ContactQuery) (*ContactQuery, error)

+ 3 - 0
ent/predicate/predicate.go

@@ -12,6 +12,9 @@ type Agent func(*sql.Selector)
 // BatchMsg is the predicate function for batchmsg builders.
 type BatchMsg func(*sql.Selector)
 
+// Category is the predicate function for category builders.
+type Category func(*sql.Selector)
+
 // Contact is the predicate function for contact builders.
 type Contact func(*sql.Selector)
 

+ 32 - 0
ent/runtime/runtime.go

@@ -6,6 +6,7 @@ import (
 	"time"
 	"wechat-api/ent/agent"
 	"wechat-api/ent/batchmsg"
+	"wechat-api/ent/category"
 	"wechat-api/ent/contact"
 	"wechat-api/ent/employee"
 	"wechat-api/ent/employeeconfig"
@@ -105,6 +106,33 @@ func init() {
 	batchmsgDescOrganizationID := batchmsgFields[12].Descriptor()
 	// batchmsg.OrganizationIDValidator is a validator for the "organization_id" field. It is called by the builders before save.
 	batchmsg.OrganizationIDValidator = batchmsgDescOrganizationID.Validators[0].(func(uint64) error)
+	categoryMixin := schema.Category{}.Mixin()
+	categoryMixinHooks1 := categoryMixin[1].Hooks()
+	category.Hooks[0] = categoryMixinHooks1[0]
+	categoryMixinInters1 := categoryMixin[1].Interceptors()
+	category.Interceptors[0] = categoryMixinInters1[0]
+	categoryMixinFields0 := categoryMixin[0].Fields()
+	_ = categoryMixinFields0
+	categoryFields := schema.Category{}.Fields()
+	_ = categoryFields
+	// categoryDescCreatedAt is the schema descriptor for created_at field.
+	categoryDescCreatedAt := categoryMixinFields0[1].Descriptor()
+	// category.DefaultCreatedAt holds the default value on creation for the created_at field.
+	category.DefaultCreatedAt = categoryDescCreatedAt.Default.(func() time.Time)
+	// categoryDescUpdatedAt is the schema descriptor for updated_at field.
+	categoryDescUpdatedAt := categoryMixinFields0[2].Descriptor()
+	// category.DefaultUpdatedAt holds the default value on creation for the updated_at field.
+	category.DefaultUpdatedAt = categoryDescUpdatedAt.Default.(func() time.Time)
+	// category.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
+	category.UpdateDefaultUpdatedAt = categoryDescUpdatedAt.UpdateDefault.(func() time.Time)
+	// categoryDescName is the schema descriptor for name field.
+	categoryDescName := categoryFields[0].Descriptor()
+	// category.NameValidator is a validator for the "name" field. It is called by the builders before save.
+	category.NameValidator = categoryDescName.Validators[0].(func(string) error)
+	// categoryDescOrganizationID is the schema descriptor for organization_id field.
+	categoryDescOrganizationID := categoryFields[1].Descriptor()
+	// category.OrganizationIDValidator is a validator for the "organization_id" field. It is called by the builders before save.
+	category.OrganizationIDValidator = categoryDescOrganizationID.Validators[0].(func(uint64) error)
 	contactMixin := schema.Contact{}.Mixin()
 	contactMixinHooks2 := contactMixin[2].Hooks()
 	contact.Hooks[0] = contactMixinHooks2[0]
@@ -283,6 +311,10 @@ func init() {
 	employeeDescOrganizationID := employeeFields[13].Descriptor()
 	// employee.OrganizationIDValidator is a validator for the "organization_id" field. It is called by the builders before save.
 	employee.OrganizationIDValidator = employeeDescOrganizationID.Validators[0].(func(uint64) error)
+	// employeeDescCategoryID is the schema descriptor for category_id field.
+	employeeDescCategoryID := employeeFields[14].Descriptor()
+	// employee.CategoryIDValidator is a validator for the "category_id" field. It is called by the builders before save.
+	employee.CategoryIDValidator = employeeDescCategoryID.Validators[0].(func(uint64) error)
 	employeeconfigMixin := schema.EmployeeConfig{}.Mixin()
 	employeeconfigMixinHooks1 := employeeconfigMixin[1].Hooks()
 	employeeconfig.Hooks[0] = employeeconfigMixinHooks1[0]

+ 47 - 0
ent/schema/category.go

@@ -0,0 +1,47 @@
+package schema
+
+import (
+	"entgo.io/ent/schema/index"
+	"wechat-api/ent/schema/localmixin"
+
+	"entgo.io/ent"
+	"entgo.io/ent/dialect/entsql"
+	"entgo.io/ent/schema"
+	"entgo.io/ent/schema/field"
+	"github.com/suyuan32/simple-admin-common/orm/ent/mixins"
+)
+
+type Category struct {
+	ent.Schema
+}
+
+func (Category) Fields() []ent.Field {
+	return []ent.Field{
+		field.String("name").MaxLen(255).Comment("name | 角色名称"),
+		field.Uint64("organization_id").Positive().Comment("organization_id | 租户ID"),
+	}
+}
+
+func (Category) Mixin() []ent.Mixin {
+	return []ent.Mixin{
+		mixins.IDMixin{},
+		localmixin.SoftDeleteMixin{},
+	}
+}
+
+func (Category) Edges() []ent.Edge {
+	return []ent.Edge{}
+}
+
+func (Category) Indexes() []ent.Index {
+	return []ent.Index{
+		index.Fields("organization_id"),
+	}
+}
+
+func (Category) Annotations() []schema.Annotation {
+	return []schema.Annotation{
+		entsql.WithComments(true),
+		entsql.Annotation{Table: "category"},
+	}
+}

+ 1 - 0
ent/schema/employee.go

@@ -32,6 +32,7 @@ func (Employee) Fields() []ent.Field {
 		field.String("switch_in").Default("").Comment("switch_in | 支持介入"),
 		field.String("video_url").MaxLen(1000).Default("").Comment("video_url | 视频地址"),
 		field.Uint64("organization_id").Positive().Comment("organization_id | 租户ID"),
+		field.Uint64("category_id").Positive().Comment("category_id | 分类ID"),
 	}
 }
 

+ 120 - 0
ent/set_not_nil.go

@@ -560,6 +560,102 @@ func (bm *BatchMsgCreate) SetNotNilOrganizationID(value *uint64) *BatchMsgCreate
 }
 
 // set field if value's pointer is not nil.
+func (c *CategoryUpdate) SetNotNilUpdatedAt(value *time.Time) *CategoryUpdate {
+	if value != nil {
+		return c.SetUpdatedAt(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
+func (c *CategoryUpdateOne) SetNotNilUpdatedAt(value *time.Time) *CategoryUpdateOne {
+	if value != nil {
+		return c.SetUpdatedAt(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
+func (c *CategoryCreate) SetNotNilUpdatedAt(value *time.Time) *CategoryCreate {
+	if value != nil {
+		return c.SetUpdatedAt(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
+func (c *CategoryUpdate) SetNotNilDeletedAt(value *time.Time) *CategoryUpdate {
+	if value != nil {
+		return c.SetDeletedAt(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
+func (c *CategoryUpdateOne) SetNotNilDeletedAt(value *time.Time) *CategoryUpdateOne {
+	if value != nil {
+		return c.SetDeletedAt(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
+func (c *CategoryCreate) SetNotNilDeletedAt(value *time.Time) *CategoryCreate {
+	if value != nil {
+		return c.SetDeletedAt(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
+func (c *CategoryUpdate) SetNotNilName(value *string) *CategoryUpdate {
+	if value != nil {
+		return c.SetName(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
+func (c *CategoryUpdateOne) SetNotNilName(value *string) *CategoryUpdateOne {
+	if value != nil {
+		return c.SetName(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
+func (c *CategoryCreate) SetNotNilName(value *string) *CategoryCreate {
+	if value != nil {
+		return c.SetName(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
+func (c *CategoryUpdate) SetNotNilOrganizationID(value *uint64) *CategoryUpdate {
+	if value != nil {
+		return c.SetOrganizationID(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
+func (c *CategoryUpdateOne) SetNotNilOrganizationID(value *uint64) *CategoryUpdateOne {
+	if value != nil {
+		return c.SetOrganizationID(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
+func (c *CategoryCreate) SetNotNilOrganizationID(value *uint64) *CategoryCreate {
+	if value != nil {
+		return c.SetOrganizationID(*value)
+	}
+	return c
+}
+
+// set field if value's pointer is not nil.
 func (c *ContactUpdate) SetNotNilUpdatedAt(value *time.Time) *ContactUpdate {
 	if value != nil {
 		return c.SetUpdatedAt(*value)
@@ -1400,6 +1496,30 @@ func (e *EmployeeCreate) SetNotNilOrganizationID(value *uint64) *EmployeeCreate
 }
 
 // set field if value's pointer is not nil.
+func (e *EmployeeUpdate) SetNotNilCategoryID(value *uint64) *EmployeeUpdate {
+	if value != nil {
+		return e.SetCategoryID(*value)
+	}
+	return e
+}
+
+// set field if value's pointer is not nil.
+func (e *EmployeeUpdateOne) SetNotNilCategoryID(value *uint64) *EmployeeUpdateOne {
+	if value != nil {
+		return e.SetCategoryID(*value)
+	}
+	return e
+}
+
+// set field if value's pointer is not nil.
+func (e *EmployeeCreate) SetNotNilCategoryID(value *uint64) *EmployeeCreate {
+	if value != nil {
+		return e.SetCategoryID(*value)
+	}
+	return e
+}
+
+// set field if value's pointer is not nil.
 func (ec *EmployeeConfigUpdate) SetNotNilUpdatedAt(value *time.Time) *EmployeeConfigUpdate {
 	if value != nil {
 		return ec.SetUpdatedAt(*value)

+ 3 - 0
ent/tx.go

@@ -18,6 +18,8 @@ type Tx struct {
 	Agent *AgentClient
 	// BatchMsg is the client for interacting with the BatchMsg builders.
 	BatchMsg *BatchMsgClient
+	// Category is the client for interacting with the Category builders.
+	Category *CategoryClient
 	// Contact is the client for interacting with the Contact builders.
 	Contact *ContactClient
 	// Employee is the client for interacting with the Employee builders.
@@ -183,6 +185,7 @@ func (tx *Tx) Client() *Client {
 func (tx *Tx) init() {
 	tx.Agent = NewAgentClient(tx.config)
 	tx.BatchMsg = NewBatchMsgClient(tx.config)
+	tx.Category = NewCategoryClient(tx.config)
 	tx.Contact = NewContactClient(tx.config)
 	tx.Employee = NewEmployeeClient(tx.config)
 	tx.EmployeeConfig = NewEmployeeConfigClient(tx.config)

+ 44 - 0
internal/handler/category/create_category_handler.go

@@ -0,0 +1,44 @@
+package category
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/category"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /category/create category CreateCategory
+//
+// Create category information | 创建Category
+//
+// Create category information | 创建Category
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: CategoryInfo
+//
+// Responses:
+//  200: BaseMsgResp
+
+func CreateCategoryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.CategoryInfo
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := category.NewCreateCategoryLogic(r.Context(), svcCtx)
+		resp, err := l.CreateCategory(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/category/delete_category_handler.go

@@ -0,0 +1,44 @@
+package category
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/category"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /category/delete category DeleteCategory
+//
+// Delete category information | 删除Category信息
+//
+// Delete category information | 删除Category信息
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: IDsReq
+//
+// Responses:
+//  200: BaseMsgResp
+
+func DeleteCategoryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.IDsReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := category.NewDeleteCategoryLogic(r.Context(), svcCtx)
+		resp, err := l.DeleteCategory(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/category/get_category_by_id_handler.go

@@ -0,0 +1,44 @@
+package category
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/category"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /category category GetCategoryById
+//
+// Get category by ID | 通过ID获取Category
+//
+// Get category by ID | 通过ID获取Category
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: IDReq
+//
+// Responses:
+//  200: CategoryInfoResp
+
+func GetCategoryByIdHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.IDReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := category.NewGetCategoryByIdLogic(r.Context(), svcCtx)
+		resp, err := l.GetCategoryById(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/category/get_category_list_handler.go

@@ -0,0 +1,44 @@
+package category
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/category"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /category/list category GetCategoryList
+//
+// Get category list | 获取Category列表
+//
+// Get category list | 获取Category列表
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: CategoryListReq
+//
+// Responses:
+//  200: CategoryListResp
+
+func GetCategoryListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.CategoryListReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := category.NewGetCategoryListLogic(r.Context(), svcCtx)
+		resp, err := l.GetCategoryList(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/category/update_category_handler.go

@@ -0,0 +1,44 @@
+package category
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/category"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /category/update category UpdateCategory
+//
+// Update category information | 更新Category
+//
+// Update category information | 更新Category
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: CategoryInfo
+//
+// Responses:
+//  200: BaseMsgResp
+
+func UpdateCategoryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.CategoryInfo
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := category.NewUpdateCategoryLogic(r.Context(), svcCtx)
+		resp, err := l.UpdateCategory(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 35 - 0
internal/handler/routes.go

@@ -14,6 +14,7 @@ import (
 	agent "wechat-api/internal/handler/agent"
 	base "wechat-api/internal/handler/base"
 	batch_msg "wechat-api/internal/handler/batch_msg"
+	category "wechat-api/internal/handler/category"
 	contact "wechat-api/internal/handler/contact"
 	employee "wechat-api/internal/handler/employee"
 	employee_config "wechat-api/internal/handler/employee_config"
@@ -793,4 +794,38 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
 			},
 		},
 	)
+
+	server.AddRoutes(
+		rest.WithMiddlewares(
+			[]rest.Middleware{serverCtx.Authority},
+			[]rest.Route{
+				{
+					Method:  http.MethodPost,
+					Path:    "/category/create",
+					Handler: category.CreateCategoryHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/category/update",
+					Handler: category.UpdateCategoryHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/category/delete",
+					Handler: category.DeleteCategoryHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/category/list",
+					Handler: category.GetCategoryListHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/category",
+					Handler: category.GetCategoryByIdHandler(serverCtx),
+				},
+			}...,
+		),
+		rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
+	)
 }

+ 62 - 0
internal/logic/base/init_api_data.go

@@ -6,6 +6,68 @@ import (
 )
 
 func (l *InitDatabaseLogic) insertApiData() (err error) {
+	// Category
+
+	_, err = l.svcCtx.CoreRpc.CreateApi(l.ctx, &core.ApiInfo{
+		ServiceName: pointy.GetPointer("Wechat"),
+		Path:        pointy.GetPointer("/category/create"),
+		Description: pointy.GetPointer("apiDesc.createCategory"),
+		ApiGroup:    pointy.GetPointer("category"),
+		Method:      pointy.GetPointer("POST"),
+	})
+
+	if err != nil {
+		return err
+	}
+
+	_, err = l.svcCtx.CoreRpc.CreateApi(l.ctx, &core.ApiInfo{
+		ServiceName: pointy.GetPointer("Wechat"),
+		Path:        pointy.GetPointer("/category/update"),
+		Description: pointy.GetPointer("apiDesc.updateCategory"),
+		ApiGroup:    pointy.GetPointer("category"),
+		Method:      pointy.GetPointer("POST"),
+	})
+
+	if err != nil {
+		return err
+	}
+
+	_, err = l.svcCtx.CoreRpc.CreateApi(l.ctx, &core.ApiInfo{
+		ServiceName: pointy.GetPointer("Wechat"),
+		Path:        pointy.GetPointer("/category/delete"),
+		Description: pointy.GetPointer("apiDesc.deleteCategory"),
+		ApiGroup:    pointy.GetPointer("category"),
+		Method:      pointy.GetPointer("POST"),
+	})
+
+	if err != nil {
+		return err
+	}
+
+	_, err = l.svcCtx.CoreRpc.CreateApi(l.ctx, &core.ApiInfo{
+		ServiceName: pointy.GetPointer("Wechat"),
+		Path:        pointy.GetPointer("/category/list"),
+		Description: pointy.GetPointer("apiDesc.getCategoryList"),
+		ApiGroup:    pointy.GetPointer("category"),
+		Method:      pointy.GetPointer("POST"),
+	})
+
+	if err != nil {
+		return err
+	}
+
+	_, err = l.svcCtx.CoreRpc.CreateApi(l.ctx, &core.ApiInfo{
+		ServiceName: pointy.GetPointer("Wechat"),
+		Path:        pointy.GetPointer("/category"),
+		Description: pointy.GetPointer("apiDesc.getCategoryById"),
+		ApiGroup:    pointy.GetPointer("category"),
+		Method:      pointy.GetPointer("POST"),
+	})
+
+	if err != nil {
+		return err
+	}
+
 	// EmployeeConfig
 
 	_, err = l.svcCtx.CoreRpc.CreateApi(l.ctx, &core.ApiInfo{

+ 40 - 0
internal/logic/category/create_category_logic.go

@@ -0,0 +1,40 @@
+package category
+
+import (
+	"context"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/dberrorhandler"
+
+    "github.com/suyuan32/simple-admin-common/msg/errormsg"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type CreateCategoryLogic struct {
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	logx.Logger
+}
+
+func NewCreateCategoryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateCategoryLogic {
+	return &CreateCategoryLogic{
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		Logger: logx.WithContext(ctx),
+	}
+}
+
+func (l *CreateCategoryLogic) CreateCategory(req *types.CategoryInfo) (*types.BaseMsgResp, error) {
+    _, err := l.svcCtx.DB.Category.Create().
+			SetNotNilName(req.Name).
+			SetNotNilOrganizationID(req.OrganizationId).
+			Save(l.ctx)
+
+    if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+    return &types.BaseMsgResp{Msg: errormsg.CreateSuccess}, nil
+}

+ 37 - 0
internal/logic/category/delete_category_logic.go

@@ -0,0 +1,37 @@
+package category
+
+import (
+	"context"
+
+    "wechat-api/ent/category"
+    "wechat-api/internal/svc"
+    "wechat-api/internal/types"
+    "wechat-api/internal/utils/dberrorhandler"
+
+    "github.com/suyuan32/simple-admin-common/msg/errormsg"
+    "github.com/zeromicro/go-zero/core/logx"
+)
+
+type DeleteCategoryLogic struct {
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	logx.Logger
+}
+
+func NewDeleteCategoryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteCategoryLogic {
+	return &DeleteCategoryLogic{
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		Logger: logx.WithContext(ctx),
+	}
+}
+
+func (l *DeleteCategoryLogic) DeleteCategory(req *types.IDsReq) (*types.BaseMsgResp, error) {
+	_, err := l.svcCtx.DB.Category.Delete().Where(category.IDIn(req.Ids...)).Exec(l.ctx)
+
+    if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+    return &types.BaseMsgResp{Msg: errormsg.DeleteSuccess}, nil
+}

+ 52 - 0
internal/logic/category/get_category_by_id_logic.go

@@ -0,0 +1,52 @@
+package category
+
+import (
+	"context"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/dberrorhandler"
+
+    "github.com/suyuan32/simple-admin-common/msg/errormsg"
+
+	"github.com/suyuan32/simple-admin-common/utils/pointy"
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type GetCategoryByIdLogic struct {
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	logx.Logger
+}
+
+func NewGetCategoryByIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetCategoryByIdLogic {
+	return &GetCategoryByIdLogic{
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		Logger: logx.WithContext(ctx),
+	}
+}
+
+func (l *GetCategoryByIdLogic) GetCategoryById(req *types.IDReq) (*types.CategoryInfoResp, error) {
+	data, err := l.svcCtx.DB.Category.Get(l.ctx, req.Id)
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	return &types.CategoryInfoResp{
+	    BaseDataInfo: types.BaseDataInfo{
+            Code: 0,
+            Msg:  errormsg.Success,
+        },
+        Data: types.CategoryInfo{
+            BaseIDInfo:    types.BaseIDInfo{
+				Id:          &data.ID,
+				CreatedAt:    pointy.GetPointer(data.CreatedAt.UnixMilli()),
+				UpdatedAt:    pointy.GetPointer(data.UpdatedAt.UnixMilli()),
+            },
+			Name:	&data.Name,
+			OrganizationId:	&data.OrganizationID,
+        },
+	}, nil
+}
+

+ 61 - 0
internal/logic/category/get_category_list_logic.go

@@ -0,0 +1,61 @@
+package category
+
+import (
+	"context"
+
+	"wechat-api/ent/category"
+	"wechat-api/ent/predicate"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/dberrorhandler"
+
+    "github.com/suyuan32/simple-admin-common/msg/errormsg"
+
+	"github.com/suyuan32/simple-admin-common/utils/pointy"
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type GetCategoryListLogic struct {
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	logx.Logger
+}
+
+func NewGetCategoryListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetCategoryListLogic {
+	return &GetCategoryListLogic{
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		Logger: logx.WithContext(ctx),
+	}
+}
+
+func (l *GetCategoryListLogic) GetCategoryList(req *types.CategoryListReq) (*types.CategoryListResp, error) {
+	var predicates []predicate.Category
+	if req.Name != nil {
+		predicates = append(predicates, category.NameContains(*req.Name))
+	}
+	data, err := l.svcCtx.DB.Category.Query().Where(predicates...).Page(l.ctx, req.Page, req.PageSize)
+
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	resp := &types.CategoryListResp{}
+	resp.Msg = errormsg.Success
+	resp.Data.Total = data.PageDetails.Total
+
+	for _, v := range data.List {
+		resp.Data.Data = append(resp.Data.Data,
+		types.CategoryInfo{
+			BaseIDInfo:    types.BaseIDInfo{
+				Id:          &v.ID,
+				CreatedAt:    pointy.GetPointer(v.CreatedAt.UnixMilli()),
+				UpdatedAt:    pointy.GetPointer(v.UpdatedAt.UnixMilli()),
+            },
+			Name:	&v.Name,
+			OrganizationId:	&v.OrganizationID,
+		})
+	}
+
+	return resp, nil
+}

+ 40 - 0
internal/logic/category/update_category_logic.go

@@ -0,0 +1,40 @@
+package category
+
+import (
+	"context"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/dberrorhandler"
+
+
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type UpdateCategoryLogic struct {
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	logx.Logger
+}
+
+func NewUpdateCategoryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateCategoryLogic {
+	return &UpdateCategoryLogic{
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		Logger: logx.WithContext(ctx),
+	}
+}
+
+func (l *UpdateCategoryLogic) UpdateCategory(req *types.CategoryInfo) (*types.BaseMsgResp, error) {
+    err := l.svcCtx.DB.Category.UpdateOneID(*req.Id).
+			SetNotNilName(req.Name).
+			SetNotNilOrganizationID(req.OrganizationId).
+			Exec(l.ctx)
+
+    if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+    return &types.BaseMsgResp{Msg: errormsg.UpdateSuccess}, nil
+}

+ 1 - 0
internal/logic/employee/create_employee_logic.go

@@ -48,6 +48,7 @@ func (l *CreateEmployeeLogic) CreateEmployee(req *types.EmployeeInfo) (*types.Ba
 		SetNotNilSwitchIn(req.SwitchIn).
 		SetNotNilVideoURL(req.VideoUrl).
 		SetNotNilOrganizationID(&organizationId).
+		SetNotNilCategoryID(req.CategoryId).
 		Save(l.ctx)
 
 	if err != nil {

+ 1 - 0
internal/logic/employee/get_employee_by_id_logic.go

@@ -104,6 +104,7 @@ func (l *GetEmployeeByIdLogic) GetEmployeeById(req *types.IDReq) (*types.Employe
 			SwitchInList:     switchInList,
 			VideoUrl:         &data.VideoURL,
 			WorkExperience:   workExperience,
+			CategoryId:       &data.CategoryID,
 		},
 	}, nil
 }

+ 4 - 3
internal/logic/employee/get_employee_list_logic.go

@@ -39,9 +39,9 @@ func (l *GetEmployeeListLogic) GetEmployeeList(req *types.EmployeeListReq) (*typ
 	if req.Title != nil {
 		predicates = append(predicates, employee.TitleContains(*req.Title))
 	}
-	//if req.Tags != nil {
-	//	predicates = append(predicates, employee.TagsContains(*req.Tags))
-	//}
+	if req.CategoryId != nil && *req.CategoryId > 0 {
+		predicates = append(predicates, employee.CategoryIDEQ(*req.CategoryId))
+	}
 	data, err := l.svcCtx.DB.Employee.Query().Where(predicates...).WithEmWorkExperiences().WithEmTutorial().Page(l.ctx, req.Page, req.PageSize)
 
 	if err != nil {
@@ -113,6 +113,7 @@ func (l *GetEmployeeListLogic) GetEmployeeList(req *types.EmployeeListReq) (*typ
 				Tutorial:         tutorial,
 				VideoUrl:         &v.VideoURL,
 				WorkExperience:   workExperience,
+				CategoryId:       &v.CategoryID,
 			})
 	}
 

+ 1 - 0
internal/logic/employee/update_employee_logic.go

@@ -43,6 +43,7 @@ func (l *UpdateEmployeeLogic) UpdateEmployee(req *types.EmployeeInfo) (*types.Ba
 		SetNotNilScene(req.Scene).
 		SetNotNilSwitchIn(req.SwitchIn).
 		SetNotNilVideoURL(req.VideoUrl).
+		SetNotNilCategoryID(req.CategoryId).
 		Save(l.ctx)
 
 	if err != nil {

+ 46 - 0
internal/types/types.go

@@ -1365,6 +1365,8 @@ type EmployeeInfo struct {
 	ServiceCount *int `json:"serviceCount,optional"`
 	// achievement_count | 业绩单数
 	AchievementCount *int `json:"achievementCount,optional"`
+	// category_id | 分类ID
+	CategoryId *uint64 `json:"categoryId,optional"`
 	// intro | 个人介绍
 	Intro *string `json:"intro,optional"`
 	// estimate | 自我评价
@@ -1410,6 +1412,8 @@ type EmployeeListReq struct {
 	Title *string `json:"title,optional"`
 	// tags | 个人标签
 	Tags *string `json:"tags,optional"`
+	// category_id | 分类ID
+	CategoryId *uint64 `json:"categoryId,optional"`
 }
 
 // Employee information response | Employee信息返回体
@@ -1623,3 +1627,45 @@ type CheckTokenResp struct {
 	// Timestamp 时间戳
 	Timestamp *int64 `json:"timestamp"`
 }
+
+// The data of category information | Category信息
+// swagger:model CategoryInfo
+type CategoryInfo struct {
+	BaseIDInfo
+	// name | 角色名称
+	Name *string `json:"name,optional"`
+	// organization_id | 租户ID
+	OrganizationId *uint64 `json:"organizationId,optional"`
+}
+
+// The response data of category list | Category列表数据
+// swagger:model CategoryListResp
+type CategoryListResp struct {
+	BaseDataInfo
+	// Category list data | Category列表数据
+	Data CategoryListInfo `json:"data"`
+}
+
+// Category list data | Category列表数据
+// swagger:model CategoryListInfo
+type CategoryListInfo struct {
+	BaseListInfo
+	// The API list data | Category列表数据
+	Data []CategoryInfo `json:"data"`
+}
+
+// Get category list request params | Category列表请求参数
+// swagger:model CategoryListReq
+type CategoryListReq struct {
+	PageInfo
+	// name | 角色名称
+	Name *string `json:"name,optional"`
+}
+
+// Category information response | Category信息返回体
+// swagger:model CategoryInfoResp
+type CategoryInfoResp struct {
+	BaseDataInfo
+	// Category information | Category数据
+	Data CategoryInfo `json:"data"`
+}