New projectinstance package validates three-duty separation, issues confirmation tokens, and activates immutable workflow instances; new projects start in planning mode. Co-authored-by: Cursor <cursoragent@cursor.com>
214 lines
6.2 KiB
Go
214 lines
6.2 KiB
Go
package projectinstance
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"chat-agent-system/internal/workflowdef"
|
|
)
|
|
|
|
func validatePlan(plan WorkflowPlan, version *workflowdef.Version) workflowdef.ValidationResult {
|
|
issues := make([]workflowdef.ValidationIssue, 0)
|
|
if version == nil {
|
|
return workflowdef.ValidationResult{
|
|
Valid: false,
|
|
Issues: []workflowdef.ValidationIssue{{
|
|
Code: "WORKFLOW_VERSION_NOT_FOUND", Message: "工作流版本不存在或未发布",
|
|
}},
|
|
}
|
|
}
|
|
if version.Status != workflowdef.StatusPublished {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "WORKFLOW_NOT_PUBLISHED", Message: "只能绑定已发布的工作流版本",
|
|
})
|
|
}
|
|
if plan.WorkflowDefinitionKey != version.WorkflowKey {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "WORKFLOW_KEY_MISMATCH", Field: "workflow_definition_key",
|
|
Message: fmt.Sprintf("规划 key %s 与版本 %s 不一致", plan.WorkflowDefinitionKey, version.WorkflowKey),
|
|
})
|
|
}
|
|
if plan.WorkflowVersion != version.Version {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "WORKFLOW_VERSION_MISMATCH", Field: "workflow_version",
|
|
Message: fmt.Sprintf("规划版本 %d 与已发布版本 %d 不一致", plan.WorkflowVersion, version.Version),
|
|
})
|
|
}
|
|
|
|
stageDefMap := make(map[string]workflowdef.StageDef, len(version.Definition.Stages))
|
|
for _, st := range version.Definition.Stages {
|
|
stageDefMap[st.Key] = st
|
|
}
|
|
seenStage := map[string]bool{}
|
|
for _, st := range plan.Stages {
|
|
if seenStage[st.StageKey] {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "DUPLICATE_STAGE", Field: st.StageKey, Message: "阶段 key 重复",
|
|
})
|
|
continue
|
|
}
|
|
seenStage[st.StageKey] = true
|
|
def, ok := stageDefMap[st.StageKey]
|
|
if !ok {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "UNKNOWN_STAGE", Field: st.StageKey, Message: "阶段不在工作流定义中",
|
|
})
|
|
continue
|
|
}
|
|
if !def.Optional && !st.Enabled {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "REQUIRED_STAGE_DISABLED", Field: st.StageKey, Message: "必需阶段不能关闭",
|
|
})
|
|
}
|
|
taskDefMap := make(map[string]workflowdef.DocTaskDef, len(def.DocTasks))
|
|
for _, dt := range def.DocTasks {
|
|
taskDefMap[dt.Key] = dt
|
|
}
|
|
seenTask := map[string]bool{}
|
|
for _, dt := range st.DocTasks {
|
|
if seenTask[dt.TaskKey] {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "DUPLICATE_DOC_TASK", Field: dt.TaskKey, Message: "文档任务 key 重复",
|
|
})
|
|
continue
|
|
}
|
|
seenTask[dt.TaskKey] = true
|
|
td, ok := taskDefMap[dt.TaskKey]
|
|
if !ok {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "UNKNOWN_DOC_TASK", Field: dt.TaskKey, Message: "文档任务不在阶段定义中",
|
|
})
|
|
continue
|
|
}
|
|
if (td.Required || td.Blocking) && !dt.Enabled {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "REQUIRED_DOC_DISABLED", Field: dt.TaskKey, Message: "必需/阻断文档任务不能关闭",
|
|
})
|
|
}
|
|
if dt.Enabled {
|
|
issues = append(issues, validateDuties(dt.Duties, dt.TaskKey)...)
|
|
}
|
|
}
|
|
}
|
|
|
|
enabledStages := 0
|
|
for _, st := range plan.Stages {
|
|
if st.Enabled {
|
|
enabledStages++
|
|
}
|
|
}
|
|
if enabledStages == 0 {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "NO_ENABLED_STAGE", Message: "至少启用一个阶段",
|
|
})
|
|
}
|
|
|
|
return workflowdef.ValidationResult{Valid: len(issues) == 0, Issues: issues}
|
|
}
|
|
|
|
func validateDuties(d DocDuty, taskKey string) []workflowdef.ValidationIssue {
|
|
issues := make([]workflowdef.ValidationIssue, 0)
|
|
if d.AuthorUserID <= 0 {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "DUTY_AUTHOR_REQUIRED", Field: taskKey + ".author",
|
|
Message: "启用的文档任务必须指定作者",
|
|
})
|
|
}
|
|
if d.ReviewerUserID <= 0 {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "DUTY_REVIEWER_REQUIRED", Field: taskKey + ".reviewer",
|
|
Message: "启用的文档任务必须指定审核人",
|
|
})
|
|
}
|
|
if d.ApproverUserID <= 0 {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "DUTY_APPROVER_REQUIRED", Field: taskKey + ".approver",
|
|
Message: "启用的文档任务必须指定批准人",
|
|
})
|
|
}
|
|
ids := []int64{d.AuthorUserID, d.ReviewerUserID, d.ApproverUserID}
|
|
for i := 0; i < len(ids); i++ {
|
|
if ids[i] <= 0 {
|
|
continue
|
|
}
|
|
for j := i + 1; j < len(ids); j++ {
|
|
if ids[j] > 0 && ids[i] == ids[j] {
|
|
issues = append(issues, workflowdef.ValidationIssue{
|
|
Code: "SEPARATION_OF_DUTY_VIOLATION", Field: taskKey,
|
|
Message: "作者、审核人、批准人必须分离",
|
|
})
|
|
return issues
|
|
}
|
|
}
|
|
}
|
|
return issues
|
|
}
|
|
|
|
func buildDefaultPlan(version *workflowdef.Version) WorkflowPlan {
|
|
plan := WorkflowPlan{
|
|
WorkflowDefinitionKey: version.WorkflowKey,
|
|
WorkflowVersion: version.Version,
|
|
WorkflowVersionID: version.ID,
|
|
Stages: make([]StagePlan, 0, len(version.Definition.Stages)),
|
|
}
|
|
for _, st := range version.Definition.Stages {
|
|
sp := StagePlan{
|
|
StageKey: st.Key,
|
|
Enabled: !st.Optional,
|
|
DocTasks: make([]DocTaskPlan, 0, len(st.DocTasks)),
|
|
}
|
|
for _, dt := range st.DocTasks {
|
|
sp.DocTasks = append(sp.DocTasks, DocTaskPlan{
|
|
TaskKey: dt.Key,
|
|
Enabled: dt.DefaultEnabled || dt.Required || dt.Blocking,
|
|
})
|
|
}
|
|
plan.Stages = append(plan.Stages, sp)
|
|
}
|
|
return plan
|
|
}
|
|
|
|
func previewSummary(plan WorkflowPlan, version *workflowdef.Version) PreviewSummary {
|
|
sum := PreviewSummary{
|
|
WorkflowVersion: plan.WorkflowVersion,
|
|
}
|
|
if version != nil {
|
|
sum.WorkflowName = version.WorkflowName
|
|
if sum.WorkflowName == "" {
|
|
sum.WorkflowName = version.WorkflowKey
|
|
}
|
|
}
|
|
for _, st := range plan.Stages {
|
|
if !st.Enabled {
|
|
continue
|
|
}
|
|
sum.EnabledStages++
|
|
if sum.FirstStageKey == "" {
|
|
sum.FirstStageKey = st.StageKey
|
|
if version != nil {
|
|
for _, sd := range version.Definition.Stages {
|
|
if sd.Key == st.StageKey {
|
|
sum.FirstStageName = sd.Name
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _, dt := range st.DocTasks {
|
|
if !dt.Enabled {
|
|
continue
|
|
}
|
|
sum.EnabledDocTasks++
|
|
if dt.Duties.AuthorUserID > 0 {
|
|
sum.AssignedDuties++
|
|
}
|
|
if dt.Duties.ReviewerUserID > 0 {
|
|
sum.AssignedDuties++
|
|
}
|
|
if dt.Duties.ApproverUserID > 0 {
|
|
sum.AssignedDuties++
|
|
}
|
|
}
|
|
}
|
|
return sum
|
|
}
|