From fff69fa57edbd67b1cfd0882cd5667aa44596f98 Mon Sep 17 00:00:00 2001 From: Tris Date: Fri, 20 Dec 2024 15:49:04 +0000 Subject: [PATCH] Implemented clearing down removed links from notifications --- .../Services/Linking/LinkingServiceTests.cs | 420 +++++++++++++++++- .../PreProcessing/MovementPreProcessor.cs | 3 - .../Services/Linking/LinkingService.cs | 67 ++- Btms.Model/Auditing/AuditEntry.cs | 13 + Btms.Model/ChangeLog/ChangeSet.cs | 10 +- Btms.Model/Ipaffs/ImportNotification.cs | 18 + Btms.Model/Movement.cs | 1 - 7 files changed, 505 insertions(+), 27 deletions(-) diff --git a/Btms.Business.Tests/Services/Linking/LinkingServiceTests.cs b/Btms.Business.Tests/Services/Linking/LinkingServiceTests.cs index 02f37355..4cddb221 100644 --- a/Btms.Business.Tests/Services/Linking/LinkingServiceTests.cs +++ b/Btms.Business.Tests/Services/Linking/LinkingServiceTests.cs @@ -36,14 +36,237 @@ public async Task Link_UnknownContextType_ShouldThrowException() await test.Should().ThrowAsync(); } + [Fact] + public async Task LinkMovement_ExistingRequest_IncludesFieldsOfInterest_AddedInitialDocuments_AddsNotificationLinks() + { + // Arrange + var testData = await AddTestData(3, 1, 0); + var beforeLinkChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); + var movementCtx = CreateMovementContextWithDocuments(testData.Movements[0], [], [testData.Cheds[0], testData.Cheds[1]]); + + // Act + var linkResult = await sut.Link(movementCtx); + + // Assert + linkResult.Should().NotBeNull(); + linkResult.Outcome.Should().Be(LinkOutcome.Linked); + linkResult.Notifications.Count.Should().Be(2); + linkResult.Movements.Count.Should().Be(1); + + var afterChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // added cheds + beforeLinkChedRelCounts[0].Should().Be(0); + afterChedRelCounts[0].Should().Be(1); + beforeLinkChedRelCounts[1].Should().Be(0); + afterChedRelCounts[1].Should().Be(1); + + // unlinked ched + beforeLinkChedRelCounts[2].Should().Be(0); + afterChedRelCounts[2].Should().Be(0); + } + + [Fact] + public async Task LinkMovement_ExistingRequest_IncludesFieldsOfInterest_AddedInitialItems_AddsNotificationLinks() + { + // Arrange + var testData = await AddTestData(3, 1, 0); + var beforeLinkChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); + var movementCtx = CreateMovementContextWithItems(testData.Movements[0], [], [testData.Cheds[0], testData.Cheds[1]]); + + // Act + var linkResult = await sut.Link(movementCtx); + + // Assert + linkResult.Should().NotBeNull(); + linkResult.Outcome.Should().Be(LinkOutcome.Linked); + linkResult.Notifications.Count.Should().Be(2); + linkResult.Movements.Count.Should().Be(1); + + var afterChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // added cheds + beforeLinkChedRelCounts[0].Should().Be(0); + afterChedRelCounts[0].Should().Be(1); + beforeLinkChedRelCounts[1].Should().Be(0); + afterChedRelCounts[1].Should().Be(1); + + // unlinked ched + beforeLinkChedRelCounts[2].Should().Be(0); + afterChedRelCounts[2].Should().Be(0); + } + + [Fact] + public async Task LinkMovement_ExistingRequest_IncludesFieldsOfInterest_AddedSomeDocuments_AddsNotificationLinks() + { + // Arrange + var testData = await AddTestData(3, 1, 0); + var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); + + // establish existing links + var movementCtx = CreateMovementContextWithDocuments(testData.Movements[0], [], [testData.Cheds[0]]); + var prelink = await sut.Link(movementCtx); + var beforeLinkChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // Act + movementCtx = CreateMovementContextWithDocuments(prelink.Movements[0], [testData.Cheds[0]], [testData.Cheds[0], testData.Cheds[1]]); + var linkResult = await sut.Link(movementCtx); + + // Assert + linkResult.Should().NotBeNull(); + linkResult.Outcome.Should().Be(LinkOutcome.Linked); + linkResult.Notifications.Count.Should().Be(2); + linkResult.Movements.Count.Should().Be(1); + + var afterChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // unchanged ched + beforeLinkChedRelCounts[0].Should().Be(1); + afterChedRelCounts[0].Should().Be(1); + + // added ched + beforeLinkChedRelCounts[1].Should().Be(0); + afterChedRelCounts[1].Should().Be(1); + + // unlinked ched + beforeLinkChedRelCounts[2].Should().Be(0); + afterChedRelCounts[2].Should().Be(0); + } + + [Fact] + public async Task LinkMovement_ExistingRequest_IncludesFieldsOfInterest_AddedSomeItems_AddsNotificationLinks() + { + // Arrange + var testData = await AddTestData(3, 1, 0); + var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); + + // establish existing links + var movementCtx = CreateMovementContextWithItems(testData.Movements[0], [], [testData.Cheds[0]]); + var prelink = await sut.Link(movementCtx); + var beforeLinkChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // Act + movementCtx = CreateMovementContextWithItems(prelink.Movements[0], [testData.Cheds[0]], [testData.Cheds[0], testData.Cheds[1]]); + var linkResult = await sut.Link(movementCtx); + + // Assert + linkResult.Should().NotBeNull(); + linkResult.Outcome.Should().Be(LinkOutcome.Linked); + linkResult.Notifications.Count.Should().Be(2); + linkResult.Movements.Count.Should().Be(1); + + var afterChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // unchanged ched + beforeLinkChedRelCounts[0].Should().Be(1); + afterChedRelCounts[0].Should().Be(1); + + // added ched + beforeLinkChedRelCounts[1].Should().Be(0); + afterChedRelCounts[1].Should().Be(1); + + // unlinked ched + beforeLinkChedRelCounts[2].Should().Be(0); + afterChedRelCounts[2].Should().Be(0); + } + + [Fact] + public async Task LinkMovement_ExistingRequest_IncludesFieldsOfInterest_ChangedSomeDocuments_RemovesAndAddsNotificationLinks() + { + // Arrange + var testData = await AddTestData(3, 1, 0); + var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); + + // establish existing links + var movementCtx = CreateMovementContextWithDocuments(testData.Movements[0], [], [testData.Cheds[0]]); + var prelink = await sut.Link(movementCtx); + var beforeLinkChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // Act + movementCtx = CreateMovementContextWithDocuments(prelink.Movements[0], [testData.Cheds[0]], [testData.Cheds[1]]); + var linkResult = await sut.Link(movementCtx); + + // Assert + linkResult.Should().NotBeNull(); + linkResult.Outcome.Should().Be(LinkOutcome.Linked); + linkResult.Notifications.Count.Should().Be(1); + linkResult.Movements.Count.Should().Be(1); + + var afterChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // removed cheds + beforeLinkChedRelCounts[0].Should().Be(1); + afterChedRelCounts[0].Should().Be(0); + + // added cheds + beforeLinkChedRelCounts[1].Should().Be(0); + afterChedRelCounts[1].Should().Be(1); + + // unlinked ched + beforeLinkChedRelCounts[2].Should().Be(0); + afterChedRelCounts[2].Should().Be(0); + } + + [Fact] + public async Task LinkMovement_ExistingRequest_IncludesFieldsOfInterest_ChangedSomeItems_RemovesAndAddsNotificationLinks() + { + // Arrange + var testData = await AddTestData(3, 1, 0); + var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); + + // establish existing links + var movementCtx = CreateMovementContextWithItems(testData.Movements[0], [], [testData.Cheds[0]]); + var prelink = await sut.Link(movementCtx); + var beforeLinkChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // Act + movementCtx = CreateMovementContextWithItems(prelink.Movements[0], [testData.Cheds[0]], [testData.Cheds[1]]); + var linkResult = await sut.Link(movementCtx); + + // Assert + linkResult.Should().NotBeNull(); + linkResult.Outcome.Should().Be(LinkOutcome.Linked); + linkResult.Notifications.Count.Should().Be(1); + linkResult.Movements.Count.Should().Be(1); + + var afterChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // removed cheds + beforeLinkChedRelCounts[0].Should().Be(1); + afterChedRelCounts[0].Should().Be(0); + + // added cheds + beforeLinkChedRelCounts[1].Should().Be(0); + afterChedRelCounts[1].Should().Be(1); + + // unlinked ched + beforeLinkChedRelCounts[2].Should().Be(0); + afterChedRelCounts[2].Should().Be(0); + } + [Fact] public async Task LinkMovement_ExistingRequest_IncludesFieldsOfInterest_MatchingCHEDs_AddsAllToLinkResult() { // Arrange var testData = await AddTestData(); - var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); - var movementCtx = CreateMovementContext(testData.Movements[0], [testData.Cheds[0], null], true, true); + var movementCtx = CreateMovementContext(testData.Movements[0], [testData.Cheds[0], null], true, false); // Act var linkResult = await sut.Link(movementCtx); @@ -60,9 +283,8 @@ public async Task LinkMovement_ExistingRequest_IncludesFieldsOfInterest_NoMatchi { // Arrange var testData = await AddTestData(); - var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); - var movementCtx = CreateMovementContext(testData.Movements[0], [null], true, true); + var movementCtx = CreateMovementContext(testData.Movements[0], [null], true, false); // Act var linkResult = await sut.Link(movementCtx); @@ -73,7 +295,89 @@ public async Task LinkMovement_ExistingRequest_IncludesFieldsOfInterest_NoMatchi linkResult.Notifications.Count.Should().Be(0); linkResult.Movements.Count.Should().Be(1); } - + + [Fact] + public async Task LinkMovement_ExistingRequest_IncludesFieldsOfInterest_RemovedSomeDocuments_RemovesNotificationLinks() + { + // Arrange + var testData = await AddTestData(4, 1, 0); + var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); + + // establish existing links + var movementCtx = CreateMovementContextWithDocuments(testData.Movements[0], [], [testData.Cheds[0], testData.Cheds[1], testData.Cheds[2]]); + var prelink = await sut.Link(movementCtx); + var beforeLinkChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // Act + movementCtx = CreateMovementContextWithDocuments(prelink.Movements[0], [testData.Cheds[0], testData.Cheds[1], testData.Cheds[2]], [testData.Cheds[0]]); + var linkResult = await sut.Link(movementCtx); + + // Assert + linkResult.Should().NotBeNull(); + linkResult.Outcome.Should().Be(LinkOutcome.Linked); + linkResult.Notifications.Count.Should().Be(1); + linkResult.Movements.Count.Should().Be(1); + + var afterChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // unchanged ched + beforeLinkChedRelCounts[0].Should().Be(1); + afterChedRelCounts[0].Should().Be(1); + + // removed cheds + beforeLinkChedRelCounts[1].Should().Be(1); + afterChedRelCounts[1].Should().Be(0); + beforeLinkChedRelCounts[2].Should().Be(1); + afterChedRelCounts[2].Should().Be(0); + + // unlinked ched + beforeLinkChedRelCounts[3].Should().Be(0); + afterChedRelCounts[3].Should().Be(0); + } + + [Fact] + public async Task LinkMovement_ExistingRequest_IncludesFieldsOfInterest_RemovedSomeItems_RemovesNotificationLinks() + { + // Arrange + var testData = await AddTestData(4, 1, 0); + var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); + + // establish existing links + var movementCtx = CreateMovementContextWithItems(testData.Movements[0], [], [testData.Cheds[0], testData.Cheds[1], testData.Cheds[2]]); + var prelink = await sut.Link(movementCtx); + var beforeLinkChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // Act + movementCtx = CreateMovementContextWithItems(prelink.Movements[0], [testData.Cheds[0], testData.Cheds[1], testData.Cheds[2]], [testData.Cheds[0]]); + var linkResult = await sut.Link(movementCtx); + + // Assert + linkResult.Should().NotBeNull(); + linkResult.Outcome.Should().Be(LinkOutcome.Linked); + linkResult.Notifications.Count.Should().Be(1); + linkResult.Movements.Count.Should().Be(1); + + var afterChedRelCounts = testData.Cheds.Select(c => + dbContext.Notifications.Single(x => x.Id == c.Id).Relationships.Movements.Data.Count).ToList(); + + // unchanged ched + beforeLinkChedRelCounts[0].Should().Be(1); + afterChedRelCounts[0].Should().Be(1); + + // removed cheds + beforeLinkChedRelCounts[1].Should().Be(1); + afterChedRelCounts[1].Should().Be(0); + beforeLinkChedRelCounts[2].Should().Be(1); + afterChedRelCounts[2].Should().Be(0); + + // unlinked ched + beforeLinkChedRelCounts[3].Should().Be(0); + afterChedRelCounts[3].Should().Be(0); + } + [Fact] public async Task LinkMovement_ExistingRequest_NoFieldsOfInterest_NoMatchingTriggered() { @@ -81,7 +385,7 @@ public async Task LinkMovement_ExistingRequest_NoFieldsOfInterest_NoMatchingTrig var testData = await AddTestData(); var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); - var movementCtx = CreateMovementContext(testData.Movements[0], [testData.Cheds[0]], true, false); + var movementCtx = CreateMovementContext(testData.Movements[0], [testData.Cheds[0]], true, true); // Act var linkResult = await sut.Link(movementCtx); @@ -100,7 +404,7 @@ public async Task LinkMovement_NewRequest_MatchingCHED_AddsAllToLinkResult() var testData = await AddTestData(); var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); - var movementCtx = CreateMovementContext(null, [testData.Cheds[0]], true, true); + var movementCtx = CreateMovementContext(null, [testData.Cheds[0]], true, false); // Act var linkResult = await sut.Link(movementCtx); @@ -119,7 +423,7 @@ public async Task LinkMovement_NewRequest_MultipleMatchingCHEDs_AddsAllToLinkRes var testData = await AddTestData(2, 1, 2); var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); - var movementCtx = CreateMovementContext(null, [testData.Cheds[0], testData.Cheds[1]], false, false); + var movementCtx = CreateMovementContext(null, [testData.Cheds[0], testData.Cheds[1]], false, true); // Act var linkResult = await sut.Link(movementCtx); @@ -136,7 +440,7 @@ public async Task LinkMovement_NewRequest_NoMatchingCHEDs_NoMatchingTriggered() { // Arrange var sut = new LinkingService(dbContext, linkingMetrics, NullLogger.Instance); - var movementCtx = CreateMovementContext(null, [null], true, true); + var movementCtx = CreateMovementContext(null, [null], true, false); // Act var linkResult = await sut.Link(movementCtx); @@ -279,7 +583,93 @@ public async Task LinkNotification_NewNotification_NoMatchingMRNs_NoMatchingTrig linkResult.Movements.Count.Should().Be(0); } - private MovementLinkContext CreateMovementContext(Movement? movement, List cheds, bool createExistingMovement, bool fieldsOfInterest) + private MovementLinkContext CreateMovementContextWithItems(Movement? movement, List existingCheds, List receivedCheds) + { + var entryReference = movement != null ? movement.EntryReference : $"TEST{GenerateRandomReference()}"; + var etag = movement != null ? movement._Etag : string.Empty; + var existingChedReferences = existingCheds + .Select(x => x != null ? x._MatchReference : $"{GenerateRandomReference()}") + .Select(y => int.Parse(y)).ToList(); + var receivedChedReferences = receivedCheds + .Select(x => x != null ? x._MatchReference : $"{GenerateRandomReference()}") + .Select(y => int.Parse(y)).ToList(); + + var mov = new Movement + { + Id = entryReference, + EntryReference = entryReference, + _Etag = etag, + Items = receivedChedReferences.Select(x => new Items + { + Documents = [ new Document { DocumentReference = GenerateDocumentReference(x) } ] + }).ToList(), + ClearanceRequests = new() + }; + + var existingMovement = new Movement + { + Id = entryReference, + EntryReference = entryReference, + Items = existingChedReferences.Select(x => new Items + { + Documents = [ new Document { DocumentReference = GenerateDocumentReference(x) } ] + }).ToList(), + ClearanceRequests = new() + }; + + var changeSet = mov.GenerateChangeSet(existingMovement); + var output = LinkContext.ForMovement(mov, changeSet); + + return output; + } + + private MovementLinkContext CreateMovementContextWithDocuments(Movement? movement, List existingCheds, List receivedCheds) + { + var entryReference = movement != null ? movement.EntryReference : $"TEST{GenerateRandomReference()}"; + var etag = movement != null ? movement._Etag : string.Empty; + var existingChedReferences = existingCheds + .Select(x => x != null ? x._MatchReference : $"{GenerateRandomReference()}") + .Select(y => int.Parse(y)).ToList(); + var receivedChedReferences = receivedCheds + .Select(x => x != null ? x._MatchReference : $"{GenerateRandomReference()}") + .Select(y => int.Parse(y)).ToList(); + + var mov = new Movement + { + Id = entryReference, + EntryReference = entryReference, + _Etag = etag, + Items = [ + new Items + { + Documents = receivedChedReferences + .Select(x => new Document { DocumentReference = GenerateDocumentReference(x)}) + .ToArray() + }], + ClearanceRequests = new() + }; + + var existingMovement = new Movement + { + Id = entryReference, + EntryReference = entryReference, + Items = [ + new Items + { + Documents = existingChedReferences + .Select(x => new Document { DocumentReference = GenerateDocumentReference(x)}) + .ToArray() + }], + ClearanceRequests = new() + }; + + var changeSet = mov.GenerateChangeSet(existingMovement); + var output = LinkContext.ForMovement(mov, changeSet); + + return output; + } + + private MovementLinkContext CreateMovementContext(Movement? movement, List cheds, bool createExistingMovement, bool existingDocs, bool newDocs = true) { var entryReference = movement != null ? movement.EntryReference : $"TEST{GenerateRandomReference()}"; var etag = movement != null ? movement._Etag : string.Empty; @@ -294,7 +684,9 @@ private MovementLinkContext CreateMovementContext(Movement? movement, List new Items { - Documents = [ new Document { DocumentReference = GenerateDocumentReference(x) } ] + Documents = newDocs + ? [ new Document { DocumentReference = GenerateDocumentReference(x) } ] + : [] }).ToList(), ClearanceRequests = new() }; @@ -306,9 +698,9 @@ private MovementLinkContext CreateMovementContext(Movement? movement, List new Items { - Documents = fieldsOfInterest - ? [] - : [ new Document { DocumentReference = GenerateDocumentReference(x) } ] + Documents = existingDocs + ? [ new Document { DocumentReference = GenerateDocumentReference(x) } ] + : [] }).ToList(), ClearanceRequests = new() } : null; diff --git a/Btms.Business/Pipelines/PreProcessing/MovementPreProcessor.cs b/Btms.Business/Pipelines/PreProcessing/MovementPreProcessor.cs index 9f39852b..5cfb63a4 100644 --- a/Btms.Business/Pipelines/PreProcessing/MovementPreProcessor.cs +++ b/Btms.Business/Pipelines/PreProcessing/MovementPreProcessor.cs @@ -12,7 +12,6 @@ public class MovementPreProcessor(IMongoDbContext dbContext, ILogger> Process(PreProcessingContext preProcessingContext) { - var internalClearanceRequest = AlvsClearanceRequestMapper.Map(preProcessingContext.Message); var movement = BuildMovement(internalClearanceRequest); var existingMovement = await dbContext.Movements.Find(movement.Id!); @@ -35,7 +34,6 @@ public async Task> Process(PreProcessingContext> Process(PreProcessingContext Link(LinkContext linkContext, CancellationToken ca logger.LinkNotAttempted(linkContext.GetType().Name, linkContext.GetIdentifiers()); return new LinkResult(LinkOutcome.NotLinked); } + + await CheckMovementForRemovedLinks(movementLinkContext, cancellationToken); result = await FindMovementLinks(movementLinkContext.PersistedMovement, cancellationToken); break; @@ -70,7 +73,6 @@ public async Task Link(LinkContext linkContext, CancellationToken ca default: throw new ArgumentException("context type not supported"); } - if (result.Outcome == LinkOutcome.NotLinked) { logger.LinkNotFound(linkContext.GetType().Name, linkContext.GetIdentifiers()); @@ -83,9 +85,9 @@ public async Task Link(LinkContext linkContext, CancellationToken ca metrics.Linked(result.Notifications.Count); using var transaction = await dbContext.StartTransaction(cancellationToken); - foreach (var notification in result.Notifications) + foreach (var movement in result.Movements) { - foreach (var movement in result.Movements) + foreach (var notification in result.Notifications) { notification.AddRelationship(new TdmRelationshipObject { @@ -129,11 +131,50 @@ await dbContext.Notifications.Update(notification, notification._Etag, transacti } } - - return result; } + private async Task CheckMovementForRemovedLinks(MovementLinkContext linkContext, + CancellationToken cancellationToken = default) + { + var jp = JsonPath.Parse($"$.{nameof(Movement._MatchReferences)}"); + var pathResult = jp.Evaluate(linkContext!.ChangeSet?.Previous); + + var chedRefs = pathResult?.Matches? + .Select(m => m.Value)? + .SelectMany(x => x!.AsArray()) + .Select(x => $"{x!.AsValue()}").ToList(); + + if (chedRefs?.Count > 0) + { + var removedRefs = chedRefs.Select(x => linkContext.PersistedMovement._MatchReferences.Contains(x) == false); + if (removedRefs.Any()) + { + foreach (var chedRef in chedRefs) + { + await RemoveMovementLinkFromNotification(linkContext.PersistedMovement.Id, chedRef, cancellationToken); + } + } + } + } + + private async Task RemoveMovementLinkFromNotification(string? movementId, string chedRef, CancellationToken cancellationToken = default) + { + var notification = dbContext.Notifications.SingleOrDefault(x => x._MatchReference == chedRef); + + if (notification != null) + { + var relationshipLink = notification.Relationships.Movements.Data? + .SingleOrDefault(x => x.Id == movementId && x.Type == "movements"); + + if (relationshipLink != null) + { + notification.RemoveRelationship(relationshipLink); + await dbContext.Notifications.Update(notification, notification._Etag); + } + } + } + private static bool ShouldLinkMovement(ChangeSet? changeSet) { return changeSet is null || changeSet.HasDocumentsChanged(); @@ -194,4 +235,20 @@ public static bool HasDocumentsChanged(this ChangeSet changeSet) .Any(x => ItemsRegex().IsMatch(x.Path.ToString()) || DocumentsRegex().IsMatch(x.Path.ToString())); } + + public static bool LinksToRemove(this ChangeSet changeSet) + { + return changeSet.JsonPatch.Operations + .Any(x => ItemsRegex().IsMatch(x.Path.ToString()) + || DocumentsRegex().IsMatch(x.Path.ToString())); + } + + private static void TidyRemovedLinks(this MovementLinkContext linkContext) + { + var changeSet = linkContext.ChangeSet; + changeSet!.JsonPatch.Operations + .Any(x => ItemsRegex().IsMatch(x.Path.ToString()) + || DocumentsRegex().IsMatch(x.Path.ToString())); + bool derp = changeSet is null || changeSet.HasDocumentsChanged(); + } } \ No newline at end of file diff --git a/Btms.Model/Auditing/AuditEntry.cs b/Btms.Model/Auditing/AuditEntry.cs index 7eb9f922..455119e1 100644 --- a/Btms.Model/Auditing/AuditEntry.cs +++ b/Btms.Model/Auditing/AuditEntry.cs @@ -116,6 +116,19 @@ public static AuditEntry CreateLinked(string id, int version) Status = "Linked" }; } + + public static AuditEntry CreateUnlinked(string id, int version, DateTime? lastUpdated) + { + return new AuditEntry + { + Id = id, + Version = version, + CreatedSource = lastUpdated, + CreatedBy = CreatedBySystem, + CreatedLocal = DateTime.UtcNow, + Status = "Unlinked" + }; + } public static AuditEntry CreateMatch(string id, int version) { diff --git a/Btms.Model/ChangeLog/ChangeSet.cs b/Btms.Model/ChangeLog/ChangeSet.cs index 2f9d5f98..2d9a6a5f 100644 --- a/Btms.Model/ChangeLog/ChangeSet.cs +++ b/Btms.Model/ChangeLog/ChangeSet.cs @@ -6,9 +6,9 @@ namespace Btms.Model.ChangeLog; -public class ChangeSet(JsonPatch jsonPatch) +public class ChangeSet(JsonPatch jsonPatch, JsonNode jsonNodePrevious) { - private static JsonSerializerOptions _jsonOptions = new() + private static readonly JsonSerializerOptions _jsonOptions = new() { TypeInfoResolver = new ChangeSetTypeInfoResolver(), PropertyNameCaseInsensitive = true, @@ -17,15 +17,17 @@ public class ChangeSet(JsonPatch jsonPatch) public JsonPatch JsonPatch { get; } = jsonPatch; + public JsonNode Previous { get; } = jsonNodePrevious; + public static ChangeSet CreateChangeSet(T current, T previous) { var previousNode = JsonNode.Parse(previous.ToJsonString(_jsonOptions)); var currentNode = JsonNode.Parse(current.ToJsonString(_jsonOptions)); var diff = previousNode.CreatePatch(currentNode); - + //exclude fields from patch, like _ts, audit entries etc var operations = diff.Operations.Where(x => !x.Path.ToString().Contains("_ts")); - return new ChangeSet(new JsonPatch(operations)); + return new ChangeSet(new JsonPatch(operations), previousNode!); } } \ No newline at end of file diff --git a/Btms.Model/Ipaffs/ImportNotification.cs b/Btms.Model/Ipaffs/ImportNotification.cs index c9ac2a02..61f3f5f1 100644 --- a/Btms.Model/Ipaffs/ImportNotification.cs +++ b/Btms.Model/Ipaffs/ImportNotification.cs @@ -147,6 +147,24 @@ public void AddRelationship(TdmRelationshipObject relationship) AuditEntries.Add(AuditEntry.CreateLinked(string.Empty, Version.GetValueOrDefault())); } } + + public void RemoveRelationship(RelationshipDataItem relationship) + { + var unlinked = false; + + if (Relationships.Movements.Data.Contains(relationship)) + { + Relationships.Movements.Data.Remove(relationship); + unlinked = true; + } + + Relationships.Movements.Matched = Relationships.Movements.Data.TrueForAll(x => x.Matched.GetValueOrDefault()); + + if (unlinked) + { + AuditEntries.Add(AuditEntry.CreateUnlinked(string.Empty, Version.GetValueOrDefault(), UpdatedSource)); + } + } public void Changed(AuditEntry auditEntry) { diff --git a/Btms.Model/Movement.cs b/Btms.Model/Movement.cs index 0b70088e..0e4d7667 100644 --- a/Btms.Model/Movement.cs +++ b/Btms.Model/Movement.cs @@ -66,7 +66,6 @@ public class Movement : IMongoIdentifiable, IDataEntity, IAuditable [BsonElement("_matchReferences")] - [ChangeSetIgnore] public List _MatchReferences { get