diff --git a/README.rst b/README.rst
index de02ce987cef9288c60958988d1a3ff29f1f006f..15952e40aed8f1e2f825eb6bcb96a6f3f85c93da 100644
--- a/README.rst
+++ b/README.rst
@@ -3,13 +3,14 @@
    :alt: License: AGPL-3
 
 
-==========================
-OACC - Gestion portail CDC
-==========================
+================================
+OACC - Affichage courbes portail
+================================
 
 Ce module hérite du module *portal_oacc* et permet:
 
  - Création des pages portail pour la visualisation des courbes de charge
+ - Export des courbes de charge
 
 
 Description
@@ -23,11 +24,16 @@ Exemple
 
 Credits
 =======
+Le développement de ce module a été financé par / The development of this module has been financially supported by:
+ - ENERCOOP Midi-Pyrénées (https://enercoop.fr)
 
 Contributors
 ------------
 
+* Benjamin Rivier <benjamin@le-filament.com>
 * Juliana Poudou <juliana@le-filament.com>
+* Julien Ortet <julien@le-filament.com>
+* Rémi Cazenave <remi@le-filament.com>
 
 
 Maintainer
diff --git a/__manifest__.py b/__manifest__.py
index 7176cfb699989c4116dabe2fff9d3723646f343b..6fddf8e32e3299ddeddecd823cb6883705cb1271 100644
--- a/__manifest__.py
+++ b/__manifest__.py
@@ -5,10 +5,11 @@
     "website": "https://le-filament.com",
     "version": "16.0.1.0.1",
     "license": "AGPL-3",
-    "depends": ["web", "oacc_portal", "oacc_overview_cdc"],
+    "depends": ["web", "oacc_portal"],
     "data": [
         # datas
         # views
+        "views/acc_operation_views.xml",
         # templates
         "templates/portal_layout.xml",
         "templates/operation_templates_page.xml",
diff --git a/controllers/main.py b/controllers/main.py
index 18def2422d68d88b9bc69531f8192a3de1d402a9..b2a873d1aa93ef88a4b1b0e6354bf3a014be6465 100644
--- a/controllers/main.py
+++ b/controllers/main.py
@@ -12,7 +12,7 @@ class CustomerPortal(CustomerPortal):
         # Si des données existent, récupérer les dates min/max/début/fin
         # de l'opération
         if is_data_cdc:
-            vals["data_values"] = operation.get_values_init_graph(partner_id)
+            vals["data_values"] = operation._get_values_init_graph(partner_id)
 
         else:
             vals["data_values"] = {
@@ -146,7 +146,7 @@ class CustomerPortal(CustomerPortal):
     @http.route(
         ["/chart/update_json"],
         type="json",
-        auth="public",
+        auth="user",
         methods=["POST"],
         website=True,
         csrf=False,
@@ -169,7 +169,7 @@ class CustomerPortal(CustomerPortal):
         """
         # TODO: add check that prm_id and partner_id are allowed for this user
         operation = request.env["acc.operation"].browse(operation_id)
-        vals = operation.graph_view_global(
+        vals = operation._graph_view_global(
             start_date, end_date, partner_id, prm_id, data_type
         )
 
@@ -197,8 +197,9 @@ class CustomerPortal(CustomerPortal):
         This route is called :
             - When click on button export
         """
+        # TODO: add check that prm_id and partner_id are allowed for this user
         operation = request.env["acc.operation"].sudo().browse(int(operation_id))
-        file_values = operation.export_cdc(
+        file_values = operation._export_cdc(
             start_date, end_date, partner_id, prm_id, data_type
         )
 
diff --git a/models/__init__.py b/models/__init__.py
index c9f67d1029451187faac73b30248b565bd1340d9..b9c11d9d929c89108a38db6f9596ab8bfd372d52 100644
--- a/models/__init__.py
+++ b/models/__init__.py
@@ -1 +1,2 @@
+from . import acc_enedis_cdc
 from . import acc_operation
diff --git a/models/acc_enedis_cdc.py b/models/acc_enedis_cdc.py
new file mode 100644
index 0000000000000000000000000000000000000000..42c445d618be7969068d74625ff408c0a90312da
--- /dev/null
+++ b/models/acc_enedis_cdc.py
@@ -0,0 +1,565 @@
+# Copyright 2021- Le Filament (https://le-filament.com)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
+from datetime import datetime
+
+from dateutil.relativedelta import relativedelta
+
+from odoo import _, api, fields, models
+from odoo.exceptions import ValidationError
+from odoo.osv import expression
+from odoo.tools import date_utils
+
+
+class AccEnedisCdc(models.Model):
+    _inherit = "acc.enedis.cdc"
+
+    # ------------------------------------------------------
+    # Actions
+    # ------------------------------------------------------
+
+    # ------------------------------------------------------
+    # Business methods
+    # ------------------------------------------------------
+    @api.model
+    def _get_last_month_power_tot(self, operation_id):
+        """
+        Fonction retournant la consommation totale, l'autoconsommation totale
+        et la production totale du dernier mois.
+        :return: la somme de la conso totale de tous les consommateurs,
+                la somme de l autoconso totale de tous les consommateurs,
+                la sommme de la prod totale de tous les producteurs
+                la date de début de mois
+        """
+        # Get last date slot recorded
+        last_record = self._get_last_cdc_record(operation_id)
+        start_date, end_date = self._get_interval("month", last_record)
+        end_date_month = end_date + relativedelta(days=1)
+        query = (
+            self._select_clause(
+                date_slot="month", curve_types=["cons", "prod", "autocons"]
+            )
+            + self._from_clause()
+            + self._where_clause(
+                operation_id=operation_id,
+                start_date=start_date,
+                end_date=end_date_month,
+            )
+            + self._group_clause(date_slot="month")
+            + self._order_clause()
+        )
+        self.env.cr.execute(query)
+        raw_data = self.env.cr.fetchone()
+        return raw_data[1], raw_data[2], raw_data[3], start_date
+
+    @api.model
+    def _get_last_cdc_record(self, operation_id, partner_id=None):
+        """
+        @param: int operation_id identifiant de l'opération
+                int partner_id identifiant du contact
+        @returns: last acc.enedis.cdc record in operations
+        """
+        domain = [("acc_operation_id", "=", operation_id)]
+
+        if partner_id:
+            domain = expression.AND([domain, [("partner_id", "=", partner_id)]])
+        # Get last date slot recorded
+        last_record = self.sudo().search(
+            domain,
+            limit=1,
+            order="date_slot DESC",
+        )
+
+        if not last_record:
+            raise ValidationError(_("L'opération ne possède pas de données"))
+
+        return last_record
+
+    @api.model
+    def _get_interval(self, scale, cdc_record):
+        """
+        Fonction retournant une date de début et une date de fin.
+        Ces dates sont calculées en fonction de l'échelle choisie et du dernier
+         élément enregistré
+        - day:  la date de début et la date de fin = dernier jour de données
+        - week: date de fin = fin du jour du dernier jour de données
+                date de début = date de fin moins 7 jours
+        - month: date de fin = fin du mois du dernier jour de données
+                date de début = début du mois du dernier jour de données
+        - year: date de fin = fin du mois du dernier jour de données
+                date de début = début de l'année du dernier jour de données
+        :param str scale: type d'affichage des graphes
+                       (day/week/month/year)
+        :param object cdc_record: courbe de charge (table acc.enedis.cdc)
+        @returns: une date de début et une date de fin
+        """
+
+        # Convert end datetime to timezone
+        cdc_datetime = fields.Datetime.context_timestamp(self, cdc_record.date_slot)
+        start_month, end_month = date_utils.get_month(cdc_datetime)
+
+        if scale == "year":
+            end_date = end_month
+            start_date = start_month.replace(month=1)
+        elif scale == "month":
+            end_date = end_month
+            start_date = start_month
+        elif scale == "week":
+            end_date = cdc_datetime.date()
+            start_date = cdc_datetime.date() - relativedelta(days=6)
+        elif scale == "day":
+            end_date = cdc_datetime.date()
+            start_date = cdc_datetime.date()
+        else:
+            raise ValueError(
+                _(
+                    "La période d'affichage est incorrecte : %s, "
+                    "valeurs attendues : day/week/month/year"
+                )
+                % scale
+            )
+
+        return start_date, end_date
+
+    @api.model
+    def _get_step_from_date(self, start_date, end_date):
+        """
+        Fonction retournant le pas des courbes en fonction de 2 dates.
+        :return:
+            display_hourly_curves (boolean) : whether or not hourly curves
+                should be displayed
+            step: hour/month/year
+            step_display_curve: hour/day/month/year
+        """
+        display_hourly_curves = False
+        step = "hour"
+        step_display_curve = "hour"
+        # Calculate delta between 2 dates
+        delta = (end_date - start_date).days
+        if delta > 1 and delta <= 31:
+            step_display_curve = "day"
+            display_hourly_curves = True
+        elif delta > 31 and delta <= 366:
+            step = "month"
+            step_display_curve = "month"
+        elif delta > 366:
+            step = "year"
+            step_display_curve = "year"
+
+        return display_hourly_curves, step, step_display_curve
+
+    @api.model
+    def _select_clause(self, date_slot, curve_types):
+        """
+        Function to build SELECT section of query for retrieving curves
+        @param
+            char date_slot : granularity
+                (one of "minute", "hour", "day", "month", "year")
+            [char] curve_types : list of type of curves
+                (allowed values : 'cons', 'autocons', 'allocons',
+                    'prod', 'surplus', 'autoprod')
+        """
+        if date_slot not in ("minute", "hour", "day", "month", "year"):
+            raise ValidationError(_("Incorrect date_slot in SELECT section"))
+        result = f"""
+            SELECT date_trunc('{date_slot}',
+                cdc.date_slot AT TIME ZONE 'UTC' AT TIME ZONE 'Europe/Paris'
+            ) AS date_slot
+            """
+        for curve_type in curve_types:
+            if curve_type in ("cons", "autocons", "prod", "surplus"):
+                result += f"""
+                    , (SUM(CASE WHEN cdc.comp_data_type = '{curve_type}'
+                    THEN cdc.power ELSE 0 END)) /2 / 1000 as {curve_type}
+                    """
+            elif curve_type == "allocons":
+                result += """
+                    ,(SUM(CASE
+                        WHEN cdc.comp_data_type = 'cons'
+                        THEN cdc.power ELSE 0 END)
+                     - SUM(CASE
+                        WHEN cdc.comp_data_type = 'autocons'
+                        THEN cdc.power ELSE 0 END)) / 2 / 1000 as allocons
+                    """
+            elif curve_type == "autoprod":
+                result += """
+                    ,(SUM(CASE
+                        WHEN cdc.comp_data_type = 'prod'
+                        THEN cdc.power ELSE 0 END)
+                     - SUM(CASE
+                        WHEN cdc.comp_data_type = 'surplus'
+                        THEN cdc.power ELSE 0 END)) / 2 / 1000 as autoprod
+                    """
+        return result
+
+    @api.model
+    def _from_clause(self):
+        """
+        Function to build FROM section of query for retrieving curves
+        """
+        return """
+            FROM
+                acc_enedis_cdc cdc
+            INNER JOIN acc_operation ope
+                 ON ope.id = cdc.acc_operation_id
+            """
+
+    @api.model
+    def _where_clause(
+        self,
+        operation_id,
+        start_date,
+        end_date,
+        prm_id=None,
+        extra_curve_type=None,
+        partner_id=None,
+    ):
+        """
+        Function to build WHERE section of query for retrieving curves
+        @param
+            int operation_id : id of operation for which curves should be retrieved
+            date start_date : first date to be retrieved
+            date end_date : last date to be retrieved
+            int prm_id : id of PRM to be retrieved (optional)
+            [char] extra_curve_type : extra curve to be retrieved (optional)
+                (allowed values : 'cons', 'autocons', 'allocons',
+                    'prod', 'surplus', 'autoprod')
+            int partner_id : id of partner to be retrieved (optional)
+        """
+        if (
+            not isinstance(operation_id, int)
+            and not isinstance(start_date, datetime)
+            and not isinstance(end_date, datetime)
+        ):
+            raise ValidationError(_("WHERE clause parameters incorrect"))
+
+        start_datetime = self._convert_time(start_date)
+        end_datetime = self._convert_time(end_date)
+        result = f"""
+            WHERE cdc.acc_operation_id = {operation_id}
+            AND cdc.date_slot >= '{start_datetime}'
+            AND cdc.date_slot < '{end_datetime}'
+            """
+
+        if partner_id and isinstance(partner_id, int):
+            result += f" AND ((cdc.partner_id = {partner_id} "
+            if prm_id and isinstance(prm_id, int):
+                result += f" AND cdc.acc_counter_id = {prm_id}) "
+            else:
+                result = f"{result})"
+            if extra_curve_type and extra_curve_type in (
+                "cons",
+                "autocons",
+                "allocons",
+                "prod",
+                "surplus",
+                "autoprod",
+            ):
+                result = f"{result} OR cdc.comp_data_type = '{extra_curve_type}' )"
+            else:
+                result = f"{result})"
+
+        return result
+
+    @api.model
+    def _group_clause(self, date_slot):
+        """
+        Function to build GROUP BY section of query for retrieving curves
+        @param
+            char date_slot : granularity
+                (one of "minute", "hour", "day", "month", "year")
+        """
+        if date_slot not in ("minute", "hour", "day", "month", "year"):
+            raise ValidationError(_("Incorrect date_slot in GROUP BY section"))
+        return f"""
+            GROUP BY date_trunc('{date_slot}',
+                cdc.date_slot AT TIME ZONE 'UTC' AT TIME ZONE 'Europe/Paris')
+            """
+
+    @api.model
+    def _order_clause(self):
+        return "ORDER BY date_slot ASC;"
+
+    @api.model
+    def _cdc_by_query_cons(
+        self,
+        operation_id,
+        slot_type,
+        start_date,
+        end_date,
+        prm_id=None,
+        partner_id=None,
+        curve_types=None,
+    ):
+        """
+        Fonction permettant de récupérer les données pour les consommateurs
+
+        :param: int operation_id: opération concernée
+                char slot_type: type de slot pour la query ("minute", "hour", "month" ou
+                    "year")
+                datetime start_date: date début
+                datetime end_date: date de fin
+                int prm_id : PRM de soutirage à récupérer
+                int partner_id: contact associé à la courbe
+                [char] curve_types: type de données
+        @returns: resultat de la requête
+                (labels et data pour les charts à afficher)
+
+        """
+        if curve_types is None:
+            curve_types = ["cons", "autocons", "prod"]
+        query = (
+            self._select_clause(date_slot=slot_type, curve_types=curve_types)
+            + self._from_clause()
+            + self._where_clause(
+                operation_id,
+                start_date,
+                end_date,
+                prm_id,
+                "prod" if "prod" in curve_types else None,
+                partner_id,
+            )
+            + "AND cdc.comp_data_type IN %s"
+            + self._group_clause(date_slot=slot_type)
+            + self._order_clause()
+        )
+        self.env.cr.execute(query, (tuple(curve_types),))
+        return self.env.cr.fetchall()
+
+    @api.model
+    def _get_cdc_by_query_cons(
+        self,
+        operation_id,
+        slot_type,
+        start_date,
+        end_date,
+        prm_id=None,
+        partner_id=None,
+    ):
+        """
+        Fonction permettant de récupérer les données pour la
+        construction des chart pour une ou des opérations données
+        pour les consommateurs
+        :param: int operation_id : Opération concernée
+                char slot_type: type de slot pour la query ("minute", "hour", "month" ou
+                    "year")
+                datetime start_date: date début
+                datetime end_date: date de fin
+                int prm_id : PRM de soutirage à récupérer
+                int partner_id: contact associé à la courbe
+        @returns: resultat de la requête
+                (labels et data pour les charts à afficher)
+        """
+        label = []
+        data_autocons = []
+        data_allocons = []
+        data_cons = []
+        data_prod = []
+
+        raw_data = self._cdc_by_query_cons(
+            operation_id, slot_type, start_date, end_date, prm_id, partner_id
+        )
+
+        for row in raw_data:
+            label.append(row[0])
+            data_cons.append({"x": row[0], "y": round(row[1], 2)})
+            data_autocons.append({"x": row[0], "y": round(row[2], 2)})
+            data_allocons.append({"x": row[0], "y": round(row[1] - row[2], 2)})
+            data_prod.append({"x": row[0], "y": round(row[3], 2)})
+
+        cdc_cons = {
+            "label": label,
+            "autocons": data_autocons,
+            "allocons": data_allocons,
+            "cons": data_cons,
+            "prod": data_prod,
+        }
+        return cdc_cons
+
+    @api.model
+    def _get_cdc_by_query_daily_histo_cons(
+        self,
+        operation_id,
+        start_date,
+        end_date,
+        prm_id=None,
+        partner_id=None,
+    ):
+        """
+        Fonction permettant de récupérer les données pour la construction
+        des chart pour une ou des opérations données pour les consommateurs
+        :param: int operation_id : Opération concernée
+                datetime start_date: date début
+                datetime end_date: date de fin
+                int prm_id : PRM de soutirage à récupérer
+                int partner_id: contact associé à la courbe
+        :return: un dictionnaire de données
+                (labels et data pour les charts à afficher)
+        """
+        label_histo = []
+        data_autocons_histo = []
+        data_allocons_histo = []
+
+        query = (
+            self._select_clause(date_slot="day", curve_types=["autocons", "allocons"])
+            + self._from_clause()
+            + self._where_clause(
+                operation_id=operation_id,
+                start_date=start_date,
+                end_date=end_date,
+                prm_id=prm_id,
+                partner_id=partner_id,
+            )
+            + self._group_clause(date_slot="day")
+            + self._order_clause()
+        )
+
+        self.env.cr.execute(query)
+        raw_data = self.env.cr.fetchall()
+        for row in raw_data:
+            data_autocons_histo.append(round(row[1], 2))
+            data_allocons_histo.append(round(row[2], 2))
+            label_histo.append(row[0])
+
+        cdc_cons = {
+            "autocons_histo": data_autocons_histo,
+            "allocons_histo": data_allocons_histo,
+            "label_histo": label_histo,
+        }
+        return cdc_cons
+
+    @api.model
+    def _cdc_by_query_prod(
+        self,
+        operation_id,
+        slot_type,
+        start_date,
+        end_date,
+        prm_id=None,
+        partner_id=None,
+        curve_types=None,
+    ):
+        """
+        Fonction permettant de récupérer les données pour la construction des
+          chart pour une ou des opérations données pour les consommateurs
+        :param: int operation_id : Opération concernée
+                char slot_type: type de slot pour la query ("minute", "hour", "month" ou
+                  "year")
+                datetime start_date: date début
+                datetime end_date: date de fin
+                int prm_id : PRM d'injection à récupérer
+                int partner_id: contact associé à la courbe
+                [char] curve_types: type de données
+        :return: un dictionnaire de données
+                (labels et data pour les charts à afficher)
+        """
+        if curve_types is None:
+            curve_types = ["autoprod", "surplus"]
+        query = (
+            self._select_clause(date_slot=slot_type, curve_types=curve_types)
+            + self._from_clause()
+            + self._where_clause(
+                operation_id=operation_id,
+                start_date=start_date,
+                end_date=end_date,
+                prm_id=prm_id,
+                partner_id=partner_id,
+            )
+            + self._group_clause(date_slot=slot_type)
+            + self._order_clause()
+        )
+
+        self.env.cr.execute(query)
+        return self.env.cr.fetchall()
+
+    @api.model
+    def _get_cdc_by_query_prod(
+        self,
+        operation_id,
+        slot_type,
+        start_date,
+        end_date,
+        prm_id=None,
+        partner_id=None,
+    ):
+        """
+        Fonction permettant de récupérer les données pour la construction des
+          chart pour une ou des opérations données pour les consommateurs
+        :param: int operation_id : Opération concernée
+                char slot_type: type de slot pour la query ("minute", "hour", "month" ou
+                  "year")
+                datetime start_date: date début
+                datetime end_date: date de fin
+                int prm_id : PRM d'injection à récupérer
+                int partner_id: contact associé à la courbe
+        :return: un dictionnaire de données
+                (labels et data pour les charts à afficher)
+        """
+        label = []
+        data_autocons = []
+        data_surplus = []
+
+        raw_data = self._cdc_by_query_prod(
+            operation_id, slot_type, start_date, end_date, prm_id, partner_id
+        )
+
+        for row in raw_data:
+            label.append(row[0])
+            data_autocons.append({"x": row[0], "y": round(row[1], 2)})
+            data_surplus.append({"x": row[0], "y": round(row[2], 2)})
+
+        cdc_prod = {
+            "label": label,
+            "autocons_prod": data_autocons,
+            "surplus": data_surplus,
+        }
+        return cdc_prod
+
+    @api.model
+    def _get_cdc_by_query_daily_histo_prod(
+        self, operation_id, start_date, end_date, prm_id=None, partner_id=None
+    ):
+        """
+        Fonction permettant de récupérer les données pour la construction des
+        chart pour une ou des opérations données pour les consommateurs
+        :param: int operation_id : Opération concernée
+                datetime start_date: date début
+                datetime end_date: date de fin
+                int prm_id : PRM d'injection à récupérer
+                int partner_id: contact associé à la courbe
+        :return: un dictionnaire de données
+                (labels et data pour les charts à afficher)
+        """
+        label_histo = []
+        data_autocons_prod_histo = []
+        data_surplus_histo = []
+
+        query = (
+            self._select_clause(date_slot="day", curve_types=["autoprod", "surplus"])
+            + self._from_clause()
+            + self._where_clause(
+                operation_id=operation_id,
+                start_date=start_date,
+                end_date=end_date,
+                prm_id=prm_id,
+                partner_id=partner_id,
+            )
+            + self._group_clause(date_slot="day")
+            + self._order_clause()
+        )
+        self.env.cr.execute(query)
+        raw_data = self.env.cr.fetchall()
+        for row in raw_data:
+            label_histo.append(row[0])
+            data_autocons_prod_histo.append(round(row[1], 2))
+            data_surplus_histo.append(round(row[2], 2))
+
+        cdc_jour = {
+            "label_histo": label_histo,
+            "autocons_prod_histo": data_autocons_prod_histo,
+            "surplus_histo": data_surplus_histo,
+        }
+        return cdc_jour
+
+    # ------------------------------------------------------
+    # Functions to manage route
+    # ------------------------------------------------------
diff --git a/models/acc_operation.py b/models/acc_operation.py
index daa382ff2b40732087773dd4f781b5a558b1cffc..56027620cd9546b678fb48aa1e3fd684579ed171 100644
--- a/models/acc_operation.py
+++ b/models/acc_operation.py
@@ -1,6 +1,13 @@
 # Copyright 2021- Le Filament (https://le-filament.com)
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
-from odoo import models
+from datetime import datetime
+from io import StringIO
+
+from dateutil.relativedelta import relativedelta
+
+from odoo import fields, http, models
+
+from ..tools import export_cdc
 
 
 class AccOperation(models.Model):
@@ -33,31 +40,189 @@ class AccOperation(models.Model):
     # ------------------------------------------------------
     # Actions
     # ------------------------------------------------------
+    def action_view_curves(self):
+        """
+        Action qui ouvre le portail sur l'opération
+        :return: Vue Qweb
+        """
+        self.ensure_one()
+        if self.id:
+            return {
+                "type": "ir.actions.act_url",
+                "url": "/operation/%s" % (self.id),
+                "target": "new",
+            }
+
+    def _graph_view_global(
+        self,
+        start_date=None,
+        end_date=None,
+        partner_id=None,
+        prm_id=None,
+        data_type=None,
+    ):
+        """
+        Fonction appelée pour l'affichage des courbes globales
+        sur le portail
+        :param: date start_date: date début
+                date end_date: date de fin
+                int partner_id: données uniquement de ce contact
+                int prm_id: données uniquement de ce PRM (mutuellement exclusif de
+                    partner_id)
+                char data_type: type de courbes à afficher:
+                  - "pmo" : vue globale (cons + prod sans filtrage)
+                  - "cons" : vue mon suivi conso (avec filtrage possible)
+                  - "prod" : vue mon suivi production (avec filtrage possible)
+        @returns: dictionnaire pour la construction des graphes
+        """
+        self.ensure_one()
+        result_graph = {}
+        Cdc_model = self.env["acc.enedis.cdc"]
+        start_date = datetime.strptime(start_date, "%d/%m/%Y")
+        end_date = datetime.strptime(end_date, "%d/%m/%Y") + relativedelta(days=1)
+        (
+            display_hourly_curves,
+            step_curve,
+            step_display_curve,
+        ) = Cdc_model._get_step_from_date(start_date=start_date, end_date=end_date)
+
+        chart_data = {}
+        if data_type == "cons" or data_type == "pmo":
+            chart_data_cons = Cdc_model._get_cdc_by_query_cons(
+                self.id, step_curve, start_date, end_date, prm_id, partner_id
+            )
+            if display_hourly_curves:
+                chart_data_histo = Cdc_model._get_cdc_by_query_daily_histo_cons(
+                    self.id, start_date, end_date, prm_id, partner_id
+                )
+                chart_data_cons.update(chart_data_histo)
+            chart_data.update(chart_data_cons)
+        if data_type == "prod" or data_type == "pmo":
+            chart_data_prod = Cdc_model._get_cdc_by_query_prod(
+                self.id, step_curve, start_date, end_date, prm_id, partner_id
+            )
+            if display_hourly_curves:
+                chart_data_histo = Cdc_model._get_cdc_by_query_daily_histo_prod(
+                    self.id, start_date, end_date, prm_id, partner_id
+                )
+                chart_data_prod.update(chart_data_histo)
+            chart_data.update(chart_data_prod)
+
+        result_graph["chart_data"] = chart_data
+        result_graph.update(self.get_date_min_max(partner_id))
+        result_graph.update(
+            {
+                "start_date": start_date,
+                "end_date": end_date,
+                "scale": step_display_curve,
+                "is_curve_line": display_hourly_curves,
+            }
+        )
+        return result_graph
+
+    def _export_cdc(self, start_date, end_date, partner_id, prm_id, data_type):
+        """
+
+        :param start_date:
+        :param end_date:
+        :param partner_id:
+        :param prm_id:
+        :param data_type:
+        :return:
+        """
+        start = datetime.strptime(start_date, "%d/%m/%Y")
+        end = datetime.strptime(end_date, "%d/%m/%Y") + relativedelta(days=1)
+
+        filename = export_cdc.make_filename(
+            self, start_date, end_date, partner_id, prm_id, data_type
+        )
+        header = export_cdc.make_header_lines(self, partner_id, prm_id, data_type)
+        if data_type in ["cons"]:
+            data_file = export_cdc.make_cons_data(
+                self.env["acc.enedis.cdc"]._cdc_by_query_cons(
+                    self.id,
+                    "minute",
+                    start,
+                    end,
+                    prm_id,
+                    partner_id,
+                    curve_types=["cons", "autocons", "allocons"],
+                )
+            )
+        if data_type in ["prod"]:
+            data_file = export_cdc.make_prod_data(
+                self.env["acc.enedis.cdc"]._cdc_by_query_prod(
+                    self.id,
+                    "minute",
+                    start,
+                    end,
+                    prm_id,
+                    partner_id,
+                    curve_types=["autoprod", "surplus", "prod"],
+                )
+            )
+
+        fout = StringIO()
+        for h_line in header:
+            fout.write(f"{h_line}\n")
+        for d_line in data_file:
+            fout.write(f"{d_line}\n")
+
+        fout.seek(0)
+        data = fout.read()
+        fout.close()
+
+        csv_http_headers = [
+            ("Content-Type", "text/csv;charset=utf8"),
+            ("Content-Disposition", http.content_disposition(filename)),
+        ]
+
+        return {"data": data, "name": filename, "headers": csv_http_headers}
 
     # ------------------------------------------------------
     # Business methods
     # ------------------------------------------------------
-    def get_values_init_graph(self, partner_id=None):
+    def get_date_min_max(self, partner_id=None):
+        self.ensure_one()
+        last_record = self.env["acc.enedis.cdc"]._get_last_cdc_record(
+            self.id, partner_id
+        )
+        date_max = fields.Datetime.context_timestamp(
+            self, last_record.date_slot
+        ).strftime("%d/%m/%Y")
+        if partner_id:
+            first_date = self.env["acc.counter.period"].search(
+                [("acc_operation_id", "=", self.id), ("partner_id", "=", partner_id)],
+                order="start_date ASC",
+                limit=1,
+            )
+            date_min = first_date.start_date.strftime("%d/%m/%Y")
+        else:
+            date_min = self.date_start_contract.strftime("%d/%m/%Y")
+        return {"date_min": date_min, "date_max": date_max}
+
+    def _get_values_init_graph(self, partner_id=None):
         self.ensure_one()
 
         values = {}
-        last_record = self.get_last_cdc_record(partner_id)
+        Cdc_model = self.env["acc.enedis.cdc"]
+        last_record = Cdc_model._get_last_cdc_record(self.id, partner_id)
 
-        date_day_start, date_day_end = self.get_interval("day", last_record)
+        date_day_start, date_day_end = Cdc_model._get_interval("day", last_record)
         date_day_start = date_day_start.strftime("%d/%m/%Y")
         date_day_end = date_day_end.strftime("%d/%m/%Y")
 
-        date_week_start, date_week_end = self.get_interval("week", last_record)
+        date_week_start, date_week_end = Cdc_model._get_interval("week", last_record)
 
         date_week_start = date_week_start.strftime("%d/%m/%Y")
         date_week_end = date_week_end.strftime("%d/%m/%Y")
 
-        date_month_start, date_month_end = self.get_interval("month", last_record)
+        date_month_start, date_month_end = Cdc_model._get_interval("month", last_record)
 
         date_month_start = date_month_start.strftime("%d/%m/%Y")
         date_month_end = date_month_end.strftime("%d/%m/%Y")
 
-        date_year_start, date_year_end = self.get_interval("year", last_record)
+        date_year_start, date_year_end = Cdc_model._get_interval("year", last_record)
 
         date_year_start = date_year_start.strftime("%d/%m/%Y")
         date_year_end = date_year_end.strftime("%d/%m/%Y")
diff --git a/static/src/js/operation_chart.js b/static/src/js/operation_chart.js
index 8749de3d05fdad7fdd03faf5b78c268568ea93cf..a9fb5d423e5d74a46bdfeefb606bff9420cdc3a5 100644
--- a/static/src/js/operation_chart.js
+++ b/static/src/js/operation_chart.js
@@ -109,16 +109,15 @@ odoo.define("oacc_portal_overview_cdc.operation_chart", function (require) {
             var ctx_histo_prod = self.$(".histo_chart_prod");
 
             this._rpc({
-                model: "acc.operation",
-                method: "graph_view_global",
-                args: [
-                    self.operation,
-                    self.first_day,
-                    self.last_day,
-                    self.partner_id,
-                    self.prm_id,
-                    self.data_type,
-                ],
+                route: "/chart/update_json",
+                params: {
+                    operation_id: self.operation,
+                    start_date: self.first_day,
+                    end_date: self.last_day,
+                    partner_id: self.partner_id,
+                    prm_id: self.prm_id,
+                    data_type: self.data_type,
+                },
             }).then(function (data) {
                 self.scale = data.scale;
                 self.chartData = data.chart_data;
@@ -659,19 +658,18 @@ odoo.define("oacc_portal_overview_cdc.operation_chart", function (require) {
         },
 
         _exportChartData: function (title_name) {
-
             var self = this;
             var url = "/chart/export_cdc?operation_id=" + self.operation;
             url = url + "&start_date=" + self.first_day;
             url = url + "&end_date=" + self.last_day;
-            url = url + "&data_type=" + self.data_type
-            if(self.partner_id) {
+            url = url + "&data_type=" + self.data_type;
+            if (self.partner_id) {
                 url = url + "&partner_id=" + self.partner_id;
             }
-            if(self.prm_id) {
-                url = url + "&prm_id=" + self.prm_id
+            if (self.prm_id) {
+                url = url + "&prm_id=" + self.prm_id;
             }
-            window.open(url, "_blank")
+            window.open(url, "_blank");
         },
 
         _updateDataTemplate: function (data) {
diff --git a/tools/__init__.py b/tools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tools/export_cdc.py b/tools/export_cdc.py
new file mode 100644
index 0000000000000000000000000000000000000000..34563d4d6803c3a411e06d59a7a6c0cd98c7d39a
--- /dev/null
+++ b/tools/export_cdc.py
@@ -0,0 +1,181 @@
+import string
+import unicodedata
+
+PROD_HEADER = [
+    "Horodatage",
+    "Production (W)",
+    "Production (kWh)",
+    "Surplus (kWh)",
+    "Production autoconsommee (kWh)",
+]
+CONS_HEADER = [
+    "Horodatage",
+    "Consommation (W)",
+    "Consommation (kWh)",
+    "Alloconsommation(kWh)",
+    "Autoconsommation (kWh)",
+]
+
+
+def make_header_lines(operation, partner_id, prm_id, data_type):
+    """
+    Données Elocoop de production de l'opération [nom opé] - [contact ou PRM]
+
+    :param operation: id de l operation
+    :param start_date: date de debut
+    :param end_date: date de fin
+    :param partner_id: id du partner
+    :param prm_id: id du prm
+    :param data_type: type de données (consomation ou production)
+    :return:
+    """
+    header = []
+    if prm_id:
+        desc = f" - {get_prm_name_from_id(operation, prm_id)}"
+    elif partner_id:
+        desc = f" - {get_partner_name_from_id(operation, partner_id)}"
+    else:
+        desc = ""
+
+    if data_type in ["cons"]:
+        header.append(
+            f"Données Elocoop de consommation de l'opération {operation.name}{desc}"
+        )
+        header.append(";".join(CONS_HEADER))
+    elif data_type in ["prod"]:
+        header.append(
+            f"Données Elocoop de production de l'opération {operation.name}{desc}"
+        )
+        header.append(";".join(PROD_HEADER))
+
+    return header
+
+
+def make_filename(
+    operation, start_date, end_date, partner_id, prm_id, data_type, file_type="csv"
+):
+    """
+    Genere le nom du fichier exporte sous la forme
+    Elocoop_[nom opé]_[date_debut]_[date_fin]_[production ou consommation]_[contact ou PRM].csv
+    :param operation: id de l operation
+    :param start_date: date de debut
+    :param end_date: date de fin
+    :param partner_id: id du partner
+    :param prm_id: id du prm
+    :param data_type: type de données (consomation ou production)
+    :param file_type: type de fichier par defaut csv
+    :return:
+    """
+    if prm_id:
+        desc = f"_{clean_for_title(get_prm_name_from_id(operation, prm_id))}"
+    elif partner_id:
+        desc = f"_{clean_for_title(get_partner_name_from_id(operation, partner_id))}"
+    else:
+        desc = ""
+
+    filename = (
+        f"Elocoop_{clean_for_title(operation.name)}_"
+        f"{start_date.replace('/', '')}_{end_date.replace('/', '')}_"
+        f"{'consommation' if data_type == 'cons' else 'production'}{desc}.{file_type}"
+    )
+    return filename
+
+
+def make_cons_data(raw_data):
+    """
+    make data file with cons hearder
+    horodatage cons w cons kwh allocons autocans
+    :param raw_data:
+    :return:
+    """
+    data_file_lines = []
+    rounding = 3
+    for row in raw_data:
+        data_file_lines.append(
+            ";".join(
+                [
+                    # horodatage
+                    row[0].strftime("%d/%m/%Y %H:%M"),
+                    # consommation en watt
+                    str(round(row[1] * 2000, rounding)),
+                    # consommation en kwh
+                    str(round(row[1], rounding)),
+                    # allocons
+                    str(round(row[3], rounding)),
+                    # autocons
+                    str(round(row[2], rounding)),
+                ]
+            )
+        )
+
+    return data_file_lines
+
+
+def make_prod_data(raw_data):
+    """
+    make data file with prod hearder
+    horodatage prod w prod kwh surplus autocons
+    :param raw_data:
+    :return:
+    """
+    data_file_lines = []
+    rounding = 3
+    for row in raw_data:
+        data_file_lines.append(
+            ";".join(
+                [
+                    # horodatage
+                    row[0].strftime("%d/%m/%Y %H:%M"),
+                    # production en watt
+                    str(round(row[3] * 2000, rounding)),
+                    # production en kwh
+                    str(round(row[3], rounding)),
+                    # surplus
+                    str(round(row[2], rounding)),
+                    # autocons
+                    str(round(row[1], rounding)),
+                ]
+            )
+        )
+
+    return data_file_lines
+
+
+def clean_for_title(word):
+    """
+    remplace les espace et caracteres speciaux par '_'
+    retire les accents
+
+    :param word: chaine de caractere d entrée
+    :return: chaine de caractere "nettoyée"
+    """
+    if isinstance(word, str):
+        char = list(string.punctuation)
+        char.append(" ")
+
+        for c in char:
+            word = word.replace(c, "_")
+
+    return "".join(
+        c for c in unicodedata.normalize("NFD", word) if unicodedata.category(c) != "Mn"
+    )
+
+
+def get_prm_name_from_id(operation, prm_id):
+    """
+    :param operation: operation
+    :param prm_id: id du prm
+    :return: chaine avec le nom du prm
+    """
+
+    return operation.env["acc.counter"].browse(int(prm_id)).name
+
+
+def get_partner_name_from_id(operation, partner_id):
+    """
+    :param operation: operation
+    :param partner_id: id du partner
+    :return: chaine avec le nom du partner
+    """
+
+    return operation.env["res.partner"].browse(int(partner_id)).name
diff --git a/views/acc_operation_views.xml b/views/acc_operation_views.xml
new file mode 100644
index 0000000000000000000000000000000000000000..364c9bcce57800c41b8c81a253f5689c89d7147a
--- /dev/null
+++ b/views/acc_operation_views.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2021- Le Filament (https://le-filament.com)
+     License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
+<odoo>
+
+    <record id="acc_operation_form_view" model="ir.ui.view">
+        <field name="name">cdc.acc_operation_form_view.api.form.inherit</field>
+        <field name="model">acc.operation</field>
+        <field name="inherit_id" ref="oacc.acc_operation_form_view" />
+        <field name="arch" type="xml">
+            <header position="inside">
+                <button
+                    string="Voir les courbes"
+                    type="object"
+                    class="btn-primary"
+                    name="action_view_curves"
+                />
+            </header>
+        </field>
+    </record>
+
+</odoo>