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

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