JobResourcePlanner.xaml.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Drawing;
  5. using System.Globalization;
  6. using System.Linq;
  7. using System.Windows;
  8. using System.Windows.Controls;
  9. using System.Windows.Data;
  10. using System.Windows.Input;
  11. using System.Windows.Media;
  12. using Comal.Classes;
  13. using InABox.Clients;
  14. using InABox.Configuration;
  15. using InABox.Core;
  16. using InABox.DynamicGrid;
  17. using InABox.WPF;
  18. using NPOI.SS.Formula.Functions;
  19. using PRSDesktop.WidgetGroups;
  20. using System.ComponentModel;
  21. using Syncfusion.UI.Xaml.Grid;
  22. using Syncfusion.Windows.Tools.Controls;
  23. using SelectionChangedEventArgs = System.Windows.Controls.SelectionChangedEventArgs;
  24. using PRS.Shared;
  25. namespace PRSDesktop
  26. {
  27. public class JobResourcePlannerProperties : IUserConfigurationSettings, IDashboardProperties
  28. {
  29. public JobSelectorSettings JobSettings { get; set; }
  30. public JobSelectorData JobSelection { get; set; }
  31. public Guid ActivityType { get; set; }
  32. public int MonthsToView { get; set; }
  33. public TeamSelectorSettings TeamSettings { get; set; }
  34. public TeamSelectorData TeamSelection { get; set; }
  35. public double SplitterPosition { get; set; }
  36. public double HoursPerDay { get; set; }
  37. public double EmployeeSplitterPosition { get; set; }
  38. public bool IncludeUnApprovedLeave { get; set; }
  39. public JobResourcePlannerProperties()
  40. {
  41. JobSettings = new JobSelectorSettings();
  42. JobSelection = new JobSelectorData();
  43. TeamSettings = new TeamSelectorSettings();
  44. TeamSelection = new TeamSelectorData();
  45. MonthsToView = 1;
  46. SplitterPosition = 0F;
  47. EmployeeSplitterPosition = 0F;
  48. HoursPerDay = 8.5;
  49. ActivityType = Guid.Empty;
  50. IncludeUnApprovedLeave = false;
  51. }
  52. }
  53. public partial class JobResourcePlanner : UserControl
  54. {
  55. private enum Suppress
  56. {
  57. This
  58. }
  59. private ActivityModel[] _activities = new ActivityModel[] { };
  60. private LeaveRequestModel[] _leaverequests = new LeaveRequestModel[] { };
  61. private StandardLeaveModel[] _standardleaves = new StandardLeaveModel[] { };
  62. private JobModel[] _jobs = new JobModel[] { };
  63. private EmployeeResourceModel[] _emps = new EmployeeResourceModel[] { };
  64. private List<AssignmentModel> _assignments = new List<AssignmentModel>();
  65. public JobResourcePlannerProperties Properties { get; set; }
  66. public event LoadSettings<JobResourcePlannerProperties>? LoadSettings;
  67. public event SaveSettings<JobResourcePlannerProperties>? SaveSettings;
  68. private void DoLoadSettings()
  69. {
  70. Properties = LoadSettings?.Invoke(this);
  71. }
  72. private void DoSaveSettings()
  73. {
  74. SaveSettings?.Invoke(this, Properties);
  75. }
  76. public JobResourcePlanner()
  77. {
  78. using (new EventSuppressor(Suppress.This))
  79. InitializeComponent();
  80. }
  81. public void Setup()
  82. {
  83. using (new EventSuppressor(Suppress.This))
  84. {
  85. DoLoadSettings();
  86. JobSelector.Setup();
  87. JobSelector.Settings = Properties.JobSettings;
  88. JobSelector.Selection = Properties.JobSelection;
  89. _jobs = JobSelector.GetJobData<JobModel>(r => new JobModel(r));
  90. TeamSelector.Setup();
  91. TeamSelector.Settings = Properties.TeamSettings;
  92. TeamSelector.Selection = Properties.TeamSelection;
  93. _emps = TeamSelector.GetEmployeeData<EmployeeResourceModel>((row, rosters) => new EmployeeResourceModel(row, rosters));
  94. ViewWindow.ItemsSource = new Dictionary<int, String>()
  95. {
  96. { 1, "1 Month" },
  97. { 3, "3 Months" },
  98. { 6, "6 Months" },
  99. { 12, "12 months" }
  100. };
  101. ViewWindow.SelectedValue = Properties.MonthsToView;
  102. if (Properties.SplitterPosition != 0F)
  103. TeamSelectorRow.Height = new GridLength(Properties.SplitterPosition, GridUnitType.Pixel);
  104. if (Properties.EmployeeSplitterPosition != 0F)
  105. AvailableEmployeesRow.Height = new GridLength(Properties.SplitterPosition, GridUnitType.Pixel);
  106. HoursSelector.Text = $"{Properties.HoursPerDay:F2}";
  107. LeaveType.SelectedIndex = Properties.IncludeUnApprovedLeave ? 1 : 0;
  108. AvailableEmployees.Refresh(true, false);
  109. AssignedEmployees.Refresh(true, false);
  110. MultiQuery query = new MultiQuery();
  111. query.Add<StandardLeave>(
  112. LookupFactory.DefineFilter<StandardLeave>(),
  113. StandardLeaveModel.Columns
  114. );
  115. query.Add<LeaveRequest>(
  116. new Filter<LeaveRequest>(x => x.Status).IsNotEqualTo(LeaveRequestStatus.Rejected),
  117. LeaveRequestModel.Columns
  118. );
  119. query.Add<Activity>(
  120. LookupFactory.DefineFilter<Activity>(),
  121. new Columns<Activity>(x => x.ID).Add(x => x.Code).Add(x => x.Description),
  122. new SortOrder<Activity>(x => x.Code)
  123. );
  124. query.Query();
  125. _standardleaves = query.Get<StandardLeave>().Rows.Select(r=>new StandardLeaveModel(r)).ToArray();
  126. _leaverequests = query.Get<LeaveRequest>().Rows.Select(r => new LeaveRequestModel(r)).ToArray();
  127. _activities = query.Get<Activity>().Rows.Select(r => new ActivityModel(r)).ToArray();
  128. ActivityType.ItemsSource = _activities;
  129. ActivityType.SelectedValue = Properties.ActivityType;
  130. }
  131. }
  132. public void Shutdown(CancelEventArgs? cancel)
  133. {
  134. }
  135. private bool GetStandardLeaveTimes(DateTime date, out TimeSpan start, out TimeSpan finish)
  136. {
  137. bool result = false;
  138. start = TimeSpan.Zero;
  139. finish = TimeSpan.FromDays(1);
  140. var requests = _standardleaves.Where(x =>
  141. (x.From <= date)
  142. && (x.To.Add(x.ToTime) > date)
  143. );
  144. if (requests.Any())
  145. {
  146. result = true;
  147. start = TimeSpan.FromDays(1);
  148. finish = TimeSpan.Zero;
  149. foreach (var leave in requests)
  150. {
  151. var curstart = leave.From == date ? leave.FromTime : TimeSpan.Zero;
  152. start = start > curstart ? curstart : start;
  153. var curfinish = leave.To == date ? leave.ToTime : TimeSpan.FromDays(1);
  154. finish = finish < curfinish ? curfinish : finish;
  155. }
  156. }
  157. return result;
  158. }
  159. private void AdjustStandardLeave(DateTime date, ref TimeSpan time)
  160. {
  161. TimeSpan result = TimeSpan.Zero;
  162. var leaves = _standardleaves.Where(x =>
  163. (x.From <= date)
  164. && (x.To.Add(x.ToTime) > date)
  165. );
  166. foreach (var leave in leaves)
  167. {
  168. result += (leave.To == date ? leave.ToTime : TimeSpan.FromDays(1)) -
  169. (leave.From == date ? leave.FromTime : TimeSpan.Zero);
  170. }
  171. time = time >= result ? time - result : TimeSpan.Zero;
  172. }
  173. private bool GetLeaveRequestTimes(DateTime date, EmployeeResourceModel emp, out TimeSpan start, out TimeSpan finish)
  174. {
  175. bool result = false;
  176. start = TimeSpan.Zero;
  177. finish = TimeSpan.FromDays(1);
  178. var requests = _leaverequests.Where(x =>
  179. (x.From <= date)
  180. && (x.To.Add(x.ToTime) > date)
  181. && (x.EmployeeID == emp.ID)
  182. && (Properties.IncludeUnApprovedLeave ? true : x.Status == LeaveRequestStatus.Approved)
  183. );
  184. if (requests.Any())
  185. {
  186. result = true;
  187. start = TimeSpan.FromDays(1);
  188. finish = TimeSpan.Zero;
  189. foreach (var leave in requests)
  190. {
  191. var curstart = leave.From == date ? leave.FromTime : TimeSpan.Zero;
  192. start = start > curstart ? curstart : start;
  193. var curfinish = leave.To == date ? leave.ToTime : TimeSpan.FromDays(1);
  194. finish = finish < curfinish ? curfinish : finish;
  195. }
  196. }
  197. return result;
  198. }
  199. private void AdjustLeaveRequests(DateTime date, EmployeeResourceModel emp, ref TimeSpan time)
  200. {
  201. TimeSpan result = TimeSpan.Zero;
  202. var requests = _leaverequests.Where(x =>
  203. (x.From <= date)
  204. && (x.To.Add(x.ToTime) > date)
  205. && (x.EmployeeID == emp.ID)
  206. && (Properties.IncludeUnApprovedLeave ? true : x.Status == LeaveRequestStatus.Approved)
  207. );
  208. foreach (var leave in requests)
  209. {
  210. result += (leave.To == date ? leave.ToTime : TimeSpan.FromDays(1)) -
  211. (leave.From == date ? leave.FromTime : TimeSpan.Zero);
  212. }
  213. time = time >= result ? time - result : TimeSpan.Zero;
  214. }
  215. public void Refresh()
  216. {
  217. using (new WaitCursor())
  218. {
  219. var jobids = _jobs.Select(x => x.ID).ToArray();
  220. var empids = _emps.Select(x => x.ID).ToArray();
  221. var actids = _activities.Select(x => x.ID).ToArray();
  222. DateTime todate = DateTime.Today.AddMonths(Properties.MonthsToView);
  223. MultiQuery query = new MultiQuery();
  224. query.Add<Assignment>(
  225. new Filter<Assignment>(x=>x.EmployeeLink.ID).InList(empids)
  226. .And(x=>x.Date).IsGreaterThanOrEqualTo(DateTime.Today)
  227. .And(x=>x.Date).IsLessThanOrEqualTo(DateTime.Today.AddMonths(Properties.MonthsToView)),
  228. AssignmentModel.Columns
  229. );
  230. query.Query();
  231. _assignments = query.Get<Assignment>().Rows.Select(r => new AssignmentModel(r)).ToList();
  232. var data = new DataTable();
  233. data.Columns.Add("Date", typeof(DateTime));
  234. data.PrimaryKey = new System.Data.DataColumn[] { data.Columns[0] };
  235. data.Columns.Add("Total", typeof(object));
  236. data.Columns.Add("Available", typeof(object));
  237. foreach (var job in _jobs)
  238. data.Columns.Add(job.ID.ToString(), typeof(object));
  239. DateTime date = DateTime.Today;
  240. while (date <= DateTime.Today.AddMonths(Properties.MonthsToView))
  241. {
  242. double avail = 0.0F;
  243. foreach (var emp in _emps)
  244. {
  245. var roster = RosterUtils.GetRoster(emp.Roster, emp.Start, date);
  246. var hours = roster.GetBlocks(date, TimeSpan.MinValue, TimeSpan.MaxValue)
  247. .Aggregate<RosterBlock, TimeSpan>(TimeSpan.Zero, (value, block) => value + block.Duration);
  248. AdjustStandardLeave(date, ref hours);
  249. AdjustLeaveRequests(date, emp, ref hours);
  250. avail += hours.TotalHours;
  251. }
  252. double total = avail;
  253. var values = new List<object?> { date };
  254. var anyjobstoday = _assignments.Where(x => (x.Date.Date == date.Date));
  255. avail -= anyjobstoday.Aggregate<AssignmentModel, double>(0F, (value, model) => value + model.BookedDuration.TotalHours);
  256. foreach (var job in _jobs)
  257. {
  258. var thisjobtoday = _assignments.Where(x => (x.Date.Date == date.Date) && (x.JobID == job.ID));
  259. if (thisjobtoday.Any())
  260. {
  261. var assigned = thisjobtoday.Aggregate<AssignmentModel, double>(0F,
  262. (value, model) => value + model.BookedDuration.TotalHours);
  263. values.Add(assigned / Properties.HoursPerDay);
  264. }
  265. else
  266. values.Add(null);
  267. }
  268. values.Insert(1,avail / Properties.HoursPerDay);
  269. values.Insert(1,total / Properties.HoursPerDay);
  270. data.Rows.Add(values.ToArray());
  271. date = date.AddDays(1);
  272. }
  273. dataGrid.ItemsSource = data;
  274. }
  275. }
  276. private abstract class LeaveConverter : IMultiValueConverter
  277. {
  278. protected System.Windows.Media.Color GetColor(object[] value)
  279. {
  280. if ((value[0] != DBNull.Value) && (value[0] != DependencyProperty.UnsetValue))
  281. return Colors.DarkSeaGreen;
  282. if (value[1] is GridCell cell)
  283. {
  284. if (cell.DataContext is DataRowView rowview)
  285. {
  286. var total = rowview.Row["Total"];
  287. if ((total == DBNull.Value) || ((double)total == 0.0F))
  288. return Colors.LightGray;
  289. var avail = rowview.Row["Available"];
  290. if ((avail == DBNull.Value) || ((double)avail == 0.0F))
  291. return Colors.LightSalmon;
  292. return Colors.LightYellow;
  293. }
  294. }
  295. return Colors.WhiteSmoke;
  296. }
  297. public abstract object Convert(object[] value, Type targetType, object parameter, CultureInfo culture);
  298. public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
  299. {
  300. throw new NotImplementedException();
  301. }
  302. }
  303. private class LeaveBackgroundConverter : LeaveConverter
  304. {
  305. public override object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
  306. {
  307. return new SolidColorBrush(base.GetColor(value)) { Opacity = 0.8 };
  308. }
  309. }
  310. private class LeaveForegroundConverter : LeaveConverter
  311. {
  312. public override object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
  313. {
  314. return new SolidColorBrush(ImageUtils.GetForegroundColor(base.GetColor(value)));
  315. }
  316. }
  317. private void DataGrid_AutoGeneratingColumn(object sender, AutoGeneratingColumnArgs e)
  318. {
  319. MultiBinding CreateBinding<TConverter>(String path) where TConverter : IMultiValueConverter, new()
  320. {
  321. var binding = new MultiBinding();
  322. binding.Bindings.Add(new Binding(path));
  323. binding.Bindings.Add(new Binding() { RelativeSource = new RelativeSource(RelativeSourceMode.Self) });
  324. binding.Converter = new TConverter();
  325. return binding;
  326. }
  327. var value = (e.Column.ValueBinding as Binding)!;
  328. if (value.Path.Path.Equals("Date"))
  329. {
  330. e.Column.Width = 80;
  331. e.Column.HeaderStyle = Resources["DateHeaderStyle"] as Style;
  332. e.Column.AllowFocus = false;
  333. }
  334. else if (value.Path.Path.Equals("Total"))
  335. {
  336. e.Cancel = true;
  337. }
  338. else if (value.Path.Path.Equals("Available"))
  339. {
  340. e.Column = new GridNumericColumn() { NumberDecimalDigits = 2, MappingName = e.Column.MappingName};
  341. e.Column.Width = 50;
  342. e.Column.HeaderStyle = e.Column.HeaderStyle = Resources["ContentHeaderStyle"] as Style;
  343. e.Column.AllowFocus = false;
  344. e.Column.HeaderText = "Available";
  345. }
  346. else
  347. {
  348. e.Column = new GridNumericColumn() { NumberDecimalDigits = 2, MappingName = e.Column.MappingName};
  349. e.Column.Width = 40;
  350. e.Column.HeaderStyle = Resources["ContentHeaderStyle"] as Style;
  351. var jobid = Guid.Parse(value.Path.Path);
  352. e.Column.HeaderText = _jobs.FirstOrDefault(x=>x.ID == jobid)?.Name ?? jobid.ToString();
  353. e.Column.DisplayBinding = new Binding { Path = new PropertyPath(e.Column.MappingName) };
  354. e.Column.AllowFocus = true;
  355. var style = new Style(typeof(GridCell));
  356. style.Setters.Add(new Setter(BackgroundProperty, CreateBinding<LeaveBackgroundConverter>(value.Path.Path)));
  357. style.Setters.Add(new Setter(ForegroundProperty, CreateBinding<LeaveForegroundConverter>(value.Path.Path)));
  358. e.Column.CellStyle = style;
  359. }
  360. e.Column.TextAlignment = TextAlignment.Center;
  361. e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Center;
  362. e.Column.ColumnSizer = GridLengthUnitType.None;
  363. e.Column.ShowHeaderToolTip = false;
  364. e.Column.ShowToolTip = false;
  365. }
  366. private void DataGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
  367. {
  368. var emps = TeamSelector.GetEmployeeData<EmployeeResourceModel>((row, rosters) => new EmployeeResourceModel(row, rosters));
  369. }
  370. private void DataGrid_OnSelectionChanging(object? sender, GridSelectionChangingEventArgs e)
  371. {
  372. }
  373. private bool bResettingSelection = false;
  374. private void DataGrid_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
  375. {
  376. bResettingSelection = true;
  377. dataGrid.SelectionController.ClearSelections(false);
  378. }
  379. private void DataGrid_OnCurrentCellActivating(object? sender, CurrentCellActivatingEventArgs e)
  380. {
  381. if (bResettingSelection)
  382. {
  383. bResettingSelection = false;
  384. return;
  385. }
  386. var thiscol = dataGrid.Columns[e.CurrentRowColumnIndex.ColumnIndex];
  387. var selected = dataGrid.SelectionController.SelectedCells;
  388. if (selected.Any(x => x.Column != thiscol))
  389. e.Cancel = true;
  390. else
  391. e.Cancel = false;
  392. }
  393. private void DataGrid_OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
  394. {
  395. }
  396. private bool ExtractSelection(out Guid jobid, out DateTime[] dates)
  397. {
  398. var selected = dataGrid.SelectionController.SelectedCells;
  399. var colname = selected.Select(x => x.Column.MappingName).Distinct().FirstOrDefault();
  400. if (Guid.TryParse(colname, out Guid job))
  401. {
  402. jobid = job;
  403. dates = selected.Select(x => ((DataRowView)x.RowData).Row["Date"]).Cast<DateTime>().ToArray();
  404. return true;
  405. }
  406. else
  407. {
  408. jobid = Guid.Empty;
  409. dates = new DateTime[] { };
  410. return false;
  411. }
  412. }
  413. private void DataGrid_OnMouseUp(object sender, MouseButtonEventArgs e)
  414. {
  415. if (ExtractSelection(out Guid jobid, out DateTime[] dates))
  416. {
  417. LoadAssignedEmployees(jobid, dates);
  418. LoadAvailableEmployees(dates);
  419. }
  420. }
  421. private JobPlannerEmployee GetEmployee(Guid id, List<JobPlannerEmployee> list)
  422. {
  423. var result = list.FirstOrDefault(x => x.ID == id);
  424. if (result == null)
  425. {
  426. result = new JobPlannerEmployee()
  427. {
  428. ID = id,
  429. Name = _emps.FirstOrDefault(x=>x.ID == id)?.Name ?? id.ToString(),
  430. Time = TimeSpan.Zero
  431. };
  432. list.Add(result);
  433. }
  434. return result;
  435. }
  436. private void LoadAvailableEmployees(DateTime[] dates)
  437. {
  438. List<JobPlannerEmployee> availableemployees = new List<JobPlannerEmployee>();
  439. foreach (var emp in _emps)
  440. {
  441. foreach (var date in dates)
  442. {
  443. var roster = RosterUtils.GetRoster(emp.Roster, emp.Start, date);
  444. var blocks = roster?.GetBlocks(date, TimeSpan.MinValue, TimeSpan.MaxValue) ?? new RosterBlock[] { };
  445. var rostered = blocks.Aggregate(TimeSpan.Zero, (time, block) => time += block.Duration);
  446. AdjustStandardLeave(date, ref rostered);
  447. AdjustLeaveRequests(date, emp, ref rostered);
  448. var assignments = _assignments.Where(x => (x.Date == date) && (x.EmployeeID == emp.ID));
  449. var assigned = assignments.Aggregate(TimeSpan.Zero, (time, assign) => time += assign.BookedDuration);
  450. if (rostered > assigned)
  451. GetEmployee(emp.ID, availableemployees).Time += rostered.Subtract(assigned);
  452. AvailableEmployees.Items = availableemployees.OrderBy(x=>x.Name).ToList();
  453. AvailableEmployees.Refresh(false, true);
  454. }
  455. }
  456. }
  457. private void LoadAssignedEmployees(Guid jobid, DateTime[] dates)
  458. {
  459. List<JobPlannerEmployee> assignedemployees = new List<JobPlannerEmployee>();
  460. foreach (var assignment in _assignments.Where(x => dates.Contains(x.Date) && (x.JobID == jobid)))
  461. GetEmployee(assignment.EmployeeID,assignedemployees).Time += assignment.BookedDuration;
  462. AssignedEmployees.Items = assignedemployees.OrderBy(x=>x.Name).ToList();;
  463. AssignedEmployees.Refresh(false, true);
  464. }
  465. private void JobSelector_OnSettingsChanged(object sender, JobSelectorSettingsChangedArgs args)
  466. {
  467. if (EventSuppressor.IsSet(Suppress.This))
  468. return;
  469. Properties.JobSettings = args.Settings;
  470. DoSaveSettings();
  471. }
  472. private void JobSelector_OnSelectionChanged(object sender, JobSelectorSelectionChangedArgs args)
  473. {
  474. if (EventSuppressor.IsSet(Suppress.This))
  475. return;
  476. Properties.JobSelection = args.Selection;
  477. _jobs = JobSelector.GetJobData<JobModel>(x => new JobModel(x));
  478. DoSaveSettings();
  479. Refresh();
  480. }
  481. private void TeamSelector_OnSettingsChanged(object sender, TeamSelectorSettingsChangedArgs args)
  482. {
  483. if (EventSuppressor.IsSet(Suppress.This))
  484. return;
  485. Properties.TeamSettings = args.Settings;
  486. DoSaveSettings();
  487. }
  488. private void TeamSelector_OnSelectionChanged(object sender, TeamSelectorSelectionChangedArgs args)
  489. {
  490. if (EventSuppressor.IsSet(Suppress.This))
  491. return;
  492. Properties.TeamSelection = args.Selection;
  493. _emps = TeamSelector.GetEmployeeData<EmployeeResourceModel>((row, rosters) => new EmployeeResourceModel(row, rosters));
  494. DoSaveSettings();
  495. Refresh();
  496. }
  497. private void JobSelector_OnSizeChanged(object sender, SizeChangedEventArgs e)
  498. {
  499. if (EventSuppressor.IsSet(Suppress.This))
  500. return;
  501. Properties.SplitterPosition = TeamSelectorRow.Height.Value;
  502. DoSaveSettings();
  503. }
  504. private void ViewWindow_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  505. {
  506. if (EventSuppressor.IsSet(Suppress.This))
  507. return;
  508. Properties.MonthsToView = (int)ViewWindow.SelectedValue;
  509. DoSaveSettings();
  510. Refresh();
  511. }
  512. private void HoursSelector_Down_Click(object sender, RoutedEventArgs e)
  513. {
  514. Properties.HoursPerDay = Math.Max(0.25F, Properties.HoursPerDay - 0.25);
  515. HoursSelector.Text = $"{Properties.HoursPerDay:F2}";
  516. DoSaveSettings();
  517. Refresh();
  518. }
  519. private void HoursSelector_Up_Click(object sender, RoutedEventArgs e)
  520. {
  521. Properties.HoursPerDay = Math.Min(24F, Properties.HoursPerDay + 0.25);
  522. HoursSelector.Text = $"{Properties.HoursPerDay:F2}";
  523. DoSaveSettings();
  524. Refresh();
  525. }
  526. private void ActivityType_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  527. {
  528. if (EventSuppressor.IsSet(Suppress.This))
  529. return;
  530. Properties.ActivityType = (Guid)(ActivityType.SelectedValue ?? Guid.Empty);
  531. DoSaveSettings();
  532. }
  533. private void AvailableEmployees_OnSizeChanged(object sender, SizeChangedEventArgs e)
  534. {
  535. if (EventSuppressor.IsSet(Suppress.This))
  536. return;
  537. Properties.EmployeeSplitterPosition = AvailableEmployeesRow.Height.Value;
  538. DoSaveSettings();
  539. }
  540. private void AvailableEmployees_OnOnAction(object sender, JobPlannerEmployee[] availables)
  541. {
  542. List<TimeSpan> edges = new List<TimeSpan>();
  543. void CheckEdges(params TimeSpan[] times)
  544. {
  545. foreach (var time in times)
  546. {
  547. if (!edges.Contains(time))
  548. edges.Add(time);
  549. }
  550. }
  551. bool IsRostered(RosterBlock[] blocks, TimeSpan start, TimeSpan finish)
  552. {
  553. foreach (var block in blocks)
  554. {
  555. if ((block.Start <= start) && (block.Finish >= finish))
  556. return true;
  557. }
  558. return false;
  559. }
  560. bool IsStandardLeave(DateTime date, TimeSpan start, TimeSpan finish)
  561. {
  562. return _standardleaves.Any(x =>
  563. (x.From.Add(x.FromTime) <= date.Add(start))
  564. && (x.To.Add(x.ToTime) >= date.Add(finish))
  565. );
  566. }
  567. bool IsLeaveRequest(DateTime date, EmployeeResourceModel emp, TimeSpan start, TimeSpan finish)
  568. {
  569. return _leaverequests.Any(x =>
  570. (x.EmployeeID == emp.ID)
  571. && (x.From.Add(x.FromTime) <= date.Add(start))
  572. && (x.To.Add(x.ToTime) >= date.Add(finish))
  573. );
  574. }
  575. bool IsAssigned(AssignmentModel[] assignments, TimeSpan start, TimeSpan finish)
  576. {
  577. foreach (var assignment in assignments)
  578. {
  579. if ((assignment.BookedStart <= start) && (assignment.BookedFinish >= finish))
  580. return true;
  581. }
  582. return false;
  583. }
  584. if (ExtractSelection(out Guid jobid, out DateTime[] dates))
  585. {
  586. if (dataGrid.ItemsSource is DataTable table)
  587. {
  588. List<Assignment> updates = new List<Assignment>();
  589. foreach (var available in availables)
  590. {
  591. foreach (var date in dates)
  592. {
  593. var emp = _emps.FirstOrDefault(x => x.ID == available.ID);
  594. if (emp != null)
  595. {
  596. var roster = RosterUtils.GetRoster(emp.Roster, emp.Start, date);
  597. var blocks = roster?.GetBlocks(date, TimeSpan.MinValue, TimeSpan.MaxValue) ?? new RosterBlock[] { };
  598. foreach (var block in blocks)
  599. CheckEdges(block.Start, block.Finish);
  600. if (GetStandardLeaveTimes(date, out TimeSpan stdleavestart, out TimeSpan stdleavefinish))
  601. CheckEdges(stdleavestart, stdleavefinish);
  602. if (GetLeaveRequestTimes(date, emp, out TimeSpan leaverequeststart, out TimeSpan leaverequestfinish))
  603. CheckEdges(leaverequeststart, leaverequestfinish);
  604. var assignments = _assignments.Where(x => (x.Date == date) && (x.EmployeeID == emp.ID)).ToArray();
  605. foreach (var assignment in assignments)
  606. CheckEdges(assignment.BookedStart, assignment.BookedFinish);
  607. edges.Sort();
  608. double adjustment = 0.0F;
  609. for (int i = 0; i < edges.Count - 1; i++)
  610. {
  611. var start = edges[i];
  612. var finish = edges[i + 1];
  613. if (IsRostered(blocks, start, finish)
  614. && (!IsStandardLeave(date,start,finish))
  615. && (!IsLeaveRequest(date,emp,start,finish))
  616. && !IsAssigned(assignments, start, finish))
  617. {
  618. Assignment assignment = new Assignment();
  619. assignment.ActivityLink.ID = Properties.ActivityType;
  620. assignment.EmployeeLink.ID = emp.ID;
  621. assignment.Date = date;
  622. assignment.JobLink.ID = jobid;
  623. assignment.Booked.Start = start;
  624. assignment.Booked.Finish = finish;
  625. assignment.Booked.Duration = finish - start;
  626. updates.Add(assignment);
  627. adjustment += assignment.Booked.Duration.TotalHours;
  628. }
  629. }
  630. System.Data.DataRow row = table.Rows.Find(date);
  631. row.BeginEdit();
  632. double jobvalue = (row[jobid.ToString()] == DBNull.Value)
  633. ? adjustment / Properties.HoursPerDay
  634. : (double)row[jobid.ToString()] + (adjustment / Properties.HoursPerDay);
  635. row[jobid.ToString()] = jobvalue <= 0F ? null : jobvalue;
  636. row["Available"] = Math.Max(0F, (double)row["Available"] - (adjustment / Properties.HoursPerDay));
  637. row.EndEdit();
  638. }
  639. }
  640. var entry = AvailableEmployees.Items.FirstOrDefault(x => x.ID == available.ID);
  641. if (entry != null)
  642. {
  643. AvailableEmployees.Items.Remove(entry);
  644. GetEmployee(entry.ID, AssignedEmployees.Items).Time += entry.Time;
  645. }
  646. }
  647. if (updates.Any())
  648. {
  649. using (new WaitCursor())
  650. {
  651. new Client<Assignment>().Save(updates, "Assigned by Job Planner");
  652. CoreTable temp = new CoreTable();
  653. temp.LoadColumns(typeof(Assignment));
  654. temp.LoadRows(updates);
  655. _assignments.AddRange(temp.Rows.Select(r => new AssignmentModel(r)));
  656. AssignedEmployees.Items = AssignedEmployees.Items.OrderBy(x => x.Name).ToList();
  657. AssignedEmployees.Refresh(false, true);
  658. AvailableEmployees.Refresh(false, true);
  659. }
  660. }
  661. }
  662. }
  663. }
  664. private void AssignedEmployees_OnOnAction(object sender, JobPlannerEmployee[] employees)
  665. {
  666. if (ExtractSelection(out Guid jobid, out DateTime[] dates))
  667. {
  668. if (dataGrid.ItemsSource is DataTable table)
  669. {
  670. foreach (var date in dates)
  671. {
  672. var emptimes = _assignments.Where(x =>
  673. (x.JobID == jobid)
  674. && (x.Date == date)
  675. && employees.Any(e => e.ID == x.EmployeeID)
  676. ).ToArray();
  677. var emptime = emptimes.Aggregate(TimeSpan.Zero, (time, ass) => time += ass.BookedDuration);
  678. System.Data.DataRow row = table.Rows.Find(date);
  679. row.BeginEdit();
  680. double value = (row[jobid.ToString()] == DBNull.Value)
  681. ? 0.0F
  682. : (double)row[jobid.ToString()] - (emptime.TotalHours / Properties.HoursPerDay);
  683. row[jobid.ToString()] = value <= 0F ? null : value;
  684. row["Available"] = (double)row["Available"] + (emptime.TotalHours/Properties.HoursPerDay);
  685. row.EndEdit();
  686. }
  687. }
  688. var assignments = _assignments.Where(x =>
  689. (x.JobID == jobid)
  690. && dates.Contains(x.Date)
  691. && employees.Any(e => e.ID == x.EmployeeID)
  692. ).ToArray();
  693. if (assignments.Any())
  694. {
  695. using (new WaitCursor())
  696. {
  697. var deletes = assignments.Select(x => new Assignment() { ID = x.ID }).ToArray();
  698. new Client<Assignment>().Delete(deletes, "Deleted from Job Planner");
  699. var removes = AssignedEmployees.Items.Where(x => employees.Any(e => e.ID == x.ID)).ToArray();
  700. foreach (var remove in removes)
  701. {
  702. AssignedEmployees.Items.Remove(remove);
  703. GetEmployee(remove.ID, AssignedEmployees.Items).Time += remove.Time;
  704. }
  705. AssignedEmployees.Refresh(false, true);
  706. AvailableEmployees.Items = AvailableEmployees.Items.OrderBy(x => x.Name).ToList();
  707. AvailableEmployees.Refresh(false, true);
  708. foreach (var assignment in assignments)
  709. _assignments.Remove(assignment);
  710. }
  711. }
  712. }
  713. }
  714. private void LeaveType_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  715. {
  716. if (EventSuppressor.IsSet(Suppress.This))
  717. return;
  718. Properties.IncludeUnApprovedLeave = LeaveType.SelectedIndex > 0;
  719. DoSaveSettings();
  720. Refresh();
  721. }
  722. }
  723. }