diff --git a/Btms.Analytics.Tests/Helpers/TestDataGeneratorHelpers.cs b/Btms.Analytics.Tests/Helpers/TestDataGeneratorHelpers.cs index aaba184..e4836c5 100644 --- a/Btms.Analytics.Tests/Helpers/TestDataGeneratorHelpers.cs +++ b/Btms.Analytics.Tests/Helpers/TestDataGeneratorHelpers.cs @@ -53,6 +53,12 @@ public static async Task PushToConsumers(this IHost app, ILogger logger, break; case Decision d: + // This sleep is to allow the system to settle, and have made any links & decisions + // Before sending in a decision and causing a concurrency issue + // Ideally we want to switch to pushing to the bus, rather than directly to the consumer + // so we get the concurrency protection. + + Thread.Sleep(1000); var decisionConsumer = (DecisionsConsumer)scope .ServiceProvider diff --git a/Btms.Analytics.Tests/MovementsByDecisionsTests.cs b/Btms.Analytics.Tests/MovementsByDecisionsTests.cs index ae215ee..5e0e312 100644 --- a/Btms.Analytics.Tests/MovementsByDecisionsTests.cs +++ b/Btms.Analytics.Tests/MovementsByDecisionsTests.cs @@ -25,8 +25,8 @@ public async Task WhenCalled_ReturnExpectedAggregation() testOutputHelper.WriteLine("{0} aggregated items found", result.Count); - result.Count.Should().Be(3); + result.Count.Should().Be(4); result.Select(r => r.Key).Order().Should() - .Equal("ALVS Linked : H01", "BTMS Linked : X00", "BTMS Not Linked : X00"); + .Equal("ALVS Linked : H01", "BTMS Linked : C03", "BTMS Linked : X00", "BTMS Not Linked : X00"); } } \ No newline at end of file diff --git a/Btms.Business.Tests/Services/Decisions/Finders/ChedADecisionFinderTests.cs b/Btms.Business.Tests/Services/Decisions/Finders/ChedADecisionFinderTests.cs index afba8cc..9280951 100644 --- a/Btms.Business.Tests/Services/Decisions/Finders/ChedADecisionFinderTests.cs +++ b/Btms.Business.Tests/Services/Decisions/Finders/ChedADecisionFinderTests.cs @@ -1,19 +1,64 @@ +using Btms.Business.Services.Decisions; using Btms.Business.Services.Decisions.Finders; +using Btms.Model.Ipaffs; +using FluentAssertions; using Xunit; namespace Btms.Business.Tests.Services.Decisions.Finders; public class ChedADecisionFinderTests { - [Fact] - public async Task Find_Should_DoThings() + [Theory] + [InlineData(true, DecisionDecisionEnum.AcceptableForTranshipment, null, DecisionCode.E03)] + [InlineData(true, DecisionDecisionEnum.AcceptableForTransit, null, DecisionCode.E03)] + [InlineData(true, DecisionDecisionEnum.AcceptableForInternalMarket, null, DecisionCode.C03)] + [InlineData(true, DecisionDecisionEnum.AcceptableForTemporaryImport, null, DecisionCode.C05)] + [InlineData(true, DecisionDecisionEnum.HorseReEntry, null, DecisionCode.C06)] + + [InlineData(true, DecisionDecisionEnum.NonAcceptable, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableIfChanneled, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableForSpecificWarehouse, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableForPrivateImport, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableForTransfer, null, DecisionCode.X00)] + + [InlineData(null, null, null, DecisionCode.X00)] + + [InlineData(false,null, DecisionNotAcceptableActionEnum.Euthanasia, DecisionCode.N02)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Reexport, DecisionCode.N04)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Slaughter, DecisionCode.N02)] + + [InlineData(false, null, DecisionNotAcceptableActionEnum.Redispatching, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Destruction, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Transformation, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Other, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.EntryRefusal, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.QuarantineImposed, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.SpecialTreatment, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.IndustrialProcessing, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.ReDispatch, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.UseForOtherPurposes, DecisionCode.X00)] + + public void DecisionFinderTest(bool? consignmentAcceptable, DecisionDecisionEnum? decision, DecisionNotAcceptableActionEnum? notAcceptableAction, DecisionCode expectedCode) { // Arrange + var notification = new ImportNotification + { + PartTwo = new PartTwo + { + Decision = new Decision + { + ConsignmentAcceptable = consignmentAcceptable, + DecisionEnum = decision, + NotAcceptableAction = notAcceptableAction + } + } + }; var sut = new ChedADecisionFinder(); // Act + var result = sut.FindDecision(notification); // Assert - await Task.CompletedTask; + result.DecisionCode.Should().Be(expectedCode); } } \ No newline at end of file diff --git a/Btms.Business.Tests/Services/Decisions/Finders/ChedDDecisionFinderTests.cs b/Btms.Business.Tests/Services/Decisions/Finders/ChedDDecisionFinderTests.cs index 323a9f3..63b2973 100644 --- a/Btms.Business.Tests/Services/Decisions/Finders/ChedDDecisionFinderTests.cs +++ b/Btms.Business.Tests/Services/Decisions/Finders/ChedDDecisionFinderTests.cs @@ -1,19 +1,64 @@ +using Btms.Business.Services.Decisions; using Btms.Business.Services.Decisions.Finders; +using Btms.Model.Ipaffs; +using FluentAssertions; using Xunit; namespace Btms.Business.Tests.Services.Decisions.Finders; public class ChedDDecisionFinderTests { - [Fact] - public async Task Find_Should_DoThings() + [Theory] + [InlineData(true, DecisionDecisionEnum.AcceptableForInternalMarket, null, DecisionCode.C03)] + + [InlineData(true, DecisionDecisionEnum.AcceptableForTranshipment, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableForTransit, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableForTemporaryImport, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.HorseReEntry, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.NonAcceptable, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableIfChanneled, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableForSpecificWarehouse, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableForPrivateImport, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableForTransfer, null, DecisionCode.X00)] + + [InlineData(null, null, null, DecisionCode.X00)] + + [InlineData(false, null, DecisionNotAcceptableActionEnum.Redispatching, DecisionCode.N04)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Destruction, DecisionCode.N02)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Transformation, DecisionCode.N03)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Other, DecisionCode.N07)] + + [InlineData(false, null, DecisionNotAcceptableActionEnum.Euthanasia, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Reexport, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Slaughter, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.EntryRefusal, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.QuarantineImposed, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.SpecialTreatment, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.IndustrialProcessing, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.ReDispatch, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.UseForOtherPurposes, DecisionCode.X00)] + + public void DecisionFinderTest(bool? consignmentAcceptable, DecisionDecisionEnum? decision, DecisionNotAcceptableActionEnum? notAcceptableAction, DecisionCode expectedCode) { // Arrange + var notification = new ImportNotification + { + PartTwo = new PartTwo + { + Decision = new Decision + { + ConsignmentAcceptable = consignmentAcceptable, + DecisionEnum = decision, + NotAcceptableAction = notAcceptableAction + } + } + }; var sut = new ChedDDecisionFinder(); - + // Act + var result = sut.FindDecision(notification); // Assert - await Task.CompletedTask; + result.DecisionCode.Should().Be(expectedCode); } } \ No newline at end of file diff --git a/Btms.Business.Tests/Services/Decisions/Finders/ChedPDecisionFinderTests.cs b/Btms.Business.Tests/Services/Decisions/Finders/ChedPDecisionFinderTests.cs index e49d7d0..91ec16b 100644 --- a/Btms.Business.Tests/Services/Decisions/Finders/ChedPDecisionFinderTests.cs +++ b/Btms.Business.Tests/Services/Decisions/Finders/ChedPDecisionFinderTests.cs @@ -1,19 +1,63 @@ +using Btms.Business.Services.Decisions; using Btms.Business.Services.Decisions.Finders; +using Btms.Model.Ipaffs; +using FluentAssertions; using Xunit; namespace Btms.Business.Tests.Services.Decisions.Finders; public class ChedPDecisionFinderTests { - [Fact] - public async Task Find_Should_DoThings() + [Theory] + [InlineData(true, DecisionDecisionEnum.AcceptableForInternalMarket, null, DecisionCode.C03)] + [InlineData(true, DecisionDecisionEnum.AcceptableForTransit, null, DecisionCode.E03)] + [InlineData(true, DecisionDecisionEnum.AcceptableIfChanneled, null, DecisionCode.C06)] + [InlineData(true, DecisionDecisionEnum.AcceptableForTranshipment, null, DecisionCode.E03)] + [InlineData(true, DecisionDecisionEnum.AcceptableForSpecificWarehouse, null, DecisionCode.E03)] + + [InlineData(true, DecisionDecisionEnum.AcceptableForTemporaryImport, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.HorseReEntry, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.NonAcceptable, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableForPrivateImport, null, DecisionCode.X00)] + [InlineData(true, DecisionDecisionEnum.AcceptableForTransfer, null, DecisionCode.X00)] + + [InlineData(null, null, null, DecisionCode.X00)] + + [InlineData(false, null, DecisionNotAcceptableActionEnum.Reexport, DecisionCode.N04)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Destruction, DecisionCode.N02)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Transformation, DecisionCode.N03)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Other, DecisionCode.N07)] + + [InlineData(false, null, DecisionNotAcceptableActionEnum.Euthanasia, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Slaughter, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.Redispatching, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.EntryRefusal, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.QuarantineImposed, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.SpecialTreatment, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.IndustrialProcessing, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.ReDispatch, DecisionCode.X00)] + [InlineData(false, null, DecisionNotAcceptableActionEnum.UseForOtherPurposes, DecisionCode.X00)] + public void DecisionFinderTest(bool? consignmentAcceptable, DecisionDecisionEnum? decision, DecisionNotAcceptableActionEnum? notAcceptableAction, DecisionCode expectedCode) { // Arrange + var notification = new ImportNotification + { + PartTwo = new PartTwo + { + Decision = new Decision + { + ConsignmentAcceptable = consignmentAcceptable, + DecisionEnum = decision, + NotAcceptableAction = notAcceptableAction + } + } + }; var sut = new ChedPDecisionFinder(); - + // Act + var result = sut.FindDecision(notification); // Assert - await Task.CompletedTask; + result.DecisionCode.Should().Be(expectedCode); } } \ No newline at end of file diff --git a/Btms.Business.Tests/Services/Decisions/NoMatchDecisionsTest.cs b/Btms.Business.Tests/Services/Decisions/NoMatchDecisionsTest.cs index 2931143..48bb034 100644 --- a/Btms.Business.Tests/Services/Decisions/NoMatchDecisionsTest.cs +++ b/Btms.Business.Tests/Services/Decisions/NoMatchDecisionsTest.cs @@ -9,21 +9,48 @@ using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using SlimMessageBus; +using TestDataGenerator; using TestDataGenerator.Scenarios; using Xunit; +using Check = Btms.Model.Alvs.Check; +using Decision = Btms.Model.Ipaffs.Decision; namespace Btms.Business.Tests.Services.Decisions; public class NoMatchDecisionsTest { + [Fact] + public async Task WhenClearanceRequest_HasNotMatch_AndNoChecks_ThenNoDecisionShouldBeGenerated() + { + // Arrange + var movements = GenerateMovements(false); + var publishBus = Substitute.For(); + + var sut = new DecisionService(NullLogger.Instance, publishBus); + + var matchingResult = new MatchingResult(); + matchingResult.AddDocumentNoMatch(movements[0].Id!, movements[0].Items[0].ItemNumber!.Value, movements[0].Items[0].Documents?[0].DocumentReference!); + + // Act + var decisionResult = await sut.Process(new DecisionContext(new List(), movements, matchingResult, true), CancellationToken.None); + + // Assert + decisionResult.Should().NotBeNull(); + decisionResult.Decisions.Count.Should().Be(0); + await publishBus.Received(0).Publish(Arg.Any(), Arg.Any(), + Arg.Any>(), Arg.Any()); + await Task.CompletedTask; + } + [Fact] public async Task WhenClearanceRequest_HasNotMatch_ThenDecisionCodeShouldBeNoMatch() { // Arrange - var movements = GenerateMovements(); + var movements = GenerateMovements(true); + movements[0].Items[0].Checks = [new Check() { CheckCode = "TEST" }]; var publishBus = Substitute.For(); - var sut = new DecisionService(publishBus); + var sut = new DecisionService(NullLogger.Instance, publishBus); var matchingResult = new MatchingResult(); matchingResult.AddDocumentNoMatch(movements[0].Id!, movements[0].Items[0].ItemNumber!.Value, movements[0].Items[0].Documents?[0].DocumentReference!); @@ -41,10 +68,14 @@ await publishBus.Received().Publish(Arg.Any(), Arg.Any GenerateMovements() + // CrNoMatchNoChecksScenarioGenerator + // CrNoMatchScenarioGenerator + private static List GenerateMovements(bool hasChecks) { - CrNoMatchScenarioGenerator generator = - new CrNoMatchScenarioGenerator(NullLogger.Instance); + ScenarioGenerator generator = hasChecks + ? new CrNoMatchScenarioGenerator(NullLogger.Instance) + : new CrNoMatchNoChecksScenarioGenerator(NullLogger.Instance); + var config = ScenarioFactory.CreateScenarioConfig(generator, 1, 1); var generatorResult = generator.Generate(1, 1, DateTime.UtcNow, config); diff --git a/Btms.Business/Services/Decisions/DecisionCode.cs b/Btms.Business/Services/Decisions/DecisionCode.cs index 050c27c..daf325b 100644 --- a/Btms.Business/Services/Decisions/DecisionCode.cs +++ b/Btms.Business/Services/Decisions/DecisionCode.cs @@ -2,6 +2,8 @@ namespace Btms.Business.Services.Decisions; public enum DecisionCode { + H01, + H02, E03, C03, C05, diff --git a/Btms.Business/Services/Decisions/DecisionService.cs b/Btms.Business/Services/Decisions/DecisionService.cs index e57e620..6cc1c31 100644 --- a/Btms.Business/Services/Decisions/DecisionService.cs +++ b/Btms.Business/Services/Decisions/DecisionService.cs @@ -1,10 +1,11 @@ using Btms.Business.Services.Decisions.Finders; using Btms.Model.Ipaffs; +using Microsoft.Extensions.Logging; using SlimMessageBus; namespace Btms.Business.Services.Decisions; -public class DecisionService(IPublishBus bus) : IDecisionService +public class DecisionService(ILogger logger, IPublishBus bus) : IDecisionService { public async Task Process(DecisionContext decisionContext, CancellationToken cancellationToken) @@ -25,33 +26,37 @@ public async Task Process(DecisionContext decisionContext, Cance return decisionResult; } - private static Task DeriveDecision(DecisionContext decisionContext) + private Task DeriveDecision(DecisionContext decisionContext) { var decisionsResult = new DecisionResult(); if (decisionContext.GenerateNoMatch) { foreach (var noMatch in decisionContext.MatchingResult.NoMatches) { - decisionsResult.AddDecision(noMatch.MovementId, noMatch.ItemNumber, noMatch.DocumentReference, - DecisionCode.X00); + if (decisionContext.HasChecks(noMatch.MovementId, noMatch.ItemNumber)) + { + decisionsResult.AddDecision(noMatch.MovementId, noMatch.ItemNumber, noMatch.DocumentReference, + DecisionCode.X00); + } } } - ////Not part of no matches, and the finders haven't been implemented yet, so leaving this commented out for the moment - ////foreach (var match in decisionContext.MatchingResult.Matches) - ////{ - //// var n = decisionContext.Notifications.First(x => x.Id == match.NotificationId); - //// var decisionCode = GetDecision(n); - //// decisionsResult.AddDecision(match.MovementId, match.ItemNumber, match.DocumentReference, decisionCode.DecisionCode); - ////} + foreach (var match in decisionContext.MatchingResult.Matches) + { + if (decisionContext.HasChecks(match.MovementId, match.ItemNumber)) + { + var n = decisionContext.Notifications.First(x => x.Id == match.NotificationId); + var decisionCode = GetDecision(n); + decisionsResult.AddDecision(match.MovementId, match.ItemNumber, match.DocumentReference, + decisionCode.DecisionCode); + } + } return Task.FromResult(decisionsResult); } - -#pragma warning disable S1144 - private static DecisionFinderResult GetDecision(ImportNotification notification) -#pragma warning restore S1144 + + private DecisionFinderResult GetDecision(ImportNotification notification) { // get decision finder - fold IUU stuff into the decision finder for fish? IDecisionFinder finder = notification.ImportNotificationType switch @@ -63,7 +68,10 @@ private static DecisionFinderResult GetDecision(ImportNotification notification) _ => throw new InvalidOperationException("Invalid ImportNotificationType") }; - return finder.FindDecision(notification); + var result = finder.FindDecision(notification); + logger.LogInformation("Decision finder result for Notification {Id} Decision {Decision} - ConsignmentAcceptable {ConsignmentAcceptable}: DecisionEnum {DecisionEnum}: NotAcceptableAction {NotAcceptableAction}", + notification.Id, result.DecisionCode.ToString(), notification.PartTwo?.Decision?.ConsignmentAcceptable, notification.PartTwo?.Decision?.DecisionEnum.ToString(), notification.PartTwo?.Decision?.NotAcceptableAction?.ToString()); + return result; } diff --git a/Btms.Business/Services/Decisions/Finders/ChedADecisionFinder.cs b/Btms.Business/Services/Decisions/Finders/ChedADecisionFinder.cs index 1753027..73846a9 100644 --- a/Btms.Business/Services/Decisions/Finders/ChedADecisionFinder.cs +++ b/Btms.Business/Services/Decisions/Finders/ChedADecisionFinder.cs @@ -6,6 +6,29 @@ public class ChedADecisionFinder : IDecisionFinder { public DecisionFinderResult FindDecision(ImportNotification notification) { - throw new NotImplementedException(); + if (notification.TryGetHoldDecision(out var code)) + { + return new DecisionFinderResult(code!.Value); + } + + var consignmentAcceptable = notification.PartTwo?.Decision?.ConsignmentAcceptable; + return consignmentAcceptable switch + { + true => notification.PartTwo?.Decision?.DecisionEnum switch + { + DecisionDecisionEnum.AcceptableForTranshipment or DecisionDecisionEnum.AcceptableForTransit => new DecisionFinderResult(DecisionCode.E03), + DecisionDecisionEnum.AcceptableForInternalMarket => new DecisionFinderResult(DecisionCode.C03), + DecisionDecisionEnum.AcceptableForTemporaryImport => new DecisionFinderResult(DecisionCode.C05), + DecisionDecisionEnum.HorseReEntry => new DecisionFinderResult(DecisionCode.C06), + _ => new DecisionFinderResult(DecisionCode.X00) + }, + false => notification.PartTwo?.Decision?.NotAcceptableAction switch + { + DecisionNotAcceptableActionEnum.Euthanasia or DecisionNotAcceptableActionEnum.Slaughter => new DecisionFinderResult(DecisionCode.N02), + DecisionNotAcceptableActionEnum.Reexport => new DecisionFinderResult(DecisionCode.N04), + _ => new DecisionFinderResult(DecisionCode.X00) + }, + _ => new DecisionFinderResult(DecisionCode.X00) + }; } } \ No newline at end of file diff --git a/Btms.Business/Services/Decisions/Finders/ChedDDecisionFinder.cs b/Btms.Business/Services/Decisions/Finders/ChedDDecisionFinder.cs index 306940b..ad087ec 100644 --- a/Btms.Business/Services/Decisions/Finders/ChedDDecisionFinder.cs +++ b/Btms.Business/Services/Decisions/Finders/ChedDDecisionFinder.cs @@ -6,6 +6,28 @@ public class ChedDDecisionFinder : IDecisionFinder { public DecisionFinderResult FindDecision(ImportNotification notification) { - throw new NotImplementedException(); + if (notification.TryGetHoldDecision(out var code)) + { + return new DecisionFinderResult(code!.Value); + } + + var consignmentAcceptable = notification.PartTwo?.Decision?.ConsignmentAcceptable; + return consignmentAcceptable switch + { + true => notification.PartTwo?.Decision?.DecisionEnum switch + { + DecisionDecisionEnum.AcceptableForInternalMarket => new DecisionFinderResult(DecisionCode.C03), + _ => new DecisionFinderResult(DecisionCode.X00) + }, + false => notification.PartTwo?.Decision?.NotAcceptableAction switch + { + DecisionNotAcceptableActionEnum.Destruction => new DecisionFinderResult(DecisionCode.N02), + DecisionNotAcceptableActionEnum.Redispatching => new DecisionFinderResult(DecisionCode.N04), + DecisionNotAcceptableActionEnum.Transformation => new DecisionFinderResult(DecisionCode.N03), + DecisionNotAcceptableActionEnum.Other => new DecisionFinderResult(DecisionCode.N07), + _ => new DecisionFinderResult(DecisionCode.X00) + }, + _ => new DecisionFinderResult(DecisionCode.X00) + }; } } \ No newline at end of file diff --git a/Btms.Business/Services/Decisions/Finders/ChedPDecisionFinder.cs b/Btms.Business/Services/Decisions/Finders/ChedPDecisionFinder.cs index fa0b73a..2aac784 100644 --- a/Btms.Business/Services/Decisions/Finders/ChedPDecisionFinder.cs +++ b/Btms.Business/Services/Decisions/Finders/ChedPDecisionFinder.cs @@ -6,6 +6,32 @@ public class ChedPDecisionFinder : IDecisionFinder { public DecisionFinderResult FindDecision(ImportNotification notification) { - throw new NotImplementedException(); + if (notification.TryGetHoldDecision(out var code)) + { + return new DecisionFinderResult(code!.Value); + } + + var consignmentAcceptable = notification.PartTwo?.Decision?.ConsignmentAcceptable; + return consignmentAcceptable switch + { + true => notification.PartTwo?.Decision?.DecisionEnum switch + { + DecisionDecisionEnum.AcceptableForTranshipment or DecisionDecisionEnum.AcceptableForTransit + or DecisionDecisionEnum.AcceptableForSpecificWarehouse => + new DecisionFinderResult(DecisionCode.E03), + DecisionDecisionEnum.AcceptableForInternalMarket => new DecisionFinderResult(DecisionCode.C03), + DecisionDecisionEnum.AcceptableIfChanneled => new DecisionFinderResult(DecisionCode.C06), + _ => new DecisionFinderResult(DecisionCode.X00) + }, + false => notification.PartTwo?.Decision?.NotAcceptableAction switch + { + DecisionNotAcceptableActionEnum.Destruction => new DecisionFinderResult(DecisionCode.N02), + DecisionNotAcceptableActionEnum.Reexport => new DecisionFinderResult(DecisionCode.N04), + DecisionNotAcceptableActionEnum.Transformation => new DecisionFinderResult(DecisionCode.N03), + DecisionNotAcceptableActionEnum.Other => new DecisionFinderResult(DecisionCode.N07), + _ => new DecisionFinderResult(DecisionCode.X00) + }, + _ => new DecisionFinderResult(DecisionCode.X00) + }; } } \ No newline at end of file diff --git a/Btms.Business/Services/Decisions/Finders/ChedPPDecisionFinder.cs b/Btms.Business/Services/Decisions/Finders/ChedPPDecisionFinder.cs index 68788b4..b57d32e 100644 --- a/Btms.Business/Services/Decisions/Finders/ChedPPDecisionFinder.cs +++ b/Btms.Business/Services/Decisions/Finders/ChedPPDecisionFinder.cs @@ -7,6 +7,11 @@ public class ChedPPDecisionFinder : IDecisionFinder { public DecisionFinderResult FindDecision(ImportNotification notification) { - throw new NotImplementedException(); + if (notification.TryGetHoldDecision(out var code)) + { + return new DecisionFinderResult(code!.Value); + } + + return new DecisionFinderResult(DecisionCode.X00); } } \ No newline at end of file diff --git a/Btms.Business/Services/Decisions/Finders/DecisionFinderExtensions.cs b/Btms.Business/Services/Decisions/Finders/DecisionFinderExtensions.cs new file mode 100644 index 0000000..231c9c8 --- /dev/null +++ b/Btms.Business/Services/Decisions/Finders/DecisionFinderExtensions.cs @@ -0,0 +1,37 @@ +using Btms.Model.Ipaffs; + +namespace Btms.Business.Services.Decisions.Finders; + +internal static class DecisionFinderExtensions +{ + public static bool TryGetHoldDecision(this ImportNotification notification, out DecisionCode? decisionCode) + { + if (notification.Status == ImportNotificationStatusEnum.Submitted || + notification.Status == ImportNotificationStatusEnum.InProgress) + { + if (notification.PartTwo?.InspectionRequired == "NOTREQUIRED" || notification.PartTwo?.InspectionRequired == "INCONCLUSIVE") + { + decisionCode = DecisionCode.H01; + return true; + } + + if (notification.PartTwo?.InspectionRequired == "REQUIRED" || notification.RiskAssessment?.CommodityResults?.Any(x => x.HmiDecision == CommodityRiskResultHmiDecisionEnum.Required) is true + || notification.RiskAssessment?.CommodityResults?.Any(x => x.PhsiDecision == CommodityRiskResultPhsiDecisionEnum.Required) is true) + { + decisionCode = DecisionCode.H02; + return true; + } + + } + + decisionCode = null; + return false; + } + + public static bool HasChecks(this DecisionContext decisionContext, string movementId, int itemNumber) + { + var checks = decisionContext.Movements.First(x => x.Id == movementId).Items + .First(x => x.ItemNumber == itemNumber).Checks; + return checks != null && checks.Any(); + } +} \ No newline at end of file diff --git a/Btms.Types.Ipaffs.Mapping.V1/ImportNotificationWithTransformMapper.cs b/Btms.Types.Ipaffs.Mapping.V1/ImportNotificationWithTransformMapper.cs index e92e69b..3791b82 100644 --- a/Btms.Types.Ipaffs.Mapping.V1/ImportNotificationWithTransformMapper.cs +++ b/Btms.Types.Ipaffs.Mapping.V1/ImportNotificationWithTransformMapper.cs @@ -59,15 +59,19 @@ private static void Map(ImportNotification from, Btms.Model.Ipaffs.ImportNotific var complementRiskAssesments = new Dictionary(); var commodityChecks = new Dictionary(); - foreach (var commoditiesCommodityComplement in commodities!.ComplementParameterSets!) + if (commodities?.ComplementParameterSets != null) { - complementParameters[commoditiesCommodityComplement.ComplementId!.Value] = - commoditiesCommodityComplement; + foreach (var commoditiesCommodityComplement in commodities.ComplementParameterSets) + { + complementParameters[commoditiesCommodityComplement.ComplementId!.Value] = + commoditiesCommodityComplement; + } } - if (from.RiskAssessment != null) + + if (from.RiskAssessment?.CommodityResults != null) { - foreach (var commoditiesRa in from.RiskAssessment.CommodityResults!) + foreach (var commoditiesRa in from.RiskAssessment.CommodityResults) { complementRiskAssesments[commoditiesRa.UniqueId!] = commoditiesRa; } @@ -81,7 +85,7 @@ private static void Map(ImportNotification from, Btms.Model.Ipaffs.ImportNotific } } - if (commodities.CommodityComplements is not null) + if (commodities?.CommodityComplements is not null) { foreach (var commodity in commodities.CommodityComplements) { @@ -105,7 +109,10 @@ parameters.UniqueComplementId is not null && } } - to.CommoditiesSummary = CommoditiesMapper.Map(commodities); - to.Commodities = commodities.CommodityComplements?.Select(x => CommodityComplementMapper.Map(x)).ToArray()!; + if (commodities != null) + { + to.CommoditiesSummary = CommoditiesMapper.Map(commodities); + to.Commodities = commodities.CommodityComplements?.Select(x => CommodityComplementMapper.Map(x)).ToArray()!; + } } } \ No newline at end of file diff --git a/TestDataGenerator/ClearanceRequestBuilder.cs b/TestDataGenerator/ClearanceRequestBuilder.cs index 34294a0..a2f2972 100644 --- a/TestDataGenerator/ClearanceRequestBuilder.cs +++ b/TestDataGenerator/ClearanceRequestBuilder.cs @@ -85,6 +85,19 @@ public ClearanceRequestBuilder WithItem(string documentCode, string commodity }); } + public ClearanceRequestBuilder WithItemNoChecks(string documentCode, string commodityCode, string description, + int netWeight) + { + return Do(cr => + { + cr.Items![0].TaricCommodityCode = commodityCode; + cr.Items![0].GoodsDescription = description; + cr.Items![0].ItemNetMass = netWeight; + cr.Items![0].Documents![0].DocumentCode = documentCode; + cr.Items![0].Checks = []; + }); + } + public ClearanceRequestBuilder WithRandomItems(int min, int max) { var commodityCount = CreateRandomInt(min, max); diff --git a/TestDataGenerator/Scenarios/CRNoMatchNoChecksScenarioGenerator.cs b/TestDataGenerator/Scenarios/CRNoMatchNoChecksScenarioGenerator.cs new file mode 100644 index 0000000..c9cb91a --- /dev/null +++ b/TestDataGenerator/Scenarios/CRNoMatchNoChecksScenarioGenerator.cs @@ -0,0 +1,23 @@ +using Btms.Common.Extensions; +using Btms.Types.Ipaffs; +using Microsoft.Extensions.Logging; +using TestDataGenerator.Helpers; + +namespace TestDataGenerator.Scenarios; + +public class CrNoMatchNoChecksScenarioGenerator(ILogger logger) : ScenarioGenerator +{ + public override GeneratorResult Generate(int scenario, int item, DateTime entryDate, ScenarioConfig config) + { + var clearanceRequest = GetClearanceRequestBuilder("cr-one-item") + .WithCreationDate(entryDate) + .WithArrivalDateTimeOffset(DateTime.Today.ToDate(), DateTime.Now.ToTime()) + .WithReferenceNumber(DataHelpers.GenerateReferenceNumber(ImportNotificationTypeEnum.Cveda, scenario, entryDate, item)) + .WithItemNoChecks("N853", "16041421", "Tuna ROW CHEDP", 900) + .ValidateAndBuild(); + + logger.LogInformation("Created {EntryReference}", clearanceRequest.Header!.EntryReference); + + return new GeneratorResult([clearanceRequest]); + } +} \ No newline at end of file diff --git a/TestDataGenerator/Scenarios/ChedPSimpleMatchScenarioGenerator.cs b/TestDataGenerator/Scenarios/ChedPSimpleMatchScenarioGenerator.cs index 2f98b5d..1e650f2 100644 --- a/TestDataGenerator/Scenarios/ChedPSimpleMatchScenarioGenerator.cs +++ b/TestDataGenerator/Scenarios/ChedPSimpleMatchScenarioGenerator.cs @@ -35,7 +35,7 @@ public override GeneratorResult Generate(int scenario, int item, DateTime entryD .ValidateAndBuild(); logger.LogInformation("Created {EntryReference}", alvsDecision.Header!.EntryReference); - + return new GeneratorResult([clearanceRequest, notification, alvsDecision]); } } \ No newline at end of file