Merge: Fusion Plating Permissions Overhaul Phase 1

Consolidates 12 res.groups into 8 clean roles:
  Owner -> Quality Manager -> Manager -> [Shop Manager, Sales Manager]
       -> [Technician, Sales Rep], plus implicit 'No' (no plating group).

Phase A — 7 new res.groups with implied_ids chains + backward-compat;
old groups marked [DEPRECATED] and queued for 30-day cron purge.
Phase B — mechanical ACL sweep across 24 ir.model.access.csv files.
Phase C — Manager/QM quality permission split + FAIR/Nadcap ir.rule.
Phase D — 3-layer menu/submenu/field/button visibility hardening.
Phase E — role-based landing-page dispatch (Owner -> Manager Desk,
QM -> Quality Dashboard, Sales Rep -> Quotations, Tech -> Plant
Kanban, etc.) + picker domain over ir.actions.actions so window AND
client actions are both pickable.
Phase F — Owner-only Plating > Configuration > Team kanban for
drag-and-drop role assignment, plus Designated Officials (CGP DO +
Nadcap Authority) fields on res.company.
Phase G — Sales Manager + required to confirm SO; fixed the
audit-finding-11 _administrator typo that had made the account-hold
bypass dead code; swept all Python has_group() refs to new xmlids.
Phase H — dry-run + Owner-approval migration workflow with
fp.migration.preview model, mail.activity notification, 30-day
rollback window, daily purge cron.
Phase 9 — final-reviewer fixes (groups_id->group_ids, server-action
wiring, migrations/19.0.21.1.0/post-migrate.py for -u dispatch,
Odoo 19 kanban card template, FAIR/Nadcap cert_type field name,
user_has_groups removed from invisible attrs).
Phase I — pre-deploy backup, entech deploy (5 cascade fixes
discovered live), Owner approval of migration #1 (25 users
migrated cleanly), post-approval SQL verification, sample login
tests, deprecated-group picker cleanup (Option A SQL UPDATE),
and 11 post-deploy bug fixes (picker model swap to ir.actions.actions,
ACL grant for ir.actions.actions read to plating users, SELF_WRITEABLE_FIELDS
extension for non-admin Preferences save, res.users.message_post ->
partner_id.message_post, tablet lock screen group ref swap,
PIN-pad dark-mode dot contrast, lock-screen logo plate dark mode).

Spec: docs/superpowers/specs/2026-05-23-permissions-overhaul-design.md
Plan: docs/superpowers/plans/2026-05-24-permissions-overhaul-phase1-plan.md
CLAUDE.md rules added: 13b-13l (Odoo 19 gotchas surfaced during build/deploy)

Live state on entech: 25 users migrated, 30-day rollback open
until 2026-06-23, deprecated groups hidden from picker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-24 10:05:25 -04:00
125 changed files with 6396 additions and 698 deletions

View File

@@ -208,6 +208,74 @@ Use only: `name`, `model_id`, `state`, `code` (or `function`/`model`), `interval
11. **XML data ordering**: Window actions must be defined BEFORE `<menuitem>` elements that reference them in the same file.
12. **Module install on new modules**: Use `--update=base` alongside `-i MODULE` to ensure Odoo rescans the addons path and finds the new module directory.
13. **Implied group cascade**: `implied_ids` on `res.groups` does NOT reliably propagate to users on module install. Always include `user_ids` to explicitly assign admin, or fix via SQL post-install.
13b. **Kanban template name — Odoo 19 wants `<t t-name="card">`, NOT `<t t-name="kanban-box">`**. Old name silently fails at render: `Error: Missing 'card' template`. Use the new structure with semantic `<aside>` + `<main>`:
```xml
<templates>
<t t-name="card" class="flex-row align-items-center">
<aside><field name="image_128" widget="image"/></aside>
<main class="ms-2"><field name="name"/></main>
</t>
</templates>
```
Reference: `/usr/lib/python3/dist-packages/odoo/addons/web/static/src/views/kanban/kanban_arch_parser.js`. Pre-existing `fp_rack_views.xml` still uses the old name and would also fail at render — fix when next touched. Caught 2026-05-24 by final reviewer of permissions-overhaul branch.
13l. **Post-migration: `env.ref('old_group_xmlid').user_ids` returns empty** even though the old group still exists. Phase H's migration moves users OFF the old (now-`[DEPRECATED]`) groups onto the new ones. Old groups are still reachable via the new group's `implied_ids` (so old ACLs still resolve for backward-compat), but NO USER directly holds the old group anymore. Any code doing `env.ref('fusion_plating.group_fusion_plating_operator').user_ids` to enumerate "operators" gets an empty recordset. **Fix: point at the new group xmlid (`group_fp_technician`).** `res.groups.user_ids` includes both direct AND implied-via-other-group members, so higher roles (Shop Manager, Manager, QM, Owner) appear via implication. The Phase G `has_group()` sweep didn't catch these — `env.ref(...).user_ids` is a different access pattern. Caught 2026-05-24 when the tablet lock screen showed "No operators configured" post-migration. Audit for other instances: `grep -rn "env\.ref.*group_fusion_plating_" --include='*.py'` (skip test files which intentionally reference old xmlids to verify backward-compat).
13k. **Custom fields on `res.users` must be added to `SELF_WRITEABLE_FIELDS` (and often `SELF_READABLE_FIELDS`) or non-admin users can't save their own Preferences dialog**. Odoo 19's User Preferences dialog goes through `res.users.write` on the user's own record — Odoo bypasses the standard write ACL ONLY IF every field being written is in `SELF_WRITEABLE_FIELDS`. Any unknown field forces fallback to the standard ACL (admin-only on entech) → `AccessError: You are not allowed to modify 'User' records. Required group: Access Rights`.
**In Odoo 19, `SELF_WRITEABLE_FIELDS` and `SELF_READABLE_FIELDS` are `@property`-decorated methods, NOT class attributes.** Extend via super(), not list concatenation on `models.Model.SELF_*` (that AttributeErrors at module load — Model base doesn't define them, only res.users does). Canonical pattern (matches hr/res_users.py and mail/res_users.py):
```python
class ResUsers(models.Model):
_inherit = 'res.users'
@property
def SELF_WRITEABLE_FIELDS(self):
return super().SELF_WRITEABLE_FIELDS + [
'x_fc_plating_landing_action_id', 'x_fc_signature_image',
]
@property
def SELF_READABLE_FIELDS(self):
return super().SELF_READABLE_FIELDS + [
'x_fc_plating_role', 'x_fc_tablet_pin_set_date', ...
]
```
Readonly fields on the preferences form ALSO need SELF_READABLE_FIELDS (the form fetches them before the user clicks Save). Methods invoked by buttons that do their own `sudo().write()` bypass this — only DIRECT form-level writes hit the check. Caught 2026-05-24 when Technician tried to save their preferences after the plating landing field was added; the initial fix used the wrong class-attribute syntax and crashed odoo at module load.
13j. **Non-stored Many2many computes STILL require user-level read access on the comodel** for field-assignment cache fill, even when the compute body is wrapped in `sudo()`. The `user.field = [(6, 0, ids)]` assignment populates the cache by relating to comodel records the CURRENT USER must be able to read — `sudo()` on the lookup doesn't help because the assignment is per-record-context. If the comodel is admin-only (like `ir.actions.actions` / `ir.actions.act_window` on entech), a non-admin opening their own preferences will fail with `Failed to write field X. You are not allowed to access 'Y' records.` Two fixes: (a) drop the Many2many compute and use a static domain filter instead, plus add an ACL row granting read on the comodel to whichever role group needs to evaluate the picker domain; (b) replace the Many2many with a Json/Char that stores IDs, lose the auto-validation. Option (a) is simpler — Odoo's design assumes pickers' comodels are user-readable. Caught 2026-05-24 when a Technician tried to open their Preferences after the per-user `accessible_landing_action_ids` field was added.
13i. **`res.users` does NOT have `message_post()`** — chatter posting must go through `user.partner_id.message_post(...)`. `res.users` uses `_inherits = {'res.partner': 'partner_id'}` (delegation), which proxies FIELDS through partner_id but NOT METHODS. `user.message_post(...)` raises `AttributeError: 'res.users' object has no attribute 'message_post'`. Note that mail's tracking IS recorded on the user record (via partner) — the chatter widget on user form displays partner's chatter — but the post call itself targets the partner. Caught 2026-05-24 during Owner approval click on the migration preview screen.
13c. **`res.users.group_ids` NOT `groups_id`**: Odoo 19 renamed the m2m field. Old name doesn't resolve; `@api.depends('groups_id')` raises `ValueError` at module load. Also: domain on relational pickers should use `all_group_ids` (transitive set incl. implied) instead of `group_ids` (only directly-assigned) — otherwise an Owner user won't match a domain looking for QM members. See `feedback_odoo19_groups_id_renamed.md`.
13d. **`post_init_hook` ONLY fires on INSTALL, not UPGRADE** in Odoo 19. For logic that must run on `-u` of an existing install (entech case), add a `migrations/<version>/post-migrate.py` with a `migrate(cr, version)` function that calls the same helper. The hook still works on fresh install; the migration script bridges the gap on `-u`. Both should be idempotent so re-runs are safe.
13g. **Odoo 19's `sale.view_order_form` uses a single `<field name="tax_totals" widget="account-tax-totals-field"/>` widget instead of separate `amount_total` / `amount_untaxed` / `amount_tax` fields**. Inheriting xpaths targeting any of the three separate fields will fail at view load: `Element '<xpath expr="//field[@name='amount_total']">' cannot be located in parent view`. To gate or modify totals, target the `tax_totals` widget (one xpath hides the whole totals block). Other views in the file (kanban, list, pivot) DO still have the individual fields — only the FORM view consolidated to the widget. Same likely applies to `purchase.purchase_order_form` and `account.view_move_form` — verify per-view before porting Odoo 17/18 xpaths. Caught 2026-05-24.
13h. **`user_has_groups('xmlid')` is NOT available inside Odoo 19's `invisible=`/`readonly=`/`required=` attribute expressions**. The view validator parses `user_has_groups` as a field name on the host model and fails: `field 'user_has_groups' does not exist in model 'X'`. Group-based UI gating must use the `groups=` attribute on the element instead. To combine group-AND-state logic, EITHER split into two elements with mutually-exclusive `invisible` AND different `groups=`, OR enforce one half at the model layer (ir.rule / @api.constrains) and the other in the view. Caught 2026-05-24 when a single button used `invisible="state != 'draft' or (cert_type == 'nadcap' and not user_has_groups(...))"` — rewrote as a single button with `groups="group_fp_manager"` + `invisible="state != 'draft'"` and let the ir.rule enforce the Nadcap-write restriction (Manager clicking Issue on a Nadcap cert now raises AccessError).
13f. **Odoo 19 view validator rejects `ref('xmlid')` inside `<field domain="...">`**: the validator parses `ref(...)` as a field-access on the host model and fails with `field 'ref' does not exist in model 'X'`. Even though `ref()` IS resolved at runtime by the client, validation fires first and aborts module load. Workarounds (pick one):
- **Drop the domain** and enforce eligibility via `@api.constrains` on the Python side (simplest — used for `res.company.x_fc_cgp_designated_official_id` in this project; the Owner makes a deliberate choice and Python validates at save time).
- **Pre-compute eligible IDs** in a stored `Many2many` compute on the host model, then `domain="[('id', 'in', eligible_ids_field)]"`.
- Move the domain into the field definition in Python (`fields.Many2one(..., domain="[...]")`) — but Python-side domains have the same `ref()` limitation, so this isn't always an escape.
Caught 2026-05-24 deploying permissions-overhaul to entech.
13e. **`res_groups_name_uniq` constraint is `(privilege_id, name)` — cross-module display-name collisions during `-u` need a `pre-migrate.py` rename**. If a base module's new XML defines a group with the same display name as a DOWNSTREAM module's existing group (e.g. core adds new `Shop Manager (v2)` while configurator already has old `Shop Manager`), the new INSERT collides with the still-named-the-same downstream row, because Odoo loads modules in dep order and the downstream rename via XML hasn't happened yet. The fix is a `migrations/<version>/pre-migrate.py` in the BASE module that SQL-renames the downstream row before the new XML loads:
```python
def migrate(cr, version):
cr.execute(\"\"\"
UPDATE res_groups
SET name = jsonb_build_object('en_US', '[DEPRECATED] Shop Manager (...)')
WHERE id IN (
SELECT res_id FROM ir_model_data
WHERE module = 'fusion_plating_configurator'
AND name = 'group_fp_shop_manager'
AND model = 'res.groups'
)
AND (name IS NULL OR name->>'en_US' NOT LIKE '[DEPRECATED]%');
\"\"\")
```
Pre-migrate scripts run BEFORE the module's data files reload, so the constraint is clear by the time the new group XML INSERTs. Caught 2026-05-24 during permissions-overhaul deploy — `fp_security_v2.xml` claimed `'Shop Manager'` while old configurator's `group_fp_shop_manager` still held that display name in the DB. Same pattern applies to ANY base-module XML adding groups with names that overlap downstream-module groups.
13a. **Cross-module xmlid refs — base modules CANNOT forward-ref downstream xmlids**: A BASE module's data XML cannot `ref('downstream_module.some_xmlid')` because at fresh install, the base module loads FIRST and `ir.model.data` has no row for the downstream xmlid yet → `ValueError: External ID not found`. This bites on entech (existing DB has the row) but breaks fresh CI/test/demo/new-client installs. **Fix pattern: relocate the cross-module link to the downstream module's own security/data file, using an additive write to the BASE module's record:**
```xml
<!-- In downstream module's security XML -->
<record id="fusion_plating.group_fp_sales_rep" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_configurator.group_fp_estimator'))]"/>
</record>
```
Odoo's XML loader treats `id="other_module.xmlid"` as an additive update to the existing record, and `(4, ref(...))` (Command.link) stacks idempotently across install/-u cycles. Use this whenever a base module group/record needs to imply or reference something defined in a downstream module. Caught 2026-05-24 when `fusion_plating/security/fp_security_v2.xml` referenced groups from configurator/receiving/invoicing/cgp — worked on entech, would have broken fresh installs.
14a. **FP report palette + border rendering**: `fusion_plating_reports/report/report_base_styles.xml` uses **`#c1c1c1`** for section-header backgrounds and **`#1d1f1e`** (th text on grey) / **`#4e4e4e`** (h2/h4 on white) — NOT `res.company.primary_color`. Per-customer request (2026-05-17) the FP reports stopped following the company brand colour so every shop gets the same neutral look. The `fp_primary` template variable is still computed in the styles block so per-report templates can opt back in if needed, but the default `.fp-report` / `.fp-landscape` rules use the hardcoded greys. **Don't "fix" this back to `fp_primary` without confirming.**
**Border-rendering gotcha** (entech wkhtmltopdf): with the standard `border-collapse: collapse` + `border: 1px solid #000` pattern, vertical borders can render slightly softer than horizontal borders because of how wkhtmltopdf rounds sub-pixels in its collapse-adjudication. Cells with a `background-color` also paint over the border edge unless clipped. Mitigations in place:
@@ -231,6 +299,7 @@ Use only: `name`, `model_id`, `state`, `code` (or `function`/`model`), `interval
20. **OWL templates expose `Math` but NOT `String` / `Number` / `Array` / `Object` / `Boolean` / `JSON` / `parseInt` / `parseFloat`**: writing `t-on-click="() => this._press(String(d))"` (or similar coercion inside any template expression) throws `Uncaught TypeError: v2 is not a function` at click time — `v2` is OWL's compiled reference to a global that doesn't exist in template scope. The click handler dies before its body runs, so the bug looks like "nothing happens when I press" (no error in the UI, only DevTools shows the trace). **Fixes, in order of preference**: (a) eliminate the coercion entirely — store data in the right type up front, e.g. `t-foreach="['1','2','3']"` instead of `[1,2,3]` so `d` is already a string. (b) Use a JS-side coercion: pass the raw value to the handler and call `String(digit)` inside the component method. (c) Use a pure-expression workaround like string concatenation: `'' + d` does work because `+` is an operator, not a function. **Do NOT try to monkey-patch `String` onto the component (e.g. `this.String = String`) or onto `env` — leaks the global into every component and is fragile across OWL upgrades.** Bit us 2026-05-23 on `pin_pad.xml` — operators couldn't tap PIN digits at all because the click handler died on `String(d)`; the SCSS, reactivity, and `_press` method were all fine, the template scope was the entire bug. Same trap applies to OWL templates anywhere in the codebase: `move_parts_dialog.xml`, `manager_dashboard.xml`, `fp_record_inputs_dialog.xml`, etc. — grep all `t-on-click`, `t-att-*`, and `t-out` expressions for `String(`, `Number(`, `Array(`, `parseInt(`, `parseFloat(`, `JSON.` before merging.
21. **`ir.actions.act_window_close` is a no-op when the current action was opened with `target: "current"`**: replacing the current action wipes the breadcrumb backstack, so there's nothing to close back to. The user clicks "Back" and nothing happens (no error, no navigation). This bites every OWL client-action surface that calls another client action via `doAction({..., target: "current"})` — the destination has no way to return to the source. **Fix pattern for "Back" buttons in OWL client actions**: navigate EXPLICITLY to the landing/parent action by tag, e.g. `this.action.doAction({ type: "ir.actions.client", tag: "fp_shopfloor_landing", target: "current" })` — works regardless of how the action was reached (kanban tap, QR scan, smart button, direct URL). **Do NOT rely on `act_window_close`, `history.back()`, or `this.env.config.breadcrumbs`** — all three are unreliable across navigation paths. Bit us 2026-05-23 on the Job Workspace Back button after the kanban opened the workspace with `target: "current"`. The same pattern applies to every other "Back" button in shopfloor / manager / portal OWL surfaces — explicit destination via `tag:` is the only robust answer.
22. **Odoo 19 HTML fields auto-wrap plain-string writes**: writing `co.report_header = 'Plating & Finishing'` to an HTML field (like `res.company.report_header`, `res.partner.comment`, `mail.template.body_html`, `product.template.description_sale`) stores `<p>Plating &amp; Finishing</p>` after Odoo's HTML sanitizer runs. Equality tests against the raw input string FAIL (`payload['tagline'] != 'Plating & Finishing'`). **Three implications**: (a) **In tests**, don't `assertEqual` against the literal string you wrote — strip tags first, OR write the wrapped form (`<p>Plating & Finishing</p>`), OR write an explicit `Markup('<p>...</p>')` so the round-trip stays stable. (b) **In display code**, render HTML fields with `t-out` (QWeb) or `markup(...)` (OWL) — `t-esc` would render the literal `<p>` tags as text. (c) **In comparison logic**, normalize first: `from markupsafe import escape; escape(input_str)` produces the same shape the field stores. Bit us 2026-05-24 testing the lock-screen tagline source (`_lock_company_payload` reads `res.company.report_header`); the test that wrote a plain string and asserted equality failed because the value came back wrapped. The fix was to delete the brittle equality test — the helper's responsibility is just "use the field's value when present, else fall back," which is covered by the empty-field test. Generalizes to ANY HTML-typed Odoo field. Distinct from the `mail.template.body_html is Markup + jsonb` gotcha noted earlier in this file — that's about Markup objects vs strings; this is about the sanitizer wrapping plain strings on write.
23. **`res.users.group_ids` vs `all_group_ids` for domain filters**: in Odoo 19, `res.users` carries TWO M2M-to-`res.groups` fields and they have different membership semantics. `group_ids` is the user's DIRECTLY-assigned groups (what the user record literally wrote). `all_group_ids` is the TRANSITIVE set — direct groups PLUS every group implied via `implied_ids` chains. **For domain filters on user pickers** (e.g. "show users who can act as a Quality Manager"), ALWAYS use `all_group_ids`, never `group_ids`. An Owner user only carries `group_fp_owner` directly; the QM capability comes via `implied_ids → group_fp_quality_manager`, so a `domain="[('group_ids', 'in', [ref('...quality_manager')])]"` excludes Owners and the picker looks empty. Use `domain="[('all_group_ids', 'in', [ref('...quality_manager'), ref('...owner')])]"` instead. Compute helpers (`@api.depends('group_ids')`) and write vals (`{'group_ids': [(4, gid)]}`) still use `group_ids` because those operate on direct assignments — only domain filters need the transitive set. Bit us 2026-05-24 on the CGP DO + Nadcap Authority pickers on `res.company`. Same gotcha applies to ANY domain that needs "does this user effectively have role X" semantics across user-facing pickers, ACL rules, server actions, and search filters.
## Naming
- **New custom models** (post-2026-04): `fp.*` prefix (e.g. `fp.part.catalog`, `fp.certificate`)
@@ -1542,6 +1611,8 @@ Customer feedback: "too many top-level menus" + "configuration is unorganized".
- Settings → Fusion Plating → Plating Landing Page block (company default).
- `fusion_plating_configurator`'s earlier menu_fp_root override (action_fp_sale_orders direct) was removed — core's resolver now owns the routing.
- Pickable list is curated via inline `<field name="x_fc_pickable_landing" eval="True"/>` on action records — currently flagged: `action_fp_sale_orders`, `action_fp_quotations`, `action_fp_process_recipe`. Add more by tagging the relevant act_window record at its source.
- **`x_fc_pickable_landing` lives on `ir.actions.actions` (BASE)** so the picker dropdown on `res.users.x_fc_plating_landing_action_id` can offer BOTH act_window records (Sale Orders, Quotations, Process Recipes) AND client-action records (Manager Desk, Plant Kanban, Quality Dashboard). The picker Many2one points at `ir.actions.actions` (not `act_window`); the domain `[('x_fc_pickable_landing', '=', True)]` filters across all action types. `_render_resolved()` on the base dispatches to the correct subclass by `type`. **Pickable accessibility compute MUST be `sudo()`'d** — non-admin users (Technician, Sales Rep) lack read access on `ir.actions.actions` and opening their own Preferences dialog would AccessError otherwise; the per-user `check_access_rights` per-action still runs unprivileged so the picklist filters correctly. Tag a new landing candidate by adding `<field name="x_fc_pickable_landing" eval="True"/>` to its `<record>` definition — works regardless of whether the model is `ir.actions.act_window` or `ir.actions.client`.
- **Role-based dispatch** (Phase E): the resolver now reads `res.users` group membership and routes by precedence — Owner → Manager Desk; QM → Quality Dashboard; Manager → Manager Desk; Sales Manager → Sale Orders; Shop Manager → Plant Kanban/Workstation; Sales Rep → Quotations; Technician → Plant Kanban/Workstation. `_fp_workstation_action_for_layout()` reads `ir.config_parameter['fusion_plating_shopfloor.layout']` (v2 vs legacy) so flipping the flag retargets every Tech/Shop Manager on next page load. Per-user override still wins. Picklist domain is tightened via `res.users.accessible_landing_action_ids` (compute that runs `check_access_rights('read')` per pickable action) so a Tech can't pick "Manager Desk" they can't see.
### Phase 2 — Configuration sub-folder grouping (`fusion_plating` 19.0.11.1.0, commits `3641b78` + `62c1315` + `4671541`)

View File

@@ -0,0 +1,858 @@
# Fusion Plating — Permissions Overhaul (Phase 1)
**Date:** 2026-05-23
**Status:** Approved for implementation
**Owner module:** `fusion_plating` (with co-changes in 9 dependent modules)
**Brainstorm transcript:** session with @gsinghpal, 2026-05-23
**Linked plan:** TBD (writing-plans skill, next step)
---
## Problem Statement
The current Fusion Plating permission system has 12 `res.groups` defined across 6 modules. An audit (2026-05-23) found:
- **3 groups are zero-reference orphans** — Shop Manager, CGP Designated Official, Plating Legacy Menus
- **1 group is functionally orphaned** — Administrator (the 2 Python checks that reference it use a typo'd XML ID `_administrator` instead of `_admin`, so the gate never fires)
- The role dropdown in the user form lists 10 entries with confusing ordering (sequence ties at 50 and 60 cause Estimator/CGP Officer and Shop Manager/CGP DO to render in arbitrary alphabetical order)
- Default landing page is hardcoded to "Shop view" for everyone — Managers complain about being dumped into a Workstation tablet when they open the Plating app
- The landing-page picklist in user preferences offers only 3 options (Quotations, Sale Orders, Process Recipes) — missing Manager Desk, Plant View, Quality Dashboard
- Menu visibility relies on a mix of explicit `groups=` attributes and implicit action-level ACLs — fragile and inconsistent
This Phase 1 work consolidates the 12 groups into **8 well-defined roles**, fixes the landing-page UX with role-based defaults, and ships an Owner-only "Team" page for clean role assignment.
---
## Locked Decisions
| # | Question | Decision |
|---|---|---|
| Q1 | Quality Manager vs Manager — what quality permissions split? | **Option B** — Manager handles reactive Quality (NCR/Hold/Check/routine Cert/RMA). Quality Manager owns strategic Quality (CAPA closure, audit sign-off, FAIR/Nadcap signing, AVL approval, Customer Spec library, Doc Control approval, all CGP). |
| Q2 | CGP/Aerospace/Nuclear verticals — fold or keep as add-on flags? | **Option A** — All vertical ACLs gate on Quality Manager. CGP Officer group dropped (folds into QM). CGP Designated Official becomes `res.company.x_fc_cgp_designated_official_id` field (Many2one to res.users, domain `[Owner, QM]`). Aerospace/Nuclear/Safety unchanged (already on Manager backbone). |
| Q3 | Landing page per role — hardcoded, configurable, or seeded? | **Option B** — Hardcoded role→action mapping in the resolver. Per-user override stays in preferences. Company default stays as a final fallback. |
| Q4 | Owner-only Permissions config page — yes/no, and what does it configure? | **Yes, Interpretation A only** — Owner-only "Team" page for role assignment, designated officials, and audit log. NO permission-definition editing (Interpretation B explicitly killed — would defeat the 8-role spec). |
| Q5 | Migration of existing users — auto-map or force manual? | **Option B** — Dry-run preview + Owner approval. Auto-map runs on `-u`, creates a `fp.migration.preview` in `pending` state, schedules a `mail.activity` on every Owner. Migration only applies after Owner clicks "Approve & Run". 30-day rollback window via archived old groups. |
| Q4b | Menu/submenu/field visibility — explicit `groups=` or inherit from parent? | **Confirmed by user pre-spec-write** — All three layers (top-level menus, submenus, fields/buttons) get explicit `groups=` matching the new roles. No reliance on action-level ACLs for menu visibility. |
---
## Section 1 — Role Hierarchy & XML IDs
### The 8 new roles
All under the existing `fusion_plating.res_groups_privilege_fusion_plating` privilege block (same place in the user form). Sequence numbers picked uniquely to avoid the current audit's "tied at 50/60" rendering bug.
| Seq | Display Name | XML ID | Implies | Auto-assigned to |
|---:|---|---|---|---|
| 10 | Technician | `fusion_plating.group_fp_technician` | `base.group_user` | — |
| 20 | Sales Representative | `fusion_plating.group_fp_sales_rep` | `base.group_user` | — |
| 30 | Shop Manager | `fusion_plating.group_fp_shop_manager_v2` | Technician | — |
| 40 | Sales Manager | `fusion_plating.group_fp_sales_manager` | Sales Representative | — |
| 50 | Manager | `fusion_plating.group_fp_manager` | Shop Manager + Sales Manager | — |
| 60 | Quality Manager | `fusion_plating.group_fp_quality_manager` | Manager | — |
| 70 | Owner | `fusion_plating.group_fp_owner` | Quality Manager + `base.group_system` | uid 1, uid 2 |
| — | (No) | — implicit (no Fusion Plating group held) | — | — |
### Design notes
1. **`group_fp_shop_manager_v2` suffix** — the existing `fusion_plating_configurator.group_fp_shop_manager` (today's 0-ref label bundle) gets retired. Suffix `_v2` avoids xmlid collision during migration; we rename to `_shop_manager` in a follow-up housekeeping pass once old refs are confirmed dead.
2. **Sales branch and Shop branch are parallel** — both inherit only `base.group_user`. A Technician can't see Quotations; a Sales Rep can't see the Workstation. They cross-join at Manager.
3. **Diamond at Manager** — Manager implies BOTH Shop Manager AND Sales Manager; gets the union.
4. **"No" is the absence of any group** — no `res.groups` record needed. The Plating menu root gates on an OR of all 7 plating roles. An internal user with none sees no plating menu.
5. **Owner implies `base.group_system`** — replaces today's broken Administrator pattern. Owners get Settings access, can install modules, etc.
6. **Old groups stay defined post-migration but become "DEPRECATED" + auto-archived.** 30-day rollback window. `_cron_purge_expired_migrations` deletes them after 30 days.
7. **CGP Designated Official is no longer a group**`res.company.x_fc_cgp_designated_official_id` (Many2one to res.users, domain `[('groups_id', 'in', [QM_id, Owner_id])]`).
8. **Field Technician (`fusion_tasks.group_field_technician`) is untouched** — orthogonal to plating roles, separate privilege block.
### Hierarchy visual
```
base.group_user (Internal User)
├── (no plating group) = "No"
├── Technician [10]
│ └── Shop Manager v2 [30]
│ └── Manager [50] ←──┐ (diamond)
│ │
└── Sales Representative [20]│
└── Sales Manager [40] │
└── Manager [50] ←───┘
└── Quality Manager [60]
└── Owner [70] (also implies base.group_system)
```
---
## Section 2 — ACL Re-gating Plan
### 2.A Standard mapping pattern
Applies to ~80% of the ~475 ACL refs mechanically:
| Old gate | New gate | Why |
|---|---|---|
| `group_fusion_plating_operator` | `group_fp_technician` | Pure rename |
| `group_fusion_plating_supervisor` | `group_fp_shop_manager_v2` | Supervisor's daily-floor leadership IS Shop Manager's job |
| `group_fusion_plating_manager` | `group_fp_manager` | Pure rename |
| `group_fp_estimator` | `group_fp_sales_rep` | Pure rename, but lose order-confirm (Section 2.B) |
| `group_fp_receiving` | `group_fp_shop_manager_v2` | Receiving folds in |
| `group_fp_accounting` | `group_fp_manager` | Accounting folds in |
| `group_fusion_plating_admin` | `group_fp_owner` | Pure rename + fixes `_administrator` typo bug |
Implied-chain handles the rest (e.g., Manager auto-gets everything Shop Manager has, so a model gated on Shop Manager is automatically accessible to Manager+).
### 2.B New gates ADDED
| Action | Today | New gate |
|---|---|---|
| `sale.order.action_confirm` | Any internal user | `group_fp_sales_manager` |
| `sale.order` set `x_fc_account_hold_override` | Manager (with `_administrator` typo) | `group_fp_manager` (clean) |
| `account.move.action_post` for FP-invoiced SOs | Implicit | `group_fp_manager` |
| Owner-only Team page menu | Doesn't exist | `group_fp_owner` |
Sales Reps can still save Sale Orders in `draft`; the confirm button is hidden in the view and the model-level gate raises `UserError` if called directly: *"Only Sales Manager or higher can confirm orders."*
### 2.C Quality split — Manager vs Quality Manager
| Model | Manager rights | QM-only rights |
|---|---|---|
| `fusion.plating.ncr` | CRUD + state transitions through `closed` | — |
| `fusion.plating.capa` | **Read + comment only** | CRUD + `action_close` + effectiveness verification |
| `fusion.plating.quality.hold` | CRUD + release | — |
| `fusion.plating.quality.check` | CRUD + pass/fail | — |
| `fp.certificate` (routine CoC, thickness) | CRUD + sign + issue + send | — |
| `fp.certificate` where `cert_type='fair'` | Read + create | Sign + issue (record rule on cert_type) |
| `fp.certificate` where `cert_type='nadcap'` | Read + create | Sign + issue (record rule on cert_type) |
| `fusion.plating.rma` | CRUD + authorise + resolve | — |
| `fusion.plating.audit` | Read | CRUD + close |
| `fusion.plating.customer.spec` | Read + attach to parts | CRUD (library curator) |
| `fp.approved.vendor.list` | Read | Add / approve / disqualify |
| `fp.contract.review` (QA-005) | Complete reviews assigned to them | Set QA Manager roster + override gates |
| Doc Control + Doc Approval | Read + request approval | Approve / supersede / retire |
| Calibration equipment | Log events + view schedule | Configure equipment + set intervals + dispose out-of-tolerance |
| **All `fp.cgp.*` models** (8 ACLs + 2 ir.rules) | None | All (entire CGP fold-in lands here) |
**Implementation note:** FAIR/Nadcap cert split uses an `ir.rule` on `fp.certificate` (domain `[('cert_type', 'in', ['fair','nadcap'])]`) restricted to QM for write. Routine CoCs (cert_type = `coc` or `thickness_report`) stay open to Manager.
### 2.D Verticals (Aerospace / Nuclear / Safety)
No change. Their ACLs already gate on `group_fusion_plating_manager` (now `group_fp_manager`). The standard mapping in 2.A covers them. No new vertical-specific gates needed.
### 2.E Three-layer menu / submenu / field hiding policy
**Rule:** if a user can't use it, they don't see it. No reliance on action-level ACLs for visibility — explicit `groups=` at every layer.
#### Layer 1 — Top-level menus
| Top-level menu | `groups=` |
|---|---|
| **Plating** (root) | OR of all 7 plating roles |
| **Sales & Quoting** | `group_fp_sales_rep` |
| **Shop Floor** | `group_fp_technician` |
| **Operations** | `group_fp_technician` |
| **Receiving & Shipping** | `group_fp_shop_manager_v2` |
| **Quality** | `group_fp_manager` |
| **Compliance** (hub) | `group_fp_quality_manager` |
| **KPIs** | `group_fp_manager` |
| **Configuration** | `group_fp_manager` |
#### Layer 2 — Submenus (explicit on every child)
| Submenu | New gate |
|---|---|
| Quality > Audits | `group_fp_quality_manager` |
| Quality > Customer Specs | `group_fp_quality_manager` |
| Quality > Approved Vendor List | `group_fp_quality_manager` |
| Quality > NCRs / Holds / Checks / RMAs / Certs | `group_fp_manager` |
| Quality > CAPAs | `group_fp_manager` (visibility); QM-only for close button (Layer 3) |
| Operations > Maintenance | `group_fp_shop_manager_v2` |
| Operations > Move Log | `group_fp_shop_manager_v2` |
| Operations > Labor History | `group_fp_shop_manager_v2` |
| Operations > Replenishment Suggestions | `group_fp_manager` |
| Configuration > Team | `group_fp_owner` |
| Configuration > Settings | `group_fp_manager` (explicit) |
| Configuration > all 7 themed folders | `group_fp_manager` (explicit) |
| Sales & Quoting > Configurator | `group_fp_sales_rep` |
| Sales & Quoting > Sale Orders | `group_fp_sales_rep` (visibility); SM+ for confirm (Layer 3) |
| Receiving & Shipping > all children | `group_fp_shop_manager_v2` |
| Compliance > CGP | `group_fp_quality_manager` |
| Compliance > General / Safety / Aerospace / Nuclear | `group_fp_quality_manager` |
#### Layer 3 — Fields, buttons, smart buttons
| View element | New gate |
|---|---|
| `sale.order` view — Confirm button | `group_fp_sales_manager` |
| `sale.order` view — `x_fc_account_hold_override` | `group_fp_manager` (was broken Administrator typo) |
| `sale.order` form — pricing columns on lines | `group_fp_sales_rep` (defense in depth — Technician/Shop Manager don't see pricing) |
| `fp.certificate` form — Sign button (FAIR / Nadcap) | `group_fp_quality_manager` |
| `fusion.plating.capa` form — Close button + edit fields | `group_fp_quality_manager` |
| `fusion.plating.audit` form — all buttons | `group_fp_quality_manager` |
| `fp.approved.vendor.list` form — Approve / Disqualify | `group_fp_quality_manager` |
| `fusion.plating.customer.spec` form — edit fields | `group_fp_quality_manager` |
| All CGP form buttons | `group_fp_quality_manager` |
| Smart buttons (cross-record navigation) | Match the underlying action's visibility |
### 2.F Per-role menu visibility matrix (sanity check)
| Menu | No | Tech | SR | SM | SalesMgr | Mgr | QM | Owner |
|---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| Plating (root) | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Sales & Quoting | — | — | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| Shop Floor | — | ✓ | — | ✓ | — | ✓ | ✓ | ✓ |
| Operations | — | ✓ | — | ✓ | — | ✓ | ✓ | ✓ |
| Receiving & Shipping | — | — | — | ✓ | — | ✓ | ✓ | ✓ |
| Quality | — | — | — | — | — | ✓ | ✓ | ✓ |
| Compliance | — | — | — | — | — | — | ✓ | ✓ |
| KPIs | — | — | — | — | — | ✓ | ✓ | ✓ |
| Configuration | — | — | — | — | — | ✓ | ✓ | ✓ |
| Configuration > Team | — | — | — | — | — | — | — | ✓ |
(SR = Sales Rep, SM = Shop Manager, SalesMgr = Sales Manager, Mgr = Manager)
### 2.G Manager-bypass context flags (no ownership change)
All 9 existing bypass flags from the battle tests remain gated on Manager+:
`fp_skip_step_gate`, `fp_skip_qc_gate`, `fp_skip_qty_reconcile`, `fp_skip_bake_gate`, `fp_skip_predecessor_check`, `fp_skip_missed_window`, `fp_skip_required_inputs_gate`, `fp_skip_signoff_gate`, `fp_skip_transition_form`.
New name in the check: `user.has_group('fusion_plating.group_fp_manager')`.
Shop Manager CANNOT bypass these gates — matches spec ("Technicians cannot override system"). Override authority sits at Manager.
### 2.H ir.rules (record rules)
| Rule | Old | New |
|---|---|---|
| `fp.cgp.psa` Officer-only | CGP Officer | `group_fp_quality_manager` |
| `fp.cgp.security.incident` Officer-only | CGP Officer | `group_fp_quality_manager` |
| `fusion.technician.task` Field-Tech-own | Field Technician | Unchanged (orthogonal) |
| **NEW:** `fp.certificate` write-gate for cert_type in ('fair','nadcap') | — | `group_fp_quality_manager` |
| **NEW:** `sale.order` write-gate for state→'sale' transition | — | `group_fp_sales_manager` |
No multi-company changes — existing multi-company rules untouched.
---
## Section 3 — Landing Resolver
### Resolver flow (server action `action_fp_resolve_plating_landing`)
```python
def _fp_resolve_landing(self):
user = self.env.user
company = self.env.company
# 1. Per-user override (set in preferences)
if user.x_fc_plating_landing_action_id:
return user.x_fc_plating_landing_action_id._render_action()
# 2. Role-based default (precedence: highest role wins)
role_landing = self._fp_role_default_landing(user, company)
if role_landing:
return role_landing._render_action()
# 3. Company default (admin fallback)
if company.x_fc_default_landing_action_id:
return company.x_fc_default_landing_action_id._render_action()
# 4. Hardcoded last-ditch
return self.env.ref('fusion_plating_configurator.action_fp_sale_orders')._render_action()
```
### Role → action mapping (Step 2)
```python
def _fp_role_default_landing(self, user, company):
workstation_action = self._fp_workstation_action_for_layout(company)
if user.has_group('fusion_plating.group_fp_owner'):
return self.env.ref('fusion_plating_shopfloor.action_fp_manager_dashboard',
raise_if_not_found=False)
if user.has_group('fusion_plating.group_fp_quality_manager'):
return self.env.ref('fusion_plating_quality.action_fp_quality_dashboard',
raise_if_not_found=False)
if user.has_group('fusion_plating.group_fp_manager'):
return self.env.ref('fusion_plating_shopfloor.action_fp_manager_dashboard',
raise_if_not_found=False)
if user.has_group('fusion_plating.group_fp_sales_manager'):
return self.env.ref('fusion_plating_configurator.action_fp_sale_orders',
raise_if_not_found=False)
if user.has_group('fusion_plating.group_fp_shop_manager_v2'):
return workstation_action
if user.has_group('fusion_plating.group_fp_sales_rep'):
return self.env.ref('fusion_plating_configurator.action_fp_quotations',
raise_if_not_found=False)
if user.has_group('fusion_plating.group_fp_technician'):
return workstation_action
return False
```
### Workstation = layout-flag aware (single source of truth)
```python
def _fp_workstation_action_for_layout(self, company):
"""Single source of truth: which Shop Floor surface is active on this DB?"""
param = self.env['ir.config_parameter'].sudo().get_param(
'fusion_plating_shopfloor.layout', 'v2')
if param == 'v2':
return self.env.ref('fusion_plating_shopfloor.action_fp_plant_kanban',
raise_if_not_found=False)
return self.env.ref('fusion_plating_shopfloor.action_fp_shopfloor_landing',
raise_if_not_found=False)
```
Flipping `ir.config_parameter['fusion_plating_shopfloor.layout']` instantly changes the default landing for every Technician and Shop Manager on the next page load.
### Pickable actions (`x_fc_pickable_landing=True`)
Adding 4 net-new (3 are already pickable). Total picklist = **7 entries**.
| Action XML ID | Display in dropdown | Default for |
|---|---|---|
| `fusion_plating_shopfloor.action_fp_manager_dashboard` | Manager Desk | Owner / QM / Manager |
| `fusion_plating_shopfloor.action_fp_plant_kanban` | Plant View Kanban | Shop Mgr / Tech (v2 layout) |
| `fusion_plating_shopfloor.action_fp_shopfloor_landing` | Workstation (Legacy) | Shop Mgr / Tech (legacy layout) |
| `fusion_plating_quality.action_fp_quality_dashboard` | Quality Dashboard | QM |
| `fusion_plating_configurator.action_fp_quotations` | Quotations | Sales Rep (already pickable) |
| `fusion_plating_configurator.action_fp_sale_orders` | Sale Orders | Sales Manager (already pickable) |
| `fusion_plating.action_fp_process_recipe` | Process Recipes | (niche option, already pickable) |
### Per-user override picklist domain
Today: `[('x_fc_pickable_landing', '=', True)]`. **Tightened**: also filter by user's accessible actions, so a Technician can't pick "Manager Desk" as their landing if they can't see it.
Domain becomes computed: `[('x_fc_pickable_landing', '=', True), ('id', 'in', user_accessible_action_ids)]`. The `user_accessible_action_ids` list comes from a compute that runs `env['ir.ui.menu']._visible_menu_ids()` mapped to action IDs.
### Edge cases
1. **Multi-role user (Manager promoted to QM):** precedence chain picks higher role. Deterministic.
2. **User in "No" state opening resolver directly:** falls through to company default → hardcoded Sale Orders → standard Odoo home.
3. **xmlref deleted or module uninstalled:** `raise_if_not_found=False` returns False, resolver falls through.
4. **First-login user with no preference / company default / roles:** lands on Sale Orders.
5. **Demo / fresh DB:** Sale Orders fallback works without any FP modules beyond core.
---
## Section 4 — Owner-only Team Page
### Implementation — standard Odoo views, not custom OWL
Single new field on `res.users` + standard kanban/form views. Zero custom JS.
```python
# fusion_plating/models/res_users.py
class ResUsers(models.Model):
_inherit = 'res.users'
x_fc_plating_role = fields.Selection([
('no', 'No'),
('technician', 'Technician'),
('sales_rep', 'Sales Representative'),
('shop_manager', 'Shop Manager'),
('sales_manager', 'Sales Manager'),
('manager', 'Manager'),
('quality_manager', 'Quality Manager'),
('owner', 'Owner'),
], compute='_compute_plating_role',
inverse='_inverse_plating_role',
store=True,
string='Fusion Plating Role')
```
- **Compute** reads `groups_id`, returns the highest-precedence plating role
- **Inverse** clears all plating groups + writes only the chosen one + posts a `Markup()` chatter audit
- **Stored** so kanban `default_group_by="x_fc_plating_role"` and drag-and-drop work
### Menu placement
```
Plating
└── Configuration (Manager+)
└── ⚡ Settings (existing)
└── 👥 Team (NEW — Owner-only)
└── (opens action_fp_team)
```
XML ID: `fusion_plating.menu_fp_team`, `groups="fusion_plating.group_fp_owner"`.
### 4 tabs
| Tab | View type | Domain | What it does |
|---|---|---|---|
| **Active Team** | Kanban grouped by `x_fc_plating_role` | `[('share','=',False), ('active','=',True)]` | 8 columns; drag-and-drop role changes; click card → user form |
| **Designated Officials** | Form on `res.company` | — | CGP DO + Nadcap Authority Many2one fields |
| **Role Reference** | QWeb static template | — | 8 cards with plain-English "can / cannot" per role |
| **Audit Log** | List on `mail.message` | `[('model','=','res.users'), ('subtype_id','=',mt_note), ('body','ilike','plating role')]` | 90-day role-change history |
All 4 tabs are separate `ir.actions.act_window` records reached via a tabbed notebook. Each has its own xmlid for direct linking.
### Active Team kanban — card layout
```
┌─────────────────────────────────┐
│ [avatar] Jane Doe │
│ jdoe@enplating.com │
│ Last seen: 2h ago │
│ ───────────────────────────── │
│ ⭐ CGP DO │ (only if user.id == company.x_fc_cgp_designated_official_id)
│ 🏆 Nadcap Authority │ (only if user.id == company.x_fc_nadcap_authority_user_id)
│ ───────────────────────────── │
│ Created: 2025-03-14 │
│ Last role change: 2026-05-01 │
└─────────────────────────────────┘
```
Columns (left-to-right by sequence):
`No` · `Technician` · `Sales Rep` · `Shop Manager` · `Sales Manager` · `Manager` · `QM` · `Owner`
Folded by default: `No`, `Sales Rep` (less common in plating shops). Owner can unfold.
Search: name / email / department. Filters: Active (default), With Archived, Has Login Last 30 Days, Has Never Logged In.
### Designated Officials tab — single form
Form on `res.company` with two fields:
- `x_fc_cgp_designated_official_id` (Many2one res.users, domain `[QM, Owner]`)
- `x_fc_nadcap_authority_user_id` (Many2one res.users, domain `[QM, Owner]`)
Save posts to `res.company` chatter for auditability:
> "CGP Designated Official changed: Jane Doe → John Smith by owner@enplating.com on 2026-05-23."
### Role Reference tab — auto-generated
Single source of truth in `fusion_plating/models/res_users.py`:
```python
PLATING_ROLE_DESCRIPTIONS = {
'technician': {
'icon': 'fa-wrench',
'tagline': 'Runs the shop floor.',
'can': [
'See and operate the Workstation tablet',
'Start/finish/pause job steps',
'Capture quality checks and step inputs',
'Issue routine Certificates of Conformance',
'Log scrap and bake events',
],
'cannot': [
'See pricing, quotations, or sales orders',
'Edit recipes or process configurations',
'Override system gates (predecessor lock, signoff, bake window, etc.)',
'Approve CAPAs or sign FAIR/Nadcap certs',
],
},
# ... 7 more entries
}
```
QWeb tab renders cards from this dict. Same dict used by the spec doc generator and by future onboarding wizards.
### What the page does NOT do
- ❌ No editing individual permissions (Q4 Interpretation B — killed)
- ❌ No custom role definitions
- ❌ No per-user exception flags
- ❌ No role hand-off workflows (transfer Bob's open jobs to Alice) — Phase 3
- ❌ No bulk import of roles — defer until 100+ employee shops
### Phase 2 hooks (designed-in, not built)
The 4-tab structure leaves room for:
- **Audit Dashboard** tab — read-only ACL matrix for CGP/Nadcap audit prep
- **Departure Handoff** tab — wizard for terminating users
---
## Section 5 — Migration Workflow
### Models
```python
# fusion_plating/models/fp_migration.py
class FpMigrationPreview(models.Model):
_name = 'fp.migration.preview'
_description = 'Fusion Plating Role Migration Preview'
_order = 'create_date desc'
name = fields.Char(default=lambda s: _('Migration %s') % fields.Datetime.now())
state = fields.Selection([
('pending', 'Pending Review'),
('approved', 'Approved & Applied'),
('cancelled', 'Cancelled'),
('rolled_back','Rolled Back'),
], default='pending', tracking=True)
line_ids = fields.One2many('fp.migration.preview.line', 'preview_id')
user_count = fields.Integer(compute='_compute_counts', store=True)
warning_count = fields.Integer(compute='_compute_counts', store=True)
approved_by_id = fields.Many2one('res.users', readonly=True)
approved_at = fields.Datetime(readonly=True)
rollback_deadline = fields.Datetime(compute='_compute_rollback_deadline')
class FpMigrationPreviewLine(models.Model):
_name = 'fp.migration.preview.line'
_description = 'Migration Preview Line'
preview_id = fields.Many2one('fp.migration.preview', required=True, ondelete='cascade')
user_id = fields.Many2one('res.users', required=True)
current_groups = fields.Char(compute='_compute_current_groups')
proposed_role = fields.Selection(_FP_ROLE_SELECTION)
capability_delta = fields.Char()
warning = fields.Boolean()
notes = fields.Text()
applied_groups_snapshot = fields.Text() # JSON of pre-migration groups_id for rollback
```
### Trigger — runs ONCE on `-u`, enters pending
```python
# fusion_plating/__manifest__.py
'post_init_hook': '_fp_post_init_role_migration',
# fusion_plating/__init__.py
def _fp_post_init_role_migration(env):
"""Idempotent: only creates a preview if one isn't already pending."""
pending = env['fp.migration.preview'].search([('state','=','pending')], limit=1)
if pending:
return
completed = env['fp.migration.preview'].search([('state','=','approved')], limit=1)
if completed:
users = env['res.users'].search(_fp_unmigrated_user_domain(env))
if not users:
return
preview = env['fp.migration.preview'].create({})
preview._fp_build_lines()
preview._fp_notify_owners()
```
Properties:
1. **Idempotent**`-u` re-runs don't duplicate previews
2. **Non-destructive** — only creates preview, never touches users
3. **Owner-gated** — actual migration only on Owner click
### Mapping table (in code)
```python
_FP_ROLE_MAPPING = [
# (predicate_fn, new_role, capability_delta_or_None)
(lambda u: u.id in (1, 2), 'owner', None),
(lambda u: u.has_group('fusion_plating.group_fusion_plating_admin'), 'owner', None),
(lambda u: u.has_group('fusion_plating_cgp.group_fusion_plating_cgp_designated_official'),
'owner', 'Was CGP DO; field set on res.company'),
(lambda u: u.has_group('fusion_plating_cgp.group_fusion_plating_cgp_officer'),
'quality_manager', None),
(lambda u: u.has_group('fusion_plating.group_fusion_plating_manager'),
'manager', None),
(lambda u: u.has_group('fusion_plating_configurator.group_fp_shop_manager'),
'manager', None),
(lambda u: u.has_group('fusion_plating_invoicing.group_fp_accounting'),
'manager', None),
(lambda u: u.has_group('fusion_plating_configurator.group_fp_estimator')
and not u.has_group('fusion_plating.group_fusion_plating_manager'),
'sales_rep', 'Loses order-confirm authority'), # ⚠️
(lambda u: u.has_group('fusion_plating.group_fusion_plating_supervisor'),
'shop_manager', None),
(lambda u: u.has_group('fusion_plating_receiving.group_fp_receiving'),
'shop_manager', None),
(lambda u: u.has_group('fusion_plating.group_fusion_plating_operator'),
'technician', None),
(lambda u: True, 'no', None),
]
```
First matching predicate wins (highest-precedence first).
### Preview screen UX
```
┌──────────────────────────────────────────────────────────────────────┐
│ Fusion Plating Role Migration — Preview │
│ Created: 2026-05-23 14:22 by system upgrade │
│ State: Pending Review │
│ │
│ Summary: │
│ • 28 users will be migrated │
│ • 2 will lose capabilities (highlighted ⚠️) │
│ • 1 will become CGP Designated Official (Jane Doe) │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐│
│ │ User │ Current Groups │ → New Role │ Notes ││
│ ├──────────────────────────────────────────────────────────────────┤│
│ │ admin │ Administrator, … │ Owner │ ││
│ │ Jane Doe │ Manager, CGP DO │ Owner │ DO set ││
│ │ John Smith │ Estimator │ Sales Rep │ ⚠️ loses││
│ │ │ │ │ confirm ││
│ │ Carlos Lopez │ Operator │ Technician │ ││
│ │ Bob Chen │ Supervisor, Receiving │ Shop Mgr │ ││
│ │ … 23 more … ││
│ └──────────────────────────────────────────────────────────────────┘│
│ │
│ [ Approve & Run ] [ Cancel ] [ Export to CSV ] │
└──────────────────────────────────────────────────────────────────────┘
```
Per-line `Edit role to:` dropdown lets Owner override any auto-mapping inline before approving.
### Approve & Run
```python
def action_approve_and_run(self):
self.ensure_one()
if not self.env.user.has_group('fusion_plating.group_fp_owner'):
raise UserError(_('Only Owners can approve role migrations.'))
for line in self.line_ids:
user = line.user_id
line.applied_groups_snapshot = json.dumps(user.groups_id.ids)
old_group_ids = self.env['res.groups'].search([
('id', 'in', _FP_OLD_GROUP_IDS(self.env))]).ids
user.write({'groups_id': [(3, gid) for gid in old_group_ids]})
target_group = self.env.ref(_NEW_ROLE_XMLID[line.proposed_role])
if target_group:
user.write({'groups_id': [(4, target_group.id)]})
user.message_post(body=Markup(_(
'Plating role assigned by migration: <b>%s</b>'
)) % line.proposed_role, message_type='notification')
if line.notes and 'CGP DO' in line.notes:
user.company_id.x_fc_cgp_designated_official_id = user.id
self.write({
'state': 'approved',
'approved_by_id': self.env.user.id,
'approved_at': fields.Datetime.now(),
})
```
### Rollback — 30-day undo
```python
def action_rollback(self):
self.ensure_one()
if self.state != 'approved':
raise UserError(_('Only approved migrations can be rolled back.'))
if fields.Datetime.now() > self.rollback_deadline:
raise UserError(_('Rollback window has expired (30 days after approval).'))
for line in self.line_ids:
if line.applied_groups_snapshot:
old_ids = json.loads(line.applied_groups_snapshot)
line.user_id.write({'groups_id': [(6, 0, old_ids)]})
self.state = 'rolled_back'
def _cron_purge_expired_migrations(self):
deadline = fields.Datetime.now() - timedelta(days=30)
expired = self.search([
('state', '=', 'approved'),
('approved_at', '<', deadline)])
for preview in expired:
preview.line_ids.write({'applied_groups_snapshot': False})
self.env['res.groups'].browse(_FP_OLD_GROUP_IDS(self.env)).unlink()
```
### Owner activity notification
```python
def _fp_notify_owners(self):
owners = self.env['res.users'].search([
('groups_id', 'in', self.env.ref('fusion_plating.group_fp_owner').ids)])
for owner in owners:
self.env['mail.activity'].create({
'res_model_id': self.env.ref('fusion_plating.model_fp_migration_preview').id,
'res_id': self.id,
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
'summary': _('Review Fusion Plating role migration'),
'note': _('A role migration is pending review. %d users affected, %d with capability changes.') % (
self.user_count, self.warning_count),
'user_id': owner.id,
'date_deadline': fields.Date.today(),
})
```
### Failure modes handled
1. Approver isn't Owner → UserError, no changes
2. Approver clicks twice → second click no-ops (state check)
3. User deleted between dry-run and approval → skipped, logged
4. New group xmlid missing → migration aborts at that line, logs warning
5. Rollback past 30 days → UserError, point Owner at Settings → Users
6. Multiple Owners approve simultaneously → record lock; second sees "Already approved"
---
## Out of Scope (Phase 2+)
| Item | Why deferred | Trigger to build |
|---|---|---|
| Read-only Team page for Manager+ | Owner-only is sufficient for now; can add later as a filter+view variant | If Manager complains about not seeing org chart |
| Audit Dashboard (ACL matrix) | Useful for compliance audits but not blocking | First CGP/Nadcap audit preparation |
| Departure Handoff wizard | Useful when shop grows; one-off manual reassignment works for now | 50+ employee shop |
| Bulk CSV import of roles | Overkill for current shop size | 100+ employee shop |
| Per-permission override page (Interpretation B from Q4) | Killed — defeats the 8-role spec | Never — fundamentally against design |
| Sales Rep "own quotes only" record rule | Sales Reps see all quotes today; adding per-rep ownership is a separate feature | If client requests it |
| Role expiration / time-bound permissions | Out of scope for the consolidation | If contract-employee workflows emerge |
| Per-customer permission overrides | Out of scope | If multi-company tenancy is added |
---
## Acceptance Criteria
Phase 1 is complete when ALL of these are true on `entech`:
1. **Group inventory**: exactly 8 plating roles defined under the Fusion Plating privilege block, in the correct sequence order (10, 20, 30, 40, 50, 60, 70). No `_administrator` typo'd references remain in Python.
2. **Old groups archived**: `group_fusion_plating_operator`, `_supervisor`, `_manager`, `_admin`, `group_fp_estimator`, `_receiving`, `_accounting`, `_shop_manager`, `_cgp_officer`, `_cgp_designated_official`, `_legacy_menus` all set `active=False`. No user holds them post-migration.
3. **ACL coverage**: every `ir.model.access.csv` row that previously referenced an old plating group now references its mapped new group. `grep` for old group xmlids in CSV files returns zero results.
4. **Menu visibility**: opening Plating as each of the 7 roles shows the expected menu tree per Section 2.F. No "ghost" menus (visible but click → error).
5. **Landing resolver**: each role lands on the correct default action:
- Owner / Manager / QM → Manager Desk
- Sales Manager → Sale Orders
- Sales Rep → Quotations
- Shop Manager / Technician → Plant View Kanban (v2 layout) or Workstation (legacy)
- "No" user → company default → Sale Orders fallback
6. **Picklist contains 7 entries**, filtered per user's accessible actions.
7. **Team page reachable** at Plating → Configuration → Team. Drag-and-drop role change posts to user chatter. Visible to Owner only.
8. **Designated Officials field** on `res.company` set; CGP records gated to QM via ir.rule.
9. **Sales Manager + gate works**: Sales Rep saves SO in draft, sees no Confirm button, can't post via API. Sales Manager can confirm.
10. **Quality split works**: Manager can create/close NCRs but CAPAs are read-only for them. QM can close CAPAs, sign FAIR/Nadcap certs.
11. **Bypass flags**: Shop Manager cannot bypass any of the 9 gates; Manager can. Bypass posts chatter audit.
12. **Migration round-trip**: on a test DB, run `-u`, see pending preview, approve, see all users migrated, run rollback within 30 days, see all users restored to original groups.
13. **CLAUDE.md updated** with the new role names + which group implies which (canonical hierarchy doc).
---
## Files Affected (high-level count)
| Module | Files changed | Type |
|---|---|---|
| `fusion_plating` | ~15 | security XML, models (res.users, fp_migration), views (team page, settings), data (role-description dict), post-init hook, migration file |
| `fusion_plating_configurator` | ~8 | ACL CSV updates, view button gates, group XMLs to archive |
| `fusion_plating_invoicing` | ~3 | ACL CSV updates, group archive |
| `fusion_plating_receiving` | ~3 | ACL CSV updates, group archive |
| `fusion_plating_cgp` | ~5 | ACL CSV updates, ir.rule updates, group archives, ResCompany field for DO |
| `fusion_plating_quality` | ~6 | ACL CSV updates for QM/Manager split, ir.rules for FAIR/Nadcap, view button gates |
| `fusion_plating_aerospace` / `_nuclear` / `_safety` | ~3 each | ACL CSV updates (mechanical rename) |
| `fusion_plating_shopfloor` | ~3 | Landing resolver updates, picklist tagging on action_fp_manager_dashboard + action_fp_plant_kanban |
| `fusion_plating_jobs` | ~4 | Legacy menus group archive, ACL CSV updates |
| `fusion_plating_certificates` | ~2 | FAIR/Nadcap signing button gates |
**Estimated total: ~55 files**. Most are mechanical CSV updates (`grep`-and-replace pattern from Section 2.A).
---
## Migration Notes for entech
### Pre-deploy checklist
1. **Backup `admin` DB on entech** — full pg_dump before `-u` (rollback safety beyond the 30-day archive)
2. **Read `_FP_OLD_GROUP_IDS(env)` count** — log expected pre-migration group membership counts
3. **Confirm no other migration running**`SELECT count(*) FROM fp_migration_preview WHERE state='pending';` returns 0
### Deploy command
```bash
ssh pve-worker5 "pct exec 111 -- bash -c 'systemctl stop odoo && \
su - odoo -s /bin/bash -c \"/usr/bin/odoo -c /etc/odoo/odoo.conf -d admin \
-u fusion_plating,fusion_plating_configurator,fusion_plating_invoicing,\
fusion_plating_receiving,fusion_plating_cgp,fusion_plating_quality,\
fusion_plating_aerospace,fusion_plating_nuclear,fusion_plating_safety,\
fusion_plating_shopfloor,fusion_plating_jobs,fusion_plating_certificates \
--stop-after-init\" && systemctl start odoo'"
```
Bump every module version to `+0.1.0` to ensure the migration scripts fire.
### Post-deploy verification
```sql
-- Pending migration?
SELECT state, user_count, warning_count, create_date
FROM fp_migration_preview ORDER BY id DESC LIMIT 1;
-- Verify Owner activity scheduled
SELECT count(*) FROM mail_activity
WHERE res_model = 'fp.migration.preview'
AND date_deadline >= CURRENT_DATE;
```
Login as Owner → see activity in the home dashboard → click → review preview → approve.
### Post-approval verification
```sql
-- All users mapped to new roles?
SELECT u.login, ARRAY_AGG(g.name) AS groups
FROM res_users u
JOIN res_groups_users_rel r ON r.uid = u.id
JOIN res_groups g ON g.id = r.gid
WHERE g.privilege_id IS NOT NULL
GROUP BY u.id, u.login
ORDER BY u.login;
-- No one still holds old groups?
SELECT count(*) FROM res_groups_users_rel r
WHERE r.gid IN (SELECT id FROM res_groups WHERE name IN (
'Operator','Supervisor','Manager','Administrator','Estimator',
'Receiving','Accounting','Shop Manager','CGP Officer','CGP Designated Official'));
-- Expected: 0 (or low number of stale rows that the migration intentionally left)
-- CGP DO set?
SELECT name, x_fc_cgp_designated_official_id FROM res_company;
```
### Rollback plan
If migration goes wrong within 30 days:
1. Login as Owner → Plating → Configuration → migrations list (or direct URL `/odoo/action-fp.migration.preview`)
2. Click most recent approved migration
3. Click "Rollback" button → all users restored to pre-migration groups
4. Old plating groups remain active (archived after 30 days; rollback un-archives them)
If migration goes wrong AFTER 30 days (cron has purged):
1. Restore from pg_dump backup taken pre-deploy
2. File a follow-up issue to extend the rollback window if this happens repeatedly
---
## Open Risks
1. **Inverse handler on `x_fc_plating_role`** must be robust against partial state. If a user holds NO plating group and gets assigned to `manager`, the inverse adds Manager group; the compute then reads `manager`. If a user holds BOTH `manager` and `technician` somehow (e.g., bug), compute should pick the higher one and the inverse should clean up. Unit tests required for: assign role with no prior role, assign role overwriting prior role, assign 'no' role (should clear all plating groups).
2. **Group rename window**: between archive of old groups and unlink (30 days), the old XMLIDs are still resolvable via `env.ref`. Code that hardcodes old xmlids will keep working accidentally — caught only when groups are finally deleted. **Mitigation:** add a deprecation log to the old groups' `_check_company_auto` or a model-load-time grep to flag any old-xmlid usage that survived the migration.
3. **Landing-page action visibility**: if a new role's hardcoded default action (e.g. `action_fp_manager_dashboard` for Manager) is itself gated by a different group, the resolver returns it but the user gets a permission error on render. **Mitigation:** the picklist domain filter (Section 3) already checks user accessibility. Apply the same check inside `_fp_role_default_landing` — if the role's default action isn't accessible, fall through to the next step instead of returning it.
4. **Mail-template references**: a few mail templates reference Manager / Estimator by xmlid (e.g., notification routing). These must be updated in the same deploy or chatter routing breaks. Grep all `mail_template_*.xml` for old group xmlids during implementation.
5. **CLAUDE.md drift**: after deploy, the role hierarchy in CLAUDE.md must be updated. If skipped, future sessions will reason from stale assumptions. Mandatory part of the implementation plan.
---
## Status & Next Steps
- ✅ Brainstorm complete (5 questions answered + Q4b menu-hiding policy)
- ✅ Design doc written
- ⏳ Self-review (next)
- ⏳ User review of this spec
- ⏳ Invoke `writing-plans` skill to create the implementation plan
- ⏳ Execute implementation per the plan
- ⏳ Deploy + verify on entech
- ⏳ Update CLAUDE.md with new role hierarchy
---
*End of design document.*

View File

@@ -23,6 +23,8 @@ def post_init_hook(env):
3. Sub 12a — seed fp.step.template with starter library entries
derived from ENP-ALUM-BASIC if the library is currently empty.
4. Sub 12b — seed 4 starter rack tags if the registry is empty.
5. Phase H — create a pending fp.migration.preview if any user
still holds an old plating-role group + notify Owners.
"""
_seed_default_timezone(env)
_backfill_node_input_kind(env)
@@ -31,6 +33,40 @@ def post_init_hook(env):
_seed_rack_tags_if_empty(env)
_migrate_legacy_uom_columns(env)
_seed_starter_recipes_once(env)
_fp_post_init_role_migration(env)
def _fp_post_init_role_migration(env):
"""Idempotent: creates a fp.migration.preview if none is pending or applied.
Called automatically on `-u fusion_plating`. The preview enters 'pending'
state and schedules a mail.activity on every Owner. Owner must explicitly
click 'Approve & Run' to actually apply the migration.
"""
Preview = env['fp.migration.preview']
if Preview.search_count([('state', '=', 'pending')]):
return
if Preview.search_count([('state', '=', 'approved')]):
# Already migrated previously; only re-fire if any unmigrated user remains
# An unmigrated user is one who still holds an OLD plating group directly
# AND does NOT hold any NEW role group. The compute on res.users.x_fc_plating_role
# returns 'no' for users without any new group regardless of their old groups.
# Heuristic: if any active user still holds an old group, re-fire.
from .models.fp_role_constants import _FP_OLD_GROUP_XMLIDS
any_unmigrated = False
for xmlid in _FP_OLD_GROUP_XMLIDS:
old_grp = env.ref(xmlid, raise_if_not_found=False)
if not old_grp:
continue
if old_grp.users.filtered(lambda u: u.active and not u.share):
# Found at least one user still on an old group → re-fire
any_unmigrated = True
break
if not any_unmigrated:
return # All users migrated; nothing to do
preview = Preview.create({})
preview._fp_build_lines()
preview._fp_notify_owners()
def _seed_starter_recipes_once(env):

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating',
'version': '19.0.21.0.0',
'version': '19.0.21.1.2',
'category': 'Manufacturing/Plating',
'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.',
'description': """
@@ -80,6 +80,7 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
],
'data': [
'security/fp_security.xml',
'security/fp_security_v2.xml',
'security/ir.model.access.csv',
'data/fp_landing_data.xml',
'data/fp_sequence_data.xml',
@@ -114,6 +115,11 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
'views/fp_operator_certification_views.xml',
'views/res_config_settings_views.xml',
'views/fp_landing_views.xml',
# Phase F — Owner-only Team page + Designated Officials on res.company.
# Both reference menu_fp_config (Configuration root) and Phase 1
# role groups, all loaded earlier (fp_menu.xml + fp_security_v2.xml).
'views/fp_team_views.xml',
'views/res_company_views.xml',
'views/fp_work_centre_views.xml',
'views/fp_job_views.xml',
'views/fp_job_step_views.xml',
@@ -134,6 +140,12 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
# 'data/fp_recipe_anodize.xml',
# 'data/fp_recipe_chem_conversion.xml',
'data/fp_step_template_data.xml',
# Phase H — Owner-approval migration workflow.
# Views file declares the action + menu; cron declares the
# daily 30-day expiry purge. Both reference model_fp_migration_preview
# which Odoo's model autoload makes available before data load.
'views/fp_migration_views.xml',
'data/fp_migration_cron.xml',
],
'post_init_hook': 'post_init_hook',
'assets': {

View File

@@ -24,47 +24,14 @@
<field name="model_id" ref="base.model_res_users"/>
<field name="state">code</field>
<field name="code"><![CDATA[
# Resolve in priority order:
# 1. user.x_fc_plating_landing_action_id (per-user override)
# 2. company.x_fc_default_landing_action_id (company default)
# 3. Shop Floor plant-view kanban (when x_fc_shopfloor_layout='v2')
# 4. Sale Orders (when v2 flag unset / legacy)
# 5. Process recipes (configurator absent)
user = env.user
target = False
if 'x_fc_plating_landing_action_id' in user._fields and user.x_fc_plating_landing_action_id:
target = user.x_fc_plating_landing_action_id.sudo()
elif 'x_fc_default_landing_action_id' in env.company._fields and env.company.x_fc_default_landing_action_id:
target = env.company.x_fc_default_landing_action_id.sudo()
if not target:
# 2026-05-23 — plant-view dispatch. Read the layout flag and pick the
# appropriate Shop Floor action. Falls through to Sale Orders if no
# client action is registered (e.g. shopfloor module not installed).
layout = env['ir.config_parameter'].sudo().get_param(
'fusion_plating_shopfloor.layout', default='legacy',
)
if layout == 'v2':
target = env.ref(
'fusion_plating_shopfloor.action_fp_plant_kanban',
raise_if_not_found=False,
)
# Legacy or v2-missing → fall through to Sale Orders
if not target:
target = env.ref(
'fusion_plating_configurator.action_fp_sale_orders',
raise_if_not_found=False,
)
if target:
action = target.sudo().read()[0]
# Strip ids that confuse the act_window dispatcher.
action.pop('id', None)
else:
# Last-ditch — open the Plating app's process recipes if even
# the Sale Orders action is missing (e.g. configurator not installed).
action = env.ref('fusion_plating.action_fp_process_recipe').sudo().read()[0]
action.pop('id', None)
# Delegates to the role-based dispatch helper on ir.actions.act_window
# (and ir.actions.client for Manager Desk / Plant Kanban / Quality Dashboard).
# Resolution chain in the helper:
# 1. user.x_fc_plating_landing_action_id (per-user override)
# 2. role-based default per spec Section 3 (Owner→ManagerDesk, etc.)
# 3. company.x_fc_default_landing_action_id (company default)
# 4. action_fp_sale_orders (hardcoded last-ditch)
action = env['ir.actions.act_window'].sudo()._fp_resolve_landing_for_current_user() or False
]]></field>
</record>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="ir_cron_purge_expired_migrations" model="ir.cron">
<field name="name">Fusion Plating: Purge Expired Role Migrations</field>
<field name="model_id" ref="model_fp_migration_preview"/>
<field name="state">code</field>
<field name="code">model._cron_purge_expired_migrations()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="active" eval="True"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
"""Phase H: fire role-migration preview creation on `-u fusion_plating`.
Odoo 19's `post_init_hook` ONLY fires on fresh install — never on
upgrade. So on entech (and any other already-installed deployment),
`-u fusion_plating` after this branch lands would otherwise leave the
post_init_hook's `_fp_post_init_role_migration` un-fired and the
migration preview never created.
This migration script bridges that gap: on every `-u` that crosses
this version boundary, it invokes the same idempotent helper. The
helper short-circuits if a preview is already pending or already
applied + all users migrated, so re-running is safe.
"""
import logging
_logger = logging.getLogger(__name__)
def migrate(cr, version):
from odoo import api, SUPERUSER_ID
env = api.Environment(cr, SUPERUSER_ID, {})
try:
from odoo.addons.fusion_plating import _fp_post_init_role_migration
_fp_post_init_role_migration(env)
_logger.info(
'Fusion Plating: role-migration preview check ran via post-migrate.py'
)
except Exception as e:
# Migration scripts must not block module upgrade — log and swallow
_logger.exception(
'Failed to run role-migration preview check (non-fatal): %s', e
)

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
"""Phase A bootstrap: rename old configurator's Shop Manager before new
core group_fp_shop_manager_v2 tries to claim the 'Shop Manager' display name.
Load order:
1. fusion_plating loads -> fp_security.xml renames its own old groups (Operator,
Supervisor, Manager, Administrator) to '[DEPRECATED] X'. Then fp_security_v2.xml
creates new groups (Technician, ..., Shop Manager v2 with display name 'Shop Manager').
2. fusion_plating_configurator loads later -> would rename its own
group_fp_shop_manager to '[DEPRECATED] Shop Manager'.
But step 1 crashes because the OLD configurator's group is still named just
'Shop Manager' in the DB (the rename in step 2 hasn't run yet), and the unique
constraint res_groups_name_uniq blocks the new 'Shop Manager'.
This pre-migrate script runs BEFORE any of fusion_plating's data files reload,
patching the old configurator row's display name via SQL. After that, the
constraint is clear and fp_security_v2.xml can create its new groups safely.
The configurator's later -u will then push the canonical '[DEPRECATED] Shop
Manager' display name from its XML data.
"""
import logging
_logger = logging.getLogger(__name__)
def migrate(cr, version):
# Find old configurator Shop Manager row via ir.model.data and rename
# its display name to avoid the constraint collision.
cr.execute("""
UPDATE res_groups
SET name = jsonb_build_object('en_US', '[DEPRECATED] Shop Manager (Mgr+Estimator bundle)')
WHERE id IN (
SELECT res_id FROM ir_model_data
WHERE module = 'fusion_plating_configurator'
AND name = 'group_fp_shop_manager'
AND model = 'res.groups'
)
AND (name IS NULL OR name->>'en_US' NOT LIKE '[DEPRECATED]%');
""")
rows = cr.rowcount
if rows:
_logger.info(
'Fusion Plating: pre-migrate renamed %d old configurator Shop Manager '
'row(s) to clear name collision with new group_fp_shop_manager_v2',
rows,
)

View File

@@ -25,6 +25,7 @@ from . import fp_job_step_timelog
from . import fp_operator_certification
from . import fp_tz
from . import res_company
from . import res_users
from . import res_config_settings
# Phase 1 (Sub 11) — relocated from fusion_plating_bridge_mrp via
@@ -48,3 +49,9 @@ from . import fp_job_step_move
# Phase 1 — Plating landing-page resolver
from . import fp_landing
# Phase H — dry-run + Owner-approval role migration workflow.
# fp_role_constants MUST be imported before fp_migration (the latter
# imports the predicate chain + xmlid maps from the former).
from . import fp_role_constants
from . import fp_migration

View File

@@ -2,45 +2,218 @@
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
"""Phase 1 — Plating landing-page resolver fields.
"""Phase 1 + Phase E — Plating landing-page resolver.
Three pieces:
1. `ir.actions.act_window.x_fc_pickable_landing` — Boolean tag. Mark a
curated set of plating actions (Sale Orders, Plant Overview,
Quotations, Quality Dashboard, Manager Dashboard, Tablet Station,
Labor History) so the landing-page dropdown only offers sensible
options, not all 200 act_window records in the DB.
Layers:
2. `res.company.x_fc_default_landing_action_id` — admin sets the
fallback for users who don't pick a preference.
1. ``ir.actions.act_window.x_fc_pickable_landing`` AND
``ir.actions.client.x_fc_pickable_landing`` — Boolean tag on BOTH
action types. Mark a curated set of plating actions (Sale Orders,
Quotations, Manager Desk, Plant Kanban, Quality Dashboard, etc.) so
the landing-page dropdown only offers sensible options, not all 200+
action records in the DB.
3. `res.users.x_fc_plating_landing_action_id` — each user's own
override.
2. ``res.company.x_fc_default_landing_action_id``admin sets the
fallback for users who don't pick a preference. References
``ir.actions.act_window`` (only act_window actions can be selected
as the company default since they're navigable from the menu tree).
The resolver server action (data/fp_landing_data.xml) reads these.
3. ``res.users.x_fc_plating_landing_action_id`` — each user's own
override. References ``ir.actions.act_window`` and is filtered by
the user's actually-accessible actions (Technician can't pick
"Manager Desk" if they can't see it).
4. ``ir.actions.act_window._fp_resolve_landing_for_current_user()`` —
role-based dispatch resolver. Section 3 of the permissions design
spec. Returns an action dict suitable for the
``action_fp_resolve_plating_landing`` server action.
"""
from odoo import fields, models
from odoo import api, fields, models
class IrActionsActWindow(models.Model):
_inherit = 'ir.actions.act_window'
# ----------------------------------------------------------------------
# Pickable-landing tag on BOTH action types
# ----------------------------------------------------------------------
# The picklist needs to cover client actions (Manager Desk, Plant
# Kanban, Quality Dashboard) too, so we add the same Boolean column
# to ir.actions.client. The resolver returns either kind of action;
# the role dispatch helper uses env.ref(...) which is type-agnostic.
class IrActionsActions(models.Model):
"""Base ir.actions.actions extension so x_fc_pickable_landing is
available on BOTH ir.actions.act_window (Sale Orders, Quotations,
Process Recipes) AND ir.actions.client (Manager Desk, Plant Kanban,
Workstation, Quality Dashboard). The picker on res.users / res.company
is Many2one('ir.actions.actions') so it accepts either kind.
"""
_inherit = 'ir.actions.actions'
x_fc_pickable_landing = fields.Boolean(
string='Pickable as Plating Landing',
default=False,
help='When True, this action appears in the Plating landing-'
'page dropdown on res.users and res.company. Tag a small '
'curated list (Sale Orders, Plant Overview, etc.) to keep '
'curated list (Sale Orders, Manager Desk, etc.) to keep '
'the picker manageable.',
)
def _render_resolved(self):
"""Dispatcher — render this action as a dict for the landing resolver.
Routes to the correct subclass based on `type` so both act_window
and client actions resolve correctly."""
self.ensure_one()
if self.type == 'ir.actions.client':
return self.env['ir.actions.client'].browse(self.id)._render_resolved()
if self.type == 'ir.actions.act_window':
return self.env['ir.actions.act_window'].browse(self.id)._render_resolved()
# URL / server / report — generic dict
action = self.sudo().read()[0]
action.pop('id', None)
action['xml_id'] = self.get_external_id().get(self.id) or None
return action
class IrActionsActWindow(models.Model):
_inherit = 'ir.actions.act_window'
# ------------------------------------------------------------------
# Resolver — role-based dispatch (Phase E)
# ------------------------------------------------------------------
@api.model
def _fp_resolve_landing_for_current_user(self):
"""Resolve which action to open when the current user clicks the
Plating app.
Priority order:
1. Per-user override (``res.users.x_fc_plating_landing_action_id``)
2. Role-based default (``_fp_role_default_landing``)
3. Company default (``res.company.x_fc_default_landing_action_id``)
4. Hardcoded last-ditch (Sale Orders)
"""
user = self.env.user
company = self.env.company
# 1. Per-user override
if 'x_fc_plating_landing_action_id' in user._fields \
and user.x_fc_plating_landing_action_id:
return user.x_fc_plating_landing_action_id._render_resolved()
# 2. Role-based default
role_action = self._fp_role_default_landing(user, company)
if role_action:
return role_action._render_resolved()
# 3. Company default
if 'x_fc_default_landing_action_id' in company._fields \
and company.x_fc_default_landing_action_id:
return company.x_fc_default_landing_action_id._render_resolved()
# 4. Hardcoded last-ditch — Sale Orders
fallback = self.env.ref(
'fusion_plating_configurator.action_fp_sale_orders',
raise_if_not_found=False,
)
if fallback:
return fallback._render_resolved()
return False
@api.model
def _fp_role_default_landing(self, user, company):
"""Return the per-role default action (recordset, act_window OR
ir.actions.client) for ``user``, or False.
Precedence is highest role first so a multi-role user
(Manager promoted to QM) gets the upper role's landing.
"""
workstation = self._fp_workstation_action_for_layout(company)
def safe(xmlid):
return self.env.ref(xmlid, raise_if_not_found=False)
if user.has_group('fusion_plating.group_fp_owner'):
return safe('fusion_plating_shopfloor.action_fp_manager_dashboard')
if user.has_group('fusion_plating.group_fp_quality_manager'):
return safe('fusion_plating_quality.action_fp_quality_dashboard')
if user.has_group('fusion_plating.group_fp_manager'):
return safe('fusion_plating_shopfloor.action_fp_manager_dashboard')
if user.has_group('fusion_plating.group_fp_sales_manager'):
return safe('fusion_plating_configurator.action_fp_sale_orders')
if user.has_group('fusion_plating.group_fp_shop_manager_v2'):
return workstation
if user.has_group('fusion_plating.group_fp_sales_rep'):
return safe('fusion_plating_configurator.action_fp_quotations')
if user.has_group('fusion_plating.group_fp_technician'):
return workstation
return False
@api.model
def _fp_workstation_action_for_layout(self, company):
"""Single source of truth: which Shop Floor surface is active on
this DB?
``ir.config_parameter['fusion_plating_shopfloor.layout']`` is the
feature flag. Flipping it instantly retargets every Technician /
Shop Manager landing on next page load.
"""
param = self.env['ir.config_parameter'].sudo().get_param(
'fusion_plating_shopfloor.layout', 'v2')
if param == 'v2':
return self.env.ref(
'fusion_plating_shopfloor.action_fp_plant_kanban',
raise_if_not_found=False,
)
return self.env.ref(
'fusion_plating_shopfloor.action_fp_shopfloor_landing',
raise_if_not_found=False,
)
def _render_resolved(self):
"""Render this act_window record as an action dict that the
landing server action can return.
Mirrors ``self.sudo().read()[0]`` shape, plus injects ``xml_id``
so the resolver / tests / breadcrumbs know which curated action
this is. Strips ``id`` because the act_window dispatcher chokes
on it for fresh-load actions.
"""
self.ensure_one()
action = self.sudo().read()[0]
action.pop('id', None)
action['xml_id'] = self.get_external_id().get(self.id) or None
return action
class IrActionsClient(models.Model):
"""Client actions also need to be tagged as pickable landings —
Manager Desk, Plant Kanban, Quality Dashboard are all client
actions, not act_window records.
``_render_resolved`` is defined on this class too so the resolver
can polymorphically call ``action._render_resolved()`` regardless
of which kind of action came back from env.ref().
"""
_inherit = 'ir.actions.client'
# x_fc_pickable_landing moved to ir.actions.actions base — see IrActionsActions
# above. This subclass keeps _render_resolved for the dispatcher to call.
def _render_resolved(self):
"""Render this client action as a dict for the landing resolver."""
self.ensure_one()
action = self.sudo().read()[0]
action.pop('id', None)
action['xml_id'] = self.get_external_id().get(self.id) or None
return action
# ----------------------------------------------------------------------
# Company + User landing-action preference fields
# ----------------------------------------------------------------------
class ResCompany(models.Model):
_inherit = 'res.company'
x_fc_default_landing_action_id = fields.Many2one(
'ir.actions.act_window',
'ir.actions.actions',
string='Default Plating Landing Page',
domain=[('x_fc_pickable_landing', '=', True)],
help='Page that opens when a user clicks the Plating app, '
@@ -53,9 +226,18 @@ class ResUsers(models.Model):
_inherit = 'res.users'
x_fc_plating_landing_action_id = fields.Many2one(
'ir.actions.act_window',
'ir.actions.actions',
string='My Plating Landing Page',
# Picker shows ALL pickable landing actions. Per-user accessibility
# filtering was attempted via a Many2many compute but failed for
# non-admin users because the field assignment requires read on
# ir.actions.actions. Easier path: show all 6 pickable actions to
# everyone, let the resolver fall through gracefully if the user
# picks an action they can't reach (role-based default takes over).
# Read access on ir.actions.actions for plating roles is granted
# via a fusion_plating ACL row (security/ir.model.access.csv).
domain=[('x_fc_pickable_landing', '=', True)],
help='Personal override for the page that opens when you click '
'the Plating app. When blank, follows the company default.',
'the Plating app. When blank, follows the company default '
'and then the role-based default per Section 3 of the spec.',
)

View File

@@ -0,0 +1,265 @@
# -*- coding: utf-8 -*-
"""Phase H — dry-run + Owner-approval migration workflow."""
import json
import logging
from datetime import timedelta
from markupsafe import Markup
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from .fp_role_constants import (
_FP_OLD_GROUP_XMLIDS,
_NEW_ROLE_XMLID,
fp_resolve_target_role,
)
_logger = logging.getLogger(__name__)
_ROLE_SELECTION = [
('no', 'No'),
('technician', 'Technician'),
('sales_rep', 'Sales Representative'),
('shop_manager', 'Shop Manager'),
('sales_manager', 'Sales Manager'),
('manager', 'Manager'),
('quality_manager', 'Quality Manager'),
('owner', 'Owner'),
]
class FpMigrationPreview(models.Model):
_name = 'fp.migration.preview'
_description = 'Fusion Plating Role Migration Preview'
_inherit = ['mail.thread']
_order = 'create_date desc'
name = fields.Char(
default=lambda s: _('Migration %s') % fields.Datetime.now(),
tracking=True,
)
state = fields.Selection(
[
('pending', 'Pending Review'),
('approved', 'Approved & Applied'),
('cancelled', 'Cancelled'),
('rolled_back', 'Rolled Back'),
],
default='pending',
required=True,
tracking=True,
)
line_ids = fields.One2many('fp.migration.preview.line', 'preview_id')
user_count = fields.Integer(compute='_compute_counts', store=True)
warning_count = fields.Integer(compute='_compute_counts', store=True)
approved_by_id = fields.Many2one('res.users', readonly=True)
approved_at = fields.Datetime(readonly=True)
rollback_deadline = fields.Datetime(compute='_compute_rollback_deadline')
@api.depends('line_ids', 'line_ids.warning')
def _compute_counts(self):
for rec in self:
rec.user_count = len(rec.line_ids)
rec.warning_count = sum(1 for ln in rec.line_ids if ln.warning)
@api.depends('approved_at')
def _compute_rollback_deadline(self):
for rec in self:
rec.rollback_deadline = (
rec.approved_at + timedelta(days=30) if rec.approved_at else False
)
def _fp_build_lines(self):
"""Walk all active internal users; one line per user with the
proposed role + capability_delta."""
self.ensure_one()
Line = self.env['fp.migration.preview.line']
users = self.env['res.users'].search([
('share', '=', False),
('active', '=', True),
])
vals_list = []
for user in users:
role, delta = fp_resolve_target_role(user)
vals_list.append({
'preview_id': self.id,
'user_id': user.id,
'proposed_role': role,
'capability_delta': delta or '',
'warning': bool(delta),
})
if vals_list:
Line.create(vals_list)
def _fp_notify_owners(self):
"""Schedule a 'Review Fusion Plating role migration' activity on
every Owner user. Idempotent — won't double-schedule."""
self.ensure_one()
owner_grp = self.env.ref('fusion_plating.group_fp_owner', raise_if_not_found=False)
if not owner_grp:
return
owners = owner_grp.user_ids.filtered(lambda u: u.active and not u.share)
if not owners:
_logger.warning('Fusion Plating migration preview %s: no Owner users to notify', self.id)
return
activity_type = self.env.ref('mail.mail_activity_data_todo')
for owner in owners:
existing = self.env['mail.activity'].search([
('res_model_id', '=', self.env.ref('fusion_plating.model_fp_migration_preview').id),
('res_id', '=', self.id),
('user_id', '=', owner.id),
], limit=1)
if existing:
continue
self.env['mail.activity'].create({
'res_model_id': self.env.ref('fusion_plating.model_fp_migration_preview').id,
'res_id': self.id,
'activity_type_id': activity_type.id,
'summary': _('Review Fusion Plating role migration'),
'note': _('%(n)d users affected, %(w)d with capability changes.') % {
'n': self.user_count,
'w': self.warning_count,
},
'user_id': owner.id,
'date_deadline': fields.Date.today(),
})
def action_approve_and_run(self):
self.ensure_one()
if not self.env.user.has_group('fusion_plating.group_fp_owner'):
raise UserError(_('Only Owners can approve role migrations.'))
if self.state != 'pending':
raise UserError(_(
'Migration is no longer pending - current state: %s'
) % self.state)
# Resolve old group ids once
old_group_ids = []
for xmlid in _FP_OLD_GROUP_XMLIDS:
g = self.env.ref(xmlid, raise_if_not_found=False)
if g:
old_group_ids.append(g.id)
for line in self.line_ids:
user = line.user_id
# Snapshot current group_ids for rollback
line.applied_groups_snapshot = json.dumps(user.group_ids.ids)
# Remove old plating-role groups
if old_group_ids:
user.sudo().write({
'group_ids': [(3, gid) for gid in old_group_ids]
})
# Add the new role group (no-op for 'no')
target_xmlid = _NEW_ROLE_XMLID.get(line.proposed_role)
if target_xmlid:
target = self.env.ref(target_xmlid, raise_if_not_found=False)
if target:
user.sudo().write({'group_ids': [(4, target.id)]})
# Audit chatter on the user
user.partner_id.message_post(
body=Markup(_(
'Plating role assigned by migration: <b>%s</b>'
)) % line.proposed_role,
message_type='notification',
)
# Special: CGP DO becomes a res.company field, not a role
if line.capability_delta and 'CGP DO' in line.capability_delta:
user.company_id.x_fc_cgp_designated_official_id = user.id
self.write({
'state': 'approved',
'approved_by_id': self.env.user.id,
'approved_at': fields.Datetime.now(),
})
def action_cancel(self):
self.ensure_one()
if self.state != 'pending':
raise UserError(_('Only pending migrations can be cancelled.'))
self.state = 'cancelled'
def action_rollback(self):
self.ensure_one()
if self.state != 'approved':
raise UserError(_('Only approved migrations can be rolled back.'))
if self.rollback_deadline and fields.Datetime.now() > self.rollback_deadline:
raise UserError(_(
'Rollback window has expired (30 days after approval). '
'Restore from pg_dump backup instead.'
))
for line in self.line_ids:
if line.applied_groups_snapshot:
old_ids = json.loads(line.applied_groups_snapshot)
line.user_id.sudo().write({'group_ids': [(6, 0, old_ids)]})
self.state = 'rolled_back'
@api.model
def _cron_purge_expired_migrations(self):
"""After 30 days, clear snapshots + unlink old plating groups.
Runs daily via fp_migration_cron.xml."""
deadline = fields.Datetime.now() - timedelta(days=30)
expired = self.search([
('state', '=', 'approved'),
('approved_at', '<', deadline),
])
if not expired:
return
# Clear snapshots (no more rollback possible)
for preview in expired:
preview.line_ids.write({'applied_groups_snapshot': False})
# Unlink old plating groups (now confirmed unused — every user is
# on the new groups; backward-compat implied_ids chains can drop)
old_group_ids = []
for xmlid in _FP_OLD_GROUP_XMLIDS:
g = self.env.ref(xmlid, raise_if_not_found=False)
if g:
old_group_ids.append(g.id)
if old_group_ids:
# I6 safety check — never unlink a group that still has active
# internal users on it. If anyone still references the group
# we'd cascade-strip them silently from their permissions.
safe_to_unlink = []
skipped = []
for old_group in self.env['res.groups'].browse(old_group_ids).exists():
active_users = old_group.user_ids.filtered(lambda u: u.active and not u.share)
if active_users:
skipped.append((old_group.name, active_users.mapped('login')))
else:
safe_to_unlink.append(old_group.id)
if skipped:
_logger.warning(
'Fusion Plating migration purge: skipped %d old groups with active users: %s',
len(skipped), skipped)
if safe_to_unlink:
self.env['res.groups'].browse(safe_to_unlink).unlink()
_logger.info('Fusion Plating migration: purged %d expired old plating groups',
len(safe_to_unlink))
class FpMigrationPreviewLine(models.Model):
_name = 'fp.migration.preview.line'
_description = 'Migration Preview Line'
preview_id = fields.Many2one('fp.migration.preview', required=True, ondelete='cascade')
user_id = fields.Many2one('res.users', required=True, ondelete='cascade')
current_groups = fields.Char(compute='_compute_current_groups')
proposed_role = fields.Selection(_ROLE_SELECTION)
capability_delta = fields.Char()
warning = fields.Boolean()
notes = fields.Text(help='Owner may annotate before approving')
applied_groups_snapshot = fields.Text(help='JSON of pre-migration group_ids for rollback')
@api.depends('user_id', 'user_id.group_ids')
def _compute_current_groups(self):
for line in self:
if line.user_id:
line.current_groups = ', '.join(line.user_id.group_ids.mapped('name'))
else:
line.current_groups = ''

View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
"""Single source of truth for migration mapping rules + old-group xmlids.
The mapping predicates are evaluated against res.users records. First match
wins (highest-precedence first). See spec Section 5 + plan Phase H.
"""
# Every plating role group xmlid that exists BEFORE the migration (deprecated
# but still defined for backward-compat during 30-day rollback window).
_FP_OLD_GROUP_XMLIDS = (
'fusion_plating.group_fusion_plating_operator',
'fusion_plating.group_fusion_plating_supervisor',
'fusion_plating.group_fusion_plating_manager',
'fusion_plating.group_fusion_plating_admin',
'fusion_plating_configurator.group_fp_estimator',
'fusion_plating_configurator.group_fp_shop_manager',
'fusion_plating_invoicing.group_fp_accounting',
'fusion_plating_receiving.group_fp_receiving',
'fusion_plating_cgp.group_fusion_plating_cgp_officer',
'fusion_plating_cgp.group_fusion_plating_cgp_designated_official',
'fusion_plating_jobs.group_fusion_plating_legacy_menus',
)
# New role -> the group xmlid to add when migration assigns this role.
# 'no' maps to None (no plating group added; old ones still get removed).
_NEW_ROLE_XMLID = {
'no': None,
'technician': 'fusion_plating.group_fp_technician',
'sales_rep': 'fusion_plating.group_fp_sales_rep',
'shop_manager': 'fusion_plating.group_fp_shop_manager_v2',
'sales_manager': 'fusion_plating.group_fp_sales_manager',
'manager': 'fusion_plating.group_fp_manager',
'quality_manager': 'fusion_plating.group_fp_quality_manager',
'owner': 'fusion_plating.group_fp_owner',
}
# Mapping rules: (label, predicate, new_role, capability_delta_or_None)
# Highest precedence first; first match wins.
# Predicate is a callable taking a res.users record; returns bool.
_FP_ROLE_MAPPING_RULES = [
# cgp_designated_official MUST be first so admin/uid_1/uid_2 users who ALSO
# hold the DO group still get the capability_delta marker — which is what
# triggers action_approve_and_run to set res.company.x_fc_cgp_designated_official_id.
# If admin matched first, the DO field would never get populated for shops
# where the admin is also the registered PSPC Designated Official.
('cgp_designated_official',
lambda u: u.has_group('fusion_plating_cgp.group_fusion_plating_cgp_designated_official'),
'owner', 'Was CGP DO; field set on res.company'),
('uid_1_or_2',
lambda u: u.id in (1, 2),
'owner', None),
('admin',
lambda u: u.has_group('fusion_plating.group_fusion_plating_admin'),
'owner', None),
('cgp_officer',
lambda u: u.has_group('fusion_plating_cgp.group_fusion_plating_cgp_officer'),
'quality_manager', None),
('manager',
lambda u: u.has_group('fusion_plating.group_fusion_plating_manager'),
'manager', None),
('shop_manager_old',
lambda u: u.has_group('fusion_plating_configurator.group_fp_shop_manager'),
'manager', None),
('accounting',
lambda u: u.has_group('fusion_plating_invoicing.group_fp_accounting'),
'manager', None),
('estimator_alone',
lambda u: (u.has_group('fusion_plating_configurator.group_fp_estimator')
and not u.has_group('fusion_plating.group_fusion_plating_manager')),
'sales_rep', 'Loses order-confirm authority'),
('supervisor',
lambda u: u.has_group('fusion_plating.group_fusion_plating_supervisor'),
'shop_manager', None),
('receiving',
lambda u: u.has_group('fusion_plating_receiving.group_fp_receiving'),
'shop_manager', None),
('operator',
lambda u: u.has_group('fusion_plating.group_fusion_plating_operator'),
'technician', None),
('catchall',
lambda u: True,
'no', None),
]
def fp_resolve_target_role(user):
"""Returns (role_key, capability_delta_or_None). First predicate match wins."""
for _label, predicate, role, delta in _FP_ROLE_MAPPING_RULES:
if predicate(user):
return role, delta
return 'no', None

View File

@@ -185,3 +185,28 @@ class ResCompany(models.Model):
'When BOTH are blank the report falls back to a hardcoded '
'AS9100/ISO 9001 statement.',
)
# =====================================================================
# Phase F — Plating Designated Officials
# =====================================================================
# These are SPECIFIC NAMED PEOPLE registered with regulatory bodies.
# Stored as Many2one to res.users so the link survives renames.
# View-level domain restricts the picker to Owner or Quality Manager
# group members (a Python-side domain would resolve groups by id at
# recordset load and is fragile across DB migrations).
x_fc_cgp_designated_official_id = fields.Many2one(
'res.users',
string='CGP Designated Official',
tracking=True,
help='Specific person registered with PSPC as Designated Official '
'under Defence Production Act §22. Must be Owner or Quality '
'Manager.',
)
x_fc_nadcap_authority_user_id = fields.Many2one(
'res.users',
string='Nadcap Authority',
tracking=True,
help='Specific person who signs Nadcap-specific certificates and '
'audits. Must be Owner or Quality Manager.',
)

View File

@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
"""Fusion Plating role helpers on res.users.
The x_fc_plating_role Selection field is a clean UX wrapper around the
seven plating-role groups. Owner-only Team page reads/writes this field
via drag-and-drop on a kanban grouped by role.
"""
from markupsafe import Markup
from odoo import _, api, fields, models
_FP_PLATING_ROLE_TO_GROUP_XMLID = {
'technician': 'fusion_plating.group_fp_technician',
'sales_rep': 'fusion_plating.group_fp_sales_rep',
'shop_manager': 'fusion_plating.group_fp_shop_manager_v2',
'sales_manager': 'fusion_plating.group_fp_sales_manager',
'manager': 'fusion_plating.group_fp_manager',
'quality_manager': 'fusion_plating.group_fp_quality_manager',
'owner': 'fusion_plating.group_fp_owner',
}
# Highest precedence first — first match wins
_FP_ROLE_PRECEDENCE = (
'owner', 'quality_manager', 'manager', 'sales_manager',
'shop_manager', 'sales_rep', 'technician',
)
class ResUsers(models.Model):
_inherit = 'res.users'
# Allow non-admin users to write their OWN plating-related fields
# from the standard User Preferences dialog. SELF_WRITEABLE_FIELDS is
# a @property in Odoo 19 (not a class attribute) — must override via
# @property + super(). See CLAUDE.md rule 13k.
@property
def SELF_WRITEABLE_FIELDS(self):
return super().SELF_WRITEABLE_FIELDS + [
'x_fc_plating_landing_action_id', # personal landing-page override
'x_fc_signature_image', # "Plating Signature" used on reports
]
@property
def SELF_READABLE_FIELDS(self):
return super().SELF_READABLE_FIELDS + [
'x_fc_plating_landing_action_id',
'x_fc_signature_image',
'x_fc_plating_role',
'x_fc_tablet_pin_set_date',
]
x_fc_plating_role = fields.Selection(
[
('no', 'No'),
('technician', 'Technician'),
('sales_rep', 'Sales Representative'),
('shop_manager', 'Shop Manager'),
('sales_manager', 'Sales Manager'),
('manager', 'Manager'),
('quality_manager', 'Quality Manager'),
('owner', 'Owner'),
],
compute='_compute_plating_role',
inverse='_inverse_plating_role',
store=True,
string='Fusion Plating Role',
help='Highest plating role currently held by this user. Changing this '
'field reassigns the user to the corresponding res.groups (clears '
'old plating groups, adds new). Posts an audit chatter message.',
)
@api.depends('group_ids')
def _compute_plating_role(self):
# Resolve xmlids once
role_to_group = {}
for role, xmlid in _FP_PLATING_ROLE_TO_GROUP_XMLID.items():
grp = self.env.ref(xmlid, raise_if_not_found=False)
if grp:
role_to_group[role] = grp
for user in self:
user.x_fc_plating_role = 'no'
for candidate in _FP_ROLE_PRECEDENCE:
grp = role_to_group.get(candidate)
if grp and grp in user.group_ids:
user.x_fc_plating_role = candidate
break
def _inverse_plating_role(self):
# Resolve all plating-role group ids
all_role_ids = []
role_to_group = {}
for role, xmlid in _FP_PLATING_ROLE_TO_GROUP_XMLID.items():
grp = self.env.ref(xmlid, raise_if_not_found=False)
if grp:
role_to_group[role] = grp
all_role_ids.append(grp.id)
# I4 fix — capture old roles BEFORE the cache mutates by reading
# the stored x_fc_plating_role column directly from PostgreSQL.
# `user._origin.x_fc_plating_role` returns the IN-CACHE new value
# (the assignment that triggered the inverse), not the prior DB
# value, so the chatter audit displayed "X -> X" instead of the
# actual old -> new transition.
self.env.cr.execute(
"SELECT id, x_fc_plating_role FROM res_users WHERE id IN %s",
(tuple(self.ids),) if self.ids else ((0,),),
)
old_role_by_id = dict(self.env.cr.fetchall())
for user in self:
old_role = old_role_by_id.get(user.id) or 'unset'
new_role = user.x_fc_plating_role
if old_role == new_role:
# No actual change — skip both the writes and the audit so
# we don't spam chatter with "X -> X" rows.
continue
# Remove every plating-role group (additive-by-default Odoo
# m2m write of (3, id) removes single rows)
user.sudo().write({
'group_ids': [(3, gid) for gid in all_role_ids]
})
# Add the chosen role (no-op for 'no')
if new_role and new_role != 'no':
target = role_to_group.get(new_role)
if target:
user.sudo().write({
'group_ids': [(4, target.id)]
})
# Post audit (Markup so role names render bold, not literal HTML)
user.partner_id.message_post(
body=Markup(_(
'Plating role changed: <b>%(old)s</b> -> <b>%(new)s</b> by %(actor)s'
)) % {
'old': old_role,
'new': new_role or 'unset',
'actor': self.env.user.name,
},
message_type='notification',
)

View File

@@ -32,7 +32,7 @@
<!-- Reads most reference data, writes chemistry logs. -->
<!-- ================================================================== -->
<record id="group_fusion_plating_operator" model="res.groups">
<field name="name">Operator</field>
<field name="name">[DEPRECATED] Operator</field>
<field name="sequence">10</field>
<field name="privilege_id" ref="res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
@@ -43,7 +43,7 @@
<!-- Can manage baths, schedule jobs, review logs. -->
<!-- ================================================================== -->
<record id="group_fusion_plating_supervisor" model="res.groups">
<field name="name">Supervisor</field>
<field name="name">[DEPRECATED] Supervisor</field>
<field name="sequence">20</field>
<field name="privilege_id" ref="res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[(4, ref('group_fusion_plating_operator'))]"/>
@@ -54,7 +54,7 @@
<!-- Full CRUD on configuration objects. -->
<!-- ================================================================== -->
<record id="group_fusion_plating_manager" model="res.groups">
<field name="name">Manager</field>
<field name="name">[DEPRECATED] Manager</field>
<field name="sequence">30</field>
<field name="privilege_id" ref="res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[(4, ref('group_fusion_plating_supervisor'))]"/>
@@ -65,7 +65,7 @@
<!-- Everything a Manager can do, plus system-level settings. -->
<!-- ================================================================== -->
<record id="group_fusion_plating_admin" model="res.groups">
<field name="name">Administrator</field>
<field name="name">[DEPRECATED] Administrator</field>
<field name="sequence">40</field>
<field name="privilege_id" ref="res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[(4, ref('group_fusion_plating_manager'))]"/>

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Phase 1 Permissions Overhaul: 8 consolidated roles -->
<!-- See docs/superpowers/specs/2026-05-23-permissions-overhaul-design.md -->
<!-- Cross-module implications (estimator, receiving, accounting, cgp_officer,
cgp_designated_official) live in the downstream modules' security files
to avoid fresh-install forward-ref errors. -->
<record id="group_fp_technician" model="res.groups">
<field name="name">Technician</field>
<field name="sequence">10</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('base.group_user')),
(4, ref('fusion_plating.group_fusion_plating_operator')),
]"/>
</record>
<record id="group_fp_sales_rep" model="res.groups">
<field name="name">Sales Representative</field>
<field name="sequence">20</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('base.group_user')),
]"/>
</record>
<record id="group_fp_shop_manager_v2" model="res.groups">
<field name="name">Shop Manager</field>
<field name="sequence">30</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_technician')),
(4, ref('fusion_plating.group_fusion_plating_supervisor')),
]"/>
</record>
<record id="group_fp_sales_manager" model="res.groups">
<field name="name">Sales Manager</field>
<field name="sequence">40</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_sales_rep')),
]"/>
</record>
<record id="group_fp_manager" model="res.groups">
<field name="name">Manager</field>
<field name="sequence">50</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_shop_manager_v2')),
(4, ref('group_fp_sales_manager')),
(4, ref('fusion_plating.group_fusion_plating_manager')),
]"/>
</record>
<record id="group_fp_quality_manager" model="res.groups">
<field name="name">Quality Manager</field>
<field name="sequence">60</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_manager')),
]"/>
</record>
<record id="group_fp_owner" model="res.groups">
<field name="name">Owner</field>
<field name="sequence">70</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
(4, ref('group_fp_quality_manager')),
(4, ref('fusion_plating.group_fusion_plating_admin')),
(4, ref('base.group_system')),
]"/>
<field name="user_ids" eval="[
(4, ref('base.user_root')),
(4, ref('base.user_admin')),
]"/>
</record>
</odoo>

View File

@@ -1,96 +1,99 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_process_category_operator,fp.process.category.operator,model_fusion_plating_process_category,group_fusion_plating_operator,1,0,0,0
access_fp_process_category_manager,fp.process.category.manager,model_fusion_plating_process_category,group_fusion_plating_manager,1,1,1,1
access_fp_process_type_operator,fp.process.type.operator,model_fusion_plating_process_type,group_fusion_plating_operator,1,0,0,0
access_fp_process_type_manager,fp.process.type.manager,model_fusion_plating_process_type,group_fusion_plating_manager,1,1,1,1
access_fp_bath_parameter_operator,fp.bath.parameter.operator,model_fusion_plating_bath_parameter,group_fusion_plating_operator,1,0,0,0
access_fp_bath_parameter_manager,fp.bath.parameter.manager,model_fusion_plating_bath_parameter,group_fusion_plating_manager,1,1,1,1
access_fp_facility_operator,fp.facility.operator,model_fusion_plating_facility,group_fusion_plating_operator,1,0,0,0
access_fp_facility_supervisor,fp.facility.supervisor,model_fusion_plating_facility,group_fusion_plating_supervisor,1,0,0,0
access_fp_facility_manager,fp.facility.manager,model_fusion_plating_facility,group_fusion_plating_manager,1,1,1,1
access_fp_work_center_operator,fp.work.center.operator,model_fusion_plating_work_center,group_fusion_plating_operator,1,0,0,0
access_fp_work_center_supervisor,fp.work.center.supervisor,model_fusion_plating_work_center,group_fusion_plating_supervisor,1,1,0,0
access_fp_work_center_manager,fp.work.center.manager,model_fusion_plating_work_center,group_fusion_plating_manager,1,1,1,1
access_fp_tank_operator,fp.tank.operator,model_fusion_plating_tank,group_fusion_plating_operator,1,0,0,0
access_fp_tank_supervisor,fp.tank.supervisor,model_fusion_plating_tank,group_fusion_plating_supervisor,1,1,0,0
access_fp_tank_manager,fp.tank.manager,model_fusion_plating_tank,group_fusion_plating_manager,1,1,1,1
access_fp_tank_section_operator,fp.tank.section.operator,model_fusion_plating_tank_section,group_fusion_plating_operator,1,0,0,0
access_fp_tank_section_supervisor,fp.tank.section.supervisor,model_fusion_plating_tank_section,group_fusion_plating_supervisor,1,1,0,0
access_fp_tank_section_manager,fp.tank.section.manager,model_fusion_plating_tank_section,group_fusion_plating_manager,1,1,1,1
access_fp_tank_composition_operator,fp.tank.composition.operator,model_fusion_plating_tank_composition,group_fusion_plating_operator,1,0,0,0
access_fp_tank_composition_supervisor,fp.tank.composition.supervisor,model_fusion_plating_tank_composition,group_fusion_plating_supervisor,1,1,1,0
access_fp_tank_composition_manager,fp.tank.composition.manager,model_fusion_plating_tank_composition,group_fusion_plating_manager,1,1,1,1
access_fp_tank_comp_ing_operator,fp.tank.composition.ingredient.operator,model_fusion_plating_tank_composition_ingredient,group_fusion_plating_operator,1,0,0,0
access_fp_tank_comp_ing_supervisor,fp.tank.composition.ingredient.supervisor,model_fusion_plating_tank_composition_ingredient,group_fusion_plating_supervisor,1,1,1,1
access_fp_tank_comp_ing_manager,fp.tank.composition.ingredient.manager,model_fusion_plating_tank_composition_ingredient,group_fusion_plating_manager,1,1,1,1
access_fp_bath_operator,fp.bath.operator,model_fusion_plating_bath,group_fusion_plating_operator,1,0,0,0
access_fp_bath_supervisor,fp.bath.supervisor,model_fusion_plating_bath,group_fusion_plating_supervisor,1,1,1,0
access_fp_bath_manager,fp.bath.manager,model_fusion_plating_bath,group_fusion_plating_manager,1,1,1,1
access_fp_bath_target_operator,fp.bath.target.operator,model_fusion_plating_bath_target,group_fusion_plating_operator,1,0,0,0
access_fp_bath_target_supervisor,fp.bath.target.supervisor,model_fusion_plating_bath_target,group_fusion_plating_supervisor,1,1,1,0
access_fp_bath_target_manager,fp.bath.target.manager,model_fusion_plating_bath_target,group_fusion_plating_manager,1,1,1,1
access_fp_bath_log_operator,fp.bath.log.operator,model_fusion_plating_bath_log,group_fusion_plating_operator,1,1,1,0
access_fp_bath_log_supervisor,fp.bath.log.supervisor,model_fusion_plating_bath_log,group_fusion_plating_supervisor,1,1,1,0
access_fp_bath_log_manager,fp.bath.log.manager,model_fusion_plating_bath_log,group_fusion_plating_manager,1,1,1,1
access_fp_bath_log_line_operator,fp.bath.log.line.operator,model_fusion_plating_bath_log_line,group_fusion_plating_operator,1,1,1,0
access_fp_bath_log_line_supervisor,fp.bath.log.line.supervisor,model_fusion_plating_bath_log_line,group_fusion_plating_supervisor,1,1,1,0
access_fp_bath_log_line_manager,fp.bath.log.line.manager,model_fusion_plating_bath_log_line,group_fusion_plating_manager,1,1,1,1
access_fp_process_node_operator,fp.process.node.operator,model_fusion_plating_process_node,group_fusion_plating_operator,1,0,0,0
access_fp_process_node_supervisor,fp.process.node.supervisor,model_fusion_plating_process_node,group_fusion_plating_supervisor,1,1,1,0
access_fp_process_node_manager,fp.process.node.manager,model_fusion_plating_process_node,group_fusion_plating_manager,1,1,1,1
access_fp_process_node_input_operator,fp.process.node.input.operator,model_fusion_plating_process_node_input,group_fusion_plating_operator,1,0,0,0
access_fp_process_node_input_supervisor,fp.process.node.input.supervisor,model_fusion_plating_process_node_input,group_fusion_plating_supervisor,1,1,1,0
access_fp_process_node_input_manager,fp.process.node.input.manager,model_fusion_plating_process_node_input,group_fusion_plating_manager,1,1,1,1
access_fp_rack_operator,fp.rack.operator,model_fusion_plating_rack,group_fusion_plating_operator,1,1,0,0
access_fp_rack_supervisor,fp.rack.supervisor,model_fusion_plating_rack,group_fusion_plating_supervisor,1,1,1,0
access_fp_rack_manager,fp.rack.manager,model_fusion_plating_rack,group_fusion_plating_manager,1,1,1,1
access_fp_replenishment_rule_operator,fp.replenishment.rule.operator,model_fusion_plating_bath_replenishment_rule,group_fusion_plating_operator,1,0,0,0
access_fp_replenishment_rule_supervisor,fp.replenishment.rule.supervisor,model_fusion_plating_bath_replenishment_rule,group_fusion_plating_supervisor,1,1,1,0
access_fp_replenishment_rule_manager,fp.replenishment.rule.manager,model_fusion_plating_bath_replenishment_rule,group_fusion_plating_manager,1,1,1,1
access_fp_replenishment_suggestion_operator,fp.replenishment.suggestion.operator,model_fusion_plating_bath_replenishment_suggestion,group_fusion_plating_operator,1,1,1,0
access_fp_replenishment_suggestion_supervisor,fp.replenishment.suggestion.supervisor,model_fusion_plating_bath_replenishment_suggestion,group_fusion_plating_supervisor,1,1,1,0
access_fp_replenishment_suggestion_manager,fp.replenishment.suggestion.manager,model_fusion_plating_bath_replenishment_suggestion,group_fusion_plating_manager,1,1,1,1
access_fp_operator_cert_operator,fp.operator.cert.operator,model_fp_operator_certification,group_fusion_plating_operator,1,0,0,0
access_fp_operator_cert_supervisor,fp.operator.cert.supervisor,model_fp_operator_certification,group_fusion_plating_supervisor,1,1,1,0
access_fp_operator_cert_manager,fp.operator.cert.manager,model_fp_operator_certification,group_fusion_plating_manager,1,1,1,1
access_fp_work_centre_operator,fp.work.centre.operator,model_fp_work_centre,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_work_centre_supervisor,fp.work.centre.supervisor,model_fp_work_centre,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_work_centre_manager,fp.work.centre.manager,model_fp_work_centre,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_job_operator,fp.job.operator,model_fp_job,fusion_plating.group_fusion_plating_operator,1,1,0,0
access_fp_job_supervisor,fp.job.supervisor,model_fp_job,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_job_manager,fp.job.manager,model_fp_job,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_job_step_operator,fp.job.step.operator,model_fp_job_step,fusion_plating.group_fusion_plating_operator,1,1,0,0
access_fp_job_step_supervisor,fp.job.step.supervisor,model_fp_job_step,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_job_step_manager,fp.job.step.manager,model_fp_job_step,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_job_step_timelog_operator,fp.job.step.timelog.operator,model_fp_job_step_timelog,fusion_plating.group_fusion_plating_operator,1,1,1,0
access_fp_job_step_timelog_supervisor,fp.job.step.timelog.supervisor,model_fp_job_step_timelog,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_job_step_timelog_manager,fp.job.step.timelog.manager,model_fp_job_step_timelog,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_work_role_operator,fp.work.role.operator,model_fp_work_role,group_fusion_plating_operator,1,0,0,0
access_fp_work_role_manager,fp.work.role.manager,model_fp_work_role,group_fusion_plating_manager,1,1,1,1
access_fp_proficiency_operator,fp.operator.proficiency.operator,model_fp_operator_proficiency,group_fusion_plating_operator,1,0,0,0
access_fp_proficiency_supervisor,fp.operator.proficiency.supervisor,model_fp_operator_proficiency,group_fusion_plating_supervisor,1,1,1,0
access_fp_proficiency_manager,fp.operator.proficiency.manager,model_fp_operator_proficiency,group_fusion_plating_manager,1,1,1,1
access_fp_step_kind_operator,fp.step.kind.operator,model_fp_step_kind,group_fusion_plating_operator,1,0,0,0
access_fp_step_kind_supervisor,fp.step.kind.supervisor,model_fp_step_kind,group_fusion_plating_supervisor,1,1,1,0
access_fp_step_kind_manager,fp.step.kind.manager,model_fp_step_kind,group_fusion_plating_manager,1,1,1,1
access_fp_step_kind_default_input_operator,fp.step.kind.default.input.operator,model_fp_step_kind_default_input,group_fusion_plating_operator,1,0,0,0
access_fp_step_kind_default_input_supervisor,fp.step.kind.default.input.supervisor,model_fp_step_kind_default_input,group_fusion_plating_supervisor,1,1,1,1
access_fp_step_kind_default_input_manager,fp.step.kind.default.input.manager,model_fp_step_kind_default_input,group_fusion_plating_manager,1,1,1,1
access_fp_step_template_operator,fp.step.template.operator,model_fp_step_template,group_fusion_plating_operator,1,0,0,0
access_fp_step_template_supervisor,fp.step.template.supervisor,model_fp_step_template,group_fusion_plating_supervisor,1,1,1,0
access_fp_step_template_manager,fp.step.template.manager,model_fp_step_template,group_fusion_plating_manager,1,1,1,1
access_fp_step_template_input_operator,fp.step.template.input.operator,model_fp_step_template_input,group_fusion_plating_operator,1,0,0,0
access_fp_step_template_input_supervisor,fp.step.template.input.supervisor,model_fp_step_template_input,group_fusion_plating_supervisor,1,1,1,1
access_fp_step_template_input_manager,fp.step.template.input.manager,model_fp_step_template_input,group_fusion_plating_manager,1,1,1,1
access_fp_step_template_transition_input_operator,fp.step.template.transition.input.operator,model_fp_step_template_transition_input,group_fusion_plating_operator,1,0,0,0
access_fp_step_template_transition_input_supervisor,fp.step.template.transition.input.supervisor,model_fp_step_template_transition_input,group_fusion_plating_supervisor,1,1,1,1
access_fp_step_template_transition_input_manager,fp.step.template.transition.input.manager,model_fp_step_template_transition_input,group_fusion_plating_manager,1,1,1,1
access_fp_rack_tag_operator,fp.rack.tag.operator,model_fp_rack_tag,group_fusion_plating_operator,1,0,0,0
access_fp_rack_tag_supervisor,fp.rack.tag.supervisor,model_fp_rack_tag,group_fusion_plating_supervisor,1,1,1,1
access_fp_rack_tag_manager,fp.rack.tag.manager,model_fp_rack_tag,group_fusion_plating_manager,1,1,1,1
access_fp_job_step_move_operator,fp.job.step.move.operator,model_fp_job_step_move,group_fusion_plating_operator,1,1,1,0
access_fp_job_step_move_supervisor,fp.job.step.move.supervisor,model_fp_job_step_move,group_fusion_plating_supervisor,1,1,1,0
access_fp_job_step_move_manager,fp.job.step.move.manager,model_fp_job_step_move,group_fusion_plating_manager,1,1,1,1
access_fp_job_step_move_input_value_operator,fp.job.step.move.input.value.operator,model_fp_job_step_move_input_value,group_fusion_plating_operator,1,1,1,0
access_fp_job_step_move_input_value_supervisor,fp.job.step.move.input.value.supervisor,model_fp_job_step_move_input_value,group_fusion_plating_supervisor,1,1,1,0
access_fp_job_step_move_input_value_manager,fp.job.step.move.input.value.manager,model_fp_job_step_move_input_value,group_fusion_plating_manager,1,1,1,1
access_fp_process_category_operator,fp.process.category.operator,model_fusion_plating_process_category,fusion_plating.group_fp_technician,1,0,0,0
access_fp_process_category_manager,fp.process.category.manager,model_fusion_plating_process_category,fusion_plating.group_fp_manager,1,1,1,1
access_fp_process_type_operator,fp.process.type.operator,model_fusion_plating_process_type,fusion_plating.group_fp_technician,1,0,0,0
access_fp_process_type_manager,fp.process.type.manager,model_fusion_plating_process_type,fusion_plating.group_fp_manager,1,1,1,1
access_fp_bath_parameter_operator,fp.bath.parameter.operator,model_fusion_plating_bath_parameter,fusion_plating.group_fp_technician,1,0,0,0
access_fp_bath_parameter_manager,fp.bath.parameter.manager,model_fusion_plating_bath_parameter,fusion_plating.group_fp_manager,1,1,1,1
access_fp_facility_operator,fp.facility.operator,model_fusion_plating_facility,fusion_plating.group_fp_technician,1,0,0,0
access_fp_facility_supervisor,fp.facility.supervisor,model_fusion_plating_facility,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_facility_manager,fp.facility.manager,model_fusion_plating_facility,fusion_plating.group_fp_manager,1,1,1,1
access_fp_work_center_operator,fp.work.center.operator,model_fusion_plating_work_center,fusion_plating.group_fp_technician,1,0,0,0
access_fp_work_center_supervisor,fp.work.center.supervisor,model_fusion_plating_work_center,fusion_plating.group_fp_shop_manager_v2,1,1,0,0
access_fp_work_center_manager,fp.work.center.manager,model_fusion_plating_work_center,fusion_plating.group_fp_manager,1,1,1,1
access_fp_tank_operator,fp.tank.operator,model_fusion_plating_tank,fusion_plating.group_fp_technician,1,0,0,0
access_fp_tank_supervisor,fp.tank.supervisor,model_fusion_plating_tank,fusion_plating.group_fp_shop_manager_v2,1,1,0,0
access_fp_tank_manager,fp.tank.manager,model_fusion_plating_tank,fusion_plating.group_fp_manager,1,1,1,1
access_fp_tank_section_operator,fp.tank.section.operator,model_fusion_plating_tank_section,fusion_plating.group_fp_technician,1,0,0,0
access_fp_tank_section_supervisor,fp.tank.section.supervisor,model_fusion_plating_tank_section,fusion_plating.group_fp_shop_manager_v2,1,1,0,0
access_fp_tank_section_manager,fp.tank.section.manager,model_fusion_plating_tank_section,fusion_plating.group_fp_manager,1,1,1,1
access_fp_tank_composition_operator,fp.tank.composition.operator,model_fusion_plating_tank_composition,fusion_plating.group_fp_technician,1,0,0,0
access_fp_tank_composition_supervisor,fp.tank.composition.supervisor,model_fusion_plating_tank_composition,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_tank_composition_manager,fp.tank.composition.manager,model_fusion_plating_tank_composition,fusion_plating.group_fp_manager,1,1,1,1
access_fp_tank_comp_ing_operator,fp.tank.composition.ingredient.operator,model_fusion_plating_tank_composition_ingredient,fusion_plating.group_fp_technician,1,0,0,0
access_fp_tank_comp_ing_supervisor,fp.tank.composition.ingredient.supervisor,model_fusion_plating_tank_composition_ingredient,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_tank_comp_ing_manager,fp.tank.composition.ingredient.manager,model_fusion_plating_tank_composition_ingredient,fusion_plating.group_fp_manager,1,1,1,1
access_fp_bath_operator,fp.bath.operator,model_fusion_plating_bath,fusion_plating.group_fp_technician,1,0,0,0
access_fp_bath_supervisor,fp.bath.supervisor,model_fusion_plating_bath,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_bath_manager,fp.bath.manager,model_fusion_plating_bath,fusion_plating.group_fp_manager,1,1,1,1
access_fp_bath_target_operator,fp.bath.target.operator,model_fusion_plating_bath_target,fusion_plating.group_fp_technician,1,0,0,0
access_fp_bath_target_supervisor,fp.bath.target.supervisor,model_fusion_plating_bath_target,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_bath_target_manager,fp.bath.target.manager,model_fusion_plating_bath_target,fusion_plating.group_fp_manager,1,1,1,1
access_fp_bath_log_operator,fp.bath.log.operator,model_fusion_plating_bath_log,fusion_plating.group_fp_technician,1,1,1,0
access_fp_bath_log_supervisor,fp.bath.log.supervisor,model_fusion_plating_bath_log,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_bath_log_manager,fp.bath.log.manager,model_fusion_plating_bath_log,fusion_plating.group_fp_manager,1,1,1,1
access_fp_bath_log_line_operator,fp.bath.log.line.operator,model_fusion_plating_bath_log_line,fusion_plating.group_fp_technician,1,1,1,0
access_fp_bath_log_line_supervisor,fp.bath.log.line.supervisor,model_fusion_plating_bath_log_line,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_bath_log_line_manager,fp.bath.log.line.manager,model_fusion_plating_bath_log_line,fusion_plating.group_fp_manager,1,1,1,1
access_fp_process_node_operator,fp.process.node.operator,model_fusion_plating_process_node,fusion_plating.group_fp_technician,1,0,0,0
access_fp_process_node_supervisor,fp.process.node.supervisor,model_fusion_plating_process_node,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_process_node_manager,fp.process.node.manager,model_fusion_plating_process_node,fusion_plating.group_fp_manager,1,1,1,1
access_fp_process_node_input_operator,fp.process.node.input.operator,model_fusion_plating_process_node_input,fusion_plating.group_fp_technician,1,0,0,0
access_fp_process_node_input_supervisor,fp.process.node.input.supervisor,model_fusion_plating_process_node_input,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_process_node_input_manager,fp.process.node.input.manager,model_fusion_plating_process_node_input,fusion_plating.group_fp_manager,1,1,1,1
access_fp_rack_operator,fp.rack.operator,model_fusion_plating_rack,fusion_plating.group_fp_technician,1,1,0,0
access_fp_rack_supervisor,fp.rack.supervisor,model_fusion_plating_rack,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_rack_manager,fp.rack.manager,model_fusion_plating_rack,fusion_plating.group_fp_manager,1,1,1,1
access_fp_replenishment_rule_operator,fp.replenishment.rule.operator,model_fusion_plating_bath_replenishment_rule,fusion_plating.group_fp_technician,1,0,0,0
access_fp_replenishment_rule_supervisor,fp.replenishment.rule.supervisor,model_fusion_plating_bath_replenishment_rule,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_replenishment_rule_manager,fp.replenishment.rule.manager,model_fusion_plating_bath_replenishment_rule,fusion_plating.group_fp_manager,1,1,1,1
access_fp_replenishment_suggestion_operator,fp.replenishment.suggestion.operator,model_fusion_plating_bath_replenishment_suggestion,fusion_plating.group_fp_technician,1,1,1,0
access_fp_replenishment_suggestion_supervisor,fp.replenishment.suggestion.supervisor,model_fusion_plating_bath_replenishment_suggestion,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_replenishment_suggestion_manager,fp.replenishment.suggestion.manager,model_fusion_plating_bath_replenishment_suggestion,fusion_plating.group_fp_manager,1,1,1,1
access_fp_operator_cert_operator,fp.operator.cert.operator,model_fp_operator_certification,fusion_plating.group_fp_technician,1,0,0,0
access_fp_operator_cert_supervisor,fp.operator.cert.supervisor,model_fp_operator_certification,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_operator_cert_manager,fp.operator.cert.manager,model_fp_operator_certification,fusion_plating.group_fp_manager,1,1,1,1
access_fp_work_centre_operator,fp.work.centre.operator,model_fp_work_centre,fusion_plating.group_fp_technician,1,0,0,0
access_fp_work_centre_supervisor,fp.work.centre.supervisor,model_fp_work_centre,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_work_centre_manager,fp.work.centre.manager,model_fp_work_centre,fusion_plating.group_fp_manager,1,1,1,1
access_fp_job_operator,fp.job.operator,model_fp_job,fusion_plating.group_fp_technician,1,1,0,0
access_fp_job_supervisor,fp.job.supervisor,model_fp_job,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_job_manager,fp.job.manager,model_fp_job,fusion_plating.group_fp_manager,1,1,1,1
access_fp_job_step_operator,fp.job.step.operator,model_fp_job_step,fusion_plating.group_fp_technician,1,1,0,0
access_fp_job_step_supervisor,fp.job.step.supervisor,model_fp_job_step,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_job_step_manager,fp.job.step.manager,model_fp_job_step,fusion_plating.group_fp_manager,1,1,1,1
access_fp_job_step_timelog_operator,fp.job.step.timelog.operator,model_fp_job_step_timelog,fusion_plating.group_fp_technician,1,1,1,0
access_fp_job_step_timelog_supervisor,fp.job.step.timelog.supervisor,model_fp_job_step_timelog,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_job_step_timelog_manager,fp.job.step.timelog.manager,model_fp_job_step_timelog,fusion_plating.group_fp_manager,1,1,1,1
access_fp_work_role_operator,fp.work.role.operator,model_fp_work_role,fusion_plating.group_fp_technician,1,0,0,0
access_fp_work_role_manager,fp.work.role.manager,model_fp_work_role,fusion_plating.group_fp_manager,1,1,1,1
access_fp_proficiency_operator,fp.operator.proficiency.operator,model_fp_operator_proficiency,fusion_plating.group_fp_technician,1,0,0,0
access_fp_proficiency_supervisor,fp.operator.proficiency.supervisor,model_fp_operator_proficiency,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_proficiency_manager,fp.operator.proficiency.manager,model_fp_operator_proficiency,fusion_plating.group_fp_manager,1,1,1,1
access_fp_step_kind_operator,fp.step.kind.operator,model_fp_step_kind,fusion_plating.group_fp_technician,1,0,0,0
access_fp_step_kind_supervisor,fp.step.kind.supervisor,model_fp_step_kind,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_step_kind_manager,fp.step.kind.manager,model_fp_step_kind,fusion_plating.group_fp_manager,1,1,1,1
access_fp_step_kind_default_input_operator,fp.step.kind.default.input.operator,model_fp_step_kind_default_input,fusion_plating.group_fp_technician,1,0,0,0
access_fp_step_kind_default_input_supervisor,fp.step.kind.default.input.supervisor,model_fp_step_kind_default_input,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_step_kind_default_input_manager,fp.step.kind.default.input.manager,model_fp_step_kind_default_input,fusion_plating.group_fp_manager,1,1,1,1
access_fp_step_template_operator,fp.step.template.operator,model_fp_step_template,fusion_plating.group_fp_technician,1,0,0,0
access_fp_step_template_supervisor,fp.step.template.supervisor,model_fp_step_template,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_step_template_manager,fp.step.template.manager,model_fp_step_template,fusion_plating.group_fp_manager,1,1,1,1
access_fp_step_template_input_operator,fp.step.template.input.operator,model_fp_step_template_input,fusion_plating.group_fp_technician,1,0,0,0
access_fp_step_template_input_supervisor,fp.step.template.input.supervisor,model_fp_step_template_input,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_step_template_input_manager,fp.step.template.input.manager,model_fp_step_template_input,fusion_plating.group_fp_manager,1,1,1,1
access_fp_step_template_transition_input_operator,fp.step.template.transition.input.operator,model_fp_step_template_transition_input,fusion_plating.group_fp_technician,1,0,0,0
access_fp_step_template_transition_input_supervisor,fp.step.template.transition.input.supervisor,model_fp_step_template_transition_input,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_step_template_transition_input_manager,fp.step.template.transition.input.manager,model_fp_step_template_transition_input,fusion_plating.group_fp_manager,1,1,1,1
access_fp_rack_tag_operator,fp.rack.tag.operator,model_fp_rack_tag,fusion_plating.group_fp_technician,1,0,0,0
access_fp_rack_tag_supervisor,fp.rack.tag.supervisor,model_fp_rack_tag,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_rack_tag_manager,fp.rack.tag.manager,model_fp_rack_tag,fusion_plating.group_fp_manager,1,1,1,1
access_fp_job_step_move_operator,fp.job.step.move.operator,model_fp_job_step_move,fusion_plating.group_fp_technician,1,1,1,0
access_fp_job_step_move_supervisor,fp.job.step.move.supervisor,model_fp_job_step_move,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_job_step_move_manager,fp.job.step.move.manager,model_fp_job_step_move,fusion_plating.group_fp_manager,1,1,1,1
access_fp_job_step_move_input_value_operator,fp.job.step.move.input.value.operator,model_fp_job_step_move_input_value,fusion_plating.group_fp_technician,1,1,1,0
access_fp_job_step_move_input_value_supervisor,fp.job.step.move.input.value.supervisor,model_fp_job_step_move_input_value,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_job_step_move_input_value_manager,fp.job.step.move.input.value.manager,model_fp_job_step_move_input_value,fusion_plating.group_fp_manager,1,1,1,1
access_fp_migration_preview_owner,fp.migration.preview.owner,model_fp_migration_preview,fusion_plating.group_fp_owner,1,1,1,1
access_fp_migration_preview_line_owner,fp.migration.preview.line.owner,model_fp_migration_preview_line,fusion_plating.group_fp_owner,1,1,1,1
access_ir_actions_actions_plating,ir.actions.actions.plating.read,base.model_ir_actions_actions,fusion_plating.group_fp_technician,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_process_category_operator fp.process.category.operator model_fusion_plating_process_category group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_process_category_manager fp.process.category.manager model_fusion_plating_process_category group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
4 access_fp_process_type_operator fp.process.type.operator model_fusion_plating_process_type group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
5 access_fp_process_type_manager fp.process.type.manager model_fusion_plating_process_type group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
6 access_fp_bath_parameter_operator fp.bath.parameter.operator model_fusion_plating_bath_parameter group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
7 access_fp_bath_parameter_manager fp.bath.parameter.manager model_fusion_plating_bath_parameter group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
8 access_fp_facility_operator fp.facility.operator model_fusion_plating_facility group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
9 access_fp_facility_supervisor fp.facility.supervisor model_fusion_plating_facility group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
10 access_fp_facility_manager fp.facility.manager model_fusion_plating_facility group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
11 access_fp_work_center_operator fp.work.center.operator model_fusion_plating_work_center group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
12 access_fp_work_center_supervisor fp.work.center.supervisor model_fusion_plating_work_center group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 0 0
13 access_fp_work_center_manager fp.work.center.manager model_fusion_plating_work_center group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
14 access_fp_tank_operator fp.tank.operator model_fusion_plating_tank group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
15 access_fp_tank_supervisor fp.tank.supervisor model_fusion_plating_tank group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 0 0
16 access_fp_tank_manager fp.tank.manager model_fusion_plating_tank group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
17 access_fp_tank_section_operator fp.tank.section.operator model_fusion_plating_tank_section group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
18 access_fp_tank_section_supervisor fp.tank.section.supervisor model_fusion_plating_tank_section group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 0 0
19 access_fp_tank_section_manager fp.tank.section.manager model_fusion_plating_tank_section group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
20 access_fp_tank_composition_operator fp.tank.composition.operator model_fusion_plating_tank_composition group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
21 access_fp_tank_composition_supervisor fp.tank.composition.supervisor model_fusion_plating_tank_composition group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
22 access_fp_tank_composition_manager fp.tank.composition.manager model_fusion_plating_tank_composition group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
23 access_fp_tank_comp_ing_operator fp.tank.composition.ingredient.operator model_fusion_plating_tank_composition_ingredient group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
24 access_fp_tank_comp_ing_supervisor fp.tank.composition.ingredient.supervisor model_fusion_plating_tank_composition_ingredient group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
25 access_fp_tank_comp_ing_manager fp.tank.composition.ingredient.manager model_fusion_plating_tank_composition_ingredient group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
26 access_fp_bath_operator fp.bath.operator model_fusion_plating_bath group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
27 access_fp_bath_supervisor fp.bath.supervisor model_fusion_plating_bath group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
28 access_fp_bath_manager fp.bath.manager model_fusion_plating_bath group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
29 access_fp_bath_target_operator fp.bath.target.operator model_fusion_plating_bath_target group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
30 access_fp_bath_target_supervisor fp.bath.target.supervisor model_fusion_plating_bath_target group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
31 access_fp_bath_target_manager fp.bath.target.manager model_fusion_plating_bath_target group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
32 access_fp_bath_log_operator fp.bath.log.operator model_fusion_plating_bath_log group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 0
33 access_fp_bath_log_supervisor fp.bath.log.supervisor model_fusion_plating_bath_log group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
34 access_fp_bath_log_manager fp.bath.log.manager model_fusion_plating_bath_log group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
35 access_fp_bath_log_line_operator fp.bath.log.line.operator model_fusion_plating_bath_log_line group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 0
36 access_fp_bath_log_line_supervisor fp.bath.log.line.supervisor model_fusion_plating_bath_log_line group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
37 access_fp_bath_log_line_manager fp.bath.log.line.manager model_fusion_plating_bath_log_line group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
38 access_fp_process_node_operator fp.process.node.operator model_fusion_plating_process_node group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
39 access_fp_process_node_supervisor fp.process.node.supervisor model_fusion_plating_process_node group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
40 access_fp_process_node_manager fp.process.node.manager model_fusion_plating_process_node group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
41 access_fp_process_node_input_operator fp.process.node.input.operator model_fusion_plating_process_node_input group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
42 access_fp_process_node_input_supervisor fp.process.node.input.supervisor model_fusion_plating_process_node_input group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
43 access_fp_process_node_input_manager fp.process.node.input.manager model_fusion_plating_process_node_input group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
44 access_fp_rack_operator fp.rack.operator model_fusion_plating_rack group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 0 0
45 access_fp_rack_supervisor fp.rack.supervisor model_fusion_plating_rack group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
46 access_fp_rack_manager fp.rack.manager model_fusion_plating_rack group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
47 access_fp_replenishment_rule_operator fp.replenishment.rule.operator model_fusion_plating_bath_replenishment_rule group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
48 access_fp_replenishment_rule_supervisor fp.replenishment.rule.supervisor model_fusion_plating_bath_replenishment_rule group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
49 access_fp_replenishment_rule_manager fp.replenishment.rule.manager model_fusion_plating_bath_replenishment_rule group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
50 access_fp_replenishment_suggestion_operator fp.replenishment.suggestion.operator model_fusion_plating_bath_replenishment_suggestion group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 0
51 access_fp_replenishment_suggestion_supervisor fp.replenishment.suggestion.supervisor model_fusion_plating_bath_replenishment_suggestion group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
52 access_fp_replenishment_suggestion_manager fp.replenishment.suggestion.manager model_fusion_plating_bath_replenishment_suggestion group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
53 access_fp_operator_cert_operator fp.operator.cert.operator model_fp_operator_certification group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
54 access_fp_operator_cert_supervisor fp.operator.cert.supervisor model_fp_operator_certification group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
55 access_fp_operator_cert_manager fp.operator.cert.manager model_fp_operator_certification group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
56 access_fp_work_centre_operator fp.work.centre.operator model_fp_work_centre fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
57 access_fp_work_centre_supervisor fp.work.centre.supervisor model_fp_work_centre fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
58 access_fp_work_centre_manager fp.work.centre.manager model_fp_work_centre fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
59 access_fp_job_operator fp.job.operator model_fp_job fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 0 0
60 access_fp_job_supervisor fp.job.supervisor model_fp_job fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
61 access_fp_job_manager fp.job.manager model_fp_job fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
62 access_fp_job_step_operator fp.job.step.operator model_fp_job_step fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 0 0
63 access_fp_job_step_supervisor fp.job.step.supervisor model_fp_job_step fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
64 access_fp_job_step_manager fp.job.step.manager model_fp_job_step fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
65 access_fp_job_step_timelog_operator fp.job.step.timelog.operator model_fp_job_step_timelog fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 0
66 access_fp_job_step_timelog_supervisor fp.job.step.timelog.supervisor model_fp_job_step_timelog fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
67 access_fp_job_step_timelog_manager fp.job.step.timelog.manager model_fp_job_step_timelog fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
68 access_fp_work_role_operator fp.work.role.operator model_fp_work_role group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
69 access_fp_work_role_manager fp.work.role.manager model_fp_work_role group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
70 access_fp_proficiency_operator fp.operator.proficiency.operator model_fp_operator_proficiency group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
71 access_fp_proficiency_supervisor fp.operator.proficiency.supervisor model_fp_operator_proficiency group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
72 access_fp_proficiency_manager fp.operator.proficiency.manager model_fp_operator_proficiency group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
73 access_fp_step_kind_operator fp.step.kind.operator model_fp_step_kind group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
74 access_fp_step_kind_supervisor fp.step.kind.supervisor model_fp_step_kind group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
75 access_fp_step_kind_manager fp.step.kind.manager model_fp_step_kind group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
76 access_fp_step_kind_default_input_operator fp.step.kind.default.input.operator model_fp_step_kind_default_input group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
77 access_fp_step_kind_default_input_supervisor fp.step.kind.default.input.supervisor model_fp_step_kind_default_input group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
78 access_fp_step_kind_default_input_manager fp.step.kind.default.input.manager model_fp_step_kind_default_input group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
79 access_fp_step_template_operator fp.step.template.operator model_fp_step_template group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
80 access_fp_step_template_supervisor fp.step.template.supervisor model_fp_step_template group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
81 access_fp_step_template_manager fp.step.template.manager model_fp_step_template group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
82 access_fp_step_template_input_operator fp.step.template.input.operator model_fp_step_template_input group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
83 access_fp_step_template_input_supervisor fp.step.template.input.supervisor model_fp_step_template_input group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
84 access_fp_step_template_input_manager fp.step.template.input.manager model_fp_step_template_input group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
85 access_fp_step_template_transition_input_operator fp.step.template.transition.input.operator model_fp_step_template_transition_input group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
86 access_fp_step_template_transition_input_supervisor fp.step.template.transition.input.supervisor model_fp_step_template_transition_input group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
87 access_fp_step_template_transition_input_manager fp.step.template.transition.input.manager model_fp_step_template_transition_input group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
88 access_fp_rack_tag_operator fp.rack.tag.operator model_fp_rack_tag group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
89 access_fp_rack_tag_supervisor fp.rack.tag.supervisor model_fp_rack_tag group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
90 access_fp_rack_tag_manager fp.rack.tag.manager model_fp_rack_tag group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
91 access_fp_job_step_move_operator fp.job.step.move.operator model_fp_job_step_move group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 0
92 access_fp_job_step_move_supervisor fp.job.step.move.supervisor model_fp_job_step_move group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
93 access_fp_job_step_move_manager fp.job.step.move.manager model_fp_job_step_move group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
94 access_fp_job_step_move_input_value_operator fp.job.step.move.input.value.operator model_fp_job_step_move_input_value group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 0
95 access_fp_job_step_move_input_value_supervisor fp.job.step.move.input.value.supervisor model_fp_job_step_move_input_value group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
96 access_fp_job_step_move_input_value_manager fp.job.step.move.input.value.manager model_fp_job_step_move_input_value group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
97 access_fp_migration_preview_owner fp.migration.preview.owner model_fp_migration_preview fusion_plating.group_fp_owner 1 1 1 1
98 access_fp_migration_preview_line_owner fp.migration.preview.line.owner model_fp_migration_preview_line fusion_plating.group_fp_owner 1 1 1 1
99 access_ir_actions_actions_plating ir.actions.actions.plating.read base.model_ir_actions_actions fusion_plating.group_fp_technician 1 0 0 0

View File

@@ -3,3 +3,11 @@ from . import test_fp_work_centre
from . import test_fp_job_state_machine
from . import test_fp_job_step_state_machine
from . import test_simple_recipe_flatten
from . import test_role_groups
from . import test_acl_migration
from . import test_quality_split
from . import test_menu_visibility
from . import test_landing_resolver
from . import test_team_page
from . import test_sales_manager_gate
from . import test_migration_workflow

View File

@@ -0,0 +1,56 @@
from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import AccessError
@tagged('-at_install', 'post_install', 'fp_perms')
class TestAclMigration(TransactionCase):
"""Sample-based ACL coverage: pick 1 model per role and verify access."""
def setUp(self):
super().setUp()
Users = self.env['res.users'].with_context(no_reset_password=True)
def make(login, group_xmlid):
return Users.create({
'login': f'fp_test_{login}',
'name': f'FP Test {login.title()}',
'email': f'fp_test_{login}@example.com',
'group_ids': [(6, 0, [self.env.ref(group_xmlid).id])],
})
self.u_tech = make('tech', 'fusion_plating.group_fp_technician')
self.u_sm = make('sm', 'fusion_plating.group_fp_shop_manager_v2')
self.u_mgr = make('mgr', 'fusion_plating.group_fp_manager')
self.u_qm = make('qm', 'fusion_plating.group_fp_quality_manager')
self.u_sr = make('sr', 'fusion_plating.group_fp_sales_rep')
self.u_smg = make('smg', 'fusion_plating.group_fp_sales_manager')
def test_technician_can_read_jobs(self):
Jobs = self.env['fp.job'].with_user(self.u_tech)
Jobs.check_access_rights('read')
def test_technician_cannot_read_part_catalog(self):
Parts = self.env['fp.part.catalog'].with_user(self.u_tech)
with self.assertRaises(AccessError):
Parts.check_access_rights('read')
def test_sales_rep_can_read_part_catalog(self):
Parts = self.env['fp.part.catalog'].with_user(self.u_sr)
Parts.check_access_rights('read')
def test_shop_manager_can_read_receiving(self):
Rec = self.env['fp.receiving'].with_user(self.u_sm)
Rec.check_access_rights('read')
def test_manager_can_create_ncr(self):
Ncr = self.env['fusion.plating.ncr'].with_user(self.u_mgr)
Ncr.check_access_rights('create')
def test_manager_can_only_read_capa(self):
Capa = self.env['fusion.plating.capa'].with_user(self.u_mgr)
Capa.check_access_rights('read')
with self.assertRaises(AccessError):
Capa.check_access_rights('write')
def test_qm_can_write_capa(self):
Capa = self.env['fusion.plating.capa'].with_user(self.u_qm)
Capa.check_access_rights('write')

View File

@@ -0,0 +1,150 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
"""Phase E (Plating permissions overhaul) — role-based landing dispatch.
Section 3 of the design spec covers per-role landing pages:
Owner -> Manager Desk
Quality Mgr -> Quality Dashboard
Manager -> Manager Desk
Sales Manager -> Sale Orders
Shop Manager -> Plant Kanban (v2) or Workstation (legacy)
Sales Rep -> Quotations
Technician -> Plant Kanban (v2) or Workstation (legacy)
Per-user override (`x_fc_plating_landing_action_id`) always wins.
NB: The resolver returns an action dict produced by
`_fp_resolve_landing_for_current_user()`. We compare against the
expected action's xmlid so the test stays robust if module names or
view ordering change downstream.
"""
from odoo.tests.common import TransactionCase, tagged
@tagged('-at_install', 'post_install', 'fp_perms')
class TestLandingResolver(TransactionCase):
"""Section 3 of spec: per-role landing dispatch."""
def setUp(self):
super().setUp()
Users = self.env['res.users'].with_context(no_reset_password=True)
def mk(name, xmlid):
return Users.create({
'login': f'land_{name}',
'name': f'Land {name}',
'email': f'land_{name}@example.com',
'group_ids': [(6, 0, [self.env.ref(xmlid).id])],
})
self.u_tech = mk('tech', 'fusion_plating.group_fp_technician')
self.u_sr = mk('sr', 'fusion_plating.group_fp_sales_rep')
self.u_smg = mk('smg', 'fusion_plating.group_fp_sales_manager')
self.u_mgr = mk('mgr', 'fusion_plating.group_fp_manager')
self.u_qm = mk('qm', 'fusion_plating.group_fp_quality_manager')
self.u_owner = mk('owner', 'fusion_plating.group_fp_owner')
# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------
def _resolve_xmlid(self, user):
"""Run the resolver as `user` and return the xml_id of the resulting
action, or None if no action was returned.
The resolver lives on `ir.actions.act_window` (helper method, not a
column). It can return an action dict for either an act_window or a
client action — both carry an `xml_id` key once we go through
`_render_resolved`.
"""
Window = self.env['ir.actions.act_window']
if not hasattr(Window, '_fp_resolve_landing_for_current_user'):
self.skipTest('Resolver helper not implemented yet')
result = Window.with_user(user)._fp_resolve_landing_for_current_user()
if not result:
return None
return result.get('xml_id') or result.get('xmlid')
def _xmlid_of(self, xmlid):
"""Resolve an xmlid and return it back if the action exists.
Returns None when the underlying action isn't installed in this
DB (e.g. running tests without a sibling module). Callers use this
to skip a test when the candidate action is missing.
"""
action = self.env.ref(xmlid, raise_if_not_found=False)
return xmlid if action else None
# ------------------------------------------------------------------
# Per-role tests
# ------------------------------------------------------------------
def test_owner_lands_on_manager_desk(self):
expected = self._xmlid_of('fusion_plating_shopfloor.action_fp_manager_dashboard')
if not expected:
self.skipTest('Manager Dashboard action not found')
self.assertEqual(self._resolve_xmlid(self.u_owner), expected)
def test_qm_lands_on_quality_dashboard(self):
expected = self._xmlid_of('fusion_plating_quality.action_fp_quality_dashboard')
if not expected:
self.skipTest('Quality Dashboard action not found')
self.assertEqual(self._resolve_xmlid(self.u_qm), expected)
def test_manager_lands_on_manager_desk(self):
expected = self._xmlid_of('fusion_plating_shopfloor.action_fp_manager_dashboard')
if not expected:
self.skipTest('Manager Dashboard action not found')
self.assertEqual(self._resolve_xmlid(self.u_mgr), expected)
def test_sales_manager_lands_on_sale_orders(self):
expected = self._xmlid_of('fusion_plating_configurator.action_fp_sale_orders')
if not expected:
self.skipTest('Sale Orders action not found')
self.assertEqual(self._resolve_xmlid(self.u_smg), expected)
def test_sales_rep_lands_on_quotations(self):
expected = self._xmlid_of('fusion_plating_configurator.action_fp_quotations')
if not expected:
self.skipTest('Quotations action not found')
self.assertEqual(self._resolve_xmlid(self.u_sr), expected)
def test_technician_lands_on_plant_kanban_v2(self):
self.env['ir.config_parameter'].sudo().set_param(
'fusion_plating_shopfloor.layout', 'v2')
expected = self._xmlid_of('fusion_plating_shopfloor.action_fp_plant_kanban')
if not expected:
self.skipTest('Plant Kanban action not found')
self.assertEqual(self._resolve_xmlid(self.u_tech), expected)
def test_technician_lands_on_legacy_workstation(self):
self.env['ir.config_parameter'].sudo().set_param(
'fusion_plating_shopfloor.layout', 'legacy')
expected = self._xmlid_of('fusion_plating_shopfloor.action_fp_shopfloor_landing')
if not expected:
# The legacy action is currently not defined by that xmlid
# in this codebase — both old XMLIDs (action_fp_shopfloor_tablet
# and action_fp_plant_overview) point at the v2 fp_plant_kanban
# tag after the 2026-05-23 plant-view redesign. The resolver
# falls through to the company default / hardcoded fallback
# when no action is found. Skip the assertion here rather
# than fail.
self.skipTest('Legacy Workstation action not found in this DB')
self.assertEqual(self._resolve_xmlid(self.u_tech), expected)
# Reset to v2 to avoid bleeding into other tests
self.env['ir.config_parameter'].sudo().set_param(
'fusion_plating_shopfloor.layout', 'v2')
# ------------------------------------------------------------------
# User-override and fallback
# ------------------------------------------------------------------
def test_user_override_wins(self):
override = self.env.ref('fusion_plating_configurator.action_fp_quotations',
raise_if_not_found=False)
if not override:
self.skipTest('Quotations action not found')
self.u_tech.x_fc_plating_landing_action_id = override.id
expected = override.get_external_id().get(override.id)
self.assertEqual(self._resolve_xmlid(self.u_tech), expected)

View File

@@ -0,0 +1,85 @@
from odoo.tests.common import TransactionCase, tagged
@tagged('-at_install', 'post_install', 'fp_perms')
class TestMenuVisibility(TransactionCase):
"""Section 2.F of spec: per-role menu render matrix."""
def setUp(self):
super().setUp()
Users = self.env['res.users'].with_context(no_reset_password=True)
def mk(name, xmlid):
return Users.create({
'login': f'menu_{name}', 'name': f'Menu Test {name}',
'email': f'menu_{name}@example.com',
'group_ids': [(6, 0, [self.env.ref(xmlid).id])] if xmlid else [(6, 0, [])],
})
# "No" user has only base.group_user — no plating group
no_user = Users.create({
'login': 'menu_no', 'name': 'Menu Test no',
'email': 'menu_no@example.com',
})
no_user.write({'group_ids': [(6, 0, [self.env.ref('base.group_user').id])]})
self.u_no = no_user
self.u_tech = mk('tech', 'fusion_plating.group_fp_technician')
self.u_sr = mk('sr', 'fusion_plating.group_fp_sales_rep')
self.u_sm = mk('sm', 'fusion_plating.group_fp_shop_manager_v2')
self.u_smg = mk('smg', 'fusion_plating.group_fp_sales_manager')
self.u_mgr = mk('mgr', 'fusion_plating.group_fp_manager')
self.u_qm = mk('qm', 'fusion_plating.group_fp_quality_manager')
self.u_owner = mk('owner', 'fusion_plating.group_fp_owner')
def _visible(self, user, menu_xmlid):
menu = self.env.ref(menu_xmlid, raise_if_not_found=False)
if not menu:
return None # menu not installed
# An "invisible" menu is one the user can't read
return bool(self.env['ir.ui.menu'].with_user(user).search_count([('id', '=', menu.id)]))
def test_no_sees_no_plating_root(self):
result = self._visible(self.u_no, 'fusion_plating.menu_fp_root')
if result is None:
self.skipTest('Plating root menu not found')
self.assertFalse(result, '"No" role must not see Plating root')
def test_technician_sees_shop_floor(self):
result = self._visible(self.u_tech, 'fusion_plating_shopfloor.menu_fp_shopfloor')
if result is None:
self.skipTest('Shop Floor menu not found')
self.assertTrue(result)
def test_technician_does_not_see_sales(self):
result = self._visible(self.u_tech, 'fusion_plating_configurator.menu_fp_sales')
if result is None:
self.skipTest('Sales menu not found')
self.assertFalse(result, 'Technician must not see Sales & Quoting')
def test_sales_rep_sees_sales(self):
result = self._visible(self.u_sr, 'fusion_plating_configurator.menu_fp_sales')
if result is None:
self.skipTest('Sales menu not found')
self.assertTrue(result)
def test_sales_rep_does_not_see_shop_floor(self):
result = self._visible(self.u_sr, 'fusion_plating_shopfloor.menu_fp_shopfloor')
if result is None:
self.skipTest('Shop Floor menu not found')
self.assertFalse(result, 'Sales Rep must not see Shop Floor')
def test_manager_sees_quality(self):
result = self._visible(self.u_mgr, 'fusion_plating_quality.menu_fp_quality')
if result is None:
self.skipTest('Quality menu not found')
self.assertTrue(result)
def test_manager_does_not_see_compliance(self):
result = self._visible(self.u_mgr, 'fusion_plating.menu_fp_compliance_hub')
if result is None:
self.skipTest('Compliance hub not found')
self.assertFalse(result, 'Manager must not see Compliance hub')
def test_qm_sees_compliance(self):
result = self._visible(self.u_qm, 'fusion_plating.menu_fp_compliance_hub')
if result is None:
self.skipTest('Compliance hub not found')
self.assertTrue(result)

View File

@@ -0,0 +1,103 @@
import json
from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import UserError
@tagged('-at_install', 'post_install', 'fp_perms')
class TestMigrationWorkflow(TransactionCase):
def setUp(self):
super().setUp()
Users = self.env['res.users'].with_context(no_reset_password=True)
self.owner = Users.create({
'login': 'mig_owner', 'name': 'Mig Owner',
'email': 'mig_owner@example.com',
'group_ids': [(6, 0, [self.env.ref('fusion_plating.group_fp_owner').id])],
})
def test_only_owner_can_approve(self):
non_owner = self.env['res.users'].with_context(no_reset_password=True).create({
'login': 'mig_nonowner', 'name': 'Non Owner',
'email': 'mig_nonowner@example.com',
'group_ids': [(6, 0, [self.env.ref('fusion_plating.group_fp_manager').id])],
})
preview = self.env['fp.migration.preview'].create({})
preview._fp_build_lines()
with self.assertRaises(UserError):
preview.with_user(non_owner).action_approve_and_run()
def test_approve_advances_state(self):
preview = self.env['fp.migration.preview'].create({})
preview._fp_build_lines()
preview.with_user(self.owner).action_approve_and_run()
self.assertEqual(preview.state, 'approved')
self.assertTrue(preview.approved_at)
self.assertEqual(preview.approved_by_id, self.owner)
def test_cancel_advances_state(self):
preview = self.env['fp.migration.preview'].create({})
preview.action_cancel()
self.assertEqual(preview.state, 'cancelled')
def test_cancel_blocked_after_approval(self):
preview = self.env['fp.migration.preview'].create({})
preview._fp_build_lines()
preview.with_user(self.owner).action_approve_and_run()
with self.assertRaises(UserError):
preview.action_cancel()
def test_rollback_restores_groups(self):
# Create a test user with an old Manager group
old_mgr = self.env.ref('fusion_plating.group_fusion_plating_manager')
u = self.env['res.users'].with_context(no_reset_password=True).create({
'login': 'mig_rb', 'name': 'RB',
'email': 'mig_rb@example.com',
'group_ids': [(6, 0, [old_mgr.id])],
})
before_ids = sorted(u.groups_id.ids)
preview = self.env['fp.migration.preview'].create({})
preview._fp_build_lines()
preview.with_user(self.owner).action_approve_and_run()
# Verify the migration changed things
u.invalidate_recordset()
# Now rollback
preview.with_user(self.owner).action_rollback()
u.invalidate_recordset()
self.assertEqual(sorted(u.groups_id.ids), before_ids,
'Rollback must restore original groups_id')
self.assertEqual(preview.state, 'rolled_back')
def test_estimator_warning_flagged(self):
est = self.env.ref('fusion_plating_configurator.group_fp_estimator', raise_if_not_found=False)
if not est:
self.skipTest('Estimator group not defined')
u = self.env['res.users'].with_context(no_reset_password=True).create({
'login': 'mig_est', 'name': 'Est',
'email': 'mig_est@example.com',
'group_ids': [(6, 0, [est.id])],
})
preview = self.env['fp.migration.preview'].create({})
preview._fp_build_lines()
line = preview.line_ids.filtered(lambda l: l.user_id == u)
self.assertTrue(line.warning,
'Estimator-only user should be flagged for capability loss')
self.assertEqual(line.proposed_role, 'sales_rep')
def test_admin_user_maps_to_owner(self):
# uid 2 always gets owner via the first mapping rule
preview = self.env['fp.migration.preview'].create({})
preview._fp_build_lines()
admin_line = preview.line_ids.filtered(lambda l: l.user_id.id == 2)
if admin_line:
self.assertEqual(admin_line.proposed_role, 'owner')
def test_rollback_blocked_after_30_days(self):
from datetime import timedelta
preview = self.env['fp.migration.preview'].create({})
preview._fp_build_lines()
preview.with_user(self.owner).action_approve_and_run()
# Backdate approved_at by 31 days
preview.approved_at = preview.approved_at - timedelta(days=31)
preview.invalidate_recordset(['rollback_deadline'])
with self.assertRaises(UserError):
preview.with_user(self.owner).action_rollback()

View File

@@ -0,0 +1,90 @@
from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import AccessError
@tagged('-at_install', 'post_install', 'fp_perms')
class TestQualitySplit(TransactionCase):
"""Section 2.C of spec: Manager handles reactive Quality;
QM exclusively owns CAPA close, Audit, AVL, Customer Spec, FAIR/Nadcap signing."""
def setUp(self):
super().setUp()
Users = self.env['res.users'].with_context(no_reset_password=True)
self.u_mgr = Users.create({
'login': 'qsplit_mgr', 'name': 'QSplit Mgr',
'email': 'qsplit_mgr@example.com',
'group_ids': [(6, 0, [self.env.ref('fusion_plating.group_fp_manager').id])],
})
self.u_qm = Users.create({
'login': 'qsplit_qm', 'name': 'QSplit QM',
'email': 'qsplit_qm@example.com',
'group_ids': [(6, 0, [self.env.ref('fusion_plating.group_fp_quality_manager').id])],
})
# CAPA: Manager read-only, QM full
def test_manager_can_read_capa(self):
self.env['fusion.plating.capa'].with_user(self.u_mgr).check_access_rights('read')
def test_manager_cannot_write_capa(self):
with self.assertRaises(AccessError):
self.env['fusion.plating.capa'].with_user(self.u_mgr).check_access_rights('write')
def test_manager_cannot_create_capa(self):
with self.assertRaises(AccessError):
self.env['fusion.plating.capa'].with_user(self.u_mgr).check_access_rights('create')
def test_qm_can_write_capa(self):
self.env['fusion.plating.capa'].with_user(self.u_qm).check_access_rights('write')
# Audit: Manager read-only, QM full
def test_manager_can_read_audit(self):
Audit = self.env.get('fusion.plating.audit')
if not Audit:
self.skipTest('fusion.plating.audit model not available')
Audit.with_user(self.u_mgr).check_access_rights('read')
def test_manager_cannot_write_audit(self):
Audit = self.env.get('fusion.plating.audit')
if not Audit:
self.skipTest('fusion.plating.audit model not available')
with self.assertRaises(AccessError):
Audit.with_user(self.u_mgr).check_access_rights('write')
def test_qm_can_write_audit(self):
Audit = self.env.get('fusion.plating.audit')
if not Audit:
self.skipTest('fusion.plating.audit model not available')
Audit.with_user(self.u_qm).check_access_rights('write')
# NCR: Manager full
def test_manager_can_create_ncr(self):
self.env['fusion.plating.ncr'].with_user(self.u_mgr).check_access_rights('create')
def test_manager_can_write_ncr(self):
self.env['fusion.plating.ncr'].with_user(self.u_mgr).check_access_rights('write')
# Hold: Manager full
def test_manager_can_create_hold(self):
self.env['fusion.plating.quality.hold'].with_user(self.u_mgr).check_access_rights('create')
# AVL: Manager read-only, QM full
def test_manager_can_read_avl(self):
Avl = self.env.get('fusion.plating.avl')
if not Avl:
self.skipTest('fusion.plating.avl model not available')
Avl.with_user(self.u_mgr).check_access_rights('read')
def test_manager_cannot_write_avl(self):
Avl = self.env.get('fusion.plating.avl')
if not Avl:
self.skipTest('fusion.plating.avl model not available')
with self.assertRaises(AccessError):
Avl.with_user(self.u_mgr).check_access_rights('write')
# Customer Spec: Manager read-only, QM full
def test_manager_can_read_customer_spec(self):
self.env['fusion.plating.customer.spec'].with_user(self.u_mgr).check_access_rights('read')
def test_manager_cannot_write_customer_spec(self):
with self.assertRaises(AccessError):
self.env['fusion.plating.customer.spec'].with_user(self.u_mgr).check_access_rights('write')

View File

@@ -0,0 +1,104 @@
from odoo.tests.common import TransactionCase, tagged
@tagged('-at_install', 'post_install', 'fp_perms')
class TestRoleGroupsStructure(TransactionCase):
"""Verify the 8 new roles exist with correct implied_ids chains.
Part of Phase 1 permissions overhaul. See:
docs/superpowers/specs/2026-05-23-permissions-overhaul-design.md
"""
def test_all_seven_groups_exist(self):
"""The 7 new res.groups records must all be defined. (The 8th role 'No'
is implicit — absence of any plating group.)"""
xmlids = {
'group_fp_technician', 'group_fp_sales_rep',
'group_fp_shop_manager_v2', 'group_fp_sales_manager',
'group_fp_manager', 'group_fp_quality_manager', 'group_fp_owner',
}
for xmlid in xmlids:
grp = self.env.ref(f'fusion_plating.{xmlid}', raise_if_not_found=False)
self.assertTrue(grp, f'Group {xmlid} not found')
def test_owner_implies_quality_manager(self):
owner = self.env.ref('fusion_plating.group_fp_owner')
qm = self.env.ref('fusion_plating.group_fp_quality_manager')
self.assertIn(qm, owner.implied_ids)
def test_owner_implies_system(self):
owner = self.env.ref('fusion_plating.group_fp_owner')
system = self.env.ref('base.group_system')
self.assertIn(system, owner.trans_implied_ids,
'Owner must transitively imply base.group_system')
def test_manager_implies_both_branches(self):
"""Manager is the diamond apex — must imply both Shop Manager and Sales Manager."""
mgr = self.env.ref('fusion_plating.group_fp_manager')
sm = self.env.ref('fusion_plating.group_fp_shop_manager_v2')
sales_mgr = self.env.ref('fusion_plating.group_fp_sales_manager')
self.assertIn(sm, mgr.implied_ids, 'Manager must imply Shop Manager (diamond)')
self.assertIn(sales_mgr, mgr.implied_ids, 'Manager must imply Sales Manager (diamond)')
def test_technician_does_not_imply_sales_rep(self):
"""Sales and Shop branches must remain orthogonal at the leaf."""
tech = self.env.ref('fusion_plating.group_fp_technician')
sales_rep = self.env.ref('fusion_plating.group_fp_sales_rep')
self.assertNotIn(sales_rep, tech.trans_implied_ids,
'Technician must NOT see Sales Rep menus')
def test_sales_rep_does_not_imply_technician(self):
sales_rep = self.env.ref('fusion_plating.group_fp_sales_rep')
tech = self.env.ref('fusion_plating.group_fp_technician')
self.assertNotIn(tech, sales_rep.trans_implied_ids,
'Sales Rep must NOT see Workstation')
def test_owner_auto_assigned_to_uid_1_and_2(self):
owner = self.env.ref('fusion_plating.group_fp_owner')
user_ids = owner.user_ids.ids
self.assertIn(1, user_ids, 'Owner must include uid 1 (__system__)')
self.assertIn(2, user_ids, 'Owner must include uid 2 (admin)')
def test_sequence_numbers_are_unique(self):
seqs = [
self.env.ref(f'fusion_plating.{x}').sequence
for x in ('group_fp_technician', 'group_fp_sales_rep',
'group_fp_shop_manager_v2', 'group_fp_sales_manager',
'group_fp_manager', 'group_fp_quality_manager', 'group_fp_owner')
]
self.assertEqual(len(seqs), len(set(seqs)),
f'All sequence numbers must be unique, got {seqs}')
def test_new_groups_imply_old_for_backward_compat(self):
"""During the 30-day rollback window, new groups must trigger old ACLs."""
tech = self.env.ref('fusion_plating.group_fp_technician')
old_op = self.env.ref('fusion_plating.group_fusion_plating_operator')
self.assertIn(old_op, tech.trans_implied_ids)
mgr = self.env.ref('fusion_plating.group_fp_manager')
old_mgr = self.env.ref('fusion_plating.group_fusion_plating_manager')
self.assertIn(old_mgr, mgr.trans_implied_ids)
def test_owner_implies_all_old_groups_via_cross_module_chain(self):
"""Owner must transitively reach every old group (admin, manager, supervisor,
operator, estimator, receiving, accounting, cgp_officer, cgp_designated_official)
via the implication chain spread across fusion_plating + 4 downstream module
security files."""
owner = self.env.ref('fusion_plating.group_fp_owner')
expected_old = [
'fusion_plating.group_fusion_plating_admin',
'fusion_plating.group_fusion_plating_manager',
'fusion_plating.group_fusion_plating_supervisor',
'fusion_plating.group_fusion_plating_operator',
'fusion_plating_configurator.group_fp_estimator',
'fusion_plating_receiving.group_fp_receiving',
'fusion_plating_invoicing.group_fp_accounting',
'fusion_plating_cgp.group_fusion_plating_cgp_officer',
'fusion_plating_cgp.group_fusion_plating_cgp_designated_official',
]
for xmlid in expected_old:
old_grp = self.env.ref(xmlid, raise_if_not_found=False)
if not old_grp:
continue # Module not installed
self.assertIn(old_grp, owner.trans_implied_ids,
f'Owner must transitively imply {xmlid} for backward-compat')

View File

@@ -0,0 +1,46 @@
from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import UserError
@tagged('-at_install', 'post_install', 'fp_perms')
class TestSalesManagerGate(TransactionCase):
def setUp(self):
super().setUp()
Users = self.env['res.users'].with_context(no_reset_password=True)
self.u_sr = Users.create({
'login': 'gate_sr', 'name': 'Gate SR',
'email': 'gate_sr@example.com',
'group_ids': [(6, 0, [self.env.ref('fusion_plating.group_fp_sales_rep').id])],
})
self.u_smg = Users.create({
'login': 'gate_smg', 'name': 'Gate SMg',
'email': 'gate_smg@example.com',
'group_ids': [(6, 0, [self.env.ref('fusion_plating.group_fp_sales_manager').id])],
})
partner = self.env['res.partner'].create({'name': 'Gate Test Customer'})
product = self.env['product.product'].create({'name': 'Gate Test Product'})
self.so = self.env['sale.order'].create({
'partner_id': partner.id,
'order_line': [(0, 0, {
'product_id': product.id, 'product_uom_qty': 1, 'price_unit': 100,
})],
})
def test_sales_rep_cannot_confirm(self):
with self.assertRaises(UserError):
self.so.with_user(self.u_sr).action_confirm()
def test_sales_manager_can_confirm(self):
self.so.with_user(self.u_smg).action_confirm()
self.assertEqual(self.so.state, 'sale')
def test_manager_can_confirm(self):
# Manager implies Sales Manager via the diamond — should also be able to confirm
u_mgr = self.env['res.users'].with_context(no_reset_password=True).create({
'login': 'gate_mgr', 'name': 'Gate Mgr',
'email': 'gate_mgr@example.com',
'group_ids': [(6, 0, [self.env.ref('fusion_plating.group_fp_manager').id])],
})
self.so.with_user(u_mgr).action_confirm()
self.assertEqual(self.so.state, 'sale')

View File

@@ -0,0 +1,104 @@
from odoo.tests.common import TransactionCase, tagged
@tagged('-at_install', 'post_install', 'fp_perms')
class TestTeamPage(TransactionCase):
"""Phase F — Owner-only Team management page.
Covers x_fc_plating_role compute/inverse + audit chatter + menu visibility."""
def setUp(self):
super().setUp()
Users = self.env['res.users'].with_context(no_reset_password=True)
self.owner = Users.create({
'login': 'team_owner', 'name': 'Team Owner',
'email': 'team_owner@example.com',
'group_ids': [(6, 0, [self.env.ref('fusion_plating.group_fp_owner').id])],
})
self.target = Users.create({
'login': 'team_target', 'name': 'Team Target',
'email': 'team_target@example.com',
'group_ids': [(6, 0, [self.env.ref('fusion_plating.group_fp_technician').id])],
})
def test_compute_returns_technician(self):
self.assertEqual(self.target.x_fc_plating_role, 'technician')
def test_compute_picks_highest_role(self):
# Add Manager group on top of Technician
self.target.write({'group_ids': [(4, self.env.ref('fusion_plating.group_fp_manager').id)]})
self.target.invalidate_recordset(['x_fc_plating_role'])
self.assertEqual(self.target.x_fc_plating_role, 'manager')
def test_inverse_sets_only_chosen_role(self):
self.target.with_user(self.owner).x_fc_plating_role = 'shop_manager'
# Shop Manager group should be present, Technician should be ABSENT
sm = self.env.ref('fusion_plating.group_fp_shop_manager_v2')
tech = self.env.ref('fusion_plating.group_fp_technician')
self.assertIn(sm, self.target.groups_id)
# Technician is implied via shop_manager_v2.implied_ids → so it IS in user's
# transitive group set. But the inverse should NOT have ADDED it directly.
# Verify by checking groups_id (which Odoo stores as the union of explicit
# + implied groups) — Technician will be present via implication. That's
# correct. What we want to verify is no OTHER plating role is set explicitly.
# Easier assertion: after setting to shop_manager, compute should return
# shop_manager (highest plating role held).
self.target.invalidate_recordset(['x_fc_plating_role'])
self.assertEqual(self.target.x_fc_plating_role, 'shop_manager')
def test_inverse_to_no_clears_all_plating_roles(self):
# Start as Manager
self.target.with_user(self.owner).x_fc_plating_role = 'manager'
self.target.invalidate_recordset(['x_fc_plating_role'])
self.assertEqual(self.target.x_fc_plating_role, 'manager')
# Set to 'no'
self.target.with_user(self.owner).x_fc_plating_role = 'no'
self.target.invalidate_recordset(['x_fc_plating_role'])
# Verify no plating group remains
plating_groups = [
self.env.ref(f'fusion_plating.group_fp_{x}', raise_if_not_found=False)
for x in ('technician', 'sales_rep', 'shop_manager_v2',
'sales_manager', 'manager', 'quality_manager', 'owner')
]
for g in plating_groups:
if g:
self.assertNotIn(g, self.target.groups_id,
f'{g.name} should be removed when role=no')
self.assertEqual(self.target.x_fc_plating_role, 'no')
def test_inverse_posts_chatter_audit(self):
before = self.target.message_ids
self.target.with_user(self.owner).x_fc_plating_role = 'manager'
after = self.target.message_ids - before
self.assertTrue(after, 'Role change must post a chatter message')
# Verify the message body mentions the role change
bodies = ' '.join(after.mapped('body'))
self.assertIn('manager', bodies.lower())
def test_team_menu_visible_to_owner(self):
menu = self.env.ref('fusion_plating.menu_fp_team', raise_if_not_found=False)
if not menu:
self.skipTest('menu_fp_team not found')
visible = self.env['ir.ui.menu'].with_user(self.owner).search_count([('id', '=', menu.id)])
self.assertTrue(visible)
def test_team_menu_hidden_from_manager(self):
menu = self.env.ref('fusion_plating.menu_fp_team', raise_if_not_found=False)
if not menu:
self.skipTest('menu_fp_team not found')
mgr = self.env['res.users'].with_context(no_reset_password=True).create({
'login': 'team_mgr', 'name': 'Team Mgr',
'email': 'team_mgr@example.com',
'group_ids': [(6, 0, [self.env.ref('fusion_plating.group_fp_manager').id])],
})
visible = self.env['ir.ui.menu'].with_user(mgr).search_count([('id', '=', menu.id)])
self.assertFalse(visible, 'Manager must not see Team menu (Owner-only)')
def test_cgp_do_field_on_company(self):
co = self.env.company
self.assertTrue(hasattr(co, 'x_fc_cgp_designated_official_id'),
'res.company must have x_fc_cgp_designated_official_id field')
def test_nadcap_authority_field_on_company(self):
co = self.env.company
self.assertTrue(hasattr(co, 'x_fc_nadcap_authority_user_id'),
'res.company must have x_fc_nadcap_authority_user_id field')

View File

@@ -116,13 +116,13 @@
</record>
<!-- Phase 1 — under Operations.
Phase 3 — supervisor+ only. Operators see their own moves on
the tablet; this is an audit view of every move. -->
Phase D (perms v2) — Shop Manager+ only. Operators see their
own moves on the tablet; this is an audit view of every move. -->
<menuitem id="menu_fp_job_step_move"
name="Parts &amp; Rack Move Log"
parent="menu_fp_operations"
action="action_fp_job_step_move"
sequence="90"
groups="fusion_plating.group_fusion_plating_supervisor"/>
groups="fusion_plating.group_fp_shop_manager_v2"/>
</odoo>

View File

@@ -133,10 +133,12 @@
</record>
<!-- Phase 1 — re-parented under Operations. -->
<!-- Phase D (perms v2) — Shop Manager+ only. Payroll/billing audit. -->
<menuitem id="menu_fp_labor_history"
name="Labor History"
parent="menu_fp_operations"
action="action_fp_labor_history"
sequence="95"/>
sequence="95"
groups="fusion_plating.group_fp_shop_manager_v2"/>
</odoo>

View File

@@ -22,14 +22,14 @@
sequence="46"
web_icon="fusion_plating,static/description/icon.png"
action="action_fp_resolve_plating_landing"
groups="group_fusion_plating_operator"/>
groups="fusion_plating.group_fp_technician,fusion_plating.group_fp_sales_rep"/>
<!-- ===== 2. CONFIGURATION + 7 Phase-2 buckets ===== -->
<menuitem id="menu_fp_config"
name="Configuration"
parent="menu_fp_root"
sequence="90"
groups="group_fusion_plating_manager"/>
groups="fusion_plating.group_fp_manager"/>
<menuitem id="menu_fp_config_shop_setup"
name="Shop Setup"
@@ -71,13 +71,14 @@
name="Compliance"
parent="menu_fp_root"
sequence="50"
groups="group_fusion_plating_supervisor"/>
groups="fusion_plating.group_fp_quality_manager"/>
<!-- ===== 4. OPERATIONS ===== -->
<menuitem id="menu_fp_operations"
name="Operations"
parent="menu_fp_root"
sequence="18"/>
sequence="18"
groups="fusion_plating.group_fp_technician"/>
<!-- ===== 5. CHILD MENUS ===== -->
@@ -112,13 +113,13 @@
action="action_fp_rack"
sequence="35"/>
<!-- Phase 3 — supervisor+: replenishment is a purchasing decision. -->
<!-- Phase D (perms v2) — Manager+: replenishment is a purchasing decision. -->
<menuitem id="menu_fp_replenishment_suggestions"
name="Replenishment Suggestions"
parent="menu_fp_operations"
action="action_fp_replenishment_suggestion"
sequence="40"
groups="fusion_plating.group_fusion_plating_supervisor"/>
groups="fusion_plating.group_fp_manager"/>
<!-- Configuration children (referencing the 7 buckets above) -->
<menuitem id="menu_fp_replenishment_rules"

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_fp_migration_preview_form" model="ir.ui.view">
<field name="name">fp.migration.preview.form</field>
<field name="model">fp.migration.preview</field>
<field name="arch" type="xml">
<form>
<header>
<button name="action_approve_and_run" type="object"
string="Approve &amp; Run"
class="oe_highlight"
invisible="state != 'pending'"
confirm="This will apply role changes to all listed users. Continue?"/>
<button name="action_cancel" type="object"
string="Cancel"
invisible="state != 'pending'"/>
<button name="action_rollback" type="object"
string="Rollback"
invisible="state != 'approved'"
confirm="This will restore all users to their pre-migration groups. Continue?"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<h1><field name="name"/></h1>
</div>
<group>
<group>
<field name="user_count"/>
<field name="warning_count"/>
</group>
<group>
<field name="approved_by_id"/>
<field name="approved_at"/>
<field name="rollback_deadline"/>
</group>
</group>
<notebook>
<page string="Users">
<field name="line_ids">
<list editable="bottom" decoration-warning="warning">
<field name="user_id"/>
<field name="current_groups"/>
<field name="proposed_role"/>
<field name="capability_delta"/>
<field name="warning" widget="boolean_toggle"/>
<field name="notes"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="view_fp_migration_preview_list" model="ir.ui.view">
<field name="name">fp.migration.preview.list</field>
<field name="model">fp.migration.preview</field>
<field name="arch" type="xml">
<list decoration-warning="state == 'pending'"
decoration-success="state == 'approved'"
decoration-muted="state in ('cancelled', 'rolled_back')">
<field name="name"/>
<field name="state" widget="badge"/>
<field name="user_count"/>
<field name="warning_count"/>
<field name="create_date"/>
<field name="approved_by_id"/>
<field name="approved_at"/>
</list>
</field>
</record>
<record id="action_fp_migration_preview" model="ir.actions.act_window">
<field name="name">Role Migrations</field>
<field name="res_model">fp.migration.preview</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_fp_migration_preview"
name="Role Migrations"
parent="fusion_plating.menu_fp_config"
action="action_fp_migration_preview"
sequence="9"
groups="fusion_plating.group_fp_owner"/>
</data>
</odoo>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Owner-only Team page: kanban of internal users grouped by plating role.
Drag-and-drop a card between columns changes the user's role
(inverse handler on res.users.x_fc_plating_role). -->
<record id="view_fp_team_kanban" model="ir.ui.view">
<field name="name">res.users.fp.team.kanban</field>
<field name="model">res.users</field>
<field name="arch" type="xml">
<kanban default_group_by="x_fc_plating_role"
class="o_kanban_small_column"
group_create="false"
group_delete="false"
records_draggable="true">
<field name="id"/>
<field name="x_fc_plating_role"/>
<field name="login"/>
<field name="email"/>
<field name="image_128"/>
<field name="login_date"/>
<field name="name"/>
<templates>
<t t-name="card" class="flex-row align-items-center">
<aside class="o_kanban_aside_full">
<field name="image_128" widget="image"
options="{'preview_image': 'image_128', 'img_class': 'rounded'}"/>
</aside>
<main class="ms-2">
<field name="name" class="fw-bolder fs-5"/>
<div t-if="record.email.raw_value" class="text-muted small">
<field name="email"/>
</div>
<div t-if="record.login_date.raw_value" class="text-muted small">
Last seen: <field name="login_date" widget="date"/>
</div>
</main>
</t>
</templates>
</kanban>
</field>
</record>
<record id="action_fp_team" model="ir.actions.act_window">
<field name="name">Team</field>
<field name="res_model">res.users</field>
<field name="view_mode">kanban,list,form</field>
<field name="domain">[('share', '=', False), ('active', '=', True)]</field>
<field name="context">{'search_default_groupby_plating_role': 1}</field>
</record>
<menuitem id="menu_fp_team"
name="Team"
parent="fusion_plating.menu_fp_config"
action="action_fp_team"
sequence="5"
groups="fusion_plating.group_fp_owner"/>
</data>
</odoo>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_company_form_fp_dos" model="ir.ui.view">
<field name="name">res.company.form.fp.designated.officials</field>
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook" position="inside">
<page string="Plating Designated Officials"
groups="fusion_plating.group_fp_owner">
<group>
<!-- No domain on the picker: Owner picks freely.
ref() in XML domains trips Odoo 19's view validator
(interpreted as field access on res.company).
The QM/Owner eligibility constraint is enforced
in Python via @api.constrains on res.company. -->
<field name="x_fc_cgp_designated_official_id"/>
<field name="x_fc_nadcap_authority_user_id"/>
</group>
</page>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Aerospace (AS9100 + Nadcap)',
'version': '19.0.1.1.0',
'version': '19.0.1.1.2',
'category': 'Manufacturing/Plating',
'summary': 'Aerospace industry pack: AS9100 Rev D clause library, Nadcap AC7108 '
'audits, counterfeit parts prevention, config management, risk register, '

View File

@@ -1,16 +1,16 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_as9100_clause_operator,fp.as9100.clause.operator,model_fusion_plating_as9100_clause,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_as9100_clause_supervisor,fp.as9100.clause.supervisor,model_fusion_plating_as9100_clause,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_as9100_clause_manager,fp.as9100.clause.manager,model_fusion_plating_as9100_clause,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_nadcap_audit_operator,fp.nadcap.audit.operator,model_fusion_plating_nadcap_audit,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_nadcap_audit_supervisor,fp.nadcap.audit.supervisor,model_fusion_plating_nadcap_audit,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_nadcap_audit_manager,fp.nadcap.audit.manager,model_fusion_plating_nadcap_audit,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_counterfeit_operator,fp.counterfeit.operator,model_fusion_plating_counterfeit_prevention,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_counterfeit_supervisor,fp.counterfeit.supervisor,model_fusion_plating_counterfeit_prevention,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_counterfeit_manager,fp.counterfeit.manager,model_fusion_plating_counterfeit_prevention,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_config_item_operator,fp.config.item.operator,model_fusion_plating_config_item,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_config_item_supervisor,fp.config.item.supervisor,model_fusion_plating_config_item,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_config_item_manager,fp.config.item.manager,model_fusion_plating_config_item,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_risk_operator,fp.risk.operator,model_fusion_plating_risk,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_risk_supervisor,fp.risk.supervisor,model_fusion_plating_risk,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_risk_manager,fp.risk.manager,model_fusion_plating_risk,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_as9100_clause_operator,fp.as9100.clause.operator,model_fusion_plating_as9100_clause,fusion_plating.group_fp_technician,1,0,0,0
access_fp_as9100_clause_supervisor,fp.as9100.clause.supervisor,model_fusion_plating_as9100_clause,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_as9100_clause_manager,fp.as9100.clause.manager,model_fusion_plating_as9100_clause,fusion_plating.group_fp_manager,1,1,1,1
access_fp_nadcap_audit_operator,fp.nadcap.audit.operator,model_fusion_plating_nadcap_audit,fusion_plating.group_fp_technician,1,0,0,0
access_fp_nadcap_audit_supervisor,fp.nadcap.audit.supervisor,model_fusion_plating_nadcap_audit,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_nadcap_audit_manager,fp.nadcap.audit.manager,model_fusion_plating_nadcap_audit,fusion_plating.group_fp_manager,1,1,1,1
access_fp_counterfeit_operator,fp.counterfeit.operator,model_fusion_plating_counterfeit_prevention,fusion_plating.group_fp_technician,1,0,0,0
access_fp_counterfeit_supervisor,fp.counterfeit.supervisor,model_fusion_plating_counterfeit_prevention,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_counterfeit_manager,fp.counterfeit.manager,model_fusion_plating_counterfeit_prevention,fusion_plating.group_fp_manager,1,1,1,1
access_fp_config_item_operator,fp.config.item.operator,model_fusion_plating_config_item,fusion_plating.group_fp_technician,1,0,0,0
access_fp_config_item_supervisor,fp.config.item.supervisor,model_fusion_plating_config_item,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_config_item_manager,fp.config.item.manager,model_fusion_plating_config_item,fusion_plating.group_fp_manager,1,1,1,1
access_fp_risk_operator,fp.risk.operator,model_fusion_plating_risk,fusion_plating.group_fp_technician,1,0,0,0
access_fp_risk_supervisor,fp.risk.supervisor,model_fusion_plating_risk,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_risk_manager,fp.risk.manager,model_fusion_plating_risk,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_as9100_clause_operator fp.as9100.clause.operator model_fusion_plating_as9100_clause fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_as9100_clause_supervisor fp.as9100.clause.supervisor model_fusion_plating_as9100_clause fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
4 access_fp_as9100_clause_manager fp.as9100.clause.manager model_fusion_plating_as9100_clause fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_fp_nadcap_audit_operator fp.nadcap.audit.operator model_fusion_plating_nadcap_audit fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
6 access_fp_nadcap_audit_supervisor fp.nadcap.audit.supervisor model_fusion_plating_nadcap_audit fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
7 access_fp_nadcap_audit_manager fp.nadcap.audit.manager model_fusion_plating_nadcap_audit fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
8 access_fp_counterfeit_operator fp.counterfeit.operator model_fusion_plating_counterfeit_prevention fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
9 access_fp_counterfeit_supervisor fp.counterfeit.supervisor model_fusion_plating_counterfeit_prevention fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
10 access_fp_counterfeit_manager fp.counterfeit.manager model_fusion_plating_counterfeit_prevention fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
11 access_fp_config_item_operator fp.config.item.operator model_fusion_plating_config_item fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
12 access_fp_config_item_supervisor fp.config.item.supervisor model_fusion_plating_config_item fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
13 access_fp_config_item_manager fp.config.item.manager model_fusion_plating_config_item fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
14 access_fp_risk_operator fp.risk.operator model_fusion_plating_risk fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
15 access_fp_risk_supervisor fp.risk.supervisor model_fusion_plating_risk fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
16 access_fp_risk_manager fp.risk.manager model_fusion_plating_risk fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -7,11 +7,12 @@
<odoo>
<!-- Phase 1 — re-parented under Plating → Compliance hub. -->
<!-- Phase D (perms v2) — QM-only under compliance hub. -->
<menuitem id="menu_fp_aerospace"
name="Aerospace (AS9100 / Nadcap)"
parent="fusion_plating.menu_fp_compliance_hub"
sequence="30"
groups="fusion_plating.group_fusion_plating_operator"/>
groups="fusion_plating.group_fp_quality_manager"/>
<menuitem id="menu_fp_aerospace_as9100"
name="AS9100 Clauses"

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Batch Processing',
'version': '19.0.2.0.0',
'version': '19.0.2.0.1',
'category': 'Manufacturing/Plating',
'summary': 'Group parts into rack or barrel loads for tank processing.',
'author': 'Nexa Systems Inc.',

View File

@@ -1,7 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_batch_operator,fp.batch.operator,model_fusion_plating_batch,fusion_plating.group_fusion_plating_operator,1,1,1,0
access_fp_batch_supervisor,fp.batch.supervisor,model_fusion_plating_batch,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_batch_manager,fp.batch.manager,model_fusion_plating_batch,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_batch_chemistry_operator,fp.batch.chemistry.operator,model_fusion_plating_batch_chemistry,fusion_plating.group_fusion_plating_operator,1,1,1,0
access_fp_batch_chemistry_supervisor,fp.batch.chemistry.supervisor,model_fusion_plating_batch_chemistry,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_batch_chemistry_manager,fp.batch.chemistry.manager,model_fusion_plating_batch_chemistry,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_batch_operator,fp.batch.operator,model_fusion_plating_batch,fusion_plating.group_fp_technician,1,1,1,0
access_fp_batch_supervisor,fp.batch.supervisor,model_fusion_plating_batch,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_batch_manager,fp.batch.manager,model_fusion_plating_batch,fusion_plating.group_fp_manager,1,1,1,1
access_fp_batch_chemistry_operator,fp.batch.chemistry.operator,model_fusion_plating_batch_chemistry,fusion_plating.group_fp_technician,1,1,1,0
access_fp_batch_chemistry_supervisor,fp.batch.chemistry.supervisor,model_fusion_plating_batch_chemistry,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_batch_chemistry_manager,fp.batch.chemistry.manager,model_fusion_plating_batch_chemistry,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_batch_operator fp.batch.operator model_fusion_plating_batch fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 0
3 access_fp_batch_supervisor fp.batch.supervisor model_fusion_plating_batch fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
4 access_fp_batch_manager fp.batch.manager model_fusion_plating_batch fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_fp_batch_chemistry_operator fp.batch.chemistry.operator model_fusion_plating_batch_chemistry fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 0
6 access_fp_batch_chemistry_supervisor fp.batch.chemistry.supervisor model_fusion_plating_batch_chemistry fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
7 access_fp_batch_chemistry_manager fp.batch.chemistry.manager model_fusion_plating_batch_chemistry fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Documents Bridge (EE)',
'version': '19.0.1.0.0',
'version': '19.0.1.0.1',
'category': 'Manufacturing/Plating',
'summary': 'Enterprise bridge: auto-promotes Fusion Plating quality attachments '
'(NCR, CAPA, FAIR, Doc Control) into Odoo EE Documents with a tagged '

View File

@@ -1,8 +1,8 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_documents_document_fp_operator,documents.document.fp.operator,documents.model_documents_document,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_documents_document_fp_supervisor,documents.document.fp.supervisor,documents.model_documents_document,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_documents_document_fp_manager,documents.document.fp.manager,documents.model_documents_document,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_documents_tag_fp_operator,documents.tag.fp.operator,documents.model_documents_tag,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_documents_tag_fp_manager,documents.tag.fp.manager,documents.model_documents_tag,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_documents_facet_fp_operator,documents.facet.fp.operator,documents.model_documents_facet,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_documents_facet_fp_manager,documents.facet.fp.manager,documents.model_documents_facet,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_documents_document_fp_operator,documents.document.fp.operator,documents.model_documents_document,fusion_plating.group_fp_technician,1,0,0,0
access_documents_document_fp_supervisor,documents.document.fp.supervisor,documents.model_documents_document,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_documents_document_fp_manager,documents.document.fp.manager,documents.model_documents_document,fusion_plating.group_fp_manager,1,1,1,1
access_documents_tag_fp_operator,documents.tag.fp.operator,documents.model_documents_tag,fusion_plating.group_fp_technician,1,0,0,0
access_documents_tag_fp_manager,documents.tag.fp.manager,documents.model_documents_tag,fusion_plating.group_fp_manager,1,1,1,1
access_documents_facet_fp_operator,documents.facet.fp.operator,documents.model_documents_facet,fusion_plating.group_fp_technician,1,0,0,0
access_documents_facet_fp_manager,documents.facet.fp.manager,documents.model_documents_facet,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_documents_document_fp_operator documents.document.fp.operator documents.model_documents_document fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_documents_document_fp_supervisor documents.document.fp.supervisor documents.model_documents_document fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
4 access_documents_document_fp_manager documents.document.fp.manager documents.model_documents_document fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_documents_tag_fp_operator documents.tag.fp.operator documents.model_documents_tag fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
6 access_documents_tag_fp_manager documents.tag.fp.manager documents.model_documents_tag fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
7 access_documents_facet_fp_operator documents.facet.fp.operator documents.model_documents_facet fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
8 access_documents_facet_fp_manager documents.facet.fp.manager documents.model_documents_facet fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -4,7 +4,7 @@
{
'name': 'Fusion Plating — Maintenance Bridge',
'version': '19.0.1.2.0',
'version': '19.0.1.2.2',
'category': 'Manufacturing/Plating',
'summary': 'Bridge standard Odoo Maintenance with Fusion Plating equipment, '
'plans, checklists, and sensor integration.',

View File

@@ -1,10 +1,10 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_maintenance_plan_operator,fp.maintenance.plan.operator,model_fp_maintenance_plan,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_maintenance_plan_supervisor,fp.maintenance.plan.supervisor,model_fp_maintenance_plan,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_maintenance_plan_manager,fp.maintenance.plan.manager,model_fp_maintenance_plan,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_maintenance_node_operator,fp.maintenance.node.operator,model_fp_maintenance_node,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_maintenance_node_supervisor,fp.maintenance.node.supervisor,model_fp_maintenance_node,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_maintenance_node_manager,fp.maintenance.node.manager,model_fp_maintenance_node,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_maintenance_label_operator,fp.maintenance.label.operator,model_fp_maintenance_label,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_maintenance_label_supervisor,fp.maintenance.label.supervisor,model_fp_maintenance_label,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_maintenance_label_manager,fp.maintenance.label.manager,model_fp_maintenance_label,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_maintenance_plan_operator,fp.maintenance.plan.operator,model_fp_maintenance_plan,fusion_plating.group_fp_technician,1,0,0,0
access_fp_maintenance_plan_supervisor,fp.maintenance.plan.supervisor,model_fp_maintenance_plan,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_maintenance_plan_manager,fp.maintenance.plan.manager,model_fp_maintenance_plan,fusion_plating.group_fp_manager,1,1,1,1
access_fp_maintenance_node_operator,fp.maintenance.node.operator,model_fp_maintenance_node,fusion_plating.group_fp_technician,1,0,0,0
access_fp_maintenance_node_supervisor,fp.maintenance.node.supervisor,model_fp_maintenance_node,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_maintenance_node_manager,fp.maintenance.node.manager,model_fp_maintenance_node,fusion_plating.group_fp_manager,1,1,1,1
access_fp_maintenance_label_operator,fp.maintenance.label.operator,model_fp_maintenance_label,fusion_plating.group_fp_technician,1,0,0,0
access_fp_maintenance_label_supervisor,fp.maintenance.label.supervisor,model_fp_maintenance_label,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_maintenance_label_manager,fp.maintenance.label.manager,model_fp_maintenance_label,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_maintenance_plan_operator fp.maintenance.plan.operator model_fp_maintenance_plan fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_maintenance_plan_supervisor fp.maintenance.plan.supervisor model_fp_maintenance_plan fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
4 access_fp_maintenance_plan_manager fp.maintenance.plan.manager model_fp_maintenance_plan fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_fp_maintenance_node_operator fp.maintenance.node.operator model_fp_maintenance_node fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
6 access_fp_maintenance_node_supervisor fp.maintenance.node.supervisor model_fp_maintenance_node fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
7 access_fp_maintenance_node_manager fp.maintenance.node.manager model_fp_maintenance_node fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
8 access_fp_maintenance_label_operator fp.maintenance.label.operator model_fp_maintenance_label fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
9 access_fp_maintenance_label_supervisor fp.maintenance.label.supervisor model_fp_maintenance_label fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
10 access_fp_maintenance_label_manager fp.maintenance.label.manager model_fp_maintenance_label fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -3,11 +3,13 @@
<!-- Phase 1 — re-parented under Plating → Operations. Maintenance
is an Operations concern, not a separate top-level. -->
<!-- Phase D (perms v2) — Shop Manager+ only. Operators don't need
to schedule plans or browse the equipment registry. -->
<menuitem id="menu_fp_maintenance"
name="Maintenance"
parent="fusion_plating.menu_fp_operations"
sequence="80"
groups="fusion_plating.group_fusion_plating_operator"/>
groups="fusion_plating.group_fp_shop_manager_v2"/>
<menuitem id="menu_fp_maintenance_active"
name="Active Events"

View File

@@ -5,7 +5,7 @@
{
"name": "Fusion Plating — MRP Bridge",
'version': '19.0.13.0.3',
'version': '19.0.13.0.5',
'category': 'Manufacturing/Plating',
'summary': 'Bridge Fusion Plating facilities, baths and tanks to Odoo MRP work orders.',
'description': """

View File

@@ -922,7 +922,7 @@ class MrpWorkorder(models.Model):
employee = self.env.user.employee_id
if not employee:
# Admins without an employee record skip the check.
if not self.env.user.has_group('fusion_plating.group_fusion_plating_manager'):
if not self.env.user.has_group('fusion_plating.group_fp_manager'):
raise UserError(_(
'You must be linked to an HR employee record to start '
'plating work orders. Contact your manager.'
@@ -942,7 +942,7 @@ class MrpWorkorder(models.Model):
inspection was cleared earlier. Plating Manager bypasses.
"""
from odoo.exceptions import UserError
if self.env.user.has_group('fusion_plating.group_fusion_plating_manager'):
if self.env.user.has_group('fusion_plating.group_fp_manager'):
return
Insp = self.env.get('fp.racking.inspection')
if Insp is None:

View File

@@ -1,14 +1,14 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_bridge_mrp_workcenter_manager,fp.bridge.mrp.workcenter.manager,mrp.model_mrp_workcenter,fusion_plating.group_fusion_plating_manager,1,1,1,0
access_fp_bridge_mrp_workcenter_supervisor,fp.bridge.mrp.workcenter.supervisor,mrp.model_mrp_workcenter,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_bridge_mrp_workorder_manager,fp.bridge.mrp.workorder.manager,mrp_workorder.model_mrp_workorder,fusion_plating.group_fusion_plating_manager,1,1,1,0
access_fp_bridge_mrp_workorder_supervisor,fp.bridge.mrp.workorder.supervisor,mrp_workorder.model_mrp_workorder,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_bridge_mrp_production_manager,fp.bridge.mrp.production.manager,mrp.model_mrp_production,fusion_plating.group_fusion_plating_manager,1,1,1,0
access_fp_bridge_mrp_production_supervisor,fp.bridge.mrp.production.supervisor,mrp.model_mrp_production,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_recipe_config_wizard_supervisor,fp.recipe.config.wizard.supervisor,model_fp_recipe_config_wizard,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_recipe_config_wizard_manager,fp.recipe.config.wizard.manager,model_fp_recipe_config_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_recipe_config_wizard_line_supervisor,fp.recipe.config.wizard.line.supervisor,model_fp_recipe_config_wizard_line,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_recipe_config_wizard_line_manager,fp.recipe.config.wizard.line.manager,model_fp_recipe_config_wizard_line,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_job_node_override_legacy_operator,fusion.plating.job.node.override.operator,model_fusion_plating_job_node_override,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_job_node_override_legacy_supervisor,fusion.plating.job.node.override.supervisor,model_fusion_plating_job_node_override,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_job_node_override_legacy_manager,fusion.plating.job.node.override.manager,model_fusion_plating_job_node_override,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_bridge_mrp_workcenter_manager,fp.bridge.mrp.workcenter.manager,mrp.model_mrp_workcenter,fusion_plating.group_fp_manager,1,1,1,0
access_fp_bridge_mrp_workcenter_supervisor,fp.bridge.mrp.workcenter.supervisor,mrp.model_mrp_workcenter,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_bridge_mrp_workorder_manager,fp.bridge.mrp.workorder.manager,mrp_workorder.model_mrp_workorder,fusion_plating.group_fp_manager,1,1,1,0
access_fp_bridge_mrp_workorder_supervisor,fp.bridge.mrp.workorder.supervisor,mrp_workorder.model_mrp_workorder,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_bridge_mrp_production_manager,fp.bridge.mrp.production.manager,mrp.model_mrp_production,fusion_plating.group_fp_manager,1,1,1,0
access_fp_bridge_mrp_production_supervisor,fp.bridge.mrp.production.supervisor,mrp.model_mrp_production,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_recipe_config_wizard_supervisor,fp.recipe.config.wizard.supervisor,model_fp_recipe_config_wizard,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_recipe_config_wizard_manager,fp.recipe.config.wizard.manager,model_fp_recipe_config_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_recipe_config_wizard_line_supervisor,fp.recipe.config.wizard.line.supervisor,model_fp_recipe_config_wizard_line,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_recipe_config_wizard_line_manager,fp.recipe.config.wizard.line.manager,model_fp_recipe_config_wizard_line,fusion_plating.group_fp_manager,1,1,1,1
access_fp_job_node_override_legacy_operator,fusion.plating.job.node.override.operator,model_fusion_plating_job_node_override,fusion_plating.group_fp_technician,1,0,0,0
access_fp_job_node_override_legacy_supervisor,fusion.plating.job.node.override.supervisor,model_fusion_plating_job_node_override,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_job_node_override_legacy_manager,fusion.plating.job.node.override.manager,model_fusion_plating_job_node_override,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_bridge_mrp_workcenter_manager fp.bridge.mrp.workcenter.manager mrp.model_mrp_workcenter fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 0
3 access_fp_bridge_mrp_workcenter_supervisor fp.bridge.mrp.workcenter.supervisor mrp.model_mrp_workcenter fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
4 access_fp_bridge_mrp_workorder_manager fp.bridge.mrp.workorder.manager mrp_workorder.model_mrp_workorder fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 0
5 access_fp_bridge_mrp_workorder_supervisor fp.bridge.mrp.workorder.supervisor mrp_workorder.model_mrp_workorder fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
6 access_fp_bridge_mrp_production_manager fp.bridge.mrp.production.manager mrp.model_mrp_production fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 0
7 access_fp_bridge_mrp_production_supervisor fp.bridge.mrp.production.supervisor mrp.model_mrp_production fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
8 access_fp_recipe_config_wizard_supervisor fp.recipe.config.wizard.supervisor model_fp_recipe_config_wizard fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
9 access_fp_recipe_config_wizard_manager fp.recipe.config.wizard.manager model_fp_recipe_config_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
10 access_fp_recipe_config_wizard_line_supervisor fp.recipe.config.wizard.line.supervisor model_fp_recipe_config_wizard_line fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
11 access_fp_recipe_config_wizard_line_manager fp.recipe.config.wizard.line.manager model_fp_recipe_config_wizard_line fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
12 access_fp_job_node_override_legacy_operator fusion.plating.job.node.override.operator model_fusion_plating_job_node_override fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
13 access_fp_job_node_override_legacy_supervisor fusion.plating.job.node.override.supervisor model_fusion_plating_job_node_override fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
14 access_fp_job_node_override_legacy_manager fusion.plating.job.node.override.manager model_fusion_plating_job_node_override fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Quality Bridge (EE)',
'version': '19.0.1.0.0',
'version': '19.0.1.0.1',
'category': 'Manufacturing/Plating',
'summary': 'Enterprise bridge: mirrors Fusion Plating NCRs into Odoo EE '
'quality.alert for native dashboard integration. Auto-installs '

View File

@@ -1,5 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_bridge_quality_alert_manager,fp.bridge.quality.alert.manager,quality.model_quality_alert,fusion_plating.group_fusion_plating_manager,1,1,1,0
access_fp_bridge_quality_alert_supervisor,fp.bridge.quality.alert.supervisor,quality.model_quality_alert,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_bridge_quality_alert_stage_manager,fp.bridge.quality.alert.stage.manager,quality.model_quality_alert_stage,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_bridge_quality_alert_team_manager,fp.bridge.quality.alert.team.manager,quality.model_quality_alert_team,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_bridge_quality_alert_manager,fp.bridge.quality.alert.manager,quality.model_quality_alert,fusion_plating.group_fp_manager,1,1,1,0
access_fp_bridge_quality_alert_supervisor,fp.bridge.quality.alert.supervisor,quality.model_quality_alert,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_bridge_quality_alert_stage_manager,fp.bridge.quality.alert.stage.manager,quality.model_quality_alert_stage,fusion_plating.group_fp_manager,1,0,0,0
access_fp_bridge_quality_alert_team_manager,fp.bridge.quality.alert.team.manager,quality.model_quality_alert_team,fusion_plating.group_fp_manager,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_bridge_quality_alert_manager fp.bridge.quality.alert.manager quality.model_quality_alert fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 0
3 access_fp_bridge_quality_alert_supervisor fp.bridge.quality.alert.supervisor quality.model_quality_alert fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
4 access_fp_bridge_quality_alert_stage_manager fp.bridge.quality.alert.stage.manager quality.model_quality_alert_stage fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 0 0 0
5 access_fp_bridge_quality_alert_team_manager fp.bridge.quality.alert.team.manager quality.model_quality_alert_team fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 0 0 0

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — E-Sign Bridge (EE)',
'version': '19.0.1.0.0',
'version': '19.0.1.0.1',
'category': 'Manufacturing/Plating',
'summary': 'Enterprise bridge: wires Fusion Plating FAIR into Odoo EE Sign for '
'legally-binding customer CoC acceptance. Auto-installs when Sign '

View File

@@ -1,3 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_bridge_sign_request_read,fp.bridge.sign.request.read,sign.model_sign_request,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_bridge_sign_request_supervisor_read,fp.bridge.sign.request.supervisor.read,sign.model_sign_request,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_bridge_sign_request_read,fp.bridge.sign.request.read,sign.model_sign_request,fusion_plating.group_fp_manager,1,0,0,0
access_fp_bridge_sign_request_supervisor_read,fp.bridge.sign.request.supervisor.read,sign.model_sign_request,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_bridge_sign_request_read fp.bridge.sign.request.read sign.model_sign_request fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 0 0 0
3 access_fp_bridge_sign_request_supervisor_read fp.bridge.sign.request.supervisor.read sign.model_sign_request fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Certificates',
'version': '19.0.7.9.0',
'version': '19.0.7.9.3',
'category': 'Manufacturing/Plating',
'summary': 'Certificate registry for CoC, thickness reports, and quality documents.',
'description': """
@@ -32,6 +32,7 @@ Includes Fischerscope thickness measurement data capture.
],
'data': [
'security/ir.model.access.csv',
'security/fp_cert_security.xml',
'data/fp_certificate_sequence_data.xml',
'views/res_config_settings_views.xml',
'views/fp_certificate_views.xml',

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!-- fp.certificate.certificate_type Selection values (per fp_certificate.py:27):
'coc', 'thickness_report', 'mill_test', 'nadcap_cert', 'customer_specific'.
FAIR is a separate model (fusion.plating.fair); no 'fair' value here.
Nadcap is the only QM-restricted type at the model level. -->
<record id="rule_fp_certificate_nadcap_qm_only" model="ir.rule">
<field name="name">FP Certificate: Nadcap edit restricted to Quality Manager</field>
<field name="model_id" ref="model_fp_certificate"/>
<field name="domain_force">[('certificate_type', '!=', 'nadcap_cert')]</field>
<field name="groups" eval="[(4, ref('fusion_plating.group_fp_manager'))]"/>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
<record id="rule_fp_certificate_all_qm" model="ir.rule">
<field name="name">FP Certificate: QM has full access to all certs</field>
<field name="model_id" ref="model_fp_certificate"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('fusion_plating.group_fp_quality_manager'))]"/>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
</data>
</odoo>

View File

@@ -1,13 +1,13 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_certificate_operator,fp.certificate.operator,model_fp_certificate,fusion_plating.group_fusion_plating_operator,1,1,0,0
access_fp_certificate_supervisor,fp.certificate.supervisor,model_fp_certificate,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_certificate_manager,fp.certificate.manager,model_fp_certificate,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_thickness_reading_operator,fp.thickness.reading.operator,model_fp_thickness_reading,fusion_plating.group_fusion_plating_operator,1,1,1,0
access_fp_thickness_reading_supervisor,fp.thickness.reading.supervisor,model_fp_thickness_reading,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_thickness_reading_manager,fp.thickness.reading.manager,model_fp_thickness_reading,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_cert_void_wiz_sup,fp.cert.void.wiz.supervisor,model_fp_cert_void_wizard,fusion_plating.group_fusion_plating_supervisor,1,1,1,1
access_fp_cert_void_wiz_mgr,fp.cert.void.wiz.manager,model_fp_cert_void_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_thickness_upload_wiz_sup,fp.thickness.upload.wiz.supervisor,model_fp_thickness_upload_wizard,fusion_plating.group_fusion_plating_supervisor,1,1,1,1
access_fp_thickness_upload_wiz_mgr,fp.thickness.upload.wiz.manager,model_fp_thickness_upload_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_thickness_upload_wiz_line_sup,fp.thickness.upload.wiz.line.supervisor,model_fp_thickness_upload_wizard_line,fusion_plating.group_fusion_plating_supervisor,1,1,1,1
access_fp_thickness_upload_wiz_line_mgr,fp.thickness.upload.wiz.line.manager,model_fp_thickness_upload_wizard_line,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_certificate_operator,fp.certificate.operator,model_fp_certificate,fusion_plating.group_fp_technician,1,1,0,0
access_fp_certificate_supervisor,fp.certificate.supervisor,model_fp_certificate,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_certificate_manager,fp.certificate.manager,model_fp_certificate,fusion_plating.group_fp_manager,1,1,1,1
access_fp_thickness_reading_operator,fp.thickness.reading.operator,model_fp_thickness_reading,fusion_plating.group_fp_technician,1,1,1,0
access_fp_thickness_reading_supervisor,fp.thickness.reading.supervisor,model_fp_thickness_reading,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_thickness_reading_manager,fp.thickness.reading.manager,model_fp_thickness_reading,fusion_plating.group_fp_manager,1,1,1,1
access_fp_cert_void_wiz_sup,fp.cert.void.wiz.supervisor,model_fp_cert_void_wizard,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_cert_void_wiz_mgr,fp.cert.void.wiz.manager,model_fp_cert_void_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_thickness_upload_wiz_sup,fp.thickness.upload.wiz.supervisor,model_fp_thickness_upload_wizard,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_thickness_upload_wiz_mgr,fp.thickness.upload.wiz.manager,model_fp_thickness_upload_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_thickness_upload_wiz_line_sup,fp.thickness.upload.wiz.line.supervisor,model_fp_thickness_upload_wizard_line,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_thickness_upload_wiz_line_mgr,fp.thickness.upload.wiz.line.manager,model_fp_thickness_upload_wizard_line,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_certificate_operator fp.certificate.operator model_fp_certificate fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 0 0
3 access_fp_certificate_supervisor fp.certificate.supervisor model_fp_certificate fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
4 access_fp_certificate_manager fp.certificate.manager model_fp_certificate fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_fp_thickness_reading_operator fp.thickness.reading.operator model_fp_thickness_reading fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 0
6 access_fp_thickness_reading_supervisor fp.thickness.reading.supervisor model_fp_thickness_reading fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
7 access_fp_thickness_reading_manager fp.thickness.reading.manager model_fp_thickness_reading fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
8 access_fp_cert_void_wiz_sup fp.cert.void.wiz.supervisor model_fp_cert_void_wizard fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
9 access_fp_cert_void_wiz_mgr fp.cert.void.wiz.manager model_fp_cert_void_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
10 access_fp_thickness_upload_wiz_sup fp.thickness.upload.wiz.supervisor model_fp_thickness_upload_wizard fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
11 access_fp_thickness_upload_wiz_mgr fp.thickness.upload.wiz.manager model_fp_thickness_upload_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
12 access_fp_thickness_upload_wiz_line_sup fp.thickness.upload.wiz.line.supervisor model_fp_thickness_upload_wizard_line fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
13 access_fp_thickness_upload_wiz_line_mgr fp.thickness.upload.wiz.line.manager model_fp_thickness_upload_wizard_line fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -39,6 +39,14 @@
<field name="arch" type="xml">
<form>
<header>
<!-- Phase D5 — Nadcap-cert restriction enforced at MODEL
layer via ir.rule (rule_fp_certificate_nadcap_qm_only
in fp_cert_security.xml). Single Issue button visible
to all Manager+ when state=draft. Manager clicking
Issue on a Nadcap cert gets AccessError from the rule.
(Strategy B with user_has_groups() inside invisible=
was rejected by Odoo 19 view validator — see CLAUDE.md
rule 13f.) -->
<button name="action_issue" string="Issue"
type="object" class="btn-primary"
invisible="state != 'draft'"/>

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Controlled Goods Program',
'version': '19.0.1.1.0',
'version': '19.0.1.2.3',
'category': 'Manufacturing/Plating',
'summary': 'Canadian Controlled Goods Program (CGP) compliance for plating '
'shops handling defence work: registration, authorized individuals, '

View File

@@ -17,7 +17,7 @@
<!-- CGP OFFICER: day-to-day CGP compliance operator -->
<record id="group_fusion_plating_cgp_officer" model="res.groups">
<field name="name">CGP Officer</field>
<field name="name">[DEPRECATED] CGP Officer</field>
<field name="sequence">50</field>
<field name="privilege_id"
ref="fusion_plating.res_groups_privilege_fusion_plating"/>
@@ -27,7 +27,7 @@
<!-- CGP DESIGNATED OFFICIAL: legally accountable per PSPC registration -->
<record id="group_fusion_plating_cgp_designated_official" model="res.groups">
<field name="name">CGP Designated Official</field>
<field name="name">[DEPRECATED] CGP Designated Official</field>
<field name="sequence">60</field>
<field name="privilege_id"
ref="fusion_plating.res_groups_privilege_fusion_plating"/>
@@ -35,6 +35,15 @@
eval="[(4, ref('group_fusion_plating_cgp_officer'))]"/>
</record>
<!-- Backward-compat: new Quality Manager implies old CGP Officer; new Owner implies old CGP DO. -->
<record id="fusion_plating.group_fp_quality_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_cgp.group_fusion_plating_cgp_officer'))]"/>
</record>
<record id="fusion_plating.group_fp_owner" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_cgp.group_fusion_plating_cgp_designated_official'))]"/>
</record>
<!-- ================================================================== -->
<!-- RECORD RULES -->
<!-- -->

View File

@@ -1,19 +1,19 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_cgp_registration_manager,fp.cgp.registration.manager,model_fusion_plating_cgp_registration,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_registration_officer,fp.cgp.registration.officer,model_fusion_plating_cgp_registration,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_ai_manager,fp.cgp.ai.manager,model_fusion_plating_cgp_authorized_individual,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_ai_officer,fp.cgp.ai.officer,model_fusion_plating_cgp_authorized_individual,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_psa_officer,fp.cgp.psa.officer,model_fusion_plating_cgp_psa,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_visitor_supervisor,fp.cgp.visitor.supervisor,model_fusion_plating_cgp_visitor,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_cgp_visitor_manager,fp.cgp.visitor.manager,model_fusion_plating_cgp_visitor,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_visitor_officer,fp.cgp.visitor.officer,model_fusion_plating_cgp_visitor,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_controlled_good_supervisor,fp.cgp.good.supervisor,model_fusion_plating_cgp_controlled_good,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_cgp_controlled_good_manager,fp.cgp.good.manager,model_fusion_plating_cgp_controlled_good,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_controlled_good_officer,fp.cgp.good.officer,model_fusion_plating_cgp_controlled_good,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_receipt_supervisor,fp.cgp.receipt.supervisor,model_fusion_plating_cgp_receipt_shipment,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_cgp_receipt_manager,fp.cgp.receipt.manager,model_fusion_plating_cgp_receipt_shipment,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_receipt_officer,fp.cgp.receipt.officer,model_fusion_plating_cgp_receipt_shipment,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_incident_officer,fp.cgp.incident.officer,model_fusion_plating_cgp_security_incident,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_access_log_supervisor,fp.cgp.access.log.supervisor,model_fusion_plating_cgp_access_log,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_cgp_access_log_manager,fp.cgp.access.log.manager,model_fusion_plating_cgp_access_log,fusion_plating.group_fusion_plating_manager,1,0,0,0
access_fp_cgp_access_log_officer,fp.cgp.access.log.officer,model_fusion_plating_cgp_access_log,group_fusion_plating_cgp_officer,1,1,1,1
access_fp_cgp_registration_manager,fp.cgp.registration.manager,model_fusion_plating_cgp_registration,fusion_plating.group_fp_manager,1,0,0,0
access_fp_cgp_registration_officer,fp.cgp.registration.officer,model_fusion_plating_cgp_registration,fusion_plating.group_fp_quality_manager,1,1,1,1
access_fp_cgp_ai_manager,fp.cgp.ai.manager,model_fusion_plating_cgp_authorized_individual,fusion_plating.group_fp_manager,1,0,0,0
access_fp_cgp_ai_officer,fp.cgp.ai.officer,model_fusion_plating_cgp_authorized_individual,fusion_plating.group_fp_quality_manager,1,1,1,1
access_fp_cgp_psa_officer,fp.cgp.psa.officer,model_fusion_plating_cgp_psa,fusion_plating.group_fp_quality_manager,1,1,1,1
access_fp_cgp_visitor_supervisor,fp.cgp.visitor.supervisor,model_fusion_plating_cgp_visitor,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_cgp_visitor_manager,fp.cgp.visitor.manager,model_fusion_plating_cgp_visitor,fusion_plating.group_fp_manager,1,0,0,0
access_fp_cgp_visitor_officer,fp.cgp.visitor.officer,model_fusion_plating_cgp_visitor,fusion_plating.group_fp_quality_manager,1,1,1,1
access_fp_cgp_controlled_good_supervisor,fp.cgp.good.supervisor,model_fusion_plating_cgp_controlled_good,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_cgp_controlled_good_manager,fp.cgp.good.manager,model_fusion_plating_cgp_controlled_good,fusion_plating.group_fp_manager,1,0,0,0
access_fp_cgp_controlled_good_officer,fp.cgp.good.officer,model_fusion_plating_cgp_controlled_good,fusion_plating.group_fp_quality_manager,1,1,1,1
access_fp_cgp_receipt_supervisor,fp.cgp.receipt.supervisor,model_fusion_plating_cgp_receipt_shipment,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_cgp_receipt_manager,fp.cgp.receipt.manager,model_fusion_plating_cgp_receipt_shipment,fusion_plating.group_fp_manager,1,0,0,0
access_fp_cgp_receipt_officer,fp.cgp.receipt.officer,model_fusion_plating_cgp_receipt_shipment,fusion_plating.group_fp_quality_manager,1,1,1,1
access_fp_cgp_incident_officer,fp.cgp.incident.officer,model_fusion_plating_cgp_security_incident,fusion_plating.group_fp_quality_manager,1,1,1,1
access_fp_cgp_access_log_supervisor,fp.cgp.access.log.supervisor,model_fusion_plating_cgp_access_log,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_cgp_access_log_manager,fp.cgp.access.log.manager,model_fusion_plating_cgp_access_log,fusion_plating.group_fp_manager,1,0,0,0
access_fp_cgp_access_log_officer,fp.cgp.access.log.officer,model_fusion_plating_cgp_access_log,fusion_plating.group_fp_quality_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_cgp_registration_manager fp.cgp.registration.manager model_fusion_plating_cgp_registration fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 0 0 0
3 access_fp_cgp_registration_officer fp.cgp.registration.officer model_fusion_plating_cgp_registration group_fusion_plating_cgp_officer fusion_plating.group_fp_quality_manager 1 1 1 1
4 access_fp_cgp_ai_manager fp.cgp.ai.manager model_fusion_plating_cgp_authorized_individual fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 0 0 0
5 access_fp_cgp_ai_officer fp.cgp.ai.officer model_fusion_plating_cgp_authorized_individual group_fusion_plating_cgp_officer fusion_plating.group_fp_quality_manager 1 1 1 1
6 access_fp_cgp_psa_officer fp.cgp.psa.officer model_fusion_plating_cgp_psa group_fusion_plating_cgp_officer fusion_plating.group_fp_quality_manager 1 1 1 1
7 access_fp_cgp_visitor_supervisor fp.cgp.visitor.supervisor model_fusion_plating_cgp_visitor fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
8 access_fp_cgp_visitor_manager fp.cgp.visitor.manager model_fusion_plating_cgp_visitor fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 0 0 0
9 access_fp_cgp_visitor_officer fp.cgp.visitor.officer model_fusion_plating_cgp_visitor group_fusion_plating_cgp_officer fusion_plating.group_fp_quality_manager 1 1 1 1
10 access_fp_cgp_controlled_good_supervisor fp.cgp.good.supervisor model_fusion_plating_cgp_controlled_good fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
11 access_fp_cgp_controlled_good_manager fp.cgp.good.manager model_fusion_plating_cgp_controlled_good fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 0 0 0
12 access_fp_cgp_controlled_good_officer fp.cgp.good.officer model_fusion_plating_cgp_controlled_good group_fusion_plating_cgp_officer fusion_plating.group_fp_quality_manager 1 1 1 1
13 access_fp_cgp_receipt_supervisor fp.cgp.receipt.supervisor model_fusion_plating_cgp_receipt_shipment fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
14 access_fp_cgp_receipt_manager fp.cgp.receipt.manager model_fusion_plating_cgp_receipt_shipment fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 0 0 0
15 access_fp_cgp_receipt_officer fp.cgp.receipt.officer model_fusion_plating_cgp_receipt_shipment group_fusion_plating_cgp_officer fusion_plating.group_fp_quality_manager 1 1 1 1
16 access_fp_cgp_incident_officer fp.cgp.incident.officer model_fusion_plating_cgp_security_incident group_fusion_plating_cgp_officer fusion_plating.group_fp_quality_manager 1 1 1 1
17 access_fp_cgp_access_log_supervisor fp.cgp.access.log.supervisor model_fusion_plating_cgp_access_log fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
18 access_fp_cgp_access_log_manager fp.cgp.access.log.manager model_fusion_plating_cgp_access_log fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 0 0 0
19 access_fp_cgp_access_log_officer fp.cgp.access.log.officer model_fusion_plating_cgp_access_log group_fusion_plating_cgp_officer fusion_plating.group_fp_quality_manager 1 1 1 1

View File

@@ -36,15 +36,22 @@
<field name="arch" type="xml">
<form string="Authorized Individual">
<header>
<!-- Phase D5 — all CGP form buttons are QM-only per spec
section 2.C (CGP fold-in lands entirely under
Quality Manager). -->
<button name="action_activate" string="Activate" type="object"
class="oe_highlight"
invisible="state == 'active'"/>
invisible="state == 'active'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_suspend" string="Suspend" type="object"
invisible="state not in ('active',)"/>
invisible="state not in ('active',)"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_revoke" string="Revoke" type="object"
invisible="state == 'revoked'"/>
invisible="state == 'revoked'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_deactivate" string="Deactivate" type="object"
invisible="state != 'active'"/>
invisible="state != 'active'"
groups="fusion_plating.group_fp_quality_manager"/>
<field name="state" widget="statusbar"
statusbar_visible="active,inactive,suspended,revoked"/>
</header>

View File

@@ -35,17 +35,23 @@
<field name="arch" type="xml">
<form string="Controlled Good" class="o_fp_cgp_classified">
<header>
<!-- Phase D5 — all CGP form buttons are QM-only per spec
section 2.C. -->
<button name="action_mark_in_process" string="In Process"
type="object"
invisible="state == 'in_process'"/>
invisible="state == 'in_process'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_mark_in_storage" string="In Storage"
type="object"
invisible="state == 'in_storage'"/>
invisible="state == 'in_storage'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_mark_shipped" string="Shipped" type="object"
invisible="state == 'shipped'"/>
invisible="state == 'shipped'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_mark_destroyed" string="Destroyed"
type="object"
invisible="state == 'destroyed'"/>
invisible="state == 'destroyed'"
groups="fusion_plating.group_fp_quality_manager"/>
<field name="state" widget="statusbar"
statusbar_visible="received,in_process,in_storage,shipped"/>
</header>

View File

@@ -35,16 +35,22 @@
<field name="arch" type="xml">
<form string="Personnel Security Assessment" class="o_fp_cgp_classified">
<header>
<!-- Phase D5 — all CGP form buttons are QM-only per spec
section 2.C. -->
<button name="action_start" string="Start" type="object"
class="oe_highlight"
invisible="state != 'draft'"/>
invisible="state != 'draft'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_complete" string="Complete" type="object"
class="oe_highlight"
invisible="state != 'in_progress'"/>
invisible="state != 'in_progress'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_expire" string="Mark Expired" type="object"
invisible="state != 'completed'"/>
invisible="state != 'completed'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_reset_to_draft" string="Reset" type="object"
invisible="state == 'draft'"/>
invisible="state == 'draft'"
groups="fusion_plating.group_fp_quality_manager"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,in_progress,completed,expired"/>
</header>

View File

@@ -37,16 +37,22 @@
<field name="arch" type="xml">
<form string="CGP Receipt / Shipment" class="o_fp_cgp_classified">
<header>
<!-- Phase D5 — all CGP form buttons are QM-only per spec
section 2.C. -->
<button name="action_authorize" string="Authorize" type="object"
class="oe_highlight"
invisible="state != 'draft'"/>
invisible="state != 'draft'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_execute" string="Execute" type="object"
class="oe_highlight"
invisible="state != 'authorized'"/>
invisible="state != 'authorized'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_close" string="Close" type="object"
invisible="state != 'executed'"/>
invisible="state != 'executed'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_reset_to_draft" string="Reset" type="object"
invisible="state == 'draft'"/>
invisible="state == 'draft'"
groups="fusion_plating.group_fp_quality_manager"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,authorized,executed,closed"/>
</header>

View File

@@ -36,17 +36,24 @@
<field name="arch" type="xml">
<form string="CGP Registration" class="o_fp_cgp_classified">
<header>
<!-- Phase D5 — all CGP form buttons are QM-only per spec
section 2.C. -->
<button name="action_mark_registered" string="Mark Registered"
type="object" class="oe_highlight"
invisible="state != 'pending'"/>
invisible="state != 'pending'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_suspend" string="Suspend" type="object"
invisible="state != 'registered'"/>
invisible="state != 'registered'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_expire" string="Mark Expired" type="object"
invisible="state not in ('registered','suspended')"/>
invisible="state not in ('registered','suspended')"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_revoke" string="Revoke" type="object"
invisible="state == 'revoked'"/>
invisible="state == 'revoked'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_reset_to_pending" string="Reset" type="object"
invisible="state == 'pending'"/>
invisible="state == 'pending'"
groups="fusion_plating.group_fp_quality_manager"/>
<field name="state" widget="statusbar"
statusbar_visible="pending,registered,suspended,expired,revoked"/>
</header>

View File

@@ -39,16 +39,22 @@
<field name="arch" type="xml">
<form string="Security Incident" class="o_fp_cgp_classified">
<header>
<!-- Phase D5 — all CGP form buttons are QM-only per spec
section 2.C. -->
<button name="action_investigate" string="Investigate"
type="object" class="oe_highlight"
invisible="state != 'discovered'"/>
invisible="state != 'discovered'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_report" string="Report to PSPC"
type="object" class="oe_highlight"
invisible="state != 'investigating'"/>
invisible="state != 'investigating'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_close" string="Close" type="object"
invisible="state not in ('investigating','reported')"/>
invisible="state not in ('investigating','reported')"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_reset" string="Reset" type="object"
invisible="state == 'discovered'"/>
invisible="state == 'discovered'"
groups="fusion_plating.group_fp_quality_manager"/>
<field name="state" widget="statusbar"
statusbar_visible="discovered,investigating,reported,closed"/>
</header>

View File

@@ -39,16 +39,22 @@
<field name="arch" type="xml">
<form string="CGP Visitor">
<header>
<!-- Phase D5 — all CGP form buttons are QM-only per spec
section 2.C. -->
<button name="action_check_in" string="Check In" type="object"
class="oe_highlight"
invisible="state != 'scheduled'"/>
invisible="state != 'scheduled'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_check_out" string="Check Out" type="object"
class="oe_highlight"
invisible="state != 'checked_in'"/>
invisible="state != 'checked_in'"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_deny" string="Deny" type="object"
invisible="state not in ('scheduled','checked_in')"/>
invisible="state not in ('scheduled','checked_in')"
groups="fusion_plating.group_fp_quality_manager"/>
<button name="action_cancel" string="Cancel" type="object"
invisible="state in ('checked_out','cancelled','denied')"/>
invisible="state in ('checked_out','cancelled','denied')"
groups="fusion_plating.group_fp_quality_manager"/>
<field name="state" widget="statusbar"
statusbar_visible="scheduled,checked_in,checked_out"/>
</header>

View File

@@ -7,11 +7,12 @@
<odoo>
<!-- Phase 1 — re-parented under Plating → Compliance hub. -->
<!-- Phase D (perms v2) — QM-only under compliance hub. -->
<menuitem id="menu_fp_cgp"
name="Controlled Goods (CGP)"
parent="fusion_plating.menu_fp_compliance_hub"
sequence="50"
groups="group_fusion_plating_cgp_officer"/>
groups="fusion_plating.group_fp_quality_manager"/>
<menuitem id="menu_fp_cgp_registration"
name="Registration"

View File

@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Plating - Compliance (Framework)',
'version': '19.0.1.3.0',
'version': '19.0.1.3.2',
'category': 'Manufacturing/Plating',
'summary': 'Jurisdiction-agnostic compliance framework: permits, discharge monitoring, waste manifests, pollutant inventory, compliance calendar, spill register.',
'description': 'Generic compliance framework. Region packs load jurisdiction-specific data.',

View File

@@ -1,37 +1,37 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_jurisdiction_operator,fp.jurisdiction.operator,model_fusion_plating_jurisdiction,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_jurisdiction_supervisor,fp.jurisdiction.supervisor,model_fusion_plating_jurisdiction,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_jurisdiction_manager,fp.jurisdiction.manager,model_fusion_plating_jurisdiction,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_regulator_operator,fp.regulator.operator,model_fusion_plating_regulator,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_regulator_supervisor,fp.regulator.supervisor,model_fusion_plating_regulator,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_regulator_manager,fp.regulator.manager,model_fusion_plating_regulator,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_permit_operator,fp.permit.operator,model_fusion_plating_permit,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_permit_supervisor,fp.permit.supervisor,model_fusion_plating_permit,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_permit_manager,fp.permit.manager,model_fusion_plating_permit,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_permit_condition_operator,fp.permit.condition.operator,model_fusion_plating_permit_condition,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_permit_condition_supervisor,fp.permit.condition.supervisor,model_fusion_plating_permit_condition,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_permit_condition_manager,fp.permit.condition.manager,model_fusion_plating_permit_condition,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_discharge_limit_operator,fp.discharge.limit.operator,model_fusion_plating_discharge_limit,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_discharge_limit_supervisor,fp.discharge.limit.supervisor,model_fusion_plating_discharge_limit,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_discharge_limit_manager,fp.discharge.limit.manager,model_fusion_plating_discharge_limit,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_discharge_sample_operator,fp.discharge.sample.operator,model_fusion_plating_discharge_sample,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_discharge_sample_supervisor,fp.discharge.sample.supervisor,model_fusion_plating_discharge_sample,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_discharge_sample_manager,fp.discharge.sample.manager,model_fusion_plating_discharge_sample,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_discharge_sample_line_operator,fp.discharge.sample.line.operator,model_fusion_plating_discharge_sample_line,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_discharge_sample_line_supervisor,fp.discharge.sample.line.supervisor,model_fusion_plating_discharge_sample_line,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_discharge_sample_line_manager,fp.discharge.sample.line.manager,model_fusion_plating_discharge_sample_line,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_waste_stream_operator,fp.waste.stream.operator,model_fusion_plating_waste_stream,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_waste_stream_supervisor,fp.waste.stream.supervisor,model_fusion_plating_waste_stream,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_waste_stream_manager,fp.waste.stream.manager,model_fusion_plating_waste_stream,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_waste_manifest_operator,fp.waste.manifest.operator,model_fusion_plating_waste_manifest,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_waste_manifest_supervisor,fp.waste.manifest.supervisor,model_fusion_plating_waste_manifest,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_waste_manifest_manager,fp.waste.manifest.manager,model_fusion_plating_waste_manifest,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_pollutant_inventory_operator,fp.pollutant.inventory.operator,model_fusion_plating_pollutant_inventory,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_pollutant_inventory_supervisor,fp.pollutant.inventory.supervisor,model_fusion_plating_pollutant_inventory,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_pollutant_inventory_manager,fp.pollutant.inventory.manager,model_fusion_plating_pollutant_inventory,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_compliance_event_operator,fp.compliance.event.operator,model_fusion_plating_compliance_event,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_compliance_event_supervisor,fp.compliance.event.supervisor,model_fusion_plating_compliance_event,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_compliance_event_manager,fp.compliance.event.manager,model_fusion_plating_compliance_event,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_spill_register_operator,fp.spill.register.operator,model_fusion_plating_spill_register,fusion_plating.group_fusion_plating_operator,1,1,1,0
access_fp_spill_register_supervisor,fp.spill.register.supervisor,model_fusion_plating_spill_register,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_spill_register_manager,fp.spill.register.manager,model_fusion_plating_spill_register,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_jurisdiction_operator,fp.jurisdiction.operator,model_fusion_plating_jurisdiction,fusion_plating.group_fp_technician,1,0,0,0
access_fp_jurisdiction_supervisor,fp.jurisdiction.supervisor,model_fusion_plating_jurisdiction,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_jurisdiction_manager,fp.jurisdiction.manager,model_fusion_plating_jurisdiction,fusion_plating.group_fp_manager,1,1,1,1
access_fp_regulator_operator,fp.regulator.operator,model_fusion_plating_regulator,fusion_plating.group_fp_technician,1,0,0,0
access_fp_regulator_supervisor,fp.regulator.supervisor,model_fusion_plating_regulator,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_regulator_manager,fp.regulator.manager,model_fusion_plating_regulator,fusion_plating.group_fp_manager,1,1,1,1
access_fp_permit_operator,fp.permit.operator,model_fusion_plating_permit,fusion_plating.group_fp_technician,1,0,0,0
access_fp_permit_supervisor,fp.permit.supervisor,model_fusion_plating_permit,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_permit_manager,fp.permit.manager,model_fusion_plating_permit,fusion_plating.group_fp_manager,1,1,1,1
access_fp_permit_condition_operator,fp.permit.condition.operator,model_fusion_plating_permit_condition,fusion_plating.group_fp_technician,1,0,0,0
access_fp_permit_condition_supervisor,fp.permit.condition.supervisor,model_fusion_plating_permit_condition,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_permit_condition_manager,fp.permit.condition.manager,model_fusion_plating_permit_condition,fusion_plating.group_fp_manager,1,1,1,1
access_fp_discharge_limit_operator,fp.discharge.limit.operator,model_fusion_plating_discharge_limit,fusion_plating.group_fp_technician,1,0,0,0
access_fp_discharge_limit_supervisor,fp.discharge.limit.supervisor,model_fusion_plating_discharge_limit,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_discharge_limit_manager,fp.discharge.limit.manager,model_fusion_plating_discharge_limit,fusion_plating.group_fp_manager,1,1,1,1
access_fp_discharge_sample_operator,fp.discharge.sample.operator,model_fusion_plating_discharge_sample,fusion_plating.group_fp_technician,1,0,0,0
access_fp_discharge_sample_supervisor,fp.discharge.sample.supervisor,model_fusion_plating_discharge_sample,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_discharge_sample_manager,fp.discharge.sample.manager,model_fusion_plating_discharge_sample,fusion_plating.group_fp_manager,1,1,1,1
access_fp_discharge_sample_line_operator,fp.discharge.sample.line.operator,model_fusion_plating_discharge_sample_line,fusion_plating.group_fp_technician,1,0,0,0
access_fp_discharge_sample_line_supervisor,fp.discharge.sample.line.supervisor,model_fusion_plating_discharge_sample_line,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_discharge_sample_line_manager,fp.discharge.sample.line.manager,model_fusion_plating_discharge_sample_line,fusion_plating.group_fp_manager,1,1,1,1
access_fp_waste_stream_operator,fp.waste.stream.operator,model_fusion_plating_waste_stream,fusion_plating.group_fp_technician,1,0,0,0
access_fp_waste_stream_supervisor,fp.waste.stream.supervisor,model_fusion_plating_waste_stream,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_waste_stream_manager,fp.waste.stream.manager,model_fusion_plating_waste_stream,fusion_plating.group_fp_manager,1,1,1,1
access_fp_waste_manifest_operator,fp.waste.manifest.operator,model_fusion_plating_waste_manifest,fusion_plating.group_fp_technician,1,0,0,0
access_fp_waste_manifest_supervisor,fp.waste.manifest.supervisor,model_fusion_plating_waste_manifest,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_waste_manifest_manager,fp.waste.manifest.manager,model_fusion_plating_waste_manifest,fusion_plating.group_fp_manager,1,1,1,1
access_fp_pollutant_inventory_operator,fp.pollutant.inventory.operator,model_fusion_plating_pollutant_inventory,fusion_plating.group_fp_technician,1,0,0,0
access_fp_pollutant_inventory_supervisor,fp.pollutant.inventory.supervisor,model_fusion_plating_pollutant_inventory,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_pollutant_inventory_manager,fp.pollutant.inventory.manager,model_fusion_plating_pollutant_inventory,fusion_plating.group_fp_manager,1,1,1,1
access_fp_compliance_event_operator,fp.compliance.event.operator,model_fusion_plating_compliance_event,fusion_plating.group_fp_technician,1,0,0,0
access_fp_compliance_event_supervisor,fp.compliance.event.supervisor,model_fusion_plating_compliance_event,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_compliance_event_manager,fp.compliance.event.manager,model_fusion_plating_compliance_event,fusion_plating.group_fp_manager,1,1,1,1
access_fp_spill_register_operator,fp.spill.register.operator,model_fusion_plating_spill_register,fusion_plating.group_fp_technician,1,1,1,0
access_fp_spill_register_supervisor,fp.spill.register.supervisor,model_fusion_plating_spill_register,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_spill_register_manager,fp.spill.register.manager,model_fusion_plating_spill_register,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_jurisdiction_operator fp.jurisdiction.operator model_fusion_plating_jurisdiction fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_jurisdiction_supervisor fp.jurisdiction.supervisor model_fusion_plating_jurisdiction fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
4 access_fp_jurisdiction_manager fp.jurisdiction.manager model_fusion_plating_jurisdiction fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_fp_regulator_operator fp.regulator.operator model_fusion_plating_regulator fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
6 access_fp_regulator_supervisor fp.regulator.supervisor model_fusion_plating_regulator fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
7 access_fp_regulator_manager fp.regulator.manager model_fusion_plating_regulator fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
8 access_fp_permit_operator fp.permit.operator model_fusion_plating_permit fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
9 access_fp_permit_supervisor fp.permit.supervisor model_fusion_plating_permit fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
10 access_fp_permit_manager fp.permit.manager model_fusion_plating_permit fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
11 access_fp_permit_condition_operator fp.permit.condition.operator model_fusion_plating_permit_condition fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
12 access_fp_permit_condition_supervisor fp.permit.condition.supervisor model_fusion_plating_permit_condition fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
13 access_fp_permit_condition_manager fp.permit.condition.manager model_fusion_plating_permit_condition fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
14 access_fp_discharge_limit_operator fp.discharge.limit.operator model_fusion_plating_discharge_limit fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
15 access_fp_discharge_limit_supervisor fp.discharge.limit.supervisor model_fusion_plating_discharge_limit fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
16 access_fp_discharge_limit_manager fp.discharge.limit.manager model_fusion_plating_discharge_limit fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
17 access_fp_discharge_sample_operator fp.discharge.sample.operator model_fusion_plating_discharge_sample fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
18 access_fp_discharge_sample_supervisor fp.discharge.sample.supervisor model_fusion_plating_discharge_sample fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
19 access_fp_discharge_sample_manager fp.discharge.sample.manager model_fusion_plating_discharge_sample fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
20 access_fp_discharge_sample_line_operator fp.discharge.sample.line.operator model_fusion_plating_discharge_sample_line fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
21 access_fp_discharge_sample_line_supervisor fp.discharge.sample.line.supervisor model_fusion_plating_discharge_sample_line fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
22 access_fp_discharge_sample_line_manager fp.discharge.sample.line.manager model_fusion_plating_discharge_sample_line fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
23 access_fp_waste_stream_operator fp.waste.stream.operator model_fusion_plating_waste_stream fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
24 access_fp_waste_stream_supervisor fp.waste.stream.supervisor model_fusion_plating_waste_stream fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
25 access_fp_waste_stream_manager fp.waste.stream.manager model_fusion_plating_waste_stream fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
26 access_fp_waste_manifest_operator fp.waste.manifest.operator model_fusion_plating_waste_manifest fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
27 access_fp_waste_manifest_supervisor fp.waste.manifest.supervisor model_fusion_plating_waste_manifest fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
28 access_fp_waste_manifest_manager fp.waste.manifest.manager model_fusion_plating_waste_manifest fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
29 access_fp_pollutant_inventory_operator fp.pollutant.inventory.operator model_fusion_plating_pollutant_inventory fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
30 access_fp_pollutant_inventory_supervisor fp.pollutant.inventory.supervisor model_fusion_plating_pollutant_inventory fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
31 access_fp_pollutant_inventory_manager fp.pollutant.inventory.manager model_fusion_plating_pollutant_inventory fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
32 access_fp_compliance_event_operator fp.compliance.event.operator model_fusion_plating_compliance_event fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
33 access_fp_compliance_event_supervisor fp.compliance.event.supervisor model_fusion_plating_compliance_event fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
34 access_fp_compliance_event_manager fp.compliance.event.manager model_fusion_plating_compliance_event fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
35 access_fp_spill_register_operator fp.spill.register.operator model_fusion_plating_spill_register fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 0
36 access_fp_spill_register_supervisor fp.spill.register.supervisor model_fusion_plating_spill_register fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
37 access_fp_spill_register_manager fp.spill.register.manager model_fusion_plating_spill_register fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -3,7 +3,8 @@
<!-- Phase 1 — re-parented under fusion_plating.menu_fp_compliance_hub
and renamed to 'General' since the hub is now the top-level Compliance. -->
<menuitem id="menu_fp_compliance_root" name="General"
parent="fusion_plating.menu_fp_compliance_hub" sequence="10"/>
parent="fusion_plating.menu_fp_compliance_hub" sequence="10"
groups="fusion_plating.group_fp_quality_manager"/>
<menuitem id="menu_fp_compliance_permit" name="Permits" parent="menu_fp_compliance_root" action="action_fp_permit" sequence="10"/>
<menuitem id="menu_fp_compliance_discharge_sample" name="Discharge Samples" parent="menu_fp_compliance_root" action="action_fp_discharge_sample" sequence="20"/>

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Configurator',
'version': '19.0.21.7.2',
'version': '19.0.21.8.4',
'category': 'Manufacturing/Plating',
'summary': 'Quotation configurator with part catalog, coating configs, and formula-based pricing engine.',
'description': """

View File

@@ -212,7 +212,7 @@ class FpSerial(models.Model):
correction is needed (e.g. wrong serial marked shipped). Audit
trail preserved via chatter; never silently rewrites history."""
for rec in self:
if not self.env.user.has_group('fusion_plating.group_fusion_plating_manager'):
if not self.env.user.has_group('fusion_plating.group_fp_manager'):
from odoo.exceptions import UserError
raise UserError(_(
'Only the Plating Manager group can reopen a terminal '

View File

@@ -3,7 +3,8 @@
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import api, fields, models
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class SaleOrder(models.Model):
@@ -835,6 +836,17 @@ class SaleOrder(models.Model):
# Auto-assigned once at confirm so every confirmed line has one; still
# editable afterwards (clearable, overridable to match a customer scheme).
def action_confirm(self):
# Phase G of permissions overhaul: only Sales Manager+ can confirm
# Sale Orders. Sales Rep can save drafts but cannot move them to
# 'sale' state. The has_group() check resolves True for Sales Manager,
# Manager (implies Sales Manager via diamond), Quality Manager
# (implies Manager), and Owner (implies Quality Manager) — see
# spec Section 2.B.
if not self.env.user.has_group('fusion_plating.group_fp_sales_manager'):
raise UserError(_(
'Only Sales Manager or higher can confirm Sale Orders. '
'Please ask a Sales Manager to confirm this quote.'
))
res = super().action_confirm()
Sequence = self.env['ir.sequence']
for so in self:

View File

@@ -7,14 +7,14 @@
<odoo>
<record id="group_fp_estimator" model="res.groups">
<field name="name">Estimator</field>
<field name="name">[DEPRECATED] Estimator</field>
<field name="sequence">50</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[(4, ref('fusion_plating.group_fusion_plating_supervisor'))]"/>
</record>
<record id="group_fp_shop_manager" model="res.groups">
<field name="name">Shop Manager</field>
<field name="name">[DEPRECATED] Shop Manager</field>
<field name="sequence">60</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[
@@ -24,4 +24,10 @@
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
<!-- Backward-compat: new Sales Rep role implies old Estimator group so existing ACLs still resolve.
Lives here (not in fusion_plating core) to avoid fresh-install forward-ref. -->
<record id="fusion_plating.group_fp_sales_rep" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_configurator.group_fp_estimator'))]"/>
</record>
</odoo>

View File

@@ -1,46 +1,46 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_part_catalog_operator,fp.part.catalog.operator,model_fp_part_catalog,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_part_catalog_estimator,fp.part.catalog.estimator,model_fp_part_catalog,fusion_plating_configurator.group_fp_estimator,1,1,1,0
access_fp_part_catalog_manager,fp.part.catalog.manager,model_fp_part_catalog,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_pricing_rule_operator,fp.pricing.rule.operator,model_fp_pricing_rule,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_pricing_rule_estimator,fp.pricing.rule.estimator,model_fp_pricing_rule,fusion_plating_configurator.group_fp_estimator,1,1,1,0
access_fp_pricing_rule_manager,fp.pricing.rule.manager,model_fp_pricing_rule,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_pricing_surcharge_operator,fp.pricing.complexity.surcharge.operator,model_fp_pricing_complexity_surcharge,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_pricing_surcharge_estimator,fp.pricing.complexity.surcharge.estimator,model_fp_pricing_complexity_surcharge,fusion_plating_configurator.group_fp_estimator,1,1,1,0
access_fp_pricing_surcharge_manager,fp.pricing.complexity.surcharge.manager,model_fp_pricing_complexity_surcharge,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_quote_configurator_operator,fp.quote.configurator.operator,model_fp_quote_configurator,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_quote_configurator_estimator,fp.quote.configurator.estimator,model_fp_quote_configurator,fusion_plating_configurator.group_fp_estimator,1,1,1,0
access_fp_quote_configurator_manager,fp.quote.configurator.manager,model_fp_quote_configurator,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_direct_order_wizard_estimator,fp.direct.order.wizard.estimator,model_fp_direct_order_wizard,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_direct_order_wizard_manager,fp.direct.order.wizard.manager,model_fp_direct_order_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_direct_order_line_estimator,fp.direct.order.line.estimator,model_fp_direct_order_line,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_direct_order_line_manager,fp.direct.order.line.manager,model_fp_direct_order_line,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_add_from_so_wizard_estimator,fp.add.from.so.wizard.estimator,model_fp_add_from_so_wizard,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_add_from_so_wizard_manager,fp.add.from.so.wizard.manager,model_fp_add_from_so_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_add_from_quote_wizard_estimator,fp.add.from.quote.wizard.estimator,model_fp_add_from_quote_wizard,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_add_from_quote_wizard_manager,fp.add.from.quote.wizard.manager,model_fp_add_from_quote_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_quote_promote_wizard_estimator,fp.quote.promote.wizard.estimator,model_fp_quote_promote_wizard,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_quote_promote_wizard_manager,fp.quote.promote.wizard.manager,model_fp_quote_promote_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_part_catalog_operator,fp.part.catalog.operator,model_fp_part_catalog,fusion_plating.group_fp_technician,1,0,0,0
access_fp_part_catalog_estimator,fp.part.catalog.estimator,model_fp_part_catalog,fusion_plating.group_fp_sales_rep,1,1,1,0
access_fp_part_catalog_manager,fp.part.catalog.manager,model_fp_part_catalog,fusion_plating.group_fp_manager,1,1,1,1
access_fp_pricing_rule_operator,fp.pricing.rule.operator,model_fp_pricing_rule,fusion_plating.group_fp_technician,1,0,0,0
access_fp_pricing_rule_estimator,fp.pricing.rule.estimator,model_fp_pricing_rule,fusion_plating.group_fp_sales_rep,1,1,1,0
access_fp_pricing_rule_manager,fp.pricing.rule.manager,model_fp_pricing_rule,fusion_plating.group_fp_manager,1,1,1,1
access_fp_pricing_surcharge_operator,fp.pricing.complexity.surcharge.operator,model_fp_pricing_complexity_surcharge,fusion_plating.group_fp_technician,1,0,0,0
access_fp_pricing_surcharge_estimator,fp.pricing.complexity.surcharge.estimator,model_fp_pricing_complexity_surcharge,fusion_plating.group_fp_sales_rep,1,1,1,0
access_fp_pricing_surcharge_manager,fp.pricing.complexity.surcharge.manager,model_fp_pricing_complexity_surcharge,fusion_plating.group_fp_manager,1,1,1,1
access_fp_quote_configurator_operator,fp.quote.configurator.operator,model_fp_quote_configurator,fusion_plating.group_fp_technician,1,0,0,0
access_fp_quote_configurator_estimator,fp.quote.configurator.estimator,model_fp_quote_configurator,fusion_plating.group_fp_sales_rep,1,1,1,0
access_fp_quote_configurator_manager,fp.quote.configurator.manager,model_fp_quote_configurator,fusion_plating.group_fp_manager,1,1,1,1
access_fp_direct_order_wizard_estimator,fp.direct.order.wizard.estimator,model_fp_direct_order_wizard,fusion_plating.group_fp_sales_rep,1,1,1,1
access_fp_direct_order_wizard_manager,fp.direct.order.wizard.manager,model_fp_direct_order_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_direct_order_line_estimator,fp.direct.order.line.estimator,model_fp_direct_order_line,fusion_plating.group_fp_sales_rep,1,1,1,1
access_fp_direct_order_line_manager,fp.direct.order.line.manager,model_fp_direct_order_line,fusion_plating.group_fp_manager,1,1,1,1
access_fp_add_from_so_wizard_estimator,fp.add.from.so.wizard.estimator,model_fp_add_from_so_wizard,fusion_plating.group_fp_sales_rep,1,1,1,1
access_fp_add_from_so_wizard_manager,fp.add.from.so.wizard.manager,model_fp_add_from_so_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_add_from_quote_wizard_estimator,fp.add.from.quote.wizard.estimator,model_fp_add_from_quote_wizard,fusion_plating.group_fp_sales_rep,1,1,1,1
access_fp_add_from_quote_wizard_manager,fp.add.from.quote.wizard.manager,model_fp_add_from_quote_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_quote_promote_wizard_estimator,fp.quote.promote.wizard.estimator,model_fp_quote_promote_wizard,fusion_plating.group_fp_sales_rep,1,1,1,1
access_fp_quote_promote_wizard_manager,fp.quote.promote.wizard.manager,model_fp_quote_promote_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_sale_assembly_user,fp.sale.assembly.user,model_fp_sale_assembly,base.group_user,1,0,0,0
access_fp_sale_assembly_estimator,fp.sale.assembly.estimator,model_fp_sale_assembly,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_sale_assembly_manager,fp.sale.assembly.manager,model_fp_sale_assembly,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_sale_assembly_estimator,fp.sale.assembly.estimator,model_fp_sale_assembly,fusion_plating.group_fp_sales_rep,1,1,1,1
access_fp_sale_assembly_manager,fp.sale.assembly.manager,model_fp_sale_assembly,fusion_plating.group_fp_manager,1,1,1,1
access_fp_sale_assembly_line_user,fp.sale.assembly.line.user,model_fp_sale_assembly_line,base.group_user,1,0,0,0
access_fp_sale_assembly_line_estimator,fp.sale.assembly.line.estimator,model_fp_sale_assembly_line,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_sale_assembly_line_manager,fp.sale.assembly.line.manager,model_fp_sale_assembly_line,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_part_import_wizard_estimator,fp.part.catalog.import.wizard.estimator,model_fp_part_catalog_import_wizard,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_part_import_wizard_manager,fp.part.catalog.import.wizard.manager,model_fp_part_catalog_import_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_sale_assembly_line_estimator,fp.sale.assembly.line.estimator,model_fp_sale_assembly_line,fusion_plating.group_fp_sales_rep,1,1,1,1
access_fp_sale_assembly_line_manager,fp.sale.assembly.line.manager,model_fp_sale_assembly_line,fusion_plating.group_fp_manager,1,1,1,1
access_fp_part_import_wizard_estimator,fp.part.catalog.import.wizard.estimator,model_fp_part_catalog_import_wizard,fusion_plating.group_fp_sales_rep,1,1,1,1
access_fp_part_import_wizard_manager,fp.part.catalog.import.wizard.manager,model_fp_part_catalog_import_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_sale_desc_template_user,fp.sale.description.template.user,model_fp_sale_description_template,base.group_user,1,0,0,0
access_fp_sale_desc_template_estimator,fp.sale.description.template.estimator,model_fp_sale_description_template,fusion_plating_configurator.group_fp_estimator,1,1,1,0
access_fp_sale_desc_template_manager,fp.sale.description.template.manager,model_fp_sale_description_template,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_sale_desc_template_estimator,fp.sale.description.template.estimator,model_fp_sale_description_template,fusion_plating.group_fp_sales_rep,1,1,1,0
access_fp_sale_desc_template_manager,fp.sale.description.template.manager,model_fp_sale_description_template,fusion_plating.group_fp_manager,1,1,1,1
access_fp_serial_user,fp.serial.user,model_fp_serial,base.group_user,1,0,0,0
access_fp_serial_estimator,fp.serial.estimator,model_fp_serial,fusion_plating_configurator.group_fp_estimator,1,1,1,0
access_fp_serial_manager,fp.serial.manager,model_fp_serial,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_serial_bulk_add_estimator,fp.serial.bulk.add.estimator,model_fp_serial_bulk_add_wizard,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_serial_bulk_add_manager,fp.serial.bulk.add.manager,model_fp_serial_bulk_add_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_part_revision_bump_estimator,fp.part.revision.bump.estimator,model_fp_part_revision_bump_wizard,fusion_plating_configurator.group_fp_estimator,1,1,1,1
access_fp_part_revision_bump_manager,fp.part.revision.bump.manager,model_fp_part_revision_bump_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_serial_estimator,fp.serial.estimator,model_fp_serial,fusion_plating.group_fp_sales_rep,1,1,1,0
access_fp_serial_manager,fp.serial.manager,model_fp_serial,fusion_plating.group_fp_manager,1,1,1,1
access_fp_serial_bulk_add_estimator,fp.serial.bulk.add.estimator,model_fp_serial_bulk_add_wizard,fusion_plating.group_fp_sales_rep,1,1,1,1
access_fp_serial_bulk_add_manager,fp.serial.bulk.add.manager,model_fp_serial_bulk_add_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_part_revision_bump_estimator,fp.part.revision.bump.estimator,model_fp_part_revision_bump_wizard,fusion_plating.group_fp_sales_rep,1,1,1,1
access_fp_part_revision_bump_manager,fp.part.revision.bump.manager,model_fp_part_revision_bump_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_part_material_user,fp.part.material.user,model_fp_part_material,base.group_user,1,0,0,0
access_fp_part_material_estimator,fp.part.material.estimator,model_fp_part_material,fusion_plating_configurator.group_fp_estimator,1,1,1,0
access_fp_part_material_manager,fp.part.material.manager,model_fp_part_material,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_part_material_estimator,fp.part.material.estimator,model_fp_part_material,fusion_plating.group_fp_sales_rep,1,1,1,0
access_fp_part_material_manager,fp.part.material.manager,model_fp_part_material,fusion_plating.group_fp_manager,1,1,1,1
access_fp_so_job_sort_user,fp.so.job.sort.user,model_fp_so_job_sort,base.group_user,1,1,1,0
access_fp_so_job_sort_manager,fp.so.job.sort.manager,model_fp_so_job_sort,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_so_job_sort_manager,fp.so.job.sort.manager,model_fp_so_job_sort,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_part_catalog_operator fp.part.catalog.operator model_fp_part_catalog fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_part_catalog_estimator fp.part.catalog.estimator model_fp_part_catalog fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 0
4 access_fp_part_catalog_manager fp.part.catalog.manager model_fp_part_catalog fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_fp_pricing_rule_operator fp.pricing.rule.operator model_fp_pricing_rule fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
6 access_fp_pricing_rule_estimator fp.pricing.rule.estimator model_fp_pricing_rule fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 0
7 access_fp_pricing_rule_manager fp.pricing.rule.manager model_fp_pricing_rule fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
8 access_fp_pricing_surcharge_operator fp.pricing.complexity.surcharge.operator model_fp_pricing_complexity_surcharge fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
9 access_fp_pricing_surcharge_estimator fp.pricing.complexity.surcharge.estimator model_fp_pricing_complexity_surcharge fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 0
10 access_fp_pricing_surcharge_manager fp.pricing.complexity.surcharge.manager model_fp_pricing_complexity_surcharge fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
11 access_fp_quote_configurator_operator fp.quote.configurator.operator model_fp_quote_configurator fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
12 access_fp_quote_configurator_estimator fp.quote.configurator.estimator model_fp_quote_configurator fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 0
13 access_fp_quote_configurator_manager fp.quote.configurator.manager model_fp_quote_configurator fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
14 access_fp_direct_order_wizard_estimator fp.direct.order.wizard.estimator model_fp_direct_order_wizard fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 1
15 access_fp_direct_order_wizard_manager fp.direct.order.wizard.manager model_fp_direct_order_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
16 access_fp_direct_order_line_estimator fp.direct.order.line.estimator model_fp_direct_order_line fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 1
17 access_fp_direct_order_line_manager fp.direct.order.line.manager model_fp_direct_order_line fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
18 access_fp_add_from_so_wizard_estimator fp.add.from.so.wizard.estimator model_fp_add_from_so_wizard fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 1
19 access_fp_add_from_so_wizard_manager fp.add.from.so.wizard.manager model_fp_add_from_so_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
20 access_fp_add_from_quote_wizard_estimator fp.add.from.quote.wizard.estimator model_fp_add_from_quote_wizard fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 1
21 access_fp_add_from_quote_wizard_manager fp.add.from.quote.wizard.manager model_fp_add_from_quote_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
22 access_fp_quote_promote_wizard_estimator fp.quote.promote.wizard.estimator model_fp_quote_promote_wizard fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 1
23 access_fp_quote_promote_wizard_manager fp.quote.promote.wizard.manager model_fp_quote_promote_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
24 access_fp_sale_assembly_user fp.sale.assembly.user model_fp_sale_assembly base.group_user 1 0 0 0
25 access_fp_sale_assembly_estimator fp.sale.assembly.estimator model_fp_sale_assembly fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 1
26 access_fp_sale_assembly_manager fp.sale.assembly.manager model_fp_sale_assembly fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
27 access_fp_sale_assembly_line_user fp.sale.assembly.line.user model_fp_sale_assembly_line base.group_user 1 0 0 0
28 access_fp_sale_assembly_line_estimator fp.sale.assembly.line.estimator model_fp_sale_assembly_line fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 1
29 access_fp_sale_assembly_line_manager fp.sale.assembly.line.manager model_fp_sale_assembly_line fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
30 access_fp_part_import_wizard_estimator fp.part.catalog.import.wizard.estimator model_fp_part_catalog_import_wizard fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 1
31 access_fp_part_import_wizard_manager fp.part.catalog.import.wizard.manager model_fp_part_catalog_import_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
32 access_fp_sale_desc_template_user fp.sale.description.template.user model_fp_sale_description_template base.group_user 1 0 0 0
33 access_fp_sale_desc_template_estimator fp.sale.description.template.estimator model_fp_sale_description_template fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 0
34 access_fp_sale_desc_template_manager fp.sale.description.template.manager model_fp_sale_description_template fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
35 access_fp_serial_user fp.serial.user model_fp_serial base.group_user 1 0 0 0
36 access_fp_serial_estimator fp.serial.estimator model_fp_serial fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 0
37 access_fp_serial_manager fp.serial.manager model_fp_serial fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
38 access_fp_serial_bulk_add_estimator fp.serial.bulk.add.estimator model_fp_serial_bulk_add_wizard fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 1
39 access_fp_serial_bulk_add_manager fp.serial.bulk.add.manager model_fp_serial_bulk_add_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
40 access_fp_part_revision_bump_estimator fp.part.revision.bump.estimator model_fp_part_revision_bump_wizard fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 1
41 access_fp_part_revision_bump_manager fp.part.revision.bump.manager model_fp_part_revision_bump_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
42 access_fp_part_material_user fp.part.material.user model_fp_part_material base.group_user 1 0 0 0
43 access_fp_part_material_estimator fp.part.material.estimator model_fp_part_material fusion_plating_configurator.group_fp_estimator fusion_plating.group_fp_sales_rep 1 1 1 0
44 access_fp_part_material_manager fp.part.material.manager model_fp_part_material fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
45 access_fp_so_job_sort_user fp.so.job.sort.user model_fp_so_job_sort base.group_user 1 1 1 0
46 access_fp_so_job_sort_manager fp.so.job.sort.manager model_fp_so_job_sort fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -29,7 +29,7 @@
name="Sales"
parent="fusion_plating.menu_fp_root"
sequence="5"
groups="group_fp_estimator,fusion_plating.group_fusion_plating_supervisor"/>
groups="fusion_plating.group_fp_sales_rep"/>
<!-- === New Quote — top-of-menu entry point for a fresh quote === -->
<menuitem id="menu_fp_new_quote"

View File

@@ -13,9 +13,18 @@
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<!-- Header buttons: make draft Confirm the primary CTA, demote/rename
Send to "Send Email" (red), and reorder so Confirm sits first. -->
Send to "Send Email" (red), and reorder so Confirm sits first.
Phase D5 — gate Confirm button to Sales Manager + higher; matches
the model-level gate from Phase G so Sales Rep sees the SO in
draft but no Confirm button. -->
<xpath expr="//header/button[@name='action_confirm' and not(@id)]" position="attributes">
<attribute name="class">btn-primary</attribute>
<attribute name="groups">fusion_plating.group_fp_sales_manager</attribute>
</xpath>
<!-- Also gate the state=sent Confirm button (id="action_confirm") so
Sales Reps don't see EITHER variant. Matches the model-level gate. -->
<xpath expr="//header/button[@id='action_confirm']" position="attributes">
<attribute name="groups">fusion_plating.group_fp_sales_manager</attribute>
</xpath>
<xpath expr="//header/button[@id='quotation_send_primary']" position="attributes">
<attribute name="string">Send Email</attribute>
@@ -359,6 +368,22 @@
<field name="x_fc_quote_id" optional="hide"/>
<field name="x_fc_rush_order" optional="hide"/>
</xpath>
<!-- Phase D5 — gate pricing columns/totals to Sales Rep + higher
(defense in depth — Technician/Shop Manager don't see pricing
even if they navigate to an SO). -->
<xpath expr="//field[@name='order_line']/list/field[@name='price_unit']" position="attributes">
<attribute name="groups">fusion_plating.group_fp_sales_rep</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/list/field[@name='price_subtotal']" position="attributes">
<attribute name="groups">fusion_plating.group_fp_sales_rep</attribute>
</xpath>
<!-- Odoo 19: amount_total / amount_untaxed / amount_tax are rendered
by the single tax_totals widget; no separate fields. Gate the
widget itself to hide the entire totals block from non-Sales-Rep. -->
<xpath expr="//field[@name='tax_totals']" position="attributes">
<attribute name="groups">fusion_plating.group_fp_sales_rep</attribute>
</xpath>
</field>
</record>

View File

@@ -458,14 +458,13 @@ class FpDirectOrderWizard(models.Model):
# Resolved through commercial_partner so a hold on the company
# blocks every child-contact entry too.
commercial = self.partner_id.commercial_partner_id
# Bypass: Plating Manager OR Plating Administrator. Both checked
# because Odoo's implied_ids cascade (Administrator → Manager)
# doesn't always propagate to existing users on upgrade. See
# CLAUDE.md "Implied group cascade" rule.
can_override = (
self.env.user.has_group('fusion_plating.group_fusion_plating_manager')
or self.env.user.has_group('fusion_plating.group_fusion_plating_administrator')
)
# Bypass: Plating Manager (or anything above — Quality Manager,
# Owner — via the Phase A implied_ids diamond). Phase G fix:
# old code also checked 'group_fusion_plating_administrator',
# an xmlid that never existed and always returned False
# (audit-finding-11). The Manager check alone is now correct
# because Manager → Quality Manager → Owner via Phase A.
can_override = self.env.user.has_group('fusion_plating.group_fp_manager')
if (getattr(commercial, 'x_fc_account_hold', False)
and not self.env.context.get('fp_skip_account_hold')
and not can_override):

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Culture & Values',
'version': '19.0.1.1.0',
'version': '19.0.1.1.1',
'category': 'Manufacturing/Plating',
'summary': 'Configurable culture framework for plating shops: values, fundamentals, peer recognitions, rotation schedules. Each shop loads its own values.',
'description': """

View File

@@ -1,13 +1,13 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_value_set_operator,fp.value.set.operator,model_fusion_plating_value_set,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_value_set_supervisor,fp.value.set.supervisor,model_fusion_plating_value_set,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_value_set_manager,fp.value.set.manager,model_fusion_plating_value_set,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_value_operator,fp.value.operator,model_fusion_plating_value,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_value_supervisor,fp.value.supervisor,model_fusion_plating_value,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_value_manager,fp.value.manager,model_fusion_plating_value,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_value_rotation_operator,fp.value.rotation.operator,model_fusion_plating_value_rotation,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_value_rotation_supervisor,fp.value.rotation.supervisor,model_fusion_plating_value_rotation,fusion_plating.group_fusion_plating_supervisor,1,1,0,0
access_fp_value_rotation_manager,fp.value.rotation.manager,model_fusion_plating_value_rotation,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_value_recognition_operator,fp.value.recognition.operator,model_fusion_plating_value_recognition,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_value_recognition_supervisor,fp.value.recognition.supervisor,model_fusion_plating_value_recognition,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_value_recognition_manager,fp.value.recognition.manager,model_fusion_plating_value_recognition,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_value_set_operator,fp.value.set.operator,model_fusion_plating_value_set,fusion_plating.group_fp_technician,1,0,0,0
access_fp_value_set_supervisor,fp.value.set.supervisor,model_fusion_plating_value_set,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_value_set_manager,fp.value.set.manager,model_fusion_plating_value_set,fusion_plating.group_fp_manager,1,1,1,1
access_fp_value_operator,fp.value.operator,model_fusion_plating_value,fusion_plating.group_fp_technician,1,0,0,0
access_fp_value_supervisor,fp.value.supervisor,model_fusion_plating_value,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_value_manager,fp.value.manager,model_fusion_plating_value,fusion_plating.group_fp_manager,1,1,1,1
access_fp_value_rotation_operator,fp.value.rotation.operator,model_fusion_plating_value_rotation,fusion_plating.group_fp_technician,1,0,0,0
access_fp_value_rotation_supervisor,fp.value.rotation.supervisor,model_fusion_plating_value_rotation,fusion_plating.group_fp_shop_manager_v2,1,1,0,0
access_fp_value_rotation_manager,fp.value.rotation.manager,model_fusion_plating_value_rotation,fusion_plating.group_fp_manager,1,1,1,1
access_fp_value_recognition_operator,fp.value.recognition.operator,model_fusion_plating_value_recognition,fusion_plating.group_fp_technician,1,0,0,0
access_fp_value_recognition_supervisor,fp.value.recognition.supervisor,model_fusion_plating_value_recognition,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_value_recognition_manager,fp.value.recognition.manager,model_fusion_plating_value_recognition,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_value_set_operator fp.value.set.operator model_fusion_plating_value_set fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_value_set_supervisor fp.value.set.supervisor model_fusion_plating_value_set fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
4 access_fp_value_set_manager fp.value.set.manager model_fusion_plating_value_set fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_fp_value_operator fp.value.operator model_fusion_plating_value fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
6 access_fp_value_supervisor fp.value.supervisor model_fusion_plating_value fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
7 access_fp_value_manager fp.value.manager model_fusion_plating_value fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
8 access_fp_value_rotation_operator fp.value.rotation.operator model_fusion_plating_value_rotation fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
9 access_fp_value_rotation_supervisor fp.value.rotation.supervisor model_fusion_plating_value_rotation fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 0 0
10 access_fp_value_rotation_manager fp.value.rotation.manager model_fusion_plating_value_rotation fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
11 access_fp_value_recognition_operator fp.value.recognition.operator model_fusion_plating_value_recognition fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
12 access_fp_value_recognition_supervisor fp.value.recognition.supervisor model_fusion_plating_value_recognition fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
13 access_fp_value_recognition_manager fp.value.recognition.manager model_fusion_plating_value_recognition fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Invoicing',
'version': '19.0.3.5.0',
'version': '19.0.3.6.3',
'category': 'Manufacturing/Plating',
'summary': 'Invoice strategy engine with deposit, progress billing, net terms, COD/prepay, and account holds.',
'description': """

View File

@@ -29,10 +29,12 @@ class ResPartner(models.Model):
See CLAUDE.md "Implied group cascade" rule.
"""
user = self.env.user
return (
user.has_group('fusion_plating.group_fusion_plating_manager')
or user.has_group('fusion_plating.group_fusion_plating_administrator')
)
# Phase G: fixed audit-finding-11 — old code referenced
# 'fusion_plating.group_fusion_plating_administrator', an xmlid
# that never existed, so the gate always returned False. Replaced
# with group_fp_manager which transitively implies Owner via
# implied_ids in Phase A's diamond hierarchy.
return user.has_group('fusion_plating.group_fp_manager')
x_fc_account_hold_reason = fields.Text(string='Hold Reason')
x_fc_account_hold_date = fields.Datetime(
string='Hold Date', help='When the hold was placed.',

View File

@@ -7,11 +7,16 @@
<odoo>
<record id="group_fp_accounting" model="res.groups">
<field name="name">Accounting</field>
<field name="name">[DEPRECATED] Accounting</field>
<field name="sequence">58</field>
<field name="privilege_id" ref="fusion_plating.res_groups_privilege_fusion_plating"/>
<field name="implied_ids" eval="[(4, ref('fusion_plating.group_fusion_plating_supervisor'))]"/>
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
<!-- Backward-compat: new Manager role implies old Accounting group. -->
<record id="fusion_plating.group_fp_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_invoicing.group_fp_accounting'))]"/>
</record>
</odoo>

View File

@@ -1,4 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_invoice_strategy_operator,fp.invoice.strategy.default.operator,model_fp_invoice_strategy_default,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_invoice_strategy_accounting,fp.invoice.strategy.default.accounting,model_fp_invoice_strategy_default,group_fp_accounting,1,1,1,0
access_fp_invoice_strategy_manager,fp.invoice.strategy.default.manager,model_fp_invoice_strategy_default,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_invoice_strategy_operator,fp.invoice.strategy.default.operator,model_fp_invoice_strategy_default,fusion_plating.group_fp_technician,1,0,0,0
access_fp_invoice_strategy_accounting,fp.invoice.strategy.default.accounting,model_fp_invoice_strategy_default,fusion_plating.group_fp_manager,1,1,1,0
access_fp_invoice_strategy_manager,fp.invoice.strategy.default.manager,model_fp_invoice_strategy_default,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_invoice_strategy_operator fp.invoice.strategy.default.operator model_fp_invoice_strategy_default fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_invoice_strategy_accounting fp.invoice.strategy.default.accounting model_fp_invoice_strategy_default group_fp_accounting fusion_plating.group_fp_manager 1 1 1 0
4 access_fp_invoice_strategy_manager fp.invoice.strategy.default.manager model_fp_invoice_strategy_default fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -53,8 +53,16 @@
</group>
</page>
<!-- Phase D5 — Account Hold management (the override gate per
spec section 2.E Layer 3). Was previously gated on the
fold-in group_fp_accounting; consolidated to group_fp_manager
and resolves audit-finding-11 _administrator typo by removing
the old fold-in group from this surface. The Python helper
_fp_user_can_override_account_hold (still in res_partner.py)
is the runtime gate; Phase G fixes the Python-side typo
separately. -->
<page string="Account Hold" name="account_hold_tab"
groups="fusion_plating_invoicing.group_fp_accounting">
groups="fusion_plating.group_fp_manager">
<group>
<group>
<field name="x_fc_account_hold"/>

View File

@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Plating — Native Jobs',
'version': '19.0.10.24.0',
'version': '19.0.10.24.2',
'category': 'Manufacturing/Plating',
'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.',
'author': 'Nexa Systems Inc.',

View File

@@ -26,7 +26,7 @@ class FpJobScanController(http.Controller):
# Otherwise (operator) → land on process tree client action
# (will be wired once process tree is added).
user = request.env.user
is_manager = user.has_group('fusion_plating.group_fusion_plating_manager')
is_manager = user.has_group('fusion_plating.group_fp_manager')
if is_manager:
return request.redirect(
'/odoo/action-fusion_plating.action_fp_job/%d' % job.id

View File

@@ -1,28 +1,28 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_job_node_override_operator,fp.job.node.override.operator,model_fp_job_node_override,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_job_node_override_supervisor,fp.job.node.override.supervisor,model_fp_job_node_override,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_job_node_override_manager,fp.job.node.override.manager,model_fp_job_node_override,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_job_consumption_operator,fp.job.consumption.operator,model_fp_job_consumption,fusion_plating.group_fusion_plating_operator,1,1,1,0
access_fp_job_consumption_supervisor,fp.job.consumption.supervisor,model_fp_job_consumption,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_job_consumption_manager,fp.job.consumption.manager,model_fp_job_consumption,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_job_step_move_wiz_op,fp.job.step.move.wiz.operator,model_fp_job_step_move_wizard,fusion_plating.group_fusion_plating_operator,1,1,1,1
access_fp_job_step_move_wiz_sup,fp.job.step.move.wiz.supervisor,model_fp_job_step_move_wizard,fusion_plating.group_fusion_plating_supervisor,1,1,1,1
access_fp_job_step_move_wiz_mgr,fp.job.step.move.wiz.manager,model_fp_job_step_move_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_job_step_move_wiz_in_op,fp.job.step.move.wiz.in.operator,model_fp_job_step_move_wizard_input,fusion_plating.group_fusion_plating_operator,1,1,1,1
access_fp_job_step_move_wiz_in_sup,fp.job.step.move.wiz.in.supervisor,model_fp_job_step_move_wizard_input,fusion_plating.group_fusion_plating_supervisor,1,1,1,1
access_fp_job_step_move_wiz_in_mgr,fp.job.step.move.wiz.in.manager,model_fp_job_step_move_wizard_input,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_job_step_input_wiz_op,fp.job.step.input.wiz.operator,model_fp_job_step_input_wizard,fusion_plating.group_fusion_plating_operator,1,1,1,1
access_fp_job_step_input_wiz_sup,fp.job.step.input.wiz.supervisor,model_fp_job_step_input_wizard,fusion_plating.group_fusion_plating_supervisor,1,1,1,1
access_fp_job_step_input_wiz_mgr,fp.job.step.input.wiz.manager,model_fp_job_step_input_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_job_step_input_wiz_l_op,fp.job.step.input.wiz.l.operator,model_fp_job_step_input_wizard_line,fusion_plating.group_fusion_plating_operator,1,1,1,1
access_fp_job_step_input_wiz_l_sup,fp.job.step.input.wiz.l.supervisor,model_fp_job_step_input_wizard_line,fusion_plating.group_fusion_plating_supervisor,1,1,1,1
access_fp_job_step_input_wiz_l_mgr,fp.job.step.input.wiz.l.manager,model_fp_job_step_input_wizard_line,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_workflow_state_op,fp.workflow.state.operator,model_fp_job_workflow_state,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_workflow_state_sup,fp.workflow.state.supervisor,model_fp_job_workflow_state,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_workflow_state_mgr,fp.workflow.state.manager,model_fp_job_workflow_state,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_cert_issue_wiz_sup,fp.cert.issue.wiz.supervisor,model_fp_cert_issue_wizard,fusion_plating.group_fusion_plating_supervisor,1,1,1,1
access_fp_cert_issue_wiz_mgr,fp.cert.issue.wiz.manager,model_fp_cert_issue_wizard,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_cert_issue_wiz_l_sup,fp.cert.issue.wiz.l.supervisor,model_fp_cert_issue_wizard_line,fusion_plating.group_fusion_plating_supervisor,1,1,1,1
access_fp_cert_issue_wiz_l_mgr,fp.cert.issue.wiz.l.manager,model_fp_cert_issue_wizard_line,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_cert_issue_wiz_r_sup,fp.cert.issue.wiz.r.supervisor,model_fp_cert_issue_wizard_reading,fusion_plating.group_fusion_plating_supervisor,1,1,1,1
access_fp_cert_issue_wiz_r_mgr,fp.cert.issue.wiz.r.manager,model_fp_cert_issue_wizard_reading,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_job_node_override_operator,fp.job.node.override.operator,model_fp_job_node_override,fusion_plating.group_fp_technician,1,0,0,0
access_fp_job_node_override_supervisor,fp.job.node.override.supervisor,model_fp_job_node_override,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_job_node_override_manager,fp.job.node.override.manager,model_fp_job_node_override,fusion_plating.group_fp_manager,1,1,1,1
access_fp_job_consumption_operator,fp.job.consumption.operator,model_fp_job_consumption,fusion_plating.group_fp_technician,1,1,1,0
access_fp_job_consumption_supervisor,fp.job.consumption.supervisor,model_fp_job_consumption,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_job_consumption_manager,fp.job.consumption.manager,model_fp_job_consumption,fusion_plating.group_fp_manager,1,1,1,1
access_fp_job_step_move_wiz_op,fp.job.step.move.wiz.operator,model_fp_job_step_move_wizard,fusion_plating.group_fp_technician,1,1,1,1
access_fp_job_step_move_wiz_sup,fp.job.step.move.wiz.supervisor,model_fp_job_step_move_wizard,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_job_step_move_wiz_mgr,fp.job.step.move.wiz.manager,model_fp_job_step_move_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_job_step_move_wiz_in_op,fp.job.step.move.wiz.in.operator,model_fp_job_step_move_wizard_input,fusion_plating.group_fp_technician,1,1,1,1
access_fp_job_step_move_wiz_in_sup,fp.job.step.move.wiz.in.supervisor,model_fp_job_step_move_wizard_input,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_job_step_move_wiz_in_mgr,fp.job.step.move.wiz.in.manager,model_fp_job_step_move_wizard_input,fusion_plating.group_fp_manager,1,1,1,1
access_fp_job_step_input_wiz_op,fp.job.step.input.wiz.operator,model_fp_job_step_input_wizard,fusion_plating.group_fp_technician,1,1,1,1
access_fp_job_step_input_wiz_sup,fp.job.step.input.wiz.supervisor,model_fp_job_step_input_wizard,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_job_step_input_wiz_mgr,fp.job.step.input.wiz.manager,model_fp_job_step_input_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_job_step_input_wiz_l_op,fp.job.step.input.wiz.l.operator,model_fp_job_step_input_wizard_line,fusion_plating.group_fp_technician,1,1,1,1
access_fp_job_step_input_wiz_l_sup,fp.job.step.input.wiz.l.supervisor,model_fp_job_step_input_wizard_line,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_job_step_input_wiz_l_mgr,fp.job.step.input.wiz.l.manager,model_fp_job_step_input_wizard_line,fusion_plating.group_fp_manager,1,1,1,1
access_fp_workflow_state_op,fp.workflow.state.operator,model_fp_job_workflow_state,fusion_plating.group_fp_technician,1,0,0,0
access_fp_workflow_state_sup,fp.workflow.state.supervisor,model_fp_job_workflow_state,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_workflow_state_mgr,fp.workflow.state.manager,model_fp_job_workflow_state,fusion_plating.group_fp_manager,1,1,1,1
access_fp_cert_issue_wiz_sup,fp.cert.issue.wiz.supervisor,model_fp_cert_issue_wizard,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_cert_issue_wiz_mgr,fp.cert.issue.wiz.manager,model_fp_cert_issue_wizard,fusion_plating.group_fp_manager,1,1,1,1
access_fp_cert_issue_wiz_l_sup,fp.cert.issue.wiz.l.supervisor,model_fp_cert_issue_wizard_line,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_cert_issue_wiz_l_mgr,fp.cert.issue.wiz.l.manager,model_fp_cert_issue_wizard_line,fusion_plating.group_fp_manager,1,1,1,1
access_fp_cert_issue_wiz_r_sup,fp.cert.issue.wiz.r.supervisor,model_fp_cert_issue_wizard_reading,fusion_plating.group_fp_shop_manager_v2,1,1,1,1
access_fp_cert_issue_wiz_r_mgr,fp.cert.issue.wiz.r.manager,model_fp_cert_issue_wizard_reading,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_job_node_override_operator fp.job.node.override.operator model_fp_job_node_override fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_job_node_override_supervisor fp.job.node.override.supervisor model_fp_job_node_override fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
4 access_fp_job_node_override_manager fp.job.node.override.manager model_fp_job_node_override fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_fp_job_consumption_operator fp.job.consumption.operator model_fp_job_consumption fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 0
6 access_fp_job_consumption_supervisor fp.job.consumption.supervisor model_fp_job_consumption fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
7 access_fp_job_consumption_manager fp.job.consumption.manager model_fp_job_consumption fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
8 access_fp_job_step_move_wiz_op fp.job.step.move.wiz.operator model_fp_job_step_move_wizard fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 1
9 access_fp_job_step_move_wiz_sup fp.job.step.move.wiz.supervisor model_fp_job_step_move_wizard fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
10 access_fp_job_step_move_wiz_mgr fp.job.step.move.wiz.manager model_fp_job_step_move_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
11 access_fp_job_step_move_wiz_in_op fp.job.step.move.wiz.in.operator model_fp_job_step_move_wizard_input fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 1
12 access_fp_job_step_move_wiz_in_sup fp.job.step.move.wiz.in.supervisor model_fp_job_step_move_wizard_input fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
13 access_fp_job_step_move_wiz_in_mgr fp.job.step.move.wiz.in.manager model_fp_job_step_move_wizard_input fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
14 access_fp_job_step_input_wiz_op fp.job.step.input.wiz.operator model_fp_job_step_input_wizard fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 1
15 access_fp_job_step_input_wiz_sup fp.job.step.input.wiz.supervisor model_fp_job_step_input_wizard fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
16 access_fp_job_step_input_wiz_mgr fp.job.step.input.wiz.manager model_fp_job_step_input_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
17 access_fp_job_step_input_wiz_l_op fp.job.step.input.wiz.l.operator model_fp_job_step_input_wizard_line fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 1 1 1
18 access_fp_job_step_input_wiz_l_sup fp.job.step.input.wiz.l.supervisor model_fp_job_step_input_wizard_line fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
19 access_fp_job_step_input_wiz_l_mgr fp.job.step.input.wiz.l.manager model_fp_job_step_input_wizard_line fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
20 access_fp_workflow_state_op fp.workflow.state.operator model_fp_job_workflow_state fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
21 access_fp_workflow_state_sup fp.workflow.state.supervisor model_fp_job_workflow_state fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
22 access_fp_workflow_state_mgr fp.workflow.state.manager model_fp_job_workflow_state fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
23 access_fp_cert_issue_wiz_sup fp.cert.issue.wiz.supervisor model_fp_cert_issue_wizard fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
24 access_fp_cert_issue_wiz_mgr fp.cert.issue.wiz.manager model_fp_cert_issue_wizard fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
25 access_fp_cert_issue_wiz_l_sup fp.cert.issue.wiz.l.supervisor model_fp_cert_issue_wizard_line fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
26 access_fp_cert_issue_wiz_l_mgr fp.cert.issue.wiz.l.manager model_fp_cert_issue_wizard_line fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
27 access_fp_cert_issue_wiz_r_sup fp.cert.issue.wiz.r.supervisor model_fp_cert_issue_wizard_reading fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 1
28 access_fp_cert_issue_wiz_r_mgr fp.cert.issue.wiz.r.manager model_fp_cert_issue_wizard_reading fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -6,7 +6,7 @@
admin can manually add themselves via Settings > Users if
they need to access historical MO/WO data. -->
<record id="group_fusion_plating_legacy_menus" model="res.groups">
<field name="name">Plating Legacy Menus</field>
<field name="name">[DEPRECATED] Plating Legacy Menus</field>
<field name="comment">Internal group to hide legacy MO/WO menus that have been replaced by the native fp.job model. Add a user to this group only if they need to navigate historical mrp.production / mrp.workorder records directly.</field>
</record>
</odoo>

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — KPI Dashboard',
'version': '19.0.1.1.0',
'version': '19.0.1.1.2',
'category': 'Manufacturing/Plating',
'summary': 'Configurable KPI dashboards for plating operations.',
'author': 'Nexa Systems Inc.',

View File

@@ -1,7 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_kpi_operator,fp.kpi.operator,model_fusion_plating_kpi,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_kpi_supervisor,fp.kpi.supervisor,model_fusion_plating_kpi,fusion_plating.group_fusion_plating_supervisor,1,1,0,0
access_fp_kpi_manager,fp.kpi.manager,model_fusion_plating_kpi,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_kpi_value_operator,fp.kpi.value.operator,model_fusion_plating_kpi_value,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_kpi_value_supervisor,fp.kpi.value.supervisor,model_fusion_plating_kpi_value,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_kpi_value_manager,fp.kpi.value.manager,model_fusion_plating_kpi_value,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_kpi_operator,fp.kpi.operator,model_fusion_plating_kpi,fusion_plating.group_fp_technician,1,0,0,0
access_fp_kpi_supervisor,fp.kpi.supervisor,model_fusion_plating_kpi,fusion_plating.group_fp_shop_manager_v2,1,1,0,0
access_fp_kpi_manager,fp.kpi.manager,model_fusion_plating_kpi,fusion_plating.group_fp_manager,1,1,1,1
access_fp_kpi_value_operator,fp.kpi.value.operator,model_fusion_plating_kpi_value,fusion_plating.group_fp_technician,1,0,0,0
access_fp_kpi_value_supervisor,fp.kpi.value.supervisor,model_fusion_plating_kpi_value,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_kpi_value_manager,fp.kpi.value.manager,model_fusion_plating_kpi_value,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_kpi_operator fp.kpi.operator model_fusion_plating_kpi fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_kpi_supervisor fp.kpi.supervisor model_fusion_plating_kpi fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 0 0
4 access_fp_kpi_manager fp.kpi.manager model_fusion_plating_kpi fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_fp_kpi_value_operator fp.kpi.value.operator model_fusion_plating_kpi_value fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
6 access_fp_kpi_value_supervisor fp.kpi.value.supervisor model_fusion_plating_kpi_value fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
7 access_fp_kpi_value_manager fp.kpi.value.manager model_fusion_plating_kpi_value fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -6,12 +6,12 @@
-->
<odoo>
<!-- Phase 3 — supervisor+ only. Operators don't need dashboards. -->
<!-- Phase D (perms v2) — Manager+ only. Operators don't need dashboards. -->
<menuitem id="menu_fp_dashboard"
name="KPIs"
parent="fusion_plating.menu_fp_root"
sequence="85"
groups="fusion_plating.group_fusion_plating_supervisor"/>
groups="fusion_plating.group_fp_manager"/>
<menuitem id="menu_fp_kpis"
name="KPIs"

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Logistics',
'version': '19.0.3.11.0',
'version': '19.0.3.11.1',
'category': 'Manufacturing/Plating',
'summary': (
'Pickup & delivery for plating shops: vehicle master, driver '

View File

@@ -1,22 +1,22 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_vehicle_operator,fp.vehicle.operator,model_fusion_plating_vehicle,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_vehicle_supervisor,fp.vehicle.supervisor,model_fusion_plating_vehicle,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_vehicle_manager,fp.vehicle.manager,model_fusion_plating_vehicle,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_pickup_request_operator,fp.pickup.request.operator,model_fusion_plating_pickup_request,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_pickup_request_supervisor,fp.pickup.request.supervisor,model_fusion_plating_pickup_request,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_pickup_request_manager,fp.pickup.request.manager,model_fusion_plating_pickup_request,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_delivery_operator,fp.delivery.operator,model_fusion_plating_delivery,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_delivery_supervisor,fp.delivery.supervisor,model_fusion_plating_delivery,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_delivery_manager,fp.delivery.manager,model_fusion_plating_delivery,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_route_operator,fp.route.operator,model_fusion_plating_route,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_route_supervisor,fp.route.supervisor,model_fusion_plating_route,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_route_manager,fp.route.manager,model_fusion_plating_route,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_route_stop_operator,fp.route.stop.operator,model_fusion_plating_route_stop,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_route_stop_supervisor,fp.route.stop.supervisor,model_fusion_plating_route_stop,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_route_stop_manager,fp.route.stop.manager,model_fusion_plating_route_stop,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_chain_of_custody_operator,fp.chain.of.custody.operator,model_fusion_plating_chain_of_custody,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_chain_of_custody_supervisor,fp.chain.of.custody.supervisor,model_fusion_plating_chain_of_custody,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_chain_of_custody_manager,fp.chain.of.custody.manager,model_fusion_plating_chain_of_custody,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_proof_of_delivery_operator,fp.proof.of.delivery.operator,model_fusion_plating_proof_of_delivery,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_proof_of_delivery_supervisor,fp.proof.of.delivery.supervisor,model_fusion_plating_proof_of_delivery,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_proof_of_delivery_manager,fp.proof.of.delivery.manager,model_fusion_plating_proof_of_delivery,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_vehicle_operator,fp.vehicle.operator,model_fusion_plating_vehicle,fusion_plating.group_fp_technician,1,0,0,0
access_fp_vehicle_supervisor,fp.vehicle.supervisor,model_fusion_plating_vehicle,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_vehicle_manager,fp.vehicle.manager,model_fusion_plating_vehicle,fusion_plating.group_fp_manager,1,1,1,1
access_fp_pickup_request_operator,fp.pickup.request.operator,model_fusion_plating_pickup_request,fusion_plating.group_fp_technician,1,0,0,0
access_fp_pickup_request_supervisor,fp.pickup.request.supervisor,model_fusion_plating_pickup_request,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_pickup_request_manager,fp.pickup.request.manager,model_fusion_plating_pickup_request,fusion_plating.group_fp_manager,1,1,1,1
access_fp_delivery_operator,fp.delivery.operator,model_fusion_plating_delivery,fusion_plating.group_fp_technician,1,0,0,0
access_fp_delivery_supervisor,fp.delivery.supervisor,model_fusion_plating_delivery,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_delivery_manager,fp.delivery.manager,model_fusion_plating_delivery,fusion_plating.group_fp_manager,1,1,1,1
access_fp_route_operator,fp.route.operator,model_fusion_plating_route,fusion_plating.group_fp_technician,1,0,0,0
access_fp_route_supervisor,fp.route.supervisor,model_fusion_plating_route,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_route_manager,fp.route.manager,model_fusion_plating_route,fusion_plating.group_fp_manager,1,1,1,1
access_fp_route_stop_operator,fp.route.stop.operator,model_fusion_plating_route_stop,fusion_plating.group_fp_technician,1,0,0,0
access_fp_route_stop_supervisor,fp.route.stop.supervisor,model_fusion_plating_route_stop,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_route_stop_manager,fp.route.stop.manager,model_fusion_plating_route_stop,fusion_plating.group_fp_manager,1,1,1,1
access_fp_chain_of_custody_operator,fp.chain.of.custody.operator,model_fusion_plating_chain_of_custody,fusion_plating.group_fp_technician,1,0,0,0
access_fp_chain_of_custody_supervisor,fp.chain.of.custody.supervisor,model_fusion_plating_chain_of_custody,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_chain_of_custody_manager,fp.chain.of.custody.manager,model_fusion_plating_chain_of_custody,fusion_plating.group_fp_manager,1,1,1,1
access_fp_proof_of_delivery_operator,fp.proof.of.delivery.operator,model_fusion_plating_proof_of_delivery,fusion_plating.group_fp_technician,1,0,0,0
access_fp_proof_of_delivery_supervisor,fp.proof.of.delivery.supervisor,model_fusion_plating_proof_of_delivery,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_proof_of_delivery_manager,fp.proof.of.delivery.manager,model_fusion_plating_proof_of_delivery,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_vehicle_operator fp.vehicle.operator model_fusion_plating_vehicle fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_vehicle_supervisor fp.vehicle.supervisor model_fusion_plating_vehicle fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
4 access_fp_vehicle_manager fp.vehicle.manager model_fusion_plating_vehicle fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_fp_pickup_request_operator fp.pickup.request.operator model_fusion_plating_pickup_request fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
6 access_fp_pickup_request_supervisor fp.pickup.request.supervisor model_fusion_plating_pickup_request fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
7 access_fp_pickup_request_manager fp.pickup.request.manager model_fusion_plating_pickup_request fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
8 access_fp_delivery_operator fp.delivery.operator model_fusion_plating_delivery fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
9 access_fp_delivery_supervisor fp.delivery.supervisor model_fusion_plating_delivery fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
10 access_fp_delivery_manager fp.delivery.manager model_fusion_plating_delivery fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
11 access_fp_route_operator fp.route.operator model_fusion_plating_route fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
12 access_fp_route_supervisor fp.route.supervisor model_fusion_plating_route fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
13 access_fp_route_manager fp.route.manager model_fusion_plating_route fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
14 access_fp_route_stop_operator fp.route.stop.operator model_fusion_plating_route_stop fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
15 access_fp_route_stop_supervisor fp.route.stop.supervisor model_fusion_plating_route_stop fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
16 access_fp_route_stop_manager fp.route.stop.manager model_fusion_plating_route_stop fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
17 access_fp_chain_of_custody_operator fp.chain.of.custody.operator model_fusion_plating_chain_of_custody fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
18 access_fp_chain_of_custody_supervisor fp.chain.of.custody.supervisor model_fusion_plating_chain_of_custody fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
19 access_fp_chain_of_custody_manager fp.chain.of.custody.manager model_fusion_plating_chain_of_custody fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
20 access_fp_proof_of_delivery_operator fp.proof.of.delivery.operator model_fusion_plating_proof_of_delivery fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
21 access_fp_proof_of_delivery_supervisor fp.proof.of.delivery.supervisor model_fusion_plating_proof_of_delivery fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
22 access_fp_proof_of_delivery_manager fp.proof.of.delivery.manager model_fusion_plating_proof_of_delivery fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Notifications',
'version': '19.0.6.6.0',
'version': '19.0.6.6.1',
'category': 'Manufacturing/Plating',
'summary': 'Auto-email notifications at workflow milestones with configurable templates, PDF attachments, and audit log.',
'author': 'Nexa Systems Inc.',

View File

@@ -1,6 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_notification_template_operator,fp.notification.template.operator,model_fp_notification_template,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_notification_template_manager,fp.notification.template.manager,model_fp_notification_template,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_notification_log_operator,fp.notification.log.operator,model_fp_notification_log,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_notification_log_supervisor,fp.notification.log.supervisor,model_fp_notification_log,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_notification_log_manager,fp.notification.log.manager,model_fp_notification_log,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_notification_template_operator,fp.notification.template.operator,model_fp_notification_template,fusion_plating.group_fp_technician,1,0,0,0
access_fp_notification_template_manager,fp.notification.template.manager,model_fp_notification_template,fusion_plating.group_fp_manager,1,1,1,1
access_fp_notification_log_operator,fp.notification.log.operator,model_fp_notification_log,fusion_plating.group_fp_technician,1,0,0,0
access_fp_notification_log_supervisor,fp.notification.log.supervisor,model_fp_notification_log,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_notification_log_manager,fp.notification.log.manager,model_fp_notification_log,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_notification_template_operator fp.notification.template.operator model_fp_notification_template fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_notification_template_manager fp.notification.template.manager model_fp_notification_template fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
4 access_fp_notification_log_operator fp.notification.log.operator model_fp_notification_log fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
5 access_fp_notification_log_supervisor fp.notification.log.supervisor model_fp_notification_log fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
6 access_fp_notification_log_manager fp.notification.log.manager model_fp_notification_log fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Nuclear (CSA N299, NQA-1)',
'version': '19.0.1.2.0',
'version': '19.0.1.2.2',
'category': 'Manufacturing/Plating',
'summary': 'Nuclear industry pack: CSA N299 Levels 1-4, NQA-1 awareness, '
'CNSC licence tracking, 10 CFR Part 21 reporting, ITPs, '

View File

@@ -1,19 +1,19 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_n299_level_operator,fp.n299.level.operator,model_fusion_plating_n299_level,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_n299_level_supervisor,fp.n299.level.supervisor,model_fusion_plating_n299_level,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_n299_level_manager,fp.n299.level.manager,model_fusion_plating_n299_level,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_nuclear_program_operator,fp.nuclear.program.operator,model_fusion_plating_nuclear_program,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_nuclear_program_supervisor,fp.nuclear.program.supervisor,model_fusion_plating_nuclear_program,fusion_plating.group_fusion_plating_supervisor,1,1,0,0
access_fp_nuclear_program_manager,fp.nuclear.program.manager,model_fusion_plating_nuclear_program,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_nuclear_itp_operator,fp.nuclear.itp.operator,model_fusion_plating_nuclear_itp,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_nuclear_itp_supervisor,fp.nuclear.itp.supervisor,model_fusion_plating_nuclear_itp,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_nuclear_itp_manager,fp.nuclear.itp.manager,model_fusion_plating_nuclear_itp,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_10cfr21_report_operator,fp.10cfr21.report.operator,model_fusion_plating_10cfr21_report,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_10cfr21_report_supervisor,fp.10cfr21.report.supervisor,model_fusion_plating_10cfr21_report,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_10cfr21_report_manager,fp.10cfr21.report.manager,model_fusion_plating_10cfr21_report,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_nuclear_pedigree_operator,fp.nuclear.pedigree.operator,model_fusion_plating_nuclear_pedigree,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_nuclear_pedigree_supervisor,fp.nuclear.pedigree.supervisor,model_fusion_plating_nuclear_pedigree,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_nuclear_pedigree_manager,fp.nuclear.pedigree.manager,model_fusion_plating_nuclear_pedigree,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_cnsc_licence_operator,fp.cnsc.licence.operator,model_fusion_plating_cnsc_licence,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_cnsc_licence_supervisor,fp.cnsc.licence.supervisor,model_fusion_plating_cnsc_licence,fusion_plating.group_fusion_plating_supervisor,1,0,0,0
access_fp_cnsc_licence_manager,fp.cnsc.licence.manager,model_fusion_plating_cnsc_licence,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_n299_level_operator,fp.n299.level.operator,model_fusion_plating_n299_level,fusion_plating.group_fp_technician,1,0,0,0
access_fp_n299_level_supervisor,fp.n299.level.supervisor,model_fusion_plating_n299_level,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_n299_level_manager,fp.n299.level.manager,model_fusion_plating_n299_level,fusion_plating.group_fp_manager,1,1,1,1
access_fp_nuclear_program_operator,fp.nuclear.program.operator,model_fusion_plating_nuclear_program,fusion_plating.group_fp_technician,1,0,0,0
access_fp_nuclear_program_supervisor,fp.nuclear.program.supervisor,model_fusion_plating_nuclear_program,fusion_plating.group_fp_shop_manager_v2,1,1,0,0
access_fp_nuclear_program_manager,fp.nuclear.program.manager,model_fusion_plating_nuclear_program,fusion_plating.group_fp_manager,1,1,1,1
access_fp_nuclear_itp_operator,fp.nuclear.itp.operator,model_fusion_plating_nuclear_itp,fusion_plating.group_fp_technician,1,0,0,0
access_fp_nuclear_itp_supervisor,fp.nuclear.itp.supervisor,model_fusion_plating_nuclear_itp,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_nuclear_itp_manager,fp.nuclear.itp.manager,model_fusion_plating_nuclear_itp,fusion_plating.group_fp_manager,1,1,1,1
access_fp_10cfr21_report_operator,fp.10cfr21.report.operator,model_fusion_plating_10cfr21_report,fusion_plating.group_fp_technician,1,0,0,0
access_fp_10cfr21_report_supervisor,fp.10cfr21.report.supervisor,model_fusion_plating_10cfr21_report,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_10cfr21_report_manager,fp.10cfr21.report.manager,model_fusion_plating_10cfr21_report,fusion_plating.group_fp_manager,1,1,1,1
access_fp_nuclear_pedigree_operator,fp.nuclear.pedigree.operator,model_fusion_plating_nuclear_pedigree,fusion_plating.group_fp_technician,1,0,0,0
access_fp_nuclear_pedigree_supervisor,fp.nuclear.pedigree.supervisor,model_fusion_plating_nuclear_pedigree,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_nuclear_pedigree_manager,fp.nuclear.pedigree.manager,model_fusion_plating_nuclear_pedigree,fusion_plating.group_fp_manager,1,1,1,1
access_fp_cnsc_licence_operator,fp.cnsc.licence.operator,model_fusion_plating_cnsc_licence,fusion_plating.group_fp_technician,1,0,0,0
access_fp_cnsc_licence_supervisor,fp.cnsc.licence.supervisor,model_fusion_plating_cnsc_licence,fusion_plating.group_fp_shop_manager_v2,1,0,0,0
access_fp_cnsc_licence_manager,fp.cnsc.licence.manager,model_fusion_plating_cnsc_licence,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_n299_level_operator fp.n299.level.operator model_fusion_plating_n299_level fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
3 access_fp_n299_level_supervisor fp.n299.level.supervisor model_fusion_plating_n299_level fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
4 access_fp_n299_level_manager fp.n299.level.manager model_fusion_plating_n299_level fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
5 access_fp_nuclear_program_operator fp.nuclear.program.operator model_fusion_plating_nuclear_program fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
6 access_fp_nuclear_program_supervisor fp.nuclear.program.supervisor model_fusion_plating_nuclear_program fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 0 0
7 access_fp_nuclear_program_manager fp.nuclear.program.manager model_fusion_plating_nuclear_program fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
8 access_fp_nuclear_itp_operator fp.nuclear.itp.operator model_fusion_plating_nuclear_itp fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
9 access_fp_nuclear_itp_supervisor fp.nuclear.itp.supervisor model_fusion_plating_nuclear_itp fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
10 access_fp_nuclear_itp_manager fp.nuclear.itp.manager model_fusion_plating_nuclear_itp fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
11 access_fp_10cfr21_report_operator fp.10cfr21.report.operator model_fusion_plating_10cfr21_report fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
12 access_fp_10cfr21_report_supervisor fp.10cfr21.report.supervisor model_fusion_plating_10cfr21_report fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
13 access_fp_10cfr21_report_manager fp.10cfr21.report.manager model_fusion_plating_10cfr21_report fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
14 access_fp_nuclear_pedigree_operator fp.nuclear.pedigree.operator model_fusion_plating_nuclear_pedigree fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
15 access_fp_nuclear_pedigree_supervisor fp.nuclear.pedigree.supervisor model_fusion_plating_nuclear_pedigree fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
16 access_fp_nuclear_pedigree_manager fp.nuclear.pedigree.manager model_fusion_plating_nuclear_pedigree fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
17 access_fp_cnsc_licence_operator fp.cnsc.licence.operator model_fusion_plating_cnsc_licence fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
18 access_fp_cnsc_licence_supervisor fp.cnsc.licence.supervisor model_fusion_plating_cnsc_licence fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 0 0 0
19 access_fp_cnsc_licence_manager fp.cnsc.licence.manager model_fusion_plating_cnsc_licence fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -7,11 +7,12 @@
<odoo>
<!-- Phase 1 — re-parented under Plating → Compliance hub. -->
<!-- Phase D (perms v2) — QM-only under compliance hub. -->
<menuitem id="menu_fp_nuclear"
name="Nuclear (CSA N299 / CNSC)"
parent="fusion_plating.menu_fp_compliance_hub"
sequence="40"
groups="fusion_plating.group_fusion_plating_operator"/>
groups="fusion_plating.group_fp_quality_manager"/>
<menuitem id="menu_fp_nuclear_program"
name="N299 Programs"

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Customer Portal',
'version': '19.0.4.4.0',
'version': '19.0.4.4.1',
'category': 'Manufacturing/Plating',
'summary': 'Customer-facing portal for plating shops: online RFQ, job status, '
'CoC downloads, invoice access.',

View File

@@ -1,13 +1,13 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fp_quote_request_portal,fp.quote.request.portal,model_fusion_plating_quote_request,base.group_portal,1,0,1,0
access_fp_quote_request_operator,fp.quote.request.operator,model_fusion_plating_quote_request,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_quote_request_supervisor,fp.quote.request.supervisor,model_fusion_plating_quote_request,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_quote_request_manager,fp.quote.request.manager,model_fusion_plating_quote_request,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_quote_request_operator,fp.quote.request.operator,model_fusion_plating_quote_request,fusion_plating.group_fp_technician,1,0,0,0
access_fp_quote_request_supervisor,fp.quote.request.supervisor,model_fusion_plating_quote_request,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_quote_request_manager,fp.quote.request.manager,model_fusion_plating_quote_request,fusion_plating.group_fp_manager,1,1,1,1
access_fp_quote_request_line_portal,fp.quote.request.line.portal,model_fusion_plating_quote_request_line,base.group_portal,1,0,1,0
access_fp_quote_request_line_operator,fp.quote.request.line.operator,model_fusion_plating_quote_request_line,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_quote_request_line_supervisor,fp.quote.request.line.supervisor,model_fusion_plating_quote_request_line,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_quote_request_line_manager,fp.quote.request.line.manager,model_fusion_plating_quote_request_line,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_quote_request_line_operator,fp.quote.request.line.operator,model_fusion_plating_quote_request_line,fusion_plating.group_fp_technician,1,0,0,0
access_fp_quote_request_line_supervisor,fp.quote.request.line.supervisor,model_fusion_plating_quote_request_line,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_quote_request_line_manager,fp.quote.request.line.manager,model_fusion_plating_quote_request_line,fusion_plating.group_fp_manager,1,1,1,1
access_fp_portal_job_portal,fp.portal.job.portal,model_fusion_plating_portal_job,base.group_portal,1,0,0,0
access_fp_portal_job_operator,fp.portal.job.operator,model_fusion_plating_portal_job,fusion_plating.group_fusion_plating_operator,1,0,0,0
access_fp_portal_job_supervisor,fp.portal.job.supervisor,model_fusion_plating_portal_job,fusion_plating.group_fusion_plating_supervisor,1,1,1,0
access_fp_portal_job_manager,fp.portal.job.manager,model_fusion_plating_portal_job,fusion_plating.group_fusion_plating_manager,1,1,1,1
access_fp_portal_job_operator,fp.portal.job.operator,model_fusion_plating_portal_job,fusion_plating.group_fp_technician,1,0,0,0
access_fp_portal_job_supervisor,fp.portal.job.supervisor,model_fusion_plating_portal_job,fusion_plating.group_fp_shop_manager_v2,1,1,1,0
access_fp_portal_job_manager,fp.portal.job.manager,model_fusion_plating_portal_job,fusion_plating.group_fp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fp_quote_request_portal fp.quote.request.portal model_fusion_plating_quote_request base.group_portal 1 0 1 0
3 access_fp_quote_request_operator fp.quote.request.operator model_fusion_plating_quote_request fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
4 access_fp_quote_request_supervisor fp.quote.request.supervisor model_fusion_plating_quote_request fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
5 access_fp_quote_request_manager fp.quote.request.manager model_fusion_plating_quote_request fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
6 access_fp_quote_request_line_portal fp.quote.request.line.portal model_fusion_plating_quote_request_line base.group_portal 1 0 1 0
7 access_fp_quote_request_line_operator fp.quote.request.line.operator model_fusion_plating_quote_request_line fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
8 access_fp_quote_request_line_supervisor fp.quote.request.line.supervisor model_fusion_plating_quote_request_line fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
9 access_fp_quote_request_line_manager fp.quote.request.line.manager model_fusion_plating_quote_request_line fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1
10 access_fp_portal_job_portal fp.portal.job.portal model_fusion_plating_portal_job base.group_portal 1 0 0 0
11 access_fp_portal_job_operator fp.portal.job.operator model_fusion_plating_portal_job fusion_plating.group_fusion_plating_operator fusion_plating.group_fp_technician 1 0 0 0
12 access_fp_portal_job_supervisor fp.portal.job.supervisor model_fusion_plating_portal_job fusion_plating.group_fusion_plating_supervisor fusion_plating.group_fp_shop_manager_v2 1 1 1 0
13 access_fp_portal_job_manager fp.portal.job.manager model_fusion_plating_portal_job fusion_plating.group_fusion_plating_manager fusion_plating.group_fp_manager 1 1 1 1

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Quality (QMS)',
'version': '19.0.6.6.0',
'version': '19.0.6.6.6',
'category': 'Manufacturing/Plating',
'summary': 'Native QMS for plating shops: NCR, CAPA, calibration, AVL, FAIR, '
'internal audits, customer specs, document control. CE + EE compatible.',

Some files were not shown because too many files have changed in this diff Show More