fix: C#5 compat - replace all ?. with explicit null checks
This commit is contained in:
@@ -7,10 +7,6 @@ using System.Linq;
|
||||
|
||||
namespace Disco.Plugins.ServiceTracker.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Core service that combines Disco Job data with ServiceTracker metadata
|
||||
/// to produce the dashboard view model.
|
||||
/// </summary>
|
||||
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<DashboardTile>();
|
||||
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); }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user