Certificate/wireless plugins; major refactoring
Migrate much of BI to Services. Added Wireless Profile Provider plugin feature. Added Certificate Authority Provider plugin feature. Modified Certificate Provider plugin feature. Database migration v17, for Device Profiles. Enrolment Client Updated to support CA Certificates, Wireless Profiles and Hardware Info. New Client Enrolment Protocol to support new features. Plugin Manifest Generator added to main solution. Improved AD search performance.
This commit is contained in:
@@ -0,0 +1,725 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.BI.Config;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Plugins;
|
||||
using Disco.Services.Plugins.Features.RepairProvider;
|
||||
using Disco.Services.Plugins.Features.WarrantyProvider;
|
||||
using Disco.Services.Users;
|
||||
using Exceptionless;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DiscoServicesJobs = Disco.Services.Interop.DiscoServices.Jobs;
|
||||
using PublishJobResult = Disco.Models.Services.Interop.DiscoServices.PublishJobResult;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class JobActionExtensions
|
||||
{
|
||||
|
||||
#region Create
|
||||
public static bool CanCreate()
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Actions.Create))
|
||||
return false;
|
||||
|
||||
if (!UserService.CurrentAuthorization.HasAny(Claims.Job.Types.CreateHMisc, Claims.Job.Types.CreateHNWar, Claims.Job.Types.CreateHWar, Claims.Job.Types.CreateSApp, Claims.Job.Types.CreateSImg, Claims.Job.Types.CreateSOS, Claims.Job.Types.CreateUMgmt))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Device Held
|
||||
public static bool CanDeviceHeld(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Properties.DeviceHeld))
|
||||
return false;
|
||||
|
||||
return (!j.ClosedDate.HasValue) && (j.DeviceSerialNumber != null) &&
|
||||
(!j.DeviceHeld.HasValue || j.DeviceReturnedDate.HasValue);
|
||||
}
|
||||
public static void OnDeviceHeld(this Job j, User Technician)
|
||||
{
|
||||
if (!j.CanDeviceHeld())
|
||||
throw new InvalidOperationException("Holding Device was Denied");
|
||||
|
||||
j.DeviceHeld = DateTime.Now;
|
||||
j.DeviceHeldTechUserId = Technician.UserId;
|
||||
j.DeviceReadyForReturn = null;
|
||||
j.DeviceReadyForReturnTechUserId = null;
|
||||
j.DeviceReturnedDate = null;
|
||||
j.DeviceReturnedTechUserId = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Device Ready for Return
|
||||
public static bool CanDeviceReadyForReturn(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Properties.DeviceReadyForReturn))
|
||||
return false;
|
||||
|
||||
return (!j.ClosedDate.HasValue) && j.DeviceHeld.HasValue &&
|
||||
!j.DeviceReadyForReturn.HasValue && !j.DeviceReturnedDate.HasValue;
|
||||
}
|
||||
public static void OnDeviceReadyForReturn(this Job j, User Technician)
|
||||
{
|
||||
if (!j.CanDeviceReadyForReturn())
|
||||
throw new InvalidOperationException("Device Ready for Return was Denied");
|
||||
|
||||
j.DeviceReadyForReturn = DateTime.Now;
|
||||
j.DeviceReadyForReturnTechUserId = Technician.UserId;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Device Returned
|
||||
public static bool CanDeviceReturned(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Properties.DeviceReturned))
|
||||
return false;
|
||||
|
||||
return (!j.ClosedDate.HasValue) && j.DeviceHeld.HasValue &&
|
||||
!j.DeviceReturnedDate.HasValue;
|
||||
}
|
||||
public static void OnDeviceReturned(this Job j, User Technician)
|
||||
{
|
||||
if (!j.CanDeviceReturned())
|
||||
throw new InvalidOperationException("Device Return was Denied");
|
||||
|
||||
j.DeviceReturnedDate = DateTime.Now;
|
||||
j.DeviceReturnedTechUserId = Technician.UserId;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Waiting For User Action
|
||||
public static bool CanWaitingForUserAction(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Properties.WaitingForUserAction))
|
||||
return false;
|
||||
|
||||
return !j.ClosedDate.HasValue && (j.UserId != null) && !j.WaitingForUserAction.HasValue;
|
||||
}
|
||||
public static void OnWaitingForUserAction(this Job j, DiscoDataContext Database, User Technician, string Reason)
|
||||
{
|
||||
if (!j.CanWaitingForUserAction())
|
||||
throw new InvalidOperationException("Waiting for User Action was Denied");
|
||||
|
||||
j.WaitingForUserAction = DateTime.Now;
|
||||
|
||||
// Write Log
|
||||
JobLog jobLog = new JobLog()
|
||||
{
|
||||
JobId = j.Id,
|
||||
TechUserId = Technician.UserId,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = string.Format("# Waiting on User Action\r\n{0}", string.IsNullOrWhiteSpace(Reason) ? "<no reason provided>" : Reason)
|
||||
};
|
||||
Database.JobLogs.Add(jobLog);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Not Waiting For User Action
|
||||
public static bool CanNotWaitingForUserAction(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Properties.NotWaitingForUserAction))
|
||||
return false;
|
||||
|
||||
return j.WaitingForUserAction.HasValue;
|
||||
}
|
||||
public static void OnNotWaitingForUserAction(this Job j, DiscoDataContext Database, User Technician, string Resolution)
|
||||
{
|
||||
if (!j.CanNotWaitingForUserAction())
|
||||
throw new InvalidOperationException("Not Waiting for User Action was Denied");
|
||||
|
||||
j.WaitingForUserAction = null;
|
||||
|
||||
// Write Log
|
||||
JobLog jobLog = new JobLog()
|
||||
{
|
||||
JobId = j.Id,
|
||||
TechUserId = Technician.UserId,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = string.Format("# User Action Resolved\r\n{0}", string.IsNullOrWhiteSpace(Resolution) ? "<no comment provided>" : Resolution)
|
||||
};
|
||||
Database.JobLogs.Add(jobLog);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Log Warranty
|
||||
public static bool CanLogWarranty(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Actions.LogWarranty))
|
||||
return false;
|
||||
|
||||
return !j.ClosedDate.HasValue &&
|
||||
(j.DeviceSerialNumber != null) &&
|
||||
j.JobTypeId == JobType.JobTypeIds.HWar &&
|
||||
!j.JobMetaWarranty.ExternalLoggedDate.HasValue;
|
||||
}
|
||||
public static void OnLogWarranty(this Job j, DiscoDataContext Database, string FaultDescription, List<JobAttachment> SendAttachments, PluginFeatureManifest WarrantyProviderDefinition, OrganisationAddress Address, User TechUser, Dictionary<string, string> WarrantyProviderProperties)
|
||||
{
|
||||
if (!j.CanLogWarranty())
|
||||
throw new InvalidOperationException("Log Warranty was Denied");
|
||||
|
||||
PublishJobResult publishJobResult = null;
|
||||
|
||||
using (WarrantyProviderFeature WarrantyProvider = WarrantyProviderDefinition.CreateInstance<WarrantyProviderFeature>())
|
||||
{
|
||||
if (SendAttachments != null && SendAttachments.Count > 0)
|
||||
{
|
||||
publishJobResult = DiscoServicesJobs.Publish(
|
||||
Database,
|
||||
j,
|
||||
TechUser,
|
||||
WarrantyProvider.WarrantyProviderId,
|
||||
null,
|
||||
FaultDescription,
|
||||
SendAttachments,
|
||||
AttachmentDataStoreExtensions.RepositoryFilename);
|
||||
|
||||
if (!publishJobResult.Success)
|
||||
throw new Exception(string.Format("Disco ICT Online Services failed with the following message: ", publishJobResult.ErrorMessage));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(FaultDescription))
|
||||
FaultDescription = publishJobResult.PublishMessage;
|
||||
else
|
||||
FaultDescription = string.Concat(FaultDescription, Environment.NewLine, "___", Environment.NewLine, publishJobResult.PublishMessage);
|
||||
}
|
||||
|
||||
string submitDescription;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(FaultDescription))
|
||||
submitDescription = j.GenerateFaultDescriptionFooter(Database, WarrantyProviderDefinition);
|
||||
else
|
||||
submitDescription = string.Concat(FaultDescription, Environment.NewLine, Environment.NewLine, j.GenerateFaultDescriptionFooter(Database, WarrantyProviderDefinition));
|
||||
|
||||
string providerRef = WarrantyProvider.SubmitJob(Database, j, Address, TechUser, submitDescription, WarrantyProviderProperties);
|
||||
|
||||
j.JobMetaWarranty.ExternalLoggedDate = DateTime.Now;
|
||||
j.JobMetaWarranty.ExternalName = WarrantyProvider.WarrantyProviderId;
|
||||
|
||||
if (providerRef != null && providerRef.Length > 100)
|
||||
j.JobMetaWarranty.ExternalReference = providerRef.Substring(0, 100);
|
||||
else
|
||||
j.JobMetaWarranty.ExternalReference = providerRef;
|
||||
|
||||
// Write Log
|
||||
JobLog jobLog = new JobLog()
|
||||
{
|
||||
JobId = j.Id,
|
||||
TechUserId = TechUser.UserId,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = string.Format("# Warranty Claim Submitted\r\nProvider: **{0}**\r\nAddress: **{1}**\r\nReference: **{2}**\r\n___\r\n```{3}```", WarrantyProvider.Manifest.Name, Address.Name, providerRef, FaultDescription)
|
||||
};
|
||||
Database.JobLogs.Add(jobLog);
|
||||
|
||||
if (publishJobResult != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
DiscoServicesJobs.UpdateRecipientReference(Database, j, publishJobResult.Id, publishJobResult.Secret, j.JobMetaWarranty.ExternalReference);
|
||||
}
|
||||
catch (Exception ex) { ex.ToExceptionless().Submit(); } // Ignore Errors as this is not completely necessary
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void OnLogWarranty(this Job j, DiscoDataContext Database, string FaultDescription, string ManualProviderName, string ManualProviderReference, OrganisationAddress Address, User TechUser)
|
||||
{
|
||||
if (!j.CanLogWarranty())
|
||||
throw new InvalidOperationException("Log Warranty was Denied");
|
||||
|
||||
j.JobMetaWarranty.ExternalLoggedDate = DateTime.Now;
|
||||
j.JobMetaWarranty.ExternalName = ManualProviderName;
|
||||
|
||||
if (ManualProviderReference != null && ManualProviderReference.Length > 100)
|
||||
j.JobMetaWarranty.ExternalReference = ManualProviderReference.Substring(0, 100);
|
||||
else
|
||||
j.JobMetaWarranty.ExternalReference = ManualProviderReference;
|
||||
|
||||
// Write Log
|
||||
JobLog jobLog = new JobLog()
|
||||
{
|
||||
JobId = j.Id,
|
||||
TechUserId = TechUser.UserId,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = string.Format("# Manual Warranty Claim Submitted\r\nProvider: **{0}**\r\nAddress: **{1}**\r\nReference: **{2}**\r\n___\r\n```{3}```", ManualProviderName, Address.Name, ManualProviderReference ?? "<none>", FaultDescription)
|
||||
};
|
||||
Database.JobLogs.Add(jobLog);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Convert HWar to HNWar
|
||||
public static bool CanConvertHWarToHNWar(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Actions.ConvertHWarToHNWar))
|
||||
return false;
|
||||
|
||||
return !j.ClosedDate.HasValue && (j.DeviceSerialNumber != null) &&
|
||||
j.JobTypeId == JobType.JobTypeIds.HWar && string.IsNullOrEmpty(j.JobMetaWarranty.ExternalReference);
|
||||
}
|
||||
public static void OnConvertHWarToHNWar(this Job j, DiscoDataContext Database)
|
||||
{
|
||||
if (!j.CanConvertHWarToHNWar())
|
||||
throw new InvalidOperationException("Convert HWar to HNWar was Denied");
|
||||
|
||||
var techUser = UserService.CurrentUser;
|
||||
|
||||
// Remove JobMetaWarranty
|
||||
if (j.JobMetaWarranty != null)
|
||||
Database.JobMetaWarranties.Remove(j.JobMetaWarranty);
|
||||
|
||||
// Add JobMetaNonWarranty
|
||||
var metaHNWar = new JobMetaNonWarranty() { Job = j };
|
||||
Database.JobMetaNonWarranties.Add(metaHNWar);
|
||||
|
||||
// Swap Job Sub Types
|
||||
List<string> jobSubTypes = j.JobSubTypes.Select(jst => jst.Id).ToList();
|
||||
j.JobSubTypes.Clear();
|
||||
foreach (var jst in Database.JobSubTypes.Where(i => i.JobTypeId == JobType.JobTypeIds.HNWar && jobSubTypes.Contains(i.Id)))
|
||||
j.JobSubTypes.Add(jst);
|
||||
|
||||
// Add Components
|
||||
var components = Database.DeviceComponents.Include("JobSubTypes").Where(c => !c.DeviceModelId.HasValue || c.DeviceModelId == j.Device.DeviceModelId);
|
||||
var jobComponents = new List<DeviceComponent>();
|
||||
foreach (var component in components)
|
||||
{
|
||||
if (component.JobSubTypes.Count == 0)
|
||||
{
|
||||
jobComponents.Add(component);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var st in component.JobSubTypes)
|
||||
{
|
||||
foreach (var jst in j.JobSubTypes)
|
||||
{
|
||||
if (st.JobTypeId == jst.JobTypeId && st.Id == jst.Id)
|
||||
{
|
||||
jobComponents.Add(component);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (jobComponents.Contains(component))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var component in jobComponents)
|
||||
{
|
||||
Database.JobComponents.Add(new JobComponent()
|
||||
{
|
||||
Job = j,
|
||||
TechUserId = techUser.UserId,
|
||||
Cost = component.Cost,
|
||||
Description = component.Description
|
||||
});
|
||||
}
|
||||
|
||||
// Write Log
|
||||
JobLog jobLog = new JobLog()
|
||||
{
|
||||
JobId = j.Id,
|
||||
TechUserId = techUser.UserId,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = string.Format("# Job Type Converted\r\nFrom: **{0}**\r\nTo: **{1}**", Database.JobTypes.Find(JobType.JobTypeIds.HWar), Database.JobTypes.Find(JobType.JobTypeIds.HNWar))
|
||||
};
|
||||
Database.JobLogs.Add(jobLog);
|
||||
|
||||
j.JobTypeId = JobType.JobTypeIds.HNWar;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Warranty Completed
|
||||
public static bool CanWarrantyCompleted(this Job j)
|
||||
{
|
||||
return (j.JobTypeId == JobType.JobTypeIds.HWar) &&
|
||||
j.JobMetaWarranty.ExternalLoggedDate.HasValue &&
|
||||
!j.JobMetaWarranty.ExternalCompletedDate.HasValue;
|
||||
}
|
||||
public static void OnWarrantyCompleted(this Job j)
|
||||
{
|
||||
if (!j.CanWarrantyCompleted())
|
||||
throw new InvalidOperationException("Warranty Completed was Denied");
|
||||
|
||||
j.JobMetaWarranty.ExternalCompletedDate = DateTime.Now;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Insurance Claim Form Sent
|
||||
public static bool CanInsuranceClaimFormSent(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Properties.NonWarrantyProperties.InsuranceClaimFormSent))
|
||||
return false;
|
||||
|
||||
return (j.JobTypeId == JobType.JobTypeIds.HNWar) &&
|
||||
j.JobMetaNonWarranty.IsInsuranceClaim &&
|
||||
!j.JobMetaInsurance.ClaimFormSentDate.HasValue;
|
||||
}
|
||||
public static void OnInsuranceClaimFormSent(this Job j)
|
||||
{
|
||||
if (!j.CanInsuranceClaimFormSent())
|
||||
throw new InvalidOperationException("Insurance Claim Form Sent was Denied");
|
||||
|
||||
var techUser = UserService.CurrentUser;
|
||||
|
||||
j.JobMetaInsurance.ClaimFormSentDate = DateTime.Now;
|
||||
j.JobMetaInsurance.ClaimFormSentUserId = techUser.UserId;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Log Repair
|
||||
public static bool CanLogRepair(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Actions.LogRepair))
|
||||
return false;
|
||||
|
||||
return (j.JobTypeId == JobType.JobTypeIds.HNWar) &&
|
||||
(j.DeviceSerialNumber != null) &&
|
||||
!j.JobMetaNonWarranty.RepairerLoggedDate.HasValue &&
|
||||
!j.JobMetaNonWarranty.RepairerCompletedDate.HasValue;
|
||||
}
|
||||
public static void OnLogRepair(this Job j, DiscoDataContext Database, string RepairDescription, List<JobAttachment> SendAttachments, PluginFeatureManifest RepairProviderDefinition, OrganisationAddress Address, User TechUser, Dictionary<string, string> RepairProviderProperties)
|
||||
{
|
||||
if (!j.CanLogRepair())
|
||||
throw new InvalidOperationException("Log Repair was Denied");
|
||||
|
||||
PublishJobResult publishJobResult = null;
|
||||
|
||||
using (RepairProviderFeature RepairProvider = RepairProviderDefinition.CreateInstance<RepairProviderFeature>())
|
||||
{
|
||||
if (SendAttachments != null && SendAttachments.Count > 0)
|
||||
{
|
||||
publishJobResult = DiscoServicesJobs.Publish(
|
||||
Database,
|
||||
j,
|
||||
TechUser,
|
||||
RepairProvider.ProviderId,
|
||||
null,
|
||||
RepairDescription,
|
||||
SendAttachments,
|
||||
AttachmentDataStoreExtensions.RepositoryFilename);
|
||||
|
||||
if (!publishJobResult.Success)
|
||||
throw new Exception(string.Format("Disco ICT Online Services failed with the following message: ", publishJobResult.ErrorMessage));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(RepairDescription))
|
||||
RepairDescription = publishJobResult.PublishMessage;
|
||||
else
|
||||
RepairDescription = string.Concat(RepairDescription, Environment.NewLine, "___", Environment.NewLine, publishJobResult.PublishMessage);
|
||||
}
|
||||
|
||||
string submitDescription;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(RepairDescription))
|
||||
submitDescription = j.GenerateFaultDescriptionFooter(Database, RepairProviderDefinition);
|
||||
else
|
||||
submitDescription = string.Concat(RepairDescription, Environment.NewLine, Environment.NewLine, j.GenerateFaultDescriptionFooter(Database, RepairProviderDefinition));
|
||||
|
||||
string providerRef = RepairProvider.SubmitJob(Database, j, Address, TechUser, submitDescription, RepairProviderProperties);
|
||||
|
||||
j.JobMetaNonWarranty.RepairerLoggedDate = DateTime.Now;
|
||||
j.JobMetaNonWarranty.RepairerName = RepairProvider.ProviderId;
|
||||
|
||||
if (providerRef != null && providerRef.Length > 100)
|
||||
j.JobMetaNonWarranty.RepairerReference = providerRef.Substring(0, 100);
|
||||
else
|
||||
j.JobMetaNonWarranty.RepairerReference = providerRef;
|
||||
|
||||
// Write Log
|
||||
JobLog jobLog = new JobLog()
|
||||
{
|
||||
JobId = j.Id,
|
||||
TechUserId = TechUser.UserId,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = string.Format("# Repair Request Submitted\r\nProvider: **{0}**\r\nAddress: **{1}**\r\nReference: **{2}**\r\n___\r\n```{3}```", RepairProvider.Manifest.Name, Address.Name, providerRef, RepairDescription)
|
||||
};
|
||||
Database.JobLogs.Add(jobLog);
|
||||
|
||||
if (publishJobResult != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
DiscoServicesJobs.UpdateRecipientReference(Database, j, publishJobResult.Id, publishJobResult.Secret, j.JobMetaNonWarranty.RepairerReference);
|
||||
}
|
||||
catch (Exception ex) { ex.ToExceptionless().Submit(); } // Ignore Errors as this is not completely necessary
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void OnLogRepair(this Job j, DiscoDataContext Database, string FaultDescription, string ManualProviderName, string ManualProviderReference, OrganisationAddress Address, User TechUser)
|
||||
{
|
||||
if (!j.CanLogRepair())
|
||||
throw new InvalidOperationException("Log Repair was Denied");
|
||||
|
||||
j.JobMetaNonWarranty.RepairerLoggedDate = DateTime.Now;
|
||||
j.JobMetaNonWarranty.RepairerName = ManualProviderName;
|
||||
|
||||
if (ManualProviderReference != null && ManualProviderReference.Length > 100)
|
||||
j.JobMetaNonWarranty.RepairerReference = ManualProviderReference.Substring(0, 100);
|
||||
else
|
||||
j.JobMetaNonWarranty.RepairerReference = ManualProviderReference;
|
||||
|
||||
// Write Log
|
||||
JobLog jobLog = new JobLog()
|
||||
{
|
||||
JobId = j.Id,
|
||||
TechUserId = TechUser.UserId,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = string.Format("# Manual Repair Request Submitted\r\nProvider: **{0}**\r\nAddress: **{1}**\r\nReference: **{2}**\r\n___\r\n```{3}```", ManualProviderName, Address.Name, ManualProviderReference ?? "<none>", FaultDescription)
|
||||
};
|
||||
Database.JobLogs.Add(jobLog);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Repair Complete
|
||||
public static bool CanRepairComplete(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Properties.NonWarrantyProperties.RepairerCompletedDate))
|
||||
return false;
|
||||
|
||||
return (j.JobTypeId == JobType.JobTypeIds.HNWar) &&
|
||||
j.JobMetaNonWarranty.RepairerLoggedDate.HasValue &&
|
||||
!j.JobMetaNonWarranty.RepairerCompletedDate.HasValue;
|
||||
}
|
||||
public static void OnRepairComplete(this Job j)
|
||||
{
|
||||
if (!j.CanRepairComplete())
|
||||
throw new InvalidOperationException("Repair Complete was Denied");
|
||||
|
||||
j.JobMetaNonWarranty.RepairerCompletedDate = DateTime.Now;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Close
|
||||
public static void OnCloseNormally(this Job j, User Technician)
|
||||
{
|
||||
if (!j.CanCloseNormally())
|
||||
throw new InvalidOperationException("Close was Denied");
|
||||
|
||||
j.ClosedDate = DateTime.Now;
|
||||
j.ClosedTechUserId = Technician.UserId;
|
||||
}
|
||||
|
||||
private static bool CanCloseNever(this Job j, JobQueueJob IgnoreJobQueueJob = null)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Actions.Close))
|
||||
return true;
|
||||
|
||||
if (j.ClosedDate.HasValue)
|
||||
return true; // Job already Closed
|
||||
|
||||
if (j.DeviceHeld.HasValue && !j.DeviceReturnedDate.HasValue)
|
||||
return true; // Device not returned to User
|
||||
|
||||
if (j.WaitingForUserAction.HasValue)
|
||||
return true; // Job waiting on User Action
|
||||
|
||||
if (j.JobQueues != null)
|
||||
{
|
||||
if (IgnoreJobQueueJob == null)
|
||||
{
|
||||
if (j.JobQueues.Any(jqj => !jqj.RemovedDate.HasValue))
|
||||
return true; // Job associated with a Job Queue
|
||||
}
|
||||
else
|
||||
{
|
||||
if (j.JobQueues.Any(jqj => jqj.Id != IgnoreJobQueueJob.Id && !jqj.RemovedDate.HasValue))
|
||||
return true; // Job associated with a Job Queue
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool CanCloseNormally(this Job j)
|
||||
{
|
||||
if (j.CanCloseNever())
|
||||
return false;
|
||||
|
||||
return j.CanCloseNormallyInternal();
|
||||
}
|
||||
|
||||
private static bool CanCloseNormallyInternal(this Job j)
|
||||
{
|
||||
switch (j.JobTypeId)
|
||||
{
|
||||
case JobType.JobTypeIds.HWar:
|
||||
|
||||
if (!string.IsNullOrEmpty(j.JobMetaWarranty.ExternalReference) && !j.JobMetaWarranty.ExternalCompletedDate.HasValue)
|
||||
return false; // Job Logged (Warranty) but not completed
|
||||
|
||||
break;
|
||||
case JobType.JobTypeIds.HNWar:
|
||||
|
||||
if (j.JobMetaNonWarranty.RepairerLoggedDate.HasValue && !j.JobMetaNonWarranty.RepairerCompletedDate.HasValue)
|
||||
return false; // Job Logged (Repair) but not completed
|
||||
|
||||
if (j.JobMetaNonWarranty.AccountingChargeRequiredDate.HasValue && !j.JobMetaNonWarranty.AccountingChargeAddedDate.HasValue)
|
||||
return false; // Accounting Charge Required, but not added
|
||||
|
||||
if ((j.JobMetaNonWarranty.AccountingChargeRequiredDate.HasValue || j.JobMetaNonWarranty.AccountingChargeAddedDate.HasValue) && !j.JobMetaNonWarranty.AccountingChargePaidDate.HasValue)
|
||||
return false; // Accounting Charge Required or Added, but not paid
|
||||
|
||||
if (j.JobMetaNonWarranty.IsInsuranceClaim && !j.JobMetaInsurance.ClaimFormSentDate.HasValue)
|
||||
return false; // Is Insurance Claim, but claim form not sent
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool CanCloseJobNormallyAfterRemoved(this JobQueueJob jqj)
|
||||
{
|
||||
if (jqj.Job.CanCloseNever(jqj))
|
||||
return false;
|
||||
|
||||
return jqj.Job.CanCloseNormallyInternal();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Force Close
|
||||
public static bool CanCloseForced(this Job j)
|
||||
{
|
||||
List<string> reasons;
|
||||
|
||||
return CanCloseForced(j, out reasons);
|
||||
}
|
||||
public static bool CanCloseForced(this Job j, out List<string> Reasons)
|
||||
{
|
||||
Reasons = null;
|
||||
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Actions.ForceClose))
|
||||
return false;
|
||||
|
||||
if (j.CanCloseNever())
|
||||
return false;
|
||||
|
||||
if (j.CanCloseNormally())
|
||||
return false;
|
||||
|
||||
Reasons = new List<string>();
|
||||
|
||||
switch (j.JobTypeId)
|
||||
{
|
||||
case JobType.JobTypeIds.HWar:
|
||||
if (!string.IsNullOrEmpty(j.JobMetaWarranty.ExternalReference) && !j.JobMetaWarranty.ExternalCompletedDate.HasValue)
|
||||
Reasons.Add("Warranty Job Not Completed"); // Job Logged (Warranty) but not completed
|
||||
break;
|
||||
case JobType.JobTypeIds.HNWar:
|
||||
|
||||
if (j.JobMetaNonWarranty.RepairerLoggedDate.HasValue && !j.JobMetaNonWarranty.RepairerCompletedDate.HasValue)
|
||||
Reasons.Add("Repair Job Not Completed"); // Job Logged (Repair) but not completed
|
||||
|
||||
if (j.JobMetaNonWarranty.AccountingChargeRequiredDate.HasValue && (!j.JobMetaNonWarranty.AccountingChargeAddedDate.HasValue && !j.JobMetaNonWarranty.AccountingChargePaidDate.HasValue))
|
||||
Reasons.Add("Accounting Charge Required But Not Added Or Paid"); // Accounting Charge Required, but not added or paid
|
||||
else if (j.JobMetaNonWarranty.AccountingChargeRequiredDate.HasValue && !j.JobMetaNonWarranty.AccountingChargeAddedDate.HasValue)
|
||||
Reasons.Add("Accounting Charge Required But Not Added"); // Accounting Charge Required, but not added
|
||||
else if (j.JobMetaNonWarranty.AccountingChargeAddedDate.HasValue && !j.JobMetaNonWarranty.AccountingChargePaidDate.HasValue)
|
||||
Reasons.Add("Accounting Charge Added But Not Paid"); // Accounting Charge Added, but not paid
|
||||
else if (j.JobMetaNonWarranty.AccountingChargeRequiredDate.HasValue && !j.JobMetaNonWarranty.AccountingChargePaidDate.HasValue)
|
||||
Reasons.Add("Accounting Charge Required But Not Paid"); // Accounting Charge Required, but not paid
|
||||
|
||||
if (j.JobMetaNonWarranty.IsInsuranceClaim && !j.JobMetaInsurance.ClaimFormSentDate.HasValue)
|
||||
Reasons.Add("Insurance Claim Form Not Sent"); // Is Insurance Claim, but claim form not sent
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return (Reasons.Count > 0);
|
||||
}
|
||||
public static void OnCloseForced(this Job j, DiscoDataContext Database, User Technician, string Reason)
|
||||
{
|
||||
if (!j.CanCloseForced())
|
||||
throw new InvalidOperationException("Force Close was Denied");
|
||||
|
||||
// Write Log
|
||||
JobLog jobLog = new JobLog()
|
||||
{
|
||||
JobId = j.Id,
|
||||
TechUserId = Technician.UserId,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = string.Format("# Job Forcibly Closed\r\n{0}", string.IsNullOrWhiteSpace(Reason) ? "<no reason provided>" : Reason)
|
||||
};
|
||||
Database.JobLogs.Add(jobLog);
|
||||
|
||||
j.ClosedDate = DateTime.Now;
|
||||
j.ClosedTechUserId = Technician.UserId;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Reopen
|
||||
public static bool CanReopen(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Actions.Reopen))
|
||||
return false;
|
||||
|
||||
return j.ClosedDate.HasValue;
|
||||
}
|
||||
public static void OnReopen(this Job j)
|
||||
{
|
||||
if (!j.CanReopen())
|
||||
throw new InvalidOperationException("Reopen was Denied");
|
||||
|
||||
j.ClosedDate = null;
|
||||
j.ClosedTechUserId = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Delete
|
||||
public static bool CanDelete(this Job j)
|
||||
{
|
||||
if (!UserService.CurrentAuthorization.Has(Claims.Job.Actions.Delete))
|
||||
return false;
|
||||
|
||||
return j.ClosedDate.HasValue;
|
||||
}
|
||||
public static void OnDelete(this Job j, DiscoDataContext Database)
|
||||
{
|
||||
// Job Sub Types
|
||||
j.JobSubTypes.Clear();
|
||||
|
||||
// Job Attachments
|
||||
foreach (var ja in j.JobAttachments.ToArray())
|
||||
ja.OnDelete(Database);
|
||||
j.JobAttachments.Clear();
|
||||
|
||||
// Job Components
|
||||
foreach (var jc in j.JobComponents.ToArray())
|
||||
Database.JobComponents.Remove(jc);
|
||||
j.JobComponents.Clear();
|
||||
|
||||
// Job Queue Jobs
|
||||
foreach (var jqj in j.JobQueues.ToArray())
|
||||
Database.JobQueueJobs.Remove(jqj);
|
||||
j.JobQueues.Clear();
|
||||
|
||||
// Job Logs
|
||||
foreach (var jl in j.JobLogs.ToArray())
|
||||
Database.JobLogs.Remove(jl);
|
||||
j.JobLogs.Clear();
|
||||
|
||||
// Job Meta
|
||||
if (j.JobMetaInsurance != null)
|
||||
{
|
||||
Database.JobMetaInsurances.Remove(j.JobMetaInsurance);
|
||||
j.JobMetaInsurance = null;
|
||||
}
|
||||
if (j.JobMetaNonWarranty != null)
|
||||
{
|
||||
Database.JobMetaNonWarranties.Remove(j.JobMetaNonWarranty);
|
||||
j.JobMetaNonWarranty = null;
|
||||
}
|
||||
if (j.JobMetaWarranty != null)
|
||||
{
|
||||
Database.JobMetaWarranties.Remove(j.JobMetaWarranty);
|
||||
j.JobMetaWarranty = null;
|
||||
}
|
||||
|
||||
// Job
|
||||
Database.Jobs.Remove(j);
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Documents;
|
||||
using Disco.Models.Services.Jobs.JobLists;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Interop.ActiveDirectory;
|
||||
using Disco.Services.Plugins;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
@@ -195,5 +198,190 @@ namespace Disco.Services
|
||||
var statusId = j.CalculateStatusId();
|
||||
return new Tuple<string, string>(statusId, JobStatusDescription(statusId, j));
|
||||
}
|
||||
|
||||
public static List<DocumentTemplate> AvailableDocumentTemplates(this Job j, DiscoDataContext Database, User User, DateTime TimeStamp)
|
||||
{
|
||||
var dts = Database.DocumentTemplates.Include("JobSubTypes")
|
||||
.Where(dt => dt.Scope == DocumentTemplate.DocumentTemplateScopes.Job)
|
||||
.ToList();
|
||||
|
||||
foreach (var dt in dts.ToArray())
|
||||
{
|
||||
if (dt.JobSubTypes.Count != 0)
|
||||
{ // Filter Applied
|
||||
bool match = false;
|
||||
foreach (var st in j.JobSubTypes)
|
||||
{
|
||||
if (dt.JobSubTypes.Contains(st))
|
||||
{
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match)
|
||||
dts.Remove(dt);
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate Filters
|
||||
dts = dts.Where(dt => dt.FilterExpressionMatches(j, Database, User, TimeStamp, DocumentState.DefaultState())).ToList();
|
||||
|
||||
return dts;
|
||||
}
|
||||
|
||||
public static DateTime ValidateDateAfterOpened(this Job j, DateTime d)
|
||||
{
|
||||
if (d < j.OpenedDate)
|
||||
{
|
||||
if (d > j.OpenedDate.AddMinutes(-1))
|
||||
return j.OpenedDate;
|
||||
else
|
||||
throw new ArgumentException("The Date must be >= the Open Date.", "d");
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
public static string GenerateFaultDescription(this Job j, DiscoDataContext Database)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("Faulty Components:");
|
||||
foreach (var jst in j.JobSubTypes)
|
||||
sb.Append("- ").AppendLine(jst.Description).AppendLine(" - ");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string GenerateFaultDescriptionFooter(this Job j, DiscoDataContext Database, PluginFeatureManifest WarrantyProviderDefinition)
|
||||
{
|
||||
var versionDisco = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
return string.Format("Automation by Disco v{0}.{1}.{2:0000}.{3:0000} (Provider: {4} v{5})",
|
||||
versionDisco.Major, versionDisco.Minor, versionDisco.Build, versionDisco.Revision, WarrantyProviderDefinition.Id, WarrantyProviderDefinition.PluginManifest.Version.ToString(4));
|
||||
}
|
||||
|
||||
public static void UpdateSubTypes(this Job j, DiscoDataContext Database, List<JobSubType> SubTypes, bool AddComponents, User TechUser)
|
||||
{
|
||||
if (SubTypes == null || SubTypes.Count == 0)
|
||||
throw new ArgumentException("The Job must contain at least one Sub Type");
|
||||
|
||||
List<JobSubType> addedSubTypes = new List<JobSubType>();
|
||||
List<JobSubType> removedSubTypes = new List<JobSubType>();
|
||||
|
||||
// Removed Sub Types
|
||||
foreach (var t in j.JobSubTypes.ToArray())
|
||||
if (!SubTypes.Contains(t))
|
||||
{
|
||||
removedSubTypes.Add(t);
|
||||
j.JobSubTypes.Remove(t);
|
||||
}
|
||||
// Added Sub Types
|
||||
foreach (var t in SubTypes)
|
||||
if (!j.JobSubTypes.Contains(t))
|
||||
{
|
||||
addedSubTypes.Add(t);
|
||||
j.JobSubTypes.Add(t);
|
||||
}
|
||||
|
||||
// Write Log
|
||||
if (addedSubTypes.Count > 0 || removedSubTypes.Count > 0)
|
||||
{
|
||||
StringBuilder logBuilder = new StringBuilder();
|
||||
logBuilder.AppendLine("# Updated Job Sub Types");
|
||||
if (removedSubTypes.Count > 0)
|
||||
{
|
||||
logBuilder.AppendLine().AppendLine("Removed:");
|
||||
foreach (var t in removedSubTypes)
|
||||
logBuilder.Append("- **").Append(t.ToString()).AppendLine("**");
|
||||
}
|
||||
if (addedSubTypes.Count > 0)
|
||||
{
|
||||
logBuilder.AppendLine().AppendLine("Added:");
|
||||
foreach (var t in addedSubTypes)
|
||||
logBuilder.Append("- **").Append(t.ToString()).AppendLine("**");
|
||||
}
|
||||
Database.JobLogs.Add(new JobLog()
|
||||
{
|
||||
JobId = j.Id,
|
||||
TechUserId = TechUser.UserId,
|
||||
Timestamp = DateTime.Now,
|
||||
Comments = logBuilder.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
// Add Components
|
||||
if (AddComponents && addedSubTypes.Count > 0 && j.DeviceSerialNumber != null)
|
||||
{
|
||||
var components = Database.DeviceComponents.Include("JobSubTypes").Where(c => !c.DeviceModelId.HasValue || c.DeviceModelId == j.Device.DeviceModelId);
|
||||
var addedComponents = new List<DeviceComponent>();
|
||||
foreach (var c in components)
|
||||
{
|
||||
foreach (var st in c.JobSubTypes)
|
||||
{
|
||||
foreach (var jst in addedSubTypes)
|
||||
{
|
||||
if (st.JobTypeId == jst.JobTypeId && st.Id == jst.Id)
|
||||
{
|
||||
addedComponents.Add(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (addedComponents.Contains(c))
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach (var c in addedComponents)
|
||||
{
|
||||
if (!j.JobComponents.Any(jc => jc.Description.Equals(c.Description, StringComparison.OrdinalIgnoreCase)))
|
||||
{ // Job Component with matching Description doesn't exist.
|
||||
Database.JobComponents.Add(new JobComponent()
|
||||
{
|
||||
Job = j,
|
||||
TechUserId = TechUser.UserId,
|
||||
Cost = c.Cost,
|
||||
Description = c.Description
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> FilterCreatableTypePermissions(AuthorizationToken Authorization)
|
||||
{
|
||||
if (!Authorization.HasAll(Claims.Job.Types.CreateHMisc, Claims.Job.Types.CreateHNWar, Claims.Job.Types.CreateHWar, Claims.Job.Types.CreateSApp, Claims.Job.Types.CreateSImg, Claims.Job.Types.CreateSOS, Claims.Job.Types.CreateUMgmt))
|
||||
{
|
||||
// Must Filter
|
||||
List<string> allowedTypes = new List<string>(6);
|
||||
if (Authorization.Has(Claims.Job.Types.CreateHMisc))
|
||||
allowedTypes.Add(JobType.JobTypeIds.HMisc);
|
||||
if (Authorization.Has(Claims.Job.Types.CreateHNWar))
|
||||
allowedTypes.Add(JobType.JobTypeIds.HNWar);
|
||||
if (Authorization.Has(Claims.Job.Types.CreateHWar))
|
||||
allowedTypes.Add(JobType.JobTypeIds.HWar);
|
||||
if (Authorization.Has(Claims.Job.Types.CreateSApp))
|
||||
allowedTypes.Add(JobType.JobTypeIds.SApp);
|
||||
if (Authorization.Has(Claims.Job.Types.CreateSImg))
|
||||
allowedTypes.Add(JobType.JobTypeIds.SImg);
|
||||
if (Authorization.Has(Claims.Job.Types.CreateSOS))
|
||||
allowedTypes.Add(JobType.JobTypeIds.SOS);
|
||||
if (Authorization.Has(Claims.Job.Types.CreateUMgmt))
|
||||
allowedTypes.Add(JobType.JobTypeIds.UMgmt);
|
||||
|
||||
return allowedTypes;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IQueryable<JobType> FilterCreatableTypePermissions(this IQueryable<JobType> JobTypes, AuthorizationToken Authorization)
|
||||
{
|
||||
var allowedTypes = FilterCreatableTypePermissions(Authorization);
|
||||
|
||||
if (allowedTypes != null)
|
||||
{
|
||||
return JobTypes.Where(jt => allowedTypes.Contains(jt.Id));
|
||||
}
|
||||
|
||||
return JobTypes;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using Disco.Models.Repository;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class JobFlagExtensions
|
||||
{
|
||||
|
||||
private static Dictionary<string, Dictionary<long, string>> allFlags;
|
||||
private static void CacheAllFlags()
|
||||
{
|
||||
if (allFlags == null)
|
||||
{
|
||||
var fType = typeof(Job.UserManagementFlags);
|
||||
var fMembers = fType.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||
|
||||
var flags = new Dictionary<string, Dictionary<long, string>>();
|
||||
foreach (var f in fMembers)
|
||||
{
|
||||
DisplayAttribute display = (DisplayAttribute)(f.GetCustomAttributes(typeof(DisplayAttribute), false)[0]);
|
||||
string gn = display.GroupName;
|
||||
Dictionary<long, string> g;
|
||||
if (!flags.TryGetValue(gn, out g))
|
||||
{
|
||||
g = new Dictionary<long, string>();
|
||||
flags.Add(gn, g);
|
||||
}
|
||||
g[(long)f.GetRawConstantValue()] = display.Name;
|
||||
}
|
||||
allFlags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<string, List<Tuple<long, string, bool>>> ValidFlagsGrouped(this Job j)
|
||||
{
|
||||
Dictionary<string, List<Tuple<long, string, bool>>> validFlags = new Dictionary<string, List<Tuple<long, string, bool>>>();
|
||||
|
||||
CacheAllFlags();
|
||||
|
||||
var currentFlags = (long)(j.Flags ?? 0);
|
||||
|
||||
foreach (var jt in j.JobSubTypes)
|
||||
{
|
||||
Dictionary<long, string> g;
|
||||
if (allFlags.TryGetValue(jt.Id, out g))
|
||||
{
|
||||
validFlags[jt.Id] = g.Select(f => new Tuple<long, string, bool>(f.Key, f.Value, ((currentFlags & f.Key) == f.Key))).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
validFlags[jt.Id] = null;
|
||||
}
|
||||
}
|
||||
return validFlags;
|
||||
}
|
||||
public static Dictionary<long, Tuple<string, bool>> ValidFlags(this Job j)
|
||||
{
|
||||
Dictionary<long, Tuple<string, bool>> validFlags = new Dictionary<long, Tuple<string, bool>>();
|
||||
|
||||
CacheAllFlags();
|
||||
|
||||
var currentFlags = (long)(j.Flags ?? 0);
|
||||
|
||||
foreach (var jt in j.JobSubTypes)
|
||||
{
|
||||
Dictionary<long, string> g;
|
||||
if (allFlags.TryGetValue(jt.Id, out g))
|
||||
{
|
||||
foreach (var f in g)
|
||||
validFlags[f.Key] = new Tuple<string, bool>(string.Format("{0}: {1}", jt.Description, f.Value), ((currentFlags & f.Key) == f.Key));
|
||||
}
|
||||
}
|
||||
return validFlags;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Jobs.JobQueues;
|
||||
using Disco.Services.Users;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class JobQueueExtensions
|
||||
{
|
||||
|
||||
#region Edit Sla
|
||||
public static bool CanEditSla(this JobQueueJob jqj)
|
||||
{
|
||||
if (jqj.RemovedDate.HasValue)
|
||||
return false;
|
||||
|
||||
if (UserService.CurrentAuthorization.Has(Claims.Job.Properties.JobQueueProperties.EditAnySLA))
|
||||
{
|
||||
// Can edit ANY queue
|
||||
return true;
|
||||
}
|
||||
else if (UserService.CurrentAuthorization.Has(Claims.Job.Properties.JobQueueProperties.EditOwnSLA))
|
||||
{
|
||||
// Can edit from OWN queue
|
||||
return JobQueueService.UsersQueues(UserService.CurrentUser).Any(q => q.JobQueue.Id == jqj.JobQueueId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static void OnEditSla(this JobQueueJob jqj, DateTime? SlaExpiresDate)
|
||||
{
|
||||
if (!jqj.CanEditSla())
|
||||
throw new InvalidOperationException("Editing job SLA for this queue is denied");
|
||||
|
||||
if (SlaExpiresDate.HasValue && jqj.AddedDate > SlaExpiresDate.Value)
|
||||
throw new ArgumentException("The SLA Expires Date must be greater than the Added Date", "SLAExpiresDate");
|
||||
|
||||
jqj.SLAExpiresDate = SlaExpiresDate;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Edit Priority
|
||||
public static bool CanEditPriority(this JobQueueJob jqj)
|
||||
{
|
||||
if (jqj.RemovedDate.HasValue)
|
||||
return false;
|
||||
|
||||
if (UserService.CurrentAuthorization.Has(Claims.Job.Properties.JobQueueProperties.EditAnyPriority))
|
||||
{
|
||||
// Can edit ANY queue
|
||||
return true;
|
||||
}
|
||||
else if (UserService.CurrentAuthorization.Has(Claims.Job.Properties.JobQueueProperties.EditOwnPriority))
|
||||
{
|
||||
// Can edit from OWN queue
|
||||
return JobQueueService.UsersQueues(UserService.CurrentUser).Any(q => q.JobQueue.Id == jqj.JobQueueId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static void OnEditPriority(this JobQueueJob jqj, JobQueuePriority Priority)
|
||||
{
|
||||
if (!jqj.CanEditPriority())
|
||||
throw new InvalidOperationException("Editing job priority for this queue is denied");
|
||||
|
||||
jqj.Priority = Priority;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Edit Comments
|
||||
private static bool CanEditComments(this JobQueueJob jqj)
|
||||
{
|
||||
if (UserService.CurrentAuthorization.Has(Claims.Job.Properties.JobQueueProperties.EditAnyComments))
|
||||
{
|
||||
// Can edit ANY queue
|
||||
return true;
|
||||
}
|
||||
else if (UserService.CurrentAuthorization.Has(Claims.Job.Properties.JobQueueProperties.EditOwnComments))
|
||||
{
|
||||
// Can edit from OWN queue
|
||||
return JobQueueService.UsersQueues(UserService.CurrentUser).Any(q => q.JobQueue.Id == jqj.JobQueueId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static bool CanEditAddedComment(this JobQueueJob jqj)
|
||||
{
|
||||
return jqj.CanEditComments();
|
||||
}
|
||||
public static bool CanEditRemovedComment(this JobQueueJob jqj)
|
||||
{
|
||||
if (!jqj.RemovedDate.HasValue)
|
||||
return false;
|
||||
|
||||
return jqj.CanEditComments();
|
||||
}
|
||||
public static void OnEditAddedComment(this JobQueueJob jqj, string AddedComment)
|
||||
{
|
||||
if (!jqj.CanEditAddedComment())
|
||||
throw new InvalidOperationException("Editing job added comments for this queue is denied");
|
||||
|
||||
jqj.AddedComment = string.IsNullOrWhiteSpace(AddedComment) ? null : AddedComment.Trim();
|
||||
}
|
||||
public static void OnEditRemovedComment(this JobQueueJob jqj, string RemovedComment)
|
||||
{
|
||||
if (!jqj.CanEditRemovedComment())
|
||||
throw new InvalidOperationException("Editing job removed comments for this queue is denied");
|
||||
|
||||
jqj.RemovedComment = string.IsNullOrWhiteSpace(RemovedComment) ? null : RemovedComment.Trim();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Remove
|
||||
public static bool CanRemove(this JobQueueJob jqj)
|
||||
{
|
||||
if (jqj.RemovedDate.HasValue)
|
||||
return false;
|
||||
|
||||
if (UserService.CurrentAuthorization.Has(Claims.Job.Actions.RemoveAnyQueues))
|
||||
{
|
||||
// Can remove from ANY queue
|
||||
return true;
|
||||
}
|
||||
else if (UserService.CurrentAuthorization.Has(Claims.Job.Actions.RemoveOwnQueues))
|
||||
{
|
||||
// Can remove from OWN queue
|
||||
return JobQueueService.UsersQueues(UserService.CurrentUser).Any(q => q.JobQueue.Id == jqj.JobQueueId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static void OnRemove(this JobQueueJob jqj, User Technician, string Comment)
|
||||
{
|
||||
if (!jqj.CanRemove())
|
||||
throw new InvalidOperationException("Removing job from queue is denied");
|
||||
|
||||
jqj.RemovedDate = DateTime.Now;
|
||||
jqj.RemovedUserId = Technician.UserId;
|
||||
jqj.RemovedComment = string.IsNullOrWhiteSpace(Comment) ? null : Comment.Trim();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Add
|
||||
public static bool CanAddQueues(this Job j)
|
||||
{
|
||||
// Job Closed?
|
||||
if (j.ClosedDate.HasValue)
|
||||
return false;
|
||||
|
||||
if (UserService.CurrentAuthorization.HasAny(Claims.Job.Actions.AddAnyQueues, Claims.Job.Actions.AddOwnQueues))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
public static bool CanAddQueue(this Job j, JobQueue jq)
|
||||
{
|
||||
// Shortcut
|
||||
if (!j.CanAddQueues())
|
||||
return false;
|
||||
|
||||
// Already in Queue?
|
||||
if (j.JobQueues.Any(jjq => !jjq.RemovedDate.HasValue && jjq.JobQueueId == jq.Id))
|
||||
return false;
|
||||
|
||||
// Can add ANY queue
|
||||
if (UserService.CurrentAuthorization.Has(Claims.Job.Actions.AddAnyQueues))
|
||||
return true;
|
||||
|
||||
// Can add OWN queue
|
||||
if (UserService.CurrentAuthorization.Has(Claims.Job.Actions.AddOwnQueues))
|
||||
{
|
||||
return JobQueueService.UsersQueues(UserService.CurrentUser).Any(q => q.JobQueue.Id == jq.Id);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public static JobQueueJob OnAddQueue(this Job j, DiscoDataContext Database, JobQueue jq, User Technician, string Comment, DateTime? SLAExpires, JobQueuePriority Priority)
|
||||
{
|
||||
if (!j.CanAddQueue(jq))
|
||||
throw new InvalidOperationException("Adding job to queue is denied");
|
||||
|
||||
if (SLAExpires.HasValue && SLAExpires.Value < DateTime.Now)
|
||||
throw new ArgumentException("The SLA Date must be greater than the current time", "SLAExpires");
|
||||
|
||||
var jqj = new JobQueueJob()
|
||||
{
|
||||
JobQueueId = jq.Id,
|
||||
JobId = j.Id,
|
||||
AddedDate = DateTime.Now,
|
||||
AddedUserId = Technician.UserId,
|
||||
AddedComment = string.IsNullOrWhiteSpace(Comment) ? null : Comment.Trim(),
|
||||
SLAExpiresDate = SLAExpires,
|
||||
Priority = Priority
|
||||
};
|
||||
|
||||
Database.JobQueueJobs.Add(jqj);
|
||||
return jqj;
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Disco.Services.Jobs
|
||||
{
|
||||
public static class Jobs
|
||||
{
|
||||
|
||||
public static Job Create(DiscoDataContext Database, Device device, User user, JobType type, List<JobSubType> subTypes, User initialTech, bool addAutoQueues = true)
|
||||
{
|
||||
Job j = new Job()
|
||||
{
|
||||
JobType = type,
|
||||
OpenedTechUserId = initialTech.UserId,
|
||||
OpenedTechUser = initialTech,
|
||||
OpenedDate = DateTime.Now
|
||||
};
|
||||
|
||||
// Device
|
||||
if (device != null)
|
||||
{
|
||||
j.Device = device;
|
||||
j.DeviceSerialNumber = device.SerialNumber;
|
||||
}
|
||||
|
||||
// User
|
||||
if (user != null)
|
||||
{
|
||||
j.User = user;
|
||||
j.UserId = user.UserId;
|
||||
}
|
||||
|
||||
// Sub Types
|
||||
List<JobSubType> jobSubTypes = subTypes.ToList();
|
||||
j.JobSubTypes = jobSubTypes;
|
||||
|
||||
Database.Jobs.Add(j);
|
||||
|
||||
// Job Queues
|
||||
if (addAutoQueues)
|
||||
{
|
||||
var queues = from st in subTypes
|
||||
from jq in st.JobQueues
|
||||
group st by jq into g
|
||||
select new { queue = g.Key, subTypes = g };
|
||||
foreach (var queue in queues)
|
||||
{
|
||||
var commentBuilder = new StringBuilder("Automatically added by:").AppendLine();
|
||||
foreach (var subType in queue.subTypes)
|
||||
{
|
||||
commentBuilder.AppendLine().Append("* ").Append(subType.Description);
|
||||
}
|
||||
|
||||
var jqj = new JobQueueJob()
|
||||
{
|
||||
JobQueueId = queue.queue.Id,
|
||||
Job = j,
|
||||
AddedDate = DateTime.Now,
|
||||
AddedUserId = initialTech.UserId,
|
||||
AddedComment = commentBuilder.ToString(),
|
||||
SLAExpiresDate = queue.queue.DefaultSLAExpiry.HasValue ? (DateTime?)DateTime.Now.AddMinutes(queue.queue.DefaultSLAExpiry.Value) : null,
|
||||
Priority = JobQueuePriority.Normal
|
||||
};
|
||||
|
||||
Database.JobQueueJobs.Add(jqj);
|
||||
}
|
||||
}
|
||||
|
||||
switch (type.Id)
|
||||
{
|
||||
case JobType.JobTypeIds.HWar:
|
||||
Database.JobMetaWarranties.Add(new JobMetaWarranty() { Job = j });
|
||||
break;
|
||||
case JobType.JobTypeIds.HNWar:
|
||||
Database.JobMetaNonWarranties.Add(new JobMetaNonWarranty() { Job = j });
|
||||
if (device != null)
|
||||
{
|
||||
// Add Job Components
|
||||
var components = Database.DeviceComponents.Include("JobSubTypes").Where(c => !c.DeviceModelId.HasValue || c.DeviceModelId == j.Device.DeviceModelId);
|
||||
var addedComponents = new List<DeviceComponent>();
|
||||
foreach (var c in components)
|
||||
{
|
||||
if (c.JobSubTypes.Count == 0)
|
||||
{ // No Filter
|
||||
addedComponents.Add(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var st in c.JobSubTypes)
|
||||
{
|
||||
foreach (var jst in jobSubTypes)
|
||||
{
|
||||
if (st.JobTypeId == jst.JobTypeId && st.Id == jst.Id)
|
||||
{
|
||||
addedComponents.Add(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (addedComponents.Contains(c))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var c in addedComponents)
|
||||
Database.JobComponents.Add(new JobComponent()
|
||||
{
|
||||
Job = j,
|
||||
TechUserId = initialTech.UserId,
|
||||
Cost = c.Cost,
|
||||
Description = c.Description
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Data.Repository.Monitor;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Job.Statistics;
|
||||
using Disco.Services.Tasks;
|
||||
using Quartz;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace Disco.Services.Jobs.Statistics
|
||||
{
|
||||
public class DailyOpenedClosed : ScheduledTask
|
||||
{
|
||||
|
||||
private static List<DailyOpenedClosedItem> _data;
|
||||
private static object _dataLock = new object();
|
||||
private static IDisposable _streamSubscription;
|
||||
|
||||
|
||||
public override string TaskName { get { return "Job Statistics - Daily Opened/Closed Task"; } }
|
||||
public override bool SingleInstanceTask { get { return true; } }
|
||||
public override bool CancelInitiallySupported { get { return false; } }
|
||||
public override bool LogExceptionsOnly { get { return true; } }
|
||||
|
||||
public override void InitalizeScheduledTask(DiscoDataContext Database)
|
||||
{
|
||||
// Trigger Daily @ 12:29am
|
||||
TriggerBuilder triggerBuilder = TriggerBuilder.Create().WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(0, 29));
|
||||
|
||||
this.ScheduleTask(triggerBuilder);
|
||||
}
|
||||
protected override void ExecuteTask()
|
||||
{
|
||||
using (var database = new DiscoDataContext())
|
||||
{
|
||||
UpdateDataHistory(database, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateDataHistory(DiscoDataContext Database, bool Refresh = false)
|
||||
{
|
||||
DateTime historyEnd = DateTime.Now.Date;
|
||||
|
||||
if (Refresh || _data == null || _data.Count == 0 || _data.Last().Timestamp < historyEnd)
|
||||
{
|
||||
lock (_dataLock)
|
||||
{
|
||||
if (Refresh || _data == null || _data.Count == 0 || _data.Last().Timestamp < historyEnd)
|
||||
{
|
||||
DateTime historyStart = DateTime.Now.AddDays(-28).Date;
|
||||
|
||||
// Initialize Memory Store
|
||||
List<DailyOpenedClosedItem> resultData;
|
||||
if (Refresh || _data == null)
|
||||
resultData = new List<DailyOpenedClosedItem>();
|
||||
else
|
||||
resultData = _data;
|
||||
|
||||
// Remove Old Data
|
||||
while (resultData.Count > 0 && resultData[0].Timestamp < historyStart)
|
||||
resultData.RemoveAt(0);
|
||||
|
||||
// Calculate Update Scope
|
||||
DateTime processDate = historyStart;
|
||||
if (resultData.Count > 0)
|
||||
processDate = resultData.Last().Timestamp.AddDays(-1);
|
||||
|
||||
// Cache Data
|
||||
while (processDate <= historyEnd)
|
||||
{
|
||||
resultData.Add(Data(Database, processDate));
|
||||
processDate = processDate.AddDays(1);
|
||||
}
|
||||
_data = resultData;
|
||||
|
||||
// Subscribe to Live Repository Events
|
||||
if (_streamSubscription != null)
|
||||
_streamSubscription.Dispose();
|
||||
_streamSubscription = RepositoryMonitor.StreamBeforeCommit.Where(
|
||||
e => e.EntityType == typeof(Job) &&
|
||||
(e.EventType == RepositoryMonitorEventType.Added || (e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Contains("ClosedDate")))).Subscribe(RepositoryEvent_JobChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RepositoryEvent_JobChange(RepositoryMonitorEvent e)
|
||||
{
|
||||
|
||||
if (e.EventType == RepositoryMonitorEventType.Added)
|
||||
{
|
||||
// New Job
|
||||
var todaysStats = _data.Last();
|
||||
todaysStats.OpenedJobs += 1;
|
||||
todaysStats.TotalJobs += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
DateTime? previousValue = e.GetPreviousPropertyValue<DateTime?>("ClosedDate");
|
||||
|
||||
if (previousValue.HasValue)
|
||||
{
|
||||
// Remove Statistics
|
||||
var statItem = _data.FirstOrDefault(i => i.Timestamp == previousValue.Value.Date);
|
||||
if (statItem != null)
|
||||
{
|
||||
statItem.ClosedJobs -= 1;
|
||||
statItem.TotalJobs += 1;
|
||||
}
|
||||
foreach (var affectedStat in _data.Where(i => i.Timestamp > previousValue))
|
||||
{
|
||||
affectedStat.TotalJobs += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DateTime? currentValue = e.GetCurrentPropertyValue<DateTime?>("ClosedDate");
|
||||
|
||||
if (currentValue.HasValue)
|
||||
{
|
||||
// Add Statistics
|
||||
// Remove Statistics
|
||||
var statItem = _data.FirstOrDefault(i => i.Timestamp == currentValue.Value.Date);
|
||||
if (statItem != null)
|
||||
{
|
||||
statItem.ClosedJobs += 1;
|
||||
statItem.TotalJobs -= 1;
|
||||
}
|
||||
foreach (var affectedStat in _data.Where(i => i.Timestamp > currentValue))
|
||||
{
|
||||
affectedStat.TotalJobs -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static DailyOpenedClosedItem Data(DiscoDataContext Database, DateTime ProcessDate)
|
||||
{
|
||||
DateTime processDateStart = ProcessDate;
|
||||
DateTime processDateEnd = ProcessDate.AddDays(1);
|
||||
|
||||
int totalJobs = Database.Jobs.Where(j => j.OpenedDate < processDateEnd && (!j.ClosedDate.HasValue || j.ClosedDate > processDateEnd)).Count();
|
||||
int openedJobs = Database.Jobs.Where(j => j.OpenedDate > processDateStart && j.OpenedDate < processDateEnd).Count();
|
||||
int closedJobs = Database.Jobs.Where(j => j.ClosedDate > processDateStart && j.ClosedDate < processDateEnd).Count();
|
||||
|
||||
return new DailyOpenedClosedItem()
|
||||
{
|
||||
Timestamp = ProcessDate,
|
||||
TotalJobs = totalJobs,
|
||||
OpenedJobs = openedJobs,
|
||||
ClosedJobs = closedJobs
|
||||
};
|
||||
}
|
||||
|
||||
public static List<DailyOpenedClosedItem> Data(DiscoDataContext Database, bool FilterUnimportantWeekends = false)
|
||||
{
|
||||
List<DailyOpenedClosedItem> resultData;
|
||||
|
||||
UpdateDataHistory(Database);
|
||||
|
||||
if (FilterUnimportantWeekends)
|
||||
resultData = _data.Where(i => (i.Timestamp.DayOfWeek != DayOfWeek.Saturday && i.Timestamp.DayOfWeek != DayOfWeek.Sunday) ||
|
||||
(i.OpenedJobs > 0 || i.ClosedJobs > 0)).ToList();
|
||||
else
|
||||
resultData = _data.ToList();
|
||||
|
||||
return resultData;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user