using System.Collections.Generic;
using System.Diagnostics;
namespace PayrollPrototype
public int Id { get; set; }
public string FullName { get; set; }
public Dictionary<string, object> HRData { get; set; } = new();
public class PayrollComponent
public string Code { get; set; }
public string Name { get; set; }
public string Formula { get; set; }
public bool IsSystem { get; set; }
public string Type { get; set; }
public Employee Employee { get; set; }
public Dictionary<string, decimal> CalculatedComponents { get; set; } = new();
public static class FormulaEvaluator
public static decimal Evaluate(string formula, Dictionary<string, decimal> components, Dictionary<string, object> hrData, List<PayrollComponent> allComponents)
var interpreter = new Interpreter();
foreach (var kv in components)
interpreter.SetVariable(kv.Key, kv.Value);
foreach (var kv in hrData)
interpreter.SetVariable(kv.Key, kv.Value);
interpreter.SetFunction("SUM_BY_TYPE", (Func<string, decimal>)(type =>
foreach (var comp in allComponents)
if (comp.Type == type && components.TryGetValue(comp.Code, out var val))
var result = interpreter.Eval(formula);
return Convert.ToDecimal(result);
public class PayrollCalculator
private readonly List<PayrollComponent> _components;
public PayrollCalculator(List<PayrollComponent> components)
_components = components;
public PayrollItem CalculateFor(Employee employee)
var item = new PayrollItem { Employee = employee };
foreach (var component in _components)
var value = FormulaEvaluator.Evaluate(component.Formula, item.CalculatedComponents, employee.HRData, _components);
item.CalculatedComponents[component.Code] = value;
public List<PayrollItem> CalculateForMany(List<Employee> employees)
var result = new List<PayrollItem>();
foreach (var employee in employees)
result.Add(CalculateFor(employee));
static void Main(string[] args)
var employees = new List<Employee>
new() { Id = 1, FullName = "Anna Nowak", HRData = {["GodzNom"] = 168m, ["DniChor"] = 0m, ["GodzPracy"] = 168m, ["GodzUrl"] = 8m, ["Nadgodziny"] = 0 } },
var components = new List<PayrollComponent>
new() { Code = "PLACA_ZASADNICZA", Name = "Płaca zasadnicza", Formula = "6000 * (GodzPracy/GodzNom)", IsSystem = false, Type = "Salary" },
new() { Code = "NADG", Name = "Premia za nadgodziny", Formula = "Nadgodziny * 25", IsSystem = false, Type = "Salary" },
new() { Code = "BRUTTO", Name = "Wynagrodzenie brutto", Formula = "SUM_BY_TYPE(\"Salary\")", IsSystem = true, Type = "Other" },
new() { Code = "ZUS_EM", Name = "Składka emerytalna", Formula = "BRUTTO * 0.0976m", IsSystem = true, Type = "Other" },
new() { Code = "ZUS_RE", Name = "Składka rentowa", Formula = "BRUTTO * 0.015m", IsSystem = true, Type = "Other" },
new() { Code = "ZUS_CH", Name = "Składka chorobowa", Formula = "BRUTTO * 0.0245m", IsSystem = true, Type = "Other" },
new() { Code = "ZUS_SUMA", Name = "Suma składek ZUS", Formula = "ZUS_EM + ZUS_RE + ZUS_CH", IsSystem = true, Type = "Other" },
new() { Code = "NFZ", Name = "Składka zdrowotna", Formula = "BRUTTO * 0.09m", IsSystem = true, Type = "Other" },
new() { Code = "KOSZTY", Name = "Koszty uz. przychodu", Formula = "250", IsSystem = true, Type = "Other" },
new() { Code = "ULGA_POD", Name = "Ulga podatkowa", Formula = "300"},
new() { Code = "PODATEK", Name = "Podatek", Formula = "Math.Max(0, ((BRUTTO - ZUS_SUMA - KOSZTY) * 0.12m) - ULGA_POD)", IsSystem = true, Type = "Other" },
new() { Code = "NETTO", Name = "Wynagrodzenie netto", Formula = "BRUTTO - ZUS_SUMA - NFZ - PODATEK", IsSystem = true, Type = "Other" }
var calculator = new PayrollCalculator(components);
var payrollList = calculator.CalculateForMany(employees);
foreach (var item in payrollList)
var sw = Stopwatch.StartNew();
Console.WriteLine("=====================================");
Console.WriteLine($"Pracownik: {item.Employee.FullName}");
foreach (var comp in item.CalculatedComponents)
Console.WriteLine($"{comp.Key,-20}\t{comp.Value,10:F2} zł");
Console.WriteLine($"⏱️ Czas przeliczenia: {sw.ElapsedMilliseconds} ms\n");