Skip to Content
ThinkDevs
  • Home
  • Work
  • Approach
  • Services
  • About
  • Journal
  • Contact
  • +92 3459498001
  • Sign in
  • Talk to us
ThinkDevs
    • Home
    • Work
    • Approach
    • Services
    • About
    • Journal
    • Contact
  • +92 3459498001
  • Sign in
  • Talk to us
Home / Work / MAFA & sister companies

Four people, one schedule, four different ways to be paid

A workforce where factory, field and remote staff share a calendar but not a contract — and an attendance engine that can tell them apart.

~24,000 LOC · 147 tests
Odoo 17 Python OWL

The problem a client brought us

“Four of our people work the same hours on the same calendar. Two get paid for overtime and two don't, because that's what their contracts say. The system can't tell them apart.”

Before and after

Derived from a schedule that cannot express them

  • Overtime eligibility
  • Site allowances
  • Break deduction
  • Day counting

Each read from what the person is entitled to

One attendance engine

What we found and built

A resource calendar models when someone is expected to work. It does not model whether they are entitled to be paid for working beyond it. Those are independent facts, and Odoo carries only the first.

So every rule that depends on the second — overtime eligibility, site allowances, how a day is counted — was being derived from a schedule that cannot express it. The same gap showed up everywhere attendance met money: device punches at the factory gate, phone check-ins from client sites, travel days — all sharing one calendar, all needing different treatment.

We gave the missing fact a home of its own, and made every rule that pays for an hour read it from there.

Result

Payroll and attendance agree for every employee, however differently their contracts treat the same hour worked. 147 tests pin the rules that money depends on.

Under the hood

  • Worked hours subtract the schedule's break lines; an attendance type can opt out and be credited the full span.
  • Rejected attendance never counts toward hours — even if its type is later switched to auto-approved.
  • The allowed attendance types are enforced on the server, not only hidden on the screen.
  • Every day-level rule converts to the schedule's timezone before taking the date, so a check-in just after midnight lands on the right day.
  • Hours are rounded before they are compared, so an exact eight-hour day can never read as short through floating-point noise.

Built with

Odoo 17 Python OWL
~24,000 Lines of code
147 Automated tests
4 + 2 Attendance types, plus two fixed sources
7 Exception states surfaced before payroll

What we built

  1. Overtime entitlement

    Overtime entitlement is its own fact

    A per-employee switch, gated by a schedule-level policy: all staff, selected departments, or named employees. Four people on one calendar, four different outcomes.

  2. Attendance types

    Each way of working carries its own rules

    Four configurable types plus device and manual sources. Each type decides whether it needs approval, whether breaks are deducted, and whether it carries an allowance.

  3. Overtime thresholds

    Small overruns never become payroll noise

    A 10-minute grace after scheduled end, then a 60-minute minimum stay before overtime is paid. Shift ending 18:00: leave at 18:55 and nothing is paid; leave at 19:30 and 1 h 20 is paid, counted from 18:10.

  4. Day classifier

    Payroll counts a day the way attendance does

    Worked hours as a share of expected become a full day, half day or absent, at per-schedule thresholds. Payroll reads the same classifier, so the two can't drift.

  5. Site allowances

    Allowances follow where the work happened

    Sites carry a centre and a radius; an allowance is claimable only when the employee was outside every assigned site. The allowance belongs to the attendance type, not the site.

  6. Exception dashboard

    Problems surface before payroll closes

    Absent, late, missing check-out, outside the hour band, under expected hours, pending approval — one detector feeds both the dashboard and the report.

  7. Overtime ledger

    Earned overtime has an exit of its own

    A ledger kept separate from payroll, so earned hours can be taken as leave or paid in cash without either path corrupting the other.

The pattern behind it

One definition everyone calls

When several parts of a system each decide the same fact, they eventually disagree. We don't add a fourth opinion — we collapse the disagreement into one definition and make everything else read from it.

How we think about problems

Further reading

Field note

We read our own attendance engine twice, and the second pass corrected six things

Nine things a business believed about its overtime, allowance and payroll rules — checked line by line against the code, then checked again.

Read the note
MAFA · Odoo 17

Overtime that can be paid, not only taken as leave

Earned overtime had one exit — a leave allocation. Now an administrator pays the days they choose, at a rate, and the rest stay pending.

~8,000 LOC Read the case study
MAFA · Odoo 17

Biometric attendance data the payroll engine can trust

Three punches from one person became one event — and HR notifications stopped firing for months-old data.

Read the case study

Next case study

MAFA · Odoo 17

A multi-day VAT exercise became a button

Every posted invoice, bill and reversal in the period, claimed automatically — and locked to that one batch.

Read the case study

Or go back to the full list and start from the symptom closest to yours.

All case studies

Recognise any of this?

Most of our work starts with a message that sounds a lot like this one did.

Tell us what's broken See the problems we've solved

ThinkDevs

A small, senior engineering team that builds and rescues production Odoo ERP systems.

Remote team · Pakistan / Saudi Arabia

Work

  • Problems we've solved
  • MAFA & sister companies
  • Aviation & Charter ERP
  • Journal

Company

  • Approach
  • Services
  • About
  • Careers
  • Contact

Talk to us

  • devs@thinkdevs.com
Tell us what's broken
© 2026 ThinkDevs. All rights reserved.
Privacy Cookies Terms