예제 #1
0
        /// <summary>
        /// Importe la décomposition vidéo spécifiée spécifié.
        /// </summary>
        /// <param name="mergeReferentials"><c>true</c> pour fusionner les référentiels.</param>
        /// <param name="videosDirectory">Le dossier où se situent les vidéos.</param>
        public async Task ImportVideoDecomposition(bool mergeReferentials, string videosDirectory)
        {
            var videoDecomposition = _import.ExportedVideoDecomposition;

            // Videos
            PrepareVideo(videosDirectory);

            // Referentials
            ImportReferentials(mergeReferentials);

            // Actions
            ImportActions();

            // Remapper les référentiels du projet, des actions et des vidéos
            RemapReferentials();

            FixupImportedActions();

            // Custom text & Numeric labels
            ImportCustomFieldsLabels();

            _context.DetectChanges();

            // Passer les entités mappées à non modifiées
            FixupReferentialsTrackingState();

            if (mergeReferentials)
            {
                ServicesDiagnosticsDebug.CheckNotInContext(_context, _referentialsToRemap.Keys);
                ServicesDiagnosticsDebug.CheckObjectStateManagerState(_context, System.Data.Entity.EntityState.Unchanged, _referentialsToRemap.Values);
            }

            await _context.SaveChangesAsync();

            ServicesDiagnosticsDebug.CheckReferentialsState();
        }
예제 #2
0
        /// <summary>
        /// Importe le projet spécifié.
        /// </summary>
        /// <param name="context">Le contexte.</param>
        /// <param name="import">Le projet exporté.</param>
        /// <param name="mergeReferentials"><c>true</c> pour fusionner les référentiels.</param>
        /// <param name="videosDirectory">Le dossier où se situent les vidéos.</param>
        /// <returns>Le projet créé.</returns>
        private async Task <Project> ImportProject(KsmedEntities context, ProjectImport import, bool mergeReferentials, string videosDirectory)
        {
            // Projet
            var p = import.ExportedProject.Project;

            p.ProjectId = default(int);
            MarkAsAdded(p);

            // Ajouter l'utilisateur courant en tant qu'analyste créateur
            var owner = await context.Users.FirstAsync(u => !u.IsDeleted && u.Username == _securityContext.CurrentUser.Username);

            p.Process.Owner = owner;
            p.Process.UserRoleProcesses.Add(new UserRoleProcess()
            {
                User     = owner,
                RoleCode = KnownRoles.Analyst,
            });

            // Videos
            foreach (var video in p.Process.Videos)
            {
                if (video.DefaultResourceId.HasValue && video.DefaultResource == null)
                {
                    // Bug présent dans certains exports de la version 2.5.0.0. Supprimer le lien vers la ressource par défaut.
                    video.DefaultResourceId = null;
                }

                video.VideoId = default(int);
                MarkAsAdded(video);
                video.FilePath = IOHelper.ChangeDirectory(video.FilePath, videosDirectory);
            }

            var referentialsToRemap = new Dictionary <IActionReferential, IActionReferential>();

            if (!mergeReferentials)
            {
                // Référentiels process
                foreach (var refe in import.ExportedProject.ReferentialsProject)
                {
                    MarkAsAdded(refe);
                }

                // Référentiels standard
                foreach (var refe in import.ExportedProject.ReferentialsStandard)
                {
                    var refProject = ReferentialsFactory.CopyToNewProject(refe);

                    // Associer au process
                    refProject.Process = p.Process;

                    referentialsToRemap[refe] = refProject;
                }
            }
            else
            {
                // Référentiels process
                foreach (var refe in import.ExportedProject.ReferentialsProject)
                {
                    // Ajouter au tableau de remap s'il y a une correspondance.
                    if (import.ProjectReferentialsMergeCandidates.ContainsKey(refe))
                    {
                        referentialsToRemap[refe] = import.ProjectReferentialsMergeCandidates[refe];
                    }
                    else
                    {
                        MarkAsAdded(refe);
                    }
                }

                // Référentiels standard
                foreach (var refe in import.ExportedProject.ReferentialsStandard)
                {
                    if (import.StandardReferentialsMergeCandidates.ContainsKey(refe))
                    {
                        referentialsToRemap[refe] = import.StandardReferentialsMergeCandidates[refe];
                    }
                    else
                    {
                        var refProject = ReferentialsFactory.CopyToNewProject(refe);

                        // Associer au process
                        refProject.Process = p.Process;

                        referentialsToRemap[refe] = refProject;
                    }
                }
            }


            // Scénarios
            foreach (var scenario in p.Scenarios.Where(s => s.OriginalScenarioId.HasValue))
            {
                // Remapper l'original
                scenario.Original = p.Scenarios.Single(s => s.ScenarioId == scenario.OriginalScenarioId);
            }

            foreach (var scenario in p.Scenarios)
            {
                foreach (var action in scenario.Actions.Where(a => a.OriginalActionId.HasValue))
                {
                    // Remapper l'original
                    action.Original = p.Scenarios.SelectMany(s => s.Actions).Single(a => a.ActionId == action.OriginalActionId);
                }
            }

            foreach (var scenario in p.Scenarios)
            {
                scenario.ScenarioId = default(int);
                MarkAsAdded(scenario);

                // Supprimer le WebPublicationGuid
                scenario.WebPublicationGuid = null;

                // Actions
                foreach (var action in scenario.Actions)
                {
                    action.ActionId = default(int);
                    MarkAsAdded(action);

                    // Actions réduites
                    if (action.IsReduced)
                    {
                        MarkAsAdded(action.Reduced);
                    }
                }
            }

            // Remapper les référentiels du projet, des actions et des vidéos
            foreach (var oldReferential in referentialsToRemap.Keys)
            {
                ReferentialsHelper.UpdateReferentialReferences(p, oldReferential, referentialsToRemap[oldReferential]);
            }

            foreach (var scenario in p.Scenarios)
            {
                if (scenario.Original != null)
                {
                    context.Scenarios.ApplyChanges(scenario);
                    ObjectContextExt.SetRelationShipReferenceValue(context, scenario, scenario.Original, s => s.OriginalScenarioId);

                    foreach (var action in scenario.Actions)
                    {
                        if (action.Original != null)
                        {
                            context.KActions.ApplyChanges(action);
                            ObjectContextExt.SetRelationShipReferenceValue(context, action, action.Original, a => a.OriginalActionId);
                        }
                    }
                }
            }

            var resources = p.Scenarios.SelectMany(s => s.Actions).Select(a => a.Resource).Distinct().ToArray();

            context.Projects.ApplyChanges(p);

            if (mergeReferentials)
            {
                ServicesDiagnosticsDebug.CheckNotInContext(context, referentialsToRemap.Keys);
                ServicesDiagnosticsDebug.CheckObjectStateManagerState(context, EntityState.Unchanged, referentialsToRemap.Values);
            }

            ServicesDiagnosticsDebug.CheckReferentialsState();

            await context.SaveChangesAsync();

            return(p);
        }
예제 #3
0
        /// <summary>
        /// Sauvegarde le scénario spécifié.
        /// </summary>
        /// <param name="context">Le contexte EF.</param>
        /// <param name="allScenarios">Tous les scénarios liés.</param>
        /// <param name="recursive"><c>true</c> pour appliquer les changements récursivement sur les scénarios dérivés.</param>
        public static Task SaveBuildScenario(KsmedEntities context, Scenario[] allScenarios, Scenario updatedScenario, bool recursive)
        {
            // Consolider les solutions
            string[] distinctSolutionsLabels = updatedScenario.Actions
                                               .Where(a => a.IsReduced && !string.IsNullOrWhiteSpace(a.Reduced.Solution))
                                               .Select(a => a.Reduced.Solution)
                                               .Distinct()
                                               .ToArray();

            // Ajouter les nouvelles solutions
            foreach (string solutionLabel in distinctSolutionsLabels)
            {
                if (!updatedScenario.Solutions.Any(s => s.SolutionDescription == solutionLabel))
                {
                    // Créer une nouvelle solution
                    Solution solution = new Solution()
                    {
                        SolutionDescription = solutionLabel,
                    };
                    updatedScenario.Solutions.Add(solution);
                }
            }

            EnsureEmptySolutionExists(updatedScenario);

            // Supprimer les anciennes solutions
            Solution[] allSolutions = updatedScenario.Solutions.Where(s => !s.IsEmpty).ToArray();
            foreach (Solution sol in allSolutions)
            {
                if (!distinctSolutionsLabels.Contains(sol.SolutionDescription))
                {
                    sol.MarkAsDeleted();
                    updatedScenario.Solutions.Remove(sol);
                }
            }

            // Copier le temps original
            foreach (KActionReduced reduced in updatedScenario.Actions
                     .Where(a => a.IsReduced)
                     .Select(a => a.Reduced)
                     .Where(r => r.OriginalBuildDuration == default(long)))
            {
                reduced.OriginalBuildDuration = reduced.Action.BuildDuration;
            }

            // Appliquer l'état Approved
            UdpateSolutionsApprovedState(updatedScenario);

            ActionsRecursiveUpdate.UpdateActions(context, updatedScenario, allScenarios, out KAction[] actionsToDelete, out IList <KAction> actionsWithOriginal);

            KAction[] allActions = allScenarios
                                   .SelectMany(s => s.Actions)
                                   .Where(a => a.IsNotMarkedAsUnchanged || (a.IsReduced && a.Reduced.IsNotMarkedAsUnchanged))
                                   .ToArray();

            foreach (KAction action in allActions)
            {
                context.KActions.ApplyChanges(action);
                if (action.IsReduced)
                {
                    context.KActionsReduced.ApplyChanges(action.Reduced);
                }
            }

            foreach (KAction action in actionsWithOriginal)
            {
                SetActionsOriginalReference(context, action);
            }


            foreach (Scenario scenario in allScenarios.Where(s => s.IsNotMarkedAsUnchanged))
            {
                context.Scenarios.ApplyChanges(scenario);
            }

            foreach (Solution solution in updatedScenario.Solutions)
            {
                context.Solutions.ApplyChanges(solution);
            }

            // Vérifier que tout est correct
            ActionsTimingsMoveManagement.DebugCheckAllWBS(allScenarios.Where(s => s.IsNotMarkedAsUnchanged));

            return(context.SaveChangesAsync());
        }
예제 #4
0
        /// <summary>
        /// Sauvegarde les actions spécifiées.
        /// </summary>
        /// <param name="context">Le contexte.</param>
        /// <param name="allScenarios">Tous les scénarios liés.</param>
        /// <param name="updatedScenario">Le scénario qui a été mis à jour.</param>
        /// <param name="recursive"><c>true</c> pour appliquer les changements récursivement sur les scénarios dérivés.</param>
        public static Task SaveAcquireData(KsmedEntities context, Scenario[] allScenarios, Scenario updatedScenario, bool recursive)
        {
            KAction[]       actionsToDelete;
            IList <KAction> actionsWithOriginal;

            if (recursive)
            {
                ActionsRecursiveUpdate.UpdateActions(context, updatedScenario, allScenarios, out actionsToDelete, out actionsWithOriginal);
            }
            else
            {
                actionsWithOriginal = null;
                actionsToDelete     = updatedScenario.Actions.Where(a => a.IsMarkedAsDeleted).ToArray();
                updatedScenario.CriticalPathIDuration = ActionsTimingsMoveManagement.GetInternalCriticalPathDuration(updatedScenario);
            }

            // Consolider les solutions vides
            EnsureEmptySolutionExists(updatedScenario);
            UdpateSolutionsApprovedState(updatedScenario);

            KAction[] allActions = allScenarios
                                   .SelectMany(s => s.Actions)
                                   .Where(a => a.IsNotMarkedAsUnchanged)
                                   .ToArray();

            foreach (KAction action in allActions)
            {
                if (!action.IsReduced)
                {
                    ApplyNewReduced(action, KnownActionCategoryTypes.I);
                }
                context.KActions.ApplyChanges(action);
                context.KActionsReduced.ApplyChanges(action.Reduced);
            }

            if (actionsWithOriginal != null)
            {
                foreach (KAction action in actionsWithOriginal)
                {
                    SetActionsOriginalReference(context, action);
                }
            }

            foreach (Scenario scenario in allScenarios.Where(s => s.IsNotMarkedAsUnchanged))
            {
                context.Scenarios.ApplyChanges(scenario);
            }

            context.Scenarios.ApplyChanges(updatedScenario);

            foreach (KAction action in actionsToDelete)
            {
                action.Predecessors.Clear();
                action.Successors.Clear();
                action.MarkAsDeleted();

                // Ne pas appeler ApplyChanges car les self tracking le gèrent mal (plantage lors de la sauvegarde)
                // Ajouter l'action au contexte si elle n'y est pas attachée
                if (!context.ObjectStateManager.TryGetObjectStateEntry(action, out ObjectStateEntry entry))
                {
                    context.AddObject(KsmedEntities.KActionsEntitySetName, action);
                    context.ObjectStateManager.ChangeObjectState(action, EntityState.Deleted);
                }
                else
                {
                    context.KActions.DeleteObject(action);
                }
            }

            // Vérifier que tout est correct
            if (recursive)
            {
                ActionsTimingsMoveManagement.DebugCheckAllWBS(allScenarios.Where(s => s.IsNotMarkedAsUnchanged));
            }

            return(context.SaveChangesAsync());
        }
예제 #5
0
        /// <summary>
        /// Crée un scénario cible à partir d'un autre scénario, initial ou cible.
        /// </summary>
        /// <param name="context">Le contexte.</param>
        /// <param name="sourceScenario">Le scenario source.</param>
        /// <param name="natureCode">Le code de la nature.</param>
        /// <param name="save"><c>true</c> pour sauvegarder le scénario créé.</param>
        /// <param name="targetNumber">Le numéro cible.</param>
        /// <returns>
        /// Le scénario créé
        /// </returns>
        public static async Task <Scenario> CreateDerivatedScenario(KsmedEntities context, Scenario sourceScenario, string natureCode, bool save, int targetNumber)
        {
            // Charger les données du scénario source
            var newScenario = new Scenario();

            ActionCloneBehavior cloneBehavior;

            if (sourceScenario.NatureCode == KnownScenarioNatures.Initial && natureCode == KnownScenarioNatures.Target)
            {
                cloneBehavior = ActionCloneBehavior.InitialToTarget;
            }
            else if (sourceScenario.NatureCode == KnownScenarioNatures.Target && natureCode == KnownScenarioNatures.Target)
            {
                cloneBehavior = ActionCloneBehavior.TargetToTarget;
            }
            else if (sourceScenario.NatureCode == KnownScenarioNatures.Target && natureCode == KnownScenarioNatures.Realized)
            {
                cloneBehavior = ActionCloneBehavior.TargetToRealized;
            }
            else if (sourceScenario.NatureCode == KnownScenarioNatures.Realized && natureCode == KnownScenarioNatures.Initial)
            {
                cloneBehavior = ActionCloneBehavior.RealizedToNewInitial;
            }
            else if (sourceScenario.NatureCode == KnownScenarioNatures.Target && natureCode == KnownScenarioNatures.Initial)
            {
                cloneBehavior = ActionCloneBehavior.TargetToNewInitial;
            }
            else if (sourceScenario.NatureCode == KnownScenarioNatures.Initial && natureCode == KnownScenarioNatures.Initial)
            {
                cloneBehavior = ActionCloneBehavior.InitialToNewInitial;
            }
            else
            {
                throw new InvalidOperationException("Conversion impossible pour ces scénarios");
            }
            switch (natureCode)
            {
            case KnownScenarioNatures.Target:
                newScenario.Label = LocalizationManager.GetString("Business_AnalyzeService_TargetScenarioLabel") + " " + targetNumber;
                break;

            case KnownScenarioNatures.Realized:
                newScenario.Label = LocalizationManager.GetString("Business_AnalyzeService_ValidationScenarioLabel");
                break;

            case KnownScenarioNatures.Initial:
                newScenario.Label = LocalizationManager.GetString("Business_AnalyzeService_InitialScenarioLabel");
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(natureCode));
            }

            newScenario.StateCode  = KnownScenarioStates.Draft;
            newScenario.NatureCode = natureCode;
            if (cloneBehavior != ActionCloneBehavior.RealizedToNewInitial &&
                cloneBehavior != ActionCloneBehavior.TargetToNewInitial &&
                cloneBehavior != ActionCloneBehavior.InitialToNewInitial)
            {
                newScenario.ProjectId          = sourceScenario.ProjectId;
                newScenario.Original           = sourceScenario;
                newScenario.OriginalScenarioId = sourceScenario.ScenarioId;
            }
            newScenario.IsShownInSummary      = true;
            newScenario.CriticalPathIDuration = sourceScenario.CriticalPathIDuration;

            string[] scenarioLAbels = await EnsureCanShowScenarioInSummary(newScenario, true);

            // Copier toutes les actions
            foreach (var action in sourceScenario.Actions.ToArray())
            {
                var newAction = CloneAction(action, cloneBehavior);

                newAction.OriginalActionId = action.ActionId;
                newAction.Original         = action;
                if (newAction.Reduced != null)
                {
                    newAction.Reduced.OriginalBuildDuration = action.BuildDuration;
                }

                // S'il s'agit d'un scénario validé, utiliser les temps process en tant que temps vidéo
                if (cloneBehavior == ActionCloneBehavior.TargetToRealized)
                {
                    newAction.Start  = newAction.BuildStart;
                    newAction.Finish = newAction.BuildFinish;
                }

                newScenario.Actions.Add(newAction);
            }

            SharedScenarioActionsOperations.EnsureEmptySolutionExists(newScenario);
            SharedScenarioActionsOperations.UdpateSolutionsApprovedState(newScenario);

            // Copier les liens prédécesseurs successeurs
            foreach (var action in sourceScenario.Actions.ToArray())
            {
                var newAction = newScenario.Actions.FirstOrDefault(a => a.OriginalActionId == action.ActionId);
                if (newAction != null)
                {
                    foreach (var predecessor in action.Predecessors)
                    {
                        var newPredecessor = newScenario.Actions.FirstOrDefault(a => a.OriginalActionId == predecessor.ActionId);
                        if (newPredecessor != null)
                        {
                            newAction.Predecessors.Add(newPredecessor);
                        }
                    }
                }
            }

            //Suppression des actions avec durée = 0
            if (cloneBehavior == ActionCloneBehavior.TargetToRealized || cloneBehavior == ActionCloneBehavior.TargetToTarget)
            {
                ActionsRecursiveUpdate.RemoveEmptyDurationActionsAndGroupsFromNewScenario(newScenario);
            }

            if (cloneBehavior != ActionCloneBehavior.TargetToRealized &&        // ToDelete ne s'applique pas aux scenarios validés
                cloneBehavior != ActionCloneBehavior.InitialToNewInitial &&     // ToDelete ne s'applique pas aux scenarios initiaux
                cloneBehavior != ActionCloneBehavior.TargetToNewInitial &&      // ToDelete ne s'applique pas aux scenarios initiaux
                cloneBehavior != ActionCloneBehavior.RealizedToNewInitial)      // ToDelete ne s'applique pas aux scenarios initiaux
            {
                foreach (var newAction in newScenario.Actions)
                {
                    // Si la category associée est dite "à supprimer", modifier la tache optimisée à "à supprimer"
                    if (newAction.Category != null && newAction.Category.ActionTypeCode == KnownActionCategoryTypes.S)
                    {
                        SharedScenarioActionsOperations.ApplyNewReduced(newAction, KnownActionCategoryTypes.S);
                    }
                }
            }

            if (save)
            {
                context.Scenarios.ApplyChanges(newScenario);
                await context.SaveChangesAsync();
            }

            ActionsTimingsMoveManagement.FixPredecessorsSuccessorsTimings(newScenario.Actions.ToArray(), false);
            ActionsTimingsMoveManagement.UpdateVideoGroupsTiming(newScenario.Actions.ToArray());
            ActionsTimingsMoveManagement.UpdateBuildGroupsTiming(newScenario.Actions.ToArray());
            newScenario.CriticalPathIDuration = ActionsTimingsMoveManagement.GetInternalCriticalPathDuration(newScenario);

            // Supprimer les liens vers les originaux car dans un autre projet
            if (cloneBehavior == ActionCloneBehavior.RealizedToNewInitial ||
                cloneBehavior == ActionCloneBehavior.TargetToNewInitial ||
                cloneBehavior == ActionCloneBehavior.InitialToNewInitial)
            {
                foreach (var action in newScenario.Actions)
                {
                    action.Original         = null;
                    action.OriginalActionId = null;
                }

                newScenario.Original           = null;
                newScenario.OriginalScenarioId = null;
            }

            return(newScenario);
        }
예제 #6
0
        /// <summary>
        /// Sauvegarde les actions spécifiées.
        /// </summary>
        /// <param name="context">Le contexte.</param>
        /// <param name="allScenarios">Tous les scénarios liés.</param>
        /// <param name="updatedScenario">Le scénario qui a été mis à jour.</param>
        /// <param name="recursive"><c>true</c> pour appliquer les changements récursivement sur les scénarios dérivés.</param>
        public async Task <Scenario> SaveAcquireData(KsmedEntities context, Scenario[] allScenarios, Scenario updatedScenario, bool recursive)
        {
            try
            {
                // Don't insert a thumbnail that already exists
                var actionsWithNewThumbnail = updatedScenario.Actions.Where(_ =>
                                                                            (_.IsMarkedAsAdded && _.Thumbnail != null) ||
                                                                            (_.IsMarkedAsModified && _.ChangeTracker.ModifiedValues.ContainsKey(nameof(KAction.Thumbnail)) && _.Thumbnail != null));
                foreach (var action in actionsWithNewThumbnail)
                {
                    CloudFile thumbnail = updatedScenario.Actions.Where(_ => _.Thumbnail != null && _.ActionId != action.ActionId).Select(_ => _.Thumbnail).FirstOrDefault(_ => _.Hash == action.ThumbnailHash);
                    if (thumbnail == null)
                    {
                        thumbnail = await context.CloudFiles.SingleOrDefaultAsync(_ => _.Hash == action.Thumbnail.Hash);
                    }
                    if (thumbnail != null)
                    {
                        action.Thumbnail = thumbnail;
                    }
                }

                KAction[]       actionsToDelete;
                IList <KAction> actionsWithOriginal;

                if (recursive)
                {
                    ActionsRecursiveUpdate.UpdateActions(context, updatedScenario, allScenarios, out actionsToDelete, out actionsWithOriginal);
                }
                else
                {
                    actionsWithOriginal = null;
                    actionsToDelete     = updatedScenario.Actions.Where(a => a.IsMarkedAsDeleted).ToArray();
                    updatedScenario.CriticalPathIDuration = ActionsTimingsMoveManagement.GetInternalCriticalPathDuration(updatedScenario);
                }

                // Consolider les solutions vides
                EnsureEmptySolutionExists(updatedScenario);
                UdpateSolutionsApprovedState(updatedScenario);

                // Update InheritedAction if original will be deleted
                var actionIdsToDelete = actionsToDelete.Select(_ => _.ActionId).ToList();
                var inheritedActions  = await context.KActions
                                        .Where(_ => _.OriginalActionId != null && actionIdsToDelete.Contains(_.OriginalActionId.Value))
                                        .ToListAsync();

                foreach (var inheritedAction in inheritedActions)
                {
                    inheritedAction.OriginalActionId = null;
                    context.KActions.ApplyChanges(inheritedAction);
                }

                KAction[] allActions = allScenarios
                                       .SelectMany(s => s.Actions)
                                       .Where(a => a.IsNotMarkedAsUnchanged)
                                       .ToArray();

                foreach (KAction action in allActions)
                {
                    if (!action.IsReduced)
                    {
                        ApplyNewReduced(action, KnownActionCategoryTypes.I);
                    }
                    context.KActions.ApplyChanges(action);
                    context.KActionsReduced.ApplyChanges(action.Reduced);
                }

                if (actionsWithOriginal != null)
                {
                    foreach (KAction action in actionsWithOriginal)
                    {
                        SetActionsOriginalReference(context, action);
                    }
                }

                foreach (Scenario scenario in allScenarios.Where(s => s.IsNotMarkedAsUnchanged))
                {
                    context.Scenarios.ApplyChanges(scenario);
                }

                context.Scenarios.ApplyChanges(updatedScenario);

                foreach (KAction action in actionsToDelete)
                {
                    action.Predecessors.Clear();
                    action.Successors.Clear();
                    action.MarkAsDeleted();

                    // Ne pas appeler ApplyChanges car les self tracking le gèrent mal (plantage lors de la sauvegarde)
                    // Ajouter l'action au contexte si elle n'y est pas attachée
                    if (!context.ObjectStateManager.TryGetObjectStateEntry(action, out ObjectStateEntry entry))
                    {
                        context.AddObject(KsmedEntities.KActionsEntitySetName, action);
                        context.ObjectStateManager.ChangeObjectState(action, EntityState.Deleted);
                    }
                    else
                    {
                        context.KActions.DeleteObject(action);
                    }
                }

                // Vérifier que tout est correct
                if (recursive)
                {
                    ActionsTimingsMoveManagement.DebugCheckAllWBS(allScenarios.Where(s => s.IsNotMarkedAsUnchanged));
                }

                await context.SaveChangesAsync();

                return(updatedScenario);
            }
            catch (Exception ex)
            {
                TraceManager.TraceError(ex, $"Error while saving scenario {updatedScenario.ScenarioId}");
                throw;
            }
        }