Skip to content

Payroll Code Modules Reference ​

The payroll system computes allowances and deductions through sheet-driven settlement. The arithmetic lives in @hereby/code-modules — one code module per source module, each a pure compute(inputs, parameters) function — and the sheet compute loader (packages/api/src/routers/sheet-rows/compute.ts) loads its inputs from the database and runs it per column. Per-item configuration (item code, source module, value key, rate/multiplier parameters) lives on sheet column metadata + the org parameter store, and AllowanceRecord / DeductionRecord are inserted only on sheet commit (commitSheet, ✓ 커밋). See Payroll Schema Refactor and Payroll Data Provenance.

The former packages/api/src/calculators/ pipeline (AllowancePipeline / DeductionPipeline) became a dead path once settlement moved to sheets — it produced no payroll and was only read for Module Manager metadata — and was removed in 2026-09. This page documents the current path only.

Architecture ​

The same modules run in the hr-system browser (apps/hr-system/src/sheets/workbook/commit-compute.ts) for previews, so preview and server commit agree by construction.

Settlement Flow ​

  1. Load inputs per column sourceModule — loadInputsByModule(ctx, sourceModule, rows, period) queries the source tables (employment contracts, overtime_records, …) for the sheet period and builds a per-row inputs map.
  2. Run compute(inputs, parameters) per row × module column — parameters are column metadata + org parameter store merged via mergeWithDefaults.
  3. The column's value key selects one output for the cell.
  4. On ✓ 커밋, commitSheet writes cell values to AllowanceRecord / DeductionRecord with sheet lineage (sourceSheetId / sourceSheetRowId / sourceSheetColumnKey).

Interfaces (packages/code-modules/src/types.ts) ​

CodeModule ​

FieldTypeDescription
sourceModuleSourceModuleRegistry key (matches the enum value)
parameterSchemaParameterField[]Configurable parameter definitions (form rendering + validation)
defaultParametersParametersDefault parameter values
keyDescriptionsKeyDescription[]Output keys returned by compute()
compute(inputs, parameters)functionPure computation — returns Record<string, number>

KeyDescription ​

Used to populate UI hints when configuring a sheet column's value key.

FieldTypeDescription
keystringValue key identifier
labelstringDisplay label
descriptionstringHuman-readable description
unitstringUnit ("원", "시간", "건", "%")

ParameterField ​

FieldTypeDescription
keystringParameter identifier
labelstringDisplay label
descriptionstringHuman-readable description
typeenumnumber, string, or boolean
defaultValuemixedDefault value
min / max / stepnumberConstraints for number type (optional)
suffixstringUnit label after the input (e.g., "%", "시간", "원")

Code Modules (packages/code-modules/src/registry.ts) ​

Allowance ​

ModuleSource ModuleInputs loaded fromDescription
employmentContractModuleemployment_contractemployment_contractsBase salary, 호봉, longevity from the active contract
overtimeRecordModuleovertime_recordovertime_records + overtime_typesApproved overtime premiums (category multipliers + managed type multipliers)
fixedIncentiveModulefixed_incentivefixed_incentivesApproved fixed incentives in period
educationSubsidyModuleeducation_subsidyeducation_subsidiesApproved education subsidies in period
resignationRecordModuleresignation_recordresignation_records etc.Severance / settlement amounts
yearEndSettlementModuleyear_end_settlementyear_end_settlementsYear-end tax settlement refund / charge

Deduction ​

ModuleSource ModuleDescription
insuranceModulepayroll_rate_config4대보험 (National Pension, Health, Employment, Industrial Accident)
taxBracketModuletax_bracket_configProgressive income tax + local income tax

Manual Module ​

Columns with sourceModule = "manual" bypass computation. Their unitPrice × multiplier is used directly as the amount.

Overtime Bucket Mapping (silent-underpayment guard) ​

The overtime_record branch of loadInputsByModule maps OvertimeCategory onto the module's input buckets. The source of truth is .claude/rules/payroll/korean-payroll-compliance.md.

CategoryBuckets filledEffective rate (defaults)
WEEKDAYweekday1.5x
NIGHTweekday + night1.5x + 0.5x = 2.0x
HOLIDAYholiday1.5x
HOLIDAY_NIGHTholiday + night1.5x + 0.5x = 2.0x
SATURDAY (deprecated)weekend0.5x (legacy data only)
  • Records with a managed type (typeId) skip the category buckets and accrue only through typeWeightedHours (hours × type multiplier). Night / holiday day counts (night meal allowance etc.) are still counted by category.
  • Only approved · report_pending · report_approved · report_rejected records are aggregated.
  • Within-schedule night / holiday hours (attendances.nightHours / holidayRegularHours / holidayNightHours) are not fed into this module; sheets reference them through attendance source ref columns instead.
  • Regression guard: packages/api/src/routers/sheet-rows/payroll-correctness.test.ts pins the module compute(). The loader-level mapping needs a database and is not unit-tested yet.

Adding a New Code Module ​

  1. Implement CodeModule in packages/code-modules/src/modules/my-module.ts (parameterSchema, defaultParameters, keyDescriptions, compute)
  2. Register it in packages/code-modules/src/registry.ts and export from index.ts
  3. Add an input-loading branch to loadInputsByModule in packages/api/src/routers/sheet-rows/compute.ts (and to hr-system commit-compute.ts if browser previews need it)
  4. Add a sheet column bound to the new source module (item code, value key, parameters on column metadata + org parameter store)
  5. Its keyDescriptions keys surface in the sheet column config UI and Module Manager

Module Registry Router ​

The module-registry router reads listCodeModules() and exposes metadata for every source module; the Module Manager app renders it.

typescript
const result = await client.moduleRegistry.list.query();
// Returns: { modules: [{ key, name, type, hasCalculator, sourceTables, targetTables,
//                        outputKeys, parameterSchema, defaultParameters, status, … }],
//            summary: { total, withCalculator, withoutCalculator, … } }

hasCalculator is a compatibility field name only; it means "a code module is registered for this source module."