Skip to content

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

  1. Group configs by sourceModule
  2. Load bulk data for each needed module via calculator.loadData()
  3. Calculate for each employee × config via calculator.calculate()
  4. Return AllowanceRecord partials ready for save

DeductionPipeline

  1. Group configs by sourceModule
  2. Load bulk data for each needed module
  3. Calculate per employee, receiving grossPay and priorDeductions map
  4. Return DeductionRecord partials

Interfaces

CalculatorContext

Passed to every calculator during initialization:

FieldTypeDescription
dbDataSourceTypeORM connection
orgIdstringOrganization ID
employeeIdsstring[]Employees to calculate for
periodobject{ year, month }
periodStartDatePeriod start date
periodEndDatePeriod end date

EmployeeData

Available during per-employee calculation:

FieldTypeDescription
idstringEmployee UUID
baseSalarynumberBase salary from contract
hourlyRatenumberDerived hourly rate
allowancesRecordPrior 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).

FieldTypeDescription
keystringValue key identifier
labelstringDisplay label
descriptionstringHuman-readable description
unitstringUnit 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.

FieldTypeDescription
keystringParameter identifier
labelstringDisplay label
descriptionstringHuman-readable description
typeenumnumber, string, or boolean
defaultValuemixedDefault value
minnumberMinimum value (optional, for number type)
maxnumberMaximum value (optional)
stepnumberStep increment (optional)
suffixstringUnit 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.

MethodDescription
sourceModuleString 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.

MethodDescription
sourceModuleString 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

CalculatorSource ModuleDescription
EmploymentContractCalculatoremployment_contractBase salary from active contract
OvertimeRecordCalculatorovertime_recordSum of approved overtime pay in period
FixedIncentiveCalculatorfixed_incentiveSum of approved fixed incentives in period
EducationSubsidyCalculatoreducation_subsidySum of approved education subsidies in period
ResignationRecordCalculatorresignation_recordSeverance/settlement amounts — applied only to TERMINATED employees (see gating note below)
YearEndSettlementCalculatoryear_end_settlementYear-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

CalculatorSource ModuleDescription
InsuranceCalculatorpayroll_rate_config4대보험 (National Pension, Health, Employment, Industrial Accident)
TaxBracketCalculatortax_bracket_configProgressive 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

  1. Create packages/api/src/calculators/my-module.calculator.ts
  2. Implement AllowanceCalculator or DeductionCalculator interface (including getKeyDescriptions(), getParameterSchema(), and getDefaultParameters())
  3. Register in pipeline.tscreateCalculatorRegistry()
  4. Add a sheet column bound to the new source module (item code, value key, parameters live on the column metadata + org parameter store).
  5. 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:

typescript
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)

  • terminatedOnlyModules gate covers only resignation_record, not year_end_settlementpackages/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, so YearEndSettlementCalculator runs for every employee, not just TERMINATED ones. Either the comment overstates the gate or year_end_settlement is intentionally ungated; verify against intended year-end behavior before relying on it.
  • sourceModule fallback silently maps unknown modules to MANUALtoSourceType() (pipeline.ts:31-35) returns PayrollSourceType.MANUAL for any sourceModule not in the enum. A typo'd or renamed sheet-column source module therefore produces a manual-typed AllowanceRecord with no error, losing provenance.
  • Deduction calculators aren't listed in pipeline.ts — the two deduction calculators (InsuranceCalculator, TaxBracketCalculator) are registered in a separate deduction-pipeline.ts, so the allowance registry count (7) and the deduction set must be read from two files.