fix(certificates): honour recipe thickness suppression at cert issue

The recipe-cert-toggles feature (fb6cccc8) taught
fp.job._resolve_required_cert_types to suppress thickness for recipes
with requires_thickness_report=False (passivation, chemical conversion,
anodize seal-only). But the actual thickness-data ENFORCEMENT never got
the memo: both fp.certificate.action_issue's hard gate AND the
Issue-Certs wizard's readiness hint re-derived 'needs thickness' from
partner flags only and ignored the recipe. Result: a passivation CoC for
a thickness/strict customer could never be issued — the gate demanded
Fischerscope data the process physically cannot produce.

Consolidate the partner-flag + recipe-suppression logic into one
fp.certificate._fp_needs_thickness_data() helper and route both the gate
and the wizard through it, so the cert-type resolver and the issue-time
gate can never drift again. Add regression tests: passivation recipe
suppresses the issue gate even for strict-thickness customers; a normal
recipe still enforces (control, guards aerospace).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-04 10:43:05 -04:00
parent 6728197570
commit 489312365e
5 changed files with 108 additions and 33 deletions

View File

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

View File

@@ -141,3 +141,48 @@ class TestRecipeCertSuppression(TransactionCase):
cert.with_context(
fp_skip_cert_authority_gate=True
).action_issue()
# ---- Test 7: passivation recipe also suppresses the ISSUE-TIME gate ----
def test_passivation_recipe_suppresses_thickness_issue_gate(self):
"""A passivation recipe (requires_thickness_report=False) must drop
the thickness-data requirement at issue time too — even for a
strict-thickness customer. Regression: the recipe-suppression
feature updated _resolve_required_cert_types but NOT the
action_issue / wizard thickness gate, so passivation CoCs could
never be issued (gate demanded Fischerscope data the process
cannot produce)."""
self.partner.x_fc_send_coc = True
self.partner.x_fc_send_thickness_report = True
self.partner.x_fc_strict_thickness_required = True
self.recipe.requires_thickness_report = False
part = self._make_part()
job = self._make_job(part_catalog_id=part.id)
cert = self.env['fp.certificate'].create({
'name': 'TEST-COC-PASSIVATION',
'certificate_type': 'coc',
'state': 'draft',
'partner_id': self.partner.id,
'x_fc_job_id': job.id,
})
# Recipe suppresses thickness -> the shared gate must NOT demand it.
self.assertFalse(cert._fp_needs_thickness_data())
# ---- Test 8: normal recipe still enforces thickness (control) ----
def test_normal_recipe_keeps_thickness_issue_gate(self):
"""Control for Test 7: a recipe that allows thickness
(requires_thickness_report default True) still demands thickness
data on the CoC for a thickness customer. Guards against
over-suppression weakening real aerospace enforcement."""
self.partner.x_fc_send_coc = True
self.partner.x_fc_send_thickness_report = True
# self.recipe.requires_thickness_report stays default True.
part = self._make_part()
job = self._make_job(part_catalog_id=part.id)
cert = self.env['fp.certificate'].create({
'name': 'TEST-COC-NORMAL',
'certificate_type': 'coc',
'state': 'draft',
'partner_id': self.partner.id,
'x_fc_job_id': job.id,
})
self.assertTrue(cert._fp_needs_thickness_data())

View File

@@ -461,17 +461,18 @@ class FpCertIssueWizardLine(models.TransientModel):
@api.depends('cert_id.certificate_type',
'cert_id.partner_id.x_fc_send_thickness_report',
'cert_id.partner_id.x_fc_strict_thickness_required')
'cert_id.partner_id.x_fc_strict_thickness_required',
'cert_id.x_fc_job_id.recipe_id.requires_thickness_report')
def _compute_needs_thickness(self):
# Delegate to fp.certificate._fp_needs_thickness_data — the single
# source of truth shared with the action_issue hard gate — so the
# wizard's readiness hint and the gate can never drift. Honours
# recipe-level thickness suppression (passivation = no thickness
# even if the customer asked).
for ln in self:
cert = ln.cert_id
partner = cert.partner_id
ln.needs_thickness = (
cert.certificate_type == 'thickness_report'
or (cert.certificate_type == 'coc' and partner and (
partner.x_fc_strict_thickness_required
or partner.x_fc_send_thickness_report
))
ln.cert_id._fp_needs_thickness_data()
if ln.cert_id else False
)
@api.depends('needs_thickness', 'fischer_file', 'reading_line_ids',