134 lines
3.7 KiB
Go
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{})
|
|
}
|
|
}
|
|
}
|