fix(admin): resolve Low code-review findings (Admin-010,011,012)

- Admin-010: vendor Bootstrap 5.3.3 (CSS + JS bundle + maps + provenance
  README) under wwwroot/lib/bootstrap and reference local paths from
  App.razor — Admin no longer pulls Bootstrap from jsDelivr.
- Admin-011: swap FleetStatusPoller's three plain dictionaries for
  ConcurrentDictionary so ResetCache can't race a poll tick.
- Admin-012: drop the EquipmentId column from EquipmentCsvImporter (per
  admin-ui.md — equipment id is system-derived from EquipmentUuid);
  EquipmentImportBatchService and the textarea placeholder updated to
  match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-23 07:24:07 -04:00
parent 3f01a24b45
commit 2b33b64a58
16 changed files with 355 additions and 48 deletions

View File

@@ -25,12 +25,12 @@ public sealed class EquipmentImportBatchServiceTests : IDisposable
// Unique SAPID per row — FinaliseBatch reserves ZTag + SAPID via filtered-unique index, so
// two rows sharing a SAPID under different EquipmentUuids collide as intended.
// Admin-012: no EquipmentId on the CSV row — it is derived from EquipmentUuid at stage/finalise.
private static EquipmentCsvRow Row(string zTag, string name = "eq-1") => new()
{
ZTag = zTag,
MachineCode = "mc",
SAPID = $"sap-{zTag}",
EquipmentId = "eq-id",
EquipmentUuid = Guid.NewGuid().ToString(),
Name = name,
UnsAreaName = "area",
@@ -189,7 +189,7 @@ public sealed class EquipmentImportBatchServiceTests : IDisposable
var row = new EquipmentCsvRow
{
ZTag = "z-shared", MachineCode = "mc", SAPID = "sap-shared",
EquipmentId = "eq-1", EquipmentUuid = sharedUuid.ToString(),
EquipmentUuid = sharedUuid.ToString(),
Name = "eq-1", UnsAreaName = "a", UnsLineName = "l",
};
await _svc.StageRowsAsync(batch1.Id, [row], [], CancellationToken.None);
@@ -212,17 +212,18 @@ public sealed class EquipmentImportBatchServiceTests : IDisposable
var rowA = new EquipmentCsvRow
{
ZTag = "z-collide", MachineCode = "mc-a", SAPID = "sap-a",
EquipmentId = "eq-a", EquipmentUuid = Guid.NewGuid().ToString(),
EquipmentUuid = Guid.NewGuid().ToString(),
Name = "a", UnsAreaName = "ar", UnsLineName = "ln",
};
await _svc.StageRowsAsync(batchA.Id, [rowA], [], CancellationToken.None);
await _svc.FinaliseBatchAsync(batchA.Id, 1, "drv", "line", CancellationToken.None);
var batchB = await _svc.CreateBatchAsync("c1", "bob", CancellationToken.None);
var rowBUuid = Guid.NewGuid();
var rowB = new EquipmentCsvRow
{
ZTag = "z-collide", MachineCode = "mc-b", SAPID = "sap-b", // same ZTag, different EquipmentUuid
EquipmentId = "eq-b", EquipmentUuid = Guid.NewGuid().ToString(),
EquipmentUuid = rowBUuid.ToString(),
Name = "b", UnsAreaName = "ar", UnsLineName = "ln",
};
await _svc.StageRowsAsync(batchB.Id, [rowB], [], CancellationToken.None);
@@ -231,9 +232,9 @@ public sealed class EquipmentImportBatchServiceTests : IDisposable
_svc.FinaliseBatchAsync(batchB.Id, 2, "drv", "line", CancellationToken.None));
ex.Message.ShouldContain("z-collide");
// Second finalise must have rolled back — no partial Equipment row for batch B.
// Second finalise must have rolled back — no partial Equipment row for batch B (match by UUID).
var equipmentB = await _db.Equipment.AsNoTracking()
.Where(e => e.EquipmentId == "eq-b")
.Where(e => e.EquipmentUuid == rowBUuid)
.ToListAsync();
equipmentB.ShouldBeEmpty();
}
@@ -245,7 +246,7 @@ public sealed class EquipmentImportBatchServiceTests : IDisposable
var row = new EquipmentCsvRow
{
ZTag = "", MachineCode = "mc", SAPID = "",
EquipmentId = "eq-nil", EquipmentUuid = Guid.NewGuid().ToString(),
EquipmentUuid = Guid.NewGuid().ToString(),
Name = "nil", UnsAreaName = "ar", UnsLineName = "ln",
};
await _svc.StageRowsAsync(batch.Id, [row], [], CancellationToken.None);
@@ -294,7 +295,7 @@ public sealed class EquipmentImportBatchServiceTests : IDisposable
var conflictRow = new EquipmentCsvRow
{
ZTag = "z-taken", MachineCode = "mc", SAPID = "sap-ok",
EquipmentId = "eq-x", EquipmentUuid = importerUuid.ToString(),
EquipmentUuid = importerUuid.ToString(),
Name = "x", UnsAreaName = "ar", UnsLineName = "ln",
};
var cleanRow = Row("z-clean");
@@ -334,7 +335,7 @@ public sealed class EquipmentImportBatchServiceTests : IDisposable
var conflictRow = new EquipmentCsvRow
{
ZTag = "z-free", MachineCode = "mc", SAPID = "sap-taken",
EquipmentId = "eq-y", EquipmentUuid = importerUuid.ToString(),
EquipmentUuid = importerUuid.ToString(),
Name = "y", UnsAreaName = "ar", UnsLineName = "ln",
};
@@ -370,7 +371,7 @@ public sealed class EquipmentImportBatchServiceTests : IDisposable
var row = new EquipmentCsvRow
{
ZTag = "z-mine", MachineCode = "mc", SAPID = "sap-mine",
EquipmentId = "eq-z", EquipmentUuid = sharedUuid.ToString(), // same UUID
EquipmentUuid = sharedUuid.ToString(), // same UUID
Name = "z", UnsAreaName = "ar", UnsLineName = "ln",
};
@@ -405,7 +406,7 @@ public sealed class EquipmentImportBatchServiceTests : IDisposable
var row = new EquipmentCsvRow
{
ZTag = "z-released", MachineCode = "mc", SAPID = "sap-new",
EquipmentId = "eq-new", EquipmentUuid = newImporterUuid.ToString(),
EquipmentUuid = newImporterUuid.ToString(),
Name = "new", UnsAreaName = "ar", UnsLineName = "ln",
};