← Back to work
2024 · Odoo 19 platform · live in production

Rebound Technologies

An Odoo 19 platform that runs the company end to end — sales, CRM, HR, projects, treatment, cashflow, GAAP audit. Forty-odd custom modules, a nightly Playwright regression on AWS, and the first project where the three of us hold distinct seats: Dan on architecture and DevOps, Dina on Odoo modules, Hasina on test automation.

Role
Architecture · Odoo modules · test automation · DevOps
Duration
Since 2024, ongoing
Team
3 engineers — Dan, Dina, Hasina
PROJECT HERO · PLACEHOLDER
FIG. 01
Context

Rebound Technologies is a US-based operations and treatment business. Their Odoo 19 platform is the spine of how the company runs day to day — sales, CRM and contacts, HR and timesheets, projects, the treatment domain, cashflow and GAAP audit, document storage, Slack and Microsoft Calendar and Mapbox integrations — all in one tree, with around forty-five custom modules and a dozen carefully chosen OCA dependencies.

This is the first project the three of us hold simultaneously, with distinct seats. Dan holds the CTO chair: architecture, code review, and the AWS / Terraform infrastructure. Dina ships Odoo modules — features, fields, views, security rules, the day-to-day shape of the product. Hasina leads test automation: a nightly Playwright regression battery and the CodeBuild pipeline that runs it. Every PR passes through at least two of us.

The shape we chose was deliberate. A `flag.mixin` so any model can be flagged with a reason — and `FlaggedRenderer` highlights those rows wherever they appear. An `audit.model` mixin for fields HR can't see. A `knock.model` mixin so any state transition fires a Knock / Slack notification without each model rewriting the wiring. Migration scripts versioned per module in `migrations/19.0.X.Y.Z/`. A test-tag for every module.

We've been on it since 2024. Live on Odoo 19 since the migration — a regular cadence of business-as-usual: bugs, features, improvements, every Jira ticket on its own database, every PR backed by a green pipeline. The Playwright regression that Hasina built fires every night at 2 AM UTC against staging and reports back to S3 + CloudFront.

Scope

What we built.

rebound_flag · rebound_audits · rebound_knock01

Three architecture mixins that the 45 business modules inherit. Flag any model with a reason; add audit fields HR can't see; fire Knock / Slack workflows on state changes — all for free, once inherited.

rebound_base · rebound_theme · rebound_widgets02

UI scaffolding and design tokens shared across the platform.

rebound_account · rebound_cashflow · rebound_gaap_audit03

Accounting layer with cashflow projection and GAAP-aligned audit support.

rebound_crm · rebound_contacts · rebound_partner_duplicates04

CRM with partner deduplication.

rebound_hr · rebound_timesheets · rebound_time_off · rebound_holidays · rebound_purchase_hr05

HR domain: employees, timesheets, leave, holidays, HR-side purchase flows.

rebound_purchase · rebound_expense · rebound_divvy · rebound_ramp06

Procurement and corporate-card spend with Divvy and Ramp integrations.

rebound_treatment · rebound_rec_center · rebound_friction07

Rebound's own operational and treatment domain — the part nobody but they have.

rebound_project · rebound_activity · rebound_my_stuff08

Project and personal-workflow surfaces.

rebound_documents · rebound_directory · rebound_portal · rebound_email_marketing · rebound_social09

Content, portal, outbound messaging.

rebound_jira_integration · rebound_microsoft_calendar · rebound_mapbox · rebound_slack_unfurl · mapsly_connector10

External integrations.

test_automation11

Playwright + Pytest battery against staging Odoo. Hasina-led; ~690 commits in its own repo.

rebound_infra12

Terraform-managed AWS: EventBridge → Lambda → CodeBuild (240-min) → S3 + CloudFront for the nightly regression at 2 AM UTC. Plus SES for two domains.

Approach

What the work looked like, in four pieces.

01

Three seats, one workshop

This is the first project the three of us hold at the same time. Dan owns architecture, code review, and the AWS / Terraform side. Dina owns the Odoo modules — features, fields, views, security. Hasina owns the test pipeline — Playwright suites, the CodeBuild that runs them nightly. Every PR passes through at least two of us.

02

Architectural mixins, not copy-paste

Three abstract Odoo models — `flag.mixin`, `audit.model`, `knock.model` — that the business modules inherit. Flagging is one line on any model; audit fields come for free; state transitions automatically fire Knock workflows. The cost is paid once; every new module collects the dividends.

03

A test pipeline that watches the platform sleep

A Playwright + Pytest regression suite that runs against staging every night at 2 AM UTC. Lambda triggers CodeBuild, CodeBuild clones the test repo, Playwright drives the live Odoo screens, results go to S3 and a CloudFront-cached report URL — and Slack is told. We see failures before a user reports them.

04

Odoo 19 readiness, in writing

We migrated the platform to Odoo 19 and wrote down what we learned, in the module's CLAUDE.md, so the same gotchas wouldn't be relived by the next person. `read_group` → `_read_group`, OWL list-button templates, `is_manually_modified` on `account.move`, `fieldDependencies` defaulting to `readonly: true`, settings-page `t-foreach` keyed by `app.name` — twenty-odd migration footguns, all documented next to the code that survives them.

Engineering highlights

A handful of the solves we are proudest of.

01

`hr.employee.public` SQL-view mirroring

Odoo 19 redirects non-HR users to `hr.employee.public`, a SQL view. Any new field on `hr.employee` that isn't mirrored as a `store=False related` field on the public model throws `AccessError` for non-HR users — including for many2many fields where you cannot `store=True` because no relation table exists for the view. We documented the rule once; it now governs every HR field added.

02

knockapi vs Odoo payment exceptions

`rebound_knock` pins `knockapi==0.5.8`, which pulls `simplejson` as a transitive dep. `simplejson` monkey-patches `requests.models.complexjson`, which breaks Odoo's payment-provider exception handling. `rebound_knock/__init__.py` reverses the patch at import time — `requests.models.complexjson = json` — and the payment flow stays correct.

03

`is_manually_modified` and OCR-filled bills

Any `write()` on `account.move` without `skip_is_manually_modified=True` in context sets the manually-modified flag, which taints OCR-filled bills (they stop being treated as importable). Post-create field assignments are wrapped in `with_context(skip_is_manually_modified=True)` so the OCR signal isn't accidentally lost.

04

Domain widget + always-invisible don't mix

Odoo 19's `domain` widget calls `record.fieldNames.includes(resModel)` to find its target. Always-invisible fields are skipped when building `fieldNames`, so the model-name field referenced in `options.model` must be visible — at minimum `readonly="1"`. The Odoo 19 migration page in CLAUDE.md catalogs the surprise so it doesn't bite the next person.

05

Nightly Playwright regression, end to end

EventBridge cron at 2 AM UTC → Lambda (`rebound-regression/src/main.py`, Python/boto3) → CodeBuild (Node 18 + Python 3.12, 240-minute timeout) → clone `rebound-tech/test_automation` → Playwright drives the staging Odoo screens → results upload to S3, served behind CloudFront. Slack and Knock are notified on completion. Failures do not wait for a user to report them.

06

Migration-per-version, scripted

Every module follows `19.0.X.Y.Z` versioning. When a migration is needed, we bump the version and drop `migrations/19.0.X.Y.Z/pre-migrate.py` or `post-migrate.py` with a `migrate(cr, version)` function. The pipeline picks it up; the data moves with the code; nothing manual on production.

Outcomes

A few shapes, in their raw form.

44+
Custom Odoo modules
Odoo 19
Live in production
Nightly
Playwright regression at 2 AM UTC
All three
First project, distinct seats

Stack
Odoo 19PythonPostgreSQLPlaywrightPytestTerraformAWSKnockSlack

Have a project that deserves this kind of care?

Start a conversation