using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using AvalonDock.Layout;
using Comal.Classes;
using Comal.Stores;
using Comal.TaskScheduler.Shared;
using H.Pipes;
using InABox.Clients;
using InABox.Configuration;
using InABox.Core;
using InABox.Database;
using InABox.Database.SQLite;
using InABox.DynamicGrid;
using InABox.Mail;
using InABox.Rpc;
using InABox.Scripting;
using InABox.Wpf;
using InABox.WPF;
using NAudio.Wave;
using PRS.Shared;
using InABox.WPF.Themes;
using PRSDesktop.Configuration;
using PRSDesktop.Forms;
using PRSServer;
using SharpAvi.Codecs;
using SharpAvi.Output;
using Syncfusion.Windows.Shared;
using Syncfusion.Windows.Tools.Controls;
using Application = System.Windows.Application;
using ButtonBase = System.Windows.Controls.Primitives.ButtonBase;
using Color = System.Windows.Media.Color;
using ColorConverter = System.Windows.Media.ColorConverter;
using Control = System.Windows.Controls.Control;
using Image = System.Drawing.Image;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
using MessageBox = System.Windows.MessageBox;
using Pen = System.Drawing.Pen;
using PixelFormat = System.Drawing.Imaging.PixelFormat;
using SortDirection = InABox.Core.SortDirection;
using InABox.Wpf.Reports;
using Comal.Classes.SecurityDescriptors;
using System.Threading;
using InABox.Formatters;
using PRSDesktop.Forms.Issues;
using Brushes = System.Windows.Media.Brushes;
using System.Windows.Media.Imaging;
using Button = System.Windows.Controls.Button;
using Visibility = System.Windows.Visibility;
using SharpVectors.Converters;
using SVGImage.SVG;
using Cursors = System.Windows.Input.Cursors;
namespace PRSDesktop;
public enum PanelType
{
InPlace,
NewWindow
}
public class SimpleCommand : ICommand
{
public Action OnExecute { get; private set; }
public bool CanExecute(object? parameter) => true;
public event EventHandler? CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public void Execute(object? parameter)
{
OnExecute?.Invoke();
}
public SimpleCommand(Action onExecute)
{
OnExecute = onExecute;
}
}
///
/// Interaction logic for Main.xaml
///
public partial class MainWindow : IPanelHostControl
{
//private const int WM_LBUTTONDOWN = 0x201;
private static PipeServer? _client;
private IRpcClientTransport? _transport;
private WaveIn? _audio;
private bool _audioMuted;
private MemoryStream? _audioStream;
private readonly Dictionary _bitmaps = new();
private DesktopConsole? _console;
private Dictionary> _notes = new();
private DispatcherTimer? _recorder;
private Process? _recordingnotes;
private int _screenheight = 720;
private int _screenleft;
private int _screentop;
private int _screenwidth = 1280;
private readonly Dictionary SecondaryWindows = new();
private CoreTable? _timesheets;
private DailyActivityHistory? ActivityHistory;
private readonly List CurrentModules = new();
private Fluent.RibbonTabItem? CurrentTab;
private Fluent.Button? CurrentButton;
private readonly int FRAMES_PER_SECOND = 10;
private DatabaseType DatabaseType;
private readonly Dictionary messages = new();
private readonly DispatcherTimer NotificationsWatchDog;
private DateTime pausestarted = DateTime.MinValue;
private readonly Scheduler scheduler = new() { Interval = new TimeSpan(0, 5, 0) };
// We use a Guid for the StationID rather than an IP or Mac address
// because we want true single-instance restriction. Using either of
// the above allows for two instances on the once machine, and thus
// double-counting in the Heartbeat() function
private Login station = new() { StationID = Guid.NewGuid().ToString() };
private TimeSpan totalpauses = new(0);
private readonly int VIDEO_HEIGHT = 1080;
private readonly int VIDEO_WIDTH = 1920;
private PanelHost PanelHost;
public MainWindow()
{
PanelHost = new PanelHost(this);
NotificationsWatchDog = new DispatcherTimer { IsEnabled = false };
NotificationsWatchDog.Tick += Notifications_Tick;
NotificationsWatchDog.Interval = new TimeSpan(0, 2, 0);
ClientFactory.PushHandlers.AddHandler(ReceiveNotification);
ClientFactory.RegisterMailer(EmailType.IMAP, typeof(IMAPMailer));
ClientFactory.RegisterMailer(EmailType.Exchange, typeof(ExchangeMailer));
ClientFactory.OnLog += (type, userid, message, parameters) => Logger.Send(LogType.Information, ClientFactory.UserID, message, parameters);
ClientFactory.OnRequestError += ClientFactory_OnRequestError;
HotKeyManager.Initialize();
HotKeyManager.RegisterHotKey(Key.F1, ShowHelp);
//HotKeyManager.RegisterHotKey(Key.F5, ToggleRecording);
//HotKeyManager.RegisterHotKey(Key.F6, ShowRecordingNotes);
//HotKeyManager.RegisterHotKey(Key.F4, ToggleRecordingAudio);
Logger.Send(LogType.Information, "", "Connecting to server");
var settings = App.DatabaseSettings;
bool dbConnected;
DatabaseType = settings.DatabaseType;
switch (DatabaseType)
{
case DatabaseType.Standalone:
ClientFactory.SetClientType(typeof(LocalClient<>), Platform.Wpf, CoreUtils.GetVersion());
DbFactory.ColorScheme = App.DatabaseSettings.ColorScheme;
DbFactory.Logo = App.DatabaseSettings.Logo;
dbConnected = true;
break;
case DatabaseType.Networked:
if (App.DatabaseSettings.Protocol == SerializerProtocol.RPC)
{
_transport = new RpcClientSocketTransport(App.DatabaseSettings.URLs);
_transport.OnClose += TransportConnectionLost;
_transport.OnException += Transport_OnException;
_transport.OnOpen += Transport_OnOpen; ;
dbConnected = _transport.Connect();
ClientFactory.SetClientType(typeof(RpcClient<>), Platform.Wpf, CoreUtils.GetVersion(),
_transport);
}
else
{
var url = RestClient.Ping(App.DatabaseSettings.URLs, out DatabaseInfo info);
ClientFactory.SetClientType(typeof(RestClient<>), Platform.Wpf, CoreUtils.GetVersion(), url, true);
dbConnected = true;
}
break;
case DatabaseType.Local:
//new RPC stuff (temporary disabled - for enabling when RPC is ready)
var pipename = DatabaseServerProperties.GetPipeName(App.DatabaseSettings.LocalServerName, true);
_transport = new RpcClientPipeTransport(pipename);
_transport.OnClose += TransportConnectionLost;
dbConnected = _transport.Connect();
ClientFactory.SetClientType(typeof(RpcClient<>), Platform.Wpf, CoreUtils.GetVersion(), _transport );
//ClientFactory.SetClientType(typeof(IPCClient<>), Platform.Wpf, CoreUtils.GetVersion(),
// DatabaseServerProperties.GetPipeName(App.DatabaseSettings.LocalServerName, false));
//dbConnected = true;
break;
default:
throw new Exception($"Invalid database type {DatabaseType}");
}
InitializeComponent();
if (!dbConnected)
{
switch (DoConnectionFailed())
{
case ConnectionFailedResult.Quit:
Close();
return;
case ConnectionFailedResult.Restart:
App.ShouldRestart = true;
Close();
return;
case ConnectionFailedResult.Ok:
// Do nothing
break;
}
}
ThemeManager.BaseColor = Colors.CornflowerBlue;
Progress.DisplayImage = PRSDesktop.Resources.splash_small.AsBitmapImage();
try
{
var dbInfo = new Client().Info();
ClientFactory.DatabaseID = dbInfo.DatabaseID;
ThemeManager.BaseColor = (Color)ColorConverter.ConvertFromString(dbInfo.ColorScheme);
if (dbInfo.Logo?.Any() == true)
using (var ms = new MemoryStream(dbInfo.Logo))
{
Progress.DisplayImage = new Bitmap(ms).AsBitmapImage();
}
}
catch
{
}
//VideoRecordingStatus.Source = PRSDesktop.Resources.videorecording.AsGrayScale().AsBitmapImage();
//AudioRecordingStatus.Source = PRSDesktop.Resources.audiorecording.AsGrayScale().AsBitmapImage();
//SecondaryWindowStatus.Source = PRSDesktop.Resources.target.AsGrayScale().AsBitmapImage();
ConsoleStatus.Source = PRSDesktop.Resources.view.AsGrayScale().AsBitmapImage();
SelectTask.Source = PRSDesktop.Resources.uparrow.Invert().AsBitmapImage();
Title = $"{(String.Equals(App.Profile?.ToUpper(), "DEFAULT") ? "PRS Desktop" : App.Profile)} (Release {CoreUtils.GetVersion()})";
Logger.Send(LogType.Information, "", "Checking for updates");
if (SupportUtils.CheckForUpdates())
{
Logger.Send(LogType.Information, "", "Update found, closing application.");
Close();
return;
}
Exception? startupException = null;
ValidationStatus? loginStatus = null;
Progress.ShowModal("Loading PRS", progress =>
{
DynamicGridUtils.PreviewReport = (t, m) => { ReportUtils.PreviewReport(t, m, false, Security.IsAllowed()); };
DynamicGridUtils.PrintMenu = (e, s, m, p) => { ReportUtils.PrintMenu(e, s, m, Security.IsAllowed(), p); };
ImportFactory.Register(typeof(ExcelImporter<>), "Excel File", "Excel Files (*.xls;*.xlsx;*.xlsm)|*.xls;*.xlsx;*.xlsm");
ImportFactory.Register(typeof(CustomImporter<>), "Custom", "All Files (*.*)|*.*");
FormUtils.Register();
DigitalFormDocumentFactory.Init(
new WpfDigitalFormDocumentHandler(
b => Dispatcher.BeginInvoke(() =>
{
BackgroundUploadStatus.Visibility = b
? Visibility.Visible
: Visibility.Hidden;
}
),
() => _transport?.IsConnected() ?? false
)
);
DigitalFormDocumentFactory.Run();
Logger.Send(LogType.Information, "", "Registering Classes");
progress.Report("Registering Classes");
var tasks = new List
{
Task.Run(() =>
{
CoreUtils.RegisterClasses(typeof(TaskGrid).Assembly);
CoreUtils.RegisterClasses();
ComalUtils.RegisterClasses();
StoreUtils.RegisterClasses();
PRSSharedUtils.RegisterClasses();
WPFUtils.RegisterClasses();
ReportUtils.RegisterClasses();
ConfigurationUtils.RegisterClasses();
}),
Task.Run(() =>
{
ScriptDocument.DefaultAssemblies.AddRange(
Assembly.Load("RoslynPad.Roslyn.Windows"),
Assembly.Load("RoslynPad.Editor.Windows"),
typeof(Control).Assembly,
typeof(MessageBox).Assembly,
typeof(SolidColorBrush).Assembly
);
ScriptDocument.Initialize();
}),
Task.Run(() => DatabaseUpdateScripts.RegisterScripts())
};
Task.WaitAll(tasks.ToArray());
Logger.Send(LogType.Information, "", "Configuring Application");
progress.Report("Configuring Application");
RegisterModules(progress);
if (DatabaseType == DatabaseType.Standalone)
{
progress.Report("Starting local database...");
try
{
StartLocalDatabase(progress);
}
catch (Exception err)
{
startupException = new Exception(
string.Format(
"Unable to open database ({0})\n\n{1}\n\n{2}",
App.DatabaseSettings.FileName,
err.Message,
err.StackTrace
)
);
}
}
});
if (startupException is null && App.DatabaseSettings.Autologin)
{
try
{
Logger.Send(LogType.Information, "", "Logging in");
Dispatcher.Invoke(() =>
{
loginStatus = TryAutoLogin();
});
if(loginStatus == ValidationStatus.VALID)
{
// Do the AfterLogin() here so that we aren't opening and closing progress windows again and again.
Progress.ShowModal("Loading PRS", progress =>
{
AfterLogin(progress);
});
}
}
catch(Exception e)
{
startupException = e;
}
}
if (startupException != null)
{
MessageWindow.ShowError("Error during startup.", startupException);
}
// If the login status is valid, then we've already loaded everything, so we don't here.
if(loginStatus != ValidationStatus.VALID)
{
Logger.Send(LogType.Information, "", "Logging in");
if (DoLogin() == ValidationStatus.VALID)
{
AfterLogin(null);
}
else
{
ConfigureMainScreen(null);
}
}
ProfileName.Content = App.Profile;
URL.Content = GetDatabaseConnectionDescription();
if (loginStatus == ValidationStatus.VALID && DatabaseType == DatabaseType.Standalone)
{
Progress.ShowModal("Starting Scheduler", progress =>
{
scheduler.Start();
});
}
}
#region Connection Management
private string GetDatabaseConnectionDescription()
{
return DatabaseType switch
{
#if RPC
DatabaseType.Networked => (ClientFactory.Parameters?.FirstOrDefault() as RpcClientSocketTransport)?.Host,
#else
DatabaseType.Networked => ClientFactory.Parameters?.FirstOrDefault() as string,
#endif
DatabaseType.Standalone => App.DatabaseSettings?.FileName,
DatabaseType.Local => App.DatabaseSettings?.LocalServerName,
_ => ""
} ?? "";
}
///
/// Reconnect to the server.
///
/// if connection was successful.
private bool Reconnect()
{
if (_transport != null)
{
return _transport.Connect();
}
Logger.Send(LogType.Error, ClientFactory.UserID, "Trying to reconnect without a transport set.");
return true; // Returning true so we don't get stuck in infinite loops in exceptional circumstances.
}
private enum ConnectionFailedResult
{
Quit,
Restart,
Ok
}
///
/// To be called when initial connection to the server has failed; asks the user if they want to retry,
/// change the database settings, or simply quit PRS.
///
/// The action to take next.
///
private ConnectionFailedResult DoConnectionFailed()
{
bool connected = false;
while (!connected)
{
var connectionFailedWindow = new ConnectionFailed();
connectionFailedWindow.ShowDialog();
var reconnect = false;
switch (connectionFailedWindow.Result)
{
case ConnectionFailedWindowResult.OpenDatabaseConfiguration:
var result = ShowDatabaseConfiguration();
switch (result)
{
case DatabaseConfigurationResult.RestartRequired:
var shouldRestart = MessageBox.Show(
"A restart is required to apply these changes. Do you wish to restart now?",
"Restart?",
MessageBoxButton.YesNo);
if (shouldRestart == MessageBoxResult.Yes)
{
return ConnectionFailedResult.Restart;
}
else
{
reconnect = true;
}
break;
case DatabaseConfigurationResult.RestartNotRequired:
reconnect = true;
break;
case DatabaseConfigurationResult.Cancel:
reconnect = true;
break;
default:
throw new Exception($"Invalid enum result {result}");
}
break;
case ConnectionFailedWindowResult.RetryConnection:
reconnect = true;
break;
case ConnectionFailedWindowResult.Quit:
return ConnectionFailedResult.Quit;
default:
throw new Exception($"Invalid enum result {connectionFailedWindow.Result}");
}
if (!reconnect)
{
return ConnectionFailedResult.Quit;
}
connected = Reconnect();
}
return ConnectionFailedResult.Ok;
}
private void Transport_OnOpen(IRpcTransport transport, RpcTransportOpenArgs e)
{
Logger.Send(LogType.Information, ClientFactory.UserID, "Connection opened");
}
private void Transport_OnException(IRpcTransport transport, RpcTransportExceptionArgs e)
{
Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in connection: {CoreUtils.FormatException(e.Exception)}");
}
private void TransportConnectionLost(IRpcTransport transport, RpcTransportCloseArgs e)
{
Logger.Send(LogType.Information, ClientFactory.UserID, "Connection lost");
if (transport is IRpcClientTransport client)
{
Dispatcher.Invoke(() =>
{
var reconnection = new ReconnectionWindow();
var cancellationTokenSource = new CancellationTokenSource();
reconnection.OnCancelled = () => cancellationTokenSource.Cancel();
var ct = cancellationTokenSource.Token;
var work = () =>
{
try
{
DateTime lost = DateTime.Now;
while (!client.IsConnected() && !ct.IsCancellationRequested)
{
try
{
Logger.Send(LogType.Error, ClientFactory.UserID, $"Reconnecting - ({DateTime.Now - lost:hh\\:mm})");
if (client.Connect(ct))
{
break;
}
}
catch (System.Exception e1)
{
Logger.Send(LogType.Error, ClientFactory.UserID, $"Reconnect Failed: {e1.Message}");
// TODO: Remove this suppression
if (e1.Message.StartsWith("The socket is connected, you needn't connect again!"))
{
break;
}
}
}
if (client.IsConnected())
{
Logger.Send(LogType.Information, ClientFactory.UserID, "Reconnected");
ClientFactory.Validate(ClientFactory.SessionID);
Logger.Send(LogType.Information, ClientFactory.UserID, "Validated");
return true;
}
}
catch (Exception e)
{
Logger.Send(LogType.Error, ClientFactory.UserID, $"Reconnect Failed: {e.Message}");
}
return false;
};
var task = Task.Run(() =>
{
var result = work();
Dispatcher.Invoke(() =>
{
reconnection.Close();
});
return result;
}, ct);
reconnection.ShowDialog();
if (!task.Result)
{
Close();
}
});
}
}
#endregion
private bool _loggingOut = false;
private void ClientFactory_OnRequestError(RequestException e)
{
if (e.Status == StatusCode.Unauthenticated)
{
switch (e.Method)
{
case RequestMethod.Query:
case RequestMethod.Save:
case RequestMethod.Delete:
case RequestMethod.MultiQuery:
case RequestMethod.MultiSave:
case RequestMethod.MultiDelete:
if (!_loggingOut)
{
Dispatcher.InvokeAsync(() =>
{
_loggingOut = true;
try
{
Logout(null, true);
}
finally
{
_loggingOut = false;
}
});
}
break;
default:
break;
}
}
}
private void ApplyColorScheme()
{
Color baseColor;
try
{
baseColor = (Color)ColorConverter.ConvertFromString(App.DatabaseSettings.ColorScheme);
}
catch
{
baseColor = Colors.CornflowerBlue;
}
ThemeManager.BaseColor = baseColor;
DynamicGridUtils.SelectionBackground = ThemeManager.SelectionBackgroundBrush;
DynamicGridUtils.SelectionForeground = ThemeManager.SelectionForegroundBrush;
DynamicGridUtils.FilterBackground = ThemeManager.FilterBackgroundBrush;
DynamicGridUtils.FilterForeground = ThemeManager.FilterForegroundBrush;
UpdateRibbonColors();
PanelHost.Refresh();
}
#region Configuration
private bool CanViewMaps => Security.CanView()
|| Security.CanView()
|| Security.CanView()
|| Security.CanView();
private void ConfigureMainScreen(IProgress? progress)
{
var bMaps = CanViewMaps;
ProgressSection ConfigureTab(string progress, Func createTab)
{
return new ProgressSection(progress, () =>
{
var tab = createTab();
if (tab is not null)
{
_ribbon.Tabs.Add(tab);
}
});
}
var sections = new[]
{
new ProgressSection("Initial Setup", () =>
{
_ribbon.Tabs.Clear();
}),
new ProgressSection("Configuring Main Screen", SetupMainScreen),
ConfigureTab("Configuring Projects", SetupProjectsTab),
ConfigureTab("Configuring Manufacturing", SetupManufacturingTab),
ConfigureTab("Configuring Logistics", SetupLogisticsTab),
ConfigureTab("Configuring Products", SetupProductsTab),
ConfigureTab("Configuring Human Resources", SetupHumanResourcesTab),
ConfigureTab("Configuring Accounts Receivable", SetupAccountsReceivableTab),
ConfigureTab("Configuring Accounts Payable", SetupAccountsPayableTab),
ConfigureTab("Configuring Equipment", SetupEquipmentTab),
ConfigureTab("Configuring DigitalForms", SetupDigitalFormsTab),
ConfigureTab("Configuring Dashboards", SetupDashboardsTab),
new ProgressSection("Configuring System Modules", SetupSystemModules)
};
if(progress is not null)
{
Dispatcher.Invoke(SetupScreen);
foreach(var section in sections)
{
progress.Report(section.Message);
Dispatcher.Invoke(section.Action);
}
}
else
{
SetupScreen();
Progress.ShowModal(sections);
}
}
private void SetupScreen()
{
var button = _ribbon.FindVisualChildren().FirstOrDefault();
if (button != null)
button.Visibility = Visibility.Collapsed;
if (ClientFactory.UserGuid == Guid.Empty)
_ribbonRow.Height = new GridLength(30, GridUnitType.Pixel);
else
_ribbonRow.Height = new GridLength(1, GridUnitType.Auto);
}
private void SetupMainScreen()
{
//DockManager.SidePanelSize = OutstandingDailyReports(false) ? 0.00F : 30.00F;
// Notifications Area
SetFrameworkItemVisibility(SendNotification, Security.CanView());
SetFrameworkItemVisibility(Notifications, Security.CanView());
SetFrameworkItemVisibility(TaskTracking, Security.IsAllowed());
UserID.Content = ClientFactory.UserID;
if (ClientFactory.PasswordExpiration != DateTime.MinValue)
{
var timeUntilExpiration = ClientFactory.PasswordExpiration - DateTime.Now;
if (timeUntilExpiration.Days < 14)
{
PasswordExpiryNotice.Content = $"Password will expire in {timeUntilExpiration.Days} days!";
PasswordExpiryNotice.Visibility = Visibility.Visible;
}
else
{
PasswordExpiryNotice.Visibility = Visibility.Collapsed;
}
}
}
private void SetupSystemModules()
{
SetFrameworkItemVisibility(CompanyInformation, Security.CanView());
SetVisibleIfAny(BackstageSeparator0, CompanyInformation);
SetFrameworkItemVisibility(SecurityDefaultsButton, Security.IsAllowed());
SetVisibleIfAny(BackstageSeparator1, SecurityDefaultsButton);
BackstageSeparator1a.Visibility = Visibility.Visible;
SystemLogsButton.Visibility = Visibility.Visible;
SetFrameworkItemVisibility(DocumentTypeList, Security.IsAllowed());
SetFrameworkItemVisibility(EventList, Security.IsAllowed());
SetVisibleIfAny(BackstageSeparator2, DocumentTypeList, EventList);
//SetModuleVisibility<>(VideoRecordingButton, Security.IsAllowed());
LogoutButton.Visibility = ClientFactory.UserGuid == Guid.Empty ? Visibility.Collapsed : Visibility.Visible;
LoginButton.Visibility = ClientFactory.UserGuid != Guid.Empty ? Visibility.Collapsed : Visibility.Visible;
EditDetailsButton.Visibility = ClientFactory.UserGuid == Guid.Empty ? Visibility.Collapsed : Visibility.Visible;
SetupDock(ContactDock, Contacts);
SetupDock(JobDock, Jobs);
SetupDock(ConsignmentDock, Consignments);
SetupDock(DeliveryDock, Deliveries);
SetupDock(ProductLookupDock, ProductLookup);
SetupDock(DigitalFormsDock, DigitalForms);
SetupDock(ProblemsDock, Problems);
SetupDock(RequisitionsDock, Requisitions);
_ribbon.InvalidateArrange();
}
private Fluent.RibbonTabItem? SetupDashboardsTab() =>
CreateTab("Dashboards", x => x
.NewGroup(x => x
.Add("Database Activity", SvgImages.kpi,
Security.IsAllowed()
&& Security.IsAllowed())
.Add("User Activity", SvgImages.kpi,
Security.IsAllowed()
&& Security.IsAllowed())
.Add("Quick Status", SvgImages.kpi,
Security.IsAllowed() && Security.IsAllowed())));
private Fluent.RibbonTabItem? SetupDigitalFormsTab() =>
CreateTab("Digital Forms", x => x
.NewGroup(x => x
.Add("Forms Library", SvgImages.formslibrary,
Security.IsAllowed())
.Add("Forms Dashboard", SvgImages.formsinstance,
Security.IsAllowed()
&& Security.IsAllowed())));
private Fluent.RibbonTabItem? SetupEquipmentTab() =>
CreateTab("Equipment", x => x
.NewGroup(x => x
.Add("Equipment List", PRSDesktop.Resources.specifications,
Security.CanView()
&& Security.IsAllowed())
.Add("Planned Maintenance", PRSDesktop.Resources.service,
Security.CanView()
&& Security.IsAllowed())
.Add("Equipment Planner", PRSDesktop.Resources.calendar,
Security.CanView()
&& Security.CanView()
&& Security.IsAllowed())
.Add("GPS Trackers", PRSDesktop.Resources.milestone,
Security.CanView() && Security.IsAllowed())));
private Fluent.RibbonTabItem? SetupAccountsReceivableTab() =>
CreateTab("Accounts Receivable", x => x
.NewGroup(x => x
.Add("Customers", PRSDesktop.Resources.customer,
Security.CanView()
&& Security.IsAllowed())
.Add("Invoices", PRSDesktop.Resources.invoice,
Security.CanView()
&& Security.IsAllowed())
.Add("Receipts", PRSDesktop.Resources.receipt,
Security.CanView()
&& Security.IsAllowed())));
private Fluent.RibbonTabItem? SetupAccountsPayableTab() =>
CreateTab("Accounts Payable", x => x
.NewGroup(x => x
.Add("Suppliers", PRSDesktop.Resources.supplier,
Security.CanView()
&& Security.IsAllowed())
.Add("Data Entry", PRSDesktop.Resources.pencil,
Security.IsAllowed())
.Add("Purchase Orders", PRSDesktop.Resources.purchase,
Security.CanView()
&& Security.IsAllowed())
.Add("Bills", PRSDesktop.Resources.bill,
Security.CanView()
&& Security.IsAllowed())
.Add("Payments", PRSDesktop.Resources.payment,
Security.CanView()
&& Security.IsAllowed())));
private Fluent.RibbonTabItem? SetupHumanResourcesTab() =>
CreateTab("Human Resources", x => x
.NewGroup(x => x
.Add("Calendar", PRSDesktop.Resources.assignments,
Security.CanView() && Security.IsAllowed())
.Add("Employee Planner", PRSDesktop.Resources.calendar,
Security.CanView() && Security.IsAllowed())
.Add("Staff Timesheets", PRSDesktop.Resources.clock,
Security.CanView() && Security.IsAllowed())
.Add("Leave Requests", SvgImages.beach,
Security.CanView() && Security.IsAllowed())
.Add("Meetings", PRSDesktop.Resources.employees,
Security.IsAllowed()))
.NewGroup(x => x
.Add("User Accounts", PRSDesktop.Resources.user,
Security.CanView() && Security.IsAllowed())
.Add("Employee List", PRSDesktop.Resources.employee,
Security.CanView() && Security.IsAllowed())
.Add("Org Chart", PRSDesktop.Resources.orgchart,
Security.IsAllowed())));
private Fluent.RibbonTabItem? SetupProductsTab() =>
CreateTab("Products", x => x
.NewGroup(x => x
.Add("Product List", PRSDesktop.Resources.product,
Security.CanView() && Security.IsAllowed())
.Add("Stock Locations", PRSDesktop.Resources.parcel,
Security.CanView() && Security.IsAllowed())
.Add("Stock Movements", PRSDesktop.Resources.forklift,
Security.CanView() && Security.IsAllowed())
.Add("Stock Forecast", SvgImages.kpi,
Security.CanView()
&& Security.CanView()
&& Security.IsAllowed())
.Add("Reservation Management", PRSDesktop.Resources.requisition,
Security.IsAllowed())));
private Fluent.RibbonTabItem? SetupLogisticsTab() =>
CreateTab("Logistics", x => x
.NewGroup(x => x
.Add("Ready To Go", SvgImages.truck,
Security.IsAllowed()
&& Security.IsAllowed())
.Add("Rack List", PRSDesktop.Resources.barcode,
Security.CanView()
&& Security.CanView()
&& Security.IsAllowed())
.Add("Picking Lists", SvgImages.box,
Security.CanView() && Security.IsAllowed())
.Add("Deliveries", SvgImages.truck,
Security.IsAllowed() && Security.IsAllowed())
.Add("Delivered On Site", PRSDesktop.Resources.lifter,
Security.IsAllowed()
&& Security.IsAllowed()))
.NewGroup(x => x
.Add("Incoming Consignments", PRSDesktop.Resources.consignment,
Security.CanView() && Security.IsAllowed())));
private Fluent.RibbonTabItem? SetupManufacturingTab() =>
CreateTab("Manufacturing", x => x
.NewGroup(x => x
.Add("Design Management", PRSDesktop.Resources.design,
Security.CanView() && Security.IsAllowed()))
.NewGroup(x => x
.Add("Manufacturing Status", PRSDesktop.Resources.factory,
Security.IsAllowed()
&& Security.IsAllowed())
.Add("Factory Allocation", PRSDesktop.Resources.assignments,
Security.IsAllowed()
&& Security.IsAllowed())
.Add("Factory Floor", PRSDesktop.Resources.wrench,
Security.IsAllowed()
&& Security.IsAllowed()))
.NewGroup(x => x
.Add("Factory KPIs", SvgImages.kpi,
Security.IsAllowed()
&& Security.IsAllowed())
.Add("Template Analysis", SvgImages.kpi,
Security.IsAllowed()
&& Security.IsAllowed())
.Add("Factory Analysis", SvgImages.kpi,
Security.IsAllowed()
&& Security.IsAllowed())));
private Fluent.RibbonTabItem? SetupProjectsTab() =>
CreateTab("Projects", x => x
.NewGroup(x => x
.Add("Quotes", PRSDesktop.Resources.quotation,
Security.CanView() && Security.IsAllowed())
.Add("Projects", PRSDesktop.Resources.project,
Security.CanView() && Security.IsAllowed())
.Add("Project Planner", PRSDesktop.Resources.calendar,
Security.CanView() && Security.IsAllowed()))
.NewGroup(x => x
.Add("Product Kits", PRSDesktop.Resources.kit,
Security.CanView() && Security.IsAllowed())
.Add("Cost Sheets", PRSDesktop.Resources.costsheet,
Security.CanView() && Security.IsAllowed()),
groupName: "Setup"));
private void SetupDock(LayoutAnchorable layout, IDockPanel dock)
where TSecurityDescriptor : ISecurityDescriptor, new()
{
if (Security.IsAllowed())
{
if (!DockGroup.Children.Any(x => x == layout))
{
DockGroup.Children.Add(layout);
}
if (layout.IsVisible && (ClientFactory.UserGuid != Guid.Empty))
dock.Setup();
}
else
{
DockGroup.RemoveChild(layout);
}
}
private class HeaderTab(string header, MainWindow window)
{
public string Header { get; set; } = header;
public List>> Groups { get; set; } = new();
public HeaderTab NewGroup(Action modify, string? groupName = null)
{
var currentGroup = (groupName is not null ? Groups.FirstOrDefault(x => x.Item1 == groupName) : Groups.LastOrDefault())?.Item2;
if(currentGroup is null)
{
currentGroup = new List();
Groups.Add(new(groupName ?? "", currentGroup));
}
var group = new HeaderGroup(window);
currentGroup.Add(group);
modify(group);
return this;
}
public Fluent.RibbonTabItem? Create()
{
var item = new Fluent.RibbonTabItem
{
Header = Header
};
var hasNonDefault = false;
foreach(var (name, groups) in Groups)
{
var groupBox = new Fluent.RibbonGroupBox
{
Header = name
};
var needsSeparator = false;
foreach(var group in groups)
{
var addedSeparator = false;
foreach(var button in group.Actions)
{
if (button.Visibility != Visibility.Visible) continue;
if (!addedSeparator && needsSeparator)
{
groupBox.Items.Add(new RibbonSeparator());
needsSeparator = false;
addedSeparator = true;
}
if (!group.IsDefaultGroup)
{
hasNonDefault = true;
}
groupBox.Items.Add(button);
needsSeparator = true;
}
}
if (groupBox.Items.Count > 0)
{
item.Groups.Add(groupBox);
}
}
return hasNonDefault ? item : null;
}
}
private class HeaderGroup(MainWindow window)
{
public List Actions { get; set; } = new();
public bool IsDefaultGroup { get; set; } = false;
private bool _locked = false;
private Fluent.Button CreateButton(string name, ImageSource image)
{
if (_locked) throw new Exception("Header Group is locked!");
var button = new Fluent.Button
{
Header = name,
LargeIcon = image,
MinWidth = 60
};
Actions.Add(button);
return button;
}
private Fluent.Button CreateButton(string name, Bitmap image)
{
return CreateButton(name, image.AsBitmapImage());
}
public HeaderGroup Add(string name, ImageSource image, Action onClick)
{
var button = CreateButton(name, image);
button.Command = new SimpleCommand(() => onClick());
return this;
}
public HeaderGroup Add(string name, Bitmap image, Action onClick)
{
var button = CreateButton(name, image);
button.Command = new SimpleCommand(() => onClick());
return this;
}
public HeaderGroup Add(string name, ImageSource image, bool visible = true)
where T : class, IBasePanel, new()
{
var button = CreateButton(name, image);
button.Command = new SimpleCommand(() => window.LoadWindow(button));
var menu = new ContextMenu();
menu.AddItem("Open in New Window", PRSDesktop.Resources.target, () => window.LoadSecondaryWindow(button));
button.ContextMenu = menu;
window.SetFrameworkItemVisibility(button, visible);
return this;
}
public HeaderGroup Add(string name, Bitmap image, bool visible = true)
where T : class, IBasePanel, new()
{
return Add(name, image.AsBitmapImage(), visible: visible);
}
public HeaderGroup Lock()
{
_locked = true;
return this;
}
}
private Fluent.RibbonTabItem? CreateTab(string header, Action modify)
{
var tab = new HeaderTab(header, this)
.NewGroup(x => x
.Add("Refresh", PRSDesktop.Resources.refresh, PanelHost.Refresh)
.Lock(),
groupName: "Actions")
.NewGroup(x => x
.Add("Dashboards", SvgImages.kpi, Security.IsAllowed())
.Add("Notification Centre", PRSDesktop.Resources.email, Security.CanView())
.Add("Task List", SvgImages.kanban, Security.CanView())
.Add("In/Out Board", PRSDesktop.Resources.attendance, Security.IsAllowed())
.Add("Live Maps", PRSDesktop.Resources.map, CanViewMaps)
.Add("Daily Report", PRSDesktop.Resources.report, Security.IsAllowed())
.Lock());
foreach(var (_, groupList) in tab.Groups)
{
foreach(var group in groupList)
{
group.IsDefaultGroup = true;
}
}
modify(tab);
var result = tab.Create();
if(result is null)
{
// This means no other items were added; in this case don't create the tab.
return null;
}
var box = new Fluent.RibbonGroupBox
{
Header = "Print",
Visibility = Visibility.Collapsed
};
result.Groups.Add(box);
return result;
}
private Fluent.RibbonTabItem? CreateTab(string header, Action modify)
where TSecurity : ISecurityDescriptor, new()
{
if (!Security.IsAllowed()) return null;
return CreateTab(header, modify);
}
#region Visibility
private void SetFrameworkItemVisibility(FrameworkElement button, bool visible)
{
var vResult = true;
var eResult = ClientFactory.UserGuid != Guid.Empty && visible;
button.Visibility = vResult && eResult ? Visibility.Visible : Visibility.Collapsed;
if (button is Fluent.Button rb)
{
CustomModules.Register(rb.Header?.ToString() ?? "");
rb.IsEnabled = !OutstandingDailyReports(false);
}
}
private void SetModuleVisibility(Fluent.Button button, bool visible) where T : class, IBasePanel, new()
{
SetFrameworkItemVisibility(button,visible);
button.MinWidth = 60;
if (button.Command == null)
{
button.Command = new SimpleCommand(() => LoadWindow(button));
var menu = new ContextMenu();
menu.Items.Add(new MenuItem()
{
Header = "Open in New Window",
Icon = new System.Windows.Controls.Image() { Source = PRSDesktop.Resources.target.AsBitmapImage() },
Command = new SimpleCommand(() => LoadSecondaryWindow(button))
});
button.ContextMenu = menu;
}
}
private static void SetVisibleIfEither(FrameworkElement separator, FrameworkElement[] left, FrameworkElement[] right)
{
var bLeft = false;
foreach (var button in left)
bLeft = bLeft || button.Visibility == Visibility.Visible;
var bRight = false;
foreach (var button in right)
bRight = bRight || button.Visibility == Visibility.Visible;
separator.Visibility = bLeft && bRight ? Visibility.Visible : Visibility.Collapsed;
}
private static void SetVisibleIfAny(FrameworkElement separator, params FrameworkElement[] buttons)
{
var bVisible = false;
foreach (var button in buttons)
bVisible = bVisible || button.Visibility == Visibility.Visible;
separator.Visibility = bVisible ? Visibility.Visible : Visibility.Collapsed;
}
private static void SetTabVisibleIfAny(Fluent.RibbonTabItem tab, params FrameworkElement[] buttons)
{
var bVisible = false;
foreach (var button in buttons)
bVisible = bVisible || button.Visibility == Visibility.Visible;
tab.Visibility = bVisible ? Visibility.Visible : Visibility.Collapsed;
}
#endregion
private Fluent.Button? FindModuleButton(string name)
{
foreach (var tab in _ribbon.Tabs)
{
foreach (var bar in tab.Groups)
{
var item = bar.Items.OfType().FirstOrDefault(x => Equals(x.Header, name));
if (item is not null) return item;
}
}
return null;
}
private void LoadApplicationState()
{
if (ClientFactory.UserGuid != Guid.Empty)
{
_ribbon.IsCollapsed = false;
if (OutstandingDailyReports(false))
{
MessageWindow.ShowMessage("There are outstanding Daily Reports that must be filled out before continuing!"
+ "\n\nAccess to PRS is restricted until this is corrected.",
"Outstanding Reports");
if(FindModuleButton("Daily Report") is Fluent.Button button)
{
var dailyReportPanel = LoadWindow(button);
if(dailyReportPanel is not null)
{
dailyReportPanel.OnTimeSheetConfirmed += e =>
{
if (!OutstandingDailyReports(true))
{
ConfigureMainScreen(null);
LoadApplicationState();
}
};
}
}
return;
}
using(new WaitCursor(this))
{
_ribbon.IsCollapsed = false;
LoadInitialWindow();
}
}
}
private void LoadInitialWindow()
{
var app = new LocalConfiguration().Load();
var module = app.Settings.ContainsKey("CurrentPanel")
? !string.IsNullOrWhiteSpace(app.Settings["CurrentPanel"])
? app.Settings["CurrentPanel"].Split([" / "], StringSplitOptions.None)
: [ "Human Resources", "Task List"]
: [ "Human Resources", "Task List"];
try
{
var bFound = false;
if (module.Length == 2)
foreach (Fluent.RibbonTabItem tab in _ribbon.Tabs)
{
if (String.Equals(tab.Header, module.First()))
{
_ribbon.SelectedTabItem = tab;
foreach (Fluent.RibbonGroupBox bar in tab.Groups)
{
foreach (var item in bar.Items)
{
var button = item as Fluent.Button;
if (button != null && String.Equals(button.Header, module.Last()))
{
bFound = true;
if (button.Command is SimpleCommand command)
command.Execute(null);
//button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
break;
}
}
if (bFound)
break;
}
}
if (bFound)
break;
}
}
catch (Exception e)
{
MessageWindow.ShowError($"Unable to load {app.Settings["CurrentPanel"]}", e);
}
}
private void _ribbon_OnLoaded(object sender, RoutedEventArgs e)
{
_ribbon.SelectedTabItem = CurrentTab;
}
private void LoadSecondaryWindows()
{
if (ClientFactory.UserGuid != Guid.Empty)
{
var windows = App.DatabaseSettings.SecondaryWindows;
foreach (var key in windows.Keys.ToArray())
{
SecondaryWindows[key] = new SecondaryWindow(
key,
windows[key].Item1,
windows[key].Item2,
windows[key].Item3,
windows[key].Item4,
windows[key].Item5,
windows[key].Item6
);
SecondaryWindows[key].Closed += (o, e) => { SecondaryWindows.Remove(key); };
SecondaryWindows[key].Show();
}
}
else
{
foreach (var key in SecondaryWindows.Keys.ToArray())
{
App.IsClosing = true;
SecondaryWindows[key].Close();
App.IsClosing = false;
}
}
}
private Fluent.RibbonTabItem GetTabItem(FrameworkElement? sender)
{
if (sender == null)
throw new Exception("No Tab Found!");
if (sender is Fluent.RibbonTabItem)
return (Fluent.RibbonTabItem)sender;
return GetTabItem(sender.Parent as FrameworkElement);
}
private IEnumerable Panels
{
get
{
if(PanelHost.CurrentPanel is not null)
{
yield return PanelHost.CurrentPanel;
}
foreach(var window in SecondaryWindows.Values)
{
yield return window.Panel;
}
}
}
#region LoadWindow / LoadSecondaryWindow
private T? LoadWindow(Fluent.Button sender) where T : class, IBasePanel, new()
{
return LoadWindow(sender, new CancelEventArgs());
}
public IBasePanel? LoadWindow(Type t, Fluent.Button sender, CancelEventArgs cancel)
{
using (new WaitCursor())
{
UnloadWindow(cancel);
if (cancel.Cancel)
{
return null;
}
CurrentTab = GetTabItem(sender);
CurrentButton = sender;
//CurrentButton.IsSelected = true;
UpdateRibbonColors();
var moduleName = sender.Header?.ToString() ?? "";
var panel = PanelHost.LoadPanel(t, moduleName);
ContentControl.Content = panel;
Title =
$"{moduleName} - {(String.Equals(App.Profile?.ToUpper(), "DEFAULT") ? "PRS Desktop" : App.Profile)} (Release {CoreUtils.GetVersion()})";
PanelHost.Refresh();
if (panel is NotificationPanel)
{
Logger.Send(LogType.Information, ClientFactory.UserID, "Disabling Heartbeat");
NotificationsWatchDog.IsEnabled = false;
Notifications.Visibility = Visibility.Collapsed;
DockingGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Pixel);
DockingGrid.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Pixel);
}
else
{
ReloadNotifications();
}
if (sender != null)
{
var settings = new LocalConfiguration().Load();
var module = string.Format("{0} / {1}", CurrentTab?.Header, sender.Header);
if (!settings.Settings.ContainsKey("CurrentPanel") || module != settings.Settings["CurrentPanel"])
{
settings.Settings["CurrentPanel"] = module;
try
{
new LocalConfiguration().Save(settings);
}
catch (Exception e)
{
Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
}
}
}
return panel;
}
}
private T? LoadWindow(Fluent.Button sender, CancelEventArgs cancel) where T : class, IBasePanel, new()
{
return LoadWindow(typeof(T), sender, cancel) as T;
}
private void LoadSecondaryWindow(Type t, Fluent.Button sender)
{
var id = Guid.NewGuid();
var window = new Tuple(
t.EntityName(),
sender.Header?.ToString() ?? "",
Left + 100,
Top + 100,
Width - 200,
Height - 200
);
App.DatabaseSettings.SecondaryWindows[id] = window;
new LocalConfiguration(App.Profile).Save(App.DatabaseSettings);
SecondaryWindows[id] = new SecondaryWindow(
id,
window.Item1,
window.Item2,
window.Item3,
window.Item4,
window.Item5,
window.Item6
);
SecondaryWindows[id].Show();
}
private void LoadSecondaryWindow(Fluent.Button sender) where T : class, IBasePanel, new()
=> LoadSecondaryWindow(typeof(T), sender);
// private void SecondaryWindow_Click(object sender, RoutedEventArgs e)
// {
// var panel = PanelHost.CurrentPanel;
// if (panel is null) return;
//
// var id = Guid.NewGuid();
// var window = new Tuple(
// panel.GetType().EntityName(),
// PanelHost.CurrentModuleName,
// Left + 100,
// Top + 100,
// Width - 200,
// Height - 200
// );
// App.DatabaseSettings.SecondaryWindows[id] = window;
// new LocalConfiguration(App.Profile).Save(App.DatabaseSettings);
// SecondaryWindows[id] = new SecondaryWindow(
// id,
// window.Item1,
// window.Item2,
// window.Item3,
// window.Item4,
// window.Item5,
// window.Item6
// );
// SecondaryWindows[id].Show();
// }
#endregion
private void UpdateRibbonColors()
{
foreach (var tab in _ribbon.Tabs)
{
bool bFound = false;
foreach (var grp in tab.Groups)
{
foreach (var btn in grp.Items)
{
if (btn is Fluent.Button fluentbutton)
{
bFound = bFound || (btn == CurrentButton);
fluentbutton.Background = (btn == CurrentButton) ? ThemeManager.SelectedTabItemBackgroundBrush : new SolidColorBrush(Colors.White);
fluentbutton.Foreground = (btn == CurrentButton) ? ThemeManager.SelectedTabItemForegroundBrush : new SolidColorBrush(Colors.Black);
}
}
tab.Background = bFound ? ThemeManager.SelectedTabItemBackgroundBrush : new SolidColorBrush(Colors.White);
tab.Foreground = bFound ? ThemeManager.SelectedTabItemForegroundBrush : new SolidColorBrush(Colors.Black);
}
}
}
private static void StartLocalDatabase(IProgress progress)
{
var dirName = Path.GetDirectoryName(App.DatabaseSettings.FileName);
if (!Directory.Exists(dirName) && dirName != null)
Directory.CreateDirectory(dirName);
var FileName = App.DatabaseSettings.FileName;
var Exists = File.Exists(FileName);
progress.Report("Configuring Stores");
DbFactory.Stores = CoreUtils.TypeList(
new[]
{
typeof(Store<>).Assembly,
typeof(EquipmentStore).Assembly
},
myType =>
myType.IsClass
&& !myType.IsAbstract
&& !myType.IsGenericType
&& myType.GetInterfaces().Contains(typeof(IStore))
).ToArray();
DbFactory.DefaultStore = typeof(BaseStore<>);
DbFactory.ProviderFactory = new SQLiteProviderFactory(App.DatabaseSettings.FileName);
DbFactory.ColorScheme = App.DatabaseSettings.ColorScheme;
DbFactory.Logo = App.DatabaseSettings.Logo;
progress.Report("Starting Local Database");
DbFactory.Start();
ClientFactory.DatabaseID = DbFactory.ID;
progress.Report("Checking Database");
var users = DbFactory.NewProvider(Logger.Main).Load();
if (!users.Any())
{
var user = new User { UserID = "ADMIN", Password = "admin" };
DbFactory.NewProvider(Logger.Main).Save(user);
var employee = DbFactory.NewProvider(Logger.Main).Load(Filter.Where(x => x.Code).IsEqualTo("ADMIN")).FirstOrDefault();
employee ??= new Employee { Code = "ADMIN", Name = "Administrator Account" };
employee.UserLink.ID = user.ID;
DbFactory.NewProvider(Logger.Main).Save(employee);
}
CoreUtils.GoogleAPIKey = App.DatabaseSettings.GoogleAPIKey;
Job.JobNumberPrefix = App.DatabaseSettings.JobPrefix;
PurchaseOrder.PONumberPrefix = App.DatabaseSettings.PurchaseOrderPrefix;
}
#endregion
#region Login Management
private ValidationStatus? TryAutoLogin()
{
ValidationStatus? result = null;
if (App.DatabaseSettings.LoginType == LoginType.UserID)
{
try
{
result = ClientFactory.Validate(App.DatabaseSettings.UserID, App.DatabaseSettings.Password);
}
catch (Exception e)
{
MessageWindow.ShowError("Error connecting to server.\nPlease check the server URL and port number.", e);
result = null;
}
if (result == ValidationStatus.INVALID)
{
MessageWindow.ShowMessage("Unable to Login with User ID: " + App.DatabaseSettings.UserID, "Login failed");
}
}
return result;
}
private ValidationStatus? DoLogin()
{
ValidationStatus? result = null;
if (result != ValidationStatus.VALID)
{
var login = new PinLogin(CoreUtils.GetVersion(), result ?? ValidationStatus.INVALID);
if (login.ShowDialog() == true)
{
result = ValidationStatus.VALID;
}
}
return result ?? ValidationStatus.INVALID;
}
///
/// To be called after and if a valid login session exists. Configures the main screen and loads the windows.
///
/// If not , then rather than opening a new progress window, just uses that.
private void AfterLogin(IProgress? progress)
{
try
{
Logger.Send(LogType.Information, "", "Checking Support Ticket Status");
CheckSupportTicketStatus();
Logger.Send(LogType.Information, "", "Loading employee");
LoadCurrentEmployee();
if (CheckTimesheetBypass(true))
{
UpdateCurrentLogin();
}
else
{
Dispatcher.Invoke(() =>
{
MessageWindow.ShowMessage("You must clock on before logging in to PRS!", "Not clocked in.");
});
ClientFactory.InvalidateUser();
App.EmployeeID = Guid.Empty;
App.EmployeeName = "";
App.EmployeeCode = "";
App.EmployeeEmail = "";
}
Logger.Send(LogType.Information, "", "Setting colours");
if (progress is null)
{
ApplyColorScheme();
}
else
{
Dispatcher.Invoke(ApplyColorScheme);
}
Logger.Send(LogType.Information, "", "Configuring main window");
ConfigureMainScreen(progress);
Logger.Send(LogType.Information, "", "Loading current window");
if (progress is null)
{
LoadApplicationState();
}
else
{
Dispatcher.Invoke(LoadApplicationState);
}
Logger.Send(LogType.Information, "", "Loading secondary window");
if (progress is null)
{
LoadSecondaryWindows();
}
else
{
Dispatcher.Invoke(LoadSecondaryWindows);
}
}
catch (Exception e)
{
}
}
///
/// Creates a new if one does not already exist. Otherwise, updates the entry in the database with new Station ID.
///
private void UpdateCurrentLogin()
{
if (CoreUtils.GetVersion().Equals("???"))
return;
// Register this station with the Server
// Later on, the heartbeat will check to make sure
// that the StationID hasn't changed. If it has,
// then we've logged in somewhere else and we'll
// drop out of this station
var curr = new Client().Query(
Filter.Where(x => x.User.ID).IsEqualTo(ClientFactory.UserGuid),
null
).Rows.FirstOrDefault();
if (curr != null)
{
var c = curr.ToObject();
c.StationID = station.StationID;
station = c;
}
else
{
station.User.ID = ClientFactory.UserGuid;
station.TimeStamp = DateTime.Now;
}
new Client().Save(station, "", (o, e) => { });
}
private void LoadCurrentEmployee()
{
var me = new Client().Query(
Filter.Where(x => x.UserLink.ID).IsEqualTo(ClientFactory.UserGuid),
Columns.None().Add(x => x.ID).Add(x=>x.Code).Add(x => x.Email).Add(x => x.Name)
);
App.EmployeeID = me.Rows.FirstOrDefault()?.Get(x => x.ID) ?? Guid.Empty;
App.EmployeeCode = me.Rows.FirstOrDefault()?.Get(x => x.Code) ?? "";
App.EmployeeName = me.Rows.FirstOrDefault()?.Get(x => x.Name) ?? "";
App.EmployeeCode = me.Rows.FirstOrDefault()?.Get(x => x.Code) ?? "";
App.EmployeeEmail = me.Rows.FirstOrDefault()?.Get(x => x.Email) ?? "";
}
private void ExecuteLogout()
{
new Client().Delete(station, "");
station.ID = Guid.Empty;
App.EmployeeID = Guid.Empty;
App.EmployeeName = "";
App.EmployeeEmail = "";
}
///
/// Logs the user out and unloads windows
///
/// A message to display as the reason for logging out, or null for no message.
private bool Logout(string? message = null, bool force = false)
{
// I really don't like all these try-catch blocks; unfortunately, if we are trying to log out and invalidate due to an unauthenticated user,
// all the queries that get called here will throw exceptions and thus break our system, failing to log out.
try
{
FinalizeAutoTimesheet();
}
catch
{
if (!force) throw;
}
// Try to unload the window;
try
{
UnloadWindow(null);
if (DatabaseType == DatabaseType.Standalone && !CoreUtils.GetVersion().Equals("???"))
scheduler.Stop();
}
catch
{
if (!force) throw;
}
// Next, try to set things to being empty
try
{
if (!CoreUtils.GetVersion().Equals("???"))
if (station.ID != Guid.Empty)
{
ExecuteLogout();
}
ClearTrackingKanban();
}
catch
{
if (!force) throw;
}
ClientFactory.InvalidateUser();
ConfigureMainScreen(null);
LoadSecondaryWindows();
if (message != null)
{
MessageWindow.ShowMessage(message, "Logged out");
}
if (DoLogin() == ValidationStatus.VALID)
{
AfterLogin(null);
}
return true;
}
#endregion
#region Timesheets
private void RefreshTimeSheets()
{
if (App.EmployeeID == Guid.Empty)
return;
var filter = Filter.Where(x => x.EmployeeLink.ID).IsEqualTo(App.EmployeeID);
filter = filter.And(Filter.Where(x => x.Confirmed).IsEqualTo(DateTime.MinValue).Or(x => x.Date).IsEqualTo(DateTime.Today));
_timesheets = new Client().Query(
filter,
Columns.None().Add(
x => x.ID,
x => x.Date,
x => x.Finish
)
);
}
private CoreTable GetTimesheet()
{
return new Client().Query(
Filter.Where(x => x.Date).IsEqualTo(DateTime.Today)
.And(x => x.EmployeeLink.ID).IsEqualTo(App.EmployeeID)
.And(x => x.Finish).IsEqualTo(TimeSpan.Zero)
);
}
private bool CheckTimesheetBypass(bool message)
{
var isClockedOn = IsClockedOn();
if (!Security.IsAllowed())
{
if (!isClockedOn)
{
return false;
}
return true;
}
if (Security.IsAllowed