EmployeeResourcePlanner.xaml.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Globalization;
  6. using System.Linq;
  7. using System.Linq.Expressions;
  8. using System.Windows;
  9. using System.Windows.Controls;
  10. using System.Windows.Data;
  11. using System.Windows.Input;
  12. using System.Windows.Media;
  13. using Comal.Classes;
  14. using InABox.Clients;
  15. using InABox.Configuration;
  16. using InABox.Core;
  17. using InABox.DynamicGrid;
  18. using InABox.WPF;
  19. using net.sf.mpxj.mspdi.schema;
  20. using PRSDesktop.WidgetGroups;
  21. using PRS.Shared;
  22. using Syncfusion.Data.Extensions;
  23. using Syncfusion.UI.Xaml.Grid;
  24. using Syncfusion.UI.Xaml.Grid.Helpers;
  25. using Syncfusion.Windows.Tools.Controls;
  26. using SelectionChangedEventArgs = System.Windows.Controls.SelectionChangedEventArgs;
  27. using System.Windows.Documents;
  28. using InABox.Wpf;
  29. namespace PRSDesktop;
  30. public class EmployeeResourcePlannerValue
  31. {
  32. public Guid[] IDs { get; set; } = Array.Empty<Guid>();
  33. public AssignmentModel[] Assignments = Array.Empty<AssignmentModel>();
  34. public Brush Background { get; set; }
  35. public Brush Foreground { get; set; }
  36. public String Text { get; set; }
  37. private String _color = "";
  38. public String Color
  39. {
  40. get { return _color; }
  41. set
  42. {
  43. _color = value;
  44. var color = String.IsNullOrWhiteSpace(value) ? Colors.Transparent : (Color)ColorConverter.ConvertFromString(value);
  45. Background = new SolidColorBrush(color) { Opacity = 0.8 };
  46. Foreground = new SolidColorBrush(ImageUtils.GetForegroundColor(color));
  47. }
  48. }
  49. }
  50. public enum EmployeePlannerDisplayMode
  51. {
  52. EmployeeColumns,
  53. DateColumns
  54. }
  55. public class EmployeeResourcePlannerSettings : BaseObject, IGlobalConfigurationSettings
  56. {
  57. [EnumLookupEditor(typeof(EmployeePlannerDisplayMode))]
  58. [EditorSequence(1)]
  59. public EmployeePlannerDisplayMode DisplayMode { get; set; }
  60. public EmployeeResourcePlannerSettings()
  61. {
  62. DisplayMode = EmployeePlannerDisplayMode.DateColumns;
  63. }
  64. }
  65. public partial class EmployeeResourcePlanner : UserControl, IPropertiesPanel<EmployeeResourcePlannerSettings>
  66. {
  67. private enum Suppress
  68. {
  69. This
  70. }
  71. private EmployeeResourceModel[] _employees = Array.Empty<EmployeeResourceModel>();
  72. private StandardLeaveModel[] _standardleaves = Array.Empty<StandardLeaveModel>();
  73. private LeaveRequestModel[] _leaverequests = Array.Empty<LeaveRequestModel>();
  74. private JobModel[] _jobs = Array.Empty<JobModel>();
  75. private ActivityModel[] _activities = Array.Empty<ActivityModel>();
  76. private DateTime[] _dates = Array.Empty<DateTime>();
  77. private CoreFilterDefinitions _jobfilters = new CoreFilterDefinitions();
  78. public event LoadSettings<EmployeeResourcePlannerProperties> LoadSettings;
  79. public event SaveSettings<EmployeeResourcePlannerProperties> SaveSettings;
  80. EmployeeResourcePlannerSettings IPropertiesPanel<EmployeeResourcePlannerSettings>.Properties
  81. {
  82. get => Settings;
  83. set => Settings = value;
  84. }
  85. private EmployeeResourcePlannerSettings Settings { get; set; }
  86. private void DoLoadSettings()
  87. {
  88. Properties = LoadSettings?.Invoke(this) ?? new EmployeeResourcePlannerProperties();
  89. _jobfilters = new GlobalConfiguration<CoreFilterDefinitions>("Job").Load();
  90. }
  91. private void DoSaveSettings()
  92. {
  93. SaveSettings?.Invoke(this, Properties);
  94. }
  95. public EmployeeResourcePlannerProperties Properties { get; set; }
  96. public EmployeeResourcePlanner()
  97. {
  98. using (new EventSuppressor(Suppress.This))
  99. InitializeComponent();
  100. }
  101. private Filter<Job>? GetJobFilter()
  102. {
  103. var jobfilter = _jobfilters.FirstOrDefault(x => String.Equals(x.Name, Properties.JobFilter));
  104. return !String.IsNullOrWhiteSpace(jobfilter?.Filter)
  105. ? Serialization.Deserialize<Filter<Job>>(jobfilter.Filter)
  106. : LookupFactory.DefineFilter<Job>();
  107. }
  108. public void Setup()
  109. {
  110. using (new EventSuppressor(Suppress.This))
  111. {
  112. DoLoadSettings();
  113. EmployeeSelector.Setup();
  114. EmployeeSelector.Settings = Properties.EmployeeSettings;
  115. EmployeeSelector.Selection = Properties.EmployeeSelection;
  116. ViewType.ItemsSource = Enum.GetValues<EmployeeResourcePlannerViewType>()
  117. .Select(x => new KeyValuePair<EmployeeResourcePlannerViewType, string>(x, x.ToString()));
  118. ViewType.SelectedValuePath = "Key";
  119. ViewType.DisplayMemberPath = "Value";
  120. ViewType.SelectedValue = Properties.ViewType;
  121. LeaveType.SelectedIndex = Properties.IncludeUnApprovedLeave ? 1 : 0;
  122. FromDate.DateTime = DateTime.Today;
  123. ToDate.DateTime = DateTime.Today.AddYears(1);
  124. MultiQuery query = new MultiQuery();
  125. query.Add<Job>(
  126. GetJobFilter(),
  127. JobModel.Columns,
  128. new SortOrder<Job>(x => x.JobNumber)
  129. );
  130. query.Add<StandardLeave>(
  131. null,
  132. StandardLeaveModel.Columns
  133. );
  134. query.Add<LeaveRequest>(
  135. new Filter<LeaveRequest>(x => x.Status).IsNotEqualTo(LeaveRequestStatus.Rejected),
  136. LeaveRequestModel.Columns
  137. );
  138. query.Add<Activity>(
  139. LookupFactory.DefineFilter<Activity>(),
  140. new Columns<Activity>(x => x.ID).Add(x => x.Code).Add(x => x.Description),
  141. new SortOrder<Activity>(x => x.Code)
  142. );
  143. query.Query();
  144. _jobs = query.Get<Job>().Rows.Select(r => new JobModel(r)).ToArray();
  145. _standardleaves = query.Get<StandardLeave>().Rows.Select(r=>new StandardLeaveModel(r)).ToArray();
  146. _leaverequests = query.Get<LeaveRequest>().Rows.Select(r => new LeaveRequestModel(r)).ToArray();
  147. _activities = query.Get<Activity>().Rows.Select(r => new ActivityModel(r)).ToArray();
  148. ActivityType.ItemsSource = _activities;
  149. ActivityType.SelectedValue = Properties.ActivityType;
  150. JobFilter.ItemsSource = _jobfilters;
  151. JobFilter.SelectedValue = _jobfilters.FirstOrDefault(x => String.Equals(x.Name, Properties.JobFilter));
  152. }
  153. }
  154. public void Shutdown(CancelEventArgs? cancel)
  155. {
  156. }
  157. private TimeSpan GetPeriod()
  158. {
  159. return Properties.ViewType switch
  160. {
  161. EmployeeResourcePlannerViewType.Week => TimeSpan.FromDays(7),
  162. EmployeeResourcePlannerViewType.Day or _ => TimeSpan.FromDays(1)
  163. };
  164. }
  165. public void Refresh()
  166. {
  167. using (new WaitCursor())
  168. {
  169. _employees = EmployeeSelector.GetEmployeeData((row, rosters) => new EmployeeResourceModel(row, rosters));
  170. var empids = _employees.Select(x => x.ID).ToArray();
  171. DateTime fromdate = FromDate.DateTime.HasValue ? FromDate.DateTime.Value.Date : DateTime.Today;
  172. DateTime todate = ToDate.DateTime.HasValue ? ToDate.DateTime.Value.Date : DateTime.Today.AddYears(1);
  173. MultiQuery query = new MultiQuery();
  174. var period = GetPeriod();
  175. if(Properties.ViewType == EmployeeResourcePlannerViewType.Week)
  176. {
  177. fromdate = fromdate.AddDays(-(((int)fromdate.DayOfWeek - 1) % 7));
  178. todate = todate.AddDays(-(((int)todate.DayOfWeek - 1) % 7));
  179. }
  180. query.Add<Assignment>(
  181. new Filter<Assignment>(x => x.EmployeeLink.ID).InList(empids)
  182. .And(x => x.Date).IsGreaterThanOrEqualTo(fromdate)
  183. .And(x => x.Date).IsLessThanOrEqualTo(todate + period),
  184. AssignmentModel.Columns,
  185. new SortOrder<Assignment>(x => x.EmployeeLink.ID).ThenBy(x => x.Date).ThenBy(x => x.Booked.Duration, SortDirection.Descending)
  186. );
  187. query.Query();
  188. var assignments = query.Get<Assignment>().Rows.Select(r => new AssignmentModel(r)).ToArray();
  189. var data = new DataTable();
  190. var standardHours = Properties.ViewType switch
  191. {
  192. EmployeeResourcePlannerViewType.Week => 38.0,
  193. EmployeeResourcePlannerViewType.Day or _ => 7.5
  194. };
  195. if(Settings.DisplayMode == EmployeePlannerDisplayMode.EmployeeColumns)
  196. {
  197. data.Columns.Add("Date", typeof(DateTime));
  198. foreach (var employee in _employees)
  199. {
  200. data.Columns.Add(employee.ID.ToString(), typeof(object));
  201. }
  202. for (var curdate = fromdate; curdate <= todate; curdate += period)
  203. {
  204. var leavevalue = GetStandardLeave(curdate, _standardleaves);
  205. var values = new List<object> { curdate };
  206. foreach (var employee in _employees)
  207. {
  208. var value = new EmployeeResourcePlannerValue();
  209. // Note use of short-circuiting here.
  210. var bOK = CheckAssignments(employee, curdate, curdate + period, assignments, value)
  211. || CheckStandardLeave(leavevalue, value)
  212. || CheckLeaveRequest(employee, curdate, curdate + period, _leaverequests, value)
  213. || CheckRoster(employee, curdate, curdate + period, standardHours, value);
  214. values.Add(value);
  215. }
  216. data.Rows.Add(values.ToArray());
  217. }
  218. }
  219. else
  220. {
  221. data.Columns.Add("Employee", typeof(object));
  222. var dates = new List<DateTime>();
  223. for (var curdate = fromdate; curdate <= todate; curdate += period)
  224. {
  225. data.Columns.Add(dates.Count.ToString(), typeof(object));
  226. dates.Add(curdate);
  227. }
  228. _dates = dates.ToArray();
  229. foreach (var employee in _employees)
  230. {
  231. var values = new List<object> { employee };
  232. for (var curdate = fromdate; curdate <= todate; curdate += period)
  233. {
  234. var leavevalue = GetStandardLeave(curdate, _standardleaves);
  235. var value = new EmployeeResourcePlannerValue();
  236. var bOK = CheckAssignments(employee, curdate, curdate + period, assignments, value)
  237. || CheckStandardLeave(leavevalue, value)
  238. || CheckLeaveRequest(employee, curdate, curdate + period, _leaverequests, value)
  239. || CheckRoster(employee, curdate, curdate + period, standardHours, value);
  240. values.Add(value);
  241. }
  242. data.Rows.Add(values.ToArray());
  243. }
  244. }
  245. dataGrid.ItemsSource = data;
  246. }
  247. }
  248. private static bool CheckAssignments(EmployeeResourceModel employee, DateTime from, DateTime to, AssignmentModel[] assignments, EmployeeResourcePlannerValue value)
  249. {
  250. var dateAssignments = assignments.Where(x => (x.EmployeeID == employee.ID) && (x.Date >= from.Date && x.Date < to.Date)).ToArray();
  251. if (dateAssignments.Length == 0)
  252. return false;
  253. value.IDs = assignments.Select(x => x.ID).ToArray();
  254. value.Text = dateAssignments.Length == 1
  255. ? (dateAssignments[0].ID != Guid.Empty ? (dateAssignments[0].JobNumber ?? "") : "XX")
  256. : $"{dateAssignments.Select(x => x.JobID).Distinct().Count()} jobs";
  257. value.Assignments = dateAssignments;
  258. value.Color = Colors.LightGreen.ToString();
  259. return true;
  260. }
  261. private static bool CheckRoster(EmployeeResourceModel employee, DateTime from, DateTime to, double standardHours, EmployeeResourcePlannerValue value)
  262. {
  263. value.Text = "";
  264. var totalRostered = 0.0;
  265. for(var date = from; date < to; date = date.AddDays(1))
  266. {
  267. var roster = RosterUtils.GetRoster(employee.Roster, employee.RosterStart, date);
  268. if(roster?.Enabled == true)
  269. {
  270. totalRostered += roster.Duration;
  271. }
  272. }
  273. var color1 = Colors.LightGray;
  274. var color2 = Colors.LightYellow;
  275. var percent = Math.Min(totalRostered / standardHours, 1.0);
  276. value.Foreground = new SolidColorBrush(ImageUtils.MixColors(color1, 1 - percent, color2)) { Opacity = 0.8 };
  277. var brush = new LinearGradientBrush { Opacity = 0.8 };
  278. brush.StartPoint = new Point(0,0);
  279. brush.GradientStops.Add(new GradientStop(Colors.LightYellow, 0.0));
  280. brush.GradientStops.Add(new GradientStop(ImageUtils.MixColors(Colors.LightYellow, percent, Colors.LightGray), percent));
  281. brush.GradientStops.Add(new GradientStop(Colors.LightGray, 1.0));
  282. brush.EndPoint = new Point(1,0);
  283. value.Background = brush;
  284. return totalRostered.IsEffectivelyEqual(0.0);
  285. }
  286. private static EmployeeResourcePlannerValue? GetStandardLeave(DateTime curdate, StandardLeaveModel[] standardleaves)
  287. {
  288. var standardleave = standardleaves.FirstOrDefault(x =>
  289. (x.From <= curdate)
  290. && (x.To.Add(x.ToTime) > curdate)
  291. );
  292. return (standardleave != null)
  293. ? new EmployeeResourcePlannerValue() { Text = standardleave.Code, Color = standardleave.Color}
  294. : null;
  295. }
  296. private static bool CheckStandardLeave(EmployeeResourcePlannerValue? leavevalue, EmployeeResourcePlannerValue value)
  297. {
  298. if (leavevalue == null)
  299. return false;
  300. value.Text = leavevalue.Text;
  301. value.Color = leavevalue.Color;
  302. return true;
  303. }
  304. private bool CheckLeaveRequest(EmployeeResourceModel employee, DateTime from, DateTime to, LeaveRequestModel[] leaverequests, EmployeeResourcePlannerValue value)
  305. {
  306. var leaverequest = leaverequests.FirstOrDefault(c =>
  307. (c.EmployeeID == employee.ID)
  308. && (c.From < to)
  309. && (c.To.Add(c.ToTime) >= from)
  310. && (Properties.IncludeUnApprovedLeave || c.Status == LeaveRequestStatus.Approved));
  311. if (leaverequest == null)
  312. return false;
  313. value.Text = leaverequest.Code;
  314. value.Color = (leaverequest.Status == LeaveRequestStatus.Approved) ? leaverequest.Color : Colors.DimGray.ToString();
  315. return true;
  316. }
  317. #region AutoGenerate Columns / Styling
  318. private class EmployeeResourcePlannerBackgroundConverter : IValueConverter
  319. {
  320. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  321. {
  322. if (value is not EmployeeResourcePlannerValue val)
  323. return DependencyProperty.UnsetValue;
  324. return val.Background;
  325. }
  326. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  327. {
  328. throw new NotImplementedException();
  329. }
  330. }
  331. private class EmployeeResourcePlannerForegroundConverter : IValueConverter
  332. {
  333. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  334. {
  335. if (value is not EmployeeResourcePlannerValue val)
  336. return DependencyProperty.UnsetValue;
  337. return val.Foreground;
  338. }
  339. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  340. {
  341. throw new NotImplementedException();
  342. }
  343. }
  344. private class EmployeeResourcePlannerFontStyleConverter : IValueConverter
  345. {
  346. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  347. {
  348. if (value is not EmployeeResourcePlannerValue val)
  349. return DependencyProperty.UnsetValue;
  350. return FontStyles.Normal;
  351. }
  352. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  353. {
  354. throw new NotImplementedException();
  355. }
  356. }
  357. private class EmployeeResourcePlannerFontWeightConverter : IValueConverter
  358. {
  359. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  360. {
  361. if (value is not EmployeeResourcePlannerValue val)
  362. return DependencyProperty.UnsetValue;
  363. return FontWeights.Normal;
  364. }
  365. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  366. {
  367. throw new NotImplementedException();
  368. }
  369. }
  370. private class EmployeeResourcePlannerContentConverter : IValueConverter
  371. {
  372. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  373. {
  374. if (value is not EmployeeResourcePlannerValue val)
  375. return DependencyProperty.UnsetValue;
  376. return val.Text;
  377. }
  378. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  379. {
  380. throw new NotImplementedException();
  381. }
  382. }
  383. private void DataGrid_AutoGeneratingColumn(object? sender, AutoGeneratingColumnArgs e)
  384. {
  385. e.Column.TextAlignment = TextAlignment.Center;
  386. e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Center;
  387. e.Column.ColumnSizer = GridLengthUnitType.None;
  388. var value = (e.Column.ValueBinding as Binding)!;
  389. if (value.Path.Path.Equals("Employee"))
  390. {
  391. e.Column.Width = 150;
  392. e.Column.HeaderStyle = Resources["DateHeaderStyle"] as Style;
  393. e.Column.AllowFocus = false;
  394. e.Column.DisplayBinding = new Binding { Path = new PropertyPath(e.Column.MappingName + ".Name") };
  395. }
  396. else if (value.Path.Path.Equals("Date"))
  397. {
  398. e.Column.Width = 80;
  399. e.Column.HeaderStyle = Resources["DateHeaderStyle"] as Style;
  400. e.Column.AllowFocus = false;
  401. }
  402. else
  403. {
  404. var style = new Style(typeof(GridCell));
  405. style.Setters.Add(new Setter(BackgroundProperty, new Binding(value.Path.Path) { Converter = new EmployeeResourcePlannerBackgroundConverter() }));
  406. style.Setters.Add(new Setter(ForegroundProperty, new Binding(value.Path.Path) { Converter = new EmployeeResourcePlannerForegroundConverter() }));
  407. style.Setters.Add(new Setter(FontStyleProperty, new Binding(value.Path.Path) { Converter = new EmployeeResourcePlannerFontStyleConverter() }));
  408. style.Setters.Add(new Setter(FontWeightProperty, new Binding(value.Path.Path) { Converter = new EmployeeResourcePlannerFontWeightConverter() }));
  409. e.Column.CellStyle = style;
  410. e.Column.Width = 55;
  411. e.Column.HeaderStyle = Resources["ContentHeaderStyle"] as Style;
  412. if(Settings.DisplayMode == EmployeePlannerDisplayMode.EmployeeColumns)
  413. {
  414. e.Column.HeaderText = (Guid.TryParse(value.Path.Path, out var id)
  415. ? _employees.FirstOrDefault(x => x.ID == id)?.Name ?? value.Path.Path
  416. : value.Path.Path);
  417. }
  418. else
  419. {
  420. if(int.TryParse(value.Path.Path, out var idx))
  421. {
  422. e.Column.HeaderText = _dates[idx].ToString("dd/MM/yyyy");
  423. }
  424. }
  425. e.Column.DisplayBinding = new Binding { Path = new PropertyPath(e.Column.MappingName), Converter = new EmployeeResourcePlannerContentConverter() };
  426. //e.Column.ValueBinding = new Binding() { Path = new PropertyPath(e.Column.MappingName), Converter = new LeaveContentConverter() };
  427. //e.Column.UseBindingValue = true;
  428. e.Column.AllowFocus = true;
  429. }
  430. }
  431. #endregion
  432. private bool HasData()
  433. {
  434. foreach (var cell in dataGrid.GetSelectedCells())
  435. {
  436. if (!cell.IsDataRowCell)
  437. continue;
  438. var propertyCollection = dataGrid.View.GetPropertyAccessProvider();
  439. var cellValue = propertyCollection.GetValue(cell.RowData, cell.Column.MappingName);
  440. if (cellValue is EmployeeResourcePlannerValue val && val.IDs.Length > 0)
  441. return true;
  442. }
  443. return false;
  444. }
  445. private bool HasData(GridCellInfo cell)
  446. {
  447. if (!cell.IsDataRowCell)
  448. return false;
  449. var propertyCollection = dataGrid.View.GetPropertyAccessProvider();
  450. var cellValue = propertyCollection.GetValue(cell.RowData, cell.Column.MappingName);
  451. return cellValue is EmployeeResourcePlannerValue val && val.IDs.Length > 0;
  452. }
  453. private void DataGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
  454. {
  455. var vc = dataGrid.GetVisualContainer();
  456. var p = Mouse.GetPosition(vc);
  457. var rci = vc.PointToCellRowColumnIndex(p);
  458. if (rci.RowIndex < 1 || rci.ColumnIndex < 1)
  459. {
  460. e.Handled = true;
  461. return;
  462. }
  463. dataGrid.ContextMenu.Items.Clear();
  464. var bAssign = !HasData(dataGrid.CurrentCellInfo);
  465. var bClear = HasData();
  466. if (bAssign)
  467. {
  468. foreach (var job in _jobs)
  469. {
  470. var assign = new MenuItem
  471. {
  472. Header = job.Name,
  473. Tag = job
  474. };
  475. assign.Click += AssignJobClick;
  476. dataGrid.ContextMenu.Items.Add(assign);
  477. }
  478. }
  479. if (bClear && bAssign)
  480. dataGrid.ContextMenu.Items.Add(new Separator());
  481. if (bClear)
  482. {
  483. var clear = new MenuItem { Header = "Clear Assignments" };
  484. clear.Click += ClearJobClick;
  485. dataGrid.ContextMenu.Items.Add(clear);
  486. }
  487. }
  488. private void dataGrid_CellToolTipOpening(object sender, GridCellToolTipOpeningEventArgs e)
  489. {
  490. var record = (e.Record as DataRowView)?[e.Column.MappingName];
  491. if (record is not EmployeeResourcePlannerValue value) return;
  492. if(value.IDs.Length > 0)
  493. {
  494. e.ToolTip.Template = TemplateGenerator.CreateControlTemplate(
  495. typeof(ToolTip),
  496. () =>
  497. {
  498. var border = new Border
  499. {
  500. BorderBrush = new SolidColorBrush(Colors.Gray),
  501. BorderThickness = new Thickness(0.75),
  502. CornerRadius = new CornerRadius(5),
  503. Background = new SolidColorBrush(Colors.LightYellow),
  504. Padding = new Thickness(5),
  505. };
  506. var panel = new StackPanel();
  507. foreach (var assignment in value.Assignments)
  508. {
  509. var textBlock = new TextBlock();
  510. textBlock.Inlines.Add(new Run
  511. {
  512. Text = assignment.JobNumber,
  513. FontWeight = FontWeights.Bold
  514. });
  515. textBlock.Inlines.Add(new Run
  516. {
  517. Text = $": {assignment.EffectiveStart():hh':'mm} - {assignment.EffectiveFinish():hh':'mm}"
  518. });
  519. panel.Children.Add(textBlock);
  520. }
  521. border.Child = panel;
  522. return border;
  523. });
  524. }
  525. }
  526. private void GetSelectionData(out DateTime from, out DateTime to, out Guid[] employees, out Guid[] assignments)
  527. {
  528. var emps = new List<Guid>();
  529. var items = new List<Guid>();
  530. from = DateTime.MaxValue;
  531. to = DateTime.MinValue;
  532. var period = GetPeriod();
  533. foreach (var cell in dataGrid.GetSelectedCells())
  534. {
  535. var row = (cell.RowData as DataRowView)!;
  536. var binding = (cell.Column.ValueBinding as Binding)!;
  537. DateTime date;
  538. if(Settings.DisplayMode == EmployeePlannerDisplayMode.EmployeeColumns)
  539. {
  540. if (Guid.TryParse(binding.Path.Path, out var emp))
  541. if (!emps.Contains(emp))
  542. emps.Add(emp);
  543. date = (DateTime)row.Row.ItemArray.First()!;
  544. }
  545. else
  546. {
  547. if(int.TryParse(binding.Path.Path, out var idx))
  548. {
  549. date = _dates[idx];
  550. }
  551. else
  552. {
  553. date = DateTime.MinValue;
  554. }
  555. var empID = ((EmployeeResourceModel)row.Row.ItemArray.First()!).ID;
  556. if (!emps.Contains(empID))
  557. emps.Add(empID);
  558. }
  559. if(date != DateTime.MinValue)
  560. {
  561. if (date < from)
  562. from = date;
  563. if (date + period > to)
  564. to = date + period;
  565. }
  566. foreach(var assignment in (row[binding.Path.Path] as EmployeeResourcePlannerValue)!.Assignments)
  567. {
  568. items.Add(assignment.ID);
  569. }
  570. }
  571. employees = emps.ToArray();
  572. assignments = items.ToArray();
  573. }
  574. private void AssignJobClick(object sender, RoutedEventArgs e)
  575. {
  576. if ((sender as MenuItem)?.Tag is not JobModel job)
  577. return;
  578. GetSelectionData(out var from, out var to, out var ids, out var assignments);
  579. var updates = new List<Assignment>();
  580. foreach (var id in ids)
  581. {
  582. for (DateTime curdate = from; curdate < to; curdate = curdate.AddDays(1))
  583. {
  584. var employee = _employees.FirstOrDefault(x => x.ID == id);
  585. if (employee != null)
  586. {
  587. bool bAvail =
  588. (GetStandardLeave(curdate, _standardleaves) == null)
  589. && !CheckLeaveRequest(employee, curdate, curdate.AddDays(1), _leaverequests, new EmployeeResourcePlannerValue());
  590. var roster = bAvail ? RosterUtils.GetRoster(employee.Roster, employee.RosterStart, curdate) : null;
  591. if (roster?.Enabled == true && roster.Finish > roster.Start)
  592. {
  593. var assign = new Assignment();
  594. assign.Date = curdate;
  595. assign.Booked.Start = roster.Start;
  596. assign.Booked.Finish = roster.Finish;
  597. assign.JobLink.ID = job.ID;
  598. assign.EmployeeLink.ID = id;
  599. assign.ActivityLink.ID = Properties.ActivityType;
  600. assign.JobScope.ID = job.DefaultScopeID;
  601. updates.Add(assign);
  602. }
  603. if (roster?.SplitShift == true && roster.Finish2 > roster.Start2)
  604. {
  605. var assign = new Assignment();
  606. assign.Date = curdate;
  607. assign.Booked.Start = roster.Start2;
  608. assign.Booked.Finish = roster.Finish2;
  609. assign.JobLink.ID = job.ID;
  610. assign.EmployeeLink.ID = id;
  611. assign.ActivityLink.ID = Properties.ActivityType;
  612. assign.JobScope.ID = job.DefaultScopeID;
  613. updates.Add(assign);
  614. }
  615. }
  616. }
  617. }
  618. if (updates.Any())
  619. {
  620. using (new WaitCursor())
  621. {
  622. new Client<Assignment>().Save(updates, "Assigned from Employee Resource Planner");
  623. Refresh();
  624. }
  625. }
  626. }
  627. private void ClearJobClick(object sender, RoutedEventArgs e)
  628. {
  629. GetSelectionData(out DateTime from, out DateTime to, out Guid[] ids, out Guid[] assignments);
  630. if (assignments.Any() && MessageBox.Show("Clear Assignments?", "Confirm", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
  631. {
  632. var deletes = assignments.Select(x => new Assignment() { ID = x }).ToArray();
  633. using (new WaitCursor())
  634. {
  635. new Client<Assignment>().Delete(deletes, "Deleted from Employee Resource Planner");
  636. Refresh();
  637. }
  638. }
  639. }
  640. public void Heartbeat(TimeSpan time)
  641. {
  642. }
  643. private void _employees_OnSettingsChanged(object sender, EmployeeSelectorSettingsChangedArgs args)
  644. {
  645. Properties.EmployeeSettings = args.Settings;
  646. DoSaveSettings();
  647. }
  648. private void _employees_OnSelectionChanged(object sender, EmployeeSelectorSelectionChangedArgs args)
  649. {
  650. Properties.EmployeeSelection = args.Selection;
  651. DoSaveSettings();
  652. Refresh();
  653. }
  654. private void DateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  655. {
  656. if (EventSuppressor.IsSet(Suppress.This))
  657. return;
  658. Refresh();
  659. }
  660. private void ViewType_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  661. {
  662. if (EventSuppressor.IsSet(Suppress.This))
  663. return;
  664. Properties.ViewType = (EmployeeResourcePlannerViewType)ViewType.SelectedValue;
  665. DoSaveSettings();
  666. Refresh();
  667. }
  668. private void LeaveType_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  669. {
  670. if (EventSuppressor.IsSet(Suppress.This))
  671. return;
  672. Properties.IncludeUnApprovedLeave = LeaveType.SelectedIndex > 0;
  673. DoSaveSettings();
  674. Refresh();
  675. }
  676. private void ActivityType_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  677. {
  678. if (EventSuppressor.IsSet(Suppress.This))
  679. return;
  680. Properties.ActivityType = (Guid)(ActivityType.SelectedValue ?? Guid.Empty);
  681. DoSaveSettings();
  682. }
  683. private void JobFilter_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  684. {
  685. if (EventSuppressor.IsSet(Suppress.This))
  686. return;
  687. var sel = JobFilter.SelectedValue as CoreFilterDefinition;
  688. Properties.JobFilter = sel?.Name ?? "";
  689. using (new WaitCursor())
  690. {
  691. DoSaveSettings();
  692. _jobs = new Client<Job>().Query(
  693. GetJobFilter(),
  694. JobModel.Columns,
  695. new SortOrder<Job>(x => x.JobNumber)
  696. ).Rows.Select(r => new JobModel(r)).ToArray();
  697. }
  698. }
  699. private void JobFilterButton_Click(object sender, RoutedEventArgs e)
  700. {
  701. var window = new DynamicGridFilterEditor(_jobfilters, typeof(Job));
  702. if (window.ShowDialog() == true)
  703. {
  704. new GlobalConfiguration<CoreFilterDefinitions>("Job").Save(_jobfilters);
  705. JobFilter.SelectedValue = _jobfilters.FirstOrDefault(x => String.Equals(x.Name, Properties.JobFilter));
  706. }
  707. }
  708. }