AttendancePanel.xaml.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Windows;
  5. using System.Windows.Controls;
  6. using System.Windows.Input;
  7. using System.Windows.Media.Imaging;
  8. using System.Windows.Threading;
  9. using Comal.Classes;
  10. using InABox.Clients;
  11. using InABox.Core;
  12. using InABox.DynamicGrid;
  13. using InABox.WPF;
  14. using Syncfusion.UI.Xaml.Kanban;
  15. namespace PRSDesktop
  16. {
  17. /// <summary>
  18. /// Interaction logic for AttendancePanel.xaml
  19. /// </summary>
  20. public partial class AttendancePanel : UserControl, IPanel<TimeSheet>
  21. {
  22. private string _search = "";
  23. public CoreTable Activities;
  24. private bool bIncludeInactive;
  25. private readonly DispatcherTimer columnsizer = new();
  26. public CoreTable Employees;
  27. private readonly List<AttendanceKanban> Kanbans = new();
  28. public CoreTable LeaveRequests;
  29. public CoreTable TimeSheets;
  30. public AttendancePanel()
  31. {
  32. InitializeComponent();
  33. columnsizer.Interval = new TimeSpan(0, 0, 0, 0, 500);
  34. columnsizer.Tick += Columnsizer_Tick;
  35. columnsizer.IsEnabled = true;
  36. }
  37. public bool IsReady { get; set; }
  38. public event DataModelUpdateEvent OnUpdateDataModel;
  39. public Dictionary<string, object[]> Selected()
  40. {
  41. return new Dictionary<string, object[]>
  42. {
  43. { typeof(Employee).EntityName(), Employees.Rows.ToArray() },
  44. { typeof(TimeSheet).EntityName(), TimeSheets.Rows.ToArray() }
  45. };
  46. }
  47. public void CreateToolbarButtons(IPanelHost host)
  48. {
  49. }
  50. public string SectionName => "Attendance";
  51. public DataModel DataModel(Selection selection)
  52. {
  53. var ids = selection != Selection.None ? Employees.ExtractValues<Employee, Guid>(x => x.ID).ToArray() : new Guid[] { };
  54. var filter = new Filter<Employee>(x => x.ID).InList(ids);
  55. if (!bIncludeInactive)
  56. {
  57. filter.And(x => x.ShowOnInOutBoard);
  58. }
  59. return new AttendanceDataModel(filter);
  60. }
  61. public void Refresh()
  62. {
  63. using (var cursor = new WaitCursor())
  64. {
  65. var query = new MultiQuery();
  66. query.Add(
  67. new Filter<TimeSheet>(x => x.Date).IsEqualTo(DateTime.Today),
  68. new Columns<TimeSheet>(x => x.EmployeeLink.ID)
  69. .Add(x => x.Start)
  70. .Add(x => x.Finish)
  71. .Add(x => x.Address)
  72. .Add(x => x.ActivityLink.ID)
  73. .Add(x => x.SoftwareVersion),
  74. new SortOrder<TimeSheet>(x => x.Start)
  75. );
  76. query.Add(
  77. new Filter<LeaveRequest>(x => x.From).IsLessThanOrEqualTo(DateTime.Today)
  78. .And(x => x.To).IsGreaterThanOrEqualTo(DateTime.Today)
  79. .And(x => x.Status).IsEqualTo(LeaveRequestStatus.InProgress),
  80. new Columns<LeaveRequest>(x => x.EmployeeLink.ID)
  81. .Add(x => x.From)
  82. .Add(x => x.FromTime)
  83. .Add(x => x.To)
  84. .Add(x => x.ToTime)
  85. .Add(x => x.LeaveType.ID)
  86. );
  87. query.Query();
  88. TimeSheets = query.Get<TimeSheet>();
  89. LeaveRequests = query.Get<LeaveRequest>();
  90. foreach (var emprow in Employees.Rows)
  91. {
  92. var empid = emprow.Get<Employee, Guid>(c => c.ID);
  93. var kanban = Kanbans.FirstOrDefault(x => string.Equals(x.ID, empid.ToString()));
  94. if (kanban != null)
  95. {
  96. var bOK = CheckTimeSheet(empid, kanban);
  97. if (!bOK)
  98. bOK = CheckLeave(empid, kanban);
  99. if (!bOK)
  100. UpdateKanban(kanban,
  101. TimeSpan.MinValue,
  102. TimeSpan.MinValue,
  103. "",
  104. "White",
  105. "Gainsboro",
  106. "",
  107. ""
  108. );
  109. }
  110. }
  111. FilterKanbans();
  112. }
  113. }
  114. public void Setup()
  115. {
  116. Kanban.Columns.Clear();
  117. var filter = LookupFactory.DefineFilter<Employee>(); //.And(x=>x.Type).IsEqualTo(EmployeeType.Employee);
  118. var tables = Client.QueryMultiple(
  119. new KeyedQueryDef<Employee>(
  120. filter,
  121. new Columns<Employee>(
  122. x => x.ID,
  123. x => x.Name,
  124. x => x.Thumbnail.ID,
  125. x => x.Group.ID,
  126. x => x.Group.Description,
  127. x => x.Type,
  128. x => x.UsualStart,
  129. x => x.UsualFinish,
  130. x => x.ShowOnInOutBoard
  131. ),
  132. new SortOrder<Employee>(x => x.Name)),
  133. new KeyedQueryDef<Activity>());
  134. Employees = tables[nameof(Employee)];
  135. Activities = tables[nameof(Activity)];
  136. CreateColumns();
  137. CreateKanbans();
  138. var imageids = Employees.Rows
  139. .Select(r => r.EntityLinkID<Employee, ImageDocumentLink>(x => x.Thumbnail) ?? Guid.Empty)
  140. .Where(x => x != Guid.Empty).ToArray();
  141. new Client<Document>().Query(
  142. new Filter<Document>(x => x.ID).InList(imageids),
  143. new Columns<Document>(
  144. x => x.ID,
  145. x => x.Data
  146. ),
  147. null,
  148. (data, error) => ProcessImages(data)
  149. );
  150. }
  151. public void Shutdown()
  152. {
  153. }
  154. public void Heartbeat(TimeSpan time)
  155. {
  156. }
  157. private void Columnsizer_Tick(object sender, EventArgs e)
  158. {
  159. columnsizer.IsEnabled = false;
  160. ResizeColumns();
  161. columnsizer.IsEnabled = true;
  162. }
  163. private void ResizeColumns()
  164. {
  165. using (var d = Dispatcher.DisableProcessing())
  166. {
  167. var CollapsedWidth = 50;
  168. var CollapsedColumns = 0;
  169. Array.ForEach(Kanban.Columns.ToArray(), x => { CollapsedColumns += x.IsExpanded ? 0 : 1; });
  170. if (Kanban.Columns.Count > 0 && CollapsedColumns != Kanban.Columns.Count)
  171. {
  172. var ColumnWidth = (Kanban.ActualWidth - CollapsedColumns * CollapsedWidth) / (Kanban.Columns.Count - CollapsedColumns) - 2;
  173. if (ColumnWidth != Kanban.ColumnWidth) Kanban.ColumnWidth = ColumnWidth;
  174. }
  175. }
  176. }
  177. private void CreateKanbans()
  178. {
  179. foreach (var row in Employees.Rows)
  180. {
  181. var empid = row.Get<Employee, Guid>(x => x.ID);
  182. var empname = row.Get<Employee, string>(x => x.Name);
  183. var groupid = row.Get<Employee, Guid>(x => x.Group.ID);
  184. var imgid = row.Get<Employee, Guid>(x => x.Thumbnail.ID);
  185. var img = PRSDesktop.Resources.anonymous.AsBitmapImage();
  186. var active = row.Get<Employee, bool>(x => x.ShowOnInOutBoard);
  187. var color = "White";
  188. var kanban = new AttendanceKanban
  189. {
  190. ID = empid.ToString(),
  191. Name = empname,
  192. Category = groupid.ToString(),
  193. Image = img,
  194. Clockin = "",
  195. Clockout = "",
  196. Address = "",
  197. ColorKey = "White",
  198. TextColor = "Gainsboro",
  199. Type = "",
  200. Version = "",
  201. Active = active
  202. };
  203. Kanbans.Add(kanban);
  204. }
  205. }
  206. private void CreateColumns()
  207. {
  208. //Dictionary<Guid, String> columns = Employees.ToDictionary<Employee, Guid>(x => x.ID, new System.Linq.Expressions.Expression<Func<Employee, object>>[] { x => x.Group.Description });
  209. Kanban.Columns.Clear();
  210. var columns = new List<Tuple<Guid, string>>();
  211. foreach (var row in Employees.Rows)
  212. {
  213. var active = row.Get<Employee, bool>(c => c.ShowOnInOutBoard);
  214. if (bIncludeInactive || active)
  215. {
  216. var id = row.Get<Employee, Guid>(c => c.Group.ID);
  217. var desc = row.Get<Employee, string>(c => c.Group.Description);
  218. if (!columns.Any(x => x.Item1.Equals(id)))
  219. columns.Add(new Tuple<Guid, string>(id, desc));
  220. }
  221. }
  222. columns = columns.OrderBy(x => x.Item2).ToList();
  223. foreach (var column in columns)
  224. {
  225. var newcol = new KanbanColumn
  226. {
  227. Title = column.Item1 != Guid.Empty ? column.Item2 : "(No Group Assigned)",
  228. Categories = column.Item1.ToString()
  229. };
  230. newcol.AllowDrag = false;
  231. newcol.AllowDrop = false;
  232. Kanban.Columns.Add(newcol);
  233. }
  234. }
  235. private void UpdateKanban(AttendanceKanban kanban, TimeSpan start, TimeSpan finish, string address, string background, string foreground,
  236. string description, string version)
  237. {
  238. kanban.Clockin = start.TotalMilliseconds > 0 ? string.Format("{0:hh\\:mm}", start) : "";
  239. kanban.Clockout = finish.TotalMilliseconds > 0 ? string.Format("{0:hh\\:mm}", finish) : "";
  240. kanban.Address = address;
  241. kanban.ColorKey = background;
  242. kanban.TextColor = foreground;
  243. kanban.Type = description;
  244. kanban.Version = version;
  245. }
  246. private bool CheckTimeSheet(Guid empid, AttendanceKanban kanban)
  247. {
  248. var firstrow = TimeSheets.Rows.FirstOrDefault(r => r.Get<TimeSheet, Guid>(c => c.EmployeeLink.ID).Equals(empid));
  249. if (firstrow == null)
  250. return false;
  251. var lastrow = TimeSheets.Rows.LastOrDefault(r => r.Get<TimeSheet, Guid>(c => c.EmployeeLink.ID).Equals(empid));
  252. var actid = lastrow.Get<TimeSheet, Guid>(c => c.ActivityLink.ID);
  253. var actrow = Equals(actid, Guid.Empty) ? null : Activities.Rows.FirstOrDefault(r => r.Get<Activity, Guid>(c => c.ID).Equals(actid));
  254. var color = "White";
  255. var finish = lastrow.Get<TimeSheet, TimeSpan>(c => c.Finish);
  256. if (finish.Ticks > 0 && finish < DateTime.Now - DateTime.Today)
  257. {
  258. color = "Gainsboro";
  259. }
  260. else
  261. {
  262. color = actrow != null ? actrow.Get<Activity, string>(c => c.Color) : "";
  263. if (string.IsNullOrWhiteSpace(color))
  264. color = "LightGreen";
  265. }
  266. UpdateKanban(kanban,
  267. firstrow.Get<TimeSheet, TimeSpan>(c => c.Start),
  268. lastrow.Get<TimeSheet, TimeSpan>(c => c.Finish),
  269. lastrow.Get<TimeSheet, string>(c => c.Address),
  270. color,
  271. kanban.TextColor = "Black",
  272. actrow != null ? actrow.Get<Activity, string>(c => c.Description) : "",
  273. lastrow.Get<TimeSheet, string>(c => c.SoftwareVersion)
  274. );
  275. //kanban.Clockin = firstrow != null ? String.Format("{0:hh\\:mm}", firstrow.Get<TimeSheet, TimeSpan>(c => c.Start)) : "";
  276. //kanban.Clockout = (lastrow != null) && (lastrow.Get<TimeSheet, TimeSpan>(c => c.Finish).Ticks > 0) ? String.Format("{0:hh\\:mm}", lastrow.Get<TimeSheet, TimeSpan>(c => c.Finish)) : "";
  277. //kanban.Address = lastrow != null ? lastrow.Get<TimeSheet, String>(c => c.Address) : "";
  278. //kanban.ColorKey = color;
  279. //kanban.TextColor = lastrow != null ? "Black" : "Gainsboro";
  280. //kanban.Type = actrow != null ? actrow.Get<Activity, String>(c => c.Description) : "";
  281. //kanban.Version = lastrow != null ? lastrow.Get<TimeSheet, String>(c => c.SoftwareVersion) : "";
  282. return true;
  283. }
  284. private bool CheckLeave(Guid empid, AttendanceKanban kanban)
  285. {
  286. var row = LeaveRequests.Rows.FirstOrDefault(r => r.Get<LeaveRequest, Guid>(c => c.EmployeeLink.ID) == empid);
  287. if (row == null)
  288. return false;
  289. var actid = row.Get<LeaveRequest, Guid>(c => c.LeaveType.ID);
  290. var actrow = Equals(actid, Guid.Empty) ? null : Activities.Rows.FirstOrDefault(r => r.Get<Activity, Guid>(c => c.ID).Equals(actid));
  291. var color = actrow?.Get<Activity, string>(c => c.Color);
  292. if (string.IsNullOrWhiteSpace(color))
  293. color = "Gainsboro";
  294. var description = actrow?.Get<Activity, string>(c => c.Description);
  295. if (string.IsNullOrWhiteSpace(description))
  296. description = "Leave";
  297. UpdateKanban(kanban,
  298. row.Get<LeaveRequest, DateTime>(c => c.From) == DateTime.Today
  299. ? row.Get<LeaveRequest, TimeSpan>(c => c.FromTime)
  300. : TimeSpan.MinValue,
  301. row.Get<LeaveRequest, DateTime>(c => c.To) == DateTime.Today
  302. ? row.Get<LeaveRequest, TimeSpan>(c => c.ToTime)
  303. : TimeSpan.MinValue,
  304. "",
  305. color,
  306. kanban.TextColor = "Black",
  307. description,
  308. ""
  309. );
  310. return true;
  311. }
  312. private void FilterKanbans()
  313. {
  314. var visible = Kanbans.Where(x =>
  315. (x.Name?.ToUpper().Contains(_search.ToUpper()) == true || x.Address?.ToUpper().Contains(_search.ToUpper()) == true)
  316. && (bIncludeInactive || x.Active)
  317. );
  318. Kanban.ItemsSource = visible;
  319. }
  320. private void ProcessImages(CoreTable data)
  321. {
  322. foreach (var row in data.Rows)
  323. {
  324. var imageid = row.Get<Document, Guid>(c => c.ID);
  325. BitmapImage img = null;
  326. var empids = Employees.Rows.Where(r => r.Get<Employee, Guid>(c => c.Thumbnail.ID).Equals(imageid))
  327. .Select(r => r.Get<Employee, Guid>(c => c.ID));
  328. foreach (var empid in empids)
  329. {
  330. var kanban = Kanbans.FirstOrDefault(x => string.Equals(x.ID, empid.ToString()));
  331. if (kanban != null)
  332. {
  333. if (img == null)
  334. {
  335. img = new BitmapImage();
  336. img.LoadImage(row.Get<Document, byte[]>(c => c.Data));
  337. }
  338. kanban.Image = img;
  339. }
  340. }
  341. }
  342. Dispatcher.Invoke(() => { FilterKanbans(); });
  343. }
  344. private void AttendanceMenu_Opened(object sender, RoutedEventArgs e)
  345. {
  346. var menu = sender as ContextMenu;
  347. var model = menu.Tag as AttendanceKanban;
  348. var sick = menu.Items[0] as MenuItem;
  349. var onoff = menu.Items[2] as MenuItem;
  350. if (string.IsNullOrWhiteSpace(model.Clockin) || !string.IsNullOrWhiteSpace(model.Clockout))
  351. onoff.Header = "Clock Employee On to PRS";
  352. else
  353. onoff.Header = "Clock Employee Out of PRS";
  354. var show = menu.Items[4] as MenuItem;
  355. show.Visibility = !model.Active ? Visibility.Visible : Visibility.Collapsed;
  356. var hide = menu.Items[5] as MenuItem;
  357. hide.Visibility = model.Active ? Visibility.Visible : Visibility.Collapsed;
  358. }
  359. private void SickLeave_Click(object sender, RoutedEventArgs e)
  360. {
  361. var actrow = Activities.Rows.FirstOrDefault(
  362. r => r.Get<Activity, bool>(c => c.IsLeave) && r.Get<Activity, bool>(c => c.IsDefault)
  363. );
  364. if (actrow == null)
  365. {
  366. MessageBox.Show("You must set up a default Sick Leave Activity before using this option!");
  367. return;
  368. }
  369. var item = (MenuItem)sender;
  370. var model = (AttendanceKanban)item.Tag;
  371. var empid = Guid.Parse(model.ID);
  372. var row = Employees.Rows.FirstOrDefault(r => r.Get<Employee, Guid>(c => c.ID).Equals(empid));
  373. if (row == null)
  374. {
  375. MessageBox.Show("Cannot Find Employee: " + empid);
  376. return;
  377. }
  378. var emp = row.ToObject<Employee>();
  379. var request = new LeaveRequest();
  380. request.EmployeeLink.ID = empid;
  381. request.From = DateTime.Today;
  382. request.FromTime = DateTime.Now.TimeOfDay;
  383. request.To = DateTime.Today;
  384. request.ToTime = emp.UsualFinish != TimeSpan.FromMilliseconds(0) ? emp.UsualFinish : new TimeSpan(23, 59, 59);
  385. request.Status = LeaveRequestStatus.InProgress;
  386. request.LeaveType.ID = actrow.Get<Activity, Guid>(c => c.ID);
  387. request.Notes = string.Format("Marked As Sick at {0:hh\\:mm} by {1}", DateTime.Now, ClientFactory.UserID);
  388. if (new LeaveRequests().EditItems(new[] { request }))
  389. Refresh();
  390. }
  391. private void ClockOnOff_Click(object sender, RoutedEventArgs e)
  392. {
  393. var item = (MenuItem)sender;
  394. var model = (AttendanceKanban)item.Tag;
  395. var empid = Guid.Parse(model.ID);
  396. var bOK = true;
  397. var time = new TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, 0);
  398. if (Security.IsAllowed<CanChangeStartFinishTimes>())
  399. bOK = TimeEdit.Execute("Enter Time", ref time);
  400. if (!bOK)
  401. return;
  402. if (string.IsNullOrWhiteSpace(model.Clockin) || !string.IsNullOrWhiteSpace(model.Clockout))
  403. {
  404. var timesheet = new TimeSheet();
  405. timesheet.EmployeeLink.ID = empid;
  406. timesheet.Date = DateTime.Today;
  407. timesheet.Start = time;
  408. timesheet.Notes = string.Format("Clocked in at {0:hh\\:mm} by {1}", DateTime.Now, ClientFactory.UserID);
  409. new Client<TimeSheet>().Save(timesheet, "Clocked on from In/Out Board");
  410. Refresh();
  411. }
  412. else
  413. {
  414. var timesheet = new Client<TimeSheet>().Load(
  415. new Filter<TimeSheet>(x => x.Date).IsEqualTo(DateTime.Today).And(x => x.Finish).IsEqualTo(new TimeSpan())
  416. .And(x => x.EmployeeLink.ID)
  417. .IsEqualTo(empid),
  418. new SortOrder<TimeSheet>(x => x.Start)
  419. ).LastOrDefault();
  420. if (timesheet != null)
  421. {
  422. if (!string.IsNullOrWhiteSpace(timesheet.Notes))
  423. timesheet.Notes = timesheet.Notes + "\n";
  424. else
  425. timesheet.Notes = "";
  426. timesheet.Notes = string.Format("{0}Clocked out at {1:hh\\:mm} by {2}", timesheet.Notes, DateTime.Now, ClientFactory.UserID);
  427. timesheet.Finish = time;
  428. new Client<TimeSheet>().Save(timesheet, "Clocked off from In/Out Board");
  429. Refresh();
  430. }
  431. }
  432. }
  433. private void Search_KeyUp(object sender, KeyEventArgs e)
  434. {
  435. _search = Search.Text;
  436. if (string.IsNullOrWhiteSpace(Search.Text) || e.Key == Key.Return) FilterKanbans();
  437. }
  438. private void Export_Click(object sender, RoutedEventArgs e)
  439. {
  440. var form = new DynamicExportForm(typeof(TimeSheet), TimeSheets.Columns.Select(x => x.ColumnName));
  441. if (form.ShowDialog() != true)
  442. return;
  443. var export = new Client<TimeSheet>().Query(
  444. new Filter<TimeSheet>(x => x.Date).IsEqualTo(DateTime.Today),
  445. new Columns<TimeSheet>(form.Fields),
  446. LookupFactory.DefineSort<TimeSheet>()
  447. );
  448. ExcelExporter.DoExport<TimeSheet>(export, string.Format("Attendance {0:dd-MMM-yy}", DateTime.Today));
  449. }
  450. private void ShowAll_Click(object sender, RoutedEventArgs e)
  451. {
  452. if (string.Equals(ShowAll.Content as string, "Show All"))
  453. {
  454. ShowAll.Content = "Hide Inactive";
  455. bIncludeInactive = true;
  456. }
  457. else
  458. {
  459. ShowAll.Content = "Show All";
  460. bIncludeInactive = false;
  461. }
  462. FilterKanbans();
  463. }
  464. private void ShowOnInOut_Click(object sender, RoutedEventArgs e)
  465. {
  466. var menu = sender as MenuItem;
  467. var model = menu.Tag as AttendanceKanban;
  468. UpdateInOutStatus(model, true);
  469. }
  470. private void UpdateInOutStatus(AttendanceKanban model, bool include)
  471. {
  472. var id = Guid.Parse(model.ID);
  473. var row = Employees.Rows.FirstOrDefault(r => r.Get<Employee, Guid>(c => c.ID) == id);
  474. if (row != null)
  475. {
  476. var emp = new Employee { ID = id, ShowOnInOutBoard = include };
  477. new Client<Employee>().Save(emp, include ? "Added To" : "Removed From" + " In/Out Board", (o, e) => { });
  478. row.Set<Employee, bool>(x => x.ShowOnInOutBoard, include);
  479. model.Active = include;
  480. }
  481. }
  482. private void RemoveFromInOut_Click(object sender, RoutedEventArgs e)
  483. {
  484. var menu = sender as MenuItem;
  485. var model = menu.Tag as AttendanceKanban;
  486. UpdateInOutStatus(model, false);
  487. FilterKanbans();
  488. }
  489. }
  490. }