diff --git a/Disco.Data/Configuration/Modules/JobPreferencesConfiguration.cs b/Disco.Data/Configuration/Modules/JobPreferencesConfiguration.cs index d179f9e4..ad74595a 100644 --- a/Disco.Data/Configuration/Modules/JobPreferencesConfiguration.cs +++ b/Disco.Data/Configuration/Modules/JobPreferencesConfiguration.cs @@ -1,5 +1,7 @@ using Disco.Data.Repository; -using Disco.Models.Services.Job; +using Disco.Models.Services.Devices.Exporting; +using Disco.Models.Services.Jobs; +using Disco.Models.Services.Jobs.Exporting; using System; using System.Collections.Generic; @@ -92,5 +94,20 @@ namespace Disco.Data.Configuration.Modules get { return Get(null); } set { Set(value); } } + + public JobExportOptions LastExportOptions + { + get { return this.Get(JobExportOptions.DefaultOptions()); } + set { + this.Set(value); + this.LastExportDate = DateTime.Now; + } + } + + public DateTime? LastExportDate + { + get { return this.Get(null); } + set { this.Set(value); } + } } } diff --git a/Disco.Models/Disco.Models.csproj b/Disco.Models/Disco.Models.csproj index 79a19d4d..759aaa29 100644 --- a/Disco.Models/Disco.Models.csproj +++ b/Disco.Models/Disco.Models.csproj @@ -80,6 +80,8 @@ + + @@ -217,6 +219,7 @@ + diff --git a/Disco.Models/Repository/Job/Job.cs b/Disco.Models/Repository/Job/Job.cs index 8c47fe4d..0d3df33f 100644 --- a/Disco.Models/Repository/Job/Job.cs +++ b/Disco.Models/Repository/Job/Job.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -99,15 +98,28 @@ namespace Disco.Models.Repository public static class JobStatusIds { - public const string AwaitingAccountingPayment = "AwaitingAccountingPayment"; - public const string AwaitingAccountingCharge = "AwaitingAccountingCharge"; - public const string AwaitingDeviceReturn = "AwaitingDeviceReturn"; - public const string AwaitingInsuranceProcessing = "AwaitingInsuranceProcessing"; - public const string AwaitingRepairs = "AwaitingRepairs"; - public const string AwaitingUserAction = "AwaitingUserAction"; - public const string AwaitingWarrantyRepair = "AwaitingWarrantyRepair"; - public const string Closed = "Closed"; - public const string Open = "Open"; + public const string AwaitingAccountingPayment = nameof(AwaitingAccountingPayment); + public const string AwaitingAccountingCharge = nameof(AwaitingAccountingCharge); + public const string AwaitingDeviceReturn = nameof(AwaitingDeviceReturn); + public const string AwaitingInsuranceProcessing = nameof(AwaitingInsuranceProcessing); + public const string AwaitingRepairs = nameof(AwaitingRepairs); + public const string AwaitingUserAction = nameof(AwaitingUserAction); + public const string AwaitingWarrantyRepair = nameof(AwaitingWarrantyRepair); + public const string Closed = nameof(Closed); + public const string Open = nameof(Open); + + public static readonly Dictionary StatusDescriptions = new Dictionary + { + { AwaitingAccountingPayment, "Awaiting Accounting Payment" }, + { AwaitingAccountingCharge, "Awaiting Accounting Charge" }, + { AwaitingDeviceReturn, "Awaiting Device Return" }, + { AwaitingInsuranceProcessing, "Awaiting Insurance Processing" }, + { AwaitingRepairs, "Awaiting Repairs" }, + { AwaitingUserAction, "Awaiting User Action" }, + { AwaitingWarrantyRepair, "Awaiting Warranty Repair" }, + { Closed, "Closed" }, + { Open, "Open" } + }; } [Flags] diff --git a/Disco.Models/Services/Jobs/Exporting/JobExportOptions.cs b/Disco.Models/Services/Jobs/Exporting/JobExportOptions.cs new file mode 100644 index 00000000..47044a68 --- /dev/null +++ b/Disco.Models/Services/Jobs/Exporting/JobExportOptions.cs @@ -0,0 +1,269 @@ +using Disco.Models.Exporting; +using Disco.Models.Services.Exporting; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Disco.Models.Services.Jobs.Exporting +{ + public class JobExportOptions : IExportOptions + { + [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true, ConvertEmptyStringToNull = true, HtmlEncode = false)] + public DateTime FilterStartDate { get; set; } + [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true, ConvertEmptyStringToNull = true, HtmlEncode = false)] + public DateTime? FilterEndDate { get; set; } + public string FilterJobStatusId { get; set; } + public string FilterJobTypeId { get; set; } + public List FilterJobSubTypeIds { get; set; } + public int? FilterJobQueueId { get; set; } + + public ExportFormat Format { get; set; } + public string FilenamePrefix { get; } = "DiscoJobExport"; + public string ExcelWorksheetName { get; } = "JobExport"; + public string ExcelTableName { get; } = "Jobs"; + + // Job + [Display(ShortName = "Job", Name = "Identifier", Description = "The identifier of the job")] + public bool JobId { get; set; } + [Display(ShortName = "Job", Name = "Status", Description = "The status of the job")] + public bool JobStatus { get; set; } + [Display(ShortName = "Job", Name = "Type", Description = "The type of the job")] + public bool JobType { get; set; } + [Display(ShortName = "Job", Name = "Sub Types", Description = "The sub types of the job")] + public bool JobSubTypes { get; set; } + [Display(ShortName = "Job", Name = "Opened Date", Description = "The date the job was opened")] + public bool JobOpenedDate { get; set; } + [Display(ShortName = "Job", Name = "Opened User", Description = "The user who opened the job")] + public bool JobOpenedUser { get; set; } + [Display(ShortName = "Job", Name = "Expected Closed Date", Description = "The expected closed date of the job")] + public bool JobExpectedClosedDate { get; set; } + [Display(ShortName = "Job", Name = "Closed Date", Description = "The date the job was closed")] + public bool JobClosedDate { get; set; } + [Display(ShortName = "Job", Name = "Closed User", Description = "The user who closed the job")] + public bool JobClosedUser { get; set; } + + // Job Details + [Display(ShortName = "Job Details", Name = "Held Date", Description = "The date the device was held")] + public bool JobDeviceHeldDate { get; set; } + [Display(ShortName = "Job Details", Name = "Held User", Description = "The user who held the device")] + public bool JobDeviceHeldUser { get; set; } + [Display(ShortName = "Job Details", Name = "Held Location", Description = "The location the device was held")] + public bool JobDeviceHeldLocation { get; set; } + [Display(ShortName = "Job Details", Name = "Ready For Return Date", Description = "The date the device was ready for return")] + public bool JobDeviceReadyForReturnDate { get; set; } + [Display(ShortName = "Job Details", Name = "Ready For Return User", Description = "The user who made the device ready for return")] + public bool JobDeviceReadyForReturnUser { get; set; } + [Display(ShortName = "Job Details", Name = "Returned Date", Description = "The date the device was returned")] + public bool JobDeviceReturnedDate { get; set; } + [Display(ShortName = "Job Details", Name = "Returned User", Description = "The user who returned the device")] + public bool JobDeviceReturnedUser { get; set; } + [Display(ShortName = "Job Details", Name = "Waiting For User Action Date", Description = "The date the job was waiting for user action")] + public bool JobWaitingForUserActionDate { get; set; } + + // Job Log + [Display(ShortName = "Job Log", Name = "Count", Description = "The number of log entries for the job")] + public bool LogCount { get; set; } + [Display(ShortName = "Job Log", Name = "First Date", Description = "The date of the first log entry for the job")] + public bool LogFirstDate { get; set; } + [Display(ShortName = "Job Log", Name = "First User", Description = "The user who made the first log entry for the job")] + public bool LogFirstUser { get; set; } + [Display(ShortName = "Job Log", Name = "First Content", Description = "The content of the first log entry for the job")] + public bool LogFirstContent { get; set; } + [Display(ShortName = "Job Log", Name = "Last Date", Description = "The date of the last log entry for the job")] + public bool LogLastDate { get; set; } + [Display(ShortName = "Job Log", Name = "Last User", Description = "The user who made the last log entry for the job")] + public bool LogLastUser { get; set; } + [Display(ShortName = "Job Log", Name = "Last Content", Description = "The content of the last log entry for the job")] + public bool LogLastContent { get; set; } + + // Job Attachments + [Display(ShortName = "Job Attachments", Name = "Count", Description = "The number of attachments for the job")] + public bool AttachmentsCount { get; set; } + + // Job Queues + [Display(ShortName = "Job Queues", Name = "Count", Description = "The number of times the job has been associated with a queue")] + public bool JobQueueCount { get; set; } + [Display(ShortName = "Job Queues", Name = "Active Count", Description = "The number of active queues the job is associated with")] + public bool JobQueueActiveCount { get; set; } + [Display(ShortName = "Job Queues", Name = "Active Latest", Description = "The latest queue the job is associated with")] + public bool JobQueueActiveLatest { get; set; } + [Display(ShortName = "Job Queues", Name = "Active Latest Date", Description = "The date the latest queue was added")] + public bool JobQueueActiveLatestAddedDate { get; set; } + [Display(ShortName = "Job Queues", Name = "Active Latest User", Description = "The user who added the latest queue")] + public bool JobQueueActiveLatestAddedUser { get; set; } + + // Job Type - Warranty + [Display(ShortName = "Job Warranty", Name = "External Name", Description = "The name of the external warranty provider")] + public bool JobWarrantyExternalName { get; set; } + [Display(ShortName = "Job Warranty", Name = "External Logged Date", Description = "The date the warranty was logged with the external provider")] + public bool JobWarrantyExternalLoggedDate { get; set; } + [Display(ShortName = "Job Warranty", Name = "External Reference", Description = "The reference of the warranty with the external provider")] + public bool JobWarrantyExternalReference { get; set; } + [Display(ShortName = "Job Warranty", Name = "External Completed Date", Description = "The date the warranty was completed with the external provider")] + public bool JobWarrantyExternalCompletedDate { get; set; } + + // Job Type - NonWarranty + [Display(ShortName = "Job Non Warranty", Name = "Accounting Charge Required Date", Description = "The date the accounting charge was required")] + public bool JobNonWarrantyAccountingChargeRequiredDate { get; set; } + [Display(ShortName = "Job Non Warranty", Name = "Accounting Charge Added Date", Description = "The date the accounting charge was added")] + public bool JobNonWarrantyAccountingChargeAddedDate { get; set; } + [Display(ShortName = "Job Non Warranty", Name = "Accounting Charge Paid Date", Description = "The date the accounting charge was paid")] + public bool JobNonWarrantyAccountingChargePaidDate { get; set; } + [Display(ShortName = "Job Non Warranty", Name = "Purchase Order Raised Date", Description = "The date the purchase order was raised")] + public bool JobNonWarrantyPurchaseOrderRaisedDate { get; set; } + [Display(ShortName = "Job Non Warranty", Name = "Purchase Order Reference", Description = "The reference of the purchase order")] + public bool JobNonWarrantyPurchaseOrderReference { get; set; } + [Display(ShortName = "Job Non Warranty", Name = "Purchase Order Sent Date", Description = "The date the purchase order was sent")] + public bool JobNonWarrantyPurchaseOrderSentDate { get; set; } + [Display(ShortName = "Job Non Warranty", Name = "Invoice Received Date", Description = "The date the invoice was received")] + public bool JobNonWarrantyInvoiceReceivedDate { get; set; } + [Display(ShortName = "Job Non Warranty", Name = "Repairer Name", Description = "The name of the repairer")] + public bool JobNonWarrantyRepairerName { get; set; } + [Display(ShortName = "Job Non Warranty", Name = "Repairer Logged Date", Description = "The date the job was logged with the repairer")] + public bool JobNonWarrantyRepairerLoggedDate { get; set; } + [Display(ShortName = "Job Non Warranty", Name = "Repairer Reference", Description = "The repairer reference for the job")] + public bool JobNonWarrantyRepairerReference { get; set; } + [Display(ShortName = "Job Non Warranty", Name = "Repairer Completed Date", Description = "The date the repairer completed the job")] + public bool JobNonWarrantyRepairerCompletedDate { get; set; } + + // Job Type - Insurance + [Display(ShortName = "Job Insurance", Name = "Loss Or Damage Date", Description = "The date of the loss or damage")] + public bool JobMetaInsuranceLossOrDamageDate { get; set; } + [Display(ShortName = "Job Insurance", Name = "Event Location", Description = "The location of the event")] + public bool JobMetaInsuranceEventLocation { get; set; } + [Display(ShortName = "Job Insurance", Name = "Description", Description = "The description of the event")] + public bool JobMetaInsuranceDescription { get; set; } + [Display(ShortName = "Job Insurance", Name = "Third Party Caused Name", Description = "The name of the third party which caused the event")] + public bool JobMetaInsuranceThirdPartyCausedName { get; set; } + [Display(ShortName = "Job Insurance", Name = "Third Party Caused Why", Description = "The reason the third party caused the event")] + public bool JobMetaInsuranceThirdPartyCausedWhy { get; set; } + [Display(ShortName = "Job Insurance", Name = "Witnesses Names Addresses", Description = "The names and addresses of the witnesses")] + public bool JobMetaInsuranceWitnessesNamesAddresses { get; set; } + [Display(ShortName = "Job Insurance", Name = "Burglary Theft Method Of Entry", Description = "The method of entry for a burglary or theft")] + public bool JobMetaInsuranceBurglaryTheftMethodOfEntry { get; set; } + [Display(ShortName = "Job Insurance", Name = "Property Last Seen Date", Description = "The date the property was last seen")] + public bool JobMetaInsurancePropertyLastSeenDate { get; set; } + [Display(ShortName = "Job Insurance", Name = "Police Station Notified", Description = "The police station which was notified")] + public bool JobMetaInsurancePoliceNotifiedStation { get; set; } + [Display(ShortName = "Job Insurance", Name = "Police Notified Date", Description = "The date the police were notified")] + public bool JobMetaInsurancePoliceNotifiedDate { get; set; } + [Display(ShortName = "Job Insurance", Name = "Police Crime Report Number", Description = "The crime report number provided by the police")] + public bool JobMetaInsurancePoliceNotifiedCrimeReportNo { get; set; } + [Display(ShortName = "Job Insurance", Name = "Recover Reduce Action", Description = "The action taken to recover or reduce the loss")] + public bool JobMetaInsuranceRecoverReduceAction { get; set; } + [Display(ShortName = "Job Insurance", Name = "Other Interested Parties", Description = "Other parties interested in the event")] + public bool JobMetaInsuranceOtherInterestedParties { get; set; } + [Display(ShortName = "Job Insurance", Name = "Date Of Purchase", Description = "The date the item was purchased")] + public bool JobMetaInsuranceDateOfPurchase { get; set; } + [Display(ShortName = "Job Insurance", Name = "Claim Form Sent Date", Description = "The date the claim form was sent")] + public bool JobMetaInsuranceClaimFormSentDate { get; set; } + [Display(ShortName = "Job Insurance", Name = "Insurer", Description = "The insurer associated with the claim")] + public bool JobMetaInsuranceInsurer { get; set; } + [Display(ShortName = "Job Insurance", Name = "Insurer Reference", Description = "The reference provided by the insurer")] + public bool JobMetaInsuranceInsurerReference { get; set; } + + // Job Type = User Management + [Display(ShortName = "Job User Management", Name = "Flags", Description = "The user management flags associated with the job")] + public bool JobUserManagementFlags { get; set; } + + // User + [Display(ShortName = "User", Name = "Identifier", Description = "The identifier of the user associated with the job")] + public bool UserId { get; set; } + [Display(ShortName = "User", Name = "Display Name", Description = "The display name of the user associated with the job")] + public bool UserDisplayName { get; set; } + [Display(ShortName = "User", Name = "Surname", Description = "The surname of the user associated with the job")] + public bool UserSurname { get; set; } + [Display(ShortName = "User", Name = "Given Name", Description = "The given name of the user associated with the job")] + public bool UserGivenName { get; set; } + [Display(ShortName = "User", Name = "Phone Number", Description = "The phone number of the user associated with the job")] + public bool UserPhoneNumber { get; set; } + [Display(ShortName = "User", Name = "Email Address", Description = "The email address of the user associated with the job")] + public bool UserEmailAddress { get; set; } + [Display(ShortName = "User", Name = "Custom Details", Description = "The custom details provided by plugins for the user associated with the job")] + public bool UserDetailCustom { get; set; } + + // Device + [Display(ShortName = "Device", Name = "Serial Number", Description = "The device serial number")] + public bool DeviceSerialNumber { get; set; } + [Display(ShortName = "Device", Name = "Asset Number", Description = "The device asset number")] + public bool DeviceAssetNumber { get; set; } + [Display(ShortName = "Device", Name = "Location", Description = "The device location")] + public bool DeviceLocation { get; set; } + [Display(ShortName = "Device", Name = "Computer Name", Description = "The device computer name")] + public bool DeviceComputerName { get; set; } + [Display(ShortName = "Device", Name = "Last Network Logon", Description = "The last recorded time the device access the network")] + public bool DeviceLastNetworkLogon { get; set; } + [Display(ShortName = "Device", Name = "Created Date", Description = "The date the device was created in Disco ICT")] + public bool DeviceCreatedDate { get; set; } + [Display(ShortName = "Device", Name = "First Enrolled Date", Description = "The date the device was first enrolled in Disco ICT")] + public bool DeviceFirstEnrolledDate { get; set; } + [Display(ShortName = "Device", Name = "Last Enrolled Date", Description = "The date the device was last enrolled in Disco ICT")] + public bool DeviceLastEnrolledDate { get; set; } + [Display(ShortName = "Device", Name = "Enrolment Trusted", Description = "The device is trusted to complete an unauthenticated enrolment")] + public bool DeviceAllowUnauthenticatedEnrol { get; set; } + [Display(ShortName = "Device", Name = "Decommissioned Date", Description = "The date the device was decommissioned in Disco ICT")] + public bool DeviceDecommissionedDate { get; set; } + [Display(ShortName = "Device", Name = "Decommissioned Reason", Description = "The reason the device was decommissioned")] + public bool DeviceDecommissionedReason { get; set; } + + // Model + [Display(ShortName = "Device Model", Name = "Identifier", Description = "The identifier of the device model associated with the job")] + public bool DeviceModelId { get; set; } + [Display(ShortName = "Device Model", Name = "Description", Description = "The description of the device model associated with the job")] + public bool DeviceModelDescription { get; set; } + [Display(ShortName = "Device Model", Name = "Manufacturer", Description = "The manufacturer of the device model associated with the job")] + public bool DeviceModelManufacturer { get; set; } + [Display(ShortName = "Device Model", Name = "Model", Description = "The model of the device model associated with the job")] + public bool DeviceModelModel { get; set; } + [Display(ShortName = "Device Model", Name = "Type", Description = "The type of device model associated with the job")] + public bool DeviceModelType { get; set; } + + // Batch + [Display(ShortName = "Device Batch", Name = "Identifier", Description = "The identifier of the device batch associated with the job")] + public bool DeviceBatchId { get; set; } + [Display(ShortName = "Device Batch", Name = "Name", Description = "The name of the device batch associated with the job")] + public bool DeviceBatchName { get; set; } + [Display(ShortName = "Device Batch", Name = "Purchase Date", Description = "The purchase date of the device batch associated with the job")] + public bool DeviceBatchPurchaseDate { get; set; } + [Display(ShortName = "Device Batch", Name = "Supplier", Description = "The supplier of the device batch associated with the job")] + public bool DeviceBatchSupplier { get; set; } + [Display(ShortName = "Device Batch", Name = "Unit Cost", Description = "The unit cost of the device batch associated with the job")] + public bool DeviceBatchUnitCost { get; set; } + [Display(ShortName = "Device Batch", Name = "Warranty Valid Until Date", Description = "The warranty valid until date of the device batch associated with the job")] + public bool DeviceBatchWarrantyValidUntilDate { get; set; } + [Display(ShortName = "Device Batch", Name = "Insured Date", Description = "The insured date of the device batch associated with the job")] + public bool DeviceBatchInsuredDate { get; set; } + [Display(ShortName = "Device Batch", Name = "Insurance Supplier", Description = "The insurance supplier of the device batch associated with the job")] + public bool DeviceBatchInsuranceSupplier { get; set; } + [Display(ShortName = "Device Batch", Name = "Insured Until Date", Description = "The insured until date of the device batch associated with the job")] + public bool DeviceBatchInsuredUntilDate { get; set; } + + // Profile + [Display(ShortName = "Device Profile", Name = "Identifier", Description = "The identifier of the device profile associated with the job")] + public bool DeviceProfileId { get; set; } + [Display(ShortName = "Device Profile", Name = "Name", Description = "The name of the device profile associated with the job")] + public bool DeviceProfileName { get; set; } + [Display(ShortName = "Device Profile", Name = "Short Name", Description = "The short name of the device profile associated with the job")] + public bool DeviceProfileShortName { get; set; } + + public static JobExportOptions DefaultOptions() + { + return new JobExportOptions() + { + FilterJobStatusId = "Open", + FilterStartDate = new DateTime(DateTime.Now.Year, 1, 1), + Format = ExportFormat.Xlsx, + JobId = true, + JobStatus = true, + JobType = true, + JobSubTypes = true, + JobOpenedDate = true, + DeviceSerialNumber = true, + DeviceModelDescription = true, + DeviceProfileName = true, + UserId = true, + UserDisplayName = true, + }; + } + } +} diff --git a/Disco.Models/Services/Jobs/Exporting/JobExportRecord.cs b/Disco.Models/Services/Jobs/Exporting/JobExportRecord.cs new file mode 100644 index 00000000..2b7be9a8 --- /dev/null +++ b/Disco.Models/Services/Jobs/Exporting/JobExportRecord.cs @@ -0,0 +1,64 @@ +using Disco.Models.Exporting; +using Disco.Models.Repository; +using Disco.Models.Services.Jobs.JobLists; +using System; +using System.Collections.Generic; + +namespace Disco.Models.Services.Jobs.Exporting +{ + public class JobExportRecord : IExportRecord + { + public Job Job { get; set; } + public string JobStatus { get; set; } + public string JobTypeDescription { get; set; } + public IEnumerable JobSubTypeDescriptions { get; set; } + + // Logs + public int? LogCount { get; set; } + public JobLog FirstLog { get; set; } + public JobLog LastLog { get; set; } + + // Attachments + public int? AttachmentsCount { get; set; } + public DateTime? AttachmentsLastDate { get; set; } + + // Queues + public int? QueueCount { get; set; } + public int? QueueActiveCount { get; set; } + public JobQueueJob QueueLatestActive { get; set; } + + public JobMetaWarranty JobMetaWarranty { get; set; } + public JobMetaNonWarranty JobMetaNonWarranty { get; set; } + public JobMetaInsurance JobMetaInsurance { get; set; } + + // User + public User User { get; set; } + public Dictionary UserCustomDetails { get; set; } + + // Device + public Device Device { get; set; } + + // Device Model + public int? DeviceModelId { get; set; } + public string DeviceModelDescription { get; set; } + public string DeviceModelManufacturer { get; set; } + public string DeviceModelModel { get; set; } + public string DeviceModelType { get; set; } + + // Device Batch + public int? DeviceBatchId { get; set; } + public string DeviceBatchName { get; set; } + public DateTime? DeviceBatchPurchaseDate { get; set; } + public string DeviceBatchSupplier { get; set; } + public decimal? DeviceBatchUnitCost { get; set; } + public DateTime? DeviceBatchWarrantyValidUntilDate { get; set; } + public DateTime? DeviceBatchInsuredDate { get; set; } + public string DeviceBatchInsuranceSupplier { get; set; } + public DateTime? DeviceBatchInsuredUntilDate { get; set; } + + // Profile + public int? DeviceProfileId { get; set; } + public string DeviceProfileName { get; set; } + public string DeviceProfileShortName { get; set; } + } +} diff --git a/Disco.Models/Services/Jobs/LocationModes.cs b/Disco.Models/Services/Jobs/LocationModes.cs index dde6e086..c60225bd 100644 --- a/Disco.Models/Services/Jobs/LocationModes.cs +++ b/Disco.Models/Services/Jobs/LocationModes.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Disco.Models.Services.Job +namespace Disco.Models.Services.Jobs { public enum LocationModes { diff --git a/Disco.Models/Services/Jobs/Statistics/DailyOpenedClosedItem.cs b/Disco.Models/Services/Jobs/Statistics/DailyOpenedClosedItem.cs index ed31f259..42613697 100644 --- a/Disco.Models/Services/Jobs/Statistics/DailyOpenedClosedItem.cs +++ b/Disco.Models/Services/Jobs/Statistics/DailyOpenedClosedItem.cs @@ -1,6 +1,6 @@ using System; -namespace Disco.Models.Services.Job.Statistics +namespace Disco.Models.Services.Jobs.Statistics { public class DailyOpenedClosedItem { diff --git a/Disco.Models/UI/Config/JobPreferences/ConfigJobPreferencesIndexModel.cs b/Disco.Models/UI/Config/JobPreferences/ConfigJobPreferencesIndexModel.cs index 629eb39a..f0f46f87 100644 --- a/Disco.Models/UI/Config/JobPreferences/ConfigJobPreferencesIndexModel.cs +++ b/Disco.Models/UI/Config/JobPreferences/ConfigJobPreferencesIndexModel.cs @@ -1,4 +1,4 @@ -using Disco.Models.Services.Job; +using Disco.Models.Services.Jobs; using System.Collections.Generic; namespace Disco.Models.UI.Config.JobPreferences diff --git a/Disco.Models/UI/Job/JobExportModel.cs b/Disco.Models/UI/Job/JobExportModel.cs new file mode 100644 index 00000000..bde91b12 --- /dev/null +++ b/Disco.Models/UI/Job/JobExportModel.cs @@ -0,0 +1,19 @@ +using Disco.Models.Repository; +using Disco.Models.Services.Exporting; +using Disco.Models.Services.Jobs.Exporting; +using System.Collections.Generic; + +namespace Disco.Models.UI.Job +{ + public interface JobExportModel : BaseUIModel + { + JobExportOptions Options { get; set; } + + string ExportSessionId { get; set; } + ExportResult ExportSessionResult { get; set; } + + List JobQueues { get; set; } + List> JobStatuses { get; set; } + List JobTypes { get; set; } + } +} diff --git a/Disco.Models/UI/Job/JobShowModel.cs b/Disco.Models/UI/Job/JobShowModel.cs index 293bd7ac..80c80269 100644 --- a/Disco.Models/UI/Job/JobShowModel.cs +++ b/Disco.Models/UI/Job/JobShowModel.cs @@ -1,5 +1,5 @@ using Disco.Models.Services.Documents; -using Disco.Models.Services.Job; +using Disco.Models.Services.Jobs; using Disco.Models.Services.Jobs.JobLists; using System; using System.Collections.Generic; diff --git a/Disco.Services/Authorization/Claims.cs b/Disco.Services/Authorization/Claims.cs index 560302aa..3bd3b7c8 100644 --- a/Disco.Services/Authorization/Claims.cs +++ b/Disco.Services/Authorization/Claims.cs @@ -107,11 +107,12 @@ namespace Disco.Services.Authorization { "Job.Actions.ConvertHWarToHNWar", new Tuple, Action, string, string, bool>(c => c.Job.Actions.ConvertHWarToHNWar, (c, v) => c.Job.Actions.ConvertHWarToHNWar = v, "Convert HWar Jobs To HNWar", "Can convert warranty jobs to non-warranty jobs", false) }, { "Job.Actions.Create", new Tuple, Action, string, string, bool>(c => c.Job.Actions.Create, (c, v) => c.Job.Actions.Create = v, "Create Jobs", "Can create jobs", false) }, { "Job.Actions.Delete", new Tuple, Action, string, string, bool>(c => c.Job.Actions.Delete, (c, v) => c.Job.Actions.Delete = v, "Delete Jobs", "Can delete jobs", false) }, + { "Job.Actions.Export", new Tuple, Action, string, string, bool>(c => c.Job.Actions.Export, (c, v) => c.Job.Actions.Export = v, "Export Jobs", "Can export jobs in a bulk format", false) }, { "Job.Actions.ForceClose", new Tuple, Action, string, string, bool>(c => c.Job.Actions.ForceClose, (c, v) => c.Job.Actions.ForceClose = v, "Force Close Jobs", "Can force close jobs", false) }, { "Job.Actions.GenerateDocuments", new Tuple, Action, string, string, bool>(c => c.Job.Actions.GenerateDocuments, (c, v) => c.Job.Actions.GenerateDocuments = v, "Generate Documents", "Can generate documents for jobs", false) }, - { "Job.Actions.LogInsurance", new Tuple, Action, string, string, bool>(c => c.Job.Actions.LogInsurance, (c, v) => c.Job.Actions.LogInsurance = v, "Log Insurance", "Can log insurance for non-warranty jobs", false) }, - { "Job.Actions.LogRepair", new Tuple, Action, string, string, bool>(c => c.Job.Actions.LogRepair, (c, v) => c.Job.Actions.LogRepair = v, "Log Repair", "Can log repair for non-warranty jobs", false) }, - { "Job.Actions.LogWarranty", new Tuple, Action, string, string, bool>(c => c.Job.Actions.LogWarranty, (c, v) => c.Job.Actions.LogWarranty = v, "Log Warranty", "Can log warranty for jobs", false) }, + { "Job.Actions.LogInsurance", new Tuple, Action, string, string, bool>(c => c.Job.Actions.LogInsurance, (c, v) => c.Job.Actions.LogInsurance = v, "Lodge Insurance", "Can lodge insurance for non-warranty jobs", false) }, + { "Job.Actions.LogRepair", new Tuple, Action, string, string, bool>(c => c.Job.Actions.LogRepair, (c, v) => c.Job.Actions.LogRepair = v, "Lodge Repair", "Can lodge repair for non-warranty jobs", false) }, + { "Job.Actions.LogWarranty", new Tuple, Action, string, string, bool>(c => c.Job.Actions.LogWarranty, (c, v) => c.Job.Actions.LogWarranty = v, "Lodge Warranty", "Can lodge warranty for jobs", false) }, { "Job.Actions.RemoveAnyAttachments", new Tuple, Action, string, string, bool>(c => c.Job.Actions.RemoveAnyAttachments, (c, v) => c.Job.Actions.RemoveAnyAttachments = v, "Remove Any Attachments", "Can remove any attachments from jobs", false) }, { "Job.Actions.RemoveAnyLogs", new Tuple, Action, string, string, bool>(c => c.Job.Actions.RemoveAnyLogs, (c, v) => c.Job.Actions.RemoveAnyLogs = v, "Remove Any Logs", "Can remove any job logs", false) }, { "Job.Actions.RemoveAnyQueues", new Tuple, Action, string, string, bool>(c => c.Job.Actions.RemoveAnyQueues, (c, v) => c.Job.Actions.RemoveAnyQueues = v, "Remove from Any Queues", "Can remove from any job queues", false) }, @@ -336,6 +337,7 @@ namespace Disco.Services.Authorization new ClaimNavigatorItem("Job.Actions.ConvertHWarToHNWar", false), new ClaimNavigatorItem("Job.Actions.Create", false), new ClaimNavigatorItem("Job.Actions.Delete", false), + new ClaimNavigatorItem("Job.Actions.Export", false), new ClaimNavigatorItem("Job.Actions.ForceClose", false), new ClaimNavigatorItem("Job.Actions.GenerateDocuments", false), new ClaimNavigatorItem("Job.Actions.LogInsurance", false), @@ -647,6 +649,7 @@ namespace Disco.Services.Authorization c.Job.Actions.ConvertHWarToHNWar = true; c.Job.Actions.Create = true; c.Job.Actions.Delete = true; + c.Job.Actions.Export = true; c.Job.Actions.ForceClose = true; c.Job.Actions.GenerateDocuments = true; c.Job.Actions.LogInsurance = true; @@ -1334,6 +1337,11 @@ namespace Disco.Services.Authorization /// public const string Delete = "Job.Actions.Delete"; + /// Export Jobs + /// Can export jobs in a bulk format + /// + public const string Export = "Job.Actions.Export"; + /// Force Close Jobs /// Can force close jobs /// @@ -1344,18 +1352,18 @@ namespace Disco.Services.Authorization /// public const string GenerateDocuments = "Job.Actions.GenerateDocuments"; - /// Log Insurance - /// Can log insurance for non-warranty jobs + /// Lodge Insurance + /// Can lodge insurance for non-warranty jobs /// public const string LogInsurance = "Job.Actions.LogInsurance"; - /// Log Repair - /// Can log repair for non-warranty jobs + /// Lodge Repair + /// Can lodge repair for non-warranty jobs /// public const string LogRepair = "Job.Actions.LogRepair"; - /// Log Warranty - /// Can log warranty for jobs + /// Lodge Warranty + /// Can lodge warranty for jobs /// public const string LogWarranty = "Job.Actions.LogWarranty"; diff --git a/Disco.Services/Authorization/Claims.tt b/Disco.Services/Authorization/Claims.tt index 9278294f..3109ce29 100644 --- a/Disco.Services/Authorization/Claims.tt +++ b/Disco.Services/Authorization/Claims.tt @@ -7,7 +7,7 @@ <#@ assembly name="VSLangProj" #> <#@ assembly name="System.Xml" #> <#@ assembly name="System.Xml.Linq" #> -<#@ assembly name="C:\Windows\Microsoft.NET\Framework\v4.0.30319\CustomMarshalers.dll" #> +<#@ assembly name="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\CustomMarshalers.dll" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Linq" #> diff --git a/Disco.Services/Authorization/Roles/ClaimGroups/Job/JobActionsClaims.cs b/Disco.Services/Authorization/Roles/ClaimGroups/Job/JobActionsClaims.cs index 63cabea2..b89050d8 100644 --- a/Disco.Services/Authorization/Roles/ClaimGroups/Job/JobActionsClaims.cs +++ b/Disco.Services/Authorization/Roles/ClaimGroups/Job/JobActionsClaims.cs @@ -52,5 +52,7 @@ [ClaimDetails("Update Sub Types", "Can update sub types for jobs")] public bool UpdateSubTypes { get; set; } + [ClaimDetails("Export Jobs", "Can export jobs in a bulk format")] + public bool Export { get; set; } } } diff --git a/Disco.Services/Disco.Services.csproj b/Disco.Services/Disco.Services.csproj index 7e22a1a9..423e380e 100644 --- a/Disco.Services/Disco.Services.csproj +++ b/Disco.Services/Disco.Services.csproj @@ -381,6 +381,8 @@ + + diff --git a/Disco.Services/Exporting/ExportHelpers.cs b/Disco.Services/Exporting/ExportHelpers.cs index af265003..f668a301 100644 --- a/Disco.Services/Exporting/ExportHelpers.cs +++ b/Disco.Services/Exporting/ExportHelpers.cs @@ -15,6 +15,9 @@ namespace Disco.Services { public static ExportResult WriteExport(IExportOptions options, IScheduledTaskStatus status, List> metadata, List records) where T : IExportRecord { + if (records.Count == 0) + return new ExportResult(); + var filenameWithoutExtension = $"{options.FilenamePrefix}-{status.StartedTimestamp.Value:yyyyMMdd-HHmmss}"; MemoryStream stream; string filename; diff --git a/Disco.Services/Jobs/Exporting/JobExport.cs b/Disco.Services/Jobs/Exporting/JobExport.cs new file mode 100644 index 00000000..731ad04f --- /dev/null +++ b/Disco.Services/Jobs/Exporting/JobExport.cs @@ -0,0 +1,402 @@ +using Disco.Data.Repository; +using Disco.Models.Exporting; +using Disco.Models.Repository; +using Disco.Models.Services.Exporting; +using Disco.Models.Services.Jobs.Exporting; +using Disco.Services.Jobs.JobQueues; +using Disco.Services.Plugins.Features.DetailsProvider; +using Disco.Services.Tasks; +using Disco.Services.Users; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Data; +using System.Linq; + +namespace Disco.Services.Jobs.Exporting +{ + using Metadata = ExportFieldMetadata; + + public static class JobExport + { + public static ExportResult GenerateExport(DiscoDataContext database, Func, IQueryable> filter, JobExportOptions options, IScheduledTaskStatus taskStatus) + { + database.Configuration.LazyLoadingEnabled = false; + database.Configuration.ProxyCreationEnabled = false; + + var jobQuery = (IQueryable)database.Jobs; + if (filter != null) + jobQuery = filter(jobQuery); + + // Update Users + if (options.UserDisplayName || + options.UserSurname || + options.UserGivenName || + options.UserPhoneNumber || + options.UserEmailAddress) + { + taskStatus.UpdateStatus(5, "Refreshing user details from Active Directory"); + var userIds = jobQuery.Where(d => d.UserId != null).Select(d => d.UserId).Distinct().ToList(); + foreach (var userId in userIds) + { + try + { + UserService.GetUser(userId, database); + } + catch (Exception) { } // Ignore Errors + } + } + + // Update Last Network Logon Date + if (options.DeviceLastNetworkLogon) + { + taskStatus.UpdateStatus(15, "Refreshing device last network logon dates from Active Directory"); + try + { + Interop.ActiveDirectory.ADNetworkLogonDatesUpdateTask.UpdateLastNetworkLogonDates(database, ScheduledTaskMockStatus.Create("UpdateLastNetworkLogonDates")); + database.SaveChanges(); + } + catch (Exception) { } // Ignore Errors + } + + taskStatus.UpdateStatus(25, "Extracting records from the database"); + + var records = BuildRecords(jobQuery).ToList(); + + records.ForEach(r => + { + if (options.JobStatus) + { + r.JobStatus = JobExtensions.CalculateStatusId( + r.Job.ClosedDate, + r.Job.JobTypeId, + r.JobMetaWarranty?.ExternalLoggedDate, + r.JobMetaWarranty?.ExternalCompletedDate, + r.JobMetaNonWarranty?.RepairerLoggedDate, + r.JobMetaNonWarranty?.RepairerCompletedDate, + r.JobMetaNonWarranty?.AccountingChargeRequiredDate, + r.JobMetaNonWarranty?.AccountingChargeAddedDate, + r.JobMetaNonWarranty?.AccountingChargePaidDate, + r.JobMetaNonWarranty?.IsInsuranceClaim, + r.JobMetaInsurance?.ClaimFormSentDate, + r.Job.WaitingForUserAction, + r.Job.DeviceReadyForReturn, + r.Job.DeviceReturnedDate); + } + + if (options.UserDetailCustom && r.User != null) + { + var detailsService = new DetailsProviderService(database); + r.UserCustomDetails = detailsService.GetDetails(r.User); + } + }); + + taskStatus.UpdateStatus(70, "Building metadata"); + var metadata = options.BuildMetadata(records); + + if (metadata.Count == 0) + throw new ArgumentException("At least one export field must be specified", "Options"); + + taskStatus.UpdateStatus(80, $"Formatting {records.Count} records for export"); + + return ExportHelpers.WriteExport(options, taskStatus, metadata, records); + } + + public static ExportResult GenerateExport(DiscoDataContext database, JobExportOptions options, IScheduledTaskStatus taskStatus) + { + Func, IQueryable> filter = q => + { + var r = q.Where(j => j.OpenedDate >= options.FilterStartDate); + if (options.FilterEndDate.HasValue) + r = r.Where(j => j.OpenedDate <= options.FilterEndDate); + + if (options.FilterJobTypeId != null) + r = r.Where(j => j.JobTypeId == options.FilterJobTypeId); + + if (options.FilterJobSubTypeIds?.Any() ?? false) + r = r.Where(j => j.JobSubTypes.Any(st => options.FilterJobSubTypeIds.Contains(st.Id))); + + if (options.FilterJobQueueId.HasValue) + r = r.Where(j => j.JobQueues.Any(jq => !jq.RemovedDate.HasValue && jq.JobQueueId == options.FilterJobQueueId)); + + if (options.FilterJobStatusId != null) + { + if (options.FilterJobStatusId != Job.JobStatusIds.Closed) + r = r.Where(j => j.ClosedDate == null); + + switch (options.FilterJobStatusId) + { + case Job.JobStatusIds.Open: + // already filtered + break; + case Job.JobStatusIds.AwaitingAccountingPayment: + r = r.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.AccountingChargeAddedDate != null && j.JobMetaNonWarranty.AccountingChargePaidDate == null); + break; + case Job.JobStatusIds.AwaitingAccountingCharge: + r = r.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.AccountingChargeRequiredDate == null && (j.JobMetaNonWarranty.AccountingChargePaidDate != null || j.JobMetaNonWarranty.AccountingChargeAddedDate != null)); + break; + case Job.JobStatusIds.AwaitingDeviceReturn: + r = r.Where(j => j.DeviceReadyForReturn != null && j.DeviceReturnedDate == null); + break; + case Job.JobStatusIds.AwaitingInsuranceProcessing: + r = r.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.IsInsuranceClaim && j.JobMetaInsurance.ClaimFormSentDate == null); + break; + case Job.JobStatusIds.AwaitingRepairs: + r = r.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.RepairerLoggedDate != null && j.JobMetaNonWarranty.RepairerCompletedDate == null); + break; + case Job.JobStatusIds.AwaitingUserAction: + r = r.Where(j => j.WaitingForUserAction != null); + break; + case Job.JobStatusIds.AwaitingWarrantyRepair: + r = r.Where(j => j.JobTypeId == JobType.JobTypeIds.HWar && j.JobMetaWarranty.ExternalLoggedDate != null && j.JobMetaWarranty.ExternalCompletedDate == null); + break; + case Job.JobStatusIds.Closed: + r = r.Where(j => j.ClosedDate != null); + break; + default: + throw new ArgumentException($"Unknown Job Status Id: {options.FilterJobStatusId}", nameof(options.FilterJobStatusId)); + } + } + + return r; + }; + + return GenerateExport(database, filter, options, taskStatus); + } + + public static ExportResult GenerateExport(DiscoDataContext database, JobExportOptions options) + { + return GenerateExport(database, options, ScheduledTaskMockStatus.Create("Job Export")); + } + + private static IEnumerable BuildRecords(IQueryable jobs) + { + return jobs.Select(j => new JobExportRecord() + { + Job = j, + JobTypeDescription = j.JobType.Description, + JobSubTypeDescriptions = j.JobSubTypes.Select(st => st.Description), + + LogCount = j.JobLogs.Count(), + FirstLog = j.JobLogs.OrderBy(l => l.Id).FirstOrDefault(), + LastLog = j.JobLogs.OrderByDescending(l => l.Id).FirstOrDefault(), + + AttachmentsCount = j.JobAttachments.Count(), + + QueueCount = j.JobQueues.Count(), + QueueActiveCount = j.JobQueues.Count(q => !q.RemovedDate.HasValue), + QueueLatestActive = j.JobQueues.Where(q => !q.RemovedDate.HasValue).OrderByDescending(q => q.Id).FirstOrDefault(), + + JobMetaWarranty = j.JobMetaWarranty, + JobMetaNonWarranty = j.JobMetaNonWarranty, + JobMetaInsurance = j.JobMetaInsurance, + + User = j.User, + + Device = j.Device, + + DeviceModelId = j.Device.DeviceModelId, + DeviceModelDescription = j.Device.DeviceModel.Description, + DeviceModelManufacturer = j.Device.DeviceModel.Manufacturer, + DeviceModelModel = j.Device.DeviceModel.Model, + DeviceModelType = j.Device.DeviceModel.ModelType, + + DeviceBatchId = j.Device.DeviceBatchId, + DeviceBatchName = j.Device.DeviceBatch.Name, + DeviceBatchPurchaseDate = j.Device.DeviceBatch.PurchaseDate, + DeviceBatchSupplier = j.Device.DeviceBatch.Supplier, + DeviceBatchUnitCost = j.Device.DeviceBatch.UnitCost, + DeviceBatchWarrantyValidUntilDate = j.Device.DeviceBatch.WarrantyValidUntil, + DeviceBatchInsuredDate = j.Device.DeviceBatch.InsuredDate, + DeviceBatchInsuranceSupplier = j.Device.DeviceBatch.InsuranceSupplier, + DeviceBatchInsuredUntilDate = j.Device.DeviceBatch.InsuredUntil, + + DeviceProfileId = j.Device.DeviceProfileId, + DeviceProfileName = j.Device.DeviceProfile.Name, + DeviceProfileShortName = j.Device.DeviceProfile.ShortName, + }); + } + + private static List BuildMetadata(this JobExportOptions options, List records) + { + IEnumerable userDetailCustomKeys = null; + if (options.UserDetailCustom) + userDetailCustomKeys = records.Where(r => r.UserCustomDetails != null).SelectMany(r => r.UserCustomDetails.Keys).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + + var allAccessors = BuildRecordAccessors(userDetailCustomKeys); + + return typeof(JobExportOptions).GetProperties() + .Where(p => p.PropertyType == typeof(bool)) + .Select(p => new + { + property = p, + details = (DisplayAttribute)p.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() + }) + .Where(p => p.details != null && (bool)p.property.GetValue(options)) + .SelectMany(p => + { + var fieldMetadata = allAccessors[p.property.Name]; + fieldMetadata.ForEach(f => + { + if (f.ColumnName == null) + f.ColumnName = (p.details.ShortName == "Job" || p.details.ShortName == "Job Details") ? p.details.Name : $"{p.details.ShortName} {p.details.Name}"; + }); + return fieldMetadata; + }).ToList(); + } + + private static Dictionary> BuildRecordAccessors(IEnumerable userDetailCustomKeys) + { + const string DateFormat = "yyyy-MM-dd"; + const string DateTimeFormat = DateFormat + " HH:mm:ss"; + + Func csvStringEncoded = (o) => o == null ? null : $"\"{((string)o).Replace("\"", "\"\"")}\""; + Func csvToStringEncoded = (o) => o == null ? null : o.ToString(); + Func csvCurrencyEncoded = (o) => ((decimal?)o).HasValue ? ((decimal?)o).Value.ToString("C") : null; + Func csvDateEncoded = (o) => ((DateTime)o).ToString(DateFormat); + Func csvDateTimeEncoded = (o) => ((DateTime)o).ToString(DateTimeFormat); + Func csvNullableDateEncoded = (o) => ((DateTime?)o).HasValue ? csvDateEncoded(o) : null; + Func csvNullableDateTimeEncoded = (o) => ((DateTime?)o).HasValue ? csvDateTimeEncoded(o) : null; + + var metadata = new Dictionary>(); + + // Job + metadata.Add(nameof(JobExportOptions.JobId), new List() { new Metadata(nameof(JobExportOptions.JobId), typeof(int), r => r.Job.Id, csvToStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobStatus), new List() { new Metadata(nameof(JobExportOptions.JobStatus), typeof(string), r => Job.JobStatusIds.StatusDescriptions.TryGetValue(r.JobStatus, out var status) ? status : "Unknown", csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobType), new List() { new Metadata(nameof(JobExportOptions.JobType), typeof(string), r => r.JobTypeDescription, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobSubTypes), new List() { new Metadata(nameof(JobExportOptions.JobSubTypes), typeof(string), r => string.Join(", ", r.JobSubTypeDescriptions), csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobOpenedDate), new List() { new Metadata(nameof(JobExportOptions.JobOpenedDate), typeof(DateTime), r => r.Job.OpenedDate, csvDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobOpenedUser), new List() { new Metadata(nameof(JobExportOptions.JobOpenedUser), typeof(string), r => r.Job.OpenedTechUserId, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobExpectedClosedDate), new List() { new Metadata(nameof(JobExportOptions.JobExpectedClosedDate), typeof(DateTime), r => r.Job.ExpectedClosedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobClosedDate), new List() { new Metadata(nameof(JobExportOptions.JobClosedDate), typeof(DateTime), r => r.Job.ClosedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobClosedUser), new List() { new Metadata(nameof(JobExportOptions.JobClosedUser), typeof(string), r => r.Job.ClosedTechUserId, csvStringEncoded) }); + + // Job Details + metadata.Add(nameof(JobExportOptions.JobDeviceHeldDate), new List() { new Metadata(nameof(JobExportOptions.JobDeviceHeldDate), typeof(DateTime), r => r.Job.DeviceHeld, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobDeviceHeldUser), new List() { new Metadata(nameof(JobExportOptions.JobDeviceHeldUser), typeof(string), r => r.Job.DeviceHeldTechUserId, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobDeviceHeldLocation), new List() { new Metadata(nameof(JobExportOptions.JobDeviceHeldLocation), typeof(string), r => r.Job.DeviceHeldLocation, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobDeviceReadyForReturnDate), new List() { new Metadata(nameof(JobExportOptions.JobDeviceReadyForReturnDate), typeof(DateTime), r => r.Job.DeviceReadyForReturn, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobDeviceReadyForReturnUser), new List() { new Metadata(nameof(JobExportOptions.JobDeviceReadyForReturnUser), typeof(string), r => r.Job.DeviceReadyForReturnTechUserId, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobDeviceReturnedDate), new List() { new Metadata(nameof(JobExportOptions.JobDeviceReturnedDate), typeof(DateTime), r => r.Job.DeviceReturnedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobDeviceReturnedUser), new List() { new Metadata(nameof(JobExportOptions.JobDeviceReturnedUser), typeof(string), r => r.Job.DeviceReturnedTechUserId, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobWaitingForUserActionDate), new List() { new Metadata(nameof(JobExportOptions.JobWaitingForUserActionDate), typeof(DateTime), r => r.Job.WaitingForUserAction, csvNullableDateTimeEncoded) }); + + // Job Logs + metadata.Add(nameof(JobExportOptions.LogCount), new List() { new Metadata(nameof(JobExportOptions.LogCount), typeof(int), r => r.LogCount, csvToStringEncoded) }); + metadata.Add(nameof(JobExportOptions.LogFirstDate), new List() { new Metadata(nameof(JobExportOptions.LogFirstDate), typeof(DateTime), r => r.FirstLog?.Timestamp, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.LogFirstUser), new List() { new Metadata(nameof(JobExportOptions.LogFirstUser), typeof(string), r => r.FirstLog?.TechUserId, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.LogFirstContent), new List() { new Metadata(nameof(JobExportOptions.LogFirstContent), typeof(string), r => r.FirstLog?.Comments, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.LogLastDate), new List() { new Metadata(nameof(JobExportOptions.LogLastDate), typeof(DateTime), r => r.LastLog?.Timestamp, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.LogLastUser), new List() { new Metadata(nameof(JobExportOptions.LogLastUser), typeof(string), r => r.LastLog?.TechUserId, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.LogLastContent), new List() { new Metadata(nameof(JobExportOptions.LogLastContent), typeof(string), r => r.LastLog?.Comments, csvStringEncoded) }); + + // Attachments + metadata.Add(nameof(JobExportOptions.AttachmentsCount), new List() { new Metadata(nameof(JobExportOptions.AttachmentsCount), typeof(int), r => r.AttachmentsCount, csvToStringEncoded) }); + + // Job Queues + metadata.Add(nameof(JobExportOptions.JobQueueCount), new List() { new Metadata(nameof(JobExportOptions.JobQueueCount), typeof(int), r => r.QueueCount, csvToStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobQueueActiveCount), new List() { new Metadata(nameof(JobExportOptions.JobQueueActiveCount), typeof(int), r => r.QueueActiveCount, csvToStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobQueueActiveLatest), new List() { new Metadata(nameof(JobExportOptions.JobQueueActiveLatest), typeof(DateTime), r => r.QueueLatestActive?.JobQueueId == null ? null : JobQueueService.GetQueue(r.QueueLatestActive.JobQueueId).JobQueue.Name, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobQueueActiveLatestAddedDate), new List() { new Metadata(nameof(JobExportOptions.JobQueueActiveLatestAddedDate), typeof(DateTime), r => r.QueueLatestActive?.AddedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobQueueActiveLatestAddedUser), new List() { new Metadata(nameof(JobExportOptions.JobQueueActiveLatestAddedUser), typeof(string), r => r.QueueLatestActive?.AddedUserId, csvStringEncoded) }); + + // Warranty + metadata.Add(nameof(JobExportOptions.JobWarrantyExternalName), new List() { new Metadata(nameof(JobExportOptions.JobWarrantyExternalName), typeof(string), r => r.JobMetaWarranty?.ExternalName, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobWarrantyExternalReference), new List() { new Metadata(nameof(JobExportOptions.JobWarrantyExternalReference), typeof(string), r => r.JobMetaWarranty?.ExternalReference, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobWarrantyExternalLoggedDate), new List() { new Metadata(nameof(JobExportOptions.JobWarrantyExternalLoggedDate), typeof(DateTime), r => r.JobMetaWarranty?.ExternalLoggedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobWarrantyExternalCompletedDate), new List() { new Metadata(nameof(JobExportOptions.JobWarrantyExternalCompletedDate), typeof(DateTime), r => r.JobMetaWarranty?.ExternalCompletedDate, csvNullableDateTimeEncoded) }); + + // Non-Warranty + metadata.Add(nameof(JobExportOptions.JobNonWarrantyAccountingChargeRequiredDate), new List() { new Metadata(nameof(JobExportOptions.JobNonWarrantyAccountingChargeRequiredDate), typeof(DateTime), r => r.JobMetaNonWarranty?.AccountingChargeRequiredDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobNonWarrantyAccountingChargeAddedDate), new List() { new Metadata(nameof(JobExportOptions.JobNonWarrantyAccountingChargeAddedDate), typeof(DateTime), r => r.JobMetaNonWarranty?.AccountingChargeAddedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobNonWarrantyAccountingChargePaidDate), new List() { new Metadata(nameof(JobExportOptions.JobNonWarrantyAccountingChargePaidDate), typeof(DateTime), r => r.JobMetaNonWarranty?.AccountingChargePaidDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobNonWarrantyPurchaseOrderRaisedDate), new List() { new Metadata(nameof(JobExportOptions.JobNonWarrantyPurchaseOrderRaisedDate), typeof(DateTime), r => r.JobMetaNonWarranty?.PurchaseOrderRaisedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobNonWarrantyPurchaseOrderReference), new List() { new Metadata(nameof(JobExportOptions.JobNonWarrantyPurchaseOrderReference), typeof(string), r => r.JobMetaNonWarranty?.PurchaseOrderReference, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobNonWarrantyPurchaseOrderSentDate), new List() { new Metadata(nameof(JobExportOptions.JobNonWarrantyPurchaseOrderSentDate), typeof(DateTime), r => r.JobMetaNonWarranty?.PurchaseOrderSentDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobNonWarrantyInvoiceReceivedDate), new List() { new Metadata(nameof(JobExportOptions.JobNonWarrantyInvoiceReceivedDate), typeof(DateTime), r => r.JobMetaNonWarranty?.InvoiceReceivedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobNonWarrantyRepairerName), new List() { new Metadata(nameof(JobExportOptions.JobNonWarrantyRepairerName), typeof(string), r => r.JobMetaNonWarranty?.RepairerName, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobNonWarrantyRepairerLoggedDate), new List() { new Metadata(nameof(JobExportOptions.JobNonWarrantyRepairerLoggedDate), typeof(DateTime), r => r.JobMetaNonWarranty?.RepairerLoggedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobNonWarrantyRepairerReference), new List() { new Metadata(nameof(JobExportOptions.JobNonWarrantyRepairerReference), typeof(string), r => r.JobMetaNonWarranty?.RepairerReference, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobNonWarrantyRepairerCompletedDate), new List() { new Metadata(nameof(JobExportOptions.JobNonWarrantyRepairerCompletedDate), typeof(DateTime), r => r.JobMetaNonWarranty?.RepairerCompletedDate, csvNullableDateTimeEncoded) }); + + // Insurance + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceLossOrDamageDate), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceLossOrDamageDate), typeof(DateTime), r => r.JobMetaInsurance?.LossOrDamageDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceEventLocation), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceEventLocation), typeof(string), r => r.JobMetaInsurance?.EventLocation, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceDescription), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceDescription), typeof(string), r => r.JobMetaInsurance?.Description, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceThirdPartyCausedName), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceThirdPartyCausedName), typeof(string), r => r.JobMetaInsurance?.ThirdPartyCausedName, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceThirdPartyCausedWhy), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceThirdPartyCausedWhy), typeof(string), r => r.JobMetaInsurance?.ThirdPartyCausedWhy, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceWitnessesNamesAddresses), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceWitnessesNamesAddresses), typeof(string), r => r.JobMetaInsurance?.WitnessesNamesAddresses, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceBurglaryTheftMethodOfEntry), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceBurglaryTheftMethodOfEntry), typeof(string), r => r.JobMetaInsurance?.BurglaryTheftMethodOfEntry, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsurancePropertyLastSeenDate), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsurancePropertyLastSeenDate), typeof(DateTime), r => r.JobMetaInsurance?.PropertyLastSeenDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsurancePoliceNotifiedStation), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsurancePoliceNotifiedStation), typeof(string), r => r.JobMetaInsurance?.PoliceNotifiedStation, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsurancePoliceNotifiedDate), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsurancePoliceNotifiedDate), typeof(DateTime), r => r.JobMetaInsurance?.PoliceNotifiedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsurancePoliceNotifiedCrimeReportNo), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsurancePoliceNotifiedCrimeReportNo), typeof(string), r => r.JobMetaInsurance?.PoliceNotifiedCrimeReportNo, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceRecoverReduceAction), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceRecoverReduceAction), typeof(string), r => r.JobMetaInsurance?.RecoverReduceAction, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceOtherInterestedParties), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceOtherInterestedParties), typeof(string), r => r.JobMetaInsurance?.OtherInterestedParties, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceDateOfPurchase), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceDateOfPurchase), typeof(DateTime), r => r.JobMetaInsurance?.DateOfPurchase, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceClaimFormSentDate), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceClaimFormSentDate), typeof(DateTime), r => r.JobMetaInsurance?.ClaimFormSentDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceInsurer), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceInsurer), typeof(string), r => r.JobMetaInsurance?.Insurer, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.JobMetaInsuranceInsurerReference), new List() { new Metadata(nameof(JobExportOptions.JobMetaInsuranceInsurerReference), typeof(string), r => r.JobMetaInsurance?.InsurerReference, csvStringEncoded) }); + + // User Management + metadata.Add(nameof(JobExportOptions.JobUserManagementFlags), new List() { new Metadata(nameof(JobExportOptions.JobUserManagementFlags), typeof(string), r => r.Job.Flags, csvToStringEncoded) }); + + // User + metadata.Add(nameof(JobExportOptions.UserId), new List() { new Metadata(nameof(JobExportOptions.UserId), typeof(string), r => r.User?.UserId, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.UserDisplayName), new List() { new Metadata(nameof(JobExportOptions.UserDisplayName), typeof(string), r => r.User?.DisplayName, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.UserSurname), new List() { new Metadata(nameof(JobExportOptions.UserSurname), typeof(string), r => r.User?.Surname, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.UserGivenName), new List() { new Metadata(nameof(JobExportOptions.UserGivenName), typeof(string), r => r.User?.GivenName, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.UserPhoneNumber), new List() { new Metadata(nameof(JobExportOptions.UserPhoneNumber), typeof(string), r => r.User?.PhoneNumber, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.UserEmailAddress), new List() { new Metadata(nameof(JobExportOptions.UserEmailAddress), typeof(string), r => r.User?.EmailAddress, csvStringEncoded) }); + if (userDetailCustomKeys != null) + { + var assignedUserDetailCustomFields = new List(); + foreach (var detailKey in userDetailCustomKeys.OrderBy(k => k, StringComparer.OrdinalIgnoreCase)) + { + var key = detailKey; + assignedUserDetailCustomFields.Add(new Metadata(detailKey, detailKey, typeof(string), r => r.UserCustomDetails != null && r.UserCustomDetails.TryGetValue(key, out var value) ? value : null, csvStringEncoded)); + } + metadata.Add(nameof(JobExportOptions.UserDetailCustom), assignedUserDetailCustomFields); + } + + // Device + metadata.Add(nameof(JobExportOptions.DeviceSerialNumber), new List() { new Metadata(nameof(JobExportOptions.DeviceSerialNumber), typeof(string), r => r.Device?.SerialNumber, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceAssetNumber), new List() { new Metadata(nameof(JobExportOptions.DeviceAssetNumber), typeof(string), r => r.Device?.AssetNumber, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceLocation), new List() { new Metadata(nameof(JobExportOptions.DeviceLocation), typeof(string), r => r.Device?.Location, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceComputerName), new List() { new Metadata(nameof(JobExportOptions.DeviceComputerName), typeof(string), r => r.Device?.DeviceDomainId, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceLastNetworkLogon), new List() { new Metadata(nameof(JobExportOptions.DeviceLastNetworkLogon), typeof(DateTime), r => r.Device?.LastNetworkLogonDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceCreatedDate), new List() { new Metadata(nameof(JobExportOptions.DeviceCreatedDate), typeof(DateTime), r => r.Device?.CreatedDate, csvDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceFirstEnrolledDate), new List() { new Metadata(nameof(JobExportOptions.DeviceFirstEnrolledDate), typeof(DateTime), r => r.Device?.EnrolledDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceLastEnrolledDate), new List() { new Metadata(nameof(JobExportOptions.DeviceLastEnrolledDate), typeof(DateTime), r => r.Device?.LastEnrolDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceAllowUnauthenticatedEnrol), new List() { new Metadata(nameof(JobExportOptions.DeviceAllowUnauthenticatedEnrol), typeof(bool), r => r.Device?.AllowUnauthenticatedEnrol, csvToStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceDecommissionedDate), new List() { new Metadata(nameof(JobExportOptions.DeviceDecommissionedDate), typeof(DateTime), r => r.Device?.DecommissionedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceDecommissionedReason), new List() { new Metadata(nameof(JobExportOptions.DeviceDecommissionedReason), typeof(string), r => r.Device?.DecommissionReason, csvToStringEncoded) }); + + // Model + metadata.Add(nameof(JobExportOptions.DeviceModelId), new List() { new Metadata(nameof(JobExportOptions.DeviceModelId), typeof(int), r => r.DeviceModelId, csvToStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceModelDescription), new List() { new Metadata(nameof(JobExportOptions.DeviceModelDescription), typeof(string), r => r.DeviceModelDescription, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceModelManufacturer), new List() { new Metadata(nameof(JobExportOptions.DeviceModelManufacturer), typeof(string), r => r.DeviceModelManufacturer, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceModelModel), new List() { new Metadata(nameof(JobExportOptions.DeviceModelModel), typeof(string), r => r.DeviceModelModel, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceModelType), new List() { new Metadata(nameof(JobExportOptions.DeviceModelType), typeof(string), r => r.DeviceModelType, csvStringEncoded) }); + + // Batch + metadata.Add(nameof(JobExportOptions.DeviceBatchId), new List() { new Metadata(nameof(JobExportOptions.DeviceBatchId), typeof(int), r => r.DeviceBatchId, csvToStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceBatchName), new List() { new Metadata(nameof(JobExportOptions.DeviceBatchName), typeof(string), r => r.DeviceBatchName, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceBatchPurchaseDate), new List() { new Metadata(nameof(JobExportOptions.DeviceBatchPurchaseDate), typeof(DateTime), r => r.DeviceBatchPurchaseDate, csvNullableDateEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceBatchSupplier), new List() { new Metadata(nameof(JobExportOptions.DeviceBatchSupplier), typeof(string), r => r.DeviceBatchSupplier, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceBatchUnitCost), new List() { new Metadata(nameof(JobExportOptions.DeviceBatchUnitCost), typeof(decimal), r => r.DeviceBatchUnitCost, csvCurrencyEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceBatchWarrantyValidUntilDate), new List() { new Metadata(nameof(JobExportOptions.DeviceBatchWarrantyValidUntilDate), typeof(DateTime), r => r.DeviceBatchWarrantyValidUntilDate, csvNullableDateEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceBatchInsuredDate), new List() { new Metadata(nameof(JobExportOptions.DeviceBatchInsuredDate), typeof(DateTime), r => r.DeviceBatchInsuredDate, csvNullableDateEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceBatchInsuranceSupplier), new List() { new Metadata(nameof(JobExportOptions.DeviceBatchInsuranceSupplier), typeof(string), r => r.DeviceBatchInsuranceSupplier, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceBatchInsuredUntilDate), new List() { new Metadata(nameof(JobExportOptions.DeviceBatchInsuredUntilDate), typeof(DateTime), r => r.DeviceBatchInsuredUntilDate, csvNullableDateEncoded) }); + + // Profile + metadata.Add(nameof(JobExportOptions.DeviceProfileId), new List() { new Metadata(nameof(JobExportOptions.DeviceProfileId), typeof(int), r => r.DeviceProfileId, csvToStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceProfileName), new List() { new Metadata(nameof(JobExportOptions.DeviceProfileName), typeof(string), r => r.DeviceProfileName, csvStringEncoded) }); + metadata.Add(nameof(JobExportOptions.DeviceProfileShortName), new List() { new Metadata(nameof(JobExportOptions.DeviceProfileShortName), typeof(string), r => r.DeviceProfileShortName, csvStringEncoded) }); + + return metadata; + } + + } +} diff --git a/Disco.Services/Jobs/Exporting/JobExportTask.cs b/Disco.Services/Jobs/Exporting/JobExportTask.cs new file mode 100644 index 00000000..3246bb5f --- /dev/null +++ b/Disco.Services/Jobs/Exporting/JobExportTask.cs @@ -0,0 +1,44 @@ +using Disco.Data.Repository; +using Disco.Models.Services.Jobs.Exporting; +using Disco.Services.Exporting; +using Disco.Services.Tasks; +using Quartz; + +namespace Disco.Services.Jobs.Exporting +{ + public class JobExportTask : ScheduledTask + { + private const string JobDataMapContext = "Context"; + + public override string TaskName { get { return "Export Jobs"; } } + public override bool SingleInstanceTask { get { return false; } } + public override bool CancelInitiallySupported { get { return false; } } + + public static ExportTaskContext ScheduleNow(JobExportOptions Options) + { + // Build Context + var context = new ExportTaskContext(Options); + + // Build Data Map + var task = new JobExportTask(); + JobDataMap taskData = new JobDataMap() { { JobDataMapContext, context} }; + + // Schedule Task + context.TaskStatus = task.ScheduleTask(taskData); + + return context; + } + + protected override void ExecuteTask() + { + var context = (ExportTaskContext)ExecutionContext.JobDetail.JobDataMap[JobDataMapContext]; + + Status.UpdateStatus(10, "Exporting Job Records", "Starting..."); + + using (DiscoDataContext Database = new DiscoDataContext()) + { + context.Result = JobExport.GenerateExport(Database, context.Options, Status); + } + } + } +} diff --git a/Disco.Services/Jobs/JobExtensions.cs b/Disco.Services/Jobs/JobExtensions.cs index 4d333378..756c69aa 100644 --- a/Disco.Services/Jobs/JobExtensions.cs +++ b/Disco.Services/Jobs/JobExtensions.cs @@ -81,116 +81,121 @@ namespace Disco.Services return i; } - public static string JobStatusDescription(string StatusId, Job j = null) - { - switch (StatusId) - { - case Job.JobStatusIds.Open: - return "Open"; - case Job.JobStatusIds.Closed: - return "Closed"; - case Job.JobStatusIds.AwaitingWarrantyRepair: - if (j == null) - return "Awaiting Warranty Repair"; - else - if (j.DeviceHeld.HasValue) - return string.Format("Awaiting Warranty Repair ({0})", j.JobMetaWarranty.ExternalName); - else - return string.Format("Awaiting Warranty Repair - Not Held ({0})", j.JobMetaWarranty.ExternalName); - case Job.JobStatusIds.AwaitingRepairs: - if (j == null) - return "Awaiting Repairs"; - else - if (j.DeviceHeld.HasValue) - return string.Format("Awaiting Repairs ({0})", j.JobMetaNonWarranty.RepairerName); - else - return string.Format("Awaiting Repairs - Not Held ({0})", j.JobMetaNonWarranty.RepairerName); - case Job.JobStatusIds.AwaitingDeviceReturn: - return "Awaiting Device Return"; - case Job.JobStatusIds.AwaitingUserAction: - return "Awaiting User Action"; - case Job.JobStatusIds.AwaitingAccountingPayment: - return "Awaiting Accounting Payment"; - case Job.JobStatusIds.AwaitingAccountingCharge: - return "Awaiting Accounting Charge"; - case Job.JobStatusIds.AwaitingInsuranceProcessing: - return "Awaiting Insurance Processing"; - default: - return "Unknown"; - } - } + public static string JobStatusDescription(string StatusId, Job j) + => JobStatusDescription(StatusId, j?.DeviceHeld, j?.JobMetaWarranty?.ExternalName, j?.JobMetaNonWarranty?.RepairerName); + + public static string JobStatusDescription(string StatusId, JobTableStatusItemModel j) + => JobStatusDescription(StatusId, j?.DeviceHeld, j?.JobMetaWarranty_ExternalName, j?.JobMetaNonWarranty_RepairerName); - public static string JobStatusDescription(string StatusId, JobTableStatusItemModel j = null) + public static string JobStatusDescription(string statusId, DateTime? deviceHeld = null, string warrantyExternalName = null, string nonWarrantyRepairerName = null) { - switch (StatusId) + if (!Job.JobStatusIds.StatusDescriptions.TryGetValue(statusId, out var statusDescription)) + return "Unknown"; + + switch (statusId) { - case Job.JobStatusIds.Open: - return "Open"; - case Job.JobStatusIds.Closed: - return "Closed"; case Job.JobStatusIds.AwaitingWarrantyRepair: - if (j == null) - return "Awaiting Warranty Repair"; + var warrantyName = string.Empty; + if (!string.IsNullOrWhiteSpace(warrantyExternalName)) + warrantyName = $" ({warrantyExternalName})"; + + if (deviceHeld.HasValue) + return statusDescription + warrantyName; else - if (j.DeviceHeld.HasValue) - return string.Format("Awaiting Warranty Repair ({0})", j.JobMetaWarranty_ExternalName); - else - return string.Format("Awaiting Warranty Repair - Not Held ({0})", j.JobMetaWarranty_ExternalName); + return $"{statusDescription} - Not Held{warrantyExternalName}"; case Job.JobStatusIds.AwaitingRepairs: - if (j == null) - return "Awaiting Repairs"; + var repairerName = string.Empty; + if (!string.IsNullOrWhiteSpace(nonWarrantyRepairerName)) + repairerName = $" ({nonWarrantyRepairerName})"; + + if (deviceHeld.HasValue) + return statusDescription + repairerName; else - if (j.DeviceHeld.HasValue) - return string.Format("Awaiting Repairs ({0})", j.JobMetaNonWarranty_RepairerName); - else - return string.Format("Awaiting Repairs - Not Held ({0})", j.JobMetaNonWarranty_RepairerName); - case Job.JobStatusIds.AwaitingDeviceReturn: - return "Awaiting Device Return"; - case Job.JobStatusIds.AwaitingUserAction: - return "Awaiting User Action"; - case Job.JobStatusIds.AwaitingAccountingPayment: - return "Awaiting Accounting Payment"; - case Job.JobStatusIds.AwaitingAccountingCharge: - return "Awaiting Accounting Charge"; - case Job.JobStatusIds.AwaitingInsuranceProcessing: - return "Awaiting Insurance Processing"; + return $"{statusDescription} - Not Held{repairerName}"; default: - return "Unknown"; + return statusDescription; } } public static string CalculateStatusId(this Job j) { - return j.ToJobTableStatusItemModel().CalculateStatusId(); + return CalculateStatusId( + j.ClosedDate, + j.JobTypeId, + j.JobMetaWarranty?.ExternalLoggedDate, + j.JobMetaWarranty?.ExternalCompletedDate, + j.JobMetaNonWarranty?.RepairerLoggedDate, + j.JobMetaNonWarranty?.RepairerCompletedDate, + j.JobMetaNonWarranty?.AccountingChargeRequiredDate, + j.JobMetaNonWarranty?.AccountingChargeAddedDate, + j.JobMetaNonWarranty?.AccountingChargePaidDate, + j.JobMetaNonWarranty?.IsInsuranceClaim, + j.JobMetaInsurance?.ClaimFormSentDate, + j.WaitingForUserAction, + j.DeviceReadyForReturn, + j.DeviceReturnedDate); } public static string CalculateStatusId(this JobTableStatusItemModel j) { - if (j.ClosedDate.HasValue) + return CalculateStatusId( + j.ClosedDate, + j.JobTypeId, + j.JobMetaWarranty_ExternalLoggedDate, + j.JobMetaWarranty_ExternalCompletedDate, + j.JobMetaNonWarranty_RepairerLoggedDate, + j.JobMetaNonWarranty_RepairerCompletedDate, + j.JobMetaNonWarranty_AccountingChargeRequiredDate, + j.JobMetaNonWarranty_AccountingChargeAddedDate, + j.JobMetaNonWarranty_AccountingChargePaidDate, + j.JobMetaNonWarranty_IsInsuranceClaim, + j.JobMetaInsurance_ClaimFormSentDate, + j.WaitingForUserAction, + j.DeviceReadyForReturn, + j.DeviceReturnedDate); + } + + public static string CalculateStatusId( + DateTime? closedDate, + string jobTypeId, + DateTime? warrantyExternallyLoggedDate, + DateTime? warrantyExternallyCompletedDate, + DateTime? nonWarrantyRepairerLoggedDate, + DateTime? nonWarrantyRepairerCompletedDate, + DateTime? nonWarrantyAccountingChargeRequiredDate, + DateTime? nonWarrantyAccountintChargeAddedDate, + DateTime? nonWarrantyAccountintChargePaidDate, + bool? nonWarrantyIsInsuranceClaim, + DateTime? insuranceClaimFormSentDate, + DateTime? waitingForUserActionDate, + DateTime? deviceReadyForReturnDate, + DateTime? deviceReturnedDate) + { + if (closedDate.HasValue) return Job.JobStatusIds.Closed; - if (j.JobTypeId == JobType.JobTypeIds.HWar) + if (jobTypeId == JobType.JobTypeIds.HWar) { - if (j.JobMetaWarranty_ExternalLoggedDate.HasValue && !j.JobMetaWarranty_ExternalCompletedDate.HasValue) + if (warrantyExternallyLoggedDate.HasValue && !warrantyExternallyCompletedDate.HasValue) return Job.JobStatusIds.AwaitingWarrantyRepair; // Job Logged - but not marked as completed } - if (j.JobTypeId == JobType.JobTypeIds.HNWar) + if (jobTypeId == JobType.JobTypeIds.HNWar) { - if (j.JobMetaNonWarranty_RepairerLoggedDate.HasValue && !j.JobMetaNonWarranty_RepairerCompletedDate.HasValue) + if (nonWarrantyRepairerLoggedDate.HasValue && !nonWarrantyRepairerCompletedDate.HasValue) return Job.JobStatusIds.AwaitingRepairs; // Repairs logged - but not complete - if (j.JobMetaNonWarranty_AccountingChargeAddedDate.HasValue && !j.JobMetaNonWarranty_AccountingChargePaidDate.HasValue) + if (nonWarrantyAccountintChargeAddedDate.HasValue && !nonWarrantyAccountintChargePaidDate.HasValue) return Job.JobStatusIds.AwaitingAccountingPayment; // Accounting Charge Added, but not paid - if (j.JobMetaNonWarranty_AccountingChargeRequiredDate.HasValue && (!j.JobMetaNonWarranty_AccountingChargePaidDate.HasValue || !j.JobMetaNonWarranty_AccountingChargeAddedDate.HasValue)) + if (nonWarrantyAccountingChargeRequiredDate.HasValue && (!nonWarrantyAccountintChargePaidDate.HasValue || !nonWarrantyAccountintChargeAddedDate.HasValue)) return Job.JobStatusIds.AwaitingAccountingCharge; // Accounting Charge Required, but not added or paid - if (j.JobMetaNonWarranty_RepairerLoggedDate.HasValue && j.JobMetaNonWarranty_IsInsuranceClaim.Value && !j.JobMetaInsurance_ClaimFormSentDate.HasValue) + if (nonWarrantyRepairerLoggedDate.HasValue && nonWarrantyIsInsuranceClaim.GetValueOrDefault() && !insuranceClaimFormSentDate.HasValue) return Job.JobStatusIds.AwaitingInsuranceProcessing; // Is insurance claim, but no Claim Form Sent } - if (j.WaitingForUserAction.HasValue) + if (waitingForUserActionDate.HasValue) return Job.JobStatusIds.AwaitingUserAction; // Awaiting for User - if (j.DeviceReadyForReturn.HasValue && !j.DeviceReturnedDate.HasValue) + if (deviceReadyForReturnDate.HasValue && !deviceReturnedDate.HasValue) return Job.JobStatusIds.AwaitingDeviceReturn; // Device not returned to User return Job.JobStatusIds.Open; diff --git a/Disco.Services/Jobs/Statistics/DailyOpenedClosed.cs b/Disco.Services/Jobs/Statistics/DailyOpenedClosed.cs index 2e8b10b2..691d058a 100644 --- a/Disco.Services/Jobs/Statistics/DailyOpenedClosed.cs +++ b/Disco.Services/Jobs/Statistics/DailyOpenedClosed.cs @@ -1,7 +1,7 @@ using Disco.Data.Repository; using Disco.Data.Repository.Monitor; using Disco.Models.Repository; -using Disco.Models.Services.Job.Statistics; +using Disco.Models.Services.Jobs.Statistics; using Disco.Services.Tasks; using Quartz; using System; diff --git a/Disco.Web.Extensions/Disco.Web.Extensions.csproj b/Disco.Web.Extensions/Disco.Web.Extensions.csproj index 07550b17..102b86de 100644 --- a/Disco.Web.Extensions/Disco.Web.Extensions.csproj +++ b/Disco.Web.Extensions/Disco.Web.Extensions.csproj @@ -111,6 +111,7 @@ + diff --git a/Disco.Web.Extensions/RazorExtensions/DropDownListExtensions.cs b/Disco.Web.Extensions/RazorExtensions/DropDownListExtensions.cs new file mode 100644 index 00000000..d7e6a321 --- /dev/null +++ b/Disco.Web.Extensions/RazorExtensions/DropDownListExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Web.Mvc.Html; +using System.Web.Mvc; + +namespace Disco.Web.Extensions +{ + public static class DropDownListExtensions + { + public static MvcHtmlString DropDownListFor(this HtmlHelper htmlHelper, Expression> expression, Func> source, Func valueGenerator, Func textGenerator, string instructionMessage = null) + { + var selectList = source(htmlHelper.ViewData.Model) + .Select(i => new SelectListItem() { Value = valueGenerator(i), Text = textGenerator(i) }); + + if (instructionMessage != null) + { + selectList = new SelectListItem[] + { new SelectListItem { Text = instructionMessage, Value = string.Empty } } + .Concat(selectList); + } + + return htmlHelper.DropDownListFor(expression, selectList); + } + } +} diff --git a/Disco.Web/Areas/API/Controllers/DeviceController.cs b/Disco.Web/Areas/API/Controllers/DeviceController.cs index aab1809f..2ed350be 100644 --- a/Disco.Web/Areas/API/Controllers/DeviceController.cs +++ b/Disco.Web/Areas/API/Controllers/DeviceController.cs @@ -693,6 +693,7 @@ namespace Disco.Web.Areas.API.Controllers internal const string ExportSessionCacheKey = "DeviceExportContext_{0}"; [DiscoAuthorize(Claims.Device.Actions.Export)] + [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult Export(ExportModel Model) { if (Model == null || Model.Options == null) @@ -734,6 +735,9 @@ namespace Disco.Web.Areas.API.Controllers if (context.Result == null || context.Result.Result == null) throw new ArgumentException("The export session is still running, or failed to complete successfully", "Id"); + if (context.Result.RecordCount == 0) + throw new ArgumentException("No records were found to export", nameof(Id)); + var fileStream = context.Result.Result; return this.File(fileStream.GetBuffer(), 0, (int)fileStream.Length, context.Result.MimeType, context.Result.Filename); diff --git a/Disco.Web/Areas/API/Controllers/DeviceFlagController.cs b/Disco.Web/Areas/API/Controllers/DeviceFlagController.cs index 25b851d2..ddaef458 100644 --- a/Disco.Web/Areas/API/Controllers/DeviceFlagController.cs +++ b/Disco.Web/Areas/API/Controllers/DeviceFlagController.cs @@ -444,6 +444,9 @@ namespace Disco.Web.Areas.API.Controllers if (context.Result == null || context.Result.Result == null) throw new ArgumentException("The export session is still running, or failed to complete successfully", nameof(Id)); + if (context.Result.RecordCount == 0) + throw new ArgumentException("No records were found to export", nameof(Id)); + var fileStream = context.Result.Result; return this.File(fileStream.GetBuffer(), 0, (int)fileStream.Length, context.Result.MimeType, context.Result.Filename); diff --git a/Disco.Web/Areas/API/Controllers/JobController.cs b/Disco.Web/Areas/API/Controllers/JobController.cs index 679ab582..978639d8 100644 --- a/Disco.Web/Areas/API/Controllers/JobController.cs +++ b/Disco.Web/Areas/API/Controllers/JobController.cs @@ -1,18 +1,25 @@ using Disco.Models.Repository; -using Disco.Models.Services.Job; +using Disco.Models.Services.Jobs; +using Disco.Models.Services.Jobs.Exporting; using Disco.Models.Services.Jobs.JobLists; using Disco.Services; using Disco.Services.Authorization; +using Disco.Services.Exporting; using Disco.Services.Interop; +using Disco.Services.Jobs.Exporting; using Disco.Services.Jobs.JobLists; using Disco.Services.Jobs.Statistics; using Disco.Services.Users; using Disco.Services.Web; +using Disco.Web.Extensions; +using Disco.Web.Models.Job; using System; using System.Collections.Generic; using System.Data.Entity; using System.IO; using System.Linq; +using System.Web; +using System.Web.Caching; using System.Web.Mvc; namespace Disco.Web.Areas.API.Controllers @@ -2152,5 +2159,62 @@ namespace Disco.Web.Areas.API.Controllers return Json(results, JsonRequestBehavior.AllowGet); } + + #region Exporting + internal const string ExportSessionCacheKey = "JobExportContext_{0}"; + + [DiscoAuthorize(Claims.Job.Actions.Export)] + [HttpPost, ValidateAntiForgeryToken] + public virtual ActionResult Export(ExportModel model) + { + if (model == null || model.Options == null) + throw new ArgumentNullException(nameof(model)); + + // Write Options to Configuration + Database.DiscoConfiguration.JobPreferences.LastExportOptions = model.Options; + Database.SaveChanges(); + + // Start Export + var exportContext = JobExportTask.ScheduleNow(model.Options); + + // Store Export Context in Web Cache + string key = string.Format(ExportSessionCacheKey, exportContext.TaskStatus.SessionId); + HttpRuntime.Cache.Insert(key, exportContext, null, DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null); + + // Set Task Finished Url + var finishedActionResult = MVC.Job.Export(exportContext.TaskStatus.SessionId); + exportContext.TaskStatus.SetFinishedUrl(Url.Action(finishedActionResult)); + + // Try waiting for completion + if (exportContext.TaskStatus.WaitUntilFinished(TimeSpan.FromSeconds(2))) + return RedirectToAction(finishedActionResult); + else + return RedirectToAction(MVC.Config.Logging.TaskStatus(exportContext.TaskStatus.SessionId)); + } + + [DiscoAuthorize(Claims.Job.Actions.Export)] + public virtual ActionResult ExportRetrieve(string id) + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentNullException("Id"); + + string key = string.Format(ExportSessionCacheKey, id); + var context = HttpRuntime.Cache.Get(key) as ExportTaskContext; + + if (context == null) + throw new ArgumentException("The Id specified is invalid, or the export data expired (60 minutes)", nameof(id)); + + if (context.Result == null || context.Result.Result == null) + throw new ArgumentException("The export session is still running, or failed to complete successfully", nameof(id)); + + if (context.Result.RecordCount == 0) + throw new ArgumentException("No records were found to export", nameof(id)); + + var fileStream = context.Result.Result; + + return this.File(fileStream.GetBuffer(), 0, (int)fileStream.Length, context.Result.MimeType, context.Result.Filename); + } + + #endregion } } diff --git a/Disco.Web/Areas/API/Controllers/JobPreferencesController.cs b/Disco.Web/Areas/API/Controllers/JobPreferencesController.cs index 846bb7a1..a71015f0 100644 --- a/Disco.Web/Areas/API/Controllers/JobPreferencesController.cs +++ b/Disco.Web/Areas/API/Controllers/JobPreferencesController.cs @@ -1,4 +1,4 @@ -using Disco.Models.Services.Job; +using Disco.Models.Services.Jobs; using Disco.Services.Authorization; using Disco.Services.Jobs; using Disco.Services.Web; diff --git a/Disco.Web/Areas/API/Controllers/UserFlagController.cs b/Disco.Web/Areas/API/Controllers/UserFlagController.cs index 8baabf27..6b8f1853 100644 --- a/Disco.Web/Areas/API/Controllers/UserFlagController.cs +++ b/Disco.Web/Areas/API/Controllers/UserFlagController.cs @@ -449,6 +449,9 @@ namespace Disco.Web.Areas.API.Controllers if (context.Result == null || context.Result.Result == null) throw new ArgumentException("The export session is still running, or failed to complete successfully", nameof(Id)); + if (context.Result.RecordCount == 0) + throw new ArgumentException("No records were found to export", nameof(Id)); + var fileStream = context.Result.Result; return this.File(fileStream.GetBuffer(), 0, (int)fileStream.Length, context.Result.MimeType, context.Result.Filename); diff --git a/Disco.Web/Areas/Config/Models/JobPreferences/IndexModel.cs b/Disco.Web/Areas/Config/Models/JobPreferences/IndexModel.cs index daf555b7..37afbd18 100644 --- a/Disco.Web/Areas/Config/Models/JobPreferences/IndexModel.cs +++ b/Disco.Web/Areas/Config/Models/JobPreferences/IndexModel.cs @@ -1,5 +1,5 @@ using Disco.Data.Repository; -using Disco.Models.Services.Job; +using Disco.Models.Services.Jobs; using Disco.Models.UI.Config.JobPreferences; using Disco.Services.Extensions; using System; diff --git a/Disco.Web/Areas/Config/Views/DeviceFlag/Export.cshtml b/Disco.Web/Areas/Config/Views/DeviceFlag/Export.cshtml index 21c31831..c6dd4717 100644 --- a/Disco.Web/Areas/Config/Views/DeviceFlag/Export.cshtml +++ b/Disco.Web/Areas/Config/Views/DeviceFlag/Export.cshtml @@ -71,7 +71,8 @@ @foreach (var optionItem in optionFields.Take(itemsPerColumn)) {
  • -
  • + + } @@ -80,7 +81,8 @@ @foreach (var optionItem in optionFields.Skip(itemsPerColumn)) {
  • -
  • + + } @@ -88,7 +90,8 @@ - + + } @@ -161,8 +164,15 @@ @if (Model.ExportSessionId != null) {
    -

    @Model.ExportSessionResult.RecordCount record@(Model.ExportSessionResult.RecordCount != 1 ? "s" : null) were successfully exported.

    - Download Device Flag Export + @if (Model.ExportSessionResult.RecordCount == 0) + { +

    No records matched the filter criteria

    + } + else + { +

    @Model.ExportSessionResult.RecordCount record@(Model.ExportSessionResult.RecordCount != 1 ? "s" : null) were successfully exported.

    + Download Device Flag Export + }
    \r\n"); - #line 159 "..\..\Areas\Config\Views\DeviceFlag\Export.cshtml" + #line 162 "..\..\Areas\Config\Views\DeviceFlag\Export.cshtml" } @@ -599,7 +600,7 @@ WriteLiteral(" \r\n"); - #line 159 "..\..\Areas\Config\Views\UserFlag\Export.cshtml" + #line 162 "..\..\Areas\Config\Views\UserFlag\Export.cshtml" } @@ -599,7 +600,7 @@ WriteLiteral(" \r\n"); - #line 172 "..\..\Views\Device\Export.cshtml" + #line 176 "..\..\Views\Device\Export.cshtml" } @@ -572,7 +582,7 @@ WriteLiteral(" + } + +@if (Model.ExportSessionId != null) +{ +
    + @if (Model.ExportSessionResult.RecordCount == 0) + { +

    No records matched the filter criteria

    + } + else + { +

    @Model.ExportSessionResult.RecordCount record@(Model.ExportSessionResult.RecordCount != 1 ? "s" : null) were successfully exported.

    + Download Job Export + } +
    + +} +
    +

    Exporting jobs...

    +
    +
    + +
    diff --git a/Disco.Web/Views/Job/Export.generated.cs b/Disco.Web/Views/Job/Export.generated.cs new file mode 100644 index 00000000..e8d230a1 --- /dev/null +++ b/Disco.Web/Views/Job/Export.generated.cs @@ -0,0 +1,1105 @@ +#pragma warning disable 1591 +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Disco.Web.Views.Job +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using System.Web; + using System.Web.Helpers; + using System.Web.Mvc; + using System.Web.Mvc.Ajax; + using System.Web.Mvc.Html; + using System.Web.Routing; + using System.Web.Security; + using System.Web.UI; + using System.Web.WebPages; + using Disco; + using Disco.Models.Repository; + using Disco.Services; + using Disco.Services.Authorization; + using Disco.Services.Web; + using Disco.Web; + using Disco.Web.Extensions; + + #line 1 "..\..\Views\Job\Export.cshtml" + using Disco.Web.Models.Job; + + #line default + #line hidden + + [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] + [System.Web.WebPages.PageVirtualPathAttribute("~/Views/Job/Export.cshtml")] + public partial class Export : Disco.Services.Web.WebViewPage + { + public Export() + { + } + public override void Execute() + { + + #line 3 "..\..\Views\Job\Export.cshtml" + + Authorization.RequireAny(Claims.Job.Actions.Export); + + ViewBag.Title = Html.ToBreadcrumb("Jobs", MVC.Job.Index(), "Export Jobs"); + + var optionsMetadata = ModelMetadata.FromLambdaExpression(m => m.Options, ViewData); + var optionGroups = optionsMetadata.Properties.Where(p => p.ShortDisplayName != null && p.ModelType == typeof(bool)) + .GroupBy(m => m.ShortDisplayName); + + + #line default + #line hidden +WriteLiteral("\r\n\r\n"); + + + #line 13 "..\..\Views\Job\Export.cshtml" + + + #line default + #line hidden + + #line 13 "..\..\Views\Job\Export.cshtml" + using (Html.BeginForm(MVC.API.Job.Export())) + { + + + #line default + #line hidden + + #line 15 "..\..\Views\Job\Export.cshtml" + Write(Html.AntiForgeryToken()); + + + #line default + #line hidden + + #line 15 "..\..\Views\Job\Export.cshtml" + + + + #line default + #line hidden +WriteLiteral(" \r\n

    Export Filter

    \r\n \r\n \r" + +"\n \r\n Start Date:\r\n \r\n " + +" \r\n \r\n \r\n " + +" \r\n \r\n \r\n \r\n " + +" \r\n \r\n \r\n \r\n " + +" \r\n \r\n \r\n Type:\r\n \r\n \r\n " + +" \r\n \r\n"); + + + #line 52 "..\..\Views\Job\Export.cshtml" + + + #line default + #line hidden + + #line 52 "..\..\Views\Job\Export.cshtml" + foreach (var jobType in Model.JobTypes) + { + var subTypes = jobType.JobSubTypes.OrderBy(s => s.Description).ToList(); + var itemsPerColumn = (int)Math.Ceiling((double)subTypes.Count / 2); + + + #line default + #line hidden +WriteLiteral(" (jobType.Id + + #line default + #line hidden +, 2665), false) +); + +WriteLiteral(" class=\"Jobs_Export_SubType_Target\""); + +WriteLiteral(" data-typeid=\""); + + + #line 56 "..\..\Views\Job\Export.cshtml" + Write(jobType.Id); + + + #line default + #line hidden +WriteLiteral("\""); + +WriteLiteral(">\r\n"); + + + #line 57 "..\..\Views\Job\Export.cshtml" + + + #line default + #line hidden + + #line 57 "..\..\Views\Job\Export.cshtml" + if (jobType.JobSubTypes.Count > 2) + { + + + #line default + #line hidden +WriteLiteral(" ALL | NONE\r\n"); + + + #line 60 "..\..\Views\Job\Export.cshtml" + } + + + #line default + #line hidden +WriteLiteral(" \r\n \r\n " + +" \r\n \r\n"); + + + #line 65 "..\..\Views\Job\Export.cshtml" + + + #line default + #line hidden + + #line 65 "..\..\Views\Job\Export.cshtml" + foreach (var subType in subTypes.Take(itemsPerColumn)) + { + + + #line default + #line hidden +WriteLiteral("
  • \r\n " + +" (jobType.Id + + #line default + #line hidden +, 3713), false) +, Tuple.Create(Tuple.Create("", 3726), Tuple.Create("_", 3726), true) + + #line 68 "..\..\Views\Job\Export.cshtml" + , Tuple.Create(Tuple.Create("", 3727), Tuple.Create(subType.Id + + #line default + #line hidden +, 3727), false) +); + +WriteLiteral(" name=\"Options.FilterJobSubTypeIds\""); + +WriteAttribute("value", Tuple.Create(" value=\"", 3776), Tuple.Create("\"", 3795) + + #line 68 "..\..\Views\Job\Export.cshtml" + , Tuple.Create(Tuple.Create("", 3784), Tuple.Create(subType.Id + + #line default + #line hidden +, 3784), false) +); + +WriteLiteral(" "); + + + #line 68 "..\..\Views\Job\Export.cshtml" + Write((Model.Options.FilterJobTypeId == jobType.Id && Model.Options.FilterJobSubTypeIds.Contains(subType.Id)) ? "checked " : null); + + + #line default + #line hidden +WriteLiteral(" />(jobType.Id + + #line default + #line hidden +, 3959), false) +, Tuple.Create(Tuple.Create("", 3972), Tuple.Create("_", 3972), true) + + #line 68 "..\..\Views\Job\Export.cshtml" + , Tuple.Create(Tuple.Create("", 3973), Tuple.Create(subType.Id + + #line default + #line hidden +, 3973), false) +); + +WriteLiteral(">"); + + + #line 68 "..\..\Views\Job\Export.cshtml" + Write(subType.Description); + + + #line default + #line hidden +WriteLiteral("\r\n
  • \r\n" + +""); + + + #line 70 "..\..\Views\Job\Export.cshtml" + } + + + #line default + #line hidden +WriteLiteral(" \r\n " + +" \r\n " + +" \r\n \r\n"); + + + #line 75 "..\..\Views\Job\Export.cshtml" + + + #line default + #line hidden + + #line 75 "..\..\Views\Job\Export.cshtml" + foreach (var subType in subTypes.Skip(itemsPerColumn)) + { + + + #line default + #line hidden +WriteLiteral("
  • \r\n " + +" (jobType.Id + + #line default + #line hidden +, 4792), false) +, Tuple.Create(Tuple.Create("", 4805), Tuple.Create("_", 4805), true) + + #line 78 "..\..\Views\Job\Export.cshtml" + , Tuple.Create(Tuple.Create("", 4806), Tuple.Create(subType.Id + + #line default + #line hidden +, 4806), false) +); + +WriteLiteral(" name=\"Options.FilterJobSubTypeIds\""); + +WriteAttribute("value", Tuple.Create(" value=\"", 4855), Tuple.Create("\"", 4874) + + #line 78 "..\..\Views\Job\Export.cshtml" + , Tuple.Create(Tuple.Create("", 4863), Tuple.Create(subType.Id + + #line default + #line hidden +, 4863), false) +); + +WriteLiteral(" "); + + + #line 78 "..\..\Views\Job\Export.cshtml" + Write((Model.Options.FilterJobTypeId == jobType.Id && Model.Options.FilterJobSubTypeIds.Contains(subType.Id)) ? "checked " : null); + + + #line default + #line hidden +WriteLiteral(" />(jobType.Id + + #line default + #line hidden +, 5038), false) +, Tuple.Create(Tuple.Create("", 5051), Tuple.Create("_", 5051), true) + + #line 78 "..\..\Views\Job\Export.cshtml" + , Tuple.Create(Tuple.Create("", 5052), Tuple.Create(subType.Id + + #line default + #line hidden +, 5052), false) +); + +WriteLiteral(">"); + + + #line 78 "..\..\Views\Job\Export.cshtml" + Write(subType.Description); + + + #line default + #line hidden +WriteLiteral("\r\n
  • \r\n" + +""); + + + #line 80 "..\..\Views\Job\Export.cshtml" + } + + + #line default + #line hidden +WriteLiteral(@" + +
    +
    \r\n"); + +WriteLiteral(" "); + + + #line 24 "..\..\Views\Job\Export.cshtml" + Write(Html.EditorFor(m => m.Options.FilterStartDate)); + + + #line default + #line hidden +WriteLiteral("\r\n"); + +WriteLiteral(" "); + + + #line 25 "..\..\Views\Job\Export.cshtml" + Write(Html.ValidationMessageFor(m => m.Options.FilterStartDate)); + + + #line default + #line hidden +WriteLiteral("\r\n
    End Date:\r\n"); + +WriteLiteral(" "); + + + #line 31 "..\..\Views\Job\Export.cshtml" + Write(Html.EditorFor(m => m.Options.FilterEndDate)); + + + #line default + #line hidden +WriteLiteral("\r\n"); + +WriteLiteral(" "); + + + #line 32 "..\..\Views\Job\Export.cshtml" + Write(Html.ValidationMessageFor(m => m.Options.FilterEndDate)); + + + #line default + #line hidden +WriteLiteral("\r\n
    Status:\r\n"); + +WriteLiteral(" "); + + + #line 38 "..\..\Views\Job\Export.cshtml" + Write(Html.DropDownListFor(m => m.Options.FilterJobStatusId, m => m.JobStatuses, i => i.Key, i => i.Value, "-- All Jobs --")); + + + #line default + #line hidden +WriteLiteral("\r\n
    \r\n"); + +WriteLiteral(" "); + + + #line 47 "..\..\Views\Job\Export.cshtml" + Write(Html.DropDownListFor(m => m.Options.FilterJobTypeId, m => m.JobTypes, i => i.Id, i => i.Description, "-- All Jobs --")); + + + #line default + #line hidden +WriteLiteral("\r\n
    + +"); + + + #line 86 "..\..\Views\Job\Export.cshtml" + } + + + #line default + #line hidden +WriteLiteral(" \r\n \r\n " + +" \r\n \r\n \r\n " + +" \r\n Job Queue:\r\n " + +"\r\n"); + +WriteLiteral(" "); + + + #line 95 "..\..\Views\Job\Export.cshtml" + Write(Html.DropDownListFor(m => m.Options.FilterJobQueueId, m => m.JobQueues, i => i.Id.ToString(), i => i.Name, "-- All Jobs --")); + + + #line default + #line hidden +WriteLiteral("\r\n \r\n \r\n \r\n " + +" "); + + + #line 99 "..\..\Views\Job\Export.cshtml" + Write(Html.LabelFor(m => m.Options.Format)); + + + #line default + #line hidden +WriteLiteral("\r\n \r\n"); + +WriteLiteral(" "); + + + #line 101 "..\..\Views\Job\Export.cshtml" + Write(Html.DropDownListFor(m => m.Options.Format, m => Enum.GetNames(typeof(Disco.Models.Exporting.ExportFormat)), i => i, i => i)); + + + #line default + #line hidden +WriteLiteral("\r\n \r\n \r\n \r\n " + +" \r\n"); + +WriteLiteral(" \r\n

    Export Fields (Defaults)

    \r\n \r\n"); + + + #line 109 "..\..\Views\Job\Export.cshtml" + + + #line default + #line hidden + + #line 109 "..\..\Views\Job\Export.cshtml" + foreach (var optionGroup in optionGroups) + { + var optionFields = optionGroup.ToList(); + var itemsPerColumn = (int)Math.Ceiling((double)optionFields.Count / 2); + + + #line default + #line hidden +WriteLiteral(" \r\n \r\n"); + +WriteLiteral(" "); + + + #line 115 "..\..\Views\Job\Export.cshtml" + Write(optionGroup.Key); + + + #line default + #line hidden +WriteLiteral("\r\n"); + + + #line 116 "..\..\Views\Job\Export.cshtml" + + + #line default + #line hidden + + #line 116 "..\..\Views\Job\Export.cshtml" + if (optionFields.Count > 2) + { + + + #line default + #line hidden +WriteLiteral(" ALL | NONE\r\n"); + + + #line 119 "..\..\Views\Job\Export.cshtml" + } + + + #line default + #line hidden +WriteLiteral(" \r\n \r\n " + +" \r\n \r\n"); + + + #line 127 "..\..\Views\Job\Export.cshtml" + + + #line default + #line hidden + + #line 127 "..\..\Views\Job\Export.cshtml" + foreach (var optionItem in optionFields.Take(itemsPerColumn)) + { + + + #line default + #line hidden +WriteLiteral(" (optionItem.Description + + #line default + #line hidden +, 7783), false) +); + +WriteLiteral(">\r\n (optionItem.PropertyName + + #line default + #line hidden +, 7901), false) +); + +WriteAttribute("name", Tuple.Create(" name=\"", 7926), Tuple.Create("\"", 7965) +, Tuple.Create(Tuple.Create("", 7933), Tuple.Create("Options.", 7933), true) + + #line 130 "..\..\Views\Job\Export.cshtml" + , Tuple.Create(Tuple.Create("", 7941), Tuple.Create(optionItem.PropertyName + + #line default + #line hidden +, 7941), false) +); + +WriteLiteral(" value=\"true\""); + +WriteLiteral(" "); + + + #line 130 "..\..\Views\Job\Export.cshtml" + Write(((bool)optionItem.Model) ? "checked " : null); + + + #line default + #line hidden +WriteLiteral(" />(optionItem.PropertyName + + #line default + #line hidden +, 8050), false) +); + +WriteLiteral(">"); + + + #line 130 "..\..\Views\Job\Export.cshtml" + Write(optionItem.DisplayName); + + + #line default + #line hidden +WriteLiteral("\r\n \r\n"); + + + #line 132 "..\..\Views\Job\Export.cshtml" + } + + + #line default + #line hidden +WriteLiteral(" \r\n " + +" \r\n \r\n \r\n"); + + + #line 137 "..\..\Views\Job\Export.cshtml" + + + #line default + #line hidden + + #line 137 "..\..\Views\Job\Export.cshtml" + foreach (var optionItem in optionFields.Skip(itemsPerColumn)) + { + + + #line default + #line hidden +WriteLiteral(" (optionItem.Description + + #line default + #line hidden +, 8671), false) +); + +WriteLiteral(">\r\n (optionItem.PropertyName + + #line default + #line hidden +, 8789), false) +); + +WriteAttribute("name", Tuple.Create(" name=\"", 8814), Tuple.Create("\"", 8853) +, Tuple.Create(Tuple.Create("", 8821), Tuple.Create("Options.", 8821), true) + + #line 140 "..\..\Views\Job\Export.cshtml" + , Tuple.Create(Tuple.Create("", 8829), Tuple.Create(optionItem.PropertyName + + #line default + #line hidden +, 8829), false) +); + +WriteLiteral(" value=\"true\""); + +WriteLiteral(" "); + + + #line 140 "..\..\Views\Job\Export.cshtml" + Write(((bool)optionItem.Model) ? "checked " : null); + + + #line default + #line hidden +WriteLiteral(" />(optionItem.PropertyName + + #line default + #line hidden +, 8938), false) +); + +WriteLiteral(">"); + + + #line 140 "..\..\Views\Job\Export.cshtml" + Write(optionItem.DisplayName); + + + #line default + #line hidden +WriteLiteral("\r\n \r\n"); + + + #line 142 "..\..\Views\Job\Export.cshtml" + } + + + #line default + #line hidden +WriteLiteral(@" + + +
    \r\n " + +" \r\n \r\n
    + + + +"); + + + #line 150 "..\..\Views\Job\Export.cshtml" + + } + + + #line default + #line hidden +WriteLiteral(" \r\n \r\n"); + +WriteLiteral(" \r\n"); + + + #line 254 "..\..\Views\Job\Export.cshtml" + } + + + #line default + #line hidden +WriteLiteral("\r\n"); + + + #line 256 "..\..\Views\Job\Export.cshtml" + if (Model.ExportSessionId != null) +{ + + + #line default + #line hidden +WriteLiteral(" \r\n"); + + + #line 259 "..\..\Views\Job\Export.cshtml" + + + #line default + #line hidden + + #line 259 "..\..\Views\Job\Export.cshtml" + if (Model.ExportSessionResult.RecordCount == 0) + { + + + #line default + #line hidden +WriteLiteral("

    No records matched the filter criteria

    \r\n"); + + + #line 262 "..\..\Views\Job\Export.cshtml" + } + else + { + + + #line default + #line hidden +WriteLiteral("

    "); + + + #line 265 "..\..\Views\Job\Export.cshtml" + Write(Model.ExportSessionResult.RecordCount); + + + #line default + #line hidden +WriteLiteral(" record"); + + + #line 265 "..\..\Views\Job\Export.cshtml" + Write(Model.ExportSessionResult.RecordCount != 1 ? "s" : null); + + + #line default + #line hidden +WriteLiteral(" were successfully exported.

    \r\n"); + +WriteLiteral(" (Url.Action(MVC.API.Job.ExportRetrieve(Model.ExportSessionId)) + + #line default + #line hidden +, 14451), false) +); + +WriteLiteral(" class=\"button\""); + +WriteLiteral(">Download Job Export\r\n"); + + + #line 267 "..\..\Views\Job\Export.cshtml" + } + + + #line default + #line hidden +WriteLiteral(" \r\n"); + +WriteLiteral(@" +"); + + + #line 281 "..\..\Views\Job\Export.cshtml" +} + + + #line default + #line hidden +WriteLiteral("\r\n

    Exporting jobs...

    \r\n\r\n\r\n Export Jobs\r\n\r\n"); + + } + } +} +#pragma warning restore 1591 diff --git a/Disco.Web/Views/Job/Index.cshtml b/Disco.Web/Views/Job/Index.cshtml index f3e64fb9..4c9b0668 100644 --- a/Disco.Web/Views/Job/Index.cshtml +++ b/Disco.Web/Views/Job/Index.cshtml @@ -145,6 +145,12 @@ @Html.Partial(MVC.Shared.Views._JobTable, Model.StaleJobs, new ViewDataDictionary()) } +@if (Authorization.Has(Claims.Job.Actions.Export)) +{ +
    + @Html.ActionLinkButton("Export Jobs", MVC.Job.Export()) +
    +} @if (Model.PendingEnrollments != null && Model.PendingEnrollments.Count > 0 && Authorization.Has(Claims.Device.Actions.EnrolDevices)) {
    diff --git a/Disco.Web/Views/Job/Index.generated.cs b/Disco.Web/Views/Job/Index.generated.cs index d1539d15..8a366ede 100644 --- a/Disco.Web/Views/Job/Index.generated.cs +++ b/Disco.Web/Views/Job/Index.generated.cs @@ -306,6 +306,38 @@ WriteLiteral("\r\n
    \r\n"); #line hidden #line 148 "..\..\Views\Job\Index.cshtml" + if (Authorization.Has(Claims.Job.Actions.Export)) +{ + + + #line default + #line hidden +WriteLiteral(" \r\n"); + +WriteLiteral(" "); + + + #line 151 "..\..\Views\Job\Index.cshtml" + Write(Html.ActionLinkButton("Export Jobs", MVC.Job.Export())); + + + #line default + #line hidden +WriteLiteral("\r\n \r\n"); + + + #line 153 "..\..\Views\Job\Index.cshtml" +} + + + #line default + #line hidden + + #line 154 "..\..\Views\Job\Index.cshtml" if (Model.PendingEnrollments != null && Model.PendingEnrollments.Count > 0 && Authorization.Has(Claims.Device.Actions.EnrolDevices)) { @@ -323,14 +355,14 @@ WriteLiteral(" class=\"fa fa-exclamation-circle info\""); WriteLiteral(">\r\n
    There are device enrollments pending approval.
    \r\n " + " (Url.Action(MVC.Config.Enrolment.Status()) + #line 159 "..\..\Views\Job\Index.cshtml" +, Tuple.Create(Tuple.Create("", 6937), Tuple.Create(Url.Action(MVC.Config.Enrolment.Status()) #line default #line hidden -, 6773), false) +, 6937), false) ); WriteLiteral(" class=\"button small alert\""); @@ -345,7 +377,7 @@ WriteLiteral(" \r\n"); - #line 163 "..\..\Views\Job\Index.cshtml" + #line 169 "..\..\Views\Job\Index.cshtml" } #line default diff --git a/Disco.Web/Views/Job/JobParts/_Subject.cshtml b/Disco.Web/Views/Job/JobParts/_Subject.cshtml index 038e6205..4042e6f6 100644 --- a/Disco.Web/Views/Job/JobParts/_Subject.cshtml +++ b/Disco.Web/Views/Job/JobParts/_Subject.cshtml @@ -1,5 +1,5 @@ @model Disco.Web.Models.Job.ShowModel -@using Disco.Models.Services.Job; +@using Disco.Models.Services.Jobs; @using Disco.Services.Users.UserFlags; @using Disco.Services.Devices.DeviceFlags; @{ diff --git a/Disco.Web/Views/Job/JobParts/_Subject.generated.cs b/Disco.Web/Views/Job/JobParts/_Subject.generated.cs index d8e932c0..0bb92e79 100644 --- a/Disco.Web/Views/Job/JobParts/_Subject.generated.cs +++ b/Disco.Web/Views/Job/JobParts/_Subject.generated.cs @@ -30,7 +30,7 @@ namespace Disco.Web.Views.Job.JobParts using Disco.Models.Repository; #line 2 "..\..\Views\Job\JobParts\_Subject.cshtml" - using Disco.Models.Services.Job; + using Disco.Models.Services.Jobs; #line default #line hidden @@ -317,14 +317,14 @@ WriteLiteral(" class=\"status\""); WriteLiteral(">\r\n (Model.Job.JobType.Id +, Tuple.Create(Tuple.Create("", 5831), Tuple.Create(Model.Job.JobType.Id #line default #line hidden -, 5830), false) +, 5831), false) ); WriteLiteral(">"); @@ -378,14 +378,14 @@ WriteLiteral(">\r\n"); #line hidden WriteLiteral(" (jobSubType.Id +, Tuple.Create(Tuple.Create("", 6448), Tuple.Create(jobSubType.Id #line default #line hidden -, 6447), false) +, 6448), false) ); WriteLiteral(">"); @@ -429,14 +429,14 @@ WriteLiteral(">\r\n"); #line hidden WriteLiteral(" (jobSubType.Id +, Tuple.Create(Tuple.Create("", 6908), Tuple.Create(jobSubType.Id #line default #line hidden -, 6907), false) +, 6908), false) ); WriteLiteral(">"); @@ -739,14 +739,14 @@ WriteLiteral(" id=\"Job_Show_Device_Model_Image\""); WriteLiteral(" alt=\"Model Image\""); -WriteAttribute("src", Tuple.Create(" src=\"", 11534), Tuple.Create("\"", 11652) +WriteAttribute("src", Tuple.Create(" src=\"", 11535), Tuple.Create("\"", 11653) #line 187 "..\..\Views\Job\JobParts\_Subject.cshtml" - , Tuple.Create(Tuple.Create("", 11540), Tuple.Create(Url.Action(MVC.API.DeviceModel.Image(Model.Job.Device.DeviceModelId, Model.Job.Device.DeviceModel.ImageHash())) + , Tuple.Create(Tuple.Create("", 11541), Tuple.Create(Url.Action(MVC.API.DeviceModel.Image(Model.Job.Device.DeviceModelId, Model.Job.Device.DeviceModel.ImageHash())) #line default #line hidden -, 11540), false) +, 11541), false) ); WriteLiteral(" />\r\n (Model.Job.Device.DeviceBatch.Name + , Tuple.Create(Tuple.Create(" ", 13385), Tuple.Create(Model.Job.Device.DeviceBatch.Name #line default #line hidden -, 13385), false) +, 13386), false) ); WriteLiteral(">\r\n
    "); @@ -1025,17 +1025,17 @@ WriteLiteral(" id=\"Job_Show_Device_Details_HNWar_Details_Dialog\""); WriteLiteral(" class=\"dialog\""); -WriteAttribute("title", Tuple.Create(" title=\"", 15908), Tuple.Create("\"", 15974) -, Tuple.Create(Tuple.Create("", 15916), Tuple.Create("Insurance", 15916), true) -, Tuple.Create(Tuple.Create(" ", 15925), Tuple.Create("Details", 15926), true) -, Tuple.Create(Tuple.Create(" ", 15933), Tuple.Create("for", 15934), true) +WriteAttribute("title", Tuple.Create(" title=\"", 15909), Tuple.Create("\"", 15975) +, Tuple.Create(Tuple.Create("", 15917), Tuple.Create("Insurance", 15917), true) +, Tuple.Create(Tuple.Create(" ", 15926), Tuple.Create("Details", 15927), true) +, Tuple.Create(Tuple.Create(" ", 15934), Tuple.Create("for", 15935), true) #line 234 "..\..\Views\Job\JobParts\_Subject.cshtml" - , Tuple.Create(Tuple.Create(" ", 15937), Tuple.Create(Model.Job.Device.DeviceBatch.Name + , Tuple.Create(Tuple.Create(" ", 15938), Tuple.Create(Model.Job.Device.DeviceBatch.Name #line default #line hidden -, 15938), false) +, 15939), false) ); WriteLiteral(">\r\n
    "); @@ -1121,26 +1121,26 @@ WriteLiteral(">\r\n"); #line hidden WriteLiteral(" (flag.Item2.Icon +, Tuple.Create(Tuple.Create("", 18111), Tuple.Create(flag.Item2.Icon #line default #line hidden -, 18110), false) -, Tuple.Create(Tuple.Create(" ", 18128), Tuple.Create("fa-fw", 18129), true) -, Tuple.Create(Tuple.Create(" ", 18134), Tuple.Create("d-", 18135), true) +, 18111), false) +, Tuple.Create(Tuple.Create(" ", 18129), Tuple.Create("fa-fw", 18130), true) +, Tuple.Create(Tuple.Create(" ", 18135), Tuple.Create("d-", 18136), true) #line 263 "..\..\Views\Job\JobParts\_Subject.cshtml" - , Tuple.Create(Tuple.Create("", 18137), Tuple.Create(flag.Item2.IconColour + , Tuple.Create(Tuple.Create("", 18138), Tuple.Create(flag.Item2.IconColour #line default #line hidden -, 18137), false) +, 18138), false) ); WriteLiteral(">\r\n \r\n (Url.Action(MVC.API.User.Photo(Model.Job.UserId)) +, Tuple.Create(Tuple.Create("", 33696), Tuple.Create(Url.Action(MVC.API.User.Photo(Model.Job.UserId)) #line default #line hidden -, 33695), false) +, 33696), false) ); WriteLiteral(" />\r\n
    \r\n"); @@ -1844,15 +1844,15 @@ WriteLiteral(" title=\"Phone Number\""); WriteLiteral(">Phone: (Model.Job.User.PhoneNumber + , Tuple.Create(Tuple.Create("", 34534), Tuple.Create(Model.Job.User.PhoneNumber #line default #line hidden -, 34533), false) +, 34534), false) ); WriteLiteral(">"); @@ -1882,15 +1882,15 @@ WriteLiteral(" title=\"Email Address\""); WriteLiteral(">Email: (Model.Job.User.EmailAddress + , Tuple.Create(Tuple.Create("", 34788), Tuple.Create(Model.Job.User.EmailAddress #line default #line hidden -, 34787), false) +, 34788), false) ); WriteLiteral(">"); @@ -1944,26 +1944,26 @@ WriteLiteral(">\r\n"); #line hidden WriteLiteral(" (flag.Item2.Icon +, Tuple.Create(Tuple.Create("", 35312), Tuple.Create(flag.Item2.Icon #line default #line hidden -, 35311), false) -, Tuple.Create(Tuple.Create(" ", 35329), Tuple.Create("fa-fw", 35330), true) -, Tuple.Create(Tuple.Create(" ", 35335), Tuple.Create("d-", 35336), true) +, 35312), false) +, Tuple.Create(Tuple.Create(" ", 35330), Tuple.Create("fa-fw", 35331), true) +, Tuple.Create(Tuple.Create(" ", 35336), Tuple.Create("d-", 35337), true) #line 503 "..\..\Views\Job\JobParts\_Subject.cshtml" -, Tuple.Create(Tuple.Create("", 35338), Tuple.Create(flag.Item2.IconColour +, Tuple.Create(Tuple.Create("", 35339), Tuple.Create(flag.Item2.IconColour #line default #line hidden -, 35338), false) +, 35339), false) ); WriteLiteral(">\r\n (Model.Job.Id + , Tuple.Create(Tuple.Create("", 50890), Tuple.Create(Model.Job.Id #line default #line hidden -, 50889), false) +, 50890), false) ); WriteLiteral(" />\r\n"); @@ -2793,26 +2793,26 @@ WriteLiteral("\""); WriteLiteral(">\r\n (jobQueue.Icon +, Tuple.Create(Tuple.Create("", 51386), Tuple.Create(jobQueue.Icon #line default #line hidden -, 51385), false) -, Tuple.Create(Tuple.Create(" ", 51401), Tuple.Create("fa-fw", 51402), true) -, Tuple.Create(Tuple.Create(" ", 51407), Tuple.Create("fa-lg", 51408), true) -, Tuple.Create(Tuple.Create(" ", 51413), Tuple.Create("d-", 51414), true) +, 51386), false) +, Tuple.Create(Tuple.Create(" ", 51402), Tuple.Create("fa-fw", 51403), true) +, Tuple.Create(Tuple.Create(" ", 51408), Tuple.Create("fa-lg", 51409), true) +, Tuple.Create(Tuple.Create(" ", 51414), Tuple.Create("d-", 51415), true) #line 785 "..\..\Views\Job\JobParts\_Subject.cshtml" - , Tuple.Create(Tuple.Create("", 51416), Tuple.Create(jobQueue.IconColour + , Tuple.Create(Tuple.Create("", 51417), Tuple.Create(jobQueue.IconColour #line default #line hidden -, 51416), false) +, 51417), false) ); WriteLiteral(">"); @@ -2853,27 +2853,27 @@ WriteLiteral(" "); #line hidden WriteLiteral(" (priorityValue.ToLower() + , Tuple.Create(Tuple.Create("", 51858), Tuple.Create(priorityValue.ToLower() #line default #line hidden -, 51857), false) +, 51858), false) ); -WriteAttribute("title", Tuple.Create(" title=\"", 51884), Tuple.Create("\"", 51917) +WriteAttribute("title", Tuple.Create(" title=\"", 51885), Tuple.Create("\"", 51918) #line 792 "..\..\Views\Job\JobParts\_Subject.cshtml" - , Tuple.Create(Tuple.Create("", 51892), Tuple.Create(priorityValue + , Tuple.Create(Tuple.Create("", 51893), Tuple.Create(priorityValue #line default #line hidden -, 51892), false) -, Tuple.Create(Tuple.Create(" ", 51908), Tuple.Create("Priority", 51909), true) +, 51893), false) +, Tuple.Create(Tuple.Create(" ", 51909), Tuple.Create("Priority", 51910), true) ); WriteLiteral(">\r\n
    \r\n
    \r\n " +