Payroll Ledger Data Provenance
Traces every table involved in generating a PayrollRecord (급여대장) — column definitions, data flows, and the AllowanceRecord-as-atom architecture.
Source entities hold observations only.
OvertimeRecord,FixedIncentive,EducationSubsidy,LeaveRequest,ResignationRecord, andYearEndSettlementcarry no calculated monetary fields. All amounts live onAllowanceRecord/DeductionRecord, which are produced only as the output of a sheet's "✓ 커밋" (commit) action and carry sheet lineage (sourceSheetId/sourceSheetRowId/sourceSheetColumnKey,NOT NULL). Insurance/tax/allowance amounts come from per-column calculators whose parameters live on sheet column metadata + the org parameter store. See Payroll Schema Refactor.
Architecture Principle
AllowanceRecord is the atom of all pay. Every earnings line item — base salary, overtime, allowances, incentives — becomes an individual AllowanceRecord. Every deduction — insurance, tax, settlements — becomes a DeductionRecord. The PayrollRecord is a header that links to these atomic records via FK and stores cached summary totals.
Append-Only Meta Tables
All meta tables (EmploymentContract, OvertimeRecord, FixedIncentive, YearEndSettlement, EducationSubsidy, ResignationRecord) are append-only. Content is never updated in place — a new version row is inserted and the old row is marked with supersededAt. This preserves full change history.
| Column | Type | Purpose |
|---|---|---|
chainId | uuid | Groups all versions of the same logical entity |
version | integer | Monotonically increasing within a chain (starts at 1) |
supersededAt | timestamp, nullable | NULL = current version; set when a newer version is inserted |
- Content changes (amounts, dates, rates): create a new version (
version + 1, samechainId) - Status changes (pending → approved → paid): direct metadata update on the same row
- Query pattern: always filter
WHERE supersededAt IS NULLto get current versions - History: query
WHERE chainId = :chainId ORDER BY version DESCfor full audit trail
Provenance Tracking
AllowanceRecord and DeductionRecord track which source-observation row generated them:
| Column | Type | Purpose |
|---|---|---|
sourceType | enum (PayrollSourceType) | Which source module generated this record |
sourceId | uuid, nullable | Specific source row ID (null for aggregated sources) |
inputSnapshot | jsonb | (planned) Frozen calculator inputs (hours, hourlyRate, multiplier, unitPrice, count) — makes amounts replayable without joining back to source tables |
formulaVersion | string | (planned) Recipe version at calc time |
PayrollSourceType values (packages/core/src/enums/payroll.enum.ts): employment_contract, overtime_record, fixed_incentive, education_subsidy, year_end_settlement, resignation_record, payroll_rate_config, tax_bracket_config, manual
Data Flow Overview
Key invariant (verified 2026-08-13): the money atoms
AllowanceRecord/DeductionRecordare created only by sheet commit (commitSheet).generateForPeriodneither creates nor deletes atoms — it builds aPayrollRecordheader snapshot and links existing atoms viapayrollRecordId. (An earlier version of this doc describedgenerateForPeriodas creating/deleting atoms filtered byisAutoGenerated— that is wrong and dangerous; see the Flow section.)
Flow: Sheet Commit vs Snapshot Generation
Money atoms are created by sheet commit (commitSheet)
AllowanceRecord/DeductionRecordare created only on the sheet "✓ 커밋".compute.tsaggregates source observations under an approval-status gate (e.g. overtimestatus IN ('approved','report_pending','report_approved','report_rejected')), andcommitSheetwrites the result as AR/DR withisAutoGenerated = true.- Re-commit soft-deletes (
deletedAt) the existing AR/DR for thatsourceSheetIdand re-inserts them (stillisAutoGenerated = true). - Settlement writers (year-end, resignation) are the only writers that set
isAutoGenerated = false— this means "settlement output", not "manual entry"; it gates their own cancel-cleanup.
generateForPeriod (PayrollRecord snapshot only — does NOT delete atoms)
- If the period is already
CONFIRMED, block (PRECONDITION_FAILED). - Delete only the stale PayrollRecord rows for the period. AR/DR are NOT deleted — they are unlinked via
payrollRecordId = NULL. - Re-create the
PayrollRecordheader (cached summary totals). - Re-link all AR/DR for the period via
payrollRecordId.
⚠️ Do NOT reintroduce an
isAutoGenerated = truefilter that deletes/recreates AR/DR insidegenerateForPeriod. That filter was deliberately removed — AR/DR are sheet-commit outputs, so deleting them here destroys sheet-committed payroll data. Source: the inline comment inpackages/api/src/routers/payroll.router.tsgenerateForPeriod, and.claude/rules/payroll/korean-payroll-compliance.md.
⚠️ 구현 모호점 · 하드닝 후보 (2026-08-13 감사)
이 문서를 근거로 기능을 하드닝할 때 우선 검토할 실코드 취약점:
- 초과근무 집계 이중 경로(SSOT 미정) — 실 급여 경로는
sheet-rows/compute.ts(SQL, 상태 게이트)이나, 별도OvertimeRecordCalculator(packages/api/src/calculators/overtime-record.calculator.ts)는 status 필터 없이supersededAt IS NULL만으로 합산한다. 이 계산기는 현재 Module Manager 상태 조회(module-registry.router.ts)에서만 생성되어 급여를 만들지 않지만, 이 파이프라인을 급여 경로로 재배선하면 대기·취소 초과근무가 급여에 새어든다. 재배선 시status게이트 필수. compute.ts가report_rejected(보고서반려)를 급여 집계에 포함 — 주석은 "pending/rejected/cancelled 제외"라 하나 IN 목록엔report_rejected가 있다. 연장 자체는 승인됐으니 근무는 실재하므로 의도일 수 있으나, 정책 문서(docs/ko/policy/.../overtime.md: "보고서 승인 시점 반영")와 어긋난다 → 반영 기준 확정 필요.isAutoGenerated = false의 의미가 오해되기 쉬움 — "수동 입력"이 아니라 "정산 라이터(연말/퇴직) 출력"이다. 이 플래그로 분기하는 cancel-cleanup 로직 수정 시 주의.- 휴일 마스터 ↔ 휴일수당 계산 미연결 —
OrganizationHoliday(조직 휴일 달력)는 존재하며 연차(leave-request/leave-ledger)·출근율(attendance-rate)은 소비하지만, 휴일수당 계산 경로(sheet-rows/compute.ts·계산기·attendance-finalizer)는 이를 읽지 않는다 — 휴일 수당은 여전히 수동HOLIDAY카테고리/Shift.treatAsHoliday로만 발생. 연결 여부 제품 판단 필요. - NIGHT/HOLIDAY_NIGHT 유효 2.0x 이중 버킷 —
holidayMultiplier/nightMultiplier를 org별로 재파라미터화하면 유효배율이 바뀐다. 어느 한 버킷을 누락하면 조용한 과소지급(과거 2회 재발). 모듈 레벨 회귀 가드는 신설됨(packages/api/src/calculators/payroll-correctness.test.ts) — 계산기 레벨 카테고리→버킷 매핑과 국민연금 상·하한(cap/floor)은 여전히 미커버.
1. Employee
Table: employees
| Column | Type | Payroll Usage |
|---|---|---|
id | uuid (PK) | AllowanceRecord.employeeId FK |
employeeNumber | string, unique | UI display, eCount name lookup |
firstName / lastName | string | eCount export: ${lastName}${firstName} |
status | enum | Filter: ACTIVE only |
organizationId | uuid (FK) | Multi-tenancy filter |
2. EmploymentContract
Table: employment_contracts — Meta table providing input to AllowanceRecord creation.
| Column | Type | Payroll Usage |
|---|---|---|
baseSalary | decimal(15,2) | → AllowanceRecord (itemCode 01 월급여) |
allowances | jsonb | → AllowanceRecords (meal→19, transportation→14, housing→14) |
salaryGradeId | uuid (FK) | SalaryGrade reference |
isActive | boolean | Only active contracts used |
3. OvertimeRecord
Table: overtime_records — Source observation table. Pay amounts are computed by OvertimeRecordCalculator (reads overtimeHours × Contract.baseSalary/209 × the overtime multiplier configured on the sheet column / org parameter store) and written to AllowanceRecord on sheet commit.
| Column | Type | Payroll Usage |
|---|---|---|
employeeId | uuid (FK) | Aggregation key |
workDate | date | Period filter |
overtimeHours | decimal(7,2) | → Calculator input |
mealBreakHours | decimal(7,2) | Subtracted for net hours |
category | enum | Category → AllowanceRecord itemCode routing |
status | enum | Sheet aggregation gate: approved / report_pending / report_approved / report_rejected (sheet-rows/compute.ts) |
hourlyRate | decimal(15,2) | (removing) — recomputed at calc time |
multiplier | decimal(5,2) | (removed column) — value now on the sheet column / org parameter store |
calculatedPay | decimal(15,2) | (removing) — lives on AllowanceRecord.amount |
Category → AllowanceRecord ItemCode (LEGACY — pre-migration 1816)
⚠️ Legacy mapping. Since migration 1816,
AllowanceRecordcarries no item-code FK — records target a sheet column viatargetSheetSlug/targetColumnKeyinstead, and eCount A/D-code classification happens at export time. The table below documents the retired fixed mapping (note it predatesHOLIDAY_NIGHT, which buckets as holiday 1.5 + night 0.5).
| Category | AllowanceRecord itemCode | Name |
|---|---|---|
WEEKDAY | 10 | 연장근무수당1 |
NIGHT | 12 | 연장근무수당2 |
SATURDAY | 13 | 야간근무수당1 |
HOLIDAY | 09 | 휴일근무수당 |
4. AllowanceRecord (THE ATOM)
Table: allowance_records — Atomic source of truth for all earnings.
| Column | Type | Usage |
|---|---|---|
id | uuid (PK) | Primary key |
employeeId | uuid (FK) | Employee reference |
year / month | number | Period |
amount | decimal(15,2) | The pay amount |
type | enum | AllowanceType (OTHER for auto-generated) |
paymentMethod | enum | SALARY for payroll inclusion |
reason | string | Human-readable description |
status | enum | APPROVED / PAID |
itemCodeId | uuid (FK) | AllowanceItemCode FK (eCount code) |
payrollRecordId | uuid (FK) | PayrollRecord FK (linked on generate) |
isAutoGenerated | boolean | true = sheet-commit output (commitSheet); false = settlement writer (year-end/resignation), NOT "manual" |
sourceType | enum | PayrollSourceType — which meta table generated this |
sourceId | uuid, nullable | Specific meta row ID (null for aggregated) |
organizationId | uuid (FK) | Multi-tenancy |
Sheet-committed AllowanceRecords (by commitSheet)
| Source | itemCode | Reason | Amount | sourceType | sourceId |
|---|---|---|---|---|---|
| Contract.baseSalary | 01 | 월급여 | baseSalary | employment_contract | contract.id |
| OvertimeRecord (WEEKDAY) | 10 | 연장근무수당1 | Σ calculatedPay | overtime_record | null |
| OvertimeRecord (NIGHT) | 12 | 연장근무수당2 | Σ calculatedPay | overtime_record | null |
| OvertimeRecord (SATURDAY) | 13 | 야간근무수당1 | Σ calculatedPay | overtime_record | null |
| OvertimeRecord (HOLIDAY) | 09 | 휴일근무수당 | Σ calculatedPay | overtime_record | null |
| Contract.allowances.meal | 19 | 식대 | meal amount | employment_contract | contract.id |
| Contract.allowances.transportation | 14 | 교통비 | amount | employment_contract | contract.id |
| Contract.allowances.housing | 14 | 주거비 | amount | ||
| FixedIncentive | 23 | 간호간병평가인센티브 | Σ amount |
5. DeductionRecord (THE ATOM)
Table: deduction_records — Atomic source of truth for all deductions.
| Column | Type | Usage |
|---|---|---|
id | uuid (PK) | Primary key |
employeeId | uuid (FK) | Employee reference |
year / month | number | Period |
amount | decimal(15,2) | The deduction amount |
reason | string | Human-readable description |
status | enum | APPROVED / APPLIED |
itemCodeId | uuid (FK) | DeductionItemCode FK (eCount code) |
payrollRecordId | uuid (FK) | PayrollRecord FK (linked on generate) |
isAutoGenerated | boolean | true = sheet-commit output (commitSheet); false = settlement writer (year-end/resignation), NOT "manual" |
sourceType | enum | PayrollSourceType — which meta table generated this |
sourceId | uuid, nullable | Specific meta row ID (null for aggregated) |
organizationId | uuid (FK) | Multi-tenancy |
Sheet-committed DeductionRecords (by commitSheet)
Insurance rates come from the org parameter store / sheet column bound to
InsuranceCalculator, and rows are written on sheet commit.
| Source | itemCode | Reason | Formula | sourceType | sourceId |
|---|---|---|---|---|---|
| InsuranceCalculator | 03 | 국민연금 | round(clamp(base, floor, cap) × rate) | employment_contract | contract.id |
| InsuranceCalculator | 04 | 건강보험 | round(baseSalary × rate) | employment_contract | contract.id |
| InsuranceCalculator | 05 | 고용보험 | round(baseSalary × rate) | employment_contract | contract.id |
| InsuranceCalculator | 20 | 장기요양보험 | round(baseSalary × rate) | employment_contract | contract.id |
| TaxBracketConfig | 01 | 소득세 | round((taxable × rate - deduction) / 12) | employment_contract | contract.id |
| TaxBracketConfig | 02 | 주민세 | round(incomeTax × 0.10) |
6. FixedIncentive
Table: fixed_incentives — Meta table providing input to AllowanceRecord creation.
| Column | Type | Payroll Usage |
|---|---|---|
employeeId | uuid (FK) | Aggregation key |
year / month | number | Period filter |
totalAmount | decimal(15,2) | → AllowanceRecord (itemCode 23) |
unitPrice | decimal(15,2) | Unit price per count |
unitCount | int | Number of units |
totalAmount | decimal(15,2) | → AllowanceRecord (itemCode 23) |
category | enum | Incentive category (rehabilitation, nursing, etc.) |
7. YearEndSettlement (연말정산)
Table: year_end_settlements — Source observation / lifecycle table. Adjustment amounts materialize as DeductionRecord rows on sheet commit. The installment-split plan is configured on the sheet column / org parameter store.
| Column | Type | Payroll Usage |
|---|---|---|
employeeId | uuid (FK) | Employee reference |
settlementYear | number | Target year |
applicationDate | date, nullable | When adjustment applied |
applicationMonth | number, nullable | Payroll month for application |
status | enum | PENDING → CALCULATED → APPROVED → APPLIED → COMPLETED |
adjustmentAmount | decimal(15,2) | (removing) — lives on DeductionRecord.amount (itemCode 10) |
installmentMonths | number | (removed column) — installment split now on the sheet column / org parameter store |
installmentAmount | decimal(15,2) | (removing) — derived from adjustmentAmount / installmentMonths |
8. EducationSubsidy (교육보조금)
Table: education_subsidies — Source observation / lifecycle table. Subsidy amount materializes as AllowanceRecord via EducationSubsidyCalculator on sheet commit. The recipe (per-course-type unit price) lives on the sheet column / org parameter store.
| Column | Type | Payroll Usage |
|---|---|---|
employeeId | uuid (FK) | Employee reference |
courseName | string | Course identification |
paymentDate | date | Payment timing |
receiptVerified | boolean | Verification status |
status | enum | PENDING → APPROVED → PAID → CANCELLED |
courseAmount | decimal(15,2) | (removing) — lives on AllowanceRecord.amount |
9. ResignationRecord (퇴직정산)
Table: resignation_records — Source observation / lifecycle table. Settlement amounts are decomposed into individual AllowanceRecord / DeductionRecord rows by ResignationRecordCalculator.
| Column | Type | Payroll Usage |
|---|---|---|
employeeId | uuid (FK) | Employee reference |
resignationDate | date | Last working day |
reason | string, nullable | Resignation reason |
status | enum | PENDING → PROCESSING → SETTLED → COMPLETED |
settlementAmount | decimal(15,2) | (removing) — decomposed into ledger rows |
healthInsuranceRecon | decimal(15,2) | (removing) — → DeductionRecord itemCode 12 |
yearEndRecon | decimal(15,2) | (removing) — → DeductionRecord itemCode 10 |
annualLeavePayout | decimal(15,2) | (removing) — → AllowanceRecord itemCode 17 |
unusedLeaveDays | decimal(5,2) | (removing) — derived from leave entitlement − usage |
totalSettlement | decimal(15,2) | (removing) — sum of ledger rows |
10. Insurance rates (4대보험)
Insurance rates (국민연금 / 건강보험 / 고용보험 / 장기요양보험 employee rate, national-pension cap/floor) are read by the InsuranceCalculator from the org-wide parameter store via the payroll_rate_config codeModule key, together with the sheet column metadata that binds the deduction column to the insurance calculator. The computed amount is written to DeductionRecord only when the sheet is committed ("✓ 커밋"), carrying the sheet lineage triple. See Payroll Schema Refactor.
11. TaxBracketConfig
Table: tax_bracket_configs — Progressive tax bracket definitions.
| Column | Type | Payroll Usage |
|---|---|---|
minIncome / maxIncome | decimal(15,2) | Bracket lookup |
rate | decimal(8,4) | → DeductionRecord amount calculation |
deduction | decimal(15,2) | → Progressive deduction |
effectiveYear | number | Year filter |
12–16. Reference Tables
SalaryGrade (salary_grades)
Grade-based salary definitions by job category. Indirect reference via EmploymentContract.salaryGradeId.
AllowanceItemCode (allowance_item_codes)
eCount allowance item code master data. Referenced via AllowanceRecord.itemCodeId.
DeductionItemCode (deduction_item_codes)
eCount deduction item code master data. Referenced via DeductionRecord.itemCodeId.
17. PayrollRecord (Header)
Table: payroll_records — Header record linking to AllowanceRecords/DeductionRecords. One per employee per month.
Relationships
| Relationship | Type | Description |
|---|---|---|
allowanceRecords | OneToMany → AllowanceRecord | All earnings for this payroll |
deductionRecords | OneToMany → DeductionRecord | All deductions for this payroll |
details | OneToMany → PayrollDetail | Itemized breakdown (backward compat) |
Cached Summary Fields
These fields are computed from linked AllowanceRecords/DeductionRecords during generation:
| Column | Type | Computed From |
|---|---|---|
baseSalary | decimal(15,2) | AllowanceRecord (itemCode 01) |
overtimePay | decimal(15,2) | AllowanceRecord (itemCode 10) |
nightDifferential | decimal(15,2) | AllowanceRecord (itemCode 12) |
weekendDifferential | decimal(15,2) | AllowanceRecord (itemCode 13) |
holidayDifferential | decimal(15,2) | AllowanceRecord (itemCode 09) |
allowances | jsonb | AllowanceRecords (meal, etc.) |
totalEarnings | decimal(15,2) | Σ ALL AllowanceRecords |
nationalPension | decimal(15,2) | DeductionRecord (itemCode 03) |
healthInsurance | decimal(15,2) | DeductionRecord (itemCode 04) |
employmentInsurance | decimal(15,2) | DeductionRecord (itemCode 05) |
longTermCareInsurance | decimal(15,2) | DeductionRecord (itemCode 20) |
incomeTax | decimal(15,2) | DeductionRecord (itemCode 01) |
localIncomeTax | decimal(15,2) | DeductionRecord (itemCode 02) |
totalDeductions | decimal(15,2) | Σ ALL DeductionRecords |
netPay | decimal(15,2) | totalEarnings - totalDeductions |
Status & Approval
| Column | Type | Usage |
|---|---|---|
status | enum | DRAFT→APPROVED→CONFIRMED→PAID |
paymentDate | date | Set to 25th during generation |
approvedBy / approvedAt | string / timestamp | Approval workflow |
confirmedBy / confirmedAt | string / timestamp | Confirmation workflow |
paidAt | timestamp | Payment processing |
18. PayrollDetail
Table: payroll_details — Itemized breakdown per PayrollRecord (backward compatibility).
| Column | Type | Description |
|---|---|---|
payrollRecordId | uuid (FK) | Parent record |
itemCode | string | Item code (BASE_SALARY, OVERTIME, etc.) |
itemName | string | Korean item name |
itemType | enum | EARNING / DEDUCTION / ADJUSTMENT |
category | enum | BASE_SALARY / OVERTIME / ALLOWANCE / TAX / INSURANCE etc. |
amount | decimal(15,2) | Amount |
19. eCount Export
The eCount export reads directly from AllowanceRecord and DeductionRecord tables, not from PayrollRecord fields. It groups records by (employeeId, itemCodeId) and uses the item code tables for code/name.
A-Codes (Allowances) — from allowance_item_codes
| Code | Name | Source |
|---|---|---|
| A01 | 월급여 | AllowanceRecord (itemCode 01) |
| A02 | 초과수당 | AllowanceRecord (itemCode 02) |
| A03 | 직책수당 | AllowanceRecord (itemCode 03) |
| A04 | 면허자격수당 | AllowanceRecord (itemCode 04) |
| A05 | 위험수당 | AllowanceRecord (itemCode 05) |
| A06 | 장기근속수당 | AllowanceRecord (itemCode 06) |
| A07 | 나이트수당1 - 간호사 | AllowanceRecord (itemCode 07) |
| A08 | 나이트수당2 - 요양보호사 | AllowanceRecord (itemCode 08) |
| A09 | 휴일근무수당 | AllowanceRecord (itemCode 09) |
| A10 | 연장근무수당1 | AllowanceRecord (itemCode 10) |
| A11 | 특별수당 | AllowanceRecord (itemCode 11) |
| A12 | 연장근무수당2 | AllowanceRecord (itemCode 12) |
| A13 | 야간근무수당1 | AllowanceRecord (itemCode 13) |
| A14 | 기타 | AllowanceRecord (itemCode 14) |
| A16 | 호봉수당 | AllowanceRecord (itemCode 16) |
| A17 | 연차수당 (퇴사자) | AllowanceRecord (itemCode 17) |
| A19 | 식대 | AllowanceRecord (itemCode 19) |
| A20 | 상여 | AllowanceRecord (itemCode 20) |
| A21 | 특별수당1 | AllowanceRecord (itemCode 21) |
| A23 | 간호간병평가인센티브 | AllowanceRecord (itemCode 23) |
| A24 | 내일채움공제 | AllowanceRecord (itemCode 24) |
D-Codes (Deductions) — from deduction_item_codes
| Code | Name | Source |
|---|---|---|
| D01 | 소득세 | DeductionRecord (itemCode 01) |
| D02 | 주민세 | DeductionRecord (itemCode 02) |
| D03 | 국민연금 | DeductionRecord (itemCode 03) |
| D04 | 건강보험 | DeductionRecord (itemCode 04) |
| D05 | 고용보험 | DeductionRecord (itemCode 05) |
| D06 | 퇴직정산 | DeductionRecord (itemCode 06) |
| D08 | 기타공제 | DeductionRecord (itemCode 08) |
| D10 | 연말정산 | DeductionRecord (itemCode 10) |
| D11 | 건강, 장기연말정산 | DeductionRecord (itemCode 11) |
| D12 | 건강, 장기퇴직정산 | DeductionRecord (itemCode 12) |
| D13 | 고용보험정산 | DeductionRecord (itemCode 13) |
| D20 | 장기요양보험 | DeductionRecord (itemCode 20) |
Calculation Formula
EARNINGS (AllowanceRecords)
───────────────────────────────────────
Auto-generated from Contract:
baseSalary (01) = Contract.baseSalary
meal (19) = Contract.allowances.meal
transportation (14)= Contract.allowances.transportation
housing (14) = Contract.allowances.housing
Auto-generated from OvertimeRecord:
overtimePay (10) = Σ OvertimeRecord(WEEKDAY).calculatedPay
nightPay (12) = Σ OvertimeRecord(NIGHT).calculatedPay
weekendPay (13) = Σ OvertimeRecord(SATURDAY).calculatedPay
holidayPay (09) = Σ OvertimeRecord(HOLIDAY).calculatedPay
Auto-generated from FixedIncentive:
incentive (23) = Σ FixedIncentive.totalAmount
Manual AllowanceRecords:
(various itemCodes, created via approval workflows)
totalEarnings = Σ ALL AllowanceRecords (auto + manual)
DEDUCTIONS (DeductionRecords)
───────────────────────────────────────
Auto-generated (insurance):
National Pension (03) = round(clamp(baseSalary, floor, cap) × rate)
Health Insurance (04) = round(baseSalary × rate)
Employment Ins. (05) = round(baseSalary × rate)
Long-term Care (20) = round(baseSalary × rate)
Auto-generated (tax):
Taxable Income = totalEarnings - insuranceTotal
Income Tax (01) = round((taxable × rate - deduction) / 12)
Local Income Tax (02) = round(incomeTax × 0.10)
Manual DeductionRecords:
(various itemCodes, created via approval workflows)
totalDeductions = Σ ALL DeductionRecords (auto + manual)
NET PAY
───────────────────────────────────────
netPay = totalEarnings - totalDeductions