diff --git a/docs/ProcessManager.Client/ReleaseNotes/ReleaseNotes.md b/docs/ProcessManager.Client/ReleaseNotes/ReleaseNotes.md index d412b333..0819e455 100644 --- a/docs/ProcessManager.Client/ReleaseNotes/ReleaseNotes.md +++ b/docs/ProcessManager.Client/ReleaseNotes/ReleaseNotes.md @@ -1,5 +1,9 @@ # ProcessManager.Client Release Notes +## Version 0.25.0 + +- Add `IBusinessValidatedDto` and `ValidationErrorDto` used to support business validation. + ## Version 0.24.0 - Add `INotifyDataDto` type constraint to `NotifyOrchestrationInstanceV1` diff --git a/docs/ProcessManager.Orchestrations.Abstractions/ReleaseNotes/ReleaseNotes.md b/docs/ProcessManager.Orchestrations.Abstractions/ReleaseNotes/ReleaseNotes.md index c7459764..f7b60d41 100644 --- a/docs/ProcessManager.Orchestrations.Abstractions/ReleaseNotes/ReleaseNotes.md +++ b/docs/ProcessManager.Orchestrations.Abstractions/ReleaseNotes/ReleaseNotes.md @@ -1,5 +1,11 @@ # ProcessManager.Orchestrations.Abstractions Release Notes +## Version 0.16.0 + +- Add `SettlementMethod` and `SettlementVersion` DataHub types. +- Update `RequestCalculatedEnergyTimeSeries` model with `IBusinessValidatedDto` interface. +- Update `RequestCalculatedEnergyTimeSeriesRejectedV1` properties to be a list of validation errors. + ## Version 0.15.2 - Update dependent NuGet package. diff --git a/source/ProcessManager.Abstractions/Components/BusinessValidation/IBusinessValidatedDto.cs b/source/ProcessManager.Abstractions/Components/BusinessValidation/IBusinessValidatedDto.cs new file mode 100644 index 00000000..d266e7ae --- /dev/null +++ b/source/ProcessManager.Abstractions/Components/BusinessValidation/IBusinessValidatedDto.cs @@ -0,0 +1,20 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; + +/// +/// Marker interface to add to types for which a BusinessValidator{T} should be registered +/// +public interface IBusinessValidatedDto; diff --git a/source/ProcessManager.Abstractions/Components/BusinessValidation/ValidationErrorDto.cs b/source/ProcessManager.Abstractions/Components/BusinessValidation/ValidationErrorDto.cs new file mode 100644 index 00000000..52114ce5 --- /dev/null +++ b/source/ProcessManager.Abstractions/Components/BusinessValidation/ValidationErrorDto.cs @@ -0,0 +1,20 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; + +/// +/// Validation error containing an error message and an error code. +/// +public record ValidationErrorDto(string Message, string ErrorCode); diff --git a/source/ProcessManager.Abstractions/ProcessManager.Abstractions.csproj b/source/ProcessManager.Abstractions/ProcessManager.Abstractions.csproj index 8e0e9dbc..24b6a38d 100644 --- a/source/ProcessManager.Abstractions/ProcessManager.Abstractions.csproj +++ b/source/ProcessManager.Abstractions/ProcessManager.Abstractions.csproj @@ -7,7 +7,7 @@ Energinet.DataHub.ProcessManager.Abstractions - 0.24.0$(VersionSuffix) + 0.25.0$(VersionSuffix) DH3 Process Manager Abstractions library Energinet-DataHub Energinet-DataHub diff --git a/source/ProcessManager.Client/ProcessManager.Client.csproj b/source/ProcessManager.Client/ProcessManager.Client.csproj index 682a19a8..3d05ab99 100644 --- a/source/ProcessManager.Client/ProcessManager.Client.csproj +++ b/source/ProcessManager.Client/ProcessManager.Client.csproj @@ -7,7 +7,7 @@ Energinet.DataHub.ProcessManager.Client - 0.24.0$(VersionSuffix) + 0.25.0$(VersionSuffix) DH3 Process Manager Client library Energinet-DataHub Energinet-DataHub diff --git a/source/ProcessManager.Components.Tests/ProcessManager.Components.Tests.csproj b/source/ProcessManager.Components.Tests/ProcessManager.Components.Tests.csproj index e9548327..1b1e0837 100644 --- a/source/ProcessManager.Components.Tests/ProcessManager.Components.Tests.csproj +++ b/source/ProcessManager.Components.Tests/ProcessManager.Components.Tests.csproj @@ -45,6 +45,7 @@ + diff --git a/source/ProcessManager.Components.Tests/Unit/Extensions/DependencyInjection/BusinessValidationExtensionsTests.cs b/source/ProcessManager.Components.Tests/Unit/Extensions/DependencyInjection/BusinessValidationExtensionsTests.cs new file mode 100644 index 00000000..66331e2e --- /dev/null +++ b/source/ProcessManager.Components.Tests/Unit/Extensions/DependencyInjection/BusinessValidationExtensionsTests.cs @@ -0,0 +1,124 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Reflection; +using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.Extensions.DependencyInjection; +using Energinet.DataHub.ProcessManager.Example.Orchestrations.Abstractions.Processes.BRS_X03_ActorRequestProcessExample.V1; +using Energinet.DataHub.ProcessManager.Example.Orchestrations.Processes.BRS_X03_ActorRequestProcessExample.V1; +using Energinet.DataHub.ProcessManager.Example.Orchestrations.Processes.BRS_X03_ActorRequestProcessExample.V1.BusinessValidation.ValidationRules; +using FluentAssertions; +using FluentAssertions.Execution; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Energinet.DataHub.ProcessManager.Components.Tests.Unit.Extensions.DependencyInjection; + +public class BusinessValidationExtensionsTests +{ + private readonly IServiceCollection _serviceCollection = new ServiceCollection(); + + private readonly Assembly _orchestrationsExampleAssembly = typeof(Orchestration_Brs_X03_V1).Assembly; + private readonly Assembly _orchestrationsExampleAbstractionsAssembly = typeof(ActorRequestProcessExampleInputV1).Assembly; + + private readonly List _businessValidatorTypes = + [ + typeof(BusinessValidator), + ]; + + public BusinessValidationExtensionsTests() + { + _serviceCollection.AddLogging(); + } + + [Fact] + public void Given_BusinessValidatorTypes_When_GettingBusinessValidatedDtoTypesFromAssembly_Then_EachBusinessValidatedDtoTypeShouldHaveABusinessValidatorType() + { + // Given + // => Business validator types are registered in _businessValidatorTypes + + // When + var businessValidatedDtoTypesFromAssembly = _orchestrationsExampleAbstractionsAssembly.GetTypes() + .Where(t => + t is { IsClass: true, IsAbstract: false, IsGenericType: false } + && t.IsAssignableTo(typeof(IBusinessValidatedDto))) + .ToList(); + + // Then + using var assertionScope = new AssertionScope(); + foreach (var businessValidatedDtoType in businessValidatedDtoTypesFromAssembly) + { + var expectedValidatorType = typeof(BusinessValidator<>).MakeGenericType(businessValidatedDtoType); + _businessValidatorTypes.Should().ContainEquivalentOf(expectedValidatorType); + } + } + + [Fact] + public void Given_BusinessValidationAddedForExampleAssemblies_When_ResolvingExpectedValidatorTypes_Then_ValidatorsCanBeResolved() + { + // Given + _serviceCollection.AddBusinessValidation(assembliesToScan: [_orchestrationsExampleAssembly, _orchestrationsExampleAbstractionsAssembly]); + + var services = _serviceCollection.BuildServiceProvider(); + + // When + var resolveActions = _businessValidatorTypes + .Select(businessValidatorType => () => + { + services.GetRequiredService(businessValidatorType); + }); + + using var assertionScope = new AssertionScope(); + foreach (var resolveAction in resolveActions) + { + resolveAction.Should().NotThrow("because the expected business validator should be resolvable in the service provider"); + } + } + + [Fact] + public void Given_BusinessValidationRulesAddedForExampleAssemblies_When_ResolvingAllValidationRules_Then_ContainsExpectedRules() + { + // Given + _serviceCollection.AddBusinessValidation(assembliesToScan: [_orchestrationsExampleAssembly, _orchestrationsExampleAbstractionsAssembly]); + var services = _serviceCollection.BuildServiceProvider(); + + List expectedValidationRuleTypes = + [ + typeof(BusinessReasonValidationRule), + ]; + + // When + var allResolvedValidationRules = new List(); + + // => Get all business validation rules for each type implementing IBusinessValidatedDto + var businessValidatedDtoTypes = _orchestrationsExampleAbstractionsAssembly.DefinedTypes + .Where(t => + t is { IsClass: true, IsAbstract: false, IsGenericType: false } + && t.IsAssignableTo(typeof(IBusinessValidatedDto))); + foreach (var businessValidatedDtoType in businessValidatedDtoTypes) + { + var interfaceTypeForBusinessValidatedDtoType = typeof(IBusinessValidationRule<>).MakeGenericType(businessValidatedDtoType); + var validationRulesForBusinessValidatedDtoType = services.GetServices(interfaceTypeForBusinessValidatedDtoType); + allResolvedValidationRules.AddRange(validationRulesForBusinessValidatedDtoType); + } + + // Then + using var assertionScope = new AssertionScope(); + foreach (var expectedValidationRuleType in expectedValidationRuleTypes) + { + allResolvedValidationRules.Should().ContainSingle(o => o!.GetType() == expectedValidationRuleType); + } + } +} diff --git a/source/ProcessManager.Components/BusinessValidation/BusinessValidator.cs b/source/ProcessManager.Components/BusinessValidation/BusinessValidator.cs new file mode 100644 index 00000000..b61ab43a --- /dev/null +++ b/source/ProcessManager.Components/BusinessValidation/BusinessValidator.cs @@ -0,0 +1,53 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Abstractions.Components; +using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; +using Microsoft.Extensions.Logging; + +namespace Energinet.DataHub.ProcessManager.Components.BusinessValidation; + +public class BusinessValidator( + ILogger> logger, + IEnumerable> validationRules) + where TInput : IBusinessValidatedDto +{ + private readonly ILogger _logger = logger; + private readonly IReadOnlyCollection> _validationRules = validationRules.ToList(); + + /// + /// Perform all validation rules for the given input. + /// + public async Task> ValidateAsync(TInput subject) + { + if (subject == null) + throw new ArgumentNullException(nameof(subject)); + + var errors = new List(); + foreach (var rule in _validationRules) + { + errors.AddRange(await rule.ValidateAsync(subject).ConfigureAwait(false)); + } + + if (errors.Count > 0) + { + _logger.LogWarning( + "Validation failed for {SubjectType} type. Validation errors: {Errors}", + subject.GetType().Name, + errors); + } + + return errors; + } +} diff --git a/source/ProcessManager.Components/BusinessValidation/GridAreaOwner/IGridAreaOwnerClient.cs b/source/ProcessManager.Components/BusinessValidation/GridAreaOwner/IGridAreaOwnerClient.cs new file mode 100644 index 00000000..98f9d44a --- /dev/null +++ b/source/ProcessManager.Components/BusinessValidation/GridAreaOwner/IGridAreaOwnerClient.cs @@ -0,0 +1,48 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Energinet.DataHub.ProcessManager.Components.BusinessValidation.GridAreaOwner; + +/// +/// Placeholder / mock until we can get grid area owners from Market Participant. +/// TODO: Replace with market participant client. +/// +public interface IGridAreaOwnerClient +{ + // TODO: Remove or reintroduce GetCurrentOwnerAsync() implementation instead of IsCurrentOwnerAsync() + // Task GetCurrentOwnerAsync(string gridArea, CancellationToken cancellationToken); + // public record GridAreaOwner( + // Guid Id, + // string GridAreaCode, + // string OwnerActorNumber, + // Instant ValidFrom, + // int SequenceNumber); + + /// + /// Check if the given is the current owner of the given . + /// + /// Returns true of the given is the current owner. + Task IsCurrentOwnerAsync(string gridArea, string actorNumber, CancellationToken cancellationToken); +} + +/// +/// Mock of the , that always returns true. +/// +public class GridAreaOwnerMockClient : IGridAreaOwnerClient +{ + public Task IsCurrentOwnerAsync(string gridArea, string actorNumber, CancellationToken cancellationToken) + { + return Task.FromResult(true); + } +} diff --git a/source/ProcessManager.Components/BusinessValidation/Helpers/ActorNumberValidationHelper.cs b/source/ProcessManager.Components/BusinessValidation/Helpers/ActorNumberValidationHelper.cs new file mode 100644 index 00000000..7474b839 --- /dev/null +++ b/source/ProcessManager.Components/BusinessValidation/Helpers/ActorNumberValidationHelper.cs @@ -0,0 +1,28 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Energinet.DataHub.ProcessManager.Components.BusinessValidation.Helpers; + +public static class ActorNumberValidationHelper +{ + public static bool IsValidGlnNumber(string actorNumber) + { + return actorNumber.Length == 13; + } + + public static bool IsValidEicNumber(string actorNumber) + { + return actorNumber.Length == 16; + } +} diff --git a/source/ProcessManager.Components/BusinessValidation/Helpers/PeriodValidationHelper.cs b/source/ProcessManager.Components/BusinessValidation/Helpers/PeriodValidationHelper.cs new file mode 100644 index 00000000..379e7762 --- /dev/null +++ b/source/ProcessManager.Components/BusinessValidation/Helpers/PeriodValidationHelper.cs @@ -0,0 +1,65 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using NodaTime; + +namespace Energinet.DataHub.ProcessManager.Components.BusinessValidation.Helpers; + +public class PeriodValidationHelper(DateTimeZone dateTimeZone, IClock clock) +{ + private readonly DateTimeZone _dateTimeZone = dateTimeZone; + private readonly IClock _clock = clock; + + public bool IsMidnight(Instant instant, out ZonedDateTime zonedDateTime) + { + zonedDateTime = new ZonedDateTime(instant, _dateTimeZone); + + return zonedDateTime.TimeOfDay == LocalTime.Midnight; + } + + public bool IsDateOlderThanAllowed(Instant date, int maxYears, int maxMonths) + { + var zonedStartDateTime = new ZonedDateTime(date, _dateTimeZone); + var zonedCurrentDateTime = new ZonedDateTime(_clock.GetCurrentInstant(), _dateTimeZone); + var latestStartDate = zonedCurrentDateTime.LocalDateTime.PlusYears(-maxYears).PlusMonths(-maxMonths); + + return zonedStartDateTime.LocalDateTime < latestStartDate; + } + + public bool IntervalMustBeLessThanAllowedPeriodSize(Instant start, Instant end, int maxAllowedPeriodSizeInMonths) + { + var zonedStartDateTime = new ZonedDateTime(start, _dateTimeZone); + var zonedEndDateTime = new ZonedDateTime(end, _dateTimeZone); + var monthsFromStart = zonedStartDateTime.LocalDateTime.PlusMonths(maxAllowedPeriodSizeInMonths); + + return zonedEndDateTime.LocalDateTime > monthsFromStart; + } + + public bool IsMonthOfDateOlderThanXYearsAndYMonths(Instant periodStart, int years, int months) + { + var dateInQuestion = periodStart.InZone(_dateTimeZone); + var someYearsAndSomeMonthsAgo = _clock.GetCurrentInstant() + .InZone(_dateTimeZone) + .Date.PlusYears(-years) + .PlusMonths(-months); + + if (dateInQuestion.Year > someYearsAndSomeMonthsAgo.Year) + return false; + + if (dateInQuestion.Year == someYearsAndSomeMonthsAgo.Year) + return dateInQuestion.Month < someYearsAndSomeMonthsAgo.Month; + + return true; + } +} diff --git a/source/ProcessManager.Components/BusinessValidation/IBusinessValidationRule.cs b/source/ProcessManager.Components/BusinessValidation/IBusinessValidationRule.cs new file mode 100644 index 00000000..40c4caac --- /dev/null +++ b/source/ProcessManager.Components/BusinessValidation/IBusinessValidationRule.cs @@ -0,0 +1,26 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; + +namespace Energinet.DataHub.ProcessManager.Components.BusinessValidation; + +/// +/// Contains the business logic for validating a specific type of entity. +/// +public interface IBusinessValidationRule + where TInput : IBusinessValidatedDto +{ + Task> ValidateAsync(TInput subject); +} diff --git a/source/ProcessManager.Components/BusinessValidation/ValidationError.cs b/source/ProcessManager.Components/BusinessValidation/ValidationError.cs new file mode 100644 index 00000000..f8eaafa9 --- /dev/null +++ b/source/ProcessManager.Components/BusinessValidation/ValidationError.cs @@ -0,0 +1,23 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Energinet.DataHub.ProcessManager.Components.BusinessValidation; + +public sealed record ValidationError(string Message, string ErrorCode) +{ + public ValidationError WithPropertyName(string propertyName) + { + return new ValidationError(Message.Replace("{PropertyName}", propertyName), ErrorCode); + } +} diff --git a/source/ProcessManager.Components/Extensions/DependencyInjection/BusinessValidationExtensions.cs b/source/ProcessManager.Components/Extensions/DependencyInjection/BusinessValidationExtensions.cs new file mode 100644 index 00000000..5a30c525 --- /dev/null +++ b/source/ProcessManager.Components/Extensions/DependencyInjection/BusinessValidationExtensions.cs @@ -0,0 +1,100 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Reflection; +using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation.GridAreaOwner; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation.Helpers; +using Microsoft.Extensions.DependencyInjection; + +namespace Energinet.DataHub.ProcessManager.Components.Extensions.DependencyInjection; + +public static class BusinessValidationExtensions +{ + /// + /// Add required services for business validation. Registers implementations of + /// and from the given . + /// + /// implementations are registered by finding + /// types in the given , and registering a + /// for each type found. This means the given must also include the assembly that + /// contains the types that implement + /// (example: ProcessManager.Orchestrations and ProcessManager.Orchestrations.Abstractions assemblies). + /// + /// + public static IServiceCollection AddBusinessValidation( + this IServiceCollection services, + IReadOnlyCollection assembliesToScan) + { + services.AddBusinessValidatorImplementations(assembliesToScan); + services.AddBusinessValidationRuleImplementations(assembliesToScan); + + services.AddTransient(); + + // TODO: Replace GridAreaOwnerMockClient with actual client + services.AddTransient(); + + return services; + } + + /// + /// Register implementations of for + /// each type found in . + /// + private static IServiceCollection AddBusinessValidatorImplementations( + this IServiceCollection services, + IReadOnlyCollection assemblies) + { + var businessValidatedDtoTypes = assemblies + .SelectMany(a => a.DefinedTypes).Distinct() + .Where(t => + t is { IsClass: true, IsAbstract: false, IsGenericType: false } + && t.IsAssignableTo(typeof(IBusinessValidatedDto))); + + foreach (var businessValidatedDtoType in businessValidatedDtoTypes) + { + var businessValidatorTypeForDtoType = typeof(BusinessValidator<>).MakeGenericType(businessValidatedDtoType); + services.AddTransient(businessValidatorTypeForDtoType); + } + + return services; + } + + /// + /// Register implementations of found in . + /// + private static IServiceCollection AddBusinessValidationRuleImplementations( + this IServiceCollection services, + IReadOnlyCollection assemblies) + { + var validationRuleInterfaceType = typeof(IBusinessValidationRule<>); + + var validationRuleImplementationTypes = assemblies + .SelectMany(a => a.DefinedTypes).Distinct() + .Where(typeInfo => + typeInfo is { IsClass: true, IsAbstract: false } + && typeInfo.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == validationRuleInterfaceType)) + .ToList(); + + foreach (var validationRuleType in validationRuleImplementationTypes) + { + var interfaceTypeForValidationRule = validationRuleType.GetInterfaces() + .Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == validationRuleInterfaceType); + services.AddTransient(interfaceTypeForValidationRule, validationRuleType); + } + + return services; + } +} diff --git a/source/ProcessManager.Example.Consumer/Functions/BRS_X03_ActorRequestProcessExample/StartTrigger_Brs_X03.cs b/source/ProcessManager.Example.Consumer/Functions/BRS_X03_ActorRequestProcessExample/StartTrigger_Brs_X03.cs index 6b87f4a6..ff3f5336 100644 --- a/source/ProcessManager.Example.Consumer/Functions/BRS_X03_ActorRequestProcessExample/StartTrigger_Brs_X03.cs +++ b/source/ProcessManager.Example.Consumer/Functions/BRS_X03_ActorRequestProcessExample/StartTrigger_Brs_X03.cs @@ -37,7 +37,7 @@ public Task Run( Route = "actor-request-process/start")] HttpRequest httpRequest, [FromBody] - string idempotencyKey, + StartTriggerInput input, FunctionContext executionContext) { return _messageClient.StartNewOrchestrationInstanceAsync( @@ -46,8 +46,12 @@ public Task Run( inputParameter: new ActorRequestProcessExampleInputV1( RequestedByActorNumber: "1234567890123", RequestedByActorRole: "EnergySupplier", - BusinessReason: "ABC"), - idempotencyKey: idempotencyKey), + BusinessReason: input.BusinessReason), + idempotencyKey: input.IdempotencyKey), CancellationToken.None); } + + public record StartTriggerInput( + string IdempotencyKey, + string BusinessReason); } diff --git a/source/ProcessManager.Example.Orchestrations.Abstrations/Processes/BRS_X03_ActorRequestProcessExample/V1/ActorRequestProcessExampleEnqueueRejectedDataV1.cs b/source/ProcessManager.Example.Orchestrations.Abstrations/Processes/BRS_X03_ActorRequestProcessExample/V1/ActorRequestProcessExampleEnqueueRejectedDataV1.cs new file mode 100644 index 00000000..1aa049e7 --- /dev/null +++ b/source/ProcessManager.Example.Orchestrations.Abstrations/Processes/BRS_X03_ActorRequestProcessExample/V1/ActorRequestProcessExampleEnqueueRejectedDataV1.cs @@ -0,0 +1,23 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; + +namespace Energinet.DataHub.ProcessManager.Example.Orchestrations.Abstractions.Processes.BRS_X03_ActorRequestProcessExample.V1; + +/// +/// Data when enqueueing BRS-X03 actor messages. +/// +public record ActorRequestProcessExampleEnqueueRejectedDataV1( + List ValidationErrors); diff --git a/source/ProcessManager.Example.Orchestrations.Abstrations/Processes/BRS_X03_ActorRequestProcessExample/V1/ActorRequestProcessExampleInputV1.cs b/source/ProcessManager.Example.Orchestrations.Abstrations/Processes/BRS_X03_ActorRequestProcessExample/V1/ActorRequestProcessExampleInputV1.cs index b7a7bd24..27eb266b 100644 --- a/source/ProcessManager.Example.Orchestrations.Abstrations/Processes/BRS_X03_ActorRequestProcessExample/V1/ActorRequestProcessExampleInputV1.cs +++ b/source/ProcessManager.Example.Orchestrations.Abstrations/Processes/BRS_X03_ActorRequestProcessExample/V1/ActorRequestProcessExampleInputV1.cs @@ -13,6 +13,7 @@ // limitations under the License. using Energinet.DataHub.ProcessManager.Abstractions.Api.Model; +using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; namespace Energinet.DataHub.ProcessManager.Example.Orchestrations.Abstractions.Processes.BRS_X03_ActorRequestProcessExample.V1; @@ -23,4 +24,4 @@ public record ActorRequestProcessExampleInputV1( string RequestedByActorNumber, string RequestedByActorRole, string BusinessReason) - : IInputParameterDto; + : IInputParameterDto, IBusinessValidatedDto; diff --git a/source/ProcessManager.Example.Orchestrations.Tests/Integration/Processes/BRS_X03_ActorRequestProcessExample/V1/MonitorOrchestrationUsingClientScenario.cs b/source/ProcessManager.Example.Orchestrations.Tests/Integration/Processes/BRS_X03_ActorRequestProcessExample/V1/MonitorOrchestrationUsingClientScenario.cs index 664b73b1..6680a40f 100644 --- a/source/ProcessManager.Example.Orchestrations.Tests/Integration/Processes/BRS_X03_ActorRequestProcessExample/V1/MonitorOrchestrationUsingClientScenario.cs +++ b/source/ProcessManager.Example.Orchestrations.Tests/Integration/Processes/BRS_X03_ActorRequestProcessExample/V1/MonitorOrchestrationUsingClientScenario.cs @@ -17,6 +17,7 @@ using Energinet.DataHub.ProcessManager.Client; using Energinet.DataHub.ProcessManager.Client.Extensions.DependencyInjection; using Energinet.DataHub.ProcessManager.Client.Extensions.Options; +using Energinet.DataHub.ProcessManager.Example.Consumer.Functions.BRS_X03_ActorRequestProcessExample; using Energinet.DataHub.ProcessManager.Example.Orchestrations.Abstractions.Processes.BRS_X02.NotifyOrchestrationInstanceExample.V1; using Energinet.DataHub.ProcessManager.Example.Orchestrations.Processes.BRS_X02.NotifyOrchestrationInstanceExample.V1; using Energinet.DataHub.ProcessManager.Example.Orchestrations.Tests.Fixtures; @@ -88,15 +89,18 @@ public async Task DisposeAsync() /// 5. Terminate orchestration (with TerminationState=Succeeded) in Orchestrations app, if ActorMessagesEnqueued event is received before timeout. /// [Fact] - public async Task Given_ActorRequestProcessExampleOrchestration_AndGiven_Consumer_When_StartedByConsumer_Then_OrchestrationTerminatesSuccessfully() + public async Task Given_ConsumerApp_When_BRS_X03_OrchestrationInstanceStartedByConsumer_Then_OrchestrationTerminatesSuccessfully() { var processManagerClient = ServiceProvider.GetRequiredService(); // Step 1: Start new BRS-X03 using the Example.Consumer app var idempotencyKey = Guid.NewGuid().ToString(); + var startTriggerInput = new StartTrigger_Brs_X03.StartTriggerInput( + IdempotencyKey: idempotencyKey, + BusinessReason: "B01"); await Fixture.ExampleConsumerAppManager.AppHostManager.HttpClient.PostAsJsonAsync( requestUri: "/api/actor-request-process/start", - value: idempotencyKey); + value: startTriggerInput); // Step 2: Query until terminated with succeeded var (isTerminated, succeededOrchestrationInstance) = await processManagerClient @@ -113,5 +117,15 @@ await Fixture.ExampleConsumerAppManager.AppHostManager.HttpClient.PostAsJsonAsyn isTerminated.Should().BeTrue("because the BRS-X03 orchestration instance should complete within given wait time"); succeededOrchestrationInstance.Should().NotBeNull(); + + succeededOrchestrationInstance!.Steps.Should() + .AllSatisfy( + s => + { + s.Lifecycle.State.Should().Be(StepInstanceLifecycleState.Terminated); + s.Lifecycle.TerminationState.Should() + .NotBeNull() + .And.Be(OrchestrationStepTerminationState.Succeeded); + }); } } diff --git a/source/ProcessManager.Example.Orchestrations/ProcessManager.Example.Orchestrations.csproj b/source/ProcessManager.Example.Orchestrations/ProcessManager.Example.Orchestrations.csproj index 820db9bd..3abde81c 100644 --- a/source/ProcessManager.Example.Orchestrations/ProcessManager.Example.Orchestrations.csproj +++ b/source/ProcessManager.Example.Orchestrations/ProcessManager.Example.Orchestrations.csproj @@ -8,6 +8,7 @@ + diff --git a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Activities/GetOrchestrationExecutionPlanActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1.cs b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Activities/GetOrchestrationInstanceContextActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1.cs similarity index 79% rename from source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Activities/GetOrchestrationExecutionPlanActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1.cs rename to source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Activities/GetOrchestrationInstanceContextActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1.cs index 6c3fce15..b4cae995 100644 --- a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Activities/GetOrchestrationExecutionPlanActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1.cs +++ b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Activities/GetOrchestrationInstanceContextActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1.cs @@ -20,20 +20,20 @@ namespace Energinet.DataHub.ProcessManager.Example.Orchestrations.Processes.BRS_X02.NotifyOrchestrationInstanceExample.V1.Activities; -internal class GetOrchestrationExecutionPlanActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1( +internal class GetOrchestrationInstanceContextActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1( IOptions options) { private readonly OrchestrationOptions_Brs_X02_NotifyOrchestrationInstanceExample_V1 _options = options.Value; - [Function(nameof(GetOrchestrationExecutionPlanActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1))] - public Task Run( + [Function(nameof(GetOrchestrationInstanceContextActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1))] + public Task Run( [ActivityTrigger] ActivityInput input) { - var plan = new OrchestrationExecutionPlan( + var orchestrationInstanceContext = new OrchestrationInstanceContext( OrchestrationInstanceId: input.OrchestrationInstanceId, Options: _options); - return Task.FromResult(plan); + return Task.FromResult(orchestrationInstanceContext); } public record ActivityInput( diff --git a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Models/OrchestrationExecutionPlan.cs b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Models/OrchestrationInstanceContext.cs similarity index 96% rename from source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Models/OrchestrationExecutionPlan.cs rename to source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Models/OrchestrationInstanceContext.cs index 77716325..52668ac4 100644 --- a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Models/OrchestrationExecutionPlan.cs +++ b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Models/OrchestrationInstanceContext.cs @@ -21,6 +21,6 @@ namespace Energinet.DataHub.ProcessManager.Example.Orchestrations.Processes.BRS_ /// The purpose of this record is to give the orchestration information specific about orchestration specific /// options from app settings. /// -public record OrchestrationExecutionPlan( +public record OrchestrationInstanceContext( OrchestrationInstanceId OrchestrationInstanceId, OrchestrationOptions_Brs_X02_NotifyOrchestrationInstanceExample_V1 Options); diff --git a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Orchestration_Brs_X02_NotifyOrchestrationInstanceExample_V1.cs b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Orchestration_Brs_X02_NotifyOrchestrationInstanceExample_V1.cs index 3be63d50..206e8b2e 100644 --- a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Orchestration_Brs_X02_NotifyOrchestrationInstanceExample_V1.cs +++ b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X02/NotifyOrchestrationInstanceExample/V1/Orchestration_Brs_X02_NotifyOrchestrationInstanceExample_V1.cs @@ -61,7 +61,7 @@ public async Task Run( input); } - private async Task InitializeOrchestrationAsync(TaskOrchestrationContext context) + private async Task InitializeOrchestrationAsync(TaskOrchestrationContext context) { var instanceId = new OrchestrationInstanceId(Guid.Parse(context.InstanceId)); @@ -70,9 +70,9 @@ await context.CallActivityAsync( new TransitionOrchestrationToRunningActivity_V1.ActivityInput(instanceId), _defaultRetryOptions); - var orchestrationExecutionPlan = await context.CallActivityAsync( - nameof(GetOrchestrationExecutionPlanActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1), - new GetOrchestrationExecutionPlanActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1.ActivityInput( + var orchestrationExecutionPlan = await context.CallActivityAsync( + nameof(GetOrchestrationInstanceContextActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1), + new GetOrchestrationInstanceContextActivity_Brs_X02_NotifyOrchestrationInstanceExample_V1.ActivityInput( instanceId), _defaultRetryOptions); diff --git a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/OrchestrationDescriptionBuilder.cs b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/OrchestrationDescriptionBuilder.cs index 32ae2916..d0f58e96 100644 --- a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/OrchestrationDescriptionBuilder.cs +++ b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/OrchestrationDescriptionBuilder.cs @@ -31,6 +31,7 @@ public OrchestrationDescription Build() description.ParameterDefinition.SetFromType(); + description.AppendStepDescription("Business validation"); description.AppendStepDescription("Enqueue actor messages"); return description; diff --git a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/Activities/EnqueueRejectedActorMessageActivity_Brs_X03_V1.cs b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/Activities/EnqueueRejectedActorMessageActivity_Brs_X03_V1.cs new file mode 100644 index 00000000..a376f1c8 --- /dev/null +++ b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/Activities/EnqueueRejectedActorMessageActivity_Brs_X03_V1.cs @@ -0,0 +1,58 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.EnqueueActorMessages; +using Energinet.DataHub.ProcessManager.Core.Application.Orchestration; +using Energinet.DataHub.ProcessManager.Core.Domain.OrchestrationInstance; +using Energinet.DataHub.ProcessManager.Example.Orchestrations.Abstractions.Processes.BRS_X03_ActorRequestProcessExample.V1; +using Microsoft.Azure.Functions.Worker; + +namespace Energinet.DataHub.ProcessManager.Example.Orchestrations.Processes.BRS_X03_ActorRequestProcessExample.V1.Activities; + +public class EnqueueRejectedActorMessageActivity_Brs_X03_V1( + IOrchestrationInstanceProgressRepository repository, + IEnqueueActorMessagesClient enqueueActorMessagesClient) +{ + private readonly IOrchestrationInstanceProgressRepository _repository = repository; + private readonly IEnqueueActorMessagesClient _enqueueActorMessagesClient = enqueueActorMessagesClient; + + [Function(nameof(EnqueueRejectedActorMessageActivity_Brs_X03_V1))] + public async Task Run( + [ActivityTrigger] ActivityInput input) + { + var orchestrationInstance = await _repository.GetAsync(input.OrchestrationInstanceId).ConfigureAwait(false); + + var rejectedMessage = new ActorRequestProcessExampleEnqueueRejectedDataV1( + ValidationErrors: input.ValidationErrors + .Select(e => new ValidationErrorDto( + Message: e.Message, + ErrorCode: e.ErrorCode)) + .ToList()); + + await _enqueueActorMessagesClient.EnqueueAsync( + Orchestration_Brs_X03_V1.UniqueName, + orchestrationInstance.Id.Value, + orchestrationInstance.Lifecycle.CreatedBy.Value.ToDto(), + input.IdempotencyKey, + rejectedMessage) + .ConfigureAwait(false); + } + + public record ActivityInput( + OrchestrationInstanceId OrchestrationInstanceId, + Guid IdempotencyKey, + IReadOnlyCollection ValidationErrors); +} diff --git a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/Activities/PerformBusinessValidationActivity_Brs_X03_V1.cs b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/Activities/PerformBusinessValidationActivity_Brs_X03_V1.cs new file mode 100644 index 00000000..408bf8ab --- /dev/null +++ b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/Activities/PerformBusinessValidationActivity_Brs_X03_V1.cs @@ -0,0 +1,41 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Example.Orchestrations.Abstractions.Processes.BRS_X03_ActorRequestProcessExample.V1; +using Microsoft.Azure.Functions.Worker; + +namespace Energinet.DataHub.ProcessManager.Example.Orchestrations.Processes.BRS_X03_ActorRequestProcessExample.V1.Activities; + +public class PerformBusinessValidationActivity_Brs_X03_V1( + BusinessValidator validator) +{ + private readonly BusinessValidator _validator = validator; + + [Function(nameof(PerformBusinessValidationActivity_Brs_X03_V1))] + public async Task Run( + [ActivityTrigger] ActivityInput activityInput) + { + var validationErrors = await _validator.ValidateAsync(activityInput.Input).ConfigureAwait(false); + + return new ActivityOutput( + validationErrors); + } + + public record ActivityInput( + ActorRequestProcessExampleInputV1 Input); + + public record ActivityOutput( + IReadOnlyCollection ValidationErrors); +} diff --git a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/BusinessValidation/ValidationRules/BusinessReasonValidationRule.cs b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/BusinessValidation/ValidationRules/BusinessReasonValidationRule.cs new file mode 100644 index 00000000..efa94325 --- /dev/null +++ b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/BusinessValidation/ValidationRules/BusinessReasonValidationRule.cs @@ -0,0 +1,37 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Example.Orchestrations.Abstractions.Processes.BRS_X03_ActorRequestProcessExample.V1; + +namespace Energinet.DataHub.ProcessManager.Example.Orchestrations.Processes.BRS_X03_ActorRequestProcessExample.V1.BusinessValidation.ValidationRules; + +public class BusinessReasonValidationRule : IBusinessValidationRule +{ + private IList NoErrors => []; + + private IList StringIsEmptyError => [ + new( + Message: "BusinessReason skal være udfyldt / BusinessReaon must have a value", + ErrorCode: "E01"), + ]; + + public Task> ValidateAsync(ActorRequestProcessExampleInputV1 subject) + { + if (string.IsNullOrEmpty(subject.BusinessReason)) + return Task.FromResult(StringIsEmptyError); + + return Task.FromResult(NoErrors); + } +} diff --git a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/Orchestration_Brs_X03_V1.cs b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/Orchestration_Brs_X03_V1.cs index d888a62c..b5d53bd3 100644 --- a/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/Orchestration_Brs_X03_V1.cs +++ b/source/ProcessManager.Example.Orchestrations/Processes/BRS_X03_ActorRequestProcessExample/V1/Orchestration_Brs_X03_V1.cs @@ -27,7 +27,8 @@ namespace Energinet.DataHub.ProcessManager.Example.Orchestrations.Processes.BRS_ internal class Orchestration_Brs_X03_V1 { - internal const int EnqueueActorMessagesStep = 1; + internal const int BusinessValidationStep = 1; + internal const int EnqueueActorMessagesStep = 2; public static readonly OrchestrationDescriptionUniqueNameDto UniqueName = Brs_X03.V1; @@ -44,20 +45,26 @@ public async Task Run( { var input = context.GetOrchestrationParameterValue(); - // Initialize + // Initialize orchestration instance var instanceId = await InitializeOrchestrationAsync(context); - // Step 1a: Enqueue actor messages + // Step: Business validation + var validationResult = await PerformBusinessValidation( + context, + instanceId, + input); + + // Step: Enqueue actor messages await EnqueueActorMessages( context, instanceId, input); - // Step 1b: Wait for actor messages enqueued event var hasReceivedActorMessagesEnqueuedEvent = await WaitForActorMessagesEnqueuedEventAsync( context, instanceId); + // Terminate orchestration instance return await TerminateOrchestrationAsync( context, instanceId, @@ -79,6 +86,39 @@ await context.CallActivityAsync( return instanceId; } + private async Task PerformBusinessValidation( + TaskOrchestrationContext context, + OrchestrationInstanceId instanceId, + ActorRequestProcessExampleInputV1 input) + { + await context.CallActivityAsync( + nameof(TransitionStepToRunningActivity_V1), + new TransitionStepToRunningActivity_V1.ActivityInput( + instanceId, + BusinessValidationStep), + _defaultRetryOptions); + + var businessValidationResult = await context.CallActivityAsync( + nameof(PerformBusinessValidationActivity_Brs_X03_V1), + new PerformBusinessValidationActivity_Brs_X03_V1.ActivityInput( + input), + _defaultRetryOptions); + + var businessValidationStepTerminationState = businessValidationResult.ValidationErrors.Count == 0 + ? OrchestrationStepTerminationState.Succeeded + : OrchestrationStepTerminationState.Failed; + + await context.CallActivityAsync( + nameof(TransitionStepToTerminatedActivity_V1), + new TransitionStepToTerminatedActivity_V1.ActivityInput( + instanceId, + BusinessValidationStep, + businessValidationStepTerminationState), + _defaultRetryOptions); + + return businessValidationResult; + } + private async Task EnqueueActorMessages( TaskOrchestrationContext context, OrchestrationInstanceId instanceId, diff --git a/source/ProcessManager.Example.Orchestrations/Program.cs b/source/ProcessManager.Example.Orchestrations/Program.cs index 7f07f5d5..2afea66e 100644 --- a/source/ProcessManager.Example.Orchestrations/Program.cs +++ b/source/ProcessManager.Example.Orchestrations/Program.cs @@ -20,6 +20,7 @@ using Energinet.DataHub.ProcessManager.Components.Extensions.DependencyInjection; using Energinet.DataHub.ProcessManager.Core.Infrastructure.Extensions.DependencyInjection; using Energinet.DataHub.ProcessManager.Core.Infrastructure.Extensions.Startup; +using Energinet.DataHub.ProcessManager.Example.Orchestrations.Abstractions.Processes.BRS_X03_ActorRequestProcessExample.V1; using Microsoft.Extensions.Hosting; var host = new HostBuilder() @@ -39,6 +40,11 @@ // => Add EnqueueActorMessages client services.AddServiceBusClientForApplication(context.Configuration); services.AddEnqueueActorMessages(azureCredential); + + // Add BusinessValidation + var orchestrationsExampleAssembly = typeof(Program).Assembly; + var orchestrationsExampleAbstractionsAssembly = typeof(ActorRequestProcessExampleInputV1).Assembly; + services.AddBusinessValidation([orchestrationsExampleAssembly, orchestrationsExampleAbstractionsAssembly]); }) .ConfigureLogging((hostingContext, logging) => { diff --git a/source/ProcessManager.Orchestrations.Abstractions/Components/Datahub/ValueObjects/SettlementMethod.cs b/source/ProcessManager.Orchestrations.Abstractions/Components/Datahub/ValueObjects/SettlementMethod.cs new file mode 100644 index 00000000..0b4dc288 --- /dev/null +++ b/source/ProcessManager.Orchestrations.Abstractions/Components/Datahub/ValueObjects/SettlementMethod.cs @@ -0,0 +1,33 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Text.Json.Serialization; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; + +[Serializable] +public class SettlementMethod : DataHubType +{ + // Customer with more than ~100.000 kwH per year + public static readonly SettlementMethod NonProfiled = new("NonProfiled", "E02"); + + // Customer with less than ~100.000 kwH per year + public static readonly SettlementMethod Flex = new("Flex", "D01"); + + [JsonConstructor] + private SettlementMethod(string name, string code) + : base(name, code) + { + } +} diff --git a/source/ProcessManager.Orchestrations.Abstractions/Components/Datahub/ValueObjects/SettlementVersion.cs b/source/ProcessManager.Orchestrations.Abstractions/Components/Datahub/ValueObjects/SettlementVersion.cs new file mode 100644 index 00000000..bcd36cf2 --- /dev/null +++ b/source/ProcessManager.Orchestrations.Abstractions/Components/Datahub/ValueObjects/SettlementVersion.cs @@ -0,0 +1,31 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Text.Json.Serialization; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; + +[Serializable] +public class SettlementVersion : DataHubType +{ + public static readonly SettlementVersion FirstCorrection = new("FirstCorrection", "D01"); + public static readonly SettlementVersion SecondCorrection = new("SecondCorrection", "D02"); + public static readonly SettlementVersion ThirdCorrection = new("ThirdCorrection", "D03"); + + [JsonConstructor] + private SettlementVersion(string name, string code) + : base(name, code) + { + } +} diff --git a/source/ProcessManager.Orchestrations.Abstractions/ProcessManager.Orchestrations.Abstractions.csproj b/source/ProcessManager.Orchestrations.Abstractions/ProcessManager.Orchestrations.Abstractions.csproj index f8044b0d..54bbd56d 100644 --- a/source/ProcessManager.Orchestrations.Abstractions/ProcessManager.Orchestrations.Abstractions.csproj +++ b/source/ProcessManager.Orchestrations.Abstractions/ProcessManager.Orchestrations.Abstractions.csproj @@ -7,7 +7,7 @@ Energinet.DataHub.ProcessManager.Orchestrations.Abstractions - 0.15.2$(VersionSuffix) + 0.16.0$(VersionSuffix) DH3 Process Manager Orchestrations Abstractions library Energinet-DataHub Energinet-DataHub @@ -59,8 +59,4 @@ - - - - diff --git a/source/ProcessManager.Orchestrations.Abstractions/Processes/BRS_026/V1/Model/RequestCalculatedEnergyTimeSeriesInputV1.cs b/source/ProcessManager.Orchestrations.Abstractions/Processes/BRS_026/V1/Model/RequestCalculatedEnergyTimeSeriesInputV1.cs index 8303a85e..534eb548 100644 --- a/source/ProcessManager.Orchestrations.Abstractions/Processes/BRS_026/V1/Model/RequestCalculatedEnergyTimeSeriesInputV1.cs +++ b/source/ProcessManager.Orchestrations.Abstractions/Processes/BRS_026/V1/Model/RequestCalculatedEnergyTimeSeriesInputV1.cs @@ -13,6 +13,8 @@ // limitations under the License. using Energinet.DataHub.ProcessManager.Abstractions.Api.Model; +using Energinet.DataHub.ProcessManager.Abstractions.Components; +using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; namespace Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; @@ -45,4 +47,4 @@ public record RequestCalculatedEnergyTimeSeriesInputV1( string? MeteringPointType, string? SettlementMethod, string? SettlementVersion) - : IInputParameterDto; + : IInputParameterDto, IBusinessValidatedDto; diff --git a/source/ProcessManager.Orchestrations.Abstractions/Processes/BRS_026/V1/Model/RequestCalculatedEnergyTimeSeriesRejectedV1.cs b/source/ProcessManager.Orchestrations.Abstractions/Processes/BRS_026/V1/Model/RequestCalculatedEnergyTimeSeriesRejectedV1.cs index a652edbc..0fb085be 100644 --- a/source/ProcessManager.Orchestrations.Abstractions/Processes/BRS_026/V1/Model/RequestCalculatedEnergyTimeSeriesRejectedV1.cs +++ b/source/ProcessManager.Orchestrations.Abstractions/Processes/BRS_026/V1/Model/RequestCalculatedEnergyTimeSeriesRejectedV1.cs @@ -12,9 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; + namespace Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; /// -/// A model containing the validation errors if a energy time series request is rejected. +/// A model containing the validation errors if an energy time series request is rejected. /// -public record RequestCalculatedEnergyTimeSeriesRejectedV1(List ValidationErrors); +public record RequestCalculatedEnergyTimeSeriesRejectedV1( + List ValidationErrors); diff --git a/source/ProcessManager.Orchestrations.Tests/Integration/Processes/BRS_026/V1/MonitorOrchestrationUsingClientsScenario.cs b/source/ProcessManager.Orchestrations.Tests/Integration/Processes/BRS_026/V1/MonitorOrchestrationUsingClientsScenario.cs index b211208e..db1f0815 100644 --- a/source/ProcessManager.Orchestrations.Tests/Integration/Processes/BRS_026/V1/MonitorOrchestrationUsingClientsScenario.cs +++ b/source/ProcessManager.Orchestrations.Tests/Integration/Processes/BRS_026/V1/MonitorOrchestrationUsingClientsScenario.cs @@ -17,6 +17,7 @@ using Energinet.DataHub.ProcessManager.Client; using Energinet.DataHub.ProcessManager.Client.Extensions.DependencyInjection; using Energinet.DataHub.ProcessManager.Client.Extensions.Options; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1; using Energinet.DataHub.ProcessManager.Orchestrations.Tests.Fixtures; @@ -83,22 +84,22 @@ public async Task DisposeAsync() /// Showing how we can orchestrate and monitor an orchestration instance only using clients. /// [Fact] - public async Task RequestCalculatedEnergyTimeSeries_WhenStarted_CanMonitorLifecycle() + public async Task RequestCalculatedEnergyTimeSeries_WhenStarted_OrchestrationInstanceTerminatesWithSuccess() { var processManagerMessageClient = ServiceProvider.GetRequiredService(); var processManagerClient = ServiceProvider.GetRequiredService(); // Step 1: Start new orchestration instance - var businessReason = "BalanceFixing"; - var energySupplierNumber = "23143245321"; + var businessReason = BusinessReason.BalanceFixing.Name; + var energySupplierNumber = "1234567891234"; var startRequestCommand = new RequestCalculatedEnergyTimeSeriesCommandV1( new ActorIdentityDto(Guid.NewGuid()), new RequestCalculatedEnergyTimeSeriesInputV1( RequestedForActorNumber: energySupplierNumber, - RequestedForActorRole: "EnergySupplier", + RequestedForActorRole: ActorRole.EnergySupplier.Name, BusinessReason: businessReason, - PeriodStart: "2024-04-07 23:00:00", - PeriodEnd: "2024-04-08 23:00:00", + PeriodStart: "2024-04-07T22:00:00Z", + PeriodEnd: "2024-04-08T22:00:00Z", EnergySupplierNumber: energySupplierNumber, BalanceResponsibleNumber: null, GridAreas: ["804"], @@ -137,7 +138,7 @@ await processManagerMessageClient.NotifyOrchestrationInstanceAsync( CancellationToken.None); // Step 4: Query until terminated with succeeded - var (isTerminated, _) = await processManagerClient + var (orchestrationTerminatedWithSucceeded, terminatedOrchestrationInstance) = await processManagerClient .TryWaitForOrchestrationInstance( idempotencyKey: startRequestCommand.IdempotencyKey, (oi) => oi is @@ -149,6 +150,21 @@ await processManagerMessageClient.NotifyOrchestrationInstanceAsync( }, }); - isTerminated.Should().BeTrue("because the orchestration instance should complete within given wait time"); + orchestrationTerminatedWithSucceeded.Should().BeTrue( + "because the orchestration instance should be succeeded within the given wait time"); + + // If isTerminated is true then terminatedOrchestrationInstance should never be null + ArgumentNullException.ThrowIfNull(terminatedOrchestrationInstance); + + // => All steps should be Succeeded + terminatedOrchestrationInstance.Steps.Should() + .AllSatisfy( + s => + { + s.Lifecycle.State.Should().Be(StepInstanceLifecycleState.Terminated); + s.Lifecycle.TerminationState.Should() + .NotBeNull() + .And.Be(OrchestrationStepTerminationState.Succeeded); + }); } } diff --git a/source/ProcessManager.Orchestrations.Tests/ProcessManager.Orchestrations.Tests.csproj b/source/ProcessManager.Orchestrations.Tests/ProcessManager.Orchestrations.Tests.csproj index 87029ef8..b63739b7 100644 --- a/source/ProcessManager.Orchestrations.Tests/ProcessManager.Orchestrations.Tests.csproj +++ b/source/ProcessManager.Orchestrations.Tests/ProcessManager.Orchestrations.Tests.csproj @@ -74,4 +74,8 @@ + + + + diff --git a/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/RequestCalculatedEnergyTimeSeriesInputV1ValidatorTests.cs b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/RequestCalculatedEnergyTimeSeriesInputV1ValidatorTests.cs new file mode 100644 index 00000000..eba429bd --- /dev/null +++ b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/RequestCalculatedEnergyTimeSeriesInputV1ValidatorTests.cs @@ -0,0 +1,317 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation.GridAreaOwner; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation.Helpers; +using Energinet.DataHub.ProcessManager.Components.Extensions.DependencyInjection; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; +using NodaTime; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Tests.Unit.Processes.BRS_026.V1.BusinessValidation; + +public class RequestCalculatedEnergyTimeSeriesInputV1ValidatorTests +{ + private const string ValidActorNumber = "1111111111111"; + + private readonly BusinessValidator _sut; + private readonly Mock _clockMock; + private readonly DateTimeZone _timeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull("Europe/Copenhagen")!; + + public RequestCalculatedEnergyTimeSeriesInputV1ValidatorTests() + { + _clockMock = new Mock(); + _clockMock.Setup(c => c.GetCurrentInstant()).Returns(Instant.FromUtc(2024, 11, 15, 16, 46, 43)); + + IServiceCollection services = new ServiceCollection(); + + services.AddLogging(); + services.AddTransient(s => _timeZone); + services.AddTransient(s => _clockMock.Object); + services.AddTransient(); + + var gridAreaOwnerClientMock = new Mock(); + services.AddScoped(_ => gridAreaOwnerClientMock.Object); + + var orchestrationsAssembly = typeof(Orchestration_Brs_026_V1).Assembly; + var orchestrationsAbstractionsAssembly = typeof(RequestCalculatedEnergyTimeSeriesInputV1).Assembly; + services.AddBusinessValidation(assembliesToScan: [orchestrationsAssembly, orchestrationsAbstractionsAssembly]); + + var serviceProvider = services.BuildServiceProvider(); + + _sut = serviceProvider.GetRequiredService>(); + } + + [Fact] + public async Task Validate_WhenAggregatedTimeSeriesRequestIsValid_ReturnsSuccessValidation() + { + // Arrange + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole.EnergySupplier) + .Build(); + + // Act + var validationErrors = await _sut.ValidateAsync(request); + + // Assert + validationErrors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenPeriodSizeIsInvalid_ReturnsUnsuccessfulValidation() + { + // Arrange + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole.EnergySupplier) + .WithPeriod( + Instant.FromUtc(2022, 1, 1, 23, 0, 0), + Instant.FromUtc(2022, 3, 2, 23, 0, 0)) + .Build(); + + // Act + var validationErrors = await _sut.ValidateAsync(request); + + // Assert + validationErrors.Should() + .ContainSingle() + .Which.ErrorCode.Should() + .Be("E17"); + } + + [Fact] + public async Task + Validate_WhenPeriodIsMoreThan3AndAHalfYearBackInTimeButPartOfCutOffMonth_ReturnsSuccessfulValidation() + { + // Prerequisite: The current time is NOT the start of a month. + _clockMock.Setup(c => c.GetCurrentInstant()).Returns(Instant.FromUtc(2024, 11, 15, 16, 46, 43)); + + // Arrange + var periodStart = _clockMock.Object.GetCurrentInstant() // Assuming 2024-11-15 16:46:43 UTC + .InZone(_timeZone) // 2024-11-15 17:46:43 CET + .Date.PlusYears(-3) // 2021-11-15 + .PlusMonths(-6) // 2021-05-15 + .With(DateAdjusters.StartOfMonth) // 2021-05-01 + .AtMidnight() // 2021-05-01 00:00:00 UTC + .InZoneStrictly(_timeZone) // 2021-05-01 00:00:00 CEST + .ToInstant(); // 2021-04-30 22:00:00 UTC + + var periodEnd = _clockMock.Object.GetCurrentInstant() // Assuming 2024-11-15 16:46:43 UTC + .InZone(_timeZone) // 2024-11-15 17:46:43 CET + .Date.PlusYears(-3) // 2021-11-15 + .PlusMonths(-6) // 2021-05-15 + .With(DateAdjusters.StartOfMonth) // 2021-05-01 + .PlusDays(1) // 2021-05-02 + .AtMidnight() // 2021-05-02 00:00:00 UTC + .InZoneStrictly(_timeZone) // 2021-05-02 00:00:00 CEST + .ToInstant(); // 2021-05-01 22:00:00 UTC + + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole.EnergySupplier) + .WithPeriod(periodStart, periodEnd) + .Build(); + + // Act + var validationErrors = await _sut.ValidateAsync(request); + + // Assert + validationErrors.Should().BeEmpty(); + } + + [Fact] + public async Task + Validate_WhenPeriodOverlapsCutOffAt3AndAHalfYearBackInTime_ReturnsSuccessfulValidation() + { + // Prerequisite: The current time is NOT the start of a month. + _clockMock.Setup(c => c.GetCurrentInstant()).Returns(Instant.FromUtc(2024, 11, 15, 16, 46, 43)); + + // Arrange + var periodStart = _clockMock.Object.GetCurrentInstant() // Assuming 2024-11-15 16:46:43 UTC + .InZone(_timeZone) // 2024-11-15 17:46:43 CET + .Date.PlusYears(-3) // 2021-11-15 + .PlusMonths(-6) // 2021-05-15 + .PlusDays(-1) // 2021-05-14 + .AtMidnight() // 2021-05-14 00:00:00 UTC + .InZoneStrictly(_timeZone) // 2021-05-14 00:00:00 CEST + .ToInstant(); // 2021-05-13 22:00:00 UTC + + var periodEnd = _clockMock.Object.GetCurrentInstant() // Assuming 2024-11-15 16:46:43 UTC + .InZone(_timeZone) // 2024-11-15 17:46:43 CET + .Date.PlusYears(-3) // 2021-11-15 + .PlusMonths(-6) // 2021-05-15 + .PlusDays(1) // 2021-05-16 + .AtMidnight() // 2021-05-16 00:00:00 UTC + .InZoneStrictly(_timeZone) // 2021-05-16 00:00:00 CEST + .ToInstant(); // 2021-05-15 22:00:00 UTC + + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole.EnergySupplier) + .WithPeriod(periodStart, periodEnd) + .Build(); + + // Act + var validationErrors = await _sut.ValidateAsync(request); + + // Assert + validationErrors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenPeriodIsMoreThan3AndAHalfYearBackInTimeAndNotPartOfCutOffMonth_ReturnsUnsuccessfulValidation() + { + // Arrange + _clockMock.Setup(c => c.GetCurrentInstant()).Returns(Instant.FromUtc(2024, 11, 15, 16, 46, 43)); + var periodStart = _clockMock.Object.GetCurrentInstant() // Assuming 2024-11-15 16:46:43 UTC + .InZone(_timeZone) // 2024-11-15 17:46:43 CET + .Date.PlusYears(-3) // 2021-11-15 + .PlusMonths(-7) // 2021-04-15 + .AtMidnight() // 2021-04-15 00:00:00 UTC + .InZoneStrictly(_timeZone) // 2021-04-15 00:00:00 CEST + .ToInstant(); // 2021-04-14 22:00:00 UTC + + var periodEnd = _clockMock.Object.GetCurrentInstant() // Assuming 2024-11-15 16:46:43 UTC + .InZone(_timeZone) // 2024-11-15 17:46:43 CET + .Date.PlusYears(-3) // 2021-11-15 + .PlusMonths(-7) // 2021-04-15 + .PlusDays(1) // 2021-04-16 + .AtMidnight() // 2021-04-16 00:00:00 UTC + .InZoneStrictly(_timeZone) // 2021-04-16 00:00:00 CEST + .ToInstant(); // 2021-04-15 22:00:00 UTC + + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole.EnergySupplier) + .WithPeriod(periodStart, periodEnd) + .Build(); + + // Act + var validationErrors = await _sut.ValidateAsync(request); + + // Assert + validationErrors.Should() + .ContainSingle() + .Which.ErrorCode.Should() + .Be("E17"); + } + + [Fact] + public async Task Validate_WhenMeteringPointTypeIsInvalid_ReturnsUnsuccessfulValidation() + { + // Arrange + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole.EnergySupplier) + .WithMeteringPointType("invalid") + .Build(); + + // Act + var validationErrors = await _sut.ValidateAsync(request); + + // Assert + validationErrors.Should() + .ContainSingle() + .Which.ErrorCode.Should() + .Be("D18"); + } + + [Fact] + public async Task Validate_WhenEnergySupplierIdIsInvalid_ReturnsUnsuccessfulValidation() + { + // Arrange + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole.EnergySupplier) + .WithRequestedForActorNumber(ValidActorNumber) + .WithEnergySupplierNumber("invalid-actor-number") + .Build(); + + // Act + var validationErrors = await _sut.ValidateAsync(request); + + // Assert + validationErrors.Should() + .ContainSingle() + .Which.ErrorCode.Should() + .Be("E16"); + } + + [Fact] + public async Task Validate_WhenSettlementMethodIsInvalid_ReturnsUnsuccessfulValidation() + { + // Arrange + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole.EnergySupplier) + .WithSettlementMethod("invalid-settlement-method") + .Build(); + + // Act + var validationErrors = await _sut.ValidateAsync(request); + + // Assert + validationErrors.Should() + .ContainSingle() + .Which.ErrorCode.Should() + .Be("D15"); + } + + [Fact] + public async Task Validate_WhenSettlementVersionIsInvalid_ReturnsUnsuccessfulValidation() + { + // Arrange + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole.EnergySupplier) + .WithBusinessReason(BusinessReason.Correction.Name) + .WithSettlementVersion("invalid-settlement-version") + .Build(); + + // Act + var validationErrors = await _sut.ValidateAsync(request); + + // Assert + validationErrors.Should() + .ContainSingle() + .Which.ErrorCode.Should() + .Be("E86"); + } + + [Fact] + public async Task Validate_WhenConsumptionAndNoSettlementMethod_ReturnsUnsuccessfulValidation() + { + // Arrange + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole.EnergySupplier) + .WithMeteringPointType(MeteringPointType.Consumption.Name) + .WithSettlementMethod(null) + .Build(); + + // Act + var validationErrors = await _sut.ValidateAsync(request); + + // Assert + validationErrors.Should() + .ContainSingle() + .Which.ErrorCode.Should() + .Be("D11"); + } + + [Fact] + public async Task Validate_WhenWholesaleFixingForBalanceResponsible_ReturnsUnsuccessfulValidation() + { + // Arrange + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole.BalanceResponsibleParty) + .WithBusinessReason(BusinessReason.WholesaleFixing.Name) + .Build(); + + // Act + var validationErrors = await _sut.ValidateAsync(request); + + // Assert + validationErrors.Should() + .ContainSingle() + .Which.ErrorCode.Should() + .Be("D11"); + } +} diff --git a/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/BalanceResponsibleValidatorTest.cs b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/BalanceResponsibleValidatorTest.cs new file mode 100644 index 00000000..8b56f9cd --- /dev/null +++ b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/BalanceResponsibleValidatorTest.cs @@ -0,0 +1,219 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Diagnostics.CodeAnalysis; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; +using FluentAssertions; +using FluentAssertions.Execution; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Tests.Unit.Processes.BRS_026.V1.BusinessValidation.Rules; + +[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "Async suffix is not needed for test methods")] +public class BalanceResponsibleValidatorTest +{ + public const string ValidGlnNumber = "qwertyuiopasd"; // Must be 13 characters to be a valid GLN + private const string ValidEicNumber = "qwertyuiopasdfgh"; // Must be 16 characters to be a valid GLN + private const string BalanceResponsibleRole = "BalanceResponsibleParty"; + private static readonly ValidationError _invalidBalanceResponsible = new("Feltet BalanceResponsibleParty skal være udfyldt med et valid GLN/EIC når en balanceansvarlig anmoder om data / BalanceResponsibleParty must be submitted with a valid GLN/EIC when a balance responsible requests data", "E18"); + private static readonly ValidationError _mismatchedBalanceResponsibleInHeaderAndMessage = new("Den balanceansvarlige i beskeden stemmer ikke overenes med den balanceansvarlige i headeren / BalanceResponsibleParty in the message does not correspond with balance responsible in header", "E18"); + private static readonly ValidationError _invalidBusinessReason = new("En balanceansvarlig kan kun benytte forretningsårsag D03 eller D04 i forbindelse med en anmodning / A BalanceResponsibleParty can only use business reason D03 or D04 in connection with a request", "D11"); + + private readonly BalanceResponsibleValidationRule _sut = new(); + + [Fact] + public async Task Validate_WhenRequesterIsBalanceResponsibleAndBalanceResponsibleFieldIsValidGlnNumber_ReturnsNoValidationErrors() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenRequesterIsBalanceResponsibleAndBalanceResponsibleFieldIsValidEicNumber_ReturnsNoValidationErrors() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .WithRequestedForActorNumber(ValidEicNumber) + .WithBalanceResponsibleNumber(ValidEicNumber) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenRequesterIsBalanceResponsibleAndMissingBalanceResponsibleField_ReturnsExpectedValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .WithBalanceResponsibleNumber(null) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + using var assertionScope = new AssertionScope(); + var error = errors.Single(); + error.Message.Should().Be(_invalidBalanceResponsible.Message); + error.ErrorCode.Should().Be(_invalidBalanceResponsible.ErrorCode); + } + + [Fact] + public async Task Validate_WhenRequesterIsBalanceResponsibleAndBalanceResponsibleFieldNotEqualRequestedById_ReturnsExpectedValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .WithRequestedForActorNumber(ValidGlnNumber) + .WithBalanceResponsibleNumber(ValidEicNumber) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + var error = errors.Single(); + error.Message.Should().Be(_mismatchedBalanceResponsibleInHeaderAndMessage.Message); + error.ErrorCode.Should().Be(_mismatchedBalanceResponsibleInHeaderAndMessage.ErrorCode); + } + + [Fact] + public async Task Validate_WhenRequesterIsBalanceResponsibleAndInvalidBalanceResponsibleField_ReturnsExpectedValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .WithBalanceResponsibleNumber("invalid-format") + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + using var assertionScope = new AssertionScope(); + var error = errors.Single(); + error.Message.Should().Be(_invalidBalanceResponsible.Message); + error.ErrorCode.Should().Be(_invalidBalanceResponsible.ErrorCode); + } + + [Fact] + public async Task Validate_WhenRequesterIsNotBalanceResponsibleAndMissingBalanceResponsibleField_ReturnsNoValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.EnergySupplier) + .WithBalanceResponsibleNumber(null) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenBusinessReasonIsBalanceFixing_ReturnsNoValidationErrors() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .WithBusinessReason(BusinessReason.BalanceFixing.Name) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenBusinessReasonIsPreliminaryAggregation_ReturnsNoValidationErrors() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .WithBusinessReason(BusinessReason.PreliminaryAggregation.Name) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenRequestingInvalidBusinessReason_ReturnsExpectedValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .WithBusinessReason(BusinessReason.WholesaleFixing.Name) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + using var assertionScope = new AssertionScope(); + var error = errors.Single(); + error.Message.Should().Be(_invalidBusinessReason.Message); + error.ErrorCode.Should().Be(_invalidBusinessReason.ErrorCode); + } + + [Fact] + public async Task Validate_WhenRequestingInvalidBusinessReasonWithInvalidId_ReturnsExpectedValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .WithBalanceResponsibleNumber("invalid-format") + .WithBusinessReason(BusinessReason.Correction.Name) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + using var assertionScope = new AssertionScope(); + errors.Should().ContainSingle(error => error.ErrorCode == _invalidBalanceResponsible.ErrorCode); + errors.Should().ContainSingle(error => error.ErrorCode == _invalidBusinessReason.ErrorCode); + } +} diff --git a/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/EnergySupplierValidatorTest.cs b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/EnergySupplierValidatorTest.cs new file mode 100644 index 00000000..6c67907e --- /dev/null +++ b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/EnergySupplierValidatorTest.cs @@ -0,0 +1,181 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Diagnostics.CodeAnalysis; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; +using FluentAssertions; +using FluentAssertions.Execution; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Tests.Unit.Processes.BRS_026.V1.BusinessValidation.Rules; + +[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "Async suffix is not needed for test methods")] +public class EnergySupplierValidatorTest +{ + public const string ValidGlnNumber = "qwertyuiopasd"; // Must be 13 characters to be a valid GLN + private const string ValidEicNumber = "qwertyuiopasdfgh"; // Must be 16 characters to be a valid GLN + + private static readonly ValidationError _invalidEnergySupplier = new("Feltet EnergySupplier skal være udfyldt med et valid GLN/EIC nummer når en elleverandør anmoder om data / EnergySupplier must be submitted with a valid GLN/EIC number when an energy supplier requests data", "E16"); + private static readonly ValidationError _notEqualToRequestedBy = new("Elleverandør i besked stemmer ikke overenes med elleverandør i header / Energy supplier in message does not correspond with energy supplier in header", "E16"); + + private readonly EnergySupplierValidationRule _sut = new(); + + [Fact] + public async Task Validate_WhenEnergySupplierAndEnergySupplierIsValidGlnNumber_ReturnsNoValidationErrors() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.EnergySupplier) + .WithRequestedForActorNumber(ValidGlnNumber) + .WithEnergySupplierNumber(ValidGlnNumber) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenEnergySupplierAndEnergySupplierIsValidEicNumber_ReturnsNoValidationErrors() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.EnergySupplier) + .WithRequestedForActorNumber(ValidEicNumber) + .WithEnergySupplierNumber(ValidEicNumber) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenEnergySupplierAndMissingEnergySupplier_ReturnsExpectedValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.EnergySupplier) + .WithEnergySupplierNumber(null) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + using var assertionScope = new AssertionScope(); + var error = errors.First(); + error.Message.Should().Be(_invalidEnergySupplier.Message); + error.ErrorCode.Should().Be(_invalidEnergySupplier.ErrorCode); + } + + [Fact] + public async Task Validate_WhenEnergySupplierAndEnergySupplierNotEqualRequestedById_ReturnsExpectedValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.EnergySupplier) + .WithRequestedForActorNumber(ValidGlnNumber) + .WithEnergySupplierNumber(ValidEicNumber) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + using var assertionScope = new AssertionScope(); + var error = errors.Single(); + error.Message.Should().Be(_notEqualToRequestedBy.Message); + error.ErrorCode.Should().Be(_notEqualToRequestedBy.ErrorCode); + } + + [Fact] + public async Task Validate_WhenEnergySupplierAndInvalidFormatEnergySupplier_ReturnsExpectedValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.EnergySupplier) + .WithEnergySupplierNumber("invalid-format") + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + using var assertionScope = new AssertionScope(); + var error = errors.Single(); + error.Message.Should().Be(_invalidEnergySupplier.Message); + error.ErrorCode.Should().Be(_invalidEnergySupplier.ErrorCode); + } + + [Fact] + public async Task Validate_WhenNotEnergySupplierAndMissingEnergySupplier_ReturnsNoValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .WithEnergySupplierNumber(null) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenNotEnergySupplierAndInvalidEnergySupplierFormat_ReturnsNoValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .WithEnergySupplierNumber("invalid-format") + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_IsNotEnergySupplierAndEnergySupplierNotEqualRequestedById_ReturnsNoValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.BalanceResponsibleParty) + .WithRequestedForActorNumber(ValidGlnNumber) + .WithEnergySupplierNumber(ValidEicNumber) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } +} diff --git a/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/GridAreaValidatorTest.cs b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/GridAreaValidatorTest.cs new file mode 100644 index 00000000..e77f4352 --- /dev/null +++ b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/GridAreaValidatorTest.cs @@ -0,0 +1,116 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Diagnostics.CodeAnalysis; +using AutoFixture.Xunit2; +using Energinet.DataHub.Core.TestCommon.AutoFixture.Attributes; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation.GridAreaOwner; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; +using FluentAssertions; +using FluentAssertions.Execution; +using Moq; +using NodaTime; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Tests.Unit.Processes.BRS_026.V1.BusinessValidation.Rules; + +[SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "Async suffix is not needed for test methods")] +public class GridAreaValidatorTest +{ + private const string ValidGlnNumber = "qwertyuiopasd"; // Must be 13 characters to be a valid GLN + private static readonly ValidationError _missingGridAreaCode = new("Netområde er obligatorisk for rollen MDR / Grid area is mandatory for the role MDR.", "D64"); + private static readonly ValidationError _invalidGridArea = new("Ugyldig netområde / Invalid gridarea", "E86"); + + [Theory] + [InlineAutoMoqData] + public async Task Validate_WhenRequesterIsGridOwnerOfRequestedGridArea_ReturnsNoValidationErrors( + [Frozen] Mock gridAreaOwnerClient, + GridAreaValidationRule sut) + { + // Arrange + var gridAreaCode = "123"; + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.MeteredDataResponsible) + .WithGridArea(gridAreaCode) + .Build(); + + gridAreaOwnerClient.Setup(repo => repo.IsCurrentOwnerAsync( + gridAreaCode, + message.RequestedForActorNumber, + It.IsAny())) + .ReturnsAsync(true); + + // Act + var errors = await sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Theory] + [InlineAutoMoqData] + public async Task Validate_WhenRequesterIsNotGridOwnerOfRequestedGridArea_ReturnsExpectedValidationError( + [Frozen] Mock gridAreaOwnerClient, + GridAreaValidationRule sut) + { + // Arrange + var gridAreaCode = "123"; + + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.MeteredDataResponsible) + .WithGridArea(gridAreaCode) + .Build(); + + gridAreaOwnerClient.Setup(repo => repo.IsCurrentOwnerAsync( + gridAreaCode, + message.RequestedForActorNumber, + It.IsAny())) + .ReturnsAsync(false); + + // Act + var errors = await sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + using var assertionScope = new AssertionScope(); + var error = errors.Single(); + error.Message.Should().Be(_invalidGridArea.Message); + error.ErrorCode.Should().Be(_invalidGridArea.ErrorCode); + } + + [Theory] + [InlineAutoMoqData] + public async Task Validate_WhenGridAreaCodeIsEmpty_ReturnsExpectedValidationError( + GridAreaValidationRule sut) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.MeteredDataResponsible) + .WithGridArea(null) + .Build(); + + // Act + var errors = await sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + using var assertionScope = new AssertionScope(); + var error = errors.Single(); + error.Message.Should().Be(_missingGridAreaCode.Message); + error.ErrorCode.Should().Be(_missingGridAreaCode.ErrorCode); + } +} diff --git a/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/MeteringPointTypeValidatorTests.cs b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/MeteringPointTypeValidatorTests.cs new file mode 100644 index 00000000..c6fe4b43 --- /dev/null +++ b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/MeteringPointTypeValidatorTests.cs @@ -0,0 +1,72 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; +using FluentAssertions; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Tests.Unit.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class MeteringPointTypeValidatorTests +{ + private static readonly ValidationError _invalidMeteringPointType = + new( + "Metering point type skal være en af følgende: {PropertyName} eller undladt / Metering point type has one of the following: {PropertyName} or omitted", + "D18"); + + private readonly MeteringPointTypeValidationRule _sut = new(); + + [Theory] + [InlineData(nameof(MeteringPointType.Consumption))] + [InlineData(nameof(MeteringPointType.Production))] + [InlineData(nameof(MeteringPointType.Exchange))] + [InlineData(null)] + public async Task Validate_WhenMeteringPointIsValid_ReturnsExpectedNoValidationErrors(string? meteringPointType) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.MeteredDataResponsible) + .WithMeteringPointType(meteringPointType) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Theory] + [InlineData("Invalid")] + [InlineData("")] + [InlineData(" ")] + public async Task Validate_WhenMeteringPointTypeIsInvalid_ReturnsExpectedValidationError(string? meteringPointType) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.MeteredDataResponsible) + .WithMeteringPointType(meteringPointType) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + var error = errors.First(); + error.ErrorCode.Should().Be(_invalidMeteringPointType.ErrorCode); + error.Message.Should().Be(_invalidMeteringPointType.WithPropertyName("E17, E18, E20").Message); + } +} diff --git a/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/PeriodValidatorTests.cs b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/PeriodValidatorTests.cs new file mode 100644 index 00000000..d47d4f38 --- /dev/null +++ b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/PeriodValidatorTests.cs @@ -0,0 +1,244 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation.Helpers; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; +using FluentAssertions; +using NodaTime; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Tests.Unit.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class PeriodValidatorTests +{ + private static readonly ValidationError _invalidDateFormat = new("Forkert dato format for {PropertyName}, skal være YYYY-MM-DDT22:00:00Z eller YYYY-MM-DDT23:00:00Z / Wrong date format for {PropertyName}, must be YYYY-MM-DDT22:00:00Z or YYYY-MM-DDT23:00:00Z", "D66"); + private static readonly ValidationError _invalidWinterMidnightFormat = new("Forkert dato format for {PropertyName}, skal være YYYY-MM-DDT23:00:00Z / Wrong date format for {PropertyName}, must be YYYY-MM-DDT23:00:00Z", "D66"); + private static readonly ValidationError _invalidSummerMidnightFormat = new("Forkert dato format for {PropertyName}, skal være YYYY-MM-DDT22:00:00Z / Wrong date format for {PropertyName}, must be YYYY-MM-DDT22:00:00Z", "D66"); + private static readonly ValidationError _startDateMustBeLessThen3Years = new("Dato må max være 3 år og 6 måneder tilbage i tid / Can maximum be 3 years and 6 months back in time", "E17"); + private static readonly ValidationError _periodIsGreaterThenAllowedPeriodSize = new("Dato må kun være for 1 måned af gangen / Can maximum be for a 1 month period", "E17"); + private static readonly ValidationError _missingStartOrAndEndDate = new("Start og slut dato skal udfyldes / Start and end date must be present in request", "E50"); + + private readonly PeriodValidationRule _sut = new( + new PeriodValidationHelper( + DateTimeZoneProviders.Tzdb.GetZoneOrNull("Europe/Copenhagen")!, + SystemClock.Instance)); + + [Fact] + public async Task Validate_WhenRequestIsValid_ReturnsNoValidationErrors() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.MeteredDataResponsible) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenEndDateIsUnspecified_ReturnsExpectedValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + ActorRole.MeteredDataResponsible) + .WithPeriodEnd(null) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + var error = errors.First(); + error.Message.Should().Be(_missingStartOrAndEndDate.Message); + error.ErrorCode.Should().Be(_missingStartOrAndEndDate.ErrorCode); + } + + [Fact] + public async Task Validate_WhenStartHourIsWrong_ReturnsExpectedValidationError() + { + // Arrange + var now = SystemClock.Instance.GetCurrentInstant(); + var notWinterTimeMidnight = Instant.FromUtc(now.InUtc().Year, 1, 1, 22, 0, 0); + var winterTimeMidnight = Instant.FromUtc(now.InUtc().Year, 1, 2, 23, 0, 0); + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.MeteredDataResponsible) + .WithPeriod( + periodStart: notWinterTimeMidnight, + periodEnd: winterTimeMidnight) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + var error = errors.First(); + error.ErrorCode.Should().Be(_invalidWinterMidnightFormat.ErrorCode); + error.Message.Should().Be(_invalidWinterMidnightFormat.WithPropertyName("Start date").Message); + } + + [Fact] + public async Task Validate_WhenEndHourIsWrong_ReturnsExpectedValidationError() + { + // Arrange + var now = SystemClock.Instance.GetCurrentInstant(); + var notSummerTimeMidnight = Instant.FromUtc(now.InUtc().Year, 7, 1, 23, 0, 0); + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.MeteredDataResponsible) + .WithPeriod( + periodStart: Instant.FromUtc(now.InUtc().Year, 7, 2, 22, 0, 0), + periodEnd: notSummerTimeMidnight) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + var error = errors.First(); + error.ErrorCode.Should().Be(_invalidSummerMidnightFormat.ErrorCode); + error.Message.Should().Be(_invalidSummerMidnightFormat.WithPropertyName("End date").Message); + } + + [Fact] + public async Task Validate_WhenStartIsUnspecified_ReturnsExpectedValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.MeteredDataResponsible) + .WithPeriod(periodStart: string.Empty, periodEnd: null) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + var error = errors.First(); + error.ErrorCode.Should().Be(_missingStartOrAndEndDate.ErrorCode); + error.Message.Should().Be(_missingStartOrAndEndDate.Message); + } + + [Fact] + public async Task Validate_WhenStartAndEndDateAreInvalid_ReturnsExpectedValidationErrors() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.MeteredDataResponsible) + .WithPeriod( + periodStart: "invalid-start-date", + periodEnd: "invalid-end-date") + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Count.Should().Be(2); + errors.Should().Contain(error => error.Message.Contains(_invalidDateFormat.WithPropertyName("Start date").Message) + && error.ErrorCode.Equals(_invalidDateFormat.ErrorCode)); + errors.Should().Contain(error => error.Message.Contains(_invalidDateFormat.WithPropertyName("End date").Message) + && error.ErrorCode.Equals(_invalidDateFormat.ErrorCode)); + } + + [Fact] + public async Task Validate_WhenPeriodSizeIsGreaterThenAllowed_ReturnsExpectedValidationError() + { + // Arrange + var now = SystemClock.Instance.GetCurrentInstant(); + var winterTimeMidnight = Instant.FromUtc(now.InUtc().Year, 1, 1, 23, 0, 0); + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.MeteredDataResponsible) + .WithPeriod( + winterTimeMidnight, + winterTimeMidnight.Plus(Duration.FromDays(32))) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + var error = errors.First(); + error.ErrorCode.Should().Be(_periodIsGreaterThenAllowedPeriodSize.ErrorCode); + error.Message.Should().Be(_periodIsGreaterThenAllowedPeriodSize.Message); + } + + [Fact] + public async Task Validate_WhenPeriodIsOlderThenAllowed_ReturnsExpectedValidationError() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.MeteredDataResponsible) + .WithPeriod( + periodStart: Instant.FromUtc(2018, 1, 1, 23, 0, 0), + periodEnd: Instant.FromUtc(2018, 1, 1, 23, 0, 0)) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + var error = errors.First(); + error.ErrorCode.Should().Be(_startDateMustBeLessThen3Years.ErrorCode); + error.Message.Should().Be(_startDateMustBeLessThen3Years.Message); + } + + [Fact] + public async Task Validate_WhenPeriodOverlapSummerDaylightSavingTime_ReturnsNoValidationErrors() + { + // Arrange + var winterTime = Instant.FromUtc(2023, 2, 26, 23, 0, 0); + var summerTime = Instant.FromUtc(2023, 3, 26, 22, 0, 0); + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.MeteredDataResponsible) + .WithPeriod( + periodStart: winterTime, + periodEnd: summerTime) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenPeriodOverlapWinterDaylightSavingTime_ReturnsNoValidationErrors() + { + // Arrange + var now = SystemClock.Instance.GetCurrentInstant(); + var summerTime = Instant.FromUtc(now.InUtc().Year, 9, 29, 22, 0, 0); + var winterTime = Instant.FromUtc(now.InUtc().Year, 10, 29, 23, 0, 0); + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.MeteredDataResponsible) + .WithPeriod( + summerTime, + winterTime) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } +} diff --git a/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/RequestedByActorRoleValidationRuleTest.cs b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/RequestedByActorRoleValidationRuleTest.cs new file mode 100644 index 00000000..89eb2a36 --- /dev/null +++ b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/RequestedByActorRoleValidationRuleTest.cs @@ -0,0 +1,87 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; +using FluentAssertions; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Tests.Unit.Processes.BRS_026.V1.BusinessValidation.Rules; + +public sealed class RequestedByActorRoleValidationRuleTest +{ + [Theory] + [InlineData(nameof(ActorRole.MeteredDataResponsible))] + [InlineData(nameof(ActorRole.EnergySupplier))] + [InlineData(nameof(ActorRole.BalanceResponsibleParty))] + public async Task ValidateAsync_WhenRequestingWithValidActorRole_ReturnsEmptyErrorListAsync(string actorRole) + { + // Arrange + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.FromName(actorRole)) + .Build(); + + var rule = new RequestedByActorRoleValidationRule(); + + // Act + var result = await rule.ValidateAsync(request); + + // Assert + result.Should().BeEmpty(); + } + + [Theory] + [InlineData("DLG")] + [InlineData("TSO")] + [InlineData("FOO")] + public async Task ValidateAsync_WhenRequestingWithUnexpectedActorRole_ReturnsEmptyErrorListAsync(string actorRole) + { + // Arrange + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithRequestedForActorRole(actorRole) + .Build(); + + var rule = new RequestedByActorRoleValidationRule(); + + // Act + var result = await rule.ValidateAsync(request); + + // Assert + result.Should().BeEmpty(); + } + + [Fact] + public async Task ValidateAsync_WhenRequestingWithDdmActorRole_ReturnsDdmShouldRequestAsMdrErrorAsync() + { + // Arrange + var request = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.GridAccessProvider) + .Build(); + var rule = new RequestedByActorRoleValidationRule(); + + // Act + var result = await rule.ValidateAsync(request); + + // Assert + result.Should().ContainSingle(); + result + .Single() + .Message + .Should() + .Be( + "Rollen skal være MDR når der anmodes om beregnede energitidsserier / Role must be MDR when requesting aggregated measure data"); + + result.Single().ErrorCode.Should().Be("D02"); + } +} diff --git a/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementMethodValidatorTest.cs b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementMethodValidatorTest.cs new file mode 100644 index 00000000..f0903492 --- /dev/null +++ b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementMethodValidatorTest.cs @@ -0,0 +1,123 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; +using FluentAssertions; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Tests.Unit.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class SettlementMethodValidatorTest +{ + private static readonly ValidationError _invalidSettlementMethod = new("SettlementMethod kan kun benyttes i kombination med E17 og skal være enten D01 og E02 / SettlementMethod can only be used in combination with E17 and must be either D01 or E02", "D15"); + + private readonly SettlementMethodValidationRule _sut = new(); + + [Theory] + [InlineData(nameof(SettlementMethod.Flex))] + [InlineData(nameof(SettlementMethod.NonProfiled))] + public async Task Validate_WhenConsumptionAndSettlementMethodIsValid_ReturnsNoValidationErrorsAsync(string settlementMethod) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithMeteringPointType(MeteringPointType.Consumption.Name) + .WithSettlementMethod(settlementMethod) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Theory] + [InlineData(nameof(MeteringPointType.Production))] + [InlineData(nameof(MeteringPointType.Exchange))] + [InlineData("not-consumption")] + public async Task Validate_WhenMeteringPointTypeIsGivenAndSettlementMethodIsNull_ReturnsNoValidationErrorsAsync(string meteringPointType) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithMeteringPointType(meteringPointType) + .WithSettlementMethod(null) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenConsumptionAndSettlementMethodIsInvalid_ReturnsExpectedValidationErrorAsync() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithMeteringPointType(MeteringPointType.Consumption.Name) + .WithSettlementMethod("invalid-settlement-method") + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + var error = errors.First(); + error.ErrorCode.Should().Be(_invalidSettlementMethod.ErrorCode); + error.Message.Should().Be(_invalidSettlementMethod.Message); + } + + [Theory] + [InlineData(nameof(MeteringPointType.Production), nameof(SettlementMethod.Flex))] + [InlineData(nameof(MeteringPointType.Production), nameof(SettlementMethod.NonProfiled))] + [InlineData(nameof(MeteringPointType.Production), "invalid-settlement-method")] + [InlineData(nameof(MeteringPointType.Exchange), nameof(SettlementMethod.Flex))] + [InlineData(nameof(MeteringPointType.Exchange), nameof(SettlementMethod.NonProfiled))] + [InlineData(nameof(MeteringPointType.Exchange), "invalid-settlement-method")] + [InlineData("not-consumption-metering-point", nameof(SettlementMethod.Flex))] + [InlineData("not-consumption-metering-point", nameof(SettlementMethod.NonProfiled))] + [InlineData("not-consumption-metering-point", "invalid-settlement-method")] + [InlineData("", nameof(SettlementMethod.Flex))] + [InlineData("", nameof(SettlementMethod.NonProfiled))] + [InlineData("", "invalid-settlement-method")] + [InlineData(null, nameof(SettlementMethod.Flex))] + [InlineData(null, nameof(SettlementMethod.NonProfiled))] + [InlineData(null, "invalid-settlement-method")] + public async Task Validate_WhenNotConsumptionAndSettlementMethodIsGiven_ReturnsExpectedValidationErrorAsync(string? meteringPointType, string settlementMethod) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithMeteringPointType(meteringPointType) + .WithSettlementMethod(settlementMethod) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + var error = errors.First(); + error.ErrorCode.Should().Be(_invalidSettlementMethod.ErrorCode); + error.Message.Should().Be(_invalidSettlementMethod.Message); + } +} diff --git a/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementVersionValidatorTest.cs b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementVersionValidatorTest.cs new file mode 100644 index 00000000..43c266ee --- /dev/null +++ b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementVersionValidatorTest.cs @@ -0,0 +1,126 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; +using FluentAssertions; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Tests.Unit.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class SettlementVersionValidatorTest +{ + private static readonly ValidationError _expectedInvalidSettlementMethodError = new("SettlementSeriesVersion kan kun benyttes i kombination med D32 og skal være enten D01, D02 eller D03 / SettlementSeriesVersion can only be used in combination with D32 and must be either D01, D02 or D03", "E86"); + + private readonly SettlementVersionValidationRule _sut = new(); + + [Theory] + [InlineData("invalid-settlement-series-version")] + [InlineData("D04")] + [InlineData("")] + public async Task Validate_WhenCorrectionAndInvalidSeriesVersion_ReturnsValidationErrorsAsync(string invalidSettlementVersion) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithBusinessReason(BusinessReason.Correction.Name) + .WithSettlementVersion(invalidSettlementVersion) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle() + .Which.Should().Be(_expectedInvalidSettlementMethodError); + } + + [Theory] + [InlineData("invalid-settlement-series-version")] + [InlineData("D04")] + [InlineData("")] + [InlineData(nameof(SettlementVersion.FirstCorrection))] + [InlineData(nameof(SettlementVersion.SecondCorrection))] + [InlineData(nameof(SettlementVersion.ThirdCorrection))] + public async Task Validate_WhenNotCorrectionAndSettlementVersionExists_ReturnsValidationErrorsAsync(string settlementVersion) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithBusinessReason(BusinessReason.WholesaleFixing.Name) + .WithSettlementVersion(settlementVersion) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle() + .Which.Should().Be(_expectedInvalidSettlementMethodError); + } + + [Theory] + [InlineData(nameof(SettlementVersion.FirstCorrection))] + [InlineData(nameof(SettlementVersion.SecondCorrection))] + [InlineData(nameof(SettlementVersion.ThirdCorrection))] + public async Task Validate_WhenCorrectionAndValidSettlementVersion_ReturnsNoValidationErrorsAsync(string validSettlementVersion) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithBusinessReason(BusinessReason.Correction.Name) + .WithSettlementVersion(validSettlementVersion) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Fact] + public async Task Validate_WhenCorrectionAndNoSettlementVersion_ReturnsNoValidationErrorsAsync() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithBusinessReason(BusinessReason.Correction.Name) + .WithSettlementVersion(null) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty("When Settlement version is empty the latest correction result is requested"); + } + + [Fact] + public async Task Validate_WhenNotCorrectionAndNoSettlementVersion_ReturnsNoValidationErrorsAsync() + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithBusinessReason(BusinessReason.WholesaleFixing.Name) + .WithSettlementVersion(null) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } +} diff --git a/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/TimeSeriesTypeValidatorTests.cs b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/TimeSeriesTypeValidatorTests.cs new file mode 100644 index 00000000..716ace2d --- /dev/null +++ b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/BusinessValidation/Rules/TimeSeriesTypeValidatorTests.cs @@ -0,0 +1,135 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; +using FluentAssertions; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Tests.Unit.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class TimeSeriesTypeValidatorTests +{ + private static readonly ValidationError _invalidTimeSeriesTypeForActor = new("Den forespurgte tidsserie type kan ikke forespørges som en {PropertyName} / The requested time series type can not be requested as a {PropertyName}", "D11"); + + private readonly TimeSeriesTypeValidationRule _sut = new(); + + [Theory] + [InlineData(nameof(MeteringPointType.Production), null)] + [InlineData(nameof(MeteringPointType.Exchange), null)] + [InlineData(nameof(MeteringPointType.Consumption), null)] + [InlineData(nameof(MeteringPointType.Consumption), nameof(SettlementMethod.NonProfiled))] + [InlineData(nameof(MeteringPointType.Consumption), nameof(SettlementMethod.Flex))] + public async Task Validate_AsMeteredDataResponsible_ReturnsNoValidationErrors(string meteringPointType, string? settlementMethod) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.MeteredDataResponsible) + .WithMeteringPointType(meteringPointType) + .WithSettlementMethod(settlementMethod) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Theory] + [InlineData(nameof(MeteringPointType.Production), null)] + [InlineData(nameof(MeteringPointType.Consumption), nameof(SettlementMethod.NonProfiled))] + [InlineData(nameof(MeteringPointType.Consumption), nameof(SettlementMethod.Flex))] + public async Task Validate_AsEnergySupplier_ReturnsNoValidationErrors(string meteringPointType, string? settlementMethod) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithMeteringPointType(meteringPointType) + .WithSettlementMethod(settlementMethod) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Theory] + [InlineData(nameof(MeteringPointType.Production), null)] + [InlineData(nameof(MeteringPointType.Consumption), nameof(SettlementMethod.NonProfiled))] + [InlineData(nameof(MeteringPointType.Consumption), nameof(SettlementMethod.Flex))] + public async Task Validate_AsBalanceResponsible_ReturnsNoValidationErrors(string meteringPointType, string? settlementMethod) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.BalanceResponsibleParty) + .WithMeteringPointType(meteringPointType) + .WithSettlementMethod(settlementMethod) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().BeEmpty(); + } + + [Theory] + [InlineData(nameof(MeteringPointType.Exchange))] + [InlineData(nameof(MeteringPointType.Consumption))] + public async Task Validate_AsEnergySupplierAndNoSettlementMethod_ReturnsExceptedValidationErrors(string meteringPointType) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.EnergySupplier) + .WithMeteringPointType(meteringPointType) + .WithSettlementMethod(null) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + var error = errors.First(); + error.Message.Should().Be(_invalidTimeSeriesTypeForActor.WithPropertyName(ActorRole.EnergySupplier.Name).Message); + error.ErrorCode.Should().Be(_invalidTimeSeriesTypeForActor.ErrorCode); + } + + [Theory] + [InlineData(nameof(MeteringPointType.Exchange))] + [InlineData(nameof(MeteringPointType.Consumption))] + public async Task Validate_AsBalanceResponsibleAndNoSettlementMethod_ValidationErrors(string meteringPointType) + { + // Arrange + var message = new RequestCalculatedEnergyTimeSeriesInputV1Builder( + forActorRole: ActorRole.BalanceResponsibleParty) + .WithMeteringPointType(meteringPointType) + .WithSettlementMethod(null) + .Build(); + + // Act + var errors = await _sut.ValidateAsync(message); + + // Assert + errors.Should().ContainSingle(); + + var error = errors.First(); + error.Message.Should().Be(_invalidTimeSeriesTypeForActor.WithPropertyName(ActorRole.BalanceResponsibleParty.Name).Message); + error.ErrorCode.Should().Be(_invalidTimeSeriesTypeForActor.ErrorCode); + } +} diff --git a/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/RequestCalculatedEnergyTimeSeriesInputV1Builder.cs b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/RequestCalculatedEnergyTimeSeriesInputV1Builder.cs new file mode 100644 index 00000000..f153f657 --- /dev/null +++ b/source/ProcessManager.Orchestrations.Tests/Unit/Processes/BRS_026/V1/RequestCalculatedEnergyTimeSeriesInputV1Builder.cs @@ -0,0 +1,158 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; +using NodaTime; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Tests.Unit.Processes.BRS_026.V1; + +public class RequestCalculatedEnergyTimeSeriesInputV1Builder +{ + private const string ValidGlnNumber = "1111111111111"; + + private string _requestedForActorNumber; + private string _requestedForActorRole; + private string _businessReason; + private string _periodStart; + private string? _periodEnd; + private string? _energySupplierNumber; + private string? _balanceResponsibleNumber; + private IReadOnlyCollection _gridAreas; + private string? _meteringPointType; + private string? _settlementMethod; + private string? _settlementVersion; + + /// + /// Creates a new RequestCalculatedEnergyTimeSeriesInputV1Builder with default values + /// + public RequestCalculatedEnergyTimeSeriesInputV1Builder(ActorRole forActorRole) + { + _requestedForActorNumber = ValidGlnNumber; + _requestedForActorRole = forActorRole.Name; + _businessReason = BusinessReason.BalanceFixing.Name; + + // Period from 1/2/2024 to 5/2/2024 + _periodStart = "2024-01-31T23:00:00Z"; + _periodEnd = "2024-02-04T23:00:00Z"; + + _gridAreas = []; + + if (forActorRole == ActorRole.EnergySupplier) + _energySupplierNumber = _requestedForActorNumber; + else if (forActorRole == ActorRole.BalanceResponsibleParty) + _balanceResponsibleNumber = _requestedForActorNumber; + else if (forActorRole == ActorRole.GridAccessProvider) + _gridAreas = ["804"]; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithRequestedForActorNumber(string requestedForActorNumber) + { + _requestedForActorNumber = requestedForActorNumber; + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithRequestedForActorRole(string requestedForActorRole) + { + _requestedForActorRole = requestedForActorRole; + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithBusinessReason(string businessReason) + { + _businessReason = businessReason; + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithPeriod(Instant periodStart, Instant? periodEnd) + { + _periodStart = periodStart.ToString(); + _periodEnd = periodEnd?.ToString(); + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithPeriod(string periodStart, string? periodEnd) + { + _periodStart = periodStart; + _periodEnd = periodEnd; + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithPeriodEnd(Instant? periodEnd) + { + _periodEnd = periodEnd?.ToString(); + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithEnergySupplierNumber(string? energySupplierNumber) + { + _energySupplierNumber = energySupplierNumber; + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithBalanceResponsibleNumber(string? balanceResponsibleNumber) + { + _balanceResponsibleNumber = balanceResponsibleNumber; + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithGridArea(string? gridArea) + { + _gridAreas = !string.IsNullOrEmpty(gridArea) + ? [gridArea] + : []; + + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithGridAreas(IReadOnlyCollection gridAreas) + { + _gridAreas = gridAreas; + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithMeteringPointType(string? meteringPointType) + { + _meteringPointType = meteringPointType; + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithSettlementMethod(string? settlementMethod) + { + _settlementMethod = settlementMethod; + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1Builder WithSettlementVersion(string? settlementVersion) + { + _settlementVersion = settlementVersion; + return this; + } + + public RequestCalculatedEnergyTimeSeriesInputV1 Build() + { + return new RequestCalculatedEnergyTimeSeriesInputV1( + _requestedForActorNumber, + _requestedForActorRole, + _businessReason, + _periodStart, + _periodEnd, + _energySupplierNumber, + _balanceResponsibleNumber, + _gridAreas, + _meteringPointType, + _settlementMethod, + _settlementVersion); + } +} diff --git a/source/ProcessManager.Orchestrations/ProcessManager.Orchestrations.csproj b/source/ProcessManager.Orchestrations/ProcessManager.Orchestrations.csproj index 438837ec..23072dfe 100644 --- a/source/ProcessManager.Orchestrations/ProcessManager.Orchestrations.csproj +++ b/source/ProcessManager.Orchestrations/ProcessManager.Orchestrations.csproj @@ -50,6 +50,7 @@ + diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/EnqueueActorMessagesActivity_Brs_026_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/EnqueueActorMessagesActivity_Brs_026_V1.cs index 6023f057..df75ed10 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/EnqueueActorMessagesActivity_Brs_026_V1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/EnqueueActorMessagesActivity_Brs_026_V1.cs @@ -17,7 +17,6 @@ using Energinet.DataHub.ProcessManager.Core.Domain.OrchestrationInstance; using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; using Microsoft.Azure.Functions.Worker; -using NodaTime; namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Activities; @@ -25,11 +24,9 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.A /// Enqueue messages in EDI (and set step to running) /// internal class EnqueueActorMessagesActivity_Brs_026_V1( - IClock clock, IOrchestrationInstanceProgressRepository progressRepository, IEnqueueActorMessagesClient enqueueActorMessagesClient) { - private readonly IClock _clock = clock; private readonly IOrchestrationInstanceProgressRepository _progressRepository = progressRepository; private readonly IEnqueueActorMessagesClient _enqueueActorMessagesClient = enqueueActorMessagesClient; @@ -41,10 +38,6 @@ public async Task Run( .GetAsync(input.InstanceId) .ConfigureAwait(false); - orchestrationInstance.TransitionStepToRunning( - Orchestration_Brs_026_V1.EnqueueActorMessagesStepSequence, - _clock); - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); await EnqueueActorMessagesAsync(orchestrationInstance.Lifecycle.CreatedBy.Value, input).ConfigureAwait(false); } diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/EnqueueRejectMessageActivity_Brs_026_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/EnqueueRejectMessageActivity_Brs_026_V1.cs index 9c881ed1..a079ce58 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/EnqueueRejectMessageActivity_Brs_026_V1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/EnqueueRejectMessageActivity_Brs_026_V1.cs @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Energinet.DataHub.ProcessManager.Abstractions.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; using Energinet.DataHub.ProcessManager.Components.EnqueueActorMessages; using Energinet.DataHub.ProcessManager.Core.Application.Orchestration; using Energinet.DataHub.ProcessManager.Core.Domain.OrchestrationInstance; using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; using Microsoft.Azure.Functions.Worker; -using NodaTime; namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Activities; @@ -25,11 +26,9 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.A /// Enqueue reject message in EDI (and set step to running) /// internal class EnqueueRejectMessageActivity_Brs_026_V1( - IClock clock, IOrchestrationInstanceProgressRepository progressRepository, IEnqueueActorMessagesClient enqueueActorMessagesClient) { - private readonly IClock _clock = clock; private readonly IOrchestrationInstanceProgressRepository _progressRepository = progressRepository; private readonly IEnqueueActorMessagesClient _enqueueActorMessagesClient = enqueueActorMessagesClient; @@ -41,27 +40,29 @@ public async Task Run( .GetAsync(input.InstanceId) .ConfigureAwait(false); - orchestrationInstance.TransitionStepToRunning( - Orchestration_Brs_026_V1.EnqueueActorMessagesStepSequence, - _clock); - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); - await EnqueueRejectMessageAsync(orchestrationInstance.Lifecycle.CreatedBy.Value, input).ConfigureAwait(false); } private Task EnqueueRejectMessageAsync(OperatingIdentity orchestrationCreatedBy, ActivityInput input) { // TODO: Set correct data when async validation is implemented + var rejectedMessage = new RequestCalculatedEnergyTimeSeriesRejectedV1( + ValidationErrors: input.ValidationErrors + .Select(e => new ValidationErrorDto( + Message: e.Message, + ErrorCode: e.ErrorCode)) + .ToList()); + return _enqueueActorMessagesClient.EnqueueAsync( Orchestration_Brs_026_V1.UniqueName, input.InstanceId.Value, orchestrationCreatedBy.ToDto(), input.IdempotencyKey, - input.RejectedData); + rejectedMessage); } public record ActivityInput( OrchestrationInstanceId InstanceId, - RequestCalculatedEnergyTimeSeriesRejectedV1 RejectedData, + IReadOnlyCollection ValidationErrors, Guid IdempotencyKey); } diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/StartOrchestrationActivity_Brs_026_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/GetOrchestrationInstanceContextActivity_Brs_026_V1.cs similarity index 58% rename from source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/StartOrchestrationActivity_Brs_026_V1.cs rename to source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/GetOrchestrationInstanceContextActivity_Brs_026_V1.cs index 297c3937..b82a61ce 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/StartOrchestrationActivity_Brs_026_V1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/GetOrchestrationInstanceContextActivity_Brs_026_V1.cs @@ -12,42 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManager.Core.Application.Orchestration; using Energinet.DataHub.ProcessManager.Core.Domain.OrchestrationInstance; using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Models; using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Options; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Options; -using NodaTime; namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Activities; /// -/// Set the orchestration instance lifecycle to running +/// Get the for the orchestration instance. /// -internal class StartOrchestrationActivity_Brs_026_V1( - IClock clock, - IOrchestrationInstanceProgressRepository progressRepository, +internal class GetOrchestrationInstanceContextActivity_Brs_026_V1( IOptions options) { - private readonly IClock _clock = clock; - private readonly IOrchestrationInstanceProgressRepository _progressRepository = progressRepository; private readonly OrchestrationOptions_Brs_026_V1 _options = options.Value; - [Function(nameof(StartOrchestrationActivity_Brs_026_V1))] - public async Task Run( + [Function(nameof(GetOrchestrationInstanceContextActivity_Brs_026_V1))] + public Task Run( [ActivityTrigger] ActivityInput input) { - var orchestrationInstance = await _progressRepository - .GetAsync(input.InstanceId) - .ConfigureAwait(false); - - orchestrationInstance.Lifecycle.TransitionToRunning(_clock); - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); - - return new OrchestrationExecutionContext( - orchestrationInstance.Id, - _options); + return Task.FromResult(new OrchestrationInstanceContext( + input.InstanceId, + _options)); } public record ActivityInput( diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/PerformAsyncValidationActivity_Brs_026_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/PerformAsyncValidationActivity_Brs_026_V1.cs deleted file mode 100644 index 031e3d1a..00000000 --- a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/PerformAsyncValidationActivity_Brs_026_V1.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2020 Energinet DataHub A/S -// -// Licensed under the Apache License, Version 2.0 (the "License2"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Energinet.DataHub.ProcessManager.Core.Application.Orchestration; -using Energinet.DataHub.ProcessManager.Core.Domain.OrchestrationInstance; -using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; -using Microsoft.Azure.Functions.Worker; -using NodaTime; - -namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Activities; - -/// -/// Perform async validation (and set step to running) -/// -internal class PerformAsyncValidationActivity_Brs_026_V1( - IClock clock, - IOrchestrationInstanceProgressRepository progressRepository) -{ - private readonly IClock _clock = clock; - private readonly IOrchestrationInstanceProgressRepository _progressRepository = progressRepository; - - [Function(nameof(PerformAsyncValidationActivity_Brs_026_V1))] - public async Task Run( - [ActivityTrigger] ActivityInput input) - { - var orchestrationInstance = await _progressRepository - .GetAsync(input.InstanceId) - .ConfigureAwait(false); - - orchestrationInstance.TransitionStepToRunning( - Orchestration_Brs_026_V1.AsyncValidationStepSequence, - _clock); - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); - - var isValid = await PerformAsyncValidationAsync(input.RequestInput).ConfigureAwait(false); - - return isValid; - } - - private async Task PerformAsyncValidationAsync(RequestCalculatedEnergyTimeSeriesInputV1 requestInput) - { - // TODO: Perform async validation instead of delay - await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); - return new ActivityOutput( - IsValid: true, - ValidationError: null); - } - - public record ActivityInput( - OrchestrationInstanceId InstanceId, - RequestCalculatedEnergyTimeSeriesInputV1 RequestInput); - - public record ActivityOutput( - bool IsValid, - RequestCalculatedEnergyTimeSeriesRejectedV1? ValidationError); -} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/PerformBusinessValidationActivity_Brs_026_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/PerformBusinessValidationActivity_Brs_026_V1.cs new file mode 100644 index 00000000..646102cd --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/PerformBusinessValidationActivity_Brs_026_V1.cs @@ -0,0 +1,65 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Text.Json; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Core.Application.Orchestration; +using Energinet.DataHub.ProcessManager.Core.Domain.OrchestrationInstance; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation; +using Microsoft.Azure.Functions.Worker; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Activities; + +/// +/// Perform async validation (and set step to running) +/// +internal class PerformBusinessValidationActivity_Brs_026_V1( + IOrchestrationInstanceProgressRepository repository, + BusinessValidator validator) +{ + private readonly IOrchestrationInstanceProgressRepository _repository = repository; + private readonly BusinessValidator _validator = validator; + + [Function(nameof(PerformBusinessValidationActivity_Brs_026_V1))] + public async Task Run( + [ActivityTrigger] ActivityInput input) + { + var validationErrors = await _validator.ValidateAsync(input.RequestInput) + .ConfigureAwait(false); + + var activityOutput = new ActivityOutput( + IsValid: validationErrors.Count == 0, + ValidationErrors: validationErrors); + + if (validationErrors.Count > 0) + { + var orchestrationInstance = await _repository.GetAsync(input.OrchestrationInstanceId).ConfigureAwait(false); + var step = orchestrationInstance.GetStep(input.StepSqeuence); + step.SetCustomState(JsonSerializer.Serialize(activityOutput)); + await _repository.UnitOfWork.CommitAsync().ConfigureAwait(false); + } + + return activityOutput; + } + + public record ActivityInput( + OrchestrationInstanceId OrchestrationInstanceId, + int StepSqeuence, + RequestCalculatedEnergyTimeSeriesInputV1 RequestInput); + + public record ActivityOutput( + bool IsValid, + IReadOnlyCollection ValidationErrors); +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/TerminateOrchestrationActivity_Brs_026_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/TerminateOrchestrationActivity_Brs_026_V1.cs deleted file mode 100644 index 0daf452c..00000000 --- a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/TerminateOrchestrationActivity_Brs_026_V1.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2020 Energinet DataHub A/S -// -// Licensed under the Apache License, Version 2.0 (the "License2"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Energinet.DataHub.ProcessManager.Core.Application.Orchestration; -using Energinet.DataHub.ProcessManager.Core.Domain.OrchestrationInstance; -using Microsoft.Azure.Functions.Worker; -using NodaTime; - -namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Activities; - -/// -/// Set the orchestration instance lifecycle to terminated -/// -internal class TerminateOrchestrationActivity_Brs_026_V1( - IClock clock, - IOrchestrationInstanceProgressRepository progressRepository) -{ - private readonly IClock _clock = clock; - private readonly IOrchestrationInstanceProgressRepository _progressRepository = progressRepository; - - [Function(nameof(TerminateOrchestrationActivity_Brs_026_V1))] - public async Task Run( - [ActivityTrigger] ActivityInput input) - { - var orchestrationInstance = await _progressRepository - .GetAsync(input.InstanceId) - .ConfigureAwait(false); - - switch (input.TerminationState) - { - case OrchestrationInstanceTerminationState.Succeeded: - orchestrationInstance.Lifecycle.TransitionToSucceeded(_clock); - break; - - case OrchestrationInstanceTerminationState.Failed: - orchestrationInstance.Lifecycle.TransitionToFailed(_clock); - break; - - case OrchestrationInstanceTerminationState.UserCanceled: - default: - throw new ArgumentOutOfRangeException(nameof(input.TerminationState), input.TerminationState, "Invalid termination state"); - } - - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); - } - - public record ActivityInput( - OrchestrationInstanceId InstanceId, - OrchestrationInstanceTerminationState TerminationState); -} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/TerminateStepActivity_Brs_026_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/TerminateStepActivity_Brs_026_V1.cs deleted file mode 100644 index e1c2248e..00000000 --- a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Activities/TerminateStepActivity_Brs_026_V1.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 Energinet DataHub A/S -// -// Licensed under the Apache License, Version 2.0 (the "License2"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Energinet.DataHub.ProcessManager.Core.Application.Orchestration; -using Energinet.DataHub.ProcessManager.Core.Domain.OrchestrationInstance; -using Microsoft.Azure.Functions.Worker; -using NodaTime; - -namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Activities; - -/// -/// Set the orchestration instance step lifecycle to terminated -/// -internal class TerminateStepActivity_Brs_026_V1( - IClock clock, - IOrchestrationInstanceProgressRepository progressRepository) -{ - private readonly IClock _clock = clock; - private readonly IOrchestrationInstanceProgressRepository _progressRepository = progressRepository; - - [Function(nameof(TerminateStepActivity_Brs_026_V1))] - public async Task Run( - [ActivityTrigger] ActivityInput input) - { - var orchestrationInstance = await _progressRepository - .GetAsync(input.InstanceId) - .ConfigureAwait(false); - - orchestrationInstance.TransitionStepToTerminated( - input.StepSequence, - input.TerminationState, - _clock); - - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); - } - - public record ActivityInput( - OrchestrationInstanceId InstanceId, - int StepSequence, - OrchestrationStepTerminationState TerminationState); -} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Helpers/EnergySupplierIsOnlyAllowedToRequestOwnDataHelper.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Helpers/EnergySupplierIsOnlyAllowedToRequestOwnDataHelper.cs new file mode 100644 index 00000000..55f6a979 --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Helpers/EnergySupplierIsOnlyAllowedToRequestOwnDataHelper.cs @@ -0,0 +1,58 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation.Helpers; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Helpers; + +public static class EnergySupplierIsOnlyAllowedToRequestOwnDataHelper +{ + private static readonly ValidationError _invalidEnergySupplierField = new("Feltet EnergySupplier skal være udfyldt med et valid GLN/EIC nummer når en elleverandør anmoder om data / EnergySupplier must be submitted with a valid GLN/EIC number when an energy supplier requests data", "E16"); + private static readonly ValidationError _notEqualToRequestedBy = new("Elleverandør i besked stemmer ikke overenes med elleverandør i header / Energy supplier in message does not correspond with energy supplier in header", "E16"); + + private static IList NoError => []; + + private static IList InvalidEnergySupplierError => [_invalidEnergySupplierField]; + + private static IList NotEqualToRequestedByError => [_notEqualToRequestedBy]; + + public static Task> ValidateAsync(string requestedForActorRole, string requestedForActorNumber, string? energySupplierNumber) + { + if (requestedForActorRole != ActorRole.EnergySupplier.Name) + return Task.FromResult(NoError); + + if (string.IsNullOrEmpty(energySupplierNumber)) + return Task.FromResult(InvalidEnergySupplierError); + + if (!IsValidEnergySupplierIdFormat(energySupplierNumber)) + return Task.FromResult(InvalidEnergySupplierError); + + if (!RequestedByIdEqualsEnergySupplier(requestedForActorNumber, energySupplierNumber)) + return Task.FromResult(NotEqualToRequestedByError); + + return Task.FromResult(NoError); + } + + private static bool IsValidEnergySupplierIdFormat(string energySupplierId) + { + return ActorNumberValidationHelper.IsValidGlnNumber(energySupplierId) || ActorNumberValidationHelper.IsValidEicNumber(energySupplierId); + } + + private static bool RequestedByIdEqualsEnergySupplier(string requestedByActorId, string energySupplierId) + { + return requestedByActorId.Equals(energySupplierId, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Helpers/GridAreaValidationHelper.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Helpers/GridAreaValidationHelper.cs new file mode 100644 index 00000000..4aa1ec8b --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Helpers/GridAreaValidationHelper.cs @@ -0,0 +1,36 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation.GridAreaOwner; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Helpers; + +public static class GridAreaValidationHelper +{ + public static async Task IsGridAreaOwnerAsync( + IGridAreaOwnerClient gridAreaOwnerClient, + string gridAreaCode, + string actorId) + { + var isGridAreaOwner = await gridAreaOwnerClient.IsCurrentOwnerAsync(gridAreaCode, actorId, CancellationToken.None) + .ConfigureAwait(false); + + return isGridAreaOwner; + + // Old implementation: + // var gridAreaOwner = await gridAreaOwnerRepository + // .GetCurrentOwnerAsync(gridAreaCode, CancellationToken.None).ConfigureAwait(false); + // return gridAreaOwner != null && gridAreaOwner.OwnerActorNumber.Equals(actorId, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Helpers/SettlementVersionValidationHelper.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Helpers/SettlementVersionValidationHelper.cs new file mode 100644 index 00000000..afabe307 --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Helpers/SettlementVersionValidationHelper.cs @@ -0,0 +1,47 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Helpers; + +public static class SettlementVersionValidationHelper +{ + private static readonly IReadOnlyList _validSettlementVersions = + [ + SettlementVersion.FirstCorrection.Name, + SettlementVersion.SecondCorrection.Name, + SettlementVersion.ThirdCorrection.Name, + ]; + + public static bool IsSettlementVersionValid(string businessReason, string? settlementVersion) + { + var isCorrection = businessReason == BusinessReason.Correction.Name; + + if (!isCorrection && settlementVersion != null) + return false; + + if (!isCorrection) + return true; + + // If the business reason is correction and settlement version is not set, latest correction result is requested + if (settlementVersion == null) + return true; + + if (!_validSettlementVersions.Contains(settlementVersion)) + return false; + + return true; + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/BalanceResponsibleValidationRule.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/BalanceResponsibleValidationRule.cs new file mode 100644 index 00000000..7322995d --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/BalanceResponsibleValidationRule.cs @@ -0,0 +1,66 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation.Helpers; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class BalanceResponsibleValidationRule : IBusinessValidationRule +{ + private static readonly string _propertyName = "BalanceResponsibleParty"; + private static readonly ValidationError _invalidBalanceResponsible = new($"Feltet {_propertyName} skal være udfyldt med et valid GLN/EIC når en balanceansvarlig anmoder om data / {_propertyName} must be submitted with a valid GLN/EIC when a balance responsible requests data", "E18"); + private static readonly ValidationError _notEqualToRequestedBy = new($"Den balanceansvarlige i beskeden stemmer ikke overenes med den balanceansvarlige i headeren / {_propertyName} in the message does not correspond with balance responsible in header", "E18"); + private static readonly ValidationError _invalidBusinessReason = new($"En balanceansvarlig kan kun benytte forretningsårsag D03 eller D04 i forbindelse med en anmodning / A {_propertyName} can only use business reason D03 or D04 in connection with a request", "D11"); + + private static IList NoError => []; + + public Task> ValidateAsync(RequestCalculatedEnergyTimeSeriesInputV1 subject) + { + if (subject.RequestedForActorRole != ActorRole.BalanceResponsibleParty.Name) + return Task.FromResult(NoError); + + IList errors = []; + + if (subject.BusinessReason != BusinessReason.BalanceFixing.Name + && subject.BusinessReason != BusinessReason.PreliminaryAggregation.Name) + { + errors.Add(_invalidBusinessReason); + } + + if (string.IsNullOrWhiteSpace(subject.BalanceResponsibleNumber)) + { + errors.Add(_invalidBalanceResponsible); + return Task.FromResult(errors); + } + + if (!IsValidBalanceResponsibleIdFormat(subject.BalanceResponsibleNumber)) + { + errors.Add(_invalidBalanceResponsible); + return Task.FromResult(errors); + } + + if (!subject.RequestedForActorNumber.Equals(subject.BalanceResponsibleNumber, StringComparison.OrdinalIgnoreCase)) + errors.Add(_notEqualToRequestedBy); + + return Task.FromResult(errors); + } + + private static bool IsValidBalanceResponsibleIdFormat(string balanceResponsibleNumber) + { + return ActorNumberValidationHelper.IsValidGlnNumber(balanceResponsibleNumber) || ActorNumberValidationHelper.IsValidEicNumber(balanceResponsibleNumber); + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/EnergySupplierValidationRule.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/EnergySupplierValidationRule.cs new file mode 100644 index 00000000..f71048cd --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/EnergySupplierValidationRule.cs @@ -0,0 +1,30 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Helpers; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class EnergySupplierValidationRule : IBusinessValidationRule +{ + public Task> ValidateAsync(RequestCalculatedEnergyTimeSeriesInputV1 subject) + { + return EnergySupplierIsOnlyAllowedToRequestOwnDataHelper.ValidateAsync( + subject.RequestedForActorRole, + subject.RequestedForActorNumber, + subject.EnergySupplierNumber); + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/GridAreaValidationRule.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/GridAreaValidationRule.cs new file mode 100644 index 00000000..44543ad4 --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/GridAreaValidationRule.cs @@ -0,0 +1,62 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation.GridAreaOwner; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Helpers; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class GridAreaValidationRule : IBusinessValidationRule +{ + private static readonly ValidationError _missingGridAreaCode = new("Netområde er obligatorisk for rollen MDR / Grid area is mandatory for the role MDR.", "D64"); + private static readonly ValidationError _invalidGridArea = new("Ugyldig netområde / Invalid gridarea", "E86"); + + private readonly IGridAreaOwnerClient _gridAreaOwnerClient; + + public GridAreaValidationRule(IGridAreaOwnerClient gridAreaOwnerClient) + { + _gridAreaOwnerClient = gridAreaOwnerClient; + } + + private static IList NoError => []; + + private static IList MissingGridAreaCodeError => [_missingGridAreaCode]; + + private static IList InvalidGridAreaError => [_invalidGridArea]; + + public async Task> ValidateAsync(RequestCalculatedEnergyTimeSeriesInputV1 subject) + { + if (subject.RequestedForActorRole != ActorRole.MeteredDataResponsible.Name) return NoError; + + if (subject.GridAreas.Count == 0) + return MissingGridAreaCodeError; + + foreach (var gridAreaCode in subject.GridAreas) + { + var isGridAreaOwner = await GridAreaValidationHelper.IsGridAreaOwnerAsync( + _gridAreaOwnerClient, + gridAreaCode, + subject.RequestedForActorNumber) + .ConfigureAwait(false); + + if (!isGridAreaOwner) + return InvalidGridAreaError; + } + + return NoError; + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/MeteringPointTypeValidationRule.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/MeteringPointTypeValidationRule.cs new file mode 100644 index 00000000..78bcb14d --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/MeteringPointTypeValidationRule.cs @@ -0,0 +1,49 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class MeteringPointTypeValidationRule : IBusinessValidationRule +{ + private static readonly IReadOnlyList _validMeteringPointTypes = + [ + MeteringPointType.Consumption.Name, + MeteringPointType.Production.Name, + MeteringPointType.Exchange.Name, + ]; + + private static readonly ValidationError _invalidMeteringPointType = + new( + "Metering point type skal være en af følgende: {PropertyName} eller undladt / Metering point type has one of the following: {PropertyName} or omitted", + "D18"); + + private static IList NoError => []; + + private static IList InvalidMeteringPointType => [_invalidMeteringPointType.WithPropertyName("E17, E18, E20")]; + + public Task> ValidateAsync(RequestCalculatedEnergyTimeSeriesInputV1 subject) + { + if (subject.MeteringPointType is null) + return Task.FromResult(NoError); + + if (_validMeteringPointTypes.Contains(subject.MeteringPointType, StringComparer.OrdinalIgnoreCase)) + return Task.FromResult(NoError); + + return Task.FromResult(InvalidMeteringPointType); + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/PeriodValidationRule.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/PeriodValidationRule.cs new file mode 100644 index 00000000..d627cc65 --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/PeriodValidationRule.cs @@ -0,0 +1,107 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Diagnostics.CodeAnalysis; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Components.BusinessValidation.Helpers; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Helpers; +using NodaTime; +using NodaTime.Text; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class PeriodValidationRule(PeriodValidationHelper periodValidationHelper) + : IBusinessValidationRule +{ + private const int MaxAllowedPeriodSizeInMonths = 1; + private const int AllowedTimeFrameYearsFromNow = 3; + private const int AllowedTimeFrameMonthsFromNow = 6; + + private static readonly ValidationError _invalidDateFormat = new("Forkert dato format for {PropertyName}, skal være YYYY-MM-DDT22:00:00Z eller YYYY-MM-DDT23:00:00Z / Wrong date format for {PropertyName}, must be YYYY-MM-DDT22:00:00Z or YYYY-MM-DDT23:00:00Z", "D66"); + private static readonly ValidationError _invalidWinterMidnightFormat = new("Forkert dato format for {PropertyName}, skal være YYYY-MM-DDT23:00:00Z / Wrong date format for {PropertyName}, must be YYYY-MM-DDT23:00:00Z", "D66"); + private static readonly ValidationError _invalidSummerMidnightFormat = new("Forkert dato format for {PropertyName}, skal være YYYY-MM-DDT22:00:00Z / Wrong date format for {PropertyName}, must be YYYY-MM-DDT22:00:00Z", "D66"); + private static readonly ValidationError _startDateMustBeLessThen3Years = new($"Dato må max være {AllowedTimeFrameYearsFromNow} år og {AllowedTimeFrameMonthsFromNow} måneder tilbage i tid / Can maximum be {AllowedTimeFrameYearsFromNow} years and {AllowedTimeFrameMonthsFromNow} months back in time", "E17"); + private static readonly ValidationError _periodIsGreaterThenAllowedPeriodSize = new("Dato må kun være for 1 måned af gangen / Can maximum be for a 1 month period", "E17"); + private static readonly ValidationError _missingStartOrAndEndDate = new("Start og slut dato skal udfyldes / Start and end date must be present in request", "E50"); + + private readonly PeriodValidationHelper _periodValidationHelper = periodValidationHelper; + + public Task> ValidateAsync(RequestCalculatedEnergyTimeSeriesInputV1 subject) + { + IList errors = new List(); + + if (MissingDates(subject.PeriodStart, subject.PeriodEnd, errors)) + return Task.FromResult(errors); + + var startInstant = ParseToInstant(subject.PeriodStart, "Start date", errors); + var endInstant = ParseToInstant(subject.PeriodEnd, "End date", errors); + + if (startInstant == null || endInstant == null) + return Task.FromResult(errors); + + MustBeMidnight(startInstant.Value, "Start date", errors); + MustBeMidnight(endInstant.Value, "End date", errors); + + StartDateMustBeGreaterThenAllowedYears(startInstant.Value, errors); + IntervalMustBeWithinAllowedPeriodSize(startInstant.Value, endInstant.Value, errors); + + return Task.FromResult(errors); + } + + private bool MissingDates(string start, [NotNullWhen(false)] string? end, IList errors) + { + if (string.IsNullOrWhiteSpace(start) || string.IsNullOrWhiteSpace(end)) + { + errors.Add(_missingStartOrAndEndDate); + return true; + } + + return false; + } + + private void IntervalMustBeWithinAllowedPeriodSize(Instant start, Instant end, IList errors) + { + if (_periodValidationHelper.IntervalMustBeLessThanAllowedPeriodSize(start, end, MaxAllowedPeriodSizeInMonths)) + errors.Add(_periodIsGreaterThenAllowedPeriodSize); + } + + private void StartDateMustBeGreaterThenAllowedYears(Instant start, IList errors) + { + if (_periodValidationHelper.IsMonthOfDateOlderThanXYearsAndYMonths(start, AllowedTimeFrameYearsFromNow, AllowedTimeFrameMonthsFromNow)) + { + errors.Add(_startDateMustBeLessThen3Years); + } + } + + private Instant? ParseToInstant(string dateTimeString, string propertyName, IList errors) + { + var parseResult = InstantPattern.General.Parse(dateTimeString); + if (parseResult.Success) + return parseResult.Value; + + errors.Add(_invalidDateFormat.WithPropertyName(propertyName)); + return null; + } + + private void MustBeMidnight(Instant instant, string propertyName, IList errors) + { + if (_periodValidationHelper.IsMidnight(instant, out var zonedDateTime)) + return; + + errors.Add(zonedDateTime.IsDaylightSavingTime() + ? _invalidSummerMidnightFormat.WithPropertyName(propertyName) + : _invalidWinterMidnightFormat.WithPropertyName(propertyName)); + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/RequestedByActorRoleValidationRule.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/RequestedByActorRoleValidationRule.cs new file mode 100644 index 00000000..14aa86d8 --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/RequestedByActorRoleValidationRule.cs @@ -0,0 +1,36 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; + +public sealed class RequestedByActorRoleValidationRule : IBusinessValidationRule +{ + private static IList NoError => []; + + private static IList UseMeteredDataResponsibleInsteadOfGridAreaOperatorError => [new( + "Rollen skal være MDR når der anmodes om beregnede energitidsserier / Role must be MDR when requesting aggregated measure data", + "D02")]; + + public Task> ValidateAsync(RequestCalculatedEnergyTimeSeriesInputV1 subject) + { + if (subject.RequestedForActorRole == ActorRole.GridAccessProvider.Name) + return Task.FromResult(UseMeteredDataResponsibleInsteadOfGridAreaOperatorError); + + return Task.FromResult(NoError); + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementMethodValidationRule.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementMethodValidationRule.cs new file mode 100644 index 00000000..e927ba87 --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementMethodValidationRule.cs @@ -0,0 +1,62 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class SettlementMethodValidationRule : IBusinessValidationRule +{ + private static readonly IReadOnlyList _validSettlementMethods = [ + SettlementMethod.Flex.Name, + SettlementMethod.NonProfiled.Name]; + + private static readonly string _validMeteringPointType = MeteringPointType.Consumption.Name; + private static readonly ValidationError _invalidSettlementMethod = new( + "SettlementMethod kan kun benyttes i kombination med E17 og skal være enten D01 og E02 / SettlementMethod can only be used in combination with E17 and must be either D01 or E02", + "D15"); + + private static IList NoError => new List(); + + private static IList InvalidSettlementMethod => new List { _invalidSettlementMethod }; + + public Task> ValidateAsync(RequestCalculatedEnergyTimeSeriesInputV1 subject) + { + if (subject.SettlementMethod is null) + return Task.FromResult(NoError); + + if (subject.MeteringPointType is null) + return Task.FromResult(InvalidSettlementMethod); + + if (!IsValidSettlementMethod(subject.SettlementMethod)) + return Task.FromResult(InvalidSettlementMethod); + + if (!IsMeteringPointTypeConsumption(subject.MeteringPointType)) + return Task.FromResult(InvalidSettlementMethod); + + return Task.FromResult(NoError); + } + + private bool IsValidSettlementMethod(string settlementMethod) + { + return _validSettlementMethods.Contains(settlementMethod); + } + + private bool IsMeteringPointTypeConsumption(string meteringPointType) + { + return meteringPointType.Equals(_validMeteringPointType, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementVersionValidationRule.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementVersionValidationRule.cs new file mode 100644 index 00000000..d23f1c78 --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/SettlementVersionValidationRule.cs @@ -0,0 +1,42 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; +using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Helpers; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class SettlementVersionValidationRule : IBusinessValidationRule +{ + private static readonly ValidationError _invalidSettlementVersionError = new( + "SettlementSeriesVersion kan kun benyttes i kombination med D32 og skal være enten D01, D02 eller D03 / SettlementSeriesVersion can only be used in combination with D32 and must be either D01, D02 or D03", + "E86"); + + private static IList NoError => new List(); + + private static IList InvalidSettlementVersionError => new List { _invalidSettlementVersionError }; + + public Task> ValidateAsync(RequestCalculatedEnergyTimeSeriesInputV1 subject) + { + var validSettlementVersion = SettlementVersionValidationHelper.IsSettlementVersionValid( + subject.BusinessReason, + subject.SettlementVersion); + + return Task.FromResult( + validSettlementVersion + ? NoError + : InvalidSettlementVersionError); + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/TimeSeriesTypeValidationRule.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/TimeSeriesTypeValidationRule.cs new file mode 100644 index 00000000..7f9f6cb1 --- /dev/null +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/BusinessValidation/Rules/TimeSeriesTypeValidationRule.cs @@ -0,0 +1,47 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManager.Components.BusinessValidation; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Components.Datahub.ValueObjects; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; + +namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.BusinessValidation.Rules; + +public class TimeSeriesTypeValidationRule : IBusinessValidationRule +{ + private static readonly ValidationError _invalidTimeSeriesTypeForActor = new( + "Den forespurgte tidsserie type kan ikke forespørges som en {PropertyName} / The requested time series type can not be requested as a {PropertyName}", + "D11"); + + private static IList NoError => new List(); + + public Task> ValidateAsync(RequestCalculatedEnergyTimeSeriesInputV1 subject) + { + if (subject.RequestedForActorRole == ActorRole.MeteredDataResponsible.Name) + return Task.FromResult(NoError); + + if (subject.MeteringPointType == MeteringPointType.Exchange.Name) + return Task.FromResult(InvalidTimeSeriesTypeForActor(subject.RequestedForActorRole)); + + if (subject.MeteringPointType == MeteringPointType.Consumption.Name && subject.SettlementMethod is null) + return Task.FromResult(InvalidTimeSeriesTypeForActor(subject.RequestedForActorRole)); + + return Task.FromResult(NoError); + } + + private IList InvalidTimeSeriesTypeForActor(string actorRole) + { + return new List { _invalidTimeSeriesTypeForActor.WithPropertyName(actorRole) }; + } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Models/OrchestrationExecutionContext.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Models/OrchestrationInstanceContext.cs similarity index 95% rename from source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Models/OrchestrationExecutionContext.cs rename to source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Models/OrchestrationInstanceContext.cs index c222f851..d3713c63 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Models/OrchestrationExecutionContext.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Models/OrchestrationInstanceContext.cs @@ -17,6 +17,6 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Models; -public record OrchestrationExecutionContext( +public record OrchestrationInstanceContext( OrchestrationInstanceId Id, OrchestrationOptions_Brs_026_V1 Options); diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/OrchestrationDescriptionBuilder.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/OrchestrationDescriptionBuilder.cs index 063b4e37..2abab8cb 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/OrchestrationDescriptionBuilder.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/OrchestrationDescriptionBuilder.cs @@ -29,7 +29,7 @@ public OrchestrationDescription Build() description.ParameterDefinition.SetFromType(); - description.AppendStepDescription("Asynkron validering"); + description.AppendStepDescription("Forretningsvalidering"); description.AppendStepDescription("Udsend beskeder"); return description; diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Orchestration_Brs_026_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Orchestration_Brs_026_V1.cs index 322e3993..1e79b4bd 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Orchestration_Brs_026_V1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_026/V1/Orchestration_Brs_026_V1.cs @@ -19,6 +19,7 @@ using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Activities; using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_026.V1.Models; +using Energinet.DataHub.ProcessManager.Shared.Processes.Activities; using Microsoft.Azure.Functions.Worker; using Microsoft.DurableTask; using Microsoft.Extensions.Logging; @@ -45,17 +46,17 @@ public async Task Run( { var input = context.GetOrchestrationParameterValue(); - var (instanceId, options) = await InitializeOrchestrationAsync(context); + var orchestrationInstanceContext = await InitializeOrchestrationAsync(context); - var validationResult = await PerformAsynchronousValidationAsync(context, instanceId, input); - await EnqueueActorMessagesInEdiAsync(context, instanceId, input, validationResult); + var validationResult = await PerformAsynchronousValidationAsync(context, orchestrationInstanceContext.Id, input); + await EnqueueActorMessagesInEdiAsync(context, orchestrationInstanceContext.Id, input, validationResult); var wasMessagesEnqueued = await WaitForEnqueueActorMessagesResponseFromEdiAsync( context, - options.EnqueueActorMessagesTimeout, - instanceId); + orchestrationInstanceContext.Options.EnqueueActorMessagesTimeout, + orchestrationInstanceContext.Id); - return await TerminateOrchestrationAsync(context, instanceId, input, wasMessagesEnqueued); + return await TerminateOrchestrationAsync(context, orchestrationInstanceContext.Id, input, wasMessagesEnqueued); } private static TaskOptions CreateDefaultRetryOptions() @@ -66,26 +67,42 @@ private static TaskOptions CreateDefaultRetryOptions() backoffCoefficient: 2.0)); } - private Task InitializeOrchestrationAsync(TaskOrchestrationContext context) + private async Task InitializeOrchestrationAsync(TaskOrchestrationContext context) { var instanceId = new OrchestrationInstanceId(Guid.Parse(context.InstanceId)); - return context.CallActivityAsync( - nameof(StartOrchestrationActivity_Brs_026_V1), - new StartOrchestrationActivity_Brs_026_V1.ActivityInput( + await context.CallActivityAsync( + nameof(TransitionOrchestrationToRunningActivity_V1), + new TransitionOrchestrationToRunningActivity_V1.ActivityInput( + instanceId), + _defaultRetryOptions); + + var orchestrationInstanceContext = await context.CallActivityAsync( + nameof(GetOrchestrationInstanceContextActivity_Brs_026_V1), + new GetOrchestrationInstanceContextActivity_Brs_026_V1.ActivityInput( instanceId), _defaultRetryOptions); + + return orchestrationInstanceContext; } - private async Task PerformAsynchronousValidationAsync( + private async Task PerformAsynchronousValidationAsync( TaskOrchestrationContext context, OrchestrationInstanceId instanceId, RequestCalculatedEnergyTimeSeriesInputV1 input) { - var validationResult = await context.CallActivityAsync( - nameof(PerformAsyncValidationActivity_Brs_026_V1), - new PerformAsyncValidationActivity_Brs_026_V1.ActivityInput( + await context.CallActivityAsync( + nameof(TransitionStepToRunningActivity_V1), + new TransitionStepToRunningActivity_V1.ActivityInput( + instanceId, + AsyncValidationStepSequence), + _defaultRetryOptions); + + var validationResult = await context.CallActivityAsync( + nameof(PerformBusinessValidationActivity_Brs_026_V1), + new PerformBusinessValidationActivity_Brs_026_V1.ActivityInput( instanceId, + AsyncValidationStepSequence, input), _defaultRetryOptions); @@ -93,8 +110,8 @@ private Task InitializeOrchestrationAsync(TaskOrc ? OrchestrationStepTerminationState.Succeeded : OrchestrationStepTerminationState.Failed; await context.CallActivityAsync( - nameof(TerminateStepActivity_Brs_026_V1), - new TerminateStepActivity_Brs_026_V1.ActivityInput( + nameof(TransitionStepToTerminatedActivity_V1), + new TransitionStepToTerminatedActivity_V1.ActivityInput( instanceId, AsyncValidationStepSequence, asyncValidationTerminationState), @@ -107,8 +124,15 @@ private async Task EnqueueActorMessagesInEdiAsync( TaskOrchestrationContext context, OrchestrationInstanceId instanceId, RequestCalculatedEnergyTimeSeriesInputV1 input, - PerformAsyncValidationActivity_Brs_026_V1.ActivityOutput validationResult) + PerformBusinessValidationActivity_Brs_026_V1.ActivityOutput validationResult) { + await context.CallActivityAsync( + nameof(TransitionStepToRunningActivity_V1), + new TransitionStepToRunningActivity_V1.ActivityInput( + instanceId, + EnqueueActorMessagesStepSequence), + _defaultRetryOptions); + var idempotencyKey = context.NewGuid(); if (validationResult.IsValid) { @@ -122,13 +146,13 @@ await context.CallActivityAsync( } else { - ArgumentNullException.ThrowIfNull(validationResult.ValidationError); + ArgumentNullException.ThrowIfNull(validationResult.ValidationErrors); await context.CallActivityAsync( nameof(EnqueueRejectMessageActivity_Brs_026_V1), new EnqueueRejectMessageActivity_Brs_026_V1.ActivityInput( instanceId, - validationResult.ValidationError, + validationResult.ValidationErrors, idempotencyKey), _defaultRetryOptions); } @@ -165,8 +189,8 @@ private async Task WaitForEnqueueActorMessagesResponseFromEdiAsync( ? OrchestrationStepTerminationState.Succeeded : OrchestrationStepTerminationState.Failed; await context.CallActivityAsync( - nameof(TerminateStepActivity_Brs_026_V1), - new TerminateStepActivity_Brs_026_V1.ActivityInput( + nameof(TransitionStepToTerminatedActivity_V1), + new TransitionStepToTerminatedActivity_V1.ActivityInput( instanceId, EnqueueActorMessagesStepSequence, enqueueActorMessagesTerminationState), @@ -186,8 +210,8 @@ private async Task TerminateOrchestrationAsync( : OrchestrationInstanceTerminationState.Failed; await context.CallActivityAsync( - nameof(TerminateOrchestrationActivity_Brs_026_V1), - new TerminateOrchestrationActivity_Brs_026_V1.ActivityInput( + nameof(TransitionOrchestrationToTerminatedActivity_V1), + new TransitionOrchestrationToTerminatedActivity_V1.ActivityInput( instanceId, orchestrationTerminationState), _defaultRetryOptions); diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/EnqueueActorMessagesActivity_Brs_028_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/EnqueueActorMessagesActivity_Brs_028_V1.cs index 9b659994..1d5b5ec1 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/EnqueueActorMessagesActivity_Brs_028_V1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/EnqueueActorMessagesActivity_Brs_028_V1.cs @@ -42,10 +42,6 @@ public async Task Run( .GetAsync(input.InstanceId) .ConfigureAwait(false); - orchestrationInstance.TransitionStepToRunning( - Orchestration_Brs_028_V1.EnqueueActorMessagesStepSequence, - _clock); - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); await EnqueueActorMessagesAsync(orchestrationInstance.Lifecycle.CreatedBy.Value, input).ConfigureAwait(false); } diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/EnqueueRejectMessageActivity_Brs_028_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/EnqueueRejectMessageActivity_Brs_028_V1.cs index e359d834..21eb5e5c 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/EnqueueRejectMessageActivity_Brs_028_V1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/EnqueueRejectMessageActivity_Brs_028_V1.cs @@ -41,11 +41,6 @@ public async Task Run( .GetAsync(input.InstanceId) .ConfigureAwait(false); - orchestrationInstance.TransitionStepToRunning( - Orchestration_Brs_028_V1.EnqueueActorMessagesStepSequence, - _clock); - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); - await EnqueueRejectMessageAsync(orchestrationInstance.Lifecycle.CreatedBy.Value, input).ConfigureAwait(false); } diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/StartOrchestrationActivity_Brs_028_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/GetOrchestrationInstanceContextActivity_Brs_028_V1.cs similarity index 62% rename from source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/StartOrchestrationActivity_Brs_028_V1.cs rename to source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/GetOrchestrationInstanceContextActivity_Brs_028_V1.cs index cc38bd89..7a72ee42 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/StartOrchestrationActivity_Brs_028_V1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/GetOrchestrationInstanceContextActivity_Brs_028_V1.cs @@ -23,31 +23,20 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_028.V1.Activities; /// -/// Set the orchestration instance lifecycle to running +/// Get the for the orchestration instance. /// -internal class StartOrchestrationActivity_Brs_028_V1( - IClock clock, - IOrchestrationInstanceProgressRepository progressRepository, +internal class GetOrchestrationInstanceContextActivity_Brs_028_V1( IOptions options) { - private readonly IClock _clock = clock; - private readonly IOrchestrationInstanceProgressRepository _progressRepository = progressRepository; private readonly OrchestrationOptions_Brs_028_V1 _options = options.Value; - [Function(nameof(StartOrchestrationActivity_Brs_028_V1))] - public async Task Run( + [Function(nameof(GetOrchestrationInstanceContextActivity_Brs_028_V1))] + public Task Run( [ActivityTrigger] ActivityInput input) { - var orchestrationInstance = await _progressRepository - .GetAsync(input.InstanceId) - .ConfigureAwait(false); - - orchestrationInstance.Lifecycle.TransitionToRunning(_clock); - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); - - return new OrchestrationExecutionContext( - orchestrationInstance.Id, - _options); + return Task.FromResult(new OrchestrationInstanceContext( + input.InstanceId, + _options)); } public record ActivityInput( diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/PerformAsyncValidationActivity_Brs_028_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/PerformAsyncValidationActivity_Brs_028_V1.cs index f7d99b06..14224b9c 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/PerformAsyncValidationActivity_Brs_028_V1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/PerformAsyncValidationActivity_Brs_028_V1.cs @@ -23,26 +23,12 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_028.V1.A /// /// Perform async validation (and set step to running) /// -internal class PerformAsyncValidationActivity_Brs_028_V1( - IClock clock, - IOrchestrationInstanceProgressRepository progressRepository) +internal class PerformAsyncValidationActivity_Brs_028_V1 { - private readonly IClock _clock = clock; - private readonly IOrchestrationInstanceProgressRepository _progressRepository = progressRepository; - [Function(nameof(PerformAsyncValidationActivity_Brs_028_V1))] public async Task Run( [ActivityTrigger] ActivityInput input) { - var orchestrationInstance = await _progressRepository - .GetAsync(input.InstanceId) - .ConfigureAwait(false); - - orchestrationInstance.TransitionStepToRunning( - Orchestration_Brs_028_V1.AsyncValidationStepSequence, - _clock); - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); - var isValid = await PerformAsyncValidationAsync(input.RequestInput).ConfigureAwait(false); return isValid; diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/TerminateOrchestrationActivity_Brs_028_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/TerminateOrchestrationActivity_Brs_028_V1.cs deleted file mode 100644 index efde8dab..00000000 --- a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/TerminateOrchestrationActivity_Brs_028_V1.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2020 Energinet DataHub A/S -// -// Licensed under the Apache License, Version 2.0 (the "License2"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Energinet.DataHub.ProcessManager.Core.Application.Orchestration; -using Energinet.DataHub.ProcessManager.Core.Domain.OrchestrationInstance; -using Microsoft.Azure.Functions.Worker; -using NodaTime; - -namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_028.V1.Activities; - -/// -/// Set the orchestration instance lifecycle to terminated -/// -internal class TerminateOrchestrationActivity_Brs_028_V1( - IClock clock, - IOrchestrationInstanceProgressRepository progressRepository) -{ - private readonly IClock _clock = clock; - private readonly IOrchestrationInstanceProgressRepository _progressRepository = progressRepository; - - [Function(nameof(TerminateOrchestrationActivity_Brs_028_V1))] - public async Task Run( - [ActivityTrigger] ActivityInput input) - { - var orchestrationInstance = await _progressRepository - .GetAsync(input.InstanceId) - .ConfigureAwait(false); - - switch (input.TerminationState) - { - case OrchestrationInstanceTerminationState.Succeeded: - orchestrationInstance.Lifecycle.TransitionToSucceeded(_clock); - break; - - case OrchestrationInstanceTerminationState.Failed: - orchestrationInstance.Lifecycle.TransitionToFailed(_clock); - break; - - case OrchestrationInstanceTerminationState.UserCanceled: - default: - throw new ArgumentOutOfRangeException(nameof(input.TerminationState), input.TerminationState, "Invalid termination state"); - } - - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); - } - - public record ActivityInput( - OrchestrationInstanceId InstanceId, - OrchestrationInstanceTerminationState TerminationState); -} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/TerminateStepActivity_Brs_028_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/TerminateStepActivity_Brs_028_V1.cs deleted file mode 100644 index 5e135f3b..00000000 --- a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Activities/TerminateStepActivity_Brs_028_V1.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 Energinet DataHub A/S -// -// Licensed under the Apache License, Version 2.0 (the "License2"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Energinet.DataHub.ProcessManager.Core.Application.Orchestration; -using Energinet.DataHub.ProcessManager.Core.Domain.OrchestrationInstance; -using Microsoft.Azure.Functions.Worker; -using NodaTime; - -namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_028.V1.Activities; - -/// -/// Set the orchestration instance step lifecycle to terminated -/// -internal class TerminateStepActivity_Brs_028_V1( - IClock clock, - IOrchestrationInstanceProgressRepository progressRepository) -{ - private readonly IClock _clock = clock; - private readonly IOrchestrationInstanceProgressRepository _progressRepository = progressRepository; - - [Function(nameof(TerminateStepActivity_Brs_028_V1))] - public async Task Run( - [ActivityTrigger] ActivityInput input) - { - var orchestrationInstance = await _progressRepository - .GetAsync(input.InstanceId) - .ConfigureAwait(false); - - orchestrationInstance.TransitionStepToTerminated( - input.StepSequence, - input.TerminationState, - _clock); - - await _progressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); - } - - public record ActivityInput( - OrchestrationInstanceId InstanceId, - int StepSequence, - OrchestrationStepTerminationState TerminationState); -} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Models/OrchestrationExecutionContext.cs b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Models/OrchestrationInstanceContext.cs similarity index 95% rename from source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Models/OrchestrationExecutionContext.cs rename to source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Models/OrchestrationInstanceContext.cs index 39c06418..a82e9d1d 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Models/OrchestrationExecutionContext.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Models/OrchestrationInstanceContext.cs @@ -17,6 +17,6 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_028.V1.Models; -public record OrchestrationExecutionContext( +public record OrchestrationInstanceContext( OrchestrationInstanceId Id, OrchestrationOptions_Brs_028_V1 Options); diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Orchestration_Brs_028_V1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Orchestration_Brs_028_V1.cs index 7f505978..dc244de9 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Orchestration_Brs_028_V1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_028/V1/Orchestration_Brs_028_V1.cs @@ -19,6 +19,7 @@ using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_028.V1.Model; using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_028.V1.Activities; using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_028.V1.Models; +using Energinet.DataHub.ProcessManager.Shared.Processes.Activities; using Microsoft.Azure.Functions.Worker; using Microsoft.DurableTask; using Microsoft.Extensions.Logging; @@ -63,15 +64,23 @@ private static TaskOptions CreateDefaultRetryOptions() backoffCoefficient: 2.0)); } - private Task InitializeOrchestrationAsync(TaskOrchestrationContext context) + private async Task InitializeOrchestrationAsync(TaskOrchestrationContext context) { var instanceId = new OrchestrationInstanceId(Guid.Parse(context.InstanceId)); - return context.CallActivityAsync( - nameof(StartOrchestrationActivity_Brs_028_V1), - new StartOrchestrationActivity_Brs_028_V1.ActivityInput( + await context.CallActivityAsync( + nameof(TransitionOrchestrationToRunningActivity_V1), + new TransitionOrchestrationToRunningActivity_V1.ActivityInput( + instanceId), + _defaultRetryOptions); + + var orchestrationInstanceContext = await context.CallActivityAsync( + nameof(GetOrchestrationInstanceContextActivity_Brs_028_V1), + new GetOrchestrationInstanceContextActivity_Brs_028_V1.ActivityInput( instanceId), _defaultRetryOptions); + + return orchestrationInstanceContext; } private async Task PerformAsynchronousValidationAsync( @@ -79,6 +88,13 @@ private Task InitializeOrchestrationAsync(TaskOrc OrchestrationInstanceId instanceId, RequestCalculatedWholesaleServicesInputV1 input) { + await context.CallActivityAsync( + nameof(TransitionStepToRunningActivity_V1), + new TransitionStepToRunningActivity_V1.ActivityInput( + instanceId, + AsyncValidationStepSequence), + _defaultRetryOptions); + var validationResult = await context.CallActivityAsync( nameof(PerformAsyncValidationActivity_Brs_028_V1), new PerformAsyncValidationActivity_Brs_028_V1.ActivityInput( @@ -90,8 +106,8 @@ private Task InitializeOrchestrationAsync(TaskOrc ? OrchestrationStepTerminationState.Succeeded : OrchestrationStepTerminationState.Failed; await context.CallActivityAsync( - nameof(TerminateStepActivity_Brs_028_V1), - new TerminateStepActivity_Brs_028_V1.ActivityInput( + nameof(TransitionStepToTerminatedActivity_V1), + new TransitionStepToTerminatedActivity_V1.ActivityInput( instanceId, AsyncValidationStepSequence, asyncValidationTerminationState), @@ -106,6 +122,13 @@ private async Task EnqueueActorMessagesInEdiAsync( RequestCalculatedWholesaleServicesInputV1 input, PerformAsyncValidationActivity_Brs_028_V1.ActivityOutput validationResult) { + await context.CallActivityAsync( + nameof(TransitionStepToRunningActivity_V1), + new TransitionStepToRunningActivity_V1.ActivityInput( + instanceId, + EnqueueActorMessagesStepSequence), + _defaultRetryOptions); + var idempotencyKey = context.NewGuid(); if (validationResult.IsValid) { @@ -159,8 +182,8 @@ private async Task WaitForEnqueueActorMessagesResponseFromEdiAsync( ? OrchestrationStepTerminationState.Succeeded : OrchestrationStepTerminationState.Failed; await context.CallActivityAsync( - nameof(TerminateStepActivity_Brs_028_V1), - new TerminateStepActivity_Brs_028_V1.ActivityInput( + nameof(TransitionStepToTerminatedActivity_V1), + new TransitionStepToTerminatedActivity_V1.ActivityInput( instanceId, EnqueueActorMessagesStepSequence, enqueueActorMessagesTerminationState), @@ -180,8 +203,8 @@ private async Task TerminateOrchestrationAsync( : OrchestrationInstanceTerminationState.Failed; await context.CallActivityAsync( - nameof(TerminateOrchestrationActivity_Brs_028_V1), - new TerminateOrchestrationActivity_Brs_028_V1.ActivityInput( + nameof(TransitionOrchestrationToTerminatedActivity_V1), + new TransitionOrchestrationToTerminatedActivity_V1.ActivityInput( instanceId, orchestrationTerminationState), _defaultRetryOptions); diff --git a/source/ProcessManager.Orchestrations/Program.cs b/source/ProcessManager.Orchestrations/Program.cs index a3f61034..bb34a4bf 100644 --- a/source/ProcessManager.Orchestrations/Program.cs +++ b/source/ProcessManager.Orchestrations/Program.cs @@ -17,14 +17,12 @@ using Energinet.DataHub.Core.App.FunctionApp.Extensions.Builder; using Energinet.DataHub.Core.App.FunctionApp.Extensions.DependencyInjection; using Energinet.DataHub.Core.Messaging.Communication.Extensions.DependencyInjection; -using Energinet.DataHub.ElectricityMarket.Integration; -using Energinet.DataHub.ElectricityMarket.Integration.Extensions.DependencyInjection; using Energinet.DataHub.ProcessManager.Components.Extensions.DependencyInjection; using Energinet.DataHub.ProcessManager.Core.Infrastructure.Extensions.DependencyInjection; using Energinet.DataHub.ProcessManager.Core.Infrastructure.Extensions.Startup; using Energinet.DataHub.ProcessManager.Core.Infrastructure.Telemetry; +using Energinet.DataHub.ProcessManager.Orchestrations.Abstractions.Processes.BRS_026.V1.Model; using Energinet.DataHub.ProcessManager.Orchestrations.Extensions.DependencyInjection; -using Energinet.DataHub.ProcessManager.Orchestrations.TestServices; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -60,6 +58,11 @@ // Enqueue Messages in EDI services.AddEnqueueActorMessages(azureCredential); + // Business validation + var orchestrationsAssembly = typeof(Program).Assembly; + var orchestrationsAbstractionsAssembly = typeof(RequestCalculatedEnergyTimeSeriesInputV1).Assembly; + services.AddBusinessValidation([orchestrationsAssembly, orchestrationsAbstractionsAssembly]); + // ProcessManager services.AddProcessManagerTopic(azureCredential); // => Auto register Orchestration Descriptions builders and custom handlers