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:
@@ -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)
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user