Skip to content

Commit

Permalink
[10.0][IMP] partner_identification: Add field computation and inverses (
Browse files Browse the repository at this point in the history
OCA#419)

* [IMP] partner_identification: Add field computation and inverses
* Add methods to allow for computation and inverse of an ID field of a specific category type

* [IMP] partner_identification: Add search option
  • Loading branch information
lasley authored and victor-champonnois committed Nov 28, 2022
1 parent 87e267a commit 93f9aea
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 11 deletions.
3 changes: 2 additions & 1 deletion partner_identification/__openerp__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{
'name': 'Partner Identification Numbers',
'category': 'Customer Relationship Management',
'version': '10.0.1.0.1',
'version': '10.0.1.1.0',
'depends': [
'sales_team',
],
Expand All @@ -25,6 +25,7 @@
'Tecnativa,'
'Camptocamp,'
'ACSONE SA/NV,'
'LasLabs,'
'Odoo Community Association (OCA)',
'website': 'https://odoo-community.org/',
'license': 'AGPL-3',
Expand Down
157 changes: 154 additions & 3 deletions partner_identification/models/res_partner.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,163 @@
# Antonio Espinosa <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from openerp import models, fields
from odoo import api, models, fields, _
from odoo.exceptions import ValidationError


class ResPartner(models.Model):
_inherit = 'res.partner'

id_numbers = fields.One2many(
comodel_name='res.partner.id_number', inverse_name='partner_id',
string="Identification Numbers")
comodel_name='res.partner.id_number',
inverse_name='partner_id',
string="Identification Numbers",
)

@api.multi
@api.depends('id_numbers')
def _compute_identification(self, field_name, category_code):
""" Compute a field that indicates a certain ID type.
Use this on a field that represents a certain ID type. It will compute
the desired field as that ID(s).
This ID can be worked with as if it were a Char field, but it will
be relating back to a ``res.partner.id_number`` instead.
Example:
.. code-block:: python
social_security = fields.Char(
compute=lambda s: s._compute_identification(
'social_security', 'SSN',
),
inverse=lambda s: s._inverse_identification(
'social_security', 'SSN',
),
search=lambda s, *a: s._search_identification(
'social_security', 'SSN', *a
),
)
Args:
field_name (str): Name of field to set.
category_code (str): Category code of the Identification type.
"""
for record in self:
id_numbers = record.id_numbers.filtered(
lambda r: r.category_id.code == category_code
)
if not id_numbers:
continue
value = id_numbers[0].name
record[field_name] = value

@api.multi
def _inverse_identification(self, field_name, category_code):
""" Inverse for an identification field.
This method will create a new record, or modify the existing one
in order to allow for the associated field to work like a Char.
If a category does not exist of the correct code, it will be created
using `category_code` as both the `name` and `code` values.
If the value of the target field is unset, the associated ID will
be deactivated in order to preserve history.
Example:
.. code-block:: python
social_security = fields.Char(
compute=lambda s: s._compute_identification(
'social_security', 'SSN',
),
inverse=lambda s: s._inverse_identification(
'social_security', 'SSN',
),
search=lambda s, *a: s._search_identification(
'social_security', 'SSN', *a
),
)
Args:
field_name (str): Name of field to set.
category_code (str): Category code of the Identification type.
"""
for record in self:
id_number = record.id_numbers.filtered(
lambda r: r.category_id.code == category_code
)
record_len = len(id_number)
# Record for category is not existent.
if record_len == 0:
name = record[field_name]
if not name:
# No value to set
continue
category = self.env['res.partner.id_category'].search([
('code', '=', category_code),
])
if not category:
category = self.env['res.partner.id_category'].create({
'code': category_code,
'name': category_code,
})
self.env['res.partner.id_number'].create({
'partner_id': record.id,
'category_id': category.id,
'name': name,
})
# There was an identification record singleton found.
elif record_len == 1:
value = record[field_name]
if value:
id_number.name = value
else:
id_number.active = False
# Guard against writing wrong records.
else:
raise ValidationError(_(
'This %s has multiple IDs of this type (%s), so a write '
'via the %s field is not possible. In order to fix this, '
'please use the IDs tab.',
) % (
record._name, category_code, field_name,
))

@api.model
def _search_identification(self, field_name, category_code,
operator, value):
""" Search method for an identification field.
Example:
.. code-block:: python
social_security = fields.Char(
compute=lambda s: s._compute_identification(
'social_security', 'SSN',
),
inverse=lambda s: s._inverse_identification(
'social_security', 'SSN',
),
search=lambda s, *a: s._search_identification(
'social_security', 'SSN', *a
),
)
Args:
field_name (str): Name of field to set.
category_code (str): Category code of the Identification type.
Returns:
list: Domain to search with.
"""

return [
(field_name, operator, value),
('category_id.code', '=', category_code),
]
7 changes: 3 additions & 4 deletions partner_identification/models/res_partner_id_category.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).


from openerp import api, models, fields
from openerp.exceptions import ValidationError, UserError
from openerp.tools.safe_eval import safe_eval
from openerp.tools.translate import _
from odoo import api, models, fields, _
from odoo.exceptions import ValidationError, UserError
from odoo.tools.safe_eval import safe_eval


class ResPartnerIdCategory(models.Model):
Expand Down
2 changes: 1 addition & 1 deletion partner_identification/models/res_partner_id_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).


from openerp import api, models, fields
from odoo import api, models, fields


class ResPartnerIdNumber(models.Model):
Expand Down
2 changes: 2 additions & 0 deletions partner_identification/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from . import test_partner_identification
from . import test_res_partner
4 changes: 2 additions & 2 deletions partner_identification/tests/test_partner_identification.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# © 2016 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from psycopg2._psycopg import IntegrityError
import openerp.tests.common as common
from openerp.exceptions import ValidationError
from odoo.tests import common
from odoo.exceptions import ValidationError


class TestPartnerIdentificationBase(common.TransactionCase):
Expand Down
126 changes: 126 additions & 0 deletions partner_identification/tests/test_res_partner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
# Copyright 2017 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo import fields, models
from odoo.tests import common
from odoo.exceptions import ValidationError


class ResPartner(models.Model):
_inherit = 'res.partner'

social_security = fields.Char(
compute=lambda s: s._compute_identification(
'social_security', 'SSN',
),
inverse=lambda s: s._inverse_identification(
'social_security', 'SSN',
),
search=lambda s, *a: s._search_identification(
'social_security', 'SSN', *a
),
)


class TestResPartner(common.SavepointCase):

@classmethod
def _init_test_model(cls, model_cls):
""" Build a model from model_cls in order to test abstract models.
Note that this does not actually create a table in the database, so
there may be some unidentified edge cases.
Args:
model_cls (openerp.models.BaseModel): Class of model to initialize
Returns:
model_cls: Instance
"""
registry = cls.env.registry
cr = cls.env.cr
inst = model_cls._build_model(registry, cr)
model = cls.env[model_cls._inherit].with_context(todo=[])
model._prepare_setup()
model._setup_base(partial=False)
model._setup_fields(partial=False)
model._setup_complete()
model._auto_init()
model.init()
model._auto_end()
return inst

@classmethod
def setUpClass(cls):
super(TestResPartner, cls).setUpClass()
cls.env.registry.enter_test_mode()
cls._init_test_model(ResPartner)

def setUp(self):
super(TestResPartner, self).setUp()
bad_cat = self.env['res.partner.id_category'].create({
'code': 'another_code',
'name': 'another_name',
})
self.env['res.partner.id_number'].create({
'name': 'Bad ID',
'category_id': bad_cat.id,
'partner_id': self.env.user.partner_id.id,
})
self.partner_id_category = self.env['res.partner.id_category'].create({
'code': 'id_code',
'name': 'id_name',
})
self.partner = self.env.user.partner_id
self.partner_id = self.env['res.partner.id_number'].create({
'name': 'Good ID',
'category_id': self.partner_id_category.id,
'partner_id': self.partner.id,
})

def test_compute_identification(self):
""" It should set the proper field to the proper ID name. """
self.partner._compute_identification('name', 'id_code')
self.assertEqual(self.partner.name, self.partner_id.name)

def test_inverse_identification_saves(self):
""" It should set the ID name to the proper field value. """
self.partner._inverse_identification('name', 'id_code')
self.assertEqual(self.partner_id.name, self.partner.name)

def test_inverse_identification_creates_new_category(self):
""" It should create a new category of the type if non-existent. """
self.partner._inverse_identification('name', 'new_code_type')
category = self.env['res.partner.id_category'].search([
('code', '=', 'new_code_type'),
])
self.assertTrue(category)

def test_inverse_identification_creates_new_id(self):
""" It should create a new ID of the type if non-existent. """
category = self.env['res.partner.id_category'].create({
'code': 'new_code_type',
'name': 'new_code_type',
})
self.partner._inverse_identification('name', 'new_code_type')
identification = self.env['res.partner.id_number'].search([
('category_id', '=', category.id),
('partner_id', '=', self.partner.id),
])
self.assertEqual(identification.name, self.partner.name)

def test_inverse_identification_multi_exception(self):
""" It should not allow a write when multiple IDs of same type. """
self.env['res.partner.id_number'].create({
'name': 'Another ID',
'category_id': self.partner_id_category.id,
'partner_id': self.partner.id,
})
with self.assertRaises(ValidationError):
self.partner._inverse_identification('name', 'id_code')

def test_search_identification(self):
""" It should return the right record when searched by ID. """
self.partner.social_security = 'Test'
partner = self.env['res.partner'].search([
('social_security', '=', 'Test'),
])
self.assertEqual(partner, self.partner)

0 comments on commit 93f9aea

Please sign in to comment.