JobResourcePlanner.xaml.cs 32 KB

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