Browse Source

Merge pull request #197 from richard-willowit/several_companies_90

Upgrade "Partner Several Companies" Module.
pull/215/head
Pedro M. Baeza 9 years ago
parent
commit
5b3942a674
  1. 40
      partner_contact_in_several_companies/README.rst
  2. 17
      partner_contact_in_several_companies/__init__.py
  3. 39
      partner_contact_in_several_companies/__openerp__.py
  4. 16
      partner_contact_in_several_companies/demo/ir_actions.xml
  5. 45
      partner_contact_in_several_companies/demo/res_partner.xml
  6. 232
      partner_contact_in_several_companies/models.py
  7. 4
      partner_contact_in_several_companies/models/__init__.py
  8. 23
      partner_contact_in_several_companies/models/ir_actions.py
  9. 161
      partner_contact_in_several_companies/models/res_partner.py
  10. 22
      partner_contact_in_several_companies/tests/__init__.py
  11. 119
      partner_contact_in_several_companies/tests/test_partner_contact_in_several_companies.py
  12. 387
      partner_contact_in_several_companies/views/res_partner.xml

40
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 <https://github.com/OCA/
partner-contact/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 <https://github.com/OCA/
partner-contact/issues/new?body=module:%20
partner_contact_in_serveral_companies%0Aversion:%20
9.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Credits
=======
@ -42,12 +58,13 @@ Contributors
------------
* Xavier ALT <xal@openerp.com> (original author)
* EL HADJI DEM <elhadji.dem@savoirfairelinux.com>
* El Hadji Dem <elhadji.dem@savoirfairelinux.com>
* TheCloneMaster <the.clone.master@gmail.com>
* Sandy Carter <bwrsandman@gmail.com>
* Rudolf Schnapka <rs@techno-flex.de>
* Sebastien Alix <sebastien.alix@osiell.com>
* Jairo Llopis <j.llopis@grupoesoc.es>
* Richard deMeester <richard@willowit.com.au>
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/

17
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 <www.grupoesoc.es>
#
# 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 <http://www.gnu.org/licenses/>.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models

39
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 <www.grupoesoc.es>
#
# 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 <http://www.gnu.org/licenses/>.
# 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 <xal@openerp.com>',
'El Hadji Dem <elhadji.dem@savoirfairelinux.com>',
'TheCloneMaster <the.clone.master@gmail.com>',
'Sandy Carter <bwrsandman@gmail.com>',
'Rudolf Schnapka <rs@techno-flex.de>',
'Sebastien Alix <sebastien.alix@osiell.com>',
'Jairo Llopis <j.llopis@grupoesoc.es>',
'Richard deMeester <richard@willowit.com.au>',
],
"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,
}

16
partner_contact_in_several_companies/demo/ir_actions.xml

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="action_partner_form" model="ir.actions.act_window">
<field name="name">All Customers in All Positions</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.partner</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="context">{"search_default_customer":1, 'search_show_all_positions': {'is_set': True, 'set_value': True}}</field>
<field name="search_view_id" ref="base.view_res_partner_filter"/>
</record>
</data>
</odoo>

45
partner_contact_in_several_companies/demo/res_partner.xml

@ -1,29 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<odoo>
<data>
<record id="res_partner_main2_position_consultant" model="res.partner">
<field name="name">Roger Scott</field>
<field name="function">Consultant</field>
<field name="parent_id" ref="base.res_partner_11"/>
<field name="contact_id" ref="base.res_partner_main2"/>
<field name="use_parent_address" eval="True"/>
</record>
<record id="res_partner_main2_position_consultant" model="res.partner">
<field name="name">Roger Scott</field>
<field name="function">Consultant</field>
<field name="parent_id" ref="base.res_partner_4"/>
<field name="contact_id" ref="base.res_partner_main2"/>
</record>
<record id="res_partner_contact1" model="res.partner">
<field name="name">Bob Egnops</field>
<field name="birthdate_date">1984-01-01</field>
<field name="email">bob@hillenburg-oceaninstitute.com</field>
</record>
<record id="res_partner_contact1" model="res.partner">
<field name="name">Bob Egnops</field>
<field name="email">bob@hillenburg-oceaninstitute.com</field>
</record>
<record id="res_partner_contact1_work_position1" model="res.partner">
<field name="name">Bob Egnops</field>
<field name="function">Technician</field>
<field name="email">bob@yourcompany.com</field>
<field name="parent_id" ref="base.main_partner"/>
<field name="contact_id" ref="res_partner_contact1"/>
<field name="use_parent_address" eval="True"/>
</record>
<record id="res_partner_contact1_work_position1" model="res.partner">
<field name="name">Bob Egnops</field>
<field name="function">Technician</field>
<field name="email">bob@yourcompany.com</field>
<field name="parent_id" ref="base.main_partner"/>
<field name="contact_id" ref="res_partner_contact1"/>
</record>
</data>
</openerp>
</data>
</odoo>

232
partner_contact_in_several_companies/models.py

@ -1,232 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013-TODAY OpenERP SA (<http://www.openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
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

4
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

23
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

161
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

22
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. (<http://openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_partner_contact_in_several_companies

119
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. (<http://openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
# -*- 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'
)

387
partner_contact_in_several_companies/views/res_partner.xml

@ -1,216 +1,211 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data>
<record id="view_res_partner_filter_contact" model="ir.ui.view">
<field name="name">res.partner.select.contact</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
<filter name="type_company" position="after">
<separator/>
<filter string="All positions" name="type_otherpositions"
context="{'search_show_all_positions': True}"
help="All partner positions"/>
</filter>
<xpath expr="/search/group/filter[@string='Company']" position="before">
<filter string="Person" name="group_person" context="{'group_by': 'contact_id'}"/>
<!-- Declared the same in every module that may need it -->
<record id="base.personal_contact_information" model="ir.ui.view">
<field name="name">Personal information page for contacts form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="priority">2</field>
<field name="arch" type="xml">
<data>
<xpath expr="//page[@name='internal_notes']" position="after">
<page name="personal_information_page"
string="Personal Information"
attrs="{'invisible': [('is_company','=',True)]}">
<group name="personal_information_group"/>
</page>
</xpath>
</field>
</record>
</data>
</field>
</record>
<record id="view_res_partner_tree_contact" model="ir.ui.view">
<field name="name">res.partner.tree.contact</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/>
<field name="arch" type="xml">
<field name="parent_id" position="after">
<field name="contact_id" invisible="1"/>
</field>
<record id="view_res_partner_filter_contact" model="ir.ui.view">
<field name="name">res.partner.select.contact</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
<filter name="type_company" position="after">
<separator/>
<filter string="All positions" name="type_otherpositions"
context="{'search_show_all_positions': {'is_set': True, 'set_value': True}}"
help="All partner positions"/>
</filter>
<xpath expr="/search/group[@name='group_by']" position="inside">
<filter string="Person" name="group_person" context="{'group_by': 'contact_id'}"/>
</xpath>
</field>
</record>
<record id="view_res_partner_tree_contact" model="ir.ui.view">
<field name="name">res.partner.tree.contact</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/>
<field name="arch" type="xml">
<field name="parent_id" position="after">
<field name="contact_id" invisible="1"/>
</field>
</record>
</field>
</record>
<record model="ir.ui.view" id="view_partner_form_inherit">
<field name="name">res.partner.form.contact</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="is_company" position="after">
<field name="contact_type" invisible="1"/>
</field>
<page string="Contacts" position="after">
<page string="Other Positions" attrs="{'invisible': ['|',('is_company','=',True),('contact_id','!=',False)]}">
<field name="other_contact_ids" context="{'default_contact_id': active_id, 'default_name': name, 'default_street': street, 'default_street2': street2, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_supplier': supplier}}" mode="kanban">
<kanban>
<field name="color"/>
<field name="name"/>
<field name="title"/>
<field name="email"/>
<field name="parent_id"/>
<field name="is_company"/>
<field name="function"/>
<field name="phone"/>
<field name="street"/>
<field name="street2"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="mobile"/>
<field name="fax"/>
<field name="state_id"/>
<field name="has_image"/>
<templates>
<t t-name="kanban-box">
<t t-set="color" t-value="kanban_color(record.color.raw_value)"/>
<div t-att-class="color + (record.title.raw_value == 1 ? ' oe_kanban_color_alert' : '')" style="position: relative">
<a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
<div class="oe_module_vignette">
<a type="open">
<t t-if="record.has_image.raw_value === true">
<img t-att-src="kanban_image('res.partner', 'image', record.id.value, {'preview_image': 'image_small'})" class="oe_avatar oe_kanban_avatar_smallbox"/>
</t>
<t t-if="record.image and record.image.raw_value !== false">
<img t-att-src="'data:image/png;base64,'+record.image.raw_value" class="oe_avatar oe_kanban_avatar_smallbox"/>
</t>
<t t-if="record.has_image.raw_value === false and (!record.image or record.image.raw_value === false)">
<t t-if="record.is_company.raw_value === true">
<img t-att-src='_s + "/base/static/src/img/company_image.png"' class="oe_kanban_image oe_kanban_avatar_smallbox"/>
</t>
<t t-if="record.is_company.raw_value === false">
<img t-att-src='_s + "/base/static/src/img/avatar.png"' class="oe_kanban_image oe_kanban_avatar_smallbox"/>
</t>
</t>
</a>
<div class="oe_module_desc">
<div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_color_border">
<table class="oe_kanban_table">
<tr>
<td class="oe_kanban_title1" align="left" valign="middle">
<h4><a type="open"><field name="name"/></a></h4>
<i>
<t t-if="record.parent_id.raw_value and !record.function.raw_value"><field name="parent_id"/></t>
<t t-if="!record.parent_id.raw_value and record.function.raw_value"><field name="function"/></t>
<t t-if="record.parent_id.raw_value and record.function.raw_value"><field name="function"/> at <field name="parent_id"/></t>
</i>
<div><a t-if="record.email.raw_value" title="Mail" t-att-href="'mailto:'+record.email.value">
<field name="email"/>
</a></div>
<div t-if="record.phone.raw_value">Phone: <field name="phone"/></div>
<div t-if="record.mobile.raw_value">Mobile: <field name="mobile"/></div>
<div t-if="record.fax.raw_value">Fax: <field name="fax"/></div>
</td>
</tr>
</table>
</div>
</div>
</div>
<record model="ir.ui.view" id="view_partner_form_inherit">
<field name="name">res.partner.form.contact</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="is_company" position="after">
<field name="contact_type" invisible="1"/>
</field>
<page name='internal_notes' position="before">
<page string="Other Positions" attrs="{'invisible': ['|',('is_company','=',True),('contact_id','!=',False)]}">
<field name="other_contact_ids" context="{'default_contact_id': active_id, 'default_name': name, 'default_street': street, 'default_street2': street2, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_supplier': supplier}}" mode="kanban">
<kanban>
<field name="color"/>
<field name="name"/>
<field name="title"/>
<field name="email"/>
<field name="parent_id"/>
<field name="is_company"/>
<field name="function"/>
<field name="phone"/>
<field name="street"/>
<field name="street2"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="mobile"/>
<field name="fax"/>
<field name="state_id"/>
<field name="image"/>
<field name="lang"/>
<templates>
<t t-name="kanban-box">
<t t-set="color" t-value="kanban_color(record.color.raw_value)"/>
<div t-att-class="color + (record.title.raw_value == 1 ? ' oe_kanban_color_alert' : '') + ' oe_kanban_global_click'">
<a t-if="!read_only_mode" type="delete" class="fa fa-times pull-right"/>
<div class="o_kanban_image">
<img t-if="record.image.raw_value" t-att-src="'data:image/png;base64,'+record.image.raw_value"/>
<t t-if="!record.image.raw_value">
<img t-if="record.is_company.raw_value === true" t-att-src='_s + "/base/static/src/img/company_image.png"'/>
<img t-if="record.is_company.raw_value === false" t-att-src='_s + "/base/static/src/img/avatar.png"'/>
</t>
</div>
</t>
</templates>
</kanban>
<form string="Contact" version="7.0">
<sheet>
<field name="image" widget='image' class="oe_avatar oe_left" options='{"preview_image": "image_medium"}'/>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name" style="width: 70%%"/></h1>
</div>
<group>
<!-- inherited part -->
<field name="category_id" widget="many2many_tags" placeholder="Tags..." style="width: 70%%"/>
<field name="parent_id" placeholder="Company" domain="[('is_company','=',True)]"/>
<!-- inherited part end -->
<field name="function" placeholder="e.g. Sales Director"/>
<field name="email"/>
<field name="phone"/>
<field name="mobile"/>
</group>
<div>
<field name="use_parent_address"/><label for="use_parent_address"/>
</div>
<group>
<label for="type"/>
<div name="div_type">
<field class="oe_inline" name="type"/>
<div class="oe_kanban_details">
<field name="name"/>
<div t-if="record.function.raw_value"><field name="function"/></div>
<div t-if="record.email.raw_value"><field name="email"/></div>
<div t-if="record.phone.raw_value">Phone: <field name="phone"/></div>
<div t-if="record.mobile.raw_value">Mobile: <field name="mobile"/></div>
<div t-if="record.fax.raw_value">Fax: <field name="fax"/></div>
</div>
<label for="street" string="Address" attrs="{'invisible': [('use_parent_address','=', True)]}"/>
<div attrs="{'invisible': [('use_parent_address','=', True)]}" name="div_address">
<field name="street" placeholder="Street..."/>
<field name="street2"/>
<div class="address_format">
<field name="city" placeholder="City" style="width: 40%%"/>
<field name="state_id" class="oe_no_button" placeholder="State" style="width: 37%%" options='{"no_open": True}' on_change="onchange_state(state_id)"/>
<field name="zip" placeholder="ZIP" style="width: 20%%"/>
</div>
</t>
</templates>
</kanban>
<form string="Contact">
<sheet>
<field name="type" required="1" widget="radio" options="{'horizontal': true}"/>
<hr/>
<group>
<group attrs="{'invisible': [('type','=', 'contact')]}">
<label for="street" string="Address"/>
<div>
<div class="o_address_format" name="div_address">
<field name="street" placeholder="Street..." class="o_address_street"/>
<field name="street2" placeholder="Street 2..." class="o_address_street"/>
<field name="city" placeholder="City" class="o_address_city"/>
<field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}' on_change="onchange_state(state_id)" context="{'country_id': country_id, 'zip': zip}"/>
<field name="zip" placeholder="ZIP" class="o_address_zip"/>
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/>
</div>
<field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": True}'/>
</div>
</group>
<field name="supplier" invisible="True"/>
</sheet>
</form>
</field>
</page>
<group>
<field name="name" string="Contact Name" attrs="{'required' : [('type', '=', 'contact')]}"/>
<field name="title" placeholder="e.g. Mr."
attrs="{'invisible': [('type','&lt;&gt;', 'contact')]}"/>
<field name="function" placeholder="e.g. Sales Director"
attrs="{'invisible': [('type','&lt;&gt;', 'contact')]}"/>
<field name="email"/>
<field name="phone" widget="phone"/>
<field name="mobile" widget="phone"/>
<field name="comment" placeholder="internal note..."/>
</group>
</group>
<field name="supplier" invisible="True"/>
<field name="customer" invisible="True"/>
<field name="lang" invisible="True"/>
</sheet>
</form>
</field>
</page>
<xpath expr="//form[@string='Contact']/sheet//field[@name='category_id']" position="before">
<group>
<label for="contact_type" class="oe_edit_only"/>
<field name="contact_type" readonly="0" on_change="onchange_contact_type(contact_type)" nolabel="1"/>
</group>
</xpath>
<xpath expr="//field[@name='child_ids']/form//field[@name='name']" position="after">
<field name="contact_id" on_change="onchange_contact_id(contact_id)" string="Contact"
attrs="{'invisible': [('contact_type','!=','attached')], 'required': [('contact_type','=','attached')]}"/>
</page>
<xpath expr="//field[@name='category_id']" position="before">
<group>
<label for="contact_type" class="oe_edit_only"/>
<field name="contact_type" readonly="0" nolabel="1"/>
</group>
</xpath>
<xpath expr="//field[@name='child_ids']/form//field[@name='name']" position="before">
<field name='contact_type' readonly='0'/>
<field name="contact_id" string="Contact"
attrs="{'invisible': [('contact_type','!=','attached')], 'required': [('contact_type','=','attached')]}"/>
</xpath>
<xpath expr="//field[@name='child_ids']/form//field[@name='name']" position="attributes">
<attribute name="attrs">{'invisible': [('contact_type','=','attached')]}</attribute>
</xpath>
</field>
</record>
<record id="personal_contact_information" model="ir.ui.view">
<field name="name">Contacts in several partners: personal info</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.personal_contact_information"/>
<field name="arch" type="xml">
<data>
<xpath expr="//page[@name='personal_information_page']/group[@name='personal_information_group']"
position='attributes'>
<attribute name='attrs'>{'invisible': [('contact_id','!=',False)]}</attribute>
</xpath>
<xpath expr="//field[@name='child_ids']/form//field[@name='name']" position="attributes">
<attribute name="attrs">{'invisible': [('contact_type','=','attached')]}</attribute>
<xpath expr="//page[@name='personal_information_page']/group[@name='personal_information_group']"
position='after'>
<p attrs="{'invisible': [('contact_id','=',False)]}">
To see personal information about this contact, please
go to to the this person form:
<field name="contact_id" class="oe_inline"
domain="[('contact_type','!=','attached')]"
context="{'show_address': 1}"
options="{'always_reload': True}"/>
</p>
</xpath>
</field>
</record>
</data>
</field>
</record>
<record id="view_res_partner_personal_information" model="ir.ui.view">
<field name="name">Contacts in several partners: personal info</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="partner_contact_personal_information_page.personal_information"/>
<field name="arch" type="xml">
<data>
<xpath expr="//page[@name='personal_information_page']">
<p attrs="{'invisible': [('contact_id','=',False)]}">
To see personal information about this contact, please
go to to the his person form:
<field name="contact_id" class="oe_inline"
domain="[('contact_type','!=','attached')]"
context="{'show_address': 1}"
on_change="onchange_contact_id(contact_id)"
options="{'always_reload': True}"/>
</p>
</xpath>
</data>
</field>
</record>
<record model="ir.ui.view" id="view_res_partner_kanban_contact">
<field name="name">res.partner.kanban.contact</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.res_partner_kanban_view"/>
<field name="arch" type="xml">
<field name="is_company" position="after">
<field name="other_contact_ids">
<tree>
<field name="parent_id"/>
<field name="function"/>
</tree>
</field>
<record model="ir.ui.view" id="view_res_partner_kanban_contact">
<field name="name">res.partner.kanban.contact</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.res_partner_kanban_view"/>
<field name="arch" type="xml">
<field name="is_company" position="after">
<field name="other_contact_ids">
<tree>
<field name="parent_id"/>
<field name="function"/>
</tree>
</field>
<xpath expr="//t[@t-name='kanban-box']//div[@class='oe_kanban_details']/ul/li[3]" position="after">
<t t-if="record.other_contact_ids.raw_value.length &gt; 0">
<li>+<t t-esc="record.other_contact_ids.raw_value.length"/>
<t t-if="record.other_contact_ids.raw_value.length == 1">other position</t>
<t t-if="record.other_contact_ids.raw_value.length &gt; 1">other positions</t></li>
</t>
</xpath>
</field>
</record>
<xpath expr="//t[@t-name='kanban-box']//div[@class='oe_kanban_details']/ul/li[3]" position="after">
<t t-if="record.other_contact_ids.raw_value.length &gt; 0">
<li>+<t t-esc="record.other_contact_ids.raw_value.length"/>
<t t-if="record.other_contact_ids.raw_value.length == 1">other position</t>
<t t-if="record.other_contact_ids.raw_value.length &gt; 1">other positions</t></li>
</t>
</xpath>
</field>
</record>
</data>
</openerp>
</odoo>
Loading…
Cancel
Save