diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260608104706_NullableEquipmentDriverInstanceId.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260608104706_NullableEquipmentDriverInstanceId.cs index 60bb5a37..bc9e28f4 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260608104706_NullableEquipmentDriverInstanceId.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260608104706_NullableEquipmentDriverInstanceId.cs @@ -24,6 +24,8 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations /// protected override void Down(MigrationBuilder migrationBuilder) { + // WARNING: this rollback converts any existing NULL (driver-less) rows to "" for DriverInstanceId. + // Only safe when no driver-less equipment exists in the database. migrationBuilder.AlterColumn( name: "DriverInstanceId", table: "Equipment", diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/ClusterEquipment.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/ClusterEquipment.razor index 4076347c..b9b9098c 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/ClusterEquipment.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/ClusterEquipment.razor @@ -83,8 +83,15 @@ else await using var db = await DbFactory.CreateDbContextAsync(); var driversInCluster = db.DriverInstances.AsNoTracking() .Where(d => d.ClusterId == ClusterId).Select(d => d.DriverInstanceId); + // Driver-less equipment (DriverInstanceId == null) has no DriverInstance FK. + // Scope it to this cluster via UnsLine → UnsArea.ClusterId instead. + var areaIds = db.UnsAreas.AsNoTracking() + .Where(a => a.ClusterId == ClusterId).Select(a => a.UnsAreaId); + var linesInCluster = db.UnsLines.AsNoTracking() + .Where(l => areaIds.Contains(l.UnsAreaId)).Select(l => l.UnsLineId); _rows = await db.Equipment.AsNoTracking() - .Where(e => driversInCluster.Contains(e.DriverInstanceId)) + .Where(e => driversInCluster.Contains(e.DriverInstanceId) + || (e.DriverInstanceId == null && linesInCluster.Contains(e.UnsLineId))) .OrderBy(e => e.Name) .ToListAsync(); } diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/ImportEquipment.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/ImportEquipment.razor index e5dd0eec..9867aade 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/ImportEquipment.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/ImportEquipment.razor @@ -108,6 +108,9 @@ private bool _busy; private string? _error; + // Bulk import requires a DriverInstanceId by design — every CSV row must reference an existing driver. + // Driver-less equipment (DriverInstanceId == null) is not supported via bulk import; + // create it via the single-add editor (/clusters/{id}/equipment/new) or the SQL loader. private static readonly string[] RequiredColumns = ["Name", "MachineCode", "UnsLineId", "DriverInstanceId"]; private static readonly string[] OptionalColumns = ["ZTag", "SAPID", "Manufacturer", "Model"];