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); }
}
}