From 613f906c25b64741bceb0153177f4c7427bf76a2 Mon Sep 17 00:00:00 2001
From: Benjamin <benjamin@le-filament.com>
Date: Fri, 20 Mar 2020 17:24:04 +0100
Subject: [PATCH] [cgscop #42] ajout calcul auto des distances sur NdF

---
 README.rst           | 18 ++++++++-
 models/hr_expense.py | 93 +++++++++++++++++++++++++++++++++++++++++++-
 models/product.py    |  6 +++
 views/hr_expense.xml | 17 +++++++-
 views/product.xml    |  1 +
 5 files changed, 132 insertions(+), 3 deletions(-)

diff --git a/README.rst b/README.rst
index 6fa1bdd..737b875 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 8bf0938..efa39d8 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 22c4c09..c3acb37 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 3f573c0..ab2af1f 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 7dd59d1..bc15a49 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>
-- 
GitLab