-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: wms labeltype by duplicating projection (V2)
GAWR-5337
- Loading branch information
Showing
13 changed files
with
4,058 additions
and
0 deletions.
There are no files selected for viewing
138 changes: 138 additions & 0 deletions
138
src/AddressRegistry.Projections.Wms/AddressWmsItemV2/AddressWmsItemV2.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
namespace AddressRegistry.Projections.Wms.AddressWmsItemV2 | ||
{ | ||
using System; | ||
using AddressRegistry.Infrastructure; | ||
using Be.Vlaanderen.Basisregisters.GrAr.Common; | ||
using Be.Vlaanderen.Basisregisters.Utilities; | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||
using NetTopologySuite.Geometries; | ||
using NodaTime; | ||
|
||
public class AddressWmsItemV2 | ||
{ | ||
protected AddressWmsItemV2() | ||
{ } | ||
|
||
public AddressWmsItemV2( | ||
int addressPersistentLocalId, | ||
int? parentAddressPersistentLocalId, | ||
int streetNamePersistentLocalId, | ||
string? postalCode, | ||
string houseNumber, | ||
string? boxNumber, | ||
string status, | ||
bool officiallyAssigned, | ||
Point position, | ||
string positionMethod, | ||
string positionSpecification, | ||
bool removed, | ||
Instant versionTimestamp) | ||
{ | ||
AddressPersistentLocalId = addressPersistentLocalId; | ||
ParentAddressPersistentLocalId = parentAddressPersistentLocalId; | ||
StreetNamePersistentLocalId = streetNamePersistentLocalId; | ||
PostalCode = postalCode; | ||
HouseNumber = houseNumber; | ||
BoxNumber = boxNumber; | ||
LabelType = string.IsNullOrWhiteSpace(boxNumber) | ||
? WmsAddressLabelType.HouseNumberWithoutBoxes | ||
: WmsAddressLabelType.BoxNumberOrHouseNumberWithBoxesOnSamePosition; | ||
Status = status; | ||
OfficiallyAssigned = officiallyAssigned; | ||
SetPosition(position); | ||
PositionMethod = positionMethod; | ||
PositionSpecification = positionSpecification; | ||
Removed = removed; | ||
VersionTimestamp = versionTimestamp; | ||
} | ||
|
||
public int AddressPersistentLocalId { get; set; } | ||
public int? ParentAddressPersistentLocalId { get; set; } | ||
public int StreetNamePersistentLocalId { get; set; } | ||
public string? PostalCode { get; set; } | ||
public string HouseNumber { get; set; } | ||
public string? HouseNumberLabel { get; private set; } | ||
public int? HouseNumberLabelLength { get; private set; } | ||
public WmsAddressLabelType LabelType { get; set; } | ||
|
||
public string? BoxNumber { get; set; } | ||
public string Status { get; set; } | ||
public bool OfficiallyAssigned { get; set; } | ||
public Point Position { get; private set; } | ||
public double PositionX { get; private set; } | ||
public double PositionY { get; private set; } | ||
|
||
public string PositionMethod { get; set; } | ||
public string PositionSpecification { get; set; } | ||
|
||
public bool Removed { get; set; } | ||
|
||
public DateTimeOffset VersionTimestampAsDateTimeOffset { get; private set; } | ||
|
||
public Instant VersionTimestamp | ||
{ | ||
get => Instant.FromDateTimeOffset(VersionTimestampAsDateTimeOffset); | ||
set | ||
{ | ||
VersionTimestampAsDateTimeOffset = value.ToDateTimeOffset(); | ||
VersionAsString = new Rfc3339SerializableDateTimeOffset(value.ToBelgianDateTimeOffset()).ToString(); | ||
} | ||
} | ||
|
||
public string? VersionAsString { get; protected set; } | ||
|
||
public void SetHouseNumberLabel(string? label) | ||
{ | ||
HouseNumberLabel = label; | ||
HouseNumberLabelLength = label?.Length; | ||
} | ||
|
||
public void SetPosition(Point position) | ||
{ | ||
Position = position; | ||
PositionX = position.X; | ||
PositionY = position.Y; | ||
} | ||
} | ||
|
||
public class AddressWmsItemV2Configuration : IEntityTypeConfiguration<AddressWmsItemV2> | ||
{ | ||
internal const string TableName = "AddressWmsV2"; | ||
|
||
public void Configure(EntityTypeBuilder<AddressWmsItemV2> b) | ||
{ | ||
b.ToTable(TableName, Schema.Wms) | ||
.HasKey(p => p.AddressPersistentLocalId) | ||
.IsClustered(); | ||
|
||
b.Property(x => x.AddressPersistentLocalId) | ||
.ValueGeneratedNever(); | ||
|
||
b.Property(p => p.VersionTimestampAsDateTimeOffset) | ||
.HasColumnName("VersionTimestamp"); | ||
|
||
b.Ignore(x => x.VersionTimestamp); | ||
|
||
b.Property(p => p.StreetNamePersistentLocalId); | ||
b.Property(p => p.ParentAddressPersistentLocalId); | ||
b.Property(p => p.PostalCode); | ||
b.Property(p => p.HouseNumber); | ||
b.Property(p => p.BoxNumber); | ||
b.Property(p => p.OfficiallyAssigned); | ||
b.Property(p => p.Position).HasColumnType("sys.geometry"); | ||
b.Property(p => p.PositionSpecification); | ||
b.Property(p => p.PositionMethod); | ||
b.Property(p => p.Status); | ||
b.Property(p => p.Removed); | ||
b.Property(p => p.VersionAsString); | ||
|
||
b.HasIndex(p => p.ParentAddressPersistentLocalId); | ||
b.HasIndex(p => p.Status); | ||
b.HasIndex(p => p.StreetNamePersistentLocalId); | ||
|
||
b.HasIndex(p => new { p.PositionX, p.PositionY, p.Removed, p.Status }) | ||
.IncludeProperties(x => x.StreetNamePersistentLocalId); | ||
} | ||
} | ||
} |
167 changes: 167 additions & 0 deletions
167
src/AddressRegistry.Projections.Wms/AddressWmsItemV2/AddressWmsItemV2Extensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// ReSharper disable CompareOfFloatsByEqualityOperator | ||
|
||
namespace AddressRegistry.Projections.Wms.AddressWmsItemV2 | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
public static class AddressWmsItemV2Extensions | ||
{ | ||
public static async Task FindAndUpdateAddressDetailV2(this WmsContext context, | ||
int addressPersistentLocalId, | ||
Action<AddressWmsItemV2> updateAddressAction, | ||
CancellationToken ct, | ||
bool updateHouseNumberLabelsBeforeAddressUpdate = false, | ||
bool updateHouseNumberLabelsAfterAddressUpdate = false, | ||
bool allowUpdateRemovedAddress = false) | ||
{ | ||
var address = await context.FindAddressDetailV2(addressPersistentLocalId, ct, allowUpdateRemovedAddress); | ||
|
||
if (updateHouseNumberLabelsBeforeAddressUpdate) | ||
{ | ||
await context.UpdateHouseNumberLabelsV2(address, ct); | ||
} | ||
|
||
updateAddressAction(address); | ||
|
||
if (updateHouseNumberLabelsAfterAddressUpdate) | ||
{ | ||
await context.UpdateHouseNumberLabelsV2(address, ct, includeAddressInUpdate: true); | ||
} | ||
} | ||
|
||
public static async Task<AddressWmsItemV2> FindAddressDetailV2( | ||
this WmsContext context, | ||
int addressPersistentLocalId, | ||
CancellationToken ct, | ||
bool allowRemovedAddress = false) | ||
{ | ||
// NOTE: We cannot depend on SQL computed columns when facing with bulk insert that needs to perform queries. | ||
var address = await context.AddressWmsItemsV2.FindAsync(addressPersistentLocalId, cancellationToken: ct); | ||
|
||
if (address == null) | ||
{ | ||
throw DatabaseItemNotFound(addressPersistentLocalId); | ||
} | ||
|
||
// exclude soft deleted entries, unless allowed | ||
if (!address.Removed || allowRemovedAddress) | ||
{ | ||
return address; | ||
} | ||
|
||
throw DatabaseItemNotFound(addressPersistentLocalId); | ||
} | ||
|
||
public static async Task UpdateHouseNumberLabelsV2(this WmsContext context, | ||
AddressWmsItemV2 address, | ||
CancellationToken ct, | ||
bool includeAddressInUpdate = false) | ||
{ | ||
var addressesWithSharedPosition = await context.FindAddressesWithSharedPosition( | ||
address.AddressPersistentLocalId, | ||
address.Position, | ||
address.Status, | ||
ct); | ||
|
||
if (includeAddressInUpdate) | ||
{ | ||
addressesWithSharedPosition.Add(address); | ||
} | ||
|
||
var label = addressesWithSharedPosition.CalculateHouseNumberLabel(); | ||
|
||
foreach (var item in addressesWithSharedPosition) | ||
{ | ||
item.SetHouseNumberLabel(label); | ||
} | ||
} | ||
|
||
private static async Task<IList<AddressWmsItemV2>> FindAddressesWithSharedPosition( | ||
this WmsContext context, | ||
int addressPersistentLocalId, | ||
NetTopologySuite.Geometries.Point position, | ||
string? status, | ||
CancellationToken ct) | ||
{ | ||
var localItems = context | ||
.AddressWmsItemsV2 | ||
.Local | ||
.Where(i => i.PositionX == position.X | ||
&& i.PositionY == position.Y | ||
&& i.AddressPersistentLocalId != addressPersistentLocalId | ||
&& i.Status == status | ||
&& !i.Removed) | ||
.ToList(); | ||
|
||
var dbItems = await context.AddressWmsItemsV2 | ||
.Where(i => i.PositionX == position.X | ||
&& i.PositionY == position.Y | ||
&& i.AddressPersistentLocalId != addressPersistentLocalId | ||
&& i.Status == status | ||
&& !i.Removed) | ||
.ToListAsync(ct); | ||
|
||
// We need to verify that the AddressWmsItemV2 retrieved from the DB is not already in the local cache. | ||
// If it is already in the local cache, but not in the localItems list, then its position or status was updated and is no longer shared. | ||
// An example: | ||
// context.AddressWmsItemsV2.Local (local cache) contains items: A, B, C and D | ||
// localItems returns: A, B and C | ||
// dbItems returns: A and D | ||
// This implies that D was updated but not yet persisted to the database, but was updated in memory only. | ||
// Because localItems didn't return D, its position (or status) didn't match the specified position. | ||
var verifiedDbItems = new List<AddressWmsItemV2>(); | ||
foreach (var dbItem in dbItems) | ||
{ | ||
if (localItems.Any(x => x.AddressPersistentLocalId == dbItem.AddressPersistentLocalId)) | ||
{ | ||
continue; | ||
} | ||
|
||
if (context.AddressWmsItemsV2.Local.Any(x => | ||
x.AddressPersistentLocalId == dbItem.AddressPersistentLocalId)) | ||
{ | ||
continue; | ||
} | ||
|
||
verifiedDbItems.Add(dbItem); | ||
} | ||
|
||
var union = localItems | ||
.Union(verifiedDbItems) | ||
.ToList(); | ||
|
||
return union; | ||
} | ||
|
||
private static string? CalculateHouseNumberLabel(this IEnumerable<AddressWmsItemV2> addresses) | ||
{ | ||
var houseNumberComparer = new HouseNumberComparer(); | ||
|
||
var orderedAddresses = addresses | ||
.Where(i => !i.Removed) | ||
.OrderBy(i => i.HouseNumber, houseNumberComparer) | ||
.ToList(); | ||
|
||
if (!orderedAddresses.Any()) | ||
{ | ||
return null; | ||
} | ||
|
||
var smallestNumber = orderedAddresses.First().HouseNumber; | ||
var highestNumber = orderedAddresses.Last().HouseNumber; | ||
|
||
return smallestNumber != highestNumber | ||
? $"{smallestNumber}-{highestNumber}" | ||
: smallestNumber; | ||
} | ||
|
||
private static ProjectionItemNotFoundException<AddressWmsItemV2Projections> DatabaseItemNotFound(int addressPersistentLocalId) => | ||
new ProjectionItemNotFoundException<AddressWmsItemV2Projections>(addressPersistentLocalId.ToString()); | ||
} | ||
} |
Oops, something went wrong.