This commit is contained in:
gsinghpal
2026-05-18 22:33:23 -04:00
parent 25f568f225
commit 091f98e1f9
76 changed files with 4521 additions and 220 deletions

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import test_action_issue_gates

View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
"""Issuance-gate tests for fp.certificate.action_issue.
Covers the 2026-05-18 hardening that adds blocking checks for
process_description, certified_by_id, contact_partner_id (with email),
and qty reconciliation. See
docs/superpowers/specs/2026-05-18-cert-creation-and-data-gates-design.md.
"""
from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase
class TestActionIssueGates(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.signer = cls.env['res.users'].create({
'name': 'Signer',
'login': 'signer_certissue',
'email': 'signer@example.com',
})
cls.contact_with_email = cls.env['res.partner'].create({
'name': 'Anne Recipient',
'email': 'anne@cust.example',
})
cls.contact_no_email = cls.env['res.partner'].create({
'name': 'Carl NoEmail',
})
cls.partner = cls.env['res.partner'].create({
'name': 'IssueCust',
'is_company': True,
})
cls.contact_with_email.parent_id = cls.partner.id
cls.contact_no_email.parent_id = cls.partner.id
def _make_cert(self, **kw):
vals = {
'partner_id': self.partner.id,
'certificate_type': 'coc',
'state': 'draft',
'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,
}
vals.update(kw)
return self.env['fp.certificate'].create(vals)
# ---- the existing gate still works (spec_reference) ----
def test_blocks_on_missing_spec_reference(self):
cert = self._make_cert(spec_reference=False)
with self.assertRaises(UserError) as exc:
cert.action_issue()
self.assertIn('Spec Reference', str(exc.exception))
# ---- new gate: process_description ----
def test_blocks_on_missing_process_description(self):
cert = self._make_cert(process_description=False)
with self.assertRaises(UserError) as exc:
cert.action_issue()
self.assertIn('Process Description', str(exc.exception))
# ---- new gate: certified_by_id ----
def test_blocks_on_missing_certified_by(self):
cert = self._make_cert(certified_by_id=False)
with self.assertRaises(UserError) as exc:
cert.action_issue()
self.assertIn('Certified By', str(exc.exception))
# ---- new gate: contact_partner_id ----
def test_blocks_on_missing_contact(self):
cert = self._make_cert(contact_partner_id=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)
with self.assertRaises(UserError) as exc:
cert.action_issue()
self.assertIn('no email', str(exc.exception))
# ---- happy path ----
def test_passes_when_all_data_present(self):
cert = self._make_cert()
cert.action_issue()
self.assertEqual(cert.state, 'issued')
# ---- order: spec_reference still wins (cheapest first) ----
def test_gate_order_spec_reference_first(self):
# Multiple missing → spec_reference message surfaces first.
cert = self._make_cert(
spec_reference=False,
process_description=False,
certified_by_id=False,
contact_partner_id=False,
)
with self.assertRaises(UserError) as exc:
cert.action_issue()
self.assertIn('Spec Reference', str(exc.exception))
# And NOT the process_description message (gate hit first).
self.assertNotIn('Process Description', str(exc.exception))
# ---- new gate: thickness_report cert needs thickness data ----
def test_blocks_thickness_report_with_no_data(self):
"""A thickness_report cert with zero readings and no Fischerscope
PDF is empty paper — must block at issue."""
cert = self._make_cert(certificate_type='thickness_report')
with self.assertRaises(UserError) as exc:
cert.action_issue()
self.assertIn('thickness data', str(exc.exception).lower())
def test_thickness_report_passes_with_readings(self):
cert = self._make_cert(certificate_type='thickness_report')
self.env['fp.thickness.reading'].create({
'certificate_id': cert.id,
'nip_mils': 0.4,
})
cert.action_issue()
self.assertEqual(cert.state, 'issued')
def test_coc_does_not_require_thickness_data_by_default(self):
"""Commercial CoC (no strict_thickness flag) should still pass
even without readings — only thickness_report type is gated."""
cert = self._make_cert(certificate_type='coc')
cert.action_issue()
self.assertEqual(cert.state, 'issued')