diff --git a/fusion_plating/fusion_plating_certificates/__manifest__.py b/fusion_plating/fusion_plating_certificates/__manifest__.py index 19257427..55376831 100644 --- a/fusion_plating/fusion_plating_certificates/__manifest__.py +++ b/fusion_plating/fusion_plating_certificates/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Certificates', - 'version': '19.0.9.2.0', + 'version': '19.0.9.3.0', 'category': 'Manufacturing/Plating', 'summary': 'Certificate registry for CoC, thickness reports, and quality documents.', 'description': """ diff --git a/fusion_plating/fusion_plating_certificates/models/fp_certificate.py b/fusion_plating/fusion_plating_certificates/models/fp_certificate.py index 9d7e2447..4da83ead 100644 --- a/fusion_plating/fusion_plating_certificates/models/fp_certificate.py +++ b/fusion_plating/fusion_plating_certificates/models/fp_certificate.py @@ -485,17 +485,28 @@ class FpCertificate(models.Model): rec.contact_partner_id = ( rec.partner_id.x_fc_default_coc_contact_id ) - # Guard with field-existence check — fp.certificate doesn't - # declare company_id directly; production picks it up from - # auto-creation context but tests can build a cert without - # one. Without the guard, AttributeError on the .company_id - # access bubbles up as a test error. - if (not rec.certified_by_id - and 'company_id' in rec._fields - and rec.company_id - 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 + # Lazy-fill the signer from the LIVE company owner (Settings + # "Certificate Owner") when no per-cert / per-spec signer was + # chosen. As of 2026-05-28 the cert no longer freezes the + # company owner at creation, so certified_by_id is empty in + # the common case and this fill is what locks the signer at + # issue. fp.certificate doesn't declare company_id directly, + # so resolve the company from the SO (then env.company) the + # same way the CoC report does — otherwise this fill is + # skipped and the "Certified By is not set" gate below would + # block issuance. + if not rec.certified_by_id: + company = ( + (rec.company_id + if 'company_id' in rec._fields and rec.company_id + else False) + or (rec.sale_order_id.company_id + if rec.sale_order_id else False) + or rec.env.company + ) + if (company and 'x_fc_owner_user_id' in company._fields + and company.x_fc_owner_user_id): + rec.certified_by_id = company.x_fc_owner_user_id # Spec Reference is OPTIONAL (client request 2026-05-28). # The customer-facing description now serves as the cert's # spec / certificate information (see diff --git a/fusion_plating/fusion_plating_certificates/views/res_config_settings_views.xml b/fusion_plating/fusion_plating_certificates/views/res_config_settings_views.xml index ea57248c..1eb7c0c5 100644 --- a/fusion_plating/fusion_plating_certificates/views/res_config_settings_views.xml +++ b/fusion_plating/fusion_plating_certificates/views/res_config_settings_views.xml @@ -18,7 +18,7 @@ help="Branding, accreditation logos, and default signer for Certificates of Conformance."> + help="Signs every CoC that has no per-spec or per-cert signer. Their Plating Signature (Preferences → My Profile) is printed. Changing this flows through to draft certificates."> diff --git a/fusion_plating/fusion_plating_jobs/__manifest__.py b/fusion_plating/fusion_plating_jobs/__manifest__.py index 2ad80667..6216518b 100644 --- a/fusion_plating/fusion_plating_jobs/__manifest__.py +++ b/fusion_plating/fusion_plating_jobs/__manifest__.py @@ -3,7 +3,7 @@ # License OPL-1 (Odoo Proprietary License v1.0) { 'name': 'Fusion Plating — Native Jobs', - 'version': '19.0.11.3.0', + 'version': '19.0.11.4.0', 'category': 'Manufacturing/Plating', 'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.', 'author': 'Nexa Systems Inc.', diff --git a/fusion_plating/fusion_plating_jobs/models/fp_job.py b/fusion_plating/fusion_plating_jobs/models/fp_job.py index b8534ad5..71e834a3 100644 --- a/fusion_plating/fusion_plating_jobs/models/fp_job.py +++ b/fusion_plating/fusion_plating_jobs/models/fp_job.py @@ -2572,8 +2572,10 @@ class FpJob(models.Model): Resolution sources for the new prefill fields: - process_description ← recipe.name (the job's process root) - - certified_by_id ← customer_spec.signer_user_id, falling - back to company.x_fc_owner_user_id + - certified_by_id ← customer_spec.signer_user_id ONLY + (a per-spec override). Left empty + otherwise so the LIVE company owner + resolves at render / issue time. - contact_partner_id ← partner.x_fc_default_coc_contact_id - nc_quantity ← qty_scrapped + qty_visual_insp_rejects @@ -2596,12 +2598,17 @@ class FpJob(models.Model): # sourced from sale_order.x_fc_coating_config_id (since retired); # recipe.name is the human-readable replacement. recipe = self.recipe_id - # Signer resolution: per-spec override wins, company default fills. + # Signer resolution (2026-05-28): snapshot ONLY a deliberate + # per-spec signer here. Do NOT freeze the company owner into + # certified_by_id — leaving it empty lets the CoC report and + # action_issue resolve the LIVE company owner (res.company + # .x_fc_owner_user_id / Settings "Certificate Owner") at render / + # issue time. That way changing the Settings signer flows through + # to existing draft certs instead of being frozen to whoever was + # the owner when the cert was created. signer = False if spec and 'signer_user_id' in spec._fields: signer = spec.signer_user_id - if not signer and 'x_fc_owner_user_id' in self.company_id._fields: - signer = self.company_id.x_fc_owner_user_id # Contact: per-customer default; blank means manager picks at issue. contact = False if 'x_fc_default_coc_contact_id' in self.partner_id._fields: