EmployeeResourcePlanner.xaml.cs 34 KB

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