This article describes how the Payroll Engine is used to develop a payroll solution based on a variable timesheet.
The Payroll Engine is an open source project that enables the development of scalable cloud payroll solutions.
The following requirements were considered:
- Support for different payroll periods: weekly, bi-weekly, monthly, bi-monthly, etc.
- Wage factor for casual workers.
- Variable daily rates divided into regular working hours and early/late periods.
- Parallel use of timesheets with different working time models.
- Timesheet data can be changed daily, scheduled and time limited.
- Customization of wage calculation with individual additional rates and factors.
- Automated payroll testing.
- Definition of special days (public holidays, trade fair, etc.).
- Integration of external working hours from Excel documents.
- Report on employee working hours and wages by period.
Use Cases
The application covers the following use cases:
-
Timesheet
: Configure timesheet -
Employment
: Manage employee employment -
Work Time
: Record employee work time -
Payrun
: Perform wage calculation -
Wage Report
: Create employee wage document -
Work Time Report
: Create employee working time report
Working times are recorded by the employee themselves (self-service) or by the HR user.
Timesheet
The timesheet describes the working time and basic wage data:
Field | Description | Value type |
---|---|---|
StartTime |
Regular period start time | Hour |
EndTime |
Regular period end time | Hour |
MinWorkTime |
Minimum work time | Hour |
MaxWorkTime |
Maximum work time | Hour |
BreakMin |
Minimum break time | Minute |
BreakMax |
Maximum break time | Minute |
RegularRate |
Regular hour rate | Money |
CasualRateFactor |
Casual worker factor to the regular rate | Percent |
Time Periods
In the timesheet, the working day is divided into different time periods:
- Regular period: Start and end of the working day and pay rates
- Early periods: Working periods from midnight to the start of regular working hours
- Late periods: Working periods after the regular working time until midnight
The periods are arranged according to the sort key next to the standard working time. Negative sort values apply to early periods and positive sort values to late periods.
The following standard fields exist for each period:
Field | Description | Value type |
---|---|---|
|
Period duration | Hour |
|
Period rate factor | Percent |
Work Time
The employee's working time is defined in the WorkTime
object with the following standard fields:
Field | Description | Value type |
---|---|---|
WorkTimeDate |
Working day | Date |
WorkTimeStart |
Start hour | Hour |
WorkTimeEnd |
End hour | Hour |
WorkTimeBreak |
Break time | Minute |
WorkTimeHours |
Working hours (calculated) | Minute |
Wage Calculation
To calculate the daily wage, the average working time is divided into different periods. The period wage is calculated according to the duration of work within the period and the wage factors. The total daily wage is calculated from the wage of the regular working time and all early and late periods.
The following example shows the distribution of the employee's working time between the daily periods. The employee has worked from 9.00 to 20.00 with a break of 30 minutes:
Application Usage
The use cases are executed in the Payroll Engine web application:
First, the timesheet is defined using the Timesheet
use case and the employee's employment type is defined using the Employment
use case. The employee's working and break times are then recorded using the Work Time
use case:
Break times are optional and can be hidden. Authorized users can also run these cases for forecast scenarios.
When a pay run job is executed, the payroll data for the employees is created and saved as Payroll Results
.
The employees' working times and the payroll results can be downloaded in various formats (pdf, excel, json, xml).
Special days import
Special days, such as public holidays to be excluded or weekend days to be included, such as trade fairs, are managed in the Workday
lookup. The special days can also be imported from an Excel file using the Payroll Console (see example Lookup/Workday/Workdays.2025.xlsx
).
Working hours import
For employees who do not have online access, the Payroll Console can also be used to import the working times of employees who have Online (see example Case.Data/WorkingTimes.2025.Week8.xlsx
).
Implementation
Tenant Setup
The payroll calendar, which determines the payroll cycle and the regular working days, is defined in the client. The users are assigned to divisions. The calendar determines the payroll cycle and the working days. Employees, who are also entered as users, can record and query their working time.
The payroll data is managed in the regulation object:
- Lookup tables such as the special days.
- Cases and their fields, which define the data model.
- Wage types and collectors for the wage run.
- Evaluation with reports.
👉 Tenants can be imported and updated as Json with the Payroll Console (see Wiki Basic Payroll).
Timesheet Scripting
A timesheet is defined by deriving the Timesheet
class. The timesheet periods are listed as members of the class and marked with the TimesheetPeriod
attribute. This determines whether it is an early or late shift and the order in which they are listed. The attribute parameter ns
stands for namespace and is used as a prefix for the case fields of the periods.
public class MyTimesheet : Timesheet
{
// case fields EarlyPeriodDuration and EarlyPeriodFactor
[TimesheetPeriod(order: -1, ns: nameof(EarlyPeriod))]
public TimesheetPeriod EarlyPeriod { get; } = new();
// case fields LatePeriodLowDuration and LatePeriodLowFactor
[TimesheetPeriod(order: +1, ns: nameof(LatePeriodLow))]
public TimesheetPeriod LatePeriodLow { get; } = new();
// case fields LatePeriodHighDuration and LatePeriodHighFactor
[TimesheetPeriod(order: +2, ns: nameof(LatePeriodHigh))]
public TimesheetPeriod LatePeriodHigh { get; } = new();
}
In the following example, an additional field WeekendRateFactor
is added to the timesheet to allow for an additional surcharge on weekend days.
public class MyTimesheet : Timesheet
{
// case fields LatePeriodDuration and LatePeriodFactor
[TimesheetPeriod(order: +1, ns: nameof(LatePeriod))]
public TimesheetPeriod LatePeriod { get; } = new();
// case field WeekendRateFactor
public decimal WeekendRateFactor { get; set; }
}
User-defined wage calculation
A derivative of the TimesheetCalculator
class is implemented for the wage calculation. The CalcRegularWage
method takes into account the additional weekend factor in the wage calculation:
public class MyTimesheetCalculator : TimesheetCalculator<MyTimesheet>
{
protected override decimal CalcRegularWage(WageDay day, HourPeriod timesheetPeriod)
{
// regular wage
var wage = base.CalcRegularWage(day, timesheetPeriod);
// weekend factor
if (day.Date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday)
{
wage *= (1m + day.Timesheet.WeekendRateFactor);
}
return wage;
}
}
The
WeekDay
parameter contains the daily values of the timesheet, the employment type and the daily working time.
Multiple Timesheets
To use multiple timesheets, the namespace can be defined with the CaseObject
attribute:
[CaseObject(ns: "Int")]
public class IntTimesheet : Timesheet
{
// case fields IntEarlyPeriodDuration and IntEarlyPeriodFactor
[TimesheetPeriod(order: -1, ns: nameof(EarlyPeriod))]
public TimesheetPeriod EarlyPeriod { get; } = new();
// case fields IntLatePeriodDuration and IntLatePeriodFactor
[TimesheetPeriod(order: +1, ns: nameof(LatePeriod))]
public TimesheetPeriod LatePeriod { get; } = new();
}
[CaseObject(ns: "Ext")]
public class ExtTimesheet : Timesheet
{
// case fields ExtEarlyPeriodDuration and ExtEarlyPeriodFactor
[TimesheetPeriod(order: -1, ns: nameof(EarlyPeriod))]
public TimesheetPeriod EarlyPeriod { get; } = new();
// case fields ExtLatePeriodDuration and ExtLatePeriodFactor
[TimesheetPeriod(order: +1, ns: nameof(LatePeriod))]
public TimesheetPeriod LatePeriod { get; } = new();
}
Scripting
The scripts in the Payroll Engine are used to control the runtime behavior and are executed on the Backerd server.
Case Scripting
For cases, scripts can be used to control file input (Case Build) and data storage (Case Validate). The Cases worksheet contains the following scripts:
The following script checks the data in the Timesheet
case:
public static bool Validate<TTimesheet>(CaseValidateFunction function)
where TTimesheet : Timesheet, new()
{
var workday = function.GetChangeCaseObject<TTimesheet>();
// retro changes
if (!function.AdminUser)
{
var start = function.GetStart(nameof(Timesheet.RegularRate));
var period = function.GetCalendarPeriod();
if (start.HasValue && period.IsBefore(start.Value))
{
function.AddIssue($"Timesheet change date {start.Value:d} is before calendar start {period.Start:d}");
return true;
}
}
// regular work time
var workTime = workday.EndTime - workday.StartTime;
if (workTime <= 0)
{
function.AddIssue("Missing timesheet regular duration.");
return true;
}
if (workday.MaxWorkTime < workday.MinWorkTime)
{
function.AddIssue("Invalid working time maximum.");
return true;
}
// break
if (workday.BreakMax / 60 >= workTime)
{
function.AddIssue("Break time maximum must be less than the regular working time.");
return true;
}
if (workday.BreakMax < workday.BreakMin)
{
function.AddIssue("Invalid break time maximum.");
return true;
}
// time step
if (workday.WorkTimeStep is < 1 or > 30)
{
function.AddIssue("Invalid working time step size.");
return true;
}
if (60m % workday.WorkTimeStep != 0)
{
function.AddIssue("Working time step must be a part of 60 minutes.");
return true;
}
// start/end date
function.UpdateStart(function.GetStart("RegularRate"));
function.UpdateEnd(function.GetEnd("RegularRate"));
return true;
}
The script checks the following aspects of the timesheet (areas):
-
retro changes
: No time recording outside the working period, which can be overridden by the administrator. -
regular work time
: Valid limits of the regular work time. -
break
: Valid break time limits. -
time step
: Valid step size for the input accuracy. -
start/end date
: Ensures that all timesheet fields apply to the same period.
The timesheet case functions are located in the classes
Timesheet/CaseBuild.cs
andTimesheet/CaseValidate.cs
.
Wage Type Scripting
The calculation of wages is divided into wage types, which are processed in numerical order. To display wages for different periods, a wage type is assigned to each period:
The timesheet wage type functions are located in the classes
Timesheet/WageTypeValue.cs
.
Report Scripting
In reports, the scripts control the file input (Report Build) and the document generation (Report End).
The Timesheet Wage Type functions are located in the classes
Timesheet/ReportBuild.cs
andTimesheet/ReportEnd.cs
.
Local Development
The Payroll Engine provides the ability to develop and debug case and report scripts locally. Based on the PayrolLEngine.Client.Services
, the backend runtime environment is mapped locally. This requires the Payroll Engine API to be available:
Testing
This example includes two tests on different employees. The tests Test/Test.Employee1.pecmd
and Test/Test.Employee2.pecmd
are run from the Payroll Console:
- create a test copy of the employee (name test n), which will be used in the following.
- run the
Work Time
case for 5 working days. - run the payroll for this week.
- check the values of the wage types and collectors.
The timing of use cases and payload runs can be freely defined to reflect past or future test scenarios.
👉 Read more about Payroll Testing.
Installation
To use the timesheet application, the Payroll Engine must be installed locally:
- Download, install and run the backend API.
- Install the timesheet payroll (folder
Examples/TimesheetPayroll
). - Run the timesheet use cases
- in the web application (uires the web app server to be started).
- with the Payroll Console (Json documents).
More information:
👉 Payroll Engine Wiki
👉 ReadMe TimesheetPayroll
👉 Payroll Engine Client Service NuGet