diff --git a/Core.ApplicationServices/Core.ApplicationServices.csproj b/Core.ApplicationServices/Core.ApplicationServices.csproj index e51e88e0e0..95a35663c9 100644 --- a/Core.ApplicationServices/Core.ApplicationServices.csproj +++ b/Core.ApplicationServices/Core.ApplicationServices.csproj @@ -112,6 +112,7 @@ + @@ -149,6 +150,7 @@ + @@ -183,6 +185,8 @@ + + diff --git a/Core.ApplicationServices/Extensions/OrganizationTreeUpdateConsequencesExtensions.cs b/Core.ApplicationServices/Extensions/OrganizationTreeUpdateConsequencesExtensions.cs new file mode 100644 index 0000000000..b828b915cd --- /dev/null +++ b/Core.ApplicationServices/Extensions/OrganizationTreeUpdateConsequencesExtensions.cs @@ -0,0 +1,127 @@ +using Core.Abstractions.Types; +using Core.DomainModel.Organization; +using Core.DomainServices.Context; +using Core.DomainServices.Time; +using System.Collections.Generic; +using System.Linq; + +namespace Core.ApplicationServices.Extensions +{ + public static class OrganizationTreeUpdateConsequencesExtensions + { + public static ExternalConnectionAddNewLogInput ToLogEntries(this OrganizationTreeUpdateConsequences consequences, Maybe activeUserIdContext, IOperationClock operationClock) + { + var changeLogType = ExternalOrganizationChangeLogResponsible.Background; + int? changeLogUserId = null; + if (activeUserIdContext.HasValue) + { + var userId = activeUserIdContext.Value.ActiveUserId; + changeLogType = ExternalOrganizationChangeLogResponsible.User; + changeLogUserId = userId; + } + + var changeLogEntries = consequences.ConvertConsequencesToConsequenceLogs().ToList(); + var changeLogLogTime = operationClock.Now; + + return new ExternalConnectionAddNewLogInput(changeLogUserId, changeLogType, changeLogLogTime, MapToExternalConnectionAddNewLogEntryInput(changeLogEntries)); + } + + private static IEnumerable MapToExternalConnectionAddNewLogEntryInput(IEnumerable entry) + { + return entry + .Select(x => new ExternalConnectionAddNewLogEntryInput(x.ExternalUnitUuid, x.Name, x.Type, x.Description)) + .ToList(); + } + + public static IEnumerable ConvertConsequencesToConsequenceLogs(this OrganizationTreeUpdateConsequences consequences) + { + var logs = new List(); + logs.AddRange(MapAddedOrganizationUnits(consequences)); + logs.AddRange(MapRenamedOrganizationUnits(consequences)); + logs.AddRange(MapMovedOrganizationUnits(consequences)); + logs.AddRange(MapRemovedOrganizationUnits(consequences)); + logs.AddRange(MapConvertedOrganizationUnits(consequences)); + + return logs; + } + + private static IEnumerable MapConvertedOrganizationUnits(OrganizationTreeUpdateConsequences consequences) + { + return consequences + .DeletedExternalUnitsBeingConvertedToNativeUnits + .Select(converted => new StsOrganizationConsequenceLog + { + Name = converted.organizationUnit.Name, + Type = ConnectionUpdateOrganizationUnitChangeType.Converted, + ExternalUnitUuid = converted.externalOriginUuid, + Description = $"'{converted.organizationUnit.Name}' er slettet i FK Organisation men konverteres til KITOS enhed, da den anvendes aktivt i KITOS." + }) + .ToList(); + } + + private static IEnumerable MapRemovedOrganizationUnits(OrganizationTreeUpdateConsequences consequences) + { + return consequences + .DeletedExternalUnitsBeingDeleted + .Select(deleted => new StsOrganizationConsequenceLog + { + Name = deleted.organizationUnit.Name, + Type = ConnectionUpdateOrganizationUnitChangeType.Deleted, + ExternalUnitUuid = deleted.externalOriginUuid, + Description = $"'{deleted.organizationUnit.Name}' slettes." + }) + .ToList(); + } + + private static IEnumerable MapMovedOrganizationUnits(OrganizationTreeUpdateConsequences consequences) + { + return consequences + .OrganizationUnitsBeingMoved + .Select(moved => + { + var (movedUnit, oldParent, newParent) = moved; + return new StsOrganizationConsequenceLog + { + Name = movedUnit.Name, + Type = ConnectionUpdateOrganizationUnitChangeType.Moved, + ExternalUnitUuid = movedUnit.ExternalOriginUuid.GetValueOrDefault(), + Description = $"'{movedUnit.Name}' flyttes fra at være underenhed til '{oldParent.Name}' til fremover at være underenhed for {newParent.Name}" + }; + }) + .ToList(); + } + + private static IEnumerable MapRenamedOrganizationUnits(OrganizationTreeUpdateConsequences consequences) + { + return consequences + .OrganizationUnitsBeingRenamed + .Select(renamed => + { + var (affectedUnit, oldName, newName) = renamed; + return new StsOrganizationConsequenceLog + { + Name = oldName, + Type = ConnectionUpdateOrganizationUnitChangeType.Renamed, + ExternalUnitUuid = affectedUnit.ExternalOriginUuid.GetValueOrDefault(), + Description = $"'{oldName}' omdøbes til '{newName}'" + }; + }) + .ToList(); + } + + private static IEnumerable MapAddedOrganizationUnits(OrganizationTreeUpdateConsequences consequences) + { + return consequences + .AddedExternalOrganizationUnits + .Select(added => new StsOrganizationConsequenceLog + { + Name = added.unitToAdd.Name, + Type = ConnectionUpdateOrganizationUnitChangeType.Added, + ExternalUnitUuid = added.unitToAdd.Uuid, + Description = $"'{added.unitToAdd.Name}' tilføjes som underenhed til '{added.parent?.Name}'" + } + ) + .ToList(); + } + } +} diff --git a/Core.ApplicationServices/Model/Organizations/AuthorizedUpdateOrganizationFromFKOrganisationCommand.cs b/Core.ApplicationServices/Model/Organizations/AuthorizedUpdateOrganizationFromFKOrganisationCommand.cs new file mode 100644 index 0000000000..3167f550ad --- /dev/null +++ b/Core.ApplicationServices/Model/Organizations/AuthorizedUpdateOrganizationFromFKOrganisationCommand.cs @@ -0,0 +1,26 @@ +using Core.Abstractions.Types; +using Core.DomainModel.Commands; +using Core.DomainModel.Organization; + +namespace Core.ApplicationServices.Model.Organizations +{ + /// + /// Describes a pre-authorized update command for the FK Org synchronization. + /// Make sure to authorize the call prior to executing this command + /// + public class AuthorizedUpdateOrganizationFromFKOrganisationCommand : ICommand + { + public bool SubscribeToChanges { get; } + public Maybe SynchronizationDepth { get; } + public Organization Organization { get; } + public Maybe PreloadedExternalTree { get; } + + public AuthorizedUpdateOrganizationFromFKOrganisationCommand(Organization organization, Maybe synchronizationDepth, bool subscribeToChanges, Maybe preloadedExternalTree) + { + SubscribeToChanges = subscribeToChanges; + PreloadedExternalTree = preloadedExternalTree; + SynchronizationDepth = synchronizationDepth; + Organization = organization; + } + } +} diff --git a/Core.ApplicationServices/Model/Organizations/StsOrganizationSynchronizationDetails.cs b/Core.ApplicationServices/Model/Organizations/StsOrganizationSynchronizationDetails.cs index 83ca24d2b4..0610a81569 100644 --- a/Core.ApplicationServices/Model/Organizations/StsOrganizationSynchronizationDetails.cs +++ b/Core.ApplicationServices/Model/Organizations/StsOrganizationSynchronizationDetails.cs @@ -1,4 +1,5 @@ -using Core.DomainServices.Model.StsOrganization; +using System; +using Core.DomainServices.Model.StsOrganization; namespace Core.ApplicationServices.Model.Organizations { @@ -10,8 +11,10 @@ public class StsOrganizationSynchronizationDetails public bool CanUpdateConnection { get; } public bool CanDeleteConnection { get; } public CheckConnectionError? CheckConnectionError { get; } + public bool SubscribesToUpdates { get; } + public DateTime? DateOfLatestCheckBySubscription { get; } - public StsOrganizationSynchronizationDetails(bool connected, int? synchronizationDepth, bool canCreateConnection, bool canUpdateConnection, bool canDeleteConnection, CheckConnectionError? checkConnectionError) + public StsOrganizationSynchronizationDetails(bool connected, int? synchronizationDepth, bool canCreateConnection, bool canUpdateConnection, bool canDeleteConnection, CheckConnectionError? checkConnectionError, bool subscribesToUpdates, DateTime? dateOfLatestCheckBySubscription) { Connected = connected; SynchronizationDepth = synchronizationDepth; @@ -19,6 +22,8 @@ public StsOrganizationSynchronizationDetails(bool connected, int? synchronizatio CanUpdateConnection = canUpdateConnection; CanDeleteConnection = canDeleteConnection; CheckConnectionError = checkConnectionError; + SubscribesToUpdates = subscribesToUpdates; + DateOfLatestCheckBySubscription = dateOfLatestCheckBySubscription; } } } diff --git a/Core.ApplicationServices/Organizations/Handlers/AuthorizedUpdateOrganizationFromFKOrganisationCommandHandler.cs b/Core.ApplicationServices/Organizations/Handlers/AuthorizedUpdateOrganizationFromFKOrganisationCommandHandler.cs new file mode 100644 index 0000000000..7d7589ec5b --- /dev/null +++ b/Core.ApplicationServices/Organizations/Handlers/AuthorizedUpdateOrganizationFromFKOrganisationCommandHandler.cs @@ -0,0 +1,141 @@ +using Core.Abstractions.Types; +using Core.ApplicationServices.Model.Organizations; +using Core.DomainModel.Commands; +using Core.DomainModel.Events; +using Core.DomainModel.Organization; +using System; +using System.Linq; +using Core.ApplicationServices.Extensions; +using Core.DomainServices; +using Core.DomainServices.Organizations; +using Infrastructure.Services.DataAccess; +using Serilog; +using Core.DomainServices.Context; +using Core.DomainServices.Time; + +namespace Core.ApplicationServices.Organizations.Handlers +{ + public class AuthorizedUpdateOrganizationFromFKOrganisationCommandHandler : ICommandHandler> + { + private readonly IStsOrganizationUnitService _stsOrganizationUnitService; + private readonly IGenericRepository _organizationUnitRepository; + private readonly ILogger _logger; + private readonly IDomainEvents _domainEvents; + private readonly IDatabaseControl _databaseControl; + private readonly ITransactionManager _transactionManager; + private readonly Maybe _userContext; + private readonly IOperationClock _operationClock; + private readonly IGenericRepository _stsChangeLogRepository; + + public AuthorizedUpdateOrganizationFromFKOrganisationCommandHandler( + IStsOrganizationUnitService stsOrganizationUnitService, + IGenericRepository organizationUnitRepository, + ILogger logger, + IDomainEvents domainEvents, + IDatabaseControl databaseControl, + ITransactionManager transactionManager, + Maybe userContext, + IOperationClock operationClock, + IGenericRepository stsChangeLogRepository) + { + _stsOrganizationUnitService = stsOrganizationUnitService; + _organizationUnitRepository = organizationUnitRepository; + _logger = logger; + _domainEvents = domainEvents; + _databaseControl = databaseControl; + _transactionManager = transactionManager; + _userContext = userContext; + _operationClock = operationClock; + _stsChangeLogRepository = stsChangeLogRepository; + } + + public Maybe Execute(AuthorizedUpdateOrganizationFromFKOrganisationCommand command) + { + var organization = command.Organization; + using var transaction = _transactionManager.Begin(); + try + { + //Load the external tree if not already provided + var organizationTree = command + .PreloadedExternalTree + .Match(tree => tree, () => _stsOrganizationUnitService.ResolveOrganizationTree(organization)); + + if (organizationTree.Failed) + { + var error = organizationTree.Error; + _logger.Error("Unable to resolve external org tree for organization with uuid {uuid}. Failed with: {code}:{detail}:{message}", command.Organization.Uuid, error.FailureType, error.Detail, error.Message); + return new OperationError($"Failed to resolve org tree:{error.Message.GetValueOrFallback("")}:{error.Detail:G}:{error.FailureType:G}", error.FailureType); + } + + //Import the external tree into the organization + var updateResult = organization.UpdateConnectionToExternalOrganizationHierarchy(OrganizationUnitOrigin.STS_Organisation, organizationTree.Value, command.SynchronizationDepth, command.SubscribeToChanges); + if (updateResult.Failed) + { + var error = updateResult.Error; + _logger.Error("Failed importing org tree for organization with uuid {uuid}. Failed with: {code}:{message}", command.Organization.Uuid, error.FailureType, error.Message); + transaction.Rollback(); + return new OperationError($"Failed to import org tree:{error.Message.GetValueOrFallback("")}:{error.FailureType:G}", error.FailureType); + } + + //React on import consequences + var consequences = updateResult.Value; + + if (consequences.DeletedExternalUnitsBeingDeleted.Any()) + { + _organizationUnitRepository.RemoveRange(consequences.DeletedExternalUnitsBeingDeleted.Select(x => x.organizationUnit).ToList()); + } + foreach (var (affectedUnit, _, _) in consequences.OrganizationUnitsBeingRenamed) + { + _domainEvents.Raise(new EntityUpdatedEvent(affectedUnit)); + } + + if (IsBackgroundImport()) + { + organization.StsOrganizationConnection.DateOfLatestCheckBySubscription = DateTime.Now; + } + + var logEntries = consequences.ToLogEntries(_userContext, _operationClock); + + //We only add a change log entry if any changes were detected + if (logEntries.Entries.Any()) + { + var addLogResult = organization.AddExternalImportLog(OrganizationUnitOrigin.STS_Organisation, logEntries); + if (addLogResult.Failed) + { + var error = addLogResult.Error; + _logger.Error("Failed adding change log while importing org tree for organization with uuid {uuid}. Failed with: {code}:{message}", command.Organization.Uuid, error.FailureType, error.Message); + transaction.Rollback(); + return error; + } + + var addNewLogsResult = addLogResult.Value; + var removedChangeLogs = addNewLogsResult.RemovedChangeLogs.OfType().ToList(); + if (removedChangeLogs.Any()) + { + _stsChangeLogRepository.RemoveRange(removedChangeLogs); + } + } + + _domainEvents.Raise(new EntityUpdatedEvent(organization)); + _databaseControl.SaveChanges(); + transaction.Commit(); + + _domainEvents.Raise(new ExternalOrganizationConnectionUpdated(organization, organization.StsOrganizationConnection, logEntries)); + + return Maybe.None; + + } + catch (Exception e) + { + _logger.Error(e, "Exception during FK Org sync of organization with uuid:{uuid}", command.Organization.Uuid); + transaction.Rollback(); + return new OperationError("Exception during import", OperationFailure.UnknownError); + } + } + + private bool IsBackgroundImport() + { + return _userContext.IsNone; + } + } +} diff --git a/Core.ApplicationServices/Organizations/Handlers/SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandler.cs b/Core.ApplicationServices/Organizations/Handlers/SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandler.cs new file mode 100644 index 0000000000..0036ec5511 --- /dev/null +++ b/Core.ApplicationServices/Organizations/Handlers/SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandler.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Mail; +using System.Text; +using Core.DomainModel; +using Core.DomainModel.Events; +using Core.DomainModel.Organization; +using Core.DomainServices; +using Infrastructure.Services.Configuration; +using Serilog; + +namespace Core.ApplicationServices.Organizations.Handlers +{ + public class SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandler : IDomainEventHandler + { + private readonly IMailClient _mailClient; + private readonly ILogger _logger; + private readonly string _changeLogLink; + + public SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandler(IMailClient mailClient, ILogger logger, KitosUrl baseUrl) + { + _mailClient = mailClient; + _logger = logger; + _changeLogLink = new Uri(baseUrl.Url, "/#/local-config/import/organization").AbsoluteUri; + } + + public void Handle(ExternalOrganizationConnectionUpdated domainEvent) + { + var organization = domainEvent.Entity; + try + { + var logEntries = domainEvent.Changes.Entries.ToList(); + var shouldHandle = + domainEvent.Changes.ResponsibleType == ExternalOrganizationChangeLogResponsible.Background && + domainEvent.Connection.Origin == OrganizationUnitOrigin.STS_Organisation && + logEntries.Any(); + if (shouldHandle) + { + if (logEntries.Any()) + { + var localAdmins = organization.GetUsersWithRole(OrganizationRole.LocalAdmin, false).ToList(); + if (localAdmins.Any()) + { + var message = CreateMessage(localAdmins, organization); + _mailClient.Send(message); + } + else + { + _logger.Warning("No local admins in organization with id {uuid}, so no email can be sent as result of background update through an external connection", organization.Uuid); + } + } + } + } + catch (Exception e) + { + _logger.Error(e, "Failed while sending background update email to local admins in {orgName} ({uuid})", organization.Name, organization.Uuid); + } + } + + private MailMessage CreateMessage(IEnumerable receivers, Organization organization) + { + var mailContent = $"

Din organisation '{organization.Name}' har automatisk indlæst opdateringer fra FK Organisation.

" + + "

" + + $"" + + "Klik her for at se ændringsloggen" + + "." + + "

" + + "

" + + "Bemærk at denne mail ikke kan besvares." + + "

"; + var mailSubject = $"{organization.Name} i KITOS har automatisk indlæst opdateringer fra FK Organisation"; + + var message = new MailMessage + { + Subject = mailSubject, + Body = mailContent, + IsBodyHtml = true, + BodyEncoding = Encoding.UTF8, + }; + + foreach (var receiver in receivers) + { + message.To.Add(receiver.Email); + } + + return message; + } + } +} diff --git a/Core.ApplicationServices/Organizations/IOrganizationService.cs b/Core.ApplicationServices/Organizations/IOrganizationService.cs index 65dc90f8aa..ac767cca1d 100644 --- a/Core.ApplicationServices/Organizations/IOrganizationService.cs +++ b/Core.ApplicationServices/Organizations/IOrganizationService.cs @@ -45,5 +45,6 @@ public interface IOrganizationService Maybe RemoveOrganization(Guid organizationUuid, bool enforceDeletion); Result, OperationError> GetUserOrganizations(int userId); + Result CanActiveUserModifyCvr(Guid organizationUuid); } } diff --git a/Core.ApplicationServices/Organizations/IStsOrganizationSynchronizationService.cs b/Core.ApplicationServices/Organizations/IStsOrganizationSynchronizationService.cs index 2bf0b92ed6..655a067a11 100644 --- a/Core.ApplicationServices/Organizations/IStsOrganizationSynchronizationService.cs +++ b/Core.ApplicationServices/Organizations/IStsOrganizationSynchronizationService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Core.Abstractions.Types; using Core.ApplicationServices.Model.Organizations; using Core.DomainModel.Organization; @@ -10,42 +11,42 @@ public interface IStsOrganizationSynchronizationService /// /// Gets the synchronization details of the organization /// - /// /// Result GetSynchronizationDetails(Guid organizationId); /// /// Retrieves a view of the organization as it exists in STS Organization /// - /// - /// /// Result GetStsOrganizationalHierarchy(Guid organizationId, Maybe levelsToInclude); + /// /// Connect the organization to "STS Organisation" /// - /// - /// /// - Maybe Connect(Guid organizationId, Maybe levelsToInclude); + Maybe Connect(Guid organizationId, Maybe levelsToInclude, bool subscribeToUpdates); /// /// Disconnect the KITOS organization from STS Organisation /// - /// /// - Maybe Disconnect(Guid organizationId); + Maybe Disconnect(Guid organizationId, bool purgeUnusedExternalOrganizationUnits = false); /// /// Retrieves a view of the consequences of updating the synchronized hierarchy from that which exists in STS Organization /// - /// - /// /// Result GetConnectionExternalHierarchyUpdateConsequences(Guid organizationId, Maybe levelsToInclude); /// /// Updates the connection to the STS Organization /// - /// - /// /// - Maybe UpdateConnection(Guid organizationId, Maybe levelsToInclude); + Maybe UpdateConnection(Guid organizationId, Maybe levelsToInclude, bool subscribeToUpdates); + /// + /// Unsubscribes from automatic updates from STS Organization + /// + /// + Maybe UnsubscribeFromAutomaticUpdates(Guid organizationId); + /// Gets the last x change logs for the organization + /// + /// + Result, OperationError> GetChangeLogs(Guid organizationUuid, int numberOfChangeLogs); } } diff --git a/Core.ApplicationServices/Organizations/OrganizationService.cs b/Core.ApplicationServices/Organizations/OrganizationService.cs index d4bde8abad..59723960f9 100644 --- a/Core.ApplicationServices/Organizations/OrganizationService.cs +++ b/Core.ApplicationServices/Organizations/OrganizationService.cs @@ -130,6 +130,12 @@ public bool CanChangeOrganizationType(Organization organization, OrganizationTyp _authorizationContext.HasPermission(new DefineOrganizationTypePermission(organizationType, organization.Id)); } + public Result CanActiveUserModifyCvr(Guid organizationUuid) + { + return GetOrganization(organizationUuid, OrganizationDataReadAccessLevel.All) + .Select(_ => _userContext.IsGlobalAdmin()); + } + public Result CreateNewOrganization(Organization newOrg) { if (newOrg == null) diff --git a/Core.ApplicationServices/Organizations/StsOrganizationSynchronizationService.cs b/Core.ApplicationServices/Organizations/StsOrganizationSynchronizationService.cs index 90ab624375..1f55ddbacb 100644 --- a/Core.ApplicationServices/Organizations/StsOrganizationSynchronizationService.cs +++ b/Core.ApplicationServices/Organizations/StsOrganizationSynchronizationService.cs @@ -1,17 +1,24 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Runtime.Remoting.Messaging; +using Core.Abstractions.Extensions; using Core.Abstractions.Types; using Core.ApplicationServices.Authorization; using Core.ApplicationServices.Authorization.Permissions; +using Core.ApplicationServices.Extensions; using Core.ApplicationServices.Model.Organizations; +using Core.DomainModel.Commands; using Core.DomainModel.Events; +using Core.DomainModel.Extensions; using Core.DomainModel.Organization; using Core.DomainServices; +using Core.DomainServices.Context; using Core.DomainServices.Model.StsOrganization; using Core.DomainServices.Organizations; +using Core.DomainServices.Time; using Infrastructure.Services.DataAccess; using Serilog; -using Organization = Core.DomainModel.Organization.Organization; namespace Core.ApplicationServices.Organizations { @@ -24,8 +31,11 @@ public class StsOrganizationSynchronizationService : IStsOrganizationSynchroniza private readonly IDatabaseControl _databaseControl; private readonly ITransactionManager _transactionManager; private readonly IDomainEvents _domainEvents; - private readonly IGenericRepository _organizationUnitRepository; private readonly IAuthorizationContext _authorizationContext; + private readonly Maybe _activeUserIdContext; + private readonly IGenericRepository _stsChangeLogRepository; + private readonly IOperationClock _operationClock; + private readonly ICommandBus _commandBus; public StsOrganizationSynchronizationService( IAuthorizationContext authorizationContext, @@ -36,7 +46,10 @@ public StsOrganizationSynchronizationService( IDatabaseControl databaseControl, ITransactionManager transactionManager, IDomainEvents domainEvents, - IGenericRepository organizationUnitRepository) + Maybe activeUserIdContext, + IGenericRepository stsChangeLogRepository, + IOperationClock operationClock, + ICommandBus commandBus) { _stsOrganizationUnitService = stsOrganizationUnitService; _organizationService = organizationService; @@ -45,8 +58,11 @@ public StsOrganizationSynchronizationService( _databaseControl = databaseControl; _transactionManager = transactionManager; _domainEvents = domainEvents; - _organizationUnitRepository = organizationUnitRepository; _authorizationContext = authorizationContext; + _activeUserIdContext = activeUserIdContext; + _stsChangeLogRepository = stsChangeLogRepository; + _operationClock = operationClock; + _commandBus = commandBus; } public Result GetSynchronizationDetails(Guid organizationId) @@ -56,6 +72,8 @@ public Result GetSynchron { var currentConnectionStatus = ValidateConnection(organization); var isConnected = organization.StsOrganizationConnection?.Connected == true; + var subscribesToUpdates = organization.StsOrganizationConnection?.SubscribeToUpdates == true; + var dateOfLatestCheckBySubscription = organization.StsOrganizationConnection?.DateOfLatestCheckBySubscription; var canCreateConnection = currentConnectionStatus.IsNone && organization.StsOrganizationConnection?.Connected != true; var canUpdateConnection = currentConnectionStatus.IsNone && isConnected; return new StsOrganizationSynchronizationDetails @@ -65,7 +83,9 @@ public Result GetSynchron canCreateConnection, canUpdateConnection, isConnected, - currentConnectionStatus.Match(error => error.Detail, () => default(CheckConnectionError?)) + currentConnectionStatus.Match(error => error.Detail, () => default(CheckConnectionError?)), + subscribesToUpdates, + dateOfLatestCheckBySubscription ); }); } @@ -83,33 +103,33 @@ public Result GetStsOrganizationalHier .Bind(root => FilterByRequestedLevels(root, levelsToInclude)); } - public Maybe Connect(Guid organizationId, Maybe levelsToInclude) + public Maybe Connect(Guid organizationId, Maybe levelsToInclude, bool subscribeToUpdates) { return Modify(organizationId, organization => { return LoadOrganizationUnits(organization) - .Match - ( - importRoot => - { - var error = organization.ConnectToExternalOrganizationHierarchy(OrganizationUnitOrigin.STS_Organisation, importRoot, levelsToInclude); - if (error.HasValue) - { - _logger.Error("Failed to import org root {rootId} and subtree into organization with id {orgId}. Failed with: {errorCode}:{errorMessage}", importRoot.Uuid, organization.Id, error.Value.FailureType, error.Value.Message.GetValueOrFallback("")); - return new OperationError("Failed to import sub tree", OperationFailure.UnknownError); - } - - return Maybe.None; - }, - error => error - ); + .Bind(importRoot => ConnectToExternalOrganizationHierarchy(organization, importRoot, levelsToInclude, subscribeToUpdates)) + .Select(ToConnectionConsequences) + .Select(x => x.ToLogEntries(_activeUserIdContext, _operationClock)) + .Bind(logEntries => organization.AddExternalImportLog(OrganizationUnitOrigin.STS_Organisation, logEntries)) + .MatchFailure(); }); } - public Maybe Disconnect(Guid organizationId) + public Maybe Disconnect(Guid organizationId, bool purgeUnusedExternalOrganizationUnits) { return Modify(organizationId, organization => { + if (purgeUnusedExternalOrganizationUnits) + { + //Perform sync to level 1 before disconnecting and let the update functionality deal with the consequence calculations + var purgeError = _commandBus.Execute>(new AuthorizedUpdateOrganizationFromFKOrganisationCommand(organization, 1, false, ToExternalUnitWithoutChildren(organization.GetRoot()))); + if (purgeError.HasValue) + { + _logger.Error("Failed to sync to level 1 prior to disconnecting from FK Org in organization {id}. Error: {code}:{message}", organizationId, purgeError.Value.FailureType, purgeError.Value.Message.GetValueOrDefault()); + return purgeError; + } + } var result = organization.DisconnectOrganizationFromExternalSource(OrganizationUnitOrigin.STS_Organisation); if (result.Failed) { @@ -117,6 +137,11 @@ public Maybe Disconnect(Guid organizationId) } var disconnectionResult = result.Value; + if (disconnectionResult.RemovedChangeLogs.Any()) + { + _stsChangeLogRepository.RemoveRange(disconnectionResult.RemovedChangeLogs); + } + foreach (var convertedUnit in disconnectionResult.ConvertedUnits) { _domainEvents.Raise(new EntityUpdatedEvent(convertedUnit)); @@ -125,6 +150,12 @@ public Maybe Disconnect(Guid organizationId) }); } + private static ExternalOrganizationUnit ToExternalUnitWithoutChildren(OrganizationUnit unit) + { + return new ExternalOrganizationUnit(unit.ExternalOriginUuid.GetValueOrDefault(), unit.Name, + new Dictionary(), Array.Empty()); + } + public Result GetConnectionExternalHierarchyUpdateConsequences(Guid organizationId, Maybe levelsToInclude) { return GetOrganizationWithImportPermission(organizationId) @@ -138,27 +169,23 @@ public Result GetConnectionE ); } - public Maybe UpdateConnection(Guid organizationId, Maybe levelsToInclude) + public Maybe UpdateConnection(Guid organizationId, Maybe levelsToInclude, bool subscribeToUpdates) { return Modify(organizationId, organization => - LoadOrganizationUnits(organization) - .Bind(importRoot => organization.UpdateConnectionToExternalOrganizationHierarchy(OrganizationUnitOrigin.STS_Organisation, importRoot, levelsToInclude)) - .Select(consequences => - { - if (consequences.DeletedExternalUnitsBeingDeleted.Any()) - { - _organizationUnitRepository.RemoveRange(consequences.DeletedExternalUnitsBeingDeleted); - } - foreach (var (affectedUnit, _, _) in consequences.OrganizationUnitsBeingRenamed) - { - _domainEvents.Raise(new EntityUpdatedEvent(affectedUnit)); - } - return consequences; - }) - .Match(_ => Maybe.None, error => error) + _commandBus.Execute>(new AuthorizedUpdateOrganizationFromFKOrganisationCommand(organization, levelsToInclude, subscribeToUpdates, Maybe.None)) ); } + public Maybe UnsubscribeFromAutomaticUpdates(Guid organizationId) + { + return Modify(organizationId, organization => organization.UnsubscribeFromAutomaticUpdates(OrganizationUnitOrigin.STS_Organisation)); + } + + public Result, OperationError> GetChangeLogs(Guid organizationUuid, int numberOfChangeLogs) + { + return GetOrganizationWithImportPermission(organizationUuid) + .Bind(organization => organization.GetExternalConnectionEntryLogs(OrganizationUnitOrigin.STS_Organisation, numberOfChangeLogs)); + } private Result LoadOrganizationUnits(Organization organization) { @@ -221,5 +248,31 @@ private Maybe Modify(Guid organizationUuid, Func.None; } + + private static Result ConnectToExternalOrganizationHierarchy( + Organization organization, ExternalOrganizationUnit importRoot, Maybe levelsToInclude, bool subscribeToUpdates) + { + return organization + .ConnectToExternalOrganizationHierarchy(OrganizationUnitOrigin.STS_Organisation, importRoot, levelsToInclude, subscribeToUpdates) + .Match + ( + error => error, + () => Result.Success(importRoot) + ); + } + + private static OrganizationTreeUpdateConsequences ToConnectionConsequences(ExternalOrganizationUnit importRoot) + { + var unitsToImport = importRoot.Flatten(); + var importedTreeToParent = importRoot.ToParentMap(importRoot.ToLookupByUuid()); + var consequences = new OrganizationTreeUpdateConsequences( + Enumerable.Empty<(Guid, OrganizationUnit)>(), + Enumerable.Empty<(Guid, OrganizationUnit)>(), + unitsToImport.Select(unit => (unit, importedTreeToParent[unit.Uuid])).ToList(), + Enumerable.Empty<(OrganizationUnit affectedUnit, string oldName, string newName)>(), + Enumerable + .Empty<(OrganizationUnit movedUnit, OrganizationUnit oldParent, ExternalOrganizationUnit newParent)>()); + return consequences; + } } } diff --git a/Core.BackgroundJobs/Core.BackgroundJobs.csproj b/Core.BackgroundJobs/Core.BackgroundJobs.csproj index 2b1036c6ef..6f690db9d3 100644 --- a/Core.BackgroundJobs/Core.BackgroundJobs.csproj +++ b/Core.BackgroundJobs/Core.BackgroundJobs.csproj @@ -81,6 +81,7 @@ + diff --git a/Core.BackgroundJobs/Model/Maintenance/ScheduleFkOrgUpdatesBackgroundJob.cs b/Core.BackgroundJobs/Model/Maintenance/ScheduleFkOrgUpdatesBackgroundJob.cs new file mode 100644 index 0000000000..4babc49465 --- /dev/null +++ b/Core.BackgroundJobs/Model/Maintenance/ScheduleFkOrgUpdatesBackgroundJob.cs @@ -0,0 +1,100 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Core.Abstractions.Extensions; +using Core.Abstractions.Types; +using Core.ApplicationServices.Model.Organizations; +using Core.ApplicationServices.ScheduledJobs; +using Core.DomainModel.Commands; +using Core.DomainModel.Organization; +using Core.DomainServices.Repositories.Organization; +using Core.DomainServices.Time; +using Serilog; + +namespace Core.BackgroundJobs.Model.Maintenance +{ + public class ScheduleFkOrgUpdatesBackgroundJob : IAsyncBackgroundJob + { + private readonly IHangfireApi _hangfireApi; + private readonly IOrganizationRepository _organizationRepository; + private readonly IOperationClock _operationClock; + private readonly ICommandBus _commandBus; + private readonly ILogger _logger; + public string Id => StandardJobIds.ScheduleFkOrgUpdates; + + public ScheduleFkOrgUpdatesBackgroundJob( + IHangfireApi hangfireApi, + IOrganizationRepository organizationRepository, + ILogger logger, + IOperationClock operationClock, + ICommandBus commandBus) + { + _hangfireApi = hangfireApi; + _organizationRepository = organizationRepository; + _logger = logger; + _operationClock = operationClock; + _commandBus = commandBus; + } + + public Task> ExecuteAsync(CancellationToken token = default) + { + var uuids = _organizationRepository + .GetAll() + .Where + (x => + x.StsOrganizationConnection != null && + x.StsOrganizationConnection.Connected && + x.StsOrganizationConnection.SubscribeToUpdates + ) + .Select(x => x.Uuid) + .ToList(); + + var counter = 0; + var offsetInMinutes = 0; + var dateTimeReference = _operationClock.Now; + foreach (var uuid in uuids) + { + //Add some spread to the start of the synchronizations + offsetInMinutes += (counter % 2); // allow two in parallel + var offset = dateTimeReference.AddMinutes(offsetInMinutes); + _hangfireApi.Schedule(() => PerformImportFromFKOrganisation(uuid), offset); + counter++; + } + + return Task.FromResult(Result.Success($"Scheduled {uuids.Count} sync jobs")); + } + + public void PerformImportFromFKOrganisation(Guid organizationUuid) + { + var getOrganizationResult = _organizationRepository.GetByUuid(organizationUuid); + if (getOrganizationResult.IsNone) + { + _logger.Error("Unable to perform sync for organization with uui {uuid} because the repository returned None", organizationUuid); + } + else + { + var organization = getOrganizationResult.Value; + if (organization.StsOrganizationConnection?.SubscribeToUpdates != true) + { + _logger.Warning("Sync job for organization with uuid {uuid} ignored since organization no longer subscribes to updates", organizationUuid); + return; + } + + try + { + var command = new AuthorizedUpdateOrganizationFromFKOrganisationCommand(organization, organization.StsOrganizationConnection.SynchronizationDepth.FromNullableValueType(), true, Maybe.None); + var error = _commandBus.Execute>(command); + if (error.HasValue) + { + _logger.Error("Error while automatically importing from FK org into org with uuid {uuid}. Error: {error}", organization, error.Select(e => e.ToString()).GetValueOrDefault()); + } + } + catch (Exception e) + { + _logger.Error(e, "Exception during FK Org sync of organization with uuid:{uuid}", organizationUuid); + } + } + } + } +} diff --git a/Core.BackgroundJobs/Model/StandardJobIds.cs b/Core.BackgroundJobs/Model/StandardJobIds.cs index b858a6f955..91dd43b59d 100644 --- a/Core.BackgroundJobs/Model/StandardJobIds.cs +++ b/Core.BackgroundJobs/Model/StandardJobIds.cs @@ -16,6 +16,7 @@ public static class StandardJobIds public static readonly string PurgeDuplicatePendingReadModelUpdates = $"{NamePrefix}purge-duplicate-read-model-updates"; public static readonly string ScheduleUpdatesForItSystemUsageReadModelsWhichChangesActiveState = $"{NamePrefix}fix-stale-itsystem-usage-rms"; public static readonly string ScheduleUpdatesForItContractOverviewReadModelsWhichChangesActiveState = $"{NamePrefix}fix-stale-itcontract-rms"; + public static readonly string ScheduleFkOrgUpdates = $"{NamePrefix}schedule-fk-org-updates"; public static readonly string PurgeOrphanedHangfireJobs = $"{NamePrefix}purge-orphaned-hangfire-jobs"; } } diff --git a/Core.BackgroundJobs/Services/BackgroundJobLauncher.cs b/Core.BackgroundJobs/Services/BackgroundJobLauncher.cs index e033b79f02..d1f6b3e12a 100644 --- a/Core.BackgroundJobs/Services/BackgroundJobLauncher.cs +++ b/Core.BackgroundJobs/Services/BackgroundJobLauncher.cs @@ -27,6 +27,7 @@ public class BackgroundJobLauncher : IBackgroundJobLauncher private readonly RebuildItContractOverviewReadModelsBatchJob _rebuildItContractOverviewReadModelsBatchJob; private readonly ScheduleItContractOverviewReadModelUpdates _scheduleItContractOverviewReadModelUpdates; private readonly ScheduleUpdatesForItContractOverviewReadModelsWhichChangesActiveState _contractOverviewReadModelsWhichChangesActiveState; + private readonly ScheduleFkOrgUpdatesBackgroundJob _scheduleFkOrgUpdatesBackgroundJob; public BackgroundJobLauncher( ILogger logger, @@ -38,10 +39,11 @@ public BackgroundJobLauncher( IRebuildReadModelsJobFactory rebuildReadModelsJobFactory, PurgeDuplicatePendingReadModelUpdates purgeDuplicatePendingReadModelUpdates, ScheduleUpdatesForItSystemUsageReadModelsWhichChangesActiveState scheduleUpdatesForItSystemUsageReadModelsWhichChangesActive, - PurgeOrphanedHangfireJobs purgeOrphanedHangfireJobs, + PurgeOrphanedHangfireJobs purgeOrphanedHangfireJobs, RebuildItContractOverviewReadModelsBatchJob rebuildItContractOverviewReadModelsBatchJob, - ScheduleItContractOverviewReadModelUpdates scheduleItContractOverviewReadModelUpdates, - ScheduleUpdatesForItContractOverviewReadModelsWhichChangesActiveState contractOverviewReadModelsWhichChangesActiveState) + ScheduleItContractOverviewReadModelUpdates scheduleItContractOverviewReadModelUpdates, + ScheduleUpdatesForItContractOverviewReadModelsWhichChangesActiveState contractOverviewReadModelsWhichChangesActiveState, + ScheduleFkOrgUpdatesBackgroundJob scheduleFkOrgUpdatesBackgroundJob) { _logger = logger; _checkExternalLinksJob = checkExternalLinksJob; @@ -56,6 +58,7 @@ public BackgroundJobLauncher( _rebuildItContractOverviewReadModelsBatchJob = rebuildItContractOverviewReadModelsBatchJob; _scheduleItContractOverviewReadModelUpdates = scheduleItContractOverviewReadModelUpdates; _contractOverviewReadModelsWhichChangesActiveState = contractOverviewReadModelsWhichChangesActiveState; + _scheduleFkOrgUpdatesBackgroundJob = scheduleFkOrgUpdatesBackgroundJob; } public async Task LaunchUpdateItContractOverviewReadModels(CancellationToken token = default) @@ -68,6 +71,11 @@ public async Task LaunchUpdateStaleContractRmAsync(CancellationToken token = def await Launch(_contractOverviewReadModelsWhichChangesActiveState, token); } + public async Task LaunchUpdateFkOrgSync(CancellationToken token = default) + { + await Launch(_scheduleFkOrgUpdatesBackgroundJob, token); + } + public async Task LaunchLinkCheckAsync(CancellationToken token = default) { await Launch(_checkExternalLinksJob, token); diff --git a/Core.BackgroundJobs/Services/BackgroundJobScheduler.cs b/Core.BackgroundJobs/Services/BackgroundJobScheduler.cs index a534b825da..e7a6adc805 100644 --- a/Core.BackgroundJobs/Services/BackgroundJobScheduler.cs +++ b/Core.BackgroundJobs/Services/BackgroundJobScheduler.cs @@ -8,7 +8,7 @@ public class BackgroundJobScheduler : IBackgroundJobScheduler { public void ScheduleLinkCheckForImmediateExecution() { - RecurringJob.Trigger(StandardJobIds.CheckExternalLinks); + RecurringJob.TriggerJob(StandardJobIds.CheckExternalLinks); } } } diff --git a/Core.DomainModel/Constants/ExternalConnectionConstants.cs b/Core.DomainModel/Constants/ExternalConnectionConstants.cs new file mode 100644 index 0000000000..6c373b4988 --- /dev/null +++ b/Core.DomainModel/Constants/ExternalConnectionConstants.cs @@ -0,0 +1,7 @@ +namespace Core.DomainModel.Constants +{ + public class ExternalConnectionConstants + { + public const int TotalNumberOfLogs = 5; + } +} diff --git a/Core.DomainModel/Core.DomainModel.csproj b/Core.DomainModel/Core.DomainModel.csproj index b7f6d79d6a..68980ef877 100644 --- a/Core.DomainModel/Core.DomainModel.csproj +++ b/Core.DomainModel/Core.DomainModel.csproj @@ -57,6 +57,7 @@ + @@ -91,15 +92,26 @@ + + + + + + + + + + + diff --git a/Core.DomainModel/Extensions/ExternalOrganizationUnitExtensions.cs b/Core.DomainModel/Extensions/ExternalOrganizationUnitExtensions.cs index e5ccc97e21..f86a78f46b 100644 --- a/Core.DomainModel/Extensions/ExternalOrganizationUnitExtensions.cs +++ b/Core.DomainModel/Extensions/ExternalOrganizationUnitExtensions.cs @@ -1,10 +1,42 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using Core.DomainModel.Organization; namespace Core.DomainModel.Extensions { public static class ExternalOrganizationUnitExtensions { + /// + /// Maps root external organization unit to parent tree + /// + /// + /// + /// + public static Dictionary ToParentMap(this ExternalOrganizationUnit root, Dictionary importedTreeByUuid) + { + var importedTreeToParent = importedTreeByUuid + .Values + .SelectMany(parent => parent.Children.Select(child => (child, parent))) + .ToDictionary(x => x.child.Uuid, x => x.parent); + + importedTreeToParent.Add(root.Uuid, null); //Add the root as that will not be part of the collection + return importedTreeToParent; + } + + /// + /// Maps root unit to flattened tree containing units children + /// + /// + /// + public static Dictionary ToLookupByUuid(this ExternalOrganizationUnit root) + { + var importedTreeByUuid = root + .Flatten() + .ToDictionary(x => x.Uuid); + return importedTreeByUuid; + } + /// /// Based on the current root, returns a collection containing the current root as well as nodes in the entire subtree /// diff --git a/Core.DomainModel/Extensions/HierarchyExtensions.cs b/Core.DomainModel/Extensions/HierarchyExtensions.cs index ffcd84b281..204fde74aa 100644 --- a/Core.DomainModel/Extensions/HierarchyExtensions.cs +++ b/Core.DomainModel/Extensions/HierarchyExtensions.cs @@ -102,5 +102,29 @@ public static Maybe SearchAncestry(this TEntity currentEntity, currentRoot = currentParent; } } + + public static Maybe SearchSubTree(this TEntity root, Predicate condition) where TEntity : class, IHierarchy + { + var unreached = new Queue(); + + unreached.Enqueue(root); + + //Process one level at the time + while (unreached.Count > 0) + { + var orgUnit = unreached.Dequeue(); + if (condition(orgUnit)) + { + return orgUnit; + } + + foreach (var child in orgUnit.Children) + { + unreached.Enqueue(child); + } + } + + return Maybe.None; + } } } diff --git a/Core.DomainModel/KendoConfig/OverviewType.cs b/Core.DomainModel/KendoConfig/OverviewType.cs index ad4be9bed6..83403cd521 100644 --- a/Core.DomainModel/KendoConfig/OverviewType.cs +++ b/Core.DomainModel/KendoConfig/OverviewType.cs @@ -4,5 +4,6 @@ public enum OverviewType { ItSystemUsage = 0, ItContract = 1, + DataProcessingRegistration = 2 } } diff --git a/Core.DomainModel/Organization/ConnectionUpdateOrganizationUnitChangeType.cs b/Core.DomainModel/Organization/ConnectionUpdateOrganizationUnitChangeType.cs new file mode 100644 index 0000000000..51a33778e6 --- /dev/null +++ b/Core.DomainModel/Organization/ConnectionUpdateOrganizationUnitChangeType.cs @@ -0,0 +1,11 @@ +namespace Core.DomainModel.Organization +{ + public enum ConnectionUpdateOrganizationUnitChangeType + { + Added = 0, + Renamed = 1, + Moved = 2, + Deleted = 3, + Converted = 4 + } +} diff --git a/Core.DomainModel/Organization/DisconnectOrganizationFromOriginResult.cs b/Core.DomainModel/Organization/DisconnectOrganizationFromOriginResult.cs index 6f6dfba60a..90f3fecd8a 100644 --- a/Core.DomainModel/Organization/DisconnectOrganizationFromOriginResult.cs +++ b/Core.DomainModel/Organization/DisconnectOrganizationFromOriginResult.cs @@ -5,10 +5,13 @@ namespace Core.DomainModel.Organization { public class DisconnectOrganizationFromOriginResult { - public IEnumerable ConvertedUnits { get; } - public DisconnectOrganizationFromOriginResult(IEnumerable convertedUnits) + public DisconnectOrganizationFromOriginResult(IEnumerable convertedUnits, IEnumerable removedChangeLogs) { ConvertedUnits = convertedUnits.ToList().AsReadOnly(); + RemovedChangeLogs = removedChangeLogs; } + + public IEnumerable ConvertedUnits { get; } + public IEnumerable RemovedChangeLogs { get; } } } diff --git a/Core.DomainModel/Organization/ExternalConnectionAddNewLogEntryInput.cs b/Core.DomainModel/Organization/ExternalConnectionAddNewLogEntryInput.cs new file mode 100644 index 0000000000..c4d80cdf22 --- /dev/null +++ b/Core.DomainModel/Organization/ExternalConnectionAddNewLogEntryInput.cs @@ -0,0 +1,20 @@ +using System; + +namespace Core.DomainModel.Organization +{ + public class ExternalConnectionAddNewLogEntryInput + { + public ExternalConnectionAddNewLogEntryInput(Guid uuid, string name, ConnectionUpdateOrganizationUnitChangeType type, string description) + { + Uuid = uuid; + Name = name; + Type = type; + Description = description; + } + + public Guid Uuid { get; } + public string Name { get; } + public ConnectionUpdateOrganizationUnitChangeType Type { get; } + public string Description { get; } + } +} diff --git a/Core.DomainModel/Organization/ExternalConnectionAddNewLogInput.cs b/Core.DomainModel/Organization/ExternalConnectionAddNewLogInput.cs new file mode 100644 index 0000000000..c79a60c8db --- /dev/null +++ b/Core.DomainModel/Organization/ExternalConnectionAddNewLogInput.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace Core.DomainModel.Organization +{ + public class ExternalConnectionAddNewLogInput + { + public ExternalConnectionAddNewLogInput(int? responsibleUserId, ExternalOrganizationChangeLogResponsible responsibleType, DateTime logTime, IEnumerable entries) + { + ResponsibleUserId = responsibleUserId; + ResponsibleType = responsibleType; + LogTime = logTime; + Entries = entries; + } + + public int? ResponsibleUserId { get; } + public ExternalOrganizationChangeLogResponsible ResponsibleType { get; } + public DateTime LogTime { get; } + public IEnumerable Entries { get; } + } +} diff --git a/Core.DomainModel/Organization/ExternalConnectionAddNewLogsResult.cs b/Core.DomainModel/Organization/ExternalConnectionAddNewLogsResult.cs new file mode 100644 index 0000000000..96f565cbe4 --- /dev/null +++ b/Core.DomainModel/Organization/ExternalConnectionAddNewLogsResult.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Core.DomainModel.Organization +{ + public class ExternalConnectionAddNewLogsResult + { + public ExternalConnectionAddNewLogsResult(IEnumerable removedChangeLogs) + { + RemovedChangeLogs = removedChangeLogs ?? new List(); + } + + public IEnumerable RemovedChangeLogs { get; } + } +} diff --git a/Core.DomainModel/Organization/ExternalOrganizationChangeLogResponsible.cs b/Core.DomainModel/Organization/ExternalOrganizationChangeLogResponsible.cs new file mode 100644 index 0000000000..a824b4c5ca --- /dev/null +++ b/Core.DomainModel/Organization/ExternalOrganizationChangeLogResponsible.cs @@ -0,0 +1,8 @@ +namespace Core.DomainModel.Organization +{ + public enum ExternalOrganizationChangeLogResponsible + { + Background = 0, + User = 1 + } +} diff --git a/Core.DomainModel/Organization/ExternalOrganizationConnectionUpdated.cs b/Core.DomainModel/Organization/ExternalOrganizationConnectionUpdated.cs new file mode 100644 index 0000000000..1f5fb70f29 --- /dev/null +++ b/Core.DomainModel/Organization/ExternalOrganizationConnectionUpdated.cs @@ -0,0 +1,17 @@ +using Core.DomainModel.Events; + +namespace Core.DomainModel.Organization +{ + public class ExternalOrganizationConnectionUpdated : EntityUpdatedEvent + { + public IExternalOrganizationalHierarchyConnection Connection { get; } + public ExternalConnectionAddNewLogInput Changes { get; } + + public ExternalOrganizationConnectionUpdated(Organization entity, IExternalOrganizationalHierarchyConnection connection, ExternalConnectionAddNewLogInput changes) + : base(entity) + { + Connection = connection; + Changes = changes; + } + } +} diff --git a/Core.DomainModel/Organization/IExternalConnectionChangeLogEntry.cs b/Core.DomainModel/Organization/IExternalConnectionChangeLogEntry.cs new file mode 100644 index 0000000000..4273f40344 --- /dev/null +++ b/Core.DomainModel/Organization/IExternalConnectionChangeLogEntry.cs @@ -0,0 +1,12 @@ +using System; + +namespace Core.DomainModel.Organization +{ + public interface IExternalConnectionChangeLogEntry + { + Guid ExternalUnitUuid { get; set; } + string Name { get; set; } + ConnectionUpdateOrganizationUnitChangeType Type { get; } + string Description { get; set; } + } +} diff --git a/Core.DomainModel/Organization/IExternalConnectionChangelog.cs b/Core.DomainModel/Organization/IExternalConnectionChangelog.cs new file mode 100644 index 0000000000..559e5b75ba --- /dev/null +++ b/Core.DomainModel/Organization/IExternalConnectionChangelog.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace Core.DomainModel.Organization +{ + public interface IExternalConnectionChangelog + { + User ResponsibleUser { get; } + ExternalOrganizationChangeLogResponsible ResponsibleType { get; } + + DateTime LogTime { get; } + IEnumerable GetEntries(); + } + +} diff --git a/Core.DomainModel/Organization/IExternalOrganizationalHierarchyConnection.cs b/Core.DomainModel/Organization/IExternalOrganizationalHierarchyConnection.cs new file mode 100644 index 0000000000..3efd1b17dd --- /dev/null +++ b/Core.DomainModel/Organization/IExternalOrganizationalHierarchyConnection.cs @@ -0,0 +1,23 @@ +using Core.Abstractions.Types; +using Core.DomainModel.Constants; +using Core.DomainModel.Organization.Strategies; +using System.Collections.Generic; + +namespace Core.DomainModel.Organization +{ + public interface IExternalOrganizationalHierarchyConnection + { + bool Connected { get; } + public int? SynchronizationDepth { get; } + IExternalOrganizationalHierarchyUpdateStrategy GetUpdateStrategy(); + bool SubscribeToUpdates { get; } + OrganizationUnitOrigin Origin { get; } + Result AddNewLog(ExternalConnectionAddNewLogInput newLog); + Result, OperationError> GetLastNumberOfChangeLogs(int number = ExternalConnectionConstants.TotalNumberOfLogs); + DisconnectOrganizationFromOriginResult Disconnect(); + void Connect(); + Maybe Subscribe(); + Maybe Unsubscribe(); + Maybe UpdateSynchronizationDepth(int? synchronizationDepth); + } +} diff --git a/Core.DomainModel/Organization/Organization.cs b/Core.DomainModel/Organization/Organization.cs index 8fc5c8a4df..3d8e59ccae 100644 --- a/Core.DomainModel/Organization/Organization.cs +++ b/Core.DomainModel/Organization/Organization.cs @@ -10,11 +10,9 @@ using Core.DomainModel.ItSystem; using Core.DomainModel.ItSystemUsage.Read; using Core.DomainModel.Notification; -using Core.DomainModel.Organization.Strategies; using Core.DomainModel.Tracking; using Core.DomainModel.UIConfiguration; - // ReSharper disable VirtualMemberCallInConstructor namespace Core.DomainModel.Organization @@ -198,30 +196,24 @@ public Result ComputeExterna { if (root == null) throw new ArgumentNullException(nameof(root)); - IExternalOrganizationalHierarchyUpdateStrategy strategy; - //Pre-validate - switch (origin) - { - case OrganizationUnitOrigin.STS_Organisation: - if (StsOrganizationConnection?.Connected != true) - { - return new OperationError($"Not connected to {origin:G}. Please connect before performing an update", OperationFailure.Conflict); - } - strategy = StsOrganizationConnection.GetUpdateStrategy(); - break; - case OrganizationUnitOrigin.Kitos: - return new OperationError("Kitos is not an external source", OperationFailure.BadInput); - default: - throw new ArgumentOutOfRangeException(); - } + return GetExternalConnection(origin) + .Bind(connection => + { + var strategy = connection.GetUpdateStrategy(); - var childLevelsToInclude = levelsIncluded.Select(levels => levels - 1); //subtract the root level before copying - var filteredTree = root.Copy(childLevelsToInclude); + var childLevelsToInclude = + levelsIncluded.Select(levels => levels - 1); //subtract the root level before copying + var filteredTree = root.Copy(childLevelsToInclude); - return strategy.ComputeUpdate(filteredTree); + return strategy.ComputeUpdate(filteredTree); + }); } - public Maybe ConnectToExternalOrganizationHierarchy(OrganizationUnitOrigin origin, ExternalOrganizationUnit root, Maybe levelsIncluded) + public Maybe ConnectToExternalOrganizationHierarchy( + OrganizationUnitOrigin origin, + ExternalOrganizationUnit root, + Maybe levelsIncluded, + bool subscribeToUpdates) { if (root == null) { @@ -257,7 +249,19 @@ public Maybe ConnectToExternalOrganizationHierarchy(Organization () => { StsOrganizationConnection ??= new StsOrganizationConnection(); - StsOrganizationConnection.Connected = true; + StsOrganizationConnection.Connect(); + + if (subscribeToUpdates != StsOrganizationConnection.SubscribeToUpdates) + { + var subscriptionError = subscribeToUpdates ? + StsOrganizationConnection.Subscribe() : + StsOrganizationConnection.Unsubscribe(); + if (subscriptionError.HasValue) + { + return subscriptionError.Value; + } + } + StsOrganizationConnection.SynchronizationDepth = levelsIncluded.Match(levels => (int?)levels, () => default); return Maybe.None; } @@ -266,28 +270,54 @@ public Maybe ConnectToExternalOrganizationHierarchy(Organization public Result DisconnectOrganizationFromExternalSource(OrganizationUnitOrigin origin) { - switch (origin) - { - case OrganizationUnitOrigin.STS_Organisation: - - if (StsOrganizationConnection?.Connected != true) - { - return new OperationError("Not connected", OperationFailure.BadState); - } - return StsOrganizationConnection.Disconnect(); - case OrganizationUnitOrigin.Kitos: - return new OperationError("Kitos is not an external source and cannot be disconnected", OperationFailure.BadInput); - default: - throw new ArgumentOutOfRangeException(); - } + return GetExternalConnection(origin) + .Bind(connection => connection.Disconnect()); } - public Result UpdateConnectionToExternalOrganizationHierarchy(OrganizationUnitOrigin origin, ExternalOrganizationUnit root, Maybe levelsIncluded) + public Result UpdateConnectionToExternalOrganizationHierarchy( + OrganizationUnitOrigin origin, + ExternalOrganizationUnit root, + Maybe levelsIncluded, + bool subscribeToUpdates) { if (root == null) throw new ArgumentNullException(nameof(root)); - IExternalOrganizationalHierarchyUpdateStrategy strategy; - //Pre-validate + return GetExternalConnection(origin) + .Bind(connection => + { + var strategy = connection.GetUpdateStrategy(); + + var childLevelsToInclude = + levelsIncluded.Select(levels => levels - 1); //subtract the root level before copying + var filteredTree = root.Copy(childLevelsToInclude); + connection.UpdateSynchronizationDepth(levelsIncluded.Match(levels => (int?)levels, + () => default)); + + if (subscribeToUpdates == StsOrganizationConnection.SubscribeToUpdates) + return strategy.PerformUpdate(filteredTree); + + var subscriptionError = subscribeToUpdates + ? StsOrganizationConnection.Subscribe() + : StsOrganizationConnection.Unsubscribe(); + return subscriptionError.Match( + error => error, () => strategy.PerformUpdate(filteredTree)); + }); + } + + public Result AddExternalImportLog(OrganizationUnitOrigin origin, ExternalConnectionAddNewLogInput changeLogToAdd) + { + return GetExternalConnection(origin) + .Bind(connection => connection.AddNewLog(changeLogToAdd)); + } + + public Result, OperationError> GetExternalConnectionEntryLogs(OrganizationUnitOrigin origin, int numberOfLogs) + { + return GetExternalConnection(origin) + .Bind(connection => connection.GetLastNumberOfChangeLogs(numberOfLogs)); + } + + private Result GetExternalConnection(OrganizationUnitOrigin origin) + { switch (origin) { case OrganizationUnitOrigin.STS_Organisation: @@ -295,19 +325,14 @@ public Result UpdateConnecti { return new OperationError($"Not connected to {origin:G}. Please connect before performing an update", OperationFailure.BadState); } - strategy = StsOrganizationConnection.GetUpdateStrategy(); - break; + + return StsOrganizationConnection; case OrganizationUnitOrigin.Kitos: return new OperationError("Kitos is not an external source", OperationFailure.BadInput); default: throw new ArgumentOutOfRangeException(); } - var childLevelsToInclude = levelsIncluded.Select(levels => levels - 1); //subtract the root level before copying - var filteredTree = root.Copy(childLevelsToInclude); - StsOrganizationConnection.SynchronizationDepth = levelsIncluded.Match(levels => (int?)levels, () => default); - - return strategy.PerformUpdate(filteredTree); } /// @@ -400,7 +425,7 @@ public Maybe RelocateOrganizationUnit(OrganizationUnit movedUnit else { //If subtree is to be moved along with it, then the target unit cannot be a descendant of the moved unit - if (movedUnit.FlattenHierarchy().Contains(newParentUnit)) + if (movedUnit.SearchSubTree(unit => unit.Uuid == newParentUnit.Uuid).HasValue) { return new OperationError($"newParentUnit with uuid {newParentUnit.Uuid} is a descendant of org unit with uuid {movedUnit.Uuid}", OperationFailure.BadInput); } @@ -461,5 +486,28 @@ private static bool MatchRoot(OrganizationUnit unit) { return unit.Parent == null; } + + public Maybe UnsubscribeFromAutomaticUpdates(OrganizationUnitOrigin origin) + { + return GetExternalConnection(origin) + .Match + ( + connection => connection.Unsubscribe(), + error => error + ); + } + + public IEnumerable GetUsersWithRole(OrganizationRole role, bool includeApiUsers = false) + { + var query = Rights.Where(x => x.Role == role); + if (!includeApiUsers) + { + query = query.Where(right => right.User.HasApiAccess != true); + } + return query + .Select(x => x.User) + .ToList() + .AsReadOnly(); + } } } \ No newline at end of file diff --git a/Core.DomainModel/Organization/OrganizationTreeUpdateConsequences.cs b/Core.DomainModel/Organization/OrganizationTreeUpdateConsequences.cs index 96528b984e..b5105171d1 100644 --- a/Core.DomainModel/Organization/OrganizationTreeUpdateConsequences.cs +++ b/Core.DomainModel/Organization/OrganizationTreeUpdateConsequences.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Core.DomainModel.Organization { @@ -7,15 +8,15 @@ namespace Core.DomainModel.Organization /// public class OrganizationTreeUpdateConsequences { - public IEnumerable DeletedExternalUnitsBeingConvertedToNativeUnits { get; } - public IEnumerable DeletedExternalUnitsBeingDeleted { get; } + public IEnumerable<(Guid externalOriginUuid, OrganizationUnit organizationUnit)> DeletedExternalUnitsBeingConvertedToNativeUnits { get; } + public IEnumerable<(Guid externalOriginUuid, OrganizationUnit organizationUnit)> DeletedExternalUnitsBeingDeleted { get; } public IEnumerable<(ExternalOrganizationUnit unitToAdd, ExternalOrganizationUnit parent)> AddedExternalOrganizationUnits { get; } public IEnumerable<(OrganizationUnit affectedUnit, string oldName, string newName)> OrganizationUnitsBeingRenamed { get; } public IEnumerable<(OrganizationUnit movedUnit, OrganizationUnit oldParent, ExternalOrganizationUnit newParent)> OrganizationUnitsBeingMoved { get; } public OrganizationTreeUpdateConsequences( - IEnumerable deletedExternalUnitsBeingConvertedToNativeUnits, - IEnumerable deletedExternalUnitsBeingDeleted, + IEnumerable<(Guid, OrganizationUnit)> deletedExternalUnitsBeingConvertedToNativeUnits, + IEnumerable<(Guid, OrganizationUnit)> deletedExternalUnitsBeingDeleted, IEnumerable<(ExternalOrganizationUnit unitToAdd, ExternalOrganizationUnit parent)> addedExternalOrganizationUnits, IEnumerable<(OrganizationUnit affectedUnit, string oldName, string newName)> organizationUnitsBeingRenamed, IEnumerable<(OrganizationUnit movedUnit, OrganizationUnit oldParent, ExternalOrganizationUnit newParent)> organizationUnitsBeingMoved) diff --git a/Core.DomainModel/Organization/Strategies/StsOrganizationalHierarchyUpdateStrategy.cs b/Core.DomainModel/Organization/Strategies/StsOrganizationalHierarchyUpdateStrategy.cs index af314f2b55..702ecd6650 100644 --- a/Core.DomainModel/Organization/Strategies/StsOrganizationalHierarchyUpdateStrategy.cs +++ b/Core.DomainModel/Organization/Strategies/StsOrganizationalHierarchyUpdateStrategy.cs @@ -27,16 +27,9 @@ public OrganizationTreeUpdateConsequences ComputeUpdate(ExternalOrganizationUnit throw new InvalidOperationException("No organization units from STS Organisation found in the current hierarchy"); } - var importedTreeByUuid = root - .Flatten() - .ToDictionary(x => x.Uuid); + var importedTreeByUuid = root.ToLookupByUuid(); - var importedTreeToParent = importedTreeByUuid - .Values - .SelectMany(parent => parent.Children.Select(child => (child, parent))) - .ToDictionary(x => x.child.Uuid, x => x.parent); - - importedTreeToParent.Add(root.Uuid, null); //Add the root as that will not be part of the collection + var importedTreeToParent = root.ToParentMap(importedTreeByUuid); //Keys in both collections var commonKeys = currentTreeByUuid.Keys.Intersect(importedTreeByUuid.Keys).ToList(); @@ -79,8 +72,8 @@ public OrganizationTreeUpdateConsequences ComputeUpdate(ExternalOrganizationUnit .Select(uuid => currentTreeByUuid[uuid]) .ToDictionary(x => x.Id); - var removedExternalUnitsWhichMustBeConverted = new List(); - var removedExternalUnitsWhichMustBeRemoved = new List(); + var removedExternalUnitsWhichMustBeConverted = new List<(Guid, OrganizationUnit)>(); + var removedExternalUnitsWhichMustBeRemoved = new List<(Guid, OrganizationUnit)>(); foreach (var candidateForRemoval in candidatesForRemovalById) { @@ -109,20 +102,21 @@ public OrganizationTreeUpdateConsequences ComputeUpdate(ExternalOrganizationUnit removedSubtreeIds.Remove(removedItem.Key); } + var externalOriginUuid = organizationUnit.ExternalOriginUuid.GetValueOrDefault(); if (removedSubtreeIds.Count != 1) { //Anything left except the candidate, then we must convert the unit to a KITOS-unit? - removedExternalUnitsWhichMustBeConverted.Add(organizationUnit); + removedExternalUnitsWhichMustBeConverted.Add((externalOriginUuid, organizationUnit)); } else if (organizationUnit.IsUsed()) { //If there is still registrations, we must convert it - removedExternalUnitsWhichMustBeConverted.Add(organizationUnit); + removedExternalUnitsWhichMustBeConverted.Add((externalOriginUuid, organizationUnit)); } else { //Safe to remove since there is no remaining sub tree and no remaining registrations tied to it - removedExternalUnitsWhichMustBeRemoved.Add(organizationUnit); + removedExternalUnitsWhichMustBeRemoved.Add((externalOriginUuid, organizationUnit)); } } @@ -160,7 +154,7 @@ public Result PerformUpdate( //Conversion to native units foreach (var unitToNativeUnit in consequences.DeletedExternalUnitsBeingConvertedToNativeUnits) { - unitToNativeUnit.ConvertToNativeKitosUnit(); + unitToNativeUnit.organizationUnit.ConvertToNativeKitosUnit(); } //Addition of new units @@ -185,8 +179,10 @@ public Result PerformUpdate( } //Relocation of existing units - foreach (var (movedUnit, oldParent, newParent) in consequences.OrganizationUnitsBeingMoved) + var processingQueue = new Queue<(OrganizationUnit movedUnit, OrganizationUnit oldParent, ExternalOrganizationUnit newParent)>(consequences.OrganizationUnitsBeingMoved); + while (processingQueue.Any()) { + var (movedUnit, oldParent, newParent) = processingQueue.Dequeue(); if (!currentTreeByUuid.TryGetValue(oldParent.ExternalOriginUuid.GetValueOrDefault(), out var oldParentUnit)) { return new OperationError($"Old parent unit with uuid {oldParent.Uuid} could not be found", OperationFailure.BadInput); @@ -198,28 +194,23 @@ public Result PerformUpdate( } - var nativeUnitsCreatedUnderMovedExternalUnit = movedUnit.Children.Where(child=>child.Origin == OrganizationUnitOrigin.Kitos).ToList(); - - //Move the moved unit without affecting the subtree (let the consequences decide that) - var relocationError = _organization.RelocateOrganizationUnit(movedUnit, oldParentUnit, newParentUnit, false); - if (relocationError.HasValue) + if (movedUnit.SearchSubTree(unit => unit.ExternalOriginUuid.GetValueOrDefault() == newParent.Uuid).HasValue) { - return relocationError.Value; + //Wait while the sub tree is processed so that we don't break relocation rules and not lose any retained child relations + processingQueue.Enqueue((movedUnit, oldParent, newParent)); } - //Make sure that "native" children created on the moved unit "tags along" as opposed to connected units which should only be moved if moved in fk org - foreach (var child in nativeUnitsCreatedUnderMovedExternalUnit) + else { - //Only native nodes can exist as children to native units, so reloacte the sub tree - var childRelocationError = _organization.RelocateOrganizationUnit(child, child.Parent, movedUnit, true); - if (childRelocationError.HasValue) + var relocationError = _organization.RelocateOrganizationUnit(movedUnit, oldParentUnit, newParentUnit, true); + if (relocationError.HasValue) { - return childRelocationError.Value; + return relocationError.Value; } } } //Deletion of units - foreach (var externalUnitToDelete in OrderUnitsToDeleteByLeafToParent(_organization.GetRoot(), consequences.DeletedExternalUnitsBeingDeleted)) + foreach (var externalUnitToDelete in OrderUnitsToDeleteByLeafToParent(_organization.GetRoot(), consequences.DeletedExternalUnitsBeingDeleted.Select(x => x.organizationUnit).ToList())) { externalUnitToDelete.ConvertToNativeKitosUnit(); //Convert to KITOS unit before deleting it (external units cannot be deleted) var deleteOrganizationUnitError = _organization.DeleteOrganizationUnit(externalUnitToDelete); diff --git a/Core.DomainModel/Organization/StsOrganizationChangelog.cs b/Core.DomainModel/Organization/StsOrganizationChangelog.cs new file mode 100644 index 0000000000..d90534fb05 --- /dev/null +++ b/Core.DomainModel/Organization/StsOrganizationChangelog.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Core.DomainModel.Organization +{ + public class StsOrganizationChangeLog : Entity, IExternalConnectionChangelog + { + public StsOrganizationChangeLog() + { + Entries = new List(); + } + + public virtual StsOrganizationConnection StsOrganizationConnection { get; set; } + public int StsOrganizationConnectionId { get; set; } + + public virtual User ResponsibleUser { get; set; } + public int? ResponsibleUserId { get; set; } + + public ExternalOrganizationChangeLogResponsible ResponsibleType { get; set; } + public DateTime LogTime { get; set; } + public virtual ICollection Entries { get; set; } + + public IEnumerable GetEntries() + { + return Entries.ToList(); + } + } +} diff --git a/Core.DomainModel/Organization/StsOrganizationConnection.cs b/Core.DomainModel/Organization/StsOrganizationConnection.cs index eb06676fd5..61872e0801 100644 --- a/Core.DomainModel/Organization/StsOrganizationConnection.cs +++ b/Core.DomainModel/Organization/StsOrganizationConnection.cs @@ -1,14 +1,22 @@ -using System.Linq; +using System; +using System.Linq; +using Core.Abstractions.Types; +using System.Collections.Generic; +using Core.DomainModel.Constants; using Core.DomainModel.Organization.Strategies; namespace Core.DomainModel.Organization { - /// /// Determines the properties of the organization's connection to STS Organisation /// - public class StsOrganizationConnection : Entity, IOwnedByOrganization + public class StsOrganizationConnection : Entity, IOwnedByOrganization, IExternalOrganizationalHierarchyConnection { + public StsOrganizationConnection() + { + StsOrganizationChangeLogs = new List(); + } + public int OrganizationId { get; set; } public virtual Organization Organization { get; set; } public bool Connected { get; set; } @@ -16,21 +24,143 @@ public class StsOrganizationConnection : Entity, IOwnedByOrganization /// Determines the optional synchronization depth used during synchronization from STS Organisation /// public int? SynchronizationDepth { get; set; } - //TODO https://os2web.atlassian.net/browse/KITOSUDV-3317 adds the change logs here - //TODO: https://os2web.atlassian.net/browse/KITOSUDV-3312 adds automatic subscription here + /// + /// Determines if the organization subscribes to automatic updates from STS Organisation + /// + public bool SubscribeToUpdates { get; set; } + + public OrganizationUnitOrigin Origin => OrganizationUnitOrigin.STS_Organisation; + + /// + /// The latest data where changes were checked due to an automatic subscription + /// This will be null if is false or no automatic check has run yet. + /// + public DateTime? DateOfLatestCheckBySubscription { get; set; } + + public virtual ICollection StsOrganizationChangeLogs { get; set; } + public DisconnectOrganizationFromOriginResult Disconnect() { var organizationUnits = Organization.OrgUnits.Where(x => x.Origin == OrganizationUnitOrigin.STS_Organisation).ToList(); organizationUnits.ForEach(unit => unit.ConvertToNativeKitosUnit()); + var removedLogs = RemoveAllLogs(); Connected = false; + SubscribeToUpdates = false; SynchronizationDepth = null; - return new DisconnectOrganizationFromOriginResult(organizationUnits); + DateOfLatestCheckBySubscription = null; + + return new DisconnectOrganizationFromOriginResult(organizationUnits, removedLogs); + } + + public void Connect() + { + Connected = true; + } + + public Maybe Subscribe() + { + if (!Connected) + return new OperationError("Organization isn't connected to the sts service", OperationFailure.BadState); + + SubscribeToUpdates = true; + return Maybe.None; + } + + public Maybe Unsubscribe() + { + if(!Connected) + return new OperationError("Organization isn't connected to the sts service", OperationFailure.BadState); + + SubscribeToUpdates = false; + return Maybe.None; + } + + public Maybe UpdateSynchronizationDepth(int? synchronizationDepth) + { + if (!Connected) + return new OperationError("Organization isn't connected to the sts service", OperationFailure.BadState); + + SynchronizationDepth = synchronizationDepth; + return Maybe.None; } public IExternalOrganizationalHierarchyUpdateStrategy GetUpdateStrategy() { return new StsOrganizationalHierarchyUpdateStrategy(Organization); } + + public Result AddNewLog(ExternalConnectionAddNewLogInput newLogInput) + { + if (newLogInput == null) + { + throw new ArgumentNullException(nameof(newLogInput)); + } + if (!Connected) + { + return new OperationError("Organization not connected to the sts organization", OperationFailure.BadState); + } + var newLogEntries = newLogInput.Entries.Select(x => + new StsOrganizationConsequenceLog + { + Description = x.Description, + ExternalUnitUuid = x.Uuid, + Name = x.Name, + Type = x.Type + } + ).ToList(); + var newLog = new StsOrganizationChangeLog + { + ResponsibleUserId = newLogInput.ResponsibleUserId, + ResponsibleType = newLogInput.ResponsibleType, + LogTime = newLogInput.LogTime, + Entries = newLogEntries + }; + + StsOrganizationChangeLogs.Add(newLog); + var removedLogs = RemoveOldestLogs(); + + return new ExternalConnectionAddNewLogsResult(removedLogs); + } + + public Result, OperationError> GetLastNumberOfChangeLogs(int number = ExternalConnectionConstants.TotalNumberOfLogs) + { + if (!Connected) + { + return new OperationError("Organization not connected to the sts organization", OperationFailure.BadState); + } + if (number <= 0) + { + return new OperationError("Number of change logs to get cannot be larger than 0", OperationFailure.BadInput); + } + + return StsOrganizationChangeLogs + .OrderByDescending(x => x.LogTime) + .Take(number) + .ToList(); + } + + private IEnumerable RemoveAllLogs() + { + var changeLogs = StsOrganizationChangeLogs.ToList(); + foreach (var changeLog in changeLogs) + { + StsOrganizationChangeLogs.Remove(changeLog); + } + + return changeLogs; + } + + private IEnumerable RemoveOldestLogs() + { + var logsToRemove = StsOrganizationChangeLogs + .OrderByDescending(x => x.LogTime) + .Skip(ExternalConnectionConstants.TotalNumberOfLogs) + .ToList(); + + logsToRemove.ForEach(log => StsOrganizationChangeLogs.Remove(log)); + + return logsToRemove; + } } } diff --git a/Core.DomainModel/Organization/StsOrganizationConsequenceLog.cs b/Core.DomainModel/Organization/StsOrganizationConsequenceLog.cs new file mode 100644 index 0000000000..4a2e208e20 --- /dev/null +++ b/Core.DomainModel/Organization/StsOrganizationConsequenceLog.cs @@ -0,0 +1,14 @@ +using System; + +namespace Core.DomainModel.Organization +{ + public class StsOrganizationConsequenceLog : Entity, IExternalConnectionChangeLogEntry + { + public int ChangeLogId { get; set; } + public virtual StsOrganizationChangeLog ChangeLog { get; set; } + public Guid ExternalUnitUuid { get; set; } + public string Name { get; set; } + public ConnectionUpdateOrganizationUnitChangeType Type { get; set; } + public string Description { get; set; } + } +} diff --git a/Core.DomainModel/User.cs b/Core.DomainModel/User.cs index 5424cdb692..92f2d98a81 100644 --- a/Core.DomainModel/User.cs +++ b/Core.DomainModel/User.cs @@ -33,6 +33,7 @@ public User() FailedAttempts = 0; Uuid = Guid.NewGuid(); DataProcessingRegistrationRights = new List(); + StsOrganizationChangeLogs = new List(); } public string Name { get; set; } @@ -117,6 +118,7 @@ public IEnumerable GetItContractRights(Guid organizationId) public virtual ICollection PasswordResetRequests { get; set; } public virtual ICollection SsoIdentities { get; set; } + public virtual ICollection StsOrganizationChangeLogs { get; set; } /// /// Rights withing dpa diff --git a/Infrastructure.DataAccess/Infrastructure.DataAccess.csproj b/Infrastructure.DataAccess/Infrastructure.DataAccess.csproj index a7d561e070..8615e6f124 100644 --- a/Infrastructure.DataAccess/Infrastructure.DataAccess.csproj +++ b/Infrastructure.DataAccess/Infrastructure.DataAccess.csproj @@ -95,7 +95,9 @@ + + @@ -955,6 +957,18 @@ 202210271249212_Removed_Delegated_SystemUsages.cs + + + 202211210847408_StsSyncState_With_Subscription.cs + + + + 202211290936278_AddedStsOrganizationChangeLog.cs + + + + 202212010827066_AddLatestSubscriptionCheckDateToSts.cs + @@ -1595,6 +1609,15 @@ 202210271249212_Removed_Delegated_SystemUsages.cs + + 202211210847408_StsSyncState_With_Subscription.cs + + + 202211290936278_AddedStsOrganizationChangeLog.cs + + + 202212010827066_AddLatestSubscriptionCheckDateToSts.cs + diff --git a/Infrastructure.DataAccess/KitosContext.cs b/Infrastructure.DataAccess/KitosContext.cs index 88cd7ff359..9c1c42f1b4 100644 --- a/Infrastructure.DataAccess/KitosContext.cs +++ b/Infrastructure.DataAccess/KitosContext.cs @@ -159,6 +159,8 @@ public KitosContext(string nameOrConnectionString) public DbSet ItContractOverviewRoleAssignmentReadModels { get; set; } public DbSet ItContractOverviewReadModelSystemRelations { get; set; } public DbSet StsOrganizationConnections { get; set; } + public DbSet StsOrganizationChangeLogs{ get; set; } + public DbSet StsOrganizationConsequenceLogs{ get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { @@ -256,6 +258,8 @@ protected override void OnModelCreating(DbModelBuilder modelBuilder) modelBuilder.Configurations.Add(new ItContractOverviewRoleAssignmentReadModelMap()); modelBuilder.Configurations.Add(new ItContractOverviewReadModelSystemRelationMap()); modelBuilder.Configurations.Add(new StsOrganizationConnectionMap()); + modelBuilder.Configurations.Add(new StsOrganizationChangeLogMap()); + modelBuilder.Configurations.Add(new StsOrganizationConsequenceLogMap()); } } } diff --git a/Infrastructure.DataAccess/Mapping/StsOrganizationChangeLogMap.cs b/Infrastructure.DataAccess/Mapping/StsOrganizationChangeLogMap.cs new file mode 100644 index 0000000000..8e536f1c01 --- /dev/null +++ b/Infrastructure.DataAccess/Mapping/StsOrganizationChangeLogMap.cs @@ -0,0 +1,31 @@ +using Core.DomainModel.Organization; + +namespace Infrastructure.DataAccess.Mapping +{ + public class StsOrganizationChangeLogMap : EntityMap + { + public StsOrganizationChangeLogMap() + { + HasRequired(x => x.StsOrganizationConnection) + .WithMany(c => c.StsOrganizationChangeLogs) + .HasForeignKey(x => x.StsOrganizationConnectionId) + .WillCascadeOnDelete(true); + + Property(x => x.ResponsibleType) + .IsRequired() + .HasIndexAnnotation("IX_ChangeLogResponsibleType"); + + Property(x => x.LogTime) + .IsRequired() + .HasIndexAnnotation("IX_LogTime"); + + HasOptional(x => x.ResponsibleUser) + .WithMany(x => x.StsOrganizationChangeLogs) + .HasForeignKey(x => x.ResponsibleUserId); + + Property(x => x.ResponsibleUserId) + .IsOptional() + .HasIndexAnnotation("IX_ChangeLogName"); + } + } +} diff --git a/Infrastructure.DataAccess/Mapping/StsOrganizationConnectionMap.cs b/Infrastructure.DataAccess/Mapping/StsOrganizationConnectionMap.cs index fab565d855..c4931b907a 100644 --- a/Infrastructure.DataAccess/Mapping/StsOrganizationConnectionMap.cs +++ b/Infrastructure.DataAccess/Mapping/StsOrganizationConnectionMap.cs @@ -12,6 +12,14 @@ public StsOrganizationConnectionMap() Property(x => x.Connected) .IsRequired() .HasIndexAnnotation("IX_Connected"); + + Property(x => x.SubscribeToUpdates) + .IsRequired() + .HasIndexAnnotation("IX_Required"); + + Property(x => x.DateOfLatestCheckBySubscription) + .IsOptional() + .HasIndexAnnotation("IX_DateOfLatestCheckBySubscription"); } } } diff --git a/Infrastructure.DataAccess/Mapping/StsOrganizationConsequenceLogMap.cs b/Infrastructure.DataAccess/Mapping/StsOrganizationConsequenceLogMap.cs new file mode 100644 index 0000000000..b6e3e3188f --- /dev/null +++ b/Infrastructure.DataAccess/Mapping/StsOrganizationConsequenceLogMap.cs @@ -0,0 +1,29 @@ +using Core.DomainModel.Organization; + +namespace Infrastructure.DataAccess.Mapping +{ + public class StsOrganizationConsequenceLogMap : EntityMap + { + public StsOrganizationConsequenceLogMap() + { + HasRequired(x => x.ChangeLog) + .WithMany(x => x.Entries) + .HasForeignKey(x => x.ChangeLogId) + .WillCascadeOnDelete(true); + + Property(x => x.ExternalUnitUuid) + .IsRequired() + .HasIndexAnnotation("IX_StsOrganizationConsequenceUuid"); + + Property(x => x.Type) + .IsRequired() + .HasIndexAnnotation("IX_StsOrganizationConsequenceType"); + + Property(x => x.Name) + .IsRequired(); + + Property(x => x.Description) + .IsRequired(); + } + } +} diff --git a/Infrastructure.DataAccess/Migrations/202211210847408_StsSyncState_With_Subscription.Designer.cs b/Infrastructure.DataAccess/Migrations/202211210847408_StsSyncState_With_Subscription.Designer.cs new file mode 100644 index 0000000000..397aaaf6e9 --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/202211210847408_StsSyncState_With_Subscription.Designer.cs @@ -0,0 +1,29 @@ +// +namespace Infrastructure.DataAccess.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.4.4")] + public sealed partial class StsSyncState_With_Subscription : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(StsSyncState_With_Subscription)); + + string IMigrationMetadata.Id + { + get { return "202211210847408_StsSyncState_With_Subscription"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/Infrastructure.DataAccess/Migrations/202211210847408_StsSyncState_With_Subscription.cs b/Infrastructure.DataAccess/Migrations/202211210847408_StsSyncState_With_Subscription.cs new file mode 100644 index 0000000000..82b79d8482 --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/202211210847408_StsSyncState_With_Subscription.cs @@ -0,0 +1,20 @@ +namespace Infrastructure.DataAccess.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class StsSyncState_With_Subscription : DbMigration + { + public override void Up() + { + AddColumn("dbo.StsOrganizationConnections", "SubscribeToUpdates", c => c.Boolean(nullable: false)); + CreateIndex("dbo.StsOrganizationConnections", "SubscribeToUpdates", name: "IX_Required"); + } + + public override void Down() + { + DropIndex("dbo.StsOrganizationConnections", "IX_Required"); + DropColumn("dbo.StsOrganizationConnections", "SubscribeToUpdates"); + } + } +} diff --git a/Infrastructure.DataAccess/Migrations/202211210847408_StsSyncState_With_Subscription.resx b/Infrastructure.DataAccess/Migrations/202211210847408_StsSyncState_With_Subscription.resx new file mode 100644 index 0000000000..24b2c0d529 --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/202211210847408_StsSyncState_With_Subscription.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/Infrastructure.DataAccess/Migrations/202211290936278_AddedStsOrganizationChangeLog.Designer.cs b/Infrastructure.DataAccess/Migrations/202211290936278_AddedStsOrganizationChangeLog.Designer.cs new file mode 100644 index 0000000000..21b7158fbe --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/202211290936278_AddedStsOrganizationChangeLog.Designer.cs @@ -0,0 +1,29 @@ +// +namespace Infrastructure.DataAccess.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.4.4")] + public sealed partial class AddedStsOrganizationChangeLog : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(AddedStsOrganizationChangeLog)); + + string IMigrationMetadata.Id + { + get { return "202211290936278_AddedStsOrganizationChangeLog"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/Infrastructure.DataAccess/Migrations/202211290936278_AddedStsOrganizationChangeLog.cs b/Infrastructure.DataAccess/Migrations/202211290936278_AddedStsOrganizationChangeLog.cs new file mode 100644 index 0000000000..766485399e --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/202211290936278_AddedStsOrganizationChangeLog.cs @@ -0,0 +1,85 @@ +namespace Infrastructure.DataAccess.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class AddedStsOrganizationChangeLog : DbMigration + { + public override void Up() + { + CreateTable( + "dbo.StsOrganizationChangeLogs", + c => new + { + Id = c.Int(nullable: false, identity: true), + StsOrganizationConnectionId = c.Int(nullable: false), + ResponsibleUserId = c.Int(), + ResponsibleType = c.Int(nullable: false), + LogTime = c.DateTime(nullable: false, precision: 7, storeType: "datetime2"), + ObjectOwnerId = c.Int(nullable: false), + LastChanged = c.DateTime(nullable: false, precision: 7, storeType: "datetime2"), + LastChangedByUserId = c.Int(nullable: false), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.User", t => t.LastChangedByUserId) + .ForeignKey("dbo.User", t => t.ObjectOwnerId) + .ForeignKey("dbo.User", t => t.ResponsibleUserId) + .ForeignKey("dbo.StsOrganizationConnections", t => t.StsOrganizationConnectionId, cascadeDelete: true) + .Index(t => t.StsOrganizationConnectionId) + .Index(t => t.ResponsibleUserId, name: "IX_ChangeLogName") + .Index(t => t.ResponsibleType, name: "IX_ChangeLogResponsibleType") + .Index(t => t.LogTime) + .Index(t => t.ObjectOwnerId) + .Index(t => t.LastChangedByUserId); + + CreateTable( + "dbo.StsOrganizationConsequenceLogs", + c => new + { + Id = c.Int(nullable: false, identity: true), + ChangeLogId = c.Int(nullable: false), + ExternalUnitUuid = c.Guid(nullable: false), + Name = c.String(nullable: false), + Type = c.Int(nullable: false), + Description = c.String(nullable: false), + ObjectOwnerId = c.Int(nullable: false), + LastChanged = c.DateTime(nullable: false, precision: 7, storeType: "datetime2"), + LastChangedByUserId = c.Int(nullable: false), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.StsOrganizationChangeLogs", t => t.ChangeLogId, cascadeDelete: true) + .ForeignKey("dbo.User", t => t.LastChangedByUserId) + .ForeignKey("dbo.User", t => t.ObjectOwnerId) + .Index(t => t.ChangeLogId) + .Index(t => t.ExternalUnitUuid, name: "IX_StsOrganizationConsequenceUuid") + .Index(t => t.Type, name: "IX_StsOrganizationConsequenceType") + .Index(t => t.ObjectOwnerId) + .Index(t => t.LastChangedByUserId); + + } + + public override void Down() + { + DropForeignKey("dbo.StsOrganizationChangeLogs", "StsOrganizationConnectionId", "dbo.StsOrganizationConnections"); + DropForeignKey("dbo.StsOrganizationChangeLogs", "ResponsibleUserId", "dbo.User"); + DropForeignKey("dbo.StsOrganizationChangeLogs", "ObjectOwnerId", "dbo.User"); + DropForeignKey("dbo.StsOrganizationChangeLogs", "LastChangedByUserId", "dbo.User"); + DropForeignKey("dbo.StsOrganizationConsequenceLogs", "ObjectOwnerId", "dbo.User"); + DropForeignKey("dbo.StsOrganizationConsequenceLogs", "LastChangedByUserId", "dbo.User"); + DropForeignKey("dbo.StsOrganizationConsequenceLogs", "ChangeLogId", "dbo.StsOrganizationChangeLogs"); + DropIndex("dbo.StsOrganizationConsequenceLogs", new[] { "LastChangedByUserId" }); + DropIndex("dbo.StsOrganizationConsequenceLogs", new[] { "ObjectOwnerId" }); + DropIndex("dbo.StsOrganizationConsequenceLogs", "IX_StsOrganizationConsequenceType"); + DropIndex("dbo.StsOrganizationConsequenceLogs", "IX_StsOrganizationConsequenceUuid"); + DropIndex("dbo.StsOrganizationConsequenceLogs", new[] { "ChangeLogId" }); + DropIndex("dbo.StsOrganizationChangeLogs", new[] { "LastChangedByUserId" }); + DropIndex("dbo.StsOrganizationChangeLogs", new[] { "ObjectOwnerId" }); + DropIndex("dbo.StsOrganizationChangeLogs", new[] { "LogTime" }); + DropIndex("dbo.StsOrganizationChangeLogs", "IX_ChangeLogResponsibleType"); + DropIndex("dbo.StsOrganizationChangeLogs", "IX_ChangeLogName"); + DropIndex("dbo.StsOrganizationChangeLogs", new[] { "StsOrganizationConnectionId" }); + DropTable("dbo.StsOrganizationConsequenceLogs"); + DropTable("dbo.StsOrganizationChangeLogs"); + } + } +} diff --git a/Infrastructure.DataAccess/Migrations/202211290936278_AddedStsOrganizationChangeLog.resx b/Infrastructure.DataAccess/Migrations/202211290936278_AddedStsOrganizationChangeLog.resx new file mode 100644 index 0000000000..9b27298863 --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/202211290936278_AddedStsOrganizationChangeLog.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + H4sIAAAAAAAEAOy923LkOLIg+L5m+w9l9bg2m9XdZ7rnzLHTO6ZUKqs0nZXSkZTd208yVgQkcTNEqkiGqnR+bR72k/YXlpe4gATgFxAXMpLWZl2ZGX6Hw90BOIH/73/9v//+P35/3nz3KooyzbO/fv/Hd3/4/juRrfJ1mj3+9ftt9fB//uv3/+P/+t//t3+/WD///t3f93D/0sDVmFn51++fqurl3374oVw9ieekfPecroq8zB+qd6v8+Ydknf/wpz/84b//8Mc//iBqEt/XtL777t9vtlmVPov2L/Vfz/NsJV6qbbL5OV+LTbn79/qX25bqd5+TZ1G+JCvx1+8vs4ciKatiu6q2hXj3IamSs9VKlOX3351t0qQW6FZsHr7/LsmyvEqqWtx/+1KK26rIs8fbl/ofks3d24uo4R6STSl2avzbEZyq0R/+1Gj0wxFxT2q1Lav8mUnwj/+yM9EPQ3QrQ39/MGFtxIva2NVbo3VryL9+f7Z+TVe16kNW/3a+KRqwv35/nje2rX9Ls3ZI3nUou//8l++Gv/+Xg2/84V37vxpku2lG6K+Z2FZFUkNcb3/ZpKu/ibe7/KvI/pptNxtZzFrQ+rfeP9T/dF3kL6Ko3m7Ew074y/X33/3Qx/thiHhAk3A6xS6z6l/+9P13n2vmyS8bcfACyQi3Va3cjyITRVKJ9XVSVaLIGhqitaPCfcDrRmxaOgpPGK0B3SM0HvyupSPWx8HDKDRzZb3d1DO3R0f+Z5jAZXm2qtLXgxjv83wjkkxjLZhO8/8HEaqi5fxz8vsnkT1WT3/9vv7j9999TH8X6/2/7Kh+ydI68tRI9exGmZxtkuK5nv0HTs2f79Jn3ExV/mKFVw+/Dd77fP3m3Rq321/+H7GqvPP5n/kvR7/2N7ZtlFFmhPzPTJe8as1z9Vs9qfFYAJP6VGef86ckexRr1RXsab1/q9MUX7jPyWv62MYbrQ0bp/3+u31IKp/Sl54t72Woj0X+fJNvDrjSj/e3+bZoMsZdboK4S4pHUdHlU1SHxNQAK9IqMCahVUCu7JI3QVL3wBR5pV9NksogXBlvxCoVL2k9NCUkYw9MkVH61SSjDKKT8d9/OGYwQlHSuaJtYdJgL8UJGlnNqYwZvzpyYyMqFJynFIt5oddF2N1PKHPY3c9KH2G3ZUAIvXo4Q8AwAPsJwS0zMAwPISCpsXBMDnWdGRlBrkFY4pr9kuOPf/gDqSy1CBmOOMOMrp/yTHzePv/S+I3n4vui9q9NeFteJ2X5W164WFwwOd8mGxcLJwvfaWJLabOm/CA2op4mI1DHrvE/iIekjjVNYLqtkqK6LsSDKES28r/y/ykpz17S/abfQA0UtZb2q/gp36xFYSDBHch89VWsr7ZWmwMf67km1md1tHt+qcpxZdRl+eMm/yXZnK2f02ysVl+26cFHfmz//I0Xdc1Gc/1PjcfUbn0jHtOyzrAN4E36+GRYZCFI98N6igKvFIUkJG6deFmd51lNZlUB6g2AFHV0vyvia4H44t6+lfUkAoWVQDSiDn/VCKqA+Km+W9Jg3a2HUGpXAxh7qyZ9EOdvq424q0fpa+1jF6/GbQU9rGJuAEyxOwTLHQBs2dASNSwYhr/pzT1mz+aqeEyy9D+xqKKAKebVQyiWNYCxjSqRqTM5FDG0oKD4fShQhQEoV419BXojSlHdiF+3ojRooYNUlDACKTqYIbkq3Jb5bsWUCr3oNUSXBrt1lSK17ndFYC0QW9aqlAevC1Gf8keD3Abo+9poLzVkWtcEii5EHFU/KqJOZ/IuAJK3WRsEP364vsGqjWUDAap3bQ5fhlvutfO42Qv1uaN6msddtMoKK5XBoouNrBQIfAp226JWuu9R6Qp3GGwtd2iud3xpbJWqjoFmqanl+V3NwUrPDpGuYAPF1qxF4qpkPS2tlslclYzzzUFCd5nLlzTuc9sK2c7/k5/Nb7naHJuwmz3O7S+S/+TFYWOxnW3/FOXn/Eu2Fqt0LdZXL52DIt1pRZKVD6K4yy+zUqxq17p7Sov1eb7NaiOJ8QwaeaXq+p65ZzhAvxHPSfHV+070Vds4WwePTsdAXG/2O+1MG12WZ4+FEM/1TGoabjfb9bGkO47bZVGIjXhNsoo2cCrNs0otFLk0ApnyfVKm5ce82Ls306KH8a+hRfGabAbmTIqf86x62v9KM6hCNJAtLssD5/P8+aV3SmQ9rVWSoabmiR0/nNVxfJU2OXG/W45tyd+bS4hSv0kPYQDb9iAad1doOCOZ9eK9ik8pGxU0RvWo4nLr4mGu5mk8xCbp20fiaDvAtNFVStQ2yvbQydrKxQVTXRmVq+/F700B2wTxXdJmj6+OAklrFZGjuAabq/u+VDxUibsu9bu84Ws7w6lUSTaiEePYjUjRT7s0IJfl1taoXa0AfX4Ac/aGj+Vej7MjP7aCPVyahhIKS0UZj63jvghs6ihu/Osh318nxaBplofJaJ7QorOPRvsrRHbwV/FpwzxAYw31EJf/5UGy7r7j5G727RF34l7stnxIG39aXE6vjJ4Ad8Bvjm1wvJGWEElDfIDnjO0RiT2o9k1O1gcL3EanwXkE+SBa3S3jDZ2GAGkIFTzOUKrI3CHtupm+lMkjvyjt49K0lVBYisp4NkcOn/MqfUhX+5WvoetHhgJWtMOGICKa4ssc3FFdBsd1OusQ4ogm/XE5evB59GC7rxvnyMLRp+J5BcnuaGfz4MABvl2+3b68bFJR7Fnepo9ZgK822lOfjnPLcXSDf5+aTW97YAtclo4Ut1ZY2icatiVyDxYcngfuB5IpQpOL6jDenMrcNolIPL7ZU7jeJNl/bJOiOvqBDYnmRMUW/7IehXb/fPSZxt6v78TzS3NFB9MsB/QahWvRug54SkrxMS+euajt+pUralHbbJVs6uzHV7J/wEj+PGnbFT3NUA+/h6FhtkdudqhX2WMuXZ9iG0GaI9TXfNVg3OVcA1z8/pJ20tiEoDtRPKeZ7OZczIZxvRTepBnXO7vZ0+wbZ2smaof0c105pm2sGtmHuc2y4S04jT/discmEtBm+fu3i2w9msrVi9hvLzxvs92f34vHbcYdn+vkrWH7sRC/1jUiOxZ32G3Nzg7j6UrU65Gt1bVGp3sOe2hauNi0/2nIoWeyKLr2XBbBUlbTDFTugvpIb/cx1F5dfdd+D+ZePcTutetjwGqfPorB3QsaqgfsjmiNbNoXQYFpYzhqL+T8Kd2s6yoA00bdY1d+hLoBLHfKh1UVJqUKr5V3CAbZWYFlW1gq7cjyt7Cw7DUISe4Gji3zscZDRZZB9RIfIUCBJbARzRJqV0vIhhhAP0ofjeMGAok73DAAAkJKOWgI6EhcrPIsf36rV5UiedZr0wPZcd4XQHnvNBeGVOIUAs7+7DizUqhDoyg0hEQUUsD9fKAseQXYVgDBQa7mv21AYmZoE9BDQFKPagOQ1k76D2clgDbO62c3AKZ+OgvAjvkEGDW4oW3BAAKafGxbwmsqfkPOq4/sFATjOTUVB6ijEET+x82F6SpIZukHDMegOKTL1lvaGj657sEAUwCG1Hx8DYLz7XxcaEOKtAC4FjowkwpaWLb8/dW+XoU+DKQFCKkqAoPzdVG2sNEJoEPRzwYVEpwaGnD2PJG2f1FFerB6DSQQUHQZjt8MBPXDSFy0/S+63yFRR/S3mM9ucMkBVIMuRgxYOzOaw36e4YU8av+OHgK92ce6P+dltykMj8MRTmv0/c+QhQ8wXHNqds+14mrggPCJQys2J6Bw7W/ZQyM3ngA9M5L10R4ZeaTc9MQYdmHHNspoyUbtntFJdK+21Og7b45mt2nCMbIeddldXyY3l0joJNXfLqtTST+HEVD1il8E3v4itzhnE8BOMfVYw2qO68fSbmLraC29cPYtaX/8s5eWtMvyU75KNpu3s9ckbRFHn+SXV/V41fMqL97G0vogylWR7k6NvfdCXWSNaKOboeoVWV60Az4mWHu9TPRbvs9ImyzgG9tJGOot6DQ057e469ia7nNHYGk6jXtyAzyicVg0kFRBDmrImbR/ys7ModKJ9aAbYEmgPiNm40uS7cdGzbvcIbHDC2nNadVDwm54D5lJpW0pz5xa0zbb0FuL/i619cVhn9ZpZlhdM5L/FqZh5KY3PVH1Gsx8ilIKilGjASSmzhDcz1fvA6Zg/YPBYhr5r3kGDA3VjhkK02BchTOI3BQNNEhGPRRYTBsVgb11mzNnywDBqEsPDtOjD8z/zFHOHRQlhhhGLfqAmBoD6FF1Z694HFF29v62VJ0+vtoY99FCkI8A262hzhHArwCpn0vC3HYuBe2BOeHTanWebDbYjpsbbvuXGG5rf9v2L30c/NZtt4b7ym0/ycfSOStWT+mrsPg461ZkZdp8E9ve88PHt/38dyfxh+1xl67bbjj++27/nkSnmYyl99m4Y9ZkCqaRml7a5h+ObwMT337asbT8HHKH3cw3rbvSvEqUlSWFvX+fN60jeXOvFJNAU6u2d1L1nET6Vxi9Syqb623xkpf+o3Vavt+WaSbKct8r3hO7mWKHG3GwmJV9vcs/pEVd9ObF25cCetvOUapRWDo6D4HZvtT8ku3OKHbGkkjsMC6yVfHW22qxPUpQaV+XYrtu2pfT2gsdfHepcuheeGuX0vnGPflP+WPmQO47sXrKGh+vY1PT9NlUDh/y1bbZye06fFAPcuO4FEEC7IOJQuJv68oDMjZ16ZBEjCFBZPDOv0jLr2dlWc+iZzHIHYzBOFBpiNgMRT33AElu6p8+idem4xYpH2pA+xF1E6YRGbyPaL1YFI0M/ou8D9eXZ7Ye0+A2ztF+HcPzlQY19rRFZPDOP8nK39qc2ph87Dis2ydjbaZt1j5ZLPO3fNMgLyv5YvlWi+M/Etca+71o5sJh6V7gn630V0vge5zdjqeCoT7NqQXUdPNC0OzP3Tv863ou5WvDsb4Mcj/YtJSO9M1gag8gAMv+or2/7KUPhb6dGgCkDYV1i7VmDU1Xpo+FKCQD05TqYdgqZmo21XN8G342bgAiKvBm8en48bCS+1G2x5sl6ZeG6m+k5H7rDz823BlZgjWN2AEEGa8jnLcPyWV22LfkMCyii4sr5qt9SkdVOYKaFNj/DRH7AGYr7HEPjyG2jIQpcIQlqiIh+Dmi77NFPhoHQRGNxp/P/5ykGdIyLzPsg5vUkKEQDXqg7r97l1kZP303ACGSe70Hf8DK+A25CQqTPciX5BJHzsfkZDTjm/UUXLdXnffNa/i6UwuCjJSTbzzNzR5DZkMUs/h9SFSLAbjTDzh3XRnGzzfV342eM+7TTflMtN2ho6Q8LZbJ7hpgxPQ6DPbd6cPDXq5acP2ugHJUsqrl75Lyaz2z9MOz+7G/Eu1/qaqFUL9O1YNx3Wov7NVLdbXVJ2k9pz0CJngHRxR/B8z/rLZGZl2fZ9+miVycZ+rqZHwhXFdbhCmwBzT5ffc74uw7IIunAGo0lsHtOv0QY+vbAr1/jG3QBIakfJKNaUP/YnO4BWjR8fduQGRp94P67kKchCwfZX6LH2VO6RTD8SeXgxMF+GtLDFj9kg/FcP6N5YCj6fNKMxiqhMePKh2dIoHrZuOZ05g01x3ujEpyHYklxUFNuVVSGBpOmHHmIjO8CcINu1n661bsRjDAezQ9Tx7dHf3yUuSv4zPYt3ciPijA/ZwjG+IweObsJTvuOFJyowkUUSZYXtzxg7OiCoSIj2VEcjbpb4QzkomM2PvLkk/sl0xuPrO5fsoz/wuzs3UhSv9NgBe1G/nvd9N+b8OM/uev0CNlf3Qxsh9r50sfM5iTo+FtW9/r+Zs+SA/qdJFt8FPohWNzupmsqjoUlvzvYMoP4iGpo08/8LEa+r696mPQe1W74b4YYKyq+A1hpFWV0j5GVeq92OTZYz17KJrcd9Bl8yCXKv7xV6PMEohFq9BD+qgVr/vJeJyt+VkRUAdj08x0mJH6++dl8gNw6fp5I5RSDwGg7p4EcfAUuWlouLhWT5GPGlSHTXl8I9jq7kzlvIlyDk1g9cK34YVughkMD4MzDVHVea7+8Wz9mhofNu8Z3IRnmOJ6cHiuG3BsJr3UREHUrY8BaCUD4vr0oPltgcY3H/gvUuCPa8A4rBcpRs3VI2H0RSPuiyHAnbMjRT5cM2OS+XgPjVloPYxGagMgX2xShYT11GHS6qBsRdX3q1m11ZFaBdmTgo47ygSU8tyiFxIuzEeJT9sn7LEAtwlhSDg0j98kPNxqcldHj691kr54FZkhYOlh77sP7ZKNLJm2HdIGXxlJKyLsMdZb5R9PNb3epS5l16X4U75ZGxwBkVfGN/m5JQmu6Yx02G9lIRvPPeKGfWcTDDwfRjZoNw+N4HVW+xyJaaxAQPWFMhCa3a9s7pjtTYuu4ZWggAoJaqABZ/fTVqWMX1c1WVdG67vZTNBG7WgYaocbDW2ktrs9etNnM3roN6quWnhMUz0S/4GbXzwuXIfUyV/VDfFYn9YpyLFf/QHWBNY7kMbG774PDVq9lR/hoG3Vzv3lsq5Etxtx3h4I7kgZekV1oMZZg0OrPaM4SqAuWKNSECDeA4uqwr7y0q4taP+H5QQX4HXYxre8gC1Ay851IV7TfFsG6c/t3uxkGmN/OZvFNYcf0tJJE6zH6xJvxCp/fhbZunU4zuWJDfZ5i1t5HzhHrQihD39Ddlz7OwN3eXXpeSGaSMa9W+fbO8WWjnBdnPSavjLSHAaTJZTiIiJkD1InpwQAiCpDcaU9f0o36zqGwpLq3oTu/WTeCh68GO3ppgriJRWc+yncXE1x2L6/+P0p/cW0d3LgqUXQ6KGBMyuiA/Z7xQPtdgfGxQ4Bej2PhxfwjQikyxDC3IPAPK4xCzvm4gPwVXly/DDKZvmePO1iAvhOAsJ1BCNuImB9bQ1+aI1/Y23z4C/x5IlxpQ584qTcvGO1eu7nVJsVtExhWUWPWHwsD3wu35IuHwSNL6p69T1YWMGQShJDwF0XWD12hiLLBAPLHuDbUuZizdxBrFvSWWU6ZXnCSncK9pLqwI3flfGlecanl7bIXDzgUI5J6S6tNlCSd/R90NAZA2ywf7n55J2HcVfvG79l4X3RRIvmiZQb8ZIXhk2RI9RlpnjIfffr4e9XRfqY9tbAFuhK2Lah4a5Fn3vKD++BsRA55/zAnhq/Dxg76sf2+QBA6PjfiQ77hbCffUpjSeFQduqViCOvBIY3AhzoQyus1RkNVtcEcKVMpeC4rrNVnoZiGwQkqOLsA3YwzLLK2v9I3h3dBs4gS8EL8Pp7stmKq4fzJ7H6KtYhXis5T7bSm3L9/L/7jXtbS1Hkxe6rHHFeC8KqnA5+0tRLVw+f6v8vq1aYdvLyqymkGlIqmWlURMq6e0RVxTtR6EpDwA5qnuhQ7k0/q7ZgkzBUiHw64za5QaajA6aJ8BIxfaz1kLhAPAiPOCX0IcJ+avHsczjabmLSCNsM6TDt0ke3s8mAhrOS6kDXUSl1oLcEhKWEmnEJdfy0llU6IWhgyYThximV3MY+qxLJED8tO+jtIp7URL+EOEqICxHUdsYG+PzJxa1c0pCDhw+eWqo1zEkh9nN+vI7U30Omjtr1l271pVv922rwOL6O2H8AgvTyxP4PUjIzvj6hwGIvUKgI7KvX2Id4nuot6NCOWquxbhzKfzN/gFv/eN+rP/oHcIOftcdsQxh+63zb4w08xzJsBzdJDENCl9XowNmHUkeRkGt2MOHvVSF0F+yomrnujj9yQBrkAUBQhRBt8tKNRKZOeS0IKLjnfvkRVzKBUmON8+Q1y27i261XdsjLWoVb4NsVH/sH1iy6srwXkN9ejQW+vrfPp7rn9oa/KTNdARjx+ZergsEkoy4vu81ee05g5jICGQX3n7H2nAzZSvOzUVhnHQ9Hf7SN98vnKcvnKZPeylg+TzGSmklmpecF/NMUM5Q22Ab6JOXACsgN6KcoWiDHn6Gw6hjTpoJS6Iw9Xdmt8kcfsuzonGA6o33fMZbOtxeasMra2R4XsPqHtsTcNo2PuRXC2DYOXSHhbd9rbzPq9pcRnjIwQTfD9kzxPTENJEUbZ2uPA2X7BUiPxAmG7WUVsqxCllXINFI9McfIEQlJLzCoGosReOdJpcfPlE9MQIj4Xj+VtzqRg568gA8PyRlvdx2O/duYOwJLpvMZ2rpyxHM+aEaythqYkl20TJFSHDX7Yz1DVfoqmmnGbcjpMJtLEnl4VvfFNjGnDpnD+/yXtS83IYJXNu5v/lJvXOv/YrwyzPK+Rvh9IeZ1ZsMUZr7wzOoDZ5qMe1CzoB0EKu0ObJzIVy/V1Vbf4q3nt0fAxO/giErsgP0cMO5ZgsWbEciogf+Cbc/JUKppfjYKO6oNRh9gYZFNOBrx9aBmVQzwDu+XJAc7k4zA5ZJWr693tnPxAntDaSk3wX7mrmdU6pAe2m8Pwv3oafelSoeulrVI4m82Xpx8QjBqY8nRxUxJJo3oX/6rn8LQYWf6sr/itpxUorcaag0g+ENelpXm7u33xgjUh7yOz8UPkxsGS3jMa4DA7tZe5Vn+/FZPYZEYquceyD2Uq2FIRRkE3P+zjy0b8tOPemjw5SQDiusKUH2kDn+ybwiH6+G1R9rR8324EmPuGm9ryqZ4g5dwDqpa0/IYq4IdVLUjYi5qfevb1A9vW3/MC8arzRIiNBo0DOgCNwjN0yORDe3de47txIQGSIZDM2MPmH9xe1sKUvxqBwq4VQuBe1UH5mwV1ervZhnVklrWUVDFbFNMDidMLl0Sq5Rg7Y8Rv1btF2jcxck3tx4YFsu+K2wouoAlub+ytGNLrksN4ATNwlamcl4BS1MFkKBKsOJ01NvMBD3G1KcM78IdiuhDRreh3yQvLwOZDWH7Aqy/SF0yLrq/eJ28NU9ENuU081L4bBQ6ev6KHRn/uk3LVD7Ytjy3fRH7W65Hkamejvdk2JE4W63ybT1y2WM9MwqoNcDRHR/bdVrd1r60LXtF012RPDykq09d9ctVoiHalBPcs/wgN8h8e1XUcJITNvZUFNPG3hBSvasZBmcfRWdsZVQUkzJDSEQZBdxPOdhnCt8LDoMi+gS4D7y/y2u4C9wEhIjvquwzrjWcbX4jelCOqa3PhB3uaBzILTVWgF0NN91pS4+bn6Zv/cYp6zzJZu0O4dkFb397zTQ9BhvTvo7FyBsQWmCWLpYd7u1eJVmTDhxTofmNKHsL6mXT4ciDNiMYk2D8BoRCt7Wrs2xZU1uSJcDrp6S8Ecm6u2By7HdSNbF/FGkl3FBbPp+z/bZg+XxOR2CppIipoklEvEJKi4GnED2a90qkYUstRAawNJ08fmg3uhihNSD06ha7L83ljwd2x3e7J+YsvjpvMd8ZacZN8bJUar7X4uh24tllwpDvqGXm2E+znL0vWMeA9vZa05uC3e/IO4I7IG4DDWlXyrqXBu2K7bfcWDRocW0toe5QTFYfQiL2V8BHxZLzPHtIH1mBo0M5wdIfnsa3T/lv+5Go9d2Or147ivujXnc0+4/KupGy0/u6EA/p7271dkOzlnD70tyX3pmx+UcH10h1JJuSeDnMi1VDd+EGrppNMEpNaQR0XRnvGBlqYfVXk6Re+3/2PAxNP5qfjWI6u7G7CQtNVKhTSSMNLy8dMU8wPQW7PclNPGvmWBBG1095Jj5vn3+RXnbx9uVe7U7+H2cC23Tts8iUkobzHHGc+miqgEB1AQ6E95A4JH7m/KEHQsQP1k16Pwjj+gVcHwpcxA1AR615+vXxjXhMy6prmmv269tkwUo8P364vnnXoL4jUF4Sk31i+pOfp8xcfhXROWbncmNp/Vx7yGHPsgk2Z7VbPWZiDX4H7yYF9XkHeJCwx/AurTaQI7j51r9b0zZ/Ls/K8/LVu467bZx6ORmIoRSP8iKkottfYrG+LM8eCyGaNsY6Zaw22/WxnmnT1D9F+Tm/LAqxEa9JVl3tTvxgqndFkpW1c97ll1kpVnVsvntKi/V50+FcpKJUOXzJ1mKV1sxpDFShzypuz3H3EvDVa5NNHp+s2pbfJ2VafsyLvb7e5+BB2rYB9TXZDCyZFD/Xif9p/yvNmO3N7Mdt0nBKdOIF9fYD7/P8+WUjKp23M31xvzMXUI9eXW2/cHn/hnU2OBlviaNullpviKElNqG+NO7mcHG1DzKwCNi0iXUlThMIzS82GIToYR9FUj/zt6WhfQHCihD3HEquKq0do0+E5xgyrpVj9AiMfuOJZXT3azk9n2Vl57nr20X3eUPjY43iKE9w27X0F3lZJwzgupOogZIRIWgRd0ykqMSq4XS2fk3L5osp2wZYLbll0sc+Zzh/hVcprk4Y/H/Aui6k1t7lCGM5CNdG9H78wZ+HI6CY4jWK5+PhOJUv8IocCExUK9yhiJZ/aTwd0YPDxyQGnNHnJb3tlDEZVCK05M4ldy65c8mdgXKnFHkIr21DwPqHrEEML69vSxyhV7j1YKgSYfOixBnOiDIgngt70CO/utjvizdb7q+p+M1uh0m63KndZwLoLglyhqf2jnoY0FPO9pGhsW3Te9cL0FHQ7eioDEl5rY8cxMTnRVqlq2SjcwkyJrbN6OSlqv4lvRa3j6kEglSfTXv9JmU3Ku7Rghz17V3utmm88d9fumfXsOF6nYQaxO0ODMXzS53freXdoQeR+bqO809J2Vy3rT4PTkcNI2uRN40mzb74bbNbLh65gUhDIbTk15ska7o4bOVu8P9jmxSVch0hmcRlHc/SxEVjRP8Y49CyE6bTo/diWHiOx961ELy7dv6rh7OyzFft4HX8DyuJcTVaVzyR2w0d7VH0mYZoqhyw3D/BdPiHAGXf2Wq1fd42AX4tXXJ6npfV2CE80L162d156pZqcwXqeIoH4W7E8zbb/fm9eNxmNv15uwsa27Ugu4Q+oobJArvLJAvx67Z2NnbyGqAHkblrobS+9VW6g/YfT+kR39IZj9RuxMhVqUTrn2KzyX9zRu7HOguOvG34w7Z/Z7G/Rs02zTdhMFtzd1Yl1CCueCeK5zRr7fJBJOtNmnErbA2FIJI3zdWv+apxAP4jyHuh7Rqgy+qidkzqV2cIerBe0o7pmVXIUZrcqYgXv7+k3bQjsTU/bGEoipE3jJTNznszId2LRnR8ZaPbioiHp5EhOczPJdOxWIqPfFYZ3fCHeJtalqk4LEW9tyhDzBV8mrIDNJa+Q1yuymjvMcTc1HNMxWFpivYYg2oO15dcTYf4RGX7aDx9B7ijmiUBRoaY6OvUy8BuOQwj70zJDbajn/kyUg5yChOwYzpKbWJ+e9GiwHE98Qd3nvmZ7j0myySnbsg6uN3qSOy4uQtMaDdrQ5ltmKPyHcfL8kNaurl0dxoxyckSgRN/DKsLV1HHwddbhLCzfLu1fLs1nVbPiYQSR8tSTjAxLWld1zD9RZKvIqbPZYkmYLPRxsnCpHFRh/XQXe6Q2ESmtaM9Cc60Nu1nWE5r+VLykV238m3nu+kMUV/m8Ax7b483OoX/tnvvTq5WGV3vRvOKarGuVbjL/55s0nVtpGtRpLl78p/SB3H+tnJwBXVrBjf9yUGeUd1F68qp93BYWjVDo/4GE9nFMv/nm82TP+dJoKq95TY0qvethRAbJ/0e7TFvTQOUgozR3mrvt2WaibK06HXWkQjT2rBj3D7lUv6Ub9bsLgMdiaB2/9uni8tQFxBKPJt/CHWbWBMFDk2OH/LV9rlXjgdiHKLDs88xzL2Z9ndMS5jBmmu+mWvlIjUGDbCbe1wtP/KSUS2/DdKRCDJ2PbUdfZx3K7KmW/pVNAd/n8Sr2ASKn2fF6qlhuz0+LNjuNUj/3vxriUb/Nr3VMu5D8FiL3KTl13pYm6V6U7zuyVKOox19agcIECTcp9nXu/xDWtRhNC8C9UT3eYZRc7cW7VqMe044+K35xb6HoTxetxv6QmZNN0Wg6d3t+2yut8VLXvo/gP0pL3vdvO0oHv+R/Uzri8jW5VXWXgn8kNSmDGi6y2yVP7cj134j0j8RDSjH1bZ6zGPIYdyi3uWGbnPK1D6n2XDt4UGXEfKx4Yf0aCS4N7UCoYZuE8qFqkQD0UnRrMWgxzadOrPpJjvgsC1kxKQZxIzO1R+MLXRLdO9zSj+wLYJSoFkGJ8O20I7NXVJ+rde8DKPsMNimMODRDGBCHvN0Kl1lSns9GU3Tl0zHZX9LACU3zkSoaYybBzAB6jRAqLAfpSV9g6AbHvLFvxboNGO4vhddXSHTzaEis02Ck6CZhUDH+ZXxyAw2f7lBRuMGDXd3xDMKPS9dBHpWS0sBtNlVJYXhA0jmCvEiWzuhE7SJJ/DKiDQzaYsrdxPU9bt81Nm6vNr3zX58xO3x8fK4WOxAE2e7gRSCLLYv3MUjdWnvJfyobJZoA27B7sw1+guoPaE4/YKxZ72PHTPSnMY33NxNYfMelJepbGa3TGlKH53zDxu/zakdYguYNNXpO8nupvxw19XLRB8yWaY3wKttOATbFByciO46DL13Q8Se265PMkjzGDsGcZmwDbvlnvK1gdsyn5d0HTBd+z6oIiZr4nGXw2/5nHzzT/yyb/nyf/ny3yo+YFcBOP0kLFoU8ns6TDsK9PgYLPfc1UsMAvgtcYjQCV+DHizX23dXDTu7KiBMOwJpHjLaGqzm4qGF+65IVl/rcHrxKpj3Ce4x3+lpLbMJymeNhbq+eWkKtfarU9jenhIU98r71WpbFE1395dq5eA4v9FJ/sT2x/bPVmRMWsu/si/4b8Yk2cgtb5oHFhiXjCebs1Vz4lX7bfqQSq8Otd/gDH6iEZQ/Nh3xvCTlUm1zWx9kKG2c1M/te4TQMUja4CsR0ooIu+URGScb85hpkS1kIsE1kpEO107GBz4NAgyf9gTAqDoZn/Mkp7/bqpSNcJ5nWfdcs/3LykaSJ5gIw11NszPj+Ltcbt+y1VOR7+X6IF6qJ1bgvd3+Uq6K9Bdxl395WTePlIwVaXkpWB9IjDMJfjOYgaYEGg6u63eEzbwNLwqTEBgqen1lGGBrSIg0DI5+Y9LdkGzrCp/yR32Xuwn6Hkg4Zs1xbKW33YLEqPvhTPzc5dE9xRNMoy5btw2jOzaxSDcVWTznI2Ery779S3jawZYQ+fkrf+zy3sg8+O2l53p2F6nh+y7Vw8ruVbg2ukjzHgrkWhQ0hpnwuN/j2FUfB3as4sOMhSYuANV36XFgTaw8dPB0/cbUHYO4xNNOQSZoOMChazlEHFuASJXDFAoQsh3wGsbRSl4KFi5X8xLZpRSBVuz7ER+bNPcFQnMr4ui9Z+TYmnoVB/dG62HRc/T6bg9hePtjZzyrTfAPotme2H3tElrRb69WkiqesNUSvu6FqyzfezUyd+Z+DYjKVDzGvo3Mn7x3Y0BiqovVUrb59Bj1HWXSPcElh1JSHzvtOduo24tK3abTwmNOrEca5b8ypS6P2jrukNLisZOrr85rWR/zon9FZi9KHgCcTBz91kjfeVufO04T5Udlu0OFGLUj++Wy9s3tRpy3bn+YuIxZ8OWyTi4P6ePuSfh3WorLbAh0AtrZPvyH9vZXTIet4N3Ws1pfh+tYIoqSC6l43Lr1cz3m+kC15yTWXy4bqPsvqTZWHHWjYSghjYjGvoUNqcj1FjVU4igwccC8np4aWBpKMhyaqpOzcmzoCaMS0ZDYkoPQvDE2+9QahS/rLjInzzd9mxlMib1g8sKhlZBBQHG91aKwNMR0CA7XY0wkN2TSkEkY1Q/O3XaLjXq0P+dV+pCu+OsMGfHdkNIS2ye32paH52dRdi/SxxRCOV5RfxxB/Uas0pfU5oOk4S5FIZLKQSZxuaq8rFa7p1LuLR/Paj9X4iKbLxPjUvr2TpzMttMvHQYB9R7Cl9YQdDR1McHA5SbY44u3NG1leEC7IxiujQTLl16aNFQFeiigDvKMJKghg/s5FlSYwjsoKDSulYt9E03sp2lnQAU01GLgWurRXJfbClvTFgoAh+vid+NE4WbaM4EACUo42ynZXfV7J8qqeVGQXU3vp/U7DaGlmPZfTCNVU9k+E7l5O3tN0laTsbsbl+VVPTz17JDOv2xpue0dwuR2tL1zXaR5kR7fx7O812RsZ9m3ucukCTJwjichKPGWhuU6/+m4GlIgAkpSaFwf9O57Z8KrG/faFGO40EIHDL+kocUYdZS9J8ht41BS4dK9MSoFUq9tZGewJSd+eznxNDdneFmzafIhZUstoDGp6KF9ZceGG5IVByCg4MGz4KBRywhEzHqjG7eO20uDrR9W5tvTeGcgFzcPHmVSMqIBYXDPKTuL9jiO2zEfc+Wq7b6mtOd4Vpb5Km3ye/8xPtmFEWCNK2MYtg9Emnc6+9Nnz780z8UDCDITj3Aj52FHdNc72n4Qa1N8qnSWGnTZhllKTi2VZRvGqqBUYwxcV1LgjRdUgkiuq0wNU0OxCUNStAlWeupSiynraeSE058GwUkebG+OG5cCWxJL9oPin4Mrul1cFd5NBTdUlvW/KRq084EWqQ2gxrBmgreLzwQt9oAm0bvfEXl3QL6SiMzEmD8UIJLMllkj38CLo45FB2YStfk3RMYWxNkNn33asMcSnHT8NZ4Heq2dRmWomsKSoABePyVlcwN5d/fx2DVDTewf9ZpBuKG2nF4sS8nl9GJM9XK2fk1X7fVUh6iv3/pX4O77IVg6AoBBlbUMBu/nsrceC1o9poU0Zzo9uLdCp2GH1TkDGFj2AGtjcq1jWv9qyiG7fgPFB1lFRYeumSRLYQHw6sw1/nRmv/uvXQXTjnYsUIHX0fnEsFeu3KTrpm9YvIpC+Xqm/wP7othV24yspSr9suTijiCSi4H0e9+OU2vSUs248q+GJNsD4eZV2OGpVQNGBawjYGS1yYBNwf7bFI4NhlhI7SQDU3QcYNh+scLTyK4WJGmjqR0dN+eoXOEeHQI8RbUAHTsqU1PjDghJ0cbZRXRYiGAUZj9+uL55B9NbSrRl72fZ+1n2fr7VetNq78dnFUfYHyLWgW6rBIQpWDFwcZV8yybgupJABDBUFXQsrsYed6cAztr9Kgq84tUkJEddxRaHZJpm4qVYWoqlpVhaiqWlWGIflNlv95AOy7QbRK6Py3pMkAMzGFZz7IQguD806zE0HpuZoDANvB6dHTgZDs/U34GPPzwUGxZNo7pqY2kcXRpHTzqjsAMvpXUUAYYCV4j20T4zXQOpDgKV2lsTqY4NlCcIjaRmWIetpBY5ApPUdUNpnzrmySTndddW2n5XcSuyMq3SV9Gs1T+JV7Gx6jRtaXUnEQj5Jd0BvPbmqkGl8ZDaDXTDNeLjXN7HrCpz4AMfDTDyhY8OY9zrzTLBrj/DpolaIbO48LILs+zCLLswUWtmJSohzxHi4OpzZgQc588OKjxNTw1CgARVgn0Vq8lCpJzZSsnImC38qHy5V+Vj0T29+GafM7Wklry55M0lby55M2re1EYmOHcSUZSkQ8VznUP1fA15FAUmquUxn+56Y/dnQW3uOzDv1QQQoJJLYWhHJwV34vll00wWfirVHBjI1JZsumTTJZsu2XQiJzdyaKIe4MA4wFY4gujvjKTHGD0qMUFTNQtyxH4/ZG24c3EIBt22qMC6yqZusuiSPZfsuWTPJXtOKHtysiY7WwbPkrTsyMiKEbKhslOrBSFlwdE7s+f1JE7r2Lj7V9sUOCCz5MAlBy45cMmBUXPgICYh73ljwOoz0SiG89e8BxxNj3mbwVAlAmXCI1dTIjxCgHlQAhuVBq/a0HPxeyWy9Zg8OKSzJMIlES6JcEmEURPhMCjBmRCHVrIIAcV1LlRYGpIhBIfr4TEdKrxkeED+IxguvgQ7qq/7Onl7rkN6c4Rp2bxzSJAaUkuOXHLkkiOXHBk1R2riEpwmSQhKiKZhuU6WOq6GfImAkhTymDV17PSJE4Yk6eE4fbapzUHuPNBZEueSOJfEuSTOKSTOQ1AiZU0A2hSZIRRP+fLIEk6WWjhcD/9p8sgLzJE6MFx8Z9mxSFfiRjxuOy1GJUiV1JIjlxy55MglR8bNkWpcQtIkBUEN0SQs58lSw9WUL2FQkkI+s6aGnSFxgpAkPdylz3xV564mN982V2CKx3H7s3pySxpd0uiSRpc0GjmNamMTlkqJSJqoTcV0n1L1nI1pFQUnKxemAUjD3dAIpIGEGoJ04KMag65r3KekFB/z4nlUXh3QWRLqklCXhLok1LgJdRCUkEyKQqtZBkdxnjuHLE1JE4DD9QiUJiW2pvwogYCJUYYblRHvRPGcZq3UH0Sy3qTZqCsIDOSW/LjkxyU/Lvkxan40xCY4TZKRlCxDx3SdNE2cDbmTAE5WzmMmNbHU7+ni0GSdXO3t9l85ep+UaVnn77siycqawdUu2I170FFPdUm/S/pd0u+SfqOmX0qg4jwcSKWAPKZHJuP3EUGDGKSnBHFcOxtEeVbwfigM7XVBBY3xyKCKO2pJ3WfU/O1GlC+1+mk9udykeS3RJcsvWX7J8kuWn1CW18YpTpInEkDyG5WK3xSvl4KU4VFUKwPEye8DWYjpfYDFye5DVIfJ/TzfZlXx5iap94gtyXxJ5ksyX5L5hJJ5Lz5xkjiCiOQuDNtv0u5zJyVrIwpL0TjJ+TIrRdNy1cmSivJ228pxl7dPx/CW5DRinFROpOgww8vsr16bBPz4VH1obz8el+2NhJfMD0Xq/hCMDLAHajfiOSm+es+b10k9K/mvAxvneUePOcd7Jrzfk6BMaB0mEtNQdIcnagf6bsrxAbllWi4F+VKQLwX5hAryQYTilOQoKhLXcXy/ZfmQP6kwB5CY6sYpzgdSlMQyfIjGKbgV3HGfXyRl+VterG9EKeqa69etKCtWmtYRWBIzwOunpHzynn26WDoytlqF0yVtEG91UacNdrMLBUNzrQgJzf0NLxq2xlteYFiaTmMSAG+YSCNDHwyj/clB/LbMOx/eRyBG/L69vXo3wF/CN8Cruf21yJINv6Dme99gXBTH0/2uvp+tAxrlbmdVlayexNpiUd9HXRwNzZxu8m/3/dCu5mg86zimfIrt0I2Wq6WiyCX/s8NKY0qFhds6oj+h4AoCg1VCB4rgumoYMDTUC2YoTAOsRiCHwJ/E5uVO/M5bKu2RlrAHrlyq46aht/VRrbF3HiF3G7/N4LefUHDYM0Mp4QIAdR3qDqwMQU73u1leZ4HtbyJb51fFY5Kl/9mKnGzO8+whfdx220+sgNcS69DfYYSXoIicir6m4je1Xur9wJyuO/M6CE7cUk1yA58bTKcb+bDZBEdEPrYSeSxIuI6gqAiGyMrB4+s9ZhtKpmupco8CR2fpNwulZWyu1n9P2ybg83yzfc70JzCoAEMaDM37qHzdB/jjs29HaXTWVYgsGRY6ZW8MUUrbLf4aA7K1+H1czsN80t1hBl4Oxp2tw3NT29luN1s/XXx5Wdfu9lPtOXnxdlmJZ95M/XTxTkdkman0qtW+0/DbLBw17oYUiyQMNW/S0JwXhTq2pkIQgaXp5Gzp3XbOnT0Wor1d82LT/qdb2DFiSktl1xryzkhyiTBT2bhzuRR2cjJzWZ6tqvR1dOPmtxlfjTMODrIMNCUqcXBdh1szb0PMJSEwVPS63AbYGtbZNAyOftjKmpleitVTPbebP7PXlZrM0qe2JJUlqSxJxVtS6U82Sj7BMAxxCEXzk0UGbMEEYoal6RQgbQw5ghkDACYq5CVP3ImycpsrZIpLvljyxZIvPOcLecLRcwaMBYYkBNVn7uixJuQPEzxdv2B5pM+VkEuMCAzl/OQUBztaR0pLDllyyJJDfOcQ4v4VBA2HnZC7VRJLSo6g7U0ZgP3mBHwnygBIUMJt7H+/LdNMlKWD4C+TWqL/Ev2X6O8r+sszjRD+YXB9yEFwvCSAHk8oA5gACar4zwF9dlASMEJS9HCbBs6LtErr/x6+PRuVCQbUlmSwJIMlGfhKBoPJRsgHKIY+/uBoXrLCkC2UGABYmk7+04PCEcoQEDBRIbd5wvm7TGryWB5pWjLKklFiZpTxrw+NIKMPaxN5h4guC5SlRr9IZEfFTz6jiQElOTaFMfbwmQ7tXzjAsuDy3MGS/JbkFyH5Me7x52NT4ljoG/1REeiJjX63Pws5RBobcKdnLzOihdI+c9X4p/awnLW8u7fkriV3RcxdFg/K2VOhhLdYT8uRRaHnNv4jc1ZEQuQ6gxT0nIcTGGEMnzlwzCMYWPZbXsRY8t6S96LkPdZTDzb4lHAW/tEHghD0/MZ5/oGJHiKnKfzp2QxCtVLdZwaT359oJHKawobElxy25LAlh4XJYcO5x05iOAFKMCNQCZDGFCnoeQxCtTJA6EymCkBPZSCunfbuk5mDdsM9mSVBLQlqSVA+ExSxwdAMao46IVsKD/ywREJrItRC+ksOeNugFgqT3W1or/1QFA/JysVXpT1aS5BfgvwS5H0F+d5UI0R6BF4fczAkLzG/zxQK/EZIijb+U8CAH5QHzKAkVRxnhOo8z+povKoc7FT1iS05YckJS07wlhN6c42SFBAEQ/DBsPykhT5XMC8YQUkKBcgMA4ZgajDD0rTxlRzuxPPLppkfDtYNWqJLsliSxZIs/CcLec6xkgaMiEUnBNtzEulxpyUTEwpL0ZDJpc+YlmSMODwtvSUdp8lmSTJLklmSTJgkw04udkklTjJhJBFu8gieNDjJgp0kvCSH27eyEs/n9cx4zItUlKMTxJDgkiSWJLEkCX9JYjjfSIkCRzJFIwKmp4ShcIaTBgROVi5E8lCZwgkEhKdr5ieRODn1OJJakseSPJbk4Tt5kE88IHA48oQ97ZB4UpIE9aTDAO03MVBOOQyQFD3cpoFual/8Xols7WC7aUhuSQdLOljSga90MJxthJSAo+hjEAHPS2pQ+ELpAQImquU/TagsoVQBQlN1cpwyJHJ1DHLRN6UjuaSOJXUsqcNb6tDMOEr6IKEZ4hIN108a0fEGUwmCwFAxQErRsgXTCobB0c9terlO3prXjD8W4leRrVzc+6+huCSXJbksycVXctFMOEJuIWHp4xIN1Utm0bGGEgsCT9fPf1rRcoWyCobAUM5LTmkDvbuEciC3ZJMlmyzZxHM2Ocw2eioBUMBQBOH5TCJHvoQMogUmqhUsd0gsCYlDD03VyXHKKNKVuBGP204/F1lDpbgkjiVxLInDW+JQJxwld1CwDDGJhOong2hYg0kEhqfrFyCV6LiC2QRBYCjnOqfkqzqSN3nrtrm/Sjw62d7SU11yy5JbltziL7doJx0pvxAxTWGKiu4pz+jZw7kGxeHpGiLnGDjDeQdHYirqOP/UrJ+SUnzMi2cXiWdAbsk4S8ZZMo63jDOYbZRUg6IYwhGO5ye5DPmCWQUAJqoVII8oLMEEAkFTdXKbMrq7dkXhIF3IpJZUsaSKJVX4ShXyTCOkCRhcH3cQHC/poccTSg0mQIIq/lNCnx2UDoyQFD1cp4FOmeaQfuuoLUtLc0kMS2JYEoO/xKCZcqQMQcIzxSUasqecoWMOJw8Eg6NliHSi5QvnFQyFpaLbTHMrsjJtprmjVzgUekuGWTLMkmF8ZRhluhGyCwFHH5AoiF6yisoYyiggNFUz/5lEwxPKIjA4WS1P2eO6juh5ZvmW0/6D+3cwzSWTLJlkySTeM8lw2nEyCo6LBCoCAb8ZRhGAlGkgLK7GATOPypuUgUA0trpuM9KdKJ7TrKX2QSTrTZq5uEPeQHXJSEtGWjKSr4xkmHSEfETG1AcrOrqXXGRiD2UiAg5PV/9ZyMgZykEUJKaizvLPtcjW7XO5ybrNBV9e1vV8Y2We98nq62ORb7P1/8x/Kd/pKS5ZB+DVjf3Y6H1eiIb7WTU6cu5uFH07pMDG//Xj2ol+RAA5kb3SuqNkvyr/UiaP4t3STUJ0wOb/vdc7l2Ub0zZvZ69J2moyts64LK/q4anDueSrtrRCVn6X5UXWiLYeK/R1keZFO75jAseXbXqQ5Mf2z0u9R6j36B00zOaZwH0zlJYZcreMs0aZOniKQmSr5vECTGgZ1iDzEQQWWYIbVVc53mRe9peXtLaktSWtBUhrlnvqY7bTY+6kszfR7fbP/WydI0nSzFSfMSnwDM1c5dI78XvFSpsNwpIYAV5/TzZb/5kRip/2QTBAQMUIjoywjXvCwVQPoUw9A5jrENmyMUTD4W96GbEYZw4F2+fetlDznub6CPv9d5flx03yWB5GihElbp+SQqzfKUTdRY56uqxFsXmrp5fsX/1h+Vk8/yKKnYbp4WG4779rJ+lfv/+DMpADDGnH64D0RxhpXYfo5ntiUZbtll6zACp2m9c7Cv9VHaFuLIDxuV09ifV200aTEQNztn5NV+LdkVrMEbl8fhbrtN0Tpw3IT/UEoI7Dh+TtAKoWcj3Qfwjx9QD7LzDsz7UPPZlGcgj8T5Ec5f0zDPsf26SoxBH8LzD4rXhO6xHfJpsDxn+zcKvOH0bP951bHanNyq1uxItIKpNjkez4T1F+zhtJV+larK92i8LxEVRHN6Zt/9lUtTSrfs6pU/Wgn2nC0ofgsijERrwmWeV4DIaEYw7C5yvqGPzz4pY6CJc3NxefLv5+9vmOGja/fP5wcX754eKDKXYShy0p2qBaqy2K132jjZuB05KOOXQ/JZuH+7dars0beQz70MgwXryK4u2+FKs8W7eMqKN5VT1J2QfJguqMtalpGs1uxWNz7YqDQT8+/PtOIRxzyC+y9dXDebIR2TopehUBMvAt4rAswIa/wenXKDax9FP6IM7fVhtxW5t9W46uD/pnxxrqMQdIdWY0s1WX2ZeSvCS4fkqatcBlRp6LL6JbMkjVHTIjdzyuttWoKXlWrJ6a7wO23XqtHFcVDojNapDfU0f3b+R8mX3N8t+yUdmy2YQ4z7dZ5WQ6vtvvJ5bvDoRjDtL7i09X/7i7+Ewdoxr07urj5ce7f1IHqwW+u/qprl1upMIFGbkd+PWnL7fkDKkURzbTsRmfXeOx6wGXSJ9cLfvh6vPd3z5f/SNsJXuTll8/iVexcT1UB8IxB+qTZE5kpH6+/PDh0wV1sH66/PGnsAP1U17umvncjtOebtQ8p1gIGayrz9c3Fz9f3pLH6+L/vru4+Xz2aVSBebZqNkhr26QPaVPdjqkyeqSiTpGmWYFq947lqE0nqb66qRd8z8/NEqOxltPSTUP6Gy/kPud9m4wKRu+Lxhqf0uzrebItxy2z/iN5dzwYfjegHHVLNntNNun6S0GeHh+y8lOef92+fEzSjTTYWHAqiry4EeVLXdYI6nie12O5zdJVa96WArXKu9uUfXibIm/4qt9VkT6m4/ZCZJLv9PRjusPf0ion7yPf3t3uPsoo+7PNJmTKtugezvVhZbfv51rEyFKQd5napHW2fk6NhlUqBknTWrHtRvTRkdnWlU06ROQ0bb+9p0NFDtd+3OS/DLX8bzDKTfr4VJU/5Zva9l2JccD8VwvHuyuSh4d09amhOsrpZEIxfewfTynnhI0cwf8pNpv8N6oz/VgIMS7/9u7EcLd863Y6f/xwffNO5RB3wf35glyjXtzcXtWl/oezuzPq+N1efL69vLv8+0UPCRnGTxc/DrjYDGU9NVZfxfqwvXzxKrJq9N51S7W27zsj+ZjD+UFsRGUuhjl2c9T7M7TXNLp/9vOS6vqXNr0/l1V70viQrMi156XalYTUnB/8NBhd/N70OfbfmO563z7lj7uKOv3FZcVE5Rh1R/rwAS7VcXq1l02JWvtDJlZtsd5+Bjos3zsTjZ6mvaGg84w5GGdrxrL/RmT1f8llx8/5q7n/BAu6yIytrfsqimrkaXlvmhw+CvaxfNlTjznW/XYEbMO5Wb2nL8mm7c8eMfc+51X6sNsIGD3DZGLvhpTjzqOmTW9UyXAjVmldyRauOgdlejFNc35OjQE3F+eXF3+/uBl3ZFSr/ZK6qFIlOx4IxjTkzdUn8jLjy61kRptp66hynUilenf7z9u7i5+/3J79aDyUoWWMFydx7EgmplkO60qbRenNxY+XtU1v7v55Da5LaWat49RrKn4bb1iJ0BTWRv21DrpAUhYuNj5Ku/pkhJFJV+j0+UVdz+OrO+zkxEjhHlqbMMgoa2msXDaT6l/uNHYBfP8+KdPyY17cFUlWPgjjuQyDZPNTb/lL23GGdH5tfOnx6dD0SvviAxwQZS4iu9v6Kf+vDCTVC/47B7vvipzNmPvh4vRIBt2eMZE5kkC3a2QS77f1UNTj0WWCPQnEffsk7pLy6414OGIjntrHVkMw4pZ9dEKw+SPHke51G2B/RL1K0eLP5Nxzf50UIpMwURc4YDbjr/OiP5N37O7Pi7Sq13SbQ/G5J4G6QE8KxQv/jDrBAf/4x74EqB/oKIjnl+aDzT4lJCzJY6G8b7qngTrRkYbpje49KbI39UPMn9HodMAjzIq/0B1UH2n/QvdTJc7+he6gXYZpNlqzdc+Mf6F7qPEG3T0p0mbWWVnmq7Slguaz9uhV8xV3X96LbP1d9x00m9LxS+q3wx18CJHvv/u5LvjSl7rEq2f7X7//PxT7jZFn/+G2JE/nvX2mqtPs2n2qNNnUA9awSbOqB/Jd85F+mrXbY2PlGxD+jnY/Q+MSBxGGv3wQL7Vv1hqMHUuKbNrrCVRhDzL1bf0DZux//0Hyctj5zx4L0Qa5i037n2bgGR5PQ9e5uQ6T49tEzmEdmidUAC/mjc8JuK58hQXLdbT3W3h2V92VGREdVSNOLBfVjAZFlMFtNNNxS+kuJpaH6O5k4jml6jB0lhqfPNZjOuQSnxLu3FW1TSxvVU1GkURLKprbSoX2/kex1o+xyZ3oJHTOLC+/hy48NMhV1p18f3fW9go0t1mXq2St3uJULwHWDsSdzETgmzjAnOAbkiSUtPKMNSm6Be/+uixJu4OrmpwLxdRNgT4Sp7zA2YEuPGT1h3fvHBUaZMEC+Cl5TEjRW8GOV3F030g1DfStYozlG4aprTr6SKwqGOVHLIPdOShZpBDFBXU4ZrZQG6hFWqOZcTz7pMWizJ83RlqS4cafy2qsfyw0dBNzaQhg6YvX3uY63ftgThr/QzzdnSuSJAtSXRLGgpSx+6ixw+G1KNJ8PTiWQYKUDgcIhx24RTDU8tFWkIDbO122jYmmkDbhoik0drT1kIQ4EedlV5omPI9OHP1MgCZNcEc8reJypxSjtFQxPDphxJ1+TI7gjncC1eT9e7HJs8fyLkdLyCMoVDfalIwSZY1L9ft5fBeJqiwBK0PVxBTmB6Ro3iR37TFSKYym87J+eyDd0xBOYQMZTZgAbkez/7yyaE8nShI1IXhzv3gZFBMjtMudRP6UXQJPdLL6zrOoTFzjWZD3+kikGnGC5lKNrUnpVMKLF8YON41dZvuv+g+H8/fdr4e/7663MkYfPi1t8IPIsKKhhTwad0ZlUD6eYlpfYVDeiJe8qO5NPyMjwKZnHgUTqUANB7b66ILSCK9ijCjwLKBJScqTgP1mZ25ZQHg4MFz7cvRqFLf3vCpR7dOMqCsgFegoN4tXeUIihHStGVec7VeT+W/3Bw+AhlsGNLnRTXMnHM+LemQ1HmTyTnfVpUmKQE6ksyuF9R4nuvfInxNiIy3BOvYhmbL2ME765tFbPDIIEtCTNAYmcT+iRfcnXjmlxXDsW1OopUA5AjrYCVVSjTrUQmoA69jB4lZRBgkCOtWMaygpcl78/pT+klakhAij6ffxhhikkojMkps0He4CWG4KUtQJsjtIGUp7QeI4MT0LU5DtHBreoiZwDRtLOSLF8csTSd4azSh5HEYL4qLxEj1NmDhuOef0v9eFuROM4GmdUUZh+SHCK7Aj0qQJ4Ym0MZhZbOwpRQqLJgyPThgxDmJyBHe8OUc/qc5lrXmO0Ejatcy3En3dugZyYpdtC4A8YTOtam8S/+h7grIKVisS9lLE1uGip1WKLIGd7lRSqqQSc53BWGDYOt4klhTx1xKnkkZ7remk8ZcQfLmYzILZTu/D1zTihHY2jdFJ3ibhTaIR8KiSfQMgQgNr/LNyTw7/UcfBrlv8Dnwb2Ue29/Vpzbu1b6AL0tZH9RnG6Klto/SCk4CrG51RLa8UnsGvaGAIFSBiM4ZlXhWpqhilLgWxAvhn1MtDSOJE8clZF667DyMYjesADufzFfSw28zFpvHf2RV1qPJBKlnUOCQp9mTi+596qERwQA0SxwO93bwIiAeXsuQzynG+C9gtpPMC9uF4b3zn5WxsmlCcf/kXfz8TEySkr51I3XjQh7aNqQI7d7OYu5dmEUK61imUf8RNSw20e4eawl4lIEtQ15rzLuVBi93jUejId3DOHWpHFrjgLcCH730hQvpQ36oUzh1GfL85ruXQMT4uxl17z5GyzarTgycpAoV0JsXOFOYHpGgutXv+73A9oXltqUDqHOrwmiDdn1S6jHhkvyc+ZNter0jXvgP3ZIIdce7Fm66NcfVSXW3N2QlC8mqYHYvw5qGvZo0Yjg0TeymLyhEgBKO2ntdCdq8OZR2rgXXsYPEWsYAEAZ1qxktYeanTvV34QTwk9VDVP7QPzprGHkPUuZiCw3E2lCGy2O2ez/VVX1KlC+CV1JGhiNLHnJCX0rMsATeAr8bOwQyJorjoieRlVTFKhgaxAjhnvPxNkiWKQ55UTiftT8NoIdxw1Nb10NKhv8en6RPHl+e8D66qw0z3gTI8L6lPz1ujlwDcrB+3A3GVZ/nz221ViGTfR3SdvDWPWX7MgRZEEE3bgyhjsPoPYVaRnnykSRXA/2gjQRFkiDsRn2ybg/g+OUTz6JMKq0n4pEmq4D5pGgmSINkkfZLxRQGM59ErY6/UidIEd8cTWaH3lSJ9NmDC8OiE8VbkqBzBHW/GK/GBIsPdaKIvDNB8ut2QVdQ9dZps4f3RMB7cFfVkdtRbJew21PWo2Gpb5zbUxbaBY7xtS1igwCtpeDzmlakVvbhb6UMk7245jX10kygxXHHGuVvVhbuJrmD5d8CT2UE3qhPFi09l/7zVBuknN8B7911zn3mcLg6NUDF8b3b950MNxvRrAPgUfxx1qgPxjpvcp9W/QRijededcj8A02s7pICuumNoEUSnl/51GkXz7f5I0mvYSXoxbwnF7khy7s9TWFFNpTsJHJG5rq0aNceUCFp0kpvWmKO8VM85squCQsXwV3B8Zl4dNLpZhdUBYjB3nVBMNYgTy0VPKaK2KaL1F17GbQ0RLsG37Ajlqs7zp1qyyjpFqw7kcaQI0cBPzHsttgkC7wzMt+dTr0M0b51V76eyR/elTLNH+oZqC26znwpd+Gfggn3/vPuyi/IttLNN1Z7+MfZUe6bhbuRHPbY/fHFaFxt1JUzuKEHwXH8PbOAyy20polIB/Jg4iCSH1pOI7tfI2VQfzLHXmg+gDETdnTvpRQjoUrM7ZarxH9JHxp6RCUHnRB0sx4eM1MMusDExAngUZud57fbstKHs76igTl0r3q6NWYBw7jTjnZm9BpTuIQ0s3YmQG8F1tGfXIgQoEdAZLduBYrpglayqa1GUtcSsnAnhGVzziMIMcyCv4A8mEAUK43aUYZhdZpWUIiZYPYZHP4z6ZAcqSnDfm3Ea7gXtvndQttT6KDpfAD2udQqHyZMkJLLDiDsuw7wfkiqpB34lymZv7UY8ps1oNQA3Iln/nK/FhlYCcQnptCTQ4IQEtkhxrt22FTNAFLEdVO7ebLToAihYG6CGTx+z5lPso7rITpctQeZ00NNyNDlgQTWTxGLexl+SjDVA3OlHcqYZ7AdSIkynzUXNr3oblX9kQhPJPz2RWFMrYjbSCT2RbKQbYopoMl7syVCJNgKerV/TMi8Y634iPuD6fVQLZ0d5h90DZUoVzompYzSvzQG9cpRNAhQzoNPG27InyxPNUU9lJ0GrnfmFCAKu1bLd/9aCQVhzqcGdTaN7ixB7BnB1hsFmtbK9EeVLTSb9hfWVBoppCsUSEjcIw/zCB2GSPIGCMGks5lcnyGpRKwQDjmeHjFsPIJJEcMJTqgEkvRjZX8aacN7viWnI+Kw54ibX66wXI8vrzDOb/H68YPXqVRSvqfjtuAnS3yg5eyyEaLYJoafF+cT0Tw4a6QR7hdxCE/D+WoWOgYy318vthybApBpj7xlsT0PqER8XpJOY34yivGloxOa85+dj9uiNH3nO6C0685lCaiCgEhg5S+DXb4kixHqfmydeZFeedWMApFj/8NUu8g9ozC/0DxWgxX7bvgXn88Vg/8hTxmDUmYd/0vk9lUCk8I+d05vfu4jf6cLVK/IkmPURPqhYW9ndiE2LYJc3BjTmlzeGCvDWDH3sCHnDYP/YU0Zv1FnkDflSg51iZ8XqKX0V16JI8zW59ZJPSj97yFR4uYUtHHoFRNDZbTmTbLUOMqFs/WWu84rSQGczyeh0yTPOcSvmWLFPdS7yTRBrYvJ9bK6ztH0D7iFZiXGT0kiGPAdVCqOnnFmoU51hqMaxJhTqH3OdP1/Kpr9F+mHcPELJkeeTmdLoeYULearzi6x5rHlG9p+5zrfdXUTjZpmBCHluDfFHzyiTQKc6jxB9Y80exC/mOmfYZ6NkGuQZ426qTOyQlC1hLN8+maNSbdarV2kOazCYGqMEMxByUIEhIp5q4qAqHq/+ovnObFOJ3UUFFrToqWX89QQ24p3qDJvMfQQjfGaus+tWZGVapa+i2X/8JF7FZtwMw+mRZxlAavRMI4h5qrONrnqsGUf3obnOOnbvEJlG1OUR3kQEdESHWR9F6wdijyBFqkm0BPUWd3aPpfEfSbN9IyX617c0YQK4JM3+FEGm893tp/RBnL+tNuKuSFZf63XRxWuTLa5eGphkI6u84zpUUXIbG2I6t9XT4TiwlSTM7Sl314OOkTaA348ZVtKeFURoqjOjfe2o/CnfrEVB2rG1pBdlfhiFmeoUwQSOP0uw8eVMFBOtqc0VsJoBcIL6fJw7yQnCxPPZ2b7hxn6+lfNsq3UBPY1nWifwPOuc7wm5rUpZlZpc1t0HxVjAMWjonNGIzvFMjgxhndVCsgD+azFm81oBmhWkBFMSdgRnjhdzWTJFdeCTjMaktRgNfZTbIm84ESWI0zHDEy6uD8/tBSdVlVL8um12Oj7lj/ddSqj/xHBdLT7NdyXUcWFXL4PGe4eoB30ndxTIVDLKLACHniLRAWmi82FMbQ3SiTg/pldnk6SL7uAnW2/LStrV3AYKEZ18SvU3Ild0xz6hOvyQg+wDt5kExZ1N9QTHkwEJ4royLlgEX8bHa97x+aCfTWjWIQd34+nEYkikmK57ihFYusvYLv4OCAR32yH/4Gd0XMlierBhsCgiDVCn58nmPTe2T5k3kkJ7t1kSwq6JcddxetsmdIVjTh/ULyjCGYlMZUpdNtyahlmbTXItMmHa7PFGzBo9a/beuKupYduc0AhK60poIL21I7TEEeO1ogboSZBlCTD/jUam8G7go83kL5c/5+vtRpxvyyp/5veOE/F1TqdF5XgflXfwAo8pWAAHZQ7TvFbWe6XE+svl53zNeYEMR9V57hCL47QEjsH9lS5TAFelD8nMvZSy7wMhefdMi/0djz4ZZ2uHMgBz2dJRdPmS6jIw2T+06N69Us9V55+U+iL+wpanYAyXB8eZIlCHPrEalxJ+UcyAdW3UaEwWKVotO+O4bFCIspeCo4b00Ensojh28TiNifRxJTn5FL7lagLU57xKH9JVp4D5nmWzu9NpaP1+gM5yeQZr85PnOmX9BW2+yCFcmz+GFKkAKtNxeentHrKfHXG8u7TEivXwkEeXVUWK4aLqGJCkqFZ7rCm5oHzzC8M1JLQAjihz495f49UdNYLF8UjNeNCcUkacjl8yjhxQVO/+GbuBjy5QDN88kQ1bRS/5Lzeitk0qgEv5aOjeXVXPleyu0VdlLH1ieDs4rBSBtASm4/WkfTIAybuHx2tDpYgSwyXnvBem6ELaBoOw/DvgvPe9KOpE8eI573b1y+zdW3+325d64IE4CmKhF4pa3x+qcIp2HRdJrADOSBoIihwD1Gj+uJPjTpTVp5y95CJh6/xTg8jxUhrf4KdhLLECeCtreOa1CNOpRqlIEbxAvhr1zJYoUCT/nHGNqk0OPbdhZd2eWbzn+B43jU8SZoHndK+TMFbK140NI+3L6NFDaNOWzk/5WiwofGo6/ClhU88n7HqeJEvAeAnafp55vFGJk78H8J4cL94WEiJFYGc7uaQMfRllxAiQhA2fSgGu7Dvphv1gCrU9J8nG/H6qr8i+S6AkOt0Bnu1y0B2AJh5gTwfTwR14nqJ8cL9TTEOSQCYxEb+7+L0SRXOB+/51A6oDqohsT2wDk8NNbIaoGodWoIK4stmMwX3abCa2c8frDhmIsfsb0af3f/OYxg8sgNak6Z30wDoE99ThOHH8M7pnnieVeMyLtJ5e9DU1BRny2iOejeuCXIPvTnKkCuialKGZ18pboxllAQ6jBfHSqLvnNHnieObJLNM1LsNLwJJR/Gd7iRmQ983u72sJb5YvWk5Xx4WT3Y/YE/FTfoo34Hn00tib50RpgrvkiaXxTqmfa0Lop0RmFLtFPtUTe5zsN51Untbm4hQ8CobHORvv3AGVI/g8PZmihtSxakbx6W6zblTFdQnvs6fTonrcMKX50gHeo78eedjs9PoqtxWpgrudYnuKBAekyTicdD0w9C0qjOa3lFG4Yd+i1jP7S5ZW/gqa9o1eiwWJAQ8yX4tiM3tNvOKUOIg0AWcvMgbzXJB0SnVlGNELO2CPrrdjwP1wfDo1jk6N4I7aHyZ6MT4pl6Qt+hQM784Zc9FnlCOSi81y0We+cKZRlJGhuYR0rgnT4PgqW5qwLmwrXgDPth3HeaV9REtK0KWTiOrq8eI0X7D47j3jSH62fk1X7Rs1N2LT7eUgTmbyKDYlbT+7QoTV1s4WgXVbnW6OOfxkzVb4AP5vPbYU2WBKEQvq/UkNs6bBEPXltYzDq68RbqHLbJo4Qapt2jjMqwYZaEVb6JlQvLpizLUeJkh495txjSBpwt6ARTARB+RvNmD8ojniBDZiiWMx23hI24zVgXt2Q2hL1nSv7BT2YyEtInjsDPdkdQqQkzVtX9axl0bO11F3Z1HLzzVjgwt3HbBnVzOtveHac3ohMfAyHBopCvuprKdb4VmFY4BakVceTs0TIxeT3Pox7n0c6m7WIPAwNhgHy7wQe5oDluw46nXPUi9cnD1K/diQZOlhTstTd2fIbD894gXyUokh0I0SwUNVwWL5pzomNO884k3JNxk3bBGQg3hp7B0ijkhxnPRE9ok0mpHu4gLRgrhoxAu6SMLEcctZr86lsG/Rzcw4VTSnWFLP3jROFCnCBFnzUOw/r6jY04nVNEo6SXTgfhPoGI15hghbe3YRj7IbOQT12I5sszaZwt6PSYeQLjnHHUhZdMaXQ54/FprbzqNJ9uDeN6tdx/7HdbciK9MqfRVN99sn8So21OtfNJgWN2g69CKOoNhXSSoOMq0YA9CjzbwRnICrGwQFjRMkKDzDVkQMiQLEAsaYzKsoVxWjVOYgVgDnjFeok2SJ4pAnULJrYjnjZnHVJP5uTFB5adwQdXpfNycYpQtYNSFjQpFEQZ6Ig9bhpQ7bRK/sgD3eO75jMObCBC93NfcVD+55fbOQ2Mv48RJyt+bYbz1/LPJn2U2MORJE06bkHgYrH8O85ncbAE2hEFmdNIoUQQaoU3FnxioIQfTq0tEXP0RxwrvkqSx7+lqR1jxGFK+uGHGpgwoS3v1mvMgZaLL/w2VWieIhAe6DwxC9up/KTZvej1p4W+JQBQvvlMYRIZ1SDJGn4qB3uUXd2UPy6ph9Tvya04dPamUK74/aQaCI0UOM5od7PT4W4tetyFZvzP1zIr7OO7WoHCel8g6byZlSBfBY5hjNq7jUK0epMVHMgE4br/AkyxPNUU+nDG1D/UE5aprvY3nN8wNWGk8k+b+3OlQvX/ikrx8Sihx9zAl8s3e//1GspXoE6llAMOGv+YI1LGBSTuDdUKIhg+zmE81F29o/mDG+e9+J55d60nIfiqcSgJ1dxrX7ghXhHutrfppYQR2XNk7zqm0N2vFumzChBnXdKVxBgQkUz11nXOFKmWOoGqWEGOLwigeaI6pMwNwPeb7LHhJUvLB53zQSFCmGuBPwRvt8b5Pn7YPkxPL6hPL5aeZxi/zNy9vjXHESeXoS+fkE8zLc3amF95uPDa2csDf7zcGh+zdBq7Nyb8yWzfMirdJVsqkHiJl4UUydBw6QOG6I8wsb9sjyBPBG8ljMK/sO1aKkXwDHs0PGy8AESSI44Ynk4KNWpBR8BPeTgSX6GidD3NlPBlZFCpyAVZuT8u8RbQqOZn5zgXSkA6B7cUSIH+sJD3dfk0rSKa+xkmyoYvFM104v36diGiFtnqN1fxZmtl7YcGA2EO0MbLUnNIWoYLMRxt0Es4wA1gW4lyw0jb2vU6m8JY14e170/S5Lr7Oosr34W+xtrjmX11cvzV+aOJ2tmbsOOKrO64ZYHN8jcAy72qMLFMAh6eMxrwCo6EUJgxCSd7eMt/1AESWGK55ShCQsZAAcG+fTZk0aN9arOo4WIgTtY/ig3SJEJjOJGrB4TLL0P7v1OqVIk+D9VIEyA4279QT2feyplSZwMagxOMnRJLwpONp10tCkuFgH6cW5dqRZUczLGqMvSFiP6tuXwrvDiOZF18nbc02p6dC3+BqOhK3zNg0ix+1ofMPWcyyZAngla2zmtczQqUZZaSB4gRw13pKDKE0k55zxwkOnDmHtAaNZuiOyAkF4xliE0MwQySvtliIDSrEd8+d8LTZ2uR1ABRz0gGURLCGOUeIlQaBwzkkYj1km86NejEyuRfLultETOChKDFecf+o+6kLP2zocG+ejZWwtt4jpGtI+hg+OStQtmXg+WKQrcSMet93n9txETcHW+qWKyIqLJL6BAyRHphBeyhmbmeVtjWqk1A3jBXLUiDmcJk0k55xzJteoQ0nmIJqlO2IpHeYZJauTzBDJKy1ze59SRMfMV9tCNHXGbdO+Kx7Z++xUCnp31SLzIiiVf+hYypQriP8yx2puaV+vHi31o7iBHThmGUCWKKLTzrgckE9lVe1Ih+Uqmp+Tcw0fjTMSp4GfM3WziAG8kzYmtJJAQY8XSWupn5JSfMyLZ249gKJq4+gAixVAcY6B4ydZoBDhkzweM0v2Q71IWR5A8u6WERM6QZQYrngiKVxSi5S7JXg/SVtmoMvWiFf7SdMaoQLnZ43dSYlZwpuCvx2/ACX4wgHYi6cdqdt8serFzxSRwjqZYm8K+wPSNNyrfKmppb9shNxM3DyiR3M4I7onFzTzQ/rIW5XC+CQqY2gvRQeJ5rdGMlPw5NvtSz2utK9c97BefPRAnPlhgxdnHAoT1vOGdqZw3+NE86k7UTynWfsvH0Sy3qQZ9xJrMgWd/xmQOc5I5x92ZcKWK4C3ssdqXgtnk3qU9TMBN7ADx1tUMySK6LQzXmKbVCIcnOOoI9wUOUAn8I5xiE43SURvtTtM11CL5rT9a7HeJ2VafsyLuyLJyppH9xkwo26wI6dzbgolTkC2lCxsjB4nZICJMG5851V4kHSlVCFcQpOZDvGKFVvxpjIFZlzGmC9KvB8qS/R4kAbu7Iy7Gq2FQC+MpM04dxsgFsIH933SuFKkGuJO0feln/LCXMmTKUTx+4EIzO09+4tR+yI1f5O3YscVmURquL21hOwNT5UrZmJlyhg8wjDHds71pV5VfnmJ0pnIRJhKbUmWbiLOf5qV5UBX2/QqkYiWX2UZ0LKSNNWCVJUG0eMWlYYxpQg1RJ2i1zMuiWdRGef7/i6R5ykR8VJ5K2vHnSrjLp0HCE9k4pzn26wq3kYuFhAq+MTpEbDPGpgcMSsiomzB3Z04dnNeBPRV5Bf/RvxIjj2VIh+VKrIzn2ZRf5mVovlEqNM1FeXtthX4Lm+Qxmwi0yhHWQIQRUNXBpz56Ga4bHMqTCTKIEzgZRgb6eJWkCeZUXsa8hOqCT2KV0d9eYYnV1xPPs10SroDn4gfx3/j35nPlC6yG8/5Tn2zXlevoijTx6eqBhHYhfs8Mjy37lFw4+NaoUa9Rul0K87xnIG0jTp1IN+gCBb5JYG+YgdlRu59oXTw2TMgYT9ncFlibheQpQvu5OQxnHPNPlSSX7YDFCI6+VR2wwhyRXfsEy3h+1paHfcNacQp5YdCoDUOb1a5MTd+8wAFOYqBp3JTAUfEuKulmd5kAGh0u/1lfNepQiSKO6tSROo97Uv1Vlbi+UuZPNp1Xsj4ccwqC6D9Sk+CCGHSmybC75K3hUVldJ5BW0w3Vu0JcbJrd52WUQO4buzpJd8E47eskO2Zg0JjErMi/hKGJNlk3PnEljGdao0BrD26gYrsyq0IrPDeajy7EC/rOYEZIY88qUKv4Sc7Byy7JI7IkecAb6d16p4+mVYKdXwp4kTdgW2FpZ8s6MF13qxzJchlDZSD9zvAcgRwL9jE89rfbyWnVMJDQGceFbV3xiRBKC+acRV6nZTlb3mxvhGlqG7Er1tRVqyXVyno+pcGVUzeU5ckzqEfveQIFcA9eeMzr6Cn1Y32IiuMGMxdY77OShMnloueWkTlB9GgcXNe6xRUhVheO6sVyW2Zd/wbctUb7KI6YJ13DuA4jqllEdwnGQY8W7+mK3HbtL7RCyYISWfQIzzHliCX4OU5RZoAU5Zi+nlVQJJGlLpHD+7F66IuB2E5wnrajAuZTot7yRfggZcgzT7ltZqAJNJ4IcfLRziixi7BfFBjAQrvDiu253GzKiej8uNa9M0FTIxwTnVS6ZOROmlp08K14m0EmAUI507zz5E3YpWKl7Smae5zU0GnkiVlkYxpsvHBG7EhdteN8UWNhcL5osYWc0iYVZWsnsSa/QUThqh10R4OK9ph3MIvFYgShXBA4lDMLMf2tSLlWiOKV2+Mu2RFZQnvgTNOyz+Jzcud+J2zMWdG0XndHprjbwCH4P6GyxLA33CTzyvWHfShRDkdsHNPixrTIClCeteM49jfaga5/NlOS/Ihfdyy75Tik9J5I0aF46UWEgX3YXsZA3i4/ZDOK66ielLiLYdIdM+PGrdtpJuCt59ynCfdTcWiEt/HR91W5XA/zNMkiXPdlZUPkKbJFC69QrX7e9reRn+eb7bPwPUPTDou5oqnLVyuJpqJ1pLofmfOdj8zR2/7KcwdvS0pkmGk482oTxdfXtZJJX5Kyyov3i4r8cxZVZDQtfNHg8nKLzTO4asnllwhvJo1RDNbGeh0I60GEMRgHhu30idKFMtLZ1zRf8pXyebssRDiuSZ4sWn/075pTQ+uDBo6fzWic5yWI0Nw/7UQLoArWwzbvKKuWUFK6CVhR/DnqJGYJVZUHz7JmEzaXqGhx/DcWe+k8PSK6/tz3jvp1CpWT+mraP7MPD+ioZu9v4/Jd3yUc6SYTZUrmONSh2iONcdAN3q5YUYM5rETqC9wiWJ56eyriqE+9IICwAznmydQQRBUiubdp1I33ImyGlk7wCQwj5exbb0ekSBqbKbJFtiPaUM233qipx+3pjAhB/fkydQXmFQxvfdE6oy+Ttxaw4gd3mdPpu5A1Yrq9SdTf9gcoECoqMdbbdKBHOPG5mkcjlCGZMb1BO8oRI/k3TOnUy/EPuaAB2CW9QHzUMOA5d8HTyf/Rz+wQMZwXvn+/bZMM1GWNgkfxjX6tIzGdmqEZ5z4ShMqlIPShmWGWb+nGDntm7AC+Gf8zI+JE8Un5577+8qQk78RLYQnzj//o/rE8eXZVwDnRVql9X/rsbYpAlB0o3cPMNkOjnOOE3PJcoVyWPIQzbAmGOpGLgsAxGAeG78+IEgUy0vnXiUo+pALBQgznG/Ov2KgqBTNu2dfN/RfjXqflGn5MS/uiiQra0bsC9JG0DTOCQo59kSxlDFOfB8nbKjJMW7YZ1i1kBQmlzJcahOcL/ErIVsxJzVH5l4z0ZQkF1JsclOcGfOvw6z1nNbcOrGK7TzfZlXx5qJQQ0gRZ1WPysjJhEk0hRxDlDHOJCAO6exrr76eliWXkUh0z59aXYVKNwVvP60qaqCbZfFkphLfx0+tQsLVm8QsObF6qPnbjShfasrN3VMu6iIiSeIM0lIbOZOoEk4hdzBljTNHmEM++/pJr69lHYUSm9xMmVp9RZZySrPjtOotg46WdRdObXpz4tTqMbqak5pVJ1afXb2KokwfnyoXlRlKjDirBnRGzidcqinkGLKUcWYDeWhnX3sNNbWsugAyk5gFU6uxCPJNw/NPq65StLOsqCA60/D3U6ufKApOZMacWM10Ix7ThmYD1RhhXNGEUyPOnyGhkROIINcU8gZdzDiTgT68s6+cFFUtSyeIzkTmwtSqJ4qAE/H/06qfVPUsCyiQ0FS8/tRqKJKGU5k3J1FF2XwMZ8YD54XVBxsAr3hxfhofvOHDMNMKhveJmw7Dox9Oo9KI/RkbZPTZVQ/MD9e0KD497jSyfPSP08Bxm1fmvswqUTwkK6sb7BBkoyf38NjujHGNE0uJUoVyUeLQzDCv9zUjJ3cjWhAvjZ/rUXnieObcs/5AG3LqN+OF8cf5VwK4QpE8ev41QVWTqamsKpszEAzb7N89RL6DY3wjxV2iWMGclTg8c6wM+qrRSwMjXiBfnUB1gAoUyT9nXx8M1KEXCGbEUF55AjUCrlEsvz6hKuFOPL9skspuC4FIheDzMoERvo/IETs+08QL79W04Zt1VdFT0aK6MOFH8u0pVR2YYJH9+XSqkL5aFtWIkUAsLz6lKgXVLPY8OKWqZVy1Yl2ljPT9iVYlU6tGTrgKsa0+LKoOB746qSpjOtXFiVUV1tWETRXhwitPqmqYULVwKlXC7VtZiefzuvB5zItUlPxKAacA+PoQ2cLfCfxjxWO6aOE8mD5cs6weFPUYFQSEG9iHp1BNUISK6LfzrypUlRiVBYgc2ltPocogaRXT30+m2rDrtoBwUX+3PCcEecaNy1PpsqAMy4wrCW6HhR4rgH9Op2KI31kBD8IsqwR2V4UBLYQnnk41MIFuCmQc51UBdNdpXPxe1dxsziVwfKN/D1HZPk7gHSfu0gUL5bX0YZphZaAoR64OIMyAfhu/UqCIFM1X514xqAqRqwYQNaSHzr+CIOkUz8fnX0lIknzJUqsvOGg0zH6vQef7Pk2GSHGaJVwwb2YN2xwrDJ2C9CoDwY7gzxOoOIhiRfXh2VceWqXo1QeGHsNzT6ASoeoV1/dnX5FcJ2/PNbmPhfhVZKs3m+0NEgnjLNBgsycBTYI4MZwlWyhnZg3ZDGsRnX7kUgRBDu7J8esQolQxvXfuVYhWJ3IRgmGH99n5VyBUtaJ6/anUHz/na7EZUXwA+JjvH1BtHR/iHTVeEwQL7LyEYZpvtXFUjltqaDED+u1kKgxQpGi+eiK1haQQt7DQo4b00JOpJ2Cd4vn4/CuJIl2JG/G43bQ/WRUTFBJmr1ex+Y5PkiBSjObIFsyROUM2x9pCox+9vICRg3vyBOoMmlQxvXf21YZOJ3rBgWCH99kTqDyIakX1+hOoP/LVthBNWXXbvB8iHu1OU6hkgJmgpWAxG6iSxIrlTPnC+TdzCGdZl+h1ZNQmKIFoHj6FOoUsWWyvnn+9YtCLUbPgFOL58inUL3TVos+G+dcxtcxPSSk+5sWzVQGD4pvnwgCVPwlw3pHiOVmwYA5MHqY5VidD5ehlCYAZ0G8nUIEQRIrmq7OvORSF6MUGhBrSQ0+grqDoFM/HZ19JdE+pisKmioBxjX4uo7F9HOEZJw7ThArlpbRhmWHF0FOMXC2YsAL4Z/wKARMnik/OvTLoK0OuCoxoITxx/pUAqk8cXz6BCqA74mmaVre235cQiQCersG3cHmaFLFiMUu6cO7MGrpZ1g06DRkFBIIexaunUFsQ5YrryfOvNrRaMcoODD+O/55CRUJVLPIMmH2NciuyMq3SV3F4N55ZnxAIGGeBgsueARTucWI4Q7JQPswYqhnWIqp25DoERA3qvfFrD5JM8Tx27jWHRiNyvQHjhvXT+dcYNKUievrp1BbXoijzLNmMrjFwQvgsGNKwnw0EaSLHcLqEwf2cPpRzrkUULfk1CUQiqrdPqFahyBbfw0+mdlE149cwII24fn1CtQ1JuQnMjNnXOneieE6z9p8/iGS9STOrJ+fJZIwzxECBPT/oksSJ/Wz5Qnk5ewhnWN+YdCRXNwQC0Tw8fl3DkCy2V8+9pjHqRa5oKBTi+fL8axmOatFnw5zrGMuGVX6vqm3z1dQ6VCfTnHqKfansllRON6oTD4xaJ0yg/fRUOk97euzorkRJc7QjPNfPtL6Ac9F42v7BsS9l8oi5M8MuLrbvR+7cO9numfR+/TS36k9+l37cBr313rxnf46akaa2GX+S+/BmpQiJi4I8ymuRlEbiD+Q3d6ntTvxeMbKYHlxnqgaSM5cNlImT19HMhYUIMFVh+84rs7S6UJLIENCZO1lkAZeOFCfam6w5zcB+UeNUbzVOVWOIYifH39IqL5t/bEd9tS2r/DnJsrxq8f+tHrjzTbseKP/6fVVsheJaDdFbUe3Ina1f0yYdfNf9ILlA94vGj3QE6qhdGWl0PyJ0Go/TidF5IoLc5IjmuhlRlmn22K2KitYgN+njU6Wji6CMYMnjhjK6rJrRLpKVVo3jrwxCZ4+FaO/ludi0/2ndBSRuwMB8g8hHB4cS372fvWu515HtQxDsI62Q9eboLaEx5YvVU13GNFvBHXtV7SEEjWJdGKX5WmtHGQAlJ+8n66j1t/2JxoPshhJ5v61nRz1FTH4i/44Sa15FLbLmo4xDzatSVIBwGYv8q8g+pdnXy4zEAkQgslMZ3YiXvNBGBBiDpd9lnWWKhwTX6wBI8BOQqPQzKQLf5L+Zwm39E4mEydmO6xe6She/P6W/pIZAPYTCCe8RjPFZBkDJ3SXl19oddIR2P7FCRvPCHxY2GhgWUWO6VoDw+b/Ks/z5rbl6ThuVjr+LBA9NytuGFEEPgHzydb1Kop5vKA4q5a2aQoNIS3IyMMqmrhUe0kcdze4XCoGqrjW6hb+BzhFgTFEoknX7rgOzMNyjjWFdD1hd76ePWVPo2AqiJ0IVqxLtyXxTnJe5tujWAtICsihf6sFLf9H77wCEUbNevYriNRW/gSYDwMew6o/EoVplSmCgMkYwQslKw7URgu7JZOQxtsDXA2RkZkSleSaEYcewV+7zuevR7USxjLW2tOyEPJRLfJlUVEsR5H/ki/GlbLYudSTsxNnVfXxBhoh27L80AzzSIAYKllOYE9Po+HbC9L72+yRexYYvEEADFepT+iDO31YbcVeHzK+1nS9eDUlPD4nv4FSlXM/WwTnrCg7tZo4JmM2mPQH4lGsrVROshS5ldwcElVMPAWdX5v12QNEApNoaYMBpB/vGWpOYVqBDGHyb97J2vu1GnLcb18DukxYQX0LsoMX6S/q59nLtKuIIc9nAkLamP+dV+pCuzOIOYKibeXeirPAtwj4UkbJps/X4M6PcYlSa3MKygz5vLojPC70L64CIdI0L9R4AnZxhYS7/TjwzaVwGKldVqJHrTv4ik+EgZsPIEByC5oHrgfCSq5oQ0VyqouDhefihvjYuq1cuIGS19wvpSBtujiLb/k48v9QUzBuPWkgOA5wwheB5URtwlWx2RLTRvg+CZ72XxnDN9nm2Nma9AQxKVPMwuY6u9ql5GunDM6UAXemxWYyo+vyYlq7uQTmUtPZVED15w7svGIvBPeFa2spt79juuf5rDu1uuul7HVb0fp+UaVmLV9fUWfkgis7p8Biux2MyH+zTUXlr0Zisz/NtHQHeqCx74CMSZLNiKpt8UoMwM2UPlSnCAZeq7wCBEB/K8re8WNfDIuoVaZ0OSm1W1cFRliFdHxW0/OjB4MuOs6pKVk9ibTZIHwIl+JPYvDSdPzpS+99QIn+rQ31vydV28zykj1tz0weGQ2N6nm+2zxmNmQYWZ/Lp4svLuvbcn2qHzou3S8Mpvg4O30NoPoSjtoEYgYls0IYLPRiDNrJaM4NyeIDWYSzeWgSspUIBopElFF46OBpx2zxIRrYRA01LGI4NU3IapuLaCEFIUTiWDWPK+pWARmcNujJ5ldZCo70bKhSRMLri1oBxSWPrPwCczYrIgkGasKFkhORwQAaAsSHUYlBWnlpAInlii4kRmMaGuNQ1wbKYgIteLSCRPG35a4KlMiEvhCF4IjPCklgLSCMvf4dqJN3/zpVGlrjfBV2XTmFE2q8z3ZPKYjD8qglnpH5PRWHI2K6A7xjBlpd1DGpz7u5UsVsiaBeYWkjCnifsWiyvYg2D/QiYVpvalab09Qf5gwbNZ2nfSXTwvX0DkZ5krWL7j4bYtA5fKh2MQ/lM4weXEuy/a5Ik2H1lMhiCH/pjQBgf3SqVNihETLMdaAR05id9xGHDK7Sh5S/nqCY2fm2HK6z7/s69WXVf5IUyqPwBA9Gepk+JcUU13xG7t6bmY2GJCffDJr6Zjxzu97+LtYGZanEGttkudCK6ceh9dgZYn8ElykD0OzslGY/fzan2x5HMBkFxddZWmlcBi+MMQEM7CSL9PVRi5kORgDmO4WpDCbzTy2TgMyYPmGP5DQCna4hltRHGC5TKel0a94rAurAKIUAxDsDTB89+txQYPyHaGuth5xfW7tc1gt8P2reM7qcFR31EhwW43+FbV9z7tJS1kRAam9Hm44RCIwpVWU4Y5JsyRgzcsaZFQA0wVTda9OPbLHDou38vNnn2WN7lULyToPBAdASGIhstqEm0NMYYfIA+2ijyMSZtHiIYZgVhRJ3hBgexgPEQ2h69qscZmYJGWKJiyAS0NFfw6dc7OYdmoAxImTiyVg7moUxOYxjQ3DaeBF16cN/9evj7VZE+ptqCzYYM4Cl8alrXRG6AgHzVQgLNaOFcrYfMdG/Eveln87DxSWGGY1M0Dx9wowY6fnwxdDOO5UUW4wlcxGo4K2BnQcI9rYPdfzycE+5d9RDQtTd7QmaiZTzwyk970wTKcrtbVO6P5156g/RgYPFlUJMhujtdEDv0CGlsYLToCDv0Lqcxm0IGw5WQoEcbRKalXfnKd+84Mws5vuiBcbXI0YVsqOCxpWFKCC1DMFwTQmAhWyVY8Ty8CQmbWQgGVP9CiPq6WnOZE1hhgxz8z0KNAKQJScJj6U2apg7sG3Tyavgj8xjBYKmKzG4Hxgw15+VGV6KDIiiAmjCm1pKDdl3IiAj1YFbE/NAITNUN8z5bm4XPM/fU9HLPzCr31GRCnpj3cPIAbT7SSNy8YZsw2JmCbrxYuYGRFPjZgJEG6JaKMAn7F8oiRjI+rWfWSPeW3mgz6d7G83mGobtV1W7DFEOnbVMiVLANUqLJORz919Skbb4Dx0Zq+23QAZmRe499alG2PwciIFufTsdN3VInpTQKmtkoBGzdODBPDihcPIZxlT2S8GAEjp5I8httx9BHhrQjFAiccN5HOwphHiLSDja8HGYcZNAueQHz6eAJqmrQHBhQRxXOZqZl/RgTEst8IzRBUWKBzzJe4NJ+xxWt6zVwBGXQip5lmtAhDC/kdYAURdASnmeXwMX7gfF10rz4A5pmB0LQooN0YI4dIaAn0qkRpEcuADscoQgaHLP2eGscaYVKYrtLc+8lQVXDqEBmXRRYnVmOzz4AVlEpefaSIcN9Ny5ukB0kXZcOwZlpduRCdBbrWV+9VFdbbXAB4bkadmiOzbYjGtJ4pHLHDIxrSCp2WDYLWursmSKVjg4M1wSpc1hWCVTlKC/N3H8QD8l2U+0eQ9EZB8Uxa4eh6symezEHMCDKAimNdi/9eLAsaW5S0DjKk+braBMHncMqe2Q2wwgcPZEZPtqO0WY9tshBMFg6Isue8UYMvBBSJaBPbvZ89jaFvW+qyi+Q7XbX9lci5fpdVRgD2PYEEbX7qv3n06AtVZi294+R+/zbrTOWGRUMqqpDRJdmVGgHNiPt4ARBoSpLOzCxtGTYc5Iea+yMxAhM1Q07G7E0WaCcO2CqvBuJWmyIQdZvgOjUdkPaEYrrli+7tjZg0TKmHhlLy1rluTwCVYUtd0ZZrcAzlGQU1RYmjFBSd3wZFbWKwFGQUU/b2C9iNd0KYD5eMIEytDMfN4w0m/n4IUQ8tNxwgFDpultuPMgPDTMsHXkD4ihCF2joJt7BczXt0Lwac8ciqu8yc4/tto4WN4Bxg+ejRiTLgKDHZOitJUAycr5BOpyIvEIbmuu9QxymwlzftTNrLM9tp00jAT0QtNDcmdkq6XPqtwwIUVU7Ps4sySsErHK/93QffCnUPr1LqkE7SEal2CJ4qEA7utjh8+5UzPlBdBMm6uhL2efAUAiHpXrM8YewBrqBKqO9GOYV0AAC18i83mEZxry6MZGxUL972YlWtBhhzbqYUHTG6WBh2xjpeYxTO55IuaGBQtVASgqGQQKVDXtuyLaLDgxXAdli4Vgj8HZKc2iSrKruVn/yXAJRQB0hTIPpjiioBUHqfp1LYo1PNwMwVTd88tnZLEIFf9+TAKugBtC0aqePpFOMYTCAMFKiubo9xfDsw/4lETTCsWmYjcElxXwGY08Dv7CFJUSAqAq/wVajpI9Zc5Z9lNBcw1nTsjIZSJI5fnpa1qMJi2a49orrWp4mZWe4TjbbSdmjMW4+yKSiTcqeEKzhczhqVZ3w6n8/W7+mZV6wnhJCUVHzYBSAoemjkgYD5eb52i9VBMIlYDASV2fCBWFObBujkDIJj1RUBjRiBaTHZpdCbAbmcIEMnu2Vdce3YxlX/IFIsE+BuMbL7Y5IuNfCHHxfASgxp1wFaACna0i5GtDWerFme19gwjzvITAmoIzneG73SBtmNTQwo16jap5lfk3Fb8eapF9zHJ5hMnxWb0HHbBobcvBbVQod7GNQCwHATlyFjtm8XkcV/66SgW1nQPz7Sy/jRvlO04jt/CtESFJs24KMa2cobJvCy+iE3paAZOkvr9mTZIhuZ5ABlWADMeRLmyfE/Q3HA4VtJZBx7UyFbR14GR9sq8Dtxx+gJMO3CZkDMEC3NEefSrhhGPDlpRPkWcexb97t+fWeoKJssFpQgczHJYY+mQdToV3wwRAH7QhB/cnVSFI2EZnDyiDJNCqdMnnAR+/CjhV0Oq5wuAPMeuTNFJj2MxIij6tKwWIYzWJMZ9S+lM1+kvSD9ejhlJjmQwnSR7PSUbEYUVyk6Yzsro3LejxN+EyTGciQx26IbzFqJhGmM1acNTYdnWkmzkqbZSVr1gGvwBtM8zrtuomJCCF2/IHpkQfLSMgqKCJCTWiesVs7bMhwnZ/d0MEhYzMb2W0ckYbzVmRlWtXrpKZg/iRexcZ6SAmkmFbEKZKHFiBlMbwEwaYzxJxNNTq6dXrCt9a8ZUZ8g83xUUAvFbO/O7T+3pD/nSHjPDPoKfqn9EGcv6024q5IVl/rJHnx2kyuq5cGLNn0LnCC7p61omM2iA053RDo6cCDYcU7QBmIyNV95fZTvlmLAqvQbUlZG81EMdCYGdnHGzZTfILA2fqbIpNDE4f8RpLzwbPFh86cD5wZ8TxQX89tVcpsz/Ms63rYaHmRg25WnEFFZ2EjOmxuDtcoI4D4LQ3RRn/Eo53bO7qvY5mPiGmlO5LX3Bs7cCZTJSnFr9um/PmUP95306z+E83selSOGbQUaIaXULnG13PVWH+Iuof1PxKW8R4mYWsjy7jvaIQix39ZFHYOMCHb2oKdCxyNQaSccJibVtMBwKbbwUyEMgDGgGHLM4bxmV6vxbNQnunrI00d28Olzna2fw9xLdQfkAhg8CHHGEY31mUs85upWJjFXCr6HxIzb0JtZC6Fx4/Y5Vp0+9HMpYEej2wZLbp2HMpcg5ciHw+RuAW+R/W+eUAd3ZdpgYhbJ+3T9u52YlpyiFFcPQL/5fLnfL3diPNtWeXPrCMKKqpZayIFnWm1qLCNqdx83pyyYy3WXy4/52vi15UELLPWOLL2UpUjVtpgIfeq4DxCGhW7kQaCZyiJ3UszzoShLooa8v2SamYJyYx6TIbKWgIeTKvnozMyKcg4i7qI3+JI3NiHeLCzGBvIlw3MkXqKgMXWF6ml3Bk2cBnVjNTnvEof0lXH09xerzU1Ax0wBZ2K1vgDdMTuDGbBb3VRhDt+j0QyvwTOsMARy4N5JeLev3zTMJf6fogWlDFYekqIXuwo0w/R46SIQFtE4FgMrWlLh3GWDVrbKtzlv9yIVfqSGloziZgMxbUEPBhYzyekkbFCDIJnKIqVX+PMGKroUvhi9RaIwFEQq7JG2i/m5xP3u69Wb7cvL5tU74YwArE5doiHduIyGm8V2gGsuGN5J8rqU85JRDREs+YkfJ11NYigiWmMPM54nQBIxMRQeNoicdOFQQMFT+106QtOnPc9JOb87GnuYf736GusSRiwEW7abKazIoAeAfcfLR7koLotfAblAFO8YUyc2kNQmlbEqcyyVNS5azhaMgNz55LhqGn0HDWcOUEjMNpo0mIeM9kRlKrYAcOpuY5Uwe0JD5eJyVJ0L20nm8OHHQQLanCoSquoTm2qIa8xrk5nx2bd/w035gGSquP+b04NdyAK7O+4tNJ5UonHvEjrASJlVBIeriyEDtnziEeyKcgmQCKR+CM5F8FgqYpk4PG2jJKOdWJT57SEw52Ikto+5rlEHpjx5qEabVbWrDehULVmzXW2TaNM8Y71z0maQec0ADRVOxnJqdl6hGOWQMQQqQJTNSUGRrYFo8RD1l04Vlff8G66YRks6kYs+Dm5CZSqGvhRuLXBwM+9A5TRvbZnw0kqgkFXtY/o2I4D4qGfD90TZzxgj6HgujMerO+h0Cwb4YH6Pmvzw/RaOKpG5oforY1kfnjeW2olvTZvBuZphqZWS4t5T63wq12sV5XoNMw24JLiv2sGjwGbf7yhoT3BRMG2NgftUSYfAxFodjRvEa3a7532l18DPXONYNpTBTYR4GyAS0t7EqEQQQ4k2Ex5T/rpnGHU5e/00IXiQOEaRoXvdsdnAErfa5nR440mUSM0WT00jVobLtga9cCVU+FiSCQ1OVVuD4lsyBiVbp85VOtqIel6QfXuCHNBNa+fRy+IJa8ZnKscZb7aGi74jDXlby0cXRtTFh5lIlOWReKkAytRQxkzenkJWN5bS9TCqD8AxHJwgMSqxAbZzk+5N2ASxPG0YuxWyBzTSihMnY+Y3swqsQD2RjyalNZNRsFjaU7rLRtv4bCdZip/rOEMxmCpirWfjTdm4NMezioOxCBsuTFWcMCU5NAOZUDqBihn5aZHcWiu0J6G1H8KFHUT11z7We4Kh8oUfa60AxrWmYzzY5iwbQ/qZfmEphwdEvX8ToPr9HxQRx87rdEaYfxdQjJVesc7Bc1sDAK29iKhIRpygxCBi897tRT22FVxIAJHT+xyuLF2jNKIoojBDAGsnnlVZ2/T39Q/j4/SaJN+aV9Sw+24g6Oq14E7tdiOZOg2il3K25fkH4v8WeKmncQwBjDLQETtNO5hIHMYph6iU2AgAi3NYDhkjWkJxt6iYVNLnzeWV8zQZPWwjGJvuFBXi/a57v9weB2UYDgVh6ykgurYiCp97Yw+/OzBonc5LzL24cma9tAcW7FPO0RM3LP+WHTXSb/RK3Aqqll7IgWdkbWosK2p3DzGAL0ISPTEkbg6I7HUmW3jRNZ2bhwEIQSCAQJ5tvbxHIeCAXGN+WjDNOoI8H7/u1j34w50HmhCopzjGXDhk0LqIaGJeOhPQfak78TzSz2EjM/8ybgUc8AkYJPLuFTzI/yCnPz3ZCA3Thix2GqT2yhGGzh4U8X9UAwkSijgpCk8xHIUGRSyYEwAB2ecf9qEghEhwGbqczwy5lTnTXGrqc2b0lzDRZzCxs1NPShvjhk3M+2nrWEHEzG+zZ3TRVqlq2RTc6DPVRzJrC2Kq71kuo8E2xPn4PMO7wFz7P5zAJyuIXb7+RjrRZi0RwmwOStB0ubXEcHVjJUoasyDmX6cpcAbjUHLQZgkvQECjiwLcQh+u7MkF/W+HwiBZADqTT9sy0a94+cgBbMqtKwIudUg2YiRqkB6Bciu/uiVH9lIgZJH92Z347DZml7FELDMSuLI2keIBliwIQk8QhoVe0kbgmcoib2oPc6EsTwSzhYQOEM5OFOMtBycLXx9EkS4LUUPSotP6D0p/JAX/IaUA+vrpB4f5BOzPRBJlw7WkWF2xLz7znXy9lwzak4ReOd/NESzriR8nTE1iLBVaZw8hjedAEh6wFB42iJJwolFA6UKHWs4WyAYPC3hnOHEkCEzx47/z/labNhTH8JCVQaQAcsesEhmhXj4d9Ejd9pc18MzlKTNclsThp3fR76kya0FZyhHmta2lgs6oYt0JW7E47ZrDmDMaRIioDUFX2tcFRGxL4mTTxfVCIBNcQSFpy020V1YNNR017BGZjyMwdMSmfcuDBl29uerbSGa8HPbbOeKR05BT0aGDECkoTe3FhkzOZWjVy/WC4FGBRyNrzkaHRxaOfxxl0YSbAmvwaAtwVVEV4t7DWWN+ahDZeOxtRpPSSk+5sUzI0TgWIAPochadx1gIX6K8/AZBobcsfkPwTOUxGb8OBNGmOOSCNjklkFpc0/CcDWdZZK6eYwNwDhzIfc8a+BIWiE3PLOtFONuZ4n54UJkeUu5+VgPtZoZk6i3kYAzy5o5IHvqDYxjU8MPP6pgJBXhRx7ZFgv5ruOdKJ7TrP3HDyJZb9KM0fJNRzbrTaahM64BGbY0naPHhGISAknKFDS+5kiKdmrlQAnbxB5evhOw+BrDy3inxg25nO83ab1PyrSsS4i7IsnKWojuyNnmzm4yJbN57Aji10brKcFDZSnL/8/euzVHjiNrgn+lrJ5212Y7u/rs2Nq0dT8olcpOTWdW6kih6tNPMmYEFKIlg4ziRZWatfnvS/ASQdwdIECCDLxUpYKAw+H4/IKbY7Jc3gKOtDJ6A2iMFJBWdm9nwzR7HvwnmjG9bOtsdahApFT08q3rjIO8WeXJUSAQ7A7R4FOWc70MvLKRnEgaEw0O1egEkSvJDP5rONMwdj9QQlApAemph4lLSGe8oJxMZtn4DGl5HjWJcdLR8juORmh+r0PxZWDThrWN7cuAyIRWbdiq0uPAIGB3dGCXGPQIGEkLdsnBwTDNeAmC5Oo6q+oJ1pu591ERgIpHQUc9LgQBnYFRtTyZBSMZ0fIq4qpmUtDyIpYkP7/XuE0LhHcIW75iVDxUDU+brMnZZzaDARI1MiMw2hPZNCAzSo8kh5PdER/5Qpul19nGvsxmPGSevMhm/hrb6JfYzF9hMxb6/HZOdasFWtWs+4pbLy4kPvGtGDErX1/rwcUZtz/gtCjiKzOaFEykwiOkNx4EBdPB4bIx+Q1rkvqJJ/OwWE0CKiwlJfWgUSR0hkrd+mQmjGZFy2nIKpvKQst1WBwDDxwI0xk9J0JXN7PqFJWpnAndrNJYqYbe7thIjy+B6hmJRXq8ycEwTH/8ScJMPdMZtYnC1jeSCUNmorFg2501yHpSZdeEVjUThiLTpgv5K/NvWs66KeZF9dwosKaJGFTPkSpqmkpf9VzplIGriC+Td841njfVoDLTwMwYLMmfxwLVM+89LjW5yJtGZ3nEXMWZ/gof6HEuSPXJR8H15KyhD5oZC0qKu8avwJMgtzMQWq4Fo7C7TBlFBxQWFCiGye6cF8UfWb67RwUq73E69QKYcA1YU9xNGAH+pXS2pupiOqitqQWtzI6gqKPZYWWWBCtinRO5WmA1wadjSLpG4UORYVK3O9TQFsqLW07cF15x7qsQRLlY9fgdj6rzV40f8KK2xmPRgvLibsmqid8zfmh2HyTSkpKdRmqgN6CZkqAugd581pHRRFaqbfFpyJ9IMMNCqi4MyorFIdculhBHDGK5mstCQ7cM9EpHp0ACmkGZYIqkpUQwBQIJZFrNuUfbGB3jGoHc5UFOKWUHBoVHimJISag9WCDCh5BMBFOW0fYF7XQ2PJV1JF1VVOWKkKijEKWKvkuMkW2r1E5cGtw9lRqaC24itfyEkuMG/QCGSJLS4o6JK/FE1peWC0tCcwphKZDFLQfojAJNmqKZCEH/ROkuG+4wRcl1lj7H+0rnoJ8BFbEI9InxxK2iIh8GAx7mHCQForXqjxCKQgMcD4kvGqM6JKhHYIxAFAcGXY/HxIcHlQz9Fjc3da6zpDrwj97okhghHZLSDMNDMcAZoIZE+13esMlgfb55PO6iEn2KizLL325LdAD6GlhNiWRABLgjwqmpGAVYWy7NFY8Dlb9Q1dHssMov2BHrRPb/c7aNkqt9jpp8eDdJ8z94Ih2d6mIBaFDhSVxYXS52nVZnGQEFrmEVTfqvQLh1ec+OdVWQA6xp1HdFWGNf2BMHMi0n+fYlfkX43/B5GLCmSggqAmKpkzUB8lY25RzdFAcgEyKpo9lhkOEYK9VJrQXdNshQyCrpdhZkHkaLdEabsEFFaW4XFLVhcpATUYl9WBssekWTE+Ga4ELDVgjrGXRew2aMl/QMtoNsX8N+iCuadFzDjlgQ85z2RHPyIq0F7LvmVOVcCxLESduYCsTg+YigvEYndQyCvgjnsADwKYaogk4HdTTdQH5zqPb7qohTVBSauq2opui1vLZQtMNqANkqWnENUaJ5iIILK+j0E6LiY+Q4pZKTDUO0XFxDq48QPR8lxDk0nXqrXkPZ1TUVnVcSEEqZqgkQtLot17ClOYDovqyOZochFmC8WKe0A0zbEFMgraTbWYhBsCDTOcwCectpTM7uMeQUkjKjKhwqCDnA+Bly5VpdQGxBjJI2IRvygpivKcdvSkMHYwhi/fQpWZEVxE5OOnjzW1SD/J8GVLSEZZAJVEVFe8BmzAmq5EbfNppkB9UhM8OQzGf3KD70zZ2EwBiB6Bs3q+MxvynDfxkm1B9BTUtmQKLA8eNS0x5HKE/TahmfK33Tp6ZjQVj6ptDl0M1nGgX86JtIACEbgtI3mU7HbX4TapT40oiOlryMUmCq6WiP26zJMAH86JtHs7SYeoRmGpz5zCDDib4BlJIYJxZ9o2d9ZOY3dHTyKWNLByCkJSs1PeCgKdNrWeBkWrViGNI3d1IS46Sjb/Dsj9B8Jo9lRd/myWmMlIy+1XMwPHPZPc0NVUkVgAA0t1D7KkDxzrRpemoaanN0t0l5dazKbGrbAN8K5ZcG9wuq2doCm0Nbb9MS5c/RVvcwo6qeotOK6kK5EvUAwlW14xqYZPsQXRbX0OoqRKvHCXNK/aZahii5pIpeNyHqPlKSsyj+6aVzzemIsqKq74r6YiETFSFSVrXkHLYkAyD1F1fR6y3IAIyU6KQmgGoaZAMkdTR7CrICY8U5rx3YoMMxiUrtSABKACwMOR2A+IcEtIZB0fJ0GCcY0bMbwqpmUtCzI3YkP49dIVnQsy/iuoYS0LM3lsQ+s/0xtjtj7Y2xndEW9+x2xcCemNsRA/thJNGZ7IWJnRhhH0zsgpk457ED7ZNB17U12mf4NV8tWwCorBSCmoZE5HRliNQBDbqHM8MEzDxIq+n3HGYmrAh5WmvBNg+zGPJ6Br2GWQ47Ep7TgGgvZ0irATuvvZRxrqYh4tmWMQbN61gH/SUMfj0ncpzDDOgsXYhqaPVRR+mNhDiHprenOvCTm+lOc9IAqKrovpqCUNB0VYCwAa25xi7DAsQCSCvp9hliCWzIdkqLwDYOsQryWtr9hVgHK4KdxUoMiD6mse42B7C6ShQgKmLhc6pDBgDWqnOM89gAWQ9VRZP+g6yIRXlPak24DIAsirKmUd9BlsWmsOewMHfRG851+TFHv6N0q5tFAlZbIQwQEeEIcGoDBgDWpmvI87iA2BZVPYPOQyyLPVFPaVe47UPMirKiScchRsWinGc0KV+yHUrM7ImsKkwKEgoqsZ+qwmUua20ieJ9Z0DAg/Eq6fdYwHaNkO4PRGDSuYTEEtbT7q2Erxgl2FiuRx1t0j/ZV+x6brqEA1VYJAkJELHm2NkT4oDadw5vDBchuKOoZdB5kPayJelIbwmsfZEZUFU06DjIm9uQ8j0nJtlXe5O5/wFdI0F57PgOmoJQJkJBkLLgUQOMBbdu9BvA5gZkadV1DQcBMjm3xT2t6BDzAzA+gsqkQYGbIuuxnMUd1716iAn3M8oOuHVJXVUlCSUEsfqoqRO7q1pxDnmYBZGRklXT7DDIrFmQ7qSFhGgdZEGkt7f6CbIYNwc5hJdoLryjXtBCKaoquy2sLhTysBhCwohXX2CWah1gDYQWdfkKswBg5Tqn9ZMMQzRfX0OojRONHCXEeTW8nSHiFtjLYbIHWVwoCREYid0590ADA2nUPah4fMPugqmkkApjFsCn0aW0IlwOYMVFWNes+zLxYlfgcBucBpUVcxq/IIJ0FpK5CEgASQukzdQGSh7TnGu0sDxDDIq+l3W2IQbEj4CkNCad1iBFRVNPvMsR4WJLurEbjrh47/HL4GOMBoAGVipqUejRoGjqjAmh/Mg1geNEyMtLaxuLQMjpWB2IWI8RyoWWM5NXNRaFlnOyOwhzGaoPyQ5w2Xz6gaJfEqe7NfzgFhWTAhITjIqAAGBV42671Q8QJxEBB6hoKAmKc7It/SsMk5AFilkCVTYUAMUkOZD+xOdJfxjVewdVfvIUvlc23ZKuzWmuwUKuzRmsoronUnWjzHj2jHKVbzKdCToOiwA6da1iU0oAoR0j9xbzHItqzVA2kNXI6Y2cmM3ISYxiweTJ1MZ61jJ2wGM9VrMt7IssgZkBuJkD1TPotNyDWxQwzLTYkvUE/SpgBEZQUd49fgSc/XFIuKgEthxBsWlToNVNG0QGFtgLFYFkH//aurYyTzkR10Jmfvv3t3cP2BR2i7oe/vauLbNGxrKKkOXJd9B++RMdjnO6Lc83ul58ejtG25vv6/374+acfhyQt/v7zS1ke//ruXdGQLv50iLd5VmTP5Z+22eFdtMve/eXPf/4f73755d2hpfFuS6js3yhuTy2VWV57Wupr3XTN6cc4L0qsXN+iohb59e7AFPtnXGYF/ruWMDmMfzvJtm+qBcjV7jXmmxNcHKtxXx7/u61zmz7nNYjzaltWOfoTZulqi/OF/6mlRhM7S/Jj3Tl8GKzpJxoMt6hiXfWhnlVE+V2eHVFevnVs3+5qAWRJdUjPf9OIE9fu94RoKsPf4dSwYEg67S9wChigu6qeK+1JOsPf4dRui6sttseUhE6/winh/5JU2l/gFK7qnw41QCgyg581pFRmR5bU+VcNSjUEOZROv8Ipvc92bySV9hcNXqrGEFKs9D/C6fzP7BsN5+4njdFqtJDF8/B3OLWBhac5oz7BaQ6cJ0mR+GBEr3XGNKfcAiz9v72jLBxtT98xBpXybLR91rDeGLhWDTiPINiI8yu7MeTnFjk2hvqmS5Xm7PxrUAGPVKANFO2gnzcVAOCeX80N4se7ZDycLJXzr3BKdy9Zin6tDt/wvGJIjPgAp3dziOKEpNT9pMFTVBR/ZDkl4/OvGo45Smiv3PyiJ2lsNQrWOFGf4DQ/oASVaMdSJD5o0+PS0qPzHFVJiVXhoYzy8i7vJ/80aXE5eGufouLqGLdaS9Inv2hRrPn5jj5lyQ7lAsqcEhpoyLbf0e5rxXFV1Cc4zY+1dqDdVVmiw7Gk+KW/6Uwg/pFk36LkaneIU3oWQXyC03ysYgpk7S/Bl3rkSyVPf+GXD225WUUzBh5Ym6Ib58wbcfEgC1cnsoQJPvvfdNWFrykhiF2M4rnXOavqNpmmjXYn4wPp4ZEFRjGob3qBSPVtIOEs5wQibAmN9co8Sos66Npkt2mB8P3czUuc766zKi3b9MzEWqaytEaQSD4Z/kRLjffdmPo9OkT5d2kDfRGNMSdfAea1ISiiszrdRcTs8vTgg9Z68D5HzSVsvPWRVDva6PJLaCyYMLWvqKkTv8SYFniiF5fSWNGNirj4mOU97ulB4H03wE/zWttrlAiwc/48grYUnXQhHTydiFxnhyNn/sgvYdCTU21pV5hSIbbxKLY5PwRiK5Y5UzSIXWSVfY1VbHuE8bEPb4fRYH8xo1dC2l80bNFpNJkxIr7o7Mcdj0mM8r72Q7xP6fVNURnNKK8l0lTecUI88rN+D9qanH1Oznc4dZlczOVxW/DEcP5Vo/eiXhv1dhAs0jmCWV2UFp17XtGPOk1x+LvGMjub2IcmLChi1MZdEqX/WUV5yew0CMoYt/JvFMmbaAsY0b+toRBHTLTEL6Gvkf27e/RA8L4bUK8ds5By901DKoNcLwxwqG86mz85FiKz+dP/qtHrvB6I+kMdNTCdJj9pSZI3+TKacX2o2vUWjEZqwk590qf5pR7VFwHR/ps+1a/pPmMOODEfNTxGnqPXbBvV5naTUW6D/KSxAfnjGLcMsf6D/qax3tJdiaEHfvi7PrXBBRsaoYIiGh5o8NgG44GobzrSxXW+VEkZNx6Hli/9VcNLV2nKYOv0o8bc/+0m3TGEzr/qSBB1+w/oUKXdv9+jfZXS0hSX07F7RKpx1gAyn7VpN0dlBYRP33S8IpGjlI0dmM9hTu/lnP605HaTNP9r7wvYnueDWtGa/AMpKtcjB9WZ5WxhIZP5LUOd+uQNNlwigkfb5KAiiIyvB7hui+ambfJ29RrFCY5y6Fkz+11rdfdbEu+jWmRvNN3hF52jQcU2j4/tjWfyLNDggw6HNynuFbNYcPpZyw9ldTz/xrif7tdwwGVVfqt7ZbK75mHLKpFUDeyRioCv69If8+wwuB9Ms8P5rDHPySSUmY86K3pdTIn3v54j3po6p8B81u6ef47y3uTYZCOuUyI89qQS/VVjP6C7l4d2ovV4folgHT2yjtRlf1tB/ICoUcgure/GNNpcqnOzqDR+66zNH9XIloEp+QlO8ze8I08bvtOPmrxd1zEsGy1TnzRoxs/o+m2boIcyKitqfZP5OPd2TQ97dtZnMi5X+fYlfkW8NXzqk8aGEp0fkdlZ4hWYb9O76+iHio72iQ/a9LDaFVyC3RdtitgFcwm2H3Qk2OZ5KZkLneQXbQ5Fe4mcz9q0sX7zVInzWR//qCgV9Oki+hp7jbc6M3w0UqS7ZAm94+XNuUv2hHn3M5zWPxBebcZvLhyzgkIH/Q1ONS7eV0Wc1n6/3yUjKfO+61jw9Psm+xDndbSX5W+PeUIbcfb7GOoc5yMoA2/lWFeNqi7L2ZA08cGIXrszU9yk2/yNM/+QlxzT4l2Bql2WZoe4RiKzfQIpP6b1Ns5sJhVZomqaKjym3c/Znt10khTT2eLbvqRYOWpDivLXGEdPH7JthePj9kwJD5vwWnY50edC1+YNSLGWj/hoTJcNzbkFzOkrhw9UwVr7Wm3rtJvHxfd6ql8rGaZANkN/M6B64F7153zW0mwZ05zPGhFYXVMLBaAK1trXalun3bQOfTE5yscNftZYULu7vaJW0ppf9ChgVHzMcpbQ6YMePa1RBVWw1r5W2zrtRmnxR+Ow8YSOHRbOZ70+7Zpr55yr7dQ3DRw2OQhYVoe/axzazeronbnacv7VYKbXL5AL5nvnz2EjalVLrdRs1tb2uJwqZGdcRSFsiodN8WCL1miL7mrfne2sndQZEjU3RKL6bsxQk5GGl2Lw9LPGtk/KuYRy+lFDhdL49wp14mC2bOmP+suV3P115qNGaHM85tkrrVnnX4Pqe6T65KsLdjRf9kgFQPHl1X0NP5qMZ5S/bH/S0JxdziSd6n/TsDsWkqjxdvP0N/CuX+nrga9a0+x6Vo7ifcqQGf6uId0GnV+yXfzM3D2gv00Z4DTZm7flXZM8nXfBivioExB2qdbIF2fI4JBbJFhojyz0OUu+3eM4I07iTGWV36MkS/fFJqPJEB/mOlJxl6PXOKsK1neQX3Ru2ti6vdhvsvLsOP1NY8oaF5wZ5vnX1R3twNWuswO7J8D7PmW0Md7r2F5+cONd3Rytus4Rexnz9GPwfh55v6GxsuUBhzQNvKC8uq/zk7A8GpZHgw0yskH4Ynqe4kRpkre3TAwRQ9jAGgFouDFJt+VWfDF1K7+YClqS5JClv+pTFhI1SDV5x02Ays1pKSmqsSgTl7TV7n7SuYtAwYXmlltAw6Ddf6bsGf4hhGYrNIvv8+w7SvGB2NvUmYmUNmISvOnRc2M6f4uSCn19vn5BOOs/c2CH81lDgaKKPs3d/aRhJPI8y7tsZgg/PEeZCPazwcwfb4N9ff5c/7co8Xi0QBesBogKe6YMrBrco2OWW8t7KW/FWB3gBN3og67R98Dgne4GuzB0J+IjDZyETjBswbABtx+sQ31A0mgTYnJYs8fl9aBr61LsoOvsNMYwW4GYohm98deS7e0yhHX2sM4eJnMgM9+8i5H9YfOxmZqc4csy3JquVrHsm1TRdXizW/C4FksnqI936mNzi6qnZ6hAYWsqbE2FrSlNeku0OwMfdfPjJf4WW3xihaY8bq4mJOLKr9s86hWQzCLVNpJ7oFjNHzwkaoJfef3gUINDDQ51VWZoExXf79GzLQPUkTMwPcKajpY4R4Nzw+QY22jmFsNd/ieiNO3045xLbmV3F59ebjv/rkuNfrHk/Oscx8Wx3ai1UfXAlaRYMGIeGTF6gFxc7MN0R17u45NwY9++5vE+pp9e6X7TPz3V1mRtJu+7BupwUMYgrf9xygDyJqJE1fwwi2VysjERQrGFWbF7/MqvCzPWEB5pxwQ0HAVqnHEUD53wsEJGzwnbX+ZWzPN1VF78wX4NCuuRwt5scUrPN/zWp7XbqmeaKDK5sqqo70ZH2zCgf3QtY4aR913zxIaEOu+7mV5z5wCjgv+r7e9VjHOC01Mz4oMGt8fuxTyKyfPPGrTKF/pARfeT1ukMnIe5RmSNQXp5jPmoQbfaxSUvbT3xQZMem51m8POUJ36CbR5+n2RK6CygOhG3MDlcaGBFU+l/01UIvi6EsMd31aqH25lm4SjdgmJxybjRq09RcY+iXdshkhD1SYvmv/K4RAKixLew8SamGDbeYBSCFR1+d3SSZXC5t5vhu3uGbNjA2CfJ5LSEquIo+6Kd+dtMILjO0ud4b2vEW2oGwyuq6MZBPrxkf/QD/iXbVbTT4H3Xpd6/uCimT5fQa4G8487Sp7+bSecuR8/xD7F0+u+m0hHTp0toHUSrjvgyZStY/CNH23lFDNr4R+3X6JCD+hacnl/27pzd0aLZOxM1s36y+m6M4PjYG480S+X8q0bkifO2/to8EEEFn8MPGsvBFhKyutluCSo+/O7sZsgdN/sLnvrWFh8lNi+NKJoyvE+iTdVXI+FGjR6yKt92MqOp0t/gVL9EcXq6J45xfVWPwD5FO7oJaUHT9ugrz+xXQ8qcLEa87xrSb2I//O/iqrguXin5M191KePFAjHl4Ve9S32dUmW5iHVhIY0eVN9ATcnK6Sz/XO1zhLAZEbyfzS+hcUQ0j9KiBsomu00LtK0N4OYlznfN46f4OVXq7KiytMaOGcM3/QwUv4SOo8NJIL6+4rwA+xfO7hy3AJz++6iIi49Z3kuFJM5+1bCqPUvN1vMr/eYr57OepnQpOWJmPZX5aMBz+0qnSDEkxbQWbnsy19nhiN8xY/SCVwLeQj8/FXWD990wBJOGX8ah4vs3fuxOfTaiTSsq9WkJoWKWoNapY26mDBz5DdsNI6FtuAkq7ezY2tk9xjU+VongcdjzF5cncecFf1lPOmtMXO1e4zoMsQxwkrg5iFV0fJ39jH2PZV1v3IQllQUvqQyiPqs5awbBpJl5kFIIhkFMIxiGYBjo79pnCPp5Dp5NvcboD+uhsqQJo3MEGtTcGI8lrYiON3S3RXvRllmV6n7Vn3HTvRv+rhumi2iyX00psxLkfdeQQh6Xcf2BM8zUJyOaLLvMR42J3tkzC+4X8UuMaYHtgKiMzpru8ZjErG0e/q5PjeWU/KKvGQ94D4KOK6hv+lR5ifXob2ZUOWBjvhpQRodjEpVingffzalLeCdKaNiN2ki/RAX6mOVMEiX6mxlVji1ivuocF83wAj92s/gCWon2jEUSFBnVBqcTokJG7dwlUfpvFNEHEXgFjOn/ZxXlJXPWQVDGqJXb2szFbCpYfgmjXbQ6yjrtv0h30tiCWueszscy2VY4n01pn/cWFa1QBTViqeYYy9fnq6LIto3kW1L3KOkegyYDLXVxnVtyONqS7QjzSxi3wOxm874bU1e+qQMpr9P6tjpU2KbvBrcmr7OC3o+UlDNq7euxu1UpbYsqZdYSvncpb+VcQmOu07N2jw5V2v37PdpXKbvZqiqrE4E3N3GbWR0b2ZPfzKjyonr6qzbljzn6varRybpRzndz6kLeqRK6++iCG67MRw18nq/a/usl5l6dJb4aUb6nfST9zYjqv1GSZH8ICfefjWj/o3am9GVu5quGT694F7rPv+poO66BjW3KHJmiv5lRZbHLftU424LyQ5w23fyAol0Sp4wjERQZ1QbbCWEhjWgmz9FrtsWXvuhMZ9Qnfd7ZDX3yi95y500NVdlqJ/3dnDoraH4JkxauuKaO/qo1v+Qd3zI6s3Xz4xi32svySH9bwlKyaDLhfoVZ0LLdhWdwI27Wo8XnRGi68pI2WmQ1VlV2hQczJFAhp6Lu8U+0Zxf1CtJusO7qDihR9bxCIKE/LGTYDqssnM/6tG8L/ntQvO+XoX1uzwKCG7Sjf+Hsn7uzfzbOQixZUXo00SuVzv0U2aBdR6Wi7UhRuvbYHdPEIOLCKbMlno/zWWOWmEkoMx9XqQjD3BTuzsHIWhmdUiOchgFQPd1soygOftePt+TRlkms1e5+4OSH+a5GwCb7LUriXd3dO5THGXerRFJ4RLuf42d0/bbl7zXxy+mOBu90EflFY4dudF7FzjqVIqTwvptSF1hGo+eWyLp8RIrKwFux9Spvk9LrOuJFbtQnTZp8wVKfDGZr4qmg5vyPPMskTTukKGqlTVb+ysL60ntfFXFae0DeOSRRmXGtiCfWbCn9ltpEoJ+yZMdOYkRlxrUi7g9bSr+lf36+ueVe1+YWMKKPf1C0MCyiqfXnp+GzbXXgxJziUqYtMccjOJ9NaXMOd3ALzHuQflCRhSfz0fBA/QXcnPV/P0lEFeegEJ2/pr+ZURWd2xWVGdcKCwpxKUMpcc/S80toxNEoxQeVXhGebH5Gr/WUlZevQlhKY36Qb18whYrOMUp80LlbgD1XPYPoTTJ9yYD5rBH1xMX3eszwrBiHpz0JwS6VurSdlhm/oSysYYXi9Psm+xDntenNcp6N4xUwps+6QM53HerdFJKXs5/5aGOvsjhnHVGdf5VWs7VvqkhoI6kB5+AfCB/MS+6q/JgVFDzob3Cqn7KCc8bk/KuGhNARpbvia3p6R7YQikZeVMMKpdvs0Ei3OSdJbtEKI1doJY2Iqir3mS4f4Ep+r7Z2DqRdsppm6ZXfpK11WCh1N4uytZnMOeebBj9rBI/pjhM19j9eynbAhEkJTdu3BV2P0hdeytEm92nglq19Jxc/jbKxzdnSLQhlRyen+pYZMsMPBvQ4i5Pkp4sB6fDHaYD6WODlKl6ztgCr08KCj/yJV9iNjuItG8j4Afl79DwNhOnGbAFXTdcNXJvNEpJA95MWDRaQpx8vBYaPOKqawaQKWrVnUcENBIO6DiQ7P4AMb9PaObNwDDkcQ3anMexu1TRaI2nXluZoNeFoebBjoebzxAR/t5AssUL0nXaWNnm0/V7/fvOK7N1M5FM3QBKUkBu8NI3h/lI26fyzxi7HdlvlOd4Xeiy31F4G+UlniRoLij2lN/xdlxqnu4PfdW+/R8nwNJ0w7YmysH67LRJr8xI/x8xj7YIy+q0Mz6FJT4QDyrvy3zNZmIeyGPbwOkvTNhm2LSMjbMDAzmjQcmNq3Fwn6PrBOUzV/6xzbD3dvuRZz8gHdCxf6OPrvBIaLVTf8FvC39AmezzucPoVij7n+7yHAUNWXanKN4x+zqw90yqib0HhxaRc7VQLDA5z60dW0OgwPA9XnM9GtNnogfmoczx4v4nZixLdj0HzPdb8esTbnFwu1Z9oxIrTl9JzYwhOlocmQ3zQOfPcpgxsHhpnZwbMV41bZsw6j+76DmsddE3CB4QjgCMn7dfwQzANPpmGIiMDWITLxdaywFBa3NF/G28PxJQcrTb0uinWWoHGzjSyQ2Fh0rZGlKZrMJRqEm7GcLyNvMZ5oLOcumhw/tWb0X+8bR+hv66KMjt0srYFAS5xAxwA6SxpTt92iLpK0/0WHJ9HCtJjDu0e41+znTXzOKB7i+kaqIWahBuNaHHKXGs7/apxVARRFrL5QWexm5Mp4PRj0COP9KjJd5qV8XO8tetjKLom7kVJwtcwY8j1l1oaOJchSZBXwIw+O+1jv5pRvkfb+BhzdlyFhTQCsRyx71Ccfpw7Crgtt93V2Sf2KBPxyfAgE4cs/dXG7QKmGUXRYJk9sszdZa8NKkqcqYFnAQ1tM4AywDyDqPhqoW+LJvtF8nb1GsVJxLxoz/uuQ/3rtyTeRyUz1SS/zLc0d1twY7TBzxrHcvI4y2P69v75V42td2ZxRncZNdil4XendsnimtSA5AhDtKiVqGCAggEKBsjwbO8psY3bxPf28tybprUXPkV7a/gM7dhrGjMf5+5W6u3tb6kJg8ZZTST4oeCHgh9amR8apsu0bJAaoiNskaC+GzNk58qTnetXLbr5mA9rXL6qUD3M1jWopjlGgbjV3ejPp6jA17XaDpCEqE9aNP9Vux0kIEp8CwGHmGIIOGAUgrUcfnez8rZ7jbfNEXLbj+iwlE3W4QBE3NjPtmWaxvlXnflxP6/mRSPsV/2ZN58u+c3G3h+vHXVpjTMeFq6T481j9Ipy3sWG4Rctis1uNJfi4Euwax7ZNTksbdk4eSsG9k6XYIgdQ+wYYsdgY2feNLE71x5SHbVFEmxmsJnBZgab6afNtLvET5AdZzXDMn9Y5l+AIg3fuGffDHKRfI1tZmyuNQhFN4pnL7XaXNeUh6KzeXKQIWxyM1lNI5zeCMFdCO5W5ZP6PYqPeZuW5M2mWeISNzBNQDrBPAXzFMzTqszTeYq3QYcjfobNpn3iUx81E5UTChYqWKhgodZqodxYptEWKViiYImCJdKjt0RLdF2Palz3q6NsLRUQSdYkE5CKQjBGwRgFY7QqY9Q+RoCTSqY7q3kbKbomeRuVJII9CvYo2KNV2aO76A03hleQba9yc0gbWCUQlWCYgmEKhmmNhql5o86BVTrRNTdJEhLBHgV7FOzRuuxRHm/RPdpXifWc+xzSJlYJQiUYpmCYgmFamWHKtrVBwQ0+4AuGaG93Fscnb2SggJSCkQpGKhipdRmpKt++RAX6mOUHq9aJomtilpQkgj0K9ijYo1XZow3KD3HaPZUc7ZI4tXqAUkDewDqBKQUjFYxUMFKrMlJkBpn3UREXdZCyyaO0eEZ5u13vJv0Nv63RSXCgZIMtC7Ys2LIV2zL81+BxepemjNvUaEsGpBoMWTBkwZCt2JBdZ1Va5m8uDRjRxGjDpaAWDFYwWMFgrdhgDfOQfn1FeYEzRNVFJsijSjRnNaGqgrIbo0Y2SkKX/GRA8x4dovy7gGr/UUPloxyxD/Wef/UUrKceu/SvVCOjgamkF3xs8LHBx67Kx95FRfFHlu/uUYFq8/x7hQpr+SB5tI0OLUPIuLFNn6Lihazf/gKnsIlp69b+oqFHVrJTBl0afneiSw9F1vKE8Hd7bzmSdN9MMgGqKLjRHnwjM0+jhPUE5BdvRvCqLKPtC9rZjdpIqiYP0CgIOJomWM5Eyz5dMvxdg1ojBYav06+6lDh8DX4PFtYj/fyEkuMG/bAWofT0DHRSXNWNNm7ikp7WdD/BafwTUWF988N885agMcPvTjTmnyjdZV/zfZTG/6tZcYqS6yx9jvdVbvWhNVU7BhqmT9LdctlrjP7g+AniC5zib3idhdah048anA2EwygQ9S1opW9a2bLrThs59E21EETKjfbdYbUo2BXg888a62TpDv2gmGl/0vGhcqtEMwop7w8uP988HndRiT7Vss3yt9sSHaxhkkPbBI8gMm6waM1uBwvLINUykpsF/6t9jppbezdJ8z+bB+WFDRhgWoOWG2BbD+zdBCbWJv63xdUWP4xB75f0vwZV9k6V8+1LPTT437zoY5QSy0mD9VdFJqhuUN0LVt0NKkqH6isjr6nCclJBjYMaX7IaW4+hz3TH6m2ImIO+Bn3tlOJ9VcRprWXWFXZI2FRj5TSCygaVvUSVdfTeA4+2qeLO9PJD0N2gu37r7pQpD8ANmmq5T8kPguoH1V+S6ju5WKtqx46iz3LFNuh30O8l6bfTDCDQ9uzo+6y5QILeB71fkt47utKrbsmOrs90uTdoedDyJWn5MDnDfZbYXYOTN2VHz9VUg6IHRb9URbe+rN4THaO8YSE9qGlQ015Nb9MS5c/R1v5JE4KyqcIqiAStDVp7kVpbXmdpHXZuS+tRM0naWG8VVILiBsW9bMXdoMMxiUoHfpfbxHhFllMLCh0U+sIV2qEi21HgoLhBcYPinvTi4a0o0eEaPzCa5fbyxwGpw/VXTSnocNDhS9ZhB5PgM+GxihsmwEFlg8r2KtuOMk5Imu6sR8w0cVPVVdMJ6hvU9yLVd4CaxzS2v/zMa8BYjUG0gioHVb5EVb6L3nCLH3P0O0q39q9AcuibKjKIVNDjoMcXrMdfsh1KXCnxifhIDZbQCeob1Pci1TePt+ge7aukAY59DWbpGysxhFTQ46DHl6nH2bbWP9zqA74QgPYOQmp+G+b6DCQXdDro9EXqdJVvX6ICfczyg31lpogba7GSTlDfoL6XqL7t5TyUW1fdIWFTtZXTCCobVPYyVbadW+Kl3srJsjS3BXMlBhEL2hy0+RK1+QGlRYwHx8llYIa6qRYDCAUNDhp80Rp8h/IiSx1d6xe2Mlqj1QSDZgfNvkTN3qD8EKcNaD6gaJfEqf2riII2TLUaTC7odNDpC9PpO5TumnxU0a45ZNG+2mlLm/nUDfQYSsiNBj9kVb5FNI3zr3BK1zmq+d5dlSSpwc8atNqbXdRj8edfvcGYi/XUkUupc6yi4v+S9dtfdCxr48+St6vXKE6ibwljY9nvOtS/fkvifVQymCK/zOf5boubFPeKHobzz3Bad3mc5TUcSFLnX+GUHquYYqj9Jfg5j2yQ8wmpzbno7NPQYKqCqQqmaiZTtUE/SltWCdMyMED8am5szW9RUlHGofspoHIOVF4VRbaNmxUEBprv8+w7Sj/H6ffb5pprnuK9zGeUo3SLntqvp7+/5vE+ro0mBKdGhGnUSolwFGpHjZABE0+bKN8jnoqBNIVPkzfueNRO/LrpSjufNe2KHtN/e8eFmS4Sma4V9+iY5eWT6LMOGg2Iu0GkNiMjh/IuqsmUXRvWwKjfi3G6NQsg26E+ZZ+1ZRKVBGXAO1XWNIGKRpdg+lRdWJ/JO/UYC8CmuaMJ20WcVuNLNG9UDxZl2jZR8b3u1lOfmQgIK041Zp7RFulLAODCEB0JhhO98ThgWRs5yh0dTd6sD/djEe2hkZOormLgm2IGo9+S9xcCHX92cNAQ8wMMX4/l16ocA4meAgQYvxgjo23Fd3x0XC4YJcNjB084b2y0Ldu1axhGZPVphAzLAoAhJj1S4CQtC+CQcDplnFp/qxvfxZiNn26LX6sk+fvPz1FSIL3+jwbVQ1mQx1lQA4InAgCwvRkQJWYXhl8LgDlIeyPRR5CyAD4Qy4uaLpEG8UsUp31CaxhoZPVpqJyTZetGNOJWxkp7hD/Q5VQTymdhMTwPv3CWvgX0BoxJxeDGEKokbRnJg6eAW5AZoJmlwSJ6UL42AjgPnhmm6bZG4npAzjHEGcZ1YT4GlsKdLio3ocwv9N/tQ15jECyjn7hz8xm9ogQ4MwWQkeoAW0NbEziN+m3keQwvauXq7FCexO+vggEEoyWODcTVQFCCND8aT6dGrIAJxPI4RIkpTz0JlnAy+JTlQLxpkKMhJ66qOXkG8zASd9YGcWQvZpuTuQTfbVognK/pOqtqbYxR8VA1Z2I2WXOsMI/S4hnlo0EJbQYOVvJLS/etvZEyDrswVpeKaWDvbJpdYmw8gv7XV5QX8f6lbDkbb3lZgqZwpiiNAzTN1lKhy/TDJkgp4h7BtFZQyxECh6JZkADZe9HhY6nIZDuyylhBf8cXTAyOP901VCALi4XeSraSH4qsPdXcbS3gP4D7N9yazH4NWQqyT8OhO1LCDQkb+zE81ha1/zK8YvmkewJXWFm6RgcYcgHh0YvSA6oWRl/E5ZSLb/C1YGnfx5sN0WU4bVDBKIkQBrEngAZGIk3chA2jA+HfRwzqSEUNyP6GTHPCIU5RThc5XcHpfjn9XfQ/YBDVBqnJVVCc6z1sX9AhakRTHKNtc4pihz7GedEsDn+LCtQW+fmnWh6v8Q7ldeca+DVA/tPD78l1Etc+4VzgSx1HPqOint5/R+nff/7Ln3/5y88/XSVxVGC5JM8///TjkKTFX7dVUWaHKE2zsun6339+KcvjX9+9K5oWiz8d4m2eFdlz+adtdngX7bJ3Na3/ePfLL+/Q7vCOrt6RBVH58//oqRTFjtg3GVxr6wBztXuNa/X4iW7vr7fpDv34+8//30//m8Te3/6JGND0YKph+5MId397R1f8Gwe7mLO//xyn/ab/P1CNB5yr4S4q8YFfXKqLN37C8MSXO08QfScl3yeBpJo5U6ltmJJIcyvYvDpG265KauiOIHJO7dKS+BaX2sLA/+3rp68RTk2d/x+H6Mf/qcvMVW2DDh9wcpCOGk4UUsYH9BesMmgbFw32/19tSZXZ0QXZGjwOyL7Pdm9WxNmtHVqh9T+zb2esjxvkxkhIsA/CHHU1dEBIbHxwzb/+dPtfT0Tl//bT17w2xX/96c+1ddJlg7hPCkaBLunzlVK9fnJIaPR2eBkVYPaxNqzEup87pKneGtT1R7OvNwqw7vUGYgNWozZsZ8Fa0y6ErD9U4kUH6mFo12Lwb0/4h7P8/1s9l3hM49+rutBH3D4ekC/Rj88o3Zcvf//5lz//2Qgzdtn8RZNNiNLcvWQp+rU6fOuX3kY64ptDFCejutxQOPX5L8M+b2oGLIzMXVQUf2S5OvIAUXuIEnU0BEYMtseFg/jvA0pQrXnuKI+J9T+g56hKSgyChzLKy5qd/kq8DUx+ioqrY9yu+QjYBJKpufuOPmVJDU0pOdhwZ9vvaPe1chHuf6x1CKcPLNHhWBZjYuLb4h9J9i1KrnaH/qK6WXfbxEJt/apR6Lix/s+x3GV1BuK/2hVvTIVvt3vjEGKXhcYu4h2ve7xJfwlhjdnwaE/COEtPWWIweWhrWZg6mGqe9/OWMN8fr/1r0e2x/k9yIsC6VzScuPxXM2Fp295kX/M9f+7SMjXAWDvNkU9u/mIQ6dPZzzWQL+kLR8BDu0C0ye2VZi9w4Mk/EGa2Q9Ad+Nxk/cHQzUuc706nQ0eQxjwOrgI96VsblsKYsIqido8OUf7dypyCOjtokfJpZ9XAH5+rjpHabXG1z1HznO91lm6Taod2I0DBErsqLU932CYsDsj7qIiLj1ne643+uLAUxgzPCXpNCqHXKBkxNgwti3K7LU7Ur7PDkVglGMPqiZhNdQ6zQE9mgec7YiHua8OQ050561He/K5mJXGmheMOWckIwszqEikTrGz4H49JjdWe7EO8Ty1tFjSBbUu9oTpqEZmk5GBN1YEAbgsL/XbV30HgzEsioGsvJMRGRUOrmOL14NUX7LnmGCniiWU9EcQh9ANeVED7N31WuEQscXWXROl/VlFenjXPIIakCP4bRZao3dY4jiM6wNWT2YmGAlA6tmqD6li5pqk/mCyFMSN5olbzMIKXpvYoRFXYWBeonowdDABO1B7FR5Ny1oCDrt6oschrnG2jpA5mDYZiWHkkIsiVBVvb0VW7Jop1e9RSVkfnS429FxuEvqb7bHCs1sTV3+Y5es22uMYmsyy3mx/H+HQt2XYo0T+4aX20OS956mOaS2RUTNKsCeIE0OnOYDmBqD2Gj5bGlyop4yZGGHMO4L5K03Gnwt+/3TRvNo5ZAzqibkcYHaq0+/d7tK9Sy7i6i96wS/6Yo9/rSa5JLEQTGOctGmLN5RFjVrra4+KxeIvua3Fz7ymAoh2KwCgtC2t23q3ZnRbib5Lmf7hQYf3kBq8V/nU3zgqhLKOVerFQ2LTmsWs+mVHHCKie6TFEVHZynp/T40s40sNf4RyeYP3v+uuNvMcSR4SVxBOJY45xDl5GtLI6dnob0Zyp86uI5rHP6CX8LsHMAg8vhgNMUAPX3srt73BegmmTq4UmsY95dqBS4OoNM0NgFKQ32ShmqOrjzmb214L7F4NMVt8ZEqOO1Vg29PdWj/s3MscTr8ps6kbXHyOpPrMA2rG7cuBYkUPD73lTcBrwmdMwW80F+AxXy75Oly+tbc43UXs74uzuPACXZLau5rocQRJ+o/Ev+lcFf8Pnr1iLb8Z2R8z4CiZY2tf1NMnslIlQ2j1Jt8zHz+j6bZugh5q7qtCzXzzOSXoK3ifbg+fspo9aAqGVC77+weqQAQNXNbbiV2S2zUlUHuPhiTT9ZrxwSIzhaP6TZp1wP1TCpQANKtghFFY8QkcQh5sj2OpTrpVv5wQoBjdcO2bMD6MwBCyMGDa5ZoaFIWCBmw0qytEckURGHdXvzNY1Pm2T4dsb5qZvSGMMTzhabi6TjAB0G3Emd1V+zAo7sVdcvK+KOEVF0Z9hGMEffrR3k32I66ASr5o+5kzOA7P4kCFrMXTh0taIYf67dgxzrNuKKszgmAMUAypdFvCbdJu/EcsPJivDLN27AlW7LM0OcQ3hkWc1WOptxoBmDp8ldkl/zvbpSH43aPuSYq2ojTfKX5up0odsW+G9mvaoJh+MRjiHNGZpLQjlgzZGoJCi5GCWSbfgSviKdqy0kcfF96uiqPF+QKMcwYnQwU1evFqXrPF6XxPSHEAzUw5oyKldV7RvBUG1PBBux06Y/eHu9mrMGb66Ogbfxyy3jD9MeQqlV7RjpY0oLf5o/CaeM1qQ+K7JLORA6dMm35WEQ9i17KzWy8FdVvPp5HnrasTkbfQmNWlpwl718rYdxAsDhseUqOoOjo5Rc/SV7HJYM9vhoI9HB328M3jhfKqWkbmrg5Nsdwm7rE0WRRdphm9S3ZumML1rdK0bJkv3lpkXtc1WRW2c4Lk6HvPsdZx5C+GTP6c2iEdzL8Cc6K5c2MlKbGd/bZcP0rTaT21stvxptCUr34WFnb55ZfIW6Iyjbj8/1jCM9ymnVbOxbBbQv2S7Lh7TEF89vSVrg88/THRAfPj64+Pj7QerU+/m3aJtede8hTTqdZnbosvVTJrAgVuD5Hi7+Xj1+Hnz9PX+H3aPoQQXOf0KA4EtgyUGur6T62n94tr6XfV7lGTpvthkBlnqzlVtHEmwFMbX+vIaZ1VhbUllztQL/QkEswCArD3qAH9cjF5vWdmRLkzlOjsMtwBHwcxst89pZjOTtzJGRzatMXh6rGzvJ9hehPQ3ulxHiqvrHDlIABJivhliPvnrqoYHjTm0HMSCQx96CfEg3w2F6+fr2ZUK18/XbHzBhg3nespTnMC7v0u7DgN2W27HZDXZqrOaaG8mGTFC1rfBywg2LDyrcMd/icTsxQchsTFcbuIyYXyf2co/rVuW1jYe7z9boaMfWnti3sMBBmDUmmffUYpvcNymazX0v0VJhb4+X78g/CyirTOp11F1vj9kEnzd5HmWd9mr0XW2ExHTugWJz1B8ff5c/7co8ai2CLIN7xY1p0a/5vE+NrHQIjqjgrZ2OfUeHbPcxLNT1V1EVG23GXUr2kZXonVWXIeRHTvlolmJJIP9CvbLK/t1W55V7AJWumxpnHmak/86ZzVRJV/Rn/Nxsn9pbytdpbuOQ96GgIun1kfkLLOUq8xa7h4be6ajNgXlo7fQ7cEBsO2vT4YtQvgWoRxdYbMwrFfbf+/5PvvjEiITrvOGL9kCHCHMGhong7KTAwpTsWKGgxr7pcZhLz3spYe99LCXvhqjNvC5Nz9eYsW9hrnMGyR4WHg6zaAUHilFrxLB3Qd3H9x9cPersWybqPheW6JLsGmj9aGTFakQnLVnbc5ar2LB9mAOaykbLbN3deWaTngA7UUQsaUdm5Xvalt2ucQsL5u2hK2/ODvnRTxsO2ubMv5deyGhEPeuxDvQQ3sJbqI9IqENPEpSTy0Zuztd/XmslvY4f8bjWZx8wTArQRPvG27ad3XljnZcqhXeLMfskHQ0SASxNzhdNKs/GLV/Sg+TjQmC1ZQiCmSHOYya9IK81H28f7kIN2U2IjbSqmbndRuTZQlvHkw6Z/MxC3/p+uFdwnWYk5stfgbj7aHMUXQReXPasPIuesMJQD5mBiPDUhh1yS8dyw9LwVaAZDhTBk+RYWsSv1cxThwgfNQDZoiPKI9GEylfqNOAmgSutlv8XlKc7mtFzJmlK7OzitUuLjlvB+qyhsn4/KRm8Br+eA1axUMgOkUgapB/q6llYYXSVOPC8uh6db6ZGq1f5T9FxT2Kdu2h/jHb3DWhf+VxicZTCgcWwoGFcGDhAmwukU2mW3tp/m3f8NKPKLxT1+FNFbWNtz+PN0w584XFfO1l7riuMTYtkYSUgywm11n6HO99DA7cH6V9eMn+6EX8JdtV49xoS+26y21lhx6Zh2k8d21fa4v8HP+w19fx9GrOqiO+dt+KDf8oTImvR/Aftc8Nixprc7ZEgvqVTF6sbfrjMbBGrHn15dfmbU47JxKsPdgi2a/zTbFDSjX4xcU7btpBPK2vfQNKVqzs6oEAyKd7jRqXhz83/ReDLBPebJc/ZFW+RS2WDFJdE7VHMfIlis8JjbCOXNXDtE/RzlJmSpK+pcw6BFFudk6bsGRb03gTXf8cXRvu4n8XV8V18WpFYt2UrI4rLRIdiDDLbTNcfXNJ/ra42ucI4R3tOizbJtUOaWvhh7v7p1Nlqyc9N3mUFjXgNtltWqBtlaPNS5zvrvHebh6jwoRTFU2rHWCFe37d3NK+b5sz7usrTjWzf3Gxs/w+KuLiY5b3ktO3MVj093dPNCGn5uMkkebYxmuUmICFIWIVHU3GlvOCkZlcay4pOtOItV0Wt26OTvSvs8MxQaWZOeLRsTp0/eqJ5f4TkbpmPAR35FQrdg/nD2m7DpSZxpxCf9Aa14o76ddVqeiTi6yyEk6zBLWR8aHJttlP7DxcgrY967NzMsTK46hWDsfY4uRjXcVM0TumegLGygvbS3Z0GcaCnpVoixu/2r3GdYR/CbpkbYVz4veXV/6M9hTLQn4s6obdGq20ksNJSrBOwToF6xSskxfW6XyOAU/4X2P0x9o2m8JWjZ3tt5qHHirjNtiAy+pNihfiWI0Oh211u5O1nrilHa12YsQS1ZtYXfOeE7SwDkVSN4YMxaRz4FzncRlvo8RIxwaVcUXbIh2QN1dBikXNeb9+DqdB6Gp8J5tHw6pk2QashaD4JF0Sm3jwc02rfe3JGkNoSMDpem+v9Q/4HICdA1Q9SUzK5BD2sL519R7QNh4cikXn6n1qDR2OSR1wjRJqR8OpcLs2bAh4yK5zQd9VmMcCfcxyg+zFw9ouxDukb+7raSbdCzXP8AEEvJXygDdY0N7A63OIOBEx24y5pAUsTynwuyRK/40ivUeHSN57Eq7kjOn/ZxXlpebTSCyXHRVXjN7WEUscGezV82hYP2Jx3sk8Hcexd2BheMnHEdXzmTVb9Nsj4V+fr4oi2zZCb9u4R0kzUtoHq5QE7c5k29mx/OSj4XtkBGFb5zQpsq5eP7/abqtDhWOC3SCby3VWlLrjKaZkeyBP7Xw9doljRvJL0HHHLc5QM5bTnobll+t6AdyjQ5V2/36P9lUqOBsIWUaU0jQ8oQJbxmmyLTULuiZLTOfaTmKiAf0RS0wUk+6joC6FVY5qqunWJOY8U8AEHMr2xORY+RK8OhdxeypXlusJtIdCEHGoZoPcVv96ic/8Gie4amndUxGhOaV/oyTBbxtaIfaPOgoclZ/sQ0XmONM7sdtVdntMtzmdiwONdGewazSo7UK3h/SN9Zph0rlSb1B+iNOGnw8o2iVxarDexCHiQsScZowlLWLZucBv8xy9ZltcgftYAmS9bEjCoQXtZWR4Nngg5RFnfqGnmG9qUzhua5+kYP0E+ZC8MWx5hJwfEG8bvBrl9UkqDrHAXPnS4xR66WsMizc/jnHrL80lStKwxqyNQyqC9alLOGUnvmQwLnuImK6FTZcnPHJPH465+6MAPh5YlyCZWLO8BPzKMmqNIXVe7rWC1ZacJH0dD64GodqwC9YUrWPeua6dXlctPsQFrjYubeEyNDdc5TI9KdCCE1PRmbY5vNtFs4apOGFt1GUvLpfOo3J7B9AXote9JpNbcBeh111fTY749TXt6gx+PHJU4k2GgF3+Ntko7qjqdnnzU92GiWLDlYMLuXJwzkVk4PikkDFK+WVy/7lngxvgWukD3YKLMx/4YZd8V8/rN9lvURLv6qG9Q3mcOeiOokHHvfscP6Prty2VhdZNx05t2e1Ty4rJRRhIL4bU7fJt7SWdzhGVzq3HKTdw06DziJrs16j0nSQpxeQbQvA3nPbHaGcWIuiOuttdBPw6xHVkPM+C9INow31vaKC46E3fxiTrRIarczq67HrBjrxzNCohP6RbguacXqQiOuVQmwStOtetnrn3VRGnqChMbhXpQJJsx+rA8ZqYwGHSzbk9zdA12jziVnzKkp3+Up9W51CSpftikzkaq2E/phirvjuT6dU/P9/c2swTO6CLf7CZNxB7wNNx8g/ZtjoQSycWids6E09SNUxeDA8PJkxcPCI/CKQ7ggwiNo7lnSk71GeqlalSNmrn04Rhy+9Umvp9mHJITE/KOUqPGc5Hub7NgTPImyYMgSCZpG9VGYekTXMq6HbBUQYGXhMOzYuoOadWhkACLxmP7lomSvEduFeET5d9Rq8osRi9XdVVMemKeunSzmrvmbjdWUAT+cfpvo82HWwo0E3YXee9j4vvNSBx29ie9Y2MOSoIWi1QNOs2HY6kcWuRfZx+32Qf4tpR4FdpXUYubEtub3WR7dkTWLf/0l5PchClkg04vGE/PG5bnB+9cPHUCudwr0Wb3G6nJ3dVfswKV/glG3G6cv0pK4i7KfbA1VO2a5w/oCNKd8XXtHl54zmqR9zyCN+m2+zQgKi5jU3mXLDc1teq3GdO2hp3cqULDtod7Ys6Alpbwtz4kg5clE9xu1ndt+ZytnqT7ibrUNeWy+4s5vgX5OHDC9Ao8H0e+2iUNW3ZLdm+XGSxr3rhr8mzkcrX4tZwnpozIKcY5KIU+tRrG/rLirDD86AVu6p6ImxRM9W9GLWytS6/+FjgDYXBh8vSHsn1QDPslTxR9vgjW7OsSRbu9o3rUdCqk9A2UfG9Bv5F6VJzEMMS6Gj5dasYTRM6i4e6wU536MN5L5wvgi5GUx5xgBzcjyX3I5Tmkj2QVqeCEzqvAYe745r3S8MzoCqmpnkGlHsbHHZKBtec4l6N/+rPnoa4KBPQd7+uexLAeAcrEWp/5Y0psf4L16et600ebb/X3vrm9ULyYzUdxfR1h+NU8enrdlvlOd6cfToL9vxdih7yRreK6InUL3JSwzsDWnT/okHXEq+YvastXmivlTB+jjUo87jVv4Z/auKx3Bqe7NSGAkAmLoZMh+5/OICCgtcRUOBxO3qjF7cyvINYNfTjxtRg7mTgGA/Mcy/dmybAwLjAjQ7d/8cBHhW8jsAjj1t909TkXo6S4XVHzmsWCljYGliF+zIeWHf+wV4S7SghWTDI1mLaCZ54TDsxHCOj9DNOAKADrCmH3s7MX9UuOEZ/KIvhmF1naYq2vuYcg+SpG5csSIJf/ftHWJK6yXqam0dtRdsZXdLtS571nfuAjuXLiPMfD9W3YpvH39AmezziwLbQ7ec9+r2Kc9vdDA+8613Cc/nAO21bmhY/Z3sfTYv9M6oCu2qQC01MahRGB6kvzGB0GlDA0q9xLhCTqcqJMYqOXVNT09/E50VzzXuwbWWXh3CDKfTXFNagbN/GuhR7eFLJEWZmJBD7pxtxRiHTBRiJUe4HFJD8STepHWdrjrleAqJkYkzFPbVvUj8gHFQe2/mHjQ4HG+itDTyZknUYuJN1GWVZHnFGhzMhvnZt6ghq5Nz2aVxuYL1pNhghwyYaU7UOaNiz39f4vfUsf4MvTIBl/3j7JdtVCbpukNANwiVEJiMyZj/SGbOfuFJ8an+zqswtSf3DMIYs/8JjeeSNJPe+WTOn0bJd84hF4H7o0e7x9tdstxa722JYX+h9vVEhX913Ozb/JpXlVQ6atihNa96yzHA0uL0YB2sv/BlK7gsq2tfqbBNm5sgjaN3XmD7GOgfz9A3NdY6i0r5KefOQyG257V+eMnlca1B5jPEijn0aMULWH8OL+Bq5AWNSYmO4DEsv/iy9dFkJNqgocUbdxvNckHOB5cRrcg0nb1evUdzUGxNx3RZfvyXxPioHk3UTOjoLorBeWggn7/I4y+NSYxWCcwxGukwW4tuFxre9oWliqMsMbWV3dIG2I1gibywRZD2tEYrklWbT9foQQHkTQJ2fanX8Vno5zLH9DlKBuiuubSFL06zew0eBR8/xRj1lKry/PpaVSSd62vc6u92gGBUrdqchmJfwtQgX6p3bC8G8np1p7kpcQjjv4m7GhIkpjGNMUz2z0ngIcP0IcDtdz5KLmLl/igqcoqG9PjfGf9aE/lX7TzSeUlhMWHskFBYTgq2t617tXuNtc+emeVPgQo4BtL3WH5S+3shZfb9SYRZq0fVtbCWbcjKs7WYb2YwzFb0xvHJTfxm5CnwkA72ifOwxj5pOc7JjLJ1gmP0xzHIEX4KRDmFxCItDWBys77x7bMHaBmsroxasbbC2wdpas7Zheyds74TtnZVr++BUCpuLeiUKrkzu7elZoFFZFYaDGc7dhig1RKkhSl2N3+o3yD7mbc6ht2DhgoULFi5YuNVYuPM8fIMOx9rahctTwcQFExdM3CpNXDBtwbQF0xZM21pM23WNsrhWytObSsG2BdsWbFuwbSuwbe0Y43TE6S4Yt2DcgnELxm01xu0uejvUoMD7CmFbIdi3YN+CfVujfWueoA/GLRi3YNyCcVuPccvjLbpH+yoZZOwO9i3Yt2Dfgn1bhX3LtlWOcAD3gC8go32YoAYbF2xcsHErsnEVRnqBPmb5IRi3YNyCcQvGbTXGbYPyQ5w2THxA0S6J03CqN9i4YOOCjVuPjSOTZb2PirioY7lNHqXFM8q/Hi8lr2EweMHgBYN3cQYP/3WPimOWFnFNOti7YO+CvQv2brX27jqr0jJ/C3Yu2Llg54KdW62dG2Z9/vqK8gJnx6uLXMTiHdVhu4g4Eb9Hhyj/bsXS3EV53VV9aPX13OPp1OvgOYPnDJ4zeM4Vec67qCj+yPLdPSpQbdXrYS0uIpHsp6h4saLum/hg3c3OluU2aKY/mvlQZC3xDuHr0Dt8FzlPo2S0cxoSsuqdsNANEsV21Zw8NlaW0fYF7S4n/DRKev3ISXr9lyEgPuKmja1ie/rDhB9c58TRf1jhqEGCgXy6eidufrHHjZF8TjX5KjxuxMID1PM/QP0JJccN+lGuxDBt4vI8ax0VN9YdsULH9vQ1qI4vqvNPlO6yr/k+SuP/1XASJddZ+hzvq3ZpcyUqhVfXXmP0h7b/UMnnaUjZrn/5DS8IAhQOJoBBFwxUjqjt+ewvqL6G6l9nSXVI16jyd1h9ikF8P27BFQ/SmIVSlSHRR4maoot54j8/3zwesa58qoWb5W+3JTqsBDGUwbW2nxUsnicWr9kZutrnqLkye5M0/2tCgnUA2HqY7k3QwFuK0N7Ku9ri94/G7JgFVfZLlWto1yOK/72iwCVocdDiC9TiDSrKoMlBk4MmL16TQ0QdNDho8BI1+H1VxCkqiqDCQYWDCi9ShemnZoIWBy0OWrw0LQZlzQiqHVQ7qPayVZu8Lx00Omh00OhlazQ/40vQ7KDZQbOXrdn0He2g00Gng04vW6eHeTzusySslwWlDkq9TKUOy91BfYP6LlJ9b9NaYZ6jbTg7EnQ46PBCdbi8ztI6kt6WIY4OShyUeOFKvEGHY1LLIHjkoMxBmZeuzEGJgxIHJV6mEj+8FSU6XOP3fbM8RkVQ5KDIQZEXq8hhdhxUOKjwIlW4hQXOCpvuQkgd1Dio8TLVeICwxzQO69VBlYMqL1OV76I3nMTrY45+R+k23HgMmhw0edGa/CXboSSocVDjoMbLVOM83qJ7tK+ShpmgyUGTgyYvVJOzbZU3OXIf8AUJtA/hddDmoM0L1eYKY7tAH7P8ENQ4qHFQ40WqcXtdEeVBhYMKBxVeqAq3U2O8Zl2FReugy0GXF6vLDygtYjym4f5x0OOgx8vX4zuUF/gltaDPQZ+DPi9WnzcoP8Rpw8gHFO2SOA33GIM2B21enDbfoXTXZOmKds3JkPbt0ZXo8UNW5VukL/q+nt33pq9zhLt1VXLABmDqVF3B1Sjcdpfg3nQl1tcbIzEwZle4Rov/a8XV3BaNf07erl6juKk3xk7fFl+/JfE+KgeQMKFj26HeFjcpLr0bw9RdHmd5M1jmzvCxik88VA3a4wYCzzHKg1dcrFdc+3Q1mJtgboK58cbcbNAPKTv/eyVm57coqezYHffY1UXYssE7JhzfRMX3Gnb99XsTrHYknljM8qHdNcUpr8b4sC09eZ5rjgINwb0eB4OqE4zkYxHtjSIew+Fs2lv2mJ67YDawff2pRveXMLxrGd5zMjJxAn6jOOJEFzrg4vaNRp9iQFfyg8qjMKDolR5bUmIu0CFucLi9YIIPrdHmUhhyYIQQL4dGvg1kwBhVfVqUWH5UbzxoJAytGUOqbo/hk6Y2J8IsPAVlG2MUS5eDMl7Hx3DK0pvP4xkFwMHlXYLLGz0NHg+T8VOnheDEwwnWVVFk27hpUCnLe2zN2PU2CjQ36e4nnArr7z83H7suPqDk+U/tD1+qpIyPSbyt2fn7z78wQjsTULBC0FaWJZv9v5hmazyiHC/5Rkk9mcKV62FhwRun2/gYJcMuUoWA69R4TE7k6C8f0BGleAEaKAkIC9y1VpanU9OUDqrk87d3AyzJIXa1z1FzI/wmaf6Hh3FKXPHaJwjyCywaQdwurQA2g22OABgvAEPtWvkDlY7+lsl9bnmgZUg6r9TxKBVEW8qyTvBlPO4j8abqLYQF7rDPBsLBqmz/Ee0AnePCRYAMFnt0T7+mH1CCSvQTPtCMd2mvo2Ib7dhgtY4Qd8uF7UAkCwGrasthCoh2ueS7u8QDmPKFOQaZf/7Tn2SGkeSEoEl/WgF+qC6BbBszNvN513z7Er8ifCiugY3l0F2BFKp10j3T35YdglG9WVi4TqHEWqQe8DECHz5E5+Q6lrQXdsZVgRhyDZR0Z8QXNwG3wSiODl+Ei76CBikm5zYqdyiPs92TbPF69PA6CqWJHvBA3H9xFObojrwdG9V1ChYXD1j0BGhTLk7Oio+JXZcGLLwLbDpoTLMAGUDhfTTz9B4lWbovNpkQB8QptOEokh+MgheuY3MECPFpOjfA4J+6F7R1GobZEPG+KuIUFcXU+1rDdglC5IdF2wiiK8vyGwQsJnEbARDe+wwh92OH0UO3oT1Uk7qNAXPzmYg8+47Sz3H6/bZ5IDJPcabPbtvyqf16+vtrHu9j8XoJU58Yas5XDbsiY5NEqLykE5ix7UxhhqQdBQGQN7rzbQ217DBdKe7RMcvLJ9FnsRGT0uOARlx0knUavzGuENEiAH8X4W53eJoN533Si6kPNjLO/PzjouMxfhIRQVv+BOcnGEwSmAcAeBSMY4bvsz+eBOlvDEdNEX93jTKkmt+cjL/WuFjAAO4JpKmer9nH/7a8TWs/9hzR/ouYNp3LkDOnwe96dmBaFAg7OT8QBqzNjoWJg4FpMTClJ4AOvl+RAIbAZIFAGHwfooCB9bn58RJ/i0t3DsHJhJXtgIih02dAxLIEJ8Ppmnmr80BtQn8zGicLM0SG6PDHIXHgMolvCkBZjvPqGZ96JYtomIQH+WXZyCD6sjDrQUBjGsMRQOGvpTgbuSdAdGs2ksq9Z+2Y2dbqh/aY2fMpsOZmXwEZAmSeqHRiTMwQXizNhQwgMXXkGcDgpeuQ5FMcedzVT1xMf+JVEx/U41uzn146Q0Xv1JLlDRYuS8LTG87Dj2nXxfhdgzS8xLNIpx7iDq/zHNL8CPbr3JEeqP04b8Qe65z2+vzIY6ELi7PMTqb5E3qzaJnqGn3AyVKi8u4qgcZZXK3D/4vAidZR/1mwInnOZWqksBsdlqDi6872AkFmuqfdsTg/yCZdO5z+LtN0C0UamPMnbjnBYKL1wgAA/wKSudYIw314/5YGT6BoJ+GTRKZ9NUxjSjRMGyYM+ghfBJkfB4Lg2d7EwsP7z7NcTNVC44mz2QByesuuY7t4esiqXAKTrgIxjqff4G6Dft+TQ84xOnqmJ8CE9DFTQZPS9xDnQcYmyvfIki/xGxtTuhQTcMy/uEGjo8keN5PxYBMN8guszIzAcwp6Z0s6tAANinYiyWUgZ/LckMb4Ub0YNSuKvh7Lr1U5o+X5RQmgX9Zpe5jl5yUZnw42fpig+SDkhRGCAckzKzThgruG8VrYaquOBfNntb2HwCSL7WHwfVlpH67sto8zfkDPUT0C9YfHlN4xFCy6NwVFC+/tR62FNYYpIXGXDzMynZgAPmzfII2SQ+YRlib0J35gZiozY4YTf7wNC5VJ/E4AyVK9ksuNYLqHVg4c+QG1qXeVDSHnw/YyizkbDmvF2PLbjM175Hqbpdnh7aHMUdSfqr2L3vDjjh8zMaZsP4hIsEFufZNfVvAcItkjSIP0sHiClubMZkCLb2ihh8UTtEw4w5oXH5Pd4tBGhj+zKhIak8yoAij4Dfowi6LgIF3Ommhlb164zLGiZwAdiksvZkSYkbmW87RBuDDrYgRLf7wOA5PJl/ICQDz1QCw0lryMNxfM5lzEg8PNtzW8Bm+KqyJTbWLi6swVkhnQM8c25qnvkHZnvlpC8z3rLuaJASlq1rbCe+7TsqOg4a6mG/szkcebF4I+WC/dvVMvMThxOB4MFwQpfkXnuE+zOrv6VzlkmgLrQgzu0sI9HYbNPEYmAEbcrpcWpnFIbBdsj/FFhkZgZMzm9HB9z7C4iDM+82PN/4DKK5dYwypO966mfeqMFM11ou44uOKaF1lqNXM4cR91FzNn3YI73cqp3Xgd902zh+vIok1/6WcO4GldAOKP6uxoU6yaawykYo28q8UsjbuFxfRXjJey/l3Xf473U64BtC0SJPqfFh3IdJ1Y1sy+G/5J5vJh4L2YofdDvrR9eTB87L1IO/U2vAaS5oRPGW3LO5QXWTrxKwRE2zQShl+Wbk8GfVmcPxmgY6pXBwIu+A364G6GNvRJ0oXxw9mAxPnKHNgNWkKX/qhbXJADtUfq/GxA+xCVUc3cFhV4Je4e7WNMDhe4R9HuS7ZDyUzJpQGcEe2Ayq/i3CGkp7qrdz4CsO5sXT7ep/gq2RmOilWXCXDjyGBqiwLaD1FtJ9pgC53ulEQgjgWsO0GsdZsM8aZur3wzUBIgptZpySX9XZBdHyJgbqiWqDGIV7vXuMjyKVdMuQzwcEaXWPR0h9+nZU2H+dCZZLU1gEYPNN7NlbldET+5ZjRrcTdl9gt+M81rTGDoy0zmHhXHmkz8bdoTwlTTDGKIb4s3VcPeLM+zDSEymU8L4BA16aUHG3RiUb5rXpjN4a+04eaDpzrnkPr6ivLXGP1xXj4gJ6FX+xwhvEwie7VWSEyQb4tTbqK3bYUcCHoN7YCwuuMUYKwkJ4C9gRwWsLQm0wny9Yh1a4Ls1QtopQtDvf47Gh5jfZ7NPgeqs9CgYizMvQ8wyO2XlVpTwH6dRq0LsafL3ZeT4R20H6edo3b6wGBmOzxt3tuxyuHFFpwUlU3Eco+SpsJKrXDPAdlZKN90rQuxwoLuL8IKD+8ydv25yrcv8Su6Q3mc7cCHiLikRMiXF50I/OCuq5kX1XP/COFMOgCXwVK1AHJw6BJUQvcokjmRS1IWWweXfNSc5j2D52iLLktR2G6rGefVuSQ14PR/qah/LPBe+eDDZaFf3H11B2R1L0kbJHJYqlZ0WQouSxfoTqvZZmtcEu6Z3i8V7fPvCTnSnsXuDI3GuyebQ1xvUc8cLjjeEPQeEm4Iq16S1RWLYbHm1+z64yr0AbZ/qlPxknRh0RupnP48oLSIy/gV4XWmz+gVJZelDZL+q3sgrXxJWiETxFI1Q/uUgfhsn+QA35KDdP2zefNH6V4cHiDuJMz02MHUWWPmyN68sBtMn+NndP22TdAmj7bf65j75rUm/PT1iMtECfEGfdvqFpbDXu8Gixg/fAYJ6qIiq1glEHQOtDogG0RfEddkPi8+ZckO5e7WqQLoXINONI6+4c55ukbfsDSVUxyBIW/eNJj+eZ8QInl0j/ehLIZ81+TSNpfBlAG0kAmCqqTUonEj7tey4mwxlCYxMQFE5iDy2xLNs5drGU/LTVY+Dloe4alAv1d4Xvg52z+1JrP+lxBQdP1TBdnwnwtNsvYu7aMCp0RJJ7ZPKJxZQDvsL4SBE7ueInjW+MwnmM3nYnUh5XOsNoTWTPFaAJUJqHyM3XrTOaeRGuWvF44iLU/rr1U6wWgWgxQAtAYLNMh25nwdPCDIEEHUIPmHIuAc2N3ywUQTSB8Q62K9YRoYCzn3BdC3uLV6rJb3KKCgIzKcnsusYsNa1DuQhx5u+Pmx7YjHDYS8pqAIfe3HZexDMnxPABptkGLGZgPI4+2XbFcl6Loqyuzg6CSfIobj8kAS5ZdYdPTG79OyZo8972j3ePtrtrOezV71NiXVPPmeIfNx0XBhurNwpEz2fmnAiLxNH5YSGHQ8xkrjaN+BuHpc2wcEGjubOSDZ8upZTDSVwQrRkB5AfbBfAsQsbcbvF/Smnu2PgKAPJ9Gxpv6alfFzvG3hB32m0uWDnypbR/HM2E7y4wpf82Q6CWlTzPJ8K00M/ESpmMfks14AnKbNQG0En9ty27HoFVyEV3DH301eBG4mvoJsCJ0Blx6hZ8JDLz6AZbKo3AQj/iw6MTAZ/nGPahHEzOUxfai4CcUDyuRtcofSH6RNcnwqgETRphdrAww0Frcs4AHMJl8MMIKbD+sAZIjWvVDwUB3rEZHYIhf5DgyD9YUiRj+Ap8ZmNsR0fGxQUX7O3ATUCqRwOCBIcr8v2p3xerSs4JqHmqn2SAJelhQBcT2SuCd2x9lrH2U6qnO5qiGjsxueeqimXPcZNMvFIOdY5EJtDPSgpH++CENikpl3AMNyHI3s1LPBOC7CoUx01tnckcx55pmESb9dJ3vd1EFC3L5ZGXFBmXXsP4m6B2p6WMMTHN38KFGOUxT2uTCtA6qxPE5W+BjeCaY4X9cBQbZj2uCbbw+UYqP7S4k5LtzmyFs/uW/ki8gNssxN2ux4uo5KtM/yGBUTLwKyDHAhMvy86Aib06Flzbo4iJlqATBgZaGTMlVHbI6x1zM1wxGdzS+d+fQESRMu/c2LlKltDBwb/rmiFhpfakKAw8cujpKGib29ib0vc6tJVpSDjeE36F8Is7jzW+EcjgJi/h3bEqxI2Vuy8zo2NlyXmxo3J/a8Ac0gpdv0t2jIN+3y/WMal4oWyFJrCX9EHYRh6jSGnsVCzdNLc8y6moa5GOq+rCIiavuyzFlXC402UrO/i+o0JpoHXLMZJTjK2tH0CloTzcCCvfFvBia+8Y57NaVXknMCTJPQFl00jhSdW5YjU8BrEvMTgGUNWD5YrKvda7xtUmvfo6S9HaHTsymAobpkwfSAPCLJ+TxxghYwIMaehWW7CmlVzviMoVW/Pj+17yRbFuyJrMCEUZ1Zli+k4DFR6B2AIWjRB182YHv6RSCiaRE0VjExI3uzWKsBWwzSzPzlbBlobnhNmy/MBGSzrwTxkDWdQwpmZxE+STqFMo4vLsDsTDWJMgGaL/OkBmE2Ap2VoslnYzXvxWR2FUimeTZM1kJWdaY3QIarOCSjfiGp28qR42hQiLuptWQMiQTgGYLObPqEnymTZ3iCmMmyJ5ghxZ/5PAcu0yTWCEBZzgzsbNZmORem49EWBgttz+aP5SBgMe3BnQAITy0EZIXGYAxXfTZwunUZ3QNhM6/KDDHl8ZpMOEzo3WoMeaL+AaVFXMavCJ/T+IxeUeIgBZRTcDVtst0Q88cru64DzqJu6szTZ75wQXA/dZZLpnHyuXf266LtF9ufZcXaLFQmCbgDSJYVf3P8nTQr5sjx9fpuqdloTn63lGHTExDVSlcbvWlTZYZrpVIxaIc287mrdrLQLyh/zLODUIreBt9kJ0jLSH1aB/KoXkFapEbWF8BNGUfPjJLJgiN9cHgUPpPwmCZ2DsAQtehDyExBov/HbVqi/DmS5GO5Lc9lSBc1+F0rTp4bKUPO/QQMMzq+wGaTOY1rfMaM/8EJMTizIaZn+WOOfq9Qun2beqWPywBBUVBi0W6J36dlhS186EwSvQTQ6IHGw5CmMXunngihYmmkPY9xzEd2er9FDpwH1yKe+o9oN/Cosk1U3y79+ZOjd55LgCYJek915wfgBh2OtQJN/hQgnwMBfsgii/aDgk4tK3oSwGfia6UBOAuKoAYuj+4HwNmNHGpwwnmgU7XusrSH1BqYQK3RbHqAohn9lQSDqzIzi/ZLc/ijAIxl+B3ZuR3j4fTfx3juW+Y8oHOdx2W8jZJa8lM7FqppghbzbdEWhO7NsnwLDZFJnEsAh6xJ3/zLuQtCRIwYUE8djMnATexhzhz6ABNxStPi6SGrcukBCe3VXwhgxBwJmpFVWNXirqSjeiu9c749AkLeJsr3SJxsEAgRODDWgsyxCPEEqeLqXiCXec3K4g5Z41fp/lnZJBv5vtgibafhu2Pl1idDOfWj7fNEbtOvHC1syjdAxHSPsgcseDbD+3rEf2C7lu6mXhyi2yafQWU+LhoaTHeWZSwYmEyyQBQAomjTSwsCCGNHDaynTsZoACd2OAMWvYg7HD6U7SFAJn/nWg8cfjxyfQLHXYRpWp4JQ1CB6cy1jTktLpqeQlpsx2I2VNxFb4eaEj7aPMdFGE7zBD3u90UHI7weLStg5UFmkpg1gGVJwSsPJoD41cIgexrFmg7mtLEKxeXc8PmS7VAyk1c6tc0D4ODjGkzMuTuLdEZnmEzpiQJARG165IDO0IB7H7OB9dvv6A3gLE6nYXE+vOTxFt2jfdXeC53c6bDNk+jjfV+2ZeH0aGHehwOZaRxQAMuSPBEHJhBnNH6QfXVJhoM5sVciuZwRPtm2yhH2kA/4cBXaT79Ox2eBgqOgzMLtDr9XS3NUfAhN5KwCePTB44PjGu4ZsV2ROC5rA+6tAzMf2KmdGMPpfFaoyrcvUYE+Zvlhcg9GtU2Ckfm4bLNDd2dhzoqGyTReKgBE3qZvDmnQB7EnGjOovvoek8Gb2OkMWPQBK4JLBfauXHgKFcNLFZNi5cSbH0ApjjW1+FuChoe28IMPoFN0TUHRSbr24yKAI++8J7gRDpUPSHqojvWgSAIWo8OXC4CLh1Dpx2I2XGxQfojT5pcPKNolcTp53kQBCwRNYZlFB7uiXi1rUiSC0CRzowAeE/D4MGESwQaw/WRx0D11W2MGd1oPxuF0NkiRV/7fR0Vc1PO8TR6lRd1GexFmSs8G4UeSbEJUYdFmC9TFZTlAEOwm8YYBcA4B54PTlCQ1oXsGTHzjCCQKv2o9+Y6T3DcjwGIVtNq5b2jGfcTr4FOWK/OETZqtSUxFuDQCrbLCjE3ArkM48TFnExzCioRj1u80Lw2mU6/FWQTmsIIvUMR/DZeaZ57ecNmRgE9QfkWxJr+HS57b8CE3w9QmgM0e2Dyf11AdA0aITgCyikmNOVDmndNQfPvihQmsaiT1tA6WtSf9nHeSY3Zexf/5zHVWpWX+NnfwSLAhUQCq3Ir8N9mzJQeJJKRmCA4DmPTB5HkQeJsWCF8WaDsWo+KhahjeZLhSv8y5kEVEM7SCKazQ+5pJYh0e2kQPtJ4+sGIuL1UT9BG3EFUgvbiPujBtSntfJ/fzBBjaDsDXYJVA1FQp8QOWRmHJ81h1ngTHvmLKn02/5SRElmxcvqK8iPcv+HEkpMqW7GC642R9Edhd6EY2WeeiZkNk3yGszJzkmezKif25lyMpRiTIY0quyDHTfVtynEdDa4ZlyQAqU1D5HvCRXVrKWUZTdGrQuCjnOx7nC1uMZHCvtexozRxeMvpNULYYBaB9to86MHf+Dl+n/bPk9xjpGebP9yEB2kP1jTpuvowoY7ig8ovBWfRfLiyKIPu+wqiBBbKf9yZ8Aqs/i6j68PTx6gQJyLeiRIfHItqjpRjV23LANLBlqs5FGVWy72s0qkMMK+ypGDxSiKwLqfqI8AicRA0f8XiPZ23dGuuKt6eabgKZ7cpelNlt+wxf4fUcyZPuFviIs/nPh+giytc9gxZWuLcG5rGpBkRDUzSYSYhEFwJsTMlbRNvYpL9QdC7JuM66sY8bn/jwL67CvBDPo7MwLJz6BWnLn6MdDQKmOqwbxt6TaOouKoo/snx3jwpU3qPfK1SU0769zLZPvcLKK7BokHC7tCxjwYXNRG8xB8CAAeOthfE2pA3w0oDXrCboocja9jG58s1fSFGMEmww3xYNJLo3UAzNt9p+tXuNt+ihJjbx5OfcMEFp+POioTDoyLIimwEippoMBSx4F7S0LD+JOGcGjzNwE3mXucDTddI/+LTF50bOhFNoMAIXaTYW6T6mmQqHgffJV9yjbYyOcU1TnJ/QD2eBx/EeJeypEN7nVTgPoktLcCJlGW1f0M7NlV1VOEo0TgKE+rRsM0N2ZmF+hkTIZFOVgA1Riz64ok8oOW7Qj6kXM/pmCTrnHxeNhFM3lmUfTkiYyjIEDHhkB/5ZN5AN74g0JJ/jfTVLmjcVOwR9deFFY0nZvWXZGSXSprI/AWOWMbYIO+YyLZyTGfgSYDr1xUg7cPUhx5wSr7/FzTsb11lSHVLxOpFTlLhEdtsxBa/cQk6QbAVZNtDN6zKkbVUH5kP655vH4y4q0ae4KLP87bZEh6mjSg4LJNi4BZbt2XldWljEyEPOZFFiwAwcMz5EgJ+zbZRc7XOEDjXBm6T5Hx6viY2NkA+CsKTUoiEk7teybI8YTVMZoIAjcxz5bY8WNxX1E4pTTz5HQtKHWWfbhXz7Er8i/O8ZVnh5LHBgRBdYgTGjurREf0ghZ1pXGDCzMN9Ho2WZbs8P2M3j7Ezg55uf26Ci9MDXDdkQgogstBr7RXRruX6PQNIcvi9gaMF+kETPkn2hLzCc0yfqw9E7vzjX0ui5eTF41rKANejOgv3e9MueASPCNr3zawte0pwXZrP6r+WtW76vijhFRTGX4xq2z8KF/Lp8s0T0Z4G+i0DLpM4r4ETaqDf+i0TIIh3Y7FCbxYXpQ84bH3adx2Vc/78eg7ncGMUCCxumwPKNFN2lBfozGjmTurSAmYX5NgYti3RvnsBuFidnBD9v/ByZ5fp9VMTFxyzf5FFa1A3NkqwAzBcLM1it5Zs8UD8X6DtBaJzUoQYcToJDb/wxDIGLdNKLg/Is7twepD318ddZlZb5my+unWBHBT+q8NoMKNm9xftvEmkzuu2AsTEY89Q3U+hagUv2DqYeOGADuHrqd/Ff96g41pRxvgBf/C+XLRX4BJXWZiv53Vy8X+YjcUb/HDDoAoOe+m0B+lbgv72HsQf+fAScPfXrX19RXuAXIX3x6BRDKgAyxddmQekOLt5/04ib0XMHrI3Hmqd+mkHZCjy0l3D1wCsbwdZTf0y/Vz6/Q5a+SQ8qvzYzafSmvNc+mUHdjE454M0K3jz1yyzSVuCYPYWsB67ZDLpe+ea5DlD3bfPBtJbjr6e+LNSHTn9IOuDCb1+34IPQs0FrNk+1wDPPt2mJ8udoO1tmBYIBFirU5+WbIrJDC/RTJGImdVYBK6pWvfFdFEoW6cA8gNssrswAdv74s7ImU1PZlnOtd5IccGBDfV+BmSJ7tESfRqJmWqcW8LIkv0YhZZmOzQfIzePaDKDnoW/boMMxicr5Jm1cTmRAIsutyYYRPVu07yNQNZMPDHjSxpOHvpFE0sJ9pEeQnNln6kPTR985v89UAWl9Nm35vnFOnxjwImrWR9+3Cp83H+Tm9nFL9G0Pb0WJDte1a95neYyKefwbzQUPQGyZNdgtpleL9HUMiib2dwE/WvjxyPexyFmo//MIgjP5QTMoeucL59v9O7cvBs96dnIG/Vmwz5tj1y/gRNKod75t0bt9M0NtVk+2wH2+9uLhzY+ybm2u1UqaBxY4bInl2ymmTwv0aQx6JvVrATfLW7tkEbNIP+cN9Gbxd2YQ9MfnDTh5TOPZTm/y+OAAiVtqBXaM168l+kAemqb1gwFHJjjyxydyEbRMv+gbFOfxj8aQ9MZH3kVvh5rcxxz9jtLtbG9xcdhg4cQttHzDxuvWAv0jD0mTuseAIX0MeeMbuehZpGv0DIazOEZjOPrmF79kO5TM7BRPPAhhNCixGlN27tNyfeEZPXM4woCb5fm/AWKW7Pzmh96cbk8Tgv74vDzeonu0r5Lm02xuj2WDAyNeoRUYMU63luj/OEia1gUGDGljyB9fyEPPMt2hXzCcxymawtEjv5htqxxh1/6AM4Wi/XzrpHxWeKASFFyDfeN3bZF+ko+siX1lwNQYTHnkNwVoWqjv9BKWM/nQEfD0x49W+fYlKtDHLD/M5kApHjhwYkqswLzRfVqir6TRM62TDLhZnj9kELNMR+gL9OZxfUYQ9Mbntc9LoHwufzdsnwUO+XX59orozwJ9HIGWSf1bwIm0UW98GomQRfqz2aE2ix/Th5xHPqxdusXHeao5T4lyGeFBiFtsDWaL17FF+jkeoiZ2eAFLZljyyBdyUbRQp+gdHGdyk8aw9MZfPqC0iMv4Fc35OB/DBAsmTpHl2zW2Uwv0jyyCJvWNATuLfNuPg5pF+kKP4DeLDzSEoX/+7w7lRZbO+0itkBkJqNiiK7JtTOeW7B8ZhM3jJwO2RmDLP//JomrZftQ/eM7rV81g6o1/3aD8EKfNzx9QtEvidLZHmwSssAATFly+9RN1bYF+VYSsSb1qwNQ4THnjT4VoWqQ39RSWs3jSUfD0wY/OeCxHuP29okMWCz6HM8sRnIAJT/0YgYaO7pZ+wGDsQCrA0WedfiyiPUmU+uIEHtrDNhImZJ8gDRJjNBtSfFn0hE3617octZZVzvkXOAOOlr2iKUYQwI1ZHnygg+P6NkdAGjfAlnwcqC3xQM6GrQ36Udp2azKA4PYIAu0Pi7YwTReW5ZSaYbfmf8KAC9qZ3nvc1HXKN/xaaV0D5R0f19kOfYzzosQG51tUIGa8ca0HVHblr3av8ba2/e3vg8HrPzxsX9Ah+vvPu29ZPbjRt2RQiYECj3ZtCksh/fajuA38vVA21CKVaaL9mUe8/aKgiiWI73CjoojTfTthyZthuI/3L7w+KWvweFFUUvdeTECLRV3u1Iyd39LlMDL8yGt4+B3cztU+R82F+5uk+R9ur5A2Lqgh50hQSaUOnFo8xeAW46qIERvda1jdkV0OA3QBXtNkGQgUiEUPzoAQ3/niJ4qohJ1vX+rwCy98C7rJlOCKmCwEGOS2Qh32xdlO3Gr/XdJmX0TRILmzwrRHfuY1R5YAjqNkCOWjp2zhfVUbmtraCLSD/MxraVhCPV74nZw8xUfYu/kVp01OGV7DTDF16+/z7DtKP8fp99sUwomiPFccsipQDtmK9+iY5Ty7rqog5lFUR0+Mt2lN5TlSim9QTiW2U1GInZO1T3zla8mgAMDt32d/CHx880Xk0JuPAPICHTx/EjUA071Bb29+vMTfYn6cwBbia1/3UdVoT03QOeo7d5SGRdTd3ETF9xrTnLZOX3itnD5qOAD8nonCCbRFVI6gLaXRsigk5pRRtQ0Me2+2WZod3h7KHEU8f0R952KGLKIpaEiXB+UgIgd2nf9Ej5qRphiIj7qkZlBXk8A1QQEeWVYZ7A2Lq7mqg/TneM9hof/Aa6//pqZd1vF/u6jHb2L4XdDSuciYyd09inZN0mq9iei5luZktK84iucaV1f1r/sUz1cMeyCiodkfPhlw70rUnE3CyxRFxlt9EJSTcEkWhXFyj4pjjaP4G9cCMCWEEcG5kM68/usryl9j9IdsJKWl5fNspsIo3kgsnCbPeiwLiWj2REBnVAfVk2xoVc3OELWNugC2Cxp1gZ0wtQMSeSiXWDTqag7FqIUZmDrLK6i9uYlSc+oTyyTaHItqg9nnEzDsi5mXNycF7qWVeIBL+TSJ0u4hrya4Q2xlQ/4fC7y1N/ig3Q8ZBXB/xEQM+9VNO7V7w9YD94GuajwiNUTHDYiQgMZ4CGiYmmINz6hTHW6mzf0jhxpxS/ozekWJdpekJMDdklBRd+1z/Iyu37YJ2tQ++Hs94Dev/EBSVJDHJr8sYD+nLIbT9zoySNt5BG9rR1yWu8sjKq7PVXMW4nPGm5eLi0J46kubCKpo09UA+aLKwwQ2qKLN4S0+LlAXUPN2LgngqiscAyL1YTXBeilbRLXABFs1fbytlbFK0HVVlNlBvMElKMfd9ecVBSwodcXR7vH212zHEwJbhLv0Q5WCHWz4NSvj51i4jckWER14GJYCb2RuUFGqN1HJUpJNzWFBOA+CjfLhV1mbwM2Ifk4Fn0ZrzZq1J8lt8Wv8cECWx4JzDGwhmQsclgO2L1rlpr7LWgWuap+K81ezyc/S9kCr1+0BI6wYkok6r5D4tNKw3NhlUu01UZMFUB21EA4LWUCuBNqNCtFHlVA0q4fARkXZCFEVofJqKANSthIgTmATKbERAluGGxvQxdStC9IZMhwIyvG44BbVwckGHY41CeGmrqCgHDXDslrMKJmANA5q9DqvB28bJV1LvNCELsGNTMhCgMDwiIcLn9hId6LAkCnCDQypUuqmua+wM61zS/EY4BQE8zB4D1fEwKCIpPVTKUDTvIcH2dZ5pbgMsAUhPAgeb+LwISjJ54VbGMAP85QGywhThMsBVQpwuEOUN4DhQFiSe/iDX1g3qHgfFXFRd2aTR2nxjPJW25ShhaiaOsDg19Rlm9qRBHItqKVmmltRl+frrKoN9xuQV6q0mkeiwpjYEi+AFTgcqovoBZlUTb1ok6isy/+pMlC6THk1r1QViAsoij+yfFcDB5X3OGwpeIEqvxjfFfBKqiLBImtvF4lXiugS3CiQKASZHl6VZVQT2glHhC7AnTURZdSNfkLJsb1UxDR3/sRrqP+qbuKfdRySDVermms5z/G+El6pUFfhsaSqBWT1OkuqQwpikVtUyBqnNIClzzePx12t4Z9qxc/yt1v+UXF+MS4rnJKA9Xmc7gZ43UJSlrtKLyoO5Up5L4FfTMyL7g2FYS3F6p64qIodvXU+oqZsmFQrfnQpYNOKOwecMsLG9W4fNFXUczh+MSEP2rO5ppZh5KhRV8iwnRiSQ0oVlKmrAHnWDM84FKAxL7wqkHXD6JdDSR2mQSoB2dYO2Dg0AGuuoFpAlvVXX09EZIZBvsBHFAG2qLojwSskbFvztkRbR7XqzC0lZkFz/ZmqpFhclJYG8KS3zEhXhvEE5kWHB8AGlbCkhBftrSqinhwwqi0kphiwdcB6qKCckAv9ldG2GuxuiKSsmCOjWyJNVdiirbiokCej5dthTdkarqCcihmN1dy2GmhJV1xUzI/J4m5XE7rCKy8uYc1wrbetrV7wFZQTM6S99Ns9fTVMEcjngiwj5GBYDNw6bCtOVljCj9GmnOipFz5TwG1KflldZtiUVwqm2Apq5ug6QCbhGwny4kIGjbcU7moH1ESv3fnIdkGGt9YpKMhd7eSWhWw+S/VNrWp6WqYDHU3UjACMYA1UvP7ZfoHdU5akeGBKSO4t94XA96NVx674xQAcAA9c8Wr9AuTjFzgjdVj3tdLJaaOV3gdWTX7yYEzKH8kGkDxPCbSi5q7ToO4Y5sds9YHp6HXN0fbguI03DUpjequ99gNDCE/bwTXNgcnLqQdlX2U0wTX12FeY1kHGOtBxRzyUnNSUPw3oqI9ACogQnDWyJRMenmTNzwpH1ABmhGuIKctSMntHCg0gUN4uFEyKwJo2RSfbjWso8Au4EdIwHSZUPMIUmmsSzDAxFFAuosTE9jtJUATm/GuI6ybu0xfmuYWn/jvaCRpj5apRGyIQQd8XJE7yEvtAKOe+sFJUV3IlPP51/4YC/Wm82pLb/UB7r6xk1aLxjzi0ek5/sy4QlW2XFF+uEIh47ImhzzM5sgpOukUpHCdu7XROlubTGB9tnoYniroQH9zi1rvDEy2ZOXQoWEFC0LEi0bEgwioOVGdyQcAsB6fwMjvfI/PpPUqydF9sMpmhGJQSM89b42l4l+WY5SoSV4csdHp4PAuGfEUNm2PPO4bW1CQ/2BWCAvXCskvs+BnMBF0Z6ocFLXdgWuDL0gA/tV9Pf3/N433MjRtMyIg7LUx53PSe81WGIUgq5XZM5CUtiVqUzfhJ9Fksbn1SKjGp8jkP5CQuuojB6DfV4MuLExh6ehPxtGBoyc6duqEw7txyS+voffbH05kkv5NEGUvsMjUHybhPFXmZts27SaQHF/d0WEzmcZhU5Z3TEeYgn6HLYLXlF7YNZvcdBqgsXWw5nWSTuaswrajhDt7i5PQ0GVHmeSviAeEfVM8mSjwRjkJXFDUWL5DhwXogUBRVrIqEd4mglQb5xbIgVKAQFl5o58/QeoJa0ieQATXrwlgjPFIEuvZyOkM5kQDgNnEKYzhVp4n1RUWvh2XdrmVOIQbuQ0VmCzqq6u6CLekDTsw6gX3xKVZYTg3i9s0XaigyPi3STCt0duUQZLYh1WzasZHLk1YEozDn8grLFsZpER62Viorbmnd3ReBsNMJuUR45V2IZJ5pYddJYOQnLG03AnK7m3PqhjLa45RbZkeVER6v4JK3qk89uovw++DSTndF3Oh0Wwg36LyrA2sp6e251DRbi65HursmdDptVDw9ZFXOF4KwrJh96v3JhnvB45K8etzeM98cCGET5XvExb2wrAsFmFcM7Q0LDUCQFZyjgj3xxi/gSjBwkJAV1EgZc5bPLyG1Vw61MURWmwZJvyilxN6WsiwmXUSR1abH1bQiA0XZ4sI2Y08N3I3osCLC5hVbTieZZ5efPqDnqErK7sldXo+VdWBB9/Cpaibw5j0/LaTE3r3jfHUgKpAqQKrZxIsPglGojLzC6oShmLgqaricwfogHrjerEBViPfmu3XKPoFVxl/fldeQRRtjbrQRzZIzZ/KLZZE064NaImFqrE0ksJ0RRRWrGwEzCUK1EyIsvIrOM8GRUgJ0jSnCsunEQ7OlH40JarnyMNqCtSEUjUiMKb8uQWhEYWyFqYKwWUQj3l4QFZ16gocLM9sPE4jGcJonq+pSqeQx7KCEI0G1xgMuna78HGjyQ1RguzzdLHlm4dT8GOobv6ZTMWUJkkupKeBGSLoAouusUjCNlrTEoTrVlHbcNV+VDbz+QpVep+2p+xane1AY1Jacwm+RmUPb9XXF/hFZyt42SG02aqsKmYeqqkwhuGn2hsShM1XC2eYpExbb62T79BfMHQvL2rQVbSNEnf4nW51VuFVOqaV1UDEP5hVzOft13OUy2pZtem0wjqVVLI/2uS1aAsMvlgWhxrig8DI7P4TiE0Vc7uup0tY7M16VDK+ZC9LS9qn4lUZCm4ZLCwJghrnMrizvUsw113WVeJ/iHauzwMSxhDGtWYUGbY/fBWjrotqT6El7Mq5l1FRPCBomAgRKark6VNYOqP79avcaF1mulSZcWdWmS+O2yRMbXcKRoACJROSV1iEcwqNzG+HenYJUm8CtzYSmwXugGhl4pJVso2nQGiMW4pt1gUAy9AiKL1cIjD4MyMMUiKjgWnWmE8z5+BB+leQ1Rn+cnTvpJ0/Z0hVJ4DXoyFYOheQEp5845aQrk8J6Am6hzQqrOx0q+kUTvQGians5LLL7B9BKTodAfcUTWNelcXEwgJbFSM6JtKFMV/cKy4DJokYtp8OgmhyC60IEZHKg1X8kk49naCOZru4Vkvt6krdDNGpZu4HdbiF2zREZ5yErVQZUoHufinGRFwXvsEpZVjcpqudmeCDLS5pjpUHSy4HTXcgyJ+JmSE/5UYxHUEzBywFj2VU3x6vjZjjqOfju/dvgg/GwqCl5OTxittXNyuq6Ga7u7IPxIInqezk0NLPqxtgaboZBZ0YHr+52UudoGO0ZotoT2bFDCkJeYl3INcQKCas6Qr/2/q4JGS+HCTY916noZogeUFrEZR2o40jvM3pFifEwAUh5OVQSvtXtSiu79inqdRV4daDQDJO1+ORViN0Y7RsWE96scHn26XP8jK7ftgna5NH2e+0Xbl5rBX1qH5GPEuLGvywRnREdl/ELnyGClqiIa6E2x/WLT1myQ7kqHDQldWGiFWmsrLhNdZ1TJIRV0rgENcnlJ5e266EshhSvszRtD37ArLlOdZviEbZLkJGUcig4BXxgFS9GWArbDazp0lT7KL4C/V5h9/852z+1Wlb/CyY/flV498+tSXp/LqQl2AFvCuESJR0L2NASykm4VXA/BaltGUWVL0N4vRKZAVBS26X4RpkHm0LTBBu33kUIanBuURtbdN2LEJjE1WuITkxlslDEZ3Hf7lDTrG6EyK83YXzYMyCT27mM3Zmr6K1ithBMIMwTfexHD+awj7dfsl2VoOuqKLOD1moktKpNu8ZtkyTBLzH+wm5HD+0eb3/NdsA7GIBaNsVDN0defGU+2heK6iKzrPyqBPEYc2EIEAm/pnOVmFV8fFOiAJO60josj6CfCtcOqOXSsc8ornokf83K+Dnetn2WXcDlyE2jurj7bu8G0zwySCY/2hfp8Dg7QISD4rKt2TEn5z0QCbEHDZLKsIbbfe/ZxQOLKNW1rJr0uYUy/OMebeNjLDjnAqy5KuGovL+s/LoEofL00gpOnfzEoiHs3VN3s+OhOh6TmA8SeQWXojE02gZC6Xq1QUX5OdOxtrCKNlWJ0yJBgPvdiYAUxkVVZflC4WoG2QZQnYhKTrs5s4K9HYGLPvIKLrBDLzUSv1sVAFBx6KLL6zQf64I1YnFhq92YSQHI3vXTMenz5nRRt7Ob8xwRklDBrXg4L90r5cSp41ZgI19iHi2k8+O8KtGcSqoFwpWFZyp0HZVon+UxKmCuBFTPpnFlG+TKZfjZhXAUbkZRYy0CIVVg2ARUbQZ1HHbRC7+kpU+iKi6QM7UgvkRxKluelZS+HDcNNDBs4TUgROsO8ExXficUh/Rikaios+DKE5EMzqYJNjQUNVzbkuHtvDle4ehpa7xIpqriwrawr7dQXywLQvziGLfcNCiZRwhK7+L2DbHpOi9PBq6VHhpOw6ao5I0Dt+zboq6FCUshDam9VgHitMvb5kh1n4xNcoajbZOzkKlNZAbhkOuJDMfksiLnswWr10frcEVX1rFrA4eNCWYalnBHdUzpA4SlVyAAnZhIVcmRODhekfpmXSCy2IhbEtIRk9NSPogBohmO46M5hSByPNxyDvTbI1FATcPCrQEnpKCHCBSHUJVcY2OOuILb627OoiOoQRXAPIkV0vCD/yKCHU6A1LO6Xe+HcFQHF+Q1li6QIZQ116qmCNINFHCsEKBLNG6D84k7rog8mFIujeZca3OwxdkVrMe26/JsYkDA9jGv0oTr+WzzYqq8suPvUw+Jwg/+QarZxBTTHnlvmv3qQDCqVBHSCssWhkRnYAcF2SrOuufFruJjk35cLZeu3Np3EDuz38ebH/PsQDDP0SZ5DbcCIxsn4Ud9si0amPVV1bFqbeYThsriikuvRAD9P06PTgDEwNaRKcuAMqEqg9/9FM0m07MgZPn12I+e1Me8zTL2Bg/ioFVtKhO3TYKEoIQjQSlMjLrSOoRDKUsD8VMbAO2iKjjv4mzqdl5Ofuq/o93ALijeiRNVcr/9Nc9B1J7yBh2O9RhoXC0D13WzTTJsVCAtsogrYYG3EIW11iegJ7oFhdIxxSfo4DglHocfEyWbVLkkIrWuTHpKNJHyTCiAJ5IyTFHkKzTGHZlcKa7zuIy3UVITh2uFupJNVFCtEZWZb9YFokpjJym+XCEMEX8mrtKNQUknHZnRY0hOfBbdo0UK6QAIuI9xgbnIYBWmEu8myvdIcVQOQkAsILcp2hYgfuhNdVkFV+id44L6qZ+aQeJkAeIkNg8cFE4QELrrcPt+FcZRuoOHQIBaNgVBN0feqmQ+2heK6jGn/7+9c1uOEwfC8Kts7Qu4cj+1VYkdb1xJ1i6Pk9sUAXlM1Qx4OXjjt18O9hiBDt0atSQgV4mH/lvoQ78QJ0kVvygQ6i5SFU5UKa89hPbrYXGoAoWFT4edIlCtSDoOoh/gtQXTVfsmem5XgG1vPOMepMCENvsJQYlcAuF2EkCanlMnWSYUdS+qUZBW0Hlf8rJP3VKyaEepVAQt51iciOpgo30oMBeJ4xcFAuQcYThRpdwbpkhj1lwL1/0zS4RnQEKrrWVaIs9WtJ0EkM4/GskyoWi8pFaQVtCDqfK4Lljr9G17i4ntMMM7sNhuOxKWOkItiSEDpjWaXrYcSMNLoGkhuosmgcJJRd2bry7ih6hkl3lxQLhOr7LakkbF8WQnG+1D0TlLFT9nEENLDLLr3DMMJaqMz/sumpnaBHFkjxj8YjjOtTa8Q9Z+m6AFI1fC7s/1hUju0fUbwwGlXhtiGjb3W5R3rDikWffjBYuSfZoh3i2Ei212rJJSuSTSGDJgmtMORLZ8SOorKYDKSYWdm5B/fv8hKtOyOZfeFVFWNjXvn+yYzIkHzmSz5UF2QfG+hEzgBjJqrjxAjvWBHb598mNcJm7+wancMxxFiRbeBrKLfrApL1Qvn6FzOEQCzC4dakElDg+E/DU1dA7Ksed8YLd/DS9SjM+W0ER0fbpwDxSMJfFOAKPOlPoUa4M6sfegSIPOYaj2CmZOp0jY66y4BL5Oju5ff+V39Dyvm8uhZ/PeV5eAroPgSlYci1EcKUBU7yqXLh0a58arrGTtc5u+yJSV27pDcZd3U9y8jOzNRsK43OGNkM0OHjhDAIfVaFyNy+3UG8s/sCcu8uBtgYcgxi7Gizp4WNAhDGCad9ChUj8X3SEgvH5iRdnOONmEMMUL7cgM4Z0sud2E3uLgNZYPwzG7+fhan4KuMxiVrUA6iSQGiepGVeI1wOM7Rb4wwzvMsizhdQqmBwWRw+nhMhovy7I4b8vrOGjKl5dAOjcvM4U9bvnRXNTZeBomTRNeZzUccb4zeEjzzvUBMeqNpGn8PxgLEfpgnjxDAwgyhNf25RP0gTXODoNZsxdkUL2/dPo8rHOFrVtDDagMr41Pl4TQxjrCbLLMKOmabfOC2O6zCb1OZwQht7ee6LxQ4+84E63zEha2dndh95ckkTbRtAGTuSpEKtOKajqtScxcKncTleV/eZHcspJVt+0E0CVwliqg0iYIUZGjb8tFATSQtDMIaDSLBYNqMItpI9syb/ftKmFdUVIKwjibAEYFcOLJtpOr3S8puG3yYdaIlMTbxPBWEKcb/myz8qA1ICeR86zwj2FOWV2HQbqdFuxwKFVFNGonDRqAyryyoEZM3IBJK3jL4pQ9pk0LEb7BKYiiabotC4dL1lZVFD+wBPNMWKuxeuS5wnggo022YeiavDx6rgA+sf3jHfsFPF8rom0CeC2GU739aK/SmuMtjJtTRT+zLMmHDwui/Xme3ae7GvMynUEWm5B0xXPZ9MH0UDWtCqVfN0jNy3e4BJSP92aH9nvafY1znu/rQyYc/mBTeIIzLanfIU0JwqDTsX/5+O0xiSr2KS2rvHi+qtgB2MfClFa7A0GRPChhAA0kXZ+p0ywBzJc8jvbvdwXr5hD7uO/+gU+rgpHbxCUtl0ujiCIEp2lWMOFqYGlOt0Al5Xk2FHxF/JA+sfb/8GE0UGm/tfFFCkiNA2gggbyo0CwXDMh3KhG95fyiumNlZW42jZqqXQ2LlQLjg+igIcwn1a0DFMKMcqErQ3pHhxydKlVk7Us8PhhutA8FYzj6AadPEBhDeRhMOkbzoS7TjJUl0jkamfUWMyxvyoXfSgAG4h6pYHEwIA6SK8gt5B6P0brCQKX11qNak1YcQAMJ4ilXCw6HBQbiL6WI3GK+UPEvDp8yxewp6ay3PMh+TBnDVA7BQxyNTvQbtoQRpJfAZyLvOuaG32BKOIMsxK1cPqOSPpgeKr7fcDNX3PxA4vsERQLHXUFoaNu/DGfkPSEbcfsV7oYOtkTkDjq+g9Dn+Q1aBAjfgQASOe5IQkdvNA2WUR7iNq6aiQYS7gIuvutwNT/WXIHiuwhlCsedQ4iIx5+nG/cIgETELVj5UT4o3glgfK+gTPEbakcE3zOoczjuGkLCjHyMoJCQtE3xTd+3LZZBQO1K/4zAR+WhtvLwHMAdjqusYsV9FGNf8dDprLcQrsApl9FmCjgQt8gVSwQCcZBCQm4jL4iOSz8ih5xaof0WxJUo4DPaTgII5Cq5ZKFQQM5SaOit5RnTHTs87qMKfdqCJiBsV8OSVeD4OFKAOBNKpSuDhjOpXOvQrIFgNDate7PqQNEBQprSpRk9QkGazqvZnGLqp809b0y7y9vFqlAGA4gJ2tO4VBGsaQwZMJjhlLIVQIIZUK1zYMIQsKGvAJUysrYlG8EPtxKAwdjNxXWfXxgYW3m53nONp38y2C41kiXIASNAar39jMucQppGEIGCGEspWjQciNHUKnKzecQ12M9vWYq9hwmU229fgnIF0IRRhOBARtQJVwMLZEytkt6c3vHdRM/tTCeXBfuXZTH20ziY2nqrExQ7pSYMooMG8adOtw5QEG9qheTWDATd1zxhezNXqqRULe1YppTVIIIIFMKJYtGi4SDcJ1G5sp4PXEUas1u2q/ups7G+A6ntt65psQJioiA6aCAPanTrAAXyo05Ib0nv6PK4LrpJ+bbt65xshx6qgjMQtDth0SKAkkBaiDCz6rXrAwczL0DswMCBoKyL+CEq2WVeHLAO1kvtt8BRmQJekwgiUCCXqkSLhgNyolJFb0FvuPqvIFiBNJxGZr09DcubwuG3EoCBGEwqWBwMiKHkCnIz+cDTD2vbmz21wb1SqJ6gJQkKFjEThlHCgzlOp1wTMJgrtVIH9vSPcMuyMq3SJ2bwsR5Ea73dTQqdAhOEUMGCmFOtWjogiBk1MnIjBoHshhVluyrPKUYE5KBrb+PCFRCnodQwUUZVqtcKEGVktdydoQNAeseKQ5p1Wy5YlOzTDPsNFTyD9bYpKXqKURpICxFia4h2feAgdgaJyc0cAEr8vSGHt4WkV/mWL/Ax94Gc3ALyUvFbds+K5iqMCRd3lIVargSX4fVd8G9ltONTjLacDOLEsaqvYSpsNEA5EDAel7ofkoYMS+09kM5ZxYUWFbrTAjL4SuMOVhmfLLxtadFtyMripKuKn1qxzVmvbT9HjZrBSXHctjnbxg/sEL380PxZ5UXTZ3fvbpXdr5uz27pRH1j/1wUr091bik2TM2sq3ZT5lvQ15iq7z2+K/JEV3X4P9+g15HXzC+qvrIqSpmG/L6r0Poqrt7nC/vzje7Svm5CPh58sucqu6+qxrpoqs8PP/fMQxuZMXf7mbLLPm/4ri9JGFZrdTNulZK+zD3W6T477fRnty5FZZSnOG/p/s+b3/li+POg/Zvonz4CJXvBdsEeWJSw7fuxfXmfb6ImZ7FvTWL+wXRQ/N78/pUnbcmVJ9AeCx765SKNdER3Klxxv+ubPpg0nh19//Q9xREV67MwUAA== + + + dbo + + \ No newline at end of file diff --git a/Infrastructure.DataAccess/Migrations/202212010827066_AddLatestSubscriptionCheckDateToSts.Designer.cs b/Infrastructure.DataAccess/Migrations/202212010827066_AddLatestSubscriptionCheckDateToSts.Designer.cs new file mode 100644 index 0000000000..6cc504b07c --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/202212010827066_AddLatestSubscriptionCheckDateToSts.Designer.cs @@ -0,0 +1,29 @@ +// +namespace Infrastructure.DataAccess.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.4.4")] + public sealed partial class AddLatestSubscriptionCheckDateToSts : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(AddLatestSubscriptionCheckDateToSts)); + + string IMigrationMetadata.Id + { + get { return "202212010827066_AddLatestSubscriptionCheckDateToSts"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/Infrastructure.DataAccess/Migrations/202212010827066_AddLatestSubscriptionCheckDateToSts.cs b/Infrastructure.DataAccess/Migrations/202212010827066_AddLatestSubscriptionCheckDateToSts.cs new file mode 100644 index 0000000000..60bc545488 --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/202212010827066_AddLatestSubscriptionCheckDateToSts.cs @@ -0,0 +1,20 @@ +namespace Infrastructure.DataAccess.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class AddLatestSubscriptionCheckDateToSts : DbMigration + { + public override void Up() + { + AddColumn("dbo.StsOrganizationConnections", "DateOfLatestCheckBySubscription", c => c.DateTime(precision: 7, storeType: "datetime2")); + CreateIndex("dbo.StsOrganizationConnections", "DateOfLatestCheckBySubscription"); + } + + public override void Down() + { + DropIndex("dbo.StsOrganizationConnections", new[] { "DateOfLatestCheckBySubscription" }); + DropColumn("dbo.StsOrganizationConnections", "DateOfLatestCheckBySubscription"); + } + } +} diff --git a/Infrastructure.DataAccess/Migrations/202212010827066_AddLatestSubscriptionCheckDateToSts.resx b/Infrastructure.DataAccess/Migrations/202212010827066_AddLatestSubscriptionCheckDateToSts.resx new file mode 100644 index 0000000000..76ac0f0044 --- /dev/null +++ b/Infrastructure.DataAccess/Migrations/202212010827066_AddLatestSubscriptionCheckDateToSts.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/Infrastructure.STS.Common/Infrastructure.STS.Common.csproj b/Infrastructure.STS.Common/Infrastructure.STS.Common.csproj index 76993849ab..0b7e4c36f1 100644 --- a/Infrastructure.STS.Common/Infrastructure.STS.Common.csproj +++ b/Infrastructure.STS.Common/Infrastructure.STS.Common.csproj @@ -32,6 +32,9 @@ 4 + + ..\packages\Polly.7.2.3\lib\net472\Polly.dll + @@ -45,6 +48,7 @@ + @@ -55,5 +59,9 @@ Core.Abstractions + + + + \ No newline at end of file diff --git a/Infrastructure.STS.Common/Model/Client/RetriedIntegrationRequest.cs b/Infrastructure.STS.Common/Model/Client/RetriedIntegrationRequest.cs new file mode 100644 index 0000000000..1a3940f2af --- /dev/null +++ b/Infrastructure.STS.Common/Model/Client/RetriedIntegrationRequest.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Polly; + +namespace Infrastructure.STS.Common.Model.Client +{ + /// + /// Wraps an integration request in a retry loop with standard retry-exponential backoff with the option to provide custom retries + /// + /// + public class RetriedIntegrationRequest + { + private static readonly IReadOnlyList StandardSleepDurations = new double[] { 1, 3, 5 } + .Select(TimeSpan.FromSeconds) + .ToList() + .AsReadOnly(); + + private readonly IEnumerable _sleepDurations; + private readonly Func _executeRequest; + + public RetriedIntegrationRequest(Func executeRequest, IEnumerable customSleepDurations = null) + { + _sleepDurations = customSleepDurations ?? StandardSleepDurations; + _executeRequest = executeRequest ?? throw new ArgumentNullException(nameof(executeRequest)); + } + + public T Execute() + { + return Policy + .Handle() + .WaitAndRetry(_sleepDurations) + .Execute(() => _executeRequest()); + } + } +} diff --git a/Infrastructure.STS.Common/packages.config b/Infrastructure.STS.Common/packages.config new file mode 100644 index 0000000000..e4147a1ae2 --- /dev/null +++ b/Infrastructure.STS.Common/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Infrastructure.STS.Company/DomainServices/StsOrganizationCompanyLookupService.cs b/Infrastructure.STS.Company/DomainServices/StsOrganizationCompanyLookupService.cs index 2f9fe82c88..e75fe7c37c 100644 --- a/Infrastructure.STS.Company/DomainServices/StsOrganizationCompanyLookupService.cs +++ b/Infrastructure.STS.Company/DomainServices/StsOrganizationCompanyLookupService.cs @@ -8,6 +8,7 @@ using Core.DomainServices.SSO; using Infrastructure.STS.Common.Factories; using Infrastructure.STS.Common.Model; +using Infrastructure.STS.Common.Model.Client; using Infrastructure.STS.Company.ServiceReference; using Serilog; @@ -40,7 +41,7 @@ public Result> ResolveStsOrganizationComp try { - var response = channel.soeg(request); + var response = GetSearchResponse(channel, request); var statusResult = response.SoegResponse1.SoegOutput.StandardRetur; var stsError = statusResult.StatusKode.ParseStsErrorFromStandardResultCode(); @@ -76,6 +77,11 @@ stsError is StsError.MissingServiceAgreement or StsError.ExistingServiceAgreemen } } + private static soegResponse GetSearchResponse(VirksomhedPortType channel, soegRequest request) + { + return new RetriedIntegrationRequest(() => channel.soeg(request)).Execute(); + } + private static soegRequest CreateSearchByCvrRequest(Organization organization) { return new soegRequest diff --git a/Infrastructure.STS.Company/Infrastructure.STS.Company.csproj b/Infrastructure.STS.Company/Infrastructure.STS.Company.csproj index b23f065c72..49577f41e1 100644 --- a/Infrastructure.STS.Company/Infrastructure.STS.Company.csproj +++ b/Infrastructure.STS.Company/Infrastructure.STS.Company.csproj @@ -32,6 +32,9 @@ 4 + + ..\packages\Polly.7.2.3\lib\net472\Polly.dll + ..\packages\Serilog.2.11.0\lib\net46\Serilog.dll diff --git a/Infrastructure.STS.Company/packages.config b/Infrastructure.STS.Company/packages.config index 22e0304f77..78898b3b3e 100644 --- a/Infrastructure.STS.Company/packages.config +++ b/Infrastructure.STS.Company/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/Infrastructure.STS.Organization/DomainServices/StsOrganizationService.cs b/Infrastructure.STS.Organization/DomainServices/StsOrganizationService.cs index 545cbfdfdf..e3de2c1138 100644 --- a/Infrastructure.STS.Organization/DomainServices/StsOrganizationService.cs +++ b/Infrastructure.STS.Organization/DomainServices/StsOrganizationService.cs @@ -10,6 +10,7 @@ using Core.DomainServices.SSO; using Infrastructure.STS.Common.Factories; using Infrastructure.STS.Common.Model; +using Infrastructure.STS.Common.Model.Client; using Infrastructure.STS.Organization.ServiceReference; using Serilog; @@ -77,7 +78,7 @@ public Result> Resolv var searchRequest = CreateSearchForOrganizationRequest(organization, companyUuid.Value); var channel = organizationPortTypeClient.ChannelFactory.CreateChannel(); - var response = channel.soeg(searchRequest); + var response = GetSearchResponse(channel, searchRequest); var statusResult = response.SoegResponse1.SoegOutput.StandardRetur; var stsError = statusResult.StatusKode.ParseStsErrorFromStandardResultCode(); if (stsError.HasValue) @@ -105,6 +106,11 @@ public Result> Resolv return uuid; } + private static soegResponse GetSearchResponse(OrganisationPortType channel, soegRequest searchRequest) + { + return new RetriedIntegrationRequest(() => channel.soeg(searchRequest)).Execute(); + } + private Result> ResolveExternalUuid(Core.DomainModel.Organization.Organization organization) { if (string.IsNullOrWhiteSpace(organization.Cvr) || organization.IsCvrInvalid()) diff --git a/Infrastructure.STS.Organization/Infrastructure.STS.Organization.csproj b/Infrastructure.STS.Organization/Infrastructure.STS.Organization.csproj index 3d13099bd2..39ad022d19 100644 --- a/Infrastructure.STS.Organization/Infrastructure.STS.Organization.csproj +++ b/Infrastructure.STS.Organization/Infrastructure.STS.Organization.csproj @@ -32,6 +32,9 @@ 4 + + ..\packages\Polly.7.2.3\lib\net472\Polly.dll + ..\packages\Serilog.2.11.0\lib\net46\Serilog.dll diff --git a/Infrastructure.STS.Organization/packages.config b/Infrastructure.STS.Organization/packages.config index 22e0304f77..78898b3b3e 100644 --- a/Infrastructure.STS.Organization/packages.config +++ b/Infrastructure.STS.Organization/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/Infrastructure.STS.OrganizationUnit/DomainServices/StsOrganizationUnitService.cs b/Infrastructure.STS.OrganizationUnit/DomainServices/StsOrganizationUnitService.cs index 3a2c2841fa..3d3bf188c1 100644 --- a/Infrastructure.STS.OrganizationUnit/DomainServices/StsOrganizationUnitService.cs +++ b/Infrastructure.STS.OrganizationUnit/DomainServices/StsOrganizationUnitService.cs @@ -10,8 +10,8 @@ using Core.DomainServices.SSO; using Infrastructure.STS.Common.Factories; using Infrastructure.STS.Common.Model; +using Infrastructure.STS.Common.Model.Client; using Infrastructure.STS.OrganizationUnit.ServiceReference; -using Polly; using Serilog; namespace Infrastructure.STS.OrganizationUnit.DomainServices @@ -142,20 +142,12 @@ public Result() - .WaitAndRetry(new double[] { 1, 3, 5, 10 }.Select(TimeSpan.FromSeconds)) - .Execute(() => channel.soeg(searchRequest)); + return new RetriedIntegrationRequest(() => channel.soeg(searchRequest)).Execute(); } private static listResponse LoadOrganizationUnits(OrganisationEnhedPortType channel, listRequest listRequest) { - //This call is unstable, so we add some retries - return Policy - .Handle() - .WaitAndRetry(new double[] { 1, 3, 5, 10 }.Select(TimeSpan.FromSeconds)) - .Execute(() => channel.list(listRequest)); + return new RetriedIntegrationRequest(() => channel.list(listRequest)).Execute(); } private static Stack CreateOrgUnitConversionStack((Guid, RegistreringType1) root, Dictionary> unitsByParent) diff --git a/Infrastructure.Services/BackgroundJobs/IBackgroundJobLauncher.cs b/Infrastructure.Services/BackgroundJobs/IBackgroundJobLauncher.cs index 640d2a014e..925ee98b7d 100644 --- a/Infrastructure.Services/BackgroundJobs/IBackgroundJobLauncher.cs +++ b/Infrastructure.Services/BackgroundJobs/IBackgroundJobLauncher.cs @@ -17,5 +17,6 @@ public interface IBackgroundJobLauncher Task LaunchPurgeOrphanedHangfireJobs(CancellationToken token); Task LaunchUpdateItContractOverviewReadModels(CancellationToken token = default); Task LaunchUpdateStaleContractRmAsync(CancellationToken token = default); + Task LaunchUpdateFkOrgSync(CancellationToken token = default); } } diff --git a/Presentation.Web/Content/less/kitos.less b/Presentation.Web/Content/less/kitos.less index d662aa0a4c..fd333b4b5c 100644 --- a/Presentation.Web/Content/less/kitos.less +++ b/Presentation.Web/Content/less/kitos.less @@ -994,6 +994,10 @@ tr.angular-ui-tree-empty { background-color: #f2dede; } +.neverDragable .tree-node-content { + cursor: pointer !important; +} + .nonDragable { cursor: pointer !important; } @@ -1641,22 +1645,26 @@ tbody.bordered > tr > td { } .org-structure-legend { - margin-top: 25px; + margin-top: 5px; margin-left: 7px; +} - .org-structure-legend-color { - display: inline-block; - width: 25px; - height: 9px; - } +.org-structure-legend-wrapper { + margin-top: 15px; +} - .org-structure-legend-color-native-unit { - background-color: @ui-tee-default-color; - } +.org-structure-legend-square { + display: inline-block; + width: 9px; + height: 9px; +} - .org-structure-legend-color-fk-org-unit { - background-color: @fkorg-orgunit-color; - } +.org-structure-legend-color-native-unit { + background-color: @ui-tee-default-color; +} + +.org-structure-legend-color-fk-org-unit { + background-color: @fkorg-orgunit-color; } .wide-modal .modal-dialog { diff --git a/Presentation.Web/Controllers/API/V1/Mapping/ConnectionUpdateOrganizationUnitChangeMappingExtensions.cs b/Presentation.Web/Controllers/API/V1/Mapping/ConnectionUpdateOrganizationUnitChangeMappingExtensions.cs new file mode 100644 index 0000000000..d9dc5a239c --- /dev/null +++ b/Presentation.Web/Controllers/API/V1/Mapping/ConnectionUpdateOrganizationUnitChangeMappingExtensions.cs @@ -0,0 +1,39 @@ +using Core.DomainModel.Organization; +using Presentation.Web.Models.API.V1.Organizations; +using System; +using System.Collections.Generic; +using System.Linq; +using Core.Abstractions.Extensions; + +namespace Presentation.Web.Controllers.API.V1.Mapping +{ + public static class ConnectionUpdateOrganizationUnitChangeMappingExtensions + { + + private static readonly IReadOnlyDictionary ApiToDataMap; + private static readonly IReadOnlyDictionary DataToApiMap; + + static ConnectionUpdateOrganizationUnitChangeMappingExtensions() + { + ApiToDataMap = new Dictionary + { + { ConnectionUpdateOrganizationUnitChangeType.Added, ConnectionUpdateOrganizationUnitChangeCategory.Added}, + { ConnectionUpdateOrganizationUnitChangeType.Renamed, ConnectionUpdateOrganizationUnitChangeCategory.Renamed}, + { ConnectionUpdateOrganizationUnitChangeType.Moved, ConnectionUpdateOrganizationUnitChangeCategory.Moved}, + { ConnectionUpdateOrganizationUnitChangeType.Deleted, ConnectionUpdateOrganizationUnitChangeCategory.Deleted}, + { ConnectionUpdateOrganizationUnitChangeType.Converted, ConnectionUpdateOrganizationUnitChangeCategory.Converted}, + }.AsReadOnly(); + + DataToApiMap = ApiToDataMap + .ToDictionary(kvp => kvp.Value, kvp => kvp.Key) + .AsReadOnly(); + } + + public static ConnectionUpdateOrganizationUnitChangeCategory ToConnectionUpdateOrganizationUnitChangeCategory(this ConnectionUpdateOrganizationUnitChangeType value) + { + return ApiToDataMap.TryGetValue(value, out var result) + ? result + : throw new ArgumentException($@"Unmapped choice:{value:G}", nameof(value)); + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Controllers/API/V1/Mapping/StsOrganizationChangeLogOriginMappingExtensions.cs b/Presentation.Web/Controllers/API/V1/Mapping/StsOrganizationChangeLogOriginMappingExtensions.cs new file mode 100644 index 0000000000..466a9ec50a --- /dev/null +++ b/Presentation.Web/Controllers/API/V1/Mapping/StsOrganizationChangeLogOriginMappingExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Core.Abstractions.Extensions; +using Core.DomainModel.Organization; +using Presentation.Web.Models.API.V1.Organizations; + +namespace Presentation.Web.Controllers.API.V1.Mapping +{ + public static class StsOrganizationChangeLogOriginMappingExtensions + { + private static readonly IReadOnlyDictionary ApiToDataMap; + private static readonly IReadOnlyDictionary DataToApiMap; + + static StsOrganizationChangeLogOriginMappingExtensions() + { + ApiToDataMap = new Dictionary + { + { ExternalOrganizationChangeLogResponsible.Background, StsOrganizationChangeLogOriginOption.Background}, + { ExternalOrganizationChangeLogResponsible.User, StsOrganizationChangeLogOriginOption.User }, + }.AsReadOnly(); + + DataToApiMap = ApiToDataMap + .ToDictionary(kvp => kvp.Value, kvp => kvp.Key) + .AsReadOnly(); + } + + public static StsOrganizationChangeLogOriginOption ToStsOrganizationChangeLogOriginOption(this ExternalOrganizationChangeLogResponsible value) + { + return ApiToDataMap.TryGetValue(value, out var result) + ? result + : throw new ArgumentException($@"Unmapped choice:{value:G}", nameof(value)); + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Controllers/API/V1/OData/ItContractsController.cs b/Presentation.Web/Controllers/API/V1/OData/ItContractsController.cs index 0b2498a9fa..4f50a90167 100644 --- a/Presentation.Web/Controllers/API/V1/OData/ItContractsController.cs +++ b/Presentation.Web/Controllers/API/V1/OData/ItContractsController.cs @@ -9,6 +9,8 @@ using Presentation.Web.Infrastructure.Attributes; using Swashbuckle.OData; using Swashbuckle.Swagger.Annotations; +using Core.DomainServices.Authorization; +using Core.DomainServices.Extensions; namespace Presentation.Web.Controllers.API.V1.OData { @@ -34,6 +36,29 @@ public override IHttpActionResult Get() return base.Get(); } + /// + /// Henter alle organisationens IT Kontrakter + /// + /// + /// + [EnableQuery(MaxExpansionDepth = 3)] + [ODataRoute("Organizations({key})/ItContracts")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [RequireTopOnOdataThroughKitosToken] + public IHttpActionResult GetItContracts(int key) + { + var organizationDataReadAccessLevel = GetOrganizationReadAccessLevel(key); + if (organizationDataReadAccessLevel != OrganizationDataReadAccessLevel.All) + { + return Forbidden(); + } + + var result = Repository.AsQueryable().ByOrganizationId(key); + + return Ok(result); + } + [NonAction] public override IHttpActionResult Post(int organizationId, ItContract entity) => throw new NotSupportedException(); } diff --git a/Presentation.Web/Controllers/API/V1/OData/ItSystemUsagesController.cs b/Presentation.Web/Controllers/API/V1/OData/ItSystemUsagesController.cs index d95b430599..e656f0ee7c 100644 --- a/Presentation.Web/Controllers/API/V1/OData/ItSystemUsagesController.cs +++ b/Presentation.Web/Controllers/API/V1/OData/ItSystemUsagesController.cs @@ -1,6 +1,15 @@ using Core.DomainModel.ItSystemUsage; using Core.DomainServices; +using Core.DomainServices.Authorization; +using Core.DomainServices.Extensions; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData; using Presentation.Web.Infrastructure.Attributes; +using Swashbuckle.OData; +using Swashbuckle.Swagger.Annotations; +using System.Collections.Generic; +using System.Net; +using System.Web.Http; namespace Presentation.Web.Controllers.API.V1.OData { @@ -11,5 +20,31 @@ public ItSystemUsagesController(IGenericRepository repository) : base(repository) { } + + /// + /// Henter alle organisationens IT-Systemanvendelser. + /// + /// + /// + [EnableQuery(MaxExpansionDepth = 4)] // MaxExpansionDepth is 4 because we need to do MainContract($expand=ItContract($expand=Supplier)) + [ODataRoute("Organizations({orgKey})/ItSystemUsages")] + [SwaggerResponse(HttpStatusCode.OK, Type = typeof(ODataResponse>))] + [SwaggerResponse(HttpStatusCode.Forbidden)] + [RequireTopOnOdataThroughKitosToken] + public IHttpActionResult GetItSystems(int orgKey) + { + //Usages are local so full access is required + var accessLevel = GetOrganizationReadAccessLevel(orgKey); + if (accessLevel < OrganizationDataReadAccessLevel.All) + { + return Forbidden(); + } + + var result = Repository + .AsQueryable() + .ByOrganizationId(orgKey); + + return Ok(result); + } } } diff --git a/Presentation.Web/Controllers/API/V1/OrganizationController.cs b/Presentation.Web/Controllers/API/V1/OrganizationController.cs index 6922b2ab39..9f617a66a8 100644 --- a/Presentation.Web/Controllers/API/V1/OrganizationController.cs +++ b/Presentation.Web/Controllers/API/V1/OrganizationController.cs @@ -112,6 +112,22 @@ public override HttpResponseMessage Patch(int id, int organizationId, JObject ob } } } + if (obj.TryGetValue("cvr", out var jtoken)) + { + var cvr = jtoken.Value(); + + if (!string.Equals(cvr, organization.Cvr)) + { + var canEdit = _organizationService + .CanActiveUserModifyCvr(organization.Uuid) + .Match(canEdit => canEdit, _ => false); + + if (!canEdit) + { + return Forbidden(); + } + } + } return base.Patch(id, organizationId, obj); } diff --git a/Presentation.Web/Controllers/API/V1/OrganizationPermissionsController.cs b/Presentation.Web/Controllers/API/V1/OrganizationPermissionsController.cs new file mode 100644 index 0000000000..1f72acf380 --- /dev/null +++ b/Presentation.Web/Controllers/API/V1/OrganizationPermissionsController.cs @@ -0,0 +1,31 @@ +using System; +using System.Net.Http; +using System.Web.Http; +using Core.ApplicationServices.Organizations; +using Presentation.Web.Infrastructure.Attributes; +using Presentation.Web.Models.API.V1.Organizations; + +namespace Presentation.Web.Controllers.API.V1 +{ + [InternalApi] + [RoutePrefix("api/v1/organizations/{organizationUuid}/permissions")] + public class OrganizationPermissionsController : BaseApiController + { + private readonly IOrganizationService _organizationService; + + public OrganizationPermissionsController(IOrganizationService organizationService) + { + _organizationService = organizationService; + } + + [HttpGet] + [Route] + public HttpResponseMessage Get(Guid organizationUuid) + { + return _organizationService + .CanActiveUserModifyCvr(organizationUuid) + .Select(canEditCvr => new OrganizationPermissionsDTO { CanEditCvr = canEditCvr }) + .Match(Ok, FromOperationError); + } + } +} \ No newline at end of file diff --git a/Presentation.Web/Controllers/API/V1/OrganizationUnitLifeCycleController.cs b/Presentation.Web/Controllers/API/V1/OrganizationUnitLifeCycleController.cs index d05538f493..f7b822b4d1 100644 --- a/Presentation.Web/Controllers/API/V1/OrganizationUnitLifeCycleController.cs +++ b/Presentation.Web/Controllers/API/V1/OrganizationUnitLifeCycleController.cs @@ -8,7 +8,7 @@ namespace Presentation.Web.Controllers.API.V1 { - [PublicApi] + [InternalApi] [RoutePrefix("api/v1/organizations/{organizationUuid}/organization-units/{unitUuid}")] public class OrganizationUnitLifeCycleController : BaseApiController diff --git a/Presentation.Web/Controllers/API/V1/OrganizationUnitPermissionsController.cs b/Presentation.Web/Controllers/API/V1/OrganizationUnitPermissionsController.cs index 32d78f8a11..2ce241032d 100644 --- a/Presentation.Web/Controllers/API/V1/OrganizationUnitPermissionsController.cs +++ b/Presentation.Web/Controllers/API/V1/OrganizationUnitPermissionsController.cs @@ -12,7 +12,7 @@ namespace Presentation.Web.Controllers.API.V1 { - [PublicApi] + [InternalApi] [RoutePrefix("api/v1/organizations/{organizationUuid}/organization-units")] public class OrganizationUnitPermissionsController : BaseApiController { diff --git a/Presentation.Web/Controllers/API/V1/OrganizationUnitRegistrationController.cs b/Presentation.Web/Controllers/API/V1/OrganizationUnitRegistrationController.cs index d7d8b9d6d1..3cf3b9fa63 100644 --- a/Presentation.Web/Controllers/API/V1/OrganizationUnitRegistrationController.cs +++ b/Presentation.Web/Controllers/API/V1/OrganizationUnitRegistrationController.cs @@ -15,7 +15,7 @@ namespace Presentation.Web.Controllers.API.V1 { - [PublicApi] + [InternalApi] [RoutePrefix("api/v1/organizations/{organizationUuid}/organization-units/{unitUuid}")] public class OrganizationUnitRegistrationController: BaseApiController diff --git a/Presentation.Web/Controllers/API/V1/StsOrganizationSynchronizationController.cs b/Presentation.Web/Controllers/API/V1/StsOrganizationSynchronizationController.cs index feede2d6d0..a69f578b1f 100644 --- a/Presentation.Web/Controllers/API/V1/StsOrganizationSynchronizationController.cs +++ b/Presentation.Web/Controllers/API/V1/StsOrganizationSynchronizationController.cs @@ -4,8 +4,10 @@ using System.Net.Http; using System.Web.Http; using Core.Abstractions.Extensions; +using Core.ApplicationServices.Extensions; using Core.ApplicationServices.Organizations; using Core.DomainModel.Organization; +using Presentation.Web.Controllers.API.V1.Mapping; using Presentation.Web.Infrastructure.Attributes; using Presentation.Web.Models.API.V1.Organizations; @@ -41,6 +43,8 @@ public HttpResponseMessage GetSynchronizationStatus(Guid organizationId) .Select(details => new StsOrganizationSynchronizationDetailsResponseDTO { Connected = details.Connected, + SubscribesToUpdates = details.SubscribesToUpdates, + DateOfLatestCheckBySubscription = details.DateOfLatestCheckBySubscription, SynchronizationDepth = details.SynchronizationDepth, CanCreateConnection = details.CanCreateConnection, CanDeleteConnection = details.CanDeleteConnection, @@ -64,17 +68,36 @@ public HttpResponseMessage CreateConnection(Guid organizationId, [FromBody] Conn return BadRequest(ModelState); } + if (request == null) + { + return BadRequest("Invalid request body"); + } + return _stsOrganizationSynchronizationService - .Connect(organizationId, (request?.SynchronizationDepth).FromNullableValueType()) + .Connect(organizationId, (request?.SynchronizationDepth).FromNullableValueType(), request.SubscribeToUpdates.GetValueOrDefault(false)) .Match(FromOperationError, Ok); } [HttpDelete] [Route("connection")] - public HttpResponseMessage Disconnect(Guid organizationId) + public HttpResponseMessage Disconnect(Guid organizationId, [FromBody] DisconnectFromStsOrganizationRequestDTO request) + { + if (request == null) + { + return BadRequest("request is null"); + } + + return _stsOrganizationSynchronizationService + .Disconnect(organizationId, request.PurgeUnusedExternalUnits) + .Match(FromOperationError, Ok); + } + + [HttpDelete] + [Route("connection/subscription")] + public HttpResponseMessage DeleteSubscription(Guid organizationId) { return _stsOrganizationSynchronizationService - .Disconnect(organizationId) + .UnsubscribeFromAutomaticUpdates(organizationId) .Match(FromOperationError, Ok); } @@ -102,119 +125,93 @@ public HttpResponseMessage UpdateConnection(Guid organizationId, [FromBody] Conn return BadRequest(ModelState); } + if (request == null) + { + return BadRequest("Invalid request body"); + } + return _stsOrganizationSynchronizationService - .UpdateConnection(organizationId, (request?.SynchronizationDepth).FromNullableValueType()) + .UpdateConnection(organizationId, (request?.SynchronizationDepth).FromNullableValueType(), request.SubscribeToUpdates.GetValueOrDefault(false)) .Match(FromOperationError, Ok); } + [HttpGet] + [Route("connection/change-log")] + public HttpResponseMessage GetChangeLogs(Guid organizationId, int numberOfChangeLogs) + { + return _stsOrganizationSynchronizationService.GetChangeLogs(organizationId, numberOfChangeLogs) + .Select(MapChangeLogResponseDtos) + .Match(Ok, FromOperationError); + } + #region DTO Mapping - private static ConnectionUpdateConsequencesResponseDTO MapUpdateConsequencesResponseDTO(OrganizationTreeUpdateConsequences consequences) - { - var dtos = new List(); - dtos.AddRange(MapAddedOrganizationUnits(consequences)); - dtos.AddRange(MapRenamedOrganizationUnits(consequences)); - dtos.AddRange(MapMovedOrganizationUnits(consequences)); - dtos.AddRange(MapRemovedOrganizationUnits(consequences)); - dtos.AddRange(MapConvertedOrganizationUnits(consequences)); + private ConnectionUpdateConsequencesResponseDTO MapUpdateConsequencesResponseDTO(OrganizationTreeUpdateConsequences consequences) + { + var logEntries = consequences + .ConvertConsequencesToConsequenceLogs() + .Transform(MapConsequenceLogsToDtos) + .Transform(OrderLogEntries); + return new ConnectionUpdateConsequencesResponseDTO { - Consequences = dtos - .OrderBy(x => x.Name) - .ThenBy(x => x.Category) - .ToList() + Consequences = logEntries }; } - private static IEnumerable MapConvertedOrganizationUnits(OrganizationTreeUpdateConsequences consequences) + private static List OrderLogEntries(IEnumerable logEntries) { - return consequences - .DeletedExternalUnitsBeingConvertedToNativeUnits - .Select(converted => new ConnectionUpdateOrganizationUnitConsequenceDTO - { - Name = converted.Name, - Category = ConnectionUpdateOrganizationUnitChangeCategory.Converted, - Uuid = converted.ExternalOriginUuid.GetValueOrDefault(), - Description = $"'{converted.Name}' er slettet i FK Organisation men konverteres til KITOS enhed, da den anvendes aktivt i KITOS." - }) + var consequenceDtos = logEntries + .OrderBy(x => x.Name) + .ThenBy(x => x.Category) .ToList(); + return consequenceDtos; } - private static IEnumerable MapRemovedOrganizationUnits(OrganizationTreeUpdateConsequences consequences) + private static StsOrganizationOrgUnitDTO MapOrganizationUnitDTO(ExternalOrganizationUnit organizationUnit) { - return consequences - .DeletedExternalUnitsBeingDeleted - .Select(deleted => new ConnectionUpdateOrganizationUnitConsequenceDTO - { - Name = deleted.Name, - Category = ConnectionUpdateOrganizationUnitChangeCategory.Deleted, - Uuid = deleted.ExternalOriginUuid.GetValueOrDefault(), - Description = $"'{deleted.Name}' slettes." - }) - .ToList(); + return new StsOrganizationOrgUnitDTO + { + Uuid = organizationUnit.Uuid, + Name = organizationUnit.Name, + Children = organizationUnit + .Children + .OrderBy(x => x.Name) + .Select(MapOrganizationUnitDTO) + .ToList() + }; } - - private static IEnumerable MapMovedOrganizationUnits(OrganizationTreeUpdateConsequences consequences) + private static IEnumerable MapChangeLogResponseDtos(IEnumerable logs) { - return consequences - .OrganizationUnitsBeingMoved - .Select(moved => - { - var (movedUnit, oldParent, newParent) = moved; - return new ConnectionUpdateOrganizationUnitConsequenceDTO - { - Name = movedUnit.Name, - Category = ConnectionUpdateOrganizationUnitChangeCategory.Moved, - Uuid = movedUnit.ExternalOriginUuid.GetValueOrDefault(), - Description = $"'{movedUnit.Name}' flyttes fra at være underenhed til '{oldParent.Name}' til fremover at være underenhed for {newParent.Name}" - }; - }) - .ToList(); + return logs.Select(MapChangeLogResponseDto).ToList(); } - private static IEnumerable MapRenamedOrganizationUnits(OrganizationTreeUpdateConsequences consequences) + private static StsOrganizationChangeLogResponseDTO MapChangeLogResponseDto(IExternalConnectionChangelog log) { - return consequences - .OrganizationUnitsBeingRenamed - .Select(renamed => - { - var (affectedUnit, oldName, newName) = renamed; - return new ConnectionUpdateOrganizationUnitConsequenceDTO - { - Name = oldName, - Category = ConnectionUpdateOrganizationUnitChangeCategory.Renamed, - Uuid = affectedUnit.ExternalOriginUuid.GetValueOrDefault(), - Description = $"'{oldName}' omdøbes til '{newName}'" - }; - }) - .ToList(); + return new StsOrganizationChangeLogResponseDTO + { + Origin = log.ResponsibleType.ToStsOrganizationChangeLogOriginOption(), + User = log.ResponsibleUser.FromNullable().Select(x => x.MapToUserWithEmailDTO()).GetValueOrDefault(), + Consequences = MapConsequenceLogsToDtos(log.GetEntries()), + LogTime = log.LogTime + }; } - private static IEnumerable MapAddedOrganizationUnits(OrganizationTreeUpdateConsequences consequences) + private static IEnumerable MapConsequenceLogsToDtos(IEnumerable logs) { - return consequences - .AddedExternalOrganizationUnits - .Select(added => new ConnectionUpdateOrganizationUnitConsequenceDTO - { - Name = added.unitToAdd.Name, - Category = ConnectionUpdateOrganizationUnitChangeCategory.Added, - Uuid = added.unitToAdd.Uuid, - Description = $"'{added.unitToAdd.Name}' tilføjes som underenhed til '{added.parent.Name}'" - } - ) + return logs + .Select(MapConsequenceToDto) + .Transform(OrderLogEntries) .ToList(); } - private static StsOrganizationOrgUnitDTO MapOrganizationUnitDTO(ExternalOrganizationUnit organizationUnit) + private static ConnectionUpdateOrganizationUnitConsequenceDTO MapConsequenceToDto(IExternalConnectionChangeLogEntry log) { - return new StsOrganizationOrgUnitDTO + return new ConnectionUpdateOrganizationUnitConsequenceDTO { - Uuid = organizationUnit.Uuid, - Name = organizationUnit.Name, - Children = organizationUnit - .Children - .OrderBy(x => x.Name) - .Select(MapOrganizationUnitDTO) - .ToList() + Uuid = log.ExternalUnitUuid, + Name = log.Name, + Category = log.Type.ToConnectionUpdateOrganizationUnitChangeCategory(), + Description = log.Description }; } #endregion DTO Mapping diff --git a/Presentation.Web/Models/API/V1/OrganizationDTO.cs b/Presentation.Web/Models/API/V1/OrganizationDTO.cs index 029ec02ab7..8378362b9b 100644 --- a/Presentation.Web/Models/API/V1/OrganizationDTO.cs +++ b/Presentation.Web/Models/API/V1/OrganizationDTO.cs @@ -10,16 +10,13 @@ public class OrganizationDTO : NamedEntityDTO public string Email { get; set; } public string Cvr { get; set; } public string ForeignCvr { get; set; } - public int TypeId { get; set; } public AccessModifier AccessModifier { get; set; } public ConfigDTO Config { get; set; } - public OrgUnitSimpleDTO Root { get; set; } public DateTime LastChanged { get; set; } public int LastChangedByUserId { get; set; } public Guid Uuid { get; set; } - public virtual int? ContactPersonId { get; set; } public virtual UserDTO ContactPerson { get; set; } } diff --git a/Presentation.Web/Models/API/V1/Organizations/ConnectToStsOrganizationRequestDTO.cs b/Presentation.Web/Models/API/V1/Organizations/ConnectToStsOrganizationRequestDTO.cs index 78dbd0979d..fd28eb1b7a 100644 --- a/Presentation.Web/Models/API/V1/Organizations/ConnectToStsOrganizationRequestDTO.cs +++ b/Presentation.Web/Models/API/V1/Organizations/ConnectToStsOrganizationRequestDTO.cs @@ -6,5 +6,6 @@ public class ConnectToStsOrganizationRequestDTO { [Range(1, int.MaxValue)] public int? SynchronizationDepth { get; set; } + public bool? SubscribeToUpdates { get; set; } } } \ No newline at end of file diff --git a/Presentation.Web/Models/API/V1/Organizations/DisconnectFromStsOrganizationRequestDTO.cs b/Presentation.Web/Models/API/V1/Organizations/DisconnectFromStsOrganizationRequestDTO.cs new file mode 100644 index 0000000000..23d30cc34f --- /dev/null +++ b/Presentation.Web/Models/API/V1/Organizations/DisconnectFromStsOrganizationRequestDTO.cs @@ -0,0 +1,10 @@ +namespace Presentation.Web.Models.API.V1.Organizations +{ + public class DisconnectFromStsOrganizationRequestDTO + { + /// + /// If set to true, KITOS will purge all unused external units while disconnecting from STS Organization + /// + public bool PurgeUnusedExternalUnits { get; set; } + } +} \ No newline at end of file diff --git a/Presentation.Web/Models/API/V1/Organizations/OrganizationPermissionsDTO.cs b/Presentation.Web/Models/API/V1/Organizations/OrganizationPermissionsDTO.cs new file mode 100644 index 0000000000..7b93b7fc79 --- /dev/null +++ b/Presentation.Web/Models/API/V1/Organizations/OrganizationPermissionsDTO.cs @@ -0,0 +1,8 @@ +namespace Presentation.Web.Models.API.V1.Organizations +{ + public class OrganizationPermissionsDTO + { + public bool CanEditCvr { get; set; } + + } +} \ No newline at end of file diff --git a/Presentation.Web/Models/API/V1/Organizations/StsOrganizationChangeLogOriginOption.cs b/Presentation.Web/Models/API/V1/Organizations/StsOrganizationChangeLogOriginOption.cs new file mode 100644 index 0000000000..faef570098 --- /dev/null +++ b/Presentation.Web/Models/API/V1/Organizations/StsOrganizationChangeLogOriginOption.cs @@ -0,0 +1,8 @@ +namespace Presentation.Web.Models.API.V1.Organizations +{ + public enum StsOrganizationChangeLogOriginOption + { + Background = 0, + User = 1 + } +} \ No newline at end of file diff --git a/Presentation.Web/Models/API/V1/Organizations/StsOrganizationChangeLogResponseDTO.cs b/Presentation.Web/Models/API/V1/Organizations/StsOrganizationChangeLogResponseDTO.cs new file mode 100644 index 0000000000..061d4fe96a --- /dev/null +++ b/Presentation.Web/Models/API/V1/Organizations/StsOrganizationChangeLogResponseDTO.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace Presentation.Web.Models.API.V1.Organizations +{ + public class StsOrganizationChangeLogResponseDTO + { + public StsOrganizationChangeLogOriginOption Origin { get; set; } + public UserWithEmailDTO User { get; set; } + public DateTime LogTime { get; set; } + public IEnumerable Consequences { get; set; } + } +} \ No newline at end of file diff --git a/Presentation.Web/Models/API/V1/Organizations/StsOrganizationSynchronizationDetailsResponseDTO.cs b/Presentation.Web/Models/API/V1/Organizations/StsOrganizationSynchronizationDetailsResponseDTO.cs index 1968ba16e1..0129ef3527 100644 --- a/Presentation.Web/Models/API/V1/Organizations/StsOrganizationSynchronizationDetailsResponseDTO.cs +++ b/Presentation.Web/Models/API/V1/Organizations/StsOrganizationSynchronizationDetailsResponseDTO.cs @@ -1,9 +1,13 @@ -namespace Presentation.Web.Models.API.V1.Organizations +using System; + +namespace Presentation.Web.Models.API.V1.Organizations { public class StsOrganizationSynchronizationDetailsResponseDTO { public StsOrganizationAccessStatusResponseDTO AccessStatus { get; set; } public bool Connected { get; set; } + public bool SubscribesToUpdates { get; set; } + public DateTime? DateOfLatestCheckBySubscription { get; set; } public int? SynchronizationDepth { get; set; } public bool CanCreateConnection { get; set; } public bool CanUpdateConnection { get; set; } diff --git a/Presentation.Web/Models/API/V1/Organizations/UnitAccessRightsDTO.cs b/Presentation.Web/Models/API/V1/Organizations/UnitAccessRightsDTO.cs index 8269291603..c25679178d 100644 --- a/Presentation.Web/Models/API/V1/Organizations/UnitAccessRightsDTO.cs +++ b/Presentation.Web/Models/API/V1/Organizations/UnitAccessRightsDTO.cs @@ -2,6 +2,10 @@ { public class UnitAccessRightsDTO { + public UnitAccessRightsDTO() + { + } + public UnitAccessRightsDTO(bool canBeRead, bool canBeModified, bool canNameBeModified, bool canEanBeModified, bool canDeviceIdBeModified, bool canBeRearranged, bool canBeDeleted) { CanBeRead = canBeRead; @@ -13,18 +17,18 @@ public UnitAccessRightsDTO(bool canBeRead, bool canBeModified, bool canNameBeMod CanBeDeleted = canBeDeleted; } - public UnitAccessRightsDTO(UnitAccessRightsDTO other) + protected UnitAccessRightsDTO(UnitAccessRightsDTO other) : this(other.CanBeRead, other.CanBeModified, other.CanNameBeModified, other.CanEanBeModified, other.CanDeviceIdBeModified, other.CanBeRearranged, other.CanBeDeleted) { } - public bool CanBeRead { get; } - public bool CanBeModified { get; } - public bool CanNameBeModified { get; } - public bool CanEanBeModified { get; } - public bool CanDeviceIdBeModified { get; } - public bool CanBeRearranged { get; } - public bool CanBeDeleted { get; } + public bool CanBeRead { get; set; } + public bool CanBeModified { get; set; } + public bool CanNameBeModified { get; set; } + public bool CanEanBeModified { get; set; } + public bool CanDeviceIdBeModified { get; set; } + public bool CanBeRearranged { get; set; } + public bool CanBeDeleted { get; set; } } } \ No newline at end of file diff --git a/Presentation.Web/Models/API/V1/Organizations/UnitAccessRightsWithUnitIdDTO.cs b/Presentation.Web/Models/API/V1/Organizations/UnitAccessRightsWithUnitIdDTO.cs index b2b74f660b..3df122cac5 100644 --- a/Presentation.Web/Models/API/V1/Organizations/UnitAccessRightsWithUnitIdDTO.cs +++ b/Presentation.Web/Models/API/V1/Organizations/UnitAccessRightsWithUnitIdDTO.cs @@ -4,6 +4,10 @@ public class UnitAccessRightsWithUnitIdDTO : UnitAccessRightsDTO { public int UnitId { get; set; } + public UnitAccessRightsWithUnitIdDTO() + { + } + public UnitAccessRightsWithUnitIdDTO(int unitId, UnitAccessRightsDTO rights) : base(rights) { diff --git a/Presentation.Web/Ninject/KernelBuilder.cs b/Presentation.Web/Ninject/KernelBuilder.cs index 0077d3bfc6..20168e6f6d 100644 --- a/Presentation.Web/Ninject/KernelBuilder.cs +++ b/Presentation.Web/Ninject/KernelBuilder.cs @@ -369,6 +369,7 @@ private void RegisterDomainEventsEngine(IKernel kernel) //Organization RegisterDomainEvents(kernel); + RegisterDomainEvents(kernel); } private void RegisterDomainEvents(IKernel kernel) @@ -388,6 +389,7 @@ private void RegisterDomainCommandsEngine(IKernel kernel) RegisterCommands(kernel); RegisterCommands(kernel); RegisterCommands(kernel); + RegisterCommands(kernel); } private void RegisterCommands(IKernel kernel) @@ -626,6 +628,9 @@ private void RegisterBackgroundJobs(IKernel kernel) //Maintenance kernel.Bind().ToSelf().InCommandScope(Mode); + + //FK Org sync + kernel.Bind().ToSelf().InCommandScope(Mode); } } } \ No newline at end of file diff --git a/Presentation.Web/Presentation.Web.csproj b/Presentation.Web/Presentation.Web.csproj index 34abfaf910..9dd60ab73f 100644 --- a/Presentation.Web/Presentation.Web.csproj +++ b/Presentation.Web/Presentation.Web.csproj @@ -356,6 +356,8 @@ + + @@ -365,6 +367,7 @@ + @@ -374,9 +377,12 @@ + + + @@ -391,6 +397,7 @@ + @@ -404,11 +411,14 @@ + + + @@ -428,9 +438,13 @@ + + + + @@ -672,6 +686,9 @@ + + + @@ -684,6 +701,7 @@ + @@ -940,6 +958,7 @@ + @@ -1339,7 +1358,6 @@ - @@ -1423,7 +1441,6 @@ - diff --git a/Presentation.Web/Startup.cs b/Presentation.Web/Startup.cs index fbfd9b80a4..de2f508d2e 100644 --- a/Presentation.Web/Startup.cs +++ b/Presentation.Web/Startup.cs @@ -86,6 +86,11 @@ private static void InitializeHangfire(IAppBuilder app) cronExpression: Cron.Daily(), // Every night at 00:00 timeZone: TimeZoneInfo.Local); + recurringJobManager.AddOrUpdate( + recurringJobId: StandardJobIds.ScheduleFkOrgUpdates, + job: Job.FromExpression((IBackgroundJobLauncher launcher) => launcher.LaunchUpdateFkOrgSync(CancellationToken.None)), + cronExpression: Cron.Daily(1), // Every night at 01:00 + timeZone: TimeZoneInfo.Local); /****************** * ON-DEMAND JOBS * diff --git a/Presentation.Web/app/Constants/Constants.ts b/Presentation.Web/app/Constants/Constants.ts index 70eca95185..c1ceba3a57 100644 --- a/Presentation.Web/app/Constants/Constants.ts +++ b/Presentation.Web/app/Constants/Constants.ts @@ -22,6 +22,7 @@ export class Select2 { static readonly EmptyField = "\u00a0"; + static readonly UnitIndentation = "    "; } export class DateFormat { diff --git a/Presentation.Web/app/components/data-processing/data-processing-registration-overview.controller.ts b/Presentation.Web/app/components/data-processing/data-processing-registration-overview.controller.ts index dd5f593e65..ceae9a5099 100644 --- a/Presentation.Web/app/components/data-processing/data-processing-registration-overview.controller.ts +++ b/Presentation.Web/app/components/data-processing/data-processing-registration-overview.controller.ts @@ -69,6 +69,7 @@ kendoGridLauncherFactory .create() .withScope($scope) + .withOverviewType(Models.Generic.OverviewType.DataProcessingRegistration) .withGridBinding(this) .withUser(user) .withEntityTypeName("Databehandling") diff --git a/Presentation.Web/app/components/data-processing/tabs/data-processing-registration-tab-main.controller.ts b/Presentation.Web/app/components/data-processing/tabs/data-processing-registration-tab-main.controller.ts index 28e9fae370..3b278a3b2f 100644 --- a/Presentation.Web/app/components/data-processing/tabs/data-processing-registration-tab-main.controller.ts +++ b/Presentation.Web/app/components/data-processing/tabs/data-processing-registration-tab-main.controller.ts @@ -194,7 +194,9 @@ } }; }); - } + }, + null, + true ); } diff --git a/Presentation.Web/app/components/data-processing/tabs/data-processing-registration-tab-main.view.html b/Presentation.Web/app/components/data-processing/tabs/data-processing-registration-tab-main.view.html index 5cca982a04..2fdd82d125 100644 --- a/Presentation.Web/app/components/data-processing/tabs/data-processing-registration-tab-main.view.html +++ b/Presentation.Web/app/components/data-processing/tabs/data-processing-registration-tab-main.view.html @@ -24,10 +24,6 @@

{{vm.headerName}}

data-placeholder="Vælg dataansvarlig" ng-model="vm.dataResponsible" ng-disabled="!vm.hasWriteAccess" /> - - - {{vm.dataResponsible.selectedElement.optionalObjectContext.description}} - diff --git a/Presentation.Web/app/components/it-advice/it-advice-modal-dialog.ts b/Presentation.Web/app/components/it-advice/it-advice-modal-dialog.ts index 1246c4ed4a..f4de9153ed 100644 --- a/Presentation.Web/app/components/it-advice/it-advice-modal-dialog.ts +++ b/Presentation.Web/app/components/it-advice/it-advice-modal-dialog.ts @@ -32,9 +32,8 @@ //Format {email1},{email2}. Space between , and {email2} is ok but not required const emailMatchRegex = "([a-zA-Z\\-0-9\\._]+@)([a-zA-Z\\-0-9\\.]+)\\.([a-zA-Z\\-0-9\\.]+)"; $scope.multipleEmailValidationRegex = `^(${emailMatchRegex}(((,)( )*)${emailMatchRegex})*)$`; - - var payloadDateFormat = "YYYY-MM-DD"; - var allowedDateFormats = [Constants.DateFormat.DanishDateFormat, payloadDateFormat]; + + var allowedDateFormats = [Constants.DateFormat.DanishDateFormat, Constants.DateFormat.EnglishDateFormat]; var select2Roles = entityMapper.mapRoleToSelect2ViewModel(roles); if (select2Roles.length > 0) { @@ -103,10 +102,12 @@ if (isCurrentAdviceRecurring()) { payload.Name = $scope.name; payload.Scheduling = $scope.adviceRepetitionData.id; - payload.AlarmDate = moment($scope.startDate, allowedDateFormats, true).format(payloadDateFormat); + payload.AlarmDate = Helpers.DateStringFormat.fromDanishToEnglishFormat($scope.startDate); //Stopdate is optional so only parse it if present - payload.StopDate = $scope.stopDate ? moment($scope.stopDate, allowedDateFormats, true).format(payloadDateFormat) : null; + payload.StopDate = $scope.stopDate + ? Helpers.DateStringFormat.fromDanishToEnglishFormat($scope.stopDate) + : null; } if (action === "POST") { url = `Odata/advice?organizationId=${currentUser.currentOrganizationId}`; diff --git a/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-deadlines.controller.ts b/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-deadlines.controller.ts index 11727c2219..fc65c63520 100644 --- a/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-deadlines.controller.ts +++ b/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-deadlines.controller.ts @@ -148,37 +148,21 @@ vm.datepickerOptions = Kitos.Configs.standardKendoDatePickerOptions; - vm.patchDate = (field, value) => { - var date = moment(value, Kitos.Constants.DateFormat.DanishDateFormat); - if (value === "") { - var payload = {}; - payload[field] = null; - patch(payload, vm.autosaveUrl + '?organizationId=' + user.currentOrganizationId); - } else if (value == null) { - - } else if (!date.isValid() || isNaN(date.valueOf()) || date.year() < 1000 || date.year() > 2099) { - notify.addErrorMessage("Den indtastede dato er ugyldig."); + vm.patchDate = (field, value, fieldName) => { + var payload = {}; + const url = vm.autosaveUrl + "?organizationId=" + user.currentOrganizationId; - } else { - const dateString = date.format("YYYY-MM-DD"); - var payload = {}; - payload[field] = dateString; - patch(payload, vm.autosaveUrl + '?organizationId=' + user.currentOrganizationId); + if (!value) { + payload[field] = null; + patch(payload, url); } - } - vm.patchDateProcurement = (field, value, id, url) => { - var date = moment(value, Kitos.Constants.DateFormat.DanishDateFormat); - - if (!date.isValid() || isNaN(date.valueOf()) || date.year() < 1000 || date.year() > 2099) { - notify.addErrorMessage("Den indtastede dato er ugyldig."); - - } else { - var dateString = date.format("YYYY-MM-DD"); - var payload = {}; + else if (Kitos.Helpers.DateValidationHelper.validateDateInput(value, notify, fieldName, true)) { + const dateString = Kitos.Helpers.DateStringFormat.fromDanishToEnglishFormat(value); payload[field] = dateString; - patch(payload, url + id + '?organizationId=' + user.currentOrganizationId); + patch(payload, url); } } + function patch(payload, url) { var msg = notify.addInfoMessage("Gemmer...", false); $http({ method: 'PATCH', url: url, data: payload }) diff --git a/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-deadlines.view.html b/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-deadlines.view.html index 5618b8188a..48c8d1147e 100644 --- a/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-deadlines.view.html +++ b/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-deadlines.view.html @@ -78,7 +78,7 @@

{{deadlinesVm.contract.name}}

data-k-options="deadlinesVm.datepickerOptions" data-ng-disabled="!hasWriteAccess" data-ng-model="deadlinesVm.contract.irrevocableTo" - ng-blur="deadlinesVm.patchDate('irrevocableTo', contract.irrevocableTo)" + ng-blur="deadlinesVm.patchDate('irrevocableTo', contract.irrevocableTo, 'Uopsigelig til')" data-field="irrevocableTo"> @@ -97,7 +97,7 @@

{{deadlinesVm.contract.name}}

data-k-options="deadlinesVm.datepickerOptions" data-ng-disabled="!hasWriteAccess" data-ng-model="deadlinesVm.contract.terminated" - ng-blur="deadlinesVm.patchDate('terminated', contract.terminated)" + ng-blur="deadlinesVm.patchDate('terminated', contract.terminated, 'Kontrakten opsagt')" data-field="terminated"> diff --git a/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-economy.controller.ts b/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-economy.controller.ts index c69e050a1e..b20be3469f 100644 --- a/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-economy.controller.ts +++ b/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-economy.controller.ts @@ -16,7 +16,7 @@ id: String(orgUnit.id), text: orgUnit.name, indentationLevel: indentationLevel, - optionalExtraObject: orgUnit.ean + optionalObjectContext: orgUnit.ean }; options.push(option); @@ -65,15 +65,7 @@ vm.isPaymentModelEnabled = uiState.isBluePrintNodeAvailable(blueprint.children.economy.children.paymentModel); vm.isExtPaymentEnabled = uiState.isBluePrintNodeAvailable(blueprint.children.economy.children.extPayment); vm.isIntPaymentEnabled = uiState.isBluePrintNodeAvailable(blueprint.children.economy.children.intPayment); - - function convertDate(value: string): moment.Moment { - return moment(value, Kitos.Constants.DateFormat.DanishDateFormat); - } - - function isDateInvalid(date: moment.Moment) { - return !date.isValid() || isNaN(date.valueOf()) || date.year() < 1000 || date.year() > 2099; - } - + vm.patchPaymentModelDate = (field, value) => { function patchContract(payload, url) { var msg = notify.addInfoMessage("Gemmer...", false); @@ -85,22 +77,18 @@ }); } - const date = convertDate(value); + var payload = {}; + const url = vm.patchPaymentModelUrl + "?organizationId=" + user.currentOrganizationId; + if (value === "") { - var payload = {}; payload[field] = null; - patchContract(payload, vm.patchPaymentModelUrl + "?organizationId=" + user.currentOrganizationId); + patchContract(payload, url); } else if (value == null) { - } else if (isDateInvalid(date)) { - notify.addErrorMessage("Den indtastede dato er ugyldig."); - - } - else { - const dateString = date.format("YYYY-MM-DD"); - var payload = {}; + } else if (Kitos.Helpers.DateValidationHelper.validateDateInput(value, notify, "Driftsvederlag påbegyndt", false)){ + const dateString = Kitos.Helpers.DateStringFormat.fromDanishToEnglishFormat(value); payload[field] = dateString; - patchContract(payload, vm.patchPaymentModelUrl + "?organizationId=" + user.currentOrganizationId); + patchContract(payload, url); } } @@ -150,7 +138,7 @@ stream.ean = " - "; if (stream.organizationUnitId !== null && stream.organizationUnitId !== undefined) { - stream.ean = stream.organizationUnitId.optionalExtraObject; + stream.ean = stream.organizationUnitId.optionalObjectContext; } }; stream.updateEan = updateEan; @@ -179,21 +167,17 @@ vm.newIntern = () => { postStream("InternPaymentForId", "OrganizationId"); }; - vm.patchDate = (field, value, id) => { - const date = convertDate(value); - if (value === "") { - var payload = {}; - payload[field] = null; - patch(payload, `api/EconomyStream/?id=${id}&organizationId=${user.currentOrganizationId}`); - } else if (isDateInvalid(date)) { - notify.addErrorMessage("Den indtastede dato er ugyldig."); + vm.patchDate= (field, value, id, fieldName) => { + const url = `api/EconomyStream/?id=${id}&organizationId=${user.currentOrganizationId}`; + var payload = {}; - } - else { - const dateString = date.format("YYYY-MM-DD"); - var payload = {}; + if (!value) { + payload[field] = null; + patch(payload, url); + } else if (Kitos.Helpers.DateValidationHelper.validateDateInput(value, notify, fieldName, true)){ + const dateString = Kitos.Helpers.DateStringFormat.fromDanishToEnglishFormat(value); payload[field] = dateString; - patch(payload, `api/EconomyStream/?id=${id}&organizationId=${user.currentOrganizationId}`); + patch(payload, url); } } function patch(payload, url) { diff --git a/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-economy.view.html b/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-economy.view.html index a7347706b4..2bcd3a019c 100644 --- a/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-economy.view.html +++ b/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-economy.view.html @@ -151,7 +151,7 @@

{{contractEconomyVm.contract.name}}

data-k-options="contractEconomyVm.datepickerOptions" data-ng-disabled="!hasWriteAccess" data-ng-model="stream.auditDate" - ng-blur="contractEconomyVm.patchDate('auditDate', stream.auditDate, stream.id)" + ng-blur="contractEconomyVm.patchDate('auditDate', stream.auditDate, stream.id, 'Dato')" data-field="auditDate"> @@ -287,7 +287,7 @@

{{contractEconomyVm.contract.name}}

data-k-options="contractEconomyVm.datepickerOptions" data-ng-disabled="!hasWriteAccess" data-ng-model="stream.auditDate" - ng-blur="contractEconomyVm.patchDate('auditDate', stream.auditDate, stream.id)" + ng-blur="contractEconomyVm.patchDate('auditDate', stream.auditDate, stream.id, 'Dato')" data-field="auditDate"> diff --git a/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-main.controller.ts b/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-main.controller.ts index cb587def91..acb52cb82d 100644 --- a/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-main.controller.ts +++ b/Presentation.Web/app/components/it-contract/tabs/it-contract-tab-main.controller.ts @@ -252,7 +252,7 @@ .then(_ => reloadValidationStatus()); } else if (Kitos.Helpers.DateValidationHelper.validateValidityPeriod(concluded, expirationDate, notify, "Gyldig fra", "Gyldig til")) { - const dateString = moment(value, [Kitos.Constants.DateFormat.DanishDateFormat, Kitos.Constants.DateFormat.EnglishDateFormat]).format(Kitos.Constants.DateFormat.EnglishDateFormat); + const dateString = Kitos.Helpers.DateStringFormat.fromDanishToEnglishFormat(value); payload[field] = dateString; patch(payload, $scope.autosaveUrl2 + '?organizationId=' + user.currentOrganizationId) .then(_ => reloadValidationStatus()); diff --git a/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-GDPR.controller.ts b/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-GDPR.controller.ts index e082fed186..45d1d3b1a2 100644 --- a/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-GDPR.controller.ts +++ b/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-GDPR.controller.ts @@ -78,18 +78,16 @@ , onError => notify.addErrorMessage("Fejl! Feltet kunne ikke opdateres!")); } - $scope.patchDate = (field, value) => { - var date = moment(value, Kitos.Constants.DateFormat.DanishDateFormat); + $scope.patchDate = (field, value, fieldName) => { var payload = {}; - if (value === "" || value == undefined) { + if (!value) { payload[field] = null; itSystemUsageService.patchSystemUsage(itSystemUsage.id, user.currentOrganizationId, payload) .then(onSuccess => notify.addSuccessMessage("Feltet er opdateret!") , onError => notify.addErrorMessage("Fejl! Feltet kunne ikke opdateres!")); - } else if (!date.isValid() || isNaN(date.valueOf()) || date.year() < 1000 || date.year() > 2099) { - notify.addErrorMessage("Den indtastede dato er ugyldig."); - } else { - date = date.format("YYYY-MM-DD"); + } + else if (Kitos.Helpers.DateValidationHelper.validateDateInput(value, notify, fieldName, true)) { + var date = Kitos.Helpers.DateStringFormat.fromDanishToEnglishFormat(value); payload[field] = date; itSystemUsageService.patchSystemUsage(itSystemUsage.id, user.currentOrganizationId, payload) .then(onSuccess => notify.addSuccessMessage("Feltet er opdateret!") diff --git a/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-GDPR.view.html b/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-GDPR.view.html index f857b4e010..54f3ce9357 100644 --- a/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-GDPR.view.html +++ b/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-GDPR.view.html @@ -141,127 +141,125 @@

{{systemUsageName}}

- -
-
- + +
+ +
+ + +
+ +
- - -
- -
-
- -
- - Kryptering
+ +
+ + Kryptering
- - Pseudonomisering
+ + Pseudonomisering
- - Adgangsstyring
+ + Adgangsstyring
- - Logning
-
+ + Logning
- +
+
-
+
+
+ + +
+
- - +
+ + Eksempelvis "25-04-2017"
-
-
-
- - Eksempelvis "25-04-2017" -
- @@ -287,7 +285,7 @@

{{systemUsageName}}

data-k-options="datepickerOptions" ng-disabled="!hasWriteAccess" ng-model="usage.riskAssesmentDate" - ng-blur="patchDate('riskAssesmentDate', usage.riskAssesmentDate)" + ng-blur="patchDate('riskAssesmentDate', usage.riskAssesmentDate, 'Dato for seneste risikovurdering')" data-field="riskAssesmentDate"> Eksempelvis "25-04-2017"
@@ -363,7 +361,7 @@

{{systemUsageName}}

data-k-options="datepickerOptions" ng-disabled="!hasWriteAccess" ng-model="usage.dpiaDateFor" - ng-blur="patchDate('DPIADateFor', usage.dpiaDateFor)" + ng-blur="patchDate('DPIADateFor', usage.dpiaDateFor, 'Dato for den seneste DPIA')" data-field="DPIADateFor"> Eksempelvis "25-04-2017"
@@ -417,7 +415,7 @@

{{systemUsageName}}

data-k-options="datepickerOptions" ng-disabled="!hasWriteAccess" ng-model="usage.dpiaDeleteDate" - ng-blur="patchDate('DPIAdeleteDate', usage.dpiaDeleteDate)" + ng-blur="patchDate('DPIAdeleteDate', usage.dpiaDeleteDate, 'Dato for hvornår der må foretages sletning af data i systemet næste gang')" data-field="DPIAdeleteDate"> Eksempelvis "25-04-2017"
diff --git a/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-archiving.controller.ts b/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-archiving.controller.ts index 39069ce918..dde19ea999 100644 --- a/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-archiving.controller.ts +++ b/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-archiving.controller.ts @@ -82,7 +82,7 @@ let dateList = []; let dateNotList = []; _.each($scope.archivePeriods, x => { - var formatDateString = "YYYY-MM-DD"; + var formatDateString = Kitos.Constants.DateFormat.EnglishDateFormat; if (moment().isBetween(moment(x.StartDate, [Kitos.Constants.DateFormat.DanishDateFormat, formatDateString]).startOf('day'), moment(x.EndDate, [Kitos.Constants.DateFormat.DanishDateFormat, formatDateString]).endOf('day'), null, '[]')) { dateList.push(x); } else { @@ -116,21 +116,14 @@ $scope.save = () => { $scope.$broadcast("show-errors-check-validity"); + + var startDate = $scope.archivePeriod.startDate; + var endDate = $scope.archivePeriod.endDate; + + if (Kitos.Helpers.DateValidationHelper.validateValidityPeriod(startDate, endDate, notify, "Startdato", "Slutdato")) { + startDate = Kitos.Helpers.DateStringFormat.fromDanishToEnglishFormat(startDate); + endDate= Kitos.Helpers.DateStringFormat.fromDanishToEnglishFormat(endDate); - var startDate = moment($scope.archivePeriod.startDate, Kitos.Constants.DateFormat.DanishDateFormat); - var endDate = moment($scope.archivePeriod.endDate, Kitos.Constants.DateFormat.DanishDateFormat); - var startDateValid = !startDate.isValid() || isNaN(startDate.valueOf()) || startDate.year() < 1000 || startDate.year() > 2099; - var endDateValid = !endDate.isValid() || isNaN(endDate.valueOf()) || endDate.year() < 1000 || endDate.year() > 2099; - var dateCheck = startDate.startOf('day') >= endDate.endOf('day'); - if (startDateValid || endDateValid) { - notify.addErrorMessage("Den indtastede dato er ugyldig."); { return; }; - } - else if (dateCheck) { - notify.addErrorMessage("Den indtastede slutdato er før startdatoen."); { return; }; - } - else { - startDate = startDate.format("YYYY-MM-DD"); - endDate = endDate.format("YYYY-MM-DD"); var payload = {}; payload["StartDate"] = startDate; payload["EndDate"] = endDate; @@ -173,28 +166,26 @@ }, "q", Kitos.Helpers.Select2OptionsFormatHelper.formatOrganizationWithCvr); $scope.patchDatePeriode = (field, value, id) => { - var formatDateString = "YYYY-MM-DD"; - - var date = moment(value, Kitos.Constants.DateFormat.DanishDateFormat); var dateObject = $scope.archivePeriods.filter(x => x.Id === id); - var dateObjectStart = moment(dateObject[0].StartDate, [Kitos.Constants.DateFormat.DanishDateFormat, formatDateString]).startOf('day'); - var dateObjectEnd = moment(dateObject[0].EndDate, [Kitos.Constants.DateFormat.DanishDateFormat, formatDateString]).endOf('day'); - if (!date.isValid() || isNaN(date.valueOf()) || date.year() < 1000 || date.year() > 2099) { - notify.addErrorMessage("Den indtastede dato er ugyldig."); - } - else if (dateObjectStart >= dateObjectEnd) { - $scope.archivePeriods = archivePeriod; - notify.addErrorMessage("Den indtastede slutdato er før startdatoen."); + if (dateObject.length === 0) { + console.log(`Archive period with id: ${id} wasn't found`); + notify.addSuccessMessage("Feltet er opdateret!"); + return; } - else { - date = date.format("YYYY-MM-DD"); + + var dateStart = dateObject[0].StartDate; + var dateEnd = dateObject[0].EndDate; + + if (Kitos.Helpers.DateValidationHelper.validateValidityPeriod(dateStart, dateEnd, notify, "Startdato", "Slutdato")) { + const dateString = Kitos.Helpers.DateStringFormat.fromDanishToEnglishFormat(value); var payload = {}; - payload[field] = date; + payload[field] = dateString; sortDate(); $http.patch(`odata/ArchivePeriods(${id})`, payload).finally(reload); notify.addSuccessMessage("Datoen er opdateret!"); } } + $scope.patchPeriode = (field, value, id) => { var payload = {}; payload[field] = value; @@ -202,9 +193,7 @@ notify.addSuccessMessage("Feltet er opdateret!"); } - $scope.datepickerOptions = { - format: "dd-MM-yyyy" - }; + $scope.datepickerOptions = Kitos.Configs.standardKendoDatePickerOptions; }]); })(angular, app); diff --git a/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-main.view.html b/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-main.view.html index d2ea18cdce..2ccbd2e1fc 100644 --- a/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-main.view.html +++ b/Presentation.Web/app/components/it-system/usage/tabs/it-system-usage-tab-main.view.html @@ -100,7 +100,10 @@

{{systemUsageName}}

- +
diff --git a/Presentation.Web/app/components/local-config/import/fk-organization-import-break-connection-prompt.view.html b/Presentation.Web/app/components/local-config/import/fk-organization-import-break-connection-prompt.view.html new file mode 100644 index 0000000000..ccc5e8aba3 --- /dev/null +++ b/Presentation.Web/app/components/local-config/import/fk-organization-import-break-connection-prompt.view.html @@ -0,0 +1,16 @@ +
+ Brydes forbindelsen til FK Organisation vil alle organisationsenheder, som er importeret fra FK Organisation, blive konverteret til KITOS enheder. +
+
+ Oprettes forbindelsen igen efterfølgende, vil der altså kunne optræde enheder med samme navn men med forskelligt "ophav". +
+
+ For at lette den administrative byrde har du derfor følgende muligheder: +
+
+
    +
  • Slet ubrugte organisationseneheder: Vælges denne mulighed slettes alle organisationseneheder (fra FK Organisation), hvortil der ikke er knyttet en registrering. De resterende konverteres til KITOS enheder.
  • +
    +
  • Bevar organisationshierarkiet: Vælges denne mulighed konverteres alle organisationsenheder fra FK Organisation til KITOS enheder.
  • +
+
\ No newline at end of file diff --git a/Presentation.Web/app/components/local-config/import/fk-organization-import-change-log.component.ts b/Presentation.Web/app/components/local-config/import/fk-organization-import-change-log.component.ts new file mode 100644 index 0000000000..d1b3721ca2 --- /dev/null +++ b/Presentation.Web/app/components/local-config/import/fk-organization-import-change-log.component.ts @@ -0,0 +1,39 @@ +module Kitos.LocalAdmin.Components { + "use strict"; + + function setupComponent(): ng.IComponentOptions { + return { + bindings: { + changeLog: "<" + }, + controller: FkOrganizationImportChangeLogController, + controllerAs: "ctrl", + templateUrl: `app/components/local-config/import/fk-organization-import-change-log.view.html` + }; + } + + interface IFkOrganizationImportChangeLogController{ + changeLog: Models.Api.Organization.ConnectionChangeLogDTO; + responsibleEntityText: string; + logTime: string; + } + + class FkOrganizationImportChangeLogController implements IFkOrganizationImportChangeLogController { + changeLog: Models.Api.Organization.ConnectionChangeLogDTO | null = null; + responsibleEntityText: string | null = null; + logTime: string | null = null; + + $onInit() { + if (!this.changeLog) { + console.error("Missing parameter 'changeLog'"); + return; + } + + this.responsibleEntityText = Helpers.ConnectionChangeLogHelper.getResponsibleEntityTextBasedOnOrigin(this.changeLog); + this.logTime = Helpers.RenderFieldsHelper.renderDate(this.changeLog.logTime); + } + } + + angular.module("app") + .component("fkOrganizationImportChangeLog", setupComponent()); +} \ No newline at end of file diff --git a/Presentation.Web/app/components/local-config/import/fk-organization-import-change-log.view.html b/Presentation.Web/app/components/local-config/import/fk-organization-import-change-log.view.html new file mode 100644 index 0000000000..cc465f050b --- /dev/null +++ b/Presentation.Web/app/components/local-config/import/fk-organization-import-change-log.view.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + +
AnsvarligAntal opdateringerDato
{{:: ctrl.responsibleEntityText }}{{:: ctrl.changeLog.consequences.length }}{{:: ctrl.logTime }}
+
+ +
\ No newline at end of file diff --git a/Presentation.Web/app/components/local-config/import/fk-organization-import-change-logs-root.component.ts b/Presentation.Web/app/components/local-config/import/fk-organization-import-change-logs-root.component.ts new file mode 100644 index 0000000000..15dfce6780 --- /dev/null +++ b/Presentation.Web/app/components/local-config/import/fk-organization-import-change-logs-root.component.ts @@ -0,0 +1,83 @@ +module Kitos.LocalAdmin.Components { + "use strict"; + + function setupComponent(): ng.IComponentOptions { + return { + bindings: { + organizationUuid: "@" + }, + controller: FkOrganizationImportChangeLogRootController, + controllerAs: "ctrl", + templateUrl: `app/components/local-config/import/fk-organization-import-change-logs-root.view.html` + }; + } + + interface ISelect2ChangeLogModel { + selectedElement: Models.ViewModel.Generic.Select2OptionViewModel; + select2Config: any, + elementSelected: (newElement: Models.ViewModel.Generic.Select2OptionViewModel) => void; + } + + interface IFkOrganizationImportChangeLogRootController { + organizationUuid: string; + } + + class FkOrganizationImportChangeLogRootController implements IFkOrganizationImportChangeLogRootController { + organizationUuid: string | null = null; + + changeLogs: Array = []; + selectedChangeLog: Models.ViewModel.Generic.Select2OptionViewModel | null = null; + + isChangeLogLoaded = false; + + selectChangeLogModel: ISelect2ChangeLogModel; + + private readonly maxNumberOfLogs = 5; + + static $inject: string[] = ["stsOrganizationSyncService", "select2LoadingService"]; + constructor( + private readonly stsOrganizationSyncService: Services.Organization.IStsOrganizationSyncService, + private readonly select2LoadingService: Services.ISelect2LoadingService) { + } + + $onInit() { + if (!this.organizationUuid) { + console.error("Missing parameter 'organizationUuid'"); + return; + } + + this.stsOrganizationSyncService.getConnectionChangeLogs(this.organizationUuid, this.maxNumberOfLogs) + .then( + response => { + this.changeLogs.pushArray(response); + this.changeLogs.forEach((x, index) => x.id = index); + + this.bindChangeLogModel(); + this.isChangeLogLoaded = true; + }, + error => { + console.log(error); + }); + + } + + bindChangeLogModel() { + var optionMap = Helpers.ConnectionChangeLogHelper.createDictionaryFromChangeLogList(this.changeLogs); + const options = this.changeLogs.map(option => optionMap[option.id]); + + this.selectChangeLogModel = { + selectedElement: this.selectedChangeLog, + select2Config: this.select2LoadingService.select2LocalDataNoSearch( + () => options, + true, + (changeLog: { optionalObjectContext: Kitos.Models.ViewModel.Organization.IFkOrganizationConnectionChangeLogsViewModel }) => Helpers.Select2OptionsFormatHelper.formatChangeLog(changeLog.optionalObjectContext)), + elementSelected: (newElement: Models.ViewModel.Generic.Select2OptionViewModel) => { + this.selectedChangeLog = newElement; + } + }; + } + } + + angular.module("app") + .component("fkOrganizationImportChangeLogsRoot", setupComponent()); +} \ No newline at end of file diff --git a/Presentation.Web/app/components/local-config/import/fk-organization-import-change-logs-root.view.html b/Presentation.Web/app/components/local-config/import/fk-organization-import-change-logs-root.view.html new file mode 100644 index 0000000000..63f17d062b --- /dev/null +++ b/Presentation.Web/app/components/local-config/import/fk-organization-import-change-logs-root.view.html @@ -0,0 +1,10 @@ + +
+ + +
+
+
+ +
+
diff --git a/Presentation.Web/app/components/local-config/import/fk-organization-import-config-import-modal.dialog.ts b/Presentation.Web/app/components/local-config/import/fk-organization-import-config-import-modal.dialog.ts index 1a8c7fc373..2ab5116a10 100644 --- a/Presentation.Web/app/components/local-config/import/fk-organization-import-config-import-modal.dialog.ts +++ b/Presentation.Web/app/components/local-config/import/fk-organization-import-config-import-modal.dialog.ts @@ -5,14 +5,14 @@ } export interface IFKOrganisationImportDialogFactory { - open(flow: FKOrganisationImportFlow, organizationUuid: string, synchronizationDepth: number | null): ng.ui.bootstrap.IModalInstanceService + open(flow: FKOrganisationImportFlow, organizationUuid: string, synchronizationDepth: number | null, subscribesToUpdates: boolean): ng.ui.bootstrap.IModalInstanceService } export class FKOrganisationImportDialogFactory implements IFKOrganisationImportDialogFactory { static $inject = ["$uibModal"]; constructor(private readonly $uibModal: ng.ui.bootstrap.IModalService) { } - open(flow: FKOrganisationImportFlow, organizationUuid: string, synchronizationDepth: number | null): ng.ui.bootstrap.IModalInstanceService { + open(flow: FKOrganisationImportFlow, organizationUuid: string, synchronizationDepth: number | null, subscribesToUpdates: boolean): ng.ui.bootstrap.IModalInstanceService { return this.$uibModal.open({ windowClass: "modal fade in wide-modal", templateUrl: "app/components/local-config/import/fk-organization-import-config-import-modal.view.html", @@ -21,7 +21,8 @@ resolve: { "flow": [() => flow], "orgUuid": [() => organizationUuid], - "synchronizationDepth": [() => synchronizationDepth] + "synchronizationDepth": [() => synchronizationDepth], + "subscribesToUpdates": [() => subscribesToUpdates] }, backdrop: "static", //Make sure accidental click outside the modal does not close it during the import process }); @@ -29,21 +30,24 @@ } class FKOrganisationImportController { - static $inject = ["flow", "orgUuid", "synchronizationDepth", "stsOrganizationSyncService", "$uibModalInstance", "notify"]; + static $inject = ["flow", "orgUuid", "synchronizationDepth", "subscribesToUpdates", "stsOrganizationSyncService", "$uibModalInstance", "notify"]; isConsequencesCollapsed: boolean = false; isHierarchyCollapsed: boolean = false; busy: boolean = false; updating: boolean = false; loadingHierarchy: boolean | null; + subscribesToUpdates: boolean = false; consequencesAwaitingApproval: Array | null = null; fkOrgHierarchy: Kitos.Shared.Components.Organization.IOrganizationTreeComponentOptions | null = null; constructor( readonly flow: FKOrganisationImportFlow, private readonly organizationUuid: string, initialImportDepth: number | null, + subscribesToUpdates: boolean, private readonly stsOrganizationSyncService: Services.Organization.IStsOrganizationSyncService, private readonly $uibModalInstance: ng.ui.bootstrap.IModalServiceInstance, private readonly notify) { + this.subscribesToUpdates = subscribesToUpdates; this.fkOrgHierarchy = { availableLevels: initialImportDepth, root: null @@ -102,11 +106,11 @@ } private performUpdate() { - + this.updating = true; this.busy = true; - return this.stsOrganizationSyncService.updateConnection(this.organizationUuid, this.fkOrgHierarchy.availableLevels) + return this.stsOrganizationSyncService.updateConnection(this.organizationUuid, this.fkOrgHierarchy.availableLevels, this.subscribesToUpdates) .then(() => { this.closeDialog(); }, error => { @@ -119,7 +123,7 @@ private createConnection() { this.stsOrganizationSyncService - .createConnection(this.organizationUuid, this.fkOrgHierarchy.availableLevels) + .createConnection(this.organizationUuid, this.fkOrgHierarchy.availableLevels, this.subscribesToUpdates) .then(() => { this.closeDialog(); }, error => { diff --git a/Presentation.Web/app/components/local-config/import/fk-organization-import-config-import-modal.view.html b/Presentation.Web/app/components/local-config/import/fk-organization-import-config-import-modal.view.html index 0ed0df7ddf..38ea35cc5a 100644 --- a/Presentation.Web/app/components/local-config/import/fk-organization-import-config-import-modal.view.html +++ b/Presentation.Web/app/components/local-config/import/fk-organization-import-config-import-modal.view.html @@ -32,6 +32,25 @@
+
+
+
+ +
+
+
+ +
+
+
+
diff --git a/Presentation.Web/app/components/local-config/import/fk-organization-import-config.component.ts b/Presentation.Web/app/components/local-config/import/fk-organization-import-config.component.ts index c9322fa976..b22dacdb75 100644 --- a/Presentation.Web/app/components/local-config/import/fk-organization-import-config.component.ts +++ b/Presentation.Web/app/components/local-config/import/fk-organization-import-config.component.ts @@ -15,6 +15,7 @@ enum CommandCategory { Create = "create", Update = "update", + Unsubscribe = "unsubscribe", Delete = "delete" } @@ -28,6 +29,7 @@ interface IFkOrganizationSynchronizationStatus { connected: boolean + subscribesToUpdates: boolean synchronizationDepth: number | null } @@ -45,13 +47,15 @@ accessGranted: boolean | null = null; accessError: string | null = null; synchronizationStatus: IFkOrganizationSynchronizationStatus | null = null; + dateOfLatestSubscriptionCheck: string | null; commands: Array | null = null; busy: boolean = false; - static $inject: string[] = ["stsOrganizationSyncService", "fkOrganisationImportDialogFactory"]; + static $inject: string[] = ["stsOrganizationSyncService", "fkOrganisationImportDialogFactory", "genericPromptFactory"]; constructor( private readonly stsOrganizationSyncService: Kitos.Services.Organization.IStsOrganizationSyncService, - private readonly fkOrganisationImportDialogFactory: Kitos.LocalAdmin.FkOrganisation.Modals.IFKOrganisationImportDialogFactory) { + private readonly fkOrganisationImportDialogFactory: Kitos.LocalAdmin.FkOrganisation.Modals.IFKOrganisationImportDialogFactory, + private readonly genericPromptFactory: Kitos.Shared.Generic.Prompt.IGenericPromptFactory) { } $onInit() { @@ -78,6 +82,11 @@ this.bindAccessProperties(result); this.bindSynchronizationStatus(result); this.bindCommands(result); + if (result.dateOfLatestCheckBySubscription !== null) { + this.dateOfLatestSubscriptionCheck = Helpers.RenderFieldsHelper.renderDate(result.dateOfLatestCheckBySubscription); + } else { + this.dateOfLatestSubscriptionCheck = result.subscribesToUpdates ? "Ikke tilgængeligt" : "Ikke relevant"; + } }, error => { console.error(error); this.accessGranted = false; @@ -87,6 +96,7 @@ private bindCommands(result: Models.Api.Organization.StsOrganizationSynchronizationStatusResponseDTO) { const newCommands: Array = []; + if (result.connected) { newCommands.push({ id: "updateSync", @@ -95,33 +105,76 @@ enabled: result.canUpdateConnection, onClick: () => { this.fkOrganisationImportDialogFactory - .open(Kitos.LocalAdmin.FkOrganisation.Modals.FKOrganisationImportFlow.Update, this.currentOrganizationUuid, this.synchronizationStatus.synchronizationDepth) + .open(Kitos.LocalAdmin.FkOrganisation.Modals.FKOrganisationImportFlow.Update, this.currentOrganizationUuid, this.synchronizationStatus.synchronizationDepth, this.synchronizationStatus.subscribesToUpdates) .closed.then(() => { //Reload state from backend if the dialog was closed this.loadState(); }); } }); + if (result.subscribesToUpdates) { + newCommands.push({ + id: "breakSubscription", + text: "Afbryd automatisk import", + category: CommandCategory.Unsubscribe, + enabled: result.canUpdateConnection, + onClick: () => { + if (confirm("Afbryd automatisk import af ændringer fra FK Organistion?")) { + this.busy = true; + this.stsOrganizationSyncService + .unsubscribeFromAutomaticUpdates(this.currentOrganizationUuid) + .then(success => { + if (success) { + this.loadState(); + } else { + this.busy = false; + } + }, _ => { + this.busy = false; + }); + } + } + }); + } + newCommands.push({ id: "breakSync", - text: "Afbryd", + text: "Bryd forbindelsen til FK Organisation", category: CommandCategory.Delete, enabled: result.canDeleteConnection, onClick: () => { - if (confirm("Afbryd forbindelsen til FK Organisation? Ved afbrydelse af forbindelsen, konverteres alle organisationsenheder til KITOS enheder, hvorefter de frit kan redigeres.")) { - this.busy = true; - this.stsOrganizationSyncService - .disconnect(this.currentOrganizationUuid) - .then(success => { - if (success) { - this.loadState(); - } else { - this.busy = false; - } - }, _ => { - this.busy = false; - }); - } + this.genericPromptFactory.open({ + title: "Bryd forbindelsen til FK Organisation", + bodyTemplatePath: "app/components/local-config/import/fk-organization-import-break-connection-prompt.view.html", + includeStandardCancelButton: true, + commands: [{ + category: Shared.Generic.Prompt.GenericCommandCategory.Primary, + text: "Slet ubrugte organisationseneheder", + value: true + }, + { + category: Shared.Generic.Prompt.GenericCommandCategory.Primary, + text: "Bevar organisationshierarkiet", + value: false + }] + }).result.then((purgeOnDisconnect: boolean) => { + { + if (purgeOnDisconnect != undefined) { + this.busy = true; + this.stsOrganizationSyncService + .disconnect(this.currentOrganizationUuid, purgeOnDisconnect) + .then(success => { + if (success) { + this.loadState(); + } else { + this.busy = false; + } + }, _ => { + this.busy = false; + }); + } + } + }); } }); } else { @@ -132,7 +185,7 @@ enabled: result.canCreateConnection, onClick: () => { this.fkOrganisationImportDialogFactory - .open(Kitos.LocalAdmin.FkOrganisation.Modals.FKOrganisationImportFlow.Create, this.currentOrganizationUuid, null) + .open(Kitos.LocalAdmin.FkOrganisation.Modals.FKOrganisationImportFlow.Create, this.currentOrganizationUuid, null, false) .closed.then(() => { //Reload state from backend if the dialog was closed this.loadState(); @@ -147,7 +200,8 @@ private bindSynchronizationStatus(result: Models.Api.Organization.StsOrganizationSynchronizationStatusResponseDTO) { this.synchronizationStatus = { connected: result.connected, - synchronizationDepth: result.synchronizationDepth + synchronizationDepth: result.synchronizationDepth, + subscribesToUpdates: result.subscribesToUpdates }; } diff --git a/Presentation.Web/app/components/local-config/import/fk-organization-import-config.view.html b/Presentation.Web/app/components/local-config/import/fk-organization-import-config.view.html index d3e3bcee81..f91898842f 100644 --- a/Presentation.Web/app/components/local-config/import/fk-organization-import-config.view.html +++ b/Presentation.Web/app/components/local-config/import/fk-organization-import-config.view.html @@ -14,22 +14,49 @@
-
- - Organisationen er forbundet til FK Organisation - - - - KITOS er forbundet i "{{::ctrl.synchronizationStatus.synchronizationDepth}}" niveauer fra FK Organisation. - - Organisationen er ikke forbundet til FK Organisation +
+
+ + + + + + + + + + + + + + + +
Niveauer der synkroniseresImporterer automatisk (dagligt) ændringerDato for seneste automatiske tjek
{{::ctrl.synchronizationStatus.synchronizationDepth === null ? 'Alle' : ctrl.synchronizationStatus.synchronizationDepth}}{{::ctrl.synchronizationStatus.subscribesToUpdates ? 'Ja' : 'Nej'}}{{::ctrl.dateOfLatestSubscriptionCheck}}
+
+
Organisationen er ikke forbundet til FK Organisation
+
- +
diff --git a/Presentation.Web/app/components/org/basicInformation/org-GDPR.controller.ts b/Presentation.Web/app/components/org/basicInformation/org-GDPR.controller.ts index 938a5bf43b..9f452ea4a8 100644 --- a/Presentation.Web/app/components/org/basicInformation/org-GDPR.controller.ts +++ b/Presentation.Web/app/components/org/basicInformation/org-GDPR.controller.ts @@ -3,32 +3,38 @@ class OrganizationGDPRController { - public static $inject: string[] = ["$http", "$timeout", "_", "$", "$state", "$scope", "notify", "user", "hasWriteAccess", - "organization", "dataResponsible", "dataProtectionAdvisor", "contactPerson", "emailExists"]; + public static $inject: string[] = [ + "$http", + "$scope", + "user", + "hasWriteAccess", + "organization", + "dataResponsible", + "dataProtectionAdvisor", + "contactPerson", + "emailExists", + "canEditCvr" + ]; public updateOrgUrl: string; public updatedataProtectionAdvisorUrl: string; public updatedataResponsibleUrl: string; public updateContactPersonUrl: string; - + public canCvrBeModified: boolean; public _$scope: any; public _contactPerson: any; public _user: any; constructor( private $http: ng.IHttpService, - private $timeout: ng.ITimeoutService, - private _: ILoDashWithMixins, - private $: JQueryStatic, - private $state: ng.ui.IStateService, - private $scope, - private notify, - private user, + $scope, + user, private hasWriteAccess, private organization, private dataResponsible, private dataProtectionAdvisor, private contactPerson, - private emailExists: boolean) { + emailExists: boolean, + canEditCvr: boolean) { this.hasWriteAccess = hasWriteAccess; this.organization = organization; @@ -43,9 +49,10 @@ this._contactPerson = contactPerson; this._user = user; + this.canCvrBeModified = canEditCvr; if (this.hasWriteAccess) { - this._$scope.$watch("_emailExists", ((newValue, oldValue) => { + this._$scope.$watch("_emailExists", ((newValue, _) => { if (newValue) { this.$http.get('odata/GetUserByEmail(email=\'' + this.contactPerson.email + '\')') .then((result) => { @@ -78,8 +85,8 @@ ], userAccessRights: ["authorizationServiceFactory", "user", (authorizationServiceFactory: Kitos.Services.Authorization.IAuthorizationServiceFactory, user) => - authorizationServiceFactory - .createOrganizationAuthorization() + authorizationServiceFactory + .createOrganizationAuthorization() .getAuthorizationForItem(user.currentOrganizationId) ], hasWriteAccess: ["userAccessRights", userAccessRights => userAccessRights.canEdit @@ -89,12 +96,12 @@ .then(function (result) { return result.data.response; }); - }], + }], dataResponsible: ['$http', 'organization', function ($http, organization) { return $http.get('api/dataResponsible/' + organization.id) - .then(function (result) { - return result.data.response; - }); + .then(function (result) { + return result.data.response; + }); }], dataProtectionAdvisor: ['$http', 'organization', function ($http, organization) { //get by org id @@ -115,10 +122,16 @@ if (contactPerson != null) { return $http.get('/odata/Users/Users.IsEmailAvailable(email=\'' + contactPerson.email + '\')') .then(function (response) { - if (response.data.value) {return false;} else {return true;}; + if (response.data.value) { return false; } else { return true; }; }); - } - return false; + } + return false; + }], + canEditCvr: ['organizationApiService', 'user', function (organizationApiService: Services.IOrganizationApiService, user) { + //get by org id + return organizationApiService + .getPermissions(user.currentOrganizationUuid) + .then(permissions => permissions.canEditCvr); }] } }); diff --git a/Presentation.Web/app/components/org/basicInformation/org-GDPR.view.html b/Presentation.Web/app/components/org/basicInformation/org-GDPR.view.html index 8b73ace034..b1cad383fe 100644 --- a/Presentation.Web/app/components/org/basicInformation/org-GDPR.view.html +++ b/Presentation.Web/app/components/org/basicInformation/org-GDPR.view.html @@ -6,13 +6,13 @@

{{ctrl.organization.name}}

+ data-placeholder="CVR-nummer" + maxlength="10" />
@@ -76,8 +76,8 @@

Dataansvarlig

class="form-control input-sm" data-field="cvr" data-ng-model="ctrl.dataResponsible.cvr" - data-placeholder="CVR-nummer" - maxlength="10"/> + data-placeholder="CVR-nummer" + maxlength="10" />
@@ -180,8 +180,8 @@

Databeskyttelsesrådgiver

-
+
@@ -207,7 +207,7 @@

Kontaktperson

class="form-control input-sm" data-field="lastName" data-ng-model="ctrl.contactPerson.lastName" - data-placeholder="Efternavn"/> + data-placeholder="Efternavn" />
@@ -227,12 +227,12 @@

Kontaktperson

E-mail (Fremsøg eksisterende bruger)
- diff --git a/Presentation.Web/app/components/org/structure/org-structure-modal-edit.view.html b/Presentation.Web/app/components/org/structure/org-structure-modal-edit.view.html index de7a6611f0..f8a0cf865a 100644 --- a/Presentation.Web/app/components/org/structure/org-structure-modal-edit.view.html +++ b/Presentation.Web/app/components/org/structure/org-structure-modal-edit.view.html @@ -14,15 +14,21 @@

Rediger {{::orgUnit.oldName}}

-
- +
+

Der kan kun vælges blandt de organisationsenheder som er indenfor samme organisation, og som ikke er en underenhed til {{::orgUnit.oldName}}.

- Du kan ikke ændre overordnet organisationsenhed for "{{ ::orgUnit.oldName}}" + Du kan ikke ændre overordnet organisationsenhed for "{{::orgUnit.oldName}}" Kontakt en lokal administrator.

diff --git a/Presentation.Web/app/components/org/structure/org-structure.controller.ts b/Presentation.Web/app/components/org/structure/org-structure.controller.ts index d9ef8576c3..595a2122f0 100644 --- a/Presentation.Web/app/components/org/structure/org-structure.controller.ts +++ b/Presentation.Web/app/components/org/structure/org-structure.controller.ts @@ -375,6 +375,7 @@ orgUnits.push( { id: node.id, + uuid: node.uuid, name: node.name, ean: node.ean, localId: node.localId, @@ -411,6 +412,7 @@ orgUnits.push( { id: unit.id, + uuid: unit.uuid, name: unit.name, ean: unit.ean, localId: unit.localId, @@ -421,8 +423,6 @@ }); } - bindParentSelect($modalScope.orgUnit, orgUnits); - // only allow changing the parent if user is admin, and the unit isn't at the root $modalScope.isAdmin = user.isGlobalAdmin || user.isLocalAdmin; $modalScope.supplementaryText = getSupplementaryTextForEditDialog(unit); @@ -440,8 +440,11 @@ $modalScope.canEanBeModified = res.canEanBeModified; $modalScope.canDeviceIdBeModified = res.canDeviceIdBeModified; $modalScope.canChangeParent = res.canBeRearranged; + $modalScope.areRightsLoaded = true; }); + bindParentSelect($modalScope.orgUnit); + $modalScope.patch = function () { // don't allow duplicate submitting if ($modalScope.submitting) return; @@ -613,37 +616,53 @@ $modalInstance.close(createResult()); }; - function bindParentSelect(currentUnit: Kitos.Models.ViewModel.Organization.IEditOrgUnitViewModel, otherOrgUnits: Kitos.Models.Api.Organization.IOrganizationUnitDto[]) { + function bindParentSelect(currentUnit: Kitos.Models.ViewModel.Organization.IEditOrgUnitViewModel) { + + const root = $scope.nodes[0]; + const idToSkip = root.id === currentUnit.id ? null : currentUnit.id; + const orgUnitsOptions = Kitos.Helpers.Select2OptionsFormatHelper.addIndentationToUnitChildren(root, 0, idToSkip); - let existingChoice: { id: number; text: string }; + let existingChoice: Kitos.Models.ViewModel.Generic.Select2OptionViewModelWithIndentation; if (currentUnit.isRoot) { - existingChoice = { id: currentUnit.id, text: currentUnit.newName }; + const rootNodes = orgUnitsOptions.filter(x => x.id === String(currentUnit.id)); + if (rootNodes.length < 1) + return; + const rootUnit = rootNodes[0]; + existingChoice = rootUnit; } else { - const parentNodes = otherOrgUnits.filter(x => x.id === currentUnit.newParent); + const parentNodes = orgUnitsOptions.filter(x => x.id === String(currentUnit.newParent)); if (parentNodes.length < 1) { return; } const parentNode = parentNodes[0]; - existingChoice = { id: parentNode.id, text: parentNode.name }; + existingChoice = parentNode; } - const options = otherOrgUnits.map(value => { - return { - id: value.id, - text: value.name, - optionalObjectContext: value - } - }); - - $modalScope.parentSelect = { - selectedElement: existingChoice, - select2Config: select2LoadingService.select2LocalDataNoSearch(() => options, false), - elementSelected: (newElement) => { - if (!!newElement) { - $modalScope.orgUnit.newParent = newElement.id; + $modalScope.existingParentChoice = existingChoice; + + $modalScope.orgStructureSelectDropdownConfig = { + selectedElement: { id: existingChoice.id, text: existingChoice.text }, + options: orgUnitsOptions.map(item => { + return { + id: item.id, + text: item.text, + indentationLevel: item.indentationLevel, + optionalObjectContext: { + externalOriginUuid: item.optionalObjectContext?.externalOriginUuid + } + } + }), + onSelected: () => { + var selectedElement = $modalScope.orgStructureSelectDropdownConfig?.selectedElement; + if (!selectedElement) { + return; + } + if (selectedElement.id !== $modalScope.existingParentChoice?.id) { + $modalScope.orgUnit.newParent = selectedElement.id; + $modalScope.existingParentChoice = selectedElement; } } - }; + } } function createResult(types: Kitos.Models.ViewModel.Organization.OrganizationUnitEditResultType[] = null, unit = null): Kitos.Models.ViewModel.Organization.IOrganizationUnitEditResult { diff --git a/Presentation.Web/app/components/org/structure/org-structure.view.html b/Presentation.Web/app/components/org/structure/org-structure.view.html index a6549e9db6..31d32e6d24 100644 --- a/Presentation.Web/app/components/org/structure/org-structure.view.html +++ b/Presentation.Web/app/components/org/structure/org-structure.view.html @@ -37,7 +37,7 @@
  • - +
    @@ -50,13 +50,9 @@
  • -
    -
    - Enheder oprettet i KITOS -
    -
    - Enheder synkroniseret fra FK Organisation -
    +
    +
    Enheder oprettet i KITOS
    +
    Enheder synkroniseret fra FK Organisation
    @@ -87,7 +83,7 @@

    Oprettet af {{ chosenOrgUnit.objectOwnerFullName }}

    - +
    diff --git a/Presentation.Web/app/components/org/structure/org-unit-migration.component.ts b/Presentation.Web/app/components/org/structure/org-unit-migration.component.ts index ffbd06a03b..a4509e3487 100644 --- a/Presentation.Web/app/components/org/structure/org-unit-migration.component.ts +++ b/Presentation.Web/app/components/org/structure/org-unit-migration.component.ts @@ -2,7 +2,7 @@ "use strict"; function setupComponent(): ng.IComponentOptions { - return{ + return { bindings: { organizationId: "<", organizationUuid: "@", @@ -50,7 +50,8 @@ allSelections = false; targetUnitSelected = false; shouldTransferBtnBeEnabled = false; - isAnyDataPresent: boolean | null = null; + isAnyDataPresent: boolean = false; + loading: boolean = true; roles: IOrganizationUnitMigrationOptions; internalPayments: IOrganizationUnitMigrationOptions; @@ -67,7 +68,7 @@ contractTableConfig: IMigrationTableColumn[]; relevantSystemTableConfig: IMigrationTableColumn[]; responsibleSystemTableConfig: IMigrationTableColumn[]; - + static $inject: string[] = ["organizationUnitService", "organizationApiService", "notify"]; constructor(private readonly organizationUnitService: Services.Organization.IOrganizationUnitService, private readonly organizationApiService: Services.IOrganizationApiService, @@ -94,12 +95,22 @@ this.createTableConfigurations(); this.setupOptions(); - this.getData(); - - this.orgUnits = []; - this.organizationApiService.getOrganizationUnit(this.organizationId).then(result => { - this.orgUnits = this.orgUnits.concat(Helpers.Select2OptionsFormatHelper.addIndentationToUnitChildren(result, 0)); - }); + const loadOrgUnitsP = this.organizationApiService.getOrganizationUnit(this.organizationId); + this.getData() + .then(_ => { + this.orgUnits = []; + return loadOrgUnitsP + .then(result => { + this.orgUnits = this.orgUnits.concat(Helpers.Select2OptionsFormatHelper.addIndentationToUnitChildren(result, 0)); + }); + }) + .then(_ => { + this.loading = false; + }, error => { + console.log(error); + this.loading = false; + } + ); } deleteSelected() { @@ -157,7 +168,7 @@ setSelectedOrg() { if (!this.selectedOrg?.id) return; - if (this.selectedOrg.optionalExtraObject.uuid === this.unitUuid) { + if (this.selectedOrg.optionalObjectContext.uuid === this.unitUuid) { this.selectedOrg = null; this.notify.addErrorMessage("Du kan ikke overføre til denne enhed"); return; @@ -179,7 +190,7 @@ updateAnySelections() { let anySelectionsFound = false; let allSelectionsFound = false; - + const roots = this.getAllRoots(); var totalRegistrations = 0; roots.forEach(root => totalRegistrations += root.children.length); @@ -304,7 +315,7 @@ private createTransferRequest(): Models.Api.Organization.TransferOrganizationUnitRegistrationRequestDto { return Helpers.OrganizationRegistrationHelper.createTransferRequest( - this.selectedOrg?.optionalExtraObject?.uuid, + this.selectedOrg?.optionalObjectContext?.uuid, this.contractRegistrations.root.children, this.externalPayments.root.children, this.internalPayments.root.children, @@ -313,13 +324,17 @@ this.responsibleSystemRegistrations.root.children); } + private sortByText(input: Models.ViewModel.Organization.IOrganizationUnitRegistration[]): Models.ViewModel.Organization.IOrganizationUnitRegistration[] { + return input.sort((a, b) => a.text.localeCompare(b.text, 'da-DK')); + } + private getData(): ng.IPromise { return this.organizationUnitService.getRegistrations(this.organizationUuid, this.unitUuid).then(response => { - this.roles.root.children = this.mapDtoWithUserFullNameToOptions(response.organizationUnitRights); + this.roles.root.children = this.sortByText(this.mapDtoWithUserFullNameToOptions(response.organizationUnitRights)); this.getPaymentOptions(response.payments); - this.contractRegistrations.root.children = this.mapOrganizationDtoToOptions(response.itContractRegistrations); - this.relevantSystemRegistrations.root.children = this.mapOrganizationDtoWithEnabledToOptions(response.relevantSystems); - this.responsibleSystemRegistrations.root.children = this.mapOrganizationDtoWithEnabledToOptions(response.responsibleSystems); + this.contractRegistrations.root.children = this.sortByText(this.mapOrganizationDtoToOptions(response.itContractRegistrations)); + this.relevantSystemRegistrations.root.children = this.sortByText(this.mapOrganizationDtoWithEnabledToOptions(response.relevantSystems)); + this.responsibleSystemRegistrations.root.children = this.sortByText(this.mapOrganizationDtoWithEnabledToOptions(response.responsibleSystems)); this.checkIsAnyDataPresent(); }, error => { console.error(error); @@ -350,7 +365,7 @@ return this.stateParameters.checkIsRootBusy(); } - private setIsBusy(value: boolean): void{ + private setIsBusy(value: boolean): void { this.stateParameters.setRootIsBusy(value); } @@ -386,8 +401,8 @@ private createPaymentTableConfig(title: string): IMigrationTableColumn[] { return [ { title: "Index", property: "index", type: MigrationTableColumnType.Text }, - { title: "Kontraktnavn", property: "objectText", type: MigrationTableColumnType.Link }, - { title: title, property: "text", type: MigrationTableColumnType.Text } + { title: "Kontraktnavn", property: "text", type: MigrationTableColumnType.Link }, + { title: title, property: "objectText", type: MigrationTableColumnType.Text } ] as IMigrationTableColumn[]; } @@ -430,17 +445,17 @@ externalPayments = externalPayments.concat(this.mapPaymentsToOptions(payment.itContract, payment.externalPayments)); }); - this.internalPayments.root.children = internalPayments; - this.externalPayments.root.children = externalPayments; + this.internalPayments.root.children = this.sortByText(internalPayments); + this.externalPayments.root.children = this.sortByText(externalPayments); } private mapPaymentsToOptions(contract: Models.Generic.NamedEntity.NamedEntityDTO, payments: Models.Generic.NamedEntity.NamedEntityDTO[]): Models.ViewModel.Organization.IOrganizationUnitRegistration[] { return payments.map((element, index) => { return { id: element.id, - text: element.name, + text: contract.name, targetPageObjectId: contract.id, - objectText: contract.name, + objectText: element.name, index: index + 1, optionalObjectContext: contract } as Models.ViewModel.Organization.IOrganizationUnitRegistration; diff --git a/Presentation.Web/app/components/org/structure/org-unit-migration.view.html b/Presentation.Web/app/components/org/structure/org-unit-migration.view.html index 8ec8682d2c..1a509e5e52 100644 --- a/Presentation.Web/app/components/org/structure/org-unit-migration.view.html +++ b/Presentation.Web/app/components/org/structure/org-unit-migration.view.html @@ -1,4 +1,4 @@ -
    +
    + data-on-change="ctrl.setSelectedOrg()" + data-render-unit-origin-indication="true">
    @@ -26,4 +27,6 @@
    -
    Der er ikke nogle registreringer der anvender '{{ctrl.unitName}}'
    \ No newline at end of file +
    Der er ikke nogle registreringer der anvender '{{ctrl.unitName}}'
    + + \ No newline at end of file diff --git a/Presentation.Web/app/components/org/user/org-user.controller.ts b/Presentation.Web/app/components/org/user/org-user.controller.ts index 8aab098d2b..418035e2fc 100644 --- a/Presentation.Web/app/components/org/user/org-user.controller.ts +++ b/Presentation.Web/app/components/org/user/org-user.controller.ts @@ -1,7 +1,7 @@ module Kitos.Organization.Users { "use strict"; - interface IGridModel extends Models.IUser { + interface IGridModel extends Models.IUser { hasApi: boolean; canEdit: boolean; isLocalAdmin: boolean; @@ -32,16 +32,20 @@ "hasWriteAccess", "notify", "gridStateService", + "exportGridToExcelService", + "$timeout" ]; constructor( - $scope: ng.IScope, - private $state: ng.ui.IStateService, - private _: ILoDashWithMixins, - private user, - private hasWriteAccess, - private notify, - private gridStateService: Services.IGridStateFactory) { + private readonly $scope: ng.IScope, + private readonly $state: ng.ui.IStateService, + private readonly _: ILoDashWithMixins, + private readonly user, + private readonly hasWriteAccess, + private readonly notify, + private readonly gridStateService: Services.IGridStateFactory, + private readonly exportGridToExcelService: Services.System.ExportGridToExcelService, + private readonly $timeout: ng.ITimeoutService) { this.hasWriteAccess = hasWriteAccess; $scope.$on("kendoWidgetCreated", (event, widget) => { if (widget === this.mainGrid) { @@ -53,7 +57,7 @@ setTimeout(() => this.activate(), 1); } - private hasRole(user : IGridModel, role: Models.OrganizationRole): boolean { + private hasRole(user: IGridModel, role: Models.OrganizationRole): boolean { return this._.find(user.OrganizationRights, (right) => right.Role === role) !== undefined; } @@ -176,6 +180,7 @@ usr.isSystemAdmin = this.hasRole(usr, Models.OrganizationRole.SystemModuleAdmin); usr.isContractAdmin = this.hasRole(usr, Models.OrganizationRole.ContractModuleAdmin); usr.isRightsHolder = this.hasRole(usr, Models.OrganizationRole.RightsHolderAccess); + usr.ObjectOwner ??= { Name: "", LastName:"" } as any; }); return response; } @@ -224,6 +229,7 @@ }, groupable: false, columnMenu: true, + excelExport: (e: any) => this.exportToExcel(e), height: window.innerHeight - 200, detailTemplate: (dataItem) => ` @@ -240,7 +246,7 @@ columns: [ { field: "Name", title: "Navn", width: 230, - persistId: "fullname", + persistId: "fullname", template: (dataItem) => `${dataItem.Name} ${dataItem.LastName}`, excelTemplate: (dataItem) => `${dataItem.Name} ${dataItem.LastName}`, hidden: false, @@ -255,7 +261,7 @@ }, { field: "Email", title: "Email", width: 230, - persistId: "email", + persistId: "email", template: (dataItem) => `${dataItem.Email}`, excelTemplate: (dataItem) => dataItem.Email, headerAttributes: { @@ -276,15 +282,15 @@ }, { field: "LastAdvisDate", title: "Advis", width: 110, - persistId: "advisdate", + persistId: "advisdate", template: (dataItem) => ``, - excelTemplate: (dataItem) => dataItem.LastAdvisDate ? dataItem.LastAdvisDate.toDateString() : "", + excelTemplate: (dataItem) => dataItem.LastAdvisDate ? Kitos.Helpers.ExcelExportHelper.renderDate(dataItem.LastAdvisDate) : "", hidden: false, filterable: false }, { field: "ObjectOwner.Name", title: "Oprettet af", width: 150, - persistId: "createdby", + persistId: "createdby", template: (dataItem) => dataItem.ObjectOwner ? `${dataItem.ObjectOwner.Name} ${dataItem.ObjectOwner.LastName}` : "", excelTemplate: (dataItem) => dataItem.ObjectOwner ? `${dataItem.ObjectOwner.Name} ${dataItem.ObjectOwner.LastName}` : "", hidden: false, @@ -298,8 +304,12 @@ } }, { - field: "OrganizationUnitRights.Role", title: "Roller", width: 150, - persistId: "role", + field: "OrganizationUnitRights.Role", + title: "Organisationsroller", + width: 150, + filterable: false, + sortable: false, + persistId: "role", attributes: { "class": "might-overflow" }, template: (dataItem) => { if (dataItem.OrganizationUnitRights.length == 0) { @@ -307,25 +317,18 @@ } return ` {{rights.Role.Name}}{{$last ? '' : ', '}}`; }, - hidden: true, - filterable: { - cell: { - template: customFilter, - dataSource: [], - showOperators: false, - operator: "contains" - } - } + excelTemplate: (dataItem) => dataItem.OrganizationUnitRights.map(right => right.Role.Name).join(", "), + hidden: true }, { - field: "hasApi", title: "API bruger", width: 96, - persistId: "apiaccess", + persistId: "apiaccess", attributes: { "class": "text-center", "data-element-type": "userObject" }, headerAttributes: { "data-element-type": "userHeader" }, template: (dataItem) => setBooleanValue(dataItem.HasApiAccess), + excelTemplate: (dataItem) => Kitos.Helpers.ExcelExportHelper.renderBoolean(dataItem.HasApiAccess), hidden: !(this.user.isGlobalAdmin || this.user.isLocalAdmin), filterable: false, sortable: false, @@ -333,36 +336,40 @@ }, { field: "isLocalAdmin", title: "Lokal Admin", width: 96, - persistId: "localadminrole", + persistId: "localadminrole", attributes: { "class": "text-center" }, template: (dataItem) => setBooleanValue(dataItem.isLocalAdmin), + excelTemplate: (dataItem) => Kitos.Helpers.ExcelExportHelper.renderBoolean(dataItem.isLocalAdmin), hidden: false, filterable: false, sortable: false }, { field: "isOrgAdmin", title: "Organisations Admin", width: 104, - persistId: "orgadminrole", + persistId: "orgadminrole", attributes: { "class": "text-center" }, template: (dataItem) => setBooleanValue(dataItem.isOrgAdmin), + excelTemplate: (dataItem) => Kitos.Helpers.ExcelExportHelper.renderBoolean(dataItem.isOrgAdmin), hidden: false, filterable: false, sortable: false }, { field: "isSystemAdmin", title: "System Admin", width: 104, - persistId: "systemadminrole", + persistId: "systemadminrole", attributes: { "class": "text-center" }, template: (dataItem) => setBooleanValue(dataItem.isSystemAdmin), + excelTemplate: (dataItem) => Kitos.Helpers.ExcelExportHelper.renderBoolean(dataItem.isSystemAdmin), hidden: false, filterable: false, sortable: false }, { field: "isContractAdmin", title: "Kontrakt Admin", width: 112, - persistId: "contractadminrole", + persistId: "contractadminrole", attributes: { "class": "text-center" }, template: (dataItem) => setBooleanValue(dataItem.isContractAdmin), + excelTemplate: (dataItem) => Kitos.Helpers.ExcelExportHelper.renderBoolean(dataItem.isContractAdmin), hidden: false, filterable: false, sortable: false @@ -370,12 +377,13 @@ { field: "rightsHolder", title: "Rettighedshaveradgang", width: 160, - persistId: "rightsHolder", + persistId: "rightsHolder", attributes: { "class": "text-center", "data-element-type": "rightsHolderObject" }, headerAttributes: { "data-element-type": "rightsHolderHeader" }, template: (dataItem) => setBooleanValue(dataItem.isRightsHolder), + excelTemplate: (dataItem) => Kitos.Helpers.ExcelExportHelper.renderBoolean(dataItem.isRightsHolder), hidden: !this.user.isGlobalAdmin, filterable: false, sortable: false, @@ -384,12 +392,13 @@ { field: "stakeHolder", title: "Interessentadgang", width: 160, - persistId: "stakeHolder", + persistId: "stakeHolder", attributes: { "class": "text-center", "data-element-type": "stakeHolderObject" }, headerAttributes: { "data-element-type": "stakeHolderHeader" }, template: (dataItem) => setBooleanValue(dataItem.HasStakeHolderAccess), + excelTemplate: (dataItem) => Kitos.Helpers.ExcelExportHelper.renderBoolean(dataItem.HasStakeHolderAccess), hidden: !this.user.isGlobalAdmin, filterable: false, sortable: false, @@ -397,13 +406,23 @@ }, { template: (dataItem) => dataItem.canEdit ? `RedigérSlet` : `RedigérSlet`, + field: "Name", //Must bind to something or it corrupts the excel outputs title: " ", + filterable: false, + sortable: false, + menu: false, width: 176, - persistId: "command" + persistId: "rowCommands", + uiOnlyColumn: true } ] }; + Helpers.ExcelExportHelper.setupExcelExportDropdown(() => this.excelConfig, + () => this.mainGrid, + this.$scope, + mainGridOptions.toolbar); + function customFilter(args) { args.element.kendoAutoComplete({ noDataTemplate: '' @@ -419,13 +438,21 @@ this.mainGridOptions = mainGridOptions; } + //NOTE: Stores the visibility parameters, and is used by the excel dropdown commands before invoking exportToExcel().. + private readonly excelConfig: Models.IExcelConfig = { + }; + + private exportToExcel = (e: IKendoGridExcelExportEvent) => { + this.exportGridToExcelService.getExcel(e, this._, this.$timeout, this.mainGrid, this.excelConfig); + } + public onEdit(entityId) { this.$state.go("organization.user.edit", { id: entityId }); } private fixNameFilter(filterUrl, column) { const pattern = new RegExp(`(\\w+\\()${column}(.*?\\))`, "i"); - if (column == 'ObjectOwner.Name') { + if (column === 'ObjectOwner.Name') { return filterUrl.replace(pattern, `$1concat(concat(ObjectOwner/Name, ' '), ObjectOwner/LastName)$2`); } return filterUrl.replace(pattern, `$1concat(concat(Name, ' '), LastName)$2`); @@ -454,14 +481,14 @@ ], userAccessRights: ["authorizationServiceFactory", "user", (authorizationServiceFactory: Kitos.Services.Authorization.IAuthorizationServiceFactory, user) => - authorizationServiceFactory - .createOrganizationAuthorization() - .getAuthorizationForItem(user.currentOrganizationId) + authorizationServiceFactory + .createOrganizationAuthorization() + .getAuthorizationForItem(user.currentOrganizationId) ], hasWriteAccess: ["userAccessRights", userAccessRights => userAccessRights.canEdit ] } }); } - ]); + ]); } diff --git a/Presentation.Web/app/components/user-notification/user-notification-modal.controller.ts b/Presentation.Web/app/components/user-notification/user-notification-modal.controller.ts index 8a45dc2d44..5582c1993f 100644 --- a/Presentation.Web/app/components/user-notification/user-notification-modal.controller.ts +++ b/Presentation.Web/app/components/user-notification/user-notification-modal.controller.ts @@ -93,7 +93,7 @@ title: "Dato", width: 50, template: (dataItem: Models.UserNotification.UserNotificationDTO) => { - return moment(dataItem.created).format("DD-MM-YYYY"); + return moment(dataItem.created).format(Constants.DateFormat.DanishDateFormat); }, attributes: { "class": "fixed-grid-left-padding" }, sortable: false diff --git a/Presentation.Web/app/helpers/DateStringFormatHelper.ts b/Presentation.Web/app/helpers/DateStringFormatHelper.ts index 95207dddec..d90e149144 100644 --- a/Presentation.Web/app/helpers/DateStringFormatHelper.ts +++ b/Presentation.Web/app/helpers/DateStringFormatHelper.ts @@ -14,5 +14,10 @@ } return { errorMessage: "Ugyldigt dato format" }; } + + static fromDanishToEnglishFormat(dateString: string): string { + return moment(dateString, [Kitos.Constants.DateFormat.DanishDateFormat, Kitos.Constants.DateFormat.EnglishDateFormat]).format(Constants.DateFormat.EnglishDateFormat); + + } } } \ No newline at end of file diff --git a/Presentation.Web/app/helpers/ExcelExportHelper.ts b/Presentation.Web/app/helpers/ExcelExportHelper.ts index 4cc159a21d..d0efb9fae5 100644 --- a/Presentation.Web/app/helpers/ExcelExportHelper.ts +++ b/Presentation.Web/app/helpers/ExcelExportHelper.ts @@ -67,6 +67,10 @@ return ExcelExportHelper.noValueFallback; } + static renderBoolean(value: boolean) { + return value ? "Ja" : "Nej"; + } + static convertColorsToDanish(color: string) { if (color === null || _.isUndefined(color)) { return ExcelExportHelper.noValueFallback; diff --git a/Presentation.Web/app/helpers/connection-change-log-helper.ts b/Presentation.Web/app/helpers/connection-change-log-helper.ts new file mode 100644 index 0000000000..beee5f9ee7 --- /dev/null +++ b/Presentation.Web/app/helpers/connection-change-log-helper.ts @@ -0,0 +1,20 @@ +module Kitos.Helpers { + export class ConnectionChangeLogHelper { + static createDictionaryFromChangeLogList(changeLogs: Array): { [key: number]: Models.ViewModel.Generic.Select2OptionViewModel} { + return changeLogs.reduce((acc, next, _) => { + acc[next.id] = { + id: next.id, + text: `${RenderFieldsHelper.renderDate(next.logTime)} - ${ConnectionChangeLogHelper.getResponsibleEntityTextBasedOnOrigin(next)}`, + optionalObjectContext: next + }; + return acc; + }, {}); + } + + static getResponsibleEntityTextBasedOnOrigin(changeLog: Models.Api.Organization.ConnectionChangeLogDTO): string { + return changeLog.origin === Models.Api.Organization.ConnectionChangeLogOrigin.Background + ? "FK Organisation" + : `${changeLog.user.name} (${changeLog.user.email})`; + } + } +} \ No newline at end of file diff --git a/Presentation.Web/app/helpers/date-validation-helper.ts b/Presentation.Web/app/helpers/date-validation-helper.ts index 18056ec190..77a543b377 100644 --- a/Presentation.Web/app/helpers/date-validation-helper.ts +++ b/Presentation.Web/app/helpers/date-validation-helper.ts @@ -23,6 +23,17 @@ endDateFieldName); } + static validateDateInput(date: string, notify, fieldName: string, emptyDateIsValid: boolean) { + if (!date && emptyDateIsValid) { + return true; + } + + const formatDateString = Kitos.Constants.DateFormat.EnglishDateFormat; + const formattedDate = moment(date, [Kitos.Constants.DateFormat.DanishDateFormat, formatDateString]); + + return DateValidationHelper.checkIfDateIsValid(formattedDate, notify, fieldName); + } + static checkIfStartDateIsSmallerThanEndDate(startDate: moment.Moment, endDate: moment.Moment, notify, startDateFieldName: string, endDateFieldName: string): boolean { if (startDate > endDate) { notify.addErrorMessage(`Den indtastede \"${endDateFieldName}\" er før \"${startDateFieldName}\".`); diff --git a/Presentation.Web/app/helpers/select2-option-format-helper.ts b/Presentation.Web/app/helpers/select2-option-format-helper.ts index 37158393de..93d4ffa831 100644 --- a/Presentation.Web/app/helpers/select2-option-format-helper.ts +++ b/Presentation.Web/app/helpers/select2-option-format-helper.ts @@ -12,13 +12,49 @@ return Select2OptionsFormatHelper.formatText(org.text, org.optionalObjectContext?.cvrNumber); } - public static addIndentationToUnitChildren(orgUnit: Models.Api.Organization.OrganizationUnit, indentationLevel: number): Kitos.Models.ViewModel.Generic.Select2OptionViewModelWithIndentation[] { + public static formatChangeLog(changeLog: Models.Api.Organization.ConnectionChangeLogDTO): string { + const dateText = Helpers.RenderFieldsHelper.renderDate(changeLog.logTime); + const responsibleEntityText = Helpers.ConnectionChangeLogHelper.getResponsibleEntityTextBasedOnOrigin(changeLog); + + return Select2OptionsFormatHelper.formatText(dateText, responsibleEntityText); + } + + public static addIndentationToUnitChildren(orgUnit: Models.Api.Organization.OrganizationUnit, indentationLevel: number, idToSkip?: number): Kitos.Models.ViewModel.Generic.Select2OptionViewModelWithIndentation[] { const options: Kitos.Models.ViewModel.Generic.Select2OptionViewModelWithIndentation[] = []; - Select2OptionsFormatHelper.visitUnit(orgUnit, indentationLevel, options); + Select2OptionsFormatHelper.visitUnit(orgUnit, indentationLevel, options, idToSkip); return options; } + public static formatIndentation(result: Models.ViewModel.Generic.Select2OptionViewModelWithIndentation, addUnitOriginIndication: boolean = false): string { + function visit(text: string, indentationLevel: number, addUnitOriginIndication: boolean, isKitosUnit: boolean = false, indentationText: string = ""): string { + if (indentationLevel <= 0) { + return addUnitOriginIndication === false ? indentationText + text : Select2OptionsFormatHelper.formatIndentationWithOriginText(text, indentationText, isKitosUnit); + } + + //indentation is four non breaking spaces + return visit(text, indentationLevel - 1, addUnitOriginIndication, isKitosUnit, indentationText + Constants.Select2.UnitIndentation); + } + + let isKitosUnit = true; + if (addUnitOriginIndication) { + if (result.optionalObjectContext?.externalOriginUuid) { + isKitosUnit = false; + } + } + + const formattedResult = visit(result.text, result.indentationLevel, addUnitOriginIndication, isKitosUnit); + return formattedResult; + } + + private static formatIndentationWithOriginText(text: string, indentationText: string, isKitosUnit: boolean) { + if (isKitosUnit) { + return `
    ${indentationText}${text}
    `; + } + + return `
    ${indentationText}${text}
    `; + } + private static formatText(text: string, subText?: string): string { let result = `
    ${text}
    `; if (subText) { @@ -26,19 +62,24 @@ } return result; } + - private static visitUnit(orgUnit: Kitos.Models.Api.Organization.OrganizationUnit, indentationLevel: number, options: Kitos.Models.ViewModel.Generic.Select2OptionViewModelWithIndentation[]) { + private static visitUnit(orgUnit: Models.Api.Organization.OrganizationUnit, indentationLevel: number, options: Models.ViewModel.Generic.Select2OptionViewModelWithIndentation[], unitIdToSkip?: number) { + if (unitIdToSkip && orgUnit.id === unitIdToSkip) { + return; + } + const option = { id: String(orgUnit.id), text: orgUnit.name, indentationLevel: indentationLevel, - optionalExtraObject: orgUnit + optionalObjectContext: orgUnit }; options.push(option); orgUnit.children.forEach(child => { - return Select2OptionsFormatHelper.visitUnit(child, indentationLevel + 1, options); + return Select2OptionsFormatHelper.visitUnit(child, indentationLevel + 1, options, unitIdToSkip); }); } diff --git a/Presentation.Web/app/kitos.ts b/Presentation.Web/app/kitos.ts index fca8aa0a80..dd86c3dc30 100644 --- a/Presentation.Web/app/kitos.ts +++ b/Presentation.Web/app/kitos.ts @@ -1,14 +1,16 @@ module Kitos { export interface IRootScope extends ng.IRootScopeService { - page: { title: string}; + page: { title: string }; } export interface IKendoGridColumn extends kendo.ui.GridColumn { persistId: string; tempVisual?: boolean; + tempHidden?: boolean; isAvailable?: boolean; excelTemplate?(dataItem: TDataSource): string; - template?: ((dataItem: TDataSource) => string)|string; + template?: ((dataItem: TDataSource) => string) | string; + uiOnlyColumn?: boolean; } export interface IKendoGridToolbarItem extends kendo.ui.GridToolbarItem { @@ -18,7 +20,7 @@ export interface IKendoGridOptions extends kendo.ui.GridOptions { toolbar?: IKendoGridToolbarItem[]; columns?: IKendoGridColumn[]; - detailTemplate?: ((dataItem: TDataSource) => string)|string; + detailTemplate?: ((dataItem: TDataSource) => string) | string; } export interface IKendoGrid extends kendo.ui.Grid { @@ -62,7 +64,7 @@ } export interface AuthRoles extends ng.ui.IStateProvider { - authRoles: [Models.OrganizationRole|"GlobalAdmin"]; + authRoles: [Models.OrganizationRole | "GlobalAdmin"]; noAuth: string; name: string; } diff --git a/Presentation.Web/app/models/ViewModel/Generic/Select2OptionViewModel.ts b/Presentation.Web/app/models/ViewModel/Generic/Select2OptionViewModel.ts index 8b2e178ed6..c1348f48f0 100644 --- a/Presentation.Web/app/models/ViewModel/Generic/Select2OptionViewModel.ts +++ b/Presentation.Web/app/models/ViewModel/Generic/Select2OptionViewModel.ts @@ -2,25 +2,26 @@ export const select2BlankOptionTextValue = "\u200B"; - export interface ISelect2Model { - id: string; + export interface ISelect2Model { + id: TId; text: string; } - export interface Select2OptionViewModel { - id: number; - text: string; + export interface ISelect2ModelOptionalObjectContext { optionalObjectContext?: T; - disabled?: boolean; } - export interface UpdatedSelect2OptionViewModel extends ISelect2Model { - optionalObjectContext?: T; + export interface ISelect2ModelItemState { disabled?: boolean; } - export interface Select2OptionViewModelWithIndentation extends ISelect2Model { + export interface Select2OptionViewModel extends ISelect2Model, ISelect2ModelOptionalObjectContext, ISelect2ModelItemState { + } + + export interface UpdatedSelect2OptionViewModel extends ISelect2Model, ISelect2ModelOptionalObjectContext, ISelect2ModelItemState { + } + + export interface Select2OptionViewModelWithIndentation extends ISelect2Model, ISelect2ModelOptionalObjectContext, ISelect2ModelItemState { indentationLevel: number; - optionalExtraObject?: T; } } \ No newline at end of file diff --git a/Presentation.Web/app/models/ViewModel/Organization/edit-org-unit-view-model.ts b/Presentation.Web/app/models/ViewModel/Organization/edit-org-unit-view-model.ts index 425f493e6b..f37b3c429e 100644 --- a/Presentation.Web/app/models/ViewModel/Organization/edit-org-unit-view-model.ts +++ b/Presentation.Web/app/models/ViewModel/Organization/edit-org-unit-view-model.ts @@ -2,6 +2,7 @@ export interface IEditOrgUnitViewModel { id: number, + uuid: string, oldName: string, newName: string, newEan: string, diff --git a/Presentation.Web/app/models/ViewModel/Organization/organization-connection-change-logs.ts b/Presentation.Web/app/models/ViewModel/Organization/organization-connection-change-logs.ts new file mode 100644 index 0000000000..d868695e6e --- /dev/null +++ b/Presentation.Web/app/models/ViewModel/Organization/organization-connection-change-logs.ts @@ -0,0 +1,5 @@ +module Kitos.Models.ViewModel.Organization { + export interface IFkOrganizationConnectionChangeLogsViewModel extends Kitos.Models.Api.Organization.ConnectionChangeLogDTO { + id: number + } +} \ No newline at end of file diff --git a/Presentation.Web/app/models/api/organization/connection-change-log-dto.ts b/Presentation.Web/app/models/api/organization/connection-change-log-dto.ts new file mode 100644 index 0000000000..46a18a23da --- /dev/null +++ b/Presentation.Web/app/models/api/organization/connection-change-log-dto.ts @@ -0,0 +1,8 @@ +module Kitos.Models.Api.Organization { + export interface ConnectionChangeLogDTO { + origin: ConnectionChangeLogOrigin + user: Api.IUserWithEmail + logTime: Date + consequences: Array + } +} \ No newline at end of file diff --git a/Presentation.Web/app/models/api/organization/connection-change-log-origin.ts b/Presentation.Web/app/models/api/organization/connection-change-log-origin.ts new file mode 100644 index 0000000000..7697c573ae --- /dev/null +++ b/Presentation.Web/app/models/api/organization/connection-change-log-origin.ts @@ -0,0 +1,6 @@ +module Kitos.Models.Api.Organization { + export enum ConnectionChangeLogOrigin { + Background = 0, + User = 1 + } +} \ No newline at end of file diff --git a/Presentation.Web/app/models/api/organization/organization-permissions-dto.ts b/Presentation.Web/app/models/api/organization/organization-permissions-dto.ts new file mode 100644 index 0000000000..b457c5501f --- /dev/null +++ b/Presentation.Web/app/models/api/organization/organization-permissions-dto.ts @@ -0,0 +1,5 @@ +module Kitos.Models.Api.Organization { + export interface OrganizationPermissionsDTO { + canEditCvr: boolean; + } +} \ No newline at end of file diff --git a/Presentation.Web/app/models/api/organization/organization-unit-dto.ts b/Presentation.Web/app/models/api/organization/organization-unit-dto.ts index 911a314ba9..5468616ca8 100644 --- a/Presentation.Web/app/models/api/organization/organization-unit-dto.ts +++ b/Presentation.Web/app/models/api/organization/organization-unit-dto.ts @@ -1,6 +1,7 @@ module Kitos.Models.Api.Organization { export interface IOrganizationUnitDto { id: number; + uuid:string; name: string; ean: string; localId: number; diff --git a/Presentation.Web/app/models/api/organization/organization-unit.ts b/Presentation.Web/app/models/api/organization/organization-unit.ts index 008464775e..eeb5888b79 100644 --- a/Presentation.Web/app/models/api/organization/organization-unit.ts +++ b/Presentation.Web/app/models/api/organization/organization-unit.ts @@ -6,5 +6,6 @@ localId: number; parentId: number; organizationId: number; + externalOriginUuid: string; } } \ No newline at end of file diff --git a/Presentation.Web/app/models/api/organization/organization.ts b/Presentation.Web/app/models/api/organization/organization.ts index c5de7df77a..e2f99267c8 100644 --- a/Presentation.Web/app/models/api/organization/organization.ts +++ b/Presentation.Web/app/models/api/organization/organization.ts @@ -1,5 +1,5 @@ module Kitos.Models.Api.Organization { export interface Organization extends Models.Generic.NamedEntity.NamedEntityDTO{ - uuid : string + uuid: string; } } \ No newline at end of file diff --git a/Presentation.Web/app/models/api/organization/sts-organization-synchronization-status-response-dto.ts b/Presentation.Web/app/models/api/organization/sts-organization-synchronization-status-response-dto.ts index 1c8ff1d09f..0088054fa8 100644 --- a/Presentation.Web/app/models/api/organization/sts-organization-synchronization-status-response-dto.ts +++ b/Presentation.Web/app/models/api/organization/sts-organization-synchronization-status-response-dto.ts @@ -2,6 +2,8 @@ export interface StsOrganizationSynchronizationStatusResponseDTO { accessStatus: StsOrganizationAccessStatusResponseDTO connected: boolean + subscribesToUpdates: boolean + dateOfLatestCheckBySubscription : Date | null synchronizationDepth: number | null canCreateConnection: boolean canUpdateConnection: boolean diff --git a/Presentation.Web/app/models/generic/KendoOrganizationalConfigurationDTO.ts b/Presentation.Web/app/models/generic/KendoOrganizationalConfigurationDTO.ts index 194a93ad16..aefdd3b50b 100644 --- a/Presentation.Web/app/models/generic/KendoOrganizationalConfigurationDTO.ts +++ b/Presentation.Web/app/models/generic/KendoOrganizationalConfigurationDTO.ts @@ -1,7 +1,8 @@ module Kitos.Models.Generic { export enum OverviewType { ItSystemUsage = 0, - ItContract = 1 + ItContract = 1, + DataProcessingRegistration = 2 } export interface IKendoOrganizationalConfigurationDTO { diff --git a/Presentation.Web/app/services/exportGridToExcelService.ts b/Presentation.Web/app/services/exportGridToExcelService.ts index c8b314a1ab..7feaac32a0 100644 --- a/Presentation.Web/app/services/exportGridToExcelService.ts +++ b/Presentation.Web/app/services/exportGridToExcelService.ts @@ -58,12 +58,16 @@ } } - // hide columns on visual grid + // hide/show columns on visual grid columns.forEach(column => { if (column.tempVisual) { delete column.tempVisual; e.sender.hideColumn(column); } + if (column.tempHidden) { + delete column.tempHidden; + e.sender.showColumn(column); + } }); // hide loadingbar when export is finished @@ -74,6 +78,8 @@ } private selectColumnsToDisplay(e: IKendoGridExcelExportEvent, columns: IKendoGridColumn[], exportOnlyVisibleColumns: boolean) { + this.hideUiOnlyColumns(e, columns); + if (!exportOnlyVisibleColumns) { this.showAllRootColumns(e, columns); } @@ -85,13 +91,23 @@ private showAllRootColumns(e: IKendoGridExcelExportEvent, columns: IKendoGridColumn[]) { _.forEach(columns, column => { - if (column.hidden && column.parentId === undefined) { + if (!column.uiOnlyColumn && column.hidden && column.parentId === undefined) { column.tempVisual = true; e.sender.showColumn(column); } }); } + private hideUiOnlyColumns(e: IKendoGridExcelExportEvent, columns: IKendoGridColumn[]) { + _.forEach(columns, + column => { + if (column.uiOnlyColumn && !column.hidden) { + column.tempHidden = true; + e.sender.hideColumn(column); + } + }); + } + private getTemplateMethod(column) { let template: Function; diff --git a/Presentation.Web/app/services/generic/bindingService.ts b/Presentation.Web/app/services/generic/bindingService.ts index 7dd5ef0aa3..616203b10f 100644 --- a/Presentation.Web/app/services/generic/bindingService.ts +++ b/Presentation.Web/app/services/generic/bindingService.ts @@ -10,7 +10,8 @@ allowRemoval: boolean, searchFunc?: (query: string) => angular.IPromise[]>, fixedValueRange?: () => Models.ViewModel.Generic.Select2OptionViewModel[], - formatResult?: (input: Models.ViewModel.Generic.Select2OptionViewModel) => string) + formatResult?: (input: Models.ViewModel.Generic.Select2OptionViewModel) => string, + allowFixedValueRangeSearch?: boolean) : void; } @@ -30,13 +31,18 @@ allowRemoval: boolean, searchFunc?: (query: string) => angular.IPromise[]>, fixedValueRange?: () => Models.ViewModel.Generic.Select2OptionViewModel[], - formatResult?: (input: Models.ViewModel.Generic.Select2OptionViewModel) => string) { + formatResult?: (input: Models.ViewModel.Generic.Select2OptionViewModel) => string, + allowFixedValueRangeSearch?: boolean ) { let select2Config; if (!!searchFunc) { select2Config = this.select2LoadingService.loadSelect2WithDataSource(searchFunc, false, formatResult); } else if (!!fixedValueRange) { - select2Config = this.select2LoadingService.select2LocalDataNoSearch(() => fixedValueRange(), false); + if (allowFixedValueRangeSearch) { + select2Config = this.select2LoadingService.select2LocalData(() => fixedValueRange()); + } else { + select2Config = this.select2LoadingService.select2LocalDataNoSearch(() => fixedValueRange(), false); + } } else { throw new Error("Either searchFunc or fixedValueRange must be provided"); } @@ -56,5 +62,6 @@ } } + app.service("bindingService", BindingService); } \ No newline at end of file diff --git a/Presentation.Web/app/services/organization-api-service.ts b/Presentation.Web/app/services/organization-api-service.ts index 3ca49d975a..a68096d774 100644 --- a/Presentation.Web/app/services/organization-api-service.ts +++ b/Presentation.Web/app/services/organization-api-service.ts @@ -3,6 +3,7 @@ export interface IOrganizationApiService { getOrganization(id: number): angular.IPromise; + getPermissions(uuid: string): angular.IPromise; getOrganizationDeleteConflicts(uuid: string) : angular.IPromise; getOrganizationUnit(organizationId: number): angular.IPromise; deleteOrganization(uuid: string, enforce : boolean): angular.IPromise; @@ -29,7 +30,11 @@ static $inject: string[] = ["$http"]; private readonly apiWrapper: Services.Generic.ApiWrapper; constructor($http: ng.IHttpService) { - this.apiWrapper = new Services.Generic.ApiWrapper($http); + this.apiWrapper = new Services.Generic.ApiWrapper($http); + } + + getPermissions(uuid: string): ng.IPromise { + return this.apiWrapper.getDataFromUrl(`api/v1/organizations/${uuid}/permissions`); } } diff --git a/Presentation.Web/app/services/select2LoadingService.ts b/Presentation.Web/app/services/select2LoadingService.ts index 13b89dba89..3441a7568d 100644 --- a/Presentation.Web/app/services/select2LoadingService.ts +++ b/Presentation.Web/app/services/select2LoadingService.ts @@ -7,9 +7,9 @@ loadSelect2WithDataSource(source: Select2AsyncDataSource, allowClear: boolean, formatResult?: (input: Models.ViewModel.Generic.Select2OptionViewModel) => string); loadSelect2WithDataHandler(url: string, allowClear: boolean, paramArray: any, resultBuilder: (candidate: any, allResults: any[]) => void, nameContentQueryParamName?: string, formatResult?: (input: Models.ViewModel.Generic.Select2OptionViewModel) => string); select2LocalData(dataFn: () => Models.ViewModel.Generic.Select2OptionViewModel[]); - select2LocalDataNoSearch(dataFn: () => Models.ViewModel.Generic.Select2OptionViewModel[], allowClear?: boolean); + select2LocalDataNoSearch(dataFn: () => Models.ViewModel.Generic.Select2OptionViewModel[], allowClear?: boolean, formatResults?: (input) => string); select2MultipleLocalDataNoSearch(dataFn: () => Models.ViewModel.Generic.Select2OptionViewModel[], allowClear?: boolean); - select2LocalDataFormatted(dataFn: () => Models.ViewModel.Generic.Select2OptionViewModel[], formatResults: (input: Models.ViewModel.Generic.ISelect2Model) => string, allowClear?: boolean); + select2LocalDataFormatted>(dataFn: () => TVm[], formatResults: (input: TVm) => string, allowClear?: boolean): any; } export class Select2LoadingService implements ISelect2LoadingService { @@ -27,7 +27,7 @@ }; } - select2LocalDataFormatted(dataFn: () => Models.ViewModel.Generic.Select2OptionViewModel[], formatResults: (input) => string, allowClear= true) { + select2LocalDataFormatted>(dataFn: () => TVm[], formatResults: (input: TVm) => string, allowClear?: boolean) { return { data: () => ({ "results": dataFn() }), allowClear: allowClear, @@ -35,11 +35,12 @@ }; } - select2LocalDataNoSearch(dataFn: () => Models.ViewModel.Generic.Select2OptionViewModel[], allowClear = true) { + select2LocalDataNoSearch(dataFn: () => Models.ViewModel.Generic.Select2OptionViewModel[], allowClear = true, formatResults?: (input) => string) { return { minimumResultsForSearch: Infinity, data: () => ({ "results": dataFn() }), - allowClear: allowClear + allowClear: allowClear, + formatResult: formatResults }; } @@ -76,7 +77,7 @@ } } }; - if (!! formatResult) { + if (!!formatResult) { config.formatResult = formatResult; } return config; @@ -119,7 +120,7 @@ quietMillis: Select2LoadingService.defaultQuietMillis, transport(queryParams) { const extraParams = paramArray ? `&${paramArray.join("&")}` : ""; - const res = self.$http.get(url + "?" + nameContentQueryParamName + "=" + encodeURIComponent(queryParams.data.query) + extraParams).then(queryParams.success, () => null); + const res = self.$http.get(url + "?" + nameContentQueryParamName + "=" + encodeURIComponent(queryParams.data.query) + extraParams).then(queryParams.success, () => null); return res; }, diff --git a/Presentation.Web/app/services/sts-organization-sync-service.ts b/Presentation.Web/app/services/sts-organization-sync-service.ts index cbb57bc9d7..74d1d8a213 100644 --- a/Presentation.Web/app/services/sts-organization-sync-service.ts +++ b/Presentation.Web/app/services/sts-organization-sync-service.ts @@ -1,11 +1,13 @@ module Kitos.Services.Organization { export interface IStsOrganizationSyncService { getConnectionStatus(organizationUuid: string): ng.IPromise; - createConnection(organizationUuidid: string, synchronizationDepth: number | null): ng.IPromise; + createConnection(organizationUuid: string, synchronizationDepth: number | null, subscribesToUpdates: boolean): ng.IPromise; getConnectionUpdateConsequences(organizationUuid: string, synchronizationDepth: number | null): ng.IPromise; getSnapshot(organizationUuid: string): ng.IPromise; - disconnect(organizationUuidid: string): ng.IPromise; - updateConnection(organizationUuidid: string, synchronizationDepth: number | null): ng.IPromise; + unsubscribeFromAutomaticUpdates(organizationUuid: string): ng.IPromise; + disconnect(organizationUuid: string, purgeUnusedExternalUnits: boolean): ng.IPromise; + updateConnection(organizationUuid: string, synchronizationDepth: number | null, subscribesToUpdates: boolean): ng.IPromise; + getConnectionChangeLogs(organizationUuid: string, numberOfLogs: number): ng.IPromise>; } export class StsOrganizationSyncService implements IStsOrganizationSyncService { @@ -72,37 +74,54 @@ }); } - createConnection(organizationUuidid: string, synchronizationDepth: number | null): ng.IPromise { + createConnection(organizationUuid: string, synchronizationDepth: number | null, subscribesToUpdates: boolean): ng.IPromise { return this.apiUseCaseFactory.createCreation("Forbindelse til FK Organisation", () => { - return this.genericApiWrapper.post(`${this.getBasePath(organizationUuidid)}/connection`, { - synchronizationDepth: synchronizationDepth + return this.genericApiWrapper.post(`${this.getBasePath(organizationUuid)}/connection`, { + synchronizationDepth: synchronizationDepth, + subscribeToUpdates: subscribesToUpdates }); }).executeAsync(() => { //Clear cache after - this.purgeCache(organizationUuidid); + this.purgeCache(organizationUuid); }); } - disconnect(organizationUuidid: string): ng.IPromise { + disconnect(organizationUuid: string, purgeUnusedExternalUnits: boolean): ng.IPromise { return this.apiUseCaseFactory.createDeletion("Forbindelse til FK Organisation", () => { - return this.genericApiWrapper.delete(`${this.getBasePath(organizationUuidid)}/connection`); + return this.genericApiWrapper.delete(`${this.getBasePath(organizationUuid)}/connection`, + { + purgeUnusedExternalUnits: purgeUnusedExternalUnits + }); }).executeAsync((result) => { //Clear cache after - this.purgeCache(organizationUuidid); + this.purgeCache(organizationUuid); return result; }); } - updateConnection(organizationUuidid: string, synchronizationDepth: number | null): ng.IPromise { + updateConnection(organizationUuid: string, synchronizationDepth: number | null, subscribesToUpdates: boolean): ng.IPromise { return this.apiUseCaseFactory.createUpdate("Forbindelse til FK Organisation", () => { - return this.genericApiWrapper.put(`${this.getBasePath(organizationUuidid)}/connection`, { - synchronizationDepth: synchronizationDepth + return this.genericApiWrapper.put(`${this.getBasePath(organizationUuid)}/connection`, { + synchronizationDepth: synchronizationDepth, + subscribeToUpdates: subscribesToUpdates }); }).executeAsync(() => { - //Clear cache after - this.purgeCache(organizationUuidid); + this.purgeCache(organizationUuid); + }); + } + + unsubscribeFromAutomaticUpdates(organizationUuid: string): ng.IPromise { + return this.apiUseCaseFactory.createUpdate("Automatisk import af opdateringer", () => { + return this.genericApiWrapper.delete(`${this.getBasePath(organizationUuid)}/connection/subscription`); + }).executeAsync((success) => { + this.purgeCache(organizationUuid); + return success; }); } + + getConnectionChangeLogs(organizationUuid: string, numberOfLogs: number): ng.IPromise> { + return this.genericApiWrapper.getDataFromUrl>(`${this.getBasePath(organizationUuid)}/connection/change-log?numberOfChangeLogs=${numberOfLogs}`); + } } app.service("stsOrganizationSyncService", StsOrganizationSyncService); diff --git a/Presentation.Web/app/shared/generic-prompt/generic-prompt.dialog.ts b/Presentation.Web/app/shared/generic-prompt/generic-prompt.dialog.ts new file mode 100644 index 0000000000..7c346c6d10 --- /dev/null +++ b/Presentation.Web/app/shared/generic-prompt/generic-prompt.dialog.ts @@ -0,0 +1,103 @@ +module Kitos.Shared.Generic.Prompt { + + export enum GenericCommandCategory { + Success = "success", + Primary = "primary", + Warning = "warning", + Danger = "danger" + } + + export interface GenericPromptCommand { + category: GenericCommandCategory + text: string + value: T + } + + export interface GenericPromptConfig { + title: string | null + body?: string + bodyTemplatePath?: string + includeStandardCancelButton?: boolean + commands: Array> + } + + export interface IGenericPromptFactory { + open(config: GenericPromptConfig): ng.ui.bootstrap.IModalInstanceService + } + + export class GenericPromptFactory implements IGenericPromptFactory { + static $inject = ["$uibModal"]; + constructor(private readonly $uibModal: ng.ui.bootstrap.IModalService) { } + + open(config: GenericPromptConfig): ng.ui.bootstrap.IModalInstanceService { + return this.$uibModal.open({ + windowClass: "modal fade in", + templateUrl: "app/shared/generic-prompt/generic-prompt.view.html", + controller: GenericPromptController, + controllerAs: "vm", + resolve: { + "genericPromptConfig": [() => config], + }, + backdrop: "static", //Make sure accidental click outside the modal does not close it during the import process + }); + } + } + + export interface GenericPromptCommandButton { + category: GenericCommandCategory + text: string + handle: () => void + } + + class GenericPromptController { + static $inject = ["$uibModalInstance", "genericPromptConfig"]; + readonly buttons: GenericPromptCommandButton[] = []; + title: string | null = null; + body: string | null = null; + bodyTemplatePath: string | null = null; + + constructor( + private readonly $uibModalInstance: ng.ui.bootstrap.IModalServiceInstance, + private readonly genericPromptConfig: GenericPromptConfig) { + } + + $onInit() { + if (!this.genericPromptConfig) { + console.error("Missing prompt config"); + } else { + this.title = this.genericPromptConfig.title ?? null; + this.body = this.genericPromptConfig.body ?? null; + this.bodyTemplatePath = this.genericPromptConfig.bodyTemplatePath ?? null; + + //Add custom commands + for (var command of this.genericPromptConfig.commands) { + const buttonCommand = command; + this.addButton(command.category, command.text, () => this.closeDialog(buttonCommand)); + } + + //Add standard cancel if requested + if (this.genericPromptConfig.includeStandardCancelButton) { + this.addButton(GenericCommandCategory.Warning, "Annuller", () => this.cancel()); + } + } + } + + private addButton(category: GenericCommandCategory, text: string, handle: () => void) { + this.buttons.push({ + category: category, + text: text, + handle: handle + }); + } + + cancel() { + this.$uibModalInstance.dismiss(); + } + + private closeDialog(command: GenericPromptCommand) { + this.$uibModalInstance.close(command.value); + } + } + + app.service("genericPromptFactory", GenericPromptFactory) +} \ No newline at end of file diff --git a/Presentation.Web/app/shared/generic-prompt/generic-prompt.view.html b/Presentation.Web/app/shared/generic-prompt/generic-prompt.view.html new file mode 100644 index 0000000000..c6557709fe --- /dev/null +++ b/Presentation.Web/app/shared/generic-prompt/generic-prompt.view.html @@ -0,0 +1,23 @@ + +
    + + + +
    \ No newline at end of file diff --git a/Presentation.Web/app/shared/optionList/optionList.directive.ts b/Presentation.Web/app/shared/optionList/optionList.directive.ts deleted file mode 100644 index a231dea5d1..0000000000 --- a/Presentation.Web/app/shared/optionList/optionList.directive.ts +++ /dev/null @@ -1,30 +0,0 @@ -(function (ng, app) { - 'use strict'; - - app.directive('optionList', [ - '$http', function ($http) { - return { - scope: { - optionsUrl: '@', - title: '@', - }, - templateUrl: 'app/shared/optionList/optionList.view.html', - link: function (scope, element, attrs) { - - scope.list = []; - - $http.get(scope.optionsUrl + '?nonsuggestions') - .then(function onSuccess(result) { - _.each(result.data.response, function (v) { - scope.list.push({ - id: v.id, - name: v.name, - note: v.note - }); - }); - }); - } - }; - } - ]); -})(angular, app); diff --git a/Presentation.Web/app/shared/optionList/optionList.view.html b/Presentation.Web/app/shared/optionList/optionList.view.html deleted file mode 100644 index ccad163446..0000000000 --- a/Presentation.Web/app/shared/optionList/optionList.view.html +++ /dev/null @@ -1,24 +0,0 @@ - -
    -
    -

    {{ title }}

    -
    -
    -
    Ingen valgmuligheder
    -
    -
    -
    - -
    -
    -
    -
    - -
    diff --git a/Presentation.Web/app/shared/organization-tree/organization-tree.view.html b/Presentation.Web/app/shared/organization-tree/organization-tree.view.html index b54c538908..59a3fd1aec 100644 --- a/Presentation.Web/app/shared/organization-tree/organization-tree.view.html +++ b/Presentation.Web/app/shared/organization-tree/organization-tree.view.html @@ -1,6 +1,6 @@ 
    -
      +
    diff --git a/Presentation.Web/app/shared/selectOrgUnit/select2OrgUnit.directive.ts b/Presentation.Web/app/shared/selectOrgUnit/select2OrgUnit.directive.ts index e1010c34f3..062db4f7de 100644 --- a/Presentation.Web/app/shared/selectOrgUnit/select2OrgUnit.directive.ts +++ b/Presentation.Web/app/shared/selectOrgUnit/select2OrgUnit.directive.ts @@ -5,21 +5,19 @@ "$scope", "select2LoadingService", ($scope: any, select2LoadingService: Kitos.Services.Select2LoadingService) => { - $scope.select2Config = select2LoadingService.select2LocalDataFormatted(() => $scope.options, formatResults, $scope.allowClear); - function formatResults(result: Kitos.Models.ViewModel.Generic.Select2OptionViewModelWithIndentation): string { - function visit(text: string, indentationLevel: number): string { - if (indentationLevel <= 0) { - return text; - } - //indentation is four non breaking spaces - return visit("    " + text, indentationLevel - 1); + const options: Kitos.Models.ViewModel.Generic.Select2OptionViewModelWithIndentation[] = $scope.options; + if ($scope.renderUnitOriginIndication === true) { + const hasExternalUnits = options.some(x => x.optionalObjectContext?.externalOriginUuid !== null); + if (hasExternalUnits) { + $scope.hasExternalUnits = true; + $scope.select2Config = select2LoadingService.select2LocalDataFormatted(() => options, unit => Kitos.Helpers.Select2OptionsFormatHelper.formatIndentation(unit, true), $scope.allowClear); + return; } - - var formattedResult = visit(result.text, result.indentationLevel); - return formattedResult; } - }]); + + $scope.select2Config = select2LoadingService.select2LocalDataFormatted(() => options, unit => Kitos.Helpers.Select2OptionsFormatHelper.formatIndentation(unit, false), $scope.allowClear); + }]); app.directive("select2OrgUnit", [ function () { @@ -36,7 +34,8 @@ field: "@", disabled: "=ngDisabled", allowClear: "=", - onChange: "&" + onChange: "&", + renderUnitOriginIndication: "=" }, controller: "select2OrgUnitController", link: function (scope, element, attr, ctrl) { diff --git a/Presentation.Web/app/shared/selectOrgUnit/select2OrgUnit.view.html b/Presentation.Web/app/shared/selectOrgUnit/select2OrgUnit.view.html index 2b1c8d0d7d..95ad25c5d6 100644 --- a/Presentation.Web/app/shared/selectOrgUnit/select2OrgUnit.view.html +++ b/Presentation.Web/app/shared/selectOrgUnit/select2OrgUnit.view.html @@ -1,11 +1,17 @@  - + ng-disabled="disabled"/> + + +
    +
    Enheder oprettet i KITOS
    +
    Enheder synkroniseret fra FK Organisation
    +
    \ No newline at end of file diff --git a/Tests.Integration.Presentation.Web/Organizations/OrganizationTest.cs b/Tests.Integration.Presentation.Web/Organizations/OrganizationTest.cs index 903b580f4b..ce3a5ab3dc 100644 --- a/Tests.Integration.Presentation.Web/Organizations/OrganizationTest.cs +++ b/Tests.Integration.Presentation.Web/Organizations/OrganizationTest.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Core.DomainModel; using Core.DomainModel.Organization; +using Core.DomainServices.Extensions; using Tests.Integration.Presentation.Web.Tools; using Tests.Toolkit.Patterns; using Xunit; @@ -67,7 +68,7 @@ public async Task Can_Create_Organization_Of_Type(OrganizationRole role, Organiz //Arrange var login = await HttpApi.GetCookieAsync(role); var name = A(); - var cvr = (A() % 9999999999).ToString("D10"); + var cvr = CreateNewCvr(); const AccessModifier accessModifier = AccessModifier.Public; //Act - perform the action with the actual role @@ -93,7 +94,7 @@ public async Task Cannot_Create_Organization_Of_Type(OrganizationRole role, Orga //Arrange var login = await HttpApi.GetCookieAsync(role); var name = A(); - var cvr = (A() % 9999999999).ToString("D10"); + var cvr = CreateNewCvr(); const AccessModifier accessModifier = AccessModifier.Public; //Act - perform the action with the actual role @@ -109,7 +110,7 @@ public async Task Can_Get_Organizations_Filtered_By_Cvr_Or_Name() //Arrange var login = await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); var nameOrg1 = A(); - var cvrOrg1 = (A() % 9999999999).ToString("D10"); + var cvrOrg1 = CreateNewCvr(); const AccessModifier accessModifier = AccessModifier.Public; //Act - perform the action with the actual role @@ -127,5 +128,52 @@ public async Task Can_Get_Organizations_Filtered_By_Cvr_Or_Name() var resultFilteredByName = await organizationsFilteredByName.ReadResponseBodyAsKitosApiResponseAsync>(); Assert.True(resultFilteredByName.Exists(prp => prp.Name.Contains(nameOrg1))); } + + [Fact] + public async Task Can_Update_Organization() + { + //Arrange + var login = await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); + var organizationName = A(); + var cvr = CreateNewCvr(); + const AccessModifier accessModifier = AccessModifier.Public; + + var organization = await OrganizationHelper.CreateOrganizationAsync(TestEnvironment.DefaultOrganizationId, organizationName, cvr, OrganizationTypeKeys.Kommune, accessModifier, login); + + var newName = A(); + var newCvr = CreateNewCvr(); + + //Act + var result = await OrganizationHelper.UpdateAsync(organization.Id, TestEnvironment.DefaultOrganizationId, newName, newCvr, login); + + Assert.Equal(newName, result.Name); + Assert.Equal(newCvr, result.Cvr); + } + + [Fact] + public async Task Update_Organization_Cvr_Returns_Forbidden_When_LocalAdmin() + { + //Arrange + var login = await HttpApi.GetCookieAsync(OrganizationRole.LocalAdmin); + var organizationName = A(); + var cvr = CreateNewCvr(); + const AccessModifier accessModifier = AccessModifier.Public; + + var organization = await OrganizationHelper.CreateOrganizationAsync(TestEnvironment.DefaultOrganizationId, organizationName, cvr, OrganizationTypeKeys.Kommune, accessModifier); + + var newCvr = CreateNewCvr(); + + //Act + using var result = await OrganizationHelper.SendUpdateAsync(organization.Id, TestEnvironment.DefaultOrganizationId, organizationName, newCvr, login); + + Assert.Equal(HttpStatusCode.Forbidden, result.StatusCode); + var cvrFromDb = DatabaseAccess.MapFromEntitySet(x => x.AsQueryable().ById(organization.Id).Cvr); + Assert.Equal(cvr, cvrFromDb); + } + + private string CreateNewCvr() + { + return (A() % 9999999999).ToString("D10"); + } } } diff --git a/Tests.Integration.Presentation.Web/Organizations/OrganizationUnitTests.cs b/Tests.Integration.Presentation.Web/Organizations/OrganizationUnitTests.cs index 10a452d152..36eaaf6e21 100644 --- a/Tests.Integration.Presentation.Web/Organizations/OrganizationUnitTests.cs +++ b/Tests.Integration.Presentation.Web/Organizations/OrganizationUnitTests.cs @@ -40,10 +40,5 @@ private async Task CreateOrganizationAsync() var organization = await OrganizationHelper.CreateOrganizationAsync(TestEnvironment.DefaultOrganizationId, organizationName, "13370000", OrganizationTypeKeys.Kommune, AccessModifier.Public); return organization; } - - private string CreateEmail() - { - return $"{nameof(OrganizationUnitTests)}{A()}@test.dk"; - } } } diff --git a/Tests.Integration.Presentation.Web/Organizations/StsOrganizationSynchronizationApiTest.cs b/Tests.Integration.Presentation.Web/Organizations/StsOrganizationSynchronizationApiTest.cs index bb2e0f161a..1033bf6a39 100644 --- a/Tests.Integration.Presentation.Web/Organizations/StsOrganizationSynchronizationApiTest.cs +++ b/Tests.Integration.Presentation.Web/Organizations/StsOrganizationSynchronizationApiTest.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Core.Abstractions.Types; using Core.DomainModel; using Core.DomainModel.Organization; using Core.DomainServices.Extensions; @@ -93,18 +94,20 @@ public async Task Can_GET_ConnectionStatus(string cvr, bool expectConnected, Che Assert.Equal(expectedError, root.AccessStatus.Error); } - [Fact] - public async Task Can_POST_Create_Connection() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Can_POST_Create_Connection(bool subscribe) { //Arrange var cookie = await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); var targetOrgUuid = await CreateOrgWithCvr(AuthorizedCvr); const int levels = 2; - using var getResponse = await SendGetSnapshotAsync(levels, targetOrgUuid, cookie); + using var getResponse = await SendGetSnapshotAsync(levels, targetOrgUuid, cookie).WithExpectedResponseCode(HttpStatusCode.OK); var expectedImport = await getResponse.ReadResponseBodyAsKitosApiResponseAsync(); //Act - using var response = await SendPostCreateConnectionAsync(targetOrgUuid, cookie, levels); + using var response = await SendPostCreateConnectionAsync(targetOrgUuid, cookie, levels, subscribe); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -115,13 +118,16 @@ public async Task Can_POST_Create_Connection() Assert.NotNull(organization.StsOrganizationConnection); Assert.True(organization.StsOrganizationConnection.Connected); Assert.Equal(levels, organization.StsOrganizationConnection.SynchronizationDepth); + Assert.Equal(subscribe, organization.StsOrganizationConnection.SubscribeToUpdates); AssertImportedTree(expectedImport, dbRoot, OrganizationUnitOrigin.STS_Organisation); return true; }); } - [Fact] - public async Task Can_DELETE_Connection() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Can_DELETE_Connection(bool purge) { //Arrange var cookie = await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); @@ -130,13 +136,21 @@ public async Task Can_DELETE_Connection() var connectionUrl = TestEnvironment.CreateUrl($"api/v1/organizations/{targetOrgUuid:D}/sts-organization-synchronization/connection"); var getUrl = TestEnvironment.CreateUrl($"api/v1/organizations/{targetOrgUuid:D}/sts-organization-synchronization/snapshot?levels={levels}"); using var getResponse = await HttpApi.GetWithCookieAsync(getUrl, cookie); - var expectedImport = await getResponse.ReadResponseBodyAsKitosApiResponseAsync(); + var expectedStructureAfterDisconnect = await getResponse.ReadResponseBodyAsKitosApiResponseAsync(); + if (purge) + { + //We expect all of the external sub units to have been removed + expectedStructureAfterDisconnect.Children = Array.Empty(); + } using var response = await HttpApi.PostWithCookieAsync(connectionUrl, cookie, new ConnectToStsOrganizationRequestDTO { SynchronizationDepth = levels }); //Act - using var deleteResponse = await HttpApi.DeleteWithCookieAsync(connectionUrl, cookie); + using var deleteResponse = await HttpApi.DeleteWithCookieAsync(connectionUrl, cookie,new DisconnectFromStsOrganizationRequestDTO() + { + PurgeUnusedExternalUnits = purge + }); //Assert DatabaseAccess.MapFromEntitySet(orgs => @@ -148,7 +162,7 @@ public async Task Can_DELETE_Connection() Assert.Null(organization.StsOrganizationConnection.SynchronizationDepth); //Assert that the imported stuff is till there - just converted to kitos units - AssertImportedTree(expectedImport, dbRoot, OrganizationUnitOrigin.Kitos); + AssertImportedTree(expectedStructureAfterDisconnect, dbRoot, OrganizationUnitOrigin.Kitos); return true; }); @@ -482,6 +496,43 @@ public async Task Can_PUT_UPDATE_Consequences_With_Relocation_Consequences() Assert.Contains(uuidOfExpectedMoval, movedItemChildrenUuids); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Can_PUT_UPDATE_Consequences_With_SubscriptionChanges(bool initiallySubscribe) + { + //Arrange + var cookie = await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); + var targetOrgUuid = await CreateOrgWithCvr(AuthorizedCvr); + const int levels = 1; + using var postResponse = await SendPostCreateConnectionAsync(targetOrgUuid, cookie, levels, initiallySubscribe); + + //Act + using var putResponse = await SendPutUpdateConsequencesAsync(targetOrgUuid, levels, cookie, !initiallySubscribe); + + //Assert + Assert.Equal(HttpStatusCode.OK, putResponse.StatusCode); + using var getResponse = await SendGetConnectionStatusAsync(targetOrgUuid, cookie).WithExpectedResponseCode(HttpStatusCode.OK); + var dto = await getResponse.ReadResponseBodyAsKitosApiResponseAsync(); + Assert.Equal(!initiallySubscribe, dto.SubscribesToUpdates); + } + + [Fact] + public async Task Can_DELETE_Subscription() + { + //Arrange + var cookie = await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); + var targetOrgUuid = await CreateOrgWithCvr(AuthorizedCvr, true, true); + + //Act + using var putResponse = await SendDeleteSubscriptionAsync(targetOrgUuid, cookie); + + //Assert + Assert.Equal(HttpStatusCode.OK, putResponse.StatusCode); + var subscriptionRemoved = DatabaseAccess.MapFromEntitySet(r=>r.AsQueryable().ByUuid(targetOrgUuid).StsOrganizationConnection?.SubscribeToUpdates == false); + Assert.True(subscriptionRemoved); + } + [Fact] public async Task Can_PUT_UPDATE_Consequences_With_Conversion_Consequences() { @@ -521,6 +572,110 @@ await ItContractV2Helper.PostContractAsync(globalAdminToken.Token, Assert.Null(convertedUnit.ExternalOriginUuid); } + [Fact] + public async Task Can_GET_LOGS() + { + //Arrange + var cookie = await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); + var targetOrgUuid = await CreateOrgWithCvr(AuthorizedCvr); + const int firstRequestLevels = 2; + const int secondRequestLevels = 3; + using var postResponse = await SendPostCreateConnectionAsync(targetOrgUuid, cookie, firstRequestLevels); + + //Addition consequences + using var additionConsequencesResponse = await SendGetUpdateConsequencesAsync(targetOrgUuid, secondRequestLevels, cookie); + Assert.Equal(HttpStatusCode.OK, additionConsequencesResponse.StatusCode); + var additionConsequencesBody = await additionConsequencesResponse.ReadResponseBodyAsKitosApiResponseAsync(); + var additionConsequences = additionConsequencesBody.Consequences.ToList(); + + //Update consequences in order to log addition consequences + using var additionPutResponse = await SendPutUpdateConsequencesAsync(targetOrgUuid, secondRequestLevels, cookie); + Assert.Equal(HttpStatusCode.OK, additionPutResponse.StatusCode); + + //Setup other consequences + var expectedConversionUuid = Guid.NewGuid(); + DatabaseAccess.MutateEntitySet(repo => + { + var availableUnits = repo + .AsQueryable() + .Where(x => x.Organization.Uuid == targetOrgUuid + && x.Origin == OrganizationUnitOrigin.STS_Organisation + && x.Parent != null + && x.Children.Any()) + .ToList(); + + var unitToRename = availableUnits.FirstOrDefault(); + Assert.NotNull(unitToRename); + availableUnits.Remove(unitToRename); + + var unitToMove = availableUnits.FirstOrDefault(); + Assert.NotNull(unitToMove); + availableUnits.Remove(unitToMove); + + var targetUnit = availableUnits.FirstOrDefault(x => x.Id != unitToMove.ParentId); + Assert.NotNull(targetUnit); + + //Since the unitToRename won't be moved and all of it's children are meant for deletion select a unit to convert from there + var unitToConvert = unitToRename.Children.FirstOrDefault(); + Assert.NotNull(unitToConvert); + expectedConversionUuid = unitToConvert.Uuid; + + unitToRename.Name += "_rn1"; //change name so we expect an update to restore the old names + unitToMove.ParentId = targetUnit.Id; + }); + + var globalAdminToken = await HttpApi.GetTokenAsync(OrganizationRole.GlobalAdmin); + await ItContractV2Helper.PostContractAsync(globalAdminToken.Token, + new CreateNewContractRequestDTO + { + Name = A(), + OrganizationUuid = targetOrgUuid, + Responsible = new ContractResponsibleDataWriteRequestDTO { OrganizationUnitUuid = expectedConversionUuid } + }); + + using var otherConsequencesResponse = await SendGetUpdateConsequencesAsync(targetOrgUuid, firstRequestLevels, cookie); + Assert.Equal(HttpStatusCode.OK, otherConsequencesResponse.StatusCode); + var otherConsequencesBody = await otherConsequencesResponse.ReadResponseBodyAsKitosApiResponseAsync(); + var otherConsequences = otherConsequencesBody.Consequences.ToList(); + + //Log deletion, renaming, conversion and relocation changes + using var putResponse = await SendPutUpdateConsequencesAsync(targetOrgUuid, firstRequestLevels, cookie); + Assert.Equal(HttpStatusCode.OK, putResponse.StatusCode); + + //Act + using var logsResponse = await SendGetLogsAsync(targetOrgUuid, 5, cookie); + + //Assert + Assert.Equal(HttpStatusCode.OK, logsResponse.StatusCode); + var deserializedLogs = await logsResponse.ReadResponseBodyAsKitosApiResponseAsync>(); + var logsList = deserializedLogs.OrderBy(x => x.LogTime).ToList(); + + //2 updates + create + Assert.Equal(3, logsList.Count); + + //Addition consequences + var additionLogs = logsList[1]; + Assert.NotNull(additionLogs); + var additionLogsConsequences = additionLogs.Consequences.ToList(); + + Assert.Equal(additionConsequences.Count, additionLogsConsequences.Count); + AssertConsequenceLogs(additionConsequences, additionLogs); + + //Get second item in the list + var otherLogs = logsList.Last(); + Assert.NotNull(otherLogs); + var otherLogsConsequences = otherLogs.Consequences.ToList(); + + Assert.Equal(otherConsequences.Count, otherLogsConsequences.Count); + var consequenceCategories = otherLogsConsequences.Select(x => x.Category).ToList(); + Assert.Contains(ConnectionUpdateOrganizationUnitChangeCategory.Deleted, consequenceCategories); + Assert.Contains(ConnectionUpdateOrganizationUnitChangeCategory.Renamed, consequenceCategories); + Assert.Contains(ConnectionUpdateOrganizationUnitChangeCategory.Converted, consequenceCategories); + Assert.Contains(ConnectionUpdateOrganizationUnitChangeCategory.Moved, consequenceCategories); + + AssertConsequenceLogs(otherConsequences, otherLogs); + } + private static void AssertImportedTree(StsOrganizationOrgUnitDTO treeToImport, OrganizationUnit importedTree, OrganizationUnitOrigin expectedOrganizationUnitOrigin = OrganizationUnitOrigin.STS_Organisation, int? remainingLevelsToImport = null) { Assert.Equal(treeToImport.Name, importedTree.Name); @@ -535,8 +690,8 @@ private static void AssertImportedTree(StsOrganizationOrgUnitDTO treeToImport, O } else { - var childrenToImport = treeToImport.Children.OrderBy(x=>x.Name).ThenBy(x=>x.Uuid.ToString()).ToList(); - var importedUnits = importedTree.Children.OrderBy(x=>x.Name).ThenBy(x=>x.ExternalOriginUuid.GetValueOrDefault().ToString()).ToList(); + var childrenToImport = treeToImport.Children.OrderBy(x => x.Name).ThenBy(x => x.Uuid.ToString()).ToList(); + var importedUnits = importedTree.Children.OrderBy(x => x.Name).ThenBy(x => x.ExternalOriginUuid.GetValueOrDefault().ToString()).ToList(); Assert.Equal(childrenToImport.Count, importedUnits.Count); for (var i = 0; i < childrenToImport.Count; i++) { @@ -563,9 +718,17 @@ private async Task GetOrCreateOrgWithCvr(GetTokenResponseDTO token, string return targetOrgUuid; } - private async Task CreateOrgWithCvr(string cvr) + private async Task CreateOrgWithCvr(string cvr, bool fakeInitialConnection = false, bool fakeInitialSubscription = false) { var org = await OrganizationHelper.CreateOrganizationAsync(TestEnvironment.DefaultOrganizationId, $"StsSync_{A():N}", cvr, OrganizationTypeKeys.Kommune, AccessModifier.Public); + if (fakeInitialConnection) + { + DatabaseAccess.MutateEntitySet(repo => + { + var organization = repo.AsQueryable().ByUuid(org.Uuid); + organization.ConnectToExternalOrganizationHierarchy(OrganizationUnitOrigin.STS_Organisation, new ExternalOrganizationUnit(Guid.NewGuid(), "FAKE ROOT", new Dictionary(), new List()), Maybe.Some(1), fakeInitialSubscription); + }); + } return org.Uuid; } @@ -587,6 +750,24 @@ private static void AssertOrgTree(StsOrganizationOrgUnitDTO unit, HashSet } } + private static void AssertConsequenceLogs( + IEnumerable consequences, + StsOrganizationChangeLogResponseDTO logs) + { + var consequencesList = consequences.ToList(); + Assert.Equal(consequencesList.Count, logs.Consequences.Count()); + foreach (var consequence in consequencesList) + { + var logConsequence = logs.Consequences.FirstOrDefault(x => x.Uuid == consequence.Uuid && x.Category == consequence.Category); + Assert.NotNull(logConsequence); + + Assert.Equal(consequence.Uuid, logConsequence.Uuid); + Assert.Equal(consequence.Category, logConsequence.Category); + Assert.Equal(consequence.Name, logConsequence.Name); + Assert.Equal(consequence.Description, logConsequence.Description); + } + } + private static int CountMaxLevels(StsOrganizationOrgUnitDTO unit) { const int currentLevelContribution = 1; @@ -611,14 +792,15 @@ private static async Task SendGetConnectionStatusAsync(Guid return await HttpApi.GetWithCookieAsync(url, cookie); } - private static async Task SendPostCreateConnectionAsync(Guid targetOrgUuid, Cookie cookie, int levels) + private static async Task SendPostCreateConnectionAsync(Guid targetOrgUuid, Cookie cookie, int levels, bool subscribe = false) { var postUrl = TestEnvironment.CreateUrl( $"api/v1/organizations/{targetOrgUuid:D}/sts-organization-synchronization/connection"); return await HttpApi.PostWithCookieAsync(postUrl, cookie, new ConnectToStsOrganizationRequestDTO { - SynchronizationDepth = levels + SynchronizationDepth = levels, + SubscribeToUpdates = subscribe }); } @@ -630,15 +812,30 @@ private static async Task SendGetUpdateConsequencesAsync(Gu return await HttpApi.GetWithCookieAsync(getUrl, cookie); } - private static async Task SendPutUpdateConsequencesAsync(Guid targetOrgUuid, int levels, Cookie cookie) + private static async Task SendPutUpdateConsequencesAsync(Guid targetOrgUuid, int levels, Cookie cookie, bool subscribe = false) { var postUrl = TestEnvironment.CreateUrl( $"api/v1/organizations/{targetOrgUuid:D}/sts-organization-synchronization/connection"); return await HttpApi.PutWithCookieAsync(postUrl, cookie, new ConnectToStsOrganizationRequestDTO { - SynchronizationDepth = levels + SynchronizationDepth = levels, + SubscribeToUpdates = subscribe }); } + + private static async Task SendDeleteSubscriptionAsync(Guid targetOrgUuid, Cookie cookie) + { + var postUrl = TestEnvironment.CreateUrl($"api/v1/organizations/{targetOrgUuid:D}/sts-organization-synchronization/connection/subscription"); + return await HttpApi.DeleteWithCookieAsync(postUrl, cookie); + } + + private static async Task SendGetLogsAsync(Guid targetOrgUuid, int numberOfChangeLogs, Cookie cookie) + { + var postUrl = + TestEnvironment.CreateUrl( + $"api/v1/organizations/{targetOrgUuid:D}/sts-organization-synchronization/connection/change-log?numberOfChangeLogs={numberOfChangeLogs}"); + return await HttpApi.GetWithCookieAsync(postUrl, cookie); + } } } diff --git a/Tests.Integration.Presentation.Web/SystemUsage/ItSystemUsageOverviewReadModelsTest.cs b/Tests.Integration.Presentation.Web/SystemUsage/ItSystemUsageOverviewReadModelsTest.cs index 3cd41ef8ba..1787b9f3e7 100644 --- a/Tests.Integration.Presentation.Web/SystemUsage/ItSystemUsageOverviewReadModelsTest.cs +++ b/Tests.Integration.Presentation.Web/SystemUsage/ItSystemUsageOverviewReadModelsTest.cs @@ -97,20 +97,20 @@ public async Task ReadModels_Contain_Correct_Content() Assert.Equal(HttpStatusCode.Created, assignRoleResponse.StatusCode); // System changes - await ItSystemHelper.SendSetDisabledRequestAsync(system.Id, systemDisabled).DisposeAsync(); - await ItSystemHelper.SendSetParentSystemRequestAsync(system.Id, systemParent.Id, organizationId).DisposeAsync(); - await ItSystemHelper.SendSetBelongsToRequestAsync(system.Id, organizationId, organizationId).DisposeAsync(); // Using default organization as BelongsTo + await ItSystemHelper.SendSetDisabledRequestAsync(system.Id, systemDisabled).WithExpectedResponseCode(HttpStatusCode.NoContent).DisposeAsync(); + await ItSystemHelper.SendSetParentSystemRequestAsync(system.Id, systemParent.Id, organizationId).WithExpectedResponseCode(HttpStatusCode.OK).DisposeAsync(); + await ItSystemHelper.SendSetBelongsToRequestAsync(system.Id, organizationId, organizationId).WithExpectedResponseCode(HttpStatusCode.OK).DisposeAsync(); // Using default organization as BelongsTo var availableBusinessTypeOptions = (await ItSystemHelper.GetBusinessTypeOptionsAsync(organizationId)).ToList(); var businessType = availableBusinessTypeOptions[Math.Abs(A()) % availableBusinessTypeOptions.Count]; - await ItSystemHelper.SendSetBusinessTypeRequestAsync(system.Id, businessType.Id, organizationId).DisposeAsync(); + await ItSystemHelper.SendSetBusinessTypeRequestAsync(system.Id, businessType.Id, organizationId).WithExpectedResponseCode(HttpStatusCode.OK).DisposeAsync(); var taskRefs = (await ItSystemHelper.GetAvailableTaskRefsRequestAsync(system.Id)).ToList(); var taskRef = taskRefs[Math.Abs(A()) % taskRefs.Count]; - await ItSystemHelper.SendAddTaskRefRequestAsync(system.Id, taskRef.TaskRef.Id, organizationId).DisposeAsync(); + await ItSystemHelper.SendAddTaskRefRequestAsync(system.Id, taskRef.TaskRef.Id, organizationId).WithExpectedResponseCode(HttpStatusCode.OK).DisposeAsync(); // Parent system - await ItSystemHelper.SendSetDisabledRequestAsync(systemParent.Id, systemParentDisabled).DisposeAsync(); + await ItSystemHelper.SendSetDisabledRequestAsync(systemParent.Id, systemParentDisabled).WithExpectedResponseCode(HttpStatusCode.NoContent).DisposeAsync(); // System Usage changes var body = new @@ -436,7 +436,7 @@ public async Task ReadModels_ItSystemRightsHolderName_Is_Updated_When_Organizati Console.Out.WriteLine("Read models are up to date"); //Act - await OrganizationHelper.SendChangeOrganizationNameRequestAsync(organization1.Id, organizationName2, defaultOrganizationId).DisposeAsync(); + await OrganizationHelper.SendChangeOrganizationNameRequestAsync(organization1.Id, organizationName2, defaultOrganizationId).WithExpectedResponseCode(HttpStatusCode.OK).DisposeAsync(); //Wait for read model to rebuild (wait for the LAST mutation) await WaitForReadModelQueueDepletion(); Console.Out.WriteLine("Read models are up to date"); diff --git a/Tests.Integration.Presentation.Web/Tools/DatabaseAccess.cs b/Tests.Integration.Presentation.Web/Tools/DatabaseAccess.cs index bc1422bcfb..6615c83010 100644 --- a/Tests.Integration.Presentation.Web/Tools/DatabaseAccess.cs +++ b/Tests.Integration.Presentation.Web/Tools/DatabaseAccess.cs @@ -35,9 +35,17 @@ public static void MutateEntitySet(Action> mu using var kitosContext = TestEnvironment.GetDatabase(); using var repository = new GenericRepository(kitosContext); - mutate(repository); + try + { + mutate(repository); - repository.Save(); + repository.Save(); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } } /// diff --git a/Tests.Integration.Presentation.Web/Tools/OrganizationHelper.cs b/Tests.Integration.Presentation.Web/Tools/OrganizationHelper.cs index cdcc94406d..0c8e2c75b5 100644 --- a/Tests.Integration.Presentation.Web/Tools/OrganizationHelper.cs +++ b/Tests.Integration.Presentation.Web/Tools/OrganizationHelper.cs @@ -17,14 +17,14 @@ public static class OrganizationHelper { public static async Task GetOrganizationAsync(int organizationId, Cookie optionalCookie = null) { - using var response = await SendGetOrganizationRequestAsync(organizationId,optionalCookie); + using var response = await SendGetOrganizationRequestAsync(organizationId, optionalCookie); Assert.Equal(HttpStatusCode.OK, response.StatusCode); return await response.ReadResponseBodyAsKitosApiResponseAsync(); } public static async Task SendGetOrganizationRequestAsync(int organizationId, Cookie optionalCookie = null) { - var cookie = optionalCookie ?? await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); + var cookie = optionalCookie ?? await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); var url = TestEnvironment.CreateUrl($"api/organization/{organizationId}"); return await HttpApi.GetWithCookieAsync(url, cookie); } @@ -33,7 +33,7 @@ public static async Task GetContactPersonAsync(int organizatio { var cookie = await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); var url = TestEnvironment.CreateUrl($"api/contactPerson/{organizationId}"); //NOTE: This looks odd but it is how it works. On GET it uses the orgId and on patch it uses the contactPersonId - + using var response = await HttpApi.GetWithCookieAsync(url, cookie); Assert.Equal(HttpStatusCode.OK, response.StatusCode); return await response.ReadResponseBodyAsKitosApiResponseAsync(); @@ -64,6 +64,29 @@ public static async Task SendChangeContactPersonRequestAsyn return await HttpApi.PatchWithCookieAsync(TestEnvironment.CreateUrl($"api/contactPerson/{contactPersonId}?organizationId={organizationId}"), cookie, body); } + public static async Task UpdateAsync(int organizationId, int owningOrganizationId, string name, string cvr, Cookie optionalLogin = null) + { + using var createdResponse = await SendUpdateAsync(organizationId, owningOrganizationId, name, cvr, optionalLogin); + Assert.Equal(HttpStatusCode.OK, createdResponse.StatusCode); + var response = await createdResponse.ReadResponseBodyAsKitosApiResponseAsync(); + + return response; + } + + public static async Task SendUpdateAsync(int organizationId, int owningOrganizationId, string name, string cvr, Cookie optionalLogin = null) + { + var cookie = optionalLogin ?? await HttpApi.GetCookieAsync(OrganizationRole.GlobalAdmin); + + var body = new + { + organizationId = organizationId, + name = name, + cvr = cvr + }; + + return await HttpApi.PatchWithCookieAsync(TestEnvironment.CreateUrl($"api/organization/{organizationId}?organizationId={owningOrganizationId}"), cookie, body); + } + public static async Task CreateOrganizationAsync(int owningOrganizationId, string name, string cvr, OrganizationTypeKeys type, AccessModifier accessModifier, Cookie optionalLogin = null) { using var createdResponse = await SendCreateOrganizationRequestAsync(owningOrganizationId, name, cvr, type, accessModifier, optionalLogin); @@ -118,7 +141,7 @@ public static async Task CreateOrganizationUnitRequestAsync(int orga }; using var createdResponse = await HttpApi.PostWithCookieAsync(url, cookie, body); - + Assert.Equal(HttpStatusCode.Created, createdResponse.StatusCode); return await createdResponse.ReadResponseBodyAsKitosApiResponseAsync(); } diff --git a/Tests.Integration.Presentation.Web/Tools/TestEnvironment.cs b/Tests.Integration.Presentation.Web/Tools/TestEnvironment.cs index f5d8d04141..5476b55697 100644 --- a/Tests.Integration.Presentation.Web/Tools/TestEnvironment.cs +++ b/Tests.Integration.Presentation.Web/Tools/TestEnvironment.cs @@ -1,7 +1,14 @@ using System; using System.Collections.Generic; +using System.Data.Entity.Infrastructure.Interception; +using Core.Abstractions.Types; +using Core.DomainModel; using Core.DomainModel.Organization; +using Core.DomainServices.Context; +using Core.DomainServices.Time; using Infrastructure.DataAccess; +using Infrastructure.DataAccess.Interceptors; +using Moq; using Tests.Integration.Presentation.Web.Tools.Model; namespace Tests.Integration.Presentation.Web.Tools @@ -23,6 +30,9 @@ public static class TestEnvironment static TestEnvironment() { + //Fake the interception for EF in the text context + DbInterception.Add(new EFEntityInterceptor(() => new OperationClock(), () => Maybe.None, () => Mock.Of(x => x.Resolve() == new User() { Id = DefaultUserId }))); + var testEnvironment = GetEnvironmentVariable("KitosTestEnvironment", false); if (string.IsNullOrWhiteSpace(testEnvironment)) { diff --git a/Tests.Unit.Core.ApplicationServices/ApplicationServices/Handlers/AuthorizedUpdateOrganizationFromFKOrganisationCommandHandlerTest.cs b/Tests.Unit.Core.ApplicationServices/ApplicationServices/Handlers/AuthorizedUpdateOrganizationFromFKOrganisationCommandHandlerTest.cs new file mode 100644 index 0000000000..89b87142c6 --- /dev/null +++ b/Tests.Unit.Core.ApplicationServices/ApplicationServices/Handlers/AuthorizedUpdateOrganizationFromFKOrganisationCommandHandlerTest.cs @@ -0,0 +1,395 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Core.Abstractions.Extensions; +using Core.Abstractions.Types; +using Core.ApplicationServices.Model.Organizations; +using Core.ApplicationServices.Organizations.Handlers; +using Core.DomainModel.Events; +using Core.DomainModel.ItContract; +using Core.DomainModel.Organization; +using Core.DomainServices; +using Core.DomainServices.Context; +using Core.DomainServices.Model.StsOrganization; +using Core.DomainServices.Organizations; +using Core.DomainServices.Time; +using Infrastructure.Services.DataAccess; +using Moq; +using Serilog; +using Tests.Toolkit.Patterns; +using Xunit; + +namespace Tests.Unit.Core.ApplicationServices.Handlers +{ + public class AuthorizedUpdateOrganizationFromFKOrganisationCommandHandlerTest : WithAutoFixture + { + private DateTime _now; + private AuthorizedUpdateOrganizationFromFKOrganisationCommandHandler _sut; + private Mock _stsOrganizationUnitService; + private Mock> _organizationUnitRepositoryMock; + private Mock _domainEventsMock; + private Mock _databaseControlMock; + private Mock _transactionManagerMock; + private Mock> _stsChangeLogRepositoryMock; + + public AuthorizedUpdateOrganizationFromFKOrganisationCommandHandlerTest() + { + CreateSut(Maybe.None); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Execute_Performs_Update_Of_Existing_Synchronized_Tree(bool withPreloadedExternalRoot) + { + //Arrange + var organization = new Organization(); + organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root + const int oldDepth = 2; + const int newDepth = 3; + organization.StsOrganizationConnection = new StsOrganizationConnection + { + Organization = organization, + Connected = true, + SynchronizationDepth = oldDepth + }; + //Add some children to the root + organization.AddOrganizationUnit(CreateOrganizationUnit(organization, true), organization.GetRoot()); + organization.AddOrganizationUnit(CreateOrganizationUnit(organization, true), organization.GetRoot()); + + // In the external tree, ensure that the last leaf is missing - this should track a deleted unit. + // Extensive testing of the import algorithm is not part of this test but part of StsOrganizationalHierarchyUpdateStrategyTest.cs + // We just need to see that the app service deletes deleted units from db + var externalRoot = organization.GetRoot().Transform(ToExternalOrganizationUnit); + + //add the leaf that will be removed because it is missing in the external tree + var expectedDeletion = CreateOrganizationUnit(organization, true); + organization.AddOrganizationUnit(expectedDeletion, organization.GetRoot()); + + //Track a rename on the root and check that an event is raised + organization.GetRoot().UpdateName(A()); + + var preloadedExternalRoot = withPreloadedExternalRoot ? externalRoot : Maybe.None; + + if (!withPreloadedExternalRoot) + { + SetupResolveOrganizationTreeReturns(organization, externalRoot); + } + var transaction = ExpectTransaction(); + + //Act + var error = _sut.Execute(new AuthorizedUpdateOrganizationFromFKOrganisationCommand(organization, newDepth, false, preloadedExternalRoot)); + + //Assert + Assert.False(error.HasValue); + Assert.NotNull(organization.StsOrganizationConnection); + Assert.True(organization.StsOrganizationConnection.Connected); + Assert.Equal(newDepth, organization.StsOrganizationConnection.SynchronizationDepth); + VerifyChangesSaved(transaction, organization); + + _organizationUnitRepositoryMock.Verify(x => x.RemoveRange(It.Is>(units => units.Single() == expectedDeletion)), Times.Once()); + Assert.Equal(2, organization.GetRoot().Children.Count); + Assert.DoesNotContain(expectedDeletion, organization.GetRoot().Children); + if (withPreloadedExternalRoot) + { + //If external tree is provided beforehand, we expect the service not to be called + _stsOrganizationUnitService.Verify(x => x.ResolveOrganizationTree(organization), Times.Never()); + } + _domainEventsMock.Verify(x => x.Raise(It.Is>(ev => ev.Entity == organization.GetRoot())), Times.Once()); + } + + [Fact] + public void Execute_Fails_If_Not_Already_Connected() + { + //Arrange + var organization = new Organization(); + organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root + organization.StsOrganizationConnection = new StsOrganizationConnection + { + Organization = organization, + Connected = false, + }; + var externalRoot = organization.GetRoot().Transform(ToExternalOrganizationUnit); + + SetupResolveOrganizationTreeReturns(organization, externalRoot); + var transaction = ExpectTransaction(); + + //Act + var error = _sut.Execute(new AuthorizedUpdateOrganizationFromFKOrganisationCommand(organization, 3, false, Maybe.None)); + + //Assert + Assert.True(error.HasValue); + Assert.Equal(OperationFailure.BadState, error.Value.FailureType); + VerifyChangesNotSaved(transaction, organization); + } + + [Fact] + public void Execute_Fails_If_LoadOrgUnits_Fail() + { + //Arrange + var organization = new Organization(); + organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root + organization.StsOrganizationConnection = new StsOrganizationConnection + { + Organization = organization, + Connected = true, + }; + var resolveOrgUnitsError = A>(); + + SetupResolveOrganizationTreeReturns(organization, resolveOrgUnitsError); + var transaction = ExpectTransaction(); + + //Act + var error = _sut.Execute(new AuthorizedUpdateOrganizationFromFKOrganisationCommand(organization, 3, false, Maybe.None)); + + //Assert + Assert.True(error.HasValue); + Assert.Equal(resolveOrgUnitsError.FailureType, error.Value.FailureType); + VerifyChangesNotSaved(transaction, organization, false); + } + + [Fact] + public void Execute_Logs_Rename_Changes() + { + //Arrange + var organization = new Organization(); + organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root + const int oldDepth = 2; + const int newDepth = 3; + organization.StsOrganizationConnection = new StsOrganizationConnection + { + Organization = organization, + Connected = true, + SynchronizationDepth = oldDepth + }; + + var externalRoot = organization.GetRoot().Transform(ToExternalOrganizationUnit); + + //Track a rename on the root and check that an event is raised + organization.GetRoot().UpdateName(A()); + + SetupResolveOrganizationTreeReturns(organization, externalRoot); + ExpectTransaction(); + + //Act + var error = _sut.Execute(new AuthorizedUpdateOrganizationFromFKOrganisationCommand(organization, newDepth, false, Maybe.None)); + + //Assert + Assert.False(error.HasValue); + + var changeLog = Assert.Single(organization.StsOrganizationConnection.StsOrganizationChangeLogs); + var log = Assert.Single(changeLog.Entries); + Assert.Equal(ConnectionUpdateOrganizationUnitChangeType.Renamed, log.Type); + } + + [Fact] + public void Execute_Logs_Addition_Changes() + { + //Arrange + var organization = new Organization(); + organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root + const int oldDepth = 2; + const int newDepth = 3; + organization.StsOrganizationConnection = new StsOrganizationConnection + { + Organization = organization, + Connected = true, + SynchronizationDepth = oldDepth + }; + + organization.AddOrganizationUnit(CreateOrganizationUnit(organization), organization.GetRoot()); + + var externalRoot = organization.GetRoot().Transform(ToExternalOrganizationUnit); + + SetupResolveOrganizationTreeReturns(organization, externalRoot); + ExpectTransaction(); + + //Act + var error = _sut.Execute(new AuthorizedUpdateOrganizationFromFKOrganisationCommand(organization, newDepth, false, Maybe.None)); + + //Assert + Assert.False(error.HasValue); + + var changeLog = Assert.Single(organization.StsOrganizationConnection.StsOrganizationChangeLogs); + var log = Assert.Single(changeLog.Entries); + Assert.Equal(ConnectionUpdateOrganizationUnitChangeType.Added, log.Type); + } + + [Fact] + public void Execute_Logs_Deletion_Changes() + { + //Arrange + var organization = new Organization(); + organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root + const int oldDepth = 2; + const int newDepth = 1; + organization.StsOrganizationConnection = new StsOrganizationConnection + { + Organization = organization, + Connected = true, + SynchronizationDepth = oldDepth + }; + + organization.AddOrganizationUnit(CreateOrganizationUnit(organization, true), organization.GetRoot()); + + var externalRoot = organization.GetRoot().Transform(ToExternalOrganizationUnit); + + SetupResolveOrganizationTreeReturns(organization, externalRoot); + ExpectTransaction(); + + //Act + var error = _sut.Execute(new AuthorizedUpdateOrganizationFromFKOrganisationCommand(organization, newDepth, false, Maybe.None)); + + //Assert + Assert.False(error.HasValue); + + var changeLog = Assert.Single(organization.StsOrganizationConnection.StsOrganizationChangeLogs); + var log = Assert.Single(changeLog.Entries); + Assert.Equal(ConnectionUpdateOrganizationUnitChangeType.Deleted, log.Type); + } + + [Fact] + public void Execute_Logs_Conversion_Changes() + { + //Arrange + var organization = new Organization(); + organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root + const int oldDepth = 2; + const int newDepth = 1; + organization.StsOrganizationConnection = new StsOrganizationConnection + { + Organization = organization, + Connected = true, + SynchronizationDepth = oldDepth + }; + + var unitToConvert = CreateOrganizationUnit(organization, true); + unitToConvert.ResponsibleForItContracts = new List { new() }; + organization.AddOrganizationUnit(unitToConvert, organization.GetRoot()); + + var externalRoot = organization.GetRoot().Transform(ToExternalOrganizationUnit); + + SetupResolveOrganizationTreeReturns(organization, externalRoot); + ExpectTransaction(); + + //Act + var error = _sut.Execute(new AuthorizedUpdateOrganizationFromFKOrganisationCommand(organization, newDepth, false, Maybe.None)); + + //Assert + Assert.False(error.HasValue); + + var changeLog = Assert.Single(organization.StsOrganizationConnection.StsOrganizationChangeLogs); + var log = Assert.Single(changeLog.Entries); + Assert.Equal(ConnectionUpdateOrganizationUnitChangeType.Converted, log.Type); + } + + [Fact] + public void Execute_Logs_Relocation_Changes() + { + //Arrange + var organization = new Organization(); + organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root + const int depth = 3; + organization.StsOrganizationConnection = new StsOrganizationConnection + { + Organization = organization, + Connected = true, + SynchronizationDepth = depth + }; + var root = organization.GetRoot(); + var child = CreateOrganizationUnit(organization, true); + organization.AddOrganizationUnit(child, root); + var child2 = CreateOrganizationUnit(organization, true); + organization.AddOrganizationUnit(child2, root); + + var externalRoot = root.Transform(ToExternalOrganizationUnit); + + child.AddChild(child2); + root.Children.Remove(child2); + + SetupResolveOrganizationTreeReturns(organization, externalRoot); + ExpectTransaction(); + + //Act + var error = _sut.Execute(new AuthorizedUpdateOrganizationFromFKOrganisationCommand(organization, depth, false, Maybe.None)); + + //Assert + Assert.False(error.HasValue); + + var changeLog = Assert.Single(organization.StsOrganizationConnection.StsOrganizationChangeLogs); + var log = Assert.Single(changeLog.Entries); + Assert.Equal(ConnectionUpdateOrganizationUnitChangeType.Moved, log.Type); + } + + private static ExternalOrganizationUnit ToExternalOrganizationUnit(OrganizationUnit root) + { + return new ExternalOrganizationUnit( + root.ExternalOriginUuid.GetValueOrDefault(), + root.Name, new Dictionary(), + root.Children.Select(ToExternalOrganizationUnit).ToList() + ); + } + + private void VerifyChangesSaved(Mock transaction, Organization organization) + { + _databaseControlMock.Verify(x => x.SaveChanges(), Times.Once()); + transaction.Verify(x => x.Commit(), Times.Once()); + transaction.Verify(x => x.Rollback(), Times.Never()); + _domainEventsMock.Verify(x => x.Raise(It.Is>(org => org.Entity == organization))); + } + + private Mock ExpectTransaction() + { + var transaction = new Mock(); + _transactionManagerMock.Setup(x => x.Begin()).Returns(transaction.Object); + return transaction; + } + + private void SetupResolveOrganizationTreeReturns(Organization organization, Result> root) + { + _stsOrganizationUnitService.Setup(x => x.ResolveOrganizationTree(organization)).Returns(root); + } + + private void VerifyChangesNotSaved(Mock transaction, Organization organization, bool expectRollback = true) + { + _databaseControlMock.Verify(x => x.SaveChanges(), Times.Never()); + transaction.Verify(x => x.Commit(), Times.Never()); + transaction.Verify(x => x.Rollback(), expectRollback ? Times.Once() : Times.Never()); + _domainEventsMock.Verify(x => x.Raise(It.Is>(org => org.Entity == organization)), Times.Never()); + } + + private void CreateSut(Maybe activeUserId) + { + _stsOrganizationUnitService = new Mock(); + _organizationUnitRepositoryMock = new Mock>(); + _domainEventsMock = new Mock(); + _databaseControlMock = new Mock(); + _transactionManagerMock = new Mock(); + _stsChangeLogRepositoryMock = new Mock>(); + _sut = new AuthorizedUpdateOrganizationFromFKOrganisationCommandHandler( + _stsOrganizationUnitService.Object, + _organizationUnitRepositoryMock.Object, + Mock.Of(), + _domainEventsMock.Object, + _databaseControlMock.Object, + _transactionManagerMock.Object, + activeUserId, + Mock.Of(x => x.Now == _now), + _stsChangeLogRepositoryMock.Object + ); + } + + private OrganizationUnit CreateOrganizationUnit(Organization organization, bool isExternal = false) + { + return new OrganizationUnit + { + Organization = organization, + Id = A(), + Uuid = A(), + Name = A(), + ExternalOriginUuid = isExternal ? A() : null, + Origin = isExternal ? OrganizationUnitOrigin.STS_Organisation : OrganizationUnitOrigin.Kitos + }; + } + } +} diff --git a/Tests.Unit.Core.ApplicationServices/ApplicationServices/Handlers/SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandlerTest.cs b/Tests.Unit.Core.ApplicationServices/ApplicationServices/Handlers/SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandlerTest.cs new file mode 100644 index 0000000000..0f6732d76d --- /dev/null +++ b/Tests.Unit.Core.ApplicationServices/ApplicationServices/Handlers/SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandlerTest.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Mail; +using Core.ApplicationServices.Organizations.Handlers; +using Core.DomainModel; +using Core.DomainModel.Organization; +using Core.DomainServices; +using Infrastructure.Services.Configuration; +using Moq; +using Serilog; +using Tests.Toolkit.Patterns; +using Xunit; + +namespace Tests.Unit.Core.ApplicationServices.Handlers +{ + public class SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandlerTest : WithAutoFixture + { + private readonly SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandler _sut; + private readonly Mock _mailClientMock; + + public SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandlerTest() + { + _mailClientMock = new Mock(); + _sut = new SendEmailToStakeholdersOnExternalOrganizationConnectionUpdatedHandler(_mailClientMock.Object, Mock.Of(), new KitosUrl(new Uri("https://kitos-test.dk"))); + } + + [Fact] + public void Handle_Ignores_Event_If_UserInitiated() + { + //Arrange + var domainEvent = CreateEvent(new Organization(), OrganizationUnitOrigin.STS_Organisation, ExternalOrganizationChangeLogResponsible.User, Many()); + + //Act + _sut.Handle(domainEvent); + + //Assert + _mailClientMock.Verify(x => x.Send(It.IsAny()), Times.Never()); + } + + [Fact] + public void Handle_Ignores_Event_If_Origin_Is_Not_Sts_Org() + { + //Arrange + var domainEvent = CreateEvent(new Organization(), OrganizationUnitOrigin.Kitos, ExternalOrganizationChangeLogResponsible.Background, Many()); + + //Act + _sut.Handle(domainEvent); + + //Assert + _mailClientMock.Verify(x => x.Send(It.IsAny()), Times.Never()); + } + + [Fact] + public void Handle_Ignores_Event_If_No_Changes() + { + //Arrange + var domainEvent = CreateEvent(new Organization(), OrganizationUnitOrigin.STS_Organisation, ExternalOrganizationChangeLogResponsible.Background, Array.Empty()); + + //Act + _sut.Handle(domainEvent); + + //Assert + _mailClientMock.Verify(x => x.Send(It.IsAny()), Times.Never()); + } + + [Fact] + public void Handle_Sends_Email_To_LocalAdmins_If_Changes_Exist_And_Background_Job_for_Sts_Org() + { + //Arrange + var expectedRightMatch1 = CreateRight(OrganizationRole.LocalAdmin); + var expectedRightMatch2 = CreateRight(OrganizationRole.LocalAdmin); + var organization = new Organization() + { + Rights = new List() + { + expectedRightMatch1, + CreateRight(OrganizationRole.User), + expectedRightMatch2 + + } + }; + var domainEvent = CreateEvent(organization, OrganizationUnitOrigin.STS_Organisation, ExternalOrganizationChangeLogResponsible.Background, Many()); + + //Act + _sut.Handle(domainEvent); + + //Assert + var expectedEmails = new[] { expectedRightMatch1.User.Email, expectedRightMatch2.User.Email }; + _mailClientMock.Verify(x => x.Send(It.Is(message => message.To.Select(x => x.Address).SequenceEqual(expectedEmails))), Times.Once()); + } + + private OrganizationRight CreateRight(OrganizationRole organizationRole) + { + return new() + { + Role = organizationRole, + User = new User() + { + Email = $"{A():N}@test.dk" + } + }; + } + + private ExternalOrganizationConnectionUpdated CreateEvent(Organization organization, OrganizationUnitOrigin organizationUnitOrigin, ExternalOrganizationChangeLogResponsible responsible, IEnumerable logInputs) + { + var domainEvent = new ExternalOrganizationConnectionUpdated(organization, + Mock.Of(x => + x.Origin == organizationUnitOrigin), + new ExternalConnectionAddNewLogInput(A(), responsible, + A(), logInputs)); + return domainEvent; + } + } +} diff --git a/Tests.Unit.Core.ApplicationServices/ApplicationServices/Organizations/OrganizationTest.cs b/Tests.Unit.Core.ApplicationServices/ApplicationServices/Organizations/OrganizationTest.cs index e9d8747eb4..11f70d23e1 100644 --- a/Tests.Unit.Core.ApplicationServices/ApplicationServices/Organizations/OrganizationTest.cs +++ b/Tests.Unit.Core.ApplicationServices/ApplicationServices/Organizations/OrganizationTest.cs @@ -3,6 +3,7 @@ using System.Linq; using AutoFixture; using Core.Abstractions.Types; +using Core.DomainModel.Constants; using Core.DomainModel.ItContract; using Core.DomainModel.ItSystemUsage; using Core.DomainModel.Organization; @@ -31,7 +32,7 @@ public void ImportNewExternalOrganizationOrgTree_Fails_Of_Already_Connected() _sut.StsOrganizationConnection = new StsOrganizationConnection() { Connected = true }; //Act - var error = _sut.ConnectToExternalOrganizationHierarchy(OrganizationUnitOrigin.STS_Organisation, CreateExternalOrganizationUnit(), Maybe.None); + var error = _sut.ConnectToExternalOrganizationHierarchy(OrganizationUnitOrigin.STS_Organisation, CreateExternalOrganizationUnit(), Maybe.None, false); //Assert Assert.True(error.HasValue); @@ -54,7 +55,7 @@ public void ImportNewExternalOrganizationOrgTree_Imports_Entire_Subtree_If_No_Co ); //Act - var error = _sut.ConnectToExternalOrganizationHierarchy(OrganizationUnitOrigin.STS_Organisation, fullImportTree, Maybe.None); + var error = _sut.ConnectToExternalOrganizationHierarchy(OrganizationUnitOrigin.STS_Organisation, fullImportTree, Maybe.None, false); //Assert Assert.False(error.HasValue); @@ -66,9 +67,9 @@ public void ImportNewExternalOrganizationOrgTree_Imports_Entire_Subtree_If_No_Co } [Theory] - [InlineData(1)] - [InlineData(2)] - public void ImportNewExternalOrganizationOrgTree_Imports_Restricted_Subtree_If_No_Constraint_And_Registers_Sts_Org_Connection(int importedLevels) + [InlineData(1, true)] + [InlineData(2, false)] + public void ImportNewExternalOrganizationOrgTree_Imports_Restricted_Subtree_If_No_Constraint_And_Registers_Sts_Org_Connection(int importedLevels, bool subscribesToUpdates) { //Arrange var rootFromOrg = _sut.GetRoot(); @@ -82,16 +83,61 @@ public void ImportNewExternalOrganizationOrgTree_Imports_Restricted_Subtree_If_N ); //Act - var error = _sut.ConnectToExternalOrganizationHierarchy(OrganizationUnitOrigin.STS_Organisation, fullImportTree, importedLevels); + var error = _sut.ConnectToExternalOrganizationHierarchy(OrganizationUnitOrigin.STS_Organisation, fullImportTree, importedLevels, subscribesToUpdates); //Assert Assert.False(error.HasValue); Assert.NotNull(_sut.StsOrganizationConnection); Assert.Equal(importedLevels, _sut.StsOrganizationConnection.SynchronizationDepth); + Assert.Equal(subscribesToUpdates, _sut.StsOrganizationConnection.SubscribeToUpdates); Assert.True(_sut.StsOrganizationConnection.Connected); AssertImportedTree(fullImportTree, rootFromOrg, importedLevels); } + [Fact] + public void UnsubscribeFromAutomaticUpdates_Fails_StsConnection_Is_Not_Set() + { + //Act + var error = _sut.UnsubscribeFromAutomaticUpdates(OrganizationUnitOrigin.STS_Organisation); + + //Assert + Assert.True(error.HasValue); + Assert.Equal(OperationFailure.BadState, error.Value.FailureType); + } + + [Fact] + public void UnsubscribeFromAutomaticUpdates_Fails_StsConnection_Is_Not_Connected() + { + //Arrange + _sut.StsOrganizationConnection = new StsOrganizationConnection(); + + //Act + var error = _sut.UnsubscribeFromAutomaticUpdates(OrganizationUnitOrigin.STS_Organisation); + + //Assert + Assert.True(error.HasValue); + Assert.Equal(OperationFailure.BadState, error.Value.FailureType); + } + + [Fact] + public void UnsubscribeFromAutomaticUpdates_Succeeds() + { + //Arrange + _sut.StsOrganizationConnection = new StsOrganizationConnection() + { + Connected = true, + SubscribeToUpdates = true + }; + + //Act + var error = _sut.UnsubscribeFromAutomaticUpdates(OrganizationUnitOrigin.STS_Organisation); + + //Assert + Assert.False(error.HasValue); + Assert.False(_sut.StsOrganizationConnection.SubscribeToUpdates); + Assert.True(_sut.StsOrganizationConnection.Connected); + } + [Fact] public void Can_Add_OrganizationUnit() { @@ -425,6 +471,192 @@ public void Can_Relocate_OrganizationUnit_To_Unknown_Parent() Assert.Equal(OperationFailure.NotFound, error.Value.FailureType); } + [Fact] + public void Can_Add_ExternalImportLog() + { + //Arrange + var log = CreateNewChangeLogInput(); + _sut.StsOrganizationConnection = new StsOrganizationConnection() {Connected = true}; + + //Act + var result = _sut.AddExternalImportLog(OrganizationUnitOrigin.STS_Organisation, log); + + //Assert + Assert.True(result.Ok); + var logResult = result.Value; + Assert.Empty(logResult.RemovedChangeLogs); + + var savedLog = Assert.Single(_sut.StsOrganizationConnection.StsOrganizationChangeLogs); + } + + [Fact] + public void Add_ExternalImportLog_Removes_Oldest_Log_If_More_Than_5_Logs_Are_Present() + { + //Arrange + var log = CreateNewChangeLogInput(); + + var oldestLog = new StsOrganizationChangeLog {Id = A(), LogTime = DateTime.Now.AddDays(-A())}; + _sut.StsOrganizationConnection = new StsOrganizationConnection() + { + Connected = true, + StsOrganizationChangeLogs = + { + new StsOrganizationChangeLog{Id = A(), LogTime = DateTime.Now}, + new StsOrganizationChangeLog{Id = A(), LogTime = DateTime.Now}, + new StsOrganizationChangeLog{Id = A(), LogTime = DateTime.Now}, + new StsOrganizationChangeLog{Id = A(), LogTime = DateTime.Now}, + oldestLog + } + }; + + //Act + var result = _sut.AddExternalImportLog(OrganizationUnitOrigin.STS_Organisation, log); + + //Assert + Assert.True(result.Ok); + var logResult = result.Value; + var removedLog = Assert.Single(logResult.RemovedChangeLogs); + Assert.Equal(oldestLog, removedLog); + + Assert.Equal(ExternalConnectionConstants.TotalNumberOfLogs, _sut.StsOrganizationConnection.StsOrganizationChangeLogs.Count); + } + + [Fact] + public void Add_ExternalImportLog_Returns_BadState_If_Not_Connected() + { + //Arrange + _sut.StsOrganizationConnection = new StsOrganizationConnection + { + Connected = false, + }; + + //Act + var result = _sut.AddExternalImportLog(OrganizationUnitOrigin.STS_Organisation, CreateNewChangeLogInput()); + + //Assert + Assert.True(result.Failed); + Assert.Equal(OperationFailure.BadState, result.Error.FailureType); + } + + [Fact] + public void Add_ExternalImportLog_Returns_BadInput_If_Origin_Is_Kitos() + { + //Arrange + var origin = OrganizationUnitOrigin.Kitos; + //Act + var result = _sut.AddExternalImportLog(origin, CreateNewChangeLogInput()); + + //Assert + Assert.True(result.Failed); + Assert.Equal(OperationFailure.BadInput, result.Error.FailureType); + } + + [Fact] + public void Add_ExternalImportLog_Returns_BadInput_If_Connection_Is_Null() + { + //Arrange + _sut.StsOrganizationConnection = null; + + //Act + var result = _sut.AddExternalImportLog(OrganizationUnitOrigin.STS_Organisation, CreateNewChangeLogInput()); + + //Assert + Assert.True(result.Failed); + Assert.Equal(OperationFailure.BadState, result.Error.FailureType); + } + + [Fact] + public void GetStsOrganizationConnectionEntryLogs_Returns_BadState_If_Not_Connected() + { + //Arrange + _sut.StsOrganizationConnection = new StsOrganizationConnection + { + Connected = false, + }; + + //Act + var result = _sut.GetExternalConnectionEntryLogs(OrganizationUnitOrigin.STS_Organisation, A()); + + //Assert + Assert.True(result.Failed); + Assert.Equal(OperationFailure.BadState, result.Error.FailureType); + } + + [Fact] + public void GetStsOrganizationConnectionEntryLogs_Returns_BadInput_If_Origin_Is_Kitos() + { + //Arrange + var origin = OrganizationUnitOrigin.Kitos; + + //Act + var result = _sut.GetExternalConnectionEntryLogs(origin, A()); + + //Assert + Assert.True(result.Failed); + Assert.Equal(OperationFailure.BadInput, result.Error.FailureType); + } + + [Fact] + public void GetStsOrganizationConnectionEntryLogs_Returns_BadState_If_Connection_Is_Null() + { + //Arrange + _sut.StsOrganizationConnection = null; + + //Act + var result = _sut.GetExternalConnectionEntryLogs(OrganizationUnitOrigin.STS_Organisation, A()); + + //Assert + Assert.True(result.Failed); + Assert.Equal(OperationFailure.BadState, result.Error.FailureType); + } + + [Theory] + [InlineData(0)] + [InlineData(-10)] + public void GetStsOrganizationConnectionEntryLogs_Returns_BadInput_If_NumberOfLogs_Is_Lower_Than_One(int numberOfLogs) + { + //Arrange + _sut.StsOrganizationConnection = new StsOrganizationConnection + { + Connected = true, + }; + + //Act + var result = _sut.GetExternalConnectionEntryLogs(OrganizationUnitOrigin.STS_Organisation, numberOfLogs); + + //Assert + Assert.True(result.Failed); + Assert.Equal(OperationFailure.BadInput, result.Error.FailureType); + } + + [Theory] + [InlineData(1)] + [InlineData(5)] + public void GetStsOrganizationConnectionEntryLogs_Returns_Logs(int numberOfLogs) + { + //Arrange + + _sut.StsOrganizationConnection = new StsOrganizationConnection + { + Connected = true, + StsOrganizationChangeLogs = new List + { + new (){ LogTime = DateTime.Now.AddDays(1) }, + new (){ LogTime = DateTime.Now.AddDays(2) }, + new (){ LogTime = DateTime.Now.AddDays(3) }, + new (){ LogTime = DateTime.Now.AddDays(4) }, + new (){ LogTime = DateTime.Now.AddDays(5) } + } + }; + + //Act + var result = _sut.GetExternalConnectionEntryLogs(OrganizationUnitOrigin.STS_Organisation, numberOfLogs); + + //Assert + Assert.True(result.Ok); + Assert.Equal(numberOfLogs, result.Value.Count()); + } + private OrganizationUnit CreateOrganizationUnit() { return new OrganizationUnit @@ -460,6 +692,11 @@ private static void AssertImportedTree(ExternalOrganizationUnit treeToImport, Or } } + private ExternalConnectionAddNewLogInput CreateNewChangeLogInput() + { + return new ExternalConnectionAddNewLogInput(A(), A(), DateTime.Now, new List()); + } + private ExternalOrganizationUnit CreateExternalOrganizationUnit(params ExternalOrganizationUnit[] children) { return new ExternalOrganizationUnit(A(), A(), new Dictionary(), children ?? Array.Empty()); diff --git a/Tests.Unit.Core.ApplicationServices/ApplicationServices/Organizations/StsOrganizationSynchronizationServiceTest.cs b/Tests.Unit.Core.ApplicationServices/ApplicationServices/Organizations/StsOrganizationSynchronizationServiceTest.cs index 9d652e66bd..6b5bf1b33d 100644 --- a/Tests.Unit.Core.ApplicationServices/ApplicationServices/Organizations/StsOrganizationSynchronizationServiceTest.cs +++ b/Tests.Unit.Core.ApplicationServices/ApplicationServices/Organizations/StsOrganizationSynchronizationServiceTest.cs @@ -6,12 +6,16 @@ using Core.Abstractions.Types; using Core.ApplicationServices.Authorization; using Core.ApplicationServices.Authorization.Permissions; +using Core.ApplicationServices.Model.Organizations; using Core.ApplicationServices.Organizations; +using Core.DomainModel.Commands; using Core.DomainModel.Events; using Core.DomainModel.Organization; using Core.DomainServices; +using Core.DomainServices.Context; using Core.DomainServices.Model.StsOrganization; using Core.DomainServices.Organizations; +using Core.DomainServices.Time; using Infrastructure.Services.DataAccess; using Moq; using Serilog; @@ -31,7 +35,10 @@ public class StsOrganizationSynchronizationServiceTest : WithAutoFixture private readonly Mock _dbControlMock; private readonly Mock _transactionManagerMock; private readonly Mock _domainEventsMock; - private Mock> _organizationUnitRepositoryMock; + private readonly ActiveUserIdContext _activeUserIdContext; + private readonly Mock> _stsOrganziationChangeLogRepositoryMock; + private readonly Mock _operationClock; + private readonly Mock _commandBusMock; public StsOrganizationSynchronizationServiceTest(ITestOutputHelper testOutputHelper) { @@ -42,7 +49,11 @@ public StsOrganizationSynchronizationServiceTest(ITestOutputHelper testOutputHel _dbControlMock = new Mock(); _transactionManagerMock = new Mock(); _domainEventsMock = new Mock(); - _organizationUnitRepositoryMock = new Mock>(); + _activeUserIdContext = new ActiveUserIdContext(A()); + _stsOrganziationChangeLogRepositoryMock = new Mock>(); + _operationClock = new Mock(); + + _commandBusMock = new Mock(); _sut = new StsOrganizationSynchronizationService( _authorizationContextMock.Object, _stsOrganizationUnitService.Object, @@ -52,8 +63,10 @@ public StsOrganizationSynchronizationServiceTest(ITestOutputHelper testOutputHel _dbControlMock.Object, _transactionManagerMock.Object, _domainEventsMock.Object, - _organizationUnitRepositoryMock.Object - ); + _activeUserIdContext, + _stsOrganziationChangeLogRepositoryMock.Object, + _operationClock.Object, + _commandBusMock.Object); } protected override void OnFixtureCreated(Fixture fixture) @@ -182,9 +195,9 @@ public void GetStsOrganizationalHierarchy_Fails_If_LoadHierarchy_Fails() } [Theory] - [InlineData(true)] - [InlineData(false)] - public void Connect__Hierarchy_Returns_Success_And_Imports_External_Units_Into_Kitos(bool onlyRoot) + [InlineData(true, false)] + [InlineData(false, true)] + public void Connect_Hierarchy_Returns_Success_And_Imports_External_Units_Into_Kitos(bool onlyRoot, bool subscribe) { //Arrange var organizationId = A(); @@ -199,13 +212,14 @@ public void Connect__Hierarchy_Returns_Success_And_Imports_External_Units_Into_K var transaction = ExpectTransaction(); //Act - var error = _sut.Connect(organizationId, onlyRoot ? 1 : Maybe.None); + var error = _sut.Connect(organizationId, onlyRoot ? 1 : Maybe.None, subscribe); //Assert Assert.False(error.HasValue); - Assert.NotNull(organization.StsOrganizationConnection); - Assert.True(organization.StsOrganizationConnection.Connected); - Assert.Equal(onlyRoot ? 1 : (int?)null, organization.StsOrganizationConnection.SynchronizationDepth); + var connection = organization.StsOrganizationConnection; + Assert.True(connection.Connected); + Assert.Equal(subscribe, connection.SubscribeToUpdates); + Assert.Equal(onlyRoot ? 1 : (int?)null, connection.SynchronizationDepth); VerifyChangesSaved(transaction, organization); var kitosOrgRoot = organization.GetRoot(); @@ -216,6 +230,14 @@ public void Connect__Hierarchy_Returns_Success_And_Imports_External_Units_Into_K Assert.Equal(externalRoot.Uuid, kitosOrgRoot.ExternalOriginUuid); //verify that origin of he root has changed Assert.Equal(externalRoot.Name, kitosOrgRoot.Name); //verify that origin of he root has changed Assert.Equal(!onlyRoot, kitosOrgRoot.Children.Any()); //If ony root (level 1) was requested validate the expected effect + + //Verify that the logs were added + var logs = connection.StsOrganizationChangeLogs.ToList(); + var log = Assert.Single(logs); + foreach (var consequenceLog in log.Entries) + { + Assert.Equal(ConnectionUpdateOrganizationUnitChangeType.Added, consequenceLog.Type); + } } [Fact] @@ -232,7 +254,7 @@ public void Connect_Hierarchy_Fails_If_Org_Tree_Resolution_Fails() var transaction = ExpectTransaction(); //Act - var error = _sut.Connect(organizationId, Maybe.None); + var error = _sut.Connect(organizationId, Maybe.None, false); //Assert Assert.True(error.HasValue); @@ -253,7 +275,7 @@ public void Connect_Hierarchy_Fails_If_HasPermission_Fails() var transaction = ExpectTransaction(); //Act - var error = _sut.Connect(organizationId, Maybe.None); + var error = _sut.Connect(organizationId, Maybe.None, false); //Assert Assert.True(error.HasValue); @@ -274,7 +296,7 @@ public void Connect_Hierarchy_Fails_If_GetOrganization_Fails() var transaction = ExpectTransaction(); //Act - var error = _sut.Connect(organizationId, Maybe.None); + var error = _sut.Connect(organizationId, Maybe.None, false); //Assert Assert.True(error.HasValue); @@ -282,114 +304,6 @@ public void Connect_Hierarchy_Fails_If_GetOrganization_Fails() VerifyChangesNotSaved(transaction, organization, false); } - [Fact] - public void UpdateConnection_Performs_Update_Of_Existing_Synchronized_Tree() - { - //Arrange - var organizationId = A(); - var organization = new Organization(); - organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root - const int oldDepth = 2; - const int newDepth = 3; - organization.StsOrganizationConnection = new StsOrganizationConnection - { - Organization = organization, - Connected = true, - SynchronizationDepth = oldDepth - }; - //Add some children to the root - organization.AddOrganizationUnit(CreateOrganizationUnit(organization, true), organization.GetRoot()); - organization.AddOrganizationUnit(CreateOrganizationUnit(organization, true), organization.GetRoot()); - - // In the external tree, ensure that the last leaf is missing - this should track a deleted unit. - // Extensive testing of the import algorithm is not part of this test but part of StsOrganizationalHierarchyUpdateStrategyTest.cs - // We just need to see that the app service deletes deleted units from db - var externalRoot = organization.GetRoot().Transform(ToExternalOrganizationUnit); - - //add the leaf that will be removed because it is missing in the external tree - var expectedDeletion = CreateOrganizationUnit(organization, true); - organization.AddOrganizationUnit(expectedDeletion, organization.GetRoot()); - - //Track a rename on the root and check that an event is raised - organization.GetRoot().UpdateName(A()); - - SetupGetOrganizationReturns(organizationId, organization); - SetupHasPermissionReturns(organization, true); - SetupResolveOrganizationTreeReturns(organization, externalRoot); - var transaction = ExpectTransaction(); - - //Act - var error = _sut.UpdateConnection(organizationId, newDepth); - - //Assert - Assert.False(error.HasValue); - Assert.NotNull(organization.StsOrganizationConnection); - Assert.True(organization.StsOrganizationConnection.Connected); - Assert.Equal(newDepth, organization.StsOrganizationConnection.SynchronizationDepth); - VerifyChangesSaved(transaction, organization); - - _organizationUnitRepositoryMock.Verify(x => x.RemoveRange(It.Is>(units => units.Single() == expectedDeletion)), Times.Once()); - Assert.Equal(2, organization.GetRoot().Children.Count); - Assert.DoesNotContain(expectedDeletion, organization.GetRoot().Children); - _domainEventsMock.Verify(x => x.Raise(It.Is>(ev => ev.Entity == organization.GetRoot())), Times.Once()); - } - - [Fact] - public void UpdateConnection_Fails_If_Not_Already_Connected() - { - //Arrange - var organizationId = A(); - var organization = new Organization(); - organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root - organization.StsOrganizationConnection = new StsOrganizationConnection - { - Organization = organization, - Connected = false, - }; - var externalRoot = organization.GetRoot().Transform(ToExternalOrganizationUnit); - - SetupGetOrganizationReturns(organizationId, organization); - SetupHasPermissionReturns(organization, true); - SetupResolveOrganizationTreeReturns(organization, externalRoot); - var transaction = ExpectTransaction(); - - //Act - var error = _sut.UpdateConnection(organizationId, 3); - - //Assert - Assert.True(error.HasValue); - Assert.Equal(OperationFailure.BadState, error.Value.FailureType); - VerifyChangesNotSaved(transaction, organization); - } - - [Fact] - public void UpdateConnection_Fails_If_LoadOrgUnits_Fail() - { - //Arrange - var organizationId = A(); - var organization = new Organization(); - organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root - organization.StsOrganizationConnection = new StsOrganizationConnection - { - Organization = organization, - Connected = true, - }; - var resolveOrgUnitsError = A>(); - - SetupGetOrganizationReturns(organizationId, organization); - SetupHasPermissionReturns(organization, true); - SetupResolveOrganizationTreeReturns(organization, resolveOrgUnitsError); - var transaction = ExpectTransaction(); - - //Act - var error = _sut.UpdateConnection(organizationId, 3); - - //Assert - Assert.True(error.HasValue); - Assert.Equal(resolveOrgUnitsError.FailureType, error.Value.FailureType); - VerifyChangesNotSaved(transaction, organization); - } - [Fact] public void UpdateConnection_Fails_If_Auth_Fails() { @@ -408,7 +322,7 @@ public void UpdateConnection_Fails_If_Auth_Fails() var transaction = ExpectTransaction(); //Act - var error = _sut.UpdateConnection(organizationId, 3); + var error = _sut.UpdateConnection(organizationId, 3, false); //Assert Assert.True(error.HasValue); @@ -434,7 +348,7 @@ public void UpdateConnection_Fails_If_GetOrganization_Fails() var transaction = ExpectTransaction(); //Act - var error = _sut.UpdateConnection(organizationId, 3); + var error = _sut.UpdateConnection(organizationId, 3, false); //Assert Assert.True(error.HasValue); @@ -451,7 +365,7 @@ public void Disconnect_Returns_Fails_If_GetOrganization_Returns_Error() SetupGetOrganizationReturns(organizationId, getOrganizationError); //Act - var error = _sut.Disconnect(organizationId); + var error = _sut.Disconnect(organizationId, false); //Assert Assert.True(error.HasValue); @@ -468,7 +382,7 @@ public void Disconnect_Returns_Fails_UnAuthorized_To_Disconnect() SetupHasPermissionReturns(organization, false); //Act - var error = _sut.Disconnect(organizationId); + var error = _sut.Disconnect(organizationId, false); //Assert Assert.True(error.HasValue); @@ -486,7 +400,7 @@ public void Disconnect_Returns_Fails_Organization_Is_Not_Connected() var transaction = ExpectTransaction(); //Act - var error = _sut.Disconnect(organizationId); + var error = _sut.Disconnect(organizationId, false); //Assert Assert.True(error.HasValue); @@ -494,8 +408,10 @@ public void Disconnect_Returns_Fails_Organization_Is_Not_Connected() transaction.Verify(x => x.Rollback(), Times.Once()); } - [Fact] - public void Disconnect_Succeeds_And_Converts_All_Imported_Org_Units_To_Kitos_Units() + [Theory] + [InlineData(true, true)] + [InlineData(false, true)] + public void Disconnect_Succeeds_And_Converts_All_Imported_Org_Units_To_Kitos_Units(bool subscribeBeforeDisconnect, bool purgeUnusedUnits) { //Arrange var organizationId = A(); @@ -516,6 +432,7 @@ public void Disconnect_Succeeds_And_Converts_All_Imported_Org_Units_To_Kitos_Uni { Connected = true, SynchronizationDepth = A(), + SubscribeToUpdates = subscribeBeforeDisconnect }; var organization = new Organization { @@ -531,10 +448,14 @@ public void Disconnect_Succeeds_And_Converts_All_Imported_Org_Units_To_Kitos_Uni SetupGetOrganizationReturns(organizationId, organization); SetupHasPermissionReturns(organization, true); + if (purgeUnusedUnits) + { + SetupExecuteUpdateCommand(false, 1, organization, Maybe.None); + } var transaction = ExpectTransaction(); //Act - var error = _sut.Disconnect(organizationId); + var error = _sut.Disconnect(organizationId, purgeUnusedUnits); //Assert Assert.False(error.HasValue); @@ -542,6 +463,7 @@ public void Disconnect_Succeeds_And_Converts_All_Imported_Org_Units_To_Kitos_Uni _dbControlMock.Verify(x => x.SaveChanges(), Times.Once()); Assert.False(organization.StsOrganizationConnection.Connected); Assert.Null(organization.StsOrganizationConnection.SynchronizationDepth); + Assert.False(organization.StsOrganizationConnection.SubscribeToUpdates); foreach (var organizationUnit in organization.OrgUnits) { Assert.Equal(OrganizationUnitOrigin.Kitos, organizationUnit.Origin); @@ -550,6 +472,145 @@ public void Disconnect_Succeeds_And_Converts_All_Imported_Org_Units_To_Kitos_Uni _domainEventsMock.Verify(x => x.Raise(It.Is>(u => u.Entity == affectedUnit1)), Times.Once()); _domainEventsMock.Verify(x => x.Raise(It.Is>(u => u.Entity == affectedUnit2)), Times.Once()); _domainEventsMock.Verify(x => x.Raise(It.Is>(u => u.Entity == unaffectedUnit)), Times.Never()); + _commandBusMock.Verify(x => + x.Execute>( + It.Is(c => + c.SubscribeToChanges == false && c.SynchronizationDepth.Value == 1 && + c.Organization == organization && c.PreloadedExternalTree.HasValue == purgeUnusedUnits)), + purgeUnusedUnits ? Times.Once() : Times.Never()); + } + + [Fact] + public void Disconnect_Fails_If_Purge_Command_Fails() + { + //Arrange + var organizationId = A(); + var organization = new Organization(); + organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root + + SetupGetOrganizationReturns(organizationId, organization); + SetupHasPermissionReturns(organization, true); + var transaction = ExpectTransaction(); + SetupExecuteUpdateCommand(false, 1, organization, A()); + + //Act + var error = _sut.Disconnect(organizationId, true); + + //Assert + Assert.True(error.HasValue); + VerifyChangesNotSaved(transaction, organization); + } + + [Fact] + public void UpdateConnection_Performs_Update_Of_Existing_Synchronized_Tree() + { + //Arrange + var organizationId = A(); + var organization = new Organization(); + organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root + + var externalRoot = organization.GetRoot().Transform(ToExternalOrganizationUnit); + + SetupGetOrganizationReturns(organizationId, organization); + SetupHasPermissionReturns(organization, true); + SetupResolveOrganizationTreeReturns(organization, externalRoot); + var transaction = ExpectTransaction(); + var levelsToInclude = new Random().Next(1, 100); + var subscribeToUpdates = A(); + SetupExecuteUpdateCommand(subscribeToUpdates, levelsToInclude, organization, Maybe.None); + + //Act + var error = _sut.UpdateConnection(organizationId, levelsToInclude, subscribeToUpdates); + + //Assert + Assert.False(error.HasValue); + VerifyChangesSaved(transaction, organization); + } + + [Fact] + public void UpdateConnection_Fails_If_Command_Fails() + { + //Arrange + var organizationId = A(); + var organization = new Organization(); + organization.OrgUnits.Add(CreateOrganizationUnit(organization, true)); //Add the root + + var externalRoot = organization.GetRoot().Transform(ToExternalOrganizationUnit); + + SetupGetOrganizationReturns(organizationId, organization); + SetupHasPermissionReturns(organization, true); + SetupResolveOrganizationTreeReturns(organization, externalRoot); + var transaction = ExpectTransaction(); + var levelsToInclude = new Random().Next(1, 100); + var subscribeToUpdates = A(); + SetupExecuteUpdateCommand(subscribeToUpdates, levelsToInclude, organization, A()); + + //Act + var error = _sut.UpdateConnection(organizationId, levelsToInclude, subscribeToUpdates); + + //Assert + Assert.True(error.HasValue); + VerifyChangesNotSaved(transaction, organization); + } + + [Fact] + public void GetChangeLogForOrganization_Returns_Number_Of_Logs() + { + var orgUuid = A(); + + var logs = new List { new(), new() }; + var changeLogs = new List { new() { Entries = logs }, new() { Entries = logs } }; + + var stsOrganizationConnection = new StsOrganizationConnection + { + Connected = true, + SynchronizationDepth = A(), + StsOrganizationChangeLogs = changeLogs + }; + var organization = new Organization + { + Uuid = orgUuid, + StsOrganizationConnection = stsOrganizationConnection + }; + + SetupGetOrganizationReturns(orgUuid, organization); + SetupHasPermissionReturns(organization, true); + + var result = _sut.GetChangeLogs(orgUuid, 1); + + Assert.True(result.Ok); + var logResult = Assert.Single(result.Value); + ; + Assert.Equal(2, logResult.GetEntries().Count()); + } + + [Fact] + public void GetChangeLogForOrganization_Fails_If_GetOrganization_Returns_Error() + { + var orgUuid = A(); + var getOperationError = A(); + + SetupGetOrganizationReturns(orgUuid, getOperationError); + + var result = _sut.GetChangeLogs(orgUuid, 1); + + Assert.True(result.Failed); + Assert.Equal(getOperationError.FailureType, result.Error); + } + + [Fact] + public void GetChangeLogForOrganization_Fails_If_UnAuthorized() + { + var orgUuid = A(); + var organization = new Organization(); + + SetupGetOrganizationReturns(orgUuid, organization); + SetupHasPermissionReturns(organization, false); + + var result = _sut.GetChangeLogs(orgUuid, 1); + + Assert.True(result.Failed); + Assert.Equal(OperationFailure.Forbidden, result.Error.FailureType); } private void VerifyChangesSaved(Mock transaction, Organization organization) @@ -649,5 +710,14 @@ private static ExternalOrganizationUnit ToExternalOrganizationUnit(OrganizationU root.Children.Select(ToExternalOrganizationUnit).ToList() ); } + + private void SetupExecuteUpdateCommand(bool subscribeToUpdates, int levelsToInclude, Organization organization, Maybe result) + { + _commandBusMock.Setup(x => + x.Execute>( + It.Is(c => + c.SubscribeToChanges == subscribeToUpdates && c.SynchronizationDepth.Value == levelsToInclude && + c.Organization == organization))).Returns(result); + } } } diff --git a/Tests.Unit.Core.ApplicationServices/ApplicationServices/UserServiceTest.cs b/Tests.Unit.Core.ApplicationServices/ApplicationServices/UserServiceTest.cs index bb6aca85dd..a826110590 100644 --- a/Tests.Unit.Core.ApplicationServices/ApplicationServices/UserServiceTest.cs +++ b/Tests.Unit.Core.ApplicationServices/ApplicationServices/UserServiceTest.cs @@ -11,7 +11,6 @@ using System.Linq; using Core.Abstractions.Types; using Core.ApplicationServices.Organizations; -using Core.ApplicationServices.Rights; using Core.DomainModel.Commands; using Core.DomainModel.Events; using Core.DomainModel.Organization.DomainEvents; @@ -37,7 +36,6 @@ public class UserServiceTest : WithAutoFixture private readonly Mock _transactionManagerMock; private readonly Mock _organizationServiceMock; private readonly Mock _organizationalUserContextMock; - private readonly Mock _userRightsService; private readonly Mock _commandBusMock; public UserServiceTest() diff --git a/Tests.Unit.Core.ApplicationServices/BackgroundJobs/ScheduleFkOrgUpdatesBackgroundJobTest.cs b/Tests.Unit.Core.ApplicationServices/BackgroundJobs/ScheduleFkOrgUpdatesBackgroundJobTest.cs new file mode 100644 index 0000000000..b3b5740381 --- /dev/null +++ b/Tests.Unit.Core.ApplicationServices/BackgroundJobs/ScheduleFkOrgUpdatesBackgroundJobTest.cs @@ -0,0 +1,108 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Core.ApplicationServices.ScheduledJobs; +using Core.BackgroundJobs.Model.Maintenance; +using Core.DomainModel.Commands; +using Core.DomainModel.Organization; +using Core.DomainServices.Repositories.Organization; +using Core.DomainServices.Time; +using Moq; +using Serilog; +using Tests.Toolkit.Patterns; +using Xunit; + +namespace Tests.Unit.Core.BackgroundJobs +{ + public class ScheduleFkOrgUpdatesBackgroundJobTest : WithAutoFixture + { + private readonly ScheduleFkOrgUpdatesBackgroundJob _sut; + private readonly Mock _hangfireApiMock; + private readonly Mock _organizationRepositoryMock; + private readonly Mock _operationClockMock; + private readonly DateTime _now; + + public ScheduleFkOrgUpdatesBackgroundJobTest() + { + _hangfireApiMock = new Mock(); + _organizationRepositoryMock = new Mock(); + _now = DateTime.UtcNow; + _operationClockMock = new Mock(); + _operationClockMock.Setup(x => x.Now).Returns(_now); + _sut = new ScheduleFkOrgUpdatesBackgroundJob( + _hangfireApiMock.Object, + _organizationRepositoryMock.Object, + Mock.Of(), + _operationClockMock.Object, + Mock.Of() + ); + } + + [Fact] + public async Task ExecuteAsync_Enqueues_Update_Jobs_For_Subscribing_Organizations() + { + //Arrange + var expectedResult1 = CreateOrganization(true, true); + var expectedResult2 = CreateOrganization(true, true); + var expectedResult3 = CreateOrganization(true, true); + var expectedResult4 = CreateOrganization(true, true); + + var organizations = new[] + { + expectedResult1, + CreateOrganization(true, false), + CreateOrganization(false, false), + expectedResult2, + expectedResult3, + expectedResult4 + }; + _organizationRepositoryMock.Setup(x => x.GetAll()).Returns(organizations.AsQueryable()); + + //Act + var result = await _sut.ExecuteAsync(); + + //Assert + Assert.True(result.Ok); + _hangfireApiMock.Verify(x => x.Schedule(It.IsAny>(), It.IsAny()), Times.Exactly(4)); + VerifyJobScheduledAt(expectedResult1, _now); //first one runs immediately + VerifyJobScheduledAt(expectedResult2, _now.AddMinutes(1)); //next two are scheduled to run in parallel 1 minute from now + VerifyJobScheduledAt(expectedResult3, _now.AddMinutes(1)); + VerifyJobScheduledAt(expectedResult4, _now.AddMinutes(2)); //fourth is pushed another minute + } + + private void VerifyJobScheduledAt(Organization expectedResult1, DateTime expectedStart) + { + _hangfireApiMock.Verify(x => x.Schedule(It.Is>(expr => MatchExpectedJobCall(expr, expectedResult1)), expectedStart), Times.Once); + } + + public Organization CreateOrganization(bool connected, bool subscribing) + { + var stsOrganizationConnection = connected || subscribing + ? new StsOrganizationConnection + { + Connected = connected, + SubscribeToUpdates = subscribing + } + : null; + + return new Organization + { + Id = A(), + StsOrganizationConnection = stsOrganizationConnection, + }; + } + + private static bool MatchExpectedJobCall(Expression jobCall, Organization expectedResult1) + { + var body = jobCall.Body as MethodCallExpression; + Assert.NotNull(body); + dynamic bodyArgument = body.Arguments[0]; + ConstantExpression arg = bodyArgument.Expression; + var actualOrgId = arg.Value; + var actualUuid = (Guid)actualOrgId.GetType().GetField("uuid").GetValue(actualOrgId); + + return actualUuid == expectedResult1.Uuid; + } + } +} diff --git a/Tests.Unit.Core.ApplicationServices/Model/ItContractTest.cs b/Tests.Unit.Core.ApplicationServices/Model/ItContractTest.cs index 45c895d42c..d2289a9464 100644 --- a/Tests.Unit.Core.ApplicationServices/Model/ItContractTest.cs +++ b/Tests.Unit.Core.ApplicationServices/Model/ItContractTest.cs @@ -779,8 +779,14 @@ public void Validate_Returns_Success_If_Termination_Deadline_Has_Not_Passed(bool public void Validate_Returns_Success_If_Termination_Deadline_Passed_But_TerminationPeriod_Has_Not_Passed(bool enforceValid, int dayOffset) { //Arrange - var now = CreateValidDate(); + var validDate = CreateValidDate(); + + //make sure the day is present in every month, so there is no month "conversion" error + //e.g. when subtracting a month from 31.10 the result would be 30.09 which would cause an error (notice the day difference) + var randomDay = new Random(A()).Next(1, 28); + var now = new DateTime(validDate.Year, validDate.Month, randomDay); var terminationDeadline = new Random(A()).Next(1, 12); + var sut = new ItContract { Terminated = now.AddMonths(-1 * terminationDeadline).AddDays(dayOffset), @@ -788,12 +794,12 @@ public void Validate_Returns_Success_If_Termination_Deadline_Passed_But_Terminat TerminationDeadline = new TerminationDeadlineType { Name = terminationDeadline.ToString("D") - }, + } }; //Act var result = sut.Validate(now); - + //Assert Assert.True(result.Result);//If not enforced valid we expect the value to be false Assert.Equal(enforceValid, result.EnforcedValid); @@ -1065,7 +1071,7 @@ public void Transfer_EconomyStream_Returns_NotFound(bool isInternal) private DateTime CreateValidDate() { - return DateTime.Now.AddMonths(new Random(A()).Next(-30, 30)); + return DateTime.Now.Date.AddMonths(new Random(A()).Next(-30, 30)); } } } diff --git a/Tests.Unit.Core.ApplicationServices/Model/Strategies/StsOrganizationalHierarchyUpdateStrategyTest.cs b/Tests.Unit.Core.ApplicationServices/Model/Strategies/StsOrganizationalHierarchyUpdateStrategyTest.cs index cd775ee44d..7a4ee7b8fd 100644 --- a/Tests.Unit.Core.ApplicationServices/Model/Strategies/StsOrganizationalHierarchyUpdateStrategyTest.cs +++ b/Tests.Unit.Core.ApplicationServices/Model/Strategies/StsOrganizationalHierarchyUpdateStrategyTest.cs @@ -10,17 +10,21 @@ using Tests.Toolkit.Extensions; using Tests.Toolkit.Patterns; using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; namespace Tests.Unit.Core.Model.Strategies { public class StsOrganizationalHierarchyUpdateStrategyTest : WithAutoFixture { + private readonly ITestOutputHelper _testOutputHelper; private readonly StsOrganizationalHierarchyUpdateStrategy _sut; private readonly Organization _organization; private int _nextOrgUnitId; - public StsOrganizationalHierarchyUpdateStrategyTest() + public StsOrganizationalHierarchyUpdateStrategyTest(ITestOutputHelper testOutputHelper) { + _testOutputHelper = testOutputHelper; _organization = new Organization(); _sut = new StsOrganizationalHierarchyUpdateStrategy(_organization); _nextOrgUnitId = 0; @@ -28,7 +32,7 @@ public StsOrganizationalHierarchyUpdateStrategyTest() private int GetNewOrgUnitId() => _nextOrgUnitId++; - private void PrepareConnectedOrganization() + private void PrepareConnectedOrganization(OrganizationUnit predefinedRoot = null) { _organization.StsOrganizationConnection = new StsOrganizationConnection { @@ -36,7 +40,7 @@ private void PrepareConnectedOrganization() Organization = _organization }; - var organizationUnit = CreateOrganizationUnit + var organizationUnit = predefinedRoot ?? CreateOrganizationUnit ( OrganizationUnitOrigin.STS_Organisation, new[] { @@ -214,7 +218,7 @@ public void ComputeUpdate_Detects_Removed_Nodes_Where_Leafs_Are_Moved_To_Removed var consequences = _sut.ComputeUpdate(externalTree); //Assert - var removedUnit = Assert.Single(consequences.DeletedExternalUnitsBeingDeleted); + var removedUnit = Assert.Single(consequences.DeletedExternalUnitsBeingDeleted).organizationUnit; Assert.Same(expectedRemovedUnit, removedUnit); var movedUnits = consequences.OrganizationUnitsBeingMoved.ToList(); Assert.Equal(expectedParentChanges.Count, movedUnits.Count); @@ -241,7 +245,7 @@ public void PerformUpdate_Updates_New_OrganizationUnits() //Assert Assert.True(consequences.Ok); - Assert.ProperSubset(root.FlattenHierarchy().Where(x=>x.Origin == OrganizationUnitOrigin.STS_Organisation).Select(x=>x.ExternalOriginUuid.GetValueOrDefault()).ToHashSet(),expectedNewUnits.Select(x=>x.ExternalOriginUuid.GetValueOrDefault()).ToHashSet()); + Assert.ProperSubset(root.FlattenHierarchy().Where(x => x.Origin == OrganizationUnitOrigin.STS_Organisation).Select(x => x.ExternalOriginUuid.GetValueOrDefault()).ToHashSet(), expectedNewUnits.Select(x => x.ExternalOriginUuid.GetValueOrDefault()).ToHashSet()); } [Fact] @@ -255,7 +259,7 @@ public void PerformUpdate_Updates_Renamed_OrganizationUnits() //Assert Assert.True(consequences.Ok); - Assert.Equal(expectedNewName,randomItemToRename.Name); + Assert.Equal(expectedNewName, randomItemToRename.Name); } [Fact] @@ -300,7 +304,64 @@ public void PerformUpdate_Updates_Units_Moved_To_Newly_Added_Parent() //Assert Assert.True(consequences.Ok); - Assert.Equal(newItem.ExternalOriginUuid.GetValueOrDefault(),randomLeafMovedToNewlyImportedItem.Parent.ExternalOriginUuid.GetValueOrDefault()); + Assert.Equal(newItem.ExternalOriginUuid.GetValueOrDefault(), randomLeafMovedToNewlyImportedItem.Parent.ExternalOriginUuid.GetValueOrDefault()); + } + + [Fact] + public void PerformUpdate_Updates_Units_Moved_To_Newly_Added_Parent_And_Sub_Tree_Is_Moved_Along() + { + //Arrange + var root = CreateOrganizationUnit(OrganizationUnitOrigin.STS_Organisation, + new[] + { + CreateOrganizationUnit(OrganizationUnitOrigin.STS_Organisation, + new [] + { + CreateOrganizationUnit(OrganizationUnitOrigin.STS_Organisation, + new [] + { + CreateOrganizationUnit(OrganizationUnitOrigin.STS_Organisation,new[] + { + CreateOrganizationUnit(OrganizationUnitOrigin.STS_Organisation) + }) + }), + CreateOrganizationUnit(OrganizationUnitOrigin.STS_Organisation), + CreateOrganizationUnit(OrganizationUnitOrigin.STS_Organisation) + }) + }); + var externalTree = ConvertToExternalTree(root); //the complete tree + var childToRemove = root.FlattenHierarchy().Skip(1).First(); + foreach (var grandChild in childToRemove.Children.ToList()) + { + childToRemove.RemoveChild(grandChild); + root.AddChild(grandChild); + } + root.RemoveChild(childToRemove); // the current tree is missing a link -> we expect the final tree to be 100% like the external tree including sub trees + + + PrepareConnectedOrganization(root); + + //Act + var consequences = _sut.PerformUpdate(externalTree); + + //Assert + Assert.True(consequences.Ok); + AssertHierarchies(externalTree, ConvertToExternalTree(_organization.GetRoot())); + } + + private void AssertHierarchies(ExternalOrganizationUnit expected, ExternalOrganizationUnit actual, int level = 1) + { + _testOutputHelper.WriteLine("Testing hierarchy consistency at level {0} currently evaluating expected node:{1} ({2})", level, expected.Name, expected.Uuid); + Assert.Equal(expected.Name, actual.Name); + Assert.Equal(expected.Uuid, actual.Uuid); + var expectedChildren = expected.Children.ToDictionary(x => x.Uuid); + var actualChildren = actual.Children.ToDictionary(x => x.Uuid); + Assert.Equivalent(expectedChildren.Keys, actualChildren.Keys, true); + + foreach (var expectedChild in expectedChildren) + { + AssertHierarchies(expectedChild.Value, actualChildren[expectedChild.Key], level + 1); + } } [Fact] @@ -314,9 +375,9 @@ public void PerformUpdate_Updates_Removed_Units_Which_Are_Converted_Since_They_C //Assert Assert.True(consequences.Ok); - Assert.DoesNotContain(root.FlattenHierarchy(),child=>expectedRemovedUnits.Contains(child)); + Assert.DoesNotContain(root.FlattenHierarchy(), child => expectedRemovedUnits.Contains(child)); var actualConverted = Assert.Single(root.FlattenHierarchy().Where(x => x == nodeExpectedToBeConverted)); - Assert.Equal(OrganizationUnitOrigin.Kitos,actualConverted.Origin); + Assert.Equal(OrganizationUnitOrigin.Kitos, actualConverted.Origin); Assert.Null(actualConverted.ExternalOriginUuid); } @@ -363,7 +424,7 @@ public void PerformUpdate_Removes_Removed_Nodes_Where_Leafs_Are_Moved_To_Removed //Assert Assert.True(consequences.Ok); - var removedUnit = Assert.Single(consequences.Value.DeletedExternalUnitsBeingDeleted); + var removedUnit = Assert.Single(consequences.Value.DeletedExternalUnitsBeingDeleted).organizationUnit; Assert.Same(expectedRemovedUnit, removedUnit); var movedUnits = consequences.Value.OrganizationUnitsBeingMoved.ToList(); Assert.Equal(expectedParentChangesCount, movedUnits.Count); @@ -410,7 +471,9 @@ private static void AssertUnitsToRenameWereDetected(OrganizationTreeUpdateConseq Assert.Equal(expectedNewName, newName); } - private static (OrganizationUnit movedUnit, ExternalOrganizationUnit newParent) AssertUnitsToMoveToExistingParentsWereDetected(OrganizationTreeUpdateConsequences consequences, OrganizationUnit randomLeafWhichMustBeMovedToRoot, OrganizationUnit expectedNewParent) + private static void AssertUnitsToMoveToExistingParentsWereDetected( + OrganizationTreeUpdateConsequences consequences, OrganizationUnit randomLeafWhichMustBeMovedToRoot, + OrganizationUnit expectedNewParent) { Assert.Empty(consequences.DeletedExternalUnitsBeingConvertedToNativeUnits); Assert.Empty(consequences.DeletedExternalUnitsBeingDeleted); @@ -420,8 +483,6 @@ private static (OrganizationUnit movedUnit, ExternalOrganizationUnit newParent) var (movedUnit, _, newParent) = Assert.Single(consequences.OrganizationUnitsBeingMoved); Assert.Equal(randomLeafWhichMustBeMovedToRoot, movedUnit); Assert.Equal(expectedNewParent.ExternalOriginUuid.GetValueOrDefault(), newParent.Uuid); - - return new ValueTuple(movedUnit, newParent); } private static void AssertUnitsToMoveToNewlyAddedParentWereDetected(OrganizationTreeUpdateConsequences consequences, OrganizationUnit root, OrganizationUnit exptectedNewItem, OrganizationUnit expectedMovedUnit) @@ -438,46 +499,44 @@ private static void AssertUnitsToMoveToNewlyAddedParentWereDetected(Organization Assert.Equal(unitToAdd.Uuid, newParent.Uuid); } - private static OrganizationUnit AssertUnitsWhichAreConvertedSinceTheyContainRetainedSubTreeContentWereDetected(OrganizationTreeUpdateConsequences consequences, OrganizationUnit nodeExpectedToBeConverted, IEnumerable expectedRemovedUnits) + private static void AssertUnitsWhichAreConvertedSinceTheyContainRetainedSubTreeContentWereDetected( + OrganizationTreeUpdateConsequences consequences, OrganizationUnit nodeExpectedToBeConverted, + IEnumerable expectedRemovedUnits) { - var organizationUnit = Assert.Single(consequences.DeletedExternalUnitsBeingConvertedToNativeUnits); + var organizationUnit = Assert.Single(consequences.DeletedExternalUnitsBeingConvertedToNativeUnits).organizationUnit; Assert.Same(nodeExpectedToBeConverted, organizationUnit); var expectedRemovedItems = expectedRemovedUnits.OrderBy(unit => unit.Id); - var actualRemovedItems = consequences.DeletedExternalUnitsBeingDeleted.OrderBy(unit => unit.Id); + var actualRemovedItems = consequences.DeletedExternalUnitsBeingDeleted.Select(x => x.organizationUnit).OrderBy(unit => unit.Id); Assert.Equal(expectedRemovedItems, actualRemovedItems); Assert.Empty(consequences.OrganizationUnitsBeingRenamed); Assert.Empty(consequences.AddedExternalOrganizationUnits); Assert.Empty(consequences.OrganizationUnitsBeingMoved); - - return organizationUnit; } - private static OrganizationUnit AssertUnitsWhichAreConvertedSinceTheyAreStillInUseWereDetected(OrganizationTreeUpdateConsequences consequences, OrganizationUnit removedNodeInUse) + private static void AssertUnitsWhichAreConvertedSinceTheyAreStillInUseWereDetected( + OrganizationTreeUpdateConsequences consequences, OrganizationUnit removedNodeInUse) { - var organizationUnit = Assert.Single(consequences.DeletedExternalUnitsBeingConvertedToNativeUnits); + var organizationUnit = Assert.Single(consequences.DeletedExternalUnitsBeingConvertedToNativeUnits).organizationUnit; Assert.Same(removedNodeInUse, organizationUnit); Assert.Empty(consequences.DeletedExternalUnitsBeingDeleted); Assert.Empty(consequences.OrganizationUnitsBeingRenamed); Assert.Empty(consequences.AddedExternalOrganizationUnits); Assert.Empty(consequences.OrganizationUnitsBeingMoved); - - return organizationUnit; } - private static OrganizationUnit AssertUnitsWhichAreDeletedWereDetected(OrganizationTreeUpdateConsequences consequences, OrganizationUnit expectedRemovedUnit) + private static void AssertUnitsWhichAreDeletedWereDetected(OrganizationTreeUpdateConsequences consequences, + OrganizationUnit expectedRemovedUnit) { - var removedUnit = Assert.Single(consequences.DeletedExternalUnitsBeingDeleted); + var removedUnit = Assert.Single(consequences.DeletedExternalUnitsBeingDeleted).organizationUnit; Assert.Same(expectedRemovedUnit, removedUnit); Assert.Empty(consequences.DeletedExternalUnitsBeingConvertedToNativeUnits); Assert.Empty(consequences.OrganizationUnitsBeingRenamed); Assert.Empty(consequences.AddedExternalOrganizationUnits); Assert.Empty(consequences.OrganizationUnitsBeingMoved); - - return removedUnit; } private static ExternalOrganizationUnit ConvertToExternalTree(OrganizationUnit root, Func, IEnumerable> customChildren = null) diff --git a/Tests.Unit.Core.ApplicationServices/Tests.Unit.Core.csproj b/Tests.Unit.Core.ApplicationServices/Tests.Unit.Core.csproj index 4e48c1cd9e..7c2d18e35a 100644 --- a/Tests.Unit.Core.ApplicationServices/Tests.Unit.Core.csproj +++ b/Tests.Unit.Core.ApplicationServices/Tests.Unit.Core.csproj @@ -193,7 +193,9 @@ + + @@ -217,6 +219,7 @@ + @@ -296,10 +299,6 @@ {adcacc1d-f538-464c-9102-f4c1d6fa35d3} Core.DomainServices - - {6CD15363-5401-43C5-9479-02FDDFA881DC} - Infrastructure.DataAccess - {c01c5f9e-6904-4b4c-94b1-12d7c83f8070} Infrastructure.Ninject diff --git a/Tests.Unit.Presentation.Web/Tests.Unit.Presentation.Web.csproj b/Tests.Unit.Presentation.Web/Tests.Unit.Presentation.Web.csproj index c46b177b4a..e71681489f 100644 --- a/Tests.Unit.Presentation.Web/Tests.Unit.Presentation.Web.csproj +++ b/Tests.Unit.Presentation.Web/Tests.Unit.Presentation.Web.csproj @@ -328,10 +328,6 @@ {adcacc1d-f538-464c-9102-f4c1d6fa35d3} Core.DomainServices - - {6CD15363-5401-43C5-9479-02FDDFA881DC} - Infrastructure.DataAccess - {0326cae6-87a1-4d66-84ae-eb8ce0340e9f} Infrastructure.Services