MainPage.xaml.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using System.Windows.Input;
  7. using Xamarin.Forms;
  8. using InABox.Core;
  9. using InABox.Clients;
  10. using InABox.Mobile;
  11. using Comal.Classes;
  12. using InABox.Configuration;
  13. using JetBrains.Annotations;
  14. using Xamarin.Forms.Xaml;
  15. using XF.Material.Forms.UI.Dialogs;
  16. using Comal.Classes.SecurityDescriptors;
  17. namespace PRS.Mobile
  18. {
  19. public class MainPageSettings : ILocalConfigurationSettings
  20. {
  21. public bool ClockOnDisabled
  22. {
  23. get;
  24. set;
  25. }
  26. public MainPageSettings()
  27. {
  28. ClockOnDisabled = true;
  29. }
  30. }
  31. public partial class MainPage
  32. {
  33. #region Fields
  34. private readonly MainPageSettings _settings;
  35. private TimeSheet _currenttimesheet;
  36. #endregion
  37. #region Constructor
  38. private readonly Task[] _setuptasks;
  39. public MainPage()
  40. {
  41. _settings = new LocalConfiguration<MainPageSettings>().Load();
  42. _setuptasks = new Task[]
  43. {
  44. Task.Run(() => App.Data.GPSTrackers.Refresh(false)),
  45. Task.Run(() => App.Data.BluetoothGates.Refresh(false)),
  46. Task.Run(() => CheckTimeSheet())
  47. };
  48. App.Data.GPSLocationUpdated += OnGPSLocationUpdated;
  49. App.Data.BluetoothScanFinished += OnBluetothScanFinished;
  50. InitializeComponent();
  51. ProgressVisible = true;
  52. // LocalNotificationCenter.Current.NotificationActionTapped += (Plugin.LocalNotification.EventArgs.NotificationActionEventArgs e) =>
  53. // {
  54. // if (MainPageUtils.DetermineCorrectPage(e) != null)
  55. // {
  56. // Device.BeginInvokeOnMainThread(() =>
  57. // {
  58. // Navigation.PushAsync(MainPageUtils.DetermineCorrectPage(e));
  59. // });
  60. // }
  61. // };
  62. //
  63. // MainPageUtils.OnMainPageNotificationsChanged += RefreshOnNotificationsChange;
  64. MessagingCenter.Subscribe<App>(this, App.MessageOnResume,
  65. (o) =>
  66. {
  67. if (!App.GPS.RecentlyLocated)
  68. App.GPS.GetLocation();
  69. RefreshScreen();
  70. }
  71. );
  72. }
  73. #endregion
  74. #region OnAppearing and Display
  75. private SelectionPage _selectionpage;
  76. protected override void OnAppearing()
  77. {
  78. _pollingToken = new CancellationTokenSource();
  79. StartMonitoringNotifications();
  80. StartMonitoringTasks();
  81. StartMonitoringVersion();
  82. StartMonitoringEmpoyeeDetails();
  83. //ProgressVisible = false;
  84. if (_selectionpage == null)
  85. {
  86. //LoadHRToDos(); to be uncommented when ready for roll out
  87. CheckTimeSheet();
  88. }
  89. _selectionpage = null;
  90. Task.WaitAll(_setuptasks);
  91. ProgressVisible = false;
  92. RefreshScreen();
  93. base.OnAppearing();
  94. }
  95. protected override void OnDisappearing()
  96. {
  97. base.OnDisappearing();
  98. _pollingToken.Cancel();
  99. }
  100. private void OnBluetothScanFinished(BluetoothEventArgs args)
  101. {
  102. Dispatcher.BeginInvokeOnMainThread(RefreshScreen);
  103. }
  104. private void OnGPSLocationUpdated(GPSEventArgs args)
  105. {
  106. Dispatcher.BeginInvokeOnMainThread(RefreshScreen);
  107. }
  108. void SetupClockOnButton(bool disable)
  109. {
  110. if (disable != _settings.ClockOnDisabled)
  111. {
  112. _settings.ClockOnDisabled = disable;
  113. new LocalConfiguration<MainPageSettings>().Save(_settings);
  114. }
  115. SplashCard.IsVisible = disable;
  116. ClockOnButton.IsVisible = !disable;
  117. jobBtn.IsVisible = !disable;
  118. addNoteBtn.IsVisible = !disable;
  119. taskBtn.IsVisible = !disable;
  120. }
  121. private void SetupModules()
  122. {
  123. SetupClockOnButton(Security.IsAllowed<IsJobOnlyEmployee>());
  124. Assignments.IsVisible = Security.IsAllowed<ViewMobileAssignmentsModule>();
  125. Deliveries.IsVisible = Security.IsAllowed<ViewMobileDeliveriesModule>();
  126. Forms.IsVisible = Security.IsAllowed<ViewMobileFormsModule>();
  127. Equipment.IsVisible = Security.IsAllowed<ViewMobileEquipmentModule>();
  128. InOut.IsVisible = Security.IsAllowed<ViewMobileInOutModule>();
  129. Manufacturing.IsVisible = Security.IsAllowed<ViewMobileManufacturingModule>();
  130. Meetings.IsVisible = Security.IsAllowed<ViewMobileMeetingsModule>();
  131. Products.IsVisible = Security.IsAllowed<ViewMobileProductsModule>();
  132. PurchaseOrders.IsVisible = Security.IsAllowed<ViewMobilePurchaseOrdersModule>();
  133. StoreRequis.IsVisible = Security.IsAllowed<ViewMobileStoreRequisModule>();
  134. MyTasks.IsVisible = Security.IsAllowed<ViewMobileMyTasksModule>();
  135. Warehousing.IsVisible = Security.IsAllowed<ViewMobileWarehousingModule>();
  136. }
  137. protected override void UpdateTransportStatus()
  138. {
  139. base.UpdateTransportStatus();
  140. RefreshScreen();
  141. }
  142. private CancellationTokenSource _pollingToken = new CancellationTokenSource();
  143. private void StartMonitoringNotifications()
  144. {
  145. //notifications are allowed to upload once every 30 seconds
  146. // This will eventually be replaced with websocket pushes
  147. // but we're not ready for that yet :-(
  148. var token = _pollingToken.Token;
  149. Task.Run(
  150. () =>
  151. {
  152. while (!_pollingToken.Token.IsCancellationRequested)
  153. {
  154. App.Data.Notifications.Refresh(true);
  155. int _notificationcount = App.Data.Notifications.Items.Where(x=>x.Closed.IsEmpty()).ToList().Count;
  156. Dispatcher.BeginInvokeOnMainThread(() =>
  157. {
  158. Notifications.Alert = _notificationcount > 0
  159. ? _notificationcount.ToString()
  160. : "";
  161. });
  162. Task.Delay(TimeSpan.FromSeconds(30), token)
  163. .Wait(token);
  164. }
  165. },
  166. token
  167. );
  168. }
  169. private void StartMonitoringTasks()
  170. {
  171. var token = _pollingToken.Token;
  172. Task.Run(
  173. () =>
  174. {
  175. while (!_pollingToken.Token.IsCancellationRequested)
  176. {
  177. App.Data.Kanbans.Refresh(true);
  178. int _taskcount = App.Data.Kanbans.Items.Where(x=>String.Equals(x.Category, Kanban.OPEN)).ToList().Count;
  179. Dispatcher.BeginInvokeOnMainThread(() =>
  180. {
  181. MyTasks.Alert = _taskcount > 0
  182. ? _taskcount.ToString()
  183. : "";
  184. });
  185. Task.Delay(TimeSpan.FromSeconds(30), token)
  186. .Wait(token);
  187. }
  188. },
  189. token
  190. );
  191. }
  192. private void StartMonitoringVersion()
  193. {
  194. var token = _pollingToken.Token;
  195. Task.Run(
  196. async () =>
  197. {
  198. while (!_pollingToken.Token.IsCancellationRequested)
  199. {
  200. bool isLatest = true;
  201. try
  202. {
  203. isLatest = await MobileUtils.AppVersion.IsUsingLatestVersion();
  204. }
  205. catch (Exception e)
  206. {
  207. InABox.Mobile.MobileLogging.Log(e);
  208. }
  209. //bool isLatest = MobileUtils.AppVersion.IsUsingLatestVersion().Result;
  210. Dispatcher.BeginInvokeOnMainThread(() =>
  211. {
  212. if (Update.IsVisible == isLatest)
  213. {
  214. Update.IsVisible = !isLatest;
  215. Tools.Refresh();
  216. Update.Alert = isLatest
  217. ? ""
  218. : "!";
  219. }
  220. });
  221. Task.Delay(TimeSpan.FromSeconds(30), token)
  222. .Wait(token);
  223. }
  224. },
  225. token
  226. );
  227. }
  228. private void StartMonitoringEmpoyeeDetails()
  229. {
  230. var token = _pollingToken.Token;
  231. Task.Run(() =>
  232. {
  233. while (!_pollingToken.Token.IsCancellationRequested)
  234. {
  235. App.Data.EmployeeQualifications.Refresh(true);
  236. bool bQualificationIssues = App.Data.EmployeeQualifications.NeedsAttention > 0;
  237. App.Data.EmployeeForms.Refresh(true);
  238. bool bFormIssues = App.Data.EmployeeForms.NeedsAttention > 0;
  239. Dispatcher.BeginInvokeOnMainThread(() =>
  240. {
  241. MyHR.Alert = (bQualificationIssues || bFormIssues)
  242. ? "!"
  243. : "";
  244. });
  245. Task.Delay(TimeSpan.FromSeconds(30), token)
  246. .Wait(token);
  247. }
  248. },
  249. token
  250. );
  251. }
  252. private void RefreshScreen()
  253. {
  254. BackButtonEnabled = App.IsSharedDevice;
  255. SetupModules();
  256. SetupClockOnButton(_settings.ClockOnDisabled);
  257. bool GateReady = CheckLocation();
  258. var location = GetAddress();
  259. Title = App.Data.Me != null
  260. ? App.Data.Me.Name
  261. : "(Not Logged In)";
  262. ClockOnButton.IsEnabled = App.Data.IsConnected() && GateReady;
  263. ClockOnOffLabel.Text = GateReady ? _currenttimesheet == null ? "CLOCK ON" : "CLOCK OFF" : "PLEASE WAIT";
  264. CurrentLocation.Text = location.ToUpper().Contains("ERROR")
  265. ? "Unknown Address"
  266. : location;
  267. ClockOnButton.BackgroundColor = App.Data.IsConnected() && GateReady
  268. ? _currenttimesheet == null
  269. ? Color.FromHex("#e6e6fa")
  270. : Color.FromHex("#15C7C1")
  271. : Color.Silver;
  272. ClockOnButton.BorderColor = App.Data.IsConnected() && GateReady
  273. ? _currenttimesheet == null
  274. ? Color.FromHex("#96969a")
  275. : Color.FromHex("#059791")
  276. : Color.Gray;
  277. addNoteBtn.IsEnabled = App.Data.IsConnected() && _currenttimesheet != null;
  278. jobBtn.IsEnabled = App.Data.IsConnected() && _currenttimesheet != null;
  279. taskBtn.IsEnabled = App.Data.IsConnected() && _currenttimesheet != null;
  280. jobBtn.Text = ((_currenttimesheet?.JobID ?? Guid.Empty) != Guid.Empty)
  281. ? $"{_currenttimesheet?.JobLink.JobNumber}: {_currenttimesheet?.JobLink.Name}"
  282. : "Select Job";
  283. Assignments.IsEnabled = App.Data.IsConnected();
  284. Deliveries.IsEnabled = App.Data.IsConnected();
  285. Equipment.IsEnabled = App.Data.IsConnected();
  286. Manufacturing.IsEnabled = App.Data.IsConnected();
  287. Forms.IsEnabled = App.Data.IsConnected();
  288. InOut.IsEnabled = App.Data.IsConnected();
  289. MyHR.IsEnabled = App.Data.IsConnected();
  290. Meetings.IsEnabled = App.Data.IsConnected();
  291. Notifications.IsEnabled = App.Data.IsConnected();
  292. Products.IsEnabled = App.Data.IsConnected();
  293. PurchaseOrders.IsEnabled = App.Data.IsConnected();
  294. Site.IsEnabled = App.Data.IsConnected();
  295. StoreRequis.IsEnabled = App.Data.IsConnected();
  296. MyTasks.IsEnabled = App.Data.IsConnected();
  297. Warehousing.IsEnabled = App.Data.IsConnected();
  298. }
  299. #endregion
  300. #region Clock on/off
  301. private void CheckTimeSheet()
  302. {
  303. if (App.Data.IsConnected())
  304. {
  305. var client = new Client<TimeSheet>();
  306. var filter = new Filter<TimeSheet>(x => x.EmployeeLink.ID).IsEqualTo(App.Data.Me.ID)
  307. .And(x => x.Date).IsEqualTo(DateTime.Today)
  308. .And(x => x.Finish).IsEqualTo(TimeSpan.Zero);
  309. var table = client.Query(filter);
  310. _currenttimesheet = table.Rows.FirstOrDefault()?.ToObject<TimeSheet>();
  311. var token = _pollingToken.Token;
  312. Task.Run(
  313. () =>
  314. {
  315. while (!token.IsCancellationRequested)
  316. {
  317. if (_currenttimesheet != null)
  318. {
  319. if (_currenttimesheet.Date != DateTime.Today)
  320. {
  321. var here = GPSLocation();
  322. _currenttimesheet.FinishLocation = here;
  323. _currenttimesheet.Finish = TimeSpan.FromDays(1).Subtract(TimeSpan.FromSeconds(1));
  324. new Client<TimeSheet>().Save(_currenttimesheet,
  325. "Closing Timesheet at Modnight on Mobile Device");
  326. CreateTimeSheet(_currenttimesheet.JobLink.ID, _currenttimesheet.JobLink.JobNumber, _currenttimesheet.JobLink.Name, here, here.Address, "Creating Timeheet at Midnight on Mobile Device");
  327. }
  328. }
  329. Task.Delay(TimeSpan.FromMinutes(1), token).Wait(token);
  330. }
  331. },
  332. token
  333. );
  334. }
  335. }
  336. private DateTime _debounce = DateTime.MinValue;
  337. async void ClockOnOff_Clicked(object sender, EventArgs e)
  338. {
  339. if (_debounce > DateTime.Now.Subtract(TimeSpan.FromMilliseconds(500)))
  340. return;
  341. _debounce = DateTime.MaxValue;
  342. string chosenOption = _currenttimesheet != null
  343. ? await DisplayActionSheet("Clock off?", "Cancel", null, "Continue", "Cancel")
  344. : "Continue";
  345. if (!String.Equals(chosenOption, "Continue"))
  346. {
  347. _debounce = DateTime.Now;
  348. return;
  349. }
  350. try
  351. {
  352. using (await MaterialDialog.Instance.LoadingDialogAsync(message: $"Clocking {(_currenttimesheet == null ? "On" : "Off")}"))
  353. {
  354. var here = GPSLocation();
  355. if (_currenttimesheet != null)
  356. {
  357. FinishTimeSheet(here);
  358. }
  359. else
  360. {
  361. if (!Security.IsAllowed<CanBypassBluetoothGates>())
  362. {
  363. var tracker = App.Data.BluetoothGates.FirstOrDefault(x => App.Bluetooth.Devices.Contains(x.DeviceID));
  364. if (tracker != null)
  365. CreateTimeSheet(tracker.JobID, tracker.JobNumber, tracker.JobName, here, here.Address, "Clocking On");
  366. }
  367. else
  368. {
  369. if ((!here.Latitude.Equals(0.0F) && !here.Longitude.Equals(0.0F))
  370. || Security.IsAllowed<CanBypassGPSClockIn>())
  371. {
  372. var job = await ChooseNearbyJob(here);
  373. CreateTimeSheet(job?.ID ?? Guid.Empty, job?.JobNumber ?? "", job?.Name ?? "" , here, here.Address, "Clocking On");
  374. }
  375. }
  376. }
  377. Dispatcher.BeginInvokeOnMainThread(RefreshScreen);
  378. }
  379. }
  380. catch (Exception ex)
  381. {
  382. InABox.Mobile.MobileLogging.Log(ex);
  383. }
  384. _debounce = DateTime.Now;
  385. }
  386. private void FinishTimeSheet(Location here)
  387. {
  388. if ((_currenttimesheet == null) || (_currenttimesheet.ID == Guid.Empty))
  389. {
  390. _currenttimesheet = null;
  391. return;
  392. }
  393. try
  394. {
  395. if (ZeroLengthTimesheet())
  396. new Client<TimeSheet>().Delete(_currenttimesheet, "Deleted due to zero duration timesheet");
  397. else
  398. {
  399. _currenttimesheet.Finish =
  400. new TimeSpan(DateTime.Now.TimeOfDay.Hours, DateTime.Now.TimeOfDay.Minutes, 0);
  401. _currenttimesheet.FinishLocation = here;
  402. //bUpdatingTimesheet = true;
  403. new Client<TimeSheet>().Save(_currenttimesheet, "Clocking Off");
  404. }
  405. _currenttimesheet = null;
  406. }
  407. catch (Exception ex)
  408. {
  409. InABox.Mobile.MobileLogging.Log(ex);
  410. }
  411. }
  412. #endregion
  413. #region Utilities
  414. //private void CheckNotificationsPushed(CoreTable table)
  415. //{
  416. // try
  417. // {
  418. // if (!Application.Current.Properties.ContainsKey("LastPushedNotifications"))
  419. // {
  420. // Application.Current.Properties.Add("LastPushedNotifications", DateTime.Now);
  421. // }
  422. // DateTime lastPushed = DateTime.Parse(Application.Current.Properties["LastPushedNotifications"].ToString());
  423. // List<NotificationShell> toNotify = new List<NotificationShell>();
  424. // foreach (CoreRow row in table.Rows)
  425. // {
  426. // List<object> list = row.Values;
  427. // DateTime created = DateTime.Parse(list[3].ToString());
  428. // if (created > new DateTime(2022, 8, 22)) // prevent spam from buildup of old notifications before this is released
  429. // {
  430. // if (created > lastPushed)
  431. // {
  432. // if (list[1] == null) list[1] = "";
  433. // if (list[2] == null) list[2] = "";
  434. // if (list[3] == null) list[3] = DateTime.MinValue;
  435. // if (list[4] == null) list[4] = "";
  436. // if (list[5] == null) list[5] = "";
  437. // if (list[6] == null) list[6] = Guid.Empty;
  438. //
  439. // NotificationShell shell = new NotificationShell
  440. // {
  441. // ID = Guid.Parse(list[0].ToString()),
  442. // Sender = list[1].ToString(),
  443. // Title = list[2].ToString(),
  444. // Created = DateTime.Parse(list[3].ToString()),
  445. // EntityType = list[5].ToString(),
  446. // EntityID = Guid.Parse(list[6].ToString())
  447. // };
  448. // toNotify.Add(shell); //add notification to be pushed
  449. // }
  450. // }
  451. // }
  452. // if (toNotify.Count > 0)
  453. // PushNotificationsAsync(toNotify);
  454. // }
  455. // catch { }
  456. //}
  457. //private async Task PushNotificationsAsync(List<NotificationShell> shells)
  458. //{
  459. // try
  460. // {
  461. // int count = 1;
  462. //
  463. // foreach (NotificationShell shell in shells)
  464. // {
  465. // var notification = new NotificationRequest
  466. // {
  467. // BadgeNumber = 1,
  468. // Description = shell.Title,
  469. // Title = "New PRS Notification: ",
  470. // ReturningData = shell.EntityID.ToString() + "$" + shell.EntityType,
  471. // NotificationId = count,
  472. // };
  473. // count++;
  474. // NotificationImage img = new NotificationImage();
  475. // img.ResourceName = "icon16.png";
  476. // notification.Image = img;
  477. //
  478. // await LocalNotificationCenter.Current.Show(notification);
  479. // }
  480. // Application.Current.Properties["LastPushedNotifications"] = DateTime.Now;
  481. // }
  482. // catch { }
  483. //}
  484. private async Task<JobShell> ChooseNearbyJob(Location here)
  485. {
  486. var nearbyjobs = App.Data.Jobs.Where(x => here.DistanceTo(x.Location, UnitOfLength.Kilometers) < 1.0F)
  487. .ToArray();
  488. if (nearbyjobs.Length > 1)
  489. {
  490. Dictionary<String, JobShell> dict = new Dictionary<string, JobShell>();
  491. foreach (var job in nearbyjobs)
  492. dict[job.DisplayName] = job;
  493. string chosenOption = await DisplayActionSheet("Choose job site", "Cancel", null, dict.Keys.ToArray());
  494. if (string.IsNullOrEmpty(chosenOption) || chosenOption.Equals("Cancel"))
  495. return null;
  496. return dict[chosenOption];
  497. }
  498. return nearbyjobs.FirstOrDefault();
  499. }
  500. void AddNote_Tapped(object sender, EventArgs e)
  501. {
  502. if (_currenttimesheet == null)
  503. return;
  504. var notepage = new TimeSheetNotePage(_currenttimesheet);
  505. Navigation.PushAsync(notepage);
  506. }
  507. private void TaskBtn_Tapped(object sender, EventArgs e)
  508. {
  509. _selectionpage = new TaskSelectionPage( (task) =>
  510. {
  511. if (_currenttimesheet != null)
  512. {
  513. // Not sure hwat to do here...
  514. }
  515. }
  516. );
  517. Navigation.PushAsync(_selectionpage);
  518. }
  519. // private async void RequestUserInput(Guid taskID)
  520. // {
  521. // const string addtask = "Change current assignment task";
  522. // const string newassignment = "Start a new assignment with this task";
  523. // string chosenOption = await DisplayActionSheet("Choose an option", "Cancel", null, addtask, newassignment);
  524. // switch (chosenOption)
  525. // {
  526. // case addtask:
  527. // MainPageUtils.ChangeAssignmentTask(taskID);
  528. // break;
  529. // case newassignment:
  530. // MainPageUtils.SaveCurrentAssignment("PRS Mobile main screen - saving assignment on task change", true);
  531. // MainPageUtils.CreateNewAssignment(Guid.Empty, taskID);
  532. // break;
  533. // default:
  534. // break;
  535. // }
  536. // }
  537. private void SaveSiteModuleJobID([CanBeNull] JobShell job)
  538. {
  539. using (var config = new LocalConfiguration<SiteModuleSettings>())
  540. {
  541. var _sitesettings = config.Load();
  542. _sitesettings.JobID = job?.ID ?? Guid.Empty;
  543. config.Save(_sitesettings);
  544. }
  545. }
  546. private void JobBtn_Tapped(object sender, EventArgs e)
  547. {
  548. _selectionpage = new JobSelectionPage(
  549. (job) =>
  550. {
  551. if (_currenttimesheet != null)
  552. {
  553. if (ZeroLengthTimesheet())
  554. {
  555. _currenttimesheet.JobLink.ID = job?.ID ?? Guid.Empty;
  556. _currenttimesheet.JobLink.JobNumber = job?.JobNumber ?? "";
  557. _currenttimesheet.JobLink.Name = job?.Name ?? "";
  558. new Client<TimeSheet>().Save(_currenttimesheet,"Updated Job Number via mobile device");
  559. }
  560. else
  561. {
  562. var here = GPSLocation();
  563. FinishTimeSheet(here);
  564. CreateTimeSheet(job?.ID ?? Guid.Empty, job?.JobNumber ?? "", job?.Name ?? "", here, here.Address,
  565. "Switched Jobs via Mobile Device");
  566. }
  567. }
  568. SaveSiteModuleJobID(job);
  569. }
  570. );
  571. Navigation.PushAsync(_selectionpage);
  572. }
  573. // private bool CheckTimeSheetAgainstGates(TimeSheet timesheet)
  574. // {
  575. // DateTime now = DateTime.Now;
  576. //
  577. // //var timesheet = CurrentTimeSheet();
  578. //
  579. // //Can't confirm if there is no timesheet
  580. // if (timesheet == null)
  581. // return false;
  582. //
  583. // // Can't confirm if there are no devices
  584. // if (App.Bluetooth.Devices.Length == 0)
  585. // return false;
  586. //
  587. // if (App.Data.Gates == null)
  588. // return false;
  589. //
  590. // long tsTicks = timesheet.Date.Add(timesheet.Start).Ticks;
  591. // long btTicks = App.Bluetooth.TimeStamp.Ticks;
  592. //
  593. // if (Math.Abs(tsTicks - btTicks) > new TimeSpan(0, 2, 0).Ticks)
  594. // return false;
  595. //
  596. // CoreRow firstgate = null;
  597. // List<String> gates = new List<string>();
  598. // // Scan every located d
  599. // foreach (var device in App.Bluetooth.Devices)
  600. // {
  601. // CoreRow gate = App.Data.Gates?.Rows.FirstOrDefault(r => r.Get<JobTracker, String>(c => c.TrackerLink.DeviceID) == device);
  602. // if (gate != null)
  603. // {
  604. //
  605. // if ((gate.Get<JobTracker, bool>(x => x.IsJobSite) == true) && (firstgate == null))
  606. // firstgate = gate;
  607. //
  608. // gates.Add(gate.Get<JobTracker, String>(x => x.Gate));
  609. // }
  610. // }
  611. // if (gates.Any())
  612. // {
  613. // timesheet.Gate = String.Join(", ", gates.OrderBy(x => x));
  614. // if (firstgate != null)
  615. // {
  616. // timesheet.JobLink.ID = firstgate.Get<JobTracker, Guid>(x => x.JobLink.ID);
  617. // timesheet.JobLink.JobNumber = firstgate.Get<JobTracker, String>(x => x.JobLink.JobNumber);
  618. // timesheet.JobLink.Name = firstgate.Get<JobTracker, String>(x => x.JobLink.Name);
  619. // }
  620. // return true;
  621. // //new Client<TimeSheet>().Save(timesheet, "Confirmed Gate Entry by Bluetooth Tracker", (o, e) => { });
  622. // }
  623. //
  624. // return false;
  625. //
  626. // }
  627. private bool ZeroLengthTimesheet()
  628. {
  629. if (_currenttimesheet == null)
  630. return true;
  631. if (!String.IsNullOrWhiteSpace(_currenttimesheet.Notes))
  632. return false;
  633. if (_currenttimesheet.Date.Equals(DateTime.Today))
  634. {
  635. var diff = (DateTime.Now.TimeOfDay - _currenttimesheet.Start).TotalSeconds;
  636. if (Math.Abs(diff) < 120.0F)
  637. return true;
  638. }
  639. return false;
  640. }
  641. private void CreateTimeSheet(Guid jobid, string jobnumber, String jobname, Location location, String address, String auditmessage)
  642. {
  643. try
  644. {
  645. _currenttimesheet = new TimeSheet
  646. {
  647. EmployeeLink =
  648. {
  649. ID = App.Data.Me.ID
  650. },
  651. Date = DateTime.Today,
  652. StartLocation = location,
  653. JobLink =
  654. {
  655. ID = jobid,
  656. JobNumber = jobnumber,
  657. Name = jobname
  658. },
  659. Address = address,
  660. SoftwareVersion = MobileUtils.AppVersion.InstalledVersionNumber + MobileUtils.GetDeviceID(),
  661. Start = (DateTime.Now - DateTime.Today).Floor(TimeSpan.FromMinutes(1))
  662. };
  663. new Client<TimeSheet>().Save(_currenttimesheet, auditmessage);
  664. }
  665. catch (Exception ex)
  666. {
  667. InABox.Mobile.MobileLogging.Log(ex);
  668. }
  669. }
  670. private bool CheckLocation()
  671. {
  672. // a 15 minute timeout is awfully long for this process.
  673. // The App times tick over every 30 seconds, so surely we can
  674. // drop this to max 1 or 2 minutes..
  675. // Also, we would expect the GPS / Bluetooth subsystems to take care
  676. // of purging stale data
  677. if (Security.IsAllowed<CanBypassGPSClockIn>())
  678. return true;
  679. if (Security.IsAllowed<CanBypassBluetoothGates>())
  680. return GPSTimeStamp() > DateTime.Now.Subtract(new TimeSpan(0, 15, 0));
  681. if (App.Bluetooth.TimeStamp < DateTime.Now.Subtract(new TimeSpan(0, 15, 0)))
  682. return false;
  683. return App.Data.BluetoothGates.Any(x =>
  684. App.Bluetooth.DetectedBlueToothMACAddresses.Contains(x.DeviceID));
  685. }
  686. private DateTime GPSTimeStamp()
  687. {
  688. return App.GPS.TimeStamp;
  689. }
  690. private static Location GPSLocation()
  691. {
  692. Location here = new InABox.Core.Location()
  693. {
  694. Latitude = App.GPS.Latitude,
  695. Longitude = App.GPS.Longitude,
  696. Address = App.GPS.Address,
  697. Timestamp = App.GPS.TimeStamp
  698. };
  699. return here;
  700. }
  701. private String GPSAddress()
  702. {
  703. return App.GPS.Address;
  704. }
  705. private String GetAddress()
  706. {
  707. if (Security.IsAllowed<CanBypassBluetoothGates>())
  708. {
  709. if (GPSTimeStamp() < DateTime.Now.Subtract(new TimeSpan(0, 5, 0)))
  710. {
  711. App.GPS.GetLocation(true);
  712. return InABox.Core.Security.IsAllowed<CanBypassGPSClockIn>()? "" : "Searching for GPS";
  713. }
  714. return GPSAddress();
  715. }
  716. else
  717. {
  718. var gate = App.Data.BluetoothGates.FirstOrDefault(x =>
  719. App.Bluetooth.DetectedBlueToothMACAddresses.Contains(x.DeviceID));
  720. return gate?.Gate ?? "Looking for Gate";
  721. }
  722. }
  723. #endregion
  724. public void SettingsTapped(object sender, EventArgs args) => Navigation.PushAsync(new SettingsPage());
  725. public void AssignmentListTapped(object sender, EventArgs args) => Navigation.PushAsync(new AssignmentList());
  726. public void DeliveryModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new DeliveryModule());
  727. public void EquipmentModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new EquipmentModule());
  728. public void KanbanFormsTapped(object sender, EventArgs args) => Navigation.PushAsync(new KanbanForms());
  729. public void StaffStatusPageTapped(object sender, EventArgs args) => Navigation.PushAsync(new StaffStatusPage());
  730. public void ManufacturingListTapped(object sender, EventArgs args) => Navigation.PushAsync(new ManufacturingList());
  731. private void MeetingsModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new MeetingList());
  732. public void HumanResourcesModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new HumanResourcesModule());
  733. public void NotificationListTapped(object sender, EventArgs args) => Navigation.PushAsync(new NotificationList());
  734. public void ProductListTapped(object sender, EventArgs args) => Navigation.PushAsync(new ProductList());
  735. public void PurchaseOrderListTapped(object sender, EventArgs args) => Navigation.PushAsync(new PurchaseOrderList());
  736. public void SiteModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new SiteModule());
  737. public void StoreRequiListTapped(object sender, EventArgs args) => Navigation.PushAsync(new StoreRequiList());
  738. public void KanbanListTapped(object sender, EventArgs args) => Navigation.PushAsync(new KanbanList()
  739. {
  740. Model = App.Data.Kanbans,
  741. Title = "My Tasks"
  742. });
  743. public void WarehouseModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new WarehouseModule());
  744. public void UpdatePageTapped(object sender, EventArgs args) => Navigation.PushAsync(new UpdatePage());
  745. }
  746. }