From 28505b3c98af276a2c9fceb06fc7bcea602baede Mon Sep 17 00:00:00 2001 From: jessikitty Date: Tue, 5 May 2026 16:09:28 +1000 Subject: [PATCH] fix: C#5 compat - replace all ?. with explicit null checks --- Services/ServiceTrackerService.cs | 174 ++++++++---------------------- 1 file changed, 46 insertions(+), 128 deletions(-) diff --git a/Services/ServiceTrackerService.cs b/Services/ServiceTrackerService.cs index 1b92936..706f8ad 100644 --- a/Services/ServiceTrackerService.cs +++ b/Services/ServiceTrackerService.cs @@ -7,10 +7,6 @@ using System.Linq; namespace Disco.Plugins.ServiceTracker.Services { - /// - /// Core service that combines Disco Job data with ServiceTracker metadata - /// to produce the dashboard view model. - /// public class ServiceTrackerService { private readonly DiscoDataContext _database; @@ -27,22 +23,16 @@ namespace Disco.Plugins.ServiceTracker.Services public DashboardViewModel BuildDashboard(string filterPriority = null, string filterLocation = null, string filterStatus = null, string filterTech = null, string sortBy = "due") { - // Get all open jobs from Disco var openJobs = _database.Jobs - .Include("Device") - .Include("Device.DeviceModel") - .Include("User") - .Include("OpenedTechUser") - .Include("JobType") - .Include("JobSubTypes") + .Include("Device").Include("Device.DeviceModel") + .Include("User").Include("OpenedTechUser") + .Include("JobType").Include("JobSubTypes") .Where(j => j.ClosedDate == null) .ToList(); - // Load all service tracker tickets var allTickets = _dataStore.LoadAllTickets(); var ticketLookup = allTickets.ToDictionary(t => t.JobId, t => t); - // Auto-create tickets for jobs that don't have one (if enabled) if (_config.AutoCreateTicketsForNewJobs) { foreach (var job in openJobs) @@ -56,7 +46,6 @@ namespace Disco.Plugins.ServiceTracker.Services } } - // Build tiles var tiles = new List(); foreach (var job in openJobs) { @@ -65,7 +54,6 @@ namespace Disco.Plugins.ServiceTracker.Services tiles.Add(BuildTile(job, ticket)); } - // Apply filters if (!string.IsNullOrEmpty(filterPriority)) tiles = tiles.Where(t => t.PriorityId == filterPriority).ToList(); if (!string.IsNullOrEmpty(filterLocation)) @@ -75,7 +63,6 @@ namespace Disco.Plugins.ServiceTracker.Services if (!string.IsNullOrEmpty(filterTech)) tiles = tiles.Where(t => t.AssignedTechId == filterTech).ToList(); - // Sort switch (sortBy) { case "priority": @@ -89,7 +76,7 @@ namespace Disco.Plugins.ServiceTracker.Services break; case "sla": tiles = tiles.OrderBy(t => t.IsSlaBreached ? 0 : t.IsSlaWarning ? 1 : 2) - .ThenBy(t => t.SlaDeadline ?? DateTime.MaxValue).ToList(); + .ThenBy(t => t.SlaDeadline.HasValue ? t.SlaDeadline.Value : DateTime.MaxValue).ToList(); break; case "due": default: @@ -97,13 +84,10 @@ namespace Disco.Plugins.ServiceTracker.Services break; } - // Build stats - var stats = BuildStats(tiles); - return new DashboardViewModel { Tiles = tiles, - Stats = stats, + Stats = BuildStats(tiles), Config = _config, CurrentFilter = filterPriority ?? filterLocation ?? filterStatus ?? "", SortBy = sortBy @@ -113,29 +97,18 @@ namespace Disco.Plugins.ServiceTracker.Services private DashboardTile BuildTile(Job job, ServiceTicket ticket) { var now = DateTime.Now; + var priorityId = (ticket != null ? ticket.PriorityId : null) ?? _config.DefaultPriorityId; + var priority = _config.Priorities.FirstOrDefault(p => p.Id == priorityId) ?? _config.Priorities.FirstOrDefault(); + var locationId = (ticket != null ? ticket.LocationId : null) ?? _config.DefaultLocationId; + var location = _config.Locations.FirstOrDefault(l => l.Id == locationId) ?? _config.Locations.FirstOrDefault(); - // Resolve priority - var priorityId = ticket?.PriorityId ?? _config.DefaultPriorityId; - var priority = _config.Priorities.FirstOrDefault(p => p.Id == priorityId) - ?? _config.Priorities.FirstOrDefault(); - - // Resolve location - var locationId = ticket?.LocationId ?? _config.DefaultLocationId; - var location = _config.Locations.FirstOrDefault(l => l.Id == locationId) - ?? _config.Locations.FirstOrDefault(); - - // Compute SLA - var slaDeadline = ticket?.SlaDeadline; + var slaDeadline = ticket != null ? ticket.SlaDeadline : (DateTime?)null; if (!slaDeadline.HasValue && priority != null && priority.SlaHours > 0) - { slaDeadline = job.OpenedDate.AddHours(priority.SlaHours); - } bool slaBreached = slaDeadline.HasValue && now > slaDeadline.Value; - bool slaWarning = !slaBreached && slaDeadline.HasValue && - priority != null && priority.SlaHours > 0 && - now > slaDeadline.Value.AddHours(-priority.SlaHours * 0.25); + bool slaWarning = !slaBreached && slaDeadline.HasValue && priority != null && priority.SlaHours > 0 + && now > slaDeadline.Value.AddHours(-priority.SlaHours * 0.25); - // Compute age int ageDays = (int)(now - job.OpenedDate).TotalDays; string ageBadge; if (ageDays == 0) ageBadge = "Today"; @@ -144,8 +117,7 @@ namespace Disco.Plugins.ServiceTracker.Services else if (ageDays < 30) ageBadge = (ageDays / 7) + " wk" + (ageDays / 7 > 1 ? "s" : ""); else ageBadge = (ageDays / 30) + " mo" + (ageDays / 30 > 1 ? "s" : ""); - // ETA display - var eta = ticket?.EstimatedCompletion ?? job.ExpectedClosedDate; + var eta = (ticket != null ? ticket.EstimatedCompletion : null) ?? job.ExpectedClosedDate; string etaDisplay = "—"; if (eta.HasValue) { @@ -156,20 +128,18 @@ namespace Disco.Plugins.ServiceTracker.Services else etaDisplay = eta.Value.ToString("dd MMM"); } - // Sort date: use SLA deadline if breached, then ETA, then expected close, then opened DateTime sortDate = slaBreached && slaDeadline.HasValue ? slaDeadline.Value - : eta ?? job.ExpectedClosedDate ?? job.OpenedDate; + : eta.HasValue ? eta.Value + : job.ExpectedClosedDate.HasValue ? job.ExpectedClosedDate.Value : job.OpenedDate; - // Determine Disco status string string discoStatus = "Open"; if (job.WaitingForUserAction.HasValue) discoStatus = "Awaiting User Action"; else if (job.DeviceHeld.HasValue && !job.DeviceReadyForReturn.HasValue) discoStatus = "Device Held"; else if (job.DeviceReadyForReturn.HasValue && !job.DeviceReturnedDate.HasValue) discoStatus = "Ready for Return"; - // Latest note string latestNote = null; int noteCount = 0; - if (ticket?.Notes != null && ticket.Notes.Count > 0) + if (ticket != null && ticket.Notes != null && ticket.Notes.Count > 0) { noteCount = ticket.Notes.Count; var latest = ticket.Notes.OrderByDescending(n => n.Timestamp).First(); @@ -183,8 +153,8 @@ namespace Disco.Plugins.ServiceTracker.Services JobId = job.Id, JobTypeDescription = job.JobType != null ? job.JobType.Description : job.JobTypeId, DeviceSerialNumber = job.DeviceSerialNumber ?? "—", - DeviceModelDescription = job.Device?.DeviceModel != null ? job.Device.DeviceModel.Description : null, - DeviceComputerName = job.Device?.DeviceDomainId, + DeviceModelDescription = job.Device != null && job.Device.DeviceModel != null ? job.Device.DeviceModel.Description : null, + DeviceComputerName = job.Device != null ? job.Device.DeviceDomainId : null, UserId = job.UserId, UserDisplayName = job.User != null ? job.User.DisplayName : job.UserId, OpenedByTechId = job.OpenedTechUserId, @@ -193,22 +163,22 @@ namespace Disco.Plugins.ServiceTracker.Services ExpectedClosedDate = job.ExpectedClosedDate, DiscoStatus = discoStatus, PriorityId = priorityId, - PriorityName = priority?.Name ?? "Unknown", - PriorityColor = priority?.Color ?? "#999", - PrioritySortOrder = priority?.SortOrder ?? 99, + PriorityName = priority != null ? priority.Name : "Unknown", + PriorityColor = priority != null ? priority.Color : "#999", + PrioritySortOrder = priority != null ? priority.SortOrder : 99, LocationId = locationId, - LocationName = location?.Name ?? "Unknown", - LocationIcon = location?.Icon ?? "", - LocationColor = location?.Color ?? "#999", - AssignedTechId = ticket?.AssignedTechId, - AssignedTechName = ResolveUserName(ticket?.AssignedTechId), + LocationName = location != null ? location.Name : "Unknown", + LocationIcon = location != null ? location.Icon : "", + LocationColor = location != null ? location.Color : "#999", + AssignedTechId = ticket != null ? ticket.AssignedTechId : null, + AssignedTechName = ResolveUserName(ticket != null ? ticket.AssignedTechId : null), EstimatedCompletion = eta, SlaDeadline = slaDeadline, - StatusOverride = ticket?.StatusOverride ?? discoStatus, - Summary = ticket?.Summary, + StatusOverride = ticket != null && ticket.StatusOverride != null ? ticket.StatusOverride : discoStatus, + Summary = ticket != null ? ticket.Summary : null, NoteCount = noteCount, LatestNote = latestNote, - LastModifiedDate = ticket?.LastModifiedDate ?? job.OpenedDate, + LastModifiedDate = ticket != null ? ticket.LastModifiedDate : job.OpenedDate, IsSlaBreached = slaBreached, IsSlaWarning = slaWarning, AgeBadge = ageBadge, @@ -225,48 +195,33 @@ namespace Disco.Plugins.ServiceTracker.Services TotalOpen = tiles.Count, SlaBreached = tiles.Count(t => t.IsSlaBreached), SlaWarning = tiles.Count(t => t.IsSlaWarning), - AvgAgeDays = tiles.Count > 0 ? Math.Round(tiles.Average(t => t.AgeDays), 1) : 0, + AvgAgeDays = tiles.Count > 0 ? Math.Round(tiles.Average(t => (double)t.AgeDays), 1) : 0, OldestJobDays = tiles.Count > 0 ? tiles.Max(t => t.AgeDays) : 0 }; - - // By priority foreach (var p in _config.Priorities) - { stats.ByPriority[p.Id] = tiles.Count(t => t.PriorityId == p.Id); - } stats.Critical = tiles.Count(t => t.PriorityId == "critical"); stats.High = tiles.Count(t => t.PriorityId == "high"); stats.Medium = tiles.Count(t => t.PriorityId == "medium"); stats.Low = tiles.Count(t => t.PriorityId == "low"); stats.Scheduled = tiles.Count(t => t.PriorityId == "scheduled"); - - // By location foreach (var l in _config.Locations) - { stats.ByLocation[l.Id] = tiles.Count(t => t.LocationId == l.Id); - } stats.InItOffice = tiles.Count(t => t.LocationId == "it-office"); stats.WithUser = tiles.Count(t => t.LocationId == "with-user"); stats.AtRepairer = tiles.Count(t => t.LocationId == "at-repairer"); - - // By status foreach (var tile in tiles) { var status = tile.StatusOverride ?? "Open"; - if (!stats.ByStatus.ContainsKey(status)) - stats.ByStatus[status] = 0; + if (!stats.ByStatus.ContainsKey(status)) stats.ByStatus[status] = 0; stats.ByStatus[status]++; } - - // By tech foreach (var tile in tiles.Where(t => !string.IsNullOrEmpty(t.AssignedTechId))) { var tech = tile.AssignedTechName ?? tile.AssignedTechId; - if (!stats.ByTech.ContainsKey(tech)) - stats.ByTech[tech] = 0; + if (!stats.ByTech.ContainsKey(tech)) stats.ByTech[tech] = 0; stats.ByTech[tech]++; } - return stats; } @@ -276,33 +231,18 @@ namespace Disco.Plugins.ServiceTracker.Services DateTime? sla = null; if (priority != null && priority.SlaHours > 0) sla = job.OpenedDate.AddHours(priority.SlaHours); - - // Determine default location based on DeviceHeld string locationId = _config.DefaultLocationId; - if (job.DeviceHeld.HasValue) + if (job.DeviceHeld.HasValue && !string.IsNullOrEmpty(job.DeviceHeldLocation)) { - if (!string.IsNullOrEmpty(job.DeviceHeldLocation)) - { - // Try to match the Disco location to a configured location - var matchedLoc = _config.Locations.FirstOrDefault( - l => l.Name.Equals(job.DeviceHeldLocation, StringComparison.OrdinalIgnoreCase)); - if (matchedLoc != null) - locationId = matchedLoc.Id; - } + var matchedLoc = _config.Locations.FirstOrDefault( + l => l.Name.Equals(job.DeviceHeldLocation, StringComparison.OrdinalIgnoreCase)); + if (matchedLoc != null) locationId = matchedLoc.Id; } - return new ServiceTicket { - JobId = job.Id, - PriorityId = _config.DefaultPriorityId, - LocationId = locationId, - AssignedTechId = job.OpenedTechUserId, - EstimatedCompletion = job.ExpectedClosedDate, - SlaDeadline = sla, - StatusOverride = null, - Summary = null, - CreatedDate = DateTime.Now, - LastModifiedDate = DateTime.Now + JobId = job.Id, PriorityId = _config.DefaultPriorityId, LocationId = locationId, + AssignedTechId = job.OpenedTechUserId, EstimatedCompletion = job.ExpectedClosedDate, + SlaDeadline = sla, CreatedDate = DateTime.Now, LastModifiedDate = DateTime.Now }; } @@ -314,23 +254,14 @@ namespace Disco.Plugins.ServiceTracker.Services var user = _database.Users.FirstOrDefault(u => u.UserId == userId); return user != null ? user.DisplayName : userId; } - catch - { - return userId; - } + catch { return userId; } } - // --- CRUD operations for tickets --- - public void UpdateTicket(int jobId, string priorityId, string locationId, string assignedTechId, DateTime? eta, string status, string summary, string modifiedBy) { var ticket = _dataStore.GetTicket(jobId); - if (ticket == null) - { - ticket = new ServiceTicket { JobId = jobId }; - } - + if (ticket == null) ticket = new ServiceTicket { JobId = jobId }; if (priorityId != null) ticket.PriorityId = priorityId; if (locationId != null) ticket.LocationId = locationId; if (assignedTechId != null) ticket.AssignedTechId = assignedTechId; @@ -338,39 +269,26 @@ namespace Disco.Plugins.ServiceTracker.Services if (status != null) ticket.StatusOverride = status; if (summary != null) ticket.Summary = summary; ticket.LastModifiedBy = modifiedBy; - - // Recalculate SLA if priority changed if (priorityId != null) { var priority = _config.Priorities.FirstOrDefault(p => p.Id == priorityId); if (priority != null && priority.SlaHours > 0) - { ticket.SlaDeadline = ticket.CreatedDate.AddHours(priority.SlaHours); - } else - { ticket.SlaDeadline = null; - } } - _dataStore.SaveTicket(ticket); } public void AddNote(int jobId, string authorId, string authorName, string content, string noteType) { - var note = new TicketNote + _dataStore.AddNote(jobId, new TicketNote { - AuthorId = authorId, - AuthorName = authorName, - Content = content, - NoteType = noteType ?? "general" - }; - _dataStore.AddNote(jobId, note); + AuthorId = authorId, AuthorName = authorName, + Content = content, NoteType = noteType ?? "general" + }); } - public ServiceTicket GetTicketDetail(int jobId) - { - return _dataStore.GetTicket(jobId); - } + public ServiceTicket GetTicketDetail(int jobId) { return _dataStore.GetTicket(jobId); } } }