Payroll Processing
Payroll is run per BU, per period. ONe-HR has two separate computation engines — one for Non-Executives and one for Executives — because the formulas diverge meaningfully: executives do not receive overtime or holiday-premium pay, but still have allowances, leave processing, and mandatory deductions.
Payroll officers, compensation specialists, MSD administrators.
Payroll screenshots have every money column blurred — basic pay, allowances, contributions, deductions, net pay. The structure of the form and the workflow is what we document, not the specific figures.
Data model
| Model | Purpose |
|---|---|
hr.payroll.master | One record per payroll batch (one per BU per period). |
hr.payroll.transactions | One record per employee per batch — the employee's payslip. |
hr.payroll.attendance | Time-card data attached to the batch. |
hr.employee.loan.deduction | Per-payroll loan deduction records linking back to the loan ledger. |
hr.payroll.supplemental | Manual additions or adjustments on a specific transaction. |
hr.employee.other.benefits.transaction | OB amounts paid in this payroll. |
Running payroll
HR ▸ Tasks ▸ Compute Payroll (Non-Executives) or Compute Payroll (Executives).
Each wizard creates a new hr.payroll.master record with:
| Field | Example |
|---|---|
| Name | NE-2026-APR (Non-Exec) / EX-2026-APR (Exec) |
| Executives | Boolean — drives the employee filter |
| Month of / Year of | Period components |
| From / To dates | Period boundaries |
Employee selection
Each wizard selects active employees for the logged-in BU matching the executive flag:
employees = env['hr.employee'].search([
('company_id', '=', company.id),
('active', '=', True),
('executive', '=', executives_flag),
('include_in_payroll', '=', True),
])
Employees with include_in_payroll=False are skipped — that's how an HR admin excludes a specific employee without archiving them.
Per-employee flow
For each selected employee, the wizard:
- Creates an
hr.payroll.transactionsrow. - Pulls attendance/time-card data for the period (
hr.payroll.attendance). - Walks the time card day-by-day, accumulating: basic pay, OT hours (various types), absences, late/undertime, leave days, LWOP days.
- Computes each pay component using the formulas below.
- Processes government contributions (SSS/PhilHealth/HDMF) with a balance guard — skip if the contribution would push net pay negative.
- Processes active loans — deduct
min(deductions_per_payroll, remaining_balance)per loan, with the same balance guard. - Sums everything into gross pay, total deductions, and net pay.
Basic pay components
Daily basic pay
- Source:
employee.daily_rate - Applied for every non-absent day, for both Non-Executives and daily-paid Executives.
Monthly basic pay
- Source:
employee.semi_monthly_rate(half of monthly — the semi-monthly payout) - Applied for
pay_type='monthly'employees.
Leave processing
For each day in the period, the wizard looks up approved leave transactions covering that day. For each match:
leave_qty = 0.5 if half_day else 1.0
if balance > 0:
balance -= leave_qty # paid leave — deduct from balance
leave_pay += leave_qty * daily_rate
else:
lwop_day += leave_qty # no balance — Leave Without Pay
lwop_ded += leave_qty * daily_rate
Key rules:
- Half-days count as 0.5.
- Balance-zero days become LWOP automatically — daily rate is deducted.
- Balance reduction happens during the payroll run; the
is_payroll_lockedflag on the leave transaction then prevents further edits.
Overtime (Non-Executives only)
Executives do not receive OT, RD-OT, holiday-premium, or night-differential pay. For Non-Executives, the multipliers are:
| Pay type | Formula | Applies when |
|---|---|---|
| Regular OT | hourly_rate × 1.25 × hours | Extra hours beyond the scheduled day |
| Rest Day OT | First 8h: hourly_rate × 1.3 × hours. Beyond 8h: hourly_rate × 1.69 × (hours - 8) | OT on a non-work day |
| National Holiday | hourly_rate × 2.0 × hours | Work on a regular holiday |
| Special Holiday | First 8h: hourly_rate × 1.3. Beyond 8h: hourly_rate × 1.69 | Work on a special non-working holiday |
| Night Differential | hourly_rate × 1.0 × hours (additional) | Hours within the 22:00–06:00 window |
| Night Diff OT | hourly_rate × 0.10 × hours (additional) | ND hours that are also OT |
All numeric multipliers are hardcoded in the wizard — changing them requires a code update.
Lunch break interaction
OT that overlaps the lunch break has 1 hour deducted from the OT hours (see Overtime & OB). The deducted-or-not decision is recorded on hr.filed.overtime.lunch_break_deducted at approval time, before payroll runs.
Allowances (both Exec and Non-Exec)
Flat amounts added per payroll — Meal, Transportation, Miscellaneous, plus COLA (daily_cola × working days in period). Active Other Benefits are also added, each recorded as one hr.employee.other.benefits.transaction row for trail.
Deductions
| Deduction | Formula |
|---|---|
| Late | late_hours × hourly_rate |
| Undertime | undertime_hours × hourly_rate |
| Absent | absent_days × daily_rate |
| LWOP | lwop_day × daily_rate |
Government contributions
Applied with a balance guard — the deduction is only taken if (net_pay - deduction) > 0, otherwise skipped (the employer's matching contribution is still recorded; the unpaid employee share becomes an HR-manual catchup).
SSS
Bracket-lookup from sss.table using employee.tm_monthly:
sss_ee = empshare / 2 # semi-monthly
sss_er = emprshare / 2
PhilHealth
From the employee's Compensation tab (ded_phic_ee, ded_phic_er) — no bracket lookup at payroll time. Already the semi-monthly amounts.
HDMF
From the employee's Compensation tab (ded_hdmf_ee, ded_hdmf_er), divided by 2 at payroll time for the semi-monthly split. Defaults to 100 each per semi-monthly period.
Loans
Active loans (active=True, remaining_balance > 0, date_start <= period_end) are deducted:
deducted_amount = min(loan.deductions_per_payroll, loan.remaining_balance)
One hr.employee.loan.deduction row is created per loan per payroll, and loan.remaining_balance is decremented.
Withholding Tax
Computed from the employee's tax_code + tm_{period} against the WHT table (see Compliance Tables — WHT).
Supplemental entries
A payroll transaction can have one or more hr.payroll.supplemental rows. Each row is a named positive or negative adjustment applied on top of the computed pay. Use cases:
- Retroactive rate adjustment.
- One-time bonus / incentive.
- Reimbursement for expenses not covered by OB.
- Correction for a prior-period error.
Supplementals are manually added after the compute wizard completes — they don't reset on recompute.
Payroll review
The payroll master form shows a stat button for the count of transactions. Each transaction can be opened individually, showing the full breakdown (basic pay, OT lines, allowances, deductions, contributions, net pay).
Re-running the wizard for the same period replaces the existing transactions (after confirmation) — useful when attendance or rates were corrected after the first run.
Posting
Once reviewed, payroll is posted — this flips is_payroll_locked=True on every leave transaction and loan deduction that contributed to the batch, preventing retroactive edits. After posting:
- Leave transactions in the period can no longer be edited (only viewed).
- Loan
remaining_balanceis locked in. - Supplementals can still be added but are flagged as post-posting adjustments.
This is the hard boundary between "working draft" and "committed payroll" — anything past this needs a supplemental in the next period, not an edit here.