diff --git a/fusion_plating/docs/superpowers/specs/2026-05-14-promote-customer-spec-design.md b/fusion_plating/docs/superpowers/specs/2026-05-14-promote-customer-spec-design.md index 9d61d54e..6ca4d436 100644 --- a/fusion_plating/docs/superpowers/specs/2026-05-14-promote-customer-spec-design.md +++ b/fusion_plating/docs/superpowers/specs/2026-05-14-promote-customer-spec-design.md @@ -136,7 +136,20 @@ Rejected alternatives: - "Customer Spec" — fine but slightly off when the spec is a public industry standard - "Treatment" / "Coating Configuration" — what we're explicitly removing -### Decision 6 — Recipe ↔ Specification relationship +### Decision 6.5 — NADCAP recipe lock (added 2026-05-15 from client review) + +After client validation of the design, ENPlating raised: "For NADCAP recipes, once it's in the system and we check it, only a manager profile should be able to change." Added to scope. + +Implementation: +- New field `fusion.plating.process.node.is_locked` Boolean (recipe root, but checked on all descendants via `recipe_root_id`) +- `write()` override blocks modifications by non-manager users when the recipe root has `is_locked=True` +- Manager bypass via `env.user.has_group('fusion_plating.group_fusion_plating_manager')` so the lock can be toggled off + edits made +- `env.su` (sudo) also bypasses (for migrations / system jobs) +- View: amber "LOCKED — Manager Edit Only" ribbon at top of recipe form when locked; `is_locked` toggle on the Specification & Bake page under "Change Control (NADCAP)" group + +The Word-doc external approval workflow (REV 0, REV 1 in filenames on Engineering Drive) lives outside the ERP. The lock is the ERP-side enforcement point that prevents accidental in-app edits between approval cycles. + +### Decision 7 — Recipe ↔ Specification relationship Many-to-many. One spec applies to multiple recipes; one recipe can satisfy multiple specs. @@ -434,6 +447,20 @@ These are nice-to-haves; the design proceeds without their answers but the answe --- +## Backlog from client review (2026-05-15) — separate sub-projects + +These surfaced from the client's scenario walkthrough but are NOT part of this refactor. Tracked here so they aren't forgotten. + +1. **Customer Approvals List** (Compliance → Aerospace → Approvals List menu) — small new model `fp.customer.approval` tracking which customer specs the shop is source-approved for, with approval letter PDF, effective date, expiry date. Filterable by prime (Boeing/Lockheed/etc.). Driven by client S4 answer: "Can we maintain a list of approvals under Compliance > Aerospace (AS9100/NADCAP) > APPROVALS LIST?" + +2. **Document Control auto-sync** — every customer-facing artifact (PO, packing slip, invoice, certificate, photos) auto-saves to a doc control folder (Engineering Drive / SharePoint / OneDrive). Major Documents-integration project. Driven by client S6: "I need the ERP to download all the files... to our DOC control folder." + +3. **Oven recorder data sync** — pull chart-recorder data from the bake oven into the ERP and attach to the relevant job. IoT / hardware-integration project, lives in `fusion_iot` family. Driven by client S6: "How can we sync the oven recorders with the ERP?" + +4. **Recipe SOP Word-doc workflow polish** — recipes already accept attachments via `mail.thread`. Add a prominent "Current Approved SOP" attachment slot on the recipe form, with revision history visible. Driven by client S3 + S6: "submit the steps in Word format to the customer for approval... First submission will be REV 0. If we make changes the file will be saved REV 1." + +5. **Final inspection signoff captured on certificate** — already partially exists (signoff workflow on jobs); ensure the "who did final inspection" name lands on the cert PDF body. Driven by client S7. + ## Out of scope (explicitly NOT doing) - Data migration of existing coating config records (per user direction: dev-stage, no historical data to preserve) diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py index 8b4b26a3..296466a3 100644 --- a/fusion_plating/fusion_plating/__manifest__.py +++ b/fusion_plating/fusion_plating/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating', - 'version': '19.0.19.0.0', + 'version': '19.0.19.1.0', 'category': 'Manufacturing/Plating', 'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.', 'description': """ diff --git a/fusion_plating/fusion_plating/models/fp_process_node.py b/fusion_plating/fusion_plating/models/fp_process_node.py index 64ea164a..e8543350 100644 --- a/fusion_plating/fusion_plating/models/fp_process_node.py +++ b/fusion_plating/fusion_plating/models/fp_process_node.py @@ -4,7 +4,7 @@ # Part of the Fusion Plating product family. from odoo import api, fields, models, _ -from odoo.exceptions import ValidationError +from odoo.exceptions import UserError, ValidationError from .fp_tz import fp_isoformat_utc from ._fp_uom_selection import FP_UOM_SELECTION @@ -393,6 +393,20 @@ class FpProcessNode(models.Model): help='Minimum bake hold time at temperature. Typical 23h.', ) + # ---- NADCAP / change-control lock (recipe root) ---- + # Per client direction: NADCAP-qualified recipes need manager-only + # edit permission once they're checked into the system. The Word-doc + # change-control workflow lives outside the ERP; this flag is the + # ERP-side enforcement point. + is_locked = fields.Boolean( + string='Locked (Manager-Edit Only)', + help='When True, only users in the Manager group can modify ' + 'this recipe (or any of its child operations / steps). ' + 'Use for NADCAP-qualified processes that need ' + 'change-control sign-off before any edit. The flag itself ' + 'can only be toggled by a manager.', + ) + # NB. `applicable_spec_ids` (reverse of customer.spec.recipe_ids) is # defined as an inherit in fusion_plating_quality (the module that # owns fusion.plating.customer.spec). Core can't reference it @@ -591,6 +605,22 @@ class FpProcessNode(models.Model): return records def write(self, vals): + # NADCAP / change-control lock — block writes on locked recipes + # (and their descendants) for non-manager users. Manager bypass + # so the lock can be toggled off. + if (self + and not self.env.su + and not self.env.user.has_group( + 'fusion_plating.group_fusion_plating_manager')): + for rec in self: + root = (rec if (rec.node_type == 'recipe' and not rec.parent_id) + else rec.recipe_root_id) + if root and root.is_locked: + raise UserError(_( + "Recipe '%s' is locked (NADCAP / change-control). " + "Only managers can edit it. Ask a manager to " + "unlock the recipe first." + ) % (root.display_name or root.name or '?')) meaningful = bool(set(vals.keys()) - self._FP_NON_VERSIONED_FIELDS) res = super().write(vals) if meaningful and self: diff --git a/fusion_plating/fusion_plating/views/fp_process_node_views.xml b/fusion_plating/fusion_plating/views/fp_process_node_views.xml index 6d976735..68120e47 100644 --- a/fusion_plating/fusion_plating/views/fp_process_node_views.xml +++ b/fusion_plating/fusion_plating/views/fp_process_node_views.xml @@ -45,6 +45,9 @@ icon="fa-list-ol" invisible="node_type != 'recipe'"/> +