Calculator Pipeline Reference
The payroll system uses a config-driven calculator pipeline to compute allowances and deductions. Each source module has a dedicated calculator that reads domain data and produces amounts. Per-item config (item code, source module, value key, rate/multiplier parameters) lives on sheet column metadata + the org-wide parameter store. The pipeline is driven by the committed sheet, and records are inserted only on sheet commit (commitSheet). See Payroll Schema Refactor.
Architecture
Pipeline Flow
AllowancePipeline
- Group configs by
sourceModule - Load bulk data for each needed module via
calculator.loadData() - Calculate for each employee × config via
calculator.calculate() - Return
AllowanceRecordpartials ready for save
DeductionPipeline
- Group configs by
sourceModule - Load bulk data for each needed module
- Calculate per employee, receiving
grossPayandpriorDeductionsmap - Return
DeductionRecordpartials
Interfaces
CalculatorContext
Passed to every calculator during initialization:
| Field | Type | Description |
|---|---|---|
db | DataSource | TypeORM connection |
orgId | string | Organization ID |
employeeIds | string[] | Employees to calculate for |
period | object | { year, month } |
periodStart | Date | Period start date |
periodEnd | Date | Period end date |
EmployeeData
Available during per-employee calculation:
| Field | Type | Description |
|---|---|---|
id | string | Employee UUID |
baseSalary | number | Base salary from contract |
hourlyRate | number | Derived hourly rate |
allowances | Record | Prior allowance amounts by config code |
KeyDescription
Describes a key that a calculator can return. Used to populate UI hints when configuring a sheet column's value key (stored on the sheet column metadata).
| Field | Type | Description |
|---|---|---|
key | string | Value key identifier |
label | string | Display label |
description | string | Human-readable description |
unit | string | Unit type ("원", "시간", "건", "%") |
ParameterField
Describes a configurable parameter for a calculator. Used to render the config form and validate parameter values. Parameters are stored per-config in the database as JSONB.
| Field | Type | Description |
|---|---|---|
key | string | Parameter identifier |
label | string | Display label |
description | string | Human-readable description |
type | enum | number, string, or boolean |
defaultValue | mixed | Default value |
min | number | Minimum value (optional, for number type) |
max | number | Maximum value (optional) |
step | number | Step increment (optional) |
suffix | string | Unit label after input (e.g., "%", "시간", "원") |
AllowanceCalculator
Each calculator returns a Record<string, number> for each employee, where keys are documented by getKeyDescriptions(). The pipeline reads config.valueKeys to select the appropriate values. Parameters (rates, multipliers, divisors) are stored per-config in the database as JSONB.
| Method | Description |
|---|---|
sourceModule | String identifier matching SourceModule enum |
getKeyDescriptions() | Return available output keys with labels and units |
getParameterSchema() | Return configurable parameter definitions for this module |
getDefaultParameters() | Return default parameter values |
loadData(ctx) | Bulk-load domain data for all employees in period |
calculate(employee, parameters) | Return Record<string, number> of computed values |
DeductionCalculator
Same key-value pattern as AllowanceCalculator. The grossPay parameter enables tax calculators to compute based on total earnings.
| Method | Description |
|---|---|
sourceModule | String identifier matching SourceModule enum |
getKeyDescriptions() | Return available output keys with labels and units |
getParameterSchema() | Return configurable parameter definitions for this module |
getDefaultParameters() | Return default parameter values |
loadData(ctx) | Bulk-load domain data for all employees in period |
calculate(employee, grossPay, priorDeductions, parameters) | Return Record<string, number> of deduction values |
Calculators
Allowance Calculators
| Calculator | Source Module | Description |
|---|---|---|
EmploymentContractCalculator | employment_contract | Base salary from active contract |
OvertimeRecordCalculator | overtime_record | Sum of approved overtime pay in period |
FixedIncentiveCalculator | fixed_incentive | Sum of approved fixed incentives in period |
EducationSubsidyCalculator | education_subsidy | Sum of approved education subsidies in period |
ResignationRecordCalculator | resignation_record | Severance/settlement amounts — applied only to TERMINATED employees (see gating note below) |
YearEndSettlementCalculator | year_end_settlement | Year-end tax settlement refund/charge as an allowance component |
All seven are registered (with ManualCalculator) in createCalculatorRegistry() — packages/api/src/calculators/pipeline.ts:42-50.
Deduction Calculators
| Calculator | Source Module | Description |
|---|---|---|
InsuranceCalculator | payroll_rate_config | 4대보험 (National Pension, Health, Employment, Industrial Accident) |
TaxBracketCalculator | tax_bracket_config | Progressive income tax + local income tax |
Manual Module
Configs with sourceModule = "manual" bypass the calculator pipeline. Their unitPrice × multiplier is used directly as the amount.
Adding a New Calculator
- Create
packages/api/src/calculators/my-module.calculator.ts - Implement
AllowanceCalculatororDeductionCalculatorinterface (includinggetKeyDescriptions(),getParameterSchema(), andgetDefaultParameters()) - Register in
pipeline.ts→createCalculatorRegistry() - Add a sheet column bound to the new source module (item code, value key, parameters live on the column metadata + org parameter store).
- The calculator's
getKeyDescriptions()keys are surfaced in the sheet column config UI.
Module Registry
The module-registry router exposes metadata about all registered modules:
const result = await client.moduleRegistry.list.query();
// Returns: { key, name, type, description, sourceEntities, targetEntities,
// calculatorClass, formula, configCount }This is used by the admin UI to show which modules are active and configured.
⚠️ 구현 모호점 · 하드닝 후보 (2026-08-13)
terminatedOnlyModulesgate covers onlyresignation_record, notyear_end_settlement—packages/api/src/calculators/pipeline.ts:118-124. The comment reads "Resignation/settlement modules only apply to terminated employees," but the array is["resignation_record"]alone, soYearEndSettlementCalculatorruns for every employee, not justTERMINATEDones. Either the comment overstates the gate oryear_end_settlementis intentionally ungated; verify against intended year-end behavior before relying on it.sourceModulefallback silently maps unknown modules toMANUAL—toSourceType()(pipeline.ts:31-35) returnsPayrollSourceType.MANUALfor anysourceModulenot in the enum. A typo'd or renamed sheet-column source module therefore produces amanual-typedAllowanceRecordwith no error, losing provenance.- Deduction calculators aren't listed in
pipeline.ts— the two deduction calculators (InsuranceCalculator,TaxBracketCalculator) are registered in a separatededuction-pipeline.ts, so the allowance registry count (7) and the deduction set must be read from two files.