fix(fusion_plating): bug review fixes + progress/net-terms invoicing + formal CoC rebuild

Bug review fixes (found by code review + live QWeb error):
- report_fp_sale.xml: product_uom → product_uom_id (Odoo 19 renamed;
  was raising KeyError during PDF render, blocking all sale-order prints)
- mrp_production.button_mark_done: add idempotency guard on delivery
  auto-create (was duplicating on every re-close)
- fp.certificate._compute_batch_ids: use empty recordset instead of
  False for Many2many computed fields
- fp_notification_template._collect_attachments: collapse attach_quotation
  + attach_sale_order into a single render so email doesn't double-attach
  the same PDF
- fp.operator.certification: SQL unique on computed state was unreliable;
  added explicit `revoked` boolean, made state pure-compute, replaced
  SQL constraint with @api.constrains that checks active-only uniqueness;
  has_active_cert now reads revoked + expires_date directly (no stale
  stored state between nightly recomputes)

Two missing invoice strategies implemented + 1 pre-existing deposit bug fix:
- Progress Billing: new x_fc_progress_initial_percent field on sale.order;
  _create_progress_initial_invoice bills the configured % on SO confirm
  via down-payment wizard, _create_final_balance_invoice bills the
  remainder on delivery
- Net Terms: no invoice on confirm; full invoice auto-created when
  fusion.plating.delivery.action_mark_delivered fires
- Fix for deposit (pre-existing, silent): sale.advance.payment.inv
  reads active_ids at wizard-create time, not on create_invoices();
  context was being set on the wrong call, so every deposit attempt
  raised "Expected singleton" and message-posted to chatter instead
  of actually invoicing
- New fusion_plating_invoicing/models/fp_delivery.py hooks
  action_mark_delivered to dispatch final invoice for progress/net_terms
- fp.direct.order.wizard + SO form surface the progress_initial_percent
  field (conditional on strategy)

Report styling cleanup:
- Hide DISCOUNT column from sale + invoice landscape reports unless at
  least one line has a non-zero discount; colspan auto-adjusts
- Replace hardcoded #0066a1 in all reports with company.primary_color
  driven by doc.company_id → company → user.company_id fallback chain,
  with #1d1f1e as ultimate fallback; new .fp-header-primary class
  exposes the colour for inline section headers (CARGO DESCRIPTION,
  PAYMENT DETAILS, OPERATOR SIGN-OFF, etc.) so they retint with the
  company theme without template edits

Certificate of Conformance — formal ENTECH-style rebuild:
- New res.company fields: x_fc_owner_user_id (default signer, sig from
  hr.employee.signature), x_fc_coc_signature_override (manual upload),
  x_fc_{nadcap,as9100,cgp}_logo + _active toggles for accreditation
  badges
- New res.config.settings section "Fusion Plating" exposing the above
  as configurable blocks; manager-only menu under Configuration →
  Fusion Plating Settings
- New fp.certificate fields: nc_quantity, customer_job_no,
  contact_partner_id (child contact for Name / Email / Phone block)
- New report_coc_en + report_coc_fr templates (primary): custom header
  (company contact | accreditations | company logo), bilingual labels
  per variant, customer info block with customer logo, 3-column cert
  info table, 6-column line-item table (Part # | Process | Customer
  PO | Shipped | NC Qty | Customer Job No.), signature image + bordered
  certification statement, footer "Fusion Plating by Nexa Systems"
- Legacy report_coc + report_coc_portrait kept for existing portal-job
  bindings (no behaviour change)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-17 01:18:22 -04:00
parent 6658544f85
commit a623c6684d
27 changed files with 822 additions and 237 deletions

View File

@@ -33,6 +33,7 @@ Includes Fischerscope thickness measurement data capture.
'data': [
'security/ir.model.access.csv',
'data/fp_certificate_sequence_data.xml',
'views/res_config_settings_views.xml',
'views/fp_certificate_views.xml',
'views/fp_certificates_menu.xml',
],

View File

@@ -5,3 +5,4 @@
from . import fp_thickness_reading
from . import fp_certificate
from . import res_config_settings

View File

@@ -45,6 +45,20 @@ class FpCertificate(models.Model):
po_number = fields.Char(string='Customer PO #')
entech_wo_number = fields.Char(string='Entech WO #')
quantity_shipped = fields.Integer(string='Qty Shipped')
nc_quantity = fields.Integer(
string='NC Qty',
help='Non-conforming quantity — parts that failed inspection / rework.',
)
customer_job_no = fields.Char(
string='Customer Job No.',
help="Customer's internal job / traveler reference.",
)
contact_partner_id = fields.Many2one(
'res.partner', string='Customer Contact',
domain="[('parent_id', '=', partner_id)]",
help="Specific contact person at the customer for this certificate. "
'Their name, email, and phone are printed on the CoC.',
)
issued_by_id = fields.Many2one(
'res.users', string='Issued By', default=lambda self: self.env.user,
)
@@ -73,6 +87,8 @@ class FpCertificate(models.Model):
@api.depends('production_id')
def _compute_batch_ids(self):
Batch = self.env.get('fusion.plating.batch')
Bath = self.env['fusion.plating.bath']
empty_batch = self.env['fusion.plating.batch']
for rec in self:
if Batch is not None and rec.production_id:
batches = Batch.search([
@@ -82,9 +98,9 @@ class FpCertificate(models.Model):
rec.batch_count = len(batches)
rec.bath_ids = batches.mapped('bath_id')
else:
rec.batch_ids = False
rec.batch_ids = empty_batch
rec.batch_count = 0
rec.bath_ids = False
rec.bath_ids = Bath
state = fields.Selection(
[('draft', 'Draft'), ('issued', 'Issued'), ('voided', 'Voided')],
string='Status', default='draft', tracking=True, required=True,

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
"""Expose Fusion Plating CoC settings on the standard Settings page
so they show up under a Fusion Plating section that the owner can
edit like any other Odoo settings."""
_inherit = 'res.config.settings'
x_fc_owner_user_id = fields.Many2one(
related='company_id.x_fc_owner_user_id', readonly=False,
)
x_fc_coc_signature_override = fields.Binary(
related='company_id.x_fc_coc_signature_override', readonly=False,
)
x_fc_nadcap_logo = fields.Binary(
related='company_id.x_fc_nadcap_logo', readonly=False,
)
x_fc_nadcap_active = fields.Boolean(
related='company_id.x_fc_nadcap_active', readonly=False,
)
x_fc_as9100_logo = fields.Binary(
related='company_id.x_fc_as9100_logo', readonly=False,
)
x_fc_as9100_active = fields.Boolean(
related='company_id.x_fc_as9100_active', readonly=False,
)
x_fc_cgp_logo = fields.Binary(
related='company_id.x_fc_cgp_logo', readonly=False,
)
x_fc_cgp_active = fields.Boolean(
related='company_id.x_fc_cgp_active', readonly=False,
)

View File

@@ -80,9 +80,14 @@
<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>

View File

@@ -41,4 +41,12 @@
action="action_fp_certificate_thickness"
sequence="30"/>
<!-- Settings menu under Configuration, manager-only -->
<menuitem id="menu_fp_settings"
name="Fusion Plating Settings"
parent="fusion_plating.menu_fp_config"
action="action_fp_settings"
sequence="1"
groups="fusion_plating.group_fusion_plating_manager"/>
</odoo>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="res_config_settings_view_form_fp" model="ir.ui.view">
<field name="name">res.config.settings.view.form.fusion.plating</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<app data-string="Fusion Plating" string="Fusion Plating"
name="fusion_plating" groups="fusion_plating.group_fusion_plating_manager">
<block title="Certificate of Conformance"
name="fp_coc_settings"
help="Branding, accreditation logos, and default signer for Certificates of Conformance.">
<setting id="fp_coc_owner"
string="Certificate Owner (Default Signer)"
help="Their HR Employee signature appears on issued certificates by default.">
<field name="x_fc_owner_user_id"
options="{'no_create': True, 'no_open': True}"/>
</setting>
<setting id="fp_coc_sig_override"
string="Signature Override Image"
help="Upload a scanned signature here to override the owner user's employee signature (useful if they don't have an HR record).">
<field name="x_fc_coc_signature_override"
widget="image" class="oe_avatar"/>
</setting>
</block>
<block title="Accreditation Logos"
name="fp_accreditation_logos"
help="Upload the logos and toggle each on to display it in the CoC header. Sized automatically in the PDF.">
<setting id="fp_nadcap"
string="Nadcap Accredited"
help="Administered by PRI. Upload the official Nadcap Accredited logo.">
<field name="x_fc_nadcap_active"/>
<field name="x_fc_nadcap_logo"
widget="image" class="oe_avatar"
invisible="not x_fc_nadcap_active"/>
</setting>
<setting id="fp_as9100"
string="AS9100 / ISO 9001"
help="AS9100D / ISO 9001 certified. Upload the combined logo.">
<field name="x_fc_as9100_active"/>
<field name="x_fc_as9100_logo"
widget="image" class="oe_avatar"
invisible="not x_fc_as9100_active"/>
</setting>
<setting id="fp_cgp"
string="Controlled Goods Program (CGP)"
help="Registered with Canada's Controlled Goods Program.">
<field name="x_fc_cgp_active"/>
<field name="x_fc_cgp_logo"
widget="image" class="oe_avatar"
invisible="not x_fc_cgp_active"/>
</setting>
</block>
</app>
</xpath>
</field>
</record>
<record id="action_fp_settings" model="ir.actions.act_window">
<field name="name">Fusion Plating Settings</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">current</field>
<field name="context">{'module': 'fusion_plating'}</field>
</record>
</odoo>