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) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-17 13:46:23 -04:00
parent ca60500c07
commit a99f9aa5ee

View File

@@ -114,6 +114,87 @@ class FpCustomerPortal(CustomerPortal):
partner = request.env.user.partner_id partner = request.env.user.partner_id
return partner.commercial_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) # Customer-visible stage timeline (detail page)
# ========================================================================== # ==========================================================================