[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)
This commit is contained in:
parent
8cc5566743
commit
2534d4d022
3 changed files with 65 additions and 4 deletions
|
|
@ -139,9 +139,22 @@ CONTROLES = (
|
|||
# chercher l'xmlid signalait donc une base parfaitement saine, et
|
||||
# aurait signalé de même celle d'un client qui a créé la sienne à
|
||||
# la main.
|
||||
#
|
||||
# Et seulement si la FONCTIONNALITÉ est active, c'est-à-dire si
|
||||
# `base.group_user` implique `product.group_product_pricelist` —
|
||||
# la question exacte que pose la case des réglages. Sans elle,
|
||||
# l'absence de liste est normale ; signaler quand même menait à
|
||||
# créer une liste dans une base qui n'en veut pas, et Odoo
|
||||
# prévenait alors à chaque ouverture des réglages qu'il allait
|
||||
# l'archiver.
|
||||
"sql": "SELECT CASE WHEN EXISTS (SELECT 1 FROM ir_module_module"
|
||||
" WHERE name='product' AND state='installed')"
|
||||
" AND to_regclass('public.product_pricelist') IS NOT NULL"
|
||||
" AND EXISTS (SELECT 1 FROM res_groups_implied_rel r"
|
||||
" JOIN ir_model_data u ON u.model='res.groups' AND u.res_id=r.gid"
|
||||
" AND u.module='base' AND u.name='group_user'"
|
||||
" JOIN ir_model_data g ON g.model='res.groups' AND g.res_id=r.hid"
|
||||
" AND g.module='product' AND g.name='group_product_pricelist')"
|
||||
" AND NOT EXISTS (SELECT 1 FROM product_pricelist)"
|
||||
" THEN 1 ELSE 0 END",
|
||||
"gravity": "broken",
|
||||
|
|
|
|||
|
|
@ -74,10 +74,21 @@ try:
|
|||
if "product.pricelist" in env:
|
||||
Liste = env["product.pricelist"].sudo().with_context(active_test=False)
|
||||
rapport["pricelist_before"] = Liste.search_count([])
|
||||
# Le groupe décide : sans lui la fonctionnalité est masquée et
|
||||
# l'absence de liste est normale, pas une perte.
|
||||
rapport["pricelist_group"] = env.user.has_group(
|
||||
"product.group_product_pricelist"
|
||||
# Ce qui décide, c'est la FONCTIONNALITÉ, pas l'appartenance de
|
||||
# celui qui exécute. `has_group` était vrai ici parce que six
|
||||
# utilisateurs sont membres directs du groupe — hérité d'un
|
||||
# palier de migration — alors que la case de configuration était
|
||||
# décochée. On créait donc 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.
|
||||
#
|
||||
# `res.config.settings` lit ce que `base.group_user` IMPLIQUE
|
||||
# (res_config.py : « which groups are implied by the group
|
||||
# Employee ») : c'est la même question qu'on pose ici.
|
||||
fonction = env.ref("product.group_product_pricelist", False)
|
||||
employe = env.ref("base.group_user", False)
|
||||
rapport["pricelist_group"] = bool(
|
||||
fonction and employe and fonction in employe.implied_ids
|
||||
)
|
||||
if not DRY and rapport["pricelist_group"]:
|
||||
Societe._activate_or_create_pricelists()
|
||||
|
|
|
|||
|
|
@ -346,3 +346,40 @@ class TestTheSourceMenuIsWrittenTwice(unittest.TestCase):
|
|||
"""La provenance la plus directe, et l'ordre du menu d'à côté."""
|
||||
self.assertEqual(self.affiches[0], "1")
|
||||
self.assertIn("A local database", self.corps.split("if answer")[0])
|
||||
|
||||
|
||||
class TestAMissingPricelistOnlyCountsWhenTheFeatureIsOn(unittest.TestCase):
|
||||
"""Une liste de prix absente n'est un défaut que si on l'a demandée.
|
||||
|
||||
Mesuré : six utilisateurs étaient membres DIRECTS de
|
||||
`product.group_product_pricelist` — hérité d'un palier de migration —
|
||||
alors que `base.group_user` ne l'impliquait pas. La case des réglages
|
||||
était donc décochée, et la réparation créait quand même une liste.
|
||||
Odoo prévenait ensuite à chaque ouverture des réglages qu'il allait
|
||||
l'archiver.
|
||||
|
||||
`res.config.settings` lit ce que `base.group_user` IMPLIQUE ; le
|
||||
contrôle pose désormais la même question.
|
||||
"""
|
||||
|
||||
def _controle(self):
|
||||
for controle in residue.CONTROLES:
|
||||
if controle["key"] == "missing_pricelist":
|
||||
return controle
|
||||
raise AssertionError("contrôle introuvable")
|
||||
|
||||
def test_it_asks_whether_the_feature_is_implied(self):
|
||||
sql = " ".join(self._controle()["sql"].split())
|
||||
self.assertIn("res_groups_implied_rel", sql)
|
||||
self.assertIn("group_product_pricelist", sql)
|
||||
self.assertIn("group_user", sql)
|
||||
|
||||
def test_it_does_not_settle_for_direct_membership(self):
|
||||
"""`res_groups_users_rel` dirait « quelqu'un est dans le groupe »,
|
||||
ce qui était vrai et menait au faux positif."""
|
||||
self.assertNotIn("res_groups_users_rel", self._controle()["sql"])
|
||||
|
||||
def test_it_still_requires_the_module_and_the_table(self):
|
||||
sql = " ".join(self._controle()["sql"].split())
|
||||
self.assertIn("ir_module_module", sql)
|
||||
self.assertIn("to_regclass", sql)
|
||||
|
|
|
|||
Loading…
Reference in a new issue