diff --git a/README.rst b/README.rst index 786b31a853d3471cb220b09011b8c88fec7af269..ae6b9aec7e6070ba8aa3d5ba6d11fff72a0f8688 100644 --- a/README.rst +++ b/README.rst @@ -3,9 +3,9 @@ :alt: License: AGPL-3 -=========== +============================ OACC - Clefs de répartition -=========== +============================ Ce module permet d'uploader de tester et d envoyer a enedis les clefs de répartion pour une Opération d'AutoConsommation Collective: diff --git a/__init__.py b/__init__.py index 0650744f6bc69b9f0b865e8c7174c813a5f5995e..9b4296142f475392fb090e036775e999aa8e4a27 100644 --- a/__init__.py +++ b/__init__.py @@ -1 +1,2 @@ from . import models +from . import wizard diff --git a/__manifest__.py b/__manifest__.py index 7fe41215d73add1783772a5f15f67759dc84d2e9..6acd5147f36b7772869334f4a4adacaaa931782b 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -11,9 +11,11 @@ # datas # views "views/acc_operation_views.xml", + "views/acc_repartition_keys_views.xml", # views menu # wizard + "wizard/acc_repartition_keys_wizard_views.xml", ], "installable": True, "auto_install": False, -} \ No newline at end of file +} diff --git a/models/__init__.py b/models/__init__.py index 95d38c215ba89c65cad06e91e331a0c07c964034..183168978d7da2e5bc4b899455e45738c23fa71c 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,2 +1,2 @@ from . import acc_operation -from . import acc_repartition_keys \ No newline at end of file +from . import acc_repartition_keys diff --git a/models/acc_operation.py b/models/acc_operation.py index fd774d86a6d9648b8a1b8f44317a7f57d7373e43..1d29e8b9847a79b0931a55fdf70b7abc905206ac 100644 --- a/models/acc_operation.py +++ b/models/acc_operation.py @@ -1,8 +1,8 @@ # Copyright 2021- Le Filament (https://le-filament.com) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) import logging -from odoo import _, fields, models +from odoo import fields, models _logger = logging.getLogger(__name__) @@ -41,4 +41,4 @@ class AccOperation(models.Model): # ------------------------------------------------------ # Actions - # ------------------------------------------------------ \ No newline at end of file + # ------------------------------------------------------ diff --git a/models/acc_repartition_keys.py b/models/acc_repartition_keys.py index b71474b6dc6bee9dae6caebb8ab0ddffc6f63120..c71adcb01b9019c664f529ed4b20cd75c6a9bff5 100644 --- a/models/acc_repartition_keys.py +++ b/models/acc_repartition_keys.py @@ -38,4 +38,4 @@ class AccRepartitionKeys(models.Model): # ------------------------------------------------------ # Business methods - # ------------------------------------------------------ \ No newline at end of file + # ------------------------------------------------------ diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv index a07c238c3c4e011114332311e63bb8eab958fd89..8ce08fbf566180e8a2427a4909f4f8e185dee91f 100644 --- a/security/ir.model.access.csv +++ b/security/ir.model.access.csv @@ -1,3 +1,4 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink "access_acc_repartition_keys_group_partner_manager","acc_repartition_keys group_partner_manager","model_acc_repartition_keys","base.group_partner_manager",1,1,1,1 -"access_acc_repartition_keys_group_user","acc_repartition_keys group_user","model_acc_repartition_keys","base.group_user",1,0,0,0 \ No newline at end of file +"access_acc_repartition_keys_group_user","acc_repartition_keys group_user","model_acc_repartition_keys","base.group_user",1,0,0,0 +"access_acc_repartition_keys_wizard_group_partner_manager","acc_repartition_keys_wizard group_partner_manager","model_acc_repartition_keys_wizard","base.group_partner_manager",1,1,1,1 diff --git a/static/description/icon.png b/static/description/icon.png new file mode 100755 index 0000000000000000000000000000000000000000..82ef47760a441cf229b5009f0a18ccf3842fbfa5 Binary files /dev/null and b/static/description/icon.png differ diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tools/key_file.py b/tools/key_file.py new file mode 100644 index 0000000000000000000000000000000000000000..7e8a0a7feff78ea413a4d4eccc9ebf58b88ff50d --- /dev/null +++ b/tools/key_file.py @@ -0,0 +1,149 @@ +""" +Repartion key entry file handler +""" +import csv +from datetime import datetime + + +class RepartitionKeyEntryFile: + def __init__(self, data, operation_counter_list): + self.data = data + self.operation_counter_list = operation_counter_list + self.json = self._to_json() + + def check(self): + """ + Lorsque le fichier est validé, des vérifications sont effectuées, et une erreur est affichée si le test ne passe pas : + • Première ligne, colonnes 2 à la fin : les numéros de PRM sont exactement ceux qui sont dans l’onglet Point de soutirage. + Aucun numéro de PRM n'apparaît plusieurs fois. + ◦ Erreur envoyée : “Les numéros de PRM ne correspondent pas à ceux de l’opération.” + + • Lignes 2 à la fin, Horodate : Les dates sont celles d’un seul mois complet. + ◦ Erreur envoyée “Les dates doivent être celles d’un seul mois complet.” + + • La somme sur chaque ligne doit être inférieure à 100. + ◦ Erreur envoyée “Ligne X : la somme dépasse 100%” + + """ + check_methods = [self._check_counter, self._check_same_month, self._check_max_value] + result = {"check": True, "message": ""} + for check in check_methods: + check_result = check() + if not check_result.get("check"): + result["check"] = False + result["message"] += f"{check_result['message']}\n" + + return result + + def _to_json(self): + """ + make data ready to send { "horodatage": [{"id": "value"}, ....]} + """ + json = {} + counter_list_from_file = self.data[0].split(";") + csv_file = csv.reader(self.data, delimiter=";") + line_count = 0 + for line in csv_file: + if line: + if line_count == 0: + line_count += 1 + continue + json[line[0]] = [] + line_count += 1 + counter_count = 1 + for counter in counter_list_from_file[1:]: + json[line[0]].append( + {"id": counter, "key": line[counter_count].replace(",", ".")} + ) + counter_count += 1 + return json + + def data_to_send(self, agreement_id, send_empty_key=False): + """ + return dict {"route", "body} to enedis + """ + call_list = [] + for horo in self.json: + date = datetime.strptime(horo, "%d-%m-%Y %H:%M") + route = f"/agreements/{agreement_id}/repartition_keys/{date.strftime('%Y%m%dT%H%MZ')}" + body = [] + for keys in self.json.get(horo): + if send_empty_key: + body.append(keys) + else: + if float(keys.get("key")) > 0: + body.append(keys) + if body: + call_list.append({"route": route, "body": body}) + return call_list + + def _check_counter(self): + """ + Check if all counter in file belong to operation + + """ + + if self.data: + counter_list_from_file = self.data[0].split(";")[1:] + + if sorted(self.operation_counter_list) == sorted(counter_list_from_file): + return {"check": True, "message": ""} + else: + missing_in_file = [ + counter + for counter in self.operation_counter_list + if counter not in counter_list_from_file + ] + missing_in_operation = [ + counter + for counter in counter_list_from_file + if counter not in self.operation_counter_list + ] + if missing_in_file or missing_in_operation: + return { + "check": False, + "message": "Les numéros de PRM ne correspondent pas à ceux de l’opération", + } + return {"check": True, "message": ""} + + def _check_max_value(self): + """ + check if all value are in the same month + """ + for hour in self.json: + max = 0 + for key in self.json[hour]: + if key["key"] == "0.00000000": + key["key"] = 0.0 + try: + max = max + float(key["key"]) + except ValueError: + pass + + if max > 100: + return { + "check": False, + "message": f"Ligne {hour} la somme dépasse 100.", + } + return {"check": True, "message": ""} + + def _check_same_month(self): + """ + check if all value are in the same month + """ + if ( + len( + set( + [ + datetime.strptime(date, "%d-%m-%Y %H:%M").month + for date in self.json + ] + ) + ) + != 1 + ): + return { + "check": False, + "message": "Les dates doivent être celles d’un seul mois complet.", + } + return {"check": True, "message": ""} diff --git a/views/acc_operation_views.xml b/views/acc_operation_views.xml index f5b2aa50b103e97f0c1393d2692860a8c545fcc8..a70536fe13406258983dce017b7891cec2e4f79f 100644 --- a/views/acc_operation_views.xml +++ b/views/acc_operation_views.xml @@ -2,7 +2,6 @@ <!-- 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">acc_operation_form_view.keys.form</field> <field name="model">acc.operation</field> @@ -11,22 +10,18 @@ <xpath expr="//notebook" position="inside"> <page string="Clefs de répartition" name="keys"> <header> -<!-- <button--> -<!-- string="Test"--> -<!-- type="object"--> -<!-- class="btn-primary"--> -<!-- name="test_file"--> -<!-- />--> + <button + string="Importer un fichier" + type="action" + name="%(oacc_repartition_keys.acc_repartition_keys_wizard_action)d" + class="btn-primary" + /> </header> - <group> + <tree> <field name="keys_repartition_ids" /> -<!-- <tree>--> -<!-- <field name="date_send"/>--> -<!-- </tree>--> - </group> + </tree> </page> </xpath> </field> </record> - </odoo> \ No newline at end of file diff --git a/views/acc_repartition_keys_views.xml b/views/acc_repartition_keys_views.xml new file mode 100644 index 0000000000000000000000000000000000000000..e247914f5a22123da9b78669b55d7187c16f996f --- /dev/null +++ b/views/acc_repartition_keys_views.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> +<!-- <data>--> + <record id="acc_keys_repatition_action" model="ir.actions.act_window"> + <field name="name">Keys</field> + <field name="res_model">acc.repartition.keys</field> + <field name="view_mode">tree,form</field> + </record> + <record id="acc_keys_repartition_tree" model="ir.ui.view"> + <field name="name">acc.repartition.keys.tree</field> + <field name="model">acc.repartition.keys</field> + <field name="arch" type="xml"> + <tree string="Keys" editable="bottom"> + <field name="date_send"/> + <field name="csv_file"/> + <field name="operation_id"/> + </tree> + </field> + </record> +<!-- </data>--> +</odoo> \ No newline at end of file diff --git a/wizard/__init__.py b/wizard/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cbd8c61e5e0dbd922961fa9b0b9117dc317a3bc3 --- /dev/null +++ b/wizard/__init__.py @@ -0,0 +1 @@ +from . import acc_repartition_keys_wizard diff --git a/wizard/acc_repartition_keys_wizard.py b/wizard/acc_repartition_keys_wizard.py new file mode 100644 index 0000000000000000000000000000000000000000..1d414ccdb6538b7365d853c5ede80999686d4da5 --- /dev/null +++ b/wizard/acc_repartition_keys_wizard.py @@ -0,0 +1,82 @@ +# Copyright 2021- Le Filament (https://le-filament.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import base64 +from odoo import fields, models +from odoo.exceptions import UserError + +from ..tools.key_file import RepartitionKeyEntryFile + + +class AccRepartitionKeysWizard(models.TransientModel): + _name = "acc.repartition.keys.wizard" + _description = "Clefs de répartition" + + # ------------------------------------------------------ + # Default methods + # ------------------------------------------------------ + def _default_operation_id(self): + return self.env.context.get("active_id") + + # ------------------------------------------------------ + # Fields declaration + # ------------------------------------------------------ + csv_file = fields.Binary("Contenu du fichier CSV") + date_send = fields.Date("Date de l'envoi des clefs", default=None) + operation_id = fields.Many2one( + "acc.operation", "Opération", default=_default_operation_id + ) + + # ------------------------------------------------------ + # SQL Constraints + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Computed fields / Search Fields + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Onchange / Constraints + # ------------------------------------------------------ + + # ------------------------------------------------------ + # CRUD methods (ORM overrides) + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Actions + # ------------------------------------------------------ + def send_imported_file(self): + """ + testing a file, check taht all prm id exist, are in operation and all operation prm are in file + :return: + """ + if self.csv_file: + file = ( + base64.b64decode(self.csv_file) + .decode("utf-8") + .replace("\r", "") + .split("\n") + ) + + counter_list_from_operation = [ + counter.name + for counter in self.env["acc.counter"].search( + [("acc_operation_id.id", "=", self.operation_id.id)] + ) + ] + + entry_file_handler = RepartitionKeyEntryFile( + data=file, operation_counter_list=counter_list_from_operation + ) + + file_check_result = entry_file_handler.check() + + if not file_check_result.get("check"): + raise UserError(file_check_result.get("message")) + + data_to_send = entry_file_handler.data_to_send(agreement_id=self.operation_id.name) + + + # ------------------------------------------------------ + # Business methods + # ------------------------------------------------------ diff --git a/wizard/acc_repartition_keys_wizard_views.xml b/wizard/acc_repartition_keys_wizard_views.xml new file mode 100644 index 0000000000000000000000000000000000000000..77b21811042851c0ec169d05da5c7384f6f6485c --- /dev/null +++ b/wizard/acc_repartition_keys_wizard_views.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="acc_repartition_keys_wizard_form" model="ir.ui.view"> + <field name="name">acc.repartition.keys.wizard.form</field> + <field name="model">acc.repartition.keys.wizard</field> + <field name="arch" type="xml"> + <form string="Création clefs de repartition"> + <header> + <button + class="btn btn-primary" + name="send_imported_file" + type="object" + string="Envoyer a enedis" + /> + </header> + <group name="keys" string="Clefs de repartition" col="2"> + <field name="csv_file"/> + </group> + </form> + </field> + </record> + + <record id="acc_repartition_keys_wizard_action" model="ir.actions.act_window"> + <field name="name">Création clefs de repartition</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">acc.repartition.keys.wizard</field> + <field name="view_mode">form</field> + <field + name="view_id" + ref="acc_repartition_keys_wizard_form" + /> + <field name="target">new</field> + </record> +</odoo> \ No newline at end of file