Files
Agent-System/internal/projectinstance/duty_mode.go
T

162 lines
4.0 KiB
Go

package projectinstance
import (
"fmt"
"chat-agent-system/internal/workflowdef"
)
type DutyMode string
const (
DutyModeNone DutyMode = "none"
DutyModePartial DutyMode = "partial"
DutyModeFull DutyMode = "full"
issueDutyPartialAssignment = "DUTY_PARTIAL_ASSIGNMENT"
issueDutyAssignmentRequired = "DUTY_ASSIGNMENT_REQUIRED"
issueNoGovernedDocument = "NO_GOVERNED_DOCUMENT"
issueDutyAssigneeNotActive = "DUTY_ASSIGNEE_NOT_ACTIVE_MEMBER"
issueSeparationOfDutyViolation = "SEPARATION_OF_DUTY_VIOLATION"
)
// ClassifyDocDuty 供运行时/门禁复用,禁止平行再写一套三职判定。
func ClassifyDocDuty(d DocDuty) DutyMode {
return classifyDocDuty(d)
}
func classifyDocDuty(d DocDuty) DutyMode {
ids := []int64{d.AuthorUserID, d.ReviewerUserID, d.ApproverUserID}
assigned := 0
for _, id := range ids {
if id < 0 {
return DutyModePartial
}
if id > 0 {
assigned++
}
}
switch assigned {
case 0:
return DutyModeNone
case 3:
return DutyModeFull
default:
return DutyModePartial
}
}
func docTaskRequiresDuty(td workflowdef.DocTaskDef) bool {
return td.Required || td.Blocking || !td.IsCreatorConfigurable()
}
func normalizeDocTaskDuties(dt DocTaskPlan) DocTaskPlan {
if !dt.Enabled || classifyDocDuty(dt.Duties) == DutyModeNone {
dt.Duties = DocDuty{}
}
return dt
}
func normalizeWorkflowPlanDuties(plan WorkflowPlan) WorkflowPlan {
for i := range plan.Stages {
for j := range plan.Stages[i].DocTasks {
plan.Stages[i].DocTasks[j] = normalizeDocTaskDuties(plan.Stages[i].DocTasks[j])
}
}
return plan
}
func validateDocTaskDuties(dt DocTaskPlan, td workflowdef.DocTaskDef) []workflowdef.ValidationIssue {
if !dt.Enabled {
return nil
}
mode := classifyDocDuty(dt.Duties)
field := dt.TaskKey + ".duties"
switch mode {
case DutyModePartial:
return []workflowdef.ValidationIssue{{
Code: issueDutyPartialAssignment,
Field: field,
Message: fmt.Sprintf("文档「%s」不能只指派部分职责", dutyTaskLabel(dt)),
}}
case DutyModeNone:
if docTaskRequiresDuty(td) {
return []workflowdef.ValidationIssue{{
Code: issueDutyAssignmentRequired,
Field: field,
Message: fmt.Sprintf("文档「%s」必须完整指派编制、审核、批准", dutyTaskLabel(dt)),
}}
}
return nil
default:
if dt.Duties.AuthorUserID == dt.Duties.ReviewerUserID ||
dt.Duties.AuthorUserID == dt.Duties.ApproverUserID ||
dt.Duties.ReviewerUserID == dt.Duties.ApproverUserID {
return []workflowdef.ValidationIssue{{
Code: issueSeparationOfDutyViolation,
Field: dt.TaskKey,
Message: "作者、审核人、批准人必须分离",
}}
}
return nil
}
}
func countFullDutyDocs(plan WorkflowPlan) int {
n := 0
for _, st := range plan.Stages {
if !st.Enabled {
continue
}
for _, dt := range st.DocTasks {
if dt.Enabled && classifyDocDuty(dt.Duties) == DutyModeFull {
n++
}
}
}
return n
}
func validateActiveMemberDuties(plan WorkflowPlan, active map[int64]struct{}) []workflowdef.ValidationIssue {
if active == nil {
return nil
}
issues := make([]workflowdef.ValidationIssue, 0)
for _, st := range plan.Stages {
if !st.Enabled {
continue
}
for _, dt := range st.DocTasks {
if !dt.Enabled || classifyDocDuty(dt.Duties) != DutyModeFull {
continue
}
for _, item := range []struct {
id int64
role string
label string
}{
{dt.Duties.AuthorUserID, "author", "编制"},
{dt.Duties.ReviewerUserID, "reviewer", "审核"},
{dt.Duties.ApproverUserID, "approver", "批准"},
} {
if _, ok := active[item.id]; ok {
continue
}
issues = append(issues, workflowdef.ValidationIssue{
Code: issueDutyAssigneeNotActive,
Field: dt.TaskKey + "." + item.role,
Message: fmt.Sprintf("文档「%s」的%s负责人不是在任项目成员", dutyTaskLabel(dt), item.label),
})
}
}
}
return issues
}
func dutyTaskLabel(dt DocTaskPlan) string {
if dt.TaskTitle != "" {
return dt.TaskTitle
}
return dt.TaskKey
}