{"action":"created","comment":{"url":"https://api.github.com/repos/odoo/odoo/pulls/comments/26821137","id":26821137,"diff_hunk":"@@ -1,818 +1,725 @@\n # -*- coding: utf-8 -*-\n-##############################################################################\n-#\n-#    OpenERP, Open Source Management Solution\n-#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).\n-#\n-#    This program is free software: you can redistribute it and/or modify\n-#    it under the terms of the GNU Affero General Public License as\n-#    published by the Free Software Foundation, either version 3 of the\n-#    License, or (at your option) any later version.\n-#\n-#    This program is distributed in the hope that it will be useful,\n-#    but WITHOUT ANY WARRANTY; without even the implied warranty of\n-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n-#    GNU Affero General Public License for more details.\n-#\n-#    You should have received a copy of the GNU Affero General Public License\n-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n-#\n-##############################################################################\n-\n-from openerp.osv import fields, osv\n-import time\n+from collections import defaultdict\n import datetime\n-from openerp import tools\n-from openerp.exceptions import UserError\n-from openerp.tools.translate import _\n from dateutil.relativedelta import relativedelta\n+import time\n \n-def str_to_datetime(strdate):\n-    return datetime.datetime.strptime(strdate, tools.DEFAULT_SERVER_DATE_FORMAT)\n+from openerp import _, api, fields, models, tools\n+from openerp.exceptions import UserError\n \n-class fleet_vehicle_cost(osv.Model):\n+\n+class FleetVehicleCost(models.Model):\n     _name = 'fleet.vehicle.cost'\n     _description = 'Cost related to a vehicle'\n-    _order = 'date desc, vehicle_id asc'\n+    _order = 'date desc, vehicle_id asc, id desc'\n \n-    def _get_odometer(self, cr, uid, ids, odometer_id, arg, context):\n-        res = dict.fromkeys(ids, False)\n-        for record in self.browse(cr,uid,ids,context=context):\n-            if record.odometer_id:\n-                res[record.id] = record.odometer_id.value\n-        return res\n+    @api.one\n+    @api.depends('odometer_id')\n+    def _compute_get_odometer(self):\n+        self.odometer = self.odometer_id and self.odometer_id.value\n \n-    def _set_odometer(self, cr, uid, id, name, value, args=None, context=None):\n-        if not value:\n+    @api.one\n+    def _compute_set_odometer(self):\n+        if not self.odometer:\n             raise UserError(_('Emptying the odometer value of a vehicle is not allowed.'))\n-        date = self.browse(cr, uid, id, context=context).date\n-        if not(date):\n-            date = fields.date.context_today(self, cr, uid, context=context)\n-        vehicle_id = self.browse(cr, uid, id, context=context).vehicle_id\n-        data = {'value': value, 'date': date, 'vehicle_id': vehicle_id.id}\n-        odometer_id = self.pool.get('fleet.vehicle.odometer').create(cr, uid, data, context=context)\n-        return self.write(cr, uid, id, {'odometer_id': odometer_id}, context=context)\n-\n-    _columns = {\n-        'name': fields.related('vehicle_id', 'name', type=\"char\", string='Name', store=True),\n-        'vehicle_id': fields.many2one('fleet.vehicle', 'Vehicle', required=True, help='Vehicle concerned by this log'),\n-        'cost_subtype_id': fields.many2one('fleet.service.type', 'Type', help='Cost type purchased with this cost'),\n-        'amount': fields.float('Total Price'),\n-        'cost_type': fields.selection([('contract', 'Contract'), ('services','Services'), ('fuel','Fuel'), ('other','Other')], 'Category of the cost', help='For internal purpose only', required=True),\n-        'parent_id': fields.many2one('fleet.vehicle.cost', 'Parent', help='Parent cost to this current cost'),\n-        'cost_ids': fields.one2many('fleet.vehicle.cost', 'parent_id', 'Included Services'),\n-        'odometer_id': fields.many2one('fleet.vehicle.odometer', 'Odometer', help='Odometer measure of the vehicle at the moment of this log'),\n-        'odometer': fields.function(_get_odometer, fnct_inv=_set_odometer, type='float', string='Odometer Value', help='Odometer measure of the vehicle at the moment of this log'),\n-        'odometer_unit': fields.related('vehicle_id', 'odometer_unit', type=\"char\", string=\"Unit\", readonly=True),\n-        'date' :fields.date('Date',help='Date when the cost has been executed'),\n-        'contract_id': fields.many2one('fleet.vehicle.log.contract', 'Contract', help='Contract attached to this cost'),\n-        'auto_generated': fields.boolean('Automatically Generated', readonly=True, required=True),\n-    }\n+        odometer = self.env['fleet.vehicle.odometer'].create({\n+            'value': self.odometer,\n+            'date': self.date or fields.Date.today(),\n+            'vehicle_id': self.vehicle_id.id})\n+        self.odometer_id = odometer.id\n+\n+    name = fields.Char(related='vehicle_id.name', store=True)\n+    vehicle_id = fields.Many2one('fleet.vehicle', string='Vehicle', required=True,\n+                                 help='Vehicle concerned by this log')\n+    cost_subtype_id = fields.Many2one('fleet.service.type', string='Type',\n+                                      help='Cost type purchased with this cost')\n+    amount = fields.Float(string='Total Price')\n+    cost_type = fields.Selection(selection=[('contract', 'Contract'), ('services', 'Services'),\n+                                            ('fuel', 'Fuel'), ('other', 'Other')],\n+                                 string='Category of the cost', help='For internal purpose only',\n+                                 required=True, default='other')\n+    parent_id = fields.Many2one('fleet.vehicle.cost', string='Parent', help='Parent cost to this current cost')\n+    cost_ids = fields.One2many('fleet.vehicle.cost', 'parent_id', string='Included Services')\n+    odometer_id = fields.Many2one('fleet.vehicle.odometer', string='Odometer',\n+                                  help='Odometer measure of the vehicle at the moment of this log')\n+    odometer = fields.Float(compute='_compute_get_odometer', inverse='_compute_set_odometer', string='Odometer Value',\n+                            help='Odometer measure of the vehicle at the moment of this log', store=True)\n+    odometer_unit = fields.Selection(related='vehicle_id.odometer_unit', string=\"Unit\", readonly=True)\n+    date = fields.Date(help='Date when the cost has been executed')\n+    contract_id = fields.Many2one('fleet.vehicle.log.contract', string='Contract',\n+                                  help='Contract attached to this cost')\n+    auto_generated = fields.Boolean('Automatically Generated', readonly=True, required=True)\n+\n+    @api.model\n+    def create(self, values):\n+        # make sure that the data are consistent with values of parent and contract records given\n+        if values.get('parent_id'):\n+            parent = self.browse(values['parent_id'])\n+            values['vehicle_id'] = parent.vehicle_id.id\n+            values['date'] = parent.date\n+            values['cost_type'] = parent.cost_type\n+        if values.get('contract_id'):\n+            Contract = self.env['fleet.vehicle.log.contract'].browse(values['contract_id'])\n+            values['vehicle_id'] = Contract.vehicle_id.id\n+            values['cost_subtype_id'] = Contract.cost_subtype_id.id\n+            values['cost_type'] = Contract.cost_type\n+        if 'odometer' in values and not values['odometer']:\n+            # if received value for odometer is 0, then remove it from the data as it would result to the creation of a\n+            # odometer log with 0, which is to be avoided\n+            del(values['odometer'])\n+        return super(FleetVehicleCost, self).create(values)\n+\n+\n+class FleetVehicleTag(models.Model):\n+    _name = 'fleet.vehicle.tag'\n \n-    _defaults ={\n-        'cost_type': 'other',\n-    }\n+    name = fields.Char(required=True, translate=True)\n \n-    def create(self, cr, uid, data, context=None):\n-        #make sure that the data are consistent with values of parent and contract records given\n-        if 'parent_id' in data and data['parent_id']:\n-            parent = self.browse(cr, uid, data['parent_id'], context=context)\n-            data['vehicle_id'] = parent.vehicle_id.id\n-            data['date'] = parent.date\n-            data['cost_type'] = parent.cost_type\n-        if 'contract_id' in data and data['contract_id']:\n-            contract = self.pool.get('fleet.vehicle.log.contract').browse(cr, uid, data['contract_id'], context=context)\n-            data['vehicle_id'] = contract.vehicle_id.id\n-            data['cost_subtype_id'] = contract.cost_subtype_id.id\n-            data['cost_type'] = contract.cost_type\n-        if 'odometer' in data and not data['odometer']:\n-            #if received value for odometer is 0, then remove it from the data as it would result to the creation of a\n-            #odometer log with 0, which is to be avoided\n-            del(data['odometer'])\n-        return super(fleet_vehicle_cost, self).create(cr, uid, data, context=context)\n-\n-\n-class fleet_vehicle_tag(osv.Model):\n-    _name = 'fleet.vehicle.tag'\n-    _columns = {\n-        'name': fields.char('Name', required=True, translate=True),\n-    }\n \n-class fleet_vehicle_state(osv.Model):\n-    _name = 'fleet.vehicle.state'\n-    _order = 'sequence asc'\n-    _columns = {\n-        'name': fields.char('Name', required=True),\n-        'sequence': fields.integer('Sequence', help=\"Used to order the note stages\")\n-    }\n-    _sql_constraints = [('fleet_state_name_unique','unique(name)', 'State name already exists')]\n+class FleetVehicleStage(models.Model):\n+    _name = 'fleet.vehicle.stage'\n+    _order = 'sequence asc, id asc'\n \n+    name = fields.Char(required=True)\n+    sequence = fields.Integer(help=\"Used to order the vehicle stages\")\n \n-class fleet_vehicle_model(osv.Model):\n+    _sql_constraints = [('fleet_stage_name_unique', 'unique(name)', _('Stage name already exists'))]\n \n-    def name_get(self, cr, uid, ids, context=None):\n-        res = []\n-        for record in self.browse(cr, uid, ids, context=context):\n-            name = record.name\n-            if record.brand_id.name:\n-                name = record.brand_id.name + '/' + name\n-            res.append((record.id, name))\n-        return res\n \n-    def on_change_brand(self, cr, uid, ids, model_id, context=None):\n-        if not model_id:\n-            return {'value': {'image_medium': False}}\n-        brand = self.pool.get('fleet.vehicle.model.brand').browse(cr, uid, model_id, context=context)\n-        return {\n-            'value': {\n-                'image_medium': brand.image,\n-            }\n-        }\n+class FleetVehicleModel(models.Model):\n \n     _name = 'fleet.vehicle.model'\n     _description = 'Model of a vehicle'\n-    _order = 'name asc'\n-\n-    _columns = {\n-        'name': fields.char('Model name', required=True),\n-        'brand_id': fields.many2one('fleet.vehicle.model.brand', 'Make', required=True, help='Make of the vehicle'),\n-        'vendors': fields.many2many('res.partner', 'fleet_vehicle_model_vendors', 'model_id', 'partner_id', string='Vendors'),\n-        'image': fields.related('brand_id', 'image', type=\"binary\", string=\"Logo\"),\n-        'image_medium': fields.related('brand_id', 'image_medium', type=\"binary\", string=\"Logo (medium)\"),\n-        'image_small': fields.related('brand_id', 'image_small', type=\"binary\", string=\"Logo (small)\"),\n-    }\n-\n-\n-class fleet_vehicle_model_brand(osv.Model):\n-    _name = 'fleet.vehicle.model.brand'\n-    _description = 'Brand model of the vehicle'\n-\n-    _order = 'name asc'\n-\n-    def _get_image(self, cr, uid, ids, name, args, context=None):\n-        result = dict.fromkeys(ids, False)\n-        for obj in self.browse(cr, uid, ids, context=context):\n-            result[obj.id] = tools.image_get_resized_images(obj.image)\n-        return result\n-\n-    def _set_image(self, cr, uid, id, name, value, args, context=None):\n-        return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)\n-\n-    _columns = {\n-        'name': fields.char('Make', required=True),\n-        'image': fields.binary(\"Logo\",\n-            help=\"This field holds the image used as logo for the brand, limited to 1024x1024px.\"),\n-        'image_medium': fields.function(_get_image, fnct_inv=_set_image,\n-            string=\"Medium-sized photo\", type=\"binary\", multi=\"_get_image\",\n-            store = {\n-                'fleet.vehicle.model.brand': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),\n-            },\n-            help=\"Medium-sized logo of the brand. It is automatically \"\\\n-                 \"resized as a 128x128px image, with aspect ratio preserved. \"\\\n-                 \"Use this field in form views or some kanban views.\"),\n-        'image_small': fields.function(_get_image, fnct_inv=_set_image,\n-            string=\"Smal-sized photo\", type=\"binary\", multi=\"_get_image\",\n-            store = {\n-                'fleet.vehicle.model.brand': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),\n-            },\n-            help=\"Small-sized photo of the brand. It is automatically \"\\\n-                 \"resized as a 64x64px image, with aspect ratio preserved. \"\\\n-                 \"Use this field anywhere a small image is required.\"),\n-    }\n-\n-\n-class fleet_vehicle(osv.Model):\n+    _order = 'name asc, id asc'\n+\n+    @api.multi\n+    def name_get(self):\n+        return self.mapped(lambda m: (m.id, '/'.join(filter(None, (m.make_id.name, m.name)))))\n+\n+    @api.onchange('make_id')\n+    def on_change_make(self):\n+        self.image = self.make_id.image\n+\n+    name = fields.Char(store=True, required=True)\n+    make_id = fields.Many2one('fleet.make', string='Make', oldname='brand_id', required=True, help='Make of the vehicle')\n+    vendors = fields.Many2many('res.partner', 'fleet_vehicle_model_vendors', 'model_id', 'partner_id')\n+    image = fields.Binary(related='make_id.image', string=\"Logo\", store=True)\n+    image_medium = fields.Binary(related='make_id.image_medium', string=\"Logo (medium)\", store=True)\n+    image_small = fields.Binary(related='make_id.image_small', string=\"Logo (small)\", store=True)\n+\n+\n+class FleetMake(models.Model):\n+    _name = 'fleet.make'\n+    _description = 'Make of the vehicle'\n+    _order = 'name asc, id asc'\n+\n+    @api.multi\n+    @api.depends('image')\n+    def _compute_get_image(self):\n+        all_image = tools.image_get_resized_images(self.image)\n+        self.image_medium = all_image.get('image_medium')\n+        self.image_small = all_image.get('image_small')\n+\n+    @api.one\n+    def _compute_set_image(self):\n+        self.image = tools.image_resize_image_big(self.image_medium)\n+\n+    name = fields.Char(string='Make', required=True)\n+    image = fields.Binary(string=\"Logo\", help=\"This field holds the image used as logo for the brand,limited to 1024x1024px.\")\n+    image_medium = fields.Binary(compute='_compute_get_image', inverse='_compute_set_image', string=\"Medium-sized photo\", store=True,\n+                                 help=\"Medium-sized logo of the brand. It is automatically \"\n+                                      \"resize as a 128x128px image, with aspect ratio preserved. \"\n+                                      \"Use this field in form views or some kanban views.\")\n+    image_small = fields.Binary(compute='_compute_get_image', inverse='_compute_set_image', string=\"Small-sized photo\", store=True,\n+                                help=\"Small-sized photo of the brand. It is automatically \"\n+                                     \"resize as a 64x64px image, with aspect ratio preserved. \"\n+                                     \"Use this field anywhere a small image is required.\")\n+\n+\n+class FleetVehicle(models.Model):\n \n+    _name = 'fleet.vehicle'\n+    _description = 'Information on a vehicle'\n+    _order = 'license_plate asc'\n     _inherit = 'mail.thread'\n \n-    def _vehicle_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):\n-        res = {}\n-        for record in self.browse(cr, uid, ids, context=context):\n-            res[record.id] = record.model_id.brand_id.name + '/' + record.model_id.name + '/' + record.license_plate\n-        return res\n-\n-    def return_action_to_open(self, cr, uid, ids, context=None):\n+    @api.one\n+    @api.depends('license_plate')\n+    def _compute_vehicle_name(self):\n+        if self.model_id:\n+            self.name = self.model_id.make_id.name + ' / ' + self.model_id.name + ' / ' \\\n+                + self.license_plate\n+        else:\n+            self.name = self.license_plate\n+\n+    @api.multi\n+    def return_action_to_open(self):\n         \"\"\" This opens the xml view specified in xml_id for the current vehicle \"\"\"\n-        if context is None:\n-            context = {}\n-        if context.get('xml_id'):\n-            res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet', context['xml_id'], context=context)\n-            res['context'] = context\n-            res['context'].update({'default_vehicle_id': ids[0]})\n-            res['domain'] = [('vehicle_id','=', ids[0])]\n-            return res\n+        if self.env.context.get('xml_id'):\n+            result = self.env.ref('fleet.' + self.env.context['xml_id']).read()[0]\n+            result['context'] = dict(self.env.context, default_vehicle_id=self[0].id)\n+            result['domain'] = [('vehicle_id', '=', self[0].id)]\n+            return result\n         return False\n \n-    def act_show_log_cost(self, cr, uid, ids, context=None):\n-        \"\"\" This opens log view to view and add new log for this vehicle, groupby default to only show effective costs\n+    @api.multi\n+    def act_show_log_cost(self):\n+        \"\"\" This opens log view to view and add new log for this vehicle,\n+            groupBy default to only show effective costs\n             @return: the costs log view\n         \"\"\"\n-        if context is None:\n-            context = {}\n-        res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet','fleet_vehicle_costs_act', context=context)\n-        res['context'] = context\n-        res['context'].update({\n-            'default_vehicle_id': ids[0],\n-            'search_default_parent_false': True\n-        })\n-        res['domain'] = [('vehicle_id','=', ids[0])]\n-        return res\n-\n-    def _get_odometer(self, cr, uid, ids, odometer_id, arg, context):\n-        res = dict.fromkeys(ids, 0)\n-        for record in self.browse(cr,uid,ids,context=context):\n-            ids = self.pool.get('fleet.vehicle.odometer').search(cr, uid, [('vehicle_id', '=', record.id)], limit=1, order='value desc')\n-            if len(ids) > 0:\n-                res[record.id] = self.pool.get('fleet.vehicle.odometer').browse(cr, uid, ids[0], context=context).value\n-        return res\n-\n-    def _set_odometer(self, cr, uid, id, name, value, args=None, context=None):\n-        if value:\n-            date = fields.date.context_today(self, cr, uid, context=context)\n-            data = {'value': value, 'date': date, 'vehicle_id': id}\n-            return self.pool.get('fleet.vehicle.odometer').create(cr, uid, data, context=context)\n-\n-    def _search_get_overdue_contract_reminder(self, cr, uid, obj, name, args, context):\n-        res = []\n-        for field, operator, value in args:\n-            assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported'\n-            if (operator == '=' and value == True) or (operator in ('<>', '!=') and value == False):\n-                search_operator = 'in'\n-            else:\n-                search_operator = 'not in'\n-            today = fields.date.context_today(self, cr, uid, context=context)\n-            cr.execute('select cost.vehicle_id, count(contract.id) as contract_number FROM fleet_vehicle_cost cost left join fleet_vehicle_log_contract contract on contract.cost_id = cost.id WHERE contract.expiration_date is not null AND contract.expiration_date < %s AND contract.state IN (\\'open\\', \\'toclose\\') GROUP BY cost.vehicle_id', (today,))\n-            res_ids = [x[0] for x in cr.fetchall()]\n-            res.append(('id', search_operator, res_ids))\n-        return res\n-\n-    def _search_contract_renewal_due_soon(self, cr, uid, obj, name, args, context):\n-        res = []\n-        for field, operator, value in args:\n-            assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported'\n-            if (operator == '=' and value == True) or (operator in ('<>', '!=') and value == False):\n-                search_operator = 'in'\n-            else:\n-                search_operator = 'not in'\n-            today = fields.date.context_today(self, cr, uid, context=context)\n-            datetime_today = datetime.datetime.strptime(today, tools.DEFAULT_SERVER_DATE_FORMAT)\n-            limit_date = str((datetime_today + relativedelta(days=+15)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT))\n-            cr.execute('select cost.vehicle_id, count(contract.id) as contract_number FROM fleet_vehicle_cost cost left join fleet_vehicle_log_contract contract on contract.cost_id = cost.id WHERE contract.expiration_date is not null AND contract.expiration_date > %s AND contract.expiration_date < %s AND contract.state IN (\\'open\\', \\'toclose\\') GROUP BY cost.vehicle_id', (today, limit_date))\n-            res_ids = [x[0] for x in cr.fetchall()]\n-            res.append(('id', search_operator, res_ids))\n-        return res\n-\n-    def _get_contract_reminder_fnc(self, cr, uid, ids, field_names, unknow_none, context=None):\n-        res= {}\n-        for record in self.browse(cr, uid, ids, context=context):\n+        result = self.env.ref('fleet.fleet_vehicle_costs_act').read()[0]\n+        result['context'] = dict(self.env.context, default_vehicle_id=self.id, search_default_parent_false=True)\n+        result['domain'] = [('vehicle_id', '=', self[0].id)]\n+        return result\n+\n+    @api.one\n+    def _compute_get_odometer(self):\n+        vehicle_odometer = self.env['fleet.vehicle.odometer'].search([('vehicle_id', '=', self.id)], limit=1, order='value desc')\n+        if vehicle_odometer:\n+            self.odometer = vehicle_odometer.value\n+\n+    @api.one\n+    def _compute_set_odometer(self):\n+        if self.odometer:\n+            return self.env['fleet.vehicle.odometer'].create({'value': self.odometer,\n+                                                              'date': fields.Date.today(),\n+                                                              'vehicle_id': self.id\n+                                                              })\n+\n+    @api.model\n+    def _search_get_overdue_contract_reminder(self, operator, value):\n+        if not (operator in ('=', '!=', '<>') and value in (True, False)):\n+            raise UserError(\"Operation not supported\")\n+        if (operator == '=' and value is True) or (operator in ('<>', '!=') and value is False):\n+            search_operator = 'in'\n+        else:\n+            search_operator = 'not in'\n+        today = fields.Date.today()\n+        overdue_contracts = self.env['fleet.vehicle.log.contract'].read_group([('expiration_date', '<', today)],\n+                                                                              fields=['vehicle_id'], groupby=['vehicle_id'])\n+        list_overdue_contract = []\n+        for overdue_contract in overdue_contracts:\n+            vehicle_id = str(overdue_contract['vehicle_id'][0])\n+            list_overdue_contract.append(vehicle_id)\n+        return [('id', search_operator, list_overdue_contract)]\n+\n+    @api.model\n+    def _search_contract_renewal_due_soon(self, operator, value):\n+        if not (operator in ('=', '!=', '<>') and value in (True, False)):\n+            raise UserError(\"Operation not supported\")\n+        if (operator == '=' and value is True) or (operator in ('<>', '!=') and value is False):\n+            search_operator = 'in'\n+        else:\n+            search_operator = 'not in'\n+        today = fields.Date.today()\n+        datetime_today = fields.Datetime.from_string(today)\n+        limit_date = str((datetime_today + relativedelta(days=+15)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT))\n+        renewal_contracts = self.env['fleet.vehicle.log.contract'].read_group([('expiration_date', '>', today),\n+                                                                             ('expiration_date', '<', limit_date)],\n+                                                                             fields=['vehicle_id'], groupby=['vehicle_id'])\n+        list_renewal_contract = []\n+        for renewal_contract in renewal_contracts:\n+            vehicle_id = str(renewal_contract['vehicle_id'][0])\n+            list_renewal_contract.append(vehicle_id)\n+        return [('id', search_operator, list_renewal_contract)]\n+\n+    @api.multi\n+    def _compute_get_contract_reminder(self):\n+        LogContract = self.env['fleet.vehicle.log.contract']\n+        for vehicle in self:\n             overdue = False\n             due_soon = False\n             total = 0\n             name = ''\n-            for element in record.log_contracts:\n+            for element in vehicle.log_contracts:\n                 if element.state in ('open', 'toclose') and element.expiration_date:\n-                    current_date_str = fields.date.context_today(self, cr, uid, context=context)\n-                    due_time_str = element.expiration_date\n-                    current_date = str_to_datetime(current_date_str)\n-                    due_time = str_to_datetime(due_time_str)\n+                    current_date = fields.Datetime.from_string(fields.Date.today())\n+                    due_time = fields.Datetime.from_string(element.expiration_date)\n                     diff_time = (due_time-current_date).days\n                     if diff_time < 0:\n                         overdue = True\n                         total += 1\n                     if diff_time < 15 and diff_time >= 0:\n-                            due_soon = True;\n+                            due_soon = True\n                             total += 1\n                     if overdue or due_soon:\n-                        ids = self.pool.get('fleet.vehicle.log.contract').search(cr,uid,[('vehicle_id', '=', record.id), ('state', 'in', ('open', 'toclose'))], limit=1, order='expiration_date asc')\n-                        if len(ids) > 0:\n-                            #we display only the name of the oldest overdue/due soon contract\n-                            name=(self.pool.get('fleet.vehicle.log.contract').browse(cr, uid, ids[0], context=context).cost_subtype_id.name)\n-\n-            res[record.id] = {\n-                'contract_renewal_overdue': overdue,\n-                'contract_renewal_due_soon': due_soon,\n-                'contract_renewal_total': (total - 1), #we remove 1 from the real total for display purposes\n-                'contract_renewal_name': name,\n-            }\n-        return res\n-\n-    def _get_default_state(self, cr, uid, context):\n+                        log_contract = LogContract.search([('vehicle_id', '=', vehicle.id),\n+                                                         ('state', 'in', ('open', 'toclose'))],\n+                                                         limit=1, order='expiration_date asc')\n+                        if log_contract:\n+                            # we display only the name of the oldest overdue/due soon contract\n+                            name = log_contract.cost_subtype_id.name\n+\n+            vehicle.contract_renewal_overdue = overdue\n+            vehicle.contract_renewal_due_soon = due_soon\n+            vehicle.contract_renewal_total = (total - 1)  # we remove 1 from the real total for display purposes\n+            vehicle.contract_renewal_name = name\n+\n+    # @api.model\n+    def default_stage(self):\n         try:\n-            model, model_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fleet', 'vehicle_state_active')\n+            model_id = self.env.ref('fleet.vehicle_stage_active')\n         except ValueError:\n             model_id = False\n         return model_id\n-    \n-    def _count_all(self, cr, uid, ids, field_name, arg, context=None):\n-        Odometer = self.pool['fleet.vehicle.odometer']\n-        LogFuel = self.pool['fleet.vehicle.log.fuel']\n-        LogService = self.pool['fleet.vehicle.log.services']\n-        LogContract = self.pool['fleet.vehicle.log.contract']\n-        Cost = self.pool['fleet.vehicle.cost']\n-        return {\n-            vehicle_id: {\n-                'odometer_count': Odometer.search_count(cr, uid, [('vehicle_id', '=', vehicle_id)], context=context),\n-                'fuel_logs_count': LogFuel.search_count(cr, uid, [('vehicle_id', '=', vehicle_id)], context=context),\n-                'service_count': LogService.search_count(cr, uid, [('vehicle_id', '=', vehicle_id)], context=context),\n-                'contract_count': LogContract.search_count(cr, uid, [('vehicle_id', '=', vehicle_id)], context=context),\n-                'cost_count': Cost.search_count(cr, uid, [('vehicle_id', '=', vehicle_id), ('parent_id', '=', False)], context=context)\n-            }\n-            for vehicle_id in ids\n-        }\n-\n-    _name = 'fleet.vehicle'\n-    _description = 'Information on a vehicle'\n-    _order= 'license_plate asc'\n-    _columns = {\n-        'name': fields.function(_vehicle_name_get_fnc, type=\"char\", string='Name', store=True),\n-        'company_id': fields.many2one('res.company', 'Company'),\n-        'license_plate': fields.char('License Plate', required=True, help='License plate number of the vehicle (ie: plate number for a car)'),\n-        'vin_sn': fields.char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False),\n-        'driver_id': fields.many2one('res.partner', 'Driver', help='Driver of the vehicle'),\n-        'model_id': fields.many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle'),\n-        'log_fuel': fields.one2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs'),\n-        'log_services': fields.one2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs'),\n-        'log_contracts': fields.one2many('fleet.vehicle.log.contract', 'vehicle_id', 'Contracts'),\n-        'cost_count': fields.function(_count_all, type='integer', string=\"Costs\" , multi=True),\n-        'contract_count': fields.function(_count_all, type='integer', string='Contracts', multi=True),\n-        'service_count': fields.function(_count_all, type='integer', string='Services', multi=True),\n-        'fuel_logs_count': fields.function(_count_all, type='integer', string='Fuel Logs', multi=True),\n-        'odometer_count': fields.function(_count_all, type='integer', string='Odometer', multi=True),\n-        'acquisition_date': fields.date('Acquisition Date', required=False, help='Date when the vehicle has been bought'),\n-        'color': fields.char('Color', help='Color of the vehicle'),\n-        'state_id': fields.many2one('fleet.vehicle.state', 'State', help='Current state of the vehicle', ondelete=\"set null\"),\n-        'location': fields.char('Location', help='Location of the vehicle (garage, ...)'),\n-        'seats': fields.integer('Seats Number', help='Number of seats of the vehicle'),\n-        'doors': fields.integer('Doors Number', help='Number of doors of the vehicle'),\n-        'tag_ids' :fields.many2many('fleet.vehicle.tag', 'fleet_vehicle_vehicle_tag_rel', 'vehicle_tag_id','tag_id', 'Tags', copy=False),\n-        'odometer': fields.function(_get_odometer, fnct_inv=_set_odometer, type='float', string='Last Odometer', help='Odometer measure of the vehicle at the moment of this log'),\n-        'odometer_unit': fields.selection([('kilometers', 'Kilometers'),('miles','Miles')], 'Odometer Unit', help='Unit of the odometer ',required=True),\n-        'transmission': fields.selection([('manual', 'Manual'), ('automatic', 'Automatic')], 'Transmission', help='Transmission Used by the vehicle'),\n-        'fuel_type': fields.selection([('gasoline', 'Gasoline'), ('diesel', 'Diesel'), ('electric', 'Electric'), ('hybrid', 'Hybrid')], 'Fuel Type', help='Fuel Used by the vehicle'),\n-        'horsepower': fields.integer('Horsepower'),\n-        'horsepower_tax': fields.float('Horsepower Taxation'),\n-        'power': fields.integer('Power', help='Power in kW of the vehicle'),\n-        'co2': fields.float('CO2 Emissions', help='CO2 emissions of the vehicle'),\n-        'image': fields.related('model_id', 'image', type=\"binary\", string=\"Logo\"),\n-        'image_medium': fields.related('model_id', 'image_medium', type=\"binary\", string=\"Logo (medium)\"),\n-        'image_small': fields.related('model_id', 'image_small', type=\"binary\", string=\"Logo (small)\"),\n-        'contract_renewal_due_soon': fields.function(_get_contract_reminder_fnc, fnct_search=_search_contract_renewal_due_soon, type=\"boolean\", string='Has Contracts to renew', multi='contract_info'),\n-        'contract_renewal_overdue': fields.function(_get_contract_reminder_fnc, fnct_search=_search_get_overdue_contract_reminder, type=\"boolean\", string='Has Contracts Overdued', multi='contract_info'),\n-        'contract_renewal_name': fields.function(_get_contract_reminder_fnc, type=\"text\", string='Name of contract to renew soon', multi='contract_info'),\n-        'contract_renewal_total': fields.function(_get_contract_reminder_fnc, type=\"integer\", string='Total of contracts due or overdue minus one', multi='contract_info'),\n-        'car_value': fields.float('Car Value', help='Value of the bought vehicle'),\n-        }\n \n-    _defaults = {\n-        'doors': 5,\n-        'odometer_unit': 'kilometers',\n-        'state_id': _get_default_state,\n-    }\n+    @api.multi\n+    def _compute_count_all(self):\n+        Odometer = self.env['fleet.vehicle.odometer']\n+        LogFuel = self.env['fleet.vehicle.log.fuel']\n+        LogService = self.env['fleet.vehicle.log.services']\n+        LogContract = self.env['fleet.vehicle.log.contract']\n+        VehicleCost = self.env['fleet.vehicle.cost']\n+        for vehicle_id in self:\n+            vehicle_id.odometer_count = Odometer.search_count([('vehicle_id', '=', vehicle_id.id)])\n+            vehicle_id.fuel_logs_count = LogFuel.search_count([('vehicle_id', '=', vehicle_id.id)])\n+            vehicle_id.service_count = LogService.search_count([('vehicle_id', '=', vehicle_id.id)])\n+            vehicle_id.contract_count = LogContract.search_count([('vehicle_id', '=', vehicle_id.id)])\n+            vehicle_id.cost_count = VehicleCost.search_count([('vehicle_id', '=', vehicle_id.id), ('parent_id', '=', False)])\n+\n+    name = fields.Char(compute='_compute_vehicle_name', store=True)\n+    company_id = fields.Many2one('res.company', string='Company')\n+    license_plate = fields.Char(required=True, track_visibility='onchange',\n+                                help='License plate number of the vehicle (ie: plate number for a car)')\n+    vin_sn = fields.Char(string='Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)',\n+                         copy=False)\n+    driver_id = fields.Many2one('res.partner', string='Driver', track_visibility='onchange',\n+                                help='Driver of the vehicle')\n+    model_id = fields.Many2one('fleet.vehicle.model', string='Model', required=True, track_visibility='onchange',\n+                               help='Model of the vehicle')\n+    log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', string='Fuel Logs')\n+    log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', string='Services Logs')\n+    log_contracts = fields.One2many('fleet.vehicle.log.contract', 'vehicle_id', string='Contracts')\n+    cost_count = fields.Integer(compute='_compute_count_all', string=\"Costs\")\n+    contract_count = fields.Integer(compute='_compute_count_all', string='Contracts')\n+    service_count = fields.Integer(compute='_compute_count_all', string='Services')\n+    fuel_logs_count = fields.Integer(compute='_compute_count_all', string='Fuel Logs')\n+    odometer_count = fields.Integer(compute='_compute_count_all', string='Odometer')\n+    acquisition_date = fields.Date('Acquisition Date', help='Date when the vehicle has been bought')\n+    color = fields.Char(help='Color of the vehicle')\n+    stage_id = fields.Many2one('fleet.vehicle.stage', string='Stage', oldname='state_id', track_visibility='onchange',\n+                               help='Current state of the vehicle', default=default_stage)\n+    location = fields.Char(help='Location of the vehicle (garage, ...)')\n+    seats = fields.Integer(string='Seats Number', help='Number of seats of the vehicle')\n+    doors = fields.Integer(string='Doors Number', help='Number of doors of the vehicle', default=5)\n+    tag_ids = fields.Many2many('fleet.vehicle.tag', 'fleet_vehicle_vehicle_tag_rel', 'vehicle_tag_id', 'tag_id',\n+                               string='Tags', copy=False)\n+    odometer = fields.Float(compute='_compute_get_odometer', inverse='_compute_set_odometer', string='Last Odometer',\n+                            help='Odometer measure of the vehicle at the moment of this log')\n+    odometer_unit = fields.Selection(selection=[('kilometers', 'Kilometers'), ('miles', 'Miles')],\n+                                     help='Unit of the odometer',\n+                                     required=True, default='kilometers')\n+    transmission = fields.Selection(selection=[('manual', 'Manual'), ('automatic', 'Automatic')],\n+                                    help='Transmission Used by the vehicle')\n+    fuel_type = fields.Selection(selection=[('gasoline', 'Gasoline'), ('diesel', 'Diesel'),\n+                                            ('electric', 'Electric'), ('hybrid', 'Hybrid')],\n+                                 help='Fuel Used by the vehicle')\n+    horsepower = fields.Integer()\n+    horsepower_tax = fields.Float(string='Horsepower Taxation')\n+    power = fields.Integer(help='Power in kW of the vehicle')\n+    co2 = fields.Float(string='CO2 Emissions', help='CO2 emissions of the vehicle')\n+    image = fields.Binary(related='model_id.make_id.image', string=\"Logo\", store=True)\n+    image_medium = fields.Binary(related='model_id.make_id.image_medium', string=\"Logo (medium)\", store=True)\n+    image_small = fields.Binary(related='model_id.make_id.image_small', string=\"Logo (small)\", store=True)\n+    contract_renewal_due_soon = fields.Boolean(compute='_compute_get_contract_reminder',\n+                                               search='_search_contract_renewal_due_soon',\n+                                               string='Has Contracts to renew')\n+    contract_renewal_overdue = fields.Boolean(compute='_compute_get_contract_reminder',\n+                                              search='_search_get_overdue_contract_reminder',\n+                                              string='Has Contracts Overdue')\n+    contract_renewal_name = fields.Text(compute='_compute_get_contract_reminder', string='Name of contract to renew soon')\n+    contract_renewal_total = fields.Integer(compute='_compute_get_contract_reminder',\n+                                            string='Total of contracts due or overdue minus one')\n+    car_value = fields.Float('Car Value', help='Value of the bought vehicle')\n+\n+    _sql_constraints = [('unique_chassis_number', 'unique(vin_sn)', 'Same Chassis Number is already exists.')]\n+\n+    @api.onchange('model_id')\n+    def on_change_model(self):\n+        self.image = self.model_id.image\n+\n+    @api.model\n+    def create(self, values):\n+        vehicle = super(FleetVehicle, self).create(values)\n+        vehicle.message_post(body=_('%s %s has been added to the fleet!') % (vehicle.model_id.name,\n+                             vehicle.license_plate))\n+        return vehicle\n+\n+\n+class FleetVehicleOdometer(models.Model):\n+\n+    _name = 'fleet.vehicle.odometer'\n+    _description = 'Odometer log for a vehicle'\n+    _order = 'date desc'\n+\n+    @api.depends('vehicle_id')\n+    def _compute_vehicle_log_name(self):\n+        name = self.vehicle_id.name if self.vehicle_id.name else ''\n+        if self.date:\n+            name = name + ' / ' + str(self.date)\n+        self.name = name\n+\n+    @api.onchange('vehicle_id')\n+    def on_change_vehicle(self):\n+        self.unit = self.vehicle_id.odometer_unit\n+\n+    name = fields.Char(compute='_compute_vehicle_log_name', store=True)\n+    date = fields.Date(default=fields.Date.today())\n+    value = fields.Float('Odometer Value', group_operator=\"max\")\n+    vehicle_id = fields.Many2one('fleet.vehicle', string='Vehicle', required=True)\n+    unit = fields.Selection(related='vehicle_id.odometer_unit', readonly=True)\n+\n+\n+class FleetVehicleLogFuel(models.Model):\n \n-    def on_change_model(self, cr, uid, ids, model_id, context=None):\n-        if not model_id:\n-            return {}\n-        model = self.pool.get('fleet.vehicle.model').browse(cr, uid, model_id, context=context)\n-        return {\n-            'value': {\n-                'image_medium': model.image,\n-            }\n-        }\n+    _name = 'fleet.vehicle.log.fuel'\n+    _description = 'Fuel log for vehicles'\n+    _inherits = {'fleet.vehicle.cost': 'cost_id'}\n \n-    def create(self, cr, uid, data, context=None):\n-        context = dict(context or {}, mail_create_nolog=True)\n-        vehicle_id = super(fleet_vehicle, self).create(cr, uid, data, context=context)\n-        vehicle = self.browse(cr, uid, vehicle_id, context=context)\n-        self.message_post(cr, uid, [vehicle_id], body=_('%s %s has been added to the fleet!') % (vehicle.model_id.name,vehicle.license_plate), context=context)\n-        return vehicle_id\n+    @api.onchange('vehicle_id')\n+    def on_change_vehicle(self):\n+        self.odometer_unit = self.vehicle_id.odometer_unit\n+        self.purchaser_id = self.vehicle_id.driver_id\n \n-    def write(self, cr, uid, ids, vals, context=None):\n+    @api.onchange('liter', 'price_per_liter', 'amount')\n+    def on_change_liter(self):\n         \"\"\"\n-        This function write an entry in the openchatter whenever we change important information\n-        on the vehicle like the model, the drive, the state of the vehicle or its license plate\n+        need to cast in float because the value received from web client maybe an integer (Javascript and JSON do not\n+        make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per\n+        liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead\n+        of 3.0/2=1.5)\n+        If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine\n+        onchange. And in order to verify that there is no change in the result, we have to limit the precision of the\n+        computation to 2 decimal\n         \"\"\"\n-        for vehicle in self.browse(cr, uid, ids, context):\n-            changes = []\n-            if 'model_id' in vals and vehicle.model_id.id != vals['model_id']:\n-                value = self.pool.get('fleet.vehicle.model').browse(cr,uid,vals['model_id'],context=context).name\n-                oldmodel = vehicle.model_id.name or _('None')\n-                changes.append(_(\"Model: from '%s' to '%s'\") %(oldmodel, value))\n-            if 'driver_id' in vals and vehicle.driver_id.id != vals['driver_id']:\n-                value = self.pool.get('res.partner').browse(cr,uid,vals['driver_id'],context=context).name\n-                olddriver = (vehicle.driver_id.name) or _('None')\n-                changes.append(_(\"Driver: from '%s' to '%s'\") %(olddriver, value))\n-            if 'state_id' in vals and vehicle.state_id.id != vals['state_id']:\n-                value = self.pool.get('fleet.vehicle.state').browse(cr,uid,vals['state_id'],context=context).name\n-                oldstate = vehicle.state_id.name or _('None')\n-                changes.append(_(\"State: from '%s' to '%s'\") %(oldstate, value))\n-            if 'license_plate' in vals and vehicle.license_plate != vals['license_plate']:\n-                old_license_plate = vehicle.license_plate or _('None')\n-                changes.append(_(\"License Plate: from '%s' to '%s'\") %(old_license_plate, vals['license_plate']))\n-\n-            if len(changes) > 0:\n-                self.message_post(cr, uid, [vehicle.id], body=\", \".join(changes), context=context)\n-\n-        vehicle_id = super(fleet_vehicle,self).write(cr, uid, ids, vals, context)\n-        return True\n-\n-\n-class fleet_vehicle_odometer(osv.Model):\n-    _name='fleet.vehicle.odometer'\n-    _description='Odometer log for a vehicle'\n-    _order='date desc'\n-\n-    def _vehicle_log_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):\n-        res = {}\n-        for record in self.browse(cr, uid, ids, context=context):\n-            name = record.vehicle_id.name\n-            if not name:\n-                name = record.date\n-            elif record.date:\n-                name += ' / '+ record.date\n-            res[record.id] = name\n-        return res\n-\n-    def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):\n-        if not vehicle_id:\n-            return {}\n-        odometer_unit = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context).odometer_unit\n-        return {\n-            'value': {\n-                'unit': odometer_unit,\n-            }\n-        }\n-\n-    _columns = {\n-        'name': fields.function(_vehicle_log_name_get_fnc, type=\"char\", string='Name', store=True),\n-        'date': fields.date('Date'),\n-        'value': fields.float('Odometer Value', group_operator=\"max\"),\n-        'vehicle_id': fields.many2one('fleet.vehicle', 'Vehicle', required=True),\n-        'unit': fields.related('vehicle_id', 'odometer_unit', type=\"char\", string=\"Unit\", readonly=True),\n-    }\n-    _defaults = {\n-        'date': fields.date.context_today,\n-    }\n-\n-\n-class fleet_vehicle_log_fuel(osv.Model):\n-\n-    def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):\n-        if not vehicle_id:\n-            return {}\n-        vehicle = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context)\n-        odometer_unit = vehicle.odometer_unit\n-        driver = vehicle.driver_id.id\n-        return {\n-            'value': {\n-                'odometer_unit': odometer_unit,\n-                'purchaser_id': driver,\n-            }\n-        }\n-\n-    def on_change_liter(self, cr, uid, ids, liter, price_per_liter, amount, context=None):\n-        #need to cast in float because the value receveid from web client maybe an integer (Javascript and JSON do not\n-        #make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per\n-        #liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead\n-        #of 3.0/2=1.5)\n-        #If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine\n-        #onchange. And in order to verify that there is no change in the result, we have to limit the precision of the \n-        #computation to 2 decimal\n-        liter = float(liter)\n-        price_per_liter = float(price_per_liter)\n-        amount = float(amount)\n-        if liter > 0 and price_per_liter > 0 and round(liter*price_per_liter,2) != amount:\n-            return {'value' : {'amount' : round(liter * price_per_liter,2),}}\n-        elif amount > 0 and liter > 0 and round(amount/liter,2) != price_per_liter:\n-            return {'value' : {'price_per_liter' : round(amount / liter,2),}}\n-        elif amount > 0 and price_per_liter > 0 and round(amount/price_per_liter,2) != liter:\n-            return {'value' : {'liter' : round(amount / price_per_liter,2),}}\n-        else :\n-            return {}\n-\n-    def on_change_price_per_liter(self, cr, uid, ids, liter, price_per_liter, amount, context=None):\n-        #need to cast in float because the value receveid from web client maybe an integer (Javascript and JSON do not\n-        #make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per\n-        #liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead\n-        #of 3.0/2=1.5)\n-        #If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine\n-        #onchange. And in order to verify that there is no change in the result, we have to limit the precision of the \n-        #computation to 2 decimal\n-        liter = float(liter)\n-        price_per_liter = float(price_per_liter)\n-        amount = float(amount)\n-        if liter > 0 and price_per_liter > 0 and round(liter*price_per_liter,2) != amount:\n-            return {'value' : {'amount' : round(liter * price_per_liter,2),}}\n-        elif amount > 0 and price_per_liter > 0 and round(amount/price_per_liter,2) != liter:\n-            return {'value' : {'liter' : round(amount / price_per_liter,2),}}\n-        elif amount > 0 and liter > 0 and round(amount/liter,2) != price_per_liter:\n-            return {'value' : {'price_per_liter' : round(amount / liter,2),}}\n-        else :\n-            return {}\n-\n-    def on_change_amount(self, cr, uid, ids, liter, price_per_liter, amount, context=None):\n-        #need to cast in float because the value receveid from web client maybe an integer (Javascript and JSON do not\n-        #make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per\n-        #liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead\n-        #of 3.0/2=1.5)\n-        #If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine\n-        #onchange. And in order to verify that there is no change in the result, we have to limit the precision of the \n-        #computation to 2 decimal\n-        liter = float(liter)\n-        price_per_liter = float(price_per_liter)\n-        amount = float(amount)\n-        if amount > 0 and liter > 0 and round(amount/liter,2) != price_per_liter:\n-            return {'value': {'price_per_liter': round(amount / liter,2),}}\n-        elif amount > 0 and price_per_liter > 0 and round(amount/price_per_liter,2) != liter:\n-            return {'value': {'liter': round(amount / price_per_liter,2),}}\n-        elif liter > 0 and price_per_liter > 0 and round(liter*price_per_liter,2) != amount:\n-            return {'value': {'amount': round(liter * price_per_liter,2),}}\n-        else :\n-            return {}\n-\n-    def _get_default_service_type(self, cr, uid, context):\n+        liter = float(self.liter)\n+        price_per_liter = float(self.price_per_liter)\n+        amount = float(self.amount)\n+        if liter > 0 and price_per_liter > 0 and round(liter * price_per_liter, 2) != amount:\n+            self.amount = round(liter * price_per_liter, 2)\n+        elif amount > 0 and liter > 0 and round(amount / liter, 2) != price_per_liter:\n+            self.price_per_liter = round(amount / liter, 2)\n+        elif amount > 0 and price_per_liter > 0 and round(amount / price_per_liter, 2) != liter:\n+            self.liter = round(amount / price_per_liter, 2)\n+\n+    @api.onchange('price_per_liter')\n+    def on_change_price_per_liter(self):\n+        \"\"\"\n+        need to cast in float because the value received from web client maybe an integer (Javascript and JSON do not\n+        make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per\n+        liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead\n+        of 3.0/2=1.5)\n+        If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine\n+        onchange. And in order to verify that there is no change in the result, we have to limit the precision of the\n+        computation to 2 decimal\n+        \"\"\"\n+        liter = float(self.liter)\n+        price_per_liter = float(self.price_per_liter)\n+        amount = float(self.amount)\n+        if liter > 0 and price_per_liter > 0 and round(liter * price_per_liter, 2) != amount:","path":"addons/fleet/fleet.py","position":null,"original_position":902,"commit_id":"c814981f589346dd79630717d3adfc59957ae16c","original_commit_id":"6fda3390269a7a2c860f3b51b8ed1f0719544dce","user":{"login":"jas-odoo","id":7599682,"avatar_url":"https://avatars.githubusercontent.com/u/7599682?v=3","gravatar_id":"","url":"https://api.github.com/users/jas-odoo","html_url":"https://github.com/jas-odoo","followers_url":"https://api.github.com/users/jas-odoo/followers","following_url":"https://api.github.com/users/jas-odoo/following{/other_user}","gists_url":"https://api.github.com/users/jas-odoo/gists{/gist_id}","starred_url":"https://api.github.com/users/jas-odoo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jas-odoo/subscriptions","organizations_url":"https://api.github.com/users/jas-odoo/orgs","repos_url":"https://api.github.com/users/jas-odoo/repos","events_url":"https://api.github.com/users/jas-odoo/events{/privacy}","received_events_url":"https://api.github.com/users/jas-odoo/received_events","type":"User","site_admin":false},"body":"done","created_at":"2015-03-20T06:32:11Z","updated_at":"2015-03-20T06:32:11Z","html_url":"https://github.com/odoo/odoo/pull/3086#discussion_r26821137","pull_request_url":"https://api.github.com/repos/odoo/odoo/pulls/3086","_links":{"self":{"href":"https://api.github.com/repos/odoo/odoo/pulls/comments/26821137"},"html":{"href":"https://github.com/odoo/odoo/pull/3086#discussion_r26821137"},"pull_request":{"href":"https://api.github.com/repos/odoo/odoo/pulls/3086"}}},"pull_request":{"url":"https://api.github.com/repos/odoo/odoo/pulls/3086","id":22766528,"html_url":"https://github.com/odoo/odoo/pull/3086","diff_url":"https://github.com/odoo/odoo/pull/3086.diff","patch_url":"https://github.com/odoo/odoo/pull/3086.patch","issue_url":"https://api.github.com/repos/odoo/odoo/issues/3086","number":3086,"state":"open","locked":false,"title":"[fleet] New API migration","user":{"login":"rim-odoo","id":7300760,"avatar_url":"https://avatars.githubusercontent.com/u/7300760?v=3","gravatar_id":"","url":"https://api.github.com/users/rim-odoo","html_url":"https://github.com/rim-odoo","followers_url":"https://api.github.com/users/rim-odoo/followers","following_url":"https://api.github.com/users/rim-odoo/following{/other_user}","gists_url":"https://api.github.com/users/rim-odoo/gists{/gist_id}","starred_url":"https://api.github.com/users/rim-odoo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rim-odoo/subscriptions","organizations_url":"https://api.github.com/users/rim-odoo/orgs","repos_url":"https://api.github.com/users/rim-odoo/repos","events_url":"https://api.github.com/users/rim-odoo/events{/privacy}","received_events_url":"https://api.github.com/users/rim-odoo/received_events","type":"User","site_admin":false},"body":"- Task: https://www.odoo.com/web#id=10075&view_type=form&model=project.task&action=333&active_id=131\r\n- Pad: https://pad.odoo.com/p/r.xFp6558VZvondGaA","created_at":"2014-10-15T08:02:17Z","updated_at":"2015-03-20T06:32:12Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f39ceae7132bf95e60440aba1a2005ccfdea7b57","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/odoo/odoo/pulls/3086/commits","review_comments_url":"https://api.github.com/repos/odoo/odoo/pulls/3086/comments","review_comment_url":"https://api.github.com/repos/odoo/odoo/pulls/comments{/number}","comments_url":"https://api.github.com/repos/odoo/odoo/issues/3086/comments","statuses_url":"https://api.github.com/repos/odoo/odoo/statuses/c814981f589346dd79630717d3adfc59957ae16c","head":{"label":"odoo-dev:master-fleet_new_api-sha","ref":"master-fleet_new_api-sha","sha":"c814981f589346dd79630717d3adfc59957ae16c","user":{"login":"odoo-dev","id":7466976,"avatar_url":"https://avatars.githubusercontent.com/u/7466976?v=3","gravatar_id":"","url":"https://api.github.com/users/odoo-dev","html_url":"https://github.com/odoo-dev","followers_url":"https://api.github.com/users/odoo-dev/followers","following_url":"https://api.github.com/users/odoo-dev/following{/other_user}","gists_url":"https://api.github.com/users/odoo-dev/gists{/gist_id}","starred_url":"https://api.github.com/users/odoo-dev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odoo-dev/subscriptions","organizations_url":"https://api.github.com/users/odoo-dev/orgs","repos_url":"https://api.github.com/users/odoo-dev/repos","events_url":"https://api.github.com/users/odoo-dev/events{/privacy}","received_events_url":"https://api.github.com/users/odoo-dev/received_events","type":"Organization","site_admin":false},"repo":{"id":19853934,"name":"odoo","full_name":"odoo-dev/odoo","owner":{"login":"odoo-dev","id":7466976,"avatar_url":"https://avatars.githubusercontent.com/u/7466976?v=3","gravatar_id":"","url":"https://api.github.com/users/odoo-dev","html_url":"https://github.com/odoo-dev","followers_url":"https://api.github.com/users/odoo-dev/followers","following_url":"https://api.github.com/users/odoo-dev/following{/other_user}","gists_url":"https://api.github.com/users/odoo-dev/gists{/gist_id}","starred_url":"https://api.github.com/users/odoo-dev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odoo-dev/subscriptions","organizations_url":"https://api.github.com/users/odoo-dev/orgs","repos_url":"https://api.github.com/users/odoo-dev/repos","events_url":"https://api.github.com/users/odoo-dev/events{/privacy}","received_events_url":"https://api.github.com/users/odoo-dev/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/odoo-dev/odoo","description":"RD Dev Repository","fork":true,"url":"https://api.github.com/repos/odoo-dev/odoo","forks_url":"https://api.github.com/repos/odoo-dev/odoo/forks","keys_url":"https://api.github.com/repos/odoo-dev/odoo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/odoo-dev/odoo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/odoo-dev/odoo/teams","hooks_url":"https://api.github.com/repos/odoo-dev/odoo/hooks","issue_events_url":"https://api.github.com/repos/odoo-dev/odoo/issues/events{/number}","events_url":"https://api.github.com/repos/odoo-dev/odoo/events","assignees_url":"https://api.github.com/repos/odoo-dev/odoo/assignees{/user}","branches_url":"https://api.github.com/repos/odoo-dev/odoo/branches{/branch}","tags_url":"https://api.github.com/repos/odoo-dev/odoo/tags","blobs_url":"https://api.github.com/repos/odoo-dev/odoo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/odoo-dev/odoo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/odoo-dev/odoo/git/refs{/sha}","trees_url":"https://api.github.com/repos/odoo-dev/odoo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/odoo-dev/odoo/statuses/{sha}","languages_url":"https://api.github.com/repos/odoo-dev/odoo/languages","stargazers_url":"https://api.github.com/repos/odoo-dev/odoo/stargazers","contributors_url":"https://api.github.com/repos/odoo-dev/odoo/contributors","subscribers_url":"https://api.github.com/repos/odoo-dev/odoo/subscribers","subscription_url":"https://api.github.com/repos/odoo-dev/odoo/subscription","commits_url":"https://api.github.com/repos/odoo-dev/odoo/commits{/sha}","git_commits_url":"https://api.github.com/repos/odoo-dev/odoo/git/commits{/sha}","comments_url":"https://api.github.com/repos/odoo-dev/odoo/comments{/number}","issue_comment_url":"https://api.github.com/repos/odoo-dev/odoo/issues/comments{/number}","contents_url":"https://api.github.com/repos/odoo-dev/odoo/contents/{+path}","compare_url":"https://api.github.com/repos/odoo-dev/odoo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/odoo-dev/odoo/merges","archive_url":"https://api.github.com/repos/odoo-dev/odoo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/odoo-dev/odoo/downloads","issues_url":"https://api.github.com/repos/odoo-dev/odoo/issues{/number}","pulls_url":"https://api.github.com/repos/odoo-dev/odoo/pulls{/number}","milestones_url":"https://api.github.com/repos/odoo-dev/odoo/milestones{/number}","notifications_url":"https://api.github.com/repos/odoo-dev/odoo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/odoo-dev/odoo/labels{/name}","releases_url":"https://api.github.com/repos/odoo-dev/odoo/releases{/id}","created_at":"2014-05-16T11:16:19Z","updated_at":"2015-03-17T10:53:55Z","pushed_at":"2015-03-20T06:29:12Z","git_url":"git://github.com/odoo-dev/odoo.git","ssh_url":"git@github.com:odoo-dev/odoo.git","clone_url":"https://github.com/odoo-dev/odoo.git","svn_url":"https://github.com/odoo-dev/odoo","homepage":"https://www.odoo.com","size":1082413,"stargazers_count":45,"watchers_count":45,"language":null,"has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":18,"mirror_url":null,"open_issues_count":14,"forks":18,"open_issues":14,"watchers":45,"default_branch":"master"}},"base":{"label":"odoo:master","ref":"master","sha":"04da153fe995e0aab56e9b03a7fec532eab9356b","user":{"login":"odoo","id":6368483,"avatar_url":"https://avatars.githubusercontent.com/u/6368483?v=3","gravatar_id":"","url":"https://api.github.com/users/odoo","html_url":"https://github.com/odoo","followers_url":"https://api.github.com/users/odoo/followers","following_url":"https://api.github.com/users/odoo/following{/other_user}","gists_url":"https://api.github.com/users/odoo/gists{/gist_id}","starred_url":"https://api.github.com/users/odoo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odoo/subscriptions","organizations_url":"https://api.github.com/users/odoo/orgs","repos_url":"https://api.github.com/users/odoo/repos","events_url":"https://api.github.com/users/odoo/events{/privacy}","received_events_url":"https://api.github.com/users/odoo/received_events","type":"Organization","site_admin":false},"repo":{"id":19745004,"name":"odoo","full_name":"odoo/odoo","owner":{"login":"odoo","id":6368483,"avatar_url":"https://avatars.githubusercontent.com/u/6368483?v=3","gravatar_id":"","url":"https://api.github.com/users/odoo","html_url":"https://github.com/odoo","followers_url":"https://api.github.com/users/odoo/followers","following_url":"https://api.github.com/users/odoo/following{/other_user}","gists_url":"https://api.github.com/users/odoo/gists{/gist_id}","starred_url":"https://api.github.com/users/odoo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odoo/subscriptions","organizations_url":"https://api.github.com/users/odoo/orgs","repos_url":"https://api.github.com/users/odoo/repos","events_url":"https://api.github.com/users/odoo/events{/privacy}","received_events_url":"https://api.github.com/users/odoo/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/odoo/odoo","description":"Odoo (formerly OpenERP). Open Source Apps To Grow Your Business.","fork":false,"url":"https://api.github.com/repos/odoo/odoo","forks_url":"https://api.github.com/repos/odoo/odoo/forks","keys_url":"https://api.github.com/repos/odoo/odoo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/odoo/odoo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/odoo/odoo/teams","hooks_url":"https://api.github.com/repos/odoo/odoo/hooks","issue_events_url":"https://api.github.com/repos/odoo/odoo/issues/events{/number}","events_url":"https://api.github.com/repos/odoo/odoo/events","assignees_url":"https://api.github.com/repos/odoo/odoo/assignees{/user}","branches_url":"https://api.github.com/repos/odoo/odoo/branches{/branch}","tags_url":"https://api.github.com/repos/odoo/odoo/tags","blobs_url":"https://api.github.com/repos/odoo/odoo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/odoo/odoo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/odoo/odoo/git/refs{/sha}","trees_url":"https://api.github.com/repos/odoo/odoo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/odoo/odoo/statuses/{sha}","languages_url":"https://api.github.com/repos/odoo/odoo/languages","stargazers_url":"https://api.github.com/repos/odoo/odoo/stargazers","contributors_url":"https://api.github.com/repos/odoo/odoo/contributors","subscribers_url":"https://api.github.com/repos/odoo/odoo/subscribers","subscription_url":"https://api.github.com/repos/odoo/odoo/subscription","commits_url":"https://api.github.com/repos/odoo/odoo/commits{/sha}","git_commits_url":"https://api.github.com/repos/odoo/odoo/git/commits{/sha}","comments_url":"https://api.github.com/repos/odoo/odoo/comments{/number}","issue_comment_url":"https://api.github.com/repos/odoo/odoo/issues/comments{/number}","contents_url":"https://api.github.com/repos/odoo/odoo/contents/{+path}","compare_url":"https://api.github.com/repos/odoo/odoo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/odoo/odoo/merges","archive_url":"https://api.github.com/repos/odoo/odoo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/odoo/odoo/downloads","issues_url":"https://api.github.com/repos/odoo/odoo/issues{/number}","pulls_url":"https://api.github.com/repos/odoo/odoo/pulls{/number}","milestones_url":"https://api.github.com/repos/odoo/odoo/milestones{/number}","notifications_url":"https://api.github.com/repos/odoo/odoo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/odoo/odoo/labels{/name}","releases_url":"https://api.github.com/repos/odoo/odoo/releases{/id}","created_at":"2014-05-13T15:38:58Z","updated_at":"2015-03-20T03:54:26Z","pushed_at":"2015-03-20T06:29:14Z","git_url":"git://github.com/odoo/odoo.git","ssh_url":"git@github.com:odoo/odoo.git","clone_url":"https://github.com/odoo/odoo.git","svn_url":"https://github.com/odoo/odoo","homepage":"https://www.odoo.com","size":1115493,"stargazers_count":2282,"watchers_count":2282,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1760,"mirror_url":null,"open_issues_count":2075,"forks":1760,"open_issues":2075,"watchers":2282,"default_branch":"8.0"}},"_links":{"self":{"href":"https://api.github.com/repos/odoo/odoo/pulls/3086"},"html":{"href":"https://github.com/odoo/odoo/pull/3086"},"issue":{"href":"https://api.github.com/repos/odoo/odoo/issues/3086"},"comments":{"href":"https://api.github.com/repos/odoo/odoo/issues/3086/comments"},"review_comments":{"href":"https://api.github.com/repos/odoo/odoo/pulls/3086/comments"},"review_comment":{"href":"https://api.github.com/repos/odoo/odoo/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/odoo/odoo/pulls/3086/commits"},"statuses":{"href":"https://api.github.com/repos/odoo/odoo/statuses/c814981f589346dd79630717d3adfc59957ae16c"}}}}