HR Attendance Pro
An immutable event log is the source of record; the day sheet and standard hr.attendance are derived projections you can rebuild at any time.
Why this module
HR Attendance Pro
The log is the truth, the day is derived
eh.hr.attendance.event is append-only: rows are never edited or deleted by user action, guarded by a write override that rejects all but a few mutable fields. The day sheet and standard hr.attendance are projections the compute service can rebuild from events at any time. Amendments are expressed as new events through a correction, never as silent edits.
Every source through one idempotent pipeline
Manual, kiosk, mobile, biometric webhook, and bulk import all funnel through a single ingest service. A UNIQUE constraint on correlation id means a retried kiosk batch, a replayed token, or a re-imported CSV row dedupes to a no-op. The same correlation id always resolves to the same event.
Payroll and reports see ordinary attendance
Paired check-in and check-out events project into standard hr.attendance rows, tagged as managed so directly-entered rows are never touched. Payroll, dashboards, and any module that reads hr.attendance work unchanged, while integrity and provenance live in the event chain underneath.
Day in the life
A late biometric scan, reconciled overnight
An employee taps a biometric reader on a flaky link; the vendor buffers and posts the scan an hour late over the signed webhook. Ingest verifies the HMAC signature, dedupes on correlation id, tags the employee timezone, and matches a geofence. The day for that civil date recomputes; because the shift is overnight, the tail event is attributed correctly. That night the reconcile cron recomputes the trailing three days for every active employee in batches, absorbing the late arrival, and the paired session re-projects into standard hr.attendance for payroll to read.
Edge cases
The cases most modules quietly ignore.
In the shipped code today, each one a place where a cheaper module silently does the wrong thing.
Ingest is keyed on a UNIQUE correlation id. A replayed kiosk batch, a refreshed token used twice within its window, or a re-uploaded import file all dedupe to the same event and return deduped=true rather than double-counting.
eh.hr.attendance.event.write rejects every field except geofence, photo, and archived unless you are a platform admin or in sudo, and unlink raises unless the retention purge context is set. Attendance data is amended through the correction workflow, not in place.
Applying an approved correction is idempotent: produced events carry correlation ids that embed the correction id and row index, so re-firing the apply transition creates nothing new. The UNIQUE constraint silently rejects the duplicates.
Civil-date attribution pulls a wide event window then filters per employee timezone, so a check-out after midnight on an overnight shift is counted against the day it started, not the calendar day it ended.
A check-in with no matching check-out still pairs as an open session and projects, while the anomaly service flags missing_out. Overlapping or doubled check-ins raise overlap and double_check_in codes rather than corrupting worked minutes.
Record rules isolate events, days, corrections, and roster assignments by employee, manager line, and company. Officers see only company_ids they belong to; there is no global read rule. Kiosk device resolution requires the device and employee to share a company.
Kiosk tokens are short-lived HS256 JWTs. Employee directory listing is gated behind a per-device flag (off by default) and page size is capped server-side, so a leaked attendance token cannot bulk-pull names and photos. Public endpoints are bounded by a DB-backed atomic rate-limit counter.
Corrections route through a serial approval chain (line manager, then HR officer for long ones) with escalation hours per step. The approval engine forbids a subject, or a delegated submitter, from approving their own request.
The retention cron archives events past the configured window in capped batches, then exports purge-eligible rows to a CSV attachment before unlinking, so audit history survives the purge. The reconcile cron commits per 200-employee batch so a worker restart does not lose progress.
What is inside
Built to do the job, end to end.
- Immutable event log. eh.hr.attendance.event: append-only, indexed on (company, employee, occurred_at) and a partial in/out index, with a UNIQUE correlation id for dedupe. Holds source, device, lat/lon, geofence, photo, raw vendor payload, and a correction backlink.
- Derived day sheet. eh.hr.attendance.day: one row per employee per civil date with worked, break, late, early-leave, and overtime minutes, a present/partial/absent/leave/off status, and a JSON anomaly code list. Rebuildable from events; non-negative minute counters enforced by constraint.
- Compute and projection services. Pairs check-in/out intervals, applies shift rounding and grace-to-schedule snapping, resolves late/early/overtime against merged shift and policy thresholds, then upserts the day and projects managed sessions into standard hr.attendance, retiring stale rows.
- Ingest, anomaly, geofence services. One ingest entry point with schema validation, employee resolution, per-device throttle, timezone tagging, and haversine geofence matching. The anomaly service detects missing in/out, overlap, geo mismatch, and over-max-daily, dispatching medium and high severities to the line manager through the notification engine.
- Kiosk, mobile, and webhook APIs. JSON endpoints under /api/hr/v1: kiosk pair/token/events/employees with HMAC-JWT auth and RFC 7807 errors, a user-authenticated mobile board with required idempotency keys, and a signature-verified biometric webhook. Throttling is enforced server-side.
- Shifts, rosters, corrections. Shifts carry start/end, overnight flag, grace and overtime minutes, and rounding mode. Per-employee dated roster assignments resolve most-specific-wins. Corrections inherit the workflow and approval mixins; states, transitions, and the escalation chain are declared as data, not Python.
Honest about the edges
What this does not do, so nothing surprises you.
- Built and tested for Odoo 16 Community. Other major versions are not the target of this listing.
- Depends on the EH HR Platform engines (core, compat, workflow, approval, policy, notification) and on hr and hr_attendance; it is not a standalone drop-in.
- Requires the pytz Python package.
- Kiosk and mobile front-end OWL components and the offline buffer service ship as assets; this is an attendance engine with surfaces, not a turnkey hardware terminal product.
- Geofence matching is a haversine point-in-radius scan over active fences; very large fence counts per company are handled by linear scan, not a spatial index.
- The off_shift anomaly code is defined but its detector is intentionally conservative and currently returns no flags.
- Kiosk device secret encryption at rest relies on the operator wiring database-level encryption (for example pgcrypto); secrets are stored as config parameters referenced per device.
- Overtime is computed per day against a full-day minute threshold, not as a weekly or pay-period accumulator.
Odoo 16 attendance, Odoo 16 Community attendance, kiosk attendance Odoo, biometric attendance Odoo, geofence check in Odoo, attendance correction workflow, shift rounding grace period, overtime late calculation Odoo, event sourced attendance, multi company attendance Odoo, attendance audit trail, hr attendance projection, idempotent attendance import, overnight shift attendance
Need this fitted to the way you work?
ERP Heritage delivers end to end Odoo work: Odoo Implementation, Customization and Development, Integration, Migration, Consultation, Support and Training. We help teams put this module into production, shape it to their process, and keep it running.
We work with businesses across Australia (Melbourne, Sydney, Brisbane, Perth, Adelaide, Canberra) and the Middle East (Dubai, Abu Dhabi, Riyadh, Jeddah, Doha, Kuwait City, Muscat). Start a conversation at erpheritage.com.au or email info@erpheritage.com.au.
Languages
Available in 19 languages
The interface ships translated out of the box. Switch language in Odoo and the fields, menus, and messages follow.
Please log in to comment on this module