Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: bulk relink roadsegment streetname #1614

Merged
merged 13 commits into from
Feb 6, 2025
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class ChangeRoadSegmentAttributeRequest
public ICollection<EuropeanRoadNumber>? EuropeanRoads { get; set; }
public ICollection<NationalRoadNumber>? NationalRoads { get; set; }
public ICollection<ChangeRoadSegmentNumberedRoadAttribute>? NumberedRoads { get; set; }
public StreetNameLocalId? LeftSideStreetNameId { get; set; }
public StreetNameLocalId? RightSideStreetNameId { get; set; }
}

public class ChangeRoadSegmentNumberedRoadAttribute
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
namespace RoadRegistry.BackOffice.Api.RoadSegments.ChangeAttributes;

using System.Runtime.Serialization;
using Infrastructure;
using Infrastructure.Controllers.Attributes;
using Newtonsoft.Json;
using RoadRegistry.BackOffice.Api.Infrastructure;
using System.Runtime.Serialization;

public record ChangeAttributeParameters
{
Expand Down Expand Up @@ -73,6 +73,20 @@ public record ChangeAttributeParameters
[DataMember(Name = "GenummerdeWegen", Order = 9)]
[JsonProperty("genummerdeWegen")]
public ChangeAttributeNumberedRoad[] GenummerdeWegen { get; set; }

/// <summary>
/// De unieke en persistente identificator van de straatnaam aan de linkerzijde van het wegsegment of "niet van toepassing".
/// </summary>
[DataMember(Name = "LinkerstraatnaamId", Order = 10)]
[JsonProperty("linkerstraatnaamId")]
public string? LinkerstraatnaamId { get; set; }

/// <summary>
/// De unieke en persistente identificator van de straatnaam aan de rechterzijde van het wegsegment of "niet van toepassing".
/// </summary>
[DataMember(Name = "RechterstraatnaamId", Order = 11)]
[JsonProperty("rechterstraatnaamId")]
public string? RechterstraatnaamId { get; set; }
}

[DataContract(Name = "EuropeseWeg", Namespace = "")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace RoadRegistry.BackOffice.Api.RoadSegments.ChangeAttributes;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using RoadRegistry.BackOffice.Handlers.Extensions;

public class ChangeAttributeParametersValidator : AbstractValidator<ChangeAttributeParameters>
{
Expand All @@ -28,6 +29,8 @@ protected override bool PreValidate(ValidationContext<ChangeAttributeParameters>
&& context.InstanceToValidate.EuropeseWegen is null
&& context.InstanceToValidate.NationaleWegen is null
&& context.InstanceToValidate.GenummerdeWegen is null
&& context.InstanceToValidate.LinkerstraatnaamId is null
&& context.InstanceToValidate.RechterstraatnaamId is null
)
{
context.AddFailure(new ValidationFailure
Expand Down Expand Up @@ -127,6 +130,22 @@ public ChangeAttributeParametersValidator(EditorContext editorContext, IOrganiza
RuleForEach(x => x.GenummerdeWegen)
.SetValidator(new ChangeAttributeNumberedRoadValidator());
});

When(x => x.LinkerstraatnaamId is not null, () =>
{
RuleFor(x => x.LinkerstraatnaamId)
.Cascade(CascadeMode.Stop)
.MustBeValidStreetNameId(allowNotApplicable: true)
.WithProblemCode(ProblemCode.RoadSegment.StreetName.Left.NotValid);
});

When(x => x.RechterstraatnaamId is not null, () =>
{
RuleFor(x => x.RechterstraatnaamId)
.Cascade(CascadeMode.Stop)
.MustBeValidStreetNameId(allowNotApplicable: true)
.WithProblemCode(ProblemCode.RoadSegment.StreetName.Right.NotValid);
});
}

private Task<bool> BeExistingNonRemovedRoadSegment(int[] ids, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,40 @@ public ChangeRoadSegmentAttributesParameters GetExamples()
{
new()
{
Wegsegmenten = new[] { 481110, 481111 },
Wegsegmenten = [481110, 481111],
Wegbeheerder = "AWV112"
},
new()
{
Wegsegmenten = new[] { 481111 },
Wegsegmenten = [481111],
Wegsegmentstatus = RoadSegmentStatus.OutOfUse.ToDutchString(),
MorfologischeWegklasse = RoadSegmentMorphology.PrimitiveRoad.ToDutchString(),
Wegbeheerder = "AWV114",
EuropeseWegen = new[]
{
EuropeseWegen =
[
new ChangeAttributeEuropeanRoad
{
EuNummer = "E40"
}
},
NationaleWegen = new[]
{
],
NationaleWegen =
[
new ChangeAttributeNationalRoad
{
Ident2 = "N180"
}
},
GenummerdeWegen = new[]
{
],
GenummerdeWegen =
[
new ChangeAttributeNumberedRoad
{
Ident8 = "N0080001",
Richting = RoadSegmentNumberedRoadDirection.Forward.ToDutchString(),
Volgnummer = new RoadSegmentNumberedRoadOrdinal(2686).ToDutchString()
}
}
],
LinkerstraatnaamId = "https://data.vlaanderen.be/id/straatnaam/1",
RechterstraatnaamId = StreetNameLocalId.NotApplicable.ToDutchString()
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace RoadRegistry.BackOffice.Api.RoadSegments;
using System.Threading;
using System.Threading.Tasks;
using Abstractions.RoadSegments;
using BackOffice.Handlers.Extensions;
using BackOffice.Handlers.Sqs.RoadSegments;
using Be.Vlaanderen.Basisregisters.Api.Exceptions;
using Be.Vlaanderen.Basisregisters.Auth.AcmIdm;
Expand Down Expand Up @@ -138,6 +139,18 @@ ChangeRoadSegmentAttributesRequest TranslateParametersIntoTypedBackOfficeRequest
Ordinal = RoadSegmentNumberedRoadOrdinal.ParseUsingDutchName(numberedRoad.Volgnummer)
}).ToArray();
}

if (attributesChange.LinkerstraatnaamId is not null)
{
var identifier = attributesChange.LinkerstraatnaamId.GetIdentifierPartFromPuri();
roadSegment.LeftSideStreetNameId = StreetNameLocalId.ParseUsingDutchName(identifier);
}

if (attributesChange.RechterstraatnaamId is not null)
{
var identifier = attributesChange.RechterstraatnaamId.GetIdentifierPartFromPuri();
roadSegment.RightSideStreetNameId = StreetNameLocalId.ParseUsingDutchName(identifier);
}
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace RoadRegistry.BackOffice.Handlers.Sqs.Lambda.Handlers;
using Microsoft.Extensions.Logging;
using Microsoft.IO;
using Requests;
using StreetName;
using TicketingService.Abstractions;
using AddRoadSegmentToEuropeanRoad = BackOffice.Uploads.AddRoadSegmentToEuropeanRoad;
using AddRoadSegmentToNationalRoad = BackOffice.Uploads.AddRoadSegmentToNationalRoad;
Expand All @@ -29,6 +30,7 @@ public sealed class ChangeRoadSegmentAttributesSqsLambdaRequestHandler : SqsLamb
private readonly IChangeRoadNetworkDispatcher _changeRoadNetworkDispatcher;
private readonly EditorContext _editorContext;
private readonly IOrganizationCache _organizationCache;
private readonly IStreetNameClient _streetNameClient;

public ChangeRoadSegmentAttributesSqsLambdaRequestHandler(
SqsLambdaHandlerOptions options,
Expand All @@ -41,6 +43,7 @@ public ChangeRoadSegmentAttributesSqsLambdaRequestHandler(
RecyclableMemoryStreamManager manager,
FileEncoding fileEncoding,
IOrganizationCache organizationCache,
IStreetNameClient streetNameClient,
ILogger<ChangeRoadSegmentAttributesSqsLambdaRequestHandler> logger)
: base(
options,
Expand All @@ -53,6 +56,7 @@ public ChangeRoadSegmentAttributesSqsLambdaRequestHandler(
_changeRoadNetworkDispatcher = changeRoadNetworkDispatcher;
_editorContext = editorContext;
_organizationCache = organizationCache;
_streetNameClient = streetNameClient;
}

protected override async Task<object> InnerHandle(ChangeRoadSegmentAttributesSqsLambdaRequest request, CancellationToken cancellationToken)
Expand All @@ -65,9 +69,9 @@ await _changeRoadNetworkDispatcher.DispatchAsync(request, "Attributen wijzigen",

foreach (var change in request.Request.ChangeRequests)
{
var roadSegmentId = new RoadSegmentId(change.Id);
var roadSegmentId = change.Id;

var editorRoadSegment = await _editorContext.RoadSegments.IncludeLocalSingleOrDefaultAsync(x => x.Id == change.Id, cancellationToken);
var editorRoadSegment = await _editorContext.RoadSegments.IncludeLocalSingleOrDefaultAsync(x => x.Id == roadSegmentId, cancellationToken);
if (editorRoadSegment is null)
{
problems = problems.Add(new RoadSegmentNotFound(roadSegmentId));
Expand Down Expand Up @@ -97,7 +101,28 @@ await _changeRoadNetworkDispatcher.DispatchAsync(request, "Attributen wijzigen",
return new ChangeRoadSegmentAttributesResponse();
}

private async Task<(TranslatedChanges, Problems)> AppendChange(TranslatedChanges changes, Problems problems, RoadSegment roadSegment, ChangeRoadSegmentAttributeRequest change, CancellationToken cancellationToken)
private async Task<(TranslatedChanges, Problems)> AppendChange(
TranslatedChanges changes,
Problems problems,
RoadSegment roadSegment,
ChangeRoadSegmentAttributeRequest change,
CancellationToken cancellationToken)
{
(changes, problems) = await AppendAttributeChanges(changes, problems, roadSegment, change, cancellationToken);

changes = AppendEuropeanRoadChanges(changes, roadSegment, change.EuropeanRoads);
changes = AppendNationalRoadChanges(changes, roadSegment, change.NationalRoads);
changes = AppendNumberedRoadChanges(changes, roadSegment, change.NumberedRoads);

return (changes, problems);
}

private async Task<(TranslatedChanges changes, Problems problems)> AppendAttributeChanges(
TranslatedChanges changes,
Problems problems,
RoadSegment roadSegment,
ChangeRoadSegmentAttributeRequest change,
CancellationToken cancellationToken)
{
var maintenanceAuthority = change.MaintenanceAuthority;
if (maintenanceAuthority is not null)
Expand All @@ -106,37 +131,43 @@ await _changeRoadNetworkDispatcher.DispatchAsync(request, "Attributen wijzigen",
problems += maintenanceAuthorityProblems;
}

if (new object?[] { maintenanceAuthority, change.Morphology, change.Status, change.Category, change.AccessRestriction }.Any(x => x is not null))
{
if (change.Category is not null
&& roadSegment.AttributeHash.GeometryDrawMethod == RoadSegmentGeometryDrawMethod.Measured
&& RoadSegmentCategory.IsUpgraded(roadSegment.AttributeHash.Category))
if (new object?[]
{
var allowedCategories = RoadSegmentCategory.All.Except(RoadSegmentCategory.Obsolete).ToArray();
if (!allowedCategories.Contains(change.Category))
{
problems += new RoadSegmentCategoryNotChangedBecauseCurrentIsNewerVersion(roadSegment.Id);
}
}
maintenanceAuthority, change.Morphology, change.Status, change.Category, change.AccessRestriction,
change.LeftSideStreetNameId, change.RightSideStreetNameId,
}.All(x => x is null))
{
return (changes, problems);
}

changes = changes.AppendChange(new ModifyRoadSegmentAttributes(RecordNumber.Initial, roadSegment.Id, roadSegment.AttributeHash.GeometryDrawMethod)
if (change.Category is not null
&& roadSegment.AttributeHash.GeometryDrawMethod == RoadSegmentGeometryDrawMethod.Measured
&& RoadSegmentCategory.IsUpgraded(roadSegment.AttributeHash.Category))
{
var allowedCategories = RoadSegmentCategory.All.Except(RoadSegmentCategory.Obsolete).ToArray();
if (!allowedCategories.Contains(change.Category))
{
MaintenanceAuthority = maintenanceAuthority,
Morphology = change.Morphology,
Status = change.Status,
Category = change.Category,
AccessRestriction = change.AccessRestriction
});
problems += new RoadSegmentCategoryNotChangedBecauseCurrentIsNewerVersion(roadSegment.Id);
}
}

changes = AppendChange(changes, roadSegment, change.EuropeanRoads);
changes = AppendChange(changes, roadSegment, change.NationalRoads);
changes = AppendChange(changes, roadSegment, change.NumberedRoads);
problems = await ValidateStreetNames(change, problems, cancellationToken);

changes = changes.AppendChange(new ModifyRoadSegmentAttributes(RecordNumber.Initial, roadSegment.Id, roadSegment.AttributeHash.GeometryDrawMethod)
{
MaintenanceAuthority = maintenanceAuthority,
Morphology = change.Morphology,
Status = change.Status,
Category = change.Category,
AccessRestriction = change.AccessRestriction,
LeftSideStreetNameId = change.LeftSideStreetNameId,
RightSideStreetNameId = change.RightSideStreetNameId
});

return (changes, problems);
}

private TranslatedChanges AppendChange(TranslatedChanges changes, RoadSegment roadSegment, ICollection<EuropeanRoadNumber>? europeanRoadNumbers)
private TranslatedChanges AppendEuropeanRoadChanges(TranslatedChanges changes, RoadSegment roadSegment, ICollection<EuropeanRoadNumber>? europeanRoadNumbers)
{
if (europeanRoadNumbers is null)
{
Expand Down Expand Up @@ -190,7 +221,7 @@ private TranslatedChanges AppendChange(TranslatedChanges changes, RoadSegment ro
return changes;
}

private static TranslatedChanges AppendChange(TranslatedChanges changes, RoadSegment roadSegment, ICollection<NationalRoadNumber>? nationalRoadNumbers)
private static TranslatedChanges AppendNationalRoadChanges(TranslatedChanges changes, RoadSegment roadSegment, ICollection<NationalRoadNumber>? nationalRoadNumbers)
{
if (nationalRoadNumbers is null)
{
Expand Down Expand Up @@ -244,7 +275,7 @@ private static TranslatedChanges AppendChange(TranslatedChanges changes, RoadSeg
return changes;
}

private TranslatedChanges AppendChange(TranslatedChanges changes, RoadSegment roadSegment, ICollection<ChangeRoadSegmentNumberedRoadAttribute>? numberedRoads)
private TranslatedChanges AppendNumberedRoadChanges(TranslatedChanges changes, RoadSegment roadSegment, ICollection<ChangeRoadSegmentNumberedRoadAttribute>? numberedRoads)
{
if (numberedRoads is null)
{
Expand Down Expand Up @@ -329,4 +360,51 @@ private TranslatedChanges AppendChange(TranslatedChanges changes, RoadSegment ro

return (organizationId, problems);
}

private async Task<Problems> ValidateStreetNames(
ChangeRoadSegmentAttributeRequest change,
Problems problems,
CancellationToken cancellationToken)
{
try
{
string[] activeStreetNameStatus = [StreetNameStatus.Current, StreetNameStatus.Proposed];

if (change.LeftSideStreetNameId is not null)
{
var streetName = await _streetNameClient.GetAsync(change.LeftSideStreetNameId.Value, cancellationToken);
if (streetName is null)
{
return problems.Add(new LeftStreetNameNotFound(change.Id));
}

if (activeStreetNameStatus.All(status => !string.Equals(streetName.Status, status, StringComparison.InvariantCultureIgnoreCase)))
{
return problems.Add(new RoadSegmentLeftStreetNameNotProposedOrCurrent(change.Id));
}
}

if (change.RightSideStreetNameId is not null)
{
var streetName = await _streetNameClient.GetAsync(change.RightSideStreetNameId.Value, cancellationToken);
if (streetName is null)
{
return problems.Add(new RightStreetNameNotFound(change.Id));
}

if (activeStreetNameStatus.All(status => !string.Equals(streetName.Status, status, StringComparison.InvariantCultureIgnoreCase)))
{
return problems.Add(new RoadSegmentRightStreetNameNotProposedOrCurrent(change.Id));
}
}
}
catch (StreetNameRegistryUnexpectedStatusCodeException ex)
{
Logger.LogError(ex.Message);

problems += new StreetNameRegistryUnexpectedError((int)ex.StatusCode);
}

return problems;
}
}
Loading
Loading