[MERGE] Forward-port of 7.0 fixes up to rev. 4961
rev-id odo@openerp.com-20130425171238-dhgpqbi9nio12vxk bzr revid: odo@openerp.com-20130424131936-5eb7omkz51arnm81 bzr revid: odo@openerp.com-20130425171444-ycu89d2393f8nnxm
This commit is contained in:
commit
ffebb64472
|
@ -97,7 +97,7 @@ class res_company(osv.osv):
|
||||||
address_data = part_obj.address_get(cr, uid, [company.partner_id.id], adr_pref=['default'])
|
address_data = part_obj.address_get(cr, uid, [company.partner_id.id], adr_pref=['default'])
|
||||||
address = address_data['default']
|
address = address_data['default']
|
||||||
if address:
|
if address:
|
||||||
part_obj.write(cr, uid, [address], {name: value or False})
|
part_obj.write(cr, uid, [address], {name: value or False}, context=context)
|
||||||
else:
|
else:
|
||||||
part_obj.create(cr, uid, {name: value or False, 'parent_id': company.partner_id.id}, context=context)
|
part_obj.create(cr, uid, {name: value or False, 'parent_id': company.partner_id.id}, context=context)
|
||||||
return True
|
return True
|
||||||
|
@ -253,7 +253,7 @@ class res_company(osv.osv):
|
||||||
vals.update({'partner_id': partner_id})
|
vals.update({'partner_id': partner_id})
|
||||||
self.cache_restart(cr)
|
self.cache_restart(cr)
|
||||||
company_id = super(res_company, self).create(cr, uid, vals, context=context)
|
company_id = super(res_company, self).create(cr, uid, vals, context=context)
|
||||||
obj_partner.write(cr, uid, partner_id, {'company_id': company_id}, context=context)
|
obj_partner.write(cr, uid, [partner_id], {'company_id': company_id}, context=context)
|
||||||
return company_id
|
return company_id
|
||||||
|
|
||||||
def write(self, cr, uid, ids, values, context=None):
|
def write(self, cr, uid, ids, values, context=None):
|
||||||
|
|
|
@ -30,6 +30,7 @@ from openerp import SUPERUSER_ID
|
||||||
from openerp import tools
|
from openerp import tools
|
||||||
from openerp.osv import osv, fields
|
from openerp.osv import osv, fields
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
|
from openerp.tools.yaml_import import is_comment
|
||||||
|
|
||||||
class format_address(object):
|
class format_address(object):
|
||||||
def fields_view_get_address(self, cr, uid, arch, context={}):
|
def fields_view_get_address(self, cr, uid, arch, context={}):
|
||||||
|
@ -159,8 +160,8 @@ def _lang_get(self, cr, uid, context=None):
|
||||||
res = lang_pool.read(cr, uid, ids, ['code', 'name'], context)
|
res = lang_pool.read(cr, uid, ids, ['code', 'name'], context)
|
||||||
return [(r['code'], r['name']) for r in res]
|
return [(r['code'], r['name']) for r in res]
|
||||||
|
|
||||||
POSTAL_ADDRESS_FIELDS = ('street', 'street2', 'zip', 'city', 'state_id', 'country_id')
|
# fields copy if 'use_parent_address' is checked
|
||||||
ADDRESS_FIELDS = POSTAL_ADDRESS_FIELDS + ('email', 'phone', 'fax', 'mobile', 'website', 'ref', 'lang')
|
ADDRESS_FIELDS = ('street', 'street2', 'zip', 'city', 'state_id', 'country_id')
|
||||||
|
|
||||||
class res_partner(osv.osv, format_address):
|
class res_partner(osv.osv, format_address):
|
||||||
_description = 'Partner'
|
_description = 'Partner'
|
||||||
|
@ -193,13 +194,42 @@ class res_partner(osv.osv, format_address):
|
||||||
result[obj.id] = obj.image != False
|
result[obj.id] = obj.image != False
|
||||||
return result
|
return result
|
||||||
|
|
||||||
_order = "name"
|
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 = dict.fromkeys(ids, False)
|
||||||
|
for partner in self.browse(cr, uid, ids, context=context):
|
||||||
|
current_partner = partner
|
||||||
|
while not current_partner.is_company and current_partner.parent_id:
|
||||||
|
current_partner = current_partner.parent_id
|
||||||
|
result[partner.id] = current_partner.id
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _display_name_compute(self, cr, uid, ids, name, args, context=None):
|
||||||
|
return dict(self.name_get(cr, uid, ids, context=context))
|
||||||
|
|
||||||
|
# indirections to avoid passing a copy of the overridable method when declaring the function field
|
||||||
|
_commercial_partner_id = lambda self, *args, **kwargs: self._commercial_partner_compute(*args, **kwargs)
|
||||||
|
_display_name = lambda self, *args, **kwargs: self._display_name_compute(*args, **kwargs)
|
||||||
|
|
||||||
|
_commercial_partner_store_triggers = {
|
||||||
|
'res.partner': (lambda self,cr,uid,ids,context=None: self.search(cr, uid, [('id','child_of',ids)]),
|
||||||
|
['parent_id', 'is_company'], 10)
|
||||||
|
}
|
||||||
|
_display_name_store_triggers = {
|
||||||
|
'res.partner': (lambda self,cr,uid,ids,context=None: self.search(cr, uid, [('id','child_of',ids)]),
|
||||||
|
['parent_id', 'is_company', 'name'], 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
_order = "display_name"
|
||||||
_columns = {
|
_columns = {
|
||||||
'name': fields.char('Name', size=128, required=True, select=True),
|
'name': fields.char('Name', size=128, required=True, select=True),
|
||||||
|
'display_name': fields.function(_display_name, type='char', string='Name', store=_display_name_store_triggers),
|
||||||
'date': fields.date('Date', select=1),
|
'date': fields.date('Date', select=1),
|
||||||
'title': fields.many2one('res.partner.title', 'Title'),
|
'title': fields.many2one('res.partner.title', 'Title'),
|
||||||
'parent_id': fields.many2one('res.partner', 'Related Company'),
|
'parent_id': fields.many2one('res.partner', 'Related Company'),
|
||||||
'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts'),
|
'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts', domain=[('active','=',True)]), # force "active_test" domain to bypass _search() override
|
||||||
'ref': fields.char('Reference', size=64, select=1),
|
'ref': fields.char('Reference', size=64, select=1),
|
||||||
'lang': fields.selection(_lang_get, 'Language',
|
'lang': fields.selection(_lang_get, 'Language',
|
||||||
help="If the selected language is loaded in the system, all documents related to this contact will be printed in this language. If not, it will be English."),
|
help="If the selected language is loaded in the system, all documents related to this contact will be printed in this language. If not, it will be English."),
|
||||||
|
@ -264,6 +294,9 @@ class res_partner(osv.osv, format_address):
|
||||||
'color': fields.integer('Color Index'),
|
'color': fields.integer('Color Index'),
|
||||||
'user_ids': fields.one2many('res.users', 'partner_id', 'Users'),
|
'user_ids': fields.one2many('res.users', 'partner_id', 'Users'),
|
||||||
'contact_address': fields.function(_address_display, type='char', string='Complete Address'),
|
'contact_address': fields.function(_address_display, type='char', string='Complete Address'),
|
||||||
|
|
||||||
|
# technical field used for managing commercial fields
|
||||||
|
'commercial_partner_id': fields.function(_commercial_partner_id, type='many2one', relation='res.partner', string='Commercial Entity', store=_commercial_partner_store_triggers)
|
||||||
}
|
}
|
||||||
|
|
||||||
def _default_category(self, cr, uid, context=None):
|
def _default_category(self, cr, uid, context=None):
|
||||||
|
@ -302,11 +335,15 @@ class res_partner(osv.osv, format_address):
|
||||||
'company_id': lambda self, cr, uid, ctx: self.pool['res.company']._company_default_get(cr, uid, 'res.partner', context=ctx),
|
'company_id': lambda self, cr, uid, ctx: self.pool['res.company']._company_default_get(cr, uid, 'res.partner', context=ctx),
|
||||||
'color': 0,
|
'color': 0,
|
||||||
'is_company': False,
|
'is_company': False,
|
||||||
'type': 'default',
|
'type': 'contact', # type 'default' is wildcard and thus inappropriate
|
||||||
'use_parent_address': True,
|
'use_parent_address': False,
|
||||||
'image': False,
|
'image': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_constraints = [
|
||||||
|
(osv.osv._check_recursion, 'You cannot create recursive Partner hierarchies.', ['parent_id']),
|
||||||
|
]
|
||||||
|
|
||||||
def copy(self, cr, uid, id, default=None, context=None):
|
def copy(self, cr, uid, id, default=None, context=None):
|
||||||
if default is None:
|
if default is None:
|
||||||
default = {}
|
default = {}
|
||||||
|
@ -318,7 +355,6 @@ class res_partner(osv.osv, format_address):
|
||||||
value = {}
|
value = {}
|
||||||
value['title'] = False
|
value['title'] = False
|
||||||
if is_company:
|
if is_company:
|
||||||
value['parent_id'] = False
|
|
||||||
domain = {'title': [('domain', '=', 'partner')]}
|
domain = {'title': [('domain', '=', 'partner')]}
|
||||||
else:
|
else:
|
||||||
domain = {'title': [('domain', '=', 'contact')]}
|
domain = {'title': [('domain', '=', 'contact')]}
|
||||||
|
@ -328,11 +364,22 @@ class res_partner(osv.osv, format_address):
|
||||||
def value_or_id(val):
|
def value_or_id(val):
|
||||||
""" return val or val.id if val is a browse record """
|
""" return val or val.id if val is a browse record """
|
||||||
return val if isinstance(val, (bool, int, long, float, basestring)) else val.id
|
return val if isinstance(val, (bool, int, long, float, basestring)) else val.id
|
||||||
|
result = {}
|
||||||
if use_parent_address and parent_id:
|
if parent_id:
|
||||||
|
if ids:
|
||||||
|
partner = self.browse(cr, uid, ids[0], context=context)
|
||||||
|
if partner.parent_id and partner.parent_id.id != parent_id:
|
||||||
|
result['warning'] = {'title': _('Warning'),
|
||||||
|
'message': _('Changing the company of a contact should only be done if it '
|
||||||
|
'was never correctly set. If an existing contact starts working for a new '
|
||||||
|
'company then a new contact should be created under that new '
|
||||||
|
'company. You can use the "Discard" button to abandon this change.')}
|
||||||
parent = self.browse(cr, uid, parent_id, context=context)
|
parent = self.browse(cr, uid, parent_id, context=context)
|
||||||
return {'value': dict((key, value_or_id(parent[key])) for key in ADDRESS_FIELDS)}
|
address_fields = self._address_fields(cr, uid, context=context)
|
||||||
return {}
|
result['value'] = dict((key, value_or_id(parent[key])) for key in address_fields)
|
||||||
|
else:
|
||||||
|
result['value'] = {'use_parent_address': False}
|
||||||
|
return result
|
||||||
|
|
||||||
def onchange_state(self, cr, uid, ids, state_id, context=None):
|
def onchange_state(self, cr, uid, ids, state_id, context=None):
|
||||||
if state_id:
|
if state_id:
|
||||||
|
@ -358,50 +405,134 @@ class res_partner(osv.osv, format_address):
|
||||||
|
|
||||||
# _constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]
|
# _constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]
|
||||||
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
def _update_fields_values(self, cr, uid, partner, fields, context=None):
|
||||||
# Update parent and siblings or children records
|
""" Returns dict of write() values for synchronizing ``fields`` """
|
||||||
if isinstance(ids, (int, long)):
|
values = {}
|
||||||
ids = [ids]
|
for field in fields:
|
||||||
for partner in self.browse(cr, uid, ids, context=context):
|
column = self._all_columns[field].column
|
||||||
update_ids = []
|
if column._type == 'one2many':
|
||||||
if partner.is_company:
|
raise AssertionError('One2Many fields cannot be synchronized as part of `commercial_fields` or `address fields`')
|
||||||
domain_children = [('parent_id', 'child_of', partner.id), ('use_parent_address', '=', True)]
|
if column._type == 'many2one':
|
||||||
update_ids = self.search(cr, uid, domain_children, context=context)
|
values[field] = partner[field].id if partner[field] else False
|
||||||
elif partner.parent_id and vals.get('use_parent_address', partner.use_parent_address):
|
elif column._type == 'many2many':
|
||||||
domain_siblings = [('parent_id', '=', partner.parent_id.id), ('use_parent_address', '=', True)]
|
values[field] = [(6,0,[r.id for r in partner[field] or []])]
|
||||||
update_ids = [partner.parent_id.id] + self.search(cr, uid, domain_siblings, context=context)
|
|
||||||
self.update_address(cr, uid, update_ids, vals, context)
|
|
||||||
return super(res_partner,self).write(cr, uid, ids, vals, context=context)
|
|
||||||
|
|
||||||
def create(self, cr, uid, vals, context=None):
|
|
||||||
if context is None:
|
|
||||||
context = {}
|
|
||||||
# Update parent and siblings records
|
|
||||||
if vals.get('parent_id'):
|
|
||||||
if 'use_parent_address' in vals:
|
|
||||||
use_parent_address = vals['use_parent_address']
|
|
||||||
else:
|
else:
|
||||||
use_parent_address = self.default_get(cr, uid, ['use_parent_address'], context=context)['use_parent_address']
|
values[field] = partner[field]
|
||||||
|
return values
|
||||||
|
|
||||||
if use_parent_address:
|
def _address_fields(self, cr, uid, context=None):
|
||||||
domain_siblings = [('parent_id', '=', vals['parent_id']), ('use_parent_address', '=', True)]
|
""" Returns the list of address fields that are synced from the parent
|
||||||
update_ids = [vals['parent_id']] + self.search(cr, uid, domain_siblings, context=context)
|
when the `use_parent_address` flag is set. """
|
||||||
self.update_address(cr, uid, update_ids, vals, context)
|
return list(ADDRESS_FIELDS)
|
||||||
|
|
||||||
# add missing address keys
|
|
||||||
onchange_values = self.onchange_address(cr, uid, [], use_parent_address,
|
|
||||||
vals['parent_id'], context=context).get('value') or {}
|
|
||||||
vals.update(dict((key, value)
|
|
||||||
for key, value in onchange_values.iteritems()
|
|
||||||
if key in ADDRESS_FIELDS and key not in vals))
|
|
||||||
|
|
||||||
return super(res_partner, self).create(cr, uid, vals, context=context)
|
|
||||||
|
|
||||||
def update_address(self, cr, uid, ids, vals, context=None):
|
def update_address(self, cr, uid, ids, vals, context=None):
|
||||||
addr_vals = dict((key, vals[key]) for key in POSTAL_ADDRESS_FIELDS if key in vals)
|
address_fields = self._address_fields(cr, uid, context=context)
|
||||||
|
addr_vals = dict((key, vals[key]) for key in address_fields if key in vals)
|
||||||
if addr_vals:
|
if addr_vals:
|
||||||
return super(res_partner, self).write(cr, uid, ids, addr_vals, context)
|
return super(res_partner, self).write(cr, uid, ids, addr_vals, context)
|
||||||
|
|
||||||
|
def _commercial_fields(self, cr, uid, context=None):
|
||||||
|
""" Returns the list of fields that are managed by the commercial entity
|
||||||
|
to which a partner belongs. These fields are meant to be hidden on
|
||||||
|
partners that aren't `commercial entities` themselves, and will be
|
||||||
|
delegated to the parent `commercial entity`. The list is meant to be
|
||||||
|
extended by inheriting classes. """
|
||||||
|
return ['vat']
|
||||||
|
|
||||||
|
def _commercial_sync_from_company(self, cr, uid, partner, context=None):
|
||||||
|
""" Handle sync of commercial fields when a new parent commercial entity is set,
|
||||||
|
as if they were related fields """
|
||||||
|
if partner.commercial_partner_id != partner:
|
||||||
|
commercial_fields = self._commercial_fields(cr, uid, context=context)
|
||||||
|
sync_vals = self._update_fields_values(cr, uid, partner.commercial_partner_id,
|
||||||
|
commercial_fields, context=context)
|
||||||
|
partner.write(sync_vals)
|
||||||
|
|
||||||
|
def _commercial_sync_to_children(self, cr, uid, partner, context=None):
|
||||||
|
""" Handle sync of commercial fields to descendants """
|
||||||
|
commercial_fields = self._commercial_fields(cr, uid, context=context)
|
||||||
|
sync_vals = self._update_fields_values(cr, uid, partner.commercial_partner_id,
|
||||||
|
commercial_fields, context=context)
|
||||||
|
sync_children = [c for c in partner.child_ids if not c.is_company]
|
||||||
|
for child in sync_children:
|
||||||
|
self._commercial_sync_to_children(cr, uid, child, context=context)
|
||||||
|
return self.write(cr, uid, [c.id for c in sync_children], sync_vals, context=context)
|
||||||
|
|
||||||
|
def _fields_sync(self, cr, uid, partner, update_values, context=None):
|
||||||
|
""" Sync commercial fields and address fields from company and to children after create/update,
|
||||||
|
just as if those were all modeled as fields.related to the parent """
|
||||||
|
# 1. From UPSTREAM: sync from parent
|
||||||
|
if update_values.get('parent_id') or update_values.get('use_parent_address'):
|
||||||
|
# 1a. Commercial fields: sync if parent changed
|
||||||
|
if update_values.get('parent_id'):
|
||||||
|
self._commercial_sync_from_company(cr, uid, partner, context=context)
|
||||||
|
# 1b. Address fields: sync if parent or use_parent changed *and* both are now set
|
||||||
|
if partner.parent_id and partner.use_parent_address:
|
||||||
|
onchange_vals = self.onchange_address(cr, uid, [partner.id],
|
||||||
|
use_parent_address=partner.use_parent_address,
|
||||||
|
parent_id=partner.parent_id.id,
|
||||||
|
context=context).get('value', {})
|
||||||
|
partner.update_address(onchange_vals)
|
||||||
|
|
||||||
|
# 2. To DOWNSTREAM: sync children
|
||||||
|
if partner.child_ids:
|
||||||
|
# 2a. Commercial Fields: sync if commercial entity
|
||||||
|
if partner.commercial_partner_id == partner:
|
||||||
|
self._commercial_sync_to_children(cr, uid, partner, context=context)
|
||||||
|
# 2b. Address fields: sync if address changed
|
||||||
|
address_fields = self._address_fields(cr, uid, context=context)
|
||||||
|
if any(field in update_values for field in address_fields):
|
||||||
|
domain_children = [('parent_id', '=', partner.id), ('use_parent_address', '=', True)]
|
||||||
|
update_ids = self.search(cr, uid, domain_children, context=context)
|
||||||
|
self.update_address(cr, uid, update_ids, update_values, context=context)
|
||||||
|
|
||||||
|
def _handle_first_contact_creation(self, cr, uid, partner, context=None):
|
||||||
|
""" On creation of first contact for a company (or root) that has no address, assume contact address
|
||||||
|
was meant to be company address """
|
||||||
|
parent = partner.parent_id
|
||||||
|
address_fields = self._address_fields(cr, uid, context=context)
|
||||||
|
if parent and (parent.is_company or not parent.parent_id) and len(parent.child_ids) == 1 and \
|
||||||
|
any(partner[f] for f in address_fields) and not any(parent[f] for f in address_fields):
|
||||||
|
addr_vals = self._update_fields_values(cr, uid, partner, address_fields, context=context)
|
||||||
|
parent.update_address(addr_vals)
|
||||||
|
if not parent.is_company:
|
||||||
|
parent.write({'is_company': True})
|
||||||
|
|
||||||
|
def write(self, cr, uid, ids, vals, context=None):
|
||||||
|
if isinstance(ids, (int, long)):
|
||||||
|
ids = [ids]
|
||||||
|
result = super(res_partner,self).write(cr, uid, ids, vals, context=context)
|
||||||
|
for partner in self.browse(cr, uid, ids, context=context):
|
||||||
|
self._fields_sync(cr, uid, partner, vals, context)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def create(self, cr, uid, vals, context=None):
|
||||||
|
new_id = super(res_partner, self).create(cr, uid, vals, context=context)
|
||||||
|
partner = self.browse(cr, uid, new_id, context=context)
|
||||||
|
self._fields_sync(cr, uid, partner, vals, context)
|
||||||
|
self._handle_first_contact_creation(cr, uid, partner, context)
|
||||||
|
return new_id
|
||||||
|
|
||||||
|
def open_commercial_entity(self, cr, uid, ids, context=None):
|
||||||
|
""" Utility method used to add an "Open Company" button in partner views """
|
||||||
|
partner = self.browse(cr, uid, ids[0], context=context)
|
||||||
|
return {'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'res.partner',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'res_id': partner.commercial_partner_id.id,
|
||||||
|
'target': 'new',
|
||||||
|
'flags': {'form': {'action_buttons': True}}}
|
||||||
|
|
||||||
|
def open_parent(self, cr, uid, ids, context=None):
|
||||||
|
""" Utility method used to add an "Open Parent" button in partner views """
|
||||||
|
partner = self.browse(cr, uid, ids[0], context=context)
|
||||||
|
return {'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'res.partner',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'res_id': partner.parent_id.id,
|
||||||
|
'target': 'new',
|
||||||
|
'flags': {'form': {'action_buttons': True}}}
|
||||||
|
|
||||||
def name_get(self, cr, uid, ids, context=None):
|
def name_get(self, cr, uid, ids, context=None):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
|
@ -410,8 +541,8 @@ class res_partner(osv.osv, format_address):
|
||||||
res = []
|
res = []
|
||||||
for record in self.browse(cr, uid, ids, context=context):
|
for record in self.browse(cr, uid, ids, context=context):
|
||||||
name = record.name
|
name = record.name
|
||||||
if record.parent_id:
|
if record.parent_id and not record.is_company:
|
||||||
name = "%s (%s)" % (name, record.parent_id.name)
|
name = "%s, %s" % (record.parent_id.name, name)
|
||||||
if context.get('show_address'):
|
if context.get('show_address'):
|
||||||
name = name + "\n" + self._display_address(cr, uid, record, without_company=True, context=context)
|
name = name + "\n" + self._display_address(cr, uid, record, without_company=True, context=context)
|
||||||
name = name.replace('\n\n','\n')
|
name = name.replace('\n\n','\n')
|
||||||
|
@ -450,6 +581,15 @@ class res_partner(osv.osv, format_address):
|
||||||
rec_id = self.create(cr, uid, {self._rec_name: name or email, 'email': email or False}, context=context)
|
rec_id = self.create(cr, uid, {self._rec_name: name or email, 'email': email or False}, context=context)
|
||||||
return self.name_get(cr, uid, [rec_id], context)[0]
|
return self.name_get(cr, uid, [rec_id], context)[0]
|
||||||
|
|
||||||
|
def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
|
||||||
|
""" Override search() to always show inactive children when searching via ``child_of`` operator. The ORM will
|
||||||
|
always call search() with a simple domain of the form [('parent_id', 'in', [ids])]. """
|
||||||
|
# a special ``domain`` is set on the ``child_ids`` o2m to bypass this logic, as it uses similar domain expressions
|
||||||
|
if len(args) == 1 and len(args[0]) == 3 and args[0][:2] == ('parent_id','in'):
|
||||||
|
context = dict(context or {}, active_test=False)
|
||||||
|
return super(res_partner, self)._search(cr, user, args, offset=offset, limit=limit, order=order, context=context,
|
||||||
|
count=count, access_rights_uid=access_rights_uid)
|
||||||
|
|
||||||
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
|
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
|
||||||
if not args:
|
if not args:
|
||||||
args = []
|
args = []
|
||||||
|
@ -510,25 +650,42 @@ class res_partner(osv.osv, format_address):
|
||||||
ids = ids[16:]
|
ids = ids[16:]
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def address_get(self, cr, uid, ids, adr_pref=None):
|
def address_get(self, cr, uid, ids, adr_pref=None, context=None):
|
||||||
if adr_pref is None:
|
""" Find contacts/addresses of the right type(s) by doing a depth-first-search
|
||||||
adr_pref = ['default']
|
through descendants within company boundaries (stop at entities flagged ``is_company``)
|
||||||
|
then continuing the search at the ancestors that are within the same company boundaries.
|
||||||
|
Defaults to partners of type ``'default'`` when the exact type is not found, or to the
|
||||||
|
provided partner itself if no type ``'default'`` is found either. """
|
||||||
|
adr_pref = set(adr_pref or [])
|
||||||
|
if 'default' not in adr_pref:
|
||||||
|
adr_pref.add('default')
|
||||||
result = {}
|
result = {}
|
||||||
# retrieve addresses from the partner itself and its children
|
visited = set()
|
||||||
res = []
|
for partner in self.browse(cr, uid, filter(None, ids), context=context):
|
||||||
# need to fix the ids ,It get False value in list like ids[False]
|
current_partner = partner
|
||||||
if ids and ids[0]!=False:
|
while current_partner:
|
||||||
for p in self.browse(cr, uid, ids):
|
to_scan = [current_partner]
|
||||||
res.append((p.type, p.id))
|
# Scan descendants, DFS
|
||||||
res.extend((c.type, c.id) for c in p.child_ids)
|
while to_scan:
|
||||||
address_dict = dict(reversed(res))
|
record = to_scan.pop(0)
|
||||||
# get the id of the (first) default address if there is one,
|
visited.add(record)
|
||||||
# otherwise get the id of the first address in the list
|
if record.type in adr_pref and not result.get(record.type):
|
||||||
default_address = False
|
result[record.type] = record.id
|
||||||
if res:
|
if len(result) == len(adr_pref):
|
||||||
default_address = address_dict.get('default', res[0][1])
|
return result
|
||||||
for adr in adr_pref:
|
to_scan = [c for c in record.child_ids
|
||||||
result[adr] = address_dict.get(adr, default_address)
|
if c not in visited
|
||||||
|
if not c.is_company] + to_scan
|
||||||
|
|
||||||
|
# Continue scanning at ancestor if current_partner is not a commercial entity
|
||||||
|
if current_partner.is_company or not current_partner.parent_id:
|
||||||
|
break
|
||||||
|
current_partner = current_partner.parent_id
|
||||||
|
|
||||||
|
# default to type 'default' or the partner itself
|
||||||
|
default = result.get('default', partner.id)
|
||||||
|
for adr_type in adr_pref:
|
||||||
|
result[adr_type] = result.get(adr_type) or default
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def view_header_get(self, cr, uid, view_id, view_type, context):
|
def view_header_get(self, cr, uid, view_id, view_type, context):
|
||||||
|
@ -570,8 +727,7 @@ class res_partner(osv.osv, format_address):
|
||||||
'country_name': address.country_id and address.country_id.name or '',
|
'country_name': address.country_id and address.country_id.name or '',
|
||||||
'company_name': address.parent_id and address.parent_id.name or '',
|
'company_name': address.parent_id and address.parent_id.name or '',
|
||||||
}
|
}
|
||||||
address_field = ['title', 'street', 'street2', 'zip', 'city']
|
for field in self._address_fields(cr, uid, context=context):
|
||||||
for field in address_field :
|
|
||||||
args[field] = getattr(address, field) or ''
|
args[field] = getattr(address, field) or ''
|
||||||
if without_company:
|
if without_company:
|
||||||
args['company_name'] = ''
|
args['company_name'] = ''
|
||||||
|
|
|
@ -77,7 +77,7 @@
|
||||||
<field eval="8" name="priority"/>
|
<field eval="8" name="priority"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="Contacts">
|
<tree string="Contacts">
|
||||||
<field name="name"/>
|
<field name="display_name"/>
|
||||||
<field name="function" invisible="1"/>
|
<field name="function" invisible="1"/>
|
||||||
<field name="phone"/>
|
<field name="phone"/>
|
||||||
<field name="email"/>
|
<field name="email"/>
|
||||||
|
@ -138,8 +138,8 @@
|
||||||
</h1>
|
</h1>
|
||||||
<field name="parent_id"
|
<field name="parent_id"
|
||||||
placeholder="Company"
|
placeholder="Company"
|
||||||
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
|
domain="[('is_company', '=', True)]" context="{'default_is_company': True, 'default_supplier': supplier}"
|
||||||
attrs="{'invisible': [('is_company','=', True)]}"
|
attrs="{'invisible': [('is_company','=', True),('parent_id', '=', False)]}"
|
||||||
on_change="onchange_address(use_parent_address, parent_id)"/>
|
on_change="onchange_address(use_parent_address, parent_id)"/>
|
||||||
<field name="category_id" widget="many2many_tags" placeholder="Tags..."/>
|
<field name="category_id" widget="many2many_tags" placeholder="Tags..."/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -151,21 +151,24 @@
|
||||||
<div attrs="{'invisible': [('parent_id','=', False)]}" name="div_type">
|
<div attrs="{'invisible': [('parent_id','=', False)]}" name="div_type">
|
||||||
<field class="oe_inline"
|
<field class="oe_inline"
|
||||||
name="type"/>
|
name="type"/>
|
||||||
<label for="use_parent_address" class="oe_edit_only"/>
|
|
||||||
<field name="use_parent_address" class="oe_edit_only oe_inline"
|
|
||||||
on_change="onchange_address(use_parent_address, parent_id)"/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label for="street" string="Address"/>
|
<label for="street" string="Address"/>
|
||||||
<div>
|
<div>
|
||||||
<field name="street" placeholder="Street..."/>
|
<field name="use_parent_address" class="oe_edit_only oe_inline"
|
||||||
<field name="street2"/>
|
on_change="onchange_address(use_parent_address, parent_id)"
|
||||||
|
attrs="{'invisible': [('parent_id','=', False),('use_parent_address','=',False)]}"/>
|
||||||
|
<label for="use_parent_address" class="oe_edit_only" attrs="{'invisible': [('parent_id','=', False),('use_parent_address','=',False)]}"/>
|
||||||
|
<button name="open_parent" type="object" string="(edit company address)" class="oe_link oe_edit_only"
|
||||||
|
attrs="{'invisible': ['|',('parent_id','=', False),('use_parent_address','=',False)]}"/>
|
||||||
|
<field name="street" placeholder="Street..." attrs="{'readonly': [('use_parent_address','=',True)]}"/>
|
||||||
|
<field name="street2" attrs="{'readonly': [('use_parent_address','=',True)]}"/>
|
||||||
<div class="address_format">
|
<div class="address_format">
|
||||||
<field name="city" placeholder="City" style="width: 40%%"/>
|
<field name="city" placeholder="City" style="width: 40%%" attrs="{'readonly': [('use_parent_address','=',True)]}"/>
|
||||||
<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="state_id" class="oe_no_button" placeholder="State" style="width: 37%%" options='{"no_open": True}' on_change="onchange_state(state_id)" attrs="{'readonly': [('use_parent_address','=',True)]}"/>
|
||||||
<field name="zip" placeholder="ZIP" style="width: 20%%"/>
|
<field name="zip" placeholder="ZIP" style="width: 20%%" attrs="{'readonly': [('use_parent_address','=',True)]}"/>
|
||||||
</div>
|
</div>
|
||||||
<field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": True}'/>
|
<field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": True}' attrs="{'readonly': [('use_parent_address','=',True)]}"/>
|
||||||
</div>
|
</div>
|
||||||
<field name="website" widget="url" placeholder="e.g. www.openerp.com"/>
|
<field name="website" widget="url" placeholder="e.g. www.openerp.com"/>
|
||||||
</group>
|
</group>
|
||||||
|
@ -182,8 +185,8 @@
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<notebook colspan="4">
|
<notebook colspan="4">
|
||||||
<page string="Contacts" attrs="{'invisible': [('is_company','=',False)]}">
|
<page string="Contacts" attrs="{'invisible': [('is_company','=',False), ('child_ids', '=', [])]}" autofocus="autofocus">
|
||||||
<field name="child_ids" context="{'default_parent_id': active_id}" mode="kanban">
|
<field name="child_ids" mode="kanban" context="{'default_parent_id': active_id, '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}">
|
||||||
<kanban>
|
<kanban>
|
||||||
<field name="color"/>
|
<field name="color"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
@ -249,17 +252,41 @@
|
||||||
</templates>
|
</templates>
|
||||||
</kanban>
|
</kanban>
|
||||||
<form string="Contact" version="7.0">
|
<form string="Contact" version="7.0">
|
||||||
<field name="image" widget='image' class="oe_avatar oe_left" options='{"preview_image": "image_medium"}'/>
|
<sheet>
|
||||||
<div class="oe_title">
|
<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>
|
||||||
|
<field name="category_id" widget="many2many_tags" placeholder="Tags..." style="width: 70%%"/>
|
||||||
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
|
||||||
<field name="category_id" widget="many2many_tags" placeholder="Tags..."/>
|
|
||||||
<field name="function" placeholder="e.g. Sales Director"/>
|
<field name="function" placeholder="e.g. Sales Director"/>
|
||||||
<field name="email"/>
|
<field name="email"/>
|
||||||
<field name="phone"/>
|
<field name="phone"/>
|
||||||
<field name="mobile"/>
|
<field name="mobile"/>
|
||||||
</group>
|
</group>
|
||||||
</div>
|
<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>
|
||||||
|
<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>
|
||||||
|
<field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": True}'/>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
<field name="supplier" invisible="True"/>
|
||||||
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
|
@ -330,7 +357,7 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<kanban>
|
<kanban>
|
||||||
<field name="color"/>
|
<field name="color"/>
|
||||||
<field name="name"/>
|
<field name="display_name"/>
|
||||||
<field name="title"/>
|
<field name="title"/>
|
||||||
<field name="email"/>
|
<field name="email"/>
|
||||||
<field name="parent_id"/>
|
<field name="parent_id"/>
|
||||||
|
@ -363,7 +390,7 @@
|
||||||
</t>
|
</t>
|
||||||
</a>
|
</a>
|
||||||
<div class="oe_kanban_details">
|
<div class="oe_kanban_details">
|
||||||
<h4 class="oe_partner_heading"><a type="open"><field name="name"/></a></h4>
|
<h4 class="oe_partner_heading"><a type="open"><field name="display_name"/></a></h4>
|
||||||
<div class="oe_kanban_partner_categories"/>
|
<div class="oe_kanban_partner_categories"/>
|
||||||
<div class="oe_kanban_partner_links"/>
|
<div class="oe_kanban_partner_links"/>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import unittest2
|
import unittest2
|
||||||
|
|
||||||
import openerp.tests.common as common
|
import openerp.tests.common as common
|
||||||
|
from openerp.osv.orm import except_orm
|
||||||
|
|
||||||
class test_base(common.TransactionCase):
|
class test_base(common.TransactionCase):
|
||||||
|
|
||||||
|
@ -39,5 +40,269 @@ class test_base(common.TransactionCase):
|
||||||
self.assertTrue(new_id2 > new_id, 'find_or_create failed - should have created new one again')
|
self.assertTrue(new_id2 > new_id, 'find_or_create failed - should have created new one again')
|
||||||
|
|
||||||
|
|
||||||
|
def test_20_res_partner_address_sync(self):
|
||||||
|
cr, uid = self.cr, self.uid
|
||||||
|
ghoststep = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||||
|
{'name': 'GhostStep',
|
||||||
|
'is_company': True,
|
||||||
|
'street': 'Main Street, 10',
|
||||||
|
'phone': '123456789',
|
||||||
|
'email': 'info@ghoststep.com',
|
||||||
|
'vat': 'BE0477472701',
|
||||||
|
'type': 'default'}))
|
||||||
|
p1 = self.res_partner.browse(cr, uid, self.res_partner.name_create(cr, uid, 'Denis Bladesmith <denis.bladesmith@ghoststep.com>')[0])
|
||||||
|
self.assertEqual(p1.type, 'contact', 'Default type must be "contact"')
|
||||||
|
p1phone = '123456789#34'
|
||||||
|
p1.write({'phone': p1phone,
|
||||||
|
'parent_id': ghoststep.id,
|
||||||
|
'use_parent_address': True})
|
||||||
|
p1.refresh()
|
||||||
|
self.assertEqual(p1.street, ghoststep.street, 'Address fields must be synced')
|
||||||
|
self.assertEqual(p1.phone, p1phone, 'Phone should be preserved after address sync')
|
||||||
|
self.assertEqual(p1.type, 'contact', 'Type should be preserved after address sync')
|
||||||
|
self.assertEqual(p1.email, 'denis.bladesmith@ghoststep.com', 'Email should be preserved after sync')
|
||||||
|
|
||||||
|
# turn off sync
|
||||||
|
p1street = 'Different street, 42'
|
||||||
|
p1.write({'street': p1street,
|
||||||
|
'use_parent_address': False})
|
||||||
|
p1.refresh(), ghoststep.refresh()
|
||||||
|
self.assertEqual(p1.street, p1street, 'Address fields must not be synced after turning sync off')
|
||||||
|
self.assertNotEqual(ghoststep.street, p1street, 'Parent address must never be touched')
|
||||||
|
|
||||||
|
# turn on sync again
|
||||||
|
p1.write({'use_parent_address': True})
|
||||||
|
p1.refresh()
|
||||||
|
self.assertEqual(p1.street, ghoststep.street, 'Address fields must be synced again')
|
||||||
|
self.assertEqual(p1.phone, p1phone, 'Phone should be preserved after address sync')
|
||||||
|
self.assertEqual(p1.type, 'contact', 'Type should be preserved after address sync')
|
||||||
|
self.assertEqual(p1.email, 'denis.bladesmith@ghoststep.com', 'Email should be preserved after sync')
|
||||||
|
|
||||||
|
# Modify parent, sync to children
|
||||||
|
ghoststreet = 'South Street, 25'
|
||||||
|
ghoststep.write({'street': ghoststreet})
|
||||||
|
p1.refresh()
|
||||||
|
self.assertEqual(p1.street, ghoststreet, 'Address fields must be synced automatically')
|
||||||
|
self.assertEqual(p1.phone, p1phone, 'Phone should not be synced')
|
||||||
|
self.assertEqual(p1.email, 'denis.bladesmith@ghoststep.com', 'Email should be preserved after sync')
|
||||||
|
|
||||||
|
p1street = 'My Street, 11'
|
||||||
|
p1.write({'street': p1street})
|
||||||
|
ghoststep.refresh()
|
||||||
|
self.assertEqual(ghoststep.street, ghoststreet, 'Touching contact should never alter parent')
|
||||||
|
|
||||||
|
|
||||||
|
def test_30_res_partner_first_contact_sync(self):
|
||||||
|
""" Test initial creation of company/contact pair where contact address gets copied to
|
||||||
|
company """
|
||||||
|
cr, uid = self.cr, self.uid
|
||||||
|
ironshield = self.res_partner.browse(cr, uid, self.res_partner.name_create(cr, uid, 'IronShield')[0])
|
||||||
|
self.assertFalse(ironshield.is_company, 'Partners are not companies by default')
|
||||||
|
self.assertFalse(ironshield.use_parent_address, 'use_parent_address defaults to False')
|
||||||
|
self.assertEqual(ironshield.type, 'contact', 'Default type must be "contact"')
|
||||||
|
ironshield.write({'type': 'default'}) # force default type to double-check sync
|
||||||
|
p1 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||||
|
{'name': 'Isen Hardearth',
|
||||||
|
'street': 'Strongarm Avenue, 12',
|
||||||
|
'parent_id': ironshield.id}))
|
||||||
|
self.assertEquals(p1.type, 'contact', 'Default type must be "contact", not the copied parent type')
|
||||||
|
ironshield.refresh()
|
||||||
|
self.assertEqual(ironshield.street, p1.street, 'Address fields should be copied to company')
|
||||||
|
self.assertTrue(ironshield.is_company, 'Company flag should be turned on after first contact creation')
|
||||||
|
|
||||||
|
def test_40_res_partner_address_getc(self):
|
||||||
|
""" Test address_get address resolution mechanism: it should first go down through descendants,
|
||||||
|
stopping when encountering another is_copmany entity, then go up, stopping again at the first
|
||||||
|
is_company entity or the root ancestor and if nothing matches, it should use the provided partner
|
||||||
|
itself """
|
||||||
|
cr, uid = self.cr, self.uid
|
||||||
|
elmtree = self.res_partner.browse(cr, uid, self.res_partner.name_create(cr, uid, 'Elmtree')[0])
|
||||||
|
branch1 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Branch 1',
|
||||||
|
'parent_id': elmtree.id,
|
||||||
|
'is_company': True}))
|
||||||
|
leaf10 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Leaf 10',
|
||||||
|
'parent_id': branch1.id,
|
||||||
|
'type': 'invoice'}))
|
||||||
|
branch11 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Branch 11',
|
||||||
|
'parent_id': branch1.id,
|
||||||
|
'type': 'other'}))
|
||||||
|
leaf111 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Leaf 111',
|
||||||
|
'parent_id': branch11.id,
|
||||||
|
'type': 'delivery'}))
|
||||||
|
branch11.write({'is_company': False}) # force is_company after creating 1rst child
|
||||||
|
branch2 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Branch 2',
|
||||||
|
'parent_id': elmtree.id,
|
||||||
|
'is_company': True}))
|
||||||
|
leaf21 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Leaf 21',
|
||||||
|
'parent_id': branch2.id,
|
||||||
|
'type': 'delivery'}))
|
||||||
|
leaf22 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Leaf 22',
|
||||||
|
'parent_id': branch2.id}))
|
||||||
|
leaf23 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid, {'name': 'Leaf 23',
|
||||||
|
'parent_id': branch2.id,
|
||||||
|
'type': 'default'}))
|
||||||
|
# go up, stop at branch1
|
||||||
|
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf111.id], ['delivery', 'invoice', 'contact', 'other', 'default']),
|
||||||
|
{'delivery': leaf111.id,
|
||||||
|
'invoice': leaf10.id,
|
||||||
|
'contact': branch1.id,
|
||||||
|
'other': branch11.id,
|
||||||
|
'default': leaf111.id}, 'Invalid address resolution')
|
||||||
|
self.assertEqual(self.res_partner.address_get(cr, uid, [branch11.id], ['delivery', 'invoice', 'contact', 'other', 'default']),
|
||||||
|
{'delivery': leaf111.id,
|
||||||
|
'invoice': leaf10.id,
|
||||||
|
'contact': branch1.id,
|
||||||
|
'other': branch11.id,
|
||||||
|
'default': branch11.id}, 'Invalid address resolution')
|
||||||
|
|
||||||
|
# go down, stop at at all child companies
|
||||||
|
self.assertEqual(self.res_partner.address_get(cr, uid, [elmtree.id], ['delivery', 'invoice', 'contact', 'other', 'default']),
|
||||||
|
{'delivery': elmtree.id,
|
||||||
|
'invoice': elmtree.id,
|
||||||
|
'contact': elmtree.id,
|
||||||
|
'other': elmtree.id,
|
||||||
|
'default': elmtree.id}, 'Invalid address resolution')
|
||||||
|
|
||||||
|
# go down through children
|
||||||
|
self.assertEqual(self.res_partner.address_get(cr, uid, [branch1.id], ['delivery', 'invoice', 'contact', 'other', 'default']),
|
||||||
|
{'delivery': leaf111.id,
|
||||||
|
'invoice': leaf10.id,
|
||||||
|
'contact': branch1.id,
|
||||||
|
'other': branch11.id,
|
||||||
|
'default': branch1.id}, 'Invalid address resolution')
|
||||||
|
self.assertEqual(self.res_partner.address_get(cr, uid, [branch2.id], ['delivery', 'invoice', 'contact', 'other', 'default']),
|
||||||
|
{'delivery': leaf21.id,
|
||||||
|
'invoice': leaf23.id,
|
||||||
|
'contact': branch2.id,
|
||||||
|
'other': leaf23.id,
|
||||||
|
'default': leaf23.id}, 'Invalid address resolution')
|
||||||
|
|
||||||
|
# go up then down through siblings
|
||||||
|
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf21.id], ['delivery', 'invoice', 'contact', 'other', 'default']),
|
||||||
|
{'delivery': leaf21.id,
|
||||||
|
'invoice': leaf23.id,
|
||||||
|
'contact': branch2.id,
|
||||||
|
'other': leaf23.id,
|
||||||
|
'default': leaf23.id
|
||||||
|
}, 'Invalid address resolution, should scan commercial entity ancestor and its descendants')
|
||||||
|
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf22.id], ['delivery', 'invoice', 'contact', 'other', 'default']),
|
||||||
|
{'delivery': leaf21.id,
|
||||||
|
'invoice': leaf23.id,
|
||||||
|
'contact': leaf22.id,
|
||||||
|
'other': leaf23.id,
|
||||||
|
'default': leaf23.id}, 'Invalid address resolution, should scan commercial entity ancestor and its descendants')
|
||||||
|
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf23.id], ['delivery', 'invoice', 'contact', 'other', 'default']),
|
||||||
|
{'delivery': leaf21.id,
|
||||||
|
'invoice': leaf23.id,
|
||||||
|
'contact': branch2.id,
|
||||||
|
'other': leaf23.id,
|
||||||
|
'default': leaf23.id}, 'Invalid address resolution, `default` should only override if no partner with specific type exists')
|
||||||
|
|
||||||
|
# empty adr_pref means only 'default'
|
||||||
|
self.assertEqual(self.res_partner.address_get(cr, uid, [elmtree.id], []),
|
||||||
|
{'default': elmtree.id}, 'Invalid address resolution, no default means commercial entity ancestor')
|
||||||
|
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf111.id], []),
|
||||||
|
{'default': leaf111.id}, 'Invalid address resolution, no default means contact itself')
|
||||||
|
branch11.write({'type': 'default'})
|
||||||
|
self.assertEqual(self.res_partner.address_get(cr, uid, [leaf111.id], []),
|
||||||
|
{'default': branch11.id}, 'Invalid address resolution, branch11 should now be default')
|
||||||
|
|
||||||
|
|
||||||
|
def test_50_res_partner_commercial_sync(self):
|
||||||
|
cr, uid = self.cr, self.uid
|
||||||
|
p0 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||||
|
{'name': 'Sigurd Sunknife',
|
||||||
|
'email': 'ssunknife@gmail.com'}))
|
||||||
|
sunhelm = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||||
|
{'name': 'Sunhelm',
|
||||||
|
'is_company': True,
|
||||||
|
'street': 'Rainbow Street, 13',
|
||||||
|
'phone': '1122334455',
|
||||||
|
'email': 'info@sunhelm.com',
|
||||||
|
'vat': 'BE0477472701',
|
||||||
|
'child_ids': [(4, p0.id),
|
||||||
|
(0, 0, {'name': 'Alrik Greenthorn',
|
||||||
|
'email': 'agr@sunhelm.com'})],
|
||||||
|
}))
|
||||||
|
p1 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||||
|
{'name': 'Otto Blackwood',
|
||||||
|
'email': 'otto.blackwood@sunhelm.com',
|
||||||
|
'parent_id': sunhelm.id}))
|
||||||
|
p11 = self.res_partner.browse(cr, uid, self.res_partner.create(cr, uid,
|
||||||
|
{'name': 'Gini Graywool',
|
||||||
|
'email': 'ggr@sunhelm.com',
|
||||||
|
'parent_id': p1.id}))
|
||||||
|
p2 = self.res_partner.browse(cr, uid, self.res_partner.search(cr, uid,
|
||||||
|
[('email', '=', 'agr@sunhelm.com')])[0])
|
||||||
|
|
||||||
|
for p in (p0, p1, p11, p2):
|
||||||
|
p.refresh()
|
||||||
|
self.assertEquals(p.commercial_partner_id, sunhelm, 'Incorrect commercial entity resolution')
|
||||||
|
self.assertEquals(p.vat, sunhelm.vat, 'Commercial fields must be automatically synced')
|
||||||
|
sunhelmvat = 'BE0123456789'
|
||||||
|
sunhelm.write({'vat': sunhelmvat})
|
||||||
|
for p in (p0, p1, p11, p2):
|
||||||
|
p.refresh()
|
||||||
|
self.assertEquals(p.vat, sunhelmvat, 'Commercial fields must be automatically and recursively synced')
|
||||||
|
|
||||||
|
p1vat = 'BE0987654321'
|
||||||
|
p1.write({'vat': p1vat})
|
||||||
|
for p in (sunhelm, p0, p11, p2):
|
||||||
|
p.refresh()
|
||||||
|
self.assertEquals(p.vat, sunhelmvat, 'Sync to children should only work downstream and on commercial entities')
|
||||||
|
|
||||||
|
# promote p1 to commercial entity
|
||||||
|
vals = p1.onchange_type(is_company=True)['value']
|
||||||
|
p1.write(dict(vals, parent_id=sunhelm.id,
|
||||||
|
is_company=True,
|
||||||
|
name='Sunhelm Subsidiary'))
|
||||||
|
p1.refresh()
|
||||||
|
self.assertEquals(p1.vat, p1vat, 'Setting is_company should stop auto-sync of commercial fields')
|
||||||
|
self.assertEquals(p1.commercial_partner_id, p1, 'Incorrect commercial entity resolution after setting is_company')
|
||||||
|
|
||||||
|
# writing on parent should not touch child commercial entities
|
||||||
|
sunhelmvat2 = 'BE0112233445'
|
||||||
|
sunhelm.write({'vat': sunhelmvat2})
|
||||||
|
p1.refresh()
|
||||||
|
self.assertEquals(p1.vat, p1vat, 'Setting is_company should stop auto-sync of commercial fields')
|
||||||
|
p0.refresh()
|
||||||
|
self.assertEquals(p0.vat, sunhelmvat2, 'Commercial fields must be automatically synced')
|
||||||
|
|
||||||
|
class test_partner_recursion(common.TransactionCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(test_partner_recursion,self).setUp()
|
||||||
|
self.res_partner = self.registry('res.partner')
|
||||||
|
cr, uid = self.cr, self.uid
|
||||||
|
self.p1 = self.res_partner.name_create(cr, uid, 'Elmtree')[0]
|
||||||
|
self.p2 = self.res_partner.create(cr, uid, {'name': 'Elmtree Child 1', 'parent_id': self.p1})
|
||||||
|
self.p3 = self.res_partner.create(cr, uid, {'name': 'Elmtree Grand-Child 1.1', 'parent_id': self.p2})
|
||||||
|
|
||||||
|
# split 101, 102, 103 tests to force SQL rollback between them
|
||||||
|
|
||||||
|
def test_101_res_partner_recursion(self):
|
||||||
|
cr, uid, p1, p3 = self.cr, self.uid, self.p1, self.p3
|
||||||
|
self.assertRaises(except_orm, self.res_partner.write, cr, uid, [p1], {'parent_id': p3})
|
||||||
|
|
||||||
|
def test_102_res_partner_recursion(self):
|
||||||
|
cr, uid, p2, p3 = self.cr, self.uid, self.p2, self.p3
|
||||||
|
self.assertRaises(except_orm, self.res_partner.write, cr, uid, [p2], {'parent_id': p3})
|
||||||
|
|
||||||
|
def test_103_res_partner_recursion(self):
|
||||||
|
cr, uid, p3 = self.cr, self.uid, self.p3
|
||||||
|
self.assertRaises(except_orm, self.res_partner.write, cr, uid, [p3], {'parent_id': p3})
|
||||||
|
|
||||||
|
def test_104_res_partner_recursion_indirect_cycle(self):
|
||||||
|
""" Indirect hacky write to create cycle in children """
|
||||||
|
cr, uid, p2, p3 = self.cr, self.uid, self.p2, self.p3
|
||||||
|
p3b = self.res_partner.create(cr, uid, {'name': 'Elmtree Grand-Child 1.2', 'parent_id': self.p2})
|
||||||
|
self.assertRaises(except_orm, self.res_partner.write, cr, uid, [p2],
|
||||||
|
{'child_ids': [(1, p3, {'parent_id': p3b}), (1, p3b, {'parent_id': p3})]})
|
||||||
|
|
||||||
|
def test_110_res_partner_recursion_multi_update(self):
|
||||||
|
""" multi-write on several partners in same hierarchy must not trigger a false cycle detection """
|
||||||
|
cr, uid, p1, p2, p3 = self.cr, self.uid, self.p1, self.p2, self.p3
|
||||||
|
self.assertTrue(self.res_partner.write(cr, uid, [p1,p2,p3], {'phone': '123456'}))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest2.main()
|
unittest2.main()
|
||||||
|
|
Loading…
Reference in New Issue