Browse Source

[10.0][IMP] partner_identification: Add field computation and inverses (#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
pull/644/head
Dave Lasley 8 years ago
committed by tarteo
parent
commit
91636b5eea
  1. 3
      partner_identification/__openerp__.py
  2. 157
      partner_identification/models/res_partner.py
  3. 7
      partner_identification/models/res_partner_id_category.py
  4. 2
      partner_identification/models/res_partner_id_number.py
  5. 2
      partner_identification/tests/__init__.py
  6. 4
      partner_identification/tests/test_partner_identification.py
  7. 126
      partner_identification/tests/test_res_partner.py

3
partner_identification/__openerp__.py

@ -11,7 +11,7 @@
{ {
'name': 'Partner Identification Numbers', 'name': 'Partner Identification Numbers',
'category': 'Customer Relationship Management', 'category': 'Customer Relationship Management',
'version': '10.0.1.0.1',
'version': '10.0.1.1.0',
'depends': [ 'depends': [
'sales_team', 'sales_team',
], ],
@ -25,6 +25,7 @@
'Tecnativa,' 'Tecnativa,'
'Camptocamp,' 'Camptocamp,'
'ACSONE SA/NV,' 'ACSONE SA/NV,'
'LasLabs,'
'Odoo Community Association (OCA)', 'Odoo Community Association (OCA)',
'website': 'https://odoo-community.org/', 'website': 'https://odoo-community.org/',
'license': 'AGPL-3', 'license': 'AGPL-3',

157
partner_identification/models/res_partner.py

@ -8,12 +8,163 @@
# Antonio Espinosa <antonioea@antiun.com> # Antonio Espinosa <antonioea@antiun.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # 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): class ResPartner(models.Model):
_inherit = 'res.partner' _inherit = 'res.partner'
id_numbers = fields.One2many( 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
partner_identification/models/res_partner_id_category.py

@ -10,10 +10,9 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # 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): class ResPartnerIdCategory(models.Model):

2
partner_identification/models/res_partner_id_number.py

@ -9,7 +9,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # 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): class ResPartnerIdNumber(models.Model):

2
partner_identification/tests/__init__.py

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_partner_identification from . import test_partner_identification
from . import test_res_partner

4
partner_identification/tests/test_partner_identification.py

@ -2,8 +2,8 @@
# © 2016 ACSONE SA/NV (<http://acsone.eu>) # © 2016 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from psycopg2._psycopg import IntegrityError 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): class TestPartnerIdentificationBase(common.TransactionCase):

126
partner_identification/tests/test_res_partner.py

@ -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)
Loading…
Cancel
Save