Skip to content

Advanced Payroll

This tutorial extends the Basic Payroll with two additional regulations:

  • Insurance regulation — income-dependent contribution using a range lookup
  • Company regulation — employee benefits that can occur multiple times per month

Both regulations are loaded on top of StartRegulation as separate payroll layers and verified in a combined test.

Insurance Regulation

The insurance contribution is determined by the employee's gross salary, divided into three rate classes:

Class Salary range Rate
A 1 – 2 999 30
B 3 000 – 5 999 45
C 6 000+ 60

The rate is stored in a Lookup and resolved at runtime using a range lookup function.

Example Insurance.yaml:

# yaml-language-server: $schema=PayrollEngine.Exchange.schema.json
createdObjectDate: 2023-01-01T00:00:00Z
tenants:
- identifier: StartTenant
  regulations:
  - name: InsuranceRegulation
    sharedRegulation: true            # available to other tenants as a shared regulation
    baseRegulations:
    - StartRegulation                 # inherits cases and wage types from StartRegulation
    wageTypes:
    - wageTypeNumber: 200             # processed after WT 100 (Salary)
      name: InsuranceRate
      valueExpression: GetRangeLookup<decimal>("InsuranceRate", WageType[100])  # lookup rate by salary
      collectors:
      - Deduction
    lookups:
    - name: InsuranceRate
      values:
      - key: A
        value: "30"
        rangeValue: 1                 # class A: salary >= 1
      - key: B
        value: "45"
        rangeValue: 3000              # class B: salary >= 3000
      - key: C
        value: "60"
        rangeValue: 6000              # class C: salary >= 6000
  payrolls:
  - name: StartPayroll
    layers:
    - regulationName: InsuranceRegulation
      level: 2                        # layer 2 runs after StartRegulation (level 1)
    updateMode: NoUpdate              # extend the payroll, do not replace it
  updateMode: NoUpdate

GetRangeLookup selects the lookup entry whose rangeValue is the highest value still at or below the input — here WageType[100], the salary from level 1. For a salary of 5 000, class B is selected and the contribution is 45.

Load the insurance regulation:

Insurance.Setup.pecmd

Company Regulation

Employees can receive a project benefit any number of times within a month. Each benefit entry is a discrete Moment value — not a period — so multiple entries for the same month are summed independently.

Example Company.yaml:

# yaml-language-server: $schema=PayrollEngine.Exchange.schema.json
createdObjectDate: 2023-01-01T00:00:00Z
tenants:
- identifier: StartTenant
  regulations:
  - name: CompanyRegulation
    baseRegulations:
    - InsuranceRegulation             # inherits from both lower regulation layers
    cases:
    - name: Benefit
      caseType: Employee
      fields:
      - name: Benefit
        valueType: Money
        timeType: Moment              # one-time value — not tied to a calendar period
    wageTypes:
    - wageTypeNumber: 100.1           # processed between WT 100 and WT 200
      name: Benefit
      valueActions:
      - ^^Benefit                     # sum all Benefit Moment entries for the period
      collectors:
      - Income
  payrolls:
  - name: StartPayroll
    layers:
    - regulationName: CompanyRegulation
      level: 3                        # top layer — can override anything in layers 1 and 2
    updateMode: NoUpdate
  updateMode: NoUpdate

Load the company regulation:

Company.Setup.pecmd

Payroll Test

The combined test verifies all three layers at once. Two benefit entries and one salary are set up, and the expected results cover all active wage types and collectors.

Full test file: Company.Test.et.yaml

Case data:

cases:
- case:
    caseName: Salary
    values:
    - caseFieldName: Salary
      value: "5000"
      start: 2023-01-01T00:00:00Z
      created: 2022-11-04T00:00:00Z
- reason: Project A
  case:
    caseName: Benefit
    values:
    - caseFieldName: Benefit
      value: "225"
      start: 2023-01-14T00:00:00Z     # Moment: 14th Jan
      created: 2022-12-12T00:00:00Z
- reason: Project B
  case:
    caseName: Benefit
    values:
    - caseFieldName: Benefit
      value: "250"
      start: 2023-01-26T00:00:00Z     # Moment: 26th Jan — independent entry
      created: 2022-01-14T00:00:00Z
updateMode: NoUpdate

Expected results for January 2023:

Wage Type / Collector Expected Calculation
100 Salary 5 000 base salary
100.1 Benefit 475 225 + 250 (two Moment entries)
200 InsuranceRate 45 class B (salary 5 000)
Income 5 475 5 000 + 475
Deduction −45 negated insurance contribution

Run the test:

Company.Test.pecmd
The test runs on a temporary copy of mario.nuñez@foo.com.

Next Steps