JobResourcePlanner.xaml.cs 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461
  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. using Columns = InABox.Core.Columns;
  26. using InABox.Wpf;
  27. using MYOB.AccountRight.SDK.Extensions;
  28. using Syncfusion.Data.Extensions;
  29. using NPOI.OpenXmlFormats.Spreadsheet;
  30. using Syncfusion.UI.Xaml.Scheduler;
  31. using Color = System.Drawing.Color;
  32. namespace PRSDesktop;
  33. public enum JobPlannerDisplayMode
  34. {
  35. JobColumns,
  36. DateColumns
  37. }
  38. public class JobResourcePlannerProperties : IUserConfigurationSettings, IDashboardProperties
  39. {
  40. public JobSelectorSettings JobSettings { get; set; }
  41. public JobSelectorData JobSelection { get; set; }
  42. public Guid ActivityType { get; set; }
  43. public int MonthsToView { get; set; }
  44. public TeamSelectorSettings TeamSettings { get; set; }
  45. public TeamSelectorData TeamSelection { get; set; }
  46. public double SplitterPosition { get; set; }
  47. public double HoursPerDay { get; set; }
  48. public double EmployeeSplitterPosition { get; set; }
  49. public bool IncludeUnApprovedLeave { get; set; }
  50. public JobPlannerDisplayMode DisplayMode { get; set; }
  51. public JobResourcePlannerProperties()
  52. {
  53. JobSettings = new JobSelectorSettings();
  54. JobSelection = new JobSelectorData();
  55. TeamSettings = new TeamSelectorSettings();
  56. TeamSelection = new TeamSelectorData();
  57. MonthsToView = 1;
  58. SplitterPosition = 0F;
  59. EmployeeSplitterPosition = 0F;
  60. HoursPerDay = 8.5;
  61. ActivityType = Guid.Empty;
  62. IncludeUnApprovedLeave = false;
  63. DisplayMode = JobPlannerDisplayMode.DateColumns;
  64. }
  65. }
  66. public partial class JobResourcePlanner : UserControl
  67. {
  68. private enum Suppress
  69. {
  70. This
  71. }
  72. private ActivityModel[] _activities = new ActivityModel[] { };
  73. private LeaveRequestModel[] _leaverequests = new LeaveRequestModel[] { };
  74. private StandardLeaveModel[] _standardleaves = new StandardLeaveModel[] { };
  75. private JobModel[] _jobs = new JobModel[] { };
  76. private EmployeeResourceModel[] _emps = new EmployeeResourceModel[] { };
  77. private List<AssignmentModel> _assignments = new List<AssignmentModel>();
  78. private DateTime[] _dates = Array.Empty<DateTime>();
  79. private double[] _totals = Array.Empty<double>();
  80. private double[] _available = Array.Empty<double>();
  81. public JobResourcePlannerProperties Properties { get; set; }
  82. public event LoadSettings<JobResourcePlannerProperties>? LoadSettings;
  83. public event SaveSettings<JobResourcePlannerProperties>? SaveSettings;
  84. private void DoLoadSettings()
  85. {
  86. Properties = LoadSettings?.Invoke(this) ?? new JobResourcePlannerProperties();
  87. }
  88. private void DoSaveSettings()
  89. {
  90. SaveSettings?.Invoke(this, Properties);
  91. }
  92. public JobResourcePlanner()
  93. {
  94. using (new EventSuppressor(Suppress.This))
  95. InitializeComponent();
  96. }
  97. public void Setup()
  98. {
  99. using (new EventSuppressor(Suppress.This))
  100. {
  101. DoLoadSettings();
  102. JobSelector.Setup();
  103. JobSelector.Settings = Properties.JobSettings;
  104. JobSelector.Selection = Properties.JobSelection;
  105. _jobs = JobSelector.GetJobData<JobModel>(r => new JobModel(r));
  106. TeamSelector.Setup();
  107. TeamSelector.Settings = Properties.TeamSettings;
  108. TeamSelector.Selection = Properties.TeamSelection;
  109. _emps = TeamSelector.GetEmployeeData<EmployeeResourceModel>((row, rosters) => new EmployeeResourceModel(row, rosters));
  110. ViewWindow.ItemsSource = new Dictionary<int, String>()
  111. {
  112. { 1, "1 Month" },
  113. { 3, "3 Months" },
  114. { 6, "6 Months" },
  115. { 12, "12 months" }
  116. };
  117. ViewWindow.SelectedValue = Properties.MonthsToView;
  118. if (Properties.SplitterPosition != 0F)
  119. TeamSelectorRow.Height = new GridLength(Properties.SplitterPosition, GridUnitType.Pixel);
  120. if (Properties.EmployeeSplitterPosition != 0F)
  121. AvailableEmployeesRow.Height = new GridLength(Properties.SplitterPosition, GridUnitType.Pixel);
  122. HoursSelector.Text = $"{Properties.HoursPerDay:F2}";
  123. LeaveType.SelectedIndex = Properties.IncludeUnApprovedLeave ? 1 : 0;
  124. Orientation.SelectedIndex = Properties.DisplayMode == JobPlannerDisplayMode.JobColumns ? 1 : 0;
  125. AvailableEmployees.Refresh(true, false);
  126. AssignedEmployees.Refresh(true, false);
  127. MultiQuery query = new MultiQuery();
  128. query.Add<StandardLeave>(
  129. LookupFactory.DefineFilter<StandardLeave>(),
  130. StandardLeaveModel.Columns
  131. );
  132. query.Add<LeaveRequest>(
  133. new Filter<LeaveRequest>(x => x.Status).IsNotEqualTo(LeaveRequestStatus.Rejected),
  134. LeaveRequestModel.Columns
  135. );
  136. query.Add<Activity>(
  137. LookupFactory.DefineFilter<Activity>(),
  138. Columns.None<Activity>().Add(x => x.ID).Add(x => x.Code).Add(x => x.Description),
  139. new SortOrder<Activity>(x => x.Code)
  140. );
  141. query.Query();
  142. _standardleaves = query.Get<StandardLeave>().Rows.Select(r=>new StandardLeaveModel(r)).ToArray();
  143. _leaverequests = query.Get<LeaveRequest>().Rows.Select(r => new LeaveRequestModel(r)).ToArray();
  144. _activities = query.Get<Activity>().Rows.Select(r => new ActivityModel(r)).ToArray();
  145. ActivityType.ItemsSource = _activities;
  146. ActivityType.SelectedValue = Properties.ActivityType;
  147. }
  148. }
  149. public void Shutdown(CancelEventArgs? cancel)
  150. {
  151. }
  152. private bool GetStandardLeaveTimes(DateTime date, out TimeSpan start, out TimeSpan finish)
  153. {
  154. bool result = false;
  155. start = TimeSpan.Zero;
  156. finish = TimeSpan.FromDays(1);
  157. var requests = _standardleaves.Where(x =>
  158. (x.From <= date)
  159. && (x.To.Add(x.ToTime) > date)
  160. );
  161. if (requests.Any())
  162. {
  163. result = true;
  164. start = TimeSpan.FromDays(1);
  165. finish = TimeSpan.Zero;
  166. foreach (var leave in requests)
  167. {
  168. var curstart = leave.From == date ? leave.FromTime : TimeSpan.Zero;
  169. start = start > curstart ? curstart : start;
  170. var curfinish = leave.To == date ? leave.ToTime : TimeSpan.FromDays(1);
  171. finish = finish < curfinish ? curfinish : finish;
  172. }
  173. }
  174. return result;
  175. }
  176. private void AdjustStandardLeave(DateTime date, ref TimeSpan time)
  177. {
  178. TimeSpan result = TimeSpan.Zero;
  179. var leaves = _standardleaves.Where(x =>
  180. (x.From <= date)
  181. && (x.To.Add(x.ToTime) > date)
  182. );
  183. foreach (var leave in leaves)
  184. {
  185. result += (leave.To == date ? leave.ToTime : TimeSpan.FromDays(1)) -
  186. (leave.From == date ? leave.FromTime : TimeSpan.Zero);
  187. }
  188. time = time >= result ? time - result : TimeSpan.Zero;
  189. }
  190. private bool GetLeaveRequestTimes(DateTime date, EmployeeResourceModel emp, out TimeSpan start, out TimeSpan finish)
  191. {
  192. bool result = false;
  193. start = TimeSpan.Zero;
  194. finish = TimeSpan.FromDays(1);
  195. var requests = _leaverequests.Where(x =>
  196. (x.From <= date)
  197. && (x.To.Add(x.ToTime) > date)
  198. && (x.EmployeeID == emp.ID)
  199. && (Properties.IncludeUnApprovedLeave ? true : x.Status == LeaveRequestStatus.Approved)
  200. );
  201. if (requests.Any())
  202. {
  203. result = true;
  204. start = TimeSpan.FromDays(1);
  205. finish = TimeSpan.Zero;
  206. foreach (var leave in requests)
  207. {
  208. var curstart = leave.From == date ? leave.FromTime : TimeSpan.Zero;
  209. start = start > curstart ? curstart : start;
  210. var curfinish = leave.To == date ? leave.ToTime : TimeSpan.FromDays(1);
  211. finish = finish < curfinish ? curfinish : finish;
  212. }
  213. }
  214. return result;
  215. }
  216. private void AdjustLeaveRequests(DateTime date, EmployeeResourceModel emp, ref TimeSpan time)
  217. {
  218. TimeSpan result = TimeSpan.Zero;
  219. var requests = _leaverequests.Where(x =>
  220. (x.From <= date)
  221. && (x.To.Add(x.ToTime) > date)
  222. && (x.EmployeeID == emp.ID)
  223. && (Properties.IncludeUnApprovedLeave ? true : x.Status == LeaveRequestStatus.Approved)
  224. );
  225. foreach (var leave in requests)
  226. {
  227. result += (leave.To == date ? leave.ToTime : TimeSpan.FromDays(1)) -
  228. (leave.From == date ? leave.FromTime : TimeSpan.Zero);
  229. }
  230. time = time >= result ? time - result : TimeSpan.Zero;
  231. }
  232. public void Refresh()
  233. {
  234. using (new WaitCursor())
  235. {
  236. var jobids = _jobs.Select(x => x.ID).ToArray();
  237. var empids = _emps.Select(x => x.ID).ToArray();
  238. var actids = _activities.Select(x => x.ID).ToArray();
  239. DateTime fromDate = DateTime.Today;
  240. DateTime toDate = DateTime.Today.AddMonths(Properties.MonthsToView);
  241. MultiQuery query = new MultiQuery();
  242. query.Add<Assignment>(
  243. new Filter<Assignment>(x=>x.EmployeeLink.ID).InList(empids)
  244. .And(x=>x.Date).IsGreaterThanOrEqualTo(fromDate)
  245. .And(x=>x.Date).IsLessThanOrEqualTo(toDate),
  246. AssignmentModel.Columns
  247. );
  248. query.Query();
  249. _assignments = query.Get<Assignment>().Rows.Select(r => new AssignmentModel(r)).ToList();
  250. var data = new DataTable();
  251. var dates = new List<DateTime>();
  252. for (var curdate = fromDate; curdate <= toDate; curdate = curdate.AddDays(1))
  253. {
  254. dates.Add(curdate);
  255. }
  256. _dates = dates.ToArray();
  257. var dataValues = new object?[dates.Count, _jobs.Length];
  258. var available = new double[dates.Count];
  259. var totals = new double[dates.Count];
  260. foreach (var (dateIdx, date) in dates.WithIndex())
  261. {
  262. double avail = 0.0F;
  263. foreach (var emp in _emps)
  264. {
  265. var roster = RosterUtils.GetRoster(emp.Roster, emp.RosterStart, date);
  266. var hours = roster.GetBlocks(date, TimeSpan.MinValue, TimeSpan.MaxValue)
  267. .Aggregate<RosterBlock, TimeSpan>(TimeSpan.Zero, (value, block) => value + block.Duration);
  268. AdjustStandardLeave(date, ref hours);
  269. AdjustLeaveRequests(date, emp, ref hours);
  270. avail += hours.TotalHours;
  271. }
  272. var total = avail;
  273. var values = new List<object?> { date };
  274. var anyjobstoday = _assignments.Where(x => (x.Date.Date == date.Date));
  275. avail -= anyjobstoday.Aggregate<AssignmentModel, double>(0F, (value, model) => value + model.BookedDuration.TotalHours);
  276. foreach (var (jobIdx, job) in _jobs.WithIndex())
  277. {
  278. var thisjobtoday = _assignments.Where(x => (x.Date.Date == date.Date) && (x.JobID == job.ID));
  279. if (thisjobtoday.Any())
  280. {
  281. var assigned = thisjobtoday.Aggregate<AssignmentModel, double>(0F,
  282. (value, model) => value + model.BookedDuration.TotalHours);
  283. dataValues[dateIdx, jobIdx] = assigned / Properties.HoursPerDay;
  284. }
  285. else
  286. dataValues[dateIdx, jobIdx] = null;
  287. }
  288. available[dateIdx] = avail / Properties.HoursPerDay;
  289. totals[dateIdx] = total / Properties.HoursPerDay;
  290. }
  291. _totals = totals;
  292. _available = available;
  293. if(Properties.DisplayMode == JobPlannerDisplayMode.JobColumns)
  294. {
  295. dataGrid.HeaderRowHeight = 200;
  296. data.Columns.Add("Date", typeof(DateTime));
  297. data.Columns.Add("Available", typeof(object));
  298. foreach (var job in _jobs)
  299. data.Columns.Add(job.ID.ToString(), typeof(object));
  300. foreach(var (dateIdx, date) in dates.WithIndex())
  301. {
  302. var values = new List<object?>(_jobs.Length + 3) { date };
  303. foreach (var (jobIdx, job) in _jobs.WithIndex())
  304. {
  305. values.Add(dataValues[dateIdx, jobIdx]);
  306. }
  307. values.Insert(1, available[dateIdx]);
  308. data.Rows.Add(values.ToArray());
  309. }
  310. }
  311. else if(Properties.DisplayMode == JobPlannerDisplayMode.DateColumns)
  312. {
  313. dataGrid.HeaderRowHeight = 30;
  314. data.Columns.Add("Job", typeof(object));
  315. var availableRow = new List<object?> { "Available" };
  316. foreach(var (dateIdx, date) in dates.WithIndex())
  317. {
  318. data.Columns.Add(dateIdx.ToString(), typeof(object));
  319. availableRow.Add(available[dateIdx]);
  320. }
  321. data.Rows.Add(availableRow.ToArray());
  322. foreach (var (jobIdx, job) in _jobs.WithIndex())
  323. {
  324. var values = new List<object?>(dates.Count + 1) { job };
  325. foreach(var (dateIdx, date) in dates.WithIndex())
  326. {
  327. values.Add(dataValues[dateIdx, jobIdx]);
  328. }
  329. data.Rows.Add(values.ToArray());
  330. }
  331. }
  332. dataGrid.ItemsSource = data;
  333. }
  334. }
  335. private interface ICellWrapper
  336. {
  337. DataRowView Row { get; }
  338. string ColumnName { get; }
  339. }
  340. public class GridCellInfoWrapper : ICellWrapper
  341. {
  342. private GridCellInfo Cell;
  343. public DataRowView Row => (Cell.RowData as DataRowView)!;
  344. public string ColumnName => (Cell.Column.ValueBinding as Binding)!.Path.Path;
  345. public GridCellInfoWrapper(GridCellInfo cell)
  346. {
  347. Cell = cell;
  348. }
  349. }
  350. public class GridCellWrapper : ICellWrapper
  351. {
  352. private GridCell Cell;
  353. public DataRowView Row => (Cell.DataContext as DataRowView)!;
  354. public string ColumnName => (Cell.ColumnBase?.GridColumn.ValueBinding as Binding)?.Path.Path ?? "";
  355. public GridCellWrapper(GridCell cell)
  356. {
  357. Cell = cell;
  358. }
  359. }
  360. private int? GetDateIndex(ICellWrapper cell)
  361. {
  362. if(Properties.DisplayMode == JobPlannerDisplayMode.JobColumns)
  363. {
  364. if (cell?.Row?.Row?.ItemArray?.First() is DateTime dt)
  365. return _dates.IndexOf(dt);
  366. return null;
  367. }
  368. else
  369. {
  370. if(int.TryParse(cell.ColumnName, out var idx))
  371. {
  372. return idx;
  373. }
  374. else
  375. {
  376. return null;
  377. }
  378. }
  379. }
  380. private double? GetTotal(ICellWrapper cell)
  381. {
  382. var dateIndex = GetDateIndex(cell);
  383. return dateIndex.HasValue ? _totals[dateIndex.Value] : null;
  384. }
  385. private double? GetAvailable(ICellWrapper cell)
  386. {
  387. if(Properties.DisplayMode == JobPlannerDisplayMode.JobColumns)
  388. {
  389. if (cell?.Row?.Row?.Table?.Columns?.Contains("Available") == true && cell.Row["Available"] is double d)
  390. return d;
  391. return null;
  392. }
  393. else
  394. {
  395. var dateIndex = GetDateIndex(cell);
  396. return dateIndex.HasValue ? _available[dateIndex.Value] : null;
  397. }
  398. }
  399. private abstract class LeaveConverter : IMultiValueConverter
  400. {
  401. protected JobResourcePlanner Planner { get; set; }
  402. public LeaveConverter(JobResourcePlanner planner)
  403. {
  404. Planner = planner;
  405. }
  406. protected System.Windows.Media.Color GetColor(object[] value)
  407. {
  408. if ((value[0] != DBNull.Value) && (value[0] != DependencyProperty.UnsetValue) && (value[0] is not double d || d != 0.0))
  409. return Colors.DarkSeaGreen;
  410. if (value[1] is GridCell cell)
  411. {
  412. var cellWrp = new GridCellWrapper(cell);
  413. var total = Planner.GetTotal(cellWrp);
  414. var available = Planner.GetAvailable(cellWrp);
  415. if (!total.HasValue || total == 0.0)
  416. return Colors.LightGray;
  417. if (!available.HasValue || available == 0.0F)
  418. return Colors.LightSalmon;
  419. return Colors.LightYellow;
  420. }
  421. return Colors.WhiteSmoke;
  422. }
  423. public abstract object Convert(object[] value, Type targetType, object parameter, CultureInfo culture);
  424. public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
  425. {
  426. throw new NotImplementedException();
  427. }
  428. }
  429. private class LeaveBackgroundConverter : LeaveConverter
  430. {
  431. public LeaveBackgroundConverter(JobResourcePlanner planner): base(planner) { }
  432. public override object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
  433. {
  434. return new SolidColorBrush(base.GetColor(value)) { Opacity = 0.8 };
  435. }
  436. }
  437. private class LeaveForegroundConverter : LeaveConverter
  438. {
  439. public LeaveForegroundConverter(JobResourcePlanner planner): base(planner) { }
  440. public override object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
  441. {
  442. return new SolidColorBrush(ImageUtils.GetForegroundColor(base.GetColor(value)));
  443. }
  444. }
  445. private void DataGrid_AutoGeneratingColumn(object sender, AutoGeneratingColumnArgs e)
  446. {
  447. MultiBinding CreateBinding<TConverter>(String path, TConverter converter) where TConverter : IMultiValueConverter
  448. {
  449. var binding = new MultiBinding();
  450. binding.Bindings.Add(new Binding(path));
  451. binding.Bindings.Add(new Binding() { RelativeSource = new RelativeSource(RelativeSourceMode.Self) });
  452. binding.Converter = converter;
  453. return binding;
  454. }
  455. var value = (e.Column.ValueBinding as Binding)!;
  456. if (value.Path.Path.Equals("Date"))
  457. {
  458. e.Column.Width = 80;
  459. e.Column.HeaderStyle = Resources["DateHeaderStyle"] as Style;
  460. e.Column.AllowFocus = false;
  461. }
  462. else if (value.Path.Path.Equals("Job"))
  463. {
  464. e.Column.Width = 250;
  465. e.Column.HeaderStyle = Resources["DateHeaderStyle"] as Style;
  466. e.Column.AllowFocus = false;
  467. e.Column.TextAlignment = TextAlignment.Left;
  468. e.Column.DisplayBinding = new Binding
  469. {
  470. Path = new PropertyPath(e.Column.MappingName),
  471. Converter = new FuncConverter<object?, object?>(x => x is JobModel job ? job.Name : x)
  472. };
  473. e.Column.HeaderText = "Project Name";
  474. }
  475. else if (value.Path.Path.Equals("Available"))
  476. {
  477. e.Column = new GridNumericColumn() { NumberDecimalDigits = 2, MappingName = e.Column.MappingName};
  478. e.Column.Width = 50;
  479. e.Column.HeaderStyle = e.Column.HeaderStyle = Resources["RotatedHeaderStyle"] as Style;
  480. e.Column.AllowFocus = false;
  481. e.Column.HeaderText = "Available";
  482. }
  483. else
  484. {
  485. e.Column = new GridNumericColumn() { NumberDecimalDigits = 2, MappingName = e.Column.MappingName};
  486. if(Properties.DisplayMode == JobPlannerDisplayMode.JobColumns)
  487. {
  488. e.Column.Width = 40;
  489. e.Column.HeaderStyle = Resources["RotatedHeaderStyle"] as Style;
  490. e.Column.HeaderText = (Guid.TryParse(value.Path.Path, out var id)
  491. ? _jobs.FirstOrDefault(x => x.ID == id)?.Name ?? value.Path.Path
  492. : value.Path.Path);
  493. }
  494. else
  495. {
  496. e.Column.Width = 50;
  497. e.Column.HeaderStyle = Resources["ContentHeaderStyle"] as Style;
  498. if(int.TryParse(value.Path.Path, out var idx))
  499. {
  500. e.Column.HeaderText = _dates[idx].ToString("dd/MM");
  501. }
  502. }
  503. e.Column.DisplayBinding = new Binding { Path = new PropertyPath(e.Column.MappingName) };
  504. e.Column.AllowFocus = true;
  505. var style = new Style(typeof(GridCell));
  506. style.Setters.Add(new Setter(BackgroundProperty, CreateBinding<LeaveBackgroundConverter>(value.Path.Path, new(this))));
  507. style.Setters.Add(new Setter(ForegroundProperty, CreateBinding<LeaveForegroundConverter>(value.Path.Path, new(this))));
  508. e.Column.CellStyle = style;
  509. e.Column.TextAlignment = TextAlignment.Center;
  510. e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Center;
  511. }
  512. e.Column.ColumnSizer = GridLengthUnitType.None;
  513. e.Column.ShowHeaderToolTip = false;
  514. e.Column.ShowToolTip = false;
  515. }
  516. private void DataGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
  517. {
  518. var emps = TeamSelector.GetEmployeeData<EmployeeResourceModel>((row, rosters) => new EmployeeResourceModel(row, rosters));
  519. }
  520. private void DataGrid_OnSelectionChanging(object? sender, GridSelectionChangingEventArgs e)
  521. {
  522. var selected = dataGrid.SelectionController.SelectedCells;
  523. var added = e.AddedItems.OfType<GridCellInfo>();
  524. if (selected.Any() && added.Any())
  525. {
  526. if (Properties.DisplayMode == JobPlannerDisplayMode.JobColumns)
  527. e.Cancel = added.Any(a => selected.All(s => s.Column != a.Column));
  528. else
  529. e.Cancel = selected.Union(added).GroupBy(x => x.Column).Any(g => g.Count() > 1);
  530. }
  531. }
  532. private bool bResettingSelection = false;
  533. private void DataGrid_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
  534. {
  535. bResettingSelection = true;
  536. dataGrid.SelectionController.ClearSelections(false);
  537. }
  538. private void DataGrid_OnCurrentCellActivating(object? sender, CurrentCellActivatingEventArgs e)
  539. {
  540. if (bResettingSelection)
  541. {
  542. bResettingSelection = false;
  543. return;
  544. }
  545. // if (Properties.DisplayMode == JobPlannerDisplayMode.JobColumns)
  546. // {
  547. // var thiscol = dataGrid.Columns[e.CurrentRowColumnIndex.ColumnIndex];
  548. // var selected = dataGrid.SelectionController.SelectedCells;
  549. // if (selected.Any(x => x.Column != thiscol))
  550. // e.Cancel = true;
  551. // else
  552. // e.Cancel = false;
  553. //
  554. // }
  555. // else
  556. // {
  557. // var current = e.CurrentRowColumnIndex.RowIndex;
  558. // var original = dataGrid.SelectionController.SelectedCells;
  559. // // This is Dumb, but it seems the only way to find if multiple rows have been selected
  560. // // ie if there are two cells with the same column, we must have multiple rows
  561. // // Stupid SelectedCell -> RowIndex is alwasy showing up as -1!
  562. // if (original.GroupBy(x => x.Column).Any(g=>g.Count() > 1))
  563. // e.Cancel = true;
  564. // else
  565. // e.Cancel = false;
  566. // }
  567. }
  568. private void DataGrid_OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
  569. {
  570. }
  571. private void GetCellData(ICellWrapper cell, out (Guid jobID, DateTime date) result)
  572. {
  573. result = (Guid.Empty, DateTime.MinValue);
  574. if(Properties.DisplayMode == JobPlannerDisplayMode.JobColumns)
  575. {
  576. if (Guid.TryParse(cell.ColumnName, out var emp))
  577. {
  578. result.jobID = emp;
  579. }
  580. result.date = (DateTime)cell.Row.Row.ItemArray.First()!;
  581. }
  582. else
  583. {
  584. if(int.TryParse(cell.ColumnName, out var idx))
  585. {
  586. result.date = _dates[idx];
  587. }
  588. else
  589. {
  590. result.date = DateTime.MinValue;
  591. }
  592. var jobModel = cell.Row.Row.ItemArray.First() as JobModel;
  593. result.jobID = jobModel?.ID ?? Guid.Empty;
  594. }
  595. }
  596. private ICellWrapper Wrap(GridCellInfo cell)
  597. {
  598. return new GridCellInfoWrapper(cell);
  599. }
  600. private ICellWrapper Wrap(GridCell cell)
  601. {
  602. return new GridCellWrapper(cell);
  603. }
  604. private bool ExtractSelection(out Guid[] jobs, out DateTime[] dates)
  605. {
  606. var from = DateTime.MaxValue;
  607. var to = DateTime.MinValue;
  608. var jobList = new List<Guid>();
  609. foreach (var cell in dataGrid.GetSelectedCells())
  610. {
  611. GetCellData(Wrap(cell), out var result);
  612. if (result.jobID == Guid.Empty) continue;
  613. if (!jobList.Contains(result.jobID))
  614. jobList.Add(result.jobID);
  615. if(result.date != DateTime.MinValue)
  616. {
  617. if (result.date < from)
  618. from = result.date;
  619. if (result.date > to)
  620. to = result.date;
  621. }
  622. }
  623. if(jobList.Count > 0 && to != DateTime.MinValue && from != DateTime.MaxValue)
  624. {
  625. jobs = jobList.ToArray();
  626. var datesList = new List<DateTime>();
  627. for(DateTime date = from; date <= to; date = date.AddDays(1))
  628. {
  629. datesList.Add(date);
  630. }
  631. dates = datesList.ToArray();
  632. return true;
  633. }
  634. else
  635. {
  636. jobs = [];
  637. dates = [];
  638. return false;
  639. }
  640. }
  641. private void DataGrid_OnMouseUp(object sender, MouseButtonEventArgs e)
  642. {
  643. if (ExtractSelection(out var jobIDs, out var dates))
  644. {
  645. if(jobIDs.Length == 1)
  646. {
  647. LoadAssignedEmployees(jobIDs, dates);
  648. LoadAvailableEmployees(dates);
  649. }
  650. else
  651. {
  652. AvailableEmployees.Items = [];
  653. AssignedEmployees.Items = [];
  654. AvailableEmployees.Refresh(false, true);
  655. AssignedEmployees.Refresh(false, true);
  656. }
  657. }
  658. }
  659. private JobPlannerEmployee GetEmployee(Guid id, List<JobPlannerEmployee> list)
  660. {
  661. var result = list.FirstOrDefault(x => x.ID == id);
  662. if (result == null)
  663. {
  664. result = new JobPlannerEmployee()
  665. {
  666. ID = id,
  667. Name = _emps.FirstOrDefault(x=>x.ID == id)?.Name ?? id.ToString(),
  668. Time = TimeSpan.Zero
  669. };
  670. list.Add(result);
  671. }
  672. return result;
  673. }
  674. private void LoadAvailableEmployees(DateTime[] dates)
  675. {
  676. List<JobPlannerEmployee> availableemployees = new List<JobPlannerEmployee>();
  677. foreach (var emp in _emps)
  678. {
  679. foreach (var date in dates)
  680. {
  681. var roster = RosterUtils.GetRoster(emp.Roster, emp.RosterStart, date);
  682. var blocks = roster?.GetBlocks(date, TimeSpan.MinValue, TimeSpan.MaxValue) ?? new RosterBlock[] { };
  683. var rostered = blocks.Aggregate(TimeSpan.Zero, (time, block) => time += block.Duration);
  684. AdjustStandardLeave(date, ref rostered);
  685. AdjustLeaveRequests(date, emp, ref rostered);
  686. var assignments = _assignments.Where(x => (x.Date == date) && (x.EmployeeID == emp.ID));
  687. var assigned = assignments.Aggregate(TimeSpan.Zero, (time, assign) => time += assign.BookedDuration);
  688. if (rostered > assigned)
  689. GetEmployee(emp.ID, availableemployees).Time += rostered.Subtract(assigned);
  690. AvailableEmployees.Items = availableemployees.OrderBy(x=>x.Name).ToList();
  691. AvailableEmployees.Refresh(false, true);
  692. }
  693. }
  694. }
  695. private void LoadAssignedEmployees(Guid[] jobIDs, DateTime[] dates)
  696. {
  697. List<JobPlannerEmployee> assignedemployees = new List<JobPlannerEmployee>();
  698. foreach (var assignment in _assignments.Where(x => dates.Contains(x.Date) && jobIDs.Contains(x.JobID)))
  699. GetEmployee(assignment.EmployeeID, assignedemployees).Time += assignment.BookedDuration;
  700. AssignedEmployees.Items = assignedemployees.OrderBy(x=>x.Name).ToList();
  701. AssignedEmployees.Refresh(false, true);
  702. }
  703. private void JobSelector_OnSettingsChanged(object sender, JobSelectorSettingsChangedArgs args)
  704. {
  705. if (EventSuppressor.IsSet(Suppress.This))
  706. return;
  707. Properties.JobSettings = args.Settings;
  708. DoSaveSettings();
  709. }
  710. private void JobSelector_OnSelectionChanged(object sender, JobSelectorSelectionChangedArgs args)
  711. {
  712. if (EventSuppressor.IsSet(Suppress.This))
  713. return;
  714. Properties.JobSelection = args.Selection;
  715. _jobs = JobSelector.GetJobData<JobModel>(x => new JobModel(x));
  716. DoSaveSettings();
  717. Refresh();
  718. }
  719. private void TeamSelector_OnSettingsChanged(object sender, TeamSelectorSettingsChangedArgs args)
  720. {
  721. if (EventSuppressor.IsSet(Suppress.This))
  722. return;
  723. Properties.TeamSettings = args.Settings;
  724. DoSaveSettings();
  725. }
  726. private void TeamSelector_OnSelectionChanged(object sender, TeamSelectorSelectionChangedArgs args)
  727. {
  728. if (EventSuppressor.IsSet(Suppress.This))
  729. return;
  730. Properties.TeamSelection = args.Selection;
  731. _emps = TeamSelector.GetEmployeeData<EmployeeResourceModel>((row, rosters) => new EmployeeResourceModel(row, rosters));
  732. DoSaveSettings();
  733. Refresh();
  734. }
  735. private void JobSelector_OnSizeChanged(object sender, SizeChangedEventArgs e)
  736. {
  737. if (EventSuppressor.IsSet(Suppress.This))
  738. return;
  739. Properties.SplitterPosition = TeamSelectorRow.Height.Value;
  740. DoSaveSettings();
  741. }
  742. private void ViewWindow_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  743. {
  744. if (EventSuppressor.IsSet(Suppress.This))
  745. return;
  746. Properties.MonthsToView = (int)ViewWindow.SelectedValue;
  747. DoSaveSettings();
  748. Refresh();
  749. }
  750. private void HoursSelector_Down_Click(object sender, RoutedEventArgs e)
  751. {
  752. Properties.HoursPerDay = Math.Max(0.25F, Properties.HoursPerDay - 0.25);
  753. HoursSelector.Text = $"{Properties.HoursPerDay:F2}";
  754. DoSaveSettings();
  755. Refresh();
  756. }
  757. private void HoursSelector_Up_Click(object sender, RoutedEventArgs e)
  758. {
  759. Properties.HoursPerDay = Math.Min(24F, Properties.HoursPerDay + 0.25);
  760. HoursSelector.Text = $"{Properties.HoursPerDay:F2}";
  761. DoSaveSettings();
  762. Refresh();
  763. }
  764. private void ActivityType_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  765. {
  766. if (EventSuppressor.IsSet(Suppress.This))
  767. return;
  768. Properties.ActivityType = (Guid)(ActivityType.SelectedValue ?? Guid.Empty);
  769. DoSaveSettings();
  770. }
  771. private void AvailableEmployees_OnSizeChanged(object sender, SizeChangedEventArgs e)
  772. {
  773. if (EventSuppressor.IsSet(Suppress.This))
  774. return;
  775. Properties.EmployeeSplitterPosition = AvailableEmployeesRow.Height.Value;
  776. DoSaveSettings();
  777. }
  778. private void AvailableEmployees_OnOnAction(object sender, JobPlannerEmployee[] availables)
  779. {
  780. List<TimeSpan> edges = new List<TimeSpan>();
  781. void CheckEdges(params TimeSpan[] times)
  782. {
  783. foreach (var time in times)
  784. {
  785. if (!edges.Contains(time))
  786. edges.Add(time);
  787. }
  788. }
  789. bool IsRostered(RosterBlock[] blocks, TimeSpan start, TimeSpan finish)
  790. {
  791. foreach (var block in blocks)
  792. {
  793. if ((block.Start <= start) && (block.Finish >= finish))
  794. return true;
  795. }
  796. return false;
  797. }
  798. bool IsStandardLeave(DateTime date, TimeSpan start, TimeSpan finish)
  799. {
  800. return _standardleaves.Any(x =>
  801. (x.From.Add(x.FromTime) <= date.Add(start))
  802. && (x.To.Add(x.ToTime) >= date.Add(finish))
  803. );
  804. }
  805. bool IsLeaveRequest(DateTime date, EmployeeResourceModel emp, TimeSpan start, TimeSpan finish)
  806. {
  807. return _leaverequests.Any(x =>
  808. (x.EmployeeID == emp.ID)
  809. && (x.From.Add(x.FromTime) <= date.Add(start))
  810. && (x.To.Add(x.ToTime) >= date.Add(finish))
  811. );
  812. }
  813. bool IsAssigned(AssignmentModel[] assignments, TimeSpan start, TimeSpan finish)
  814. {
  815. foreach (var assignment in assignments)
  816. {
  817. if ((assignment.BookedStart <= start) && (assignment.BookedFinish >= finish))
  818. return true;
  819. }
  820. return false;
  821. }
  822. if (ExtractSelection(out var jobIDs, out var dates))
  823. {
  824. if (dataGrid.ItemsSource is DataTable table)
  825. {
  826. List<Assignment> updates = new List<Assignment>();
  827. foreach (var available in availables)
  828. {
  829. foreach (var date in dates)
  830. {
  831. var dateIdx = _dates.IndexOf(date);
  832. var emp = _emps.FirstOrDefault(x => x.ID == available.ID);
  833. if (emp != null)
  834. {
  835. var roster = RosterUtils.GetRoster(emp.Roster, emp.RosterStart, date);
  836. var blocks = roster?.GetBlocks(date, TimeSpan.MinValue, TimeSpan.MaxValue) ?? new RosterBlock[] { };
  837. foreach (var block in blocks)
  838. CheckEdges(block.Start, block.Finish);
  839. if (GetStandardLeaveTimes(date, out TimeSpan stdleavestart, out TimeSpan stdleavefinish))
  840. CheckEdges(stdleavestart, stdleavefinish);
  841. if (GetLeaveRequestTimes(date, emp, out TimeSpan leaverequeststart, out TimeSpan leaverequestfinish))
  842. CheckEdges(leaverequeststart, leaverequestfinish);
  843. var assignments = _assignments.Where(x => (x.Date == date) && (x.EmployeeID == emp.ID)).ToArray();
  844. foreach (var assignment in assignments)
  845. CheckEdges(assignment.BookedStart, assignment.BookedFinish);
  846. edges.Sort();
  847. var adjustment = new double[jobIDs.Length];
  848. for (int i = 0; i < edges.Count - 1; i++)
  849. {
  850. var start = edges[i];
  851. var finish = edges[i + 1];
  852. if (IsRostered(blocks, start, finish)
  853. && (!IsStandardLeave(date,start,finish))
  854. && (!IsLeaveRequest(date,emp,start,finish))
  855. && !IsAssigned(assignments, start, finish))
  856. {
  857. foreach(var (idx, jobid) in jobIDs.WithIndex())
  858. {
  859. Assignment assignment = new Assignment();
  860. assignment.ActivityLink.ID = Properties.ActivityType;
  861. assignment.EmployeeLink.ID = emp.ID;
  862. assignment.Date = date;
  863. assignment.JobLink.ID = jobid;
  864. assignment.Booked.Start = start;
  865. assignment.Booked.Finish = finish;
  866. assignment.Booked.Duration = finish - start;
  867. updates.Add(assignment);
  868. adjustment[idx] += assignment.Booked.Duration.TotalHours;
  869. }
  870. }
  871. }
  872. if(Properties.DisplayMode == JobPlannerDisplayMode.JobColumns)
  873. {
  874. System.Data.DataRow row = table.Rows[dateIdx];
  875. row.BeginEdit();
  876. foreach(var (idx, jobid) in jobIDs.WithIndex())
  877. {
  878. var adj = adjustment[idx];
  879. _available[dateIdx] = Math.Max(0F, _available[dateIdx] - (adj / Properties.HoursPerDay));
  880. row["Available"] = _available[dateIdx];
  881. double jobvalue = (row[jobid.ToString()] == DBNull.Value)
  882. ? adj / Properties.HoursPerDay
  883. : (double)row[jobid.ToString()] + (adj / Properties.HoursPerDay);
  884. row[jobid.ToString()] = jobvalue <= 0F ? null : jobvalue;
  885. }
  886. row.EndEdit();
  887. }
  888. else
  889. {
  890. var availableRow = table.Rows[0];
  891. availableRow.BeginEdit();
  892. foreach(var (idx, jobid) in jobIDs.WithIndex())
  893. {
  894. var job = _jobs.WithIndex().First(x => x.Value.ID == jobid);
  895. var row = table.Rows[job.Key + 1];
  896. var adj = adjustment[idx];
  897. _available[dateIdx] = Math.Max(0F, _available[dateIdx] - (adj / Properties.HoursPerDay));
  898. availableRow[dateIdx.ToString()] = _available[dateIdx];
  899. row.BeginEdit();
  900. double jobvalue = (row[dateIdx.ToString()] == DBNull.Value)
  901. ? adj / Properties.HoursPerDay
  902. : (double)row[dateIdx.ToString()] + (adj / Properties.HoursPerDay);
  903. row[dateIdx.ToString()] = jobvalue <= 0F ? null : jobvalue;
  904. row.EndEdit();
  905. }
  906. availableRow.EndEdit();
  907. foreach(var row in table.Rows.Cast<System.Data.DataRow>())
  908. {
  909. row.BeginEdit();
  910. row[dateIdx.ToString()] = row[dateIdx.ToString()];
  911. row.EndEdit();
  912. }
  913. }
  914. }
  915. }
  916. var entry = AvailableEmployees.Items.FirstOrDefault(x => x.ID == available.ID);
  917. if (entry != null)
  918. {
  919. AvailableEmployees.Items.Remove(entry);
  920. GetEmployee(entry.ID, AssignedEmployees.Items).Time += entry.Time;
  921. }
  922. }
  923. if (updates.Any())
  924. {
  925. using (new WaitCursor())
  926. {
  927. new Client<Assignment>().Save(updates, "Assigned by Job Planner");
  928. CoreTable temp = new CoreTable();
  929. temp.LoadColumns(typeof(Assignment));
  930. temp.LoadRows(updates);
  931. _assignments.AddRange(temp.Rows.Select(r => new AssignmentModel(r)));
  932. AssignedEmployees.Refresh(false, true);
  933. AvailableEmployees.Refresh(false, true);
  934. }
  935. }
  936. }
  937. }
  938. }
  939. private void AssignedEmployees_OnOnAction(object sender, JobPlannerEmployee[] employees)
  940. {
  941. if (ExtractSelection(out var jobIDs, out var dates))
  942. {
  943. if (dataGrid.ItemsSource is DataTable table)
  944. {
  945. foreach (var date in dates)
  946. {
  947. var dateIdx = _dates.IndexOf(date);
  948. var emptimes = _assignments.Where(x =>
  949. jobIDs.Contains(x.JobID)
  950. && (x.Date == date)
  951. && employees.Any(e => e.ID == x.EmployeeID)
  952. ).ToArray();
  953. var emptime = emptimes.Aggregate(TimeSpan.Zero, (time, ass) => time += ass.BookedDuration);
  954. if(Properties.DisplayMode == JobPlannerDisplayMode.JobColumns)
  955. {
  956. System.Data.DataRow row = table.Rows[dateIdx];
  957. row.BeginEdit();
  958. foreach(var jobid in jobIDs)
  959. {
  960. _available[dateIdx] = _available[dateIdx] + (emptime.TotalHours / Properties.HoursPerDay);
  961. row["Available"] = _available[dateIdx];
  962. double value = (row[jobid.ToString()] == DBNull.Value)
  963. ? 0.0F
  964. : (double)row[jobid.ToString()] - (emptime.TotalHours / Properties.HoursPerDay);
  965. row[jobid.ToString()] = value <= 0F ? null : value;
  966. }
  967. row.EndEdit();
  968. }
  969. else
  970. {
  971. var availableRow = table.Rows[0];
  972. availableRow.BeginEdit();
  973. foreach(var jobid in jobIDs)
  974. {
  975. var jobIdx = _jobs.WithIndex().First(x => x.Value.ID == jobid).Key;
  976. _available[dateIdx] = _available[dateIdx] + (emptime.TotalHours/Properties.HoursPerDay);
  977. availableRow[dateIdx.ToString()] = _available[dateIdx];
  978. var row = table.Rows[jobIdx + 1];
  979. row.BeginEdit();
  980. double value = (row[dateIdx.ToString()] == DBNull.Value)
  981. ? 0.0F
  982. : (double)row[dateIdx.ToString()] - (emptime.TotalHours / Properties.HoursPerDay);
  983. row[dateIdx.ToString()] = value <= 0F ? null : value;
  984. row.EndEdit();
  985. }
  986. availableRow.EndEdit();
  987. foreach(var row in table.Rows.Cast<System.Data.DataRow>())
  988. {
  989. row.BeginEdit();
  990. row[dateIdx.ToString()] = row[dateIdx.ToString()];
  991. row.EndEdit();
  992. }
  993. }
  994. }
  995. }
  996. var assignments = _assignments.Where(x =>
  997. jobIDs.Contains(x.JobID)
  998. && dates.Contains(x.Date)
  999. && employees.Any(e => e.ID == x.EmployeeID)
  1000. ).ToArray();
  1001. if (assignments.Any())
  1002. {
  1003. using (new WaitCursor())
  1004. {
  1005. var deletes = assignments.Select(x => new Assignment() { ID = x.ID }).ToArray();
  1006. new Client<Assignment>().Delete(deletes, "Deleted from Job Planner");
  1007. var removes = AssignedEmployees.Items.Where(x => employees.Any(e => e.ID == x.ID)).ToArray();
  1008. foreach (var remove in removes)
  1009. {
  1010. GetEmployee(remove.ID, AvailableEmployees.Items).Time += remove.Time;
  1011. AssignedEmployees.Items.Remove(remove);
  1012. }
  1013. foreach (var assignment in assignments)
  1014. _assignments.Remove(assignment);
  1015. AssignedEmployees.Refresh(false, true);
  1016. AvailableEmployees.Refresh(false, true);
  1017. }
  1018. }
  1019. }
  1020. }
  1021. private void LeaveType_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  1022. {
  1023. if (EventSuppressor.IsSet(Suppress.This))
  1024. return;
  1025. Properties.IncludeUnApprovedLeave = LeaveType.SelectedIndex > 0;
  1026. DoSaveSettings();
  1027. Refresh();
  1028. }
  1029. private void Orientation_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  1030. {
  1031. if (EventSuppressor.IsSet(Suppress.This))
  1032. return;
  1033. Properties.DisplayMode = Orientation.SelectedIndex <= 0
  1034. ? JobPlannerDisplayMode.DateColumns
  1035. : JobPlannerDisplayMode.JobColumns;
  1036. DoSaveSettings();
  1037. Refresh();
  1038. }
  1039. private void AvailableEmployees_OnAfterRefresh(object sender, AfterRefreshEventArgs args)
  1040. {
  1041. AvailableEmployees.Items = AvailableEmployees.Items.OrderBy(x => x.Name).ToList();
  1042. }
  1043. private void AssignedEmployees_OnAfterRefresh(object sender, AfterRefreshEventArgs args)
  1044. {
  1045. AssignedEmployees.Items = AssignedEmployees.Items.OrderBy(x => x.Name).ToList();
  1046. }
  1047. private void Data_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  1048. {
  1049. Settings.Visibility = Data.SelectedTab == JobView
  1050. ? Visibility.Visible
  1051. : Visibility.Collapsed;
  1052. }
  1053. ScheduleAppointmentCollection appointments = new();
  1054. private void Schedule_OnQueryAppointments(object? sender, QueryAppointmentsEventArgs e)
  1055. {
  1056. appointments.Clear();
  1057. var cols = Columns.Required<JobStage>()
  1058. .Add(x => x.ID)
  1059. .Add(x => x.StartDate)
  1060. .Add(x => x.EndDate)
  1061. .Add(x=>x.Name)
  1062. .Add(x => x.Job.ID)
  1063. .Add(x => x.Job.JobNumber)
  1064. .Add(x => x.Job.Name)
  1065. .Add(x => x.Type.ID)
  1066. .Add(x => x.Type.Color)
  1067. .Add(x => x.Type.Description)
  1068. .Add(x => x.Type.Color)
  1069. .Add(x=>x.Calendar.ID)
  1070. .Add(x=>x.Calendar.Name)
  1071. .Add(x=>x.Calendar.Monday)
  1072. .Add(x=>x.Calendar.Tuesday)
  1073. .Add(x=>x.Calendar.Wednesday)
  1074. .Add(x=>x.Calendar.Thursday)
  1075. .Add(x=>x.Calendar.Friday)
  1076. .Add(x=>x.Calendar.Saturday)
  1077. .Add(x=>x.Calendar.Sunday)
  1078. .Add(x => x.Apprentices)
  1079. .Add(x=>x.ApprenticeHours)
  1080. .Add(x=>x.Supervisors)
  1081. .Add(x=>x.SupervisionHours)
  1082. .Add(x=>x.Tradespersons)
  1083. .Add(x=>x.TradesHours);
  1084. var stages = Client.Query<JobStage>(
  1085. new Filter<JobStage>(x => x.StartDate).IsLessThanOrEqualTo(e.VisibleDateRange.ActualEndDate)
  1086. .And(x => x.EndDate).IsGreaterThanOrEqualTo(e.VisibleDateRange.ActualStartDate),
  1087. cols
  1088. ).ToObjects<JobStage>()
  1089. .ToArray();
  1090. foreach (var stage in stages)
  1091. CreateAppointment(stage);
  1092. Schedule.ItemsSource = appointments;
  1093. }
  1094. private void CreateAppointment(JobStage stage)
  1095. {
  1096. var model = new StageModel(stage);
  1097. appointments.Add(model);
  1098. }
  1099. private class StageModel : ScheduleAppointment
  1100. {
  1101. public StageModel(JobStage stage)
  1102. {
  1103. Stage = stage;
  1104. }
  1105. private JobStage _stage;
  1106. public JobStage Stage
  1107. {
  1108. get => _stage;
  1109. set
  1110. {
  1111. _stage = value;
  1112. Reload();
  1113. }
  1114. }
  1115. public void Reload()
  1116. {
  1117. var color = !string.IsNullOrWhiteSpace(_stage.Type.Color)
  1118. ? (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(_stage.Type.Color)
  1119. : Colors.Transparent;
  1120. StartTime = _stage.StartDate;
  1121. EndTime = _stage.EndDate;
  1122. IsAllDay = true;
  1123. AppointmentBackground = new SolidColorBrush(color);
  1124. Foreground = new SolidColorBrush(color.GetForegroundColor());
  1125. Subject = $"{_stage.Job.JobNumber}: {_stage.Job.Name}";
  1126. }
  1127. }
  1128. private void Schedule_AppointmentEditorOpening(object? sender, AppointmentEditorOpeningEventArgs e)
  1129. {
  1130. }
  1131. void CreateMenu<T>(ItemsControl menu, CalendarMenuName name, string header, Action<T?>? action, T data)
  1132. {
  1133. var item = new MenuItem();
  1134. item.Name = name.ToString();
  1135. item.Header = header;
  1136. if (action != null)
  1137. item.Click += (o,args) => action(data);
  1138. menu.Items.Add(item);
  1139. }
  1140. private StageModel? _copiedModel;
  1141. private void CreateJobStage(JobStageType type)
  1142. {
  1143. if (Schedule.SelectedDate is null)
  1144. return;
  1145. var stage = new JobStage
  1146. {
  1147. StartDate = Schedule.SelectedDate.Value,
  1148. EndDate = Schedule.SelectedDate.Value,
  1149. Name = type.Description
  1150. };
  1151. stage.Type.CopyFrom(type);
  1152. if (new JobStagesGrid().EditItems([stage]))
  1153. {
  1154. CreateAppointment(stage);
  1155. _copiedModel = null;
  1156. }
  1157. if (!types?.Any(x => x.ID == stage.Type.ID) == false)
  1158. types = null;
  1159. }
  1160. private void EditJobStage(StageModel? stage)
  1161. {
  1162. if (stage is null)
  1163. return;
  1164. if (new JobStagesGrid().EditItems([stage.Stage]))
  1165. {
  1166. stage.Reload();
  1167. _copiedModel = null;
  1168. }
  1169. }
  1170. private void DeleteJobStage(StageModel? stage)
  1171. {
  1172. if (stage is null)
  1173. return;
  1174. //Schedule.ItemsSource = null;
  1175. appointments.Remove(stage);
  1176. new Client<JobStage>().Delete(stage.Stage,"Deleted from Project Planner");
  1177. _copiedModel = null;
  1178. }
  1179. private void CopyJobStage(StageModel? stage)
  1180. {
  1181. _copiedModel = stage;
  1182. }
  1183. private void PasteJobStage(StageModel? stage)
  1184. {
  1185. if (stage is null || Schedule.SelectedDate is null)
  1186. return;
  1187. var newstage = stage.Stage.Clone();
  1188. newstage.ID = Guid.Empty;
  1189. newstage.OriginalValues?.TryRemove("ID", out _);
  1190. var span = newstage.EndDate - newstage.StartDate;
  1191. newstage.StartDate = Schedule.SelectedDate.Value;
  1192. newstage.EndDate = newstage.StartDate + span;
  1193. new Client<JobStage>().Save(newstage,"Created from Project Planner");
  1194. CreateAppointment(newstage);
  1195. _copiedModel = null;
  1196. }
  1197. private List<JobStageType>? types;
  1198. private void Schedule_OnSchedulerContextMenuOpening(object? sender, SchedulerContextMenuOpeningEventArgs e)
  1199. {
  1200. e.ContextMenu.Items.Clear();
  1201. if ((e.MenuType == SchedulerContextMenuType.Appointment) && (e.MenuInfo.Appointment is StageModel model))
  1202. {
  1203. CreateMenu(e.ContextMenu, CalendarMenuName.Edit, "Edit Booking", EditJobStage, model);
  1204. CreateMenu(e.ContextMenu, CalendarMenuName.Copy, "Copy Booking", CopyJobStage, model);
  1205. e.ContextMenu.Items.Add(new Separator());
  1206. CreateMenu(e.ContextMenu, CalendarMenuName.Edit, "Delete Booking", DeleteJobStage, model);
  1207. }
  1208. else if (e.MenuType == SchedulerContextMenuType.MonthCell)
  1209. {
  1210. types ??= new Client<JobStageType>().Query(null, Columns.All<JobStageType>()).ToList<JobStageType>();
  1211. if (types.Any())
  1212. {
  1213. var header = new MenuItem() { Header = "Create Booking..." };
  1214. foreach (var type in types)
  1215. CreateMenu(header, CalendarMenuName.CreateNew, type.Description, CreateJobStage, type);
  1216. e.ContextMenu.Items.Add(header);
  1217. }
  1218. else
  1219. CreateMenu(e.ContextMenu, CalendarMenuName.CreateNew, "Create Booking", CreateJobStage, new JobStageType());
  1220. if (_copiedModel != null)
  1221. {
  1222. e.ContextMenu.Items.Add(new Separator());
  1223. CreateMenu(e.ContextMenu, CalendarMenuName.Paste, "Paste Booking", PasteJobStage, _copiedModel);
  1224. }
  1225. }
  1226. }
  1227. private void Schedule_OnAppointmentDropping(object? sender, AppointmentDroppingEventArgs e)
  1228. {
  1229. if (e.Appointment is StageModel model)
  1230. {
  1231. var duration = model.Stage.EndDate - model.Stage.StartDate;
  1232. model.Stage.StartDate = e.DropTime;
  1233. model.Stage.EndDate = e.DropTime + duration;
  1234. model.Reload();
  1235. new Client<JobStage>().Save(model.Stage,"Reallocated By Project Planner", (o,e) => { });
  1236. }
  1237. }
  1238. }