@@ -51,6 +51,7 @@ namespace Disco.Services.Authorization
|
||||
{ "Config.DocumentTemplate.Configure", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.DocumentTemplate.Configure, (c, v) => c.Config.DocumentTemplate.Configure = v, "Configure Document Templates", "Can configure document templates", false) },
|
||||
{ "Config.DocumentTemplate.Create", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.DocumentTemplate.Create, (c, v) => c.Config.DocumentTemplate.Create = v, "Create Document Templates", "Can create document templates", false) },
|
||||
{ "Config.DocumentTemplate.Delete", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.DocumentTemplate.Delete, (c, v) => c.Config.DocumentTemplate.Delete = v, "Delete Document Templates", "Can delete document templates", false) },
|
||||
{ "Config.DocumentTemplate.Export", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.DocumentTemplate.Export, (c, v) => c.Config.DocumentTemplate.Export = v, "Export Attachment Instances", "Can export document attachment instances", false) },
|
||||
{ "Config.DocumentTemplate.UndetectedPages", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.DocumentTemplate.UndetectedPages, (c, v) => c.Config.DocumentTemplate.UndetectedPages = v, "Process Undetected Pages", "Can show and assign imported documents which were not undetected", false) },
|
||||
{ "Config.DocumentTemplate.ShowStatus", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.DocumentTemplate.ShowStatus, (c, v) => c.Config.DocumentTemplate.ShowStatus = v, "Show Document Template Import Status", "Can show the document template import status", false) },
|
||||
{ "Config.DocumentTemplate.Show", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.DocumentTemplate.Show, (c, v) => c.Config.DocumentTemplate.Show = v, "Show Document Templates", "Can show document templates", false) },
|
||||
@@ -275,6 +276,7 @@ namespace Disco.Services.Authorization
|
||||
new ClaimNavigatorItem("Config.DocumentTemplate.Configure", false),
|
||||
new ClaimNavigatorItem("Config.DocumentTemplate.Create", false),
|
||||
new ClaimNavigatorItem("Config.DocumentTemplate.Delete", false),
|
||||
new ClaimNavigatorItem("Config.DocumentTemplate.Export", false),
|
||||
new ClaimNavigatorItem("Config.DocumentTemplate.UndetectedPages", false),
|
||||
new ClaimNavigatorItem("Config.DocumentTemplate.ShowStatus", false),
|
||||
new ClaimNavigatorItem("Config.DocumentTemplate.Show", false),
|
||||
@@ -595,6 +597,7 @@ namespace Disco.Services.Authorization
|
||||
c.Config.DocumentTemplate.Configure = true;
|
||||
c.Config.DocumentTemplate.Create = true;
|
||||
c.Config.DocumentTemplate.Delete = true;
|
||||
c.Config.DocumentTemplate.Export = true;
|
||||
c.Config.DocumentTemplate.UndetectedPages = true;
|
||||
c.Config.DocumentTemplate.ShowStatus = true;
|
||||
c.Config.DocumentTemplate.Show = true;
|
||||
@@ -990,6 +993,11 @@ namespace Disco.Services.Authorization
|
||||
/// </summary>
|
||||
public const string Delete = "Config.DocumentTemplate.Delete";
|
||||
|
||||
/// <summary>Export Attachment Instances
|
||||
/// <para>Can export document attachment instances</para>
|
||||
/// </summary>
|
||||
public const string Export = "Config.DocumentTemplate.Export";
|
||||
|
||||
/// <summary>Process Undetected Pages
|
||||
/// <para>Can show and assign imported documents which were not undetected</para>
|
||||
/// </summary>
|
||||
|
||||
+3
@@ -26,6 +26,9 @@
|
||||
|
||||
[ClaimDetails("Bulk Generate Document Templates", "Can bulk generate document templates")]
|
||||
public bool BulkGenerate { get; set; }
|
||||
|
||||
[ClaimDetails("Export Attachment Instances", "Can export document attachment instances")]
|
||||
public bool Export { get; set; }
|
||||
|
||||
[ClaimDetails("Process Undetected Pages", "Can show and assign imported documents which were not undetected")]
|
||||
public bool UndetectedPages { get; set; }
|
||||
|
||||
@@ -366,6 +366,7 @@
|
||||
<Compile Include="Devices\DeviceFlags\DeviceFlagService.cs" />
|
||||
<Compile Include="Devices\DeviceFlags\DeviceFlagDeviceAssignedUsersManagedGroup.cs" />
|
||||
<Compile Include="Devices\DeviceFlags\DeviceFlagDevicesManagedGroup.cs" />
|
||||
<Compile Include="Documents\DocumentExport.cs" />
|
||||
<Compile Include="Exporting\Exporter.cs" />
|
||||
<Compile Include="Exporting\ExportTask.cs" />
|
||||
<Compile Include="Exporting\ExportTaskContext.cs" />
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Exporting;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Documents;
|
||||
using Disco.Models.Services.Exporting;
|
||||
using Disco.Services.Exporting;
|
||||
using Disco.Services.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using System.Data.Entity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Disco.Services.Users;
|
||||
using Disco.Services.Plugins.Features.DetailsProvider;
|
||||
|
||||
namespace Disco.Services.Documents
|
||||
{
|
||||
public class DocumentExport : IExport<DocumentExportOptions, DocumentExportRecord>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; } = "Document Export";
|
||||
public DocumentExportOptions Options { get; set; }
|
||||
|
||||
public string FilenamePrefix { get; } = "DocumentExport";
|
||||
public string ExcelWorksheetName { get; } = "DocumentExport";
|
||||
public string ExcelTableName { get; } = "Documents";
|
||||
|
||||
public DocumentExport(DocumentExportOptions options)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
Options = options;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public DocumentExport()
|
||||
: this(DocumentExportOptions.DefaultOptions())
|
||||
{
|
||||
}
|
||||
|
||||
public ExportMetadata<DocumentExportOptions, DocumentExportRecord> BuildMetadata(DiscoDataContext database, List<DocumentExportRecord> records, IScheduledTaskStatus status)
|
||||
{
|
||||
var metadata = new ExportMetadata<DocumentExportOptions, DocumentExportRecord>(Options);
|
||||
|
||||
// Document Template
|
||||
metadata.Add(o => o.Id, r => r.DocumentTemplate.Id);
|
||||
metadata.Add(o => o.Description, r => r.DocumentTemplate?.Description);
|
||||
metadata.Add(o => o.Scope, r => r.DocumentTemplate?.Scope);
|
||||
|
||||
// Attachment
|
||||
metadata.Add(o => o.AttachmentId, r => r.Attachment.Id);
|
||||
metadata.Add(o => o.AttachmentCreatedDate, r => r.Attachment.Timestamp);
|
||||
metadata.Add(o => o.AttachmentCreatedUser, r => r.Attachment.TechUserId);
|
||||
metadata.Add(o => o.AttachmentFilename, r => r.Attachment.Filename);
|
||||
metadata.Add(o => o.AttachmentMimeType, r => r.Attachment.MimeType);
|
||||
metadata.Add(o => o.AttachmentComments, r => r.Attachment.Comments);
|
||||
|
||||
// Device
|
||||
metadata.Add(o => o.DeviceSerialNumber, r => r.Device?.SerialNumber);
|
||||
metadata.Add(o => o.DeviceAssetNumber, r => r.Device?.AssetNumber);
|
||||
metadata.Add(o => o.DeviceLocation, r => r.Device?.Location);
|
||||
metadata.Add(o => o.DeviceComputerName, r => r.Device?.DeviceDomainId);
|
||||
metadata.Add(o => o.DeviceLastNetworkLogon, r => r.Device?.LastNetworkLogonDate);
|
||||
metadata.Add(o => o.DeviceCreatedDate, r => r.Device?.CreatedDate);
|
||||
metadata.Add(o => o.DeviceFirstEnrolledDate, r => r.Device?.EnrolledDate);
|
||||
metadata.Add(o => o.DeviceLastEnrolledDate, r => r.Device?.LastEnrolDate);
|
||||
metadata.Add(o => o.DeviceAllowUnauthenticatedEnrol, r => r.Device?.AllowUnauthenticatedEnrol);
|
||||
metadata.Add(o => o.DeviceDecommissionedDate, r => r.Device?.DecommissionedDate);
|
||||
metadata.Add(o => o.DeviceDecommissionedReason, r => r.Device?.DecommissionReason?.ToString());
|
||||
|
||||
// Model
|
||||
metadata.Add(o => o.ModelId, r => r.Device?.DeviceModel?.Id);
|
||||
metadata.Add(o => o.ModelDescription, r => r.Device?.DeviceModel?.Description);
|
||||
metadata.Add(o => o.ModelManufacturer, r => r.Device?.DeviceModel?.Manufacturer);
|
||||
metadata.Add(o => o.ModelModel, r => r.Device?.DeviceModel?.Model);
|
||||
metadata.Add(o => o.ModelType, r => r.Device?.DeviceModel?.ModelType);
|
||||
|
||||
// Batch
|
||||
metadata.Add(o => o.BatchId, r => r.Device?.DeviceBatch?.Id);
|
||||
metadata.Add(o => o.BatchName, r => r.Device?.DeviceBatch?.Name);
|
||||
metadata.Add(o => o.BatchPurchaseDate, r => r.Device?.DeviceBatch?.PurchaseDate);
|
||||
metadata.Add(o => o.BatchSupplier, r => r.Device?.DeviceBatch?.Supplier);
|
||||
metadata.Add(o => o.BatchUnitCost, r => r.Device?.DeviceBatch?.UnitCost, Exporter.CsvEncoders.NullableCurrencyEncoder);
|
||||
metadata.Add(o => o.BatchWarrantyValidUntilDate, r => r.Device?.DeviceBatch?.WarrantyValidUntil);
|
||||
metadata.Add(o => o.BatchInsuredDate, r => r.Device?.DeviceBatch?.InsuredDate);
|
||||
metadata.Add(o => o.BatchInsuranceSupplier, r => r.Device?.DeviceBatch?.InsuranceSupplier);
|
||||
metadata.Add(o => o.BatchInsuredUntilDate, r => r.Device?.DeviceBatch?.InsuredUntil);
|
||||
|
||||
// Profile
|
||||
metadata.Add(o => o.ProfileId, r => r.Device?.DeviceProfile?.Id);
|
||||
metadata.Add(o => o.ProfileName, r => r.Device?.DeviceProfile?.Name);
|
||||
metadata.Add(o => o.ProfileShortName, r => r.Device?.DeviceProfile?.ShortName);
|
||||
|
||||
// Job
|
||||
metadata.Add(o => o.JobId, r => r.Job?.Id);
|
||||
metadata.Add(o => o.JobStatus, r => r.JobStatus == null ? null : Job.JobStatusIds.StatusDescriptions.TryGetValue(r.JobStatus, out var jobStatus) ? jobStatus : "Unknown");
|
||||
metadata.Add(o => o.JobType, r => r.JobTypeDescription);
|
||||
metadata.Add(o => o.JobSubTypes, r => r.JobSubTypeDescriptions == null ? null : string.Join(", ", r.JobSubTypeDescriptions));
|
||||
metadata.Add(o => o.JobOpenedDate, r => r.Job?.OpenedDate);
|
||||
metadata.Add(o => o.JobOpenedUser, r => r.Job?.OpenedTechUserId);
|
||||
metadata.Add(o => o.JobExpectedClosedDate, r => r.Job?.ExpectedClosedDate);
|
||||
metadata.Add(o => o.JobClosedDate, r => r.Job?.ClosedDate);
|
||||
metadata.Add(o => o.JobClosedUser, r => r.Job?.ClosedTechUserId);
|
||||
|
||||
// User
|
||||
metadata.Add(o => o.UserId, r => r.User?.UserId);
|
||||
metadata.Add(o => o.UserDisplayName, r => r.User?.DisplayName);
|
||||
metadata.Add(o => o.UserSurname, r => r.User?.Surname);
|
||||
metadata.Add(o => o.UserGivenName, r => r.User?.GivenName);
|
||||
metadata.Add(o => o.UserPhoneNumber, r => r.User?.PhoneNumber);
|
||||
metadata.Add(o => o.UserEmailAddress, r => r.User?.EmailAddress);
|
||||
|
||||
// User Custom Details
|
||||
if (Options.UserDetailCustom.Any())
|
||||
{
|
||||
foreach (var key in Options.UserDetailCustom.OrderBy(k => k, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
metadata.Add(key, r => r.UserCustomDetails != null && r.UserCustomDetails.TryGetValue(key, out var value) ? value : null);
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public List<DocumentExportRecord> BuildRecords(DiscoDataContext database, IScheduledTaskStatus status)
|
||||
{
|
||||
var records = new List<DocumentExportRecord>();
|
||||
var documentTemplates = database.DocumentTemplates.Where(t => Options.DocumentTemplateIds.Contains(t.Id)).ToList();
|
||||
|
||||
foreach (var documentTemplate in documentTemplates)
|
||||
{
|
||||
var documentRecords = BuildDocumentRecords(database, documentTemplate, status);
|
||||
records.AddRange(documentRecords);
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
private List<DocumentExportRecord> BuildDocumentRecords(DiscoDataContext database, DocumentTemplate document, IScheduledTaskStatus status)
|
||||
{
|
||||
switch (document.AttachmentType)
|
||||
{
|
||||
case AttachmentTypes.Device:
|
||||
return BuildDeviceDocumentRecords(database, document, status);
|
||||
case AttachmentTypes.Job:
|
||||
return BuildJobDocumentRecords(database, document, status);
|
||||
case AttachmentTypes.User:
|
||||
return BuildUserDocumentRecords(database, document, status);
|
||||
default:
|
||||
throw new NotSupportedException($"Unsupported document scope: {document.Scope}");
|
||||
}
|
||||
}
|
||||
|
||||
private List<DocumentExportRecord> BuildDeviceDocumentRecords(DiscoDataContext database, DocumentTemplate document, IScheduledTaskStatus status)
|
||||
{
|
||||
var query = database.DeviceAttachments
|
||||
.Include(a => a.Device);
|
||||
if (Options.HasDeviceBatchOptions())
|
||||
query = query.Include(a => a.Device.DeviceBatch);
|
||||
if (Options.HasDeviceModelOptions())
|
||||
query = query.Include(a => a.Device.DeviceModel);
|
||||
if (Options.HasDeviceProfileOptions())
|
||||
query = query.Include(a => a.Device.DeviceProfile);
|
||||
if (Options.HasUserOptions())
|
||||
query = query.Include(a => a.Device.AssignedUser);
|
||||
|
||||
query = query.Where(a => a.DocumentTemplateId == document.Id);
|
||||
|
||||
if (Options.HasUserOptions())
|
||||
RefreshAdUsers(database, query.Select(d => d.Device.AssignedUserId).Distinct().ToList(), status);
|
||||
|
||||
status.UpdateStatus(15, "Extracting records from the database");
|
||||
var attachments = query.OrderBy(a => a.Timestamp).ToList();
|
||||
|
||||
if (Options.LatestOnly)
|
||||
{
|
||||
attachments.Reverse();
|
||||
attachments = attachments.GroupBy(a => a.DeviceSerialNumber).Select(g => g.First()).OrderBy(a => a.Timestamp).ToList();
|
||||
}
|
||||
|
||||
var records = attachments.Select(a => new DocumentExportRecord()
|
||||
{
|
||||
DocumentTemplate = document,
|
||||
Attachment = a,
|
||||
Device = a.Device,
|
||||
Job = null,
|
||||
User = a.Device.AssignedUser,
|
||||
}).ToList();
|
||||
|
||||
if (Options.UserDetailCustom.Any())
|
||||
AddUserCustomDetails(database, records, status);
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
private List<DocumentExportRecord> BuildJobDocumentRecords(DiscoDataContext database, DocumentTemplate document, IScheduledTaskStatus status)
|
||||
{
|
||||
var query = database.JobAttachments
|
||||
.Include(a => a.Job);
|
||||
if (Options.JobStatus)
|
||||
{
|
||||
query = query
|
||||
.Include(a => a.Job.JobMetaWarranty)
|
||||
.Include(a => a.Job.JobMetaNonWarranty)
|
||||
.Include(a => a.Job.JobMetaInsurance);
|
||||
}
|
||||
if (Options.JobType)
|
||||
query = query.Include(a => a.Job.JobType);
|
||||
if (Options.JobSubTypes)
|
||||
query = query.Include(a => a.Job.JobSubTypes);
|
||||
if (Options.HasDeviceOptions())
|
||||
query = query.Include(a => a.Job.Device);
|
||||
if (Options.HasDeviceBatchOptions())
|
||||
query = query.Include(a => a.Job.Device.DeviceBatch);
|
||||
if (Options.HasDeviceModelOptions())
|
||||
query = query.Include(a => a.Job.Device.DeviceModel);
|
||||
if (Options.HasDeviceProfileOptions())
|
||||
query = query.Include(a => a.Job.Device.DeviceProfile);
|
||||
if (Options.HasUserOptions())
|
||||
query = query.Include(a => a.Job.User);
|
||||
|
||||
query = query.Where(a => a.DocumentTemplateId == document.Id);
|
||||
|
||||
if (Options.HasUserOptions())
|
||||
RefreshAdUsers(database, query.Select(d => d.Job.UserId).Distinct().ToList(), status);
|
||||
|
||||
status.UpdateStatus(15, "Extracting records from the database");
|
||||
var attachments = query.OrderBy(a => a.Timestamp).ToList();
|
||||
|
||||
if (Options.LatestOnly)
|
||||
{
|
||||
attachments.Reverse();
|
||||
attachments = attachments.GroupBy(a => a.JobId).Select(g => g.First()).OrderBy(a => a.Timestamp).ToList();
|
||||
}
|
||||
|
||||
var records = attachments.Select(a => new DocumentExportRecord()
|
||||
{
|
||||
DocumentTemplate = document,
|
||||
Attachment = a,
|
||||
Device = a.Job.Device,
|
||||
Job = a.Job,
|
||||
User = a.Job.User,
|
||||
}).ToList();
|
||||
|
||||
if (Options.UserDetailCustom.Any())
|
||||
AddUserCustomDetails(database, records, status);
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
private List<DocumentExportRecord> BuildUserDocumentRecords(DiscoDataContext database, DocumentTemplate document, IScheduledTaskStatus status)
|
||||
{
|
||||
var query = database.UserAttachments
|
||||
.Include(a => a.User);
|
||||
|
||||
if (Options.HasDeviceOptions())
|
||||
query = query.Include(a => a.User.DeviceUserAssignments.Select(u => u.Device));
|
||||
if (Options.HasDeviceBatchOptions())
|
||||
query = query.Include(a => a.User.DeviceUserAssignments.Select(u => u.Device.DeviceBatch));
|
||||
if (Options.HasDeviceModelOptions())
|
||||
query = query.Include(a => a.User.DeviceUserAssignments.Select(u => u.Device.DeviceModel));
|
||||
if (Options.HasDeviceProfileOptions())
|
||||
query = query.Include(a => a.User.DeviceUserAssignments.Select(u => u.Device.DeviceProfile));
|
||||
|
||||
query = query.Where(a => a.DocumentTemplateId == document.Id);
|
||||
|
||||
if (Options.HasUserOptions())
|
||||
RefreshAdUsers(database, query.Select(d => d.UserId).Distinct().ToList(), status);
|
||||
|
||||
status.UpdateStatus(15, "Extracting records from the database");
|
||||
var attachments = query.OrderBy(a => a.Timestamp).ToList();
|
||||
|
||||
if (Options.LatestOnly)
|
||||
{
|
||||
attachments.Reverse();
|
||||
attachments = attachments.GroupBy(a => a.UserId).Select(g => g.First()).OrderBy(a => a.Timestamp).ToList();
|
||||
}
|
||||
|
||||
var records = attachments.Select(a => new DocumentExportRecord()
|
||||
{
|
||||
DocumentTemplate = document,
|
||||
Attachment = a,
|
||||
Device = a.User.DeviceUserAssignments?
|
||||
.Where(u => !u.UnassignedDate.HasValue)
|
||||
.OrderByDescending(u => u.AssignedDate)
|
||||
.FirstOrDefault()?.Device,
|
||||
Job = null,
|
||||
User = a.User,
|
||||
}).ToList();
|
||||
|
||||
if (Options.UserDetailCustom.Any())
|
||||
AddUserCustomDetails(database, records, status);
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
private static void RefreshAdUsers(DiscoDataContext database, List<string> userIds, IScheduledTaskStatus status)
|
||||
{
|
||||
if (!userIds.Any())
|
||||
return;
|
||||
|
||||
status.UpdateStatus(5, "Refreshing user details from Active Directory");
|
||||
foreach (var userId in userIds)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
UserService.GetUser(userId, database, true);
|
||||
}
|
||||
catch (Exception) { } // Ignore Errors
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddUserCustomDetails(DiscoDataContext database, List<DocumentExportRecord> records, IScheduledTaskStatus status)
|
||||
{
|
||||
if (!records.Any(r => r.User != null))
|
||||
return;
|
||||
status.UpdateStatus(50, "Extracting custom user detail records");
|
||||
var detailsService = new DetailsProviderService(database);
|
||||
var cache = new Dictionary<string, Dictionary<string, string>>(StringComparer.Ordinal);
|
||||
foreach (var record in records)
|
||||
{
|
||||
var userId = record.User?.UserId;
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
continue;
|
||||
if (!cache.TryGetValue(userId, out var details))
|
||||
details = detailsService.GetDetails(record.User);
|
||||
record.UserCustomDetails = details;
|
||||
}
|
||||
}
|
||||
|
||||
public ExportResult Export(DiscoDataContext database, IScheduledTaskStatus status)
|
||||
=> Exporter.Export(this, database, status);
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,14 @@ using Disco.Models.Exporting;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Devices;
|
||||
using Disco.Models.Services.Devices.DeviceFlag;
|
||||
using Disco.Models.Services.Documents;
|
||||
using Disco.Models.Services.Exporting;
|
||||
using Disco.Models.Services.Jobs;
|
||||
using Disco.Models.Services.Users.UserFlags;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Devices;
|
||||
using Disco.Services.Devices.DeviceFlags;
|
||||
using Disco.Services.Documents;
|
||||
using Disco.Services.Jobs;
|
||||
using Disco.Services.Logging;
|
||||
using Disco.Services.Tasks;
|
||||
@@ -32,6 +34,7 @@ namespace Disco.Services.Exporting
|
||||
RegisterExportType<DeviceExport, DeviceExportOptions, DeviceExportRecord>();
|
||||
RegisterExportType<JobExport, JobExportOptions, JobExportRecord>();
|
||||
RegisterExportType<UserFlagExport, UserFlagExportOptions, UserFlagExportRecord>();
|
||||
RegisterExportType<DocumentExport, DocumentExportOptions, DocumentExportRecord>();
|
||||
}
|
||||
|
||||
internal static void RegisterExportType<T, E, R>()
|
||||
|
||||
@@ -43,7 +43,9 @@ namespace Disco.Services
|
||||
|
||||
public static List<DeviceUserAssignment> CurrentDeviceUserAssignments(this User u)
|
||||
{
|
||||
return u.DeviceUserAssignments.Where(dua => !dua.UnassignedDate.HasValue).ToList();
|
||||
return u.DeviceUserAssignments?
|
||||
.Where(dua => !dua.UnassignedDate.HasValue)
|
||||
.ToList() ?? new List<DeviceUserAssignment>(0);
|
||||
}
|
||||
|
||||
public static bool CanCreateJob(this User u)
|
||||
|
||||
Reference in New Issue
Block a user