Просмотр исходного кода

Merge branch 'master' into yhg_240730

jimmyyem 5 месяцев назад
Родитель
Сommit
583cf6d3b7
49 измененных файлов с 3292 добавлено и 512 удалено
  1. 53 21
      crontask/send_wx.go
  2. 72 9
      crontask/send_wx_on_timeout.go
  3. 5 0
      desc/base.api
  4. 17 2
      desc/wechat/sop_stage.api
  5. 77 0
      desc/wechat/sop_task.api
  6. 5 0
      ent/custom_types/types.go
  7. 9 4
      ent/migrate/schema.go
  8. 539 159
      ent/mutation.go
  9. 5 1
      ent/runtime/runtime.go
  10. 10 1
      ent/schema/sop_node.go
  11. 7 1
      ent/schema/sop_stage.go
  12. 132 12
      ent/set_not_nil.go
  13. 46 9
      ent/sopnode.go
  14. 19 3
      ent/sopnode/sopnode.go
  15. 96 6
      ent/sopnode/where.go
  16. 238 33
      ent/sopnode_create.go
  17. 158 30
      ent/sopnode_update.go
  18. 34 8
      ent/sopstage.go
  19. 9 3
      ent/sopstage/sopstage.go
  20. 26 6
      ent/sopstage/where.go
  21. 173 33
      ent/sopstage_create.go
  22. 124 30
      ent/sopstage_update.go
  23. 1 0
      go.mod
  24. 2 0
      go.sum
  25. 20 0
      internal/handler/routes.go
  26. 31 0
      internal/handler/sop_task/get_sop_message_var_handler.go
  27. 44 0
      internal/handler/sop_task/get_sop_task_outline_handler.go
  28. 44 0
      internal/handler/sop_task/sop_task_copy_handler.go
  29. 44 0
      internal/handler/sop_task/test_node_handler.go
  30. 3 0
      internal/logic/contact/get_contact_list_logic.go
  31. 123 33
      internal/logic/label_relationship/batch_update_label_relationships_logic.go
  32. 130 39
      internal/logic/label_relationship/update_label_relationships_logic.go
  33. 39 3
      internal/logic/sop_node/create_sop_node_logic.go
  34. 22 1
      internal/logic/sop_node/get_sop_node_by_id_logic.go
  35. 22 1
      internal/logic/sop_node/get_sop_node_detail_logic.go
  36. 22 1
      internal/logic/sop_node/get_sop_node_list_logic.go
  37. 41 3
      internal/logic/sop_node/update_sop_node_logic.go
  38. 37 4
      internal/logic/sop_stage/create_sop_stage_logic.go
  39. 21 1
      internal/logic/sop_stage/get_sop_stage_by_id_logic.go
  40. 21 1
      internal/logic/sop_stage/get_sop_stage_detail_logic.go
  41. 21 1
      internal/logic/sop_stage/get_sop_stage_list_logic.go
  42. 41 4
      internal/logic/sop_stage/update_sop_stage_logic.go
  43. 42 0
      internal/logic/sop_task/get_sop_message_var_logic.go
  44. 7 1
      internal/logic/sop_task/get_sop_task_list_logic.go
  45. 79 0
      internal/logic/sop_task/get_sop_task_outline_logic.go
  46. 150 46
      internal/logic/sop_task/publish_sop_task_logic.go
  47. 197 0
      internal/logic/sop_task/sop_task_copy_logic.go
  48. 157 0
      internal/logic/sop_task/test_node_logic.go
  49. 77 2
      internal/types/types.go

+ 53 - 21
crontask/send_wx.go

@@ -3,9 +3,11 @@ package crontask
 import (
 	"context"
 	"encoding/json"
+	"regexp"
 	"strings"
 	"time"
 	"wechat-api/ent"
+	"wechat-api/ent/contact"
 	"wechat-api/ent/messagerecords"
 	"wechat-api/ent/server"
 	"wechat-api/ent/wx"
@@ -94,31 +96,52 @@ func (l *CronTask) sendWx() {
 		return val, nil
 	}
 
-	for _, v := range messages {
-		serverInfo, _ := getServerInfo(v.BotWxid)
-		infoArray := strings.Split(serverInfo, ":")
-		serverIp, adminPort, wxPort := infoArray[0], infoArray[1], infoArray[2]
+	// 从 Redis 中获取服务器信息(ip, port)
+	getContactInfo := func(botwxid string, wxid string) (*ent.Contact, error) {
+		contactInfo, _ := l.svcCtx.DB.Contact.Query().Where(contact.WxWxidEQ(botwxid), contact.WxidEQ(wxid)).First(l.ctx)
+
+		return contactInfo, nil
+	}
 
+	for _, v := range messages {
 		// 更新 status 值为 2(发送中)
-		tx, err := l.svcCtx.DB.Tx(l.ctx)
+		tx, _ := l.svcCtx.DB.Tx(l.ctx)
+		if v.Content != "" {
+			serverInfo, _ := getServerInfo(v.BotWxid)
+			infoArray := strings.Split(serverInfo, ":")
+			serverIp, adminPort, wxPort := infoArray[0], infoArray[1], infoArray[2]
 
-		_, err = tx.MessageRecords.UpdateOneID(v.ID).SetStatus(2).Save(ctx)
-		if err != nil {
-			l.Logger.Errorf("update messageRecords failed id=%v err=%v", v.ID, err)
-			continue
-		}
+			_, err = tx.MessageRecords.UpdateOneID(v.ID).SetStatus(2).Save(ctx)
+			if err != nil {
+				l.Logger.Errorf("update messageRecords failed id=%v err=%v", v.ID, err)
+				continue
+			}
 
-		hookClient := hook.NewHook(serverIp, adminPort, wxPort)
+			hookClient := hook.NewHook(serverIp, adminPort, wxPort)
+			if v.ContentType == 1 {
+				content := v.Content
+				if containsPlaceholder(content) {
+					contactInfo, _ := getContactInfo(v.BotWxid, v.ContactWxid)
+					content = varReplace(content, contactInfo)
+				}
+				err = hookClient.SendTextMsg(v.ContactWxid, content)
+			} else {
+				err = hookClient.SendPicMsg(v.ContactWxid, v.Content, v.Meta.Filename)
+			}
 
-		if v.ContentType == 1 {
-			err = hookClient.SendTextMsg(v.ContactWxid, v.Content)
-		} else {
-			err = hookClient.SendPicMsg(v.ContactWxid, v.Content, v.Meta.Filename)
-		}
+			if err != nil {
+				_ = tx.Rollback()
+				continue
+			} else {
+				_, err := tx.MessageRecords.UpdateOneID(v.ID).SetStatus(3).SetSendTime(time.Now()).Save(ctx)
+				if err != nil {
+					_ = tx.Rollback()
+				} else {
+					_ = tx.Commit()
+				}
+			}
 
-		if err != nil {
-			_ = tx.Rollback()
-			continue
+			time.Sleep(time.Duration(60/p.Number) * time.Second)
 		} else {
 			_, err := tx.MessageRecords.UpdateOneID(v.ID).SetStatus(3).SetSendTime(time.Now()).Save(ctx)
 			if err != nil {
@@ -127,11 +150,20 @@ func (l *CronTask) sendWx() {
 				_ = tx.Commit()
 			}
 		}
-
-		time.Sleep(time.Duration(60/p.Number) * time.Second)
 	}
 
 	finishTime := time.Now()
 	l.Logger.Infof("This process cost %v", finishTime.Sub(startTime).String())
 	return
 }
+
+func containsPlaceholder(s string) bool {
+	pattern := `\$\{.*?\}`
+	matched, _ := regexp.MatchString(pattern, s)
+	return matched
+}
+
+func varReplace(s string, contactInfo *ent.Contact) string {
+	s = strings.Replace(s, "${nickname}", contactInfo.Nickname, -1)
+	return s
+}

+ 72 - 9
crontask/send_wx_on_timeout.go

@@ -2,6 +2,7 @@ package crontask
 
 import (
 	"context"
+	"regexp"
 	"time"
 	"wechat-api/ent"
 	"wechat-api/ent/custom_types"
@@ -37,8 +38,18 @@ func (l *CronTask) sendWxOnTimeout() {
 	//stages := make([]uint64, 0)
 	messages := make([]*ent.MessageRecords, 0)
 	for _, node := range nodes {
-		lowerBound := startTime.Add(-time.Minute * time.Duration(node.NoReplyCondition+2))
-		upperBound := startTime.Add(-time.Minute * time.Duration(node.NoReplyCondition))
+		var coef uint64 = 1
+		switch node.NoReplyUnit {
+		case "W":
+			coef = 60 * 24 * 7
+		case "D":
+			coef = 60 * 24
+		case "h":
+			coef = 60
+		}
+		// 查询 node 对应的 stage 记录
+		lowerBound := startTime.Add(-time.Minute * time.Duration(node.NoReplyCondition*coef+2))
+		upperBound := startTime.Add(-time.Minute * time.Duration(node.NoReplyCondition*coef))
 		if node.ParentID == 0 {
 			messages, _ = l.svcCtx.DB.MessageRecords.Query().
 				Where(messagerecords.StatusEQ(3)).
@@ -68,25 +79,69 @@ func (l *CronTask) sendWxOnTimeout() {
 
 			if latest.ID == s.ID {
 				// 创建 MessageRecords 记录
-				for i, c := range node.ActionMessage {
-					meta := custom_types.Meta{}
-					if c.Meta != nil {
-						meta.Filename = c.Meta.Filename
+				if node.ActionMessage != nil {
+					for i, c := range node.ActionMessage {
+						meta := custom_types.Meta{}
+						if c.Meta != nil {
+							meta.Filename = c.Meta.Filename
+						}
+						_, _ = l.svcCtx.DB.MessageRecords.Create().
+							SetStatus(1).
+							SetBotWxid(s.BotWxid).
+							SetContactID(s.ContactID).
+							SetContactType(s.ContactType).
+							SetContactWxid(s.ContactWxid).
+							SetContentType(c.Type).
+							SetContent(c.Content).
+							SetMeta(meta).
+							SetSourceType(4).
+							SetSourceID(node.ID).
+							SetSubSourceID(uint64(i)).
+							SetOrganizationID(s.OrganizationID).
+							Save(ctx)
 					}
+				} else {
+					meta := custom_types.Meta{}
 					_, _ = l.svcCtx.DB.MessageRecords.Create().
 						SetStatus(1).
 						SetBotWxid(s.BotWxid).
 						SetContactID(s.ContactID).
 						SetContactType(s.ContactType).
 						SetContactWxid(s.ContactWxid).
-						SetContentType(c.Type).
-						SetContent(c.Content).
+						SetContentType(1).
 						SetMeta(meta).
 						SetSourceType(4).
 						SetSourceID(node.ID).
-						SetSubSourceID(uint64(i)).
+						SetSubSourceID(0).
+						SetOrganizationID(s.OrganizationID).
 						Save(ctx)
 				}
+				if node.ActionForward != nil {
+					if node.ActionForward.Wxid != "" {
+						forwardWxids := splitString(node.ActionForward.Wxid)
+						for _, forwardWxid := range forwardWxids {
+							for i, message := range node.ActionForward.Action {
+								meta := custom_types.Meta{}
+								if message.Meta != nil {
+									meta.Filename = message.Meta.Filename
+								}
+								_, _ = l.svcCtx.DB.MessageRecords.Create().
+									SetBotWxid(s.BotWxid).
+									SetContactID(0).
+									SetContactType(0).
+									SetContactWxid(forwardWxid).
+									SetContentType(message.Type).
+									SetContent(message.Content).
+									SetMeta(meta).
+									SetSourceType(4).
+									SetSourceID(node.ID).
+									SetSubSourceID(s.ContactID + uint64(i)).
+									SetOrganizationID(s.OrganizationID).
+									Save(l.ctx)
+							}
+						}
+					}
+				}
 			}
 		}
 	}
@@ -95,3 +150,11 @@ func (l *CronTask) sendWxOnTimeout() {
 	l.Logger.Infof("This process cost %v", finishTime.Sub(startTime).String())
 	return
 }
+
+func splitString(input string) []string {
+	// Define the regular expression pattern to match Chinese comma, English comma, and Chinese enumeration comma
+	pattern := `[,,、]`
+	re := regexp.MustCompile(pattern)
+	// Split the input string based on the pattern
+	return re.Split(input, -1)
+}

+ 5 - 0
desc/base.api

@@ -221,6 +221,11 @@ type Meta struct {
 	Filename    string    `json:"filename,optional"`
 }
 
+type ActionForward struct {
+	Wxid    string    `json:"wxid"`
+	Action []Action `json:"action"`
+}
+
 @server(
 	group: base
 )

+ 17 - 2
desc/wechat/sop_stage.api

@@ -27,7 +27,13 @@ type (
         ActionMessage  []Action `json:"actionMessage,optional"`
 
         // 命中后需要打的标签 
-        ActionLabel  []uint64 `json:"actionLabel,optional"`
+        ActionLabelAdd  []uint64 `json:"actionLabelAdd,optional"`
+
+        // 命中后需要移除的标签
+        ActionLabelDel  []uint64 `json:"actionLabelDel,optional"`
+
+        // 命中后转发的消息内容
+        ActionForward  *ActionForward `json:"actionForward,optional"`
 
         // 阶段顺序 
         IndexSort  *int `json:"indexSort,optional"`
@@ -96,11 +102,20 @@ type (
         // 超时触发时间(分钟)
         NoReplyCondition  *uint64 `json:"noReplyCondition,optional"`
 
+        // 超时触发时间单位
+        NoReplyUnit  *string `json:"noReplyUnit,optional"`
+
         // 命中后发送的消息内容
         ActionMessage  []Action `json:"actionMessage,optional"`
 
         // 命中后需要打的标签
-        ActionLabel  []uint64 `json:"actionLabel,optional"`
+        ActionLabelAdd  []uint64 `json:"actionLabelAdd,optional"`
+
+        // 命中后需要移除的标签
+        ActionLabelDel  []uint64 `json:"actionLabelDel,optional"`
+
+        // 命中后转发的消息内容
+        ActionForward  *ActionForward `json:"actionForward,optional"`
 
         // 阶段信息
         StageInfo  *SopStageInfo `json:"stageInfo,optional"`

+ 77 - 0
desc/wechat/sop_task.api

@@ -76,6 +76,67 @@ type (
         // SopTask id | SopTask id
         Data uint64 `json:"data"`
     }
+
+    CopyReq {
+        // SopTask id | SopTask id
+        Id uint64 `json:"id"`
+
+        // SOP 任务名称
+        Name  *string `json:"name,optional"`
+
+        // Organization id | Organization id
+        OrganizationId uint64 `json:"organizationId,optional"`
+    }
+
+    MessageVarResp {
+        BaseDataInfo
+
+        Data MessageVarRespData `json:"data"`
+    }
+
+    MessageVarRespData {
+        // 消息变量
+        MessageVar []MessageVarInfo `json:"messageVar"`
+
+        // 转发变量
+        ForwardVar []MessageVarInfo `json:"forwardVar"`
+    }
+
+    MessageVarInfo {
+        // 显示名称
+        Label string `json:"label"`
+
+        // 变量
+        Value string `json:"value"`
+    }
+
+    SopTaskOutlineResp {
+        BaseDataInfo
+
+        Data []SopTaskOutlineInfo `json:"data"`
+    }
+
+    SopTaskOutlineInfo {
+        // 标题
+        Title string `json:"title,optional"`
+
+        // key
+        Key string `json:"key,optional"`
+
+        // 子节点
+        Children []SopTaskOutlineInfo `json:"children,optional"`
+    }
+
+    TestNodeReq {
+        Type int `json:"type"`
+        Id uint64 `json:"id"`
+        content string `json:"content"`
+    }
+
+    TestNodeResp {
+        BaseDataInfo
+        Data []string `json:"data"`
+    }
 )
 
 @server(
@@ -124,4 +185,20 @@ service Wechat {
     // task stop | SopTask 停止
     @handler sopTaskStop
     post /sop_task/stop (IDReq) returns (BaseMsgResp)
+
+    // task start | SopTask 复制
+    @handler sopTaskCopy
+    post /sop_task/copy (CopyReq) returns (BaseMsgResp)
+
+    // Get sop task list | 获取Sop消息变量
+    @handler getSopMessageVar
+    post /sop_task/message_var () returns (MessageVarResp)
+
+    // 获取SopTask大纲
+    @handler getSopTaskOutline
+    post /sop_task/outline (IDReq) returns (SopTaskOutlineResp)
+
+    // 测试Sop节点
+    @handler testNode
+    post /sop_task/test_node (TestNodeReq) returns (TestNodeResp)
 }

+ 5 - 0
ent/custom_types/types.go

@@ -14,3 +14,8 @@ type Action struct {
 type Meta struct {
 	Filename string `json:"filename,omitempty"`
 }
+
+type ActionForward struct {
+	Wxid   string   `json:"wxid"`
+	Action []Action `json:"action"`
+}

+ 9 - 4
ent/migrate/schema.go

@@ -504,8 +504,11 @@ var (
 		{Name: "condition_type", Type: field.TypeInt, Comment: "触发条件类型 1 客户回复后触发 2 超时后触发", Default: 1},
 		{Name: "condition_list", Type: field.TypeJSON, Nullable: true, Comment: "触发语义列表 当为空时则代表用户回复任意内容后触发"},
 		{Name: "no_reply_condition", Type: field.TypeUint64, Comment: "超时触发时间(分钟)", Default: 0},
+		{Name: "no_reply_unit", Type: field.TypeString, Comment: "超时触发时间单位", Default: ""},
 		{Name: "action_message", Type: field.TypeJSON, Nullable: true, Comment: "命中后发送的消息内容"},
-		{Name: "action_label", Type: field.TypeJSON, Nullable: true, Comment: "命中后需要打的标签"},
+		{Name: "action_label_add", Type: field.TypeJSON, Nullable: true, Comment: "命中后需要打的标签"},
+		{Name: "action_label_del", Type: field.TypeJSON, Nullable: true, Comment: "命中后需要移除的标签"},
+		{Name: "action_forward", Type: field.TypeJSON, Nullable: true, Comment: "命中后转发的消息"},
 		{Name: "stage_id", Type: field.TypeUint64, Comment: "阶段 ID"},
 	}
 	// SopNodeTable holds the schema information for the "sop_node" table.
@@ -516,7 +519,7 @@ var (
 		ForeignKeys: []*schema.ForeignKey{
 			{
 				Symbol:     "sop_node_sop_stage_stage_nodes",
-				Columns:    []*schema.Column{SopNodeColumns[12]},
+				Columns:    []*schema.Column{SopNodeColumns[15]},
 				RefColumns: []*schema.Column{SopStageColumns[0]},
 				OnDelete:   schema.NoAction,
 			},
@@ -541,7 +544,9 @@ var (
 		{Name: "condition_operator", Type: field.TypeInt, Comment: "筛选条件关系  1 满足所有条件(and) 2 满足任意条件(or)", Default: 1},
 		{Name: "condition_list", Type: field.TypeJSON, Comment: "筛选条件列表"},
 		{Name: "action_message", Type: field.TypeJSON, Nullable: true, Comment: "命中后发送的消息内容"},
-		{Name: "action_label", Type: field.TypeJSON, Nullable: true, Comment: "命中后需要打的标签"},
+		{Name: "action_label_add", Type: field.TypeJSON, Nullable: true, Comment: "命中后需要打的标签"},
+		{Name: "action_label_del", Type: field.TypeJSON, Nullable: true, Comment: "命中后需要移除的标签"},
+		{Name: "action_forward", Type: field.TypeJSON, Nullable: true, Comment: "命中后转发的消息"},
 		{Name: "index_sort", Type: field.TypeInt, Nullable: true, Comment: "阶段顺序", Default: 1},
 		{Name: "task_id", Type: field.TypeUint64, Comment: "SOP 任务 ID"},
 	}
@@ -553,7 +558,7 @@ var (
 		ForeignKeys: []*schema.ForeignKey{
 			{
 				Symbol:     "sop_stage_sop_task_task_stages",
-				Columns:    []*schema.Column{SopStageColumns[12]},
+				Columns:    []*schema.Column{SopStageColumns[14]},
 				RefColumns: []*schema.Column{SopTaskColumns[0]},
 				OnDelete:   schema.NoAction,
 			},

+ 539 - 159
ent/mutation.go

@@ -16611,36 +16611,40 @@ func (m *ServerMutation) ResetEdge(name string) error {
 // SopNodeMutation represents an operation that mutates the SopNode nodes in the graph.
 type SopNodeMutation struct {
 	config
-	op                    Op
-	typ                   string
-	id                    *uint64
-	created_at            *time.Time
-	updated_at            *time.Time
-	status                *uint8
-	addstatus             *int8
-	deleted_at            *time.Time
-	parent_id             *uint64
-	addparent_id          *int64
-	name                  *string
-	condition_type        *int
-	addcondition_type     *int
-	condition_list        *[]string
-	appendcondition_list  []string
-	no_reply_condition    *uint64
-	addno_reply_condition *int64
-	action_message        *[]custom_types.Action
-	appendaction_message  []custom_types.Action
-	action_label          *[]uint64
-	appendaction_label    []uint64
-	clearedFields         map[string]struct{}
-	sop_stage             *uint64
-	clearedsop_stage      bool
-	node_messages         map[uint64]struct{}
-	removednode_messages  map[uint64]struct{}
-	clearednode_messages  bool
-	done                  bool
-	oldValue              func(context.Context) (*SopNode, error)
-	predicates            []predicate.SopNode
+	op                     Op
+	typ                    string
+	id                     *uint64
+	created_at             *time.Time
+	updated_at             *time.Time
+	status                 *uint8
+	addstatus              *int8
+	deleted_at             *time.Time
+	parent_id              *uint64
+	addparent_id           *int64
+	name                   *string
+	condition_type         *int
+	addcondition_type      *int
+	condition_list         *[]string
+	appendcondition_list   []string
+	no_reply_condition     *uint64
+	addno_reply_condition  *int64
+	no_reply_unit          *string
+	action_message         *[]custom_types.Action
+	appendaction_message   []custom_types.Action
+	action_label_add       *[]uint64
+	appendaction_label_add []uint64
+	action_label_del       *[]uint64
+	appendaction_label_del []uint64
+	action_forward         **custom_types.ActionForward
+	clearedFields          map[string]struct{}
+	sop_stage              *uint64
+	clearedsop_stage       bool
+	node_messages          map[uint64]struct{}
+	removednode_messages   map[uint64]struct{}
+	clearednode_messages   bool
+	done                   bool
+	oldValue               func(context.Context) (*SopNode, error)
+	predicates             []predicate.SopNode
 }
 
 var _ ent.Mutation = (*SopNodeMutation)(nil)
@@ -17243,6 +17247,42 @@ func (m *SopNodeMutation) ResetNoReplyCondition() {
 	m.addno_reply_condition = nil
 }
 
+// SetNoReplyUnit sets the "no_reply_unit" field.
+func (m *SopNodeMutation) SetNoReplyUnit(s string) {
+	m.no_reply_unit = &s
+}
+
+// NoReplyUnit returns the value of the "no_reply_unit" field in the mutation.
+func (m *SopNodeMutation) NoReplyUnit() (r string, exists bool) {
+	v := m.no_reply_unit
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldNoReplyUnit returns the old "no_reply_unit" field's value of the SopNode entity.
+// If the SopNode 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 *SopNodeMutation) OldNoReplyUnit(ctx context.Context) (v string, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldNoReplyUnit is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldNoReplyUnit requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldNoReplyUnit: %w", err)
+	}
+	return oldValue.NoReplyUnit, nil
+}
+
+// ResetNoReplyUnit resets all changes to the "no_reply_unit" field.
+func (m *SopNodeMutation) ResetNoReplyUnit() {
+	m.no_reply_unit = nil
+}
+
 // SetActionMessage sets the "action_message" field.
 func (m *SopNodeMutation) SetActionMessage(ct []custom_types.Action) {
 	m.action_message = &ct
@@ -17308,69 +17348,183 @@ func (m *SopNodeMutation) ResetActionMessage() {
 	delete(m.clearedFields, sopnode.FieldActionMessage)
 }
 
-// SetActionLabel sets the "action_label" field.
-func (m *SopNodeMutation) SetActionLabel(u []uint64) {
-	m.action_label = &u
-	m.appendaction_label = nil
+// SetActionLabelAdd sets the "action_label_add" field.
+func (m *SopNodeMutation) SetActionLabelAdd(u []uint64) {
+	m.action_label_add = &u
+	m.appendaction_label_add = nil
 }
 
-// ActionLabel returns the value of the "action_label" field in the mutation.
-func (m *SopNodeMutation) ActionLabel() (r []uint64, exists bool) {
-	v := m.action_label
+// ActionLabelAdd returns the value of the "action_label_add" field in the mutation.
+func (m *SopNodeMutation) ActionLabelAdd() (r []uint64, exists bool) {
+	v := m.action_label_add
 	if v == nil {
 		return
 	}
 	return *v, true
 }
 
-// OldActionLabel returns the old "action_label" field's value of the SopNode entity.
+// OldActionLabelAdd returns the old "action_label_add" field's value of the SopNode entity.
 // If the SopNode 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 *SopNodeMutation) OldActionLabel(ctx context.Context) (v []uint64, err error) {
+func (m *SopNodeMutation) OldActionLabelAdd(ctx context.Context) (v []uint64, err error) {
 	if !m.op.Is(OpUpdateOne) {
-		return v, errors.New("OldActionLabel is only allowed on UpdateOne operations")
+		return v, errors.New("OldActionLabelAdd is only allowed on UpdateOne operations")
 	}
 	if m.id == nil || m.oldValue == nil {
-		return v, errors.New("OldActionLabel requires an ID field in the mutation")
+		return v, errors.New("OldActionLabelAdd requires an ID field in the mutation")
 	}
 	oldValue, err := m.oldValue(ctx)
 	if err != nil {
-		return v, fmt.Errorf("querying old value for OldActionLabel: %w", err)
+		return v, fmt.Errorf("querying old value for OldActionLabelAdd: %w", err)
 	}
-	return oldValue.ActionLabel, nil
+	return oldValue.ActionLabelAdd, nil
 }
 
-// AppendActionLabel adds u to the "action_label" field.
-func (m *SopNodeMutation) AppendActionLabel(u []uint64) {
-	m.appendaction_label = append(m.appendaction_label, u...)
+// AppendActionLabelAdd adds u to the "action_label_add" field.
+func (m *SopNodeMutation) AppendActionLabelAdd(u []uint64) {
+	m.appendaction_label_add = append(m.appendaction_label_add, u...)
 }
 
-// AppendedActionLabel returns the list of values that were appended to the "action_label" field in this mutation.
-func (m *SopNodeMutation) AppendedActionLabel() ([]uint64, bool) {
-	if len(m.appendaction_label) == 0 {
+// AppendedActionLabelAdd returns the list of values that were appended to the "action_label_add" field in this mutation.
+func (m *SopNodeMutation) AppendedActionLabelAdd() ([]uint64, bool) {
+	if len(m.appendaction_label_add) == 0 {
 		return nil, false
 	}
-	return m.appendaction_label, true
+	return m.appendaction_label_add, true
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (m *SopNodeMutation) ClearActionLabel() {
-	m.action_label = nil
-	m.appendaction_label = nil
-	m.clearedFields[sopnode.FieldActionLabel] = struct{}{}
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (m *SopNodeMutation) ClearActionLabelAdd() {
+	m.action_label_add = nil
+	m.appendaction_label_add = nil
+	m.clearedFields[sopnode.FieldActionLabelAdd] = struct{}{}
 }
 
-// ActionLabelCleared returns if the "action_label" field was cleared in this mutation.
-func (m *SopNodeMutation) ActionLabelCleared() bool {
-	_, ok := m.clearedFields[sopnode.FieldActionLabel]
+// ActionLabelAddCleared returns if the "action_label_add" field was cleared in this mutation.
+func (m *SopNodeMutation) ActionLabelAddCleared() bool {
+	_, ok := m.clearedFields[sopnode.FieldActionLabelAdd]
 	return ok
 }
 
-// ResetActionLabel resets all changes to the "action_label" field.
-func (m *SopNodeMutation) ResetActionLabel() {
-	m.action_label = nil
-	m.appendaction_label = nil
-	delete(m.clearedFields, sopnode.FieldActionLabel)
+// ResetActionLabelAdd resets all changes to the "action_label_add" field.
+func (m *SopNodeMutation) ResetActionLabelAdd() {
+	m.action_label_add = nil
+	m.appendaction_label_add = nil
+	delete(m.clearedFields, sopnode.FieldActionLabelAdd)
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (m *SopNodeMutation) SetActionLabelDel(u []uint64) {
+	m.action_label_del = &u
+	m.appendaction_label_del = nil
+}
+
+// ActionLabelDel returns the value of the "action_label_del" field in the mutation.
+func (m *SopNodeMutation) ActionLabelDel() (r []uint64, exists bool) {
+	v := m.action_label_del
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldActionLabelDel returns the old "action_label_del" field's value of the SopNode entity.
+// If the SopNode 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 *SopNodeMutation) OldActionLabelDel(ctx context.Context) (v []uint64, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldActionLabelDel is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldActionLabelDel requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldActionLabelDel: %w", err)
+	}
+	return oldValue.ActionLabelDel, nil
+}
+
+// AppendActionLabelDel adds u to the "action_label_del" field.
+func (m *SopNodeMutation) AppendActionLabelDel(u []uint64) {
+	m.appendaction_label_del = append(m.appendaction_label_del, u...)
+}
+
+// AppendedActionLabelDel returns the list of values that were appended to the "action_label_del" field in this mutation.
+func (m *SopNodeMutation) AppendedActionLabelDel() ([]uint64, bool) {
+	if len(m.appendaction_label_del) == 0 {
+		return nil, false
+	}
+	return m.appendaction_label_del, true
+}
+
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (m *SopNodeMutation) ClearActionLabelDel() {
+	m.action_label_del = nil
+	m.appendaction_label_del = nil
+	m.clearedFields[sopnode.FieldActionLabelDel] = struct{}{}
+}
+
+// ActionLabelDelCleared returns if the "action_label_del" field was cleared in this mutation.
+func (m *SopNodeMutation) ActionLabelDelCleared() bool {
+	_, ok := m.clearedFields[sopnode.FieldActionLabelDel]
+	return ok
+}
+
+// ResetActionLabelDel resets all changes to the "action_label_del" field.
+func (m *SopNodeMutation) ResetActionLabelDel() {
+	m.action_label_del = nil
+	m.appendaction_label_del = nil
+	delete(m.clearedFields, sopnode.FieldActionLabelDel)
+}
+
+// SetActionForward sets the "action_forward" field.
+func (m *SopNodeMutation) SetActionForward(ctf *custom_types.ActionForward) {
+	m.action_forward = &ctf
+}
+
+// ActionForward returns the value of the "action_forward" field in the mutation.
+func (m *SopNodeMutation) ActionForward() (r *custom_types.ActionForward, exists bool) {
+	v := m.action_forward
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldActionForward returns the old "action_forward" field's value of the SopNode entity.
+// If the SopNode 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 *SopNodeMutation) OldActionForward(ctx context.Context) (v *custom_types.ActionForward, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldActionForward is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldActionForward requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldActionForward: %w", err)
+	}
+	return oldValue.ActionForward, nil
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (m *SopNodeMutation) ClearActionForward() {
+	m.action_forward = nil
+	m.clearedFields[sopnode.FieldActionForward] = struct{}{}
+}
+
+// ActionForwardCleared returns if the "action_forward" field was cleared in this mutation.
+func (m *SopNodeMutation) ActionForwardCleared() bool {
+	_, ok := m.clearedFields[sopnode.FieldActionForward]
+	return ok
+}
+
+// ResetActionForward resets all changes to the "action_forward" field.
+func (m *SopNodeMutation) ResetActionForward() {
+	m.action_forward = nil
+	delete(m.clearedFields, sopnode.FieldActionForward)
 }
 
 // SetSopStageID sets the "sop_stage" edge to the SopStage entity by id.
@@ -17501,7 +17655,7 @@ func (m *SopNodeMutation) Type() string {
 // order to get all numeric fields that were incremented/decremented, call
 // AddedFields().
 func (m *SopNodeMutation) Fields() []string {
-	fields := make([]string, 0, 12)
+	fields := make([]string, 0, 15)
 	if m.created_at != nil {
 		fields = append(fields, sopnode.FieldCreatedAt)
 	}
@@ -17532,11 +17686,20 @@ func (m *SopNodeMutation) Fields() []string {
 	if m.no_reply_condition != nil {
 		fields = append(fields, sopnode.FieldNoReplyCondition)
 	}
+	if m.no_reply_unit != nil {
+		fields = append(fields, sopnode.FieldNoReplyUnit)
+	}
 	if m.action_message != nil {
 		fields = append(fields, sopnode.FieldActionMessage)
 	}
-	if m.action_label != nil {
-		fields = append(fields, sopnode.FieldActionLabel)
+	if m.action_label_add != nil {
+		fields = append(fields, sopnode.FieldActionLabelAdd)
+	}
+	if m.action_label_del != nil {
+		fields = append(fields, sopnode.FieldActionLabelDel)
+	}
+	if m.action_forward != nil {
+		fields = append(fields, sopnode.FieldActionForward)
 	}
 	return fields
 }
@@ -17566,10 +17729,16 @@ func (m *SopNodeMutation) Field(name string) (ent.Value, bool) {
 		return m.ConditionList()
 	case sopnode.FieldNoReplyCondition:
 		return m.NoReplyCondition()
+	case sopnode.FieldNoReplyUnit:
+		return m.NoReplyUnit()
 	case sopnode.FieldActionMessage:
 		return m.ActionMessage()
-	case sopnode.FieldActionLabel:
-		return m.ActionLabel()
+	case sopnode.FieldActionLabelAdd:
+		return m.ActionLabelAdd()
+	case sopnode.FieldActionLabelDel:
+		return m.ActionLabelDel()
+	case sopnode.FieldActionForward:
+		return m.ActionForward()
 	}
 	return nil, false
 }
@@ -17599,10 +17768,16 @@ func (m *SopNodeMutation) OldField(ctx context.Context, name string) (ent.Value,
 		return m.OldConditionList(ctx)
 	case sopnode.FieldNoReplyCondition:
 		return m.OldNoReplyCondition(ctx)
+	case sopnode.FieldNoReplyUnit:
+		return m.OldNoReplyUnit(ctx)
 	case sopnode.FieldActionMessage:
 		return m.OldActionMessage(ctx)
-	case sopnode.FieldActionLabel:
-		return m.OldActionLabel(ctx)
+	case sopnode.FieldActionLabelAdd:
+		return m.OldActionLabelAdd(ctx)
+	case sopnode.FieldActionLabelDel:
+		return m.OldActionLabelDel(ctx)
+	case sopnode.FieldActionForward:
+		return m.OldActionForward(ctx)
 	}
 	return nil, fmt.Errorf("unknown SopNode field %s", name)
 }
@@ -17682,6 +17857,13 @@ func (m *SopNodeMutation) SetField(name string, value ent.Value) error {
 		}
 		m.SetNoReplyCondition(v)
 		return nil
+	case sopnode.FieldNoReplyUnit:
+		v, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetNoReplyUnit(v)
+		return nil
 	case sopnode.FieldActionMessage:
 		v, ok := value.([]custom_types.Action)
 		if !ok {
@@ -17689,12 +17871,26 @@ func (m *SopNodeMutation) SetField(name string, value ent.Value) error {
 		}
 		m.SetActionMessage(v)
 		return nil
-	case sopnode.FieldActionLabel:
+	case sopnode.FieldActionLabelAdd:
+		v, ok := value.([]uint64)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetActionLabelAdd(v)
+		return nil
+	case sopnode.FieldActionLabelDel:
 		v, ok := value.([]uint64)
 		if !ok {
 			return fmt.Errorf("unexpected type %T for field %s", value, name)
 		}
-		m.SetActionLabel(v)
+		m.SetActionLabelDel(v)
+		return nil
+	case sopnode.FieldActionForward:
+		v, ok := value.(*custom_types.ActionForward)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetActionForward(v)
 		return nil
 	}
 	return fmt.Errorf("unknown SopNode field %s", name)
@@ -17789,8 +17985,14 @@ func (m *SopNodeMutation) ClearedFields() []string {
 	if m.FieldCleared(sopnode.FieldActionMessage) {
 		fields = append(fields, sopnode.FieldActionMessage)
 	}
-	if m.FieldCleared(sopnode.FieldActionLabel) {
-		fields = append(fields, sopnode.FieldActionLabel)
+	if m.FieldCleared(sopnode.FieldActionLabelAdd) {
+		fields = append(fields, sopnode.FieldActionLabelAdd)
+	}
+	if m.FieldCleared(sopnode.FieldActionLabelDel) {
+		fields = append(fields, sopnode.FieldActionLabelDel)
+	}
+	if m.FieldCleared(sopnode.FieldActionForward) {
+		fields = append(fields, sopnode.FieldActionForward)
 	}
 	return fields
 }
@@ -17818,8 +18020,14 @@ func (m *SopNodeMutation) ClearField(name string) error {
 	case sopnode.FieldActionMessage:
 		m.ClearActionMessage()
 		return nil
-	case sopnode.FieldActionLabel:
-		m.ClearActionLabel()
+	case sopnode.FieldActionLabelAdd:
+		m.ClearActionLabelAdd()
+		return nil
+	case sopnode.FieldActionLabelDel:
+		m.ClearActionLabelDel()
+		return nil
+	case sopnode.FieldActionForward:
+		m.ClearActionForward()
 		return nil
 	}
 	return fmt.Errorf("unknown SopNode nullable field %s", name)
@@ -17859,11 +18067,20 @@ func (m *SopNodeMutation) ResetField(name string) error {
 	case sopnode.FieldNoReplyCondition:
 		m.ResetNoReplyCondition()
 		return nil
+	case sopnode.FieldNoReplyUnit:
+		m.ResetNoReplyUnit()
+		return nil
 	case sopnode.FieldActionMessage:
 		m.ResetActionMessage()
 		return nil
-	case sopnode.FieldActionLabel:
-		m.ResetActionLabel()
+	case sopnode.FieldActionLabelAdd:
+		m.ResetActionLabelAdd()
+		return nil
+	case sopnode.FieldActionLabelDel:
+		m.ResetActionLabelDel()
+		return nil
+	case sopnode.FieldActionForward:
+		m.ResetActionForward()
 		return nil
 	}
 	return fmt.Errorf("unknown SopNode field %s", name)
@@ -17974,39 +18191,42 @@ func (m *SopNodeMutation) ResetEdge(name string) error {
 // SopStageMutation represents an operation that mutates the SopStage nodes in the graph.
 type SopStageMutation struct {
 	config
-	op                    Op
-	typ                   string
-	id                    *uint64
-	created_at            *time.Time
-	updated_at            *time.Time
-	status                *uint8
-	addstatus             *int8
-	deleted_at            *time.Time
-	name                  *string
-	condition_type        *int
-	addcondition_type     *int
-	condition_operator    *int
-	addcondition_operator *int
-	condition_list        *[]custom_types.Condition
-	appendcondition_list  []custom_types.Condition
-	action_message        *[]custom_types.Action
-	appendaction_message  []custom_types.Action
-	action_label          *[]uint64
-	appendaction_label    []uint64
-	index_sort            *int
-	addindex_sort         *int
-	clearedFields         map[string]struct{}
-	sop_task              *uint64
-	clearedsop_task       bool
-	stage_nodes           map[uint64]struct{}
-	removedstage_nodes    map[uint64]struct{}
-	clearedstage_nodes    bool
-	stage_messages        map[uint64]struct{}
-	removedstage_messages map[uint64]struct{}
-	clearedstage_messages bool
-	done                  bool
-	oldValue              func(context.Context) (*SopStage, error)
-	predicates            []predicate.SopStage
+	op                     Op
+	typ                    string
+	id                     *uint64
+	created_at             *time.Time
+	updated_at             *time.Time
+	status                 *uint8
+	addstatus              *int8
+	deleted_at             *time.Time
+	name                   *string
+	condition_type         *int
+	addcondition_type      *int
+	condition_operator     *int
+	addcondition_operator  *int
+	condition_list         *[]custom_types.Condition
+	appendcondition_list   []custom_types.Condition
+	action_message         *[]custom_types.Action
+	appendaction_message   []custom_types.Action
+	action_label_add       *[]uint64
+	appendaction_label_add []uint64
+	action_label_del       *[]uint64
+	appendaction_label_del []uint64
+	action_forward         **custom_types.ActionForward
+	index_sort             *int
+	addindex_sort          *int
+	clearedFields          map[string]struct{}
+	sop_task               *uint64
+	clearedsop_task        bool
+	stage_nodes            map[uint64]struct{}
+	removedstage_nodes     map[uint64]struct{}
+	clearedstage_nodes     bool
+	stage_messages         map[uint64]struct{}
+	removedstage_messages  map[uint64]struct{}
+	clearedstage_messages  bool
+	done                   bool
+	oldValue               func(context.Context) (*SopStage, error)
+	predicates             []predicate.SopStage
 }
 
 var _ ent.Mutation = (*SopStageMutation)(nil)
@@ -18604,69 +18824,183 @@ func (m *SopStageMutation) ResetActionMessage() {
 	delete(m.clearedFields, sopstage.FieldActionMessage)
 }
 
-// SetActionLabel sets the "action_label" field.
-func (m *SopStageMutation) SetActionLabel(u []uint64) {
-	m.action_label = &u
-	m.appendaction_label = nil
+// SetActionLabelAdd sets the "action_label_add" field.
+func (m *SopStageMutation) SetActionLabelAdd(u []uint64) {
+	m.action_label_add = &u
+	m.appendaction_label_add = nil
 }
 
-// ActionLabel returns the value of the "action_label" field in the mutation.
-func (m *SopStageMutation) ActionLabel() (r []uint64, exists bool) {
-	v := m.action_label
+// ActionLabelAdd returns the value of the "action_label_add" field in the mutation.
+func (m *SopStageMutation) ActionLabelAdd() (r []uint64, exists bool) {
+	v := m.action_label_add
 	if v == nil {
 		return
 	}
 	return *v, true
 }
 
-// OldActionLabel returns the old "action_label" field's value of the SopStage entity.
+// OldActionLabelAdd returns the old "action_label_add" field's value of the SopStage entity.
 // If the SopStage 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 *SopStageMutation) OldActionLabel(ctx context.Context) (v []uint64, err error) {
+func (m *SopStageMutation) OldActionLabelAdd(ctx context.Context) (v []uint64, err error) {
 	if !m.op.Is(OpUpdateOne) {
-		return v, errors.New("OldActionLabel is only allowed on UpdateOne operations")
+		return v, errors.New("OldActionLabelAdd is only allowed on UpdateOne operations")
 	}
 	if m.id == nil || m.oldValue == nil {
-		return v, errors.New("OldActionLabel requires an ID field in the mutation")
+		return v, errors.New("OldActionLabelAdd requires an ID field in the mutation")
 	}
 	oldValue, err := m.oldValue(ctx)
 	if err != nil {
-		return v, fmt.Errorf("querying old value for OldActionLabel: %w", err)
+		return v, fmt.Errorf("querying old value for OldActionLabelAdd: %w", err)
 	}
-	return oldValue.ActionLabel, nil
+	return oldValue.ActionLabelAdd, nil
 }
 
-// AppendActionLabel adds u to the "action_label" field.
-func (m *SopStageMutation) AppendActionLabel(u []uint64) {
-	m.appendaction_label = append(m.appendaction_label, u...)
+// AppendActionLabelAdd adds u to the "action_label_add" field.
+func (m *SopStageMutation) AppendActionLabelAdd(u []uint64) {
+	m.appendaction_label_add = append(m.appendaction_label_add, u...)
 }
 
-// AppendedActionLabel returns the list of values that were appended to the "action_label" field in this mutation.
-func (m *SopStageMutation) AppendedActionLabel() ([]uint64, bool) {
-	if len(m.appendaction_label) == 0 {
+// AppendedActionLabelAdd returns the list of values that were appended to the "action_label_add" field in this mutation.
+func (m *SopStageMutation) AppendedActionLabelAdd() ([]uint64, bool) {
+	if len(m.appendaction_label_add) == 0 {
 		return nil, false
 	}
-	return m.appendaction_label, true
+	return m.appendaction_label_add, true
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (m *SopStageMutation) ClearActionLabel() {
-	m.action_label = nil
-	m.appendaction_label = nil
-	m.clearedFields[sopstage.FieldActionLabel] = struct{}{}
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (m *SopStageMutation) ClearActionLabelAdd() {
+	m.action_label_add = nil
+	m.appendaction_label_add = nil
+	m.clearedFields[sopstage.FieldActionLabelAdd] = struct{}{}
 }
 
-// ActionLabelCleared returns if the "action_label" field was cleared in this mutation.
-func (m *SopStageMutation) ActionLabelCleared() bool {
-	_, ok := m.clearedFields[sopstage.FieldActionLabel]
+// ActionLabelAddCleared returns if the "action_label_add" field was cleared in this mutation.
+func (m *SopStageMutation) ActionLabelAddCleared() bool {
+	_, ok := m.clearedFields[sopstage.FieldActionLabelAdd]
 	return ok
 }
 
-// ResetActionLabel resets all changes to the "action_label" field.
-func (m *SopStageMutation) ResetActionLabel() {
-	m.action_label = nil
-	m.appendaction_label = nil
-	delete(m.clearedFields, sopstage.FieldActionLabel)
+// ResetActionLabelAdd resets all changes to the "action_label_add" field.
+func (m *SopStageMutation) ResetActionLabelAdd() {
+	m.action_label_add = nil
+	m.appendaction_label_add = nil
+	delete(m.clearedFields, sopstage.FieldActionLabelAdd)
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (m *SopStageMutation) SetActionLabelDel(u []uint64) {
+	m.action_label_del = &u
+	m.appendaction_label_del = nil
+}
+
+// ActionLabelDel returns the value of the "action_label_del" field in the mutation.
+func (m *SopStageMutation) ActionLabelDel() (r []uint64, exists bool) {
+	v := m.action_label_del
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldActionLabelDel returns the old "action_label_del" field's value of the SopStage entity.
+// If the SopStage 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 *SopStageMutation) OldActionLabelDel(ctx context.Context) (v []uint64, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldActionLabelDel is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldActionLabelDel requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldActionLabelDel: %w", err)
+	}
+	return oldValue.ActionLabelDel, nil
+}
+
+// AppendActionLabelDel adds u to the "action_label_del" field.
+func (m *SopStageMutation) AppendActionLabelDel(u []uint64) {
+	m.appendaction_label_del = append(m.appendaction_label_del, u...)
+}
+
+// AppendedActionLabelDel returns the list of values that were appended to the "action_label_del" field in this mutation.
+func (m *SopStageMutation) AppendedActionLabelDel() ([]uint64, bool) {
+	if len(m.appendaction_label_del) == 0 {
+		return nil, false
+	}
+	return m.appendaction_label_del, true
+}
+
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (m *SopStageMutation) ClearActionLabelDel() {
+	m.action_label_del = nil
+	m.appendaction_label_del = nil
+	m.clearedFields[sopstage.FieldActionLabelDel] = struct{}{}
+}
+
+// ActionLabelDelCleared returns if the "action_label_del" field was cleared in this mutation.
+func (m *SopStageMutation) ActionLabelDelCleared() bool {
+	_, ok := m.clearedFields[sopstage.FieldActionLabelDel]
+	return ok
+}
+
+// ResetActionLabelDel resets all changes to the "action_label_del" field.
+func (m *SopStageMutation) ResetActionLabelDel() {
+	m.action_label_del = nil
+	m.appendaction_label_del = nil
+	delete(m.clearedFields, sopstage.FieldActionLabelDel)
+}
+
+// SetActionForward sets the "action_forward" field.
+func (m *SopStageMutation) SetActionForward(ctf *custom_types.ActionForward) {
+	m.action_forward = &ctf
+}
+
+// ActionForward returns the value of the "action_forward" field in the mutation.
+func (m *SopStageMutation) ActionForward() (r *custom_types.ActionForward, exists bool) {
+	v := m.action_forward
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldActionForward returns the old "action_forward" field's value of the SopStage entity.
+// If the SopStage 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 *SopStageMutation) OldActionForward(ctx context.Context) (v *custom_types.ActionForward, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldActionForward is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldActionForward requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldActionForward: %w", err)
+	}
+	return oldValue.ActionForward, nil
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (m *SopStageMutation) ClearActionForward() {
+	m.action_forward = nil
+	m.clearedFields[sopstage.FieldActionForward] = struct{}{}
+}
+
+// ActionForwardCleared returns if the "action_forward" field was cleared in this mutation.
+func (m *SopStageMutation) ActionForwardCleared() bool {
+	_, ok := m.clearedFields[sopstage.FieldActionForward]
+	return ok
+}
+
+// ResetActionForward resets all changes to the "action_forward" field.
+func (m *SopStageMutation) ResetActionForward() {
+	m.action_forward = nil
+	delete(m.clearedFields, sopstage.FieldActionForward)
 }
 
 // SetIndexSort sets the "index_sort" field.
@@ -18921,7 +19255,7 @@ func (m *SopStageMutation) Type() string {
 // order to get all numeric fields that were incremented/decremented, call
 // AddedFields().
 func (m *SopStageMutation) Fields() []string {
-	fields := make([]string, 0, 12)
+	fields := make([]string, 0, 14)
 	if m.created_at != nil {
 		fields = append(fields, sopstage.FieldCreatedAt)
 	}
@@ -18952,8 +19286,14 @@ func (m *SopStageMutation) Fields() []string {
 	if m.action_message != nil {
 		fields = append(fields, sopstage.FieldActionMessage)
 	}
-	if m.action_label != nil {
-		fields = append(fields, sopstage.FieldActionLabel)
+	if m.action_label_add != nil {
+		fields = append(fields, sopstage.FieldActionLabelAdd)
+	}
+	if m.action_label_del != nil {
+		fields = append(fields, sopstage.FieldActionLabelDel)
+	}
+	if m.action_forward != nil {
+		fields = append(fields, sopstage.FieldActionForward)
 	}
 	if m.index_sort != nil {
 		fields = append(fields, sopstage.FieldIndexSort)
@@ -18986,8 +19326,12 @@ func (m *SopStageMutation) Field(name string) (ent.Value, bool) {
 		return m.ConditionList()
 	case sopstage.FieldActionMessage:
 		return m.ActionMessage()
-	case sopstage.FieldActionLabel:
-		return m.ActionLabel()
+	case sopstage.FieldActionLabelAdd:
+		return m.ActionLabelAdd()
+	case sopstage.FieldActionLabelDel:
+		return m.ActionLabelDel()
+	case sopstage.FieldActionForward:
+		return m.ActionForward()
 	case sopstage.FieldIndexSort:
 		return m.IndexSort()
 	}
@@ -19019,8 +19363,12 @@ func (m *SopStageMutation) OldField(ctx context.Context, name string) (ent.Value
 		return m.OldConditionList(ctx)
 	case sopstage.FieldActionMessage:
 		return m.OldActionMessage(ctx)
-	case sopstage.FieldActionLabel:
-		return m.OldActionLabel(ctx)
+	case sopstage.FieldActionLabelAdd:
+		return m.OldActionLabelAdd(ctx)
+	case sopstage.FieldActionLabelDel:
+		return m.OldActionLabelDel(ctx)
+	case sopstage.FieldActionForward:
+		return m.OldActionForward(ctx)
 	case sopstage.FieldIndexSort:
 		return m.OldIndexSort(ctx)
 	}
@@ -19102,12 +19450,26 @@ func (m *SopStageMutation) SetField(name string, value ent.Value) error {
 		}
 		m.SetActionMessage(v)
 		return nil
-	case sopstage.FieldActionLabel:
+	case sopstage.FieldActionLabelAdd:
 		v, ok := value.([]uint64)
 		if !ok {
 			return fmt.Errorf("unexpected type %T for field %s", value, name)
 		}
-		m.SetActionLabel(v)
+		m.SetActionLabelAdd(v)
+		return nil
+	case sopstage.FieldActionLabelDel:
+		v, ok := value.([]uint64)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetActionLabelDel(v)
+		return nil
+	case sopstage.FieldActionForward:
+		v, ok := value.(*custom_types.ActionForward)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetActionForward(v)
 		return nil
 	case sopstage.FieldIndexSort:
 		v, ok := value.(int)
@@ -19206,8 +19568,14 @@ func (m *SopStageMutation) ClearedFields() []string {
 	if m.FieldCleared(sopstage.FieldActionMessage) {
 		fields = append(fields, sopstage.FieldActionMessage)
 	}
-	if m.FieldCleared(sopstage.FieldActionLabel) {
-		fields = append(fields, sopstage.FieldActionLabel)
+	if m.FieldCleared(sopstage.FieldActionLabelAdd) {
+		fields = append(fields, sopstage.FieldActionLabelAdd)
+	}
+	if m.FieldCleared(sopstage.FieldActionLabelDel) {
+		fields = append(fields, sopstage.FieldActionLabelDel)
+	}
+	if m.FieldCleared(sopstage.FieldActionForward) {
+		fields = append(fields, sopstage.FieldActionForward)
 	}
 	if m.FieldCleared(sopstage.FieldIndexSort) {
 		fields = append(fields, sopstage.FieldIndexSort)
@@ -19235,8 +19603,14 @@ func (m *SopStageMutation) ClearField(name string) error {
 	case sopstage.FieldActionMessage:
 		m.ClearActionMessage()
 		return nil
-	case sopstage.FieldActionLabel:
-		m.ClearActionLabel()
+	case sopstage.FieldActionLabelAdd:
+		m.ClearActionLabelAdd()
+		return nil
+	case sopstage.FieldActionLabelDel:
+		m.ClearActionLabelDel()
+		return nil
+	case sopstage.FieldActionForward:
+		m.ClearActionForward()
 		return nil
 	case sopstage.FieldIndexSort:
 		m.ClearIndexSort()
@@ -19279,8 +19653,14 @@ func (m *SopStageMutation) ResetField(name string) error {
 	case sopstage.FieldActionMessage:
 		m.ResetActionMessage()
 		return nil
-	case sopstage.FieldActionLabel:
-		m.ResetActionLabel()
+	case sopstage.FieldActionLabelAdd:
+		m.ResetActionLabelAdd()
+		return nil
+	case sopstage.FieldActionLabelDel:
+		m.ResetActionLabelDel()
+		return nil
+	case sopstage.FieldActionForward:
+		m.ResetActionForward()
 		return nil
 	case sopstage.FieldIndexSort:
 		m.ResetIndexSort()

+ 5 - 1
ent/runtime/runtime.go

@@ -714,6 +714,10 @@ func init() {
 	sopnodeDescNoReplyCondition := sopnodeFields[5].Descriptor()
 	// sopnode.DefaultNoReplyCondition holds the default value on creation for the no_reply_condition field.
 	sopnode.DefaultNoReplyCondition = sopnodeDescNoReplyCondition.Default.(uint64)
+	// sopnodeDescNoReplyUnit is the schema descriptor for no_reply_unit field.
+	sopnodeDescNoReplyUnit := sopnodeFields[6].Descriptor()
+	// sopnode.DefaultNoReplyUnit holds the default value on creation for the no_reply_unit field.
+	sopnode.DefaultNoReplyUnit = sopnodeDescNoReplyUnit.Default.(string)
 	sopstageMixin := schema.SopStage{}.Mixin()
 	sopstageMixinHooks2 := sopstageMixin[2].Hooks()
 	sopstage.Hooks[0] = sopstageMixinHooks2[0]
@@ -752,7 +756,7 @@ func init() {
 	// sopstage.DefaultConditionOperator holds the default value on creation for the condition_operator field.
 	sopstage.DefaultConditionOperator = sopstageDescConditionOperator.Default.(int)
 	// sopstageDescIndexSort is the schema descriptor for index_sort field.
-	sopstageDescIndexSort := sopstageFields[7].Descriptor()
+	sopstageDescIndexSort := sopstageFields[9].Descriptor()
 	// sopstage.DefaultIndexSort holds the default value on creation for the index_sort field.
 	sopstage.DefaultIndexSort = sopstageDescIndexSort.Default.(int)
 	soptaskMixin := schema.SopTask{}.Mixin()

+ 10 - 1
ent/schema/sop_node.go

@@ -37,12 +37,21 @@ func (SopNode) Fields() []ent.Field {
 		field.Uint64("no_reply_condition").Default(0).
 			Annotations(entsql.WithComments(true)).
 			Comment("超时触发时间(分钟)"),
+		field.String("no_reply_unit").Default("").
+			Annotations(entsql.WithComments(true)).
+			Comment("超时触发时间单位"),
 		field.JSON("action_message", []custom_types.Action{}).Optional().
 			Annotations(entsql.WithComments(true)).
 			Comment("命中后发送的消息内容"),
-		field.JSON("action_label", []uint64{}).Optional().
+		field.JSON("action_label_add", []uint64{}).Optional().
 			Annotations(entsql.WithComments(true)).
 			Comment("命中后需要打的标签"),
+		field.JSON("action_label_del", []uint64{}).Optional().
+			Annotations(entsql.WithComments(true)).
+			Comment("命中后需要移除的标签"),
+		field.JSON("action_forward", &custom_types.ActionForward{}).Optional().
+			Annotations(entsql.WithComments(true)).
+			Comment("命中后转发的消息"),
 	}
 }
 

+ 7 - 1
ent/schema/sop_stage.go

@@ -37,9 +37,15 @@ func (SopStage) Fields() []ent.Field {
 		field.JSON("action_message", []custom_types.Action{}).Optional().
 			Annotations(entsql.WithComments(true)).
 			Comment("命中后发送的消息内容"),
-		field.JSON("action_label", []uint64{}).Optional().
+		field.JSON("action_label_add", []uint64{}).Optional().
 			Annotations(entsql.WithComments(true)).
 			Comment("命中后需要打的标签"),
+		field.JSON("action_label_del", []uint64{}).Optional().
+			Annotations(entsql.WithComments(true)).
+			Comment("命中后需要移除的标签"),
+		field.JSON("action_forward", &custom_types.ActionForward{}).Optional().
+			Annotations(entsql.WithComments(true)).
+			Comment("命中后转发的消息"),
 		field.Int("index_sort").Default(1).Optional().
 			Annotations(entsql.WithComments(true)).
 			Comment("阶段顺序"),

+ 132 - 12
ent/set_not_nil.go

@@ -3704,6 +3704,30 @@ func (sn *SopNodeCreate) SetNotNilNoReplyCondition(value *uint64) *SopNodeCreate
 }
 
 // set field if value's pointer is not nil.
+func (sn *SopNodeUpdate) SetNotNilNoReplyUnit(value *string) *SopNodeUpdate {
+	if value != nil {
+		return sn.SetNoReplyUnit(*value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
+func (sn *SopNodeUpdateOne) SetNotNilNoReplyUnit(value *string) *SopNodeUpdateOne {
+	if value != nil {
+		return sn.SetNoReplyUnit(*value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
+func (sn *SopNodeCreate) SetNotNilNoReplyUnit(value *string) *SopNodeCreate {
+	if value != nil {
+		return sn.SetNoReplyUnit(*value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
 func (sn *SopNodeUpdate) SetNotNilActionMessage(value []custom_types.Action) *SopNodeUpdate {
 	if value != nil {
 		return sn.SetActionMessage(value)
@@ -3728,25 +3752,73 @@ func (sn *SopNodeCreate) SetNotNilActionMessage(value []custom_types.Action) *So
 }
 
 // set field if value's pointer is not nil.
-func (sn *SopNodeUpdate) SetNotNilActionLabel(value []uint64) *SopNodeUpdate {
+func (sn *SopNodeUpdate) SetNotNilActionLabelAdd(value []uint64) *SopNodeUpdate {
+	if value != nil {
+		return sn.SetActionLabelAdd(value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
+func (sn *SopNodeUpdateOne) SetNotNilActionLabelAdd(value []uint64) *SopNodeUpdateOne {
+	if value != nil {
+		return sn.SetActionLabelAdd(value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
+func (sn *SopNodeCreate) SetNotNilActionLabelAdd(value []uint64) *SopNodeCreate {
+	if value != nil {
+		return sn.SetActionLabelAdd(value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
+func (sn *SopNodeUpdate) SetNotNilActionLabelDel(value []uint64) *SopNodeUpdate {
+	if value != nil {
+		return sn.SetActionLabelDel(value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
+func (sn *SopNodeUpdateOne) SetNotNilActionLabelDel(value []uint64) *SopNodeUpdateOne {
+	if value != nil {
+		return sn.SetActionLabelDel(value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
+func (sn *SopNodeCreate) SetNotNilActionLabelDel(value []uint64) *SopNodeCreate {
+	if value != nil {
+		return sn.SetActionLabelDel(value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
+func (sn *SopNodeUpdate) SetNotNilActionForward(value **custom_types.ActionForward) *SopNodeUpdate {
 	if value != nil {
-		return sn.SetActionLabel(value)
+		return sn.SetActionForward(*value)
 	}
 	return sn
 }
 
 // set field if value's pointer is not nil.
-func (sn *SopNodeUpdateOne) SetNotNilActionLabel(value []uint64) *SopNodeUpdateOne {
+func (sn *SopNodeUpdateOne) SetNotNilActionForward(value **custom_types.ActionForward) *SopNodeUpdateOne {
 	if value != nil {
-		return sn.SetActionLabel(value)
+		return sn.SetActionForward(*value)
 	}
 	return sn
 }
 
 // set field if value's pointer is not nil.
-func (sn *SopNodeCreate) SetNotNilActionLabel(value []uint64) *SopNodeCreate {
+func (sn *SopNodeCreate) SetNotNilActionForward(value **custom_types.ActionForward) *SopNodeCreate {
 	if value != nil {
-		return sn.SetActionLabel(value)
+		return sn.SetActionForward(*value)
 	}
 	return sn
 }
@@ -3968,25 +4040,73 @@ func (ss *SopStageCreate) SetNotNilActionMessage(value []custom_types.Action) *S
 }
 
 // set field if value's pointer is not nil.
-func (ss *SopStageUpdate) SetNotNilActionLabel(value []uint64) *SopStageUpdate {
+func (ss *SopStageUpdate) SetNotNilActionLabelAdd(value []uint64) *SopStageUpdate {
+	if value != nil {
+		return ss.SetActionLabelAdd(value)
+	}
+	return ss
+}
+
+// set field if value's pointer is not nil.
+func (ss *SopStageUpdateOne) SetNotNilActionLabelAdd(value []uint64) *SopStageUpdateOne {
+	if value != nil {
+		return ss.SetActionLabelAdd(value)
+	}
+	return ss
+}
+
+// set field if value's pointer is not nil.
+func (ss *SopStageCreate) SetNotNilActionLabelAdd(value []uint64) *SopStageCreate {
+	if value != nil {
+		return ss.SetActionLabelAdd(value)
+	}
+	return ss
+}
+
+// set field if value's pointer is not nil.
+func (ss *SopStageUpdate) SetNotNilActionLabelDel(value []uint64) *SopStageUpdate {
+	if value != nil {
+		return ss.SetActionLabelDel(value)
+	}
+	return ss
+}
+
+// set field if value's pointer is not nil.
+func (ss *SopStageUpdateOne) SetNotNilActionLabelDel(value []uint64) *SopStageUpdateOne {
+	if value != nil {
+		return ss.SetActionLabelDel(value)
+	}
+	return ss
+}
+
+// set field if value's pointer is not nil.
+func (ss *SopStageCreate) SetNotNilActionLabelDel(value []uint64) *SopStageCreate {
+	if value != nil {
+		return ss.SetActionLabelDel(value)
+	}
+	return ss
+}
+
+// set field if value's pointer is not nil.
+func (ss *SopStageUpdate) SetNotNilActionForward(value **custom_types.ActionForward) *SopStageUpdate {
 	if value != nil {
-		return ss.SetActionLabel(value)
+		return ss.SetActionForward(*value)
 	}
 	return ss
 }
 
 // set field if value's pointer is not nil.
-func (ss *SopStageUpdateOne) SetNotNilActionLabel(value []uint64) *SopStageUpdateOne {
+func (ss *SopStageUpdateOne) SetNotNilActionForward(value **custom_types.ActionForward) *SopStageUpdateOne {
 	if value != nil {
-		return ss.SetActionLabel(value)
+		return ss.SetActionForward(*value)
 	}
 	return ss
 }
 
 // set field if value's pointer is not nil.
-func (ss *SopStageCreate) SetNotNilActionLabel(value []uint64) *SopStageCreate {
+func (ss *SopStageCreate) SetNotNilActionForward(value **custom_types.ActionForward) *SopStageCreate {
 	if value != nil {
-		return ss.SetActionLabel(value)
+		return ss.SetActionForward(*value)
 	}
 	return ss
 }

+ 46 - 9
ent/sopnode.go

@@ -40,10 +40,16 @@ type SopNode struct {
 	ConditionList []string `json:"condition_list,omitempty"`
 	// 超时触发时间(分钟)
 	NoReplyCondition uint64 `json:"no_reply_condition,omitempty"`
+	// 超时触发时间单位
+	NoReplyUnit string `json:"no_reply_unit,omitempty"`
 	// 命中后发送的消息内容
 	ActionMessage []custom_types.Action `json:"action_message,omitempty"`
 	// 命中后需要打的标签
-	ActionLabel []uint64 `json:"action_label,omitempty"`
+	ActionLabelAdd []uint64 `json:"action_label_add,omitempty"`
+	// 命中后需要移除的标签
+	ActionLabelDel []uint64 `json:"action_label_del,omitempty"`
+	// 命中后转发的消息
+	ActionForward *custom_types.ActionForward `json:"action_forward,omitempty"`
 	// Edges holds the relations/edges for other nodes in the graph.
 	// The values are being populated by the SopNodeQuery when eager-loading is set.
 	Edges        SopNodeEdges `json:"edges"`
@@ -86,11 +92,11 @@ func (*SopNode) scanValues(columns []string) ([]any, error) {
 	values := make([]any, len(columns))
 	for i := range columns {
 		switch columns[i] {
-		case sopnode.FieldConditionList, sopnode.FieldActionMessage, sopnode.FieldActionLabel:
+		case sopnode.FieldConditionList, sopnode.FieldActionMessage, sopnode.FieldActionLabelAdd, sopnode.FieldActionLabelDel, sopnode.FieldActionForward:
 			values[i] = new([]byte)
 		case sopnode.FieldID, sopnode.FieldStatus, sopnode.FieldStageID, sopnode.FieldParentID, sopnode.FieldConditionType, sopnode.FieldNoReplyCondition:
 			values[i] = new(sql.NullInt64)
-		case sopnode.FieldName:
+		case sopnode.FieldName, sopnode.FieldNoReplyUnit:
 			values[i] = new(sql.NullString)
 		case sopnode.FieldCreatedAt, sopnode.FieldUpdatedAt, sopnode.FieldDeletedAt:
 			values[i] = new(sql.NullTime)
@@ -177,6 +183,12 @@ func (sn *SopNode) assignValues(columns []string, values []any) error {
 			} else if value.Valid {
 				sn.NoReplyCondition = uint64(value.Int64)
 			}
+		case sopnode.FieldNoReplyUnit:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field no_reply_unit", values[i])
+			} else if value.Valid {
+				sn.NoReplyUnit = value.String
+			}
 		case sopnode.FieldActionMessage:
 			if value, ok := values[i].(*[]byte); !ok {
 				return fmt.Errorf("unexpected type %T for field action_message", values[i])
@@ -185,12 +197,28 @@ func (sn *SopNode) assignValues(columns []string, values []any) error {
 					return fmt.Errorf("unmarshal field action_message: %w", err)
 				}
 			}
-		case sopnode.FieldActionLabel:
+		case sopnode.FieldActionLabelAdd:
+			if value, ok := values[i].(*[]byte); !ok {
+				return fmt.Errorf("unexpected type %T for field action_label_add", values[i])
+			} else if value != nil && len(*value) > 0 {
+				if err := json.Unmarshal(*value, &sn.ActionLabelAdd); err != nil {
+					return fmt.Errorf("unmarshal field action_label_add: %w", err)
+				}
+			}
+		case sopnode.FieldActionLabelDel:
 			if value, ok := values[i].(*[]byte); !ok {
-				return fmt.Errorf("unexpected type %T for field action_label", values[i])
+				return fmt.Errorf("unexpected type %T for field action_label_del", values[i])
 			} else if value != nil && len(*value) > 0 {
-				if err := json.Unmarshal(*value, &sn.ActionLabel); err != nil {
-					return fmt.Errorf("unmarshal field action_label: %w", err)
+				if err := json.Unmarshal(*value, &sn.ActionLabelDel); err != nil {
+					return fmt.Errorf("unmarshal field action_label_del: %w", err)
+				}
+			}
+		case sopnode.FieldActionForward:
+			if value, ok := values[i].(*[]byte); !ok {
+				return fmt.Errorf("unexpected type %T for field action_forward", values[i])
+			} else if value != nil && len(*value) > 0 {
+				if err := json.Unmarshal(*value, &sn.ActionForward); err != nil {
+					return fmt.Errorf("unmarshal field action_forward: %w", err)
 				}
 			}
 		default:
@@ -269,11 +297,20 @@ func (sn *SopNode) String() string {
 	builder.WriteString("no_reply_condition=")
 	builder.WriteString(fmt.Sprintf("%v", sn.NoReplyCondition))
 	builder.WriteString(", ")
+	builder.WriteString("no_reply_unit=")
+	builder.WriteString(sn.NoReplyUnit)
+	builder.WriteString(", ")
 	builder.WriteString("action_message=")
 	builder.WriteString(fmt.Sprintf("%v", sn.ActionMessage))
 	builder.WriteString(", ")
-	builder.WriteString("action_label=")
-	builder.WriteString(fmt.Sprintf("%v", sn.ActionLabel))
+	builder.WriteString("action_label_add=")
+	builder.WriteString(fmt.Sprintf("%v", sn.ActionLabelAdd))
+	builder.WriteString(", ")
+	builder.WriteString("action_label_del=")
+	builder.WriteString(fmt.Sprintf("%v", sn.ActionLabelDel))
+	builder.WriteString(", ")
+	builder.WriteString("action_forward=")
+	builder.WriteString(fmt.Sprintf("%v", sn.ActionForward))
 	builder.WriteByte(')')
 	return builder.String()
 }

+ 19 - 3
ent/sopnode/sopnode.go

@@ -35,10 +35,16 @@ const (
 	FieldConditionList = "condition_list"
 	// FieldNoReplyCondition holds the string denoting the no_reply_condition field in the database.
 	FieldNoReplyCondition = "no_reply_condition"
+	// FieldNoReplyUnit holds the string denoting the no_reply_unit field in the database.
+	FieldNoReplyUnit = "no_reply_unit"
 	// FieldActionMessage holds the string denoting the action_message field in the database.
 	FieldActionMessage = "action_message"
-	// FieldActionLabel holds the string denoting the action_label field in the database.
-	FieldActionLabel = "action_label"
+	// FieldActionLabelAdd holds the string denoting the action_label_add field in the database.
+	FieldActionLabelAdd = "action_label_add"
+	// FieldActionLabelDel holds the string denoting the action_label_del field in the database.
+	FieldActionLabelDel = "action_label_del"
+	// FieldActionForward holds the string denoting the action_forward field in the database.
+	FieldActionForward = "action_forward"
 	// EdgeSopStage holds the string denoting the sop_stage edge name in mutations.
 	EdgeSopStage = "sop_stage"
 	// EdgeNodeMessages holds the string denoting the node_messages edge name in mutations.
@@ -74,8 +80,11 @@ var Columns = []string{
 	FieldConditionType,
 	FieldConditionList,
 	FieldNoReplyCondition,
+	FieldNoReplyUnit,
 	FieldActionMessage,
-	FieldActionLabel,
+	FieldActionLabelAdd,
+	FieldActionLabelDel,
+	FieldActionForward,
 }
 
 // ValidColumn reports if the column name is valid (part of the table columns).
@@ -110,6 +119,8 @@ var (
 	DefaultConditionType int
 	// DefaultNoReplyCondition holds the default value on creation for the "no_reply_condition" field.
 	DefaultNoReplyCondition uint64
+	// DefaultNoReplyUnit holds the default value on creation for the "no_reply_unit" field.
+	DefaultNoReplyUnit string
 )
 
 // OrderOption defines the ordering options for the SopNode queries.
@@ -165,6 +176,11 @@ func ByNoReplyCondition(opts ...sql.OrderTermOption) OrderOption {
 	return sql.OrderByField(FieldNoReplyCondition, opts...).ToFunc()
 }
 
+// ByNoReplyUnit orders the results by the no_reply_unit field.
+func ByNoReplyUnit(opts ...sql.OrderTermOption) OrderOption {
+	return sql.OrderByField(FieldNoReplyUnit, opts...).ToFunc()
+}
+
 // BySopStageField orders the results by sop_stage field.
 func BySopStageField(field string, opts ...sql.OrderTermOption) OrderOption {
 	return func(s *sql.Selector) {

+ 96 - 6
ent/sopnode/where.go

@@ -100,6 +100,11 @@ func NoReplyCondition(v uint64) predicate.SopNode {
 	return predicate.SopNode(sql.FieldEQ(FieldNoReplyCondition, v))
 }
 
+// NoReplyUnit applies equality check predicate on the "no_reply_unit" field. It's identical to NoReplyUnitEQ.
+func NoReplyUnit(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldEQ(FieldNoReplyUnit, v))
+}
+
 // CreatedAtEQ applies the EQ predicate on the "created_at" field.
 func CreatedAtEQ(v time.Time) predicate.SopNode {
 	return predicate.SopNode(sql.FieldEQ(FieldCreatedAt, v))
@@ -495,6 +500,71 @@ func NoReplyConditionLTE(v uint64) predicate.SopNode {
 	return predicate.SopNode(sql.FieldLTE(FieldNoReplyCondition, v))
 }
 
+// NoReplyUnitEQ applies the EQ predicate on the "no_reply_unit" field.
+func NoReplyUnitEQ(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldEQ(FieldNoReplyUnit, v))
+}
+
+// NoReplyUnitNEQ applies the NEQ predicate on the "no_reply_unit" field.
+func NoReplyUnitNEQ(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldNEQ(FieldNoReplyUnit, v))
+}
+
+// NoReplyUnitIn applies the In predicate on the "no_reply_unit" field.
+func NoReplyUnitIn(vs ...string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldIn(FieldNoReplyUnit, vs...))
+}
+
+// NoReplyUnitNotIn applies the NotIn predicate on the "no_reply_unit" field.
+func NoReplyUnitNotIn(vs ...string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldNotIn(FieldNoReplyUnit, vs...))
+}
+
+// NoReplyUnitGT applies the GT predicate on the "no_reply_unit" field.
+func NoReplyUnitGT(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldGT(FieldNoReplyUnit, v))
+}
+
+// NoReplyUnitGTE applies the GTE predicate on the "no_reply_unit" field.
+func NoReplyUnitGTE(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldGTE(FieldNoReplyUnit, v))
+}
+
+// NoReplyUnitLT applies the LT predicate on the "no_reply_unit" field.
+func NoReplyUnitLT(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldLT(FieldNoReplyUnit, v))
+}
+
+// NoReplyUnitLTE applies the LTE predicate on the "no_reply_unit" field.
+func NoReplyUnitLTE(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldLTE(FieldNoReplyUnit, v))
+}
+
+// NoReplyUnitContains applies the Contains predicate on the "no_reply_unit" field.
+func NoReplyUnitContains(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldContains(FieldNoReplyUnit, v))
+}
+
+// NoReplyUnitHasPrefix applies the HasPrefix predicate on the "no_reply_unit" field.
+func NoReplyUnitHasPrefix(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldHasPrefix(FieldNoReplyUnit, v))
+}
+
+// NoReplyUnitHasSuffix applies the HasSuffix predicate on the "no_reply_unit" field.
+func NoReplyUnitHasSuffix(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldHasSuffix(FieldNoReplyUnit, v))
+}
+
+// NoReplyUnitEqualFold applies the EqualFold predicate on the "no_reply_unit" field.
+func NoReplyUnitEqualFold(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldEqualFold(FieldNoReplyUnit, v))
+}
+
+// NoReplyUnitContainsFold applies the ContainsFold predicate on the "no_reply_unit" field.
+func NoReplyUnitContainsFold(v string) predicate.SopNode {
+	return predicate.SopNode(sql.FieldContainsFold(FieldNoReplyUnit, v))
+}
+
 // ActionMessageIsNil applies the IsNil predicate on the "action_message" field.
 func ActionMessageIsNil() predicate.SopNode {
 	return predicate.SopNode(sql.FieldIsNull(FieldActionMessage))
@@ -505,14 +575,34 @@ func ActionMessageNotNil() predicate.SopNode {
 	return predicate.SopNode(sql.FieldNotNull(FieldActionMessage))
 }
 
-// ActionLabelIsNil applies the IsNil predicate on the "action_label" field.
-func ActionLabelIsNil() predicate.SopNode {
-	return predicate.SopNode(sql.FieldIsNull(FieldActionLabel))
+// ActionLabelAddIsNil applies the IsNil predicate on the "action_label_add" field.
+func ActionLabelAddIsNil() predicate.SopNode {
+	return predicate.SopNode(sql.FieldIsNull(FieldActionLabelAdd))
+}
+
+// ActionLabelAddNotNil applies the NotNil predicate on the "action_label_add" field.
+func ActionLabelAddNotNil() predicate.SopNode {
+	return predicate.SopNode(sql.FieldNotNull(FieldActionLabelAdd))
+}
+
+// ActionLabelDelIsNil applies the IsNil predicate on the "action_label_del" field.
+func ActionLabelDelIsNil() predicate.SopNode {
+	return predicate.SopNode(sql.FieldIsNull(FieldActionLabelDel))
+}
+
+// ActionLabelDelNotNil applies the NotNil predicate on the "action_label_del" field.
+func ActionLabelDelNotNil() predicate.SopNode {
+	return predicate.SopNode(sql.FieldNotNull(FieldActionLabelDel))
+}
+
+// ActionForwardIsNil applies the IsNil predicate on the "action_forward" field.
+func ActionForwardIsNil() predicate.SopNode {
+	return predicate.SopNode(sql.FieldIsNull(FieldActionForward))
 }
 
-// ActionLabelNotNil applies the NotNil predicate on the "action_label" field.
-func ActionLabelNotNil() predicate.SopNode {
-	return predicate.SopNode(sql.FieldNotNull(FieldActionLabel))
+// ActionForwardNotNil applies the NotNil predicate on the "action_forward" field.
+func ActionForwardNotNil() predicate.SopNode {
+	return predicate.SopNode(sql.FieldNotNull(FieldActionForward))
 }
 
 // HasSopStage applies the HasEdge predicate on the "sop_stage" edge.

+ 238 - 33
ent/sopnode_create.go

@@ -141,15 +141,41 @@ func (snc *SopNodeCreate) SetNillableNoReplyCondition(u *uint64) *SopNodeCreate
 	return snc
 }
 
+// SetNoReplyUnit sets the "no_reply_unit" field.
+func (snc *SopNodeCreate) SetNoReplyUnit(s string) *SopNodeCreate {
+	snc.mutation.SetNoReplyUnit(s)
+	return snc
+}
+
+// SetNillableNoReplyUnit sets the "no_reply_unit" field if the given value is not nil.
+func (snc *SopNodeCreate) SetNillableNoReplyUnit(s *string) *SopNodeCreate {
+	if s != nil {
+		snc.SetNoReplyUnit(*s)
+	}
+	return snc
+}
+
 // SetActionMessage sets the "action_message" field.
 func (snc *SopNodeCreate) SetActionMessage(ct []custom_types.Action) *SopNodeCreate {
 	snc.mutation.SetActionMessage(ct)
 	return snc
 }
 
-// SetActionLabel sets the "action_label" field.
-func (snc *SopNodeCreate) SetActionLabel(u []uint64) *SopNodeCreate {
-	snc.mutation.SetActionLabel(u)
+// SetActionLabelAdd sets the "action_label_add" field.
+func (snc *SopNodeCreate) SetActionLabelAdd(u []uint64) *SopNodeCreate {
+	snc.mutation.SetActionLabelAdd(u)
+	return snc
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (snc *SopNodeCreate) SetActionLabelDel(u []uint64) *SopNodeCreate {
+	snc.mutation.SetActionLabelDel(u)
+	return snc
+}
+
+// SetActionForward sets the "action_forward" field.
+func (snc *SopNodeCreate) SetActionForward(ctf *custom_types.ActionForward) *SopNodeCreate {
+	snc.mutation.SetActionForward(ctf)
 	return snc
 }
 
@@ -252,6 +278,10 @@ func (snc *SopNodeCreate) defaults() error {
 		v := sopnode.DefaultNoReplyCondition
 		snc.mutation.SetNoReplyCondition(v)
 	}
+	if _, ok := snc.mutation.NoReplyUnit(); !ok {
+		v := sopnode.DefaultNoReplyUnit
+		snc.mutation.SetNoReplyUnit(v)
+	}
 	return nil
 }
 
@@ -278,6 +308,9 @@ func (snc *SopNodeCreate) check() error {
 	if _, ok := snc.mutation.NoReplyCondition(); !ok {
 		return &ValidationError{Name: "no_reply_condition", err: errors.New(`ent: missing required field "SopNode.no_reply_condition"`)}
 	}
+	if _, ok := snc.mutation.NoReplyUnit(); !ok {
+		return &ValidationError{Name: "no_reply_unit", err: errors.New(`ent: missing required field "SopNode.no_reply_unit"`)}
+	}
 	if _, ok := snc.mutation.SopStageID(); !ok {
 		return &ValidationError{Name: "sop_stage", err: errors.New(`ent: missing required edge "SopNode.sop_stage"`)}
 	}
@@ -350,13 +383,25 @@ func (snc *SopNodeCreate) createSpec() (*SopNode, *sqlgraph.CreateSpec) {
 		_spec.SetField(sopnode.FieldNoReplyCondition, field.TypeUint64, value)
 		_node.NoReplyCondition = value
 	}
+	if value, ok := snc.mutation.NoReplyUnit(); ok {
+		_spec.SetField(sopnode.FieldNoReplyUnit, field.TypeString, value)
+		_node.NoReplyUnit = value
+	}
 	if value, ok := snc.mutation.ActionMessage(); ok {
 		_spec.SetField(sopnode.FieldActionMessage, field.TypeJSON, value)
 		_node.ActionMessage = value
 	}
-	if value, ok := snc.mutation.ActionLabel(); ok {
-		_spec.SetField(sopnode.FieldActionLabel, field.TypeJSON, value)
-		_node.ActionLabel = value
+	if value, ok := snc.mutation.ActionLabelAdd(); ok {
+		_spec.SetField(sopnode.FieldActionLabelAdd, field.TypeJSON, value)
+		_node.ActionLabelAdd = value
+	}
+	if value, ok := snc.mutation.ActionLabelDel(); ok {
+		_spec.SetField(sopnode.FieldActionLabelDel, field.TypeJSON, value)
+		_node.ActionLabelDel = value
+	}
+	if value, ok := snc.mutation.ActionForward(); ok {
+		_spec.SetField(sopnode.FieldActionForward, field.TypeJSON, value)
+		_node.ActionForward = value
 	}
 	if nodes := snc.mutation.SopStageIDs(); len(nodes) > 0 {
 		edge := &sqlgraph.EdgeSpec{
@@ -593,6 +638,18 @@ func (u *SopNodeUpsert) AddNoReplyCondition(v uint64) *SopNodeUpsert {
 	return u
 }
 
+// SetNoReplyUnit sets the "no_reply_unit" field.
+func (u *SopNodeUpsert) SetNoReplyUnit(v string) *SopNodeUpsert {
+	u.Set(sopnode.FieldNoReplyUnit, v)
+	return u
+}
+
+// UpdateNoReplyUnit sets the "no_reply_unit" field to the value that was provided on create.
+func (u *SopNodeUpsert) UpdateNoReplyUnit() *SopNodeUpsert {
+	u.SetExcluded(sopnode.FieldNoReplyUnit)
+	return u
+}
+
 // SetActionMessage sets the "action_message" field.
 func (u *SopNodeUpsert) SetActionMessage(v []custom_types.Action) *SopNodeUpsert {
 	u.Set(sopnode.FieldActionMessage, v)
@@ -611,21 +668,57 @@ func (u *SopNodeUpsert) ClearActionMessage() *SopNodeUpsert {
 	return u
 }
 
-// SetActionLabel sets the "action_label" field.
-func (u *SopNodeUpsert) SetActionLabel(v []uint64) *SopNodeUpsert {
-	u.Set(sopnode.FieldActionLabel, v)
+// SetActionLabelAdd sets the "action_label_add" field.
+func (u *SopNodeUpsert) SetActionLabelAdd(v []uint64) *SopNodeUpsert {
+	u.Set(sopnode.FieldActionLabelAdd, v)
+	return u
+}
+
+// UpdateActionLabelAdd sets the "action_label_add" field to the value that was provided on create.
+func (u *SopNodeUpsert) UpdateActionLabelAdd() *SopNodeUpsert {
+	u.SetExcluded(sopnode.FieldActionLabelAdd)
+	return u
+}
+
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (u *SopNodeUpsert) ClearActionLabelAdd() *SopNodeUpsert {
+	u.SetNull(sopnode.FieldActionLabelAdd)
+	return u
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (u *SopNodeUpsert) SetActionLabelDel(v []uint64) *SopNodeUpsert {
+	u.Set(sopnode.FieldActionLabelDel, v)
 	return u
 }
 
-// UpdateActionLabel sets the "action_label" field to the value that was provided on create.
-func (u *SopNodeUpsert) UpdateActionLabel() *SopNodeUpsert {
-	u.SetExcluded(sopnode.FieldActionLabel)
+// UpdateActionLabelDel sets the "action_label_del" field to the value that was provided on create.
+func (u *SopNodeUpsert) UpdateActionLabelDel() *SopNodeUpsert {
+	u.SetExcluded(sopnode.FieldActionLabelDel)
 	return u
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (u *SopNodeUpsert) ClearActionLabel() *SopNodeUpsert {
-	u.SetNull(sopnode.FieldActionLabel)
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (u *SopNodeUpsert) ClearActionLabelDel() *SopNodeUpsert {
+	u.SetNull(sopnode.FieldActionLabelDel)
+	return u
+}
+
+// SetActionForward sets the "action_forward" field.
+func (u *SopNodeUpsert) SetActionForward(v *custom_types.ActionForward) *SopNodeUpsert {
+	u.Set(sopnode.FieldActionForward, v)
+	return u
+}
+
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopNodeUpsert) UpdateActionForward() *SopNodeUpsert {
+	u.SetExcluded(sopnode.FieldActionForward)
+	return u
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopNodeUpsert) ClearActionForward() *SopNodeUpsert {
+	u.SetNull(sopnode.FieldActionForward)
 	return u
 }
 
@@ -855,6 +948,20 @@ func (u *SopNodeUpsertOne) UpdateNoReplyCondition() *SopNodeUpsertOne {
 	})
 }
 
+// SetNoReplyUnit sets the "no_reply_unit" field.
+func (u *SopNodeUpsertOne) SetNoReplyUnit(v string) *SopNodeUpsertOne {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.SetNoReplyUnit(v)
+	})
+}
+
+// UpdateNoReplyUnit sets the "no_reply_unit" field to the value that was provided on create.
+func (u *SopNodeUpsertOne) UpdateNoReplyUnit() *SopNodeUpsertOne {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.UpdateNoReplyUnit()
+	})
+}
+
 // SetActionMessage sets the "action_message" field.
 func (u *SopNodeUpsertOne) SetActionMessage(v []custom_types.Action) *SopNodeUpsertOne {
 	return u.Update(func(s *SopNodeUpsert) {
@@ -876,24 +983,66 @@ func (u *SopNodeUpsertOne) ClearActionMessage() *SopNodeUpsertOne {
 	})
 }
 
-// SetActionLabel sets the "action_label" field.
-func (u *SopNodeUpsertOne) SetActionLabel(v []uint64) *SopNodeUpsertOne {
+// SetActionLabelAdd sets the "action_label_add" field.
+func (u *SopNodeUpsertOne) SetActionLabelAdd(v []uint64) *SopNodeUpsertOne {
 	return u.Update(func(s *SopNodeUpsert) {
-		s.SetActionLabel(v)
+		s.SetActionLabelAdd(v)
 	})
 }
 
-// UpdateActionLabel sets the "action_label" field to the value that was provided on create.
-func (u *SopNodeUpsertOne) UpdateActionLabel() *SopNodeUpsertOne {
+// UpdateActionLabelAdd sets the "action_label_add" field to the value that was provided on create.
+func (u *SopNodeUpsertOne) UpdateActionLabelAdd() *SopNodeUpsertOne {
 	return u.Update(func(s *SopNodeUpsert) {
-		s.UpdateActionLabel()
+		s.UpdateActionLabelAdd()
 	})
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (u *SopNodeUpsertOne) ClearActionLabel() *SopNodeUpsertOne {
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (u *SopNodeUpsertOne) ClearActionLabelAdd() *SopNodeUpsertOne {
 	return u.Update(func(s *SopNodeUpsert) {
-		s.ClearActionLabel()
+		s.ClearActionLabelAdd()
+	})
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (u *SopNodeUpsertOne) SetActionLabelDel(v []uint64) *SopNodeUpsertOne {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.SetActionLabelDel(v)
+	})
+}
+
+// UpdateActionLabelDel sets the "action_label_del" field to the value that was provided on create.
+func (u *SopNodeUpsertOne) UpdateActionLabelDel() *SopNodeUpsertOne {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.UpdateActionLabelDel()
+	})
+}
+
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (u *SopNodeUpsertOne) ClearActionLabelDel() *SopNodeUpsertOne {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.ClearActionLabelDel()
+	})
+}
+
+// SetActionForward sets the "action_forward" field.
+func (u *SopNodeUpsertOne) SetActionForward(v *custom_types.ActionForward) *SopNodeUpsertOne {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.SetActionForward(v)
+	})
+}
+
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopNodeUpsertOne) UpdateActionForward() *SopNodeUpsertOne {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.UpdateActionForward()
+	})
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopNodeUpsertOne) ClearActionForward() *SopNodeUpsertOne {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.ClearActionForward()
 	})
 }
 
@@ -1289,6 +1438,20 @@ func (u *SopNodeUpsertBulk) UpdateNoReplyCondition() *SopNodeUpsertBulk {
 	})
 }
 
+// SetNoReplyUnit sets the "no_reply_unit" field.
+func (u *SopNodeUpsertBulk) SetNoReplyUnit(v string) *SopNodeUpsertBulk {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.SetNoReplyUnit(v)
+	})
+}
+
+// UpdateNoReplyUnit sets the "no_reply_unit" field to the value that was provided on create.
+func (u *SopNodeUpsertBulk) UpdateNoReplyUnit() *SopNodeUpsertBulk {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.UpdateNoReplyUnit()
+	})
+}
+
 // SetActionMessage sets the "action_message" field.
 func (u *SopNodeUpsertBulk) SetActionMessage(v []custom_types.Action) *SopNodeUpsertBulk {
 	return u.Update(func(s *SopNodeUpsert) {
@@ -1310,24 +1473,66 @@ func (u *SopNodeUpsertBulk) ClearActionMessage() *SopNodeUpsertBulk {
 	})
 }
 
-// SetActionLabel sets the "action_label" field.
-func (u *SopNodeUpsertBulk) SetActionLabel(v []uint64) *SopNodeUpsertBulk {
+// SetActionLabelAdd sets the "action_label_add" field.
+func (u *SopNodeUpsertBulk) SetActionLabelAdd(v []uint64) *SopNodeUpsertBulk {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.SetActionLabelAdd(v)
+	})
+}
+
+// UpdateActionLabelAdd sets the "action_label_add" field to the value that was provided on create.
+func (u *SopNodeUpsertBulk) UpdateActionLabelAdd() *SopNodeUpsertBulk {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.UpdateActionLabelAdd()
+	})
+}
+
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (u *SopNodeUpsertBulk) ClearActionLabelAdd() *SopNodeUpsertBulk {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.ClearActionLabelAdd()
+	})
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (u *SopNodeUpsertBulk) SetActionLabelDel(v []uint64) *SopNodeUpsertBulk {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.SetActionLabelDel(v)
+	})
+}
+
+// UpdateActionLabelDel sets the "action_label_del" field to the value that was provided on create.
+func (u *SopNodeUpsertBulk) UpdateActionLabelDel() *SopNodeUpsertBulk {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.UpdateActionLabelDel()
+	})
+}
+
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (u *SopNodeUpsertBulk) ClearActionLabelDel() *SopNodeUpsertBulk {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.ClearActionLabelDel()
+	})
+}
+
+// SetActionForward sets the "action_forward" field.
+func (u *SopNodeUpsertBulk) SetActionForward(v *custom_types.ActionForward) *SopNodeUpsertBulk {
 	return u.Update(func(s *SopNodeUpsert) {
-		s.SetActionLabel(v)
+		s.SetActionForward(v)
 	})
 }
 
-// UpdateActionLabel sets the "action_label" field to the value that was provided on create.
-func (u *SopNodeUpsertBulk) UpdateActionLabel() *SopNodeUpsertBulk {
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopNodeUpsertBulk) UpdateActionForward() *SopNodeUpsertBulk {
 	return u.Update(func(s *SopNodeUpsert) {
-		s.UpdateActionLabel()
+		s.UpdateActionForward()
 	})
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (u *SopNodeUpsertBulk) ClearActionLabel() *SopNodeUpsertBulk {
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopNodeUpsertBulk) ClearActionForward() *SopNodeUpsertBulk {
 	return u.Update(func(s *SopNodeUpsert) {
-		s.ClearActionLabel()
+		s.ClearActionForward()
 	})
 }
 

+ 158 - 30
ent/sopnode_update.go

@@ -194,6 +194,20 @@ func (snu *SopNodeUpdate) AddNoReplyCondition(u int64) *SopNodeUpdate {
 	return snu
 }
 
+// SetNoReplyUnit sets the "no_reply_unit" field.
+func (snu *SopNodeUpdate) SetNoReplyUnit(s string) *SopNodeUpdate {
+	snu.mutation.SetNoReplyUnit(s)
+	return snu
+}
+
+// SetNillableNoReplyUnit sets the "no_reply_unit" field if the given value is not nil.
+func (snu *SopNodeUpdate) SetNillableNoReplyUnit(s *string) *SopNodeUpdate {
+	if s != nil {
+		snu.SetNoReplyUnit(*s)
+	}
+	return snu
+}
+
 // SetActionMessage sets the "action_message" field.
 func (snu *SopNodeUpdate) SetActionMessage(ct []custom_types.Action) *SopNodeUpdate {
 	snu.mutation.SetActionMessage(ct)
@@ -212,21 +226,51 @@ func (snu *SopNodeUpdate) ClearActionMessage() *SopNodeUpdate {
 	return snu
 }
 
-// SetActionLabel sets the "action_label" field.
-func (snu *SopNodeUpdate) SetActionLabel(u []uint64) *SopNodeUpdate {
-	snu.mutation.SetActionLabel(u)
+// SetActionLabelAdd sets the "action_label_add" field.
+func (snu *SopNodeUpdate) SetActionLabelAdd(u []uint64) *SopNodeUpdate {
+	snu.mutation.SetActionLabelAdd(u)
 	return snu
 }
 
-// AppendActionLabel appends u to the "action_label" field.
-func (snu *SopNodeUpdate) AppendActionLabel(u []uint64) *SopNodeUpdate {
-	snu.mutation.AppendActionLabel(u)
+// AppendActionLabelAdd appends u to the "action_label_add" field.
+func (snu *SopNodeUpdate) AppendActionLabelAdd(u []uint64) *SopNodeUpdate {
+	snu.mutation.AppendActionLabelAdd(u)
 	return snu
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (snu *SopNodeUpdate) ClearActionLabel() *SopNodeUpdate {
-	snu.mutation.ClearActionLabel()
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (snu *SopNodeUpdate) ClearActionLabelAdd() *SopNodeUpdate {
+	snu.mutation.ClearActionLabelAdd()
+	return snu
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (snu *SopNodeUpdate) SetActionLabelDel(u []uint64) *SopNodeUpdate {
+	snu.mutation.SetActionLabelDel(u)
+	return snu
+}
+
+// AppendActionLabelDel appends u to the "action_label_del" field.
+func (snu *SopNodeUpdate) AppendActionLabelDel(u []uint64) *SopNodeUpdate {
+	snu.mutation.AppendActionLabelDel(u)
+	return snu
+}
+
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (snu *SopNodeUpdate) ClearActionLabelDel() *SopNodeUpdate {
+	snu.mutation.ClearActionLabelDel()
+	return snu
+}
+
+// SetActionForward sets the "action_forward" field.
+func (snu *SopNodeUpdate) SetActionForward(ctf *custom_types.ActionForward) *SopNodeUpdate {
+	snu.mutation.SetActionForward(ctf)
+	return snu
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (snu *SopNodeUpdate) ClearActionForward() *SopNodeUpdate {
+	snu.mutation.ClearActionForward()
 	return snu
 }
 
@@ -400,6 +444,9 @@ func (snu *SopNodeUpdate) sqlSave(ctx context.Context) (n int, err error) {
 	if value, ok := snu.mutation.AddedNoReplyCondition(); ok {
 		_spec.AddField(sopnode.FieldNoReplyCondition, field.TypeUint64, value)
 	}
+	if value, ok := snu.mutation.NoReplyUnit(); ok {
+		_spec.SetField(sopnode.FieldNoReplyUnit, field.TypeString, value)
+	}
 	if value, ok := snu.mutation.ActionMessage(); ok {
 		_spec.SetField(sopnode.FieldActionMessage, field.TypeJSON, value)
 	}
@@ -411,16 +458,33 @@ func (snu *SopNodeUpdate) sqlSave(ctx context.Context) (n int, err error) {
 	if snu.mutation.ActionMessageCleared() {
 		_spec.ClearField(sopnode.FieldActionMessage, field.TypeJSON)
 	}
-	if value, ok := snu.mutation.ActionLabel(); ok {
-		_spec.SetField(sopnode.FieldActionLabel, field.TypeJSON, value)
+	if value, ok := snu.mutation.ActionLabelAdd(); ok {
+		_spec.SetField(sopnode.FieldActionLabelAdd, field.TypeJSON, value)
 	}
-	if value, ok := snu.mutation.AppendedActionLabel(); ok {
+	if value, ok := snu.mutation.AppendedActionLabelAdd(); ok {
 		_spec.AddModifier(func(u *sql.UpdateBuilder) {
-			sqljson.Append(u, sopnode.FieldActionLabel, value)
+			sqljson.Append(u, sopnode.FieldActionLabelAdd, value)
 		})
 	}
-	if snu.mutation.ActionLabelCleared() {
-		_spec.ClearField(sopnode.FieldActionLabel, field.TypeJSON)
+	if snu.mutation.ActionLabelAddCleared() {
+		_spec.ClearField(sopnode.FieldActionLabelAdd, field.TypeJSON)
+	}
+	if value, ok := snu.mutation.ActionLabelDel(); ok {
+		_spec.SetField(sopnode.FieldActionLabelDel, field.TypeJSON, value)
+	}
+	if value, ok := snu.mutation.AppendedActionLabelDel(); ok {
+		_spec.AddModifier(func(u *sql.UpdateBuilder) {
+			sqljson.Append(u, sopnode.FieldActionLabelDel, value)
+		})
+	}
+	if snu.mutation.ActionLabelDelCleared() {
+		_spec.ClearField(sopnode.FieldActionLabelDel, field.TypeJSON)
+	}
+	if value, ok := snu.mutation.ActionForward(); ok {
+		_spec.SetField(sopnode.FieldActionForward, field.TypeJSON, value)
+	}
+	if snu.mutation.ActionForwardCleared() {
+		_spec.ClearField(sopnode.FieldActionForward, field.TypeJSON)
 	}
 	if snu.mutation.SopStageCleared() {
 		edge := &sqlgraph.EdgeSpec{
@@ -678,6 +742,20 @@ func (snuo *SopNodeUpdateOne) AddNoReplyCondition(u int64) *SopNodeUpdateOne {
 	return snuo
 }
 
+// SetNoReplyUnit sets the "no_reply_unit" field.
+func (snuo *SopNodeUpdateOne) SetNoReplyUnit(s string) *SopNodeUpdateOne {
+	snuo.mutation.SetNoReplyUnit(s)
+	return snuo
+}
+
+// SetNillableNoReplyUnit sets the "no_reply_unit" field if the given value is not nil.
+func (snuo *SopNodeUpdateOne) SetNillableNoReplyUnit(s *string) *SopNodeUpdateOne {
+	if s != nil {
+		snuo.SetNoReplyUnit(*s)
+	}
+	return snuo
+}
+
 // SetActionMessage sets the "action_message" field.
 func (snuo *SopNodeUpdateOne) SetActionMessage(ct []custom_types.Action) *SopNodeUpdateOne {
 	snuo.mutation.SetActionMessage(ct)
@@ -696,21 +774,51 @@ func (snuo *SopNodeUpdateOne) ClearActionMessage() *SopNodeUpdateOne {
 	return snuo
 }
 
-// SetActionLabel sets the "action_label" field.
-func (snuo *SopNodeUpdateOne) SetActionLabel(u []uint64) *SopNodeUpdateOne {
-	snuo.mutation.SetActionLabel(u)
+// SetActionLabelAdd sets the "action_label_add" field.
+func (snuo *SopNodeUpdateOne) SetActionLabelAdd(u []uint64) *SopNodeUpdateOne {
+	snuo.mutation.SetActionLabelAdd(u)
 	return snuo
 }
 
-// AppendActionLabel appends u to the "action_label" field.
-func (snuo *SopNodeUpdateOne) AppendActionLabel(u []uint64) *SopNodeUpdateOne {
-	snuo.mutation.AppendActionLabel(u)
+// AppendActionLabelAdd appends u to the "action_label_add" field.
+func (snuo *SopNodeUpdateOne) AppendActionLabelAdd(u []uint64) *SopNodeUpdateOne {
+	snuo.mutation.AppendActionLabelAdd(u)
 	return snuo
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (snuo *SopNodeUpdateOne) ClearActionLabel() *SopNodeUpdateOne {
-	snuo.mutation.ClearActionLabel()
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (snuo *SopNodeUpdateOne) ClearActionLabelAdd() *SopNodeUpdateOne {
+	snuo.mutation.ClearActionLabelAdd()
+	return snuo
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (snuo *SopNodeUpdateOne) SetActionLabelDel(u []uint64) *SopNodeUpdateOne {
+	snuo.mutation.SetActionLabelDel(u)
+	return snuo
+}
+
+// AppendActionLabelDel appends u to the "action_label_del" field.
+func (snuo *SopNodeUpdateOne) AppendActionLabelDel(u []uint64) *SopNodeUpdateOne {
+	snuo.mutation.AppendActionLabelDel(u)
+	return snuo
+}
+
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (snuo *SopNodeUpdateOne) ClearActionLabelDel() *SopNodeUpdateOne {
+	snuo.mutation.ClearActionLabelDel()
+	return snuo
+}
+
+// SetActionForward sets the "action_forward" field.
+func (snuo *SopNodeUpdateOne) SetActionForward(ctf *custom_types.ActionForward) *SopNodeUpdateOne {
+	snuo.mutation.SetActionForward(ctf)
+	return snuo
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (snuo *SopNodeUpdateOne) ClearActionForward() *SopNodeUpdateOne {
+	snuo.mutation.ClearActionForward()
 	return snuo
 }
 
@@ -914,6 +1022,9 @@ func (snuo *SopNodeUpdateOne) sqlSave(ctx context.Context) (_node *SopNode, err
 	if value, ok := snuo.mutation.AddedNoReplyCondition(); ok {
 		_spec.AddField(sopnode.FieldNoReplyCondition, field.TypeUint64, value)
 	}
+	if value, ok := snuo.mutation.NoReplyUnit(); ok {
+		_spec.SetField(sopnode.FieldNoReplyUnit, field.TypeString, value)
+	}
 	if value, ok := snuo.mutation.ActionMessage(); ok {
 		_spec.SetField(sopnode.FieldActionMessage, field.TypeJSON, value)
 	}
@@ -925,16 +1036,33 @@ func (snuo *SopNodeUpdateOne) sqlSave(ctx context.Context) (_node *SopNode, err
 	if snuo.mutation.ActionMessageCleared() {
 		_spec.ClearField(sopnode.FieldActionMessage, field.TypeJSON)
 	}
-	if value, ok := snuo.mutation.ActionLabel(); ok {
-		_spec.SetField(sopnode.FieldActionLabel, field.TypeJSON, value)
+	if value, ok := snuo.mutation.ActionLabelAdd(); ok {
+		_spec.SetField(sopnode.FieldActionLabelAdd, field.TypeJSON, value)
 	}
-	if value, ok := snuo.mutation.AppendedActionLabel(); ok {
+	if value, ok := snuo.mutation.AppendedActionLabelAdd(); ok {
 		_spec.AddModifier(func(u *sql.UpdateBuilder) {
-			sqljson.Append(u, sopnode.FieldActionLabel, value)
+			sqljson.Append(u, sopnode.FieldActionLabelAdd, value)
 		})
 	}
-	if snuo.mutation.ActionLabelCleared() {
-		_spec.ClearField(sopnode.FieldActionLabel, field.TypeJSON)
+	if snuo.mutation.ActionLabelAddCleared() {
+		_spec.ClearField(sopnode.FieldActionLabelAdd, field.TypeJSON)
+	}
+	if value, ok := snuo.mutation.ActionLabelDel(); ok {
+		_spec.SetField(sopnode.FieldActionLabelDel, field.TypeJSON, value)
+	}
+	if value, ok := snuo.mutation.AppendedActionLabelDel(); ok {
+		_spec.AddModifier(func(u *sql.UpdateBuilder) {
+			sqljson.Append(u, sopnode.FieldActionLabelDel, value)
+		})
+	}
+	if snuo.mutation.ActionLabelDelCleared() {
+		_spec.ClearField(sopnode.FieldActionLabelDel, field.TypeJSON)
+	}
+	if value, ok := snuo.mutation.ActionForward(); ok {
+		_spec.SetField(sopnode.FieldActionForward, field.TypeJSON, value)
+	}
+	if snuo.mutation.ActionForwardCleared() {
+		_spec.ClearField(sopnode.FieldActionForward, field.TypeJSON)
 	}
 	if snuo.mutation.SopStageCleared() {
 		edge := &sqlgraph.EdgeSpec{

+ 34 - 8
ent/sopstage.go

@@ -41,7 +41,11 @@ type SopStage struct {
 	// 命中后发送的消息内容
 	ActionMessage []custom_types.Action `json:"action_message,omitempty"`
 	// 命中后需要打的标签
-	ActionLabel []uint64 `json:"action_label,omitempty"`
+	ActionLabelAdd []uint64 `json:"action_label_add,omitempty"`
+	// 命中后需要移除的标签
+	ActionLabelDel []uint64 `json:"action_label_del,omitempty"`
+	// 命中后转发的消息
+	ActionForward *custom_types.ActionForward `json:"action_forward,omitempty"`
 	// 阶段顺序
 	IndexSort int `json:"index_sort,omitempty"`
 	// Edges holds the relations/edges for other nodes in the graph.
@@ -97,7 +101,7 @@ func (*SopStage) scanValues(columns []string) ([]any, error) {
 	values := make([]any, len(columns))
 	for i := range columns {
 		switch columns[i] {
-		case sopstage.FieldConditionList, sopstage.FieldActionMessage, sopstage.FieldActionLabel:
+		case sopstage.FieldConditionList, sopstage.FieldActionMessage, sopstage.FieldActionLabelAdd, sopstage.FieldActionLabelDel, sopstage.FieldActionForward:
 			values[i] = new([]byte)
 		case sopstage.FieldID, sopstage.FieldStatus, sopstage.FieldTaskID, sopstage.FieldConditionType, sopstage.FieldConditionOperator, sopstage.FieldIndexSort:
 			values[i] = new(sql.NullInt64)
@@ -190,12 +194,28 @@ func (ss *SopStage) assignValues(columns []string, values []any) error {
 					return fmt.Errorf("unmarshal field action_message: %w", err)
 				}
 			}
-		case sopstage.FieldActionLabel:
+		case sopstage.FieldActionLabelAdd:
 			if value, ok := values[i].(*[]byte); !ok {
-				return fmt.Errorf("unexpected type %T for field action_label", values[i])
+				return fmt.Errorf("unexpected type %T for field action_label_add", values[i])
 			} else if value != nil && len(*value) > 0 {
-				if err := json.Unmarshal(*value, &ss.ActionLabel); err != nil {
-					return fmt.Errorf("unmarshal field action_label: %w", err)
+				if err := json.Unmarshal(*value, &ss.ActionLabelAdd); err != nil {
+					return fmt.Errorf("unmarshal field action_label_add: %w", err)
+				}
+			}
+		case sopstage.FieldActionLabelDel:
+			if value, ok := values[i].(*[]byte); !ok {
+				return fmt.Errorf("unexpected type %T for field action_label_del", values[i])
+			} else if value != nil && len(*value) > 0 {
+				if err := json.Unmarshal(*value, &ss.ActionLabelDel); err != nil {
+					return fmt.Errorf("unmarshal field action_label_del: %w", err)
+				}
+			}
+		case sopstage.FieldActionForward:
+			if value, ok := values[i].(*[]byte); !ok {
+				return fmt.Errorf("unexpected type %T for field action_forward", values[i])
+			} else if value != nil && len(*value) > 0 {
+				if err := json.Unmarshal(*value, &ss.ActionForward); err != nil {
+					return fmt.Errorf("unmarshal field action_forward: %w", err)
 				}
 			}
 		case sopstage.FieldIndexSort:
@@ -285,8 +305,14 @@ func (ss *SopStage) String() string {
 	builder.WriteString("action_message=")
 	builder.WriteString(fmt.Sprintf("%v", ss.ActionMessage))
 	builder.WriteString(", ")
-	builder.WriteString("action_label=")
-	builder.WriteString(fmt.Sprintf("%v", ss.ActionLabel))
+	builder.WriteString("action_label_add=")
+	builder.WriteString(fmt.Sprintf("%v", ss.ActionLabelAdd))
+	builder.WriteString(", ")
+	builder.WriteString("action_label_del=")
+	builder.WriteString(fmt.Sprintf("%v", ss.ActionLabelDel))
+	builder.WriteString(", ")
+	builder.WriteString("action_forward=")
+	builder.WriteString(fmt.Sprintf("%v", ss.ActionForward))
 	builder.WriteString(", ")
 	builder.WriteString("index_sort=")
 	builder.WriteString(fmt.Sprintf("%v", ss.IndexSort))

+ 9 - 3
ent/sopstage/sopstage.go

@@ -35,8 +35,12 @@ const (
 	FieldConditionList = "condition_list"
 	// FieldActionMessage holds the string denoting the action_message field in the database.
 	FieldActionMessage = "action_message"
-	// FieldActionLabel holds the string denoting the action_label field in the database.
-	FieldActionLabel = "action_label"
+	// FieldActionLabelAdd holds the string denoting the action_label_add field in the database.
+	FieldActionLabelAdd = "action_label_add"
+	// FieldActionLabelDel holds the string denoting the action_label_del field in the database.
+	FieldActionLabelDel = "action_label_del"
+	// FieldActionForward holds the string denoting the action_forward field in the database.
+	FieldActionForward = "action_forward"
 	// FieldIndexSort holds the string denoting the index_sort field in the database.
 	FieldIndexSort = "index_sort"
 	// EdgeSopTask holds the string denoting the sop_task edge name in mutations.
@@ -83,7 +87,9 @@ var Columns = []string{
 	FieldConditionOperator,
 	FieldConditionList,
 	FieldActionMessage,
-	FieldActionLabel,
+	FieldActionLabelAdd,
+	FieldActionLabelDel,
+	FieldActionForward,
 	FieldIndexSort,
 }
 

+ 26 - 6
ent/sopstage/where.go

@@ -455,14 +455,34 @@ func ActionMessageNotNil() predicate.SopStage {
 	return predicate.SopStage(sql.FieldNotNull(FieldActionMessage))
 }
 
-// ActionLabelIsNil applies the IsNil predicate on the "action_label" field.
-func ActionLabelIsNil() predicate.SopStage {
-	return predicate.SopStage(sql.FieldIsNull(FieldActionLabel))
+// ActionLabelAddIsNil applies the IsNil predicate on the "action_label_add" field.
+func ActionLabelAddIsNil() predicate.SopStage {
+	return predicate.SopStage(sql.FieldIsNull(FieldActionLabelAdd))
 }
 
-// ActionLabelNotNil applies the NotNil predicate on the "action_label" field.
-func ActionLabelNotNil() predicate.SopStage {
-	return predicate.SopStage(sql.FieldNotNull(FieldActionLabel))
+// ActionLabelAddNotNil applies the NotNil predicate on the "action_label_add" field.
+func ActionLabelAddNotNil() predicate.SopStage {
+	return predicate.SopStage(sql.FieldNotNull(FieldActionLabelAdd))
+}
+
+// ActionLabelDelIsNil applies the IsNil predicate on the "action_label_del" field.
+func ActionLabelDelIsNil() predicate.SopStage {
+	return predicate.SopStage(sql.FieldIsNull(FieldActionLabelDel))
+}
+
+// ActionLabelDelNotNil applies the NotNil predicate on the "action_label_del" field.
+func ActionLabelDelNotNil() predicate.SopStage {
+	return predicate.SopStage(sql.FieldNotNull(FieldActionLabelDel))
+}
+
+// ActionForwardIsNil applies the IsNil predicate on the "action_forward" field.
+func ActionForwardIsNil() predicate.SopStage {
+	return predicate.SopStage(sql.FieldIsNull(FieldActionForward))
+}
+
+// ActionForwardNotNil applies the NotNil predicate on the "action_forward" field.
+func ActionForwardNotNil() predicate.SopStage {
+	return predicate.SopStage(sql.FieldNotNull(FieldActionForward))
 }
 
 // IndexSortEQ applies the EQ predicate on the "index_sort" field.

+ 173 - 33
ent/sopstage_create.go

@@ -142,9 +142,21 @@ func (ssc *SopStageCreate) SetActionMessage(ct []custom_types.Action) *SopStageC
 	return ssc
 }
 
-// SetActionLabel sets the "action_label" field.
-func (ssc *SopStageCreate) SetActionLabel(u []uint64) *SopStageCreate {
-	ssc.mutation.SetActionLabel(u)
+// SetActionLabelAdd sets the "action_label_add" field.
+func (ssc *SopStageCreate) SetActionLabelAdd(u []uint64) *SopStageCreate {
+	ssc.mutation.SetActionLabelAdd(u)
+	return ssc
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (ssc *SopStageCreate) SetActionLabelDel(u []uint64) *SopStageCreate {
+	ssc.mutation.SetActionLabelDel(u)
+	return ssc
+}
+
+// SetActionForward sets the "action_forward" field.
+func (ssc *SopStageCreate) SetActionForward(ctf *custom_types.ActionForward) *SopStageCreate {
+	ssc.mutation.SetActionForward(ctf)
 	return ssc
 }
 
@@ -378,9 +390,17 @@ func (ssc *SopStageCreate) createSpec() (*SopStage, *sqlgraph.CreateSpec) {
 		_spec.SetField(sopstage.FieldActionMessage, field.TypeJSON, value)
 		_node.ActionMessage = value
 	}
-	if value, ok := ssc.mutation.ActionLabel(); ok {
-		_spec.SetField(sopstage.FieldActionLabel, field.TypeJSON, value)
-		_node.ActionLabel = value
+	if value, ok := ssc.mutation.ActionLabelAdd(); ok {
+		_spec.SetField(sopstage.FieldActionLabelAdd, field.TypeJSON, value)
+		_node.ActionLabelAdd = value
+	}
+	if value, ok := ssc.mutation.ActionLabelDel(); ok {
+		_spec.SetField(sopstage.FieldActionLabelDel, field.TypeJSON, value)
+		_node.ActionLabelDel = value
+	}
+	if value, ok := ssc.mutation.ActionForward(); ok {
+		_spec.SetField(sopstage.FieldActionForward, field.TypeJSON, value)
+		_node.ActionForward = value
 	}
 	if value, ok := ssc.mutation.IndexSort(); ok {
 		_spec.SetField(sopstage.FieldIndexSort, field.TypeInt, value)
@@ -631,21 +651,57 @@ func (u *SopStageUpsert) ClearActionMessage() *SopStageUpsert {
 	return u
 }
 
-// SetActionLabel sets the "action_label" field.
-func (u *SopStageUpsert) SetActionLabel(v []uint64) *SopStageUpsert {
-	u.Set(sopstage.FieldActionLabel, v)
+// SetActionLabelAdd sets the "action_label_add" field.
+func (u *SopStageUpsert) SetActionLabelAdd(v []uint64) *SopStageUpsert {
+	u.Set(sopstage.FieldActionLabelAdd, v)
 	return u
 }
 
-// UpdateActionLabel sets the "action_label" field to the value that was provided on create.
-func (u *SopStageUpsert) UpdateActionLabel() *SopStageUpsert {
-	u.SetExcluded(sopstage.FieldActionLabel)
+// UpdateActionLabelAdd sets the "action_label_add" field to the value that was provided on create.
+func (u *SopStageUpsert) UpdateActionLabelAdd() *SopStageUpsert {
+	u.SetExcluded(sopstage.FieldActionLabelAdd)
 	return u
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (u *SopStageUpsert) ClearActionLabel() *SopStageUpsert {
-	u.SetNull(sopstage.FieldActionLabel)
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (u *SopStageUpsert) ClearActionLabelAdd() *SopStageUpsert {
+	u.SetNull(sopstage.FieldActionLabelAdd)
+	return u
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (u *SopStageUpsert) SetActionLabelDel(v []uint64) *SopStageUpsert {
+	u.Set(sopstage.FieldActionLabelDel, v)
+	return u
+}
+
+// UpdateActionLabelDel sets the "action_label_del" field to the value that was provided on create.
+func (u *SopStageUpsert) UpdateActionLabelDel() *SopStageUpsert {
+	u.SetExcluded(sopstage.FieldActionLabelDel)
+	return u
+}
+
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (u *SopStageUpsert) ClearActionLabelDel() *SopStageUpsert {
+	u.SetNull(sopstage.FieldActionLabelDel)
+	return u
+}
+
+// SetActionForward sets the "action_forward" field.
+func (u *SopStageUpsert) SetActionForward(v *custom_types.ActionForward) *SopStageUpsert {
+	u.Set(sopstage.FieldActionForward, v)
+	return u
+}
+
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopStageUpsert) UpdateActionForward() *SopStageUpsert {
+	u.SetExcluded(sopstage.FieldActionForward)
+	return u
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopStageUpsert) ClearActionForward() *SopStageUpsert {
+	u.SetNull(sopstage.FieldActionForward)
 	return u
 }
 
@@ -892,24 +948,66 @@ func (u *SopStageUpsertOne) ClearActionMessage() *SopStageUpsertOne {
 	})
 }
 
-// SetActionLabel sets the "action_label" field.
-func (u *SopStageUpsertOne) SetActionLabel(v []uint64) *SopStageUpsertOne {
+// SetActionLabelAdd sets the "action_label_add" field.
+func (u *SopStageUpsertOne) SetActionLabelAdd(v []uint64) *SopStageUpsertOne {
+	return u.Update(func(s *SopStageUpsert) {
+		s.SetActionLabelAdd(v)
+	})
+}
+
+// UpdateActionLabelAdd sets the "action_label_add" field to the value that was provided on create.
+func (u *SopStageUpsertOne) UpdateActionLabelAdd() *SopStageUpsertOne {
+	return u.Update(func(s *SopStageUpsert) {
+		s.UpdateActionLabelAdd()
+	})
+}
+
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (u *SopStageUpsertOne) ClearActionLabelAdd() *SopStageUpsertOne {
+	return u.Update(func(s *SopStageUpsert) {
+		s.ClearActionLabelAdd()
+	})
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (u *SopStageUpsertOne) SetActionLabelDel(v []uint64) *SopStageUpsertOne {
+	return u.Update(func(s *SopStageUpsert) {
+		s.SetActionLabelDel(v)
+	})
+}
+
+// UpdateActionLabelDel sets the "action_label_del" field to the value that was provided on create.
+func (u *SopStageUpsertOne) UpdateActionLabelDel() *SopStageUpsertOne {
 	return u.Update(func(s *SopStageUpsert) {
-		s.SetActionLabel(v)
+		s.UpdateActionLabelDel()
 	})
 }
 
-// UpdateActionLabel sets the "action_label" field to the value that was provided on create.
-func (u *SopStageUpsertOne) UpdateActionLabel() *SopStageUpsertOne {
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (u *SopStageUpsertOne) ClearActionLabelDel() *SopStageUpsertOne {
 	return u.Update(func(s *SopStageUpsert) {
-		s.UpdateActionLabel()
+		s.ClearActionLabelDel()
 	})
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (u *SopStageUpsertOne) ClearActionLabel() *SopStageUpsertOne {
+// SetActionForward sets the "action_forward" field.
+func (u *SopStageUpsertOne) SetActionForward(v *custom_types.ActionForward) *SopStageUpsertOne {
 	return u.Update(func(s *SopStageUpsert) {
-		s.ClearActionLabel()
+		s.SetActionForward(v)
+	})
+}
+
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopStageUpsertOne) UpdateActionForward() *SopStageUpsertOne {
+	return u.Update(func(s *SopStageUpsert) {
+		s.UpdateActionForward()
+	})
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopStageUpsertOne) ClearActionForward() *SopStageUpsertOne {
+	return u.Update(func(s *SopStageUpsert) {
+		s.ClearActionForward()
 	})
 }
 
@@ -1326,24 +1424,66 @@ func (u *SopStageUpsertBulk) ClearActionMessage() *SopStageUpsertBulk {
 	})
 }
 
-// SetActionLabel sets the "action_label" field.
-func (u *SopStageUpsertBulk) SetActionLabel(v []uint64) *SopStageUpsertBulk {
+// SetActionLabelAdd sets the "action_label_add" field.
+func (u *SopStageUpsertBulk) SetActionLabelAdd(v []uint64) *SopStageUpsertBulk {
+	return u.Update(func(s *SopStageUpsert) {
+		s.SetActionLabelAdd(v)
+	})
+}
+
+// UpdateActionLabelAdd sets the "action_label_add" field to the value that was provided on create.
+func (u *SopStageUpsertBulk) UpdateActionLabelAdd() *SopStageUpsertBulk {
+	return u.Update(func(s *SopStageUpsert) {
+		s.UpdateActionLabelAdd()
+	})
+}
+
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (u *SopStageUpsertBulk) ClearActionLabelAdd() *SopStageUpsertBulk {
+	return u.Update(func(s *SopStageUpsert) {
+		s.ClearActionLabelAdd()
+	})
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (u *SopStageUpsertBulk) SetActionLabelDel(v []uint64) *SopStageUpsertBulk {
+	return u.Update(func(s *SopStageUpsert) {
+		s.SetActionLabelDel(v)
+	})
+}
+
+// UpdateActionLabelDel sets the "action_label_del" field to the value that was provided on create.
+func (u *SopStageUpsertBulk) UpdateActionLabelDel() *SopStageUpsertBulk {
+	return u.Update(func(s *SopStageUpsert) {
+		s.UpdateActionLabelDel()
+	})
+}
+
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (u *SopStageUpsertBulk) ClearActionLabelDel() *SopStageUpsertBulk {
+	return u.Update(func(s *SopStageUpsert) {
+		s.ClearActionLabelDel()
+	})
+}
+
+// SetActionForward sets the "action_forward" field.
+func (u *SopStageUpsertBulk) SetActionForward(v *custom_types.ActionForward) *SopStageUpsertBulk {
 	return u.Update(func(s *SopStageUpsert) {
-		s.SetActionLabel(v)
+		s.SetActionForward(v)
 	})
 }
 
-// UpdateActionLabel sets the "action_label" field to the value that was provided on create.
-func (u *SopStageUpsertBulk) UpdateActionLabel() *SopStageUpsertBulk {
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopStageUpsertBulk) UpdateActionForward() *SopStageUpsertBulk {
 	return u.Update(func(s *SopStageUpsert) {
-		s.UpdateActionLabel()
+		s.UpdateActionForward()
 	})
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (u *SopStageUpsertBulk) ClearActionLabel() *SopStageUpsertBulk {
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopStageUpsertBulk) ClearActionForward() *SopStageUpsertBulk {
 	return u.Update(func(s *SopStageUpsert) {
-		s.ClearActionLabel()
+		s.ClearActionForward()
 	})
 }
 

+ 124 - 30
ent/sopstage_update.go

@@ -186,21 +186,51 @@ func (ssu *SopStageUpdate) ClearActionMessage() *SopStageUpdate {
 	return ssu
 }
 
-// SetActionLabel sets the "action_label" field.
-func (ssu *SopStageUpdate) SetActionLabel(u []uint64) *SopStageUpdate {
-	ssu.mutation.SetActionLabel(u)
+// SetActionLabelAdd sets the "action_label_add" field.
+func (ssu *SopStageUpdate) SetActionLabelAdd(u []uint64) *SopStageUpdate {
+	ssu.mutation.SetActionLabelAdd(u)
 	return ssu
 }
 
-// AppendActionLabel appends u to the "action_label" field.
-func (ssu *SopStageUpdate) AppendActionLabel(u []uint64) *SopStageUpdate {
-	ssu.mutation.AppendActionLabel(u)
+// AppendActionLabelAdd appends u to the "action_label_add" field.
+func (ssu *SopStageUpdate) AppendActionLabelAdd(u []uint64) *SopStageUpdate {
+	ssu.mutation.AppendActionLabelAdd(u)
 	return ssu
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (ssu *SopStageUpdate) ClearActionLabel() *SopStageUpdate {
-	ssu.mutation.ClearActionLabel()
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (ssu *SopStageUpdate) ClearActionLabelAdd() *SopStageUpdate {
+	ssu.mutation.ClearActionLabelAdd()
+	return ssu
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (ssu *SopStageUpdate) SetActionLabelDel(u []uint64) *SopStageUpdate {
+	ssu.mutation.SetActionLabelDel(u)
+	return ssu
+}
+
+// AppendActionLabelDel appends u to the "action_label_del" field.
+func (ssu *SopStageUpdate) AppendActionLabelDel(u []uint64) *SopStageUpdate {
+	ssu.mutation.AppendActionLabelDel(u)
+	return ssu
+}
+
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (ssu *SopStageUpdate) ClearActionLabelDel() *SopStageUpdate {
+	ssu.mutation.ClearActionLabelDel()
+	return ssu
+}
+
+// SetActionForward sets the "action_forward" field.
+func (ssu *SopStageUpdate) SetActionForward(ctf *custom_types.ActionForward) *SopStageUpdate {
+	ssu.mutation.SetActionForward(ctf)
+	return ssu
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (ssu *SopStageUpdate) ClearActionForward() *SopStageUpdate {
+	ssu.mutation.ClearActionForward()
 	return ssu
 }
 
@@ -439,16 +469,33 @@ func (ssu *SopStageUpdate) sqlSave(ctx context.Context) (n int, err error) {
 	if ssu.mutation.ActionMessageCleared() {
 		_spec.ClearField(sopstage.FieldActionMessage, field.TypeJSON)
 	}
-	if value, ok := ssu.mutation.ActionLabel(); ok {
-		_spec.SetField(sopstage.FieldActionLabel, field.TypeJSON, value)
+	if value, ok := ssu.mutation.ActionLabelAdd(); ok {
+		_spec.SetField(sopstage.FieldActionLabelAdd, field.TypeJSON, value)
 	}
-	if value, ok := ssu.mutation.AppendedActionLabel(); ok {
+	if value, ok := ssu.mutation.AppendedActionLabelAdd(); ok {
 		_spec.AddModifier(func(u *sql.UpdateBuilder) {
-			sqljson.Append(u, sopstage.FieldActionLabel, value)
+			sqljson.Append(u, sopstage.FieldActionLabelAdd, value)
 		})
 	}
-	if ssu.mutation.ActionLabelCleared() {
-		_spec.ClearField(sopstage.FieldActionLabel, field.TypeJSON)
+	if ssu.mutation.ActionLabelAddCleared() {
+		_spec.ClearField(sopstage.FieldActionLabelAdd, field.TypeJSON)
+	}
+	if value, ok := ssu.mutation.ActionLabelDel(); ok {
+		_spec.SetField(sopstage.FieldActionLabelDel, field.TypeJSON, value)
+	}
+	if value, ok := ssu.mutation.AppendedActionLabelDel(); ok {
+		_spec.AddModifier(func(u *sql.UpdateBuilder) {
+			sqljson.Append(u, sopstage.FieldActionLabelDel, value)
+		})
+	}
+	if ssu.mutation.ActionLabelDelCleared() {
+		_spec.ClearField(sopstage.FieldActionLabelDel, field.TypeJSON)
+	}
+	if value, ok := ssu.mutation.ActionForward(); ok {
+		_spec.SetField(sopstage.FieldActionForward, field.TypeJSON, value)
+	}
+	if ssu.mutation.ActionForwardCleared() {
+		_spec.ClearField(sopstage.FieldActionForward, field.TypeJSON)
 	}
 	if value, ok := ssu.mutation.IndexSort(); ok {
 		_spec.SetField(sopstage.FieldIndexSort, field.TypeInt, value)
@@ -751,21 +798,51 @@ func (ssuo *SopStageUpdateOne) ClearActionMessage() *SopStageUpdateOne {
 	return ssuo
 }
 
-// SetActionLabel sets the "action_label" field.
-func (ssuo *SopStageUpdateOne) SetActionLabel(u []uint64) *SopStageUpdateOne {
-	ssuo.mutation.SetActionLabel(u)
+// SetActionLabelAdd sets the "action_label_add" field.
+func (ssuo *SopStageUpdateOne) SetActionLabelAdd(u []uint64) *SopStageUpdateOne {
+	ssuo.mutation.SetActionLabelAdd(u)
 	return ssuo
 }
 
-// AppendActionLabel appends u to the "action_label" field.
-func (ssuo *SopStageUpdateOne) AppendActionLabel(u []uint64) *SopStageUpdateOne {
-	ssuo.mutation.AppendActionLabel(u)
+// AppendActionLabelAdd appends u to the "action_label_add" field.
+func (ssuo *SopStageUpdateOne) AppendActionLabelAdd(u []uint64) *SopStageUpdateOne {
+	ssuo.mutation.AppendActionLabelAdd(u)
 	return ssuo
 }
 
-// ClearActionLabel clears the value of the "action_label" field.
-func (ssuo *SopStageUpdateOne) ClearActionLabel() *SopStageUpdateOne {
-	ssuo.mutation.ClearActionLabel()
+// ClearActionLabelAdd clears the value of the "action_label_add" field.
+func (ssuo *SopStageUpdateOne) ClearActionLabelAdd() *SopStageUpdateOne {
+	ssuo.mutation.ClearActionLabelAdd()
+	return ssuo
+}
+
+// SetActionLabelDel sets the "action_label_del" field.
+func (ssuo *SopStageUpdateOne) SetActionLabelDel(u []uint64) *SopStageUpdateOne {
+	ssuo.mutation.SetActionLabelDel(u)
+	return ssuo
+}
+
+// AppendActionLabelDel appends u to the "action_label_del" field.
+func (ssuo *SopStageUpdateOne) AppendActionLabelDel(u []uint64) *SopStageUpdateOne {
+	ssuo.mutation.AppendActionLabelDel(u)
+	return ssuo
+}
+
+// ClearActionLabelDel clears the value of the "action_label_del" field.
+func (ssuo *SopStageUpdateOne) ClearActionLabelDel() *SopStageUpdateOne {
+	ssuo.mutation.ClearActionLabelDel()
+	return ssuo
+}
+
+// SetActionForward sets the "action_forward" field.
+func (ssuo *SopStageUpdateOne) SetActionForward(ctf *custom_types.ActionForward) *SopStageUpdateOne {
+	ssuo.mutation.SetActionForward(ctf)
+	return ssuo
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (ssuo *SopStageUpdateOne) ClearActionForward() *SopStageUpdateOne {
+	ssuo.mutation.ClearActionForward()
 	return ssuo
 }
 
@@ -1034,16 +1111,33 @@ func (ssuo *SopStageUpdateOne) sqlSave(ctx context.Context) (_node *SopStage, er
 	if ssuo.mutation.ActionMessageCleared() {
 		_spec.ClearField(sopstage.FieldActionMessage, field.TypeJSON)
 	}
-	if value, ok := ssuo.mutation.ActionLabel(); ok {
-		_spec.SetField(sopstage.FieldActionLabel, field.TypeJSON, value)
+	if value, ok := ssuo.mutation.ActionLabelAdd(); ok {
+		_spec.SetField(sopstage.FieldActionLabelAdd, field.TypeJSON, value)
 	}
-	if value, ok := ssuo.mutation.AppendedActionLabel(); ok {
+	if value, ok := ssuo.mutation.AppendedActionLabelAdd(); ok {
 		_spec.AddModifier(func(u *sql.UpdateBuilder) {
-			sqljson.Append(u, sopstage.FieldActionLabel, value)
+			sqljson.Append(u, sopstage.FieldActionLabelAdd, value)
 		})
 	}
-	if ssuo.mutation.ActionLabelCleared() {
-		_spec.ClearField(sopstage.FieldActionLabel, field.TypeJSON)
+	if ssuo.mutation.ActionLabelAddCleared() {
+		_spec.ClearField(sopstage.FieldActionLabelAdd, field.TypeJSON)
+	}
+	if value, ok := ssuo.mutation.ActionLabelDel(); ok {
+		_spec.SetField(sopstage.FieldActionLabelDel, field.TypeJSON, value)
+	}
+	if value, ok := ssuo.mutation.AppendedActionLabelDel(); ok {
+		_spec.AddModifier(func(u *sql.UpdateBuilder) {
+			sqljson.Append(u, sopstage.FieldActionLabelDel, value)
+		})
+	}
+	if ssuo.mutation.ActionLabelDelCleared() {
+		_spec.ClearField(sopstage.FieldActionLabelDel, field.TypeJSON)
+	}
+	if value, ok := ssuo.mutation.ActionForward(); ok {
+		_spec.SetField(sopstage.FieldActionForward, field.TypeJSON, value)
+	}
+	if ssuo.mutation.ActionForwardCleared() {
+		_spec.ClearField(sopstage.FieldActionForward, field.TypeJSON)
 	}
 	if value, ok := ssuo.mutation.IndexSort(); ok {
 		_spec.SetField(sopstage.FieldIndexSort, field.TypeInt, value)

+ 1 - 0
go.mod

@@ -16,6 +16,7 @@ require (
 	github.com/redis/go-redis/v9 v9.6.1
 	github.com/robfig/cron/v3 v3.0.1
 	github.com/speps/go-hashids/v2 v2.0.1
+	github.com/sashabaranov/go-openai v1.31.0
 	github.com/spf13/cast v1.6.0
 	github.com/suyuan32/simple-admin-common v1.3.11
 	github.com/suyuan32/simple-admin-core v1.3.11

+ 2 - 0
go.sum

@@ -535,6 +535,8 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK
 github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sashabaranov/go-openai v1.31.0 h1:rGe77x7zUeCjtS2IS7NCY6Tp4bQviXNMhkQM6hz/UC4=
+github.com/sashabaranov/go-openai v1.31.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=

+ 20 - 0
internal/handler/routes.go

@@ -471,6 +471,26 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
 					Path:    "/sop_task/stop",
 					Handler: sop_task.SopTaskStopHandler(serverCtx),
 				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/sop_task/copy",
+					Handler: sop_task.SopTaskCopyHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/sop_task/message_var",
+					Handler: sop_task.GetSopMessageVarHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/sop_task/outline",
+					Handler: sop_task.GetSopTaskOutlineHandler(serverCtx),
+				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/sop_task/test_node",
+					Handler: sop_task.TestNodeHandler(serverCtx),
+				},
 			}...,
 		),
 		rest.WithJwt(serverCtx.Config.Auth.AccessSecret),

+ 31 - 0
internal/handler/sop_task/get_sop_message_var_handler.go

@@ -0,0 +1,31 @@
+package sop_task
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/sop_task"
+	"wechat-api/internal/svc"
+)
+
+// swagger:route post /sop_task/message_var sop_task GetSopMessageVar
+//
+// Get sop task list | 获取Sop消息变量
+//
+// Get sop task list | 获取Sop消息变量
+//
+// Responses:
+//  200: MessageVarResp
+
+func GetSopMessageVarHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		l := sop_task.NewGetSopMessageVarLogic(r.Context(), svcCtx)
+		resp, err := l.GetSopMessageVar()
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/sop_task/get_sop_task_outline_handler.go

@@ -0,0 +1,44 @@
+package sop_task
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/sop_task"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /sop_task/outline sop_task GetSopTaskOutline
+//
+// 获取SopTask大纲
+//
+// 获取SopTask大纲
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: IDReq
+//
+// Responses:
+//  200: SopTaskOutlineResp
+
+func GetSopTaskOutlineHandler(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 := sop_task.NewGetSopTaskOutlineLogic(r.Context(), svcCtx)
+		resp, err := l.GetSopTaskOutline(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/sop_task/sop_task_copy_handler.go

@@ -0,0 +1,44 @@
+package sop_task
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/sop_task"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /sop_task/copy sop_task SopTaskCopy
+//
+// task start | SopTask 复制
+//
+// task start | SopTask 复制
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: CopyReq
+//
+// Responses:
+//  200: BaseMsgResp
+
+func SopTaskCopyHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.CopyReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := sop_task.NewSopTaskCopyLogic(r.Context(), svcCtx)
+		resp, err := l.SopTaskCopy(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 44 - 0
internal/handler/sop_task/test_node_handler.go

@@ -0,0 +1,44 @@
+package sop_task
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/sop_task"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /sop_task/test_node sop_task TestNode
+//
+// 获取SopTask大纲
+//
+// 获取SopTask大纲
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: TestNodeReq
+//
+// Responses:
+//  200: TestNodeResp
+
+func TestNodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.TestNodeReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := sop_task.NewTestNodeLogic(r.Context(), svcCtx)
+		resp, err := l.TestNode(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 3 - 0
internal/logic/contact/get_contact_list_logic.go

@@ -91,6 +91,9 @@ func (l *GetContactListLogic) GetContactList(req *types.ContactListReq) (*types.
 		labelRelationships := make([]types.ContactLabelList, 0)
 		if v.Edges.ContactRelationships != nil {
 			for _, lr := range v.Edges.ContactRelationships {
+				if lr.Edges.Labels == nil {
+					continue
+				}
 				labelRelationships = append(labelRelationships, types.ContactLabelList{
 					Label: &lr.Edges.Labels.Name,
 					Value: &lr.LabelID,

+ 123 - 33
internal/logic/label_relationship/batch_update_label_relationships_logic.go

@@ -2,7 +2,6 @@ package label_relationship
 
 import (
 	"context"
-	"fmt"
 	"github.com/suyuan32/simple-admin-common/msg/errormsg"
 	"wechat-api/ent"
 	"wechat-api/ent/contact"
@@ -114,25 +113,32 @@ func (l *BatchUpdateLabelRelationshipsLogic) BatchUpdateLabelRelationships(req *
 			}
 			sopStages = append(sopStages, stages...)
 		}
-		err = AddLabelRelationships(tx, sopStages, *c, finalLabelIds, organizationId, l.ctx, l.Logger)
 
-		if err != nil {
-			_ = tx.Rollback()
-			return nil, err
-		}
 		// 所有操作成功,提交事务
 		err = tx.Commit()
 		if err != nil {
 			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
 		}
+
+		err = l.AddLabelRelationships(sopStages, *c, finalLabelIds, organizationId, l.ctx, l.Logger)
+
+		if err != nil {
+			_ = tx.Rollback()
+			return nil, err
+		}
 	}
 
 	return &types.BaseMsgResp{Msg: errormsg.UpdateSuccess}, nil
 }
 
-func AddLabelRelationships(tx *ent.Tx, sopStages []*ent.SopStage, contact ent.Contact, currentLabelIds []uint64, organizationId uint64, ctx context.Context, logger logx.Logger) (err error) {
+func (l *BatchUpdateLabelRelationshipsLogic) AddLabelRelationships(sopStages []*ent.SopStage, contact ent.Contact, currentLabelIds []uint64, organizationId uint64, ctx context.Context, logger logx.Logger) (err error) {
 	for _, stage := range sopStages {
 		if stage.ConditionType == 1 && isLabelIdListMatchFilter(currentLabelIds, stage.ConditionOperator, stage.ConditionList) {
+			// 开始事务
+			tx, err := l.svcCtx.DB.Tx(context.Background())
+			if err != nil {
+				return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+			}
 			// 判断是否有 contact_wxid、source_type、source_id、sub_source_id 相同的记录
 			_, err = tx.MessageRecords.Query().
 				Where(
@@ -153,7 +159,7 @@ func AddLabelRelationships(tx *ent.Tx, sopStages []*ent.SopStage, contact ent.Co
 					if message.Meta != nil {
 						meta.Filename = message.Meta.Filename
 					}
-					_, _ = tx.MessageRecords.Create().
+					_, err = tx.MessageRecords.Create().
 						SetNotNilBotWxid(&contact.WxWxid).
 						SetNotNilContactID(&contact.ID).
 						SetNotNilContactType(&contact.Type).
@@ -167,48 +173,132 @@ func AddLabelRelationships(tx *ent.Tx, sopStages []*ent.SopStage, contact ent.Co
 						SetOrganizationID(organizationId).
 						Save(ctx)
 
-					//if err != nil {
-					//	return dberrorhandler.DefaultEntError(l.Logger, err, nil)
-					//}
+					if err != nil {
+						_ = tx.Rollback()
+						return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+					}
+				}
+			}
+			if stage.ActionForward != nil {
+				if stage.ActionForward.Wxid != "" {
+					forwardWxids := splitString(stage.ActionForward.Wxid)
+					for _, forwardWxid := range forwardWxids {
+						for i, message := range stage.ActionForward.Action {
+							meta := custom_types.Meta{}
+							if message.Meta != nil {
+								meta.Filename = message.Meta.Filename
+							}
+							_, err = tx.MessageRecords.Create().
+								SetBotWxid(contact.WxWxid).
+								SetContactID(0).
+								SetContactType(0).
+								SetContactWxid(forwardWxid).
+								SetContentType(message.Type).
+								SetContent(message.Content).
+								SetMeta(meta).
+								SetSourceType(sourceType).
+								SetSourceID(stage.ID).
+								SetSubSourceID(contact.ID + uint64(i)).
+								SetOrganizationID(organizationId).
+								Save(l.ctx)
+
+							if err != nil {
+								_ = tx.Rollback()
+								return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+							}
+						}
+					}
 				}
 			}
-			if stage.ActionLabel != nil {
-				fmt.Printf("---------------stage.ActionLabel----------------: %+v\n\n", stage.ActionLabel)
+			if stage.ActionLabelAdd != nil || stage.ActionLabelDel != nil {
+
 				// 获取 addLabelIds 中不在 currentLabelIds 中的标签ID
 				var newLabelIds []uint64
+				var remLabelIds []uint64
+				var finalLabelIds []uint64
 				// 创建一个映射,用于快速查找 currentLabelIds 中的元素
 				currentLabelIdSet := make(map[uint64]struct{})
 				for _, id := range currentLabelIds {
 					currentLabelIdSet[id] = struct{}{}
 				}
 
-				// 遍历 addLabelIds,找出不在 currentLabelIds 中的元素
-				for _, id := range stage.ActionLabel {
-					if _, exists := currentLabelIdSet[id]; !exists {
-						newLabelIds = append(newLabelIds, id)
+				delLabelIdSet := make(map[uint64]struct{})
+				for _, id := range stage.ActionLabelDel {
+					delLabelIdSet[id] = struct{}{}
+				}
+
+				if stage.ActionLabelAdd != nil {
+					// 遍历 addLabelIds,找出不在 currentLabelIds 中的元素
+					for _, id := range stage.ActionLabelAdd {
+						if _, ce := currentLabelIdSet[id]; !ce {
+							if _, re := delLabelIdSet[id]; !re {
+								newLabelIds = append(newLabelIds, id)
+							}
+						}
+					}
+
+					if len(newLabelIds) > 0 {
+						// 创建需要新增的标签关系
+						for _, id := range newLabelIds {
+							_, err = tx.LabelRelationship.Create().
+								SetLabelID(id).
+								SetContactID(contact.ID).
+								SetOrganizationID(organizationId).
+								Save(ctx)
+							if err != nil {
+								_ = tx.Rollback()
+								return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+							}
+						}
+						// 合并 currentLabelIds 和 newLabelIds
+						currentLabelIds = append(currentLabelIds, newLabelIds...)
 					}
 				}
 
-				if len(newLabelIds) == 0 {
+				if stage.ActionLabelDel != nil {
+					// 遍历 delLabelIds,找出在 currentLabelIds 中的元素
+					for _, id := range stage.ActionLabelDel {
+						if _, exists := currentLabelIdSet[id]; exists {
+							remLabelIds = append(newLabelIds, id)
+							delete(currentLabelIdSet, id)
+						}
+					}
+
+					if len(remLabelIds) > 0 {
+						_, err = tx.LabelRelationship.Delete().Where(labelrelationship.IDIn(remLabelIds...), labelrelationship.ContactIDEQ(contact.ID), labelrelationship.OrganizationIDEQ(organizationId)).Exec(l.ctx)
+						if err != nil {
+							//_ = tx.Rollback()
+							return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+						}
+					}
+				}
+
+				// 所有操作成功,提交事务
+				err = tx.Commit()
+				if err != nil {
+					return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+				}
+
+				if len(newLabelIds) == 0 && len(remLabelIds) == 0 {
 					return nil
 				}
-				// 创建需要新增的标签关系
-				fmt.Printf("---------------newLabelIds----------------: %+v\n\n", newLabelIds)
-				for _, id := range newLabelIds {
-					_, err = tx.LabelRelationship.Create().
-						SetLabelID(id).
-						SetContactID(contact.ID).
-						SetOrganizationID(organizationId).
-						Save(ctx)
-					if err != nil {
-						fmt.Printf("---------------err----------------: %+v\n\n", err)
-					}
+
+				for id := range currentLabelIdSet {
+					finalLabelIds = append(finalLabelIds, id)
 				}
-				// 合并 currentLabelIds 和 newLabelIds
-				currentLabelIds = append(currentLabelIds, newLabelIds...)
+
 				// 递归调用 AddLabelRelationships
-				_ = AddLabelRelationships(tx, sopStages, contact, currentLabelIds, organizationId, ctx, logger)
-				return
+				err = l.AddLabelRelationships(sopStages, contact, finalLabelIds, organizationId, ctx, logger)
+				if err != nil {
+					return err
+				}
+				return nil
+			} else {
+				// 所有操作成功,提交事务
+				err = tx.Commit()
+				if err != nil {
+					return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+				}
 			}
 		}
 	}

+ 130 - 39
internal/logic/label_relationship/update_label_relationships_logic.go

@@ -2,8 +2,8 @@ package label_relationship
 
 import (
 	"context"
-	"fmt"
 	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"regexp"
 	"wechat-api/ent"
 	"wechat-api/ent/contact"
 	"wechat-api/ent/custom_types"
@@ -113,32 +113,32 @@ func (l *UpdateLabelRelationshipsLogic) UpdateLabelRelationships(req *types.Labe
 		}
 		sopStages = append(sopStages, stages...)
 	}
-	err = l.AddLabelRelationships(sopStages, *c, req.LabelIds, organizationId)
-
-	if err != nil {
-		_ = tx.Rollback()
-		return nil, err
-	}
 	// 所有操作成功,提交事务
 	err = tx.Commit()
 	if err != nil {
 		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
 	}
+	err = l.AddLabelRelationships(sopStages, *c, req.LabelIds, organizationId)
+
+	if err != nil {
+		return nil, err
+	}
 
 	return &types.BaseMsgResp{Msg: errormsg.UpdateSuccess}, nil
 }
 
 func (l *UpdateLabelRelationshipsLogic) AddLabelRelationships(sopStages []*ent.SopStage, contact ent.Contact, currentLabelIds []uint64, organizationId uint64) (err error) {
-	//// 开始事务
-	//tx, err := l.svcCtx.DB.Tx(context.Background())
-	//if err != nil {
-	//	return dberrorhandler.DefaultEntError(l.Logger, err, nil)
-	//}
 	// 遍历 sop_stages,找出满足条件的 stage
 	for _, stage := range sopStages {
 		if stage.ConditionType == 1 && isLabelIdListMatchFilter(currentLabelIds, stage.ConditionOperator, stage.ConditionList) {
+			// 开始事务
+			tx, err := l.svcCtx.DB.Tx(context.Background())
+			if err != nil {
+				return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+			}
+
 			// 判断是否有 contact_wxid、source_type、source_id、sub_source_id 相同的记录
-			_, err = l.svcCtx.DB.MessageRecords.Query().
+			_, err = tx.MessageRecords.Query().
 				Where(
 					messagerecords.ContactWxid(contact.Wxid),
 					messagerecords.SourceType(3),
@@ -157,7 +157,7 @@ func (l *UpdateLabelRelationshipsLogic) AddLabelRelationships(sopStages []*ent.S
 					if message.Meta != nil {
 						meta.Filename = message.Meta.Filename
 					}
-					_, _ = l.svcCtx.DB.MessageRecords.Create().
+					_, err = tx.MessageRecords.Create().
 						SetNotNilBotWxid(&contact.WxWxid).
 						SetNotNilContactID(&contact.ID).
 						SetNotNilContactType(&contact.Type).
@@ -171,48 +171,131 @@ func (l *UpdateLabelRelationshipsLogic) AddLabelRelationships(sopStages []*ent.S
 						SetOrganizationID(organizationId).
 						Save(l.ctx)
 
-					//if err != nil {
-					//	return dberrorhandler.DefaultEntError(l.Logger, err, nil)
-					//}
+					if err != nil {
+						_ = tx.Rollback()
+						return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+					}
+				}
+			}
+			if stage.ActionForward != nil {
+				if stage.ActionForward.Wxid != "" {
+					forwardWxids := splitString(stage.ActionForward.Wxid)
+					for _, forwardWxid := range forwardWxids {
+						for i, message := range stage.ActionForward.Action {
+							meta := custom_types.Meta{}
+							if message.Meta != nil {
+								meta.Filename = message.Meta.Filename
+							}
+							_, err = tx.MessageRecords.Create().
+								SetBotWxid(contact.WxWxid).
+								SetContactID(0).
+								SetContactType(0).
+								SetContactWxid(forwardWxid).
+								SetContentType(message.Type).
+								SetContent(message.Content).
+								SetMeta(meta).
+								SetSourceType(sourceType).
+								SetSourceID(stage.ID).
+								SetSubSourceID(contact.ID + uint64(i)).
+								SetOrganizationID(organizationId).
+								Save(l.ctx)
+
+							if err != nil {
+								_ = tx.Rollback()
+								return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+							}
+						}
+					}
 				}
 			}
-			if stage.ActionLabel != nil {
-				fmt.Printf("---------------stage.ActionLabel----------------: %+v\n\n", stage.ActionLabel)
+			if stage.ActionLabelAdd != nil || stage.ActionLabelDel != nil {
 				// 获取 addLabelIds 中不在 currentLabelIds 中的标签ID
 				var newLabelIds []uint64
+				var remLabelIds []uint64
+				var finalLabelIds []uint64
 				// 创建一个映射,用于快速查找 currentLabelIds 中的元素
 				currentLabelIdSet := make(map[uint64]struct{})
 				for _, id := range currentLabelIds {
 					currentLabelIdSet[id] = struct{}{}
 				}
 
-				// 遍历 addLabelIds,找出不在 currentLabelIds 中的元素
-				for _, id := range stage.ActionLabel {
-					if _, exists := currentLabelIdSet[id]; !exists {
-						newLabelIds = append(newLabelIds, id)
+				delLabelIdSet := make(map[uint64]struct{})
+				for _, id := range stage.ActionLabelDel {
+					delLabelIdSet[id] = struct{}{}
+				}
+
+				if stage.ActionLabelAdd != nil {
+					// 遍历 addLabelIds,找出不在 currentLabelIds 中的元素
+					for _, id := range stage.ActionLabelAdd {
+						if _, ce := currentLabelIdSet[id]; !ce {
+							if _, re := delLabelIdSet[id]; !re {
+								newLabelIds = append(newLabelIds, id)
+							}
+						}
 					}
+
+					if len(newLabelIds) > 0 {
+						// 创建需要新增的标签关系
+						for _, id := range newLabelIds {
+							_, err = tx.LabelRelationship.Create().
+								SetLabelID(id).
+								SetContactID(contact.ID).
+								SetOrganizationID(organizationId).
+								Save(l.ctx)
+							if err != nil {
+								_ = tx.Rollback()
+								return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+							}
+						}
+					}
+					// 合并 currentLabelIds 和 newLabelIds
+					currentLabelIds = append(currentLabelIds, newLabelIds...)
+				}
+
+				if stage.ActionLabelDel != nil {
+					// 遍历 delLabelIds,找出在 currentLabelIds 中的元素
+					for _, id := range stage.ActionLabelDel {
+						if _, exists := currentLabelIdSet[id]; exists {
+							remLabelIds = append(newLabelIds, id)
+							delete(currentLabelIdSet, id)
+						}
+					}
+
+					if len(remLabelIds) > 0 {
+						_, err = tx.LabelRelationship.Delete().Where(labelrelationship.IDIn(remLabelIds...), labelrelationship.ContactIDEQ(contact.ID), labelrelationship.OrganizationIDEQ(organizationId)).Exec(l.ctx)
+						if err != nil {
+							//_ = tx.Rollback()
+							return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+						}
+					}
+				}
+
+				// 所有操作成功,提交事务
+				err = tx.Commit()
+				if err != nil {
+					return dberrorhandler.DefaultEntError(l.Logger, err, nil)
 				}
 
-				if len(newLabelIds) == 0 {
+				if len(newLabelIds) == 0 && len(remLabelIds) == 0 {
 					return nil
 				}
-				// 创建需要新增的标签关系
-				fmt.Printf("---------------newLabelIds----------------: %+v\n\n", newLabelIds)
-				for _, id := range newLabelIds {
-					_, err = l.svcCtx.DB.LabelRelationship.Create().
-						SetLabelID(id).
-						SetContactID(contact.ID).
-						SetOrganizationID(organizationId).
-						Save(l.ctx)
-					if err != nil {
-						fmt.Printf("---------------err----------------: %+v\n\n", err)
-					}
+
+				for id := range currentLabelIdSet {
+					finalLabelIds = append(finalLabelIds, id)
 				}
-				// 合并 currentLabelIds 和 newLabelIds
-				currentLabelIds = append(currentLabelIds, newLabelIds...)
+
 				// 递归调用 AddLabelRelationships
-				_ = l.AddLabelRelationships(sopStages, contact, currentLabelIds, organizationId)
-				return
+				err = l.AddLabelRelationships(sopStages, contact, finalLabelIds, organizationId)
+				if err != nil {
+					return err
+				}
+				return nil
+			} else {
+				// 所有操作成功,提交事务
+				err = tx.Commit()
+				if err != nil {
+					return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+				}
 			}
 		}
 	}
@@ -272,3 +355,11 @@ func isLabelIdListMatchFilter(labelIdList []uint64, conditionOperator int, condi
 
 	return conditionOperator == 1
 }
+
+func splitString(input string) []string {
+	// Define the regular expression pattern to match Chinese comma, English comma, and Chinese enumeration comma
+	pattern := `[,,、]`
+	re := regexp.MustCompile(pattern)
+	// Split the input string based on the pattern
+	return re.Split(input, -1)
+}

+ 39 - 3
internal/logic/sop_node/create_sop_node_logic.go

@@ -64,7 +64,36 @@ func (l *CreateSopNodeLogic) CreateSopNode(req *types.SopNodeInfo) (*types.SopNo
 		}
 	}
 
-	newNode, err := l.svcCtx.DB.SopNode.Create().
+	var forward *custom_types.ActionForward
+	if req.ActionForward != nil {
+		forward = &custom_types.ActionForward{}
+		forward.Wxid = req.ActionForward.Wxid
+		var forwardAction []custom_types.Action
+		if len(req.ActionForward.Action) > 0 {
+			forwardAction = make([]custom_types.Action, len(req.ActionForward.Action))
+			for i, condition := range req.ActionForward.Action {
+				if condition.Type == 1 {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+					}
+				} else {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+						Meta: &custom_types.Meta{
+							Filename: condition.Meta.Filename,
+						},
+					}
+				}
+			}
+		}
+		forward.Action = forwardAction
+	} else {
+		forward = nil
+	}
+
+	createStmt := l.svcCtx.DB.SopNode.Create().
 		SetNotNilStatus(req.Status).
 		SetNotNilStageID(req.StageId).
 		SetNotNilParentID(req.ParentId).
@@ -72,9 +101,16 @@ func (l *CreateSopNodeLogic) CreateSopNode(req *types.SopNodeInfo) (*types.SopNo
 		SetNotNilConditionType(req.ConditionType).
 		SetNotNilConditionList(req.ConditionList).
 		SetNotNilNoReplyCondition(req.NoReplyCondition).
+		SetNotNilNoReplyUnit(req.NoReplyUnit).
 		SetNotNilActionMessage(actionMessage).
-		SetNotNilActionLabel(req.ActionLabel).
-		Save(l.ctx)
+		SetNotNilActionLabelAdd(req.ActionLabelAdd).
+		SetNotNilActionLabelDel(req.ActionLabelDel)
+
+	if forward != nil {
+		createStmt.SetActionForward(forward)
+	}
+
+	newNode, err := createStmt.Save(l.ctx)
 
 	if err != nil {
 		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)

+ 22 - 1
internal/logic/sop_node/get_sop_node_by_id_logic.go

@@ -44,6 +44,24 @@ func (l *GetSopNodeByIdLogic) GetSopNodeById(req *types.IDReq) (*types.SopNodeIn
 		}
 	}
 
+	var actionForward *types.ActionForward
+	if data.ActionForward != nil {
+		actionForward = &types.ActionForward{}
+		actionForward.Wxid = data.ActionForward.Wxid
+		if len(data.ActionForward.Action) > 0 {
+			forwardAction := make([]types.Action, len(data.ActionForward.Action))
+			for i, condition := range data.ActionForward.Action {
+				forwardAction[i] = types.Action{
+					Type:    condition.Type,
+					Content: condition.Content,
+				}
+			}
+			actionForward.Action = forwardAction
+		}
+	} else {
+		actionForward = nil
+	}
+
 	return &types.SopNodeInfoResp{
 		BaseDataInfo: types.BaseDataInfo{
 			Code: 0,
@@ -62,8 +80,11 @@ func (l *GetSopNodeByIdLogic) GetSopNodeById(req *types.IDReq) (*types.SopNodeIn
 			ConditionType:    &data.ConditionType,
 			ConditionList:    data.ConditionList,
 			NoReplyCondition: &data.NoReplyCondition,
+			NoReplyUnit:      &data.NoReplyUnit,
 			ActionMessage:    actionMessage,
-			ActionLabel:      data.ActionLabel,
+			ActionLabelAdd:   data.ActionLabelAdd,
+			ActionLabelDel:   data.ActionLabelDel,
+			ActionForward:    actionForward,
 		},
 	}, nil
 }

+ 22 - 1
internal/logic/sop_node/get_sop_node_detail_logic.go

@@ -42,6 +42,24 @@ func (l *GetSopNodeDetailLogic) GetSopNodeDetail(req *types.IDReq) (resp *types.
 		}
 	}
 
+	var actionForward *types.ActionForward
+	if data.ActionForward != nil {
+		actionForward = &types.ActionForward{}
+		actionForward.Wxid = data.ActionForward.Wxid
+		if len(data.ActionForward.Action) > 0 {
+			forwardAction := make([]types.Action, len(data.ActionForward.Action))
+			for i, condition := range data.ActionForward.Action {
+				forwardAction[i] = types.Action{
+					Type:    condition.Type,
+					Content: condition.Content,
+				}
+			}
+			actionForward.Action = forwardAction
+		}
+	} else {
+		actionForward = nil
+	}
+
 	return &types.SopNodeInfoResp{
 		BaseDataInfo: types.BaseDataInfo{
 			Code: 0,
@@ -60,8 +78,11 @@ func (l *GetSopNodeDetailLogic) GetSopNodeDetail(req *types.IDReq) (resp *types.
 			ConditionType:    &data.ConditionType,
 			ConditionList:    data.ConditionList,
 			NoReplyCondition: &data.NoReplyCondition,
+			NoReplyUnit:      &data.NoReplyUnit,
 			ActionMessage:    actionMessage,
-			ActionLabel:      data.ActionLabel,
+			ActionLabelAdd:   data.ActionLabelAdd,
+			ActionLabelDel:   data.ActionLabelDel,
+			ActionForward:    actionForward,
 		},
 	}, nil
 }

+ 22 - 1
internal/logic/sop_node/get_sop_node_list_logic.go

@@ -68,6 +68,24 @@ func GetSopNodeListByParentId(parentId uint64, nodeList []*ent.SopNode) ([]*type
 				}
 			}
 
+			var actionForward *types.ActionForward
+			if node.ActionForward != nil {
+				actionForward = &types.ActionForward{}
+				actionForward.Wxid = node.ActionForward.Wxid
+				if len(node.ActionForward.Action) > 0 {
+					forwardAction := make([]types.Action, len(node.ActionForward.Action))
+					for i, condition := range node.ActionForward.Action {
+						forwardAction[i] = types.Action{
+							Type:    condition.Type,
+							Content: condition.Content,
+						}
+					}
+					actionForward.Action = forwardAction
+				}
+			} else {
+				actionForward = nil
+			}
+
 			var childNode *types.SopChildNodeInfo
 			if len(childNodeList) > 0 {
 				childNode = &types.SopChildNodeInfo{
@@ -89,8 +107,11 @@ func GetSopNodeListByParentId(parentId uint64, nodeList []*ent.SopNode) ([]*type
 					ConditionType:    &node.ConditionType,
 					ConditionList:    node.ConditionList,
 					NoReplyCondition: &node.NoReplyCondition,
+					NoReplyUnit:      &node.NoReplyUnit,
 					ActionMessage:    actionMessage,
-					ActionLabel:      node.ActionLabel,
+					ActionLabelAdd:   node.ActionLabelAdd,
+					ActionLabelDel:   node.ActionLabelDel,
+					ActionForward:    actionForward,
 				},
 			}
 			result = append(result, &child)

+ 41 - 3
internal/logic/sop_node/update_sop_node_logic.go

@@ -67,7 +67,36 @@ func (l *UpdateSopNodeLogic) UpdateSopNode(req *types.SopNodeInfo) (*types.BaseM
 		}
 	}
 
-	err = l.svcCtx.DB.SopNode.UpdateOneID(*req.Id).
+	var forward *custom_types.ActionForward
+	if req.ActionForward != nil {
+		forward = &custom_types.ActionForward{}
+		forward.Wxid = req.ActionForward.Wxid
+		var forwardAction []custom_types.Action
+		if len(req.ActionForward.Action) > 0 {
+			forwardAction = make([]custom_types.Action, len(req.ActionForward.Action))
+			for i, condition := range req.ActionForward.Action {
+				if condition.Type == 1 {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+					}
+				} else {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+						Meta: &custom_types.Meta{
+							Filename: condition.Meta.Filename,
+						},
+					}
+				}
+			}
+		}
+		forward.Action = forwardAction
+	} else {
+		forward = nil
+	}
+
+	createStmt := l.svcCtx.DB.SopNode.UpdateOneID(*req.Id).
 		SetNotNilStatus(req.Status).
 		SetNotNilStageID(req.StageId).
 		SetNotNilParentID(req.ParentId).
@@ -75,9 +104,18 @@ func (l *UpdateSopNodeLogic) UpdateSopNode(req *types.SopNodeInfo) (*types.BaseM
 		SetNotNilConditionType(req.ConditionType).
 		SetNotNilConditionList(req.ConditionList).
 		SetNotNilNoReplyCondition(req.NoReplyCondition).
+		SetNotNilNoReplyUnit(req.NoReplyUnit).
 		SetNotNilActionMessage(actionMessage).
-		SetNotNilActionLabel(req.ActionLabel).
-		Exec(l.ctx)
+		SetNotNilActionLabelAdd(req.ActionLabelAdd).
+		SetNotNilActionLabelDel(req.ActionLabelDel)
+
+	if forward != nil {
+		createStmt.SetActionForward(forward)
+	} else {
+		createStmt.ClearActionForward()
+	}
+
+	err = createStmt.Exec(l.ctx)
 
 	if err != nil {
 		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)

+ 37 - 4
internal/logic/sop_stage/create_sop_stage_logic.go

@@ -79,7 +79,36 @@ func (l *CreateSopStageLogic) CreateSopStage(req *types.SopStageInfo) (*types.So
 		}
 	}
 
-	newStage, err := l.svcCtx.DB.SopStage.Create().
+	var forward *custom_types.ActionForward
+	if req.ActionForward != nil {
+		forward = &custom_types.ActionForward{}
+		forward.Wxid = req.ActionForward.Wxid
+		var forwardAction []custom_types.Action
+		if len(req.ActionForward.Action) > 0 {
+			forwardAction = make([]custom_types.Action, len(req.ActionForward.Action))
+			for i, condition := range req.ActionForward.Action {
+				if condition.Type == 1 {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+					}
+				} else {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+						Meta: &custom_types.Meta{
+							Filename: condition.Meta.Filename,
+						},
+					}
+				}
+			}
+		}
+		forward.Action = forwardAction
+	} else {
+		forward = nil
+	}
+
+	createStmt := l.svcCtx.DB.SopStage.Create().
 		SetNotNilStatus(req.Status).
 		SetNotNilTaskID(req.TaskId).
 		SetNotNilName(req.Name).
@@ -87,9 +116,13 @@ func (l *CreateSopStageLogic) CreateSopStage(req *types.SopStageInfo) (*types.So
 		SetNotNilConditionOperator(req.ConditionOperator).
 		SetNotNilConditionList(conditionList).
 		SetNotNilActionMessage(actionMessage).
-		SetNotNilActionLabel(req.ActionLabel).
-		SetNotNilIndexSort(&count).
-		Save(l.ctx)
+		SetNotNilActionLabelAdd(req.ActionLabelAdd).
+		SetNotNilActionLabelDel(req.ActionLabelDel).
+		SetNotNilIndexSort(&count)
+	if forward != nil {
+		createStmt.SetActionForward(forward)
+	}
+	newStage, err := createStmt.Save(l.ctx)
 
 	if err != nil {
 		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)

+ 21 - 1
internal/logic/sop_stage/get_sop_stage_by_id_logic.go

@@ -58,6 +58,24 @@ func (l *GetSopStageByIdLogic) GetSopStageById(req *types.IDReq) (*types.SopStag
 		}
 	}
 
+	var actionForward *types.ActionForward
+	if data.ActionForward != nil {
+		actionForward = &types.ActionForward{}
+		actionForward.Wxid = data.ActionForward.Wxid
+		if len(data.ActionForward.Action) > 0 {
+			forwardAction := make([]types.Action, len(data.ActionForward.Action))
+			for i, condition := range data.ActionForward.Action {
+				forwardAction[i] = types.Action{
+					Type:    condition.Type,
+					Content: condition.Content,
+				}
+			}
+			actionForward.Action = forwardAction
+		}
+	} else {
+		actionForward = nil
+	}
+
 	var nodeList []types.SopNodeInfo
 	if data.Edges.StageNodes != nil {
 		nodeList = make([]types.SopNodeInfo, 0)
@@ -92,7 +110,9 @@ func (l *GetSopStageByIdLogic) GetSopStageById(req *types.IDReq) (*types.SopStag
 			ConditionOperator: &data.ConditionOperator,
 			ConditionList:     conditionList,
 			ActionMessage:     actionMessage,
-			ActionLabel:       data.ActionLabel,
+			ActionLabelAdd:    data.ActionLabelAdd,
+			ActionLabelDel:    data.ActionLabelDel,
+			ActionForward:     actionForward,
 			IndexSort:         &data.IndexSort,
 			NodeList:          nodeList,
 		},

+ 21 - 1
internal/logic/sop_stage/get_sop_stage_detail_logic.go

@@ -57,6 +57,24 @@ func (l *GetSopStageDetailLogic) GetSopStageDetail(req *types.IDReq) (resp *type
 		}
 	}
 
+	var actionForward *types.ActionForward
+	if data.ActionForward != nil {
+		actionForward = &types.ActionForward{}
+		actionForward.Wxid = data.ActionForward.Wxid
+		if len(data.ActionForward.Action) > 0 {
+			forwardAction := make([]types.Action, len(data.ActionForward.Action))
+			for i, condition := range data.ActionForward.Action {
+				forwardAction[i] = types.Action{
+					Type:    condition.Type,
+					Content: condition.Content,
+				}
+			}
+			actionForward.Action = forwardAction
+		}
+	} else {
+		actionForward = nil
+	}
+
 	return &types.SopStageInfoResp{
 		BaseDataInfo: types.BaseDataInfo{
 			Code: 0,
@@ -75,7 +93,9 @@ func (l *GetSopStageDetailLogic) GetSopStageDetail(req *types.IDReq) (resp *type
 			ConditionOperator: &data.ConditionOperator,
 			ConditionList:     conditionList,
 			ActionMessage:     actionMessage,
-			ActionLabel:       data.ActionLabel,
+			ActionLabelAdd:    data.ActionLabelAdd,
+			ActionLabelDel:    data.ActionLabelDel,
+			ActionForward:     actionForward,
 			IndexSort:         &data.IndexSort,
 		},
 	}, nil

+ 21 - 1
internal/logic/sop_stage/get_sop_stage_list_logic.go

@@ -69,6 +69,24 @@ func (l *GetSopStageListLogic) GetSopStageList(req *types.SopStageListReq) (*typ
 			}
 		}
 
+		var actionForward *types.ActionForward
+		if v.ActionForward != nil {
+			actionForward = &types.ActionForward{}
+			actionForward.Wxid = v.ActionForward.Wxid
+			if len(v.ActionForward.Action) > 0 {
+				forwardAction := make([]types.Action, len(v.ActionForward.Action))
+				for i, condition := range v.ActionForward.Action {
+					forwardAction[i] = types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+					}
+				}
+				actionForward.Action = forwardAction
+			}
+		} else {
+			actionForward = nil
+		}
+
 		resp.Data.Data = append(resp.Data.Data,
 			types.SopStageInfo{
 				BaseIDInfo: types.BaseIDInfo{
@@ -83,7 +101,9 @@ func (l *GetSopStageListLogic) GetSopStageList(req *types.SopStageListReq) (*typ
 				ConditionOperator: &v.ConditionOperator,
 				ConditionList:     conditionList,
 				ActionMessage:     actionMessage,
-				ActionLabel:       v.ActionLabel,
+				ActionLabelAdd:    v.ActionLabelAdd,
+				ActionLabelDel:    v.ActionLabelDel,
+				ActionForward:     actionForward,
 				IndexSort:         &v.IndexSort,
 			})
 	}

+ 41 - 4
internal/logic/sop_stage/update_sop_stage_logic.go

@@ -74,7 +74,36 @@ func (l *UpdateSopStageLogic) UpdateSopStage(req *types.SopStageInfo) (*types.Ba
 		}
 	}
 
-	err = l.svcCtx.DB.SopStage.UpdateOneID(*req.Id).
+	var forward *custom_types.ActionForward
+	if req.ActionForward != nil {
+		forward = &custom_types.ActionForward{}
+		forward.Wxid = req.ActionForward.Wxid
+		var forwardAction []custom_types.Action
+		if len(req.ActionForward.Action) > 0 {
+			forwardAction = make([]custom_types.Action, len(req.ActionForward.Action))
+			for i, condition := range req.ActionForward.Action {
+				if condition.Type == 1 {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+					}
+				} else {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+						Meta: &custom_types.Meta{
+							Filename: condition.Meta.Filename,
+						},
+					}
+				}
+			}
+		}
+		forward.Action = forwardAction
+	} else {
+		forward = nil
+	}
+
+	createStmt := l.svcCtx.DB.SopStage.UpdateOneID(*req.Id).
 		SetNotNilStatus(req.Status).
 		SetNotNilTaskID(req.TaskId).
 		SetNotNilName(req.Name).
@@ -82,9 +111,17 @@ func (l *UpdateSopStageLogic) UpdateSopStage(req *types.SopStageInfo) (*types.Ba
 		SetNotNilConditionOperator(req.ConditionOperator).
 		SetNotNilConditionList(conditionList).
 		SetNotNilActionMessage(actionMessage).
-		SetNotNilActionLabel(req.ActionLabel).
-		SetNotNilIndexSort(req.IndexSort).
-		Exec(l.ctx)
+		SetNotNilActionLabelAdd(req.ActionLabelAdd).
+		SetNotNilActionLabelDel(req.ActionLabelDel).
+		SetNotNilIndexSort(req.IndexSort)
+
+	if forward != nil {
+		createStmt.SetActionForward(forward)
+	} else {
+		createStmt.ClearActionForward()
+	}
+
+	err = createStmt.Exec(l.ctx)
 
 	if err != nil {
 		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)

+ 42 - 0
internal/logic/sop_task/get_sop_message_var_logic.go

@@ -0,0 +1,42 @@
+package sop_task
+
+import (
+	"context"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type GetSopMessageVarLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewGetSopMessageVarLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSopMessageVarLogic {
+	return &GetSopMessageVarLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *GetSopMessageVarLogic) GetSopMessageVar() (resp *types.MessageVarResp, err error) {
+	resp = &types.MessageVarResp{}
+	resp.Msg = errormsg.Success
+	resp.Data.MessageVar = []types.MessageVarInfo{
+		{
+			Label: "昵称",
+			Value: "nickname",
+		},
+	}
+	resp.Data.ForwardVar = []types.MessageVarInfo{
+		{
+			Label: "昵称",
+			Value: "nickname",
+		},
+	}
+
+	return resp, nil
+}

+ 7 - 1
internal/logic/sop_task/get_sop_task_list_logic.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"wechat-api/ent/predicate"
 	"wechat-api/ent/soptask"
+	"wechat-api/ent/wx"
 	"wechat-api/internal/svc"
 	"wechat-api/internal/types"
 	"wechat-api/internal/utils/dberrorhandler"
@@ -49,6 +50,11 @@ func (l *GetSopTaskListLogic) GetSopTaskList(req *types.SopTaskListReq) (*types.
 	resp.Data.Total = data.PageDetails.Total
 
 	for _, v := range data.List {
+		wxInfos, _ := l.svcCtx.DB.Wx.Query().Where(wx.WxidIn(v.BotWxidList...)).All(l.ctx)
+		var botWxidList []string
+		for _, b := range wxInfos {
+			botWxidList = append(botWxidList, b.Nickname)
+		}
 		resp.Data.Data = append(resp.Data.Data,
 			types.SopTaskInfo{
 				BaseIDInfo: types.BaseIDInfo{
@@ -58,7 +64,7 @@ func (l *GetSopTaskListLogic) GetSopTaskList(req *types.SopTaskListReq) (*types.
 				},
 				Status:        &v.Status,
 				Name:          &v.Name,
-				BotWxidList:   v.BotWxidList,
+				BotWxidList:   botWxidList,
 				Type:          &v.Type,
 				PlanStartTime: pointy.GetUnixMilliPointer(v.PlanStartTime.UnixMilli()),
 				PlanEndTime:   pointy.GetUnixMilliPointer(v.PlanEndTime.UnixMilli()),

+ 79 - 0
internal/logic/sop_task/get_sop_task_outline_logic.go

@@ -0,0 +1,79 @@
+package sop_task
+
+import (
+	"context"
+	"strconv"
+	"wechat-api/ent"
+	"wechat-api/ent/soptask"
+	"wechat-api/internal/utils/dberrorhandler"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type GetSopTaskOutlineLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewGetSopTaskOutlineLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSopTaskOutlineLogic {
+	return &GetSopTaskOutlineLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *GetSopTaskOutlineLogic) GetSopTaskOutline(req *types.IDReq) (resp *types.SopTaskOutlineResp, err error) {
+	organizationId := l.ctx.Value("organizationId").(uint64)
+	data, err := l.svcCtx.DB.SopTask.Query().
+		Where(soptask.ID(req.Id), soptask.OrganizationIDEQ(organizationId)).
+		WithTaskStages(func(query *ent.SopStageQuery) {
+			query.WithStageNodes()
+		}).
+		Only(l.ctx)
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	var sopTaskOutlineInfo []types.SopTaskOutlineInfo
+	sopTaskOutlineInfo = []types.SopTaskOutlineInfo{}
+	for _, stage := range data.Edges.TaskStages {
+		nodes := stage.Edges.StageNodes
+		nodesSet := make(map[uint64][]*ent.SopNode)
+		for _, node := range nodes {
+			nodesSet[node.ParentID] = append(nodesSet[node.ParentID], node)
+		}
+
+		key := strconv.Itoa(int(stage.ID))
+		children := getChildren(nodesSet, 0)
+		outlineInfo := types.SopTaskOutlineInfo{
+			Title:    stage.Name,
+			Key:      key,
+			Children: children,
+		}
+		sopTaskOutlineInfo = append(sopTaskOutlineInfo, outlineInfo)
+	}
+	resp = &types.SopTaskOutlineResp{}
+	resp.Msg = "success"
+	resp.Data = sopTaskOutlineInfo
+	return resp, nil
+}
+
+func getChildren(nodesSet map[uint64][]*ent.SopNode, parentID uint64) (nodeChildren []types.SopTaskOutlineInfo) {
+	if nodesSet[parentID] == nil || len(nodesSet[parentID]) == 0 {
+		return nil
+	}
+	nodeChildren = []types.SopTaskOutlineInfo{}
+	for _, node := range nodesSet[parentID] {
+		key := strconv.Itoa(int(node.ID))
+		nodeChildren = append(nodeChildren, types.SopTaskOutlineInfo{
+			Title:    node.Name,
+			Key:      key,
+			Children: getChildren(nodesSet, node.ID),
+		})
+	}
+	return nodeChildren
+}

+ 150 - 46
internal/logic/sop_task/publish_sop_task_logic.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"regexp"
 	"wechat-api/ent"
 	"wechat-api/ent/contact"
 	"wechat-api/ent/custom_types"
@@ -35,6 +36,7 @@ func NewPublishSopTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Pu
 
 func (l *PublishSopTaskLogic) PublishSopTask(req *types.IDReq) (resp *types.BaseMsgResp, err error) {
 	organizationId := l.ctx.Value("organizationId").(uint64)
+
 	// 开始事务
 	//tx, err := l.svcCtx.DB.Tx(context.Background())
 	//if err != nil {
@@ -115,29 +117,59 @@ func (l *PublishSopTaskLogic) PublishSopTask(req *types.IDReq) (resp *types.Base
 				for _, c := range contacts {
 					// 判断联系人所属微信是否包含在任务当中
 					if sopTask.BotWxidList == nil || (sopTask.BotWxidList != nil && valueInArray(c.WxWxid, sopTask.BotWxidList)) {
-						for i, message := range stage.ActionMessage {
-							meta := custom_types.Meta{}
-							if message.Meta != nil {
-								meta.Filename = message.Meta.Filename
+						if stage.ActionMessage != nil {
+							for i, message := range stage.ActionMessage {
+								meta := custom_types.Meta{}
+								if message.Meta != nil {
+									meta.Filename = message.Meta.Filename
+								}
+
+								_, _ = l.svcCtx.DB.MessageRecords.Create().
+									SetNotNilBotWxid(&c.WxWxid).
+									SetNotNilContactID(&c.ID).
+									SetNotNilContactType(&c.Type).
+									SetNotNilContactWxid(&c.Wxid).
+									SetNotNilContentType(&message.Type).
+									SetNotNilContent(&message.Content).
+									SetMeta(meta).
+									SetNotNilSourceType(&sourceType).
+									SetNotNilSourceID(&stage.ID).
+									SetSubSourceID(uint64(i)).
+									SetOrganizationID(organizationId).
+									Save(l.ctx)
+
+								//if err != nil {
+								//	return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+								//}
 							}
+						}
 
-							_, _ = l.svcCtx.DB.MessageRecords.Create().
-								SetNotNilBotWxid(&c.WxWxid).
-								SetNotNilContactID(&c.ID).
-								SetNotNilContactType(&c.Type).
-								SetNotNilContactWxid(&c.Wxid).
-								SetNotNilContentType(&message.Type).
-								SetNotNilContent(&message.Content).
-								SetMeta(meta).
-								SetNotNilSourceType(&sourceType).
-								SetNotNilSourceID(&stage.ID).
-								SetSubSourceID(uint64(i)).
-								SetOrganizationID(organizationId).
-								Save(l.ctx)
-
-							//if err != nil {
-							//	return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
-							//}
+						if stage.ActionForward != nil {
+							if stage.ActionForward.Wxid != "" {
+								forwardWxids := splitString(stage.ActionForward.Wxid)
+								for _, forwardWxid := range forwardWxids {
+									for i, message := range stage.ActionForward.Action {
+										meta := custom_types.Meta{}
+										if message.Meta != nil {
+											meta.Filename = message.Meta.Filename
+										}
+										_, err = l.svcCtx.DB.MessageRecords.Create().
+											SetBotWxid(c.WxWxid).
+											SetContactID(0).
+											SetContactType(0).
+											SetContactWxid(forwardWxid).
+											SetContentType(message.Type).
+											SetContent(message.Content).
+											SetMeta(meta).
+											SetSourceType(sourceType).
+											SetSourceID(stage.ID).
+											SetSubSourceID(c.ID + uint64(i)).
+											SetOrganizationID(organizationId).
+											Save(l.ctx)
+
+									}
+								}
+							}
 						}
 
 						// 查询当前联系人的标签关系
@@ -153,9 +185,9 @@ func (l *PublishSopTaskLogic) PublishSopTask(req *types.IDReq) (resp *types.Base
 							currentLabelIds = append(currentLabelIds, relationship.LabelID)
 						}
 
-						if stage.ActionLabel != nil {
+						if stage.ActionLabelAdd != nil || stage.ActionLabelDel != nil {
 							// 递归调用 AddLabelRelationships
-							err = l.AddLabelRelationships(sopStages, *c, currentLabelIds, stage.ActionLabel, organizationId)
+							err = l.AddLabelRelationships(sopStages, *c, currentLabelIds, stage.ActionLabelAdd, stage.ActionLabelDel, organizationId)
 							if err != nil {
 								//_ = tx.Rollback()
 								return nil, err
@@ -182,7 +214,7 @@ func (l *PublishSopTaskLogic) PublishSopTask(req *types.IDReq) (resp *types.Base
 	}
 }
 
-func (l *PublishSopTaskLogic) AddLabelRelationships(sopStages []*ent.SopStage, contact ent.Contact, currentLabelIds []uint64, addLabelIds []uint64, organizationId uint64) (err error) {
+func (l *PublishSopTaskLogic) AddLabelRelationships(sopStages []*ent.SopStage, contact ent.Contact, currentLabelIds []uint64, addLabelIds []uint64, delLabelIds []uint64, organizationId uint64) (err error) {
 	//// 开始事务
 	//tx, err := l.svcCtx.DB.Tx(context.Background())
 	//if err != nil {
@@ -191,42 +223,77 @@ func (l *PublishSopTaskLogic) AddLabelRelationships(sopStages []*ent.SopStage, c
 
 	// 获取 addLabelIds 中不在 currentLabelIds 中的标签ID
 	var newLabelIds []uint64
+	var remLabelIds []uint64
+	var finalLabelIds []uint64
 	// 创建一个映射,用于快速查找 currentLabelIds 中的元素
 	currentLabelIdSet := make(map[uint64]struct{})
 	for _, id := range currentLabelIds {
 		currentLabelIdSet[id] = struct{}{}
 	}
 
-	// 遍历 addLabelIds,找出不在 currentLabelIds 中的元素
-	for _, id := range addLabelIds {
-		if _, exists := currentLabelIdSet[id]; !exists {
-			newLabelIds = append(newLabelIds, id)
-		}
+	delLabelIdSet := make(map[uint64]struct{})
+	for _, id := range delLabelIds {
+		delLabelIdSet[id] = struct{}{}
 	}
 
-	if len(newLabelIds) == 0 {
-		return nil
+	if addLabelIds != nil {
+		// 遍历 addLabelIds,找出不在 currentLabelIds 中的元素
+		for _, id := range addLabelIds {
+			if _, ce := currentLabelIdSet[id]; !ce {
+				if _, re := delLabelIdSet[id]; !re {
+					newLabelIds = append(newLabelIds, id)
+				}
+			}
+		}
+
+		if len(newLabelIds) > 0 {
+			// 创建需要新增的标签关系
+			for _, id := range newLabelIds {
+				_, err = l.svcCtx.DB.LabelRelationship.Create().
+					SetLabelID(id).
+					SetContactID(contact.ID).
+					SetOrganizationID(organizationId).
+					Save(l.ctx)
+				if err != nil {
+					//_ = tx.Rollback()
+					return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+				}
+			}
+
+			// 合并 currentLabelIds 和 newLabelIds
+			currentLabelIds = append(currentLabelIds, newLabelIds...)
+		}
 	}
 
-	// 创建需要新增的标签关系
-	for _, id := range newLabelIds {
-		_, err = l.svcCtx.DB.LabelRelationship.Create().
-			SetLabelID(id).
-			SetContactID(contact.ID).
-			SetOrganizationID(organizationId).
-			Save(l.ctx)
-		if err != nil {
-			//_ = tx.Rollback()
-			return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+	if delLabelIds != nil {
+		// 遍历 delLabelIds,找出在 currentLabelIds 中的元素
+		for _, id := range delLabelIds {
+			if _, exists := currentLabelIdSet[id]; exists {
+				remLabelIds = append(newLabelIds, id)
+				delete(currentLabelIdSet, id)
+			}
 		}
+
+		if len(remLabelIds) > 0 {
+			_, err = l.svcCtx.DB.LabelRelationship.Delete().Where(labelrelationship.IDIn(remLabelIds...), labelrelationship.ContactIDEQ(contact.ID), labelrelationship.OrganizationIDEQ(organizationId)).Exec(l.ctx)
+			if err != nil {
+				//_ = tx.Rollback()
+				return dberrorhandler.DefaultEntError(l.Logger, err, nil)
+			}
+		}
+	}
+
+	if len(newLabelIds) == 0 && len(remLabelIds) == 0 {
+		return nil
 	}
 
-	// 合并 currentLabelIds 和 newLabelIds
-	currentLabelIds = append(currentLabelIds, newLabelIds...)
+	for id := range currentLabelIdSet {
+		finalLabelIds = append(finalLabelIds, id)
+	}
 
 	// 遍历 sop_stages,找出满足条件的 stage
 	for _, stage := range sopStages {
-		if stage.ConditionType == 1 && isLabelIdListMatchFilter(currentLabelIds, stage.ConditionOperator, stage.ConditionList) {
+		if stage.ConditionType == 1 && isLabelIdListMatchFilter(finalLabelIds, stage.ConditionOperator, stage.ConditionList) {
 			// 判断是否有 contact_wxid、source_type、source_id、sub_source_id 相同的记录
 			_, err := l.svcCtx.DB.MessageRecords.Query().
 				Where(
@@ -266,9 +333,38 @@ func (l *PublishSopTaskLogic) AddLabelRelationships(sopStages []*ent.SopStage, c
 					//}
 				}
 			}
-			if stage.ActionLabel != nil {
+
+			if stage.ActionForward != nil {
+				if stage.ActionForward.Wxid != "" {
+					forwardWxids := splitString(stage.ActionForward.Wxid)
+					for _, forwardWxid := range forwardWxids {
+						for i, message := range stage.ActionForward.Action {
+							meta := custom_types.Meta{}
+							if message.Meta != nil {
+								meta.Filename = message.Meta.Filename
+							}
+							_, err = l.svcCtx.DB.MessageRecords.Create().
+								SetBotWxid(contact.WxWxid).
+								SetContactID(0).
+								SetContactType(0).
+								SetContactWxid(forwardWxid).
+								SetContentType(message.Type).
+								SetContent(message.Content).
+								SetMeta(meta).
+								SetSourceType(sourceType).
+								SetSourceID(stage.ID).
+								SetSubSourceID(contact.ID + uint64(i)).
+								SetOrganizationID(organizationId).
+								Save(l.ctx)
+
+						}
+					}
+				}
+			}
+
+			if stage.ActionLabelAdd != nil || stage.ActionLabelDel != nil {
 				// 递归调用 AddLabelRelationships
-				err = l.AddLabelRelationships(sopStages, contact, currentLabelIds, stage.ActionLabel, organizationId)
+				err = l.AddLabelRelationships(sopStages, contact, finalLabelIds, stage.ActionLabelAdd, stage.ActionLabelDel, organizationId)
 				if err != nil {
 					//_ = tx.Rollback()
 					return err
@@ -321,3 +417,11 @@ func isLabelIdListMatchFilter(labelIdList []uint64, conditionOperator int, condi
 
 	return conditionOperator == 1
 }
+
+func splitString(input string) []string {
+	// Define the regular expression pattern to match Chinese comma, English comma, and Chinese enumeration comma
+	pattern := `[,,、]`
+	re := regexp.MustCompile(pattern)
+	// Split the input string based on the pattern
+	return re.Split(input, -1)
+}

+ 197 - 0
internal/logic/sop_task/sop_task_copy_logic.go

@@ -0,0 +1,197 @@
+package sop_task
+
+import (
+	"context"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"wechat-api/ent"
+	"wechat-api/ent/custom_types"
+	"wechat-api/ent/label"
+	"wechat-api/ent/sopnode"
+	"wechat-api/ent/soptask"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/dberrorhandler"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type SopTaskCopyLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewSopTaskCopyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SopTaskCopyLogic {
+	return &SopTaskCopyLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *SopTaskCopyLogic) SopTaskCopy(req *types.CopyReq) (resp *types.BaseMsgResp, err error) {
+	isAdmin := l.ctx.Value("isAdmin").(bool)
+	organizationId := l.ctx.Value("organizationId").(uint64)
+
+	//  目标组织ID
+	var targetOrganizationId uint64
+	if isAdmin {
+		if req.OrganizationId == 0 {
+			targetOrganizationId = organizationId
+		} else {
+			targetOrganizationId = req.OrganizationId
+		}
+	} else {
+		targetOrganizationId = organizationId
+	}
+
+	//  查询任务及阶段
+	data, err := l.svcCtx.DB.SopTask.Query().
+		Where(soptask.ID(req.Id), soptask.OrganizationIDEQ(organizationId)).
+		WithTaskStages().
+		Only(l.ctx)
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	//  开始事务
+	tx, err := l.svcCtx.DB.Tx(context.Background())
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	//  创建新任务
+	newTask, err := tx.SopTask.Create().
+		SetName(*req.Name).
+		SetType(data.Type).
+		SetOrganizationID(targetOrganizationId).
+		Save(l.ctx)
+	if err != nil {
+		_ = tx.Rollback()
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	//  创建新阶段
+	for _, s := range data.Edges.TaskStages {
+		newConditionList := make([]custom_types.Condition, 0)
+		for _, c := range s.ConditionList {
+			newLabels, err := CreateLabels(tx, c.LabelIdList, targetOrganizationId, l.ctx, l.Logger)
+			if err != nil {
+				_ = tx.Rollback()
+				return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+			}
+			c.LabelIdList = newLabels
+			newConditionList = append(newConditionList, c)
+		}
+		newStage, err := tx.SopStage.Create().
+			SetTaskID(newTask.ID).
+			SetName(s.Name).
+			SetConditionType(s.ConditionType).
+			SetConditionOperator(s.ConditionOperator).
+			SetConditionList(newConditionList).
+			SetNotNilActionMessage(s.ActionMessage).
+			SetNotNilActionLabelAdd(s.ActionLabelAdd).
+			SetNotNilActionLabelDel(s.ActionLabelDel).
+			SetNotNilActionForward(&s.ActionForward).
+			SetIndexSort(s.IndexSort).
+			Save(l.ctx)
+		if err != nil {
+			_ = tx.Rollback()
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+		}
+		err = CreateNodes(tx, s.ID, 0, newStage.ID, 0, targetOrganizationId, l.ctx, l.Logger)
+		if err != nil {
+			_ = tx.Rollback()
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+		}
+	}
+
+	// 所有操作成功,提交事务
+	err = tx.Commit()
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	return &types.BaseMsgResp{Msg: errormsg.CreateSuccess}, nil
+}
+
+func CreateNodes(tx *ent.Tx, stageId uint64, parentId uint64, newStageId uint64, newParentId uint64, targetOrganizationId uint64, ctx context.Context, logger logx.Logger) (err error) {
+	data, err := tx.SopNode.Query().
+		Where(sopnode.StageID(stageId), sopnode.ParentID(parentId)).All(ctx)
+	if err != nil {
+		return nil
+	}
+
+	for _, n := range data {
+		newLabelAdd, err := CreateLabels(tx, n.ActionLabelAdd, targetOrganizationId, ctx, logger)
+		if err != nil {
+			_ = tx.Rollback()
+			return err
+		}
+		newLabelDel, err := CreateLabels(tx, n.ActionLabelDel, targetOrganizationId, ctx, logger)
+		if err != nil {
+			_ = tx.Rollback()
+			return err
+		}
+		newNode, err := tx.SopNode.Create().
+			SetStageID(newStageId).
+			SetParentID(newParentId).
+			SetName(n.Name).
+			SetConditionType(n.ConditionType).
+			SetConditionList(n.ConditionList).
+			SetNotNilNoReplyCondition(&n.NoReplyCondition).
+			SetNotNilActionMessage(n.ActionMessage).
+			SetNotNilActionLabelAdd(newLabelAdd).
+			SetNotNilActionLabelDel(newLabelDel).
+			SetNotNilActionForward(&n.ActionForward).
+			Save(ctx)
+		if err != nil {
+			return err
+		}
+		err = CreateNodes(tx, stageId, n.ID, newStageId, newNode.ID, targetOrganizationId, ctx, logger)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func CreateLabels(tx *ent.Tx, labels []uint64, targetOrganizationId uint64, ctx context.Context, logger logx.Logger) ([]uint64, error) {
+	newLabels := make([]uint64, 0)
+
+	// 查询标签
+	data, err := tx.Label.Query().Where(label.IDIn(labels...)).All(ctx)
+	if err != nil {
+		return nil, err
+	}
+	// 创建标签
+	for _, l := range data {
+		// 判断标签是否存在
+		ol, err := tx.Label.Query().
+			Where(
+				label.NameEQ(l.Name),                       // Filter by ID
+				label.OrganizationID(targetOrganizationId), // Additional filter by organizationId
+			).
+			Only(ctx)
+		if err != nil && !ent.IsNotFound(err) {
+			return nil, err
+		}
+		if ol != nil {
+			newLabels = append(newLabels, ol.ID)
+		} else {
+			var conditions = "{}"
+			nl, err := tx.Label.Create().
+				SetType(1).
+				SetName(l.Name).
+				SetFrom(1).
+				SetMode(1).
+				SetOrganizationID(targetOrganizationId).
+				SetConditions(conditions).
+				Save(ctx)
+			if err != nil {
+				return nil, err
+			}
+			newLabels = append(newLabels, nl.ID)
+		}
+	}
+	return newLabels, nil
+}

+ 157 - 0
internal/logic/sop_task/test_node_logic.go

@@ -0,0 +1,157 @@
+package sop_task
+
+import (
+	"context"
+	"fmt"
+	"strconv"
+	"wechat-api/ent"
+	"wechat-api/ent/sopnode"
+	"wechat-api/ent/sopstage"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	"github.com/sashabaranov/go-openai"
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type TestNodeLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewTestNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *TestNodeLogic {
+	return &TestNodeLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *TestNodeLogic) TestNode(req *types.TestNodeReq) (resp *types.TestNodeResp, err error) {
+	var nodes []*ent.SopNode
+	q := ""
+	if req.Type == 1 {
+		parentNode, err := l.svcCtx.DB.SopStage.Query().Where(sopstage.IDEQ(req.Id)).Only(l.ctx)
+		if err != nil {
+			return nil, err
+		}
+		for _, message := range parentNode.ActionMessage {
+			if message.Type == 1 {
+				q += message.Content
+			}
+		}
+		nodes, err = l.svcCtx.DB.SopNode.Query().Where(sopnode.StageIDEQ(req.Id), sopnode.ParentIDEQ(0)).All(l.ctx)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		parentNode, err := l.svcCtx.DB.SopNode.Query().Where(sopnode.IDEQ(req.Id)).Only(l.ctx)
+		if err != nil {
+			return nil, err
+		}
+		for _, message := range parentNode.ActionMessage {
+			if message.Type == 1 {
+				q += message.Content
+			}
+		}
+		nodes, err = l.svcCtx.DB.SopNode.Query().Where(sopnode.ParentIDEQ(req.Id)).All(l.ctx)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	backupNodeIndex := -1
+	needJudge := false
+
+	prompt := fmt.Sprintf(`# 任务
+请根据历史消息,判断用户回复的内容或深层意图,与哪个节点的意图相匹配。
+
+# 历史消息:
+助手发送:%s
+用户回复:%s
+
+# 节点列表:`, q, req.Content)
+
+	for i, node := range nodes {
+		if node.ConditionList != nil && node.ConditionList[0] != "" {
+			needJudge = true
+			prompt += fmt.Sprintf(`
+节点 id: %d
+节点意图:%s
+`, i, node.ConditionList)
+		} else {
+			if node.NoReplyCondition == 0 {
+				backupNodeIndex = i
+			}
+		}
+	}
+
+	prompt += `
+# 回复要求
+- 如果命中节点:则仅回复节点 id 数字(如命中多个节点,则仅回复最小值)
+- 如果未命中节点:则仅回复一个单词: None`
+	index := ""
+	if needJudge {
+		// 调用openai接口,使用自定义base和key
+		index, err = ChatWithCustomConfig("https://newapi.gkscrm.com/v1", "sk-ZQRNypQOC8ID5WbpCdF263C58dF44271842e86D408Bb3848", prompt)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		index = "None"
+	}
+	if index == "None" && backupNodeIndex != -1 {
+		index = strconv.Itoa(backupNodeIndex)
+	}
+	nodeName := ""
+
+	if index != "None" {
+		// 将index转换成int
+		indexInt, err := strconv.Atoi(index)
+		if err != nil {
+			return nil, err
+		}
+		nodeName = nodes[indexInt].Name
+	} else {
+		nodeName = "FastGPT"
+	}
+
+	var nodeNames []string
+	nodeNames = append(nodeNames, nodeName)
+
+	resp = &types.TestNodeResp{}
+	resp.Msg = "success"
+	resp.Data = nodeNames
+
+	return resp, nil
+}
+
+func ChatWithCustomConfig(baseURL, apiKey, prompt string) (string, error) {
+	// 创建OpenAI客户端配置
+	config := openai.DefaultConfig(apiKey)
+	config.BaseURL = baseURL
+
+	// 创建OpenAI客户端
+	openaiClient := openai.NewClientWithConfig(config)
+
+	// 构建请求
+	request := openai.ChatCompletionRequest{
+		Model: openai.GPT4o,
+		Messages: []openai.ChatCompletionMessage{
+			{
+				Role:    "user",
+				Content: prompt,
+			},
+		},
+	}
+
+	// 调用Chat接口
+	response, err := openaiClient.CreateChatCompletion(context.Background(), request)
+	if err != nil {
+		return "", err
+	}
+
+	// 返回响应内容
+	return response.Choices[0].Message.Content, nil
+}

+ 77 - 2
internal/types/types.go

@@ -235,6 +235,11 @@ type Meta struct {
 	Filename string `json:"filename,optional"`
 }
 
+type ActionForward struct {
+	Wxid   string   `json:"wxid"`
+	Action []Action `json:"action"`
+}
+
 // The response data of server information | Server信息
 // swagger:model ServerInfo
 type ServerInfo struct {
@@ -1143,6 +1148,66 @@ type SopTaskCreateResp struct {
 	Data uint64 `json:"data"`
 }
 
+// swagger:model CopyReq
+type CopyReq struct {
+	// SopTask id | SopTask id
+	Id uint64 `json:"id"`
+	// SOP 任务名称
+	Name *string `json:"name,optional"`
+	// Organization id | Organization id
+	OrganizationId uint64 `json:"organizationId,optional"`
+}
+
+// swagger:model MessageVarResp
+type MessageVarResp struct {
+	BaseDataInfo
+	Data MessageVarRespData `json:"data"`
+}
+
+type MessageVarRespData struct {
+	// 消息变量
+	MessageVar []MessageVarInfo `json:"messageVar"`
+	// 转发变量
+	ForwardVar []MessageVarInfo `json:"forwardVar"`
+}
+
+// swagger:model MessageVarInfo
+type MessageVarInfo struct {
+	// 显示名称
+	Label string `json:"label"`
+	// 变量
+	Value string `json:"value"`
+}
+
+// swagger:model SopTaskOutlineResp
+type SopTaskOutlineResp struct {
+	BaseDataInfo
+	Data []SopTaskOutlineInfo `json:"data"`
+}
+
+// swagger:model SopTaskOutlineInfo
+type SopTaskOutlineInfo struct {
+	// 标题
+	Title string `json:"title,optional"`
+	// key
+	Key string `json:"key,optional"`
+	// 子节点
+	Children []SopTaskOutlineInfo `json:"children,optional"`
+}
+
+// swagger:model TestNodeReq
+type TestNodeReq struct {
+	Type    int    `json:"type"`
+	Id      uint64 `json:"id"`
+	Content string `json:"content"`
+}
+
+// swagger:model TestNodeResp
+type TestNodeResp struct {
+	BaseDataInfo
+	Data []string `json:"data"`
+}
+
 // The response data of sop stage information | SopStage信息
 // swagger:model SopStageInfo
 type SopStageInfo struct {
@@ -1162,7 +1227,11 @@ type SopStageInfo struct {
 	// 命中后发送的消息内容
 	ActionMessage []Action `json:"actionMessage,optional"`
 	// 命中后需要打的标签
-	ActionLabel []uint64 `json:"actionLabel,optional"`
+	ActionLabelAdd []uint64 `json:"actionLabelAdd,optional"`
+	// 命中后需要移除的标签
+	ActionLabelDel []uint64 `json:"actionLabelDel,optional"`
+	// 命中后转发的消息内容
+	ActionForward *ActionForward `json:"actionForward,optional"`
 	// 阶段顺序
 	IndexSort *int `json:"indexSort,optional"`
 	// sop 任务信息
@@ -1213,10 +1282,16 @@ type SopNodeInfo struct {
 	ConditionList []string `json:"conditionList,optional"`
 	// 超时触发时间(分钟)
 	NoReplyCondition *uint64 `json:"noReplyCondition,optional"`
+	// 超时触发时间单位
+	NoReplyUnit *string `json:"noReplyUnit,optional"`
 	// 命中后发送的消息内容
 	ActionMessage []Action `json:"actionMessage,optional"`
 	// 命中后需要打的标签
-	ActionLabel []uint64 `json:"actionLabel,optional"`
+	ActionLabelAdd []uint64 `json:"actionLabelAdd,optional"`
+	// 命中后需要移除的标签
+	ActionLabelDel []uint64 `json:"actionLabelDel,optional"`
+	// 命中后转发的消息内容
+	ActionForward *ActionForward `json:"actionForward,optional"`
 	// 阶段信息
 	StageInfo *SopStageInfo `json:"stageInfo,optional"`
 }