@page "/clusters/{ClusterId}/equipment/new" @page "/clusters/{ClusterId}/equipment/{EquipmentId}" @* Equipment CRUD. EquipmentId is system-generated (decision #125) — operator picks Name + MachineCode + UnsLine + Driver; the EquipmentId is derived from the EquipmentUuid on create. OPC 40010 identification fields (Manufacturer, Model, etc.) are all optional. *@ @attribute [Microsoft.AspNetCore.Authorization.Authorize] @rendermode RenderMode.InteractiveServer @using Microsoft.AspNetCore.Components.Forms @using Microsoft.EntityFrameworkCore @using System.ComponentModel.DataAnnotations @using ZB.MOM.WW.OtOpcUa.Configuration @using ZB.MOM.WW.OtOpcUa.Configuration.Entities @inject IDbContextFactory DbFactory @inject NavigationManager Nav

@(IsNew ? "New equipment" : "Edit equipment") · @ClusterId

Cancel
@if (!_loaded) {

Loading…

} else if (!IsNew && _existing is null) {
Equipment @EquipmentId was not found.
} else {
Identity
@if (!IsNew) {
System-generated; never operator-edited.
}
UNS level 5 segment; lowercase letters, digits, dashes, up to 32 chars.
@foreach (var l in _lines) { }
@foreach (var d in _drivers) { }
Unique fleet-wide via ExternalIdReservation.
OPC 40010 identification (optional)
@if (!string.IsNullOrWhiteSpace(_error)) {
@_error
}
Cancel @if (!IsNew) { }
} @code { [Parameter] public string ClusterId { get; set; } = ""; [Parameter] public string? EquipmentId { get; set; } private bool IsNew => string.IsNullOrEmpty(EquipmentId); private FormModel _form = new(); private Equipment? _existing; private List _lines = new(); private List _drivers = new(); private bool _loaded; private bool _busy; private string? _error; protected override async Task OnInitializedAsync() { await using var db = await DbFactory.CreateDbContextAsync(); var areaIds = await db.UnsAreas.AsNoTracking() .Where(a => a.ClusterId == ClusterId).Select(a => a.UnsAreaId).ToListAsync(); _lines = await db.UnsLines.AsNoTracking() .Where(l => areaIds.Contains(l.UnsAreaId)) .OrderBy(l => l.UnsAreaId).ThenBy(l => l.UnsLineId) .ToListAsync(); _drivers = await db.DriverInstances.AsNoTracking() .Where(d => d.ClusterId == ClusterId) .OrderBy(d => d.DriverInstanceId) .ToListAsync(); if (!IsNew) { _existing = await db.Equipment.AsNoTracking() .FirstOrDefaultAsync(e => e.EquipmentId == EquipmentId); if (_existing is not null) { _form = new FormModel { Name = _existing.Name, MachineCode = _existing.MachineCode, UnsLineId = _existing.UnsLineId, DriverInstanceId = _existing.DriverInstanceId, ZTag = _existing.ZTag, SAPID = _existing.SAPID, Manufacturer = _existing.Manufacturer, Model = _existing.Model, SerialNumber = _existing.SerialNumber, HardwareRevision = _existing.HardwareRevision, SoftwareRevision = _existing.SoftwareRevision, YearOfConstruction = _existing.YearOfConstruction, AssetLocation = _existing.AssetLocation, ManufacturerUri = _existing.ManufacturerUri, DeviceManualUri = _existing.DeviceManualUri, Enabled = _existing.Enabled, RowVersion = _existing.RowVersion, }; } } _loaded = true; } private async Task SubmitAsync() { _busy = true; _error = null; try { if (string.IsNullOrEmpty(_form.UnsLineId)) { _error = "Pick a UNS line."; return; } await using var db = await DbFactory.CreateDbContextAsync(); if (IsNew) { var uuid = Guid.NewGuid(); var equipmentId = $"EQ-{uuid.ToString("N")[..12]}"; if (await db.Equipment.AnyAsync(e => e.MachineCode == _form.MachineCode)) { _error = $"MachineCode '{_form.MachineCode}' already exists in this fleet."; return; } db.Equipment.Add(new Equipment { EquipmentId = equipmentId, EquipmentUuid = uuid, DriverInstanceId = string.IsNullOrWhiteSpace(_form.DriverInstanceId) ? null : _form.DriverInstanceId, UnsLineId = _form.UnsLineId, Name = _form.Name, MachineCode = _form.MachineCode, ZTag = string.IsNullOrWhiteSpace(_form.ZTag) ? null : _form.ZTag, SAPID = string.IsNullOrWhiteSpace(_form.SAPID) ? null : _form.SAPID, Manufacturer = _form.Manufacturer, Model = _form.Model, SerialNumber = _form.SerialNumber, HardwareRevision = _form.HardwareRevision, SoftwareRevision = _form.SoftwareRevision, YearOfConstruction = _form.YearOfConstruction, AssetLocation = _form.AssetLocation, ManufacturerUri = _form.ManufacturerUri, DeviceManualUri = _form.DeviceManualUri, Enabled = _form.Enabled, }); } else { var entity = await db.Equipment.FirstOrDefaultAsync(e => e.EquipmentId == EquipmentId); if (entity is null) { _error = "Row no longer exists."; return; } db.Entry(entity).Property(e => e.RowVersion).OriginalValue = _form.RowVersion; entity.DriverInstanceId = string.IsNullOrWhiteSpace(_form.DriverInstanceId) ? null : _form.DriverInstanceId; entity.UnsLineId = _form.UnsLineId; entity.Name = _form.Name; entity.MachineCode = _form.MachineCode; entity.ZTag = string.IsNullOrWhiteSpace(_form.ZTag) ? null : _form.ZTag; entity.SAPID = string.IsNullOrWhiteSpace(_form.SAPID) ? null : _form.SAPID; entity.Manufacturer = _form.Manufacturer; entity.Model = _form.Model; entity.SerialNumber = _form.SerialNumber; entity.HardwareRevision = _form.HardwareRevision; entity.SoftwareRevision = _form.SoftwareRevision; entity.YearOfConstruction = _form.YearOfConstruction; entity.AssetLocation = _form.AssetLocation; entity.ManufacturerUri = _form.ManufacturerUri; entity.DeviceManualUri = _form.DeviceManualUri; entity.Enabled = _form.Enabled; } await db.SaveChangesAsync(); Nav.NavigateTo($"/clusters/{ClusterId}/equipment"); } catch (DbUpdateConcurrencyException) { _error = "Another user changed this equipment while you were editing."; } catch (Exception ex) { _error = ex.Message; } finally { _busy = false; } } private async Task DeleteAsync() { if (IsNew) return; _busy = true; _error = null; try { await using var db = await DbFactory.CreateDbContextAsync(); var entity = await db.Equipment.FirstOrDefaultAsync(e => e.EquipmentId == EquipmentId); if (entity is null) { Nav.NavigateTo($"/clusters/{ClusterId}/equipment"); return; } db.Entry(entity).Property(e => e.RowVersion).OriginalValue = _form.RowVersion; db.Equipment.Remove(entity); await db.SaveChangesAsync(); Nav.NavigateTo($"/clusters/{ClusterId}/equipment"); } catch (DbUpdateConcurrencyException) { _error = "Another user changed this equipment while you were viewing it."; } catch (Exception ex) { _error = $"Delete failed: {ex.Message}. Likely because tags or virtual tags reference this equipment — remove them first."; } finally { _busy = false; } } private sealed class FormModel { [Required, RegularExpression("^[a-z0-9-]{1,32}$", ErrorMessage = "Lowercase letters, digits, dashes only; max 32 chars.")] public string Name { get; set; } = ""; [Required] public string MachineCode { get; set; } = ""; [Required] public string UnsLineId { get; set; } = ""; public string? DriverInstanceId { get; set; } public string? ZTag { get; set; } public string? SAPID { get; set; } public string? Manufacturer { get; set; } public string? Model { get; set; } public string? SerialNumber { get; set; } public string? HardwareRevision { get; set; } public string? SoftwareRevision { get; set; } public short? YearOfConstruction { get; set; } public string? AssetLocation { get; set; } public string? ManufacturerUri { get; set; } public string? DeviceManualUri { get; set; } public bool Enabled { get; set; } = true; public byte[] RowVersion { get; set; } = []; } }