# HG changeset patch
# User Etienne Ferriere <etienne.ferriere@xcg-consulting.fr>
# Date 1528219539 -7200
#      Tue Jun 05 19:25:39 2018 +0200
# Branch 10.0
# Node ID 5907004843f9d4672966c582f4ac04a781b405f2
# Parent  0d9c5e6020e13eb9cf0c395fb3963b6f99005ba0
Port to Odoo 10 the "MetaAnalytic" metaclass.

diff --git a/MetaAnalytic.py b/MetaAnalytic.py
--- a/MetaAnalytic.py
+++ b/MetaAnalytic.py
@@ -1,6 +1,7 @@
 from odoo import SUPERUSER_ID
 from odoo import api
 from odoo import fields
+from odoo import models
 from odoo.osv import orm
 from odoo.tools import config
 from odoo.tools import frozendict
@@ -239,22 +240,29 @@
         if ref_id is None:
             ref_id = orm_name.replace('.', '_') + "_analytic_dimension_id"
 
+        code_name = dimension.get('code_name', 'name')
+        code_description = dimension.get('code_description', 'description')
+
         # To use an inherited, renamed parent field, you have to give its name.
         sync_parent = dimension.get('sync_parent', False)
         if sync_parent is True:
             sync_parent = nmspc.get('_parent_name', 'parent_id')
 
+        parent_column = dimension.get('parent_column', column)
+
+        child_class = dimension.get('child_class', False)
+
         rel_name = dimension.get('rel_name', tuple())
         if rel_name is True:
             rel_name = u"Name"
         if isinstance(rel_name, basestring):
-            rel_name = (rel_name, 'name')
+            rel_name = (rel_name, code_name)
 
         rel_description = dimension.get('rel_description', tuple())
         if rel_description is True:
             rel_description = u"Description"
         if isinstance(rel_description, basestring):
-            rel_description = (rel_description, 'description')
+            rel_description = (rel_description, code_description)
 
         rel_active = dimension.get('rel_active', tuple())
         if rel_active is True:
@@ -322,11 +330,11 @@
 
         if rel_cols:
             # NOT a method nor a class member. 'self' is the analytic_code OSV.
-            def _record_from_code_id(self, cr, uid, ids, context=None):
+            def _record_from_code_id(self):
                 """Get the entries to update from the modified codes."""
-                osv = self.pool.get(orm_name)
-                domain = [(column, 'in', ids)]
-                return osv.search(cr, uid, domain, context=context)
+                obj = self.env.get(orm_name)
+                domain = [(column, 'in', self.ids)]
+                return obj.search(domain)
 
             for string, model_col, code_col, dtype, req, default in rel_cols:
                 nmspc[model_col] = getattr(fields, dtype)(
@@ -354,20 +362,22 @@
         # We don't want that cause we set _bound_dimension_id.
         # Keep the old api until we fix all this module.
         @AddMethod(superclass)
-        def _setup_complete(self, cr, ids):
+        def _setup_complete(self):
             """Load or create the analytic dimension bound to the model."""
 
-            super(superclass, self)._setup_complete(cr, ids)
+            super(superclass, self)._setup_complete()
 
-            data_osv = self.pool['ir.model.data']
+            data_obj = self.env['ir.model.data'].sudo()
             try:
-                self._bound_dimension_id = data_osv.get_object_reference(
-                    cr, SUPERUSER_ID, ref_module, ref_id
+                self._bound_dimension_id = data_obj.get_object_reference(
+                    ref_module, ref_id
                 )[1]
             except ValueError:
-                vals = {'name': dimension_name, 'validated': True}
-                self._bound_dimension_id = data_osv._update(
-                    cr, SUPERUSER_ID, 'analytic.dimension', ref_module, vals,
+                vals = {
+                    'name': dimension_name, 'validated': True,
+                }
+                self._bound_dimension_id = data_obj._update(
+                    'analytic.dimension', ref_module, vals,
                     xml_id=ref_id, noupdate=True
                 )
 
@@ -376,11 +386,9 @@
 
             # This function is called as a method and can be overridden.
             @AddMethod(superclass)
-            def _generate_code_ref_id(self, cr, uid, ids, context=None):
-                data_osv = self.pool['ir.model.data']
-                records = self.browse(cr, uid, ids, context=None)
-                if not isinstance(records, list):
-                    records = [records]
+            def _generate_code_ref_id(self, context=None):
+                data_obj = self.env['ir.model.data']
+                records = self
 
                 for record in records:
                     code = record[column]
@@ -397,39 +405,32 @@
                         'model': 'analytic.code',
                         'res_id': code.id,
                     }
-                    data_osv.create(cr, uid, vals, context=context)
+                    data_obj.create(vals)
+
+        @AddMethod(superclass)
+        @api.model
+        def _get_bound_dimension_id(self):
+            return self.env['ir.model.data'].sudo().get_object_reference(
+                ref_module, ref_id
+            )[1]
 
         @AddMethod(superclass)
-        @api.cr_uid_context
-        @api.returns(orm_name, lambda a: a.id)
-        def create(self, cr, uid, vals, context=None, **kwargs):
-            """Create the analytic code."""
-
-            code_vals = {}
-
-            if sync_parent:
-                cp = self._get_code_parent(cr, uid, vals, context=context)
-                if cp is not None:
-                    code_vals['code_parent_id'] = cp
-
-            # Direct changes to the 'bound analytic code' field are ignored
-            # unless the 'force_code_id' context key is passed as True.
-            force_code_id = vals.pop(column, False)
+        @api.model
+        def _create_analytic_code(self, vals, code_vals):
 
             # Will be set if a new code is created
-            new_code_id = False
+            new_code = False
 
-            if context and context.get('force_code_id', False):
-                self._force_code(cr, uid, force_code_id, code_vals, context)
-                vals[column] = force_code_id
-
+            if use_inherits:
+                code_vals.update(vals)
             else:
-                if use_inherits:
-                    code_vals.update(vals)
-                else:
-                    code_vals['name'] = vals.get('name')
+                code_vals['name'] = vals.get(code_name)
+                code_vals['description'] = vals.get(code_description)
 
-                # OpenERP bug: related fields do not work properly on creation.
+            if code_vals.get('name'):
+
+                # OpenERP bug: related fields do not work properly on
+                # creation.
                 for rel in rel_cols:
                     model_col, code_col = rel[1:3]
                     if model_col in vals:
@@ -437,152 +438,286 @@
                     elif model_col in self._defaults:
                         code_vals[code_col] = self._defaults[model_col]
 
-                # We have to create the code separately, even with inherits.
-                code_osv = self.pool['analytic.code']
-                code_vals['nd_id'] = self._bound_dimension_id
-                code_id = code_osv.create(cr, uid, code_vals, context=context)
-                vals[column] = code_id
-                new_code_id = code_id
+                # We have to create the code separately, even with
+                # inherits.
+                code_obj = self.env['analytic.code']
+                code_vals['nd_id'] = self._get_bound_dimension_id()
+                code = code_obj.create(code_vals)
+                vals[column] = code.id
+                new_code = code
+
+            return new_code, vals
+
+        if child_class:
+
+            @AddMethod(superclass)
+            @api.model
+            def protect_analytic_code(self, vals):
+
+                context = self.env.context
+                code_vals = {}
+
+                # Direct changes to the 'bound analytic code' field are ignored
+                # unless the 'force_code_id' context key is passed as True.
+                force_code_id = vals.pop(column, False)
+
+                if sync_parent:
+                    cp = self._get_code_parent(vals)
+                    if cp is not None:
+                        code_vals['code_parent_id'] = cp.id
+
+                if context and context.get('force_code_id', False):
+                    self._force_code(force_code_id, code_vals)
+                    vals[column] = force_code_id
+
+                return vals
+
+            @AddMethod(superclass)
+            @api.multi
+            def create_analytic_code(self):
+                """Create the analytic code."""
+
+                code_obj = self.env['analytic.code']
+
+                for record in self:
+
+                    if not getattr(record, column):
+
+                        code_vals = {
+                            'name': getattr(record, code_name),
+                            'description': getattr(record, code_description),
+                            'nd_id': self._get_bound_dimension_id(),
+                            'origin_id': '{},{}'.format(self._name, record.id),
+                        }
+
+                        if sync_parent:
+                            cp = self._get_code_parent({
+                                sync_parent: getattr(record, sync_parent).id
+                            })
+                            if cp is not None:
+                                code_vals['code_parent_id'] = cp.id
+
+                        code = code_obj.create(code_vals)
+
+                        super(superclass, record).write({column: code.id})
 
-            res = super(superclass, self).create(
-                cr, uid, vals, context=context, **kwargs
-            )
+                        if code_ref_ids:
+                            self._generate_code_ref_id(record)
+
+                return True
+
+        else:
+
+            @AddMethod(superclass)
+            def create(self, vals, **kwargs):
+                """Create the analytic code."""
+
+                context = self.env.context
+                code_vals = {}
 
-            if code_ref_ids:
-                self._generate_code_ref_id(cr, uid, res, context=context)
+                if sync_parent:
+                    cp = self._get_code_parent(vals)
+                    if cp is not None:
+                        code_vals['code_parent_id'] = cp.id
+
+                # Direct changes to the 'bound analytic code' field are ignored
+                # unless the 'force_code_id' context key is passed as True.
+                force_code_id = vals.pop(column, False)
+
+                # Will be set if a new code is created
+                new_code = False
+
+                if context and context.get('force_code_id', False):
+                    self._force_code(force_code_id, code_vals)
+                    vals[column] = force_code_id
 
-            if new_code_id:
-                code_osv.write(cr, uid, new_code_id, {
-                    'origin_id': '{},{}'.format(self._name, res),
-                })
+                else:
+                    new_code, vals = self._create_analytic_code(
+                        vals, code_vals
+                    )
+
+                res = super(superclass, self).create(vals, **kwargs)
+
+                if not getattr(res, column):
+
+                    if sync_parent:
+                        cp = self._get_code_parent(vals)
+                        if cp is not None:
+                            code_vals['code_parent_id'] = cp.id
 
-            return res
+                    new_code, vals = self._create_analytic_code(
+                        {
+                            field: extract(
+                                getattr(res, field), field_data['type']
+                            )
+                            for field, field_data
+                            in res.fields_get().iteritems()
+                            if field in (
+                                vals.keys() + [code_name, code_description]
+                            )
+                        }, code_vals
+                    )
+                    super(superclass, res).write({column: new_code.id})
+
+                if code_ref_ids:
+                    self._generate_code_ref_id(res)
+
+                if new_code:
+                    new_code.write({
+                        'origin_id': '{},{}'.format(self._name, res.id),
+                    })
+
+                return res
 
         @AddMethod(superclass)
-        @api.cr_uid_ids_context
-        def write(self, cr, uid, ids, vals, context=None, **kwargs):
+        def write(self, vals, **kwargs):
             """Update the analytic code's name if it is not inherited,
             and its parent code if parent-child relations are synchronized.
             """
 
+            context = self.env.context
             code_vals = {}
             new = False
 
-            if not isinstance(ids, (list, tuple)):
-                ids = [ids]
+            ids = self.ids
 
             if sync_parent:
-                cp = self._get_code_parent(cr, uid, vals, context=context)
+                cp = self._get_code_parent(vals)
                 if cp is not None:
-                    code_vals['code_parent_id'] = cp
+                    code_vals['code_parent_id'] = cp.id
+                else:
+                    parent = getattr(self, sync_parent)
+                    if parent:
+                        cp = getattr(parent, parent_column)
+                        if cp:
+                            code_vals['code_parent_id'] = cp.id
 
             # Direct changes to the 'bound analytic code' field are ignored
             # unless the 'force_code_id' context key is passed as True.
             force_code_id = vals.pop(column, False)
 
             if context and context.get('force_code_id', False):
-                self._force_code(cr, uid, force_code_id, code_vals, context)
+                self._force_code(force_code_id, code_vals)
                 vals[column] = force_code_id
 
             elif use_inherits:
                 vals.update(code_vals)
 
             else:
-                name_col = rel_name[1] if rel_name else 'name'
+                name_col = rel_name[1] if rel_name else code_name
+                description_col = \
+                    rel_description[1] if rel_description else code_description
                 if name_col in vals:
                     code_vals['name'] = vals[name_col]
-                records = self.browse(cr, uid, ids, context=context)
-                code_ids = [getattr(rec, column).id for rec in records]
+                if description_col in vals:
+                    code_vals['description'] = vals[description_col]
+                codes = {rec: getattr(rec, column) for rec in self}
+
+            res = super(superclass, self).write(vals, **kwargs)
+
+            news = []
+
+            # If updating records with no code, create these.
+            if codes:
+                code_obj = self.env['analytic.code']
+
+                for rec, code in codes.iteritems():
+
+                    rec_code_vals = code_vals.copy()
+                    rec_vals = dict().copy()
 
-                # If updating a single record with no code, create it.
-                code_osv = self.pool['analytic.code']
-                if code_ids == [False]:
-                    new = ids[0]
-                    code_vals['nd_id'] = self._bound_dimension_id
-                    if 'name' not in code_vals:
-                        code_vals['name'] = self.read(
-                            cr, uid, new, [name_col], context=context
-                        )[name_col]
-                    code_vals['origin_id'] = '{},{}'.format(self._name, new)
-                    vals[column] = code_osv.create(
-                        cr, uid, code_vals, context=context
-                    )
-                elif code_vals:
-                    code_osv.write(
-                        cr, uid, code_ids, code_vals, context=context
-                    )
+                    if 'name' not in rec_code_vals:
+                        rec_code_vals['name'] = \
+                            rec.read([name_col])[0][name_col]
+                    if 'description' not in code_vals:
+                        rec_code_vals['description'] = (
+                            self.read([description_col])[0]
+                            [description_col]
+                        )
 
-            res = super(superclass, self).write(
-                cr, uid, ids, vals, context=context, **kwargs
-            )
+                    if not code and not getattr(rec, column):
+                        news.append(rec.id)
+                        rec_code_vals['nd_id'] = rec._get_bound_dimension_id()
+                        rec_code_vals['origin_id'] = \
+                            '{},{}'.format(self._name, rec.id)
+                        rec_vals[column] = code_obj.create(rec_code_vals).id
 
-            if code_ref_ids and new is not False:
-                self._generate_code_ref_id(cr, uid, new, context=context)
+                        super(superclass, rec).write(rec_vals, **kwargs)
+
+                    elif rec_code_vals:
+                        code.write(rec_code_vals)
+
+            if code_ref_ids and news is not False:
+                for new in news:
+                    self._generate_code_ref_id(new)
 
             return res
 
         @AddMethod(superclass)
-        @api.cr_uid_ids_context
-        def unlink(self, cr, uid, ids, context=None, **kwargs):
+        def unlink(self, **kwargs):
             """When removing this object, remove all associated analytic
             codes referenced by this object.
             Note: the method will fail if the code is referenced by any other
             object due to the RESTRICT constraint. That is the intended
             behavior.
             """
-            code_obj = self.pool['analytic.code']
 
             # Find all related codes
-            code_ids = [
-                record[column].id for record
-                in self.browse(cr, uid, ids, context=context)
-            ]
+            codes = self.env['analytic.code']
 
-            res = super(superclass, self).unlink(
-                cr, uid, ids, context=context
-            )
+            for record in self:
+                codes |= getattr(record, column)
 
-            code_obj.unlink(cr, uid, code_ids, context=context, **kwargs)
+            res = super(superclass, self).unlink()
+
+            codes.unlink(**kwargs)
 
             return res
 
         @AddMethod(superclass)
-        def _force_code(self, cr, uid, force_code_id, code_vals, context=None):
+        def _force_code(self, force_code_id, code_vals):
 
-            code_osv = self.pool['analytic.code']
+            code_obj = self.env['analytic.code']
 
             if not force_code_id:
                 raise ValueError(
                     "An analytic code ID MUST be specified if the "
                     "force_code_id key is enabled in the context"
                 )
-            force_code_dim = code_osv.read(
-                cr, uid, force_code_id, ['nd_id'], context=context
-            )['nd_id'][0]
-            if force_code_dim != self._bound_dimension_id:
+            force_code = code_obj.browse(force_code_id)
+            force_code_dim_id = force_code.nd_id.id
+            if force_code_dim_id != self._get_bound_dimension_id():
                 raise ValueError(
                     "If specified, codes must belong to the bound "
                     "analytic dimension {}".format(dimension_name)
                 )
             if code_vals:
-                code_osv.write(
-                    cr, uid, force_code_id, code_vals, context=context
-                )
+                force_code.write(code_vals)
 
         if sync_parent:
             # This function is called as a method and can be overridden.
             @AddMethod(superclass)
-            def _get_code_parent(self, cr, uid, vals, context=None):
+            def _get_code_parent(self, vals):
                 """If parent_id is in the submitted values, return the analytic
                 code of this parent, to be used as the child's code's parent.
                 """
                 parent_id = vals.get(sync_parent, None)
                 if parent_id is not None:
+
                     if parent_id:
-                        res = self.read(
-                            cr, uid, parent_id, [column], context=context
-                        )[column]
-                        return res[0] if res else False
+
+                        parent_model = \
+                            self.fields_get([sync_parent])[sync_parent].get(
+                                'relation'
+                            )
+
+                        if parent_model:
+                            res = getattr(
+                                self.env[parent_model].browse(parent_id),
+                                parent_column
+                            )
+
+                        return res if res else False
                     else:
                         return False
                 return None
@@ -590,36 +725,32 @@
         if use_code_name_methods:
 
             @AddMethod(superclass)
-            def name_get(self, cr, uid, ids, context=None):
+            def name_get(self):
                 """Return the analytic code's name."""
 
-                code_osv = self.pool.get('analytic.code')
-                code_reads = self.read(cr, uid, ids, [column], context=context)
+                code_reads = self.read([column])
                 c2m = {  # Code IDs to model IDs
                     code_read[column][0]: code_read['id']
                     for code_read in code_reads
                     if code_read[column] is not False
                 }
-                names = code_osv.name_get(cr, uid, c2m.keys(), context=context)
+                names = self.env['analytic.code'].browse(c2m.keys()).name_get()
                 return [(c2m[cid], name) for cid, name in names if cid in c2m]
 
             @AddMethod(superclass)
             def name_search(
-                self, cr, uid, name, args=None, operator='ilike', context=None,
-                limit=100
+                self, name, args=None, operator='ilike', limit=100
             ):
                 """Return the records whose analytic code matches the name."""
 
-                code_osv = self.pool.get('analytic.code')
-                args.append(('nd_id', '=', self._bound_dimension_id))
-                names = code_osv.name_search(
-                    cr, uid, name, args, operator, context, limit
-                )
+                code_obj = self.env.get('analytic.code')
+                args.append(('nd_id', '=', self._get_bound_dimension_id()))
+                names = code_obj.name_search(name, args, operator, limit)
                 if not names:
                     return []
                 dom = [(column, 'in', zip(*names)[0])]
-                ids = self.search(cr, uid, dom, context=context)
-                code_reads = self.read(cr, uid, ids, [column], context=context)
+                ids = self.search(dom)
+                code_reads = self.read([column])
                 c2m = {  # Code IDs to model IDs
                     code_read[column][0]: code_read['id']
                     for code_read in code_reads
@@ -632,3 +763,22 @@
                 ]
 
         return (superclass,)
+
+
+def extract(value, typ):
+    """Returns the ID, if the value is a browse record.
+    Returns the IDs, if the value is a browse record list.
+    Otherwise, returns the value.
+    """
+
+    result = value
+    if typ in ['many2many']:
+        res = value.ids
+        res.sort()
+        result = [(6, 0, list(set(res)))]
+    elif typ == 'many2one':
+        result = value.id
+    elif typ == 'one2many':
+        result = False
+
+    return result