using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media.Imaging; using Comal.Classes; using InABox.Clients; using InABox.Core; using InABox.DynamicGrid; using InABox.WPF; namespace PRSDesktop { public class TimesheetGrid : DynamicDataGrid { private readonly BitmapImage leave = PRSDesktop.Resources.leave.AsBitmapImage(); private LeaveRequestGrid leavegrid; private readonly BitmapImage post = PRSDesktop.Resources.post.AsBitmapImage(); private readonly BitmapImage tick = PRSDesktop.Resources.tick.AsBitmapImage(); private readonly BitmapImage warning = PRSDesktop.Resources.warning.AsBitmapImage(); public TimesheetGrid() { Options.AddRange( DynamicGridOption.SelectColumns, DynamicGridOption.FilterRows, DynamicGridOption.MultiSelect, DynamicGridOption.RecordCount ); ActionColumns.Add(new DynamicMenuColumn(BuildMenu) { Position = DynamicActionColumnPosition.Start }); ActionColumns.Add(new DynamicMapColumn(this, x => x.StartLocation)); //HiddenColumns.Add(x => x.StartLocation.Timestamp); //HiddenColumns.Add(x => x.StartLocation.Latitude); //HiddenColumns.Add(x => x.StartLocation.Longitude); ActionColumns.Add(new DynamicMapColumn(this, x => x.FinishLocation)); //HiddenColumns.Add(x => x.FinishLocation.Timestamp); //HiddenColumns.Add(x => x.FinishLocation.Latitude); //HiddenColumns.Add(x => x.FinishLocation.Longitude); //ActionColumns.Add(new DynamicImageColumn() { Image = WarningImage }); ActionColumns.Add(new DynamicTickColumn(x => x.Approved, tick, tick, null)); ActionColumns.Add(new DynamicTickColumn(x => x.Processed, post, tick, null)); HiddenColumns.Add(x => x.Date); HiddenColumns.Add(x => x.Start); HiddenColumns.Add(x => x.Finish); HiddenColumns.Add(x => x.Duration); HiddenColumns.Add(x => x.ApprovedStart); HiddenColumns.Add(x => x.ApprovedFinish); //HiddenColumns.Add(x => x.ApprovedHours); HiddenColumns.Add(x => x.ApprovedDuration); HiddenColumns.Add(x => x.EmployeeLink.ID); HiddenColumns.Add(x => x.LeaveRequestLink.ID); HiddenColumns.Add(x => x.JobLink.ID); HiddenColumns.Add(x => x.JobLink.Deleted); HiddenColumns.Add(x => x.Approved); if (Security.CanView()) AddButton("Employee", PRSDesktop.Resources.anonymous.AsBitmapImage(Color.White), EditEmployee); if (Security.IsAllowed()) { AddButton("Approve", tick, ApproveTimeSheet); AddButton("UnApprove", warning, UnApproveTimeSheet); } if (Security.CanView()) AddButton("Create Leave", leave, CreateLeaveRequest); } public DateTime StartDate { get; set; } = DateTime.MinValue; public DateTime EndDate { get; set; } = DateTime.MaxValue; public string Search { get; set; } = ""; public bool ApprovedOnly { get; set; } = false; private void BuildMenu(DynamicMenuColumn column, CoreRow row) { if (row == null) return; var formsItem = column.AddItem("Digital Forms", PRSDesktop.Resources.kanban, null); DynamicGridUtils.PopulateFormMenu( formsItem, row.Get(x => x.ID), () => row.ToObject()); } protected override TimeSheet CreateItem() { var result = base.CreateItem(); result.Date = DateTime.Today; return result; } private Tuple[] GroupTimeSheets(CoreRow[] rows) { var results = new List>(); foreach (var row in rows) { var date = row.Get(x => x.Date); var empid = row.Get(x => x.EmployeeLink.ID); var name = row.Get(x => x.EmployeeLink.Name); if (!results.Any(x => x.Item1.Equals(empid) && x.Item2.Equals(date))) results.Add(new Tuple(empid, date, name)); } return results.OrderBy(x => x.Item3).ThenBy(x => x.Item2).ToArray(); } private bool CheckApproval(TimeSheet time) { time.ApprovedStart = time.Start.Round(new TimeSpan(0, 15, 0)); time.ApprovedFinish = time.Finish.Round(new TimeSpan(0, 15, 0)); time.Approved = DateTime.Now; return true; } private bool ApproveTimeSheet(Button sender, CoreRow[] rows) { if (!rows.Any()) { MessageBox.Show("Please select at least one row to process!"); return false; } Progress.Show(""); Progress.SetMessage("Loading Time Sheets"); var employeedays = GroupTimeSheets(rows); var updates = new List(); var lastemp = Guid.Empty; Employee emp = null; foreach (var day in employeedays) { var date = day.Item2; var empid = day.Item1; var name = day.Item3; if (empid != lastemp) { Progress.SetMessage("Processing: " + name); emp = new Client().Load(new Filter(x => x.ID).IsEqualTo(empid)).FirstOrDefault(); lastemp = empid; } if (emp != null) { var times = new Client().Load(new Filter(x => x.Date).IsEqualTo(date) .And(x => x.EmployeeLink.ID).IsEqualTo(empid) .And(x => x.Approved).IsEqualTo(DateTime.MinValue) .And(x => x.Finish).IsNotEqualTo(new TimeSpan(0)) ); if (times.Length > 0) { // Rules: // if the employee has a usual start / usual end set, it will be treated as salary, // and the approved times will always reflect the usual, eather than the actual times // Then, it will go through each timesheet, and wind back the start / finish time IF // a: timesheet is joblinked // b: job has a usual start / end // b: timesheet activity == job.usual activity var first = times.OrderBy(x => x.Start).First(); var start = first.Start.Round(new TimeSpan(0, 15, 0)); var last = times.OrderBy(x => x.Finish).Last(); var finish = last.Finish.Round(new TimeSpan(0, 15, 0)); var jobfilter = new Filter(x => x.ID).IsEqualTo(Guid.Empty); Array.ForEach(times, t => jobfilter.Ors.Add(new Filter(j => j.ID).IsEqualTo(t.JobLink.ID))); var jobs = new Client().Query(jobfilter, new Columns(x => x.ID, x => x.UsualStart, x => x.UsualFinish, x => x.UsualActivity.ID)); for (var i = 0; i < times.Length; i++) { //Progress.SetMessage(String.Format("Checking Time Sheet({0:F2}% complete)", (i * 100.0F) / (double)times.Length)); var time = times[i]; time.ApprovedStart = time == first || time.Start < start ? start : time.Start; time.ApprovedFinish = time == last || time.Finish > finish ? finish : time.Finish; var job = jobs.Rows.Where(r => r.Get(c => c.ID).Equals(time.JobLink.ID) && Entity.IsEntityLinkValid(x => x.UsualActivity, r) && r.Get(c => c.UsualActivity.ID).Equals(time.ActivityLink.ID) ).FirstOrDefault(); if (job != null) { var jobstart = job.Get(x => x.UsualStart); if (jobstart > new TimeSpan(0) && time.Start < jobstart) time.ApprovedStart = jobstart; var jobend = job.Get(x => x.UsualFinish); if (jobend > new TimeSpan(0) && time.Finish > jobend) time.ApprovedFinish = jobend; } if (time.ApprovedFinish < time.ApprovedStart) time.ApprovedFinish = time.ApprovedStart; time.Approved = DateTime.Now; updates.Add(time); } } } } if (updates.Any()) { Progress.SetMessage("Updating Database"); new Client().Save(updates, "Approving Time Sheets"); } Progress.Close(); MessageBox.Show("All Done"); return true; } private bool UnApproveTimeSheet(Button sender, CoreRow[] rows) { if (!rows.Any()) { MessageBox.Show("Please select at least one row to process!"); return false; } Progress.Show(""); Progress.SetMessage("Processing Time Sheets"); var employeedays = GroupTimeSheets(rows); var updates = new List(); foreach (var day in employeedays) { var date = day.Item2; var empid = day.Item1; var name = day.Item3; Progress.SetMessage("Clearing Approvals for " + name); var times = new Client().Load(new Filter(x => x.Date).IsEqualTo(date) .And(x => x.EmployeeLink.ID).IsEqualTo(empid) ); for (var i = 0; i < times.Length; i++) { //Progress.SetMessage(String.Format("Clearing Approval ({0:F2}% complete)", (i * 100.0F) / (double)times.Length)); var time = times[i]; time.ApprovedStart = new TimeSpan(0); time.ApprovedFinish = new TimeSpan(0); time.Approved = DateTime.MinValue; updates.Add(time); } } if (updates.Any()) { Progress.SetMessage("Updating Database"); new Client().Save(updates, "Clearing Approvals"); } Progress.Close(); MessageBox.Show("All Done"); return true; } private bool EditEmployee(Button btn, CoreRow[] rows) { if (rows.Length != 1) { MessageBox.Show("Please select a single employee!"); return false; } var row = rows.First(); var employee = new Client().Load(new Filter(x => x.ID).IsEqualTo(row.Get(c => c.EmployeeLink.ID))) .FirstOrDefault(); if (employee == null) { MessageBox.Show("Cannot Locate Employee!"); return false; } var employeegrid = new EmployeeGrid(); return employeegrid.EditItems(new[] { employee }); } private bool CreateLeaveRequest(Button arg1, CoreRow[] arg2) { var leave = new LeaveRequest(); leave.From = DateTime.Today; leave.To = DateTime.Today; if (leavegrid == null) leavegrid = new LeaveRequestGrid(); return leavegrid.EditItems(new[] { leave }); } public override bool EditItems(TimeSheet[] items, Func PageDataHandler = null, bool PreloadPages = false) { var leaveids = items.Select(x => x.LeaveRequestLink.ID).Distinct().ToArray(); if (leaveids.Length > 1 && leaveids.Contains(Guid.Empty)) { MessageBox.Show( "At least one of these timesheets is linked to a leave request!\nPlease select either regular or leave timesheets and try again."); return false; } if (!leaveids.Contains(Guid.Empty)) { if (!Security.CanView()) { MessageBox.Show("Editing leave requests is not permitted here"); return false; } LeaveRequest[] leaves = null; using (new WaitCursor()) { leaves = new Client().Load(new Filter(x => x.ID).InList(leaveids)); if (leavegrid == null) leavegrid = new LeaveRequestGrid(); } return leavegrid.EditItems(leaves); } return base.EditItems(items, PageDataHandler, PreloadPages); } protected override Dictionary EditorValueChanged(IDynamicEditorForm editor, TimeSheet[] items, string name, object value) { var result = base.EditorValueChanged(editor, items, name, value); if (name == "ActivityLink.ID") { ReloadForms(editor, items.FirstOrDefault(), x => x.Activity.ID, value != null ? (Guid)value : Guid.Empty); } else if (name.Equals("EmployeeLink.ID")) { var activity = editor.FindEditor("ActivityLink.ID") as ILookupEditorControl; if (activity != null) DefineLookups(activity, items); } return result; } protected override void AfterLoad(IDynamicEditorForm editor, TimeSheet[] items) { base.AfterLoad(editor, items); if (items.FirstOrDefault().ID == Guid.Empty) ReloadForms(editor, items.FirstOrDefault(), x => x.Activity.ID, items.FirstOrDefault().ActivityLink.ID); } //private BitmapImage WarningImage(DataRow row) //{ // if (row == null) // return warning; // if (row.Get(x => x.Approved) != DateTime.MinValue) // return null; // TimeSpan start = row.Get(x => x.Start); // if (start.Equals(new TimeSpan(0))) // return warning; // TimeSpan finish = row.Get(x => x.Finish); // if (finish.Equals(new TimeSpan(0))) // return warning; // TimeSpan actual = row.Get(x => x.Duration); // TimeSpan approved = row.Get(x => x.ApprovedDuration); // if (!actual.Equals(approved)) // return warning; // return null; //} //private bool ApproveClick(DataRow row) //{ // if (row == null) // return false; // Guid id = row.Get(x => x.ID); // TimeSheet timesheet = new Client().Load(new Filter(x => x.ID).IsEqualTo(id)).FirstOrDefault(); // if (timesheet != null) // { // timesheet.Approved = timesheet.Approved.IsEmpty() ? DateTime.Now : DateTime.MinValue; // new Client().Save(timesheet, "Toggled Approval flag"); // return true; // } // return false; //} protected override void Reload(Filters criteria, Columns columns, ref SortOrder sort, Action action) { var filter = new Filter(x => x.Processed).IsEqualTo(DateTime.MinValue); if (!string.IsNullOrWhiteSpace(Search)) filter = filter.TextSearch(Search, x => x.EmployeeLink.Name, x => x.Address, x => x.Notes); criteria.Add(filter); criteria.Add(new Filter(x => x.EmployeeLink.FinishDate).IsEqualTo(DateTime.MinValue).Or(x => x.EmployeeLink.FinishDate) .IsGreaterThanOrEqualTo(DateTime.Today)); criteria.Add(new Filter(x => x.Date).IsGreaterThanOrEqualTo(StartDate.Date)); criteria.Add(new Filter(x => x.Date).IsLessThan(EndDate == DateTime.MaxValue ? DateTime.MaxValue : EndDate.AddDays(1))); if (ApprovedOnly) criteria.Add(new Filter(x => x.Approved).IsNotEqualTo(DateTime.MinValue)); base.Reload(criteria, columns, ref sort, action); } protected override bool CanDeleteItems(CoreRow[] rows) { var result = true; foreach (var row in rows) { if (row.IsEntityLinkValid(x => x.LeaveRequestLink)) result = false; } if (!result) MessageBox.Show( "You cannot delete timesheets linked to Leave Requests!\n\nPlease un-approve or delete the related Leave Request instead."); return result; } protected override BaseEditor? GetEditor(object item, DynamicGridColumn column) { var result = base.GetEditor(item, column); if (result == null) return null; if (!Security.IsAllowed() && (column.ColumnName.Equals("ApprovedStart") || column.ColumnName.Equals("ApprovedFinish") || column.ColumnName.Equals("Approved"))) result.Editable = Editable.Disabled; if (!Security.IsAllowed() && (column.ColumnName.Equals("Date") || column.ColumnName.Equals("Start") || column.ColumnName.Equals("Finish"))) result.Editable = Editable.Disabled; if (!Security.IsAllowed() && column.ColumnName.Equals("Processed")) result.Editable = Editable.Disabled; return result; } } }