Merge lp:~savoirfairelinux-openerp/partner-contact-management/partner-contact-management-base_contact into lp:~partner-contact-core-editors/partner-contact-management/7.0
- partner-contact-management-base_contact
- Merge into 7.0
Status: | Merged |
---|---|
Merged at revision: | 38 |
Proposed branch: | lp:~savoirfairelinux-openerp/partner-contact-management/partner-contact-management-base_contact |
Merge into: | lp:~partner-contact-core-editors/partner-contact-management/7.0 |
Diff against target: |
694 lines (+657/-0) 7 files modified
base_contact/__init__.py (+22/-0) base_contact/__openerp__.py (+56/-0) base_contact/base_contact.py (+186/-0) base_contact/base_contact_demo.xml (+29/-0) base_contact/base_contact_view.xml (+202/-0) base_contact/tests/__init__.py (+26/-0) base_contact/tests/test_base_contact.py (+136/-0) |
To merge this branch: | bzr merge lp:~savoirfairelinux-openerp/partner-contact-management/partner-contact-management-base_contact |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Guewen Baconnier @ Camptocamp | code review | Approve | |
Mario Arias (community) | code review and test | Approve | |
Sandy Carter (http://www.savoirfairelinux.com) | code review, test | Approve | |
Partner and Contact Core Editors | Pending | ||
Xavier ALT | Pending | ||
Review via email: mp+203979@code.launchpad.net |
Commit message
Description of the change
- Added base_contact module.This module has been migrated from OP6.1 to OP7.0 and a merge is proposed at 2013-10-22.
Details of MP: https:/
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : | # |
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : | # |
The MP: https:/
There is no technical reason that it cannot be moved here. It will be beneficial to have it here instead of OCB
Pedro Manuel Baeza (pedro.baeza) wrote : | # |
But do you approve the module as is? If the module is finally merged with changes, how can we handle migration?
At least I think that we must rename it to avoid these things, but this means also renaming fields. Uf, too many doubts...
Regards.
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : | # |
We have a few options:
1/ If openobject-addons MP changes, keep up with those changes, if it gets merged, remove it from here
2/ Ask that the openobject-addons MP be closed and suggest here as the repository to put it
3/ Duplicate the module under a different name
I would rather do 2. The openobjects MP is dead in its tracks.
Pedro Manuel Baeza (pedro.baeza) wrote : | # |
Let's wait what others say.
Regards.
- 33. By Sandy Carter (http://www.savoirfairelinux.com)
-
[FIX] base_location - Add context propagation to base_location
- 34. By El Hadji Dem (http://www.savoirfairelinux.com)
-
[ADD] Add passport and passport_partner modules: You can manage several passports for each contact.
- 35. By Jonathan Nemry (Acsone)
-
[MRG]
For Partner:
1) Create always the SQL constraint on 'lastname'
2) Better implementation of "_write_name" allowing more intuitive update of the partner name when processing from another model (ex: res_user), i.e., try to keep the firstname if unchanged
3) When duplicating a partner, avoid to repeat the firstname in the name
4) Beautify the inner form for children contacts (placing fields as in the main form)
5) Allow edition of the field name in the inner form if child is a companyFor User:
1) Reintegrate the name as "required"
2) When duplicating a user, avoid to repeat the firstname in the related partner name
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : | # |
Any update on this?
Mario Arias (the-clone-master) wrote : | # |
We are using the original one for a basic project, to map clients and employees to multiple companies.
It works flawlessly, but the scope is really reduced (no HR, no stock management, only service invoices, ...)
Is a very handy module, but OpenERP S.A. seems to have forgotten about it...
:(
I prefer having it here, so others could also try and improve it...
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
> There is no technical reason that it cannot be moved here. It will be beneficial to have it here instead of OCB
> We have a few options:
> 1/ If openobject-addons MP changes, keep up with those changes, if it gets
> merged, remove it from here
> 2/ Ask that the openobject-addons MP be closed and suggest here as the
> repository to put it
> 3/ Duplicate the module under a different name
>
> I would rather do 2. The openobjects MP is dead in its tracks.
Why would it be beneficial to have it here rather than in OCB? Wouldn't be easier to track the changes if the branches have the same origin? On the other hand I'm unsure what is the OCB policy about adding a module, even if it is "supposed" to be merged in the official addons.
Unrelated: the bzr history and authorship has been lost, please use bzr-super-replay / --author must be used to keep the original author.
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : | # |
For one, I do not use OCB, so there would be no point for me to propose a merge into it if I want to use it. ;)
Second, I made a mistake in my comment when saying OCB, I meant OpenObject-Addons.
You are absolutely right about the authorship, I will fix that. Thanks for pointing that out with a solution.
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : | # |
@Guewen
I reconstructed the history to the best of my abilities.
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
Thanks!
That's ok for me now.
Preview Diff
1 | === added directory 'base_contact' |
2 | === added file 'base_contact/__init__.py' |
3 | --- base_contact/__init__.py 1970-01-01 00:00:00 +0000 |
4 | +++ base_contact/__init__.py 2014-03-21 15:26:56 +0000 |
5 | @@ -0,0 +1,22 @@ |
6 | +# -*- coding: utf-8 -*- |
7 | +############################################################################## |
8 | +# |
9 | +# OpenERP, Open Source Management Solution |
10 | +# Copyright (C) 2013-TODAY OpenERP SA (<http://www.openerp.com>). |
11 | +# |
12 | +# This program is free software: you can redistribute it and/or modify |
13 | +# it under the terms of the GNU Affero General Public License as |
14 | +# published by the Free Software Foundation, either version 3 of the |
15 | +# License, or (at your option) any later version. |
16 | +# |
17 | +# This program is distributed in the hope that it will be useful, |
18 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | +# GNU Affero General Public License for more details. |
21 | +# |
22 | +# You should have received a copy of the GNU Affero General Public License |
23 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
24 | +# |
25 | +############################################################################## |
26 | + |
27 | +from . import base_contact |
28 | |
29 | === added file 'base_contact/__openerp__.py' |
30 | --- base_contact/__openerp__.py 1970-01-01 00:00:00 +0000 |
31 | +++ base_contact/__openerp__.py 2014-03-21 15:26:56 +0000 |
32 | @@ -0,0 +1,56 @@ |
33 | +# -*- coding: utf-8 -*- |
34 | +############################################################################## |
35 | +# |
36 | +# OpenERP, Open Source Business Applications |
37 | +# Copyright (C) 2013-TODAY OpenERP S.A. (<http://openerp.com>). |
38 | +# |
39 | +# This program is free software: you can redistribute it and/or modify |
40 | +# it under the terms of the GNU Affero General Public License as |
41 | +# published by the Free Software Foundation, either version 3 of the |
42 | +# License, or (at your option) any later version. |
43 | +# |
44 | +# This program is distributed in the hope that it will be useful, |
45 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
46 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
47 | +# GNU Affero General Public License for more details. |
48 | +# |
49 | +# You should have received a copy of the GNU Affero General Public License |
50 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
51 | +# |
52 | +############################################################################## |
53 | + |
54 | +{ |
55 | + 'name': 'Contacts Management', |
56 | + 'version': '1.0', |
57 | + 'author': 'OpenERP SA', |
58 | + 'website': 'http://www.openerp.com', |
59 | + 'category': 'Customer Relationship Management', |
60 | + 'complexity': "expert", |
61 | + 'description': """ |
62 | +This module allows you to manage your contacts |
63 | +============================================== |
64 | + |
65 | +It lets you define groups of contacts sharing some common information, like: |
66 | + * Birthdate |
67 | + * Nationality |
68 | + * Native Language |
69 | +""", |
70 | + 'depends': [ |
71 | + 'base', |
72 | + 'process', |
73 | + 'contacts' |
74 | + ], |
75 | + 'external_dependencies': {}, |
76 | + 'data': [ |
77 | + 'base_contact_view.xml', |
78 | + ], |
79 | + 'demo': [ |
80 | + 'base_contact_demo.xml', |
81 | + ], |
82 | + 'test': [], |
83 | + 'installable': True, |
84 | + 'auto_install': False, |
85 | + 'images': [], |
86 | +} |
87 | + |
88 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
89 | |
90 | === added file 'base_contact/base_contact.py' |
91 | --- base_contact/base_contact.py 1970-01-01 00:00:00 +0000 |
92 | +++ base_contact/base_contact.py 2014-03-21 15:26:56 +0000 |
93 | @@ -0,0 +1,186 @@ |
94 | +# -*- coding: utf-8 -*- |
95 | +############################################################################## |
96 | +# |
97 | +# OpenERP, Open Source Management Solution |
98 | +# Copyright (C) 2013-TODAY OpenERP SA (<http://www.openerp.com>). |
99 | +# |
100 | +# This program is free software: you can redistribute it and/or modify |
101 | +# it under the terms of the GNU Affero General Public License as |
102 | +# published by the Free Software Foundation, either version 3 of the |
103 | +# License, or (at your option) any later version. |
104 | +# |
105 | +# This program is distributed in the hope that it will be useful, |
106 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
107 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
108 | +# GNU Affero General Public License for more details. |
109 | +# |
110 | +# You should have received a copy of the GNU Affero General Public License |
111 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
112 | +# |
113 | +############################################################################## |
114 | + |
115 | +from openerp.osv import fields, orm, expression |
116 | + |
117 | + |
118 | +class res_partner(orm.Model): |
119 | + _inherit = 'res.partner' |
120 | + |
121 | + _contact_type = [ |
122 | + ('standalone', 'Standalone Contact'), |
123 | + ('attached', 'Attached to existing Contact'), |
124 | + ] |
125 | + |
126 | + def _get_contact_type(self, cr, uid, ids, field_name, args, context=None): |
127 | + result = dict.fromkeys(ids, 'standalone') |
128 | + for partner in self.browse(cr, uid, ids, context=context): |
129 | + if partner.contact_id: |
130 | + result[partner.id] = 'attached' |
131 | + return result |
132 | + |
133 | + _columns = { |
134 | + 'contact_type': fields.function(_get_contact_type, type='selection', selection=_contact_type, |
135 | + string='Contact Type', required=True, select=1, store=True), |
136 | + 'contact_id': fields.many2one('res.partner', 'Main Contact', |
137 | + domain=[('is_company', '=', False), ('contact_type', '=', 'standalone')]), |
138 | + 'other_contact_ids': fields.one2many('res.partner', 'contact_id', 'Others Positions'), |
139 | + |
140 | + # Person specific fields |
141 | + # add a 'birthdate' as date field, i.e different from char 'birthdate' introduced v6.1! |
142 | + 'birthdate_date': fields.date('Birthdate'), |
143 | + 'nationality_id': fields.many2one('res.country', 'Nationality'), |
144 | + } |
145 | + |
146 | + _defaults = { |
147 | + 'contact_type': 'standalone', |
148 | + } |
149 | + |
150 | + def _basecontact_check_context(self, cr, user, mode, context=None): |
151 | + """ Remove 'search_show_all_positions' for non-search mode. |
152 | + Keeping it in context can result in unexpected behaviour (ex: reading |
153 | + one2many might return wrong result - i.e with "attached contact" removed |
154 | + even if it's directly linked to a company). """ |
155 | + if context is None: |
156 | + context = {} |
157 | + if mode != 'search': |
158 | + context.pop('search_show_all_positions', None) |
159 | + return context |
160 | + |
161 | + def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False): |
162 | + """ Display only standalone contact matching ``args`` or having |
163 | + attached contact matching ``args`` """ |
164 | + if context is None: |
165 | + context = {} |
166 | + if context.get('search_show_all_positions') is False: |
167 | + args = expression.normalize_domain(args) |
168 | + attached_contact_args = expression.AND((args, [('contact_type', '=', 'attached')])) |
169 | + attached_contact_ids = super(res_partner, self).search(cr, user, attached_contact_args, |
170 | + context=context) |
171 | + args = expression.OR(( |
172 | + expression.AND(([('contact_type', '=', 'standalone')], args)), |
173 | + [('other_contact_ids', 'in', attached_contact_ids)], |
174 | + )) |
175 | + return super(res_partner, self).search(cr, user, args, offset=offset, limit=limit, |
176 | + order=order, context=context, count=count) |
177 | + |
178 | + def create(self, cr, user, vals, context=None): |
179 | + context = self._basecontact_check_context(cr, user, 'create', context) |
180 | + if not vals.get('name') and vals.get('contact_id'): |
181 | + vals['name'] = self.browse(cr, user, vals['contact_id'], context=context).name |
182 | + return super(res_partner, self).create(cr, user, vals, context=context) |
183 | + |
184 | + def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'): |
185 | + context = self._basecontact_check_context(cr, user, 'read', context) |
186 | + return super(res_partner, self).read(cr, user, ids, fields=fields, context=context, load=load) |
187 | + |
188 | + def write(self, cr, user, ids, vals, context=None): |
189 | + context = self._basecontact_check_context(cr, user, 'write', context) |
190 | + return super(res_partner, self).write(cr, user, ids, vals, context=context) |
191 | + |
192 | + def unlink(self, cr, user, ids, context=None): |
193 | + context = self._basecontact_check_context(cr, user, 'unlink', context) |
194 | + return super(res_partner, self).unlink(cr, user, ids, context=context) |
195 | + |
196 | + def _commercial_partner_compute(self, cr, uid, ids, name, args, context=None): |
197 | + """ Returns the partner that is considered the commercial |
198 | + entity of this partner. The commercial entity holds the master data |
199 | + for all commercial fields (see :py:meth:`~_commercial_fields`) """ |
200 | + result = super(res_partner, self)._commercial_partner_compute(cr, uid, ids, name, args, context=context) |
201 | + for partner in self.browse(cr, uid, ids, context=context): |
202 | + if partner.contact_type == 'attached' and not partner.parent_id: |
203 | + result[partner.id] = partner.contact_id.id |
204 | + return result |
205 | + |
206 | + def _contact_fields(self, cr, uid, context=None): |
207 | + """ Returns the list of contact fields that are synced from the parent |
208 | + when a partner is attached to him. """ |
209 | + return ['name', 'title'] |
210 | + |
211 | + def _contact_sync_from_parent(self, cr, uid, partner, context=None): |
212 | + """ Handle sync of contact fields when a new parent contact entity is set, |
213 | + as if they were related fields """ |
214 | + if partner.contact_id: |
215 | + contact_fields = self._contact_fields(cr, uid, context=context) |
216 | + sync_vals = self._update_fields_values(cr, uid, partner.contact_id, |
217 | + contact_fields, context=context) |
218 | + partner.write(sync_vals) |
219 | + |
220 | + def update_contact(self, cr, uid, ids, vals, context=None): |
221 | + if context is None: |
222 | + context = {} |
223 | + if context.get('__update_contact_lock'): |
224 | + return |
225 | + contact_fields = self._contact_fields(cr, uid, context=context) |
226 | + contact_vals = dict((field, vals[field]) for field in contact_fields if field in vals) |
227 | + if contact_vals: |
228 | + ctx = dict(context, __update_contact_lock=True) |
229 | + self.write(cr, uid, ids, contact_vals, context=ctx) |
230 | + |
231 | + def _fields_sync(self, cr, uid, partner, update_values, context=None): |
232 | + """ Sync commercial fields and address fields from company and to children, |
233 | + contact fields from contact and to attached contact after create/update, |
234 | + just as if those were all modeled as fields.related to the parent """ |
235 | + super(res_partner, self)._fields_sync(cr, uid, partner, update_values, context=context) |
236 | + contact_fields = self._contact_fields(cr, uid, context=context) |
237 | + # 1. From UPSTREAM: sync from parent contact |
238 | + if update_values.get('contact_id'): |
239 | + self._contact_sync_from_parent(cr, uid, partner, context=context) |
240 | + # 2. To DOWNSTREAM: sync contact fields to parent or related |
241 | + elif any(field in contact_fields for field in update_values): |
242 | + update_ids = [c.id for c in partner.other_contact_ids if not c.is_company] |
243 | + if partner.contact_id: |
244 | + update_ids.append(partner.contact_id.id) |
245 | + self.update_contact(cr, uid, update_ids, update_values, context=context) |
246 | + |
247 | + def onchange_contact_id(self, cr, uid, ids, contact_id, context=None): |
248 | + values = {} |
249 | + if contact_id: |
250 | + values['name'] = self.browse(cr, uid, contact_id, context=context).name |
251 | + return {'value': values} |
252 | + |
253 | + def onchange_contact_type(self, cr, uid, ids, contact_type, context=None): |
254 | + values = {} |
255 | + if contact_type == 'standalone': |
256 | + values['contact_id'] = False |
257 | + return {'value': values} |
258 | + |
259 | + |
260 | +class ir_actions_window(orm.Model): |
261 | + _inherit = 'ir.actions.act_window' |
262 | + |
263 | + def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'): |
264 | + action_ids = ids |
265 | + if isinstance(ids, (int, long)): |
266 | + action_ids = [ids] |
267 | + actions = super(ir_actions_window, self).read(cr, user, action_ids, fields=fields, context=context, load=load) |
268 | + for action in actions: |
269 | + if action.get('res_model', '') == 'res.partner': |
270 | + # By default, only show standalone contact |
271 | + action_context = action.get('context', '{}') or '{}' |
272 | + if 'search_show_all_positions' not in action_context: |
273 | + action['context'] = action_context.replace('{', |
274 | + "{'search_show_all_positions': False,", 1) |
275 | + if isinstance(ids, (int, long)): |
276 | + if actions: |
277 | + return actions[0] |
278 | + return False |
279 | + return actions |
280 | |
281 | === added file 'base_contact/base_contact_demo.xml' |
282 | --- base_contact/base_contact_demo.xml 1970-01-01 00:00:00 +0000 |
283 | +++ base_contact/base_contact_demo.xml 2014-03-21 15:26:56 +0000 |
284 | @@ -0,0 +1,29 @@ |
285 | +<?xml version="1.0" encoding="UTF-8"?> |
286 | +<openerp> |
287 | + <data> |
288 | + |
289 | + <record id="res_partner_main2_position_consultant" model="res.partner"> |
290 | + <field name="name">Roger Scott</field> |
291 | + <field name="function">Consultant</field> |
292 | + <field name="parent_id" ref="base.res_partner_11"/> |
293 | + <field name="contact_id" ref="base.res_partner_main2"/> |
294 | + <field name="use_parent_address" eval="True"/> |
295 | + </record> |
296 | + |
297 | + <record id="res_partner_contact1" model="res.partner"> |
298 | + <field name="name">Bob Egnops</field> |
299 | + <field name="birthdate_date">1984-01-01</field> |
300 | + <field name="email">bob@hillenburg-oceaninstitute.com</field> |
301 | + </record> |
302 | + |
303 | + <record id="res_partner_contact1_work_position1" model="res.partner"> |
304 | + <field name="name">Bob Egnops</field> |
305 | + <field name="function">Technician</field> |
306 | + <field name="email">bob@yourcompany.com</field> |
307 | + <field name="parent_id" ref="base.main_partner"/> |
308 | + <field name="contact_id" ref="res_partner_contact1"/> |
309 | + <field name="use_parent_address" eval="True"/> |
310 | + </record> |
311 | + |
312 | + </data> |
313 | +</openerp> |
314 | \ No newline at end of file |
315 | |
316 | === added file 'base_contact/base_contact_view.xml' |
317 | --- base_contact/base_contact_view.xml 1970-01-01 00:00:00 +0000 |
318 | +++ base_contact/base_contact_view.xml 2014-03-21 15:26:56 +0000 |
319 | @@ -0,0 +1,202 @@ |
320 | +<?xml version="1.0" encoding="utf-8"?> |
321 | +<openerp> |
322 | +<data> |
323 | + |
324 | + <record id="view_res_partner_filter_contact" model="ir.ui.view"> |
325 | + <field name="name">res.partner.select.contact</field> |
326 | + <field name="model">res.partner</field> |
327 | + <field name="inherit_id" ref="base.view_res_partner_filter"/> |
328 | + <field name="arch" type="xml"> |
329 | + <filter name="type_company" position="after"> |
330 | + <separator/> |
331 | + <filter string="All positions" name="type_otherpositions" |
332 | + context="{'search_show_all_positions': True}" |
333 | + help="All partner positions"/> |
334 | + </filter> |
335 | + <xpath expr="/search/group/filter[@string='Company']" position="before"> |
336 | + <filter string="Person" name="group_person" context="{'group_by': 'contact_id'}"/> |
337 | + </xpath> |
338 | + </field> |
339 | + </record> |
340 | + |
341 | + <record id="view_res_partner_tree_contact" model="ir.ui.view"> |
342 | + <field name="name">res.partner.tree.contact</field> |
343 | + <field name="model">res.partner</field> |
344 | + <field name="inherit_id" ref="base.view_partner_tree"/> |
345 | + <field name="arch" type="xml"> |
346 | + <field name="parent_id" position="after"> |
347 | + <field name="contact_id" invisible="1"/> |
348 | + </field> |
349 | + </field> |
350 | + </record> |
351 | + |
352 | + <record model="ir.ui.view" id="view_partner_form_inherit"> |
353 | + <field name="name">res.partner.form.contact</field> |
354 | + <field name="model">res.partner</field> |
355 | + <field name="inherit_id" ref="base.view_partner_form"/> |
356 | + <field name="type">form</field> |
357 | + <field name="arch" type="xml"> |
358 | + <field name="is_company" position="after"> |
359 | + <field name="contact_type" invisible="1"/> |
360 | + </field> |
361 | + <page string="Contacts" position="after"> |
362 | + <page string="Other Positions" attrs="{'invisible': ['|',('is_company','=',True),('contact_id','!=',False)]}"> |
363 | + <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"> |
364 | + <kanban> |
365 | + <field name="color"/> |
366 | + <field name="name"/> |
367 | + <field name="title"/> |
368 | + <field name="email"/> |
369 | + <field name="parent_id"/> |
370 | + <field name="is_company"/> |
371 | + <field name="function"/> |
372 | + <field name="phone"/> |
373 | + <field name="street"/> |
374 | + <field name="street2"/> |
375 | + <field name="zip"/> |
376 | + <field name="city"/> |
377 | + <field name="country_id"/> |
378 | + <field name="mobile"/> |
379 | + <field name="fax"/> |
380 | + <field name="state_id"/> |
381 | + <field name="has_image"/> |
382 | + <templates> |
383 | + <t t-name="kanban-box"> |
384 | + <t t-set="color" t-value="kanban_color(record.color.raw_value)"/> |
385 | + <div t-att-class="color + (record.title.raw_value == 1 ? ' oe_kanban_color_alert' : '')" style="position: relative"> |
386 | + <a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a> |
387 | + <div class="oe_module_vignette"> |
388 | + <a type="open"> |
389 | + <t t-if="record.has_image.raw_value === true"> |
390 | + <img t-att-src="kanban_image('res.partner', 'image', record.id.value, {'preview_image': 'image_small'})" class="oe_avatar oe_kanban_avatar_smallbox"/> |
391 | + </t> |
392 | + <t t-if="record.image and record.image.raw_value !== false"> |
393 | + <img t-att-src="'data:image/png;base64,'+record.image.raw_value" class="oe_avatar oe_kanban_avatar_smallbox"/> |
394 | + </t> |
395 | + <t t-if="record.has_image.raw_value === false and (!record.image or record.image.raw_value === false)"> |
396 | + <t t-if="record.is_company.raw_value === true"> |
397 | + <img t-att-src='_s + "/base/static/src/img/company_image.png"' class="oe_kanban_image oe_kanban_avatar_smallbox"/> |
398 | + </t> |
399 | + <t t-if="record.is_company.raw_value === false"> |
400 | + <img t-att-src='_s + "/base/static/src/img/avatar.png"' class="oe_kanban_image oe_kanban_avatar_smallbox"/> |
401 | + </t> |
402 | + </t> |
403 | + </a> |
404 | + <div class="oe_module_desc"> |
405 | + <div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_color_border"> |
406 | + <table class="oe_kanban_table"> |
407 | + <tr> |
408 | + <td class="oe_kanban_title1" align="left" valign="middle"> |
409 | + <h4><a type="open"><field name="name"/></a></h4> |
410 | + <i> |
411 | + <t t-if="record.parent_id.raw_value and !record.function.raw_value"><field name="parent_id"/></t> |
412 | + <t t-if="!record.parent_id.raw_value and record.function.raw_value"><field name="function"/></t> |
413 | + <t t-if="record.parent_id.raw_value and record.function.raw_value"><field name="function"/> at <field name="parent_id"/></t> |
414 | + </i> |
415 | + <div><a t-if="record.email.raw_value" title="Mail" t-att-href="'mailto:'+record.email.value"> |
416 | + <field name="email"/> |
417 | + </a></div> |
418 | + <div t-if="record.phone.raw_value">Phone: <field name="phone"/></div> |
419 | + <div t-if="record.mobile.raw_value">Mobile: <field name="mobile"/></div> |
420 | + <div t-if="record.fax.raw_value">Fax: <field name="fax"/></div> |
421 | + </td> |
422 | + </tr> |
423 | + </table> |
424 | + </div> |
425 | + </div> |
426 | + </div> |
427 | + </div> |
428 | + </t> |
429 | + </templates> |
430 | + </kanban> |
431 | + <form string="Contact" version="7.0"> |
432 | + <sheet> |
433 | + <field name="image" widget='image' class="oe_avatar oe_left" options='{"preview_image": "image_medium"}'/> |
434 | + <div class="oe_title"> |
435 | + <label for="name" class="oe_edit_only"/> |
436 | + <h1><field name="name" style="width: 70%%"/></h1> |
437 | + </div> |
438 | + <group> |
439 | + <!-- inherited part --> |
440 | + <field name="category_id" widget="many2many_tags" placeholder="Tags..." style="width: 70%%"/> |
441 | + <field name="parent_id" placeholder="Company" domain="[('is_company','=',True)]"/> |
442 | + <!-- inherited part end --> |
443 | + <field name="function" placeholder="e.g. Sales Director"/> |
444 | + <field name="email"/> |
445 | + <field name="phone"/> |
446 | + <field name="mobile"/> |
447 | + </group> |
448 | + <div> |
449 | + <field name="use_parent_address"/><label for="use_parent_address"/> |
450 | + </div> |
451 | + <group> |
452 | + <label for="type"/> |
453 | + <div name="div_type"> |
454 | + <field class="oe_inline" name="type"/> |
455 | + </div> |
456 | + <label for="street" string="Address" attrs="{'invisible': [('use_parent_address','=', True)]}"/> |
457 | + <div attrs="{'invisible': [('use_parent_address','=', True)]}" name="div_address"> |
458 | + <field name="street" placeholder="Street..."/> |
459 | + <field name="street2"/> |
460 | + <div class="address_format"> |
461 | + <field name="city" placeholder="City" style="width: 40%%"/> |
462 | + <field name="state_id" class="oe_no_button" placeholder="State" style="width: 37%%" options='{"no_open": True}' on_change="onchange_state(state_id)"/> |
463 | + <field name="zip" placeholder="ZIP" style="width: 20%%"/> |
464 | + </div> |
465 | + <field name="country_id" placeholder="Country" class="oe_no_button" options='{"no_open": True}'/> |
466 | + </div> |
467 | + </group> |
468 | + <field name="supplier" invisible="True"/> |
469 | + </sheet> |
470 | + </form> |
471 | + </field> |
472 | + </page> |
473 | + <page name="personal-info" string="Personal Information" attrs="{'invisible': ['|',('is_company','=',True)]}"> |
474 | + <p attrs="{'invisible': [('contact_id','=',False)]}"> |
475 | + 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}" |
476 | + on_change="onchange_contact_id(contact_id)" options="{'always_reload': True}"/> |
477 | + </p> |
478 | + <group attrs="{'invisible': [('contact_id','!=',False)]}"> |
479 | + <field name="birthdate_date"/> |
480 | + <field name="nationality_id"/> |
481 | + </group> |
482 | + </page> |
483 | + </page> |
484 | + <xpath expr="//field[@name='child_ids']/form//field[@name='name']/.." position="before"> |
485 | + <field name="contact_type" readonly="0" on_change="onchange_contact_type(contact_type)"/> |
486 | + </xpath> |
487 | + <xpath expr="//field[@name='child_ids']/form//field[@name='name']" position="after"> |
488 | + <field name="contact_id" on_change="onchange_contact_id(contact_id)" string="Contact" |
489 | + attrs="{'invisible': [('contact_type','!=','attached')], 'required': [('contact_type','=','attached')]}"/> |
490 | + </xpath> |
491 | + <xpath expr="//field[@name='child_ids']/form//field[@name='name']" position="attributes"> |
492 | + <attribute name="attrs">{'invisible': [('contact_type','=','attached')]}</attribute> |
493 | + </xpath> |
494 | + </field> |
495 | + </record> |
496 | + |
497 | + <record model="ir.ui.view" id="view_res_partner_kanban_contact"> |
498 | + <field name="name">res.partner.kanban.contact</field> |
499 | + <field name="model">res.partner</field> |
500 | + <field name="inherit_id" ref="base.res_partner_kanban_view"/> |
501 | + <field name="arch" type="xml"> |
502 | + <field name="is_company" position="after"> |
503 | + <field name="other_contact_ids"> |
504 | + <tree> |
505 | + <field name="parent_id"/> |
506 | + <field name="function"/> |
507 | + </tree> |
508 | + </field> |
509 | + </field> |
510 | + <xpath expr="//t[@t-name='kanban-box']//div[@class='oe_kanban_details']/ul/li[3]" position="after"> |
511 | + <t t-if="record.other_contact_ids.raw_value.length > 0"> |
512 | + <li>+<t t-esc="record.other_contact_ids.raw_value.length"/> |
513 | + <t t-if="record.other_contact_ids.raw_value.length == 1">other position</t> |
514 | + <t t-if="record.other_contact_ids.raw_value.length > 1">other positions</t></li> |
515 | + </t> |
516 | + </xpath> |
517 | + </field> |
518 | + </record> |
519 | + |
520 | +</data> |
521 | +</openerp> |
522 | |
523 | === added directory 'base_contact/tests' |
524 | === added file 'base_contact/tests/__init__.py' |
525 | --- base_contact/tests/__init__.py 1970-01-01 00:00:00 +0000 |
526 | +++ base_contact/tests/__init__.py 2014-03-21 15:26:56 +0000 |
527 | @@ -0,0 +1,26 @@ |
528 | +# -*- coding: utf-8 ⁻*- |
529 | +############################################################################## |
530 | +# |
531 | +# OpenERP, Open Source Business Applications |
532 | +# Copyright (C) 2013-TODAY OpenERP S.A. (<http://openerp.com>). |
533 | +# |
534 | +# This program is free software: you can redistribute it and/or modify |
535 | +# it under the terms of the GNU Affero General Public License as |
536 | +# published by the Free Software Foundation, either version 3 of the |
537 | +# License, or (at your option) any later version. |
538 | +# |
539 | +# This program is distributed in the hope that it will be useful, |
540 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
541 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
542 | +# GNU Affero General Public License for more details. |
543 | +# |
544 | +# You should have received a copy of the GNU Affero General Public License |
545 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
546 | +# |
547 | +############################################################################## |
548 | + |
549 | +from . import test_base_contact |
550 | + |
551 | +checks = [ |
552 | + test_base_contact, |
553 | +] |
554 | |
555 | === added file 'base_contact/tests/test_base_contact.py' |
556 | --- base_contact/tests/test_base_contact.py 1970-01-01 00:00:00 +0000 |
557 | +++ base_contact/tests/test_base_contact.py 2014-03-21 15:26:56 +0000 |
558 | @@ -0,0 +1,136 @@ |
559 | +# -*- coding: utf-8 ⁻*- |
560 | +############################################################################## |
561 | +# |
562 | +# OpenERP, Open Source Business Applications |
563 | +# Copyright (C) 2013-TODAY OpenERP S.A. (<http://openerp.com>). |
564 | +# |
565 | +# This program is free software: you can redistribute it and/or modify |
566 | +# it under the terms of the GNU Affero General Public License as |
567 | +# published by the Free Software Foundation, either version 3 of the |
568 | +# License, or (at your option) any later version. |
569 | +# |
570 | +# This program is distributed in the hope that it will be useful, |
571 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
572 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
573 | +# GNU Affero General Public License for more details. |
574 | +# |
575 | +# You should have received a copy of the GNU Affero General Public License |
576 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
577 | +# |
578 | +############################################################################## |
579 | + |
580 | +from openerp.tests import common |
581 | + |
582 | + |
583 | +class Test_Base_Contact(common.TransactionCase): |
584 | + |
585 | + def setUp(self): |
586 | + """*****setUp*****""" |
587 | + super(Test_Base_Contact, self).setUp() |
588 | + cr, uid = self.cr, self.uid |
589 | + ModelData = self.registry('ir.model.data') |
590 | + self.partner = self.registry('res.partner') |
591 | + |
592 | + # Get test records reference |
593 | + for attr, module, name in [ |
594 | + ('main_partner_id', 'base', 'main_partner'), |
595 | + ('bob_contact_id', 'base_contact', 'res_partner_contact1'), |
596 | + ('bob_job1_id', 'base_contact', 'res_partner_contact1_work_position1'), |
597 | + ('roger_contact_id', 'base', 'res_partner_main2'), |
598 | + ('roger_job2_id', 'base_contact', 'res_partner_main2_position_consultant')]: |
599 | + r = ModelData.get_object_reference(cr, uid, module, name) |
600 | + setattr(self, attr, r[1] if r else False) |
601 | + |
602 | + def test_00_show_only_standalone_contact(self): |
603 | + """Check that only standalone contact are shown if context explicitly state to not display all positions""" |
604 | + cr, uid = self.cr, self.uid |
605 | + ctx = {'search_show_all_positions': False} |
606 | + partner_ids = self.partner.search(cr, uid, [], context=ctx) |
607 | + partner_ids.sort() |
608 | + self.assertTrue(self.bob_job1_id not in partner_ids) |
609 | + self.assertTrue(self.roger_job2_id not in partner_ids) |
610 | + |
611 | + def test_01_show_all_positions(self): |
612 | + """Check that all contact are show if context is empty or explicitly state to display all positions""" |
613 | + cr, uid = self.cr, self.uid |
614 | + |
615 | + partner_ids = self.partner.search(cr, uid, [], context=None) |
616 | + self.assertTrue(self.bob_job1_id in partner_ids) |
617 | + self.assertTrue(self.roger_job2_id in partner_ids) |
618 | + |
619 | + ctx = {'search_show_all_positions': True} |
620 | + partner_ids = self.partner.search(cr, uid, [], context=ctx) |
621 | + self.assertTrue(self.bob_job1_id in partner_ids) |
622 | + self.assertTrue(self.roger_job2_id in partner_ids) |
623 | + |
624 | + def test_02_reading_other_contact_one2many_show_all_positions(self): |
625 | + """Check that readonly partner's ``other_contact_ids`` return all values whatever the context""" |
626 | + cr, uid = self.cr, self.uid |
627 | + |
628 | + def read_other_contacts(pid, context=None): |
629 | + return self.partner.read(cr, uid, [pid], ['other_contact_ids'], context=context)[0]['other_contact_ids'] |
630 | + |
631 | + def read_contacts(pid, context=None): |
632 | + return self.partner.read(cr, uid, [pid], ['child_ids'], context=context)[0]['child_ids'] |
633 | + |
634 | + ctx = None |
635 | + self.assertEqual(read_other_contacts(self.bob_contact_id, context=ctx), [self.bob_job1_id]) |
636 | + ctx = {'search_show_all_positions': False} |
637 | + self.assertEqual(read_other_contacts(self.bob_contact_id, context=ctx), [self.bob_job1_id]) |
638 | + ctx = {'search_show_all_positions': True} |
639 | + self.assertEqual(read_other_contacts(self.bob_contact_id, context=ctx), [self.bob_job1_id]) |
640 | + |
641 | + ctx = None |
642 | + self.assertTrue(self.bob_job1_id in read_contacts(self.main_partner_id, context=ctx)) |
643 | + ctx = {'search_show_all_positions': False} |
644 | + self.assertTrue(self.bob_job1_id in read_contacts(self.main_partner_id, context=ctx)) |
645 | + ctx = {'search_show_all_positions': True} |
646 | + self.assertTrue(self.bob_job1_id in read_contacts(self.main_partner_id, context=ctx)) |
647 | + |
648 | + def test_03_search_match_attached_contacts(self): |
649 | + """Check that searching partner also return partners having attached contacts matching search criteria""" |
650 | + cr, uid = self.cr, self.uid |
651 | + # Bob's contact has one other position which is related to 'Your Company' |
652 | + # so search for all contacts working for 'Your Company' should contain bob position. |
653 | + partner_ids = self.partner.search(cr, uid, [('parent_id', 'ilike', 'Your Company')], context=None) |
654 | + self.assertTrue(self.bob_job1_id in partner_ids) |
655 | + |
656 | + # but when searching without 'all positions', we should get the position standalone contact instead. |
657 | + ctx = {'search_show_all_positions': False} |
658 | + partner_ids = self.partner.search(cr, uid, [('parent_id', 'ilike', 'Your Company')], context=ctx) |
659 | + self.assertTrue(self.bob_contact_id in partner_ids) |
660 | + |
661 | + def test_04_contact_creation(self): |
662 | + """Check that we're begin to create a contact""" |
663 | + cr, uid = self.cr, self.uid |
664 | + |
665 | + # Create a contact using only name |
666 | + new_contact_id = self.partner.create(cr, uid, {'name': 'Bob Egnops'}) |
667 | + self.assertEqual(self.partner.browse(cr, uid, new_contact_id).contact_type, 'standalone') |
668 | + |
669 | + # Create a contact with only contact_id |
670 | + new_contact_id = self.partner.create(cr, uid, {'contact_id': self.bob_contact_id}) |
671 | + new_contact = self.partner.browse(cr, uid, new_contact_id) |
672 | + self.assertEqual(new_contact.name, 'Bob Egnops') |
673 | + self.assertEqual(new_contact.contact_type, 'attached') |
674 | + |
675 | + # Create a contact with both contact_id and name; |
676 | + # contact's name should override provided value in that case |
677 | + new_contact_id = self.partner.create(cr, uid, {'contact_id': self.bob_contact_id, 'name': 'Rob Egnops'}) |
678 | + self.assertEqual(self.partner.browse(cr, uid, new_contact_id).name, 'Bob Egnops') |
679 | + |
680 | + # Reset contact to standalone |
681 | + self.partner.write(cr, uid, [new_contact_id], {'contact_id': False}) |
682 | + self.assertEqual(self.partner.browse(cr, uid, new_contact_id).contact_type, 'standalone') |
683 | + |
684 | + def test_05_contact_fields_sync(self): |
685 | + """Check that contact's fields are correctly synced between parent contact or related contacts""" |
686 | + cr, uid = self.cr, self.uid |
687 | + |
688 | + # Test DOWNSTREAM sync |
689 | + self.partner.write(cr, uid, [self.bob_contact_id], {'name': 'Rob Egnops'}) |
690 | + self.assertEqual(self.partner.browse(cr, uid, self.bob_job1_id).name, 'Rob Egnops') |
691 | + |
692 | + # Test UPSTREAM sync |
693 | + self.partner.write(cr, uid, [self.bob_job1_id], {'name': 'Bob Egnops'}) |
694 | + self.assertEqual(self.partner.browse(cr, uid, self.bob_contact_id).name, 'Bob Egnops') |
LGTM