Skip to content

Commit

Permalink
fix: wms labeltype by duplicating projection (V2)
Browse files Browse the repository at this point in the history
GAWR-5337
  • Loading branch information
emalfroy authored and jvandaal committed Dec 5, 2023
1 parent 79cd967 commit 56ca872
Show file tree
Hide file tree
Showing 13 changed files with 4,058 additions and 0 deletions.
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);
}
}
}
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());
}
}
Loading

0 comments on commit 56ca872

Please sign in to comment.