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 !workflowdef.IsOfficialWorkflowKey(plan.WorkflowDefinitionKey) { issues = append(issues, workflowdef.ValidationIssue{ Code: "WORKFLOW_NOT_SELECTABLE", Field: "workflow_definition_key", 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 !dt.Enabled && !td.IsCreatorConfigurable() { issues = append(issues, workflowdef.ValidationIssue{ Code: "REQUIRED_DOC_DISABLED", Field: dt.TaskKey, Message: "该文档任务不可由创建者关闭", }) } if dt.Enabled { issues = append(issues, validateDocTaskDuties(dt, td)...) } } } 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 validateActivationPlan(plan WorkflowPlan, version *workflowdef.Version) workflowdef.ValidationResult { base := validatePlan(plan, version) if !base.Valid { return base } if countEnabledDocTasks(plan) == 0 { base.Valid = false base.Issues = append(base.Issues, workflowdef.ValidationIssue{ Code: "EMPTY_WORKFLOW", Message: "至少启用一个文档任务才能激活工作流", }) } if countFullDutyDocs(plan) == 0 { base.Valid = false base.Issues = append(base.Issues, workflowdef.ValidationIssue{ Code: issueNoGovernedDocument, Message: "至少一份文档必须纳入人工审批才能激活工作流", }) } return base } func countEnabledDocTasks(plan WorkflowPlan) int { n := 0 for _, st := range plan.Stages { if !st.Enabled { continue } for _, dt := range st.DocTasks { if dt.Enabled { n++ } } } return n } func validateDuties(d DocDuty, taskKey string) []workflowdef.ValidationIssue { return validateDocTaskDuties(DocTaskPlan{TaskKey: taskKey, Enabled: true, Duties: d}, workflowdef.DocTaskDef{Key: taskKey, Required: true}) } 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, StageName: st.Name, Enabled: !st.Optional, DocTasks: make([]DocTaskPlan, 0, len(st.DocTasks)), } for _, dt := range st.DocTasks { sp.DocTasks = append(sp.DocTasks, DocTaskPlan{ TaskKey: dt.Key, TaskTitle: dt.Title, Enabled: dt.DefaultEnabled, ApprovalConfigurable: !docTaskRequiresDuty(dt), }) } plan.Stages = append(plan.Stages, sp) } return plan } // applyDefinitionLabels 使用已发布定义覆盖展示名称,避免客户端把自定义文本写入冻结快照。 func applyDefinitionLabels(plan *WorkflowPlan, version *workflowdef.Version) { if plan == nil || version == nil { return } stageDefs := make(map[string]workflowdef.StageDef, len(version.Definition.Stages)) for _, stage := range version.Definition.Stages { stageDefs[stage.Key] = stage } for stageIdx := range plan.Stages { stage := &plan.Stages[stageIdx] stageDef, ok := stageDefs[stage.StageKey] if !ok { continue } stage.StageName = stageDef.Name taskDefs := make(map[string]workflowdef.DocTaskDef, len(stageDef.DocTasks)) for _, task := range stageDef.DocTasks { taskDefs[task.Key] = task } for taskIdx := range stage.DocTasks { task := &stage.DocTasks[taskIdx] if taskDef, exists := taskDefs[task.TaskKey]; exists { task.TaskTitle = taskDef.Title task.ApprovalConfigurable = !docTaskRequiresDuty(taskDef) } } } } 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++ mode, effective, isAuthorOnly := ResolveDocTaskDuty(dt) switch mode { case DutyModeFull: sum.GovernedDocTasks++ if isAuthorOnly { sum.AuthorOnlyDocTasks++ sum.AuthorOnlyDocuments = append(sum.AuthorOnlyDocuments, PreviewDocRef{ TaskKey: dt.TaskKey, Title: dutyTaskLabel(dt), AuthorUserID: effective.AuthorUserID, }) } else { if effective.AuthorUserID > 0 { sum.AssignedDuties++ } if effective.ReviewerUserID > 0 { sum.AssignedDuties++ } if effective.ApproverUserID > 0 { sum.AssignedDuties++ } } case DutyModeNone: sum.UnmanagedDocTasks++ sum.UnmanagedDocuments = append(sum.UnmanagedDocuments, PreviewDocRef{ TaskKey: dt.TaskKey, Title: dutyTaskLabel(dt), }) } } } return sum }