From ea6f62aa83bc58098a6a0a2dd97e0734babe0dab Mon Sep 17 00:00:00 2001 From: bosd <5e2fd43-d292-4c90-9d1f-74ff3436329a@anonaddy.me> Date: Tue, 23 Jun 2026 12:00:12 +0200 Subject: [PATCH 1/2] [FIX] base_tier_validation: reviewer systray counter drifting (negative) The reviewer counter in the systray can display a wrong, even negative, value. The counter was mutated by real-time bus deltas (``review_created`` -> ++, ``review_deleted`` -> --), but ``_update_counter`` sent those deltas to ``self.env.user.partner_id`` -- the *acting* user -- instead of the reviewers whose pending count actually changes. So whenever a user acted on a tier-validated record without being one of its reviewers (creating, editing, force-validating), the wrong user's counter was moved, and an active approver accumulated a drift that eventually went negative. The systray's own absolute count is a ``len()`` and can never be negative; only the blind delta could. Fixes: - JS: the systray component now re-fetches the authoritative absolute count (``review_user_count``) on the bus event, instead of a separate service nudging a +/- delta. The dedicated ``tierReviewService`` (whose only job was that delta) is removed; the component already owns ``fetchSystrayReviewer`` and the ORM via ``useService``, so the badge stays live without drift and can never go below zero. - Python: ``_update_counter`` now notifies the reviewers' partners (the users whose pending count changes), falling back to the acting user only when the reviews are already gone (deletions). --- base_tier_validation/__manifest__.py | 2 +- .../models/tier_validation.py | 13 +++++++- .../tier_review_menu/tier_review_menu.esm.js | 10 ++++++ .../js/services/tier_review_service.esm.js | 33 ------------------- 4 files changed, 23 insertions(+), 35 deletions(-) delete mode 100644 base_tier_validation/static/src/js/services/tier_review_service.esm.js diff --git a/base_tier_validation/__manifest__.py b/base_tier_validation/__manifest__.py index e826d37b..763d20f6 100644 --- a/base_tier_validation/__manifest__.py +++ b/base_tier_validation/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Base Tier Validation", "summary": "Implement a validation process based on tiers.", - "version": "19.0.1.0.3", + "version": "19.0.1.0.4", "development_status": "Mature", "maintainers": ["LoisRForgeFlow"], "category": "Tools", diff --git a/base_tier_validation/models/tier_validation.py b/base_tier_validation/models/tier_validation.py index 29896f80..65158aa1 100644 --- a/base_tier_validation/models/tier_validation.py +++ b/base_tier_validation/models/tier_validation.py @@ -853,7 +853,18 @@ def restart_validation(self): def _update_counter(self, review_counter): self.review_ids._update_review_status() channel = "base.tier.validation/updated" - self.env.user.partner_id._bus_send(channel, review_counter) + # Notify the reviewers whose pending count actually changes, not the + # acting user. Sending the delta to ``self.env.user`` made unrelated + # users' systray counters drift (even negative) whenever someone acted + # on a tier-validated record without being one of its reviewers. When + # the reviews are already gone (deletions), fall back to the acting + # user. The client recomputes the absolute count on receipt, so the + # exact audience only affects how promptly a reviewer sees the update. + partners = self.review_ids.mapped("reviewer_ids.partner_id") + if not partners: + partners = self.env.user.partner_id + for partner in partners: + partner._bus_send(channel, review_counter) def unlink(self): self.mapped("review_ids").unlink() diff --git a/base_tier_validation/static/src/components/tier_review_menu/tier_review_menu.esm.js b/base_tier_validation/static/src/components/tier_review_menu/tier_review_menu.esm.js index 553796b8..1c7de6d0 100644 --- a/base_tier_validation/static/src/components/tier_review_menu/tier_review_menu.esm.js +++ b/base_tier_validation/static/src/components/tier_review_menu/tier_review_menu.esm.js @@ -16,8 +16,18 @@ export class TierReviewMenu extends Component { this.orm = useService("orm"); this.store = useState(useService("mail.store")); this.action = useService("action"); + this.busService = useService("bus_service"); this.dropdown = useDropdownState(); this.fetchSystrayReviewer(); + // Keep the badge live and correct: re-fetch the authoritative count + // whenever a tier review changes server-side, instead of nudging a + // running +/- delta. The absolute value is a len() and so can never go + // negative, which a delta could when an update reaches a user for whom + // the review was never part of their pending count. + this.busService.subscribe("base.tier.validation/updated", () => + this.fetchSystrayReviewer() + ); + this.busService.start(); } async fetchSystrayReviewer() { diff --git a/base_tier_validation/static/src/js/services/tier_review_service.esm.js b/base_tier_validation/static/src/js/services/tier_review_service.esm.js deleted file mode 100644 index bde257be..00000000 --- a/base_tier_validation/static/src/js/services/tier_review_service.esm.js +++ /dev/null @@ -1,33 +0,0 @@ -import {reactive} from "@odoo/owl"; -import {registry} from "@web/core/registry"; - -export class TierReviewService { - constructor(env, services) { - this.env = env; - this.store = services["mail.store"]; - this.busService = services.bus_service; - } - setup() { - this.busService.subscribe("base.tier.validation/updated", (payload) => { - if (payload.review_created) { - this.store.tierReviewCounter++; - } - if (payload.review_deleted) { - this.store.tierReviewCounter--; - } - }); - this.busService.start(); - } -} - -export const tierReviewService = { - dependencies: ["bus_service", "mail.store"], - - start(env, services) { - const tier_review_service = reactive(new TierReviewService(env, services)); - tier_review_service.setup(); - return tier_review_service; - }, -}; - -registry.category("services").add("tierReviewService", tierReviewService); From dff0f9f8762f0f45a6d5cd6dc18d84ba87c9c4d5 Mon Sep 17 00:00:00 2001 From: bosd <11499387+bosd@users.noreply.github.com> Date: Wed, 8 Jul 2026 11:54:28 +0200 Subject: [PATCH 2/2] Update __manifest__.py --- base_tier_validation/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base_tier_validation/__manifest__.py b/base_tier_validation/__manifest__.py index 763d20f6..e826d37b 100644 --- a/base_tier_validation/__manifest__.py +++ b/base_tier_validation/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Base Tier Validation", "summary": "Implement a validation process based on tiers.", - "version": "19.0.1.0.4", + "version": "19.0.1.0.3", "development_status": "Mature", "maintainers": ["LoisRForgeFlow"], "category": "Tools",