From 4a9f31cef574d434c5649e6c458e3403add0782e Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sat, 30 May 2026 21:22:27 -0400 Subject: [PATCH] docs(employee-portal): design spec for staff Clock + Payslips portal Separate internal employees from the customer portal: suppress the fusion_plating_portal sidebar for internal users, redirect them to the clock page, and add a finalized-payslip view (inline paystub + optional PDF) under /my/clock/payslips in fusion_clock. Co-Authored-By: Claude Opus 4.8 --- .../2026-05-30-employee-portal-design.md | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 fusion_plating/docs/superpowers/specs/2026-05-30-employee-portal-design.md diff --git a/fusion_plating/docs/superpowers/specs/2026-05-30-employee-portal-design.md b/fusion_plating/docs/superpowers/specs/2026-05-30-employee-portal-design.md new file mode 100644 index 00000000..0481c883 --- /dev/null +++ b/fusion_plating/docs/superpowers/specs/2026-05-30-employee-portal-design.md @@ -0,0 +1,190 @@ +# Employee Portal — Clock + Payslips (separating staff from the customer portal) + +**Date:** 2026-05-30 +**Status:** Design approved — ready for implementation plan +**Deployment target:** entech (LXC 111 on pve-worker5, DB `admin`) +**Modules touched:** `fusion_plating_portal`, `fusion_clock` (+ optional `fusion_payroll` if a payslip PDF must be built) + +--- + +## 1. Context & Problem + +EN Technologies (entech) runs three relevant modules on one Odoo: + +- `fusion_plating_portal` — the **customer** portal: a rich dashboard + a left sidebar shell wrapping every `/my/*` page. +- `fusion_clock` — employee clock-in/out, exposed on the front end at `/my/clock` (+ `/my/clock/timesheets`, `/my/clock/reports`). A polished dark, mobile-first UI with its own bottom nav. +- `fusion_payroll` — Canadian payroll; owns `hr.payslip` with full earnings/deductions/YTD data. **No employee self-service surface today** (payslips are backend-only). + +Two concrete problems: + +1. **The customer portal applies to everyone.** `fusion_plating_portal`'s `home()` override (`/my`, `/my/home`) returns the customer dashboard for *every* logged-in user, with no internal-vs-customer check. Internal employees who land on `/my` see a customer dashboard that is empty/irrelevant to them. + +2. **The customer sidebar bleeds onto the clock page.** `fp_portal_shell` (`fusion_plating_portal/views/fp_portal_shell.xml`) inherits `portal.portal_layout` and injects the left sidebar into **all** `/my/*` pages, unconditionally. Because `fusion_clock`'s `/my/clock` page renders inside `portal.portal_layout`, it inherits that customer sidebar even though the clock UI was designed clean (it sets `no_header`/`no_breadcrumbs` and has its own bottom nav). + +**Goal:** internal employees get a dedicated, clean employee portal (Clock + Payslips, no customer sidebar); external customers keep the customer portal exactly as it is. + +--- + +## 2. Goals / Non-goals + +**Goals** +- Internal staff never see the customer dashboard or the customer sidebar. +- Employees land on the Clock page and navigate Clock / Timesheets / Reports / **Payslips** via the existing bottom nav. +- Employees can view their own finalized pay slips (inline paystub) and download a PDF when one is available. +- Zero change to the customer experience. + +**Non-goals (v1)** +- Profile/password editing in the employee portal (internal staff use the backend). +- Payslips for non-finalized states (draft / verify hidden). +- RMA/quote/customer features for employees. +- Building a new payslip PDF report *unless* Odoo's standard one is absent on entech (see §9 Open items). +- Cross-instance payroll (payslips are confirmed to live on the same Odoo). + +--- + +## 3. Decisions (locked during brainstorming) + +| # | Decision | +|---|----------| +| Audience split | **Internal users → employee portal; share/portal users → customer portal.** Detected via `request.env.user.share` (`False` = internal staff, `True` = customer). | +| Employee landing & nav | **Clock is home.** `/my` and `/my/home` redirect employees to `/my/clock`. The existing bottom nav gains a **Payslips** tab. **No left sidebar anywhere** for employees. | +| Payslip scope | **Finalized only** — `hr.payslip` where `state in ('done','paid')`, scoped to the logged-in employee. From `fusion_payroll` on the same Odoo. | +| Payslip presentation | **Inline paystub page + Download-PDF button.** Inline always works; the PDF button appears only when a payslip PDF report exists on the server. | +| Architecture | **Approach 1** — `fusion_clock` owns the employee-portal pages (incl. payslips, via a *soft* `hr.payslip` read); `fusion_plating_portal` fixes its own gating + redirect. No new module, no hard cross-dependency. | +| Sign out | A compact **Sign Out** affordance in the employee header (the bottom-nav UI lacks one today). | + +--- + +## 4. Architecture — Approach 1 + +Two modules change; responsibilities stay where they naturally belong. + +**`fusion_plating_portal`** (the module causing the bleed — fixes itself): +- Adds `fp_show_customer_sidebar` to the portal layout context = `request.env.user.share`. +- Gates its sidebar shell on that flag. +- Branches `home()`: internal user → redirect to `/my/clock`; customer → existing dashboard. + +**`fusion_clock`** (owns all employee-portal pages + the bottom nav + the dark SCSS): +- Adds `/my/clock/payslips` (list) and `/my/clock/payslips/` (inline paystub) routes. +- Reads `hr.payslip` through a **soft** check (`'hr.payslip' in request.env`) — **no** `fusion_payroll` dependency added, so `fusion_clock` stays installable without payroll (the Payslips tab simply doesn't appear). +- Adds the **Payslips** tab to the existing bottom nav. + +**Why not the alternatives:** payslip page in `fusion_payroll` makes the bottom nav shared chrome across two modules (duplication or a new cross-dep); a dedicated `fusion_employee_portal` module is new scaffolding that would have to couple to the entech-specific customer module to win the `/my/home` override — and it conflicts with the "edit existing files, don't add modules" rule. + +> **Why the redirect lives in `fusion_plating_portal.home()`:** that override is the one currently winning at `/my/home` on entech (it's why employees see the customer dashboard today). Editing it to branch is guaranteed to take effect on entech without depending on fragile multi-module MRO ordering (`fusion_clock` does **not** override `home()`). + +--- + +## 5. Detailed design + +### 5.1 Audience detection +Single signal: `request.env.user.share`. +- `share == True` → customer → customer portal (dashboard + sidebar). +- `share == False` → internal staff → employee portal (clock + payslips, no sidebar). + +The clock pages already resolve the person via `hr.employee` where `user_id == env.user.id` (`_get_portal_employee`). An internal user with no employee record (e.g. a bare admin) gets the clean layout, an empty clock that redirects, and retains full backend access. + +### 5.2 Suppress the sidebar for employees +In `fusion_plating_portal/controllers/portal.py`, `_prepare_portal_layout_values` adds: +```python +values['fp_show_customer_sidebar'] = request.env.user.share +``` +In `fp_portal_shell.xml`, the `#wrap` replacement becomes conditional: +- `fp_show_customer_sidebar` true → render the full `.o_fp_portal_shell` (sidebar + `
$0
`). +- false → re-emit the raw original `#wrap` ($0) with no shell. + +**Implementation note / risk:** the shell uses Odoo 19's `$0` re-emission inside a `position="replace"`. Putting `$0` in **both** a `t-if` and a `t-else` branch needs verifying — the inheritance engine appends a deep copy of the original node for *each* `$0` occurrence; both copies live in the compiled arch but only one renders at runtime (t-if/t-else). If that proves unreliable at load time, **fallback:** keep a single `$0` inside `.o_fp_portal_main` always, gate only the `