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

134 lines
3.7 KiB
Go

package projectinstance
import (
"context"
"strings"
)
// UpdateLivePlan 在已激活实例上更新职责、审批策略和文档/阶段启用。
// 工作流定义与版本不可变。项目 143 保持冻结。
func (s *Service) UpdateLivePlan(ctx context.Context, projectID int64, userID int64, isCreator bool, in SavePlanInput) (*ProjectSettings, error) {
if !isCreator {
return nil, ErrForbiddenNotCreator
}
if projectID == readonlyLivePlanProjectID {
return nil, ErrPlanFrozen
}
if !s.Available() {
return nil, ErrNotAvailable
}
live, err := s.store.GetLiveInstance(ctx, projectID)
if err != nil {
return nil, err
}
if live == nil {
return nil, ErrInstanceNotFound
}
if live.Status == "completed" || live.Status == "terminated" {
return nil, ErrPlanFrozen
}
if in.ExpectedPlanRevision != live.ConfigRevision {
return nil, ErrInstanceRevisionConflict
}
plan := normalizeWorkflowPlanDuties(in.Plan)
if strings.TrimSpace(plan.WorkflowDefinitionKey) == "" {
plan.WorkflowDefinitionKey = live.ConfigSnapshot.WorkflowDefinitionKey
}
if plan.WorkflowVersion == 0 {
plan.WorkflowVersion = live.ConfigSnapshot.WorkflowVersion
}
if plan.WorkflowVersionID == 0 {
plan.WorkflowVersionID = live.WorkflowVersionID
}
if plan.WorkflowDefinitionKey != live.ConfigSnapshot.WorkflowDefinitionKey ||
plan.WorkflowVersion != live.ConfigSnapshot.WorkflowVersion ||
plan.WorkflowVersionID != live.WorkflowVersionID {
return nil, ErrWorkflowSwitchFrozen
}
plan.WorkflowVersionID = live.WorkflowVersionID
version, err := s.resolvePublishedVersion(ctx, plan)
if err != nil {
return nil, err
}
validation := validatePlan(plan, version)
if !validation.Valid {
return nil, newValidationError(validation)
}
if err := s.checkActiveMembers(ctx, projectID, plan); err != nil {
return nil, err
}
if planUsesAuthorOnly(plan) {
if _, err := s.requireAuthorOnlyAllowed(ctx); err != nil {
return nil, err
}
}
if err := s.rejectBusyDocTaskChanges(ctx, projectID, live.ConfigSnapshot, plan); err != nil {
return nil, err
}
stampPlanStageGating(&plan, version)
updated, err := s.store.UpdateLivePlan(ctx, projectID, plan, live.ConfigRevision, userID)
if err != nil {
return nil, err
}
s.projectLiveDuties(ctx, projectID, updated.ConfigSnapshot)
s.materializePlan(ctx, projectID, updated.ConfigSnapshot)
return s.GetSettings(ctx, projectID, userID, isCreator)
}
func (s *Service) rejectBusyDocTaskChanges(ctx context.Context, projectID int64, prev, next WorkflowPlan) error {
if s == nil || s.busyChecker == nil {
return nil
}
for _, st := range prev.Stages {
for _, before := range st.DocTasks {
after := findDocTaskPlan(next, before.TaskKey)
if after == nil || !liveDocTaskChanged(before, *after) {
continue
}
busy, reason, err := s.busyChecker.DocTaskSquadBusy(ctx, projectID, before.TaskKey)
if err != nil {
return err
}
if busy {
if reason == "" {
reason = "该文档小队任务进行中,完成后可更换负责人"
}
return &DocTaskBusyError{TaskKey: before.TaskKey, Reason: reason}
}
}
}
return nil
}
func liveDocTaskChanged(before, after DocTaskPlan) bool {
if before.Enabled != after.Enabled || before.DutyPolicy != after.DutyPolicy {
return true
}
return before.Duties != after.Duties
}
func (s *Service) projectLiveDuties(ctx context.Context, projectID int64, plan WorkflowPlan) {
if s == nil || s.projector == nil {
return
}
for _, st := range plan.Stages {
if !st.Enabled {
continue
}
for _, dt := range st.DocTasks {
if !dt.Enabled {
continue
}
mode, effective, _ := ResolveDocTaskDuty(dt)
if mode == DutyModeNone {
continue
}
_ = s.projector.ProjectDocTaskDuties(ctx, projectID, dt.TaskKey, effective, dt.DutyPolicy, DutyDisplayNames{})
}
}
}