Skip to content

Commit

Permalink
Feature/cdms 147 (#24)
Browse files Browse the repository at this point in the history
* Added Ched finders logic

* updated decision service not to send out decision when no checks exist and updated tests

* tidy up some code

* Updated tests

* Fixed up check tests

---------

Co-authored-by: Thomas Anderson <[email protected]>
Co-authored-by: Craig Edmunds <[email protected]>
  • Loading branch information
3 people authored Dec 18, 2024
1 parent 5259079 commit 5862145
Show file tree
Hide file tree
Showing 17 changed files with 384 additions and 47 deletions.
6 changes: 6 additions & 0 deletions Btms.Analytics.Tests/Helpers/TestDataGeneratorHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ public static async Task<IHost> 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
Expand Down
4 changes: 2 additions & 2 deletions Btms.Analytics.Tests/MovementsByDecisionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
41 changes: 36 additions & 5 deletions Btms.Business.Tests/Services/Decisions/NoMatchDecisionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IPublishBus>();

var sut = new DecisionService(NullLogger<DecisionService>.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<ImportNotification>(), movements, matchingResult, true), CancellationToken.None);

// Assert
decisionResult.Should().NotBeNull();
decisionResult.Decisions.Count.Should().Be(0);
await publishBus.Received(0).Publish(Arg.Any<Decision>(), Arg.Any<string>(),
Arg.Any<IDictionary<string, object>>(), Arg.Any<CancellationToken>());
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<IPublishBus>();

var sut = new DecisionService(publishBus);
var sut = new DecisionService(NullLogger<DecisionService>.Instance, publishBus);

var matchingResult = new MatchingResult();
matchingResult.AddDocumentNoMatch(movements[0].Id!, movements[0].Items[0].ItemNumber!.Value, movements[0].Items[0].Documents?[0].DocumentReference!);
Expand All @@ -41,10 +68,14 @@ await publishBus.Received().Publish(Arg.Any<Types.Alvs.Decision>(), Arg.Any<stri
await Task.CompletedTask;
}

private static List<Movement> GenerateMovements()
// CrNoMatchNoChecksScenarioGenerator
// CrNoMatchScenarioGenerator
private static List<Movement> GenerateMovements(bool hasChecks)
{
CrNoMatchScenarioGenerator generator =
new CrNoMatchScenarioGenerator(NullLogger<CrNoMatchScenarioGenerator>.Instance);
ScenarioGenerator generator = hasChecks
? new CrNoMatchScenarioGenerator(NullLogger<CrNoMatchScenarioGenerator>.Instance)
: new CrNoMatchNoChecksScenarioGenerator(NullLogger<CrNoMatchNoChecksScenarioGenerator>.Instance);

var config = ScenarioFactory.CreateScenarioConfig(generator, 1, 1);

var generatorResult = generator.Generate(1, 1, DateTime.UtcNow, config);
Expand Down
2 changes: 2 additions & 0 deletions Btms.Business/Services/Decisions/DecisionCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ namespace Btms.Business.Services.Decisions;

public enum DecisionCode
{
H01,
H02,
E03,
C03,
C05,
Expand Down
40 changes: 24 additions & 16 deletions Btms.Business/Services/Decisions/DecisionService.cs
Original file line number Diff line number Diff line change
@@ -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<DecisionService> logger, IPublishBus bus) : IDecisionService
{

public async Task<DecisionResult> Process(DecisionContext decisionContext, CancellationToken cancellationToken)
Expand All @@ -25,33 +26,37 @@ public async Task<DecisionResult> Process(DecisionContext decisionContext, Cance
return decisionResult;
}

private static Task<DecisionResult> DeriveDecision(DecisionContext decisionContext)
private Task<DecisionResult> 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
Expand All @@ -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;
}


Expand Down
Loading

0 comments on commit 5862145

Please sign in to comment.