# ugaroll

A standalone Uganda payroll engine for Python. Handles PAYE, NSSF, Local Service Tax (LST), payslips, and paysheets — all with zero dependencies.

## Features

- `basic`, `gross`, or `net` driven payroll requests
- Built-in preset categories: overtime, bonus, transport, SACCO, housing benefit, gratuity, and more
- Simple `from_hrm()` bridge for Django HRM-style fields (`taxable`, `nssfable`, `nature`)
- Percentage-based line items (% of basic or % of gross)
- Batch payroll processing with aggregate totals
- Resident, non-resident, secondary-employment, and secondary-above-10M tax modes
- Configurable PAYE, NSSF, and LST policies
- HTML payslip and paysheet rendering out of the box

## Installation

```bash
pip install ugaroll
```

## Quick Example — Single Payslip

```python
from uganda_payroll import (
    EmployerProfile,
    EmployeeProfile,
    EmploymentTaxMode,
    InputMode,
    PayrollCalculator,
    PayrollLineItem,
    PayrollPeriod,
    PayrollRequest,
    PaymentCategory,
    render_payslip_html,
)

calculator = PayrollCalculator()

# Use preset categories — no need to configure effects manually
overtime = PaymentCategory.overtime()
sacco = PaymentCategory.sacco_deduction()

request = PayrollRequest(
    employer=EmployerProfile(name="Nkozi Farms Ltd", tin="1005827461"),
    employee=EmployeeProfile(
        full_name="Grace Atuhaire",
        staff_number="7023",
        tin="1028463915",
        nssf_number="8842100557031",
        department="Logistics",
        position="Fleet Coordinator",
        employment_tax_mode=EmploymentTaxMode.PRIMARY_RESIDENT,
    ),
    period=PayrollPeriod.from_month(year=2026, month=3),
    input_mode=InputMode.BASIC_PAY,
    target_amount=2_450_000,
    items=[
        PayrollLineItem(category=overtime, amount=185_000),
        PayrollLineItem(category=sacco, amount=50_000),
    ],
)

result = calculator.calculate(request)
payslip_html = render_payslip_html(result)

print(f"Gross:  {result.gross_earnings:,}")
print(f"PAYE:   {result.total_paye:,}")
print(f"NSSF:   {result.nssf_employee:,}")
print(f"Net:    {result.net_pay:,}")

# Save payslip to file
with open("payslip_grace.html", "w") as f:
    f.write(payslip_html)
```

## Paysheet — Multiple Employees

```python
from uganda_payroll import (
    AmountMode,
    EmployerProfile,
    EmployeeProfile,
    InputMode,
    PaymentCategory,
    PayrollBatchRequest,
    PayrollCalculator,
    PayrollLineItem,
    PayrollPeriod,
    PayrollRequest,
    render_paysheet_html,
)

calculator = PayrollCalculator()
employer = EmployerProfile(name="Nkozi Farms Ltd", tin="1005827461")
period = PayrollPeriod.from_month(2026, 3)

bonus = PaymentCategory.bonus()
transport = PaymentCategory.transport()
overtime = PaymentCategory.overtime()

requests = [
    PayrollRequest(
        employer=employer,
        employee=EmployeeProfile(
            full_name="Grace Atuhaire", staff_number="7023",
            tin="1028463915", department="Logistics",
        ),
        period=period,
        input_mode=InputMode.BASIC_PAY,
        target_amount=2_450_000,
        items=[PayrollLineItem(category=overtime, amount=185_000)],
    ),
    PayrollRequest(
        employer=employer,
        employee=EmployeeProfile(
            full_name="Kenneth Obol", staff_number="7041",
            tin="1033918204", department="Engineering",
        ),
        period=period,
        input_mode=InputMode.BASIC_PAY,
        target_amount=4_100_000,
        items=[
            PayrollLineItem(category=bonus, amount=300_000),
            PayrollLineItem(category=transport, amount=120_000),
        ],
    ),
    PayrollRequest(
        employer=employer,
        employee=EmployeeProfile(
            full_name="Esther Nambooze", staff_number="7055",
            tin="1041672583", department="Finance",
        ),
        period=period,
        input_mode=InputMode.BASIC_PAY,
        target_amount=1_800_000,
        items=[
            # 5% of basic as overtime
            PayrollLineItem(
                category=overtime, amount=5,
                amount_mode=AmountMode.PERCENTAGE_OF_BASIC,
            ),
        ],
    ),
]

# Calculate individually and render a paysheet
results = [calculator.calculate(r) for r in requests]
paysheet_html = render_paysheet_html(results, title="March 2026 Paysheet — Nkozi Farms Ltd")

with open("paysheet_march.html", "w") as f:
    f.write(paysheet_html)

# Or use batch processing for aggregate totals
batch = PayrollBatchRequest(
    employer=employer,
    period=period,
    employee_requests=tuple(requests),
)
batch_result = calculator.calculate_batch(batch)

print(f"Total Net Pay:       {batch_result.total_net_pay:,}")
print(f"Total PAYE:          {batch_result.total_paye:,}")
print(f"Total NSSF (EE):     {batch_result.total_nssf_employee:,}")
print(f"Total NSSF (ER):     {batch_result.total_nssf_employer:,}")
print(f"Total Employer Cost: {batch_result.total_employer_cost:,}")
```

## Preset Categories

Create common Ugandan payroll categories in one line:

```python
from uganda_payroll import PaymentCategory

PaymentCategory.overtime()            # Taxable + NSSF
PaymentCategory.bonus()               # Taxable + NSSF
PaymentCategory.transport()           # Non-taxable, no NSSF
PaymentCategory.housing_benefit()     # Taxable memo (non-cash benefit)
PaymentCategory.sacco_deduction()     # Post-tax deduction
PaymentCategory.salary_advance()      # Post-tax deduction
PaymentCategory.payroll_correction()  # Reduces PAYE base, not NSSF
PaymentCategory.medical_insurance()   # Employer contribution
PaymentCategory.gratuity()            # Post-tax addition
PaymentCategory.pension_deduction()   # Pre-tax, reduces PAYE base
PaymentCategory.notice_pay()          # Taxable + NSSF
PaymentCategory.leave_compensation()  # Taxable + NSSF
```

All presets accept optional `code` and `name` overrides:

```python
PaymentCategory.overtime(code="ot_weekend", name="Weekend Overtime")
```

## HRM Bridge

If you use a Django HRM model with `taxable` / `nssfable` / `nature` fields:

```python
cat = PaymentCategory.from_hrm(
    code="meal",
    name="Meal Allowance",
    nature="Increment",
    taxable=False,
    nssfable=False,
)
```

## Notes on Defaults

- Resident monthly PAYE bands match current URA schedules
- Secondary employment: 30% flat, or 40% if all employments exceed UGX 10M
- Non-resident rates are configurable; the default uses 10% for the first bracket
- LST is configurable — the default mirrors common "Chop Once" / "Chop Four times" patterns
- NSSF: 5% employee, 10% employer on total employment income (basic + all qualifying allowances)
