feat(plating): CoC spec-optional + SO-style header + thickness for any cert

- Drop the hard spec_reference gate on fp.certificate.action_issue. The
  customer-facing description (_fp_resolve_customer_facing_description,
  walks job -> SO line, reuses fp_customer_description) now drives the CoC
  Process column; spec_reference prints only when an estimator fills it.
- CoC EN/FR reports swap web.external_layout for fp_external_layout_clean +
  paperformat_fp_a4_portrait. New shared coc_header (company logo + address
  left, Nadcap logo centre, title + Code128 barcode right) mirrors the Sale
  Order header. Removed the 3-logo Nadcap/AS9100/CGP accreditation strip and
  the body H1s; padding-top 0 on both body wrappers.
- Un-gate the Issue Certs wizard thickness upload (was invisible unless the
  customer was thickness-flagged) so a Fischerscope report can be attached to
  ANY cert; merge (page 2) + inline readings already render unconditionally.
- Update issue-gate tests, bump versions (certificates 19.0.9.1.0,
  reports 19.0.11.27.0, jobs 19.0.11.2.0), record CLAUDE.md rule 14c.

Deployed + render-verified on entech (CoC-30065, 223KB PDF, no QWeb errors).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-28 21:17:09 -04:00
parent fecd2415f6
commit 307afbf3c0
10 changed files with 209 additions and 110 deletions

View File

@@ -55,13 +55,16 @@ class TestActionIssueGates(TransactionCase):
vals.update(kw)
return self.env['fp.certificate'].create(vals)
# ---- the existing gate still works (spec_reference) ----
# ---- spec_reference is OPTIONAL (client request 2026-05-28) ----
def test_blocks_on_missing_spec_reference(self):
def test_no_block_on_missing_spec_reference(self):
"""Spec Reference no longer gates issuance — the customer-facing
description now serves as the cert's spec/certificate info. A
cert with everything else present must issue even with a blank
spec_reference."""
cert = self._make_cert(spec_reference=False)
with self.assertRaises(UserError) as exc:
cert.action_issue()
self.assertIn('Spec Reference', str(exc.exception))
cert.action_issue()
self.assertEqual(cert.state, 'issued')
# ---- new gate: process_description ----
@@ -100,10 +103,12 @@ class TestActionIssueGates(TransactionCase):
cert.action_issue()
self.assertEqual(cert.state, 'issued')
# ---- order: spec_reference still wins (cheapest first) ----
# ---- order: process_description is now the first gate ----
def test_gate_order_spec_reference_first(self):
# Multiple missing → spec_reference message surfaces first.
def test_gate_order_process_description_first(self):
# spec_reference no longer gates, so with everything missing the
# process_description message surfaces first; the (removed) Spec
# Reference message must NOT appear.
cert = self._make_cert(
spec_reference=False,
process_description=False,
@@ -112,9 +117,8 @@ class TestActionIssueGates(TransactionCase):
)
with self.assertRaises(UserError) as exc:
cert.action_issue()
self.assertIn('Spec Reference', str(exc.exception))
# And NOT the process_description message (gate hit first).
self.assertNotIn('Process Description', str(exc.exception))
self.assertIn('Process Description', str(exc.exception))
self.assertNotIn('Spec Reference', str(exc.exception))
# ---- new gate: thickness_report cert needs thickness data ----