This commit is contained in:
gsinghpal
2026-05-21 03:37:25 -04:00
parent b2f483d67c
commit 1314f4581d
47 changed files with 5730 additions and 177 deletions

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Certificates',
'version': '19.0.7.0.0',
'version': '19.0.7.7.0',
'category': 'Manufacturing/Plating',
'summary': 'Certificate registry for CoC, thickness reports, and quality documents.',
'description': """

View File

@@ -106,6 +106,81 @@ class FpCertificate(models.Model):
string='Fischerscope PDF filename',
)
# Non-PDF Fischerscope uploads (.doc / .docx / .xlsx / images) — the
# Issue Certs wizard stashes them here so the thickness-required gate
# can still pass. Unlike `x_fc_local_thickness_pdf`, this attachment
# is NOT merged into the CoC PDF as page 2 (we can't rasterize .doc
# server-side without LibreOffice). It rides along as a separate
# evidence attachment on the cert and on any email/portal delivery.
x_fc_local_thickness_evidence_id = fields.Many2one(
'ir.attachment',
string='Fischerscope Evidence (non-PDF)',
copy=False,
help='Original Fischerscope/XRF upload when not a PDF. Counts '
'as valid thickness evidence for the cert-issue gate but '
'is delivered as a separate attachment, not merged into '
'the CoC PDF.',
)
# Report-level Fischerscope metadata — populated by the Issue Certs
# wizard when parsing an RTF/.docx upload. Rendered on the CoC so
# the printed cert shows the same context an auditor would see on
# the original XDAL 600 export (equipment, operator, calibration,
# product/application, measuring time, date/time). Per-reading
# values (mils, Ni%, P%) live on fp.thickness.reading.
x_fc_thickness_equipment = fields.Char(
string='Thickness Equipment',
help='XRF/thickness gauge model (e.g. "Fischerscope XDAL 600").',
)
x_fc_thickness_operator = fields.Char(
string='Thickness Operator',
help='Operator initials/name as recorded by the gauge.',
)
x_fc_thickness_datetime = fields.Datetime(
string='Thickness Reading Date/Time',
help='When the readings were taken on the gauge.',
)
x_fc_thickness_product = fields.Char(
string='Thickness Product Profile',
help='XDAL 600 product line + part-family reference '
'(e.g. "2805031 / NiP/Al-alloys 2805030").',
)
x_fc_thickness_application = fields.Char(
string='Thickness Application',
help='XDAL 600 application profile '
'(e.g. "16 / NiP/Al-alloys").',
)
x_fc_thickness_directory = fields.Char(
string='Thickness Directory',
help='XDAL 600 directory the measurements were saved into.',
)
x_fc_thickness_measuring_time_sec = fields.Integer(
string='Thickness Measuring Time (sec)',
help='Per-reading measuring time configured on the gauge.',
)
x_fc_thickness_source_filename = fields.Char(
string='Thickness Source File',
help='Filename of the Fischerscope upload the readings were '
'parsed from.',
)
# Two paths populate this field, with operator upload winning:
# 1. RTF auto-extraction — Issue Certs wizard runs libwmf
# (wmf2svg) on the embedded WMF blocks and picks the
# largest raster (header banners filtered by area threshold).
# 2. Manual PNG/JPEG upload via the wizard's "Measurement
# Image" field — operator override path when the
# auto-extracted image is wrong, missing, or low-quality.
# See _apply_to_cert and _apply_image_to_cert in the wizard.
x_fc_thickness_image_id = fields.Many2one(
'ir.attachment',
string='Thickness Microscope Image',
copy=False,
help='Microscope photo of the measurement site. Auto-extracted '
'from the Fischerscope RTF export when libwmf can parse '
'the embedded WMF; operator can also upload a PNG/JPEG '
'directly via the Issue Certs wizard to override.',
)
# ---- Material traceability (T2.3) ----
batch_ids = fields.Many2many(
'fusion.plating.batch', compute='_compute_batch_ids',
@@ -456,7 +531,11 @@ class FpCertificate(models.Model):
if 'x_fc_thickness_pdf_id' in rec._fields else False
)
has_local_pdf = bool(rec.x_fc_local_thickness_pdf)
if not (has_readings or has_qc_fischer_pdf or has_local_pdf):
has_local_evidence = bool(
rec.x_fc_local_thickness_evidence_id
)
if not (has_readings or has_qc_fischer_pdf
or has_local_pdf or has_local_evidence):
type_label = (
_('Thickness Report')
if rec.certificate_type == 'thickness_report'
@@ -685,6 +764,32 @@ class FpCertificate(models.Model):
) % source)
return merged
def action_reset_to_draft(self):
"""Move an issued/voided cert back to draft so the manager can
correct typos in the thickness metadata, swap the microscope
image, re-pick the void reason, etc. — then re-Issue.
Wipes the existing `attachment_id` so the next render picks up
whatever was changed. The original PDF stays around as a
regular ir.attachment on the cert (for audit) since we only
clear the FK, not the attachment record itself. Re-issue
creates a fresh PDF.
"""
for rec in self:
if rec.state == 'draft':
raise UserError(_(
'Certificate %s is already a draft.'
) % rec.name)
rec.state = 'draft'
old_att = rec.attachment_id
if old_att:
rec.attachment_id = False
rec.message_post(body=_(
'Reset to draft for edits. The previously-issued PDF '
'%s remains attached for audit; a fresh PDF will be '
'generated on re-issue.'
) % (old_att.name if old_att else '(none)'))
def action_void(self):
for rec in self:
if rec.state != 'issued':

View File

@@ -42,12 +42,27 @@
<button name="action_issue" string="Issue"
type="object" class="btn-primary"
invisible="state != 'draft'"/>
<!-- Print = the same EN report action the gear-menu
Print > Certificate of Conformance (English)
calls. Routes through fusion_pdf_preview's
report interceptor automatically. For the
French variant or any other language report,
use the gear menu. -->
<button name="%(fusion_plating_reports.action_report_coc_en)d"
string="Print"
type="action" class="btn-secondary"
icon="fa-print"/>
<button name="action_open_void_wizard" string="Void"
type="object" class="btn-danger"
invisible="state != 'issued'"/>
<button name="action_send_to_customer" string="Send to Customer"
type="object"
invisible="state != 'issued'"/>
<button name="action_reset_to_draft" string="Reset to Draft"
type="object" class="btn-secondary"
icon="fa-undo"
confirm="Reset this certificate to draft? You'll be able to edit and re-issue. The previously-issued PDF stays attached for audit."
invisible="state == 'draft'"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,issued"/>
</header>
@@ -67,48 +82,52 @@
<field name="name" readonly="1"/>
</h1>
</div>
<!-- Main info — collapsed from 3 separate groups
into 1 to eliminate the dead rows that
appeared when one sub-group ran shorter than
the other. Left column is identity / signer /
dates; right column is part / process / qty /
derived stats. Reorganized 2026-05-21. -->
<group>
<group>
<field name="certificate_type"/>
<field name="partner_id"/>
<field name="sale_order_id"/>
<field name="portal_job_id"/>
<field name="issue_date"/>
</group>
<group>
<field name="part_number"/>
<field name="po_number"/>
<field name="entech_wo_number"/>
<field name="customer_job_no"/>
<field name="process_description"/>
<field name="spec_reference"/>
<field name="quantity_shipped"/>
<field name="nc_quantity"/>
<field name="contact_partner_id"
options="{'no_create': True}"
invisible="not partner_id"/>
</group>
</group>
<group>
<group>
<field name="sale_order_id"/>
<field name="entech_wo_number"/>
<field name="portal_job_id"/>
<field name="issue_date"/>
<field name="issued_by_id"/>
<field name="certified_by_id"/>
<field name="body_style"/>
</group>
<group>
<field name="part_number"/>
<field name="process_description"/>
<field name="spec_reference"/>
<field name="po_number"/>
<field name="customer_job_no"/>
<field name="quantity_shipped"/>
<field name="nc_quantity"/>
<field name="reading_count" readonly="1"/>
<field name="mean_nip_mils" readonly="1"/>
</group>
</group>
<!-- SPC rebalanced — spec/min/max on the left,
derived stats on the right; trend_explanation
spans both columns so the long message doesn't
get cropped. -->
<group string="SPC — Statistical Process Control">
<group>
<field name="spec_min_mils"/>
<field name="spec_max_mils"/>
<field name="min_reading_mils" readonly="1"/>
<field name="max_reading_mils" readonly="1"/>
<field name="std_dev_mils" readonly="1"/>
</group>
<group>
<field name="std_dev_mils" readonly="1"/>
<field name="cpk" readonly="1"/>
<field name="cpk_status" readonly="1" widget="badge"
decoration-success="cpk_status in ('capable','excellent')"
@@ -119,9 +138,9 @@
decoration-success="trend_alert == 'ok'"
decoration-warning="trend_alert == 'warning'"
decoration-danger="trend_alert == 'alert'"/>
<field name="trend_explanation" readonly="1"
invisible="trend_alert == 'ok'"/>
</group>
<field name="trend_explanation" readonly="1" colspan="2"
invisible="trend_alert == 'ok'"/>
</group>
<notebook>
<page string="Thickness Readings" name="readings">