Commit graph

5 commits

Author SHA1 Message Date
ccbb2a8597 [FIX] liste de prix : ne rien créer quand il en existe déjà une
`_activate_or_create_pricelists` ne compte pas une liste de prix partagée
entre sociétés (company_id vide) comme appartenant à la société : elle voit
une société « sans liste » et lui en fabrique une. Sur une base migrée qui
n'a rien perdu, cela ajoute un doublon vide à côté de la liste existante.

Elle interroge maintenant le détecteur `pricelist_missing` du même outil,
qui distingue déjà les deux cas.

Vérifié dans les deux sens sur copie jetable : liste présente, rien n'est
créé ; aucune liste, la réparation en crée une.

--- EN ---

`_activate_or_create_pricelists` does not count a pricelist shared across
companies (empty company_id) as belonging to the company: it sees a company
"without a pricelist" and makes one. On a migrated database that lost
nothing, that adds an empty duplicate beside the existing list.

It now asks the same tool's `pricelist_missing` detector, which already
tells the two cases apart.

Verified both ways on a throwaway copy: a pricelist present, nothing is
created; no pricelist, the repair creates one.

Assisted-by: Claude Opus 5
(cherry picked from commit b5bd2e4e44e89bdba4d639672611dcb964265987)
2026-08-29 02:11:04 -04:00
2534d4d022 [FIX] liste de prix : décider sur la fonctionnalité, pas sur l'exécutant
`env.user.has_group()` répond « oui » dès que l'exécutant est membre du
groupe, et la migration l'y ajoute en cours de route. Or la case des
réglages lit tout autre chose : ce que `base.group_user` IMPLIQUE
(res_config.py, « which groups are implied by the group Employee »).
Décider sur l'exécutant créait une liste de prix dans une base dont la
fonctionnalité est éteinte, et Odoo prévenait à chaque ouverture des
réglages qu'il allait l'archiver. Le contrôle « restant de migration »
posait la même mauvaise question ; les deux lisent désormais l'implication
du groupe. Vérifié sur copie jetable, dans les deux sens : fonctionnalité
éteinte, rien n'est signalé ; activée, constat et réparation reviennent.

--- EN ---

`env.user.has_group()` says yes as soon as the caller belongs to the
group, and the migration adds it along the way. But the settings checkbox
reads something else: what `base.group_user` IMPLIES (res_config.py,
"which groups are implied by the group Employee"). Deciding on the caller
created a pricelist in a database whose feature is off, and Odoo warned on
every opening of the settings that it would archive it. The
migration-residue check asked the same wrong question; both now read the
group implication. Verified on a throwaway copy, both ways: feature off,
nothing is reported; feature on, finding and repair come back.

Assisted-by: Claude Opus 5
(cherry picked from commit 38ba25e01894f45fb956bcbb08cc3d96a125e648)
2026-08-29 02:11:04 -04:00
67a59d0522 [ADD] analyse: anonymiser une copie, sans IA et sans rien casser
Des mots pris dans une liste, des nombres tirés entre 0 et 1000, écrits
en SQL. Aucun modèle, aucun réseau — un test le vérifie sur les imports.

Le difficile n'est pas de remplacer, c'est de savoir ce qu'on n'a PAS le
droit de toucher. Mesuré sur une base 18 réelle : 505 champs `selection`
sont stockés en varchar, 2693 many2one sont des entiers, 194 textes sont
des jsonb par langue, 301 contraintes d'unicité attendent une collision.
« Tous les champs string » n'existe pas ; on croise ir_model_fields,
pg_attribute et pg_constraint, et aucune des trois ne suffit seule.

Trois pièges ont été trouvés en LANÇANT l'outil, pas en le relisant :
PostgreSQL refuse d'indexer un ARRAY[...] sans parenthèses,
res_partner.credit_limit est un jsonb qu'Odoo appelle float, et
crm_lead.probability porte un CHECK qui interdit 1000. Chaque fois
l'écriture a échoué et la base est restée intacte : une seule
transaction, tout ou rien.

Preuve sur copie jetable : empreinte du schéma identique, 848 tables,
6495 contraintes, arch_db et xmlid intacts, lang et many2one inchangés —
seules les colonnes visées ont changé.

--- EN ---

Words from a list, numbers drawn between 0 and 1000, written in SQL. No
model, no network — a test checks that on the imports.

The hard part is not replacing, it is knowing what must NOT be touched.
Measured on a real 18 database: 505 `selection` fields are stored as
varchar, 2693 many2one are integers, 194 texts are per-language jsonb,
301 unique constraints await a collision. "All string fields" does not
exist; we cross ir_model_fields, pg_attribute and pg_constraint, and none
of the three is enough alone.

Three traps were found by RUNNING it, not by rereading it: PostgreSQL
refuses to subscript a bare ARRAY[...], res_partner.credit_limit is a
jsonb Odoo calls float, and crm_lead.probability has a CHECK forbidding
1000. Each time the write failed and the database stayed intact: one
transaction, all or nothing.

Proof on a throwaway copy: identical schema fingerprint, 848 tables, 6495
constraints, arch_db and xmlids intact, lang and many2one unchanged —
only the targeted columns changed.

Assisted-by: Claude Opus 5
2026-08-25 03:31:12 -04:00
9a7b8cb36f [ADD] analyse: l'état d'une instance, lu pour l'usage qu'on en fait
Le même chiffre veut dire deux choses opposées. Zéro cron actif est le
succès attendu d'une copie et une panne totale sur une production. Un
rapport qui ignore cela crie au loup sur ce qu'on vient de demander, et
l'on cesse de le lire. L'attente est donc déclarée, copy ou live, et
chaque contrôle dit ce qu'il juge sous l'une et sous l'autre.

Deux contrôles ont été ÉCARTÉS sous copy après mesure : sur la base 12
d'origine, jamais démarrée, 11 crons étaient déjà en retard et db_backup
déjà vide — notre propre update_prod_to_dev les efface. Les afficher en
rouge aurait été du bruit ; en vert, un mensonge. Ils sont montrés non
jugés, avec la raison.

Le code Python en base a été mesuré et abandonné : 132 actions serveur,
zéro citant un modèle inexistant, et les 3 « modèles sans table » sont
ir.autovacuum et deux autres modèles abstraits d'Odoo.

--- EN ---

The same number means two opposite things. Zero active cron is the
expected success of a copy and a total outage on production. A report
that ignores this cries wolf over what was just requested, and stops
being read. The expectation is therefore declared, copy or live, and each
check states what it judges under either.

Two checks were DROPPED under copy after measuring: on the untouched 12
source database, 11 crons were already late and db_backup already empty —
our own update_prod_to_dev deletes them. Red would have been noise; green
a lie. They are shown unjudged, with the reason.

In-database Python was measured and dropped: 132 server actions, none
naming a missing model, and the 3 "models without a table" are
ir.autovacuum and two other Odoo abstract models.

Assisted-by: Claude Opus 5
2026-08-25 03:28:39 -04:00
2521267896 [ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.

« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.

Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.

Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.

--- EN ---

The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.

« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.

Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.

The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.

Assisted-by: Claude Opus 5
2026-08-25 03:28:39 -04:00