diff --git a/README.rst b/README.rst index 6fa1bdd0d13527009a7e438b789ae2bfa5983e16..737b87571b3ea330f634a32af9ad8b9c911ef23b 100644 --- a/README.rst +++ b/README.rst @@ -15,13 +15,29 @@ Ce module permet hérite le module *hr_expense* d'Odoo pour ajouter les fonction * modification de la form vue *hr_expense* * ajout d'un journal de note de frais * modification du modèle *hr_expense_sheet* pour mettre le journal de NdF par défaut +* ajout du calcul automatique des kilomètres +Calcul des KM +------------- + +Cette option se configure dans les articles de notes de frais. Pour activer le calcul automatique, il faut configurer un article avec un type de calcul **Prix fixe** et cocher **Calcul automatique de la distance**. + +Sur la note de frais, lorsque l'article est sélectionné, les adresses de départ et d'arrivée apparaîssent ainsi que le bouton de calcul. Par défaut, l'adresse de départ est l'adresse de l'UR, l'adresse d'arrivée est l'adresse deu contact de la ligne de temps. + +Les 2 adresses doivent être renseignées pour lancer le calcul. + +Un arrondi est effctué aux 5km supérieurs. + +Le calcul s'appuie sur 2 API : + +* **api-adresse.data.gouv.fr** : pour la transformation de l'adresse en coordonnées +* **router.project-osrm.org** : pour le calcul de la distance Credits ======= Funders ------------- +------- The development of this module has been financially supported by: diff --git a/models/hr_expense.py b/models/hr_expense.py index 8bf0938d069ad9b6a44513a49d8338ba607a98bf..efa39d88b5740538b5a10a1cdff0e758302b1591 100644 --- a/models/hr_expense.py +++ b/models/hr_expense.py @@ -1,7 +1,13 @@ # © 2019 Le Filament (<http://www.le-filament.com>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import models, fields, api +import re +import requests +import logging + +from odoo import models, fields, api, exceptions + +_logger = logging.getLogger(__name__) class CGScopExpense(models.Model): @@ -28,14 +34,29 @@ class CGScopExpense(models.Model): store=True) expense_formula = fields.Selection( related='product_id.expense_formula') + is_ik = fields.Boolean( + related='product_id.is_ik') quantity_computed = fields.Float( string="Quantité", compute="_compute_values") unit_amount_computed = fields.Float( string="Prix", compute="_compute_values") + from_address = fields.Text( + string="Adresse de départ", + compute='_compute_from_address', + store=True) + to_address = fields.Text( + string="Adresse d'arrivée", + compute='_compute_to_address', + store=True) # ------------------------------------------------------ + # Default Fields + # ------------------------------------------------------ + def _default_from_address(self): + return self.env.uid + # ------------------------------------------------------ # Computed Fields # ------------------------------------------------------ @api.depends('quantity', 'unit_amount') @@ -43,6 +64,18 @@ class CGScopExpense(models.Model): self.quantity_computed = self.quantity self.unit_amount_computed = self.unit_amount + @api.depends('employee_id') + def _compute_from_address(self): + if self.employee_id: + self.from_address = self._format_address( + self.employee_id.address_id) + + @api.depends('timesheet_id') + def _compute_to_address(self): + if self.timesheet_id: + self.to_address = self._format_address( + self.timesheet_id.partner_id) + # ------------------------------------------------------ # Onchange Fields # ------------------------------------------------------ @@ -67,3 +100,61 @@ class CGScopExpense(models.Model): elif self.product_id.expense_formula == 'fixed_rate': self.quantity = 1.0 self.product_id.unit_amount = self.product_id.standard_price + + # ------------------------------------------------------ + # Button function + # ------------------------------------------------------ + def get_ik(self): + if not self.from_address or not self.to_address: + raise exceptions.Warning( + "Les adresses de départ et d'arrivée doivent être renseignées") + else: + # retourne la distance entre les 2 adresses + distance = self._get_distance( + self._get_coord(self.from_address), + self._get_coord(self.to_address)) + self.quantity = distance + + # ------------------------------------------------------ + # Global function + # ------------------------------------------------------ + def _format_address(self, partner): + address = partner.street + if partner.street2: + address += '\n' + partner.street2 + if partner.street3: + address += '\n' + partner.street3 + address += '\n' + partner.zip + ' ' + partner.city + return address + + def _get_coord(self, address): + addr = re.sub(r"\W", "+", address, flags=re.I) + url = 'https://api-adresse.data.gouv.fr/search/?q=' + addr + try: + response = requests.get(url) + coord = response.json().get('features')[0].get('geometry').get('coordinates') + return str(coord[0]) + ',' + str(coord[1]) + except Exception as err: + _logger.warning( + "Erreur de connexion. URL: %s", + err.__str__(), + ) + raise exceptions.Warning( + "Erreur de connexion avec l'API") + return False + + def _get_distance(self, coord1, coord2): + url = ('http://router.project-osrm.org/route/v1/car/' + coord1 + + ';' + coord2 + '?alternatives=false&overview=false') + try: + response = requests.get(url) + dist = response.json().get('routes')[0].get('legs')[0].get('distance') + return dist / 1000 + except Exception as err: + _logger.warning( + "Erreur de connexion. URL: %s", + err.__str__(), + ) + raise exceptions.Warning( + "Erreur de connexion avec l'API") + return False diff --git a/models/product.py b/models/product.py index 22c4c09be70a26bb485619bb4697b8c27cdda72b..c3acb37609a1439e490e2f7e2c24c9f97024aa4a 100644 --- a/models/product.py +++ b/models/product.py @@ -12,6 +12,7 @@ class CGScopProductTemplate(models.Model): return self.env['res.company']._ur_default_get() expense_gap = fields.Float("Plafond de dépense") + is_ik = fields.Boolean("Calcul automatique de la distance") expense_formula = fields.Selection( [('free', 'Libre'), ('fixed_rate', 'Forfait'), @@ -33,6 +34,11 @@ class CGScopProductTemplate(models.Model): on_delete='restrict', default=_default_ur) + @api.onchange('expense_formula') + def _onchange_expense_formula(self): + if self.expense_formula != 'fixed_price': + self.is_ik = False + class CGScopProductProduct(models.Model): _inherit = 'product.product' diff --git a/views/hr_expense.xml b/views/hr_expense.xml index 3f573c0e34f22df2b80e5ceb12590cd4e7494202..ab2af1fb08a6c3854e561b6cf682f54a3e23cc06 100644 --- a/views/hr_expense.xml +++ b/views/hr_expense.xml @@ -61,11 +61,26 @@ <attribute name="attrs">{'invisible': [('expense_formula', '=', 'fixed_rate')]}</attribute> </label> <field name="quantity" position="attributes"> - <attribute name="attrs">{'invisible': [('expense_formula', '=', 'fixed_rate')]}</attribute> + <attribute name="attrs">{'invisible': [('expense_formula', '=', 'fixed_rate')], 'readonly': [('is_ik', '=', True)]}</attribute> </field> + <!-- gestion des adresses de départ et d'arrivée --> + <xpath expr="//group/group[2]" position="after"> + <group attrs="{'invisible':[('is_ik','!=', True)], 'required':[('is_ik','=', True)]}"> + <field name="from_address" readonly="False"/> + </group> + <group attrs="{'invisible':[('is_ik','!=', True)], 'required':[('is_ik','=', True)]}"> + <field name="to_address" readonly="False"/> + </group> + <group attrs="{'invisible':[('is_ik','!=', True)], 'required':[('is_ik','=', True)]}"> + <button type="object" name="get_ik" string="Calcul distance" class="btn-primary"></button> + </group> + <group attrs="{'invisible':[('is_ik','!=', True)], 'required':[('is_ik','=', True)]}"></group> + </xpath> + <field name="tax_ids" position="before"> <field name="expense_gap" attrs="{'invisible': [('expense_gap', '=', 0.0)]}" readonly="True" /> <field name="expense_formula" invisible="True" /> + <field name="is_ik" invisible="True" /> </field> <field name="analytic_account_id" position="before"> <field name="timesheet_id" options="{'no_create': True, 'no_open': True}" domain="[('employee_id', '=', employee_id )]"/> diff --git a/views/product.xml b/views/product.xml index 7dd59d10971d5b2148132646e98e3428f32314a0..bc15a49bb1e9c939db94d8faa8d2457a3a046217 100644 --- a/views/product.xml +++ b/views/product.xml @@ -50,6 +50,7 @@ </field> <field name="uom_po_id" position="after"> <field name="expense_gap" /> + <field name="is_ik" widget="boolean_toggle" attrs="{'invisible':[('expense_formula','!=','fixed_price')],]}"/> <field name="ur_id" options="{'no_open': True, 'no_create': True}"/> </field> </field>