using Comal.Classes; using InABox.Clients; using InABox.Core; using InABox.DynamicGrid; using InABox.Scripting; using InABox.Wpf.Reports; using InABox.WPF; using PRSDesktop.Configuration; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; namespace PRSDesktop; public class DigitalFormDockGrid : DynamicGrid { private static readonly CoreFieldMap _mappings = new CoreFieldMap() .Add(x => x.ID, x => x.ID) .Add(x => x.Form.ID, x => x.FormID) .Add(x => x.Number, x => x.Number) .Add(x => x.Description, x => x.FormName) .Add(x => x.FormCompleted, x => x.Completed) .Add(x => x.FormCompletedBy.UserID, x => x.CompletedBy) .Add(x => x.FormProcessed, x => x.Processed) .Add(x => x.Parent.ID, x => x.ParentID); public static readonly Dictionary Images = new() { { typeof(AssignmentForm), PRSDesktop.Resources.assignments }, { typeof(KanbanForm), PRSDesktop.Resources.kanban }, { typeof(JobForm), PRSDesktop.Resources.project }, { typeof(JobITPForm), PRSDesktop.Resources.checklist }, { typeof(EmployeeForm), PRSDesktop.Resources.employees }, { typeof(LeaveRequestForm), PRSDesktop.Resources.leave }, { typeof(ManufacturingPacketStage), PRSDesktop.Resources.factory }, { typeof(TimeSheetForm), PRSDesktop.Resources.time }, { typeof(PurchaseOrderItemForm), PRSDesktop.Resources.purchase }, { typeof(DeliveryForm), PRSDesktop.Resources.truck }, }; public List ExcludedTypes { get; private set; } public DateTime StartDate { get; set; } public DigitalFormDockGrid() { StartDate = DateTime.Today; ExcludedTypes = new List(); ActionColumns.Add(new DynamicImageColumn(TypeImage) { Position = DynamicActionColumnPosition.Start, GetFilter = () => new StaticColumnFilter( FormType, Images.Keys.Select(x => new Tuple(x.EntityName().Split('.').Last().SplitCamelCase(), x)).ToArray()) }); ActionColumns.Add(new DynamicMenuColumn(MenuBuild, MenuStatus) { Position = DynamicActionColumnPosition.End }); } protected override void DoReconfigure(DynamicGridOptions options) { options.Clear(); options.FilterRows = true; } protected override DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style) { var result = base.GetRowStyle(row, style); if (!row.Get(x => x.Processed).IsEmpty()) result = new DynamicGridRowStyle(result) { Background = new SolidColorBrush(Colors.LightGray), }; return result; } private Type FormType(CoreRow row) { return row.Get(x => x.FormType); } private BitmapImage? TypeImage(CoreRow? arg) { if (arg is null) return null; var type = arg.Get(x => x.FormType); return Images.GetValueOrDefault(type)?.AsBitmapImage(); } private static readonly MethodInfo MenuBuildGenericMethod = typeof(DigitalFormDockGrid) .GetMethod(nameof(MenuBuildGeneric), BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)!; private void MenuBuildGeneric(ContextMenu menu, DigitalFormDockModel formModel) where TEntityForm : Entity, IDigitalFormInstance, IRemotable, IPersistent, new() where TEntity : Entity, IRemotable, IPersistent, new() where TEntityLink : IEntityLink, new() { var filter = new Filter(x => x.ID).IsEqualTo(formModel.ID); var model = new DigitalFormReportDataModel(filter, formModel.FormID); if (Security.CanView()) { menu.AddItem($"View {typeof(TEntity).Name}", Images.GetValueOrDefault(typeof(TEntityForm)), formModel, ViewEntity_Click); } var moduleTask = Task.Run(() => { return new Client().Query( new Filter(x => x.DataModel).IsEqualTo(model.Name) .And(x => x.Section).IsEqualTo(formModel.FormID.ToString()) .And(x => x.Visible).IsEqualTo(true) ).ToObjects(); }); var modulesSeparator = menu.AddSeparatorIfNeeded(); var modulesItem = menu.AddItem("Loading...", null, null, enabled: false); moduleTask.ContinueWith((task) => { try { var index = menu.Items.IndexOf(modulesItem); menu.Items.Remove(modulesItem); var any = false; foreach (var module in task.Result) { any = true; menu.AddItem( module.Name, PRSDesktop.Resources.edit, () => { try { if (ScriptDocument.RunCustomModule(model, new Dictionary(), module.Script)) { Refresh(false, true); } } catch (CompileException c) { MessageBox.Show(c.Message); } catch (Exception e) { MessageBox.Show(CoreUtils.FormatException(e)); } }, enabled: formModel.Processed.IsEmpty(), index: index); ++index; } if (!any && modulesSeparator is not null) { menu.Items.Remove(modulesSeparator); } } catch (Exception ex) { Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in digital form dock while loading custom modules: {CoreUtils.FormatException(ex)}"); MessageBox.Show($"Error: {ex.Message}", "Error"); } }, TaskScheduler.FromCurrentSynchronizationContext()); menu.AddSeparator(); if (formModel.Processed.IsEmpty()) { menu.AddItem( "Mark as Processed", PRSDesktop.Resources.lock_sml, () => { var form = new TEntityForm { FormProcessed = formModel.Processed }.SetID(formModel.ID); form.CommitChanges(); form.FormProcessed = DateTime.Now; using (new WaitCursor()) { Client.Save(form, "Marked As Processed"); Refresh(false, true); } }); } else { menu.AddItem( "Clear Processed Flag", PRSDesktop.Resources.lock_sml, () => { var form = new TEntityForm { FormProcessed = formModel.Processed }.SetID(formModel.ID); form.CommitChanges(); form.FormProcessed = DateTime.MinValue; using (new WaitCursor()) { Client.Save(form, "Processed Flag Cleared"); Refresh(false, true); } }); } if (typeof(TEntity).HasInterface()) { menu.AddSeparatorIfNeeded(); menu.AddItem("Set Job", PRSDesktop.Resources.project, formModel, SetJob_ClickMethod.MakeGenericMethod(typeof(TEntity)).CreateDelegate>()); menu.AddItem("Set Job Scope", PRSDesktop.Resources.project, formModel, SetJobScope_ClickMethod.MakeGenericMethod(typeof(TEntity)).CreateDelegate>()); } else if (typeof(TEntity) == typeof(Job) && typeof(TEntityForm) == typeof(JobForm)) { menu.AddSeparatorIfNeeded(); var scopeheader = menu.AddItem("Set Job Scope", PRSDesktop.Resources.project, formModel, dockModel => { }); var scopes = Client.Query( new Filter(x => x.Job.ID).IsEqualTo(formModel.ParentID), Columns.None() .Add(x => x.ID) .Add(x => x.Number) .Add(x => x.Description) ).ToObjects(); foreach (var scope in scopes) { var existingscope = new MenuItem() { Header = $"{scope.Number}: {scope.Description}", DataContext = formModel, Tag = scope }; existingscope.Click += SetJobScopeFromMenu_Click; scopeheader.Items.Add(existingscope); } if (scopes.Any()) scopeheader.Items.Add(new Separator()); var newscope = new MenuItem() { Header = $"Create New Scope", DataContext = formModel }; newscope.Click += CreateJobScopeFromMenu_Click; scopeheader.Items.Add(newscope); } if (Security.IsAllowed()) { menu.AddSeparatorIfNeeded(); menu.AddItem("Customise Modules", PRSDesktop.Resources.script, () => { var manager = new CustomModuleManager { Section = formModel.FormID.ToString(), DataModel = model }; manager.ShowDialog(); }); } if (Security.IsAllowed()) { menu.AddSeparatorIfNeeded(); var printItem = menu.AddItem("Print", PRSDesktop.Resources.printer, null); ReportUtils.PopulateMenu(printItem, formModel.FormID.ToString(), model, Security.IsAllowed(), true); } } private static void UpdateScopeID(DigitalFormDockModel formModel, Guid scopeid) { var instance = Client.Query( new Filter(x => x.ID).IsEqualTo(formModel.ID), Columns.Required() ).ToObjects().First(); instance.JobScope.ID = scopeid; Client.Save(instance, "Linked scope set by user from Digital forms dock."); } private void CreateJobScopeFromMenu_Click(object sender, RoutedEventArgs e) { if ((sender as MenuItem)?.DataContext is DigitalFormDockModel model) { var scope = new JobScope(); scope.Job.ID = model.ParentID; scope.Description = model.FormName; if (new DynamicDataGrid().EditItems(new[] { scope })) { UpdateScopeID(model, scope.ID); MessageBox.Show($"Form has been assigned to scope {scope.Number}."); // Update the form scope here (from the SetScope) stuff } } } private void SetJobScopeFromMenu_Click(object sender, RoutedEventArgs e) { if (sender is MenuItem mi && mi.DataContext is DigitalFormDockModel model && mi.Tag is JobScope scope) { UpdateScopeID(model, scope.ID); MessageBox.Show($"Form has been assigned to scope {scope.Number}."); // Update the form scope here (from the SetScope) stuff } } private static void ViewEntity_Click(DigitalFormDockModel model) where TEntity : Entity, IRemotable, IPersistent, new() { var entity = Client.Query( new Filter(x => x.ID).IsEqualTo(model.ParentID), DynamicGridUtils.LoadEditorColumns(Columns.None())) .ToObjects().First(); var grid = (DynamicGridUtils.CreateDynamicGrid(typeof(DynamicGrid<>), typeof(TEntity)) as DynamicGrid)!; grid.EditItems(new TEntity[] { entity }); } private static void SetJobScopeFromJob_Click(DigitalFormDockModel formModel) { var instance = Client.Query( new Filter(x => x.ID).IsEqualTo(formModel.ID), LookupFactory.DefineLookupFilterColumns(x => x.JobScope) .Add(x => x.ID) .Add(x => x.Parent.ID)) .ToObjects().First(); var job = Client.Query( new Filter(x => x.ID).IsEqualTo(instance.Parent.ID), LookupFactory.DefineChildFilterColumns()).ToObjects().First(); var window = new MultiSelectDialog( new Filters() .Add(LookupFactory.DefineChildFilter(new Job[] { job })) .Add(new Filter(x => x.Job.ID).IsEqualTo(instance.Parent.ID)) .Combine(), Columns.None().Add(x => x.ID).Add(x => x.Number), multiselect: false); if (!window.ShowDialog()) { return; } var scope = window.Data().ToObjects().First(); instance.JobScope.ID = scope.ID; Client.Save(instance, "Linked scope set by user from Digital forms dock."); MessageBox.Show($"Form has been assigned to scope {scope.Number}."); } private static readonly MethodInfo SetJobScope_ClickMethod = typeof(DigitalFormDockGrid).GetMethod(nameof(SetJobScope_Click), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!; private static void SetJobScope_Click(DigitalFormDockModel formModel) where TEntity : Entity, IJobScopedItem, IRemotable, IPersistent, new() { var entity = Client.Query( new Filter(x => x.ID).IsEqualTo(formModel.ParentID), LookupFactory.DefineLookupFilterColumns(x => x.JobScope) .Add(x => x.ID) .Add(x => x.JobLink.ID) .Add(x => x.JobScope.ID) .Add(x => x.JobScope.Number)) .ToObjects().First(); if (entity.JobLink.ID == Guid.Empty) { MessageBox.Show($"{typeof(TEntity).Name} is not linked to a job. Please select a job first."); return; } var window = new MultiSelectDialog( new Filters() .Add(LookupFactory.DefineLookupFilter(x => x.JobScope, new TEntity[] { entity })) .Add(new Filter(x => x.Job.ID).IsEqualTo(entity.JobLink.ID)) .Combine(), Columns.None().Add(x => x.ID).Add(x => x.Number), multiselect: false); if (!window.ShowDialog(nameof(JobScope.Number), entity.JobScope.Number, Syncfusion.Data.FilterType.Equals)) { return; } var scope = window.Data().ToObjects().First(); entity.JobScope.ID = scope.ID; Client.Save(entity, "Linked scope set by user from Digital forms dock."); MessageBox.Show($"{typeof(TEntity).Name} has been assigned to scope {scope.Number}."); } private static readonly MethodInfo SetJob_ClickMethod = typeof(DigitalFormDockGrid).GetMethod(nameof(SetJob_Click), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!; private static void SetJob_Click(DigitalFormDockModel formModel) where TEntity: Entity, IJobScopedItem, IRemotable, IPersistent, new() { var entity = Client.Query( new Filter(x => x.ID).IsEqualTo(formModel.ParentID), LookupFactory.DefineLookupFilterColumns(x => x.JobLink) .Add(x => x.ID) .Add(x => x.JobLink.ID) .Add(x => x.JobLink.JobNumber)) .ToObjects().First(); var window = new MultiSelectDialog( LookupFactory.DefineLookupFilter(x => x.JobLink, new TEntity[] { entity }), Columns.None().Add(x => x.DefaultScope.ID).Add(x => x.JobNumber), multiselect: false); if (!window.ShowDialog(nameof(Job.JobNumber), entity.JobLink.JobNumber, Syncfusion.Data.FilterType.Equals)) { return; } var job = window.Data().ToObjects().First(); entity.JobLink.ID = job.ID; entity.JobLink.Synchronise(job); Client.Save(entity, "Linked job set by user from Digital forms dock."); MessageBox.Show($"{typeof(TEntity).Name} has been assigned to job {job.JobNumber}."); } private void MenuBuild(DynamicMenuColumn column, CoreRow? row) { if (row is null) return; var form = row.ToObject(); var linkType = DFUtils.FormEntityLinkType(form.FormType); var entityType = DFUtils.FormEntityType(form.FormType); MenuBuildGenericMethod.MakeGenericMethod(form.FormType, entityType, linkType).Invoke(this, new object?[] { column.GetMenu(), form }); } private DynamicMenuStatus MenuStatus(CoreRow row) { if (row == null) return DynamicMenuStatus.Hidden; return DynamicMenuStatus.Enabled; } protected override void Reload( Filters criteria, Columns columns, ref SortOrder? sort, CancellationToken token, Action action) { var queryDefs = new Dictionary(); var types = DFUtils.GetFormInstanceTypes().Except(ExcludedTypes).ToList(); foreach (var type in types) { var filter = Filter.Create(type, x => x.FormCompleted).IsGreaterThanOrEqualTo(StartDate) .And(x => x.FormCancelled).IsEqualTo(DateTime.MinValue); var cols = Columns.Create(type, ColumnTypeFlags.None) .Add(c => c.ID) .Add(c => c.Parent.ID) .Add(c => c.Form.ID) .Add(c => c.Number) .Add(c => c.Description) .Add(c => c.FormCompleted) .Add(c => c.FormCompletedBy.UserID) .Add(c => c.FormProcessed); var sorts = SortOrder.Create(type, x => x.FormCompleted, SortDirection.Descending); queryDefs.Add( type.ToString(), new QueryDef(type) { Filter = filter, Columns = cols, SortOrder = sorts }); } Client.QueryMultiple( (results, e) => { if(results is not null) { var data = new CoreTable(); data.LoadColumns(typeof(DigitalFormDockModel)); foreach (var type in types) data.LoadFrom( results[type.ToString()], _mappings, (r) => r.Set(x => x.FormType, type) ); action.Invoke(data, null); } else if(e is not null) { Logger.Send(LogType.Error, ClientFactory.UserID, CoreUtils.FormatException(e)); MessageBox.Show($"Error: {e.Message}"); } }, queryDefs); } public override DigitalFormDockModel LoadItem(CoreRow row) { return row.ToObject(); } public override void SaveItem(DigitalFormDockModel item) { } public override void DeleteItems(params CoreRow[] rows) { } protected override void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args) { base.DoDoubleClick(sender, args); var row = SelectedRows.FirstOrDefault(); if (row is null) return; var instanceID = row.Get(x => x.ID); var formID = row.Get(x => x.FormID); var formType = row.Get(x => x.FormType); var formInstance = Client.Create(formType) .Query( Filter.Create(formType, x => x.ID).IsEqualTo(instanceID), DynamicFormEditWindow.FormColumns(formType)) .Rows.FirstOrDefault() ?.ToObject(formType) as IDigitalFormInstance; if (formInstance is null) { return; } if (formID == Guid.Empty) { var window = new DeletedFormWindow(); window.FormData = formInstance?.FormData ?? ""; window.ShowDialog(); return; } if (DynamicFormEditWindow.EditDigitalForm(formInstance, out var model)) { model.Update(null); } } }