Files

288 lines
8.5 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 !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)),
}
stampPlanStageGating(&plan, version)
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
}
stampPlanStageGating(plan, version)
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)
}
}
}
}
// stampPlanStageGating 以已发布定义为准。空值不写入快照,避免改变 strict 工作流的 plan hash。
func stampPlanStageGating(plan *WorkflowPlan, version *workflowdef.Version) {
if plan == nil {
return
}
plan.StageGating = ""
if version != nil && version.Definition.StageGating == workflowdef.StageGatingNarrative {
plan.StageGating = workflowdef.StageGatingNarrative
}
}
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
}