| Availability |
Odoo Online
Odoo.sh
On Premise
|
| Odoo Apps Dependencies |
•
Discuss (mail)
• Employees (hr) |
| Community Apps Dependencies | Show |
| Lines of code | 875 |
| Technical Name |
eh_hr_core |
| License | LGPL-3 |
| Website | https://erpheritage.com.au |
| Versions | 16.0 17.0 18.0 19.0 |
HR Platform Core
The hash-chained audit log, strict multi-company mixins, and service layer every EH HR module builds on.
Why this module
HR Platform Core
An audit trail you can prove
Every audited create, write and unlink lands in an append-only log where each row stores the sha256 of the previous row plus its own fields. verify_chain() walks the whole chain and returns the first row that does not hash, so any silent edit is detectable. No group has write or delete on the log; appends happen only through a sudo write path. Independent of mail.thread.
Multi-company that refuses to leak
The company-aware mixin makes company_id required, defaults it to the active company, and rejects any write that moves a record into a company the user is not a member of, even under sudo, unless an explicit audited override context key is set. Every cross-company elevation writes its own audit row recording all affected record ids.
A foundation, not a black box
Typed per-company settings that version on write instead of destroying the old value, feature flags with company, group, date-window and deterministic percentage rollout, and a plain-Python service registry that keeps business logic off the ORM. LGPL-3 source on disk, no activation key, no phone-home. Read it, extend it, build on it.
Day in the life
An auditor asks who changed a sensitive record, and when.
An officer reopens a question from three months ago: a record was altered and nobody remembers by whom. Because the model mixes in the audited mixin, the change was captured automatically at write time, with a before and after snapshot of exactly the whitelisted fields that changed and the acting user. The auditor, who holds a read-only group and sees only their own company's rows through the global record rule, opens the audit log and reads the entry. To be sure nothing was quietly rewritten underneath, an administrator runs verify_chain over the table. It walks every row in keyset-paginated batches, recomputing each sha256 from the prior row's hash, and returns no broken id. The trail holds. No mail.thread, no guesswork, one query.
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.
A hash chain is inherently serial. Before each append the log takes a transaction-scoped Postgres advisory lock, so two concurrent transactions cannot read the same tail and fork the chain. The lock releases automatically on commit or rollback.
The company-aware mixin rejects cross-company writes even when called through sudo. The only way through is an explicit allow_cross_company context key, and taking it writes an audit row listing every affected record id, so the elevation is never silent.
The rate limiter increments with a single INSERT ON CONFLICT DO UPDATE RETURNING, so racing gunicorn or gevent workers can never both read a stale count. The counter is shared across workers and survives a restart, unlike an in-memory dict.
Audit payloads are built from raw field reads, so recordsets and date objects arrive un-serializable. The log coerces them identically for both the stored jsonb column and the hash material, so the chain stays symmetric and never raises on a relational field.
A missing actor is normalised to 0 on both the append and verify paths. An earlier version hashed it as 0 on write but as the string False on verify, which broke the chain for every system-initiated row. Fixed and covered.
The timezone service computes the civil date an event belongs to in the employee's own locale and returns day windows that correctly span 23 or 25 hour days across daylight-saving jumps, falling back to company timezone then UTC.
Writing a settings key never destroys the prior value. The old entry is archived and pointed at its successor, and a unique constraint guarantees exactly one active entry per company and key.
The rate-limit table is vacuumed by a dedicated daily cron that deletes only windows closed beyond the cutoff, keeping a high-churn counter table small without touching live counters.
What is inside
Built to do the job, end to end.
- Hash-chained audit log. The eh.hr.audit.log model: append-only, sha256-chained rows with prev_hash and row_hash, advisory-locked appends, per-company read isolation via a global ir.rule, and a keyset-paginated verify_chain() that stays memory-bounded no matter how large the log grows.
- Audit and company mixins. An audited mixin that auto-captures create, write and unlink with before and after snapshots over a configurable field whitelist and skips no-op writes, plus a company-aware mixin that makes company_id required and refuses unsanctioned cross-company writes.
- Feature flags and rate limiting. Runtime feature flags scoped by company, group, date window and deterministic percentage rollout hashed on user id, and a fixed-window rate limiter built on an atomic upsert for throttling public kiosk, mobile and visitor endpoints.
- Settings, services and OWL kit. A typed per-company settings registry that versions on write, a plain-Python service registry with timezone and audit services, correlation-id tracing on the platform mixin, and a small OWL backend kit (card, stat and skeleton-loading primitives plus a toast service).
Honest about the edges
What this does not do, so nothing surprises you.
- This is a framework and platform module, not a feature surface. It ships mixins, an audit log, settings, flags, a rate limiter and a service layer; attendance, leave, approvals, workflow, policy and notification logic live in their own EH HR modules.
- Targets Odoo 16 Community. It depends on base, mail, hr and the eh_hr_compat helper, and is intended to be installed alongside the rest of the EH HR suite rather than on its own.
- The audit log is independent of mail.thread and records only what audited models emit. A model gains auditing by mixing in the audited mixin; plain Odoo models are not captured automatically.
- Retention-days and PII-redact-in-exports are exposed as configuration flags consumed by other modules. This core module does not itself run an automated archival, purge or redaction job.
- The platform event bus uses the in-process bus.bus. Clustered or cross-node delivery would require swapping in a queue or message-broker adapter.
Odoo 16 HR platform, hash chained audit log, tamper evident audit trail Odoo, append only audit log, multi company HR Odoo, cross company write protection, feature flag percentage rollout Odoo, rate limiting Odoo endpoints, typed settings registry, OWL component kit, HR framework module, correlation id tracing, ir.rule company isolation, verify chain integrity
Please log in to comment on this module