From 60c25f82419b4407a0e60ad361fdbb92fb4b4c02 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sat, 30 May 2026 21:33:12 -0400 Subject: [PATCH] feat(fusion_plating_portal): hide customer sidebar from internal staff + redirect them to the clock portal Co-Authored-By: Claude Opus 4.8 --- .../fusion_plating_portal/__manifest__.py | 2 +- .../controllers/portal.py | 14 +++++ .../static/src/scss/fp_portal_sidebar.scss | 8 +++ .../fusion_plating_portal/tests/__init__.py | 1 + .../tests/test_employee_portal_gating.py | 60 +++++++++++++++++++ .../views/fp_portal_shell.xml | 26 ++++---- 6 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 fusion_plating/fusion_plating_portal/tests/test_employee_portal_gating.py diff --git a/fusion_plating/fusion_plating_portal/__manifest__.py b/fusion_plating/fusion_plating_portal/__manifest__.py index c1f28fa1..bf93b6e5 100644 --- a/fusion_plating/fusion_plating_portal/__manifest__.py +++ b/fusion_plating/fusion_plating_portal/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Customer Portal', - 'version': '19.0.4.4.1', + 'version': '19.0.4.5.0', 'category': 'Manufacturing/Plating', 'summary': 'Customer-facing portal for plating shops: online RFQ, job status, ' 'CoC downloads, invoice access.', diff --git a/fusion_plating/fusion_plating_portal/controllers/portal.py b/fusion_plating/fusion_plating_portal/controllers/portal.py index bf6233f0..ede82b0e 100644 --- a/fusion_plating/fusion_plating_portal/controllers/portal.py +++ b/fusion_plating/fusion_plating_portal/controllers/portal.py @@ -194,6 +194,9 @@ class FpCustomerPortal(CustomerPortal): partner = request.env.user.partner_id commercial = partner.commercial_partner_id values['fp_partner_display_name'] = commercial.name or partner.name + # Internal staff (share=False) get the clean employee experience — no + # customer sidebar. Customers (share=True / portal users) keep it. + values['fp_show_customer_sidebar'] = bool(request.env.user.share) return values def _get_page_view_values(self, document, access_token, values, session_history, no_breadcrumbs, **kwargs): @@ -208,6 +211,7 @@ class FpCustomerPortal(CustomerPortal): layout = self._prepare_portal_layout_values() values.setdefault('fp_sidebar_items', layout.get('fp_sidebar_items')) values.setdefault('fp_partner_display_name', layout.get('fp_partner_display_name')) + values.setdefault('fp_show_customer_sidebar', layout.get('fp_show_customer_sidebar')) return values # ========================================================================== @@ -616,6 +620,16 @@ class FpCustomerPortal(CustomerPortal): website=True, ) def home(self, **kw): + # Internal staff don't belong on the customer dashboard. Send them to + # the employee clock portal — but only when fusion_clock is installed + # (x_fclk_enable_clock proves it) AND the user actually has an employee + # record, otherwise /my/clock -> /my would bounce into a redirect loop. + user = request.env.user + if not user.share and 'hr.employee' in request.env: + Employee = request.env['hr.employee'].sudo() + if 'x_fclk_enable_clock' in Employee._fields and \ + Employee.search_count([('user_id', '=', user.id)]): + return request.redirect('/my/clock') partner = request.env.user.partner_id commercial = partner.commercial_partner_id diff --git a/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_sidebar.scss b/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_sidebar.scss index 84ae2ae3..3ec99dc6 100644 --- a/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_sidebar.scss +++ b/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_sidebar.scss @@ -21,6 +21,14 @@ } } +// Internal staff (employee portal) — no customer sidebar. Collapse the grid +// to a single column so page content isn't pushed right by the now-empty +// 240px sidebar track. +.o_fp_portal_shell--no-sidebar { + grid-template-columns: 1fr; + gap: 0; +} + .o_fp_portal_sidebar { position: sticky; top: $fp-space-4; diff --git a/fusion_plating/fusion_plating_portal/tests/__init__.py b/fusion_plating/fusion_plating_portal/tests/__init__.py index 31c4afb1..01352639 100644 --- a/fusion_plating/fusion_plating_portal/tests/__init__.py +++ b/fusion_plating/fusion_plating_portal/tests/__init__.py @@ -1 +1,2 @@ from . import test_portal_dashboard +from . import test_employee_portal_gating diff --git a/fusion_plating/fusion_plating_portal/tests/test_employee_portal_gating.py b/fusion_plating/fusion_plating_portal/tests/test_employee_portal_gating.py new file mode 100644 index 00000000..ecfada8f --- /dev/null +++ b/fusion_plating/fusion_plating_portal/tests/test_employee_portal_gating.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1. + +from odoo.tests import HttpCase, tagged + + +@tagged('post_install', '-at_install', 'fp_portal') +class TestEmployeePortalGating(HttpCase): + """Internal staff get the clean employee experience (no customer sidebar, + redirected off the customer dashboard); customers are untouched.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.customer_partner = cls.env['res.partner'].create({ + 'name': 'Gating Customer Co.', + 'email': 'gating_customer@example.com', + }) + cls.customer_user = cls.env['res.users'].create({ + 'name': 'Gating Portal User', + 'login': 'gating_portal_user', + 'password': 'gating_portal_user', + 'partner_id': cls.customer_partner.id, + 'group_ids': [(6, 0, [cls.env.ref('base.group_portal').id])], + }) + + def test_customer_sees_sidebar_on_home(self): + """A share/portal user still gets the FP customer sidebar shell.""" + self.assertTrue(self.customer_user.share) + self.authenticate('gating_portal_user', 'gating_portal_user') + r = self.url_open('/my/home') + self.assertEqual(r.status_code, 200) + self.assertIn('o_fp_portal_sidebar', r.text, + "customer should still see the FP sidebar shell") + + def test_internal_employee_redirected_to_clock(self): + """An internal user with an employee record is bounced to /my/clock. + + Only meaningful when fusion_clock is installed (the redirect guard + checks for its x_fclk_enable_clock field, so it never sends anyone to + a non-existent /my/clock). Skip otherwise. + """ + if 'hr.employee' not in self.env: + self.skipTest('hr not installed') + if 'x_fclk_enable_clock' not in self.env['hr.employee']._fields: + self.skipTest('fusion_clock not installed — redirect intentionally inert') + internal = self.env['res.users'].create({ + 'name': 'Shop Hand', + 'login': 'gating_shop_hand', + 'password': 'gating_shop_hand', + 'group_ids': [(6, 0, [self.env.ref('base.group_user').id])], + }) + self.assertFalse(internal.share) + self.env['hr.employee'].create({'name': 'Shop Hand', 'user_id': internal.id}) + self.authenticate('gating_shop_hand', 'gating_shop_hand') + # Don't follow the redirect — just assert we're bounced toward /my/clock. + r = self.url_open('/my/home', allow_redirects=False) + self.assertIn(r.status_code, (301, 302, 303, 307, 308)) + self.assertIn('/my/clock', r.headers.get('Location', '')) diff --git a/fusion_plating/fusion_plating_portal/views/fp_portal_shell.xml b/fusion_plating/fusion_plating_portal/views/fp_portal_shell.xml index 06156716..588fd8d1 100644 --- a/fusion_plating/fusion_plating_portal/views/fp_portal_shell.xml +++ b/fusion_plating/fusion_plating_portal/views/fp_portal_shell.xml @@ -57,17 +57,21 @@ content slot inside #wrap is preserved verbatim. --> -
- - - -
- - +
+ + + + + +
+ + +
$0