changes
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
{
|
||||
'name': 'Fusion Plating — Certificates',
|
||||
'version': '19.0.5.0.0',
|
||||
'version': '19.0.5.2.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'Certificate registry for CoC, thickness reports, and quality documents.',
|
||||
'description': """
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FpCertificate(models.Model):
|
||||
"""Unified certificate registry.
|
||||
@@ -307,8 +311,170 @@ class FpCertificate(models.Model):
|
||||
'so': rec.sale_order_id.name if rec.sale_order_id else '?',
|
||||
})
|
||||
rec.state = 'issued'
|
||||
# Generate the CoC PDF and attach it so action_send_to_customer
|
||||
# has something to email. Without this the workflow goes:
|
||||
# Issue → Send → opens composer with no attachment → operator
|
||||
# closes confused. Best-effort: if the report renders, attach;
|
||||
# if it fails, log + continue (cert is still issued).
|
||||
try:
|
||||
rec._fp_render_and_attach_pdf()
|
||||
except Exception as e:
|
||||
_logger.warning(
|
||||
'Cert %s: PDF render failed: %s', rec.name, e,
|
||||
)
|
||||
rec.message_post(body=_('Certificate issued.'))
|
||||
|
||||
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
|
||||
QC tablet operator) as page 2, and attach the result.
|
||||
|
||||
Without the merge, a customer who specs "CoC must include the
|
||||
XRF report" gets two separate PDFs to chase down. AS9100 wants
|
||||
the supporting evidence inline with the cert.
|
||||
|
||||
Tries the EN-language CoC report first, falls back to the
|
||||
generic action_report_coc. Idempotent — skips if attachment_id
|
||||
is already set. PDF merge is best-effort: corrupt Fischerscope
|
||||
upload or missing pypdf falls back to CoC-only with a warning.
|
||||
"""
|
||||
import base64
|
||||
import io
|
||||
self.ensure_one()
|
||||
if self.attachment_id:
|
||||
return self.attachment_id
|
||||
report = (
|
||||
self.env.ref(
|
||||
'fusion_plating_reports.action_report_coc_en',
|
||||
raise_if_not_found=False,
|
||||
)
|
||||
or self.env.ref(
|
||||
'fusion_plating_reports.action_report_coc',
|
||||
raise_if_not_found=False,
|
||||
)
|
||||
)
|
||||
if not report:
|
||||
_logger.warning(
|
||||
'Cert %s: no CoC report action found, cannot render PDF',
|
||||
self.name,
|
||||
)
|
||||
return False
|
||||
coc_pdf_bytes, _content_type = report._render_qweb_pdf(
|
||||
report.report_name, res_ids=self.ids,
|
||||
)
|
||||
# Try to append the Fischerscope thickness-report PDF as page 2.
|
||||
merged_bytes = self._fp_merge_thickness_into_pdf(coc_pdf_bytes)
|
||||
final_pdf = merged_bytes or coc_pdf_bytes
|
||||
|
||||
att = self.env['ir.attachment'].sudo().create({
|
||||
'name': '%s.pdf' % (self.name or 'certificate'),
|
||||
'type': 'binary',
|
||||
'datas': base64.b64encode(final_pdf),
|
||||
'mimetype': 'application/pdf',
|
||||
'res_model': self._name,
|
||||
'res_id': self.id,
|
||||
})
|
||||
self.attachment_id = att.id
|
||||
return att
|
||||
|
||||
def _fp_merge_thickness_into_pdf(self, coc_pdf_bytes):
|
||||
"""Look up the linked QC check, find its thickness_report_pdf_id
|
||||
(Fischerscope / XDAL 600 XRF export), and return a merged PDF
|
||||
with the CoC first + Fischerscope appended as page 2+.
|
||||
|
||||
Returns None when:
|
||||
- cert isn't a CoC, or
|
||||
- no fp.job linked, or
|
||||
- no fp.quality.check on the job has a PDF uploaded, or
|
||||
- pypdf / PyPDF2 not installed, or
|
||||
- either PDF fails to parse.
|
||||
|
||||
Caller falls back to CoC-only when None is returned.
|
||||
"""
|
||||
import io
|
||||
import base64 as _b64
|
||||
self.ensure_one()
|
||||
if self.certificate_type != 'coc':
|
||||
return None
|
||||
# Find the linked job. fp.certificate has either x_fc_job_id
|
||||
# (preferred — added by fusion_plating_jobs) or job_id (older).
|
||||
job = False
|
||||
if 'x_fc_job_id' in self._fields:
|
||||
job = self.x_fc_job_id
|
||||
if not job and 'job_id' in self._fields:
|
||||
job = self.job_id
|
||||
if not job:
|
||||
return None
|
||||
# Find a passed QC on this job with an uploaded Fischerscope PDF.
|
||||
# Prefer state=passed; fall through to any with a PDF.
|
||||
QC = self.env.get('fusion.plating.quality.check')
|
||||
if QC is None:
|
||||
return None
|
||||
qc = QC.sudo().search([
|
||||
('job_id', '=', job.id),
|
||||
('state', '=', 'passed'),
|
||||
('thickness_report_pdf_id', '!=', False),
|
||||
], order='completed_at desc', limit=1)
|
||||
if not qc:
|
||||
qc = QC.sudo().search([
|
||||
('job_id', '=', job.id),
|
||||
('thickness_report_pdf_id', '!=', False),
|
||||
], order='create_date desc', limit=1)
|
||||
if not qc or not qc.thickness_report_pdf_id:
|
||||
return None
|
||||
fischer_bytes = _b64.b64decode(
|
||||
qc.thickness_report_pdf_id.datas or b''
|
||||
)
|
||||
if not fischer_bytes:
|
||||
return None
|
||||
# Merge — pypdf is the modern name; PyPDF2 still works on older
|
||||
# Odoo bundles. Either is fine.
|
||||
try:
|
||||
from pypdf import PdfWriter
|
||||
writer_cls = PdfWriter
|
||||
use_append = True
|
||||
except ImportError:
|
||||
try:
|
||||
from PyPDF2 import PdfMerger
|
||||
writer_cls = PdfMerger
|
||||
use_append = False
|
||||
except ImportError:
|
||||
_logger.warning(
|
||||
'Cert %s: neither pypdf nor PyPDF2 installed, '
|
||||
'cannot append Fischerscope PDF to CoC.',
|
||||
self.name,
|
||||
)
|
||||
return None
|
||||
try:
|
||||
if use_append:
|
||||
# pypdf 3.x — PdfWriter.append() handles bytes/streams
|
||||
writer = writer_cls()
|
||||
writer.append(io.BytesIO(coc_pdf_bytes))
|
||||
writer.append(io.BytesIO(fischer_bytes))
|
||||
out = io.BytesIO()
|
||||
writer.write(out)
|
||||
merged = out.getvalue()
|
||||
else:
|
||||
# PyPDF2 — PdfMerger.append + write
|
||||
merger = writer_cls()
|
||||
merger.append(io.BytesIO(coc_pdf_bytes))
|
||||
merger.append(io.BytesIO(fischer_bytes))
|
||||
out = io.BytesIO()
|
||||
merger.write(out)
|
||||
merger.close()
|
||||
merged = out.getvalue()
|
||||
except Exception:
|
||||
_logger.exception(
|
||||
'PDF merge failed for cert %s — Fischerscope PDF may '
|
||||
'be corrupt / encrypted / malformed. Falling back to '
|
||||
'CoC-only.', self.name,
|
||||
)
|
||||
return None
|
||||
self.message_post(body=_(
|
||||
'Fischerscope thickness report from QC %s appended to CoC PDF.'
|
||||
) % qc.name)
|
||||
return merged
|
||||
|
||||
def action_void(self):
|
||||
for rec in self:
|
||||
if rec.state != 'issued':
|
||||
|
||||
Reference in New Issue
Block a user