Correct three claims that a live 19.0+e build disproved

Built a full demo against a real BuildEase instance and three documented
facts turned out to be wrong, each costing a debugging round.

buildease installs nothing. Its manifest depends on base alone, so the
"depends on nothing and installs the set" line left an empty database with
no error to catch it. Route and install every module explicitly.

The purge chain had invoices after statements. An invoiced sale.progress
refuses action_cancel outright -- cancelling its invoice is what reverts it
to approved, the first state it can be cancelled from. The documented order
deadlocks on the one statement you invoiced.

custom_product_sale, custom_product_bom and bom_on_sale_order_line are
absent from mev but present on the Callista demo servers, so "not in the
repo" is not the same as "not installable" when pricing.

Also records the revision-formula object graph, which no reference covered:
the coefficients live on formula.input, not on revision.formula, and that
model hangs off either the take-off (contract S, I) or the statement (that
period's s, i). That split is what makes "de index van die maand, niet van
vandaag" work. Its rows come from an @api.onchange that write() never fires
over XML-RPC, and a stale initial_index_value computes a plausible but
wildly wrong revision with no guard behind it -- a factor of 65 instead of
1.005 in this build.

Plus: percentage is a cumulative fraction not 0-100, index_value truncates
to 3 decimals, and button methods return an action dict Odoo cannot marshal
over XML-RPC after the transition has already committed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
glenn
2026-07-31 14:43:15 +02:00
co-authored by Claude Opus 5
parent 8454aa132e
commit 5aee1c61b5
3 changed files with 108 additions and 5 deletions
+21 -4
View File
@@ -31,8 +31,15 @@ its pain → module table. Match on **the client's own words** — the table is
they actually say ("we measure in Excel then re-type it into the quote"), not on module
names they will never utter.
Install the umbrella module `buildease` plus the routed set. `buildease` depends on
nothing and installs the set.
Install the routed set **explicitly**, module by module. `buildease` is a marker whose
manifest depends on `base` alone — it installs none of the other 33, so installing it and
stopping there leaves an empty database and no error to catch it. Install it alongside the
routed set if you want the marker; never instead of it.
Verify afterwards: read back `ir.module.module` state for every routed name and fail loudly
on anything not `installed`. `button_immediate_install` also dies on a transient
`ir_cron` row lock (`LockNotAvailable`) when a cron is mid-run — back off and retry, it is
not a real failure.
## Overlay 2 — Solution precedence
@@ -74,15 +81,25 @@ BuildEase-shaped config will drive it.
*Applies at:* **Purge test residue**, replacing the generic chain.
```
transient wizard rows → statements (cancelled only, newest first) → invoices
timesheet lines → tasks → projects → sale orders → take-offs → the partner
transient wizard rows → invoices (draft → cancel → unlink)
statements (cancelled only, newest first) → timesheet lines → tasks
→ projects → sale orders → take-offs → the partner
```
The generic traps still apply. These are the BuildEase-specific ones, each of which cost
a debugging round:
- **Invoices go before statements.** An `invoiced` `sale.progress` refuses `action_cancel`
outright; cancelling its invoice is what reverts it to `approved`, the first state it can
be cancelled from. Statements-then-invoices deadlocks on the one statement you invoiced.
- **Cancel before deleting, on all three.** `unlink()` refuses unless `quantity.take.off` is
`cancel`, a QTO-linked `sale.order` is `cancel`, and `sale.progress` is `cancelled`.
- **Guard each step separately and loop until a pass keeps nothing.** One `try` around
draft → cancel → unlink lets a raise on an already-draft invoice skip the unlink beneath
it, and the purge then reports success over residue.
- **Button methods return an unmarshallable action dict** over XML-RPC (`Fault` mentioning
`Marshal`/`dumps`). The transition already committed — catch it and move on. Retrying
fails on a state guard and reads like a real error.
- **Deleting the take-off deletes its sale order** — `quantity.take.off.unlink()` unlinks
`sale_order_ids` first. The take-off is the handle for both.
- A take-off with **no** sale order still cancels and deletes normally: call `cancel()`, which