feat(certificates): cert Customer Contact is multi-contact, auto-filled, sends to all
fp.certificate.contact_partner_id (single 'Customer Contact') becomes contact_partner_ids (Many2many) — same shape as the partner's Default CoC Contacts, as requested. - Auto-populate: at job creation (fp.job cert resolution) + lazy-fill at issue, contact_partner_ids = the customer's x_fc_default_coc_contact_ids (ALL). - Send: action_send_to_customer pre-fills the composer with exactly the cert's contact_partner_ids, so the CoC goes to all the defined clients (fallback: company). - Primary: the FIRST contact prints on the CoC + is gated for email; report uses contact_partner_ids[:1]. - Gate: requires >=1 Customer Contact + the primary has an email. - View: many2many_tags. - Migration 19.0.10.3.0: copies each cert's old single contact into the new M2m, drops the orphaned column. Deployed + verified on entech: migration copied 16 certs, old column dropped, field is M2m, send pre-fills the cert contacts, CoC report renders. entech-only part_line_ids preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -58,11 +58,18 @@ class FpCertificate(models.Model):
|
||||
string='Customer Job No.',
|
||||
help="Customer's internal job / traveler reference.",
|
||||
)
|
||||
contact_partner_id = fields.Many2one(
|
||||
'res.partner', string='Customer Contact',
|
||||
contact_partner_ids = fields.Many2many(
|
||||
'res.partner',
|
||||
relation='fp_certificate_contact_partner_rel',
|
||||
column1='cert_id', column2='partner_id',
|
||||
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.',
|
||||
help="Contacts at the customer who receive this certificate. "
|
||||
"Auto-filled from the customer's Default CoC Contacts when a "
|
||||
'job ships. The first is the primary (its name, email and '
|
||||
'phone print on the CoC); ALL of them are emailed when the '
|
||||
'cert is sent to the customer. (Renamed from the single '
|
||||
'contact_partner_id — see migration 19.0.10.3.0.)',
|
||||
)
|
||||
issued_by_id = fields.Many2one(
|
||||
'res.users', string='Issued By', default=lambda self: self.env.user,
|
||||
@@ -519,14 +526,15 @@ class FpCertificate(models.Model):
|
||||
# was configured would still trip the gate even after sales
|
||||
# set the default. Robust-by-construction: the defaults take
|
||||
# effect retroactively at issue time.
|
||||
if (not rec.contact_partner_id
|
||||
if (not rec.contact_partner_ids
|
||||
and rec.partner_id
|
||||
and 'x_fc_default_coc_contact_ids' in rec.partner_id._fields
|
||||
and rec.partner_id.x_fc_default_coc_contact_ids):
|
||||
# Primary = first of the customer's CoC contacts; the rest
|
||||
# are CC'd at send (action_send_to_customer).
|
||||
rec.contact_partner_id = (
|
||||
rec.partner_id.x_fc_default_coc_contact_ids[:1]
|
||||
# Auto-fill ALL the customer's CoC contacts. The first is
|
||||
# the primary (printed on the CoC); every contact is emailed
|
||||
# when the cert is sent (action_send_to_customer).
|
||||
rec.contact_partner_ids = (
|
||||
rec.partner_id.x_fc_default_coc_contact_ids
|
||||
)
|
||||
# Lazy-fill the signer from the LIVE company owner (Settings
|
||||
# "Certificate Owner") when no per-cert / per-spec signer was
|
||||
@@ -582,25 +590,25 @@ class FpCertificate(models.Model):
|
||||
# Customer contact — the named recipient printed on the
|
||||
# cert and emailed when it ships. Auto-filled from the FIRST
|
||||
# of partner.x_fc_default_coc_contact_ids when set.
|
||||
if not rec.contact_partner_id:
|
||||
if not rec.contact_partner_ids:
|
||||
raise UserError(_(
|
||||
'Cannot issue certificate "%(name)s" — Customer '
|
||||
'Contact is not set.\n\nPick the recipient contact, '
|
||||
'Contact is not set.\n\nPick the recipient contact(s), '
|
||||
'or configure Default CoC Contacts on customer '
|
||||
'"%(cust)s".'
|
||||
) % {
|
||||
'name': rec.name or rec.display_name,
|
||||
'cust': rec.partner_id.name if rec.partner_id else '?',
|
||||
})
|
||||
if not (rec.contact_partner_id.email or '').strip():
|
||||
if not (rec.contact_partner_ids[:1].email or '').strip():
|
||||
raise UserError(_(
|
||||
'Cannot issue certificate "%(name)s" — contact '
|
||||
'"%(c)s" has no email address.\n\nAdd an email '
|
||||
'to the contact before issuing (the cert is sent '
|
||||
'by email post-issue).'
|
||||
'Cannot issue certificate "%(name)s" — primary contact '
|
||||
'"%(c)s" has no email address.\n\nAdd an email to the '
|
||||
'contact before issuing (the cert is sent by email '
|
||||
'post-issue).'
|
||||
) % {
|
||||
'name': rec.name or rec.display_name,
|
||||
'c': rec.contact_partner_id.name,
|
||||
'c': rec.contact_partner_ids[:1].name,
|
||||
})
|
||||
# Orphan cert types (Nadcap / Mill Test / Customer-Specific)
|
||||
# are manual-attach only — operator uploads supplier doc /
|
||||
@@ -1116,14 +1124,11 @@ class FpCertificate(models.Model):
|
||||
"""Open email composer with the certificate PDF attached."""
|
||||
self.ensure_one()
|
||||
template = self.env.ref('mail.email_compose_message_wizard_form', raise_if_not_found=False)
|
||||
# CoC goes to ALL the customer's CoC contacts (the cert's primary
|
||||
# contact + the rest of partner.x_fc_default_coc_contact_ids),
|
||||
# falling back to the company. Every contact who needs the cert is
|
||||
# pre-filled on the composer.
|
||||
recipients = self.contact_partner_id
|
||||
if (self.partner_id
|
||||
and 'x_fc_default_coc_contact_ids' in self.partner_id._fields):
|
||||
recipients |= self.partner_id.x_fc_default_coc_contact_ids
|
||||
# Send to ALL the Customer Contacts defined on this cert (auto-filled
|
||||
# from the customer's Default CoC Contacts at job creation), falling
|
||||
# back to the company. The CoC goes to exactly the contacts shown on
|
||||
# the cert's Customer Contact field.
|
||||
recipients = self.contact_partner_ids
|
||||
partner_ids = recipients.ids or (
|
||||
[self.partner_id.id] if self.partner_id else [])
|
||||
ctx = {
|
||||
|
||||
Reference in New Issue
Block a user