Equipment CSV import UI (#163 UI slice) #135

Merged
dohertj2 merged 1 commits from equipment-csv-import-ui into v2 2026-04-19 22:02:38 -04:00
Owner

Closes UI slice of #163. New /clusters/{ClusterId}/draft/{GenerationId}/import-equipment page wraps EquipmentCsvImporter.Parse + EquipmentImportBatchService (CreateBatch?StageRows?FinaliseBatch). EquipmentTab gains Import CSV� button. EquipmentImportBatchService now registered in DI. ExternalIdReservation merge inside FinaliseBatch split to task #197 pending concurrent-insert test matrix. Admin 72/72.

Closes UI slice of #163. New /clusters/{ClusterId}/draft/{GenerationId}/import-equipment page wraps EquipmentCsvImporter.Parse + EquipmentImportBatchService (CreateBatch?StageRows?FinaliseBatch). EquipmentTab gains Import CSV� button. EquipmentImportBatchService now registered in DI. ExternalIdReservation merge inside FinaliseBatch split to task #197 pending concurrent-insert test matrix. Admin 72/72.
dohertj2 added 1 commit 2026-04-19 22:02:29 -04:00
Equipment CSV import UI — Stream B.3/B.5 operator page + EquipmentTab "Import CSV" button. Closes the UI slice of task #163 (Phase 6.4 Stream B.3/B.5); the ExternalIdReservation merge follow-up inside FinaliseBatchAsync is split into new task #197 so it gets a proper concurrent-insert test matrix rather than riding this UI PR. New /clusters/{ClusterId}/draft/{GenerationId}/import-equipment page driving the full staged-import flow end-to-end. Operator selects a driver instance + UNS line (both scoped to the draft generation via DriverInstanceService.ListAsync + UnsService.ListLinesAsync dropdowns), pastes or uploads a CSV (InputFile with 5 MiB cap so pathological files can't OOM the server), clicks Parse — EquipmentCsvImporter.Parse runs + shows two side-by-side cards (accepted rows in green with ZTag/Machine/Name/Line columns, rejected rows in red with line-number + reason). Click Stage + Finalise and the page calls CreateBatchAsync → StageRowsAsync → FinaliseBatchAsync in sequence using the authenticated user's identity as CreatedBy; on success, 600ms banner then NavigateTo back to the draft editor so operator sees the newly-imported rows in EquipmentTab without a manual refresh. Parse errors (missing version marker, bad header, malformed CSV) surface InvalidCsvFormatException.Message inline alongside the Parse button — no page reload needed to retry. Finalise errors surface the service-layer exception message (ImportBatchNotFoundException / ImportBatchAlreadyFinalisedException / any DbUpdate* exception from the atomic transaction) so operator sees exactly why the finalise rejected before the tx rolled back. EquipmentTab gains an "Import CSV…" button next to "Add equipment" that NavigateTo's the new page; it needs a ClusterId parameter to build the URL so the @code block adds [Parameter] string ClusterId, and DraftEditor now passes ClusterId="@ClusterId" alongside the existing GenerationId. EquipmentImportBatchService was already implemented in Phase 6.4 Stream B.4 but missing from the Admin DI container — this PR adds AddScoped so the @inject resolves. The FinaliseBatch docstring explicitly defers ExternalIdReservation merge as a narrower follow-up with a concurrent-insert test matrix — task #197 captures that work. For now the finalise may surface a DB-level UNIQUE-constraint violation if a ZTag conflict exists at commit time; the UI shows the raw message + the batch + staged rows are still in the DB for re-use once the conflict is resolved. Admin project builds 0 errors; Admin.Tests 72/72 passing. ac69a1c39d
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dohertj2 merged commit d1686ed82d into v2 2026-04-19 22:02:38 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#135