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 } 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{}) } } }