diff --git a/src/AssociationRegistry.Admin.ProjectionHost/Projections/Detail/BeheerVerenigingDetailProjection.cs b/src/AssociationRegistry.Admin.ProjectionHost/Projections/Detail/BeheerVerenigingDetailProjection.cs index 35fcc2924..70fdd2fb3 100644 --- a/src/AssociationRegistry.Admin.ProjectionHost/Projections/Detail/BeheerVerenigingDetailProjection.cs +++ b/src/AssociationRegistry.Admin.ProjectionHost/Projections/Detail/BeheerVerenigingDetailProjection.cs @@ -37,10 +37,10 @@ await Update(@event.Data.Moedervereniging.VCode, @event, ops, public async Task Project(IEvent @event, IDocumentOperations ops) { var updateDocs = Enumerable.Empty().ToList(); - var afdeling = (await ops.LoadAsync(@event.StreamKey!))!; + var vereniging = (await ops.LoadAsync(@event.StreamKey!))!; var gerelateerdeVerenigingen = ops.Query() - .Where(d => d.Relaties.Any(r => r.AndereVereniging.VCode == afdeling.VCode)) + .Where(d => d.Relaties.Any(r => r.AndereVereniging.VCode == vereniging.VCode)) .ToList(); foreach (var gerelateerdeVereniging in gerelateerdeVerenigingen) @@ -61,9 +61,9 @@ public async Task Project(IEvent @event, IDocumentOperations updateDocs.Add(gerelateerdeVereniging); } - BeheerVerenigingDetailProjector.Apply(@event, afdeling); - BeheerVerenigingDetailProjector.UpdateMetadata(@event, afdeling); - updateDocs.Add(afdeling); + BeheerVerenigingDetailProjector.Apply(@event, vereniging); + BeheerVerenigingDetailProjector.UpdateMetadata(@event, vereniging); + updateDocs.Add(vereniging); ops.StoreObjects(updateDocs); } diff --git a/src/AssociationRegistry.Public.ProjectionHost/Infrastructure/Extensions/IEnumerableExtensions.cs b/src/AssociationRegistry.Public.ProjectionHost/Infrastructure/Extensions/IEnumerableExtensions.cs new file mode 100644 index 000000000..f862e1d5b --- /dev/null +++ b/src/AssociationRegistry.Public.ProjectionHost/Infrastructure/Extensions/IEnumerableExtensions.cs @@ -0,0 +1,24 @@ +namespace AssociationRegistry.Public.ProjectionHost.Infrastructure.Extensions; + +public static class IEnumerableExtensions +{ + public static IEnumerable UpdateSingle(this IEnumerable collection, Func identityFunc, Func update) + { + var array = collection as T[] ?? collection.ToArray(); + var objectToUpdate = array.Single(identityFunc); + var updatedObject = update(objectToUpdate); + return array + .Where(t1 => !identityFunc(t1)) + .Append(updatedObject); + } + + public static IEnumerable UpdateSingleWith(this IEnumerable collection, Func identityFunc, Func update) + { + var array = collection as T[] ?? collection.ToArray(); + var objectToUpdate = array.Single(identityFunc); + var updatedObject = update(objectToUpdate); + return array + .Where(t1 => !identityFunc(t1)) + .Append(updatedObject); + } +} diff --git a/src/AssociationRegistry.Public.ProjectionHost/Projections/Detail/PubliekVerenigingDetailProjection.cs b/src/AssociationRegistry.Public.ProjectionHost/Projections/Detail/PubliekVerenigingDetailProjection.cs index b87aced0a..ad9253f18 100644 --- a/src/AssociationRegistry.Public.ProjectionHost/Projections/Detail/PubliekVerenigingDetailProjection.cs +++ b/src/AssociationRegistry.Public.ProjectionHost/Projections/Detail/PubliekVerenigingDetailProjection.cs @@ -1,6 +1,7 @@ namespace AssociationRegistry.Public.ProjectionHost.Projections.Detail; using Events; +using Infrastructure.Extensions; using Marten; using Marten.Events; using Marten.Events.Projections; @@ -35,7 +36,39 @@ await Update(@event.Data.Moedervereniging.VCode, @event, ops, } public async Task Project(IEvent @event, IDocumentOperations ops) - => await Update(@event, ops, PubliekVerenigingDetailProjector.Apply); + { + var updateDocs = Enumerable.Empty().ToList(); + var vereniging = (await ops.LoadAsync(@event.StreamKey!))!; + + var gerelateerdeVerenigingen = ops.Query() + .Where(d => d.Relaties.Any(r => r.AndereVereniging.VCode == vereniging.VCode)) + .ToList(); + + foreach (var gerelateerdeVereniging in gerelateerdeVerenigingen) + { + gerelateerdeVereniging.Relaties = gerelateerdeVereniging.Relaties + .UpdateSingle( + identityFunc: relatie + => relatie.AndereVereniging.VCode == @event.Data.VCode, + update: r => + { + r.AndereVereniging.Naam = @event.Data.Naam; + + return r; + }) + .ToArray(); + + PubliekVerenigingDetailProjector.UpdateMetadata(@event, gerelateerdeVereniging); + updateDocs.Add(gerelateerdeVereniging); + } + + + PubliekVerenigingDetailProjector.Apply(@event, vereniging); + PubliekVerenigingDetailProjector.UpdateMetadata(@event, vereniging); + + updateDocs.Add(vereniging); + ops.StoreObjects(updateDocs); + } public async Task Project(IEvent @event, IDocumentOperations ops) => await Update(@event, ops, PubliekVerenigingDetailProjector.Apply); diff --git a/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/ElasticEventProjection.cs b/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/ElasticEventProjection.cs index 841ee3ef5..c52f468b7 100644 --- a/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/ElasticEventProjection.cs +++ b/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/ElasticEventProjection.cs @@ -136,13 +136,22 @@ public async Task Handle(EventEnvelope message) - => await _elasticRepository.UpdateAsync( + { + await _elasticRepository.UpdateAsync( message.Data.VCode, new VerenigingZoekDocument { Naam = message.Data.Naam, } ); + await _elasticRepository.UpdateNaamInRelaties( + new VerenigingZoekDocument + { + VCode = message.Data.VCode, + Naam = message.Data.Naam, + } + ); + } public async Task Handle(EventEnvelope message) => await _elasticRepository.UpdateAsync( diff --git a/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/ElasticRepository.cs b/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/ElasticRepository.cs index 4fd63a64b..1ccb64a52 100644 --- a/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/ElasticRepository.cs +++ b/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/ElasticRepository.cs @@ -120,4 +120,56 @@ public async Task AppendRelatie(string id, Relatie relatie) if (!response.IsValid) throw new IndexDocumentFailed(response.DebugInformation); } + + public async Task UpdateNaamInRelaties(VerenigingZoekDocument documentToUpdate) + { + var matches = _elasticClient.Search(descriptor => descriptor.Query( + q => q + .Nested(n => n + .Path(document => document.Relaties) + .Query(nq => nq + .Nested(n => n + .Path(doc => doc.Relaties.First() + .AndereVereniging) + .Query(nq2 => nq2 + .Term(m => m + .Field( + doc => doc + .Relaties + .First() + .AndereVereniging + .VCode) + .Value( + documentToUpdate + .VCode))))) + ) + )); + + var response = await _elasticClient.UpdateByQueryAsync( + u => u + .Query( + q => q + .Nested(n => n + .Path(document => document.Relaties) + .Query(nq => nq + .Nested(n => n + .Path(doc => doc.Relaties.First().AndereVereniging) + .Query(nq2 => nq2 + .Term(m => m + .Field(doc => doc.Relaties.First().AndereVereniging.VCode) + .Value(documentToUpdate.VCode))))) + ) + ) + .Script(s => s + .Source("for(r in ctx._source.relaties){" + + " if(r.andereVereniging.vCode == params.gewijzigdeVereniging.vCode){" + + " r.andereVereniging.naam = params.gewijzigdeVereniging.naam" + + " }" + + "}") + .Params(objects => objects.Add(key: "gewijzigdeVereniging", documentToUpdate))) + ); + + if (!response.IsValid) + throw new IndexDocumentFailed(response.DebugInformation); + } } diff --git a/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/IElasticRepository.cs b/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/IElasticRepository.cs index be49f923c..ab3217b48 100644 --- a/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/IElasticRepository.cs +++ b/src/AssociationRegistry.Public.ProjectionHost/Projections/Search/IElasticRepository.cs @@ -17,4 +17,5 @@ Task IndexAsync(TDocument document) Task UpdateLocatie(string id, VerenigingZoekDocument.Locatie locatie); Task Remove(string id); Task AppendRelatie(string id, Relatie relatie); + Task UpdateNaamInRelaties(VerenigingZoekDocument documentToUpdate); } diff --git a/test/AssociationRegistry.Test.Public.Api/Fixtures/GivenEvents/GivenEventsFixture.cs b/test/AssociationRegistry.Test.Public.Api/Fixtures/GivenEvents/GivenEventsFixture.cs index 76265aa48..0a134dda9 100644 --- a/test/AssociationRegistry.Test.Public.Api/Fixtures/GivenEvents/GivenEventsFixture.cs +++ b/test/AssociationRegistry.Test.Public.Api/Fixtures/GivenEvents/GivenEventsFixture.cs @@ -20,6 +20,7 @@ public class GivenEventsFixture : PublicApiFixture public readonly V015_VerenigingMetRechtspersoonlijkheidWerdGeregistreerd_With_WijzigBasisgegevens V015VerenigingMetRechtspersoonlijkheidWerdGeregistreerdWithWijzigBasisgegevens = new(); public readonly V016_VerenigingWerdGestopt V016VerenigingWerdGestopt = new(); public readonly V017_VerenigingMetRechtspersoonlijkheidWerdGeregistreerd_With_WijzigMaatschappelijkeZetel_Scenario V017VerenigingMetRechtspersoonlijkheidWerdGeregistreerdWithWijzigMaatschappelijkeZetelScenario = new(); + public readonly V018_AfdelingWerdGeregistreerd_MetBestaandeMoeder_VoorNaamWerdGewijzigd V018AfdelingWerdGeregistreerdMetBestaandeMoederVoorNaamWerdGewijzigd = new(); private IEnumerable Scenarios => new IScenario[] @@ -40,8 +41,10 @@ private IEnumerable Scenarios V015VerenigingMetRechtspersoonlijkheidWerdGeregistreerdWithWijzigBasisgegevens, V016VerenigingWerdGestopt, V017VerenigingMetRechtspersoonlijkheidWerdGeregistreerdWithWijzigMaatschappelijkeZetelScenario, + V018AfdelingWerdGeregistreerdMetBestaandeMoederVoorNaamWerdGewijzigd, }; + public override async Task InitializeAsync() { foreach (var scenario in Scenarios) diff --git a/test/AssociationRegistry.Test.Public.Api/Fixtures/GivenEvents/Scenarios/V018_AfdelingWerdGeregistreerd_MetBestaandeMoeder_VoorNaamWerdGewijzigd.cs b/test/AssociationRegistry.Test.Public.Api/Fixtures/GivenEvents/Scenarios/V018_AfdelingWerdGeregistreerd_MetBestaandeMoeder_VoorNaamWerdGewijzigd.cs new file mode 100644 index 000000000..586fe1994 --- /dev/null +++ b/test/AssociationRegistry.Test.Public.Api/Fixtures/GivenEvents/Scenarios/V018_AfdelingWerdGeregistreerd_MetBestaandeMoeder_VoorNaamWerdGewijzigd.cs @@ -0,0 +1,72 @@ +namespace AssociationRegistry.Test.Public.Api.Fixtures.GivenEvents.Scenarios; + +using AssociationRegistry.Framework; +using AutoFixture; +using Events; +using EventStore; +using Framework; +using Vereniging; + +public class V018_AfdelingWerdGeregistreerd_MetBestaandeMoeder_VoorNaamWerdGewijzigd : IScenario +{ + public readonly VerenigingMetRechtspersoonlijkheidWerdGeregistreerd MoederWerdGeregistreerd; + public readonly AfdelingWerdGeregistreerd AfdelingWerdGeregistreerd; + public readonly NaamWerdGewijzigd NaamWerdGewijzigd; + public readonly CommandMetadata Metadata; + + public V018_AfdelingWerdGeregistreerd_MetBestaandeMoeder_VoorNaamWerdGewijzigd() + { + var fixture = new Fixture().CustomizePublicApi(); + + VCodeMoeder = "V9999018"; + NaamMoeder = "De coolste moeder"; + + VCode = VCode.Create("V9999019"); + NaamAfdeling = "De coolste afdeling"; + + MoederWerdGeregistreerd = fixture.Create() with + { + VCode = VCodeMoeder, + Naam = NaamMoeder, + Rechtsvorm = "SVON" + }; + + KboNummerMoeder = MoederWerdGeregistreerd.KboNummer; + + AfdelingWerdGeregistreerd = fixture.Create() with + { + VCode = VCode, + Moedervereniging = new AfdelingWerdGeregistreerd.MoederverenigingsData(KboNummerMoeder, VCodeMoeder, NaamMoeder), + Locaties = Array.Empty(), + Naam = "De minder coole afdeling", + KorteNaam = string.Empty, + Startdatum = null, + KorteBeschrijving = string.Empty, + Contactgegevens = Array.Empty(), + Vertegenwoordigers = Array.Empty(), + HoofdactiviteitenVerenigingsloket = Array.Empty(), + }; + + NaamWerdGewijzigd = fixture.Create() with + { + VCode = VCode, + Naam = NaamAfdeling + }; + + Metadata = fixture.Create() with { ExpectedVersion = null }; + } + + public string KboNummerMoeder { get; set; } + public string NaamMoeder { get; set; } + public string NaamAfdeling { get; set; } + public string VCodeMoeder { get; set; } + public VCode VCode { get; set; } + public StreamActionResult Result { get; set; } = null!; + + public IEvent[] GetEvents() + => new IEvent[] + { MoederWerdGeregistreerd, AfdelingWerdGeregistreerd, NaamWerdGewijzigd }; + + public CommandMetadata GetCommandMetadata() + => Metadata; +} diff --git a/test/AssociationRegistry.Test.Public.Api/When_Retrieving_Detail/Given_MoederWerdGeregistreerd_And_Then_AfdelingWerdGeregistreerd_AndThen_NaamWerdGewijzigd.cs b/test/AssociationRegistry.Test.Public.Api/When_Retrieving_Detail/Given_MoederWerdGeregistreerd_And_Then_AfdelingWerdGeregistreerd_AndThen_NaamWerdGewijzigd.cs new file mode 100644 index 000000000..8ffb53f92 --- /dev/null +++ b/test/AssociationRegistry.Test.Public.Api/When_Retrieving_Detail/Given_MoederWerdGeregistreerd_And_Then_AfdelingWerdGeregistreerd_AndThen_NaamWerdGewijzigd.cs @@ -0,0 +1,67 @@ +namespace AssociationRegistry.Test.Public.Api.When_Retrieving_Detail; + +using Fixtures; +using Fixtures.GivenEvents; +using Fixtures.GivenEvents.Scenarios; +using FluentAssertions; +using Microsoft.Net.Http.Headers; +using System.Net; +using templates; +using Test.Framework; +using Xunit; +using Xunit.Categories; + +[Collection(nameof(PublicApiCollection))] +[Category("AdminApi")] +[IntegrationTest] +public class Given_MoederWerdGeregistreerd_And_Then_AfdelingWerdGeregistreerd_AndThen_NaamWerdGewijzigd +{ + private readonly PublicApiClient _apiClient; + private readonly V018_AfdelingWerdGeregistreerd_MetBestaandeMoeder_VoorNaamWerdGewijzigd _scenario; + + public Given_MoederWerdGeregistreerd_And_Then_AfdelingWerdGeregistreerd_AndThen_NaamWerdGewijzigd(GivenEventsFixture fixture) + { + _scenario = fixture.V018AfdelingWerdGeregistreerdMetBestaandeMoederVoorNaamWerdGewijzigd; + _apiClient = fixture.PublicApiClient; + } + + [Fact] + public async Task Then_we_get_a_successful_response_if_sequence_is_equal_or_greater_than_expected_sequence() + => (await _apiClient.GetDetail(_scenario.VCode)) + .Should().BeSuccessful(); + + [Fact] + public async Task Then_we_get_a_successful_response_if_no_sequence_provided() + => (await _apiClient.GetDetail(_scenario.VCode)) + .Should().BeSuccessful(); + + + [Fact] + public async Task Then_we_get_a_detail_afdeling_response() + { + var response = await _apiClient.GetDetail(_scenario.VCode); + var content = await response.Content.ReadAsStringAsync(); + + var expected = new DetailVerenigingResponseTemplate() + .FromEvent(_scenario.AfdelingWerdGeregistreerd) + .WithNaam(_scenario.NaamWerdGewijzigd.Naam) + .WithDatumLaatsteAanpassing(_scenario.Metadata.Tijdstip); + + content.Should().BeEquivalentJson(expected); + } + + [Fact] + public async Task Then_we_get_a_detail_moeder_response() + { + var response = await _apiClient.GetDetail(_scenario.MoederWerdGeregistreerd.VCode); + + var content = await response.Content.ReadAsStringAsync(); + + var expected = new DetailVerenigingResponseTemplate() + .FromEvent(_scenario.MoederWerdGeregistreerd) + .WithDatumLaatsteAanpassing(_scenario.Metadata.Tijdstip) + .HeeftAfdeling(_scenario.AfdelingWerdGeregistreerd.VCode, _scenario.NaamWerdGewijzigd.Naam); + + content.Should().BeEquivalentJson(expected); + } +} diff --git a/test/AssociationRegistry.Test.Public.Api/When_Searching/Given_MoederWerdGeregistreerd_And_Then_AfdelingWerdGeregistreerd_AndThen_NaamWerdGewijzigd.cs b/test/AssociationRegistry.Test.Public.Api/When_Searching/Given_MoederWerdGeregistreerd_And_Then_AfdelingWerdGeregistreerd_AndThen_NaamWerdGewijzigd.cs new file mode 100644 index 000000000..407f6d415 --- /dev/null +++ b/test/AssociationRegistry.Test.Public.Api/When_Searching/Given_MoederWerdGeregistreerd_And_Then_AfdelingWerdGeregistreerd_AndThen_NaamWerdGewijzigd.cs @@ -0,0 +1,67 @@ +namespace AssociationRegistry.Test.Public.Api.When_Searching; + +using Fixtures; +using Fixtures.GivenEvents; +using Fixtures.GivenEvents.Scenarios; +using FluentAssertions; +using templates; +using Test.Framework; +using Xunit; +using Xunit.Categories; + +[Collection(nameof(PublicApiCollection))] +[Category("AdminApi")] +[IntegrationTest] +public class Given_MoederWerdGeregistreerd_And_Then_AfdelingWerdGeregistreerd_AndThen_NaamWerdGewijzigd +{ + private readonly PublicApiClient _publicApiClient; + private readonly V018_AfdelingWerdGeregistreerd_MetBestaandeMoeder_VoorNaamWerdGewijzigd _scenario; + + public Given_MoederWerdGeregistreerd_And_Then_AfdelingWerdGeregistreerd_AndThen_NaamWerdGewijzigd(GivenEventsFixture fixture) + { + _scenario = fixture.V018AfdelingWerdGeregistreerdMetBestaandeMoederVoorNaamWerdGewijzigd; + _publicApiClient = fixture.PublicApiClient; + } + + [Fact] + public async Task Then_we_get_a_successful_response_for_dochter() + => (await _publicApiClient.Search(_scenario.VCode)).Should().BeSuccessful(); + + [Fact] + public async Task Then_we_retrieve_one_vereniging_matching_the_vCode_searched_for_dochter() + { + var response = await _publicApiClient.Search($"vCode:{_scenario.VCode}"); + var content = await response.Content.ReadAsStringAsync(); + + var goldenMaster = new ZoekVerenigingenResponseTemplate() + .FromQuery(_scenario.VCode) + .WithVereniging( + v => v + .FromEvent(_scenario.AfdelingWerdGeregistreerd) + .WithNaam(_scenario.NaamWerdGewijzigd.Naam) + ); + + content.Should().BeEquivalentJson(goldenMaster); + } + + [Fact] + public async Task Then_we_get_a_successful_response_for_moeder() + => (await _publicApiClient.Search(_scenario.VCodeMoeder)).Should().BeSuccessful(); + + [Fact] + public async Task Then_we_retrieve_one_vereniging_matching_the_vCode_searched_for_moeder() + { + var response = await _publicApiClient.Search($"vCode:{_scenario.VCodeMoeder}"); + var content = await response.Content.ReadAsStringAsync(); + + var goldenMaster = new ZoekVerenigingenResponseTemplate() + .FromQuery(_scenario.VCodeMoeder) + .WithVereniging( + v => v + .FromEvent(_scenario.MoederWerdGeregistreerd) + .HeeftAfdeling(_scenario.AfdelingWerdGeregistreerd.VCode, _scenario.NaamWerdGewijzigd.Naam) + ); + + content.Should().BeEquivalentJson(goldenMaster); + } +}