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 }