diff --git a/__init__.py b/__init__.py index 0650744f6bc69b9f0b865e8c7174c813a5f5995e..aa3b1de85c711057427efc896571a5cc9a75dd1b 100644 --- a/__init__.py +++ b/__init__.py @@ -1 +1,5 @@ +# Copyright 2023 Le Filament (<http://www.le-filament.com>) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + from . import models +from . import wizards diff --git a/__manifest__.py b/__manifest__.py index c2f9c7dc250f6ef28ca6046674e817ee75d3b62b..fa2324b8b61a3845f7f5b005aaf65fd0d77d8e22 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -5,14 +5,15 @@ "website": "https://le-filament.com", "version": "16.0.1.0.0", "license": "AGPL-3", - "depends": ["api_enedis", "oacc"], + "depends": ["api_enedis", "oacc", "queue_job"], "data": [ - # "security/ir.model.access.csv", + "security/ir.model.access.csv", # datas # views "views/acc_operation_views.xml", # views menu # wizard + "wizards/acc_operation_wizard_views.xml" ], "assets": { "web._assets_primary_variables": [], diff --git a/models/acc_operation.py b/models/acc_operation.py index 0fb76b770136f03b2a5e82175c3f963099e55c3a..67ea361ab329ec27f44b659b3cf927d10ee0ad34 100644 --- a/models/acc_operation.py +++ b/models/acc_operation.py @@ -1,7 +1,9 @@ # Copyright 2023 Le Filament (<http://www.le-filament.com>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging -from datetime import date +import pytz + +from datetime import date, datetime from odoo import _, fields, models from odoo.exceptions import UserError @@ -38,88 +40,38 @@ class AccOperation(models.Model): # ------------------------------------------------------ # CRUD methods (ORM overrides) # ------------------------------------------------------ - # @api.model - # def _auto_get_enedis_data(self): - # """This method is called from a cron job. - # It is used to get data from Enedis with API. - # """ - # day_birthday = date.today() - relativedelta(months=1, days=10) - # records = self.search( - # [ - # ("birthday_date", "=", day_birthday.day), - # ("client_id", "!=", False), - # ("secret_id", "!=", False), - # ] - # ) - # for rec in records: - # last_date = day_birthday + relativedelta(months=1, days=-1) - # token = self.access_token() - # rec.get_curves(day_birthday, last_date, token) # ------------------------------------------------------ # Actions # ------------------------------------------------------ - # def get_curves(self, date_start, date_end, token=None): - # # Ask token to API - # if not token: - # token = self.access_token() - # - # # Création du lot - # batch_name = ( - # "Courbes du " - # + str(date_start) - # + " au " - # + str(date_end) - # + " - Opération :" - # + str(self.name) - # ) - # batch = self.env["queue.job.batch"].get_new_batch(batch_name) - # # Load consommation data by PRM - # for delivery_counter_id in self.acc_delivery_ids: - # # Mise en file d'attente des appels API consommateur - # self.with_context(job_batch=batch).with_delay().definitive_load_curves( - # date_start, date_end, delivery_counter_id, token=token - # ) - # - # # Load production data by PRM - # for injection_counter_id in self.acc_injection_ids: - # # Mise en file d'attente des appels API producteur - # self.with_context(job_batch=batch).with_delay().definitive_load_curves( - # date_start, date_end, injection_counter_id, token=token - # ) - # # lancement de la file d'attente - # batch.enqueue() + # def curves(self): + # self.ensure_one() + # message = self.get_curves() # - # def get_curves_all(self, date_start): - # # Calcul du nombre de mois entre la date de début de contrat - # # de l'opération et la date du jour pour lancer la récupération des données - # num_months = (date.today().year - date_start.year) * 12 + ( - # date.today().month - date_start.month + # # Logs information + # log_id = self.env["acc.logs"].create( + # { + # "name": "Appel API Enedis Courbes du " + str(fields.Date.today()), + # "date_launched": fields.Datetime.now(), + # "type_log": "api", + # "message": message, + # "acc_operation_id": self.id, + # } # ) # - # date_start_it = date_start - # date_end_it = date_start + relativedelta(months=1, days=-1) - # - # token = self.access_token() - # i = 1 - # while i < num_months: - # self.get_curves(date_start_it, date_end_it, token) - # date_start_it = date_start_it + relativedelta(months=1) - # date_end_it = date_start_it + relativedelta(months=1, days=-1) - # i += 1 - - # ------------------------------------------------------ - # API functions - # ------------------------------------------------------ - def _check_access_api(self): - if not self.client_id and not self.secret_id: - raise UserError( - _( - "L'identifiant et la clé de l'opération pour l'utilisation de " - "l'API Enedis ne sont pas renseignées. " - "Veuillez les renseigner dans l'onglet API Enedis." - ) - ) + # view_id = self.env.ref("oacc.acc_logs_form").id + # return { + # "name": "LOGS", + # "view_type": "form", + # "view_mode": "form", + # "views": [(view_id, "form")], + # "res_model": "acc.logs", + # "view_id": view_id, + # "type": "ir.actions.act_window", + # "res_id": log_id.id, + # "target": "new", + # "flags": {"initial_mode": "view"}, + # } def get_perimeter(self): self.ensure_one() @@ -150,6 +102,134 @@ class AccOperation(models.Model): "flags": {"initial_mode": "view"}, } + # ------------------------------------------------------ + # API functions + # ------------------------------------------------------ + def curves( + self, + date_start, + date_end, + usage_point_cons_ids=None, + usage_point_prod_ids=None): + """ + Récupère les données de l'opération concernant le périmètre: + - liste des PRM + - date de début opération + """ + self._check_access_api() + + # Si pas de PRM sélectionnés + if not usage_point_cons_ids and not usage_point_prod_ids: + usage_point_cons_ids = self.acc_delivery_ids + usage_point_prod_ids = self.acc_injection_ids + + message = str() + message += ( + "<h1>API Enedis OACC - Appel Courbes " + + str(fields.Datetime.now()) + + "</h1>" + "Appel API pour la période " + "" + str(date_start) + " " + str(date_end) + "<br/>" + ) + + if usage_point_cons_ids: + # Traitement données de cons + message += "<br/><strong>Traitement des données de consommation</strong><br/>" + for usage_point_id in usage_point_cons_ids: + desc = "Opération: " + self.name + " - PRM: " + usage_point_id.name + " - Date: " + str(fields.Datetime.today()) + # message += self.with_delay(description=desc).get_definitive_load_curves( + self.with_delay(description=desc).get_definitive_load_curves( + date_end, + date_start, + ["cons,autocons,complement"], + usage_point_id) + + if usage_point_prod_ids: + # Traitement données de prod + message += "<br/><strong>Traitement des données de production</strong><br/>" + for usage_point_id in usage_point_prod_ids: + desc = "Opération: " + self.name + " - PRM: " + usage_point_id.name + " - Date: " + str( + fields.Datetime.today()) + self.with_delay(description=desc).get_definitive_load_curves( + # message += self.with_delay(description=desc).get_definitive_load_curves( + date_end, + date_start, + ["surplus,prod"], + usage_point_id) + + message += ( + "<br/><h1>Fin appel API Courbes: " + str(fields.Datetime.now()) + "</h1>" + ) + # Logs information + log_id = self.env["acc.logs"].create( + { + "name": "Appel API Enedis Courbes du " + str(fields.Date.today()) + " - Période " + str(date_start) + " " + str(date_end), + "date_launched": fields.Datetime.now(), + "type_log": "api", + "message": message, + "acc_operation_id": self.id, + } + ) + + def get_definitive_load_curves( + self, date_end, date_start, type_curve, usage_point_id): + + message = str() + message += "PRM " + usage_point_id.name + "\n" + message += "Appel API ...\n" + curves_data = self._get_definitive_load_curves( + self.name, date_end, date_start, type_curve, + usage_point_id.name, self.client_id, self.secret_id) + message += "Appel API terminé. Traitement des données ...\n" + + curves = curves_data.get("curves") + + if curves: + name = usage_point_id.name + "_" + str(date_start) + "_" + str(date_end) + for curve in curves: + type = curve["type"] + + for point in curve["interval_reading"]: + dt = pytz.utc.localize( + datetime.strptime(point["timestamp"], "%Y-%m-%dT%H:%M:%SZ") + ) + date_slot = fields.Datetime.to_string( + dt.astimezone(pytz.timezone("Europe/Paris")) + ) + date_slot_utc = fields.Datetime.to_string(dt) + + self.env["acc.enedis.cdc"].create( + { + "name": name, + "acc_operation_id": self.id, + "acc_counter_id": usage_point_id.id or False, + "comp_data_type": type, + "power": point["value"], + "date_slot": date_slot, + "date_slot_utc": date_slot_utc, + } + ) + message += "Fin du traitement des données\n" + return message + + def get_curves_all(self, date_start): + # Calcul du nombre de mois entre la date de début de contrat + # de l'opération et la date du jour pour lancer la récupération des données + num_months = (date.today().year - date_start.year) * 12 + ( + date.today().month - date_start.month + ) + + date_start_it = date_start + date_end_it = date_start + relativedelta(months=1, days=-1) + + token = self.access_token() + i = 1 + while i < num_months: + self.get_curves(date_start_it, date_end_it, token) + date_start_it = date_start_it + relativedelta(months=1) + date_end_it = date_start_it + relativedelta(months=1, days=-1) + i += 1 + def perimeter(self): """ Récupère les données de l'opération concernant le périmètre: @@ -230,3 +310,12 @@ class AccOperation(models.Model): # ------------------------------------------------------ # Business methods # ------------------------------------------------------ + def _check_access_api(self): + if not self.client_id and not self.secret_id: + raise UserError( + _( + "L'identifiant et la clé de l'opération pour l'utilisation de " + "l'API Enedis ne sont pas renseignées. " + "Veuillez les renseigner dans l'onglet API Enedis." + ) + ) diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv index 301b7dab167cbcb978ea78e7da9e7f032c40b90e..8dfe7ddba803a0ace1cd62fe854300e1012eb97a 100644 --- a/security/ir.model.access.csv +++ b/security/ir.model.access.csv @@ -1 +1,3 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +"access_acc_operation_wizard_group_partner_manager","acc_operation_wizard group_partner_manager","model_acc_operation_wizard","base.group_partner_manager",1,1,1,1 +"access_acc_operation_wizard_group_user","acc_operation_wizard group_user","model_acc_operation_wizard","base.group_user",1,0,0,0 diff --git a/views/acc_operation_views.xml b/views/acc_operation_views.xml index a26f66fa2eae5b12eb1ecd11e5b1721737e3f83c..583f3150f493d9e0e28d795d940ec530bfc8e016 100644 --- a/views/acc_operation_views.xml +++ b/views/acc_operation_views.xml @@ -16,6 +16,13 @@ name="get_perimeter" attrs="{'invisible':[('client_id','=', False), ('secret_id','=', False)]}" /> + <button + string="Récupération des courbes" + type="action" + class="btn-primary" + name="%(oacc_enedis_api.acc_operation_wizard_action)d" + attrs="{'invisible':[('client_id','=', False), ('secret_id','=', False)]}" + /> </header> <group> <field name="client_id" /> diff --git a/wizards/__init__.py b/wizards/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ddbfb3b9f6d01d7faaea73339637505722e74e49 --- /dev/null +++ b/wizards/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2023 Le Filament (<http://www.le-filament.com>) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import acc_operation_wizard diff --git a/wizards/acc_operation_wizard.py b/wizards/acc_operation_wizard.py new file mode 100644 index 0000000000000000000000000000000000000000..f6b966f4f8d9e2a0b22970bfae703e653dcb722a --- /dev/null +++ b/wizards/acc_operation_wizard.py @@ -0,0 +1,96 @@ +# Copyright 2023 Le Filament (<http://www.le-filament.com>) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import _, fields, models +from odoo.exceptions import UserError + + +class AccOperationWizard(models.TransientModel): + _name = "acc.operation.wizard" + _description = "Récupération des courbes pour une date donnée" + + # ------------------------------------------------------ + # Fields declaration + # ------------------------------------------------------ + def _default_operation_id(self): + op = self.env['acc.operation'].browse(self.env.context.get('active_ids')[0] if self.env.context.get('active_ids') else []) + return op + + operation_id = fields.Many2one( + "acc.operation", default=lambda self: self._default_operation_id()) + date_start = fields.Date("Date de début") + date_end = fields.Date("Date de fin") + prm_cons_ids = fields.Many2many( + "acc.counter", + relation="acc_counter_cons_rel", + column1="cons_id", + column2="op_id", + domain=[("is_delivery", "=", True)], + string="PRM de soutirage") + prm_prod_ids = fields.Many2many( + "acc.counter", + relation="acc_counter_prod_rel", + column1="prod_id", + column2="op_id", + domain=[("is_injection", "=", True)], + string="PRM d'injection") + + # ------------------------------------------------------ + # SQL Constraints + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Default methods + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Computed fields / Search Fields + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Onchange / Constraints + # ------------------------------------------------------ + + # ------------------------------------------------------ + # CRUD methods (ORM overrides) + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Actions + # ------------------------------------------------------ + def get_curves(self): + if not self.date_end and not self.date_start: + raise UserError( + _("Les champs Date de début et Date de fin sont obligatoires") + ) + if (self.date_end - self.date_start).days > 31: + raise UserError(_("L'intervalle de temps ne doit pas dépasser 31 Jours")) + + if (self.date_end <= self.date_start): + raise UserError(_("La date de fin doit être supérieure à la date de début")) + + self.operation_id.curves( + self.date_start, self.date_end, self.prm_cons_ids, self.prm_prod_ids + ) + return {"type": "ir.actions.act_window_close"} + + def get_curves_all(self): + context = dict(self._context or {}) + if context.get("active_ids", False): + op = self.env["acc.operation"].browse(context.get("active_ids")) + if not op.date_start_contract: + raise UserError( + _( + "Renseigner une date de début de contrat pour" + " pouvoir récupérer les courbes à partie de cette date." + ) + ) + date_start = op.date_start_contract + self.env["acc.operation"].browse(context.get("active_ids")).get_curves_all( + date_start + ) + return {"type": "ir.actions.act_window_close"} + + # ------------------------------------------------------ + # Business methods + # ------------------------------------------------------ diff --git a/wizards/acc_operation_wizard_views.xml b/wizards/acc_operation_wizard_views.xml new file mode 100644 index 0000000000000000000000000000000000000000..f9e9c4bbf24657a9f40b7a01feecf89d13bf8059 --- /dev/null +++ b/wizards/acc_operation_wizard_views.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <!-- WIZARD FORM --> + <record id="acc_operation_wizard_view_form" model="ir.ui.view"> + <field name="name">acc.operation.wizard.form</field> + <field name="model">acc.operation.wizard</field> + <field name="arch" type="xml"> + <form> + <group> + <field name="operation_id" invisible="1"/> + <field + name="prm_cons_ids" + widget="many2many_tags" + domain="[('is_delivery', '=', True), ('acc_operation_id', '=', operation_id)]" + options="{'no_create_edit': True, 'no_create': True}" + /> + <field + name="prm_prod_ids" + widget="many2many_tags" + options="{'no_create_edit': True, 'no_create': True}" + domain="[('is_injection', '=', True), ('acc_operation_id', '=', operation_id)]" + /> + </group> + <group name="period" string="Période" col="2"> + <field name="date_start" /> + <field name="date_end" /> + </group> + <footer> + <button + class="btn btn-sm btn-primary" + name="get_curves" + string="Récupérer" + type="object" + /> + <button + class="btn btn-sm btn-primary" + name="get_curves_all" + string="Récupérer depuis le début" + type="object" + /> + <button + class="btn btn-sm btn-default" + special="cancel" + string="Annuler" + /> + </footer> + </form> + </field> + </record> + + <record id="acc_operation_wizard_action" model="ir.actions.act_window"> + <field name="name">Récupération des courbes</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">acc.operation.wizard</field> + <field name="view_mode">form</field> + <field name="view_id" ref="acc_operation_wizard_view_form" /> + <field name="target">new</field> + </record> + +</odoo>