feat(promote-customer-spec): NADCAP recipe lock (Phase A+)

Per client review: NADCAP-qualified recipes need manager-only edit
permission. Word-doc external approval workflow stays outside ERP;
this is the in-app enforcement.

- New field fp.process.node.is_locked (recipe root)
- write() override blocks non-manager edits when recipe root is_locked
  Lock checks via recipe_root_id so child ops/steps are also protected
  Manager bypass via group + env.su (sudo) bypass for system jobs
- Amber "LOCKED — Manager Edit Only" ribbon at top of recipe form
- Toggle on Specification & Bake page under "Change Control (NADCAP)"
- Spec doc updated with Decision 6.5 + backlog from client review:
  approvals list, doc control auto-sync, oven recorder sync, SOP
  word-doc workflow, final-inspection signoff on cert

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-15 00:55:07 -04:00
parent 406cac1362
commit c96f27b96c
4 changed files with 67 additions and 3 deletions

View File

@@ -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.5NADCAP 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)

View File

@@ -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': """

View File

@@ -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:

View File

@@ -45,6 +45,9 @@
icon="fa-list-ol"
invisible="node_type != 'recipe'"/>
</header>
<widget name="web_ribbon" title="LOCKED — Manager Edit Only"
bg_color="text-bg-warning"
invisible="not is_locked"/>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_open_tree_editor" type="object"
@@ -248,6 +251,10 @@
invisible="not requires_bake_relief"/>
</group>
</group>
<group string="Change Control (NADCAP)">
<field name="is_locked" widget="boolean_toggle"
help="When ON, only managers can edit this recipe and its child operations / steps. Use for NADCAP-qualified processes."/>
</group>
<group string="Thickness Options">
<field name="thickness_option_ids" nolabel="1">
<list editable="bottom">