feat: implement strangler-fig-demo Session 1 (backend + smoke script)
Builds the four-seam, three-write-path reference demo backend: case-framework (seam D stand-in), legacy-backend/frontend (SQL Server, seams A/B/C targets), and new-backend (Domain/Application/Infrastructure.*/Api implementing the source resolver, take/release-ownership, write-through translator, and owned assessment flow), wired together via docker-compose with a plain placeholder frontend standing in for the Angular portal until Session 2. All 11 Architecture.Tests pass and scripts/smoke.sh passes end-to-end against a fresh `docker compose up`, covering acceptance criteria 1-3 and 7-22. Fixes two real domain bugs found only once the stack ran for real: the BSN eleven-proof checksum trivially passes all-zero digits, and the adoption mapper silently treated a partial legacy address as absent instead of failing loudly. Also fixes several environment-specific integration issues (rootless Podman/SELinux bind-mount permissions, a buildah NuGet layer-caching bug, SqlClient's invariant-globalization incompatibility, and an nginx path-prefix mismatch for the legacy frontend). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
namespace Legacy.Api.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Maps to dbo.AANVR. Property names deliberately mirror the legacy column
|
||||
/// vocabulary (abbreviated Dutch) rather than modern domain terms - that
|
||||
/// translation is the downstream anti-corruption layer's job, not ours.
|
||||
/// </summary>
|
||||
public class Aanvraag
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Bsn { get; set; } = "";
|
||||
public string Naam { get; set; } = "";
|
||||
public string? Voorl { get; set; }
|
||||
public string? AdresStr { get; set; }
|
||||
public string? AdresNr { get; set; }
|
||||
public string? AdresPc { get; set; }
|
||||
public string? AdresPl { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Telnr { get; set; }
|
||||
public string CorrKanaal { get; set; } = "P";
|
||||
public string StatCd { get; set; } = "O";
|
||||
public string? DiplCd { get; set; }
|
||||
public string? DiplLand { get; set; }
|
||||
public DateOnly? DiplDat { get; set; }
|
||||
public DateOnly DatOntv { get; set; }
|
||||
public DateOnly? DatBeoord { get; set; }
|
||||
public string? BeoordRes { get; set; }
|
||||
public string? BeoordMotiv { get; set; }
|
||||
public bool Migrated { get; set; }
|
||||
public DateTime MutDat { get; set; }
|
||||
public string MutUser { get; set; } = "seed";
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Legacy.Api.Data;
|
||||
|
||||
public class LegacyDbContext(DbContextOptions<LegacyDbContext> options) : DbContext(options)
|
||||
{
|
||||
public DbSet<Aanvraag> Aanvragen => Set<Aanvraag>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Aanvraag>(e =>
|
||||
{
|
||||
e.ToTable("AANVR", "dbo");
|
||||
e.HasKey(a => a.Id);
|
||||
|
||||
e.Property(a => a.Id).HasColumnName("AANVR_ID").ValueGeneratedOnAdd();
|
||||
e.Property(a => a.Bsn).HasColumnName("BSN").HasColumnType("char(9)").IsRequired();
|
||||
e.Property(a => a.Naam).HasColumnName("NAAM").HasMaxLength(60).IsRequired();
|
||||
e.Property(a => a.Voorl).HasColumnName("VOORL").HasMaxLength(10);
|
||||
e.Property(a => a.AdresStr).HasColumnName("ADRES_STR").HasMaxLength(80);
|
||||
e.Property(a => a.AdresNr).HasColumnName("ADRES_NR").HasMaxLength(10);
|
||||
e.Property(a => a.AdresPc).HasColumnName("ADRES_PC").HasColumnType("char(6)");
|
||||
e.Property(a => a.AdresPl).HasColumnName("ADRES_PL").HasMaxLength(60);
|
||||
e.Property(a => a.Email).HasColumnName("EMAIL").HasMaxLength(120);
|
||||
e.Property(a => a.Telnr).HasColumnName("TELNR").HasMaxLength(20);
|
||||
e.Property(a => a.CorrKanaal).HasColumnName("CORR_KANAAL").HasColumnType("char(1)")
|
||||
.HasDefaultValue("P").IsRequired();
|
||||
e.Property(a => a.StatCd).HasColumnName("STAT_CD").HasColumnType("char(1)").IsRequired();
|
||||
e.Property(a => a.DiplCd).HasColumnName("DIPL_CD").HasMaxLength(10);
|
||||
e.Property(a => a.DiplLand).HasColumnName("DIPL_LAND").HasColumnType("char(2)");
|
||||
e.Property(a => a.DiplDat).HasColumnName("DIPL_DAT").HasColumnType("date");
|
||||
e.Property(a => a.DatOntv).HasColumnName("DAT_ONTV").HasColumnType("date").IsRequired();
|
||||
e.Property(a => a.DatBeoord).HasColumnName("DAT_BEOORD").HasColumnType("date");
|
||||
e.Property(a => a.BeoordRes).HasColumnName("BEOORD_RES").HasColumnType("char(1)");
|
||||
e.Property(a => a.BeoordMotiv).HasColumnName("BEOORD_MOTIV").HasMaxLength(500);
|
||||
e.Property(a => a.Migrated).HasColumnName("MIGRATED").HasDefaultValue(false).IsRequired();
|
||||
e.Property(a => a.MutDat).HasColumnName("MUT_DAT").HasColumnType("datetime2").IsRequired();
|
||||
e.Property(a => a.MutUser).HasColumnName("MUT_USER").HasMaxLength(30).IsRequired();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Legacy.Api.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Idempotent seed of the 12 demo AANVR rows at ids 1001-1012. Safe to run on
|
||||
/// every startup: it only inserts when the table is empty.
|
||||
/// </summary>
|
||||
public static class LegacySeeder
|
||||
{
|
||||
public static async Task SeedAsync(LegacyDbContext db)
|
||||
{
|
||||
if (await db.Aanvragen.AnyAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var rows = new List<Aanvraag>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = 1001, Bsn = "195751814", Naam = "de Vries", Voorl = "A.",
|
||||
AdresStr = "Kerkweg", AdresNr = "12", AdresPc = "3512JK", AdresPl = "Utrecht",
|
||||
Email = "anna.devries@example.nl", Telnr = "+31 6 12345678", CorrKanaal = "P",
|
||||
StatCd = "O", DiplCd = "WO-ECO", DiplLand = "DE", DiplDat = new DateOnly(2015, 6, 20),
|
||||
DatOntv = new DateOnly(2026, 3, 10),
|
||||
MutDat = new DateTime(2026, 3, 10, 9, 15, 0), MutUser = "seed",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 1002, Bsn = "254488808", Naam = "Jansen", Voorl = "P.",
|
||||
AdresStr = "Prinsengracht", AdresNr = "45", AdresPc = "1016HB", AdresPl = "Amsterdam",
|
||||
Email = "piet.jansen@example.nl", Telnr = "020 1234567", CorrKanaal = "P",
|
||||
StatCd = "B", DiplCd = "HBO-VPK", DiplLand = "BE", DiplDat = new DateOnly(2012, 7, 1),
|
||||
DatOntv = new DateOnly(2025, 11, 20), DatBeoord = new DateOnly(2025, 12, 5),
|
||||
BeoordRes = "G",
|
||||
BeoordMotiv = "Aanvraag voldoet aan alle diploma-eisen en documentatie is compleet.",
|
||||
MutDat = new DateTime(2025, 12, 5, 11, 0, 0), MutUser = "seed",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 1003, Bsn = "862102455", Naam = "El Amrani", Voorl = "F.",
|
||||
AdresStr = "Molenstraat", AdresNr = "8", AdresPc = "5611EM", AdresPl = "Eindhoven",
|
||||
Email = null, Telnr = "+31 6 87654321", CorrKanaal = "E",
|
||||
StatCd = "O", DiplCd = "WO-ING", DiplLand = "MA", DiplDat = new DateOnly(2018, 6, 15),
|
||||
DatOntv = new DateOnly(2026, 5, 2),
|
||||
MutDat = new DateTime(2026, 5, 2, 8, 45, 0), MutUser = "seed",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 1004, Bsn = "501061964", Naam = "Bakker", Voorl = "L.",
|
||||
AdresStr = "Nieuwstraat", AdresNr = "22", AdresPc = "4811XB", AdresPl = "Breda",
|
||||
Email = "lisa.bakker@example.nl", Telnr = "076 5432109", CorrKanaal = "P",
|
||||
StatCd = "X", DiplCd = "HBO-ICT", DiplLand = "GB", DiplDat = new DateOnly(2010, 5, 10),
|
||||
DatOntv = new DateOnly(2025, 8, 14),
|
||||
MutDat = new DateTime(2025, 8, 20, 14, 30, 0), MutUser = "seed",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 1005, Bsn = "000000000", Naam = "Visser", Voorl = "J.",
|
||||
AdresStr = "Hoofdstraat", AdresNr = "3", AdresPc = "9711AA", AdresPl = "Groningen",
|
||||
Email = "jan.visser@example.nl", Telnr = "+31 6 11223344", CorrKanaal = "P",
|
||||
StatCd = "O", DiplCd = "MBO-ZORG", DiplLand = "PL", DiplDat = new DateOnly(2019, 9, 1),
|
||||
DatOntv = new DateOnly(2026, 2, 18),
|
||||
MutDat = new DateTime(2026, 2, 18, 10, 5, 0), MutUser = "seed",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 1006, Bsn = "184513418", Naam = "Okonkwo", Voorl = "C.",
|
||||
AdresStr = "Zuidplein", AdresNr = "14", AdresPc = "3083CN", AdresPl = "Rotterdam",
|
||||
Email = "c.okonkwo@example.nl", Telnr = "+31 6 22334455", CorrKanaal = "P",
|
||||
StatCd = "B", DiplCd = "WO-GEN", DiplLand = "NG", DiplDat = new DateOnly(2016, 7, 1),
|
||||
DatOntv = new DateOnly(2025, 10, 1), DatBeoord = new DateOnly(2025, 10, 15),
|
||||
BeoordRes = "G", BeoordMotiv = "Akkoord",
|
||||
MutDat = new DateTime(2025, 10, 15, 13, 20, 0), MutUser = "seed",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 1007, Bsn = "682298268", Naam = "Smit", Voorl = "R.",
|
||||
AdresStr = "Kerkstraat", AdresNr = null, AdresPc = "2611GA", AdresPl = "Delft",
|
||||
Email = "r.smit@example.nl", Telnr = "+31 6 33445566", CorrKanaal = "P",
|
||||
StatCd = "O", DiplCd = "HBO-BWI", DiplLand = "TR", DiplDat = new DateOnly(2014, 6, 30),
|
||||
DatOntv = new DateOnly(2026, 4, 22),
|
||||
MutDat = new DateTime(2026, 4, 22, 15, 50, 0), MutUser = "seed",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 1008, Bsn = "794413821", Naam = "Vermeulen", Voorl = "M.",
|
||||
AdresStr = "Julianastraat", AdresNr = "31", AdresPc = "6511PJ", AdresPl = "Nijmegen",
|
||||
Email = "m.vermeulen@example.nl", Telnr = "+31 6 44556677", CorrKanaal = "P",
|
||||
StatCd = "O", DiplCd = "WO-RECHT", DiplLand = "FR", DiplDat = new DateOnly(2013, 6, 25),
|
||||
DatOntv = new DateOnly(2025, 9, 12),
|
||||
MutDat = new DateTime(2025, 9, 12, 9, 0, 0), MutUser = "seed",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 1009, Bsn = "469486879", Naam = "Willems", Voorl = "S.",
|
||||
AdresStr = "Grote Markt", AdresNr = "2", AdresPc = "2511BE", AdresPl = "Den Haag",
|
||||
Email = "s.willems@example.nl", Telnr = "+31 6 55667788", CorrKanaal = "E",
|
||||
StatCd = "B", DiplCd = "HBO-ECO", DiplLand = "ES", DiplDat = new DateOnly(2017, 7, 5),
|
||||
DatOntv = new DateOnly(2025, 11, 3), DatBeoord = new DateOnly(2025, 11, 25),
|
||||
BeoordRes = "G", BeoordMotiv = "Diploma is gewaardeerd conform de geldende richtlijnen.",
|
||||
MutDat = new DateTime(2025, 11, 25, 16, 10, 0), MutUser = "seed",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 1010, Bsn = "349496213", Naam = "Peeters", Voorl = "K.",
|
||||
AdresStr = "Stationsplein", AdresNr = "10", AdresPc = "5611AZ", AdresPl = "Eindhoven",
|
||||
Email = "k.peeters@example.nl", Telnr = "040 1122334", CorrKanaal = "P",
|
||||
StatCd = "A", DiplCd = "MBO-TECH", DiplLand = "IT", DiplDat = new DateOnly(2011, 6, 18),
|
||||
DatOntv = new DateOnly(2025, 8, 20), DatBeoord = new DateOnly(2025, 9, 10),
|
||||
BeoordRes = "G", BeoordMotiv = "Alle documenten zijn gecontroleerd en akkoord bevonden.",
|
||||
MutDat = new DateTime(2025, 9, 10, 10, 40, 0), MutUser = "seed",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 1011, Bsn = "944293797", Naam = "Dekker", Voorl = "T.",
|
||||
AdresStr = "Torenlaan", AdresNr = "18", AdresPc = "7511AB", AdresPl = "Enschede",
|
||||
Email = null, Telnr = "+31 6 66778899", CorrKanaal = "E",
|
||||
StatCd = "O", DiplCd = "WO-PSY", DiplLand = "PT", DiplDat = new DateOnly(2020, 6, 1),
|
||||
DatOntv = new DateOnly(2026, 1, 15),
|
||||
MutDat = new DateTime(2026, 1, 15, 12, 25, 0), MutUser = "seed",
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 1012, Bsn = "380075131", Naam = "Mulder", Voorl = "H.",
|
||||
AdresStr = "Beukenlaan", AdresNr = "4", AdresPc = "8011MN", AdresPl = "Zwolle",
|
||||
Email = "h.mulder@example.nl", Telnr = "038 9988776", CorrKanaal = "P",
|
||||
StatCd = "B", DiplCd = "HBO-EDU", DiplLand = "RO", DiplDat = new DateOnly(2015, 7, 14),
|
||||
DatOntv = new DateOnly(2025, 12, 1), DatBeoord = new DateOnly(2025, 12, 20),
|
||||
BeoordRes = "A",
|
||||
BeoordMotiv = "Buitenlands diploma komt niet overeen met een erkend Nederlands diploma-niveau.",
|
||||
MutDat = new DateTime(2025, 12, 20, 14, 5, 0), MutUser = "seed",
|
||||
},
|
||||
};
|
||||
|
||||
await using var transaction = await db.Database.BeginTransactionAsync();
|
||||
await db.Database.ExecuteSqlRawAsync("SET IDENTITY_INSERT dbo.AANVR ON");
|
||||
db.Aanvragen.AddRange(rows);
|
||||
await db.SaveChangesAsync();
|
||||
await db.Database.ExecuteSqlRawAsync("SET IDENTITY_INSERT dbo.AANVR OFF");
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY src/Legacy.Api/ src/Legacy.Api/
|
||||
# restore+publish combined in one RUN/layer: podman/buildah has a known issue
|
||||
# where the NuGet global-packages cache uses hardlinks that break when a
|
||||
# restore layer and a later --no-restore publish layer are committed
|
||||
# separately, surfacing as a false "package not found" error.
|
||||
RUN dotnet restore src/Legacy.Api/Legacy.Api.csproj && \
|
||||
dotnet publish src/Legacy.Api/Legacy.Api.csproj -c Release -o /app/publish --no-restore
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["dotnet", "Legacy.Api.dll"]
|
||||
@@ -0,0 +1,127 @@
|
||||
using Legacy.Api.Data;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Legacy.Api.Endpoints;
|
||||
|
||||
public static class AanvragenEndpoints
|
||||
{
|
||||
public static void MapAanvragenEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/api/aanvragen");
|
||||
|
||||
group.MapGet("", GetAll);
|
||||
group.MapGet("/{id:int}", GetById);
|
||||
group.MapPut("/{id:int}/gegevens", PutGegevens);
|
||||
group.MapPost("/{id:int}/beoordeling", PostBeoordeling);
|
||||
group.MapPut("/{id:int}/migratie-vlag", PutMigratieVlag);
|
||||
}
|
||||
|
||||
private static async Task<Ok<List<Aanvraag>>> GetAll(
|
||||
LegacyDbContext db, string? zoek, string? status)
|
||||
{
|
||||
var query = db.Aanvragen.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(zoek))
|
||||
{
|
||||
var term = zoek.ToLower();
|
||||
query = query.Where(a => a.Naam.ToLower().Contains(term) || a.Bsn.ToLower().Contains(term));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(status))
|
||||
{
|
||||
query = query.Where(a => a.StatCd == status);
|
||||
}
|
||||
|
||||
var result = await query.OrderBy(a => a.Id).ToListAsync();
|
||||
return TypedResults.Ok(result);
|
||||
}
|
||||
|
||||
private static async Task<Results<Ok<Aanvraag>, NotFound>> GetById(LegacyDbContext db, int id)
|
||||
{
|
||||
var aanvraag = await db.Aanvragen.FindAsync(id);
|
||||
return aanvraag is null ? TypedResults.NotFound() : TypedResults.Ok(aanvraag);
|
||||
}
|
||||
|
||||
private static async Task<Results<NoContent, BadRequest<ValidationErrorResponse>, NotFound, Conflict<MigratedResponse>>> PutGegevens(
|
||||
LegacyDbContext db, int id, GegevensInput input)
|
||||
{
|
||||
var aanvraag = await db.Aanvragen.FindAsync(id);
|
||||
if (aanvraag is null)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
|
||||
if (aanvraag.Migrated)
|
||||
{
|
||||
return TypedResults.Conflict(
|
||||
new MigratedResponse("Deze aanvraag wordt beheerd in het nieuwe portaal."));
|
||||
}
|
||||
|
||||
var errors = GegevensValidator.Validate(input);
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
return TypedResults.BadRequest(new ValidationErrorResponse(errors));
|
||||
}
|
||||
|
||||
aanvraag.Naam = input.Surname;
|
||||
aanvraag.Voorl = input.Initials;
|
||||
aanvraag.AdresStr = input.Address?.Street;
|
||||
aanvraag.AdresNr = input.Address?.Number;
|
||||
aanvraag.AdresPc = input.Address?.PostalCode;
|
||||
aanvraag.AdresPl = input.Address?.City;
|
||||
aanvraag.Email = input.Email;
|
||||
aanvraag.Telnr = input.Phone;
|
||||
aanvraag.CorrKanaal = string.Equals(input.PreferredChannel, "Email", StringComparison.OrdinalIgnoreCase)
|
||||
? "E"
|
||||
: "P";
|
||||
aanvraag.MutDat = DateTime.Now;
|
||||
aanvraag.MutUser = "systeem";
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return TypedResults.NoContent();
|
||||
}
|
||||
|
||||
private static async Task<Results<NoContent, NotFound, Conflict<MigratedResponse>>> PostBeoordeling(
|
||||
LegacyDbContext db, int id, BeoordelingInput input)
|
||||
{
|
||||
var aanvraag = await db.Aanvragen.FindAsync(id);
|
||||
if (aanvraag is null)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
|
||||
if (aanvraag.Migrated)
|
||||
{
|
||||
return TypedResults.Conflict(
|
||||
new MigratedResponse("Deze aanvraag wordt beheerd in het nieuwe portaal."));
|
||||
}
|
||||
|
||||
aanvraag.StatCd = "B";
|
||||
aanvraag.BeoordRes = input.Res;
|
||||
aanvraag.BeoordMotiv = input.Motiv;
|
||||
aanvraag.DatBeoord = DateOnly.FromDateTime(DateTime.Now);
|
||||
aanvraag.MutDat = DateTime.Now;
|
||||
aanvraag.MutUser = "systeem";
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return TypedResults.NoContent();
|
||||
}
|
||||
|
||||
private static async Task<Results<NoContent, NotFound>> PutMigratieVlag(
|
||||
LegacyDbContext db, int id, MigratieVlagInput input)
|
||||
{
|
||||
var aanvraag = await db.Aanvragen.FindAsync(id);
|
||||
if (aanvraag is null)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
|
||||
aanvraag.Migrated = input.Migrated;
|
||||
aanvraag.MutDat = DateTime.Now;
|
||||
aanvraag.MutUser = "systeem";
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return TypedResults.NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Legacy.Api.Endpoints;
|
||||
|
||||
public record AdresInput(string? Street, string? Number, string? PostalCode, string? City);
|
||||
|
||||
public record GegevensInput(
|
||||
string Surname,
|
||||
string? Initials,
|
||||
AdresInput? Address,
|
||||
string? Email,
|
||||
string? Phone,
|
||||
string PreferredChannel);
|
||||
|
||||
public record BeoordelingInput(string Res, string Motiv);
|
||||
|
||||
public record MigratieVlagInput(bool Migrated);
|
||||
|
||||
public record ValidationError(string Veld, string Code, string Melding);
|
||||
|
||||
public record ValidationErrorResponse(IReadOnlyList<ValidationError> Errors);
|
||||
|
||||
public record MigratedResponse(string Message);
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Legacy.Api.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Validates the "gegevens" (particulars) command against the legacy field
|
||||
/// rules. Collects every violation instead of stopping at the first one.
|
||||
/// </summary>
|
||||
public static partial class GegevensValidator
|
||||
{
|
||||
public static List<ValidationError> Validate(GegevensInput input)
|
||||
{
|
||||
var errors = new List<ValidationError>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.Surname))
|
||||
{
|
||||
errors.Add(new ValidationError("NAAM", "NAAM_VERPLICHT", "Achternaam is verplicht"));
|
||||
}
|
||||
else if (input.Surname.Length > 60)
|
||||
{
|
||||
errors.Add(new ValidationError("NAAM", "NAAM_TE_LANG", "Achternaam is te lang"));
|
||||
}
|
||||
|
||||
var street = input.Address?.Street;
|
||||
var number = input.Address?.Number;
|
||||
var postalCode = input.Address?.PostalCode;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(street) && string.IsNullOrWhiteSpace(number))
|
||||
{
|
||||
errors.Add(new ValidationError("ADRES_NR", "HUISNR_VERPLICHT", "Huisnummer is verplicht"));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(postalCode) && !PostcodeRegex().IsMatch(postalCode))
|
||||
{
|
||||
errors.Add(new ValidationError("ADRES_PC", "POSTCODE_ONGELDIG", "Postcode ongeldig"));
|
||||
}
|
||||
|
||||
var wantsEmailChannel = string.Equals(input.PreferredChannel, "Email", StringComparison.OrdinalIgnoreCase);
|
||||
if (wantsEmailChannel && string.IsNullOrWhiteSpace(input.Email))
|
||||
{
|
||||
errors.Add(new ValidationError(
|
||||
"EMAIL", "EMAIL_VERPLICHT_BIJ_KANAAL", "E-mailadres is verplicht bij communicatiekanaal e-mail"));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(input.Email) && !EmailRegex().IsMatch(input.Email))
|
||||
{
|
||||
errors.Add(new ValidationError("EMAIL", "EMAIL_ONGELDIG", "E-mailadres is ongeldig"));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(input.Phone) && !PhoneRegex().IsMatch(input.Phone))
|
||||
{
|
||||
errors.Add(new ValidationError("TELNR", "TELNR_ONGELDIG", "Telefoonnummer is ongeldig"));
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^[0-9]{4}[A-Z]{2}$")]
|
||||
private static partial Regex PostcodeRegex();
|
||||
|
||||
[GeneratedRegex(@"^[^@\s]+@[^@\s]+\.[^@\s]+$")]
|
||||
private static partial Regex EmailRegex();
|
||||
|
||||
[GeneratedRegex(@"^[0-9 +]+$")]
|
||||
private static partial Regex PhoneRegex();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!--
|
||||
Microsoft.Data.SqlClient requires real culture data (it resolves
|
||||
culture info while opening a connection) - invariant globalization mode
|
||||
makes it throw CultureNotFoundException on every connection attempt.
|
||||
The base runtime image (non-Alpine, non-chiseled) ships ICU, so turning
|
||||
this off just works.
|
||||
-->
|
||||
<InvariantGlobalization>false</InvariantGlobalization>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,147 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Legacy.Api.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Legacy.Api.Migrations
|
||||
{
|
||||
[DbContext(typeof(LegacyDbContext))]
|
||||
[Migration("20260730160055_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Legacy.Api.Data.Aanvraag", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("AANVR_ID");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AdresNr")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)")
|
||||
.HasColumnName("ADRES_NR");
|
||||
|
||||
b.Property<string>("AdresPc")
|
||||
.HasColumnType("char(6)")
|
||||
.HasColumnName("ADRES_PC");
|
||||
|
||||
b.Property<string>("AdresPl")
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("nvarchar(60)")
|
||||
.HasColumnName("ADRES_PL");
|
||||
|
||||
b.Property<string>("AdresStr")
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("nvarchar(80)")
|
||||
.HasColumnName("ADRES_STR");
|
||||
|
||||
b.Property<string>("BeoordMotiv")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)")
|
||||
.HasColumnName("BEOORD_MOTIV");
|
||||
|
||||
b.Property<string>("BeoordRes")
|
||||
.HasColumnType("char(1)")
|
||||
.HasColumnName("BEOORD_RES");
|
||||
|
||||
b.Property<string>("Bsn")
|
||||
.IsRequired()
|
||||
.HasColumnType("char(9)")
|
||||
.HasColumnName("BSN");
|
||||
|
||||
b.Property<string>("CorrKanaal")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(1)")
|
||||
.HasDefaultValue("P")
|
||||
.HasColumnName("CORR_KANAAL");
|
||||
|
||||
b.Property<DateOnly?>("DatBeoord")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("DAT_BEOORD");
|
||||
|
||||
b.Property<DateOnly>("DatOntv")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("DAT_ONTV");
|
||||
|
||||
b.Property<string>("DiplCd")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)")
|
||||
.HasColumnName("DIPL_CD");
|
||||
|
||||
b.Property<DateOnly?>("DiplDat")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("DIPL_DAT");
|
||||
|
||||
b.Property<string>("DiplLand")
|
||||
.HasColumnType("char(2)")
|
||||
.HasColumnName("DIPL_LAND");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("nvarchar(120)")
|
||||
.HasColumnName("EMAIL");
|
||||
|
||||
b.Property<bool>("Migrated")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("MIGRATED");
|
||||
|
||||
b.Property<DateTime>("MutDat")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("MUT_DAT");
|
||||
|
||||
b.Property<string>("MutUser")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("nvarchar(30)")
|
||||
.HasColumnName("MUT_USER");
|
||||
|
||||
b.Property<string>("Naam")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("nvarchar(60)")
|
||||
.HasColumnName("NAAM");
|
||||
|
||||
b.Property<string>("StatCd")
|
||||
.IsRequired()
|
||||
.HasColumnType("char(1)")
|
||||
.HasColumnName("STAT_CD");
|
||||
|
||||
b.Property<string>("Telnr")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("nvarchar(20)")
|
||||
.HasColumnName("TELNR");
|
||||
|
||||
b.Property<string>("Voorl")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)")
|
||||
.HasColumnName("VOORL");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AANVR", "dbo");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Legacy.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "dbo");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AANVR",
|
||||
schema: "dbo",
|
||||
columns: table => new
|
||||
{
|
||||
AANVR_ID = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
BSN = table.Column<string>(type: "char(9)", nullable: false),
|
||||
NAAM = table.Column<string>(type: "nvarchar(60)", maxLength: 60, nullable: false),
|
||||
VOORL = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: true),
|
||||
ADRES_STR = table.Column<string>(type: "nvarchar(80)", maxLength: 80, nullable: true),
|
||||
ADRES_NR = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: true),
|
||||
ADRES_PC = table.Column<string>(type: "char(6)", nullable: true),
|
||||
ADRES_PL = table.Column<string>(type: "nvarchar(60)", maxLength: 60, nullable: true),
|
||||
EMAIL = table.Column<string>(type: "nvarchar(120)", maxLength: 120, nullable: true),
|
||||
TELNR = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||
CORR_KANAAL = table.Column<string>(type: "char(1)", nullable: false, defaultValue: "P"),
|
||||
STAT_CD = table.Column<string>(type: "char(1)", nullable: false),
|
||||
DIPL_CD = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: true),
|
||||
DIPL_LAND = table.Column<string>(type: "char(2)", nullable: true),
|
||||
DIPL_DAT = table.Column<DateOnly>(type: "date", nullable: true),
|
||||
DAT_ONTV = table.Column<DateOnly>(type: "date", nullable: false),
|
||||
DAT_BEOORD = table.Column<DateOnly>(type: "date", nullable: true),
|
||||
BEOORD_RES = table.Column<string>(type: "char(1)", nullable: true),
|
||||
BEOORD_MOTIV = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
MIGRATED = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
MUT_DAT = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
MUT_USER = table.Column<string>(type: "nvarchar(30)", maxLength: 30, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AANVR", x => x.AANVR_ID);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AANVR",
|
||||
schema: "dbo");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Legacy.Api.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Legacy.Api.Migrations
|
||||
{
|
||||
[DbContext(typeof(LegacyDbContext))]
|
||||
partial class LegacyDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Legacy.Api.Data.Aanvraag", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("AANVR_ID");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AdresNr")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)")
|
||||
.HasColumnName("ADRES_NR");
|
||||
|
||||
b.Property<string>("AdresPc")
|
||||
.HasColumnType("char(6)")
|
||||
.HasColumnName("ADRES_PC");
|
||||
|
||||
b.Property<string>("AdresPl")
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("nvarchar(60)")
|
||||
.HasColumnName("ADRES_PL");
|
||||
|
||||
b.Property<string>("AdresStr")
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("nvarchar(80)")
|
||||
.HasColumnName("ADRES_STR");
|
||||
|
||||
b.Property<string>("BeoordMotiv")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)")
|
||||
.HasColumnName("BEOORD_MOTIV");
|
||||
|
||||
b.Property<string>("BeoordRes")
|
||||
.HasColumnType("char(1)")
|
||||
.HasColumnName("BEOORD_RES");
|
||||
|
||||
b.Property<string>("Bsn")
|
||||
.IsRequired()
|
||||
.HasColumnType("char(9)")
|
||||
.HasColumnName("BSN");
|
||||
|
||||
b.Property<string>("CorrKanaal")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(1)")
|
||||
.HasDefaultValue("P")
|
||||
.HasColumnName("CORR_KANAAL");
|
||||
|
||||
b.Property<DateOnly?>("DatBeoord")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("DAT_BEOORD");
|
||||
|
||||
b.Property<DateOnly>("DatOntv")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("DAT_ONTV");
|
||||
|
||||
b.Property<string>("DiplCd")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)")
|
||||
.HasColumnName("DIPL_CD");
|
||||
|
||||
b.Property<DateOnly?>("DiplDat")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("DIPL_DAT");
|
||||
|
||||
b.Property<string>("DiplLand")
|
||||
.HasColumnType("char(2)")
|
||||
.HasColumnName("DIPL_LAND");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("nvarchar(120)")
|
||||
.HasColumnName("EMAIL");
|
||||
|
||||
b.Property<bool>("Migrated")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("MIGRATED");
|
||||
|
||||
b.Property<DateTime>("MutDat")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("MUT_DAT");
|
||||
|
||||
b.Property<string>("MutUser")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("nvarchar(30)")
|
||||
.HasColumnName("MUT_USER");
|
||||
|
||||
b.Property<string>("Naam")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("nvarchar(60)")
|
||||
.HasColumnName("NAAM");
|
||||
|
||||
b.Property<string>("StatCd")
|
||||
.IsRequired()
|
||||
.HasColumnType("char(1)")
|
||||
.HasColumnName("STAT_CD");
|
||||
|
||||
b.Property<string>("Telnr")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("nvarchar(20)")
|
||||
.HasColumnName("TELNR");
|
||||
|
||||
b.Property<string>("Voorl")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)")
|
||||
.HasColumnName("VOORL");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AANVR", "dbo");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Legacy.Api.Data;
|
||||
using Legacy.Api.Endpoints;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddDbContext<LegacyDbContext>(options =>
|
||||
options.UseSqlServer(builder.Configuration.GetConnectionString("Legacy")));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<LegacyDbContext>();
|
||||
await db.Database.MigrateAsync();
|
||||
await LegacySeeder.SeedAsync(db);
|
||||
}
|
||||
|
||||
app.MapAanvragenEndpoints();
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace Legacy.Web;
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors the JSON shape returned by Legacy.Api - legacy column vocabulary,
|
||||
/// camelCase over the wire, matched here case-insensitively.
|
||||
/// </summary>
|
||||
public class AanvraagDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Bsn { get; set; } = "";
|
||||
public string Naam { get; set; } = "";
|
||||
public string? Voorl { get; set; }
|
||||
public string? AdresStr { get; set; }
|
||||
public string? AdresNr { get; set; }
|
||||
public string? AdresPc { get; set; }
|
||||
public string? AdresPl { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Telnr { get; set; }
|
||||
public string CorrKanaal { get; set; } = "P";
|
||||
public string StatCd { get; set; } = "O";
|
||||
public string? DiplCd { get; set; }
|
||||
public string? DiplLand { get; set; }
|
||||
public DateOnly? DiplDat { get; set; }
|
||||
public DateOnly DatOntv { get; set; }
|
||||
public DateOnly? DatBeoord { get; set; }
|
||||
public string? BeoordRes { get; set; }
|
||||
public string? BeoordMotiv { get; set; }
|
||||
public bool Migrated { get; set; }
|
||||
public DateTime MutDat { get; set; }
|
||||
public string MutUser { get; set; } = "";
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY src/Legacy.Web/ src/Legacy.Web/
|
||||
# restore+publish combined in one RUN/layer - see Legacy.Api/Dockerfile for why.
|
||||
RUN dotnet restore src/Legacy.Web/Legacy.Web.csproj && \
|
||||
dotnet publish src/Legacy.Web/Legacy.Web.csproj -c Release -o /app/publish --no-restore
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["dotnet", "Legacy.Web.dll"]
|
||||
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Legacy.Web;
|
||||
|
||||
public enum BeoordelingUitkomst
|
||||
{
|
||||
Success,
|
||||
Conflict,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thin HTTP client for Legacy.Api. Legacy.Web never touches the database
|
||||
/// directly - it only talks to the backend over HTTP, same as any other
|
||||
/// caller.
|
||||
/// </summary>
|
||||
public class LegacyApiClient(HttpClient httpClient)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
||||
|
||||
public async Task<List<AanvraagDto>> GetAllAsync(string? zoek, string? status)
|
||||
{
|
||||
var query = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(zoek))
|
||||
{
|
||||
query.Add($"zoek={Uri.EscapeDataString(zoek)}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(status))
|
||||
{
|
||||
query.Add($"status={Uri.EscapeDataString(status)}");
|
||||
}
|
||||
|
||||
var url = "/api/aanvragen" + (query.Count > 0 ? "?" + string.Join("&", query) : "");
|
||||
var result = await httpClient.GetFromJsonAsync<List<AanvraagDto>>(url, JsonOptions);
|
||||
return result ?? [];
|
||||
}
|
||||
|
||||
public async Task<AanvraagDto?> GetByIdAsync(int id)
|
||||
{
|
||||
var response = await httpClient.GetAsync($"/api/aanvragen/{id}");
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<AanvraagDto>(JsonOptions);
|
||||
}
|
||||
|
||||
public async Task<BeoordelingUitkomst> SubmitBeoordelingAsync(int id, string res, string motiv)
|
||||
{
|
||||
var response = await httpClient.PostAsJsonAsync(
|
||||
$"/api/aanvragen/{id}/beoordeling", new { res, motiv });
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Conflict)
|
||||
{
|
||||
return BeoordelingUitkomst.Conflict;
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
return BeoordelingUitkomst.Success;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
@page "/legacy/aanvraag/{id:int}/beoordeling"
|
||||
@model Legacy.Web.Pages.BeoordelingModel
|
||||
|
||||
<h1>Beoordeling aanvraag #@Model.Aanvraag.Id</h1>
|
||||
|
||||
@if (Model.Ingediend)
|
||||
{
|
||||
<div class="melding">
|
||||
<p>De beoordeling is verwerkt.</p>
|
||||
<p><a href="/worklist/legacy/@Model.Aanvraag.Id">Terug naar aanvraag</a></p>
|
||||
</div>
|
||||
}
|
||||
else if (Model.Aanvraag.Migrated)
|
||||
{
|
||||
<div class="melding">
|
||||
<p>Deze aanvraag wordt beheerd in het nieuwe portaal.</p>
|
||||
<p><a href="/worklist/legacy/@Model.Aanvraag.Id">Naar nieuw portaal</a></p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (Model.Conflict)
|
||||
{
|
||||
<div class="melding">
|
||||
<p>Deze aanvraag wordt inmiddels beheerd in het nieuwe portaal. De beoordeling is niet opgeslagen.</p>
|
||||
<p><a href="/worklist/legacy/@Model.Aanvraag.Id">Naar nieuw portaal</a></p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<fieldset>
|
||||
<legend>Aanvraaggegevens</legend>
|
||||
<p><label>Naam:</label> @Model.Aanvraag.Naam @Model.Aanvraag.Voorl</p>
|
||||
<p><label>BSN:</label> @Model.Aanvraag.Bsn</p>
|
||||
<p><label>Ontvangen:</label> @Model.Aanvraag.DatOntv.ToString("dd-MM-yyyy")</p>
|
||||
</fieldset>
|
||||
|
||||
<form method="post">
|
||||
<fieldset>
|
||||
<legend>Beoordeling</legend>
|
||||
<p>
|
||||
<label for="res">Uitkomst:</label>
|
||||
<select id="res" name="Res">
|
||||
<option value="G" selected="@(Model.Res == "G")">Goedgekeurd</option>
|
||||
<option value="A" selected="@(Model.Res == "A")">Afgewezen</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="motiv">Motivatie:</label><br />
|
||||
<textarea id="motiv" name="Motiv" rows="5" cols="60">@Model.Motiv</textarea>
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Beoordeling opslaan" />
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Legacy.Web.Pages;
|
||||
|
||||
public class BeoordelingModel(LegacyApiClient client) : PageModel
|
||||
{
|
||||
public AanvraagDto Aanvraag { get; set; } = null!;
|
||||
|
||||
public bool Ingediend { get; set; }
|
||||
|
||||
public bool Conflict { get; set; }
|
||||
|
||||
[BindProperty]
|
||||
public string Res { get; set; } = "G";
|
||||
|
||||
[BindProperty]
|
||||
public string Motiv { get; set; } = "";
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(int id)
|
||||
{
|
||||
var aanvraag = await client.GetByIdAsync(id);
|
||||
if (aanvraag is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
Aanvraag = aanvraag;
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(int id)
|
||||
{
|
||||
var aanvraag = await client.GetByIdAsync(id);
|
||||
if (aanvraag is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
Aanvraag = aanvraag;
|
||||
|
||||
if (Aanvraag.Migrated)
|
||||
{
|
||||
// Blocked page is rendered from the razor markup; the write
|
||||
// endpoint is never called for a migrated case.
|
||||
return Page();
|
||||
}
|
||||
|
||||
var uitkomst = await client.SubmitBeoordelingAsync(id, Res, Motiv);
|
||||
if (uitkomst == BeoordelingUitkomst.Conflict)
|
||||
{
|
||||
Conflict = true;
|
||||
Aanvraag = (await client.GetByIdAsync(id))!;
|
||||
return Page();
|
||||
}
|
||||
|
||||
Ingediend = true;
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
@page "/legacy"
|
||||
@model Legacy.Web.Pages.IndexModel
|
||||
|
||||
<h1>Aanvragen diplomawaardering</h1>
|
||||
|
||||
<form method="get">
|
||||
<label for="zoek">Zoeken (naam/BSN):</label>
|
||||
<input type="text" id="zoek" name="Zoek" value="@Model.Zoek" />
|
||||
|
||||
<label for="status" style="width:auto;">Status:</label>
|
||||
<select id="status" name="Status">
|
||||
<option value="">(alle)</option>
|
||||
<option value="O" selected="@(Model.Status == "O")">open</option>
|
||||
<option value="B" selected="@(Model.Status == "B")">beoordeeld</option>
|
||||
<option value="A" selected="@(Model.Status == "A")">afgerond</option>
|
||||
<option value="X" selected="@(Model.Status == "X")">ingetrokken</option>
|
||||
</select>
|
||||
|
||||
<input type="submit" value="Filteren" />
|
||||
</form>
|
||||
|
||||
<br />
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>BSN</th>
|
||||
<th>Naam</th>
|
||||
<th>Status</th>
|
||||
<th>Ontvangen</th>
|
||||
<th>Kanaal</th>
|
||||
<th>Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var a in Model.Aanvragen)
|
||||
{
|
||||
<tr class="@(a.Migrated ? "migrated" : "")">
|
||||
<td>@a.Id</td>
|
||||
<td>@a.Bsn</td>
|
||||
<td>@a.Naam @a.Voorl</td>
|
||||
<td>@Legacy.Web.Pages.IndexModel.StatusLabel(a.StatCd)</td>
|
||||
<td>@a.DatOntv.ToString("dd-MM-yyyy")</td>
|
||||
<td>@(a.CorrKanaal == "E" ? "e-mail" : "post")</td>
|
||||
<td>
|
||||
@if (a.Migrated)
|
||||
{
|
||||
<a href="/worklist/legacy/@a.Id">beheerd in nieuw portaal</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="/legacy/aanvraag/@a.Id/beoordeling">Beoordelen</a>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Legacy.Web.Pages;
|
||||
|
||||
public class IndexModel(LegacyApiClient client) : PageModel
|
||||
{
|
||||
public List<AanvraagDto> Aanvragen { get; set; } = [];
|
||||
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public string? Zoek { get; set; }
|
||||
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public string? Status { get; set; }
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
Aanvragen = await client.GetAllAsync(Zoek, Status);
|
||||
}
|
||||
|
||||
public static string StatusLabel(string statCd) => statCd switch
|
||||
{
|
||||
"O" => "open",
|
||||
"B" => "beoordeeld",
|
||||
"A" => "afgerond",
|
||||
"X" => "ingetrokken",
|
||||
_ => statCd,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Diplomawaardering - Legacy systeem</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background-color: #d4d0c8;
|
||||
color: #000000;
|
||||
margin: 0;
|
||||
padding: 0 0 24px 0;
|
||||
}
|
||||
h1, h2 {
|
||||
font-family: "Times New Roman", Times, serif;
|
||||
}
|
||||
.banner {
|
||||
background-color: #000080;
|
||||
color: #ffffff;
|
||||
padding: 10px 16px;
|
||||
font-family: "Times New Roman", Times, serif;
|
||||
font-size: 1.3em;
|
||||
border-bottom: 2px solid #000000;
|
||||
}
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
table, th, td {
|
||||
border: 1px solid #000000;
|
||||
}
|
||||
th, td {
|
||||
padding: 4px 8px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
th {
|
||||
background-color: #c0c0c0;
|
||||
}
|
||||
tr.migrated {
|
||||
color: #808080;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
a {
|
||||
color: #0000ee;
|
||||
}
|
||||
button, input[type=submit] {
|
||||
background-color: #c0c0c0;
|
||||
border: 2px outset #808080;
|
||||
padding: 4px 14px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
button:active, input[type=submit]:active {
|
||||
border-style: inset;
|
||||
}
|
||||
fieldset {
|
||||
border: 1px solid #000000;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
label {
|
||||
display: inline-block;
|
||||
width: 140px;
|
||||
font-weight: bold;
|
||||
}
|
||||
input[type=text], select, textarea {
|
||||
border: 1px solid #000000;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.melding {
|
||||
border: 1px solid #000000;
|
||||
background-color: #ffffcc;
|
||||
padding: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="banner">Diplomawaardering — Aanvraagregistratie (Legacy systeem)</div>
|
||||
<div class="content">
|
||||
@RenderBody()
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
@namespace Legacy.Web.Pages
|
||||
@using Legacy.Web
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Legacy.Web;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddRazorPages();
|
||||
|
||||
var legacyApiBaseUrl = builder.Configuration["Services:LegacyApi:BaseUrl"]
|
||||
?? "http://localhost:8081";
|
||||
|
||||
builder.Services.AddHttpClient<LegacyApiClient>(client =>
|
||||
{
|
||||
client.BaseAddress = new Uri(legacyApiBaseUrl);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapRazorPages();
|
||||
|
||||
app.Run();
|
||||
Reference in New Issue
Block a user