diff --git a/partner_contact_in_several_companies/README.rst b/partner_contact_in_several_companies/README.rst index 2c9d9dc91..18d9a8d8c 100644 --- a/partner_contact_in_several_companies/README.rst +++ b/partner_contact_in_several_companies/README.rst @@ -1,18 +1,18 @@ .. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :alt: License: AGPL-3 + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 -Module name -=========== +==================================== +Partner Contact in Several Companies +==================================== -This module was written to extend the contact management functionality. It -allows you to set several job positions in different companies per contact. +This module extends the contact management functionality. It allows one +contact to have several job positions in different companies. Installation ============ -To install this module, you need to: - -* Install the OCA repository `partner-contact`_. +There are no special instructions regarding installation. Configuration ============= @@ -30,10 +30,26 @@ For further information, please visit: * https://www.odoo.com/forum/help-1 * https://github.com/OCA/partner-contact/ +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/134/9.0 + Known issues / Roadmap ====================== -* Update to v8 API. +* No known issues. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed feedback `here `_. + Credits ======= @@ -42,12 +58,13 @@ Contributors ------------ * Xavier ALT (original author) -* EL HADJI DEM +* El Hadji Dem * TheCloneMaster * Sandy Carter * Rudolf Schnapka * Sebastien Alix * Jairo Llopis +* Richard deMeester Maintainer ---------- @@ -63,6 +80,3 @@ mission is to support the collaborative development of Odoo features and promote its widespread use. To contribute to this module, please visit http://odoo-community.org. - - -.. _partner-contact: https://github.com/OCA/partner-contact/ diff --git a/partner_contact_in_several_companies/__init__.py b/partner_contact_in_several_companies/__init__.py index 3b3430c28..a77a6fcbc 100644 --- a/partner_contact_in_several_companies/__init__.py +++ b/partner_contact_in_several_companies/__init__.py @@ -1,19 +1,4 @@ # -*- coding: utf-8 -*- - -# Odoo, Open Source Management Solution -# Copyright (C) 2014-2015 Grupo ESOC -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import models diff --git a/partner_contact_in_several_companies/__openerp__.py b/partner_contact_in_several_companies/__openerp__.py index 299d374e6..a668001fc 100644 --- a/partner_contact_in_several_companies/__openerp__.py +++ b/partner_contact_in_several_companies/__openerp__.py @@ -1,36 +1,35 @@ # -*- coding: utf-8 -*- - -# Odoo, Open Source Management Solution -# Copyright (C) 2014-2015 Grupo ESOC -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { "name": "Contacts in several partners", "summary": "Allow to have one contact in several partners", - "version": "8.0.1.0.0", - "author": "Odoo Community Association (OCA)", + "version": "9.0.1.0.0", "category": "Customer Relationship Management", "website": "https://odoo-community.org/", + "author": "Odoo Community Association (OCA)", + "contributors": [ + 'Xavier ALT ', + 'El Hadji Dem ', + 'TheCloneMaster ', + 'Sandy Carter ', + 'Rudolf Schnapka ', + 'Sebastien Alix ', + 'Jairo Llopis ', + 'Richard deMeester ', + ], + "license": "AGPL-3", + 'application': False, + 'installable': True, + 'auto_install': False, "depends": [ - "partner_contact_personal_information_page", + "base" ], "data": [ "views/res_partner.xml", ], "demo": [ "demo/res_partner.xml", + "demo/ir_actions.xml", ], - 'installable': False, } diff --git a/partner_contact_in_several_companies/demo/ir_actions.xml b/partner_contact_in_several_companies/demo/ir_actions.xml new file mode 100644 index 000000000..6f69124c8 --- /dev/null +++ b/partner_contact_in_several_companies/demo/ir_actions.xml @@ -0,0 +1,16 @@ + + + + + + All Customers in All Positions + ir.actions.act_window + res.partner + form + kanban,tree,form + {"search_default_customer":1, 'search_show_all_positions': {'is_set': True, 'set_value': True}} + + + + + \ No newline at end of file diff --git a/partner_contact_in_several_companies/demo/res_partner.xml b/partner_contact_in_several_companies/demo/res_partner.xml index a7af9373f..4834c791e 100644 --- a/partner_contact_in_several_companies/demo/res_partner.xml +++ b/partner_contact_in_several_companies/demo/res_partner.xml @@ -1,29 +1,26 @@ - - + + - - Roger Scott - Consultant - - - - + + Roger Scott + Consultant + + + - - Bob Egnops - 1984-01-01 - bob@hillenburg-oceaninstitute.com - + + Bob Egnops + bob@hillenburg-oceaninstitute.com + - - Bob Egnops - Technician - bob@yourcompany.com - - - - + + Bob Egnops + Technician + bob@yourcompany.com + + + - - \ No newline at end of file + + \ No newline at end of file diff --git a/partner_contact_in_several_companies/models.py b/partner_contact_in_several_companies/models.py deleted file mode 100644 index 2493dc023..000000000 --- a/partner_contact_in_several_companies/models.py +++ /dev/null @@ -1,232 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2013-TODAY OpenERP SA (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from openerp.osv import fields, orm, expression -from openerp.tools.translate import _ - - -class res_partner(orm.Model): - _inherit = 'res.partner' - - def _type_selection(self, cr, uid, context=None): - return [ - ('standalone', _('Standalone Contact')), - ('attached', _('Attached to existing Contact')), - ] - - def _get_contact_type(self, cr, uid, ids, field_name, args, context=None): - result = dict.fromkeys(ids, 'standalone') - for partner in self.browse(cr, uid, ids, context=context): - if partner.contact_id: - result[partner.id] = 'attached' - return result - - _columns = { - 'contact_type': fields.function( - _get_contact_type, - type='selection', - selection=lambda self, *a, **kw: self._type_selection(*a, **kw), - string='Contact Type', - required=True, - select=1, - store=True, - ), - 'contact_id': fields.many2one( - 'res.partner', - 'Main Contact', - domain=[ - ('is_company', '=', False), - ('contact_type', '=', 'standalone'), - ], - ), - 'other_contact_ids': fields.one2many( - 'res.partner', - 'contact_id', - 'Others Positions', - ), - } - - _defaults = { - 'contact_type': 'standalone', - } - - def _basecontact_check_context(self, cr, user, mode, context=None): - """ Remove 'search_show_all_positions' for non-search mode. - Keeping it in context can result in unexpected behaviour (ex: reading - one2many might return wrong result - i.e with "attached contact" - removed even if it's directly linked to a company). - """ - context = dict(context or {}) - if mode != 'search': - context.pop('search_show_all_positions', None) - return context - - def search( - self, cr, user, args, offset=0, limit=None, order=None, - context=None, count=False): - """ Display only standalone contact matching ``args`` or having - attached contact matching ``args`` """ - if context is None: - context = {} - if context.get('search_show_all_positions') is False: - args = expression.normalize_domain(args) - attached_contact_args = expression.AND( - (args, [('contact_type', '=', 'attached')]) - ) - attached_contact_ids = super(res_partner, self).search( - cr, user, attached_contact_args, context=context - ) - args = expression.OR(( - expression.AND(([('contact_type', '=', 'standalone')], args)), - [('other_contact_ids', 'in', attached_contact_ids)], - )) - return super(res_partner, self).search( - cr, user, args, offset=offset, limit=limit, order=order, - context=context, count=count - ) - - def create(self, cr, user, vals, context=None): - context = self._basecontact_check_context(cr, user, 'create', context) - if not vals.get('name') and vals.get('contact_id'): - vals['name'] = self.browse( - cr, user, vals['contact_id'], context=context).name - return super(res_partner, self).create(cr, user, vals, context=context) - - def read( - self, cr, user, ids, fields=None, context=None, - load='_classic_read'): - context = self._basecontact_check_context(cr, user, 'read', context) - return super(res_partner, self).read( - cr, user, ids, fields=fields, context=context, load=load) - - def write(self, cr, user, ids, vals, context=None): - context = self._basecontact_check_context(cr, user, 'write', context) - return super( - res_partner, self).write(cr, user, ids, vals, context=context) - - def unlink(self, cr, user, ids, context=None): - context = self._basecontact_check_context(cr, user, 'unlink', context) - return super(res_partner, self).unlink(cr, user, ids, context=context) - - def _commercial_partner_compute( - self, cr, uid, ids, name, args, context=None): - """ Returns the partner that is considered the commercial - entity of this partner. The commercial entity holds the master data - for all commercial fields (see :py:meth:`~_commercial_fields`) """ - result = super(res_partner, self)._commercial_partner_compute( - cr, uid, ids, name, args, context=context) - for partner in self.browse(cr, uid, ids, context=context): - if partner.contact_type == 'attached' and not partner.parent_id: - result[partner.id] = partner.contact_id.id - return result - - def _contact_fields(self, cr, uid, context=None): - """ Returns the list of contact fields that are synced from the parent - when a partner is attached to him. """ - return ['name', 'title'] - - def _contact_sync_from_parent(self, cr, uid, partner, context=None): - """ Handle sync of contact fields when a new parent contact entity - is set, as if they were related fields - """ - if partner.contact_id: - contact_fields = self._contact_fields(cr, uid, context=context) - sync_vals = self._update_fields_values( - cr, uid, partner.contact_id, contact_fields, context=context - ) - partner.write(sync_vals) - - def update_contact(self, cr, uid, ids, vals, context=None): - if context is None: - context = {} - if context.get('__update_contact_lock'): - return - contact_fields = self._contact_fields(cr, uid, context=context) - contact_vals = dict( - (field, vals[field]) for field in contact_fields if field in vals - ) - if contact_vals: - ctx = dict(context, __update_contact_lock=True) - self.write(cr, uid, ids, contact_vals, context=ctx) - - def _fields_sync(self, cr, uid, partner, update_values, context=None): - """Sync commercial fields and address fields from company and to - children, contact fields from contact and to attached contact - after create/update, just as if those were all modeled as - fields.related to the parent - """ - super(res_partner, self)._fields_sync( - cr, uid, partner, update_values, context=context - ) - contact_fields = self._contact_fields(cr, uid, context=context) - # 1. From UPSTREAM: sync from parent contact - if update_values.get('contact_id'): - self._contact_sync_from_parent(cr, uid, partner, context=context) - # 2. To DOWNSTREAM: sync contact fields to parent or related - elif any(field in contact_fields for field in update_values): - update_ids = [ - c.id for c in partner.other_contact_ids if not c.is_company - ] - if partner.contact_id: - update_ids.append(partner.contact_id.id) - self.update_contact( - cr, uid, update_ids, update_values, context=context - ) - - def onchange_contact_id(self, cr, uid, ids, contact_id, context=None): - values = {} - if contact_id: - values['name'] = self.browse( - cr, uid, contact_id, context=context).name - return {'value': values} - - def onchange_contact_type(self, cr, uid, ids, contact_type, context=None): - values = {} - if contact_type == 'standalone': - values['contact_id'] = False - return {'value': values} - - -class ir_actions_window(orm.Model): - _inherit = 'ir.actions.act_window' - - def read( - self, cr, user, ids, fields=None, context=None, - load='_classic_read'): - action_ids = ids - if isinstance(ids, (int, long)): - action_ids = [ids] - actions = super(ir_actions_window, self).read( - cr, user, action_ids, fields=fields, context=context, load=load - ) - for action in actions: - if action.get('res_model', '') == 'res.partner': - # By default, only show standalone contact - action_context = action.get('context', '{}') or '{}' - if 'search_show_all_positions' not in action_context: - action['context'] = action_context.replace( - '{', "{'search_show_all_positions': False,", 1 - ) - if isinstance(ids, (int, long)): - if actions: - return actions[0] - return False - return actions diff --git a/partner_contact_in_several_companies/models/__init__.py b/partner_contact_in_several_companies/models/__init__.py new file mode 100644 index 000000000..46c28af40 --- /dev/null +++ b/partner_contact_in_several_companies/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import res_partner, ir_actions diff --git a/partner_contact_in_several_companies/models/ir_actions.py b/partner_contact_in_several_companies/models/ir_actions.py new file mode 100644 index 000000000..756163aa1 --- /dev/null +++ b/partner_contact_in_several_companies/models/ir_actions.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, api + + +class IRActionsWindow(models.Model): + _inherit = 'ir.actions.act_window' + + @api.multi + def read(self, fields=None, context=None, load='_classic_read'): + actions = super(IRActionsWindow, self).read(fields=fields, load=load) + for action in actions: + if action.get('res_model', '') == 'res.partner': + # By default, only show standalone contact + action_context = action.get('context', '{}') or '{}' + if 'search_show_all_positions' not in action_context: + action['context'] = action_context.replace( + '{', + ("{'search_show_all_positions': " + "{'is_set': True, 'set_value': False},"), + 1) + return actions diff --git a/partner_contact_in_several_companies/models/res_partner.py b/partner_contact_in_several_companies/models/res_partner.py new file mode 100644 index 000000000..3338dc52a --- /dev/null +++ b/partner_contact_in_several_companies/models/res_partner.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import fields, models, _, api +from openerp.osv import expression + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + contact_type = fields.Selection( + [('standalone', _('Standalone Contact')), + ('attached', _('Attached to existing Contact')), + ], + compute='_get_contact_type', + required=True, select=1, store=True, + default='standalone') + contact_id = fields.Many2one('res.partner', string='Main Contact', + domain=[('is_company', '=', False), + ('contact_type', '=', 'standalone'), + ], + ) + other_contact_ids = fields.One2many('res.partner', 'contact_id', + string='Others Positions') + + @api.one + @api.depends('contact_id') + def _get_contact_type(self): + self.contact_type = self.contact_id and 'attached' or 'standalone' + + def _basecontact_check_context(self, mode): + """ Remove 'search_show_all_positions' for non-search mode. + Keeping it in context can result in unexpected behaviour (ex: reading + one2many might return wrong result - i.e with "attached contact" + removed even if it's directly linked to a company). + Actually, is easier to override a dictionary value to indicate it + should be ignored... + """ + if mode != 'search' \ + and 'search_show_all_positions' in self.env.context: + result = self.with_context( + search_show_all_positions={'is_set': False}) + else: + result = self + return result + + @api.model + def search(self, args, offset=0, limit=None, order=None, count=False): + """ Display only standalone contact matching ``args`` or having + attached contact matching ``args`` """ + if self.env.context.get('search_show_all_positions', {}).get('is_set') \ + and not self.env.context[ + 'search_show_all_positions']['set_value']: + args = expression.normalize_domain(args) + attached_contact_args = expression.AND( + (args, [('contact_type', '=', 'attached')]) + ) + attached_contacts = super(ResPartner, self).search( + attached_contact_args) + args = expression.OR(( + expression.AND(([('contact_type', '=', 'standalone')], args)), + [('other_contact_ids', 'in', attached_contacts.ids)], + )) + return super(ResPartner, self).search(args, offset=offset, + limit=limit, order=order, + count=count) + + @api.model + def create(self, vals): + """ When creating, use a modified self to alter the context (see + comment in _basecontact_check_context). Also, we need to ensure + that the name on an attached contact is the same as the name on the + contact it is attached to.""" + modified_self = self._basecontact_check_context('create') + if not vals.get('name') and vals.get('contact_id'): + vals['name'] = modified_self.browse(vals['contact_id']).name + return super(ResPartner, modified_self).create(vals) + + @api.multi + def read(self, fields=None, load='_classic_read'): + modified_self = self._basecontact_check_context('read') + return super(ResPartner, modified_self).read(fields=fields, load=load) + + @api.multi + def write(self, vals): + modified_self = self._basecontact_check_context('write') + return super(ResPartner, modified_self).write(vals) + + @api.multi + def unlink(self): + modified_self = self._basecontact_check_context('unlink') + return super(ResPartner, modified_self).unlink() + + @api.multi + def _commercial_partner_compute(self, name, args): + """ Returns the partner that is considered the commercial + entity of this partner. The commercial entity holds the master data + for all commercial fields (see :py:meth:`~_commercial_fields`) """ + result = super(ResPartner, self)._commercial_partner_compute(name, + args) + for partner in self: + if partner.contact_type == 'attached' and not partner.parent_id: + result[partner.id] = partner.contact_id.id + return result + + def _contact_fields(self): + """ Returns the list of contact fields that are synced from the parent + when a partner is attached to him. """ + return ['name', 'title'] + + def _contact_sync_from_parent(self): + """ Handle sync of contact fields when a new parent contact entity + is set, as if they were related fields + """ + self.ensure_one() + if self.contact_id: + contact_fields = self._contact_fields() + sync_vals = self._update_fields_values(self.contact_id, + contact_fields) + self.write(sync_vals) + + def update_contact(self, vals): + if self.env.context.get('__update_contact_lock'): + return + contact_fields = self._contact_fields() + contact_vals = dict( + (field, vals[field]) for field in contact_fields if field in vals + ) + if contact_vals: + self.with_context(__update_contact_lock=True).write(contact_vals) + + @api.model + def _fields_sync(self, partner, update_values): + """Sync commercial fields and address fields from company and to + children, contact fields from contact and to attached contact + after create/update, just as if those were all modeled as + fields.related to the parent + """ + super(ResPartner, self)._fields_sync(partner, update_values) + contact_fields = self._contact_fields() + # 1. From UPSTREAM: sync from parent contact + if update_values.get('contact_id'): + partner._contact_sync_from_parent() + # 2. To DOWNSTREAM: sync contact fields to parent or related + elif any(field in contact_fields for field in update_values): + update_ids = [ + c.id for c in partner.other_contact_ids if not c.is_company + ] + if partner.contact_id: + update_ids.append(partner.contact_id.id) + self.browse(update_ids).update_contact(update_values) + + @api.onchange('contact_id') + def _onchange_contact_id(self): + if self.contact_id: + self.name = self.contact_id.name + + @api.onchange('contact_type') + def _onchange_contact_type(self): + if self.contact_type == 'standalone': + self.contact_id = False diff --git a/partner_contact_in_several_companies/tests/__init__.py b/partner_contact_in_several_companies/tests/__init__.py index bcbea6801..8a8361492 100644 --- a/partner_contact_in_several_companies/tests/__init__.py +++ b/partner_contact_in_several_companies/tests/__init__.py @@ -1,22 +1,4 @@ -# -*- coding: utf-8 ⁻*- -############################################################################## -# -# OpenERP, Open Source Business Applications -# Copyright (C) 2013-TODAY OpenERP S.A. (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import test_partner_contact_in_several_companies diff --git a/partner_contact_in_several_companies/tests/test_partner_contact_in_several_companies.py b/partner_contact_in_several_companies/tests/test_partner_contact_in_several_companies.py index fef06fd58..2e6714a43 100644 --- a/partner_contact_in_several_companies/tests/test_partner_contact_in_several_companies.py +++ b/partner_contact_in_several_companies/tests/test_partner_contact_in_several_companies.py @@ -1,23 +1,5 @@ -# -*- coding: utf-8 ⁻*- -############################################################################## -# -# OpenERP, Open Source Business Applications -# Copyright (C) 2013-TODAY OpenERP S.A. (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from openerp.tests import common @@ -30,6 +12,7 @@ class PartnerContactInSeveralCompaniesCase(common.TransactionCase): cr, uid = self.cr, self.uid ModelData = self.registry('ir.model.data') self.partner = self.registry('res.partner') + self.action = self.registry('ir.actions.act_window') # Get test records reference for attr, module, name in [ @@ -43,7 +26,12 @@ class PartnerContactInSeveralCompaniesCase(common.TransactionCase): ('roger_contact_id', 'base', 'res_partner_main2'), ('roger_job2_id', 'partner_contact_in_several_companies', - 'res_partner_main2_position_consultant')]: + 'res_partner_main2_position_consultant'), + ('base_partner_action_id', 'base', 'action_partner_form'), + ('custom_partner_action_id', + 'partner_contact_in_several_companies', + 'action_partner_form'), + ]: r = ModelData.get_object_reference(cr, uid, module, name) setattr(self, attr, r[1] if r else False) @@ -52,7 +40,9 @@ class PartnerContactInSeveralCompaniesCase(common.TransactionCase): explicitly state to not display all positions """ cr, uid = self.cr, self.uid - ctx = {'search_show_all_positions': False} + ctx = {'search_show_all_positions': {'is_set': True, + 'set_value': False + }} partner_ids = self.partner.search(cr, uid, [], context=ctx) partner_ids.sort() self.assertTrue(self.bob_job1_id not in partner_ids) @@ -60,7 +50,8 @@ class PartnerContactInSeveralCompaniesCase(common.TransactionCase): def test_01_show_all_positions(self): """Check that all contact are show if context is empty or - explicitly state to display all positions + explicitly state to display all positions or the "is_set" + value has been set to False. """ cr, uid = self.cr, self.uid @@ -68,7 +59,14 @@ class PartnerContactInSeveralCompaniesCase(common.TransactionCase): self.assertTrue(self.bob_job1_id in partner_ids) self.assertTrue(self.roger_job2_id in partner_ids) - ctx = {'search_show_all_positions': True} + ctx = {'search_show_all_positions': {'is_set': False}} + partner_ids = self.partner.search(cr, uid, [], context=ctx) + self.assertTrue(self.bob_job1_id in partner_ids) + self.assertTrue(self.roger_job2_id in partner_ids) + + ctx = {'search_show_all_positions': {'is_set': True, + 'set_value': True + }} partner_ids = self.partner.search(cr, uid, [], context=ctx) self.assertTrue(self.bob_job1_id in partner_ids) self.assertTrue(self.roger_job2_id in partner_ids) @@ -93,12 +91,21 @@ class PartnerContactInSeveralCompaniesCase(common.TransactionCase): read_other_contacts(self.bob_contact_id, context=ctx), [self.bob_job1_id], ) - ctx = {'search_show_all_positions': False} + ctx = {'search_show_all_positions': {'is_set': False}} self.assertEqual(read_other_contacts( self.bob_contact_id, context=ctx), [self.bob_job1_id], ) - ctx = {'search_show_all_positions': True} + ctx = {'search_show_all_positions': {'is_set': True, + 'set_value': False + }} + self.assertEqual(read_other_contacts( + self.bob_contact_id, context=ctx), + [self.bob_job1_id], + ) + ctx = {'search_show_all_positions': {'is_set': True, + 'set_value': True + }} self.assertEqual( read_other_contacts(self.bob_contact_id, context=ctx), [self.bob_job1_id], @@ -109,12 +116,21 @@ class PartnerContactInSeveralCompaniesCase(common.TransactionCase): self.bob_job1_id, read_contacts(self.main_partner_id, context=ctx), ) - ctx = {'search_show_all_positions': False} + ctx = {'search_show_all_positions': {'is_set': False}} + self.assertIn( + self.bob_job1_id, + read_contacts(self.main_partner_id, context=ctx), + ) + ctx = {'search_show_all_positions': {'is_set': True, + 'set_value': False + }} self.assertIn( self.bob_job1_id, read_contacts(self.main_partner_id, context=ctx), ) - ctx = {'search_show_all_positions': True} + ctx = {'search_show_all_positions': {'is_set': True, + 'set_value': True + }} self.assertIn( self.bob_job1_id, read_contacts(self.main_partner_id, context=ctx), @@ -127,8 +143,8 @@ class PartnerContactInSeveralCompaniesCase(common.TransactionCase): cr, uid = self.cr, self.uid # Bob's contact has one other position which is related to # 'YourCompany' - # so search for all contacts working for 'YourCompany' should contain - # bob position. + # so search for all contacts working for 'YourCompany' + # should contain Bob position. partner_ids = self.partner.search( cr, uid, [('parent_id', 'ilike', 'YourCompany')], @@ -138,7 +154,9 @@ class PartnerContactInSeveralCompaniesCase(common.TransactionCase): # but when searching without 'all positions', # we should get the position standalone contact instead. - ctx = {'search_show_all_positions': False} + ctx = {'search_show_all_positions': {'is_set': True, + 'set_value': False + }} partner_ids = self.partner.search( cr, uid, [('parent_id', 'ilike', 'YourCompany')], @@ -182,6 +200,19 @@ class PartnerContactInSeveralCompaniesCase(common.TransactionCase): 'standalone', ) + # Reset contact to attached, and ensure only it is unlinked (i.e. + # context is ignored). + self.partner.write(cr, uid, [new_contact_id], + {'contact_id': self.bob_contact_id}) + ctx = {'search_show_all_positions': {'is_set': True, + 'set_value': True + }} + self.partner.unlink(cr, uid, [new_contact_id], context=ctx) + partner_ids = self.partner.search( + cr, uid, [('id', 'in', [new_contact_id, self.bob_contact_id])]) + self.assertIn(self.bob_contact_id, partner_ids) + self.assertNotIn(new_contact_id, partner_ids) + def test_05_contact_fields_sync(self): """Check that contact's fields are correctly synced between parent contact or related contacts @@ -203,3 +234,29 @@ class PartnerContactInSeveralCompaniesCase(common.TransactionCase): self.partner.browse(cr, uid, self.bob_contact_id).name, 'Bob Egnops', ) + + def test_06_ir_action(self): + """Check ir_action context is auto updated. + """ + cr, uid = self.cr, self.uid + + new_context_val = "'search_show_all_positions': " \ + "{'is_set': True, 'set_value': False}," + + details = self.action.read( + cr, uid, [self.base_partner_action_id] + ) + self.assertIn( + new_context_val, + details[0]['context'], + msg='Default actions not updated with new context' + ) + + details = self.action.read( + cr, uid, [self.custom_partner_action_id] + ) + self.assertNotIn( + new_context_val, + details[0]['context'], + msg='Custom actions incorrectly updated with new context' + ) diff --git a/partner_contact_in_several_companies/views/res_partner.xml b/partner_contact_in_several_companies/views/res_partner.xml index 9026d1f96..2e4fcaab4 100644 --- a/partner_contact_in_several_companies/views/res_partner.xml +++ b/partner_contact_in_several_companies/views/res_partner.xml @@ -1,216 +1,211 @@ - + - - res.partner.select.contact - res.partner - - - - - - - - + + + Personal information page for contacts form + res.partner + + 2 + + + + + + - - + + + - - res.partner.tree.contact - res.partner - - - - - + + res.partner.select.contact + res.partner + + + + + + + + + + + + + + res.partner.tree.contact + res.partner + + + + - + + - - res.partner.form.contact - res.partner - - form - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- X -
- - - - - - - - - - - - - - - - -
-
- - - - -
-

- - - - at - - -
Phone:
-
Mobile:
-
Fax:
-
-
-
-
+ + res.partner.form.contact + res.partner + + form + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
- - - -
- - -
-
- - - - - - - - - - -
-
- -