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:
@@ -496,16 +496,13 @@ class FpCertificate(models.Model):
|
||||
and 'x_fc_owner_user_id' in rec.company_id._fields
|
||||
and rec.company_id.x_fc_owner_user_id):
|
||||
rec.certified_by_id = rec.company_id.x_fc_owner_user_id
|
||||
# Spec reference is what the cert ATTESTS — without it the
|
||||
# cert is just a piece of paper. AS9100 / Nadcap require
|
||||
# naming the spec the work was performed to.
|
||||
if not rec.spec_reference:
|
||||
raise UserError(_(
|
||||
'Cannot issue certificate "%(name)s" — no Spec '
|
||||
'Reference set.\n\nFill the Spec Reference field '
|
||||
'(e.g. "AMS 2404", "MIL-C-26074") so the cert '
|
||||
'states which standard the work meets.'
|
||||
) % {'name': rec.name or rec.display_name})
|
||||
# Spec Reference is OPTIONAL (client request 2026-05-28).
|
||||
# The customer-facing description now serves as the cert's
|
||||
# spec / certificate information (see
|
||||
# _fp_resolve_customer_facing_description + the CoC Process
|
||||
# column). spec_reference still prints below the description
|
||||
# when an estimator chooses to fill it, but it no longer
|
||||
# blocks issuance.
|
||||
# Process description (what was done to the parts). Without
|
||||
# it the cert PDF just shows blank process text — customer
|
||||
# has no idea what they paid for. Auto-filled from the
|
||||
@@ -733,6 +730,53 @@ class FpCertificate(models.Model):
|
||||
return
|
||||
delivery.coc_attachment_id = self.attachment_id.id
|
||||
|
||||
def _fp_resolve_customer_facing_description(self):
|
||||
"""Resolve the customer-facing description used as the cert's
|
||||
spec / certificate information on the printed CoC.
|
||||
|
||||
Client request 2026-05-28: Spec Reference is no longer
|
||||
mandatory; the customer-facing description (what the estimator
|
||||
actually typed on the order) now carries the descriptive text.
|
||||
|
||||
Resolution — first non-empty wins:
|
||||
1. The order line matching this cert's job/part — cleaned via
|
||||
sale.order.line.fp_customer_description() (strips the
|
||||
"[code] product" prefix Odoo re-prepends).
|
||||
2. Any product line on the linked sale order.
|
||||
Returns '' when no order context exists; the report template
|
||||
then falls back to process_description. All cross-module field
|
||||
access is guarded so the method stays safe even if the jobs /
|
||||
configurator layers aren't installed.
|
||||
"""
|
||||
self.ensure_one()
|
||||
job = self.x_fc_job_id if 'x_fc_job_id' in self._fields else False
|
||||
so = self.sale_order_id or (
|
||||
job.sale_order_id
|
||||
if job and 'sale_order_id' in job._fields else False
|
||||
)
|
||||
if not so:
|
||||
return ''
|
||||
lines = so.order_line.filtered(lambda l: not l.display_type)
|
||||
if not lines:
|
||||
return ''
|
||||
# Prefer the line whose part matches this cert's job.
|
||||
part = (job.part_catalog_id
|
||||
if job and 'part_catalog_id' in job._fields else False)
|
||||
line = self.env['sale.order.line']
|
||||
if part and 'x_fc_part_catalog_id' in lines._fields:
|
||||
line = lines.filtered(
|
||||
lambda l: l.x_fc_part_catalog_id == part
|
||||
)[:1]
|
||||
if not line:
|
||||
line = lines[:1]
|
||||
if not line:
|
||||
return ''
|
||||
if hasattr(line, 'fp_customer_description'):
|
||||
desc = line.fp_customer_description()
|
||||
else:
|
||||
desc = line.name
|
||||
return (desc or '').strip()
|
||||
|
||||
def _fp_render_and_attach_pdf(self):
|
||||
"""Render the CoC PDF via the bound report action, OPTIONALLY
|
||||
merge the Fischerscope thickness report PDF (uploaded by the
|
||||
|
||||
Reference in New Issue
Block a user