/// <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="projectId">L'identifiant du projet.</param> /// <param name="sourceScenarioId">L'identifiant du scénario source.</param> /// <param name="natureCode">Le code de la nature.</param> /// <param name="save"><c>true</c> pour sauvegarder le scénario créé.</param> /// <returns> /// Le scénario créé /// </returns> public static async Task <Scenario> CreateDerivatedScenario(KsmedEntities context, int projectId, int sourceScenarioId, string natureCode, bool save) { Scenario fromScenario; using (var tempContext = ContextFactory.GetNewContext()) { // Charger les référentiels var referentialsUsed = await SharedScenarioActionsOperations.GetReferentialsUse(context, projectId); await Queries.LoadAllReferentialsOfProject(context, projectId, referentialsUsed); var videos = await context.Projects .Include(nameof(Project.Process)) .Include($"{nameof(Project.Process)}.{nameof(Procedure.Videos)}") .Where(p => p.ProjectId == projectId) .SelectMany(p => p.Process.Videos) .ToArrayAsync(); fromScenario = await context.Scenarios.FirstAsync(s => s.ScenarioId == sourceScenarioId); await Queries.LoadScenariosDetails(context, EnumerableExt.Concat(fromScenario), referentialsUsed); } return(await CreateDerivatedScenario(context, fromScenario, natureCode, save)); }
/// <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); }
/// <summary> /// Clone une action pour qu'elle soit utilisée dans un nouveau scénario. /// </summary> /// <param name="action">The action.</param> /// <param name="cloneBehavior">The clone behavior.</param> /// <returns></returns> public static KAction CloneAction(KAction action, ActionCloneBehavior cloneBehavior) { var newAction = new KAction(); var originalValues = action.GetCurrentValues(); // Ignorer certaines propriétés var excludedPropertyNames = new List <string> { "ActionId", "ScenarioId", "OriginalActionId", "Predecessors", "Successors", "Reduced" }; // Quand on passe vers un scénario de validation, il ne faut pas copier la vignette car on supprime les leins avec les vidéos if (cloneBehavior == ActionCloneBehavior.TargetToRealized) { excludedPropertyNames.Add("Thumbnail"); excludedPropertyNames.Add("IsThumbnailSpecific"); excludedPropertyNames.Add("ThumbnailPosition"); } // Vérifier que ces noms de propriétés soient corrects if (excludedPropertyNames.Except(originalValues.Keys).Any()) { throw new InvalidOperationException("Les noms de propriétés présents dans excludedPropertyNames ne sont pas valides."); } foreach (var kvp in originalValues) { if (!excludedPropertyNames.Contains(kvp.Key)) { newAction.SetPropertyValue(kvp.Key, kvp.Value); } } // Copier les liens actions / référentiel CloneReferentialActionsLinks(action, newAction); // Copier la partie Reduced switch (cloneBehavior) { case ActionCloneBehavior.InitialToTarget: { // S'il existe un prétypage par la catégorie, il est prioritaire. Sinon, utiliser celui sur l'action s'il y en a. string actionTypeCode; if (action.Category != null && action.Category.ActionTypeCode != null) { actionTypeCode = action.Category.ActionTypeCode; } else { actionTypeCode = action.IsReduced ? action.Reduced.ActionTypeCode : KnownActionCategoryTypes.I; } SharedScenarioActionsOperations.ApplyNewReduced(newAction, actionTypeCode); } break; case ActionCloneBehavior.TargetToTarget: if (action.Reduced.ActionTypeCode == null) { throw new InvalidOperationException("Une action cible doit toujours être réduite avec un type"); } if (!ActionsTimingsMoveManagement.GetIsSolutionApproved(action).GetValueOrDefault(true)) { // Tache I provenant du grand parent, on n'applique pas le prétype mais on applique la réduc SharedScenarioActionsOperations.ApplyNewReduced(newAction, KnownActionCategoryTypes.I); } else { switch (action.Reduced.ActionTypeCode) { case KnownActionCategoryTypes.I: // Déterminer si la tâche provient d'un scénario ancêtre if (action.OriginalActionId != null) { // Tache I provenant du grand parent, on n'applique pas le prétype mais on applique la réduc SharedScenarioActionsOperations.ApplyNewReduced(newAction, KnownActionCategoryTypes.I); } else { // Tache I provenant du parent, on applique le prétypage et la réduc s'il y en a string actionTypeCode; if (action.Category != null && action.Category.ActionTypeCode != null) { actionTypeCode = action.Category.ActionTypeCode; } else { actionTypeCode = KnownActionCategoryTypes.I; } SharedScenarioActionsOperations.ApplyNewReduced(newAction, actionTypeCode); } break; case KnownActionCategoryTypes.E: // Tâche E, on conserve E et on applique la réduc SharedScenarioActionsOperations.ApplyNewReduced(newAction, KnownActionCategoryTypes.E); break; case KnownActionCategoryTypes.S: SharedScenarioActionsOperations.ApplyReduced(action, newAction); break; default: throw new ArgumentOutOfRangeException(nameof(action), new ArgumentOutOfRangeException(nameof(KAction.Reduced), new ArgumentOutOfRangeException(nameof(KActionReduced.ActionTypeCode)))); } } break; case ActionCloneBehavior.TargetToRealized: // Pour les tâches externes, il faut les faire réapparaitre comme externes // Pour les tâches internes, il faut qu'elle aient une partie réduite pour pouvoir les repasser à exerne. switch (action.Reduced.ActionTypeCode) { case KnownActionCategoryTypes.I: SharedScenarioActionsOperations.ApplyNewReduced(newAction, KnownActionCategoryTypes.I); break; case KnownActionCategoryTypes.E: SharedScenarioActionsOperations.ApplyNewReduced(newAction, KnownActionCategoryTypes.E); break; } break; case ActionCloneBehavior.Cascade: SharedScenarioActionsOperations.ApplyReduced(action, newAction); break; case ActionCloneBehavior.RealizedToNewInitial: case ActionCloneBehavior.TargetToNewInitial: case ActionCloneBehavior.InitialToNewInitial: // Pour les tâches externes, il faut les faire réapparaitre comme externes // Pour les tâches internes, il faut qu'elle aient une partie réduite pour pouvoir les repasser à exerne. if (action.Reduced != null) { switch (action.Reduced.ActionTypeCode) { case KnownActionCategoryTypes.I: SharedScenarioActionsOperations.ApplyNewReduced(newAction, KnownActionCategoryTypes.I); break; case KnownActionCategoryTypes.E: SharedScenarioActionsOperations.ApplyNewReduced(newAction, KnownActionCategoryTypes.E); break; } } break; default: throw new ArgumentOutOfRangeException(nameof(cloneBehavior)); } return(newAction); }
/// <summary> /// Met à jour les actions récursivement sur les scénarios dérivés de celui spécifié. /// </summary> /// <param name="context">Le contexte EF.</param> /// <param name="sourceScenario">Le scénario source.</param> /// <param name="allScenarios">Tous les scénarios qui peuvent être impactés.</param> /// <param name="actionsToRemove">Les actions à supprimer manuellement.</param> internal static void UpdateActions(KsmedEntities context, Scenario sourceScenario, Scenario[] allScenarios, out KAction[] actionsToRemove, out IList <KAction> actionsWithOriginal) { var derivedScenarios = ScenarioActionHierarchyHelper.GetDerivedScenarios(sourceScenario, allScenarios); var actions = GetActionsSortedWBS(sourceScenario); actionsWithOriginal = new List <KAction>(); foreach (var scenario in derivedScenarios) { // Mettre à jour IsGroup foreach (var action in scenario.Actions) { action.IsGroup = WBSHelper.HasChildren(action, scenario.Actions); } } foreach (var originalAction in actions) { // J'enlève le IsMArkedAsModified car les 2 références sont la sauvegarde des actions depuis la construction et depuis l'optimisation // Or depuis la construction, en modification, le bout de code cidessous est déjà appelé // Et depuis l'optimisation, il n'y a pas de changement de temps video if (originalAction.IsMarkedAsAdded /*|| originalAction.IsMarkedAsModified*/) { var originalValues = originalAction.ChangeTracker.OriginalValues; var modifiedValues = originalAction.ChangeTracker.ModifiedValues; if (originalAction.IsMarkedAsAdded || modifiedValues.ContainsKey(ActionsTimingsMoveManagement.KActionStartPropertyName) || modifiedValues.ContainsKey(ActionsTimingsMoveManagement.KActionFinishPropertyName)) { // Vérifier si le temps vidéo a changé ActionsTimingsMoveManagement.GetOrignalModifiedVideoDurations(originalAction, out long originalDuration, out long modifiedDuration); bool hasVideoDurationChanged = originalDuration != modifiedDuration; // Si c'est une tâche créée et non dupliquée, le buildDuration est à 0, donc on doit le mettre à jour //Sinon, si c'est une tâche dupliquée, on le laisse tel quel. if (originalAction.BuildDuration == 0) { var paceRating = originalAction.Resource != null ? originalAction.Resource.PaceRating : 1d; originalAction.BuildDuration = Convert.ToInt64(modifiedDuration * paceRating); } } } if (originalAction.IsMarkedAsAdded) { // Si l'action est une action nouvelle dans un scénario cible, définir automatiquement la partie réduite if (sourceScenario.NatureCode == KnownScenarioNatures.Target && originalAction.Reduced == null) { SharedScenarioActionsOperations.ApplyNewReduced(originalAction); } var originalActionKey = context.CreateEntityKey(KsmedEntities.KActionsEntitySetName, originalAction); var parentOriginalAction = WBSHelper.GetParent(originalAction, actions); foreach (var derivedScenario in derivedScenarios) { var derivedActions = GetActionsSortedWBS(derivedScenario); // Rechercher le parent dans le scénario dérivé var parentDerivedAction = ScenarioActionHierarchyHelper.GetDerivedAction(parentOriginalAction, derivedScenario); // Cloner l'action originale var newAction = ScenarioCloneManager.CloneAction(originalAction, ActionCloneBehavior.Cascade); // Assigner l'original var originalActionForCurrentDerivedScenario = derivedScenario.Original == sourceScenario ? originalAction : ScenarioActionHierarchyHelper.GetDerivedAction(originalAction, derivedScenario.Original); newAction.Original = originalActionForCurrentDerivedScenario; actionsWithOriginal.Add(newAction); // Insérer l'action clonée dans le scénario dérivé ActionsTimingsMoveManagement.InsertUpdateWBS( derivedActions, newAction, parentDerivedAction, WBSHelper.GetParts(originalAction.WBS).Last(), (a, wbs) => EnsureTracking(a)); // Rafraichir les actions derivedScenario.Actions.Add(newAction); derivedActions = GetActionsSortedWBS(derivedScenario); // Ajouter les mêmes prédécesseurs et successeurs foreach (var originalPredecessor in originalAction.Predecessors) { var derivedPredecessor = ScenarioActionHierarchyHelper.GetDerivedAction(originalPredecessor, derivedScenario); if (derivedPredecessor != null) { EnsureTracking(derivedPredecessor); ActionsTimingsMoveManagement.AddPredecessor(derivedActions, newAction, derivedPredecessor); } } foreach (var originalSuccessor in originalAction.Successors) { var derivedSuccessor = ScenarioActionHierarchyHelper.GetDerivedAction(originalSuccessor, derivedScenario); if (derivedSuccessor != null) { EnsureTracking(derivedSuccessor); ActionsTimingsMoveManagement.AddPredecessor(derivedActions, derivedSuccessor, newAction); } } EnsureTracking(derivedScenario); SharedScenarioActionsOperations.EnsureEmptySolutionExists(derivedScenario); SharedScenarioActionsOperations.UdpateSolutionsApprovedState(derivedScenario); ActionsTimingsMoveManagement.DebugCheckAllWBS(derivedActions); } } else if (originalAction.IsMarkedAsModified) { var originalValues = originalAction.ChangeTracker.OriginalValues; var modifiedValues = originalAction.ChangeTracker.ModifiedValues; var propertiesToCopyValues = new Dictionary <string, object>(); foreach (var propertyName in _kActionPropertyNamesToCopy) { if (modifiedValues.ContainsKey(propertyName)) { propertiesToCopyValues[propertyName] = modifiedValues[propertyName]; } } // Vérifier si les reduced doit être impactés également ActionsTimingsMoveManagement.GetOrignalModifiedBuildDurations(originalAction, out long originalDuration, out long modifiedDuration); bool hasBuildDurationChanged = originalDuration != modifiedDuration; foreach (var derivedScenario in derivedScenarios) { var derivedAction = ScenarioActionHierarchyHelper.GetDerivedAction(originalAction, derivedScenario); if (derivedAction != null) { EnsureTracking(derivedAction); foreach (var kvp in propertiesToCopyValues) { derivedAction.SetPropertyValue(kvp.Key, kvp.Value); } if (hasBuildDurationChanged) { if (derivedAction.IsReduced) { // Modifier l'original duration et recalculer le temps final en fonction du gain EnsureTracking(derivedAction.Reduced); derivedAction.Reduced.OriginalBuildDuration = modifiedDuration; ActionsTimingsMoveManagement.UpdateTimingsFromReducedReduction(derivedAction); } else { // Simplement recopier la durée derivedAction.BuildDuration = modifiedDuration; } } } } } } var toRemove = new List <KAction>(); // Gérer les actions supprimées // EF gérant mal l'ordre des suppressions, ça créer une ConstraintException sur la FK OriginalActionId // Malheureusement un CascadeDelete est impossible puisque la FK est sur un même table if (sourceScenario.ChangeTracker.ObjectsRemovedFromCollectionProperties.ContainsKey("Actions")) { var removedActions = sourceScenario.ChangeTracker.ObjectsRemovedFromCollectionProperties["Actions"].ToArray(); foreach (KAction originalAction in removedActions) { EnsureTracking(originalAction); toRemove.Add(originalAction); originalAction.MarkAsUnchanged(); foreach (var derivedScenario in derivedScenarios) { var derivedAction = ScenarioActionHierarchyHelper.GetDerivedAction(originalAction, derivedScenario); if (derivedAction != null) { var derivedActions = GetActionsSortedWBS(derivedScenario); // Mettre à jour les WBS des autres actions ActionsTimingsMoveManagement.DeleteUpdateWBS(derivedActions, derivedAction, (a, wbs) => EnsureTracking(a)); EnsureTracking(derivedAction); toRemove.Add(derivedAction); } } } // Il faut maintenant trier les actions à supprimer pour que la suppression se fasse dans le bon ordre toRemove.Reverse(); actionsToRemove = toRemove.ToArray(); } else { actionsToRemove = new KAction[] { } }; sourceScenario.CriticalPathIDuration = ActionsTimingsMoveManagement.GetInternalCriticalPathDuration(sourceScenario); foreach (var scenario in derivedScenarios) { EnsureTracking(scenario); ActionsTimingsMoveManagement.FixPredecessorsSuccessorsTimings(scenario.Actions.ToArray(), false); ActionsTimingsMoveManagement.UpdateVideoGroupsTiming(scenario.Actions.ToArray()); ActionsTimingsMoveManagement.UpdateBuildGroupsTiming(scenario.Actions.ToArray()); scenario.CriticalPathIDuration = ActionsTimingsMoveManagement.GetInternalCriticalPathDuration(scenario); } }