|
@@ -6,7 +6,8 @@ using InABox.Core.Postable;
|
|
|
using InABox.Poster.Timberline;
|
|
|
using InABox.Scripting;
|
|
|
using Microsoft.Win32;
|
|
|
-using NPOI.SS.UserModel;
|
|
|
+using PRS.Shared.TimeSheetTimberline;
|
|
|
+using Syncfusion.Windows.Shared;
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Globalization;
|
|
@@ -14,9 +15,132 @@ using System.IO;
|
|
|
using System.Linq;
|
|
|
using System.Text;
|
|
|
using System.Threading.Tasks;
|
|
|
+using System.Windows.Input;
|
|
|
|
|
|
namespace PRS.Shared
|
|
|
{
|
|
|
+ namespace TimeSheetTimberline
|
|
|
+ {
|
|
|
+ public class ActivityBlock
|
|
|
+ {
|
|
|
+ public Guid Activity { get; set; }
|
|
|
+
|
|
|
+ public TimeSpan Start { get; set; }
|
|
|
+
|
|
|
+ public TimeSpan Finish { get; set; }
|
|
|
+
|
|
|
+ public TimeSheet TimeSheet { get; set; }
|
|
|
+
|
|
|
+ public TimeSpan Duration => Finish - Start;
|
|
|
+
|
|
|
+ public ActivityBlock(Assignment assignment, TimeSheet sheet)
|
|
|
+ {
|
|
|
+ Activity = assignment.ActivityLink.ID != Guid.Empty
|
|
|
+ ? assignment.ActivityLink.ID
|
|
|
+ : sheet.ActivityLink.ID;
|
|
|
+
|
|
|
+ Start = assignment.EffectiveStartTime();
|
|
|
+ Finish = assignment.EffectiveFinishTime();
|
|
|
+ TimeSheet = sheet;
|
|
|
+ }
|
|
|
+
|
|
|
+ public ActivityBlock(TimeSheet sheet)
|
|
|
+ {
|
|
|
+ Activity = sheet.ActivityLink.ID;
|
|
|
+ Start = sheet.ApprovedStart;
|
|
|
+ Finish = sheet.ApprovedFinish;
|
|
|
+ TimeSheet = sheet;
|
|
|
+ }
|
|
|
+
|
|
|
+ public ActivityBlock(TimeSheet sheet, TimeSpan start, TimeSpan finish)
|
|
|
+ {
|
|
|
+ Activity = sheet.ActivityLink.ID;
|
|
|
+ Start = start;
|
|
|
+ Finish = finish;
|
|
|
+ TimeSheet = sheet;
|
|
|
+ }
|
|
|
+
|
|
|
+ public ActivityBlock Chop(TimeSheet sheet)
|
|
|
+ {
|
|
|
+ if (Start < sheet.ApprovedStart)
|
|
|
+ {
|
|
|
+ Start = sheet.ApprovedStart;
|
|
|
+ }
|
|
|
+ if (Finish > sheet.ApprovedFinish)
|
|
|
+ {
|
|
|
+ Finish = sheet.ApprovedFinish;
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool ContainedInTimeSheet(TimeSheet sheet) =>
|
|
|
+ Start < sheet.ApprovedFinish && Finish > sheet.ApprovedStart;
|
|
|
+
|
|
|
+ public bool IntersectsWith(ActivityBlock other)
|
|
|
+ {
|
|
|
+ return Start < other.Finish && Finish > other.Start;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public interface IBlock
|
|
|
+ {
|
|
|
+ string Job { get; set; }
|
|
|
+
|
|
|
+ string Extra { get; set; }
|
|
|
+
|
|
|
+ string TaskID { get; set; }
|
|
|
+
|
|
|
+ TimeSpan Duration { get; set; }
|
|
|
+
|
|
|
+ string PayrollID { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class PaidWorkBlock : IBlock
|
|
|
+ {
|
|
|
+ public string Job { get; set; }
|
|
|
+
|
|
|
+ public string Extra { get; set; }
|
|
|
+
|
|
|
+ public string TaskID { get; set; }
|
|
|
+
|
|
|
+ public TimeSpan Duration { get; set; }
|
|
|
+
|
|
|
+ public string PayrollID { get; set; }
|
|
|
+
|
|
|
+ public PaidWorkBlock(string taskID, TimeSpan duration, string payID, string job)
|
|
|
+ {
|
|
|
+ TaskID = taskID;
|
|
|
+ Duration = duration;
|
|
|
+ PayrollID = payID;
|
|
|
+ Job = job;
|
|
|
+ Extra = "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class LeaveBlock : IBlock
|
|
|
+ {
|
|
|
+ public string Job { get; set; }
|
|
|
+
|
|
|
+ public string Extra { get; set; }
|
|
|
+
|
|
|
+ public string TaskID { get; set; }
|
|
|
+
|
|
|
+ public TimeSpan Duration { get; set; }
|
|
|
+
|
|
|
+ public string PayrollID { get; set; }
|
|
|
+
|
|
|
+ public LeaveBlock(string payrollID, TimeSpan duration)
|
|
|
+ {
|
|
|
+ PayrollID = payrollID;
|
|
|
+ Duration = duration;
|
|
|
+ Job = "";
|
|
|
+ Extra = "";
|
|
|
+ TaskID = "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
public class TimesheetTimberlineItem
|
|
|
{
|
|
|
[Index(0)]
|
|
@@ -56,8 +180,9 @@ namespace PRS.Shared
|
|
|
|
|
|
protected override string DefaultScript()
|
|
|
{
|
|
|
- return @"
|
|
|
-using PRS.Shared;
|
|
|
+ return
|
|
|
+@"using PRS.Shared;
|
|
|
+using PRS.Shared.TimeSheetTimberline;
|
|
|
using InABox.Core;
|
|
|
using System.Collections.Generic;
|
|
|
|
|
@@ -67,6 +192,39 @@ public class Module
|
|
|
{
|
|
|
// Perform pre-processing
|
|
|
}
|
|
|
+
|
|
|
+ public bool ProcessDailyTimeSheets(IDataModel<TimeSheet> model, Guid employee, DateTime date, List<TimeSheet> timesheets, List<Assignment> assignments)
|
|
|
+ {
|
|
|
+ // Before PRS calculates anything, you can edit the list of timesheets and assignments it is working with here.
|
|
|
+
|
|
|
+ // Return false unless you wish PRS to skip these entries.
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool ProcessActivityBlocks(IDataModel<TimeSheet> model, Guid employee, DateTime date, List<ActivityBlock> blocks)
|
|
|
+ {
|
|
|
+ // Once PRS has aggregated the list of timesheets and assignments into a list of time blocks with given activities, you can edit these time blocks here.
|
|
|
+
|
|
|
+ // Always return false, unless you wish PRS to skip all these entries.
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool ProcessBlocks(IDataModel<TimeSheet> model, Guid employee, DateTime date, List<PaidWorkBlock> work, List<LeaveBlock> leave)
|
|
|
+ {
|
|
|
+ // This function is called after PRS has determined the length, duration and overtime rules for all the blocks of time. Here, you can edit
|
|
|
+ // this data before it is collated into the export.
|
|
|
+
|
|
|
+ // Always return false, unless you wish PRS to skip these entries.
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool ProcessItem(IDataModel<TimeSheet> model, Guid employee, DateTime date, TimesheetTimberlineItem line)
|
|
|
+ {
|
|
|
+ // This is the final function before PRS exports each item. You can edit the data as you wish.
|
|
|
+
|
|
|
+ // Returning true here means that PRS adds this item to the export. If you wish this item to be skipped, return false.
|
|
|
+ return true;
|
|
|
+ }
|
|
|
|
|
|
public void AfterPost(IDataModel<TimeSheet> model)
|
|
|
{
|
|
@@ -144,65 +302,52 @@ public class Module
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- private class ActivityBlock
|
|
|
+ /// <summary>
|
|
|
+ /// Preprocess the timesheets and assignments for a given day and employee, and optionally skip them.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="employee"></param>
|
|
|
+ /// <param name="date"></param>
|
|
|
+ /// <param name="timesheets"></param>
|
|
|
+ /// <param name="assignments"></param>
|
|
|
+ /// <returns><see langword="true"/> if this set of timesheets has now been processed, and so they should be skipped.</returns>
|
|
|
+ private bool ProcessDailyTimeSheets(IDataModel<TimeSheet> model, Guid employee, DateTime date, List<TimeSheet> timesheets, List<Assignment> assignments)
|
|
|
{
|
|
|
- public Guid Activity { get; set; }
|
|
|
-
|
|
|
- public TimeSpan Start { get; set; }
|
|
|
-
|
|
|
- public TimeSpan Finish { get; set; }
|
|
|
-
|
|
|
- public TimeSheet TimeSheet { get; set; }
|
|
|
-
|
|
|
- public TimeSpan Duration => Finish - Start;
|
|
|
-
|
|
|
- public ActivityBlock(Assignment assignment, TimeSheet sheet)
|
|
|
- {
|
|
|
- Activity = assignment.ActivityLink.ID != Guid.Empty
|
|
|
- ? assignment.ActivityLink.ID
|
|
|
- : sheet.ActivityLink.ID;
|
|
|
-
|
|
|
- Start = assignment.EffectiveStartTime();
|
|
|
- Finish = assignment.EffectiveFinishTime();
|
|
|
- TimeSheet = sheet;
|
|
|
- }
|
|
|
-
|
|
|
- public ActivityBlock(TimeSheet sheet)
|
|
|
- {
|
|
|
- Activity = sheet.ActivityLink.ID;
|
|
|
- Start = sheet.ApprovedStart;
|
|
|
- Finish = sheet.ApprovedFinish;
|
|
|
- TimeSheet = sheet;
|
|
|
- }
|
|
|
-
|
|
|
- public ActivityBlock(TimeSheet sheet, TimeSpan start, TimeSpan finish)
|
|
|
- {
|
|
|
- Activity = sheet.ActivityLink.ID;
|
|
|
- Start = start;
|
|
|
- Finish = finish;
|
|
|
- TimeSheet = sheet;
|
|
|
- }
|
|
|
+ return Script?.Execute(methodname: "ProcessDailyTimeSheets", parameters: new object[] { model, employee, date, timesheets, assignments })
|
|
|
+ ?? false;
|
|
|
+ }
|
|
|
|
|
|
- public ActivityBlock Chop(TimeSheet sheet)
|
|
|
- {
|
|
|
- if(Start < sheet.ApprovedStart)
|
|
|
- {
|
|
|
- Start = sheet.ApprovedStart;
|
|
|
- }
|
|
|
- if (Finish > sheet.ApprovedFinish)
|
|
|
- {
|
|
|
- Finish = sheet.ApprovedFinish;
|
|
|
- }
|
|
|
- return this;
|
|
|
- }
|
|
|
+ /// <summary>
|
|
|
+ /// Process the activity blocks for a given day and employee, and optionally skip them.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="employee"></param>
|
|
|
+ /// <param name="date"></param>
|
|
|
+ /// <param name="blocks"></param>
|
|
|
+ /// <returns><see langword="true"/> if this set of timesheets should be skipped.</returns>
|
|
|
+ private bool ProcessActivityBlocks(IDataModel<TimeSheet> model, Guid employee, DateTime date, List<ActivityBlock> blocks)
|
|
|
+ {
|
|
|
+ return Script?.Execute(methodname: "ProcessActivityBlocks", parameters: new object[] { model, employee, date, blocks })
|
|
|
+ ?? false;
|
|
|
+ }
|
|
|
|
|
|
- public bool ContainedInTimeSheet(TimeSheet sheet) =>
|
|
|
- Start < sheet.ApprovedFinish && Finish > sheet.ApprovedStart;
|
|
|
+ /// <summary>
|
|
|
+ /// Process the blocks for a given day and employee after the roster has been calculated, and optionally skip them.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns><see langword="true"/> if this set of timesheets should be skipped.</returns>
|
|
|
+ private bool ProcessBlocks(IDataModel<TimeSheet> model, Guid employee, DateTime date, List<PaidWorkBlock> work, List<LeaveBlock> leave)
|
|
|
+ {
|
|
|
+ return Script?.Execute(methodname: "ProcessBlocks", parameters: new object[] { model, employee, date, work, leave })
|
|
|
+ ?? false;
|
|
|
+ }
|
|
|
|
|
|
- public bool IntersectsWith(ActivityBlock other)
|
|
|
- {
|
|
|
- return Start < other.Finish && Finish > other.Start;
|
|
|
- }
|
|
|
+ /// <summary>
|
|
|
+ /// Process the item before it gets added to the export.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="line"></param>
|
|
|
+ /// <returns><see langword="false"/> if this item shouldn't be exported.</returns>
|
|
|
+ private bool ProcessItem(IDataModel<TimeSheet> model, Guid employee, DateTime date, TimesheetTimberlineItem line)
|
|
|
+ {
|
|
|
+ return Script?.Execute(methodname: "ProcessItem", parameters: new object[] { model, employee, date, line }, defaultResult: true)
|
|
|
+ ?? true;
|
|
|
}
|
|
|
|
|
|
private IEnumerable<ActivityBlock> GetMaskedActivityBlocks(IEnumerable<Assignment> assignments, TimeSheet sheet)
|
|
@@ -285,63 +430,6 @@ public class Module
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private interface IBlock
|
|
|
- {
|
|
|
- string Job { get; set; }
|
|
|
-
|
|
|
- string Extra { get; set; }
|
|
|
-
|
|
|
- string TaskID { get; set; }
|
|
|
-
|
|
|
- TimeSpan Duration { get; set; }
|
|
|
-
|
|
|
- string PayrollID { get; set; }
|
|
|
- }
|
|
|
-
|
|
|
- private class PaidWorkBlock : IBlock
|
|
|
- {
|
|
|
- public string Job { get; set; }
|
|
|
-
|
|
|
- public string Extra { get; set; }
|
|
|
-
|
|
|
- public string TaskID { get; set; }
|
|
|
-
|
|
|
- public TimeSpan Duration { get; set; }
|
|
|
-
|
|
|
- public string PayrollID { get; set; }
|
|
|
-
|
|
|
- public PaidWorkBlock(string taskID, TimeSpan duration, string payID, string job)
|
|
|
- {
|
|
|
- TaskID = taskID;
|
|
|
- Duration = duration;
|
|
|
- PayrollID = payID;
|
|
|
- Job = job;
|
|
|
- Extra = "";
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private class LeaveBlock : IBlock
|
|
|
- {
|
|
|
- public string Job { get; set; }
|
|
|
-
|
|
|
- public string Extra { get; set; }
|
|
|
-
|
|
|
- public string TaskID { get; set; }
|
|
|
-
|
|
|
- public TimeSpan Duration { get; set; }
|
|
|
-
|
|
|
- public string PayrollID { get; set; }
|
|
|
-
|
|
|
- public LeaveBlock(string payrollID, TimeSpan duration)
|
|
|
- {
|
|
|
- PayrollID = payrollID;
|
|
|
- Duration = duration;
|
|
|
- Job = "";
|
|
|
- Extra = "";
|
|
|
- TaskID = "";
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
private List<PaidWorkBlock> EvaluateOvertime(IEnumerable<PaidWorkBlock> workTime, Guid overtimeID)
|
|
|
{
|
|
|
var overtimeIntervals = _overtimeIntervals.GetValueOrDefault(overtimeID)?.ToList() ?? new List<OvertimeInterval>();
|
|
@@ -423,15 +511,26 @@ public class Module
|
|
|
.ToDictionary(x => x.ID, x => x);
|
|
|
|
|
|
var assignments = model.GetTable<Assignment>().ToObjects<Assignment>()
|
|
|
- .GroupBy(x => new { x.Date, Employee = x.EmployeeLink.ID }).ToDictionary(x => x.Key, x => x.ToArray());
|
|
|
+ .GroupBy(x => new { x.Date, Employee = x.EmployeeLink.ID }).ToDictionary(x => x.Key, x => x.ToList());
|
|
|
|
|
|
- var daily = timesheets.GroupBy(x => new { x.Date, Employee = x.EmployeeLink.ID }).ToDictionary(x => x.Key, x => x.ToArray());
|
|
|
+ var daily = timesheets.GroupBy(x => new { x.Date, Employee = x.EmployeeLink.ID }).ToDictionary(x => x.Key, x => x.ToList());
|
|
|
|
|
|
foreach(var (key, sheets) in daily)
|
|
|
{
|
|
|
- var dateAssignments = assignments.GetValueOrDefault(new { key.Date, Employee = key.Employee }, Array.Empty<Assignment>());
|
|
|
+ var dateAssignments = assignments.GetValueOrDefault(new { key.Date, key.Employee }, new List<Assignment>());
|
|
|
+
|
|
|
+ if(ProcessDailyTimeSheets(model, key.Employee, key.Date, sheets, dateAssignments))
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
var activityBlocks = GetActivityBlocks(dateAssignments, sheets);
|
|
|
+
|
|
|
+ if (ProcessActivityBlocks(model, key.Employee, key.Date, activityBlocks))
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
var approvedDuration = sheets.Aggregate(TimeSpan.Zero, (x, y) => x + y.ApprovedDuration);
|
|
|
|
|
|
var leave = new List<LeaveBlock>();
|
|
@@ -477,10 +576,15 @@ public class Module
|
|
|
|
|
|
var workItems = EvaluateOvertime(workTime, overtimeID);
|
|
|
|
|
|
+ if (ProcessBlocks(model, key.Employee, key.Date, workItems, leave))
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
var blocks = (workItems as IEnumerable<IBlock>).Concat(leave);
|
|
|
foreach(var block in blocks.GroupBy(x => new { x.Job, x.TaskID, x.PayrollID }, x => x))
|
|
|
{
|
|
|
- items.Add(new TimesheetTimberlineItem
|
|
|
+ var item = new TimesheetTimberlineItem
|
|
|
{
|
|
|
Employee = employee?.PayrollID ?? "",
|
|
|
InDate = DateOnly.FromDateTime(key.Date),
|
|
@@ -489,7 +593,11 @@ public class Module
|
|
|
Task = block.Key.TaskID,
|
|
|
Hours = block.Sum(x => x.Duration.TotalHours),
|
|
|
PayID = block.Key.PayrollID
|
|
|
- });
|
|
|
+ };
|
|
|
+ if (ProcessItem(model, key.Employee, key.Date, item))
|
|
|
+ {
|
|
|
+ items.Add(item);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|