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:
gsinghpal
2026-06-04 18:09:36 -04:00
parent ba6aeaaca9
commit 6f006e24ad
9 changed files with 99 additions and 40 deletions

View File

@@ -50,7 +50,7 @@ class TestActionIssueGates(TransactionCase):
'spec_reference': 'AMS 2404',
'process_description': 'ELECTROLESS NICKEL PER AMS 2404',
'certified_by_id': self.signer.id,
'contact_partner_id': self.contact_with_email.id,
'contact_partner_ids': [(6, 0, [self.contact_with_email.id])],
}
vals.update(kw)
return self.env['fp.certificate'].create(vals)
@@ -85,13 +85,13 @@ class TestActionIssueGates(TransactionCase):
# ---- new gate: contact_partner_id ----
def test_blocks_on_missing_contact(self):
cert = self._make_cert(contact_partner_id=False)
cert = self._make_cert(contact_partner_ids=False)
with self.assertRaises(UserError) as exc:
cert.action_issue()
self.assertIn('Customer Contact', str(exc.exception))
def test_blocks_on_contact_without_email(self):
cert = self._make_cert(contact_partner_id=self.contact_no_email.id)
cert = self._make_cert(contact_partner_ids=[(6, 0, [self.contact_no_email.id])])
with self.assertRaises(UserError) as exc:
cert.action_issue()
self.assertIn('no email', str(exc.exception))
@@ -113,7 +113,7 @@ class TestActionIssueGates(TransactionCase):
spec_reference=False,
process_description=False,
certified_by_id=False,
contact_partner_id=False,
contact_partner_ids=False,
)
with self.assertRaises(UserError) as exc:
cert.action_issue()