using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Comal.Classes;
using InABox.Clients;
using InABox.Configuration;
using InABox.Core;
using InABox.Mobile;
using InABox.Rpc;
namespace PRS.Mobile
{
public class GPSEventArgs : EventArgs
{
}
public delegate void GPSLocationUpdatedEvent(GPSEventArgs args);
public class BluetoothEventArgs : EventArgs
{
}
public delegate void BluetoothScanFinishedEvent(BluetoothEventArgs args);
public class DataModel : IModelHost
{
public event GPSLocationUpdatedEvent GPSLocationUpdated;
public event BluetoothScanFinishedEvent BluetoothScanFinished;
///
/// All Active Employees of the Company
///
public EmployeeDetailModel CurrentEmployee { get; private set; }
private readonly object _employeelock = new object();
private EmployeeDetailShell _me;
///
/// EmployeeDetails for Currently Logged in User
///
public EmployeeDetailShell Me
{
get
{
lock (_employeelock)
{
if (_me == null)
{
CurrentEmployee.Refresh(false);
_me = CurrentEmployee.FirstOrDefault(x => x.UserID == ClientFactory.UserGuid);
}
}
return _me;
}
}
///
/// All Active Employees of the Company
///
public EmployeeModel Employees { get; private set; }
///
/// All Employee Teams for the Company
/// Should this be just teams I am a part of, or all available teams
/// Need to implement a Security Token to differentiate
///
public EmployeeTeamModel EmployeeTeams { get; private set; }
///
/// Current Employee Form Instances for this employee
///
public EmployeeFormModel EmployeeForms { get; private set; }
public EmployeeQualificationModel EmployeeQualifications { get; private set; }
///
/// Available Activity Codes for this Employee
///
public ActivityModel Activities { get; private set; }
///
/// All Available Contacts - used in Deliveries
///
public ContactModel Contacts { get; private set; }
///
/// All Defined Bluetooth-enabled Gates
///
public BluetoothGateModel BluetoothGates { get; private set; }
///
/// Unprocessed Timesheets for Currently Logged in User
///
public TimeSheetModel TimeSheets { get; private set; }
///
/// List of Jobs that Currently Logged in User has access To
///
public JobModel Jobs { get; private set; }
///
/// List of Assignments for ??? need to specify this.
/// Can I see other people's assignments? (Security Token)
/// How far back / forward can I look.
/// This smells Transient, or maybe split into lookups (for all)
/// and shells (for mine)
/// Possibly need a back/forward window as well?
///
public AssignmentModel Assignments { get; private set; }
///
/// Master Product Catalogue for the company
/// This should be split into Lookups and Shells
///
public ProductModel Products { get; private set; }
///
/// Master Supplier List for Lookups, etc
///
public SupplierModel Suppliers { get; private set; }
public ConsignmentTypeModel ConsignmentTypes { get; private set; }
///
/// Master List of Dimension Units for the company
///
public ProductDimensionUnitModel ProductDimensionUnits { get; private set; }
///
/// Master Product Groups List (Lookup?)
///
public ProductGroupModel ProductGroups { get; private set; }
///
/// Master Product Styles List (Lookup?)
///
public ProductStyleModel ProductStyles { get; private set; }
///
/// All GPS Tracker Devices registered by the company
///
public GPSTrackerModel GPSTrackers { get; private set; }
///
/// Master Equipment Register
///
public EquipmentModel Equipment { get; private set; }
///
/// Master List of Equipment Groups
///
public EquipmentGroupModel EquipmentGroups { get; private set; }
///
/// Master List of Available Delivery Types
///
public DeliveryTypeModel DeliveryTypes { get; private set; }
///
/// List of all Deliveries
/// hmmm.. should be configurable?
///
public DeliveryModel Deliveries { get; private set; }
///
/// List of all available Delivery Barcodes
/// hmmm.. should be configurable?
///
public DeliveryItemModel DeliveryItems { get; private set; }
public RequisitionDestinationModel RequisitionDestinations { get; private set; }
///
/// My Notifications (open only?)
///
public NotificationModel Notifications { get; private set; }
///
/// All Tasks I am subscribed to
///
public KanbanModel Kanbans { get; private set; }
///
/// All Kanban Types
///
public KanbanTypeModel KanbanTypes { get; private set; }
///
/// Available Form Library - this includes all "Applies To" values
/// You can filter to a specific type by using the "Search" function
///
public DigitalFormModel DigitalForms { get; private set; }
// The list of open task-kased forms for the current user
// Specifically for us in the "Forms" module
// This overlaps the "Task" module a fair amount, but it provides
// an alternative paradigm for tasks
public KanbanFormModel KanbanForms { get; private set; }
///
/// List of Employees, along with Clock on / off status
///
public InOutModel InOut { get; private set; }
///
/// List of Open Purchase Orders
///
public PurchaseOrderModel PurchaseOrders { get; private set; }
public ManufacturingFactoryModel ManufacturingFactories { get; private set; }
public StockLocationModel StockLocations { get; set; }
public StockAreaModel StockAreas { get; set; }
public StockWarehouseModel StockWarehouses { get; set; }
public DataModel()
{
Reset();
}
public void Reset()
{
_me = null;
CurrentEmployee = new EmployeeDetailModel(this,
() => new Filter(x => x.UserLink.ID).IsEqualTo(ClientFactory.UserGuid)
);
Employees = new EmployeeModel(this,
LookupFactory.DefineFilter
);
EmployeeTeams = new EmployeeTeamModel(this,
() => new Filter(x => x.EmployeeLink.ID)
.InQuery(LookupFactory.DefineFilter(), x => x.ID)
);
EmployeeForms = new EmployeeFormModel(this,
() => new Filter(x => x.Parent.ID).IsEqualTo(App.Data.Me.ID)
);
EmployeeQualifications = new EmployeeQualificationModel(this,
() => new Filter(x => x.Employee.ID).IsEqualTo(App.Data.Me.ID)
);
Activities = new ActivityModel(this,
() => new Filter(x => x.Employee.ID).IsEqualTo(App.Data.Me.ID)
);
InOut = new InOutModel(this,
() => new Filters()
.Add(LookupFactory.DefineFilter())
.Add(new Filter(x=>x.ID).IsNotEqualTo(App.Data.Me.ID).And(x=>x.ShowOnInOutBoard).IsEqualTo(true))
.Combine()!
);
Jobs = new JobModel(this,
MyJobsFilter,
CoreRepository.CacheFileName
);
Products = new ProductModel(this,
LookupFactory.DefineFilter,
CoreRepository.CacheFileName
);
Suppliers = new SupplierModel(this,
LookupFactory.DefineFilter,
CoreRepository.CacheFileName
);
ConsignmentTypes = new ConsignmentTypeModel(this,
LookupFactory.DefineFilter,
CoreRepository.CacheFileName
);
ProductDimensionUnits = new ProductDimensionUnitModel(this,
LookupFactory.DefineFilter,
() => null);
ProductGroups = new ProductGroupModel(this,
LookupFactory.DefineFilter,
CoreRepository.CacheFileName
);
ProductStyles = new ProductStyleModel(this,
LookupFactory.DefineFilter
);
GPSTrackers = new GPSTrackerModel(this,
LookupFactory.DefineFilter
);
Equipment = new EquipmentModel(this,
() =>
{
var filters = new Filters();
filters.Add(LookupFactory.DefineFilter());
if (!Security.IsAllowed())
filters.Add(new Filter(x => x.Private).IsEqualTo(false));
return filters!.Combine();
},
CoreRepository.CacheFileName
);
EquipmentGroups = new EquipmentGroupModel(this,
LookupFactory.DefineFilter
);
DeliveryTypes= new DeliveryTypeModel(this,
null,
CoreRepository.CacheFileName);
Deliveries = new DeliveryModel(this);
DeliveryItems = new DeliveryItemModel(this,
LookupFactory.DefineFilter
);
RequisitionDestinations = new RequisitionDestinationModel(this,
null,
CoreRepository.CacheFileName
);
Notifications = new NotificationModel(this,
() => new Filter(x=>x.Employee.ID).IsEqualTo(App.Data.Me.ID)
.And(new Filter(x=>x.Closed).IsEqualTo(DateTime.MinValue)
.Or(x=>x.Created).IsGreaterThanOrEqualTo(DateTime.Today.AddDays(-90)))
);
TimeSheets = new TimeSheetModel(this,
() => new Filter(x=>x.Processed).IsEqualTo(DateTime.MinValue)
.And(x=>x.EmployeeLink.ID).IsEqualTo(App.Data.Me.ID)
);
Assignments = new AssignmentModel(this,
() => new Filter(x => x.Date).IsGreaterThanOrEqualTo(DateTime.Today.AddDays(-90))
.And(x => x.Date).IsLessThanOrEqualTo(DateTime.Today.AddDays(90))
);
Contacts = new ContactModel(this,
() => LookupFactory.DefineFilter()
);
Kanbans = new KanbanModel(this,
() => new Filter(x => x.ID)
.InQuery(new Filter(x=>x.Employee.ID).IsEqualTo(App.Data.Me.ID), x=>x.Kanban.ID)
.And(
new Filter(x=>x.Status).IsNotEqualTo(KanbanStatus.Complete)
.Or(x=>x.Completed).IsGreaterThanOrEqualTo(DateTime.Today.AddDays(-7)
)
),
CoreRepository.CacheFileName
);
KanbanTypes = new KanbanTypeModel(this);
BluetoothGates = new BluetoothGateModel(this,
() => new Filter(x => x.Active).IsEqualTo(true)
);
DigitalForms = new DigitalFormModel(this,
() => new Filter(x => x.Employee.ID).IsEqualTo(App.Data.Me.ID)
.And(x => x.Form.Active).IsEqualTo(true)
);
KanbanForms = new KanbanFormModel(this,
() => new Filter(x=>x.Parent.EmployeeLink.ID).IsEqualTo(App.Data.Me.ID)
.And(new Filter(x=>x.FormCompleted).IsEqualTo(DateTime.MinValue)
.Or(x=>x.FormCompleted).IsGreaterThanOrEqualTo(DateTime.Today.AddDays(-7)))
);
PurchaseOrders = new PurchaseOrderModel(this,
() => new Filter(x => x.ClosedDate).IsEqualTo(DateTime.MinValue)
);
ManufacturingFactories = new ManufacturingFactoryModel(this);
StockLocations = new StockLocationModel(this,
null,
CoreRepository.CacheFileName
);
StockAreas = new StockAreaModel(this,
null,
CoreRepository.CacheFileName
);
StockWarehouses = new StockWarehouseModel(this,
null,
CoreRepository.CacheFileName
);
}
private static Filter MyJobsFilter()
{
return Security.IsAllowed()
? new Filter(X => X.Completed).IsEqualTo(DateTime.MinValue)
.And(x => x.JobStatus.Active).IsEqualTo(true)
: new Filter(X => X.Completed).IsEqualTo(DateTime.MinValue)
.And(x => x.JobStatus.Active).IsEqualTo(true)
.And(x => x.ID).InQuery(
new Filter(x => x.EmployeeLink.ID).IsEqualTo(App.Data.Me.ID),
x => x.JobLink.ID
);
}
public void Setup()
{
// Clear All Transient Caches
Reset();
// Always load the current Employee and Security Tokens
Task[] init = new Task[]
{
Task.Run(() => { CurrentEmployee.Refresh(true); }),
Task.Run(() => { Security.CheckTokens(); })
};
Task.WaitAll(init);
App.Bluetooth.OnScanFinished += OnBluetoothScanFinished;
App.GPS.OnLocationFound += OnGPSLocationFound;
App.Transport.OnOpen += OnTransportConnected;
App.Transport.OnClose += OnTransportDisconnected;
DigitalFormDocumentFactory.Init(
new MobileDigitalFormDocumentHandler(
b =>
{
IsBackgroundUpdateStatusActive = b;
BackgroundUpdateStatusChanged?.Invoke(this, EventArgs.Empty);
})
);
DigitalFormDocumentFactory.Run();
StartUpdateQueue();
}
public bool IsBackgroundUpdateStatusActive { get; private set; }
public event BackgroundUpdateStatusEvent BackgroundUpdateStatusChanged;
public bool IsConnected() => App.Transport?.IsConnected() == true;
public event TransportDisconnectedEvent TransportDisconnected;
public event TransportConnectedEvent TransportConnected;
private void OnTransportDisconnected(IRpcTransport transport, RpcTransportCloseArgs e)
{
DigitalFormDocumentFactory.Stop();
TransportDisconnected?.Invoke(new TransportDisconnectedEventArgs());
Task.Run(() =>
{
ValidationStatus status = ValidationStatus.INVALID;
while (status != ValidationStatus.VALID)
{
try
{
while (!IsConnected())
{
try
{
App.Transport.Connect();
}
catch (Exception eConnect)
{
MobileLogging.Log($"Reconnect() {eConnect.Message} \n {eConnect.StackTrace}");
Task.Delay(TimeSpan.FromMilliseconds(1000));
}
}
status = ClientFactory.Validate(ClientFactory.SessionID);
if (status != ValidationStatus.VALID)
{
ClientFactory.InvalidateUser();
var settings = new LocalConfiguration().LoadAll().Values
.FirstOrDefault(x => x.IsDefault);
if (settings != null)
{
var userid = settings.UserID;
var password = settings.Password;
status = ClientFactory.Validate(userid, password);
if (status != ValidationStatus.VALID)
break;
}
}
}
catch (Exception ex)
{
MobileLogging.Log($"Revalidate() {ex.Message} \n {ex.StackTrace}");
Task.Delay(TimeSpan.FromMilliseconds(1000));
}
}
DigitalFormDocumentFactory.Run();
});
}
private void OnTransportConnected(IRpcTransport transport, RpcTransportOpenArgs e)
{
TransportConnected?.Invoke(new TransportConnectedEventArgs());
}
#region QueueUpdaterSupport
private CancellationTokenSource? _cancelQueueUpdate;
private List _updateQueues = new();
private void StartUpdateQueue()
{
StopUpdateQueue();
_cancelQueueUpdate = new CancellationTokenSource();
Task.Run(
() =>
{
while (_cancelQueueUpdate?.IsCancellationRequested != true)
{
foreach (var queue in _updateQueues.ToArray())
queue.ProcessQueue();
Thread.Sleep(100);
}
},
_cancelQueueUpdate.Token
);
}
private void StopUpdateQueue()
{
if (_cancelQueueUpdate != null)
{
_cancelQueueUpdate.Cancel();
_cancelQueueUpdate.Dispose();
_cancelQueueUpdate = null;
}
}
public void AddUpdateQueue(QueueUpdater queue)
where TEntity : Entity, IPersistent, IRemotable, new()
{
_updateQueues.Add(queue);
}
public void RemoveUpdateQueue(QueueUpdater queue)
where TEntity : Entity, IPersistent, IRemotable, new()
{
_updateQueues.Remove(queue);
}
#endregion
private Location _lastgpslocation = new Location();
private GPSTrackerLocation GetGPSTrackerUpdate(string deviceid, Location location,
TimeSpan threshold, double distance)
{
GPSTrackerShell tracker = GPSTrackers.FirstOrDefault(x => x.DeviceID.Equals(deviceid));
if (tracker != null)
{
if ((tracker.Timestamp < location.Timestamp.Subtract(threshold)) || (tracker.Location.DistanceTo(location, UnitOfLength.Kilometers) > distance))
{
GPSTrackerLocation gpsTrackerLocation = new GPSTrackerLocation();
gpsTrackerLocation.DeviceID = tracker.DeviceID;
gpsTrackerLocation.Tracker.ID = tracker.ID;
gpsTrackerLocation.Location = location;
return gpsTrackerLocation;
}
}
return null;
}
private void OnGPSLocationFound(LocationServices sender)
{
if (_lastgpslocation.Timestamp < DateTime.Now.Subtract(TimeSpan.FromMinutes(2)))
{
var devicelocation = new InABox.Core.Location()
{
Latitude = App.GPS.Latitude,
Longitude = App.GPS.Longitude,
Timestamp = App.GPS.TimeStamp,
Address = App.GPS.Address
};
GPSTrackers.Refresh(false);
var update = GetGPSTrackerUpdate(MobileUtils.GetDeviceID(), devicelocation, TimeSpan.FromMinutes(2),
0.1);
if (update != null)
new Client().Save(update, "Updated via Mobile Device", (o, e) => { });
}
GPSLocationUpdated?.Invoke(new GPSEventArgs());
}
private void OnBluetoothScanFinished(Bluetooth sender)
{
UploadTiles();
BluetoothScanFinished?.Invoke(new BluetoothEventArgs());
}
private void UploadTiles()
{
try
{
if (App.GPS.Latitude.Equals(0.0F) && App.GPS.Longitude.Equals(0.0F))
return;
if (App.Bluetooth.DetectedBlueToothMACAddresses.Count == 0)
return;
var devicelocation = new InABox.Core.Location()
{
Latitude = App.GPS.Latitude,
Longitude = App.GPS.Longitude,
Timestamp = App.GPS.TimeStamp,
Address = App.GPS.Address
};
GPSTrackers.Refresh(true);
List updates = new List();
foreach (String deviceid in App.Bluetooth.DetectedBlueToothMACAddresses)
{
var update = GetGPSTrackerUpdate(deviceid, devicelocation, TimeSpan.FromMinutes(2), 0.1);
if (update != null)
updates.Add(update);
}
if (updates.Any())
new Client().Save(updates, $"Updated by Mobile {MobileUtils.GetDeviceID()}",
(o, e) => { });
}
catch (Exception ex)
{
MobileLogging.Log($"UploadTiles() {ex.Message} \n {ex.StackTrace}");
}
}
}
}