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 libraryEnerginet-DataHubEnerginet-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 libraryEnerginet-DataHubEnerginet-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
+
+
+
+
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