Payroll Reports — Payroll Register & Payroll Tax Liability¶
Date: 2026-07-01
Problem / motivation¶
Peachtree's payroll section prints per-paycheck and per-tax reports: Payroll Register ("employee
paycheck information for the selected date range showing hours, deductions, and paycheck amount") and the
Payroll Tax / Tax Liability reports ("adjusted/taxable gross, tax withheld, and liability amounts").
We store calculated payroll in payroll_items (gross/net/bonuses/hours plus deductions and
employer_contributions JSONB maps keyed by concept). These two reports surface that data by pay date.
Closes Payroll rows on the Peachtree Report Parity tracker.
See Payroll for the underlying module.
In scope¶
payroll_register(date_filter, generate_csv)— one row per employee paycheck in the date range (bypay_date): hours, gross, bonuses, total deductions, net, and employer cost.payroll_tax_liability(date_filter)— totals for the range plus a per-concept breakdown of employee deductions (CSS/SE/ISR/…) and employer contributions, summed from the JSONB maps. Covers the Payroll Tax Report and Tax Liability Report intent for Panama (CSS, Seguro Educativo, ISR retention).Path.REPORTS. Register is CSV-exportable.
Out of scope¶
- Payroll Journal — payroll postings reach the ledger via
payroll_runs.ledger_entry_idbut carry no dedicatedSourceType; deferred until one exists (would then reuse the journal renderer). - Per-check register (printed/unprinted) and vacation/sick accrual — separate payroll data not modeled.
- Quarterly/Yearly earnings variants — the register already accepts any date range (a quarter/year is just a wider filter), so they are the same report with different bounds.
Data model changes¶
None. Reads payroll_items (gross, net, bonuses, worked_hours, overtime_hours, deductions,
employer_contributions) → payroll_runs → payroll_periods (pay_date) and employees. No migration,
no new Path/Resource.
What's being implemented¶
- Responses —
app/graphql/reports/strawberry/payroll/payroll_report_response.py(PayrollRegisterLineResponse,PayrollTaxLiabilityResponse+ concept line type). - Repository —
app/graphql/reports/repositories/payroll/payroll_report_repository.py(payroll_items). - Service —
app/graphql/reports/services/payroll/payroll_report_service.py(sums the JSONB maps). - Queries —
app/graphql/reports/queries/payroll/payroll_report_queries.py. - Tests —
tests/graphql/reports/test_payroll_reports.py.
GraphQL surface¶
payrollRegister(dateFilter: DateRangeFilter!, generateCsv: Boolean! = false): GenericReportPage!
payrollTaxLiability(dateFilter: DateRangeFilter!): PayrollTaxLiabilityReport!
type PayrollRegisterLine { employeeNumber: String!, firstName: String!, lastName: String!, payDate: Date!, workedHours: Decimal, overtimeHours: Decimal, gross: Decimal!, bonuses: Decimal!, totalDeductions: Decimal!, net: Decimal!, employerCost: Decimal! }
type PayrollTaxLiabilityReport { startDate: Date!, endDate: Date!, totalGross: Decimal!, totalDeductions: Decimal!, totalEmployerContributions: Decimal!, deductionLines: [PayrollTaxConceptLine!]!, employerContributionLines: [PayrollTaxConceptLine!]! }
type PayrollTaxConceptLine { concept: String!, amount: Decimal! }
Frontend contract¶
- Payroll Register: date range over
payDate; one row per paycheck. Sumnetfor the net payroll,totalDeductionsfor withholdings,employerCostfor employer burden.generateCsv: true→csvUrl. - Payroll Tax Liability:
deductionLines/employerContributionLinesare per-concept totals (the JSONB keys) — render as the DGI/CSS liability breakdown;totalGrossis the taxable base.
Future additions¶
- Payroll Journal via the journal renderer once payroll postings carry a
SourceType. - Vacation & sick-time and per-check registers when that data is modeled.