feat(uns): equipment CSV import folded into the tree toolbar

This commit is contained in:
Joseph Doherty
2026-06-08 13:56:01 -04:00
parent c0346f14ce
commit 7db9a24403
5 changed files with 515 additions and 1 deletions
@@ -89,6 +89,17 @@ public sealed record TagEditDto(string TagId, string EquipmentId, string Name, s
public sealed record VirtualTagEditDto(string VirtualTagId, string EquipmentId, string Name, string DataType, string ScriptId,
bool ChangeTriggered, int? TimerIntervalMs, bool Historize, bool Enabled, byte[] RowVersion);
/// <summary>
/// The outcome of a bulk equipment CSV import: how many rows were inserted, how many were skipped
/// (existing MachineCode — the importer is additive-only, never an update), and a per-row error list
/// for rows that could not be inserted (unknown line, unknown driver, or a decision-#122 cluster
/// mismatch). Skipped rows never appear in <see cref="Errors"/>.
/// </summary>
/// <param name="Inserted">The count of new Equipment rows added.</param>
/// <param name="Skipped">The count of rows skipped because their MachineCode already exists.</param>
/// <param name="Errors">The human-readable error strings for rows that failed validation.</param>
public sealed record EquipmentImportResult(int Inserted, int Skipped, IReadOnlyList<string> Errors);
/// <summary>
/// Loads the structural portion of the unified-namespace (UNS) browse tree —
/// Enterprise → Cluster → Area → Line → Equipment — from the config database.
@@ -258,6 +269,21 @@ public interface IUnsTreeService
/// <returns>Success, a missing-line failure, a duplicate-MachineCode failure, or a #122 guard failure.</returns>
Task<UnsMutationResult> CreateEquipmentAsync(EquipmentInput input, CancellationToken ct = default);
/// <summary>
/// Bulk-imports equipment from a parsed set of <see cref="EquipmentInput"/> rows in a single
/// context, applying the same rules as the single-add path: a row whose <c>UnsLineId</c> does not
/// exist is an error; a row whose <c>DriverInstanceId</c> is set but does not resolve is an error;
/// a driver-bound row whose driver is in a different cluster than its line fails the decision-#122
/// guard; and a row whose <c>MachineCode</c> already exists in the DB <em>or</em> earlier in the
/// same batch is silently skipped (additive-only — never an update). Inserted rows get a
/// system-generated <c>EQ-</c> id and a fresh <c>EquipmentUuid</c>. All inserts are saved once at
/// the end.
/// </summary>
/// <param name="rows">The parsed equipment rows to import.</param>
/// <param name="ct">A token to cancel the operation.</param>
/// <returns>The insert/skip counts and the per-row error list.</returns>
Task<EquipmentImportResult> ImportEquipmentAsync(IReadOnlyList<EquipmentInput> rows, CancellationToken ct = default);
/// <summary>
/// Updates an equipment's mutable fields (driver binding, line, name, MachineCode, external
/// ids, and the OPC 40010 identification fields). The decision-#122 driver-cluster guard blocks