From a99f9aa5eea078e7934c8f9d7cea3eb4c0b102d9 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 17 May 2026 13:46:23 -0400 Subject: [PATCH] feat(portal): _fp_sidebar_items helper + layout-values inject Drives the sidebar from a single Python data structure (_FP_SIDEBAR_LAYOUT). Active state resolved by page_name lookup OR URL-prefix match (so Odoo default pages like /my/orders and /my/account light up correctly). _prepare_portal_layout_values extends super() so existing counter injection (fp_quote_request_count etc.) keeps firing. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../controllers/portal.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/fusion_plating/fusion_plating_portal/controllers/portal.py b/fusion_plating/fusion_plating_portal/controllers/portal.py index 7048e855..602a2071 100644 --- a/fusion_plating/fusion_plating_portal/controllers/portal.py +++ b/fusion_plating/fusion_plating_portal/controllers/portal.py @@ -114,6 +114,87 @@ class FpCustomerPortal(CustomerPortal): partner = request.env.user.partner_id return partner.commercial_partner_id + # ========================================================================== + # Sidebar — items + active-state resolution + # ========================================================================== + # Sidebar item structure: list of dicts with `type` = 'item' | 'section_label'. + # Items have label / url / icon / key. Key matches either a page_name set by + # an FP route OR a URL prefix for Odoo default pages. + _FP_SIDEBAR_LAYOUT = [ + {'type': 'item', 'key': 'fp_dashboard', 'label': 'Dashboard', 'icon': '🏠', 'url': '/my/home'}, + {'type': 'section_label', 'label': 'Activity'}, + {'type': 'item', 'key': 'fp_quote_requests', 'label': 'Quote Requests', 'icon': '📄', 'url': '/my/quote_requests'}, + {'type': 'item', 'key': 'fp_configurator', 'label': 'Get a Quote', 'icon': '+', 'url': '/my/configurator'}, + {'type': 'item', 'key': 'odoo_orders', 'label': 'Purchase Orders', 'icon': '🛒', 'url': '/my/orders'}, + {'type': 'item', 'key': 'fp_jobs', 'label': 'Work Orders', 'icon': '⚙️', 'url': '/my/jobs'}, + {'type': 'section_label', 'label': 'Documents'}, + {'type': 'item', 'key': 'fp_certifications', 'label': 'Certifications', 'icon': '📑', 'url': '/my/certifications'}, + {'type': 'item', 'key': 'fp_deliveries', 'label': 'Packing Slips', 'icon': '📦', 'url': '/my/deliveries'}, + {'type': 'item', 'key': 'fp_account_summary','label': 'Account Summary', 'icon': '💰', 'url': '/my/account_summary'}, + {'type': 'section_label', 'label': 'Account'}, + {'type': 'item', 'key': 'odoo_account', 'label': 'Profile', 'icon': '👤', 'url': '/my/account'}, + ] + + # Map either a page_name (set by FP routes) OR a URL prefix + # (for Odoo defaults that don't set page_name) to a sidebar item key. + _FP_PAGE_NAME_TO_SIDEBAR_KEY = { + 'fp_dashboard': 'fp_dashboard', + 'fp_quote_requests': 'fp_quote_requests', + 'fp_quote_request': 'fp_quote_requests', + 'fp_configurator': 'fp_configurator', + 'fp_jobs': 'fp_jobs', + 'fp_portal_job': 'fp_jobs', + 'fp_certifications': 'fp_certifications', + 'fp_deliveries': 'fp_deliveries', + 'fp_account_summary': 'fp_account_summary', + } + _FP_URL_PREFIX_TO_SIDEBAR_KEY = [ + # Order matters — first match wins, so list longer prefixes first. + ('/my/orders', 'odoo_orders'), + ('/my/quotes', 'odoo_orders'), # /my/quotes is also sale_portal + ('/my/invoices', 'fp_account_summary'), + ('/my/account_summary', 'fp_account_summary'), + ('/my/account', 'odoo_account'), + ('/my/security', 'odoo_account'), + ('/my/home', 'fp_dashboard'), + ('/my', 'fp_dashboard'), # /my (no trailing) -> dashboard + ] + + def _fp_resolve_active_sidebar_key(self, url, page_name): + """Resolve which sidebar item should be marked active for this request.""" + if page_name and page_name in self._FP_PAGE_NAME_TO_SIDEBAR_KEY: + return self._FP_PAGE_NAME_TO_SIDEBAR_KEY[page_name] + if url: + for prefix, key in self._FP_URL_PREFIX_TO_SIDEBAR_KEY: + if url.startswith(prefix): + return key + return None + + def _fp_sidebar_items(self, url, page_name): + """Return the sidebar item list with the right item marked active.""" + active_key = self._fp_resolve_active_sidebar_key(url, page_name) + out = [] + for entry in self._FP_SIDEBAR_LAYOUT: + if entry.get('type') == 'item': + copy = dict(entry) + copy['active'] = (active_key == entry['key']) + out.append(copy) + else: + out.append(entry) + return out + + def _prepare_portal_layout_values(self): + values = super()._prepare_portal_layout_values() + # Resolve current URL + page_name for sidebar active-state + url = request.httprequest.path if request else '' + page_name = values.get('page_name') + values['fp_sidebar_items'] = self._fp_sidebar_items(url, page_name) + # Partner display name for the sidebar header + partner = request.env.user.partner_id + commercial = partner.commercial_partner_id + values['fp_partner_display_name'] = commercial.name or partner.name + return values + # ========================================================================== # Customer-visible stage timeline (detail page) # ==========================================================================