diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-04-05 18:46:10 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-04-05 18:46:10 +0200 |
| commit | 3f95b074e959f0f4ac93fbf4048c0c7276f2c17a (patch) | |
| tree | f4e793da5d83bcd1bd4347f71cb3f45cbc953df3 /odoo/addons | |
| parent | 1a871d72438c731d08ef10e668b81b4ade900b85 (diff) | |
| parent | f2be461f0906ed47306da58c31240014b6239d80 (diff) | |
[MERGE] forward port branch saas-12.2 up to f2be461f090
Diffstat (limited to 'odoo/addons')
| -rw-r--r-- | odoo/addons/base/models/res_bank.py | 4 | ||||
| -rw-r--r-- | odoo/addons/base/models/res_partner.py | 103 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_base.py | 35 | ||||
| -rw-r--r-- | odoo/addons/base/views/res_bank_views.xml | 3 | ||||
| -rw-r--r-- | odoo/addons/test_testing_utilities/ir.model.access.csv | 3 | ||||
| -rw-r--r-- | odoo/addons/test_testing_utilities/menu.xml | 13 | ||||
| -rw-r--r-- | odoo/addons/test_testing_utilities/models.py | 28 | ||||
| -rw-r--r-- | odoo/addons/test_testing_utilities/tests/test_form_impl.py | 24 |
8 files changed, 192 insertions, 21 deletions
diff --git a/odoo/addons/base/models/res_bank.py b/odoo/addons/base/models/res_bank.py index a4e75a2532c..d9a0433ecc2 100644 --- a/odoo/addons/base/models/res_bank.py +++ b/odoo/addons/base/models/res_bank.py @@ -66,7 +66,7 @@ class ResPartnerBank(models.Model): _name = 'res.partner.bank' _rec_name = 'acc_number' _description = 'Bank Accounts' - _order = 'sequence' + _order = 'sequence, id' @api.model def get_supported_account_types(self): @@ -84,7 +84,7 @@ class ResPartnerBank(models.Model): bank_id = fields.Many2one('res.bank', string='Bank') bank_name = fields.Char(related='bank_id.name', readonly=False) bank_bic = fields.Char(related='bank_id.bic', readonly=False) - sequence = fields.Integer() + sequence = fields.Integer(default=10) currency_id = fields.Many2one('res.currency', string='Currency') company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.user.company_id, ondelete='cascade') qr_code_valid = fields.Boolean(string="Has all required arguments", compute="_validate_qr_code_arguments") diff --git a/odoo/addons/base/models/res_partner.py b/odoo/addons/base/models/res_partner.py index 603fbd6c4d2..49979b9e2c5 100644 --- a/odoo/addons/base/models/res_partner.py +++ b/odoo/addons/base/models/res_partner.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import base64 +import collections import datetime import hashlib import pytz @@ -286,8 +287,32 @@ class Partner(models.Model): @api.depends('is_company', 'parent_id.commercial_partner_id') def _compute_commercial_partner(self): + self.env.cr.execute(""" + WITH RECURSIVE cpid(id, parent_id, commercial_partner_id, final) AS ( + SELECT + id, parent_id, id, + (coalesce(is_company, false) OR parent_id IS NULL) as final + FROM res_partner + WHERE id = ANY(%s) + UNION + SELECT + cpid.id, p.parent_id, p.id, + (coalesce(is_company, false) OR p.parent_id IS NULL) as final + FROM res_partner p + JOIN cpid ON (cpid.parent_id = p.id) + WHERE NOT cpid.final + ) + SELECT cpid.id, cpid.commercial_partner_id + FROM cpid + WHERE final AND id = ANY(%s); + """, [self.ids, self.ids]) + + d = dict(self.env.cr.fetchall()) for partner in self: - if partner.is_company or not partner.parent_id: + fetched = d.get(partner.id) + if fetched is not None: + partner.commercial_partner_id = fetched + elif partner.is_company or not partner.parent_id: partner.commercial_partner_id = partner else: partner.commercial_partner_id = partner.parent_id.commercial_partner_id @@ -483,21 +508,25 @@ class Partner(models.Model): self.update_address(onchange_vals) # 2. To DOWNSTREAM: sync children - if self.child_ids: - # 2a. Commercial Fields: sync if commercial entity - if self.commercial_partner_id == self: - commercial_fields = self._commercial_fields() - if any(field in values for field in commercial_fields): - self._commercial_sync_to_children() - for child in self.child_ids.filtered(lambda c: not c.is_company): - if child.commercial_partner_id != self.commercial_partner_id : - self._commercial_sync_to_children() - break - # 2b. Address fields: sync if address changed - address_fields = self._address_fields() - if any(field in values for field in address_fields): - contacts = self.child_ids.filtered(lambda c: c.type == 'contact') - contacts.update_address(values) + self._children_sync(values) + + def _children_sync(self, values): + if not self.child_ids: + return + # 2a. Commercial Fields: sync if commercial entity + if self.commercial_partner_id == self: + commercial_fields = self._commercial_fields() + if any(field in values for field in commercial_fields): + self._commercial_sync_to_children() + for child in self.child_ids.filtered(lambda c: not c.is_company): + if child.commercial_partner_id != self.commercial_partner_id: + self._commercial_sync_to_children() + break + # 2b. Address fields: sync if address changed + address_fields = self._address_fields() + if any(field in values for field in address_fields): + contacts = self.child_ids.filtered(lambda c: c.type == 'contact') + contacts.update_address(values) @api.multi def _handle_first_contact_creation(self): @@ -574,11 +603,53 @@ class Partner(models.Model): tools.image_resize_images(vals, return_big=False) tools.image_resize_images(vals, return_medium=False, return_small=False, preserve_aspect_ratio=True) partners = super(Partner, self).create(vals_list) + + if self.env.context.get('_partners_skip_fields_sync'): + return partners + for partner, vals in zip(partners, vals_list): partner._fields_sync(vals) partner._handle_first_contact_creation() return partners + def _load_records_create(self, vals_list): + partners = super(Partner, self.with_context(_partners_skip_fields_sync=True))._load_records_create(vals_list) + + # batch up first part of _fields_sync + # group partners by commercial_partner_id (if not self) and parent_id (if type == contact) + groups = collections.defaultdict(list) + for partner, vals in zip(partners, vals_list): + cp_id = None + if vals.get('parent_id') and partner.commercial_partner_id != partner: + cp_id = partner.commercial_partner_id.id + + add_id = None + if partner.parent_id and partner.type == 'contact': + add_id = partner.parent_id.id + groups[(cp_id, add_id)].append(partner.id) + + for (cp_id, add_id), children in groups.items(): + # values from parents (commercial, regular) written to their common children + to_write = {} + # commercial fields from commercial partner + if cp_id: + to_write = self.browse(cp_id)._update_fields_values(self._commercial_fields()) + # address fields from parent + if add_id: + parent = self.browse(add_id) + for f in self._address_fields(): + v = parent[f] + if v: + to_write[f] = v.id if isinstance(v, models.BaseModel) else v + if to_write: + self.browse(children).write(to_write) + + # do the second half of _fields_sync the "normal" way + for partner, vals in zip(partners, vals_list): + partner._children_sync(vals) + partner._handle_first_contact_creation() + return partners + @api.multi def create_company(self): self.ensure_one() diff --git a/odoo/addons/base/tests/test_base.py b/odoo/addons/base/tests/test_base.py index 5e1a24958bf..a823c65dc31 100644 --- a/odoo/addons/base/tests/test_base.py +++ b/odoo/addons/base/tests/test_base.py @@ -252,6 +252,41 @@ class TestBase(TransactionCase): self.assertEqual(leaf111.address_get([]), {'contact': branch11.id}, 'Invalid address resolution, branch11 should now be contact') + def test_commercial_partner_nullcompany(self): + """ The commercial partner is the first/nearest ancestor-or-self which + is a company or doesn't have a parent + """ + P = self.env['res.partner'] + p0 = P.create({'name': '0', 'email': '0'}) + self.assertEqual(p0.commercial_partner_id, p0, "partner without a parent is their own commercial partner") + + p1 = P.create({'name': '1', 'email': '1', 'parent_id': p0.id}) + self.assertEqual(p1.commercial_partner_id, p0, "partner's parent is their commercial partner") + p12 = P.create({'name': '12', 'email': '12', 'parent_id': p1.id}) + self.assertEqual(p12.commercial_partner_id, p0, "partner's GP is their commercial partner") + + p2 = P.create({'name': '2', 'email': '2', 'parent_id': p0.id, 'is_company': True}) + self.assertEqual(p2.commercial_partner_id, p2, "partner flagged as company is their own commercial partner") + p21 = P.create({'name': '21', 'email': '21', 'parent_id': p2.id}) + self.assertEqual(p21.commercial_partner_id, p2, "commercial partner is closest ancestor with themselves as commercial partner") + + p3 = P.create({'name': '3', 'email': '3', 'is_company': True}) + self.assertEqual(p3.commercial_partner_id, p3, "being both parent-less and company should be the same as either") + + notcompanies = p0 | p1 | p12 | p21 + self.env.cr.execute('update res_partner set is_company=null where id = any(%s)', [notcompanies.ids]) + for parent in notcompanies: + p = P.create({ + 'name': parent.name + '_sub', + 'email': parent.email + '_sub', + 'parent_id': parent.id, + }) + self.assertEqual( + p.commercial_partner_id, + parent.commercial_partner_id, + "check that is_company=null is properly handled when looking for ancestor" + ) + def test_50_res_partner_commercial_sync(self): res_partner = self.env['res.partner'] p0 = res_partner.create({'name': 'Sigurd Sunknife', diff --git a/odoo/addons/base/views/res_bank_views.xml b/odoo/addons/base/views/res_bank_views.xml index b83bcb46222..dcec863abfa 100644 --- a/odoo/addons/base/views/res_bank_views.xml +++ b/odoo/addons/base/views/res_bank_views.xml @@ -69,6 +69,7 @@ <form string="Bank account"> <group> <group> + <field name="sequence" invisible="1"/> <field name="acc_number"/> <field name="acc_type"/> <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> @@ -89,7 +90,7 @@ <field name="model">res.partner.bank</field> <field name="arch" type="xml"> <tree string="Bank Accounts"> - <field name="sequence" invisible="1"/> + <field name="sequence" widget="handle"/> <field name="acc_number"/> <field name="bank_name"/> <field name="company_id" groups="base.group_multi_company"/> diff --git a/odoo/addons/test_testing_utilities/ir.model.access.csv b/odoo/addons/test_testing_utilities/ir.model.access.csv index c069b568ad4..c43b736c531 100644 --- a/odoo/addons/test_testing_utilities/ir.model.access.csv +++ b/odoo/addons/test_testing_utilities/ir.model.access.csv @@ -12,7 +12,10 @@ access_test_testing_utilities_parent,access_test_testing_utilities_parent,model_ access_test_testing_utilities_sub,access_test_testing_utilities_sub,model_test_testing_utilities_sub,,1,0,0,0 access_test_testing_utilities_sub2,access_test_testing_utilities_sub2,model_test_testing_utilities_sub2,,1,0,0,0 access_test_testing_utilities_sub3,access_test_testing_utilities_sub3,model_test_testing_utilities_sub3,,1,0,0,0 +access_test_testing_utilities_recursive,access_test_testing_utilities_recursive,model_test_testing_utilities_recursive,,1,0,0,0 access_test_testing_utilities_onchange_parent,access_test_testing_utilities_onchange_parent,model_test_testing_utilities_onchange_parent,,1,0,0,0 access_test_testing_utilities_onchange_line,access_test_testing_utilities_onchange_line,model_test_testing_utilities_onchange_line,,1,0,0,0 access_model_test_testing_utilities_onchange_count,access_model_test_testing_utilities_onchange_count,model_test_testing_utilities_onchange_count,,1,1,1,1 access_test_testing_utilities_onchange_count_sub,access_test_testing_utilities_onchange_count_sub,model_test_testing_utilities_onchange_count_sub,,1,1,1,1 +access_o2m_readonly_subfield_parent,access_o2m_readonly_subfield_parent,model_o2m_readonly_subfield_parent,,1,1,1,1 +access_o2m_readonly_subfield_child,access_o2m_readonly_subfield_child,model_o2m_readonly_subfield_child,,1,1,1,1 diff --git a/odoo/addons/test_testing_utilities/menu.xml b/odoo/addons/test_testing_utilities/menu.xml index fa5f4f37163..491a74dda46 100644 --- a/odoo/addons/test_testing_utilities/menu.xml +++ b/odoo/addons/test_testing_utilities/menu.xml @@ -151,4 +151,17 @@ </form> </field> </record> + + <record id="o2m_recursive_relation_view" model="ir.ui.view"> + <field name="name">Recursive Relation Test View</field> + <field name="model">test_testing_utilities.recursive</field> + <field name="arch" type="xml"> + <form> + <sheet> + <field name="one_to_many_id"/> + <field name="many_to_one_ids"/> + </sheet> + </form> + </field> + </record> </odoo> diff --git a/odoo/addons/test_testing_utilities/models.py b/odoo/addons/test_testing_utilities/models.py index fbdec47a7cf..0c04bc06e8d 100644 --- a/odoo/addons/test_testing_utilities/models.py +++ b/odoo/addons/test_testing_utilities/models.py @@ -188,6 +188,13 @@ class O2MSub3(models.Model): r.name = str(r.v) +class O2MRecursive(models.Model): + _name = _description = 'test_testing_utilities.recursive' + + one_to_many_id = fields.Many2one('test_testing_utilities.recursive', readonly=True) + many_to_one_ids = fields.One2many('test_testing_utilities.recursive', 'one_to_many_id', readonly=True) + + class O2MOnchangeParent(models.Model): _name = 'test_testing_utilities.onchange_parent' _description = 'Testing Utilities Onchange Parent' @@ -233,3 +240,24 @@ class O2MChangeSub(models.Model): parent = fields.Many2one('test_testing_utilities.onchange_count') name = fields.Char() + +class O2MReadonlySubfield(models.Model): + _name = 'o2m_readonly_subfield_parent' + _description = _name + + line_ids = fields.One2many('o2m_readonly_subfield_child', 'parent_id') + +class O2MReadonlySubfieldChild(models.Model): + _name = _description = 'o2m_readonly_subfield_child' + + name = fields.Char() + parent_id = fields.Many2one('o2m_readonly_subfield_parent') + f = fields.Integer(compute='_compute_f', inverse='_inverse_f', readonly=True) + + @api.depends('name') + def _compute_f(self): + for r in self: + r.f = len(r.name) if r.name else 0 + + def _inverse_f(self): + raise AssertionError("Inverse of f should not be called") diff --git a/odoo/addons/test_testing_utilities/tests/test_form_impl.py b/odoo/addons/test_testing_utilities/tests/test_form_impl.py index c13dbd73a60..0191a4da7ad 100644 --- a/odoo/addons/test_testing_utilities/tests/test_form_impl.py +++ b/odoo/addons/test_testing_utilities/tests/test_form_impl.py @@ -359,7 +359,7 @@ class TestO2M(TransactionCase): self.assertEqual( [get(s) for s in r.subs], - [("5", 2, 5)] + [("5", 0, 5)] ) def test_o2m_inner_default(self): @@ -409,7 +409,10 @@ class TestO2M(TransactionCase): self.assertEqual(sub.value, 1) self.assertEqual(sub.v, 1) - def test_m2o_readonly(self): + def test_readonly_o2m(self): + """ Tests that o2m fields flagged as readonly (readonly="1" in the + view) can't be written to + """ r = self.env['test_testing_utilities.parent'].create({ 'subs': [(0, 0, {})] }) @@ -422,6 +425,20 @@ class TestO2M(TransactionCase): with self.assertRaises(AssertionError): f.subs.remove(index=0) + def test_o2m_readonly_subfield(self): + """ Tests that readonly is applied to the field of the o2m = not sent + as part of the create / write values + """ + f = Form(self.env['o2m_readonly_subfield_parent']) + with f.line_ids.new() as new_line: + new_line.name = "ok" + self.assertEqual(new_line.f, 2) + r = f.save() + self.assertEqual( + (r.line_ids.name, r.line_ids.f), + ('ok', 2) + ) + def test_o2m_dyn_onchange(self): f = Form(self.env['test_testing_utilities.onchange_parent'], view='test_testing_utilities.m2o_onchange_view') @@ -467,6 +484,9 @@ class TestO2M(TransactionCase): self.assertEqual(commands(), [0, 2, 2], "should contain 1 '0' command and 2 deletions") self.assertEqual(len(r.line_ids), 1) + def test_o2m_self_recursive(self): + Form(self.env['test_testing_utilities.recursive'], view='test_testing_utilities.o2m_recursive_relation_view') + class TestEdition(TransactionCase): """ These use the context manager form as we don't need the record post-save (we already have it) and it's easier to see what bits act on |
