From 6c8bc72fec48c540a88afdb6262035ae298b4338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Laporte?= <stephane.laporte@enercoop.org> Date: Wed, 5 Mar 2025 11:29:35 +0100 Subject: [PATCH 1/4] =?UTF-8?q?[ADD]=20Nouvelle=20r=C3=A9partition=20au=20?= =?UTF-8?q?sein=20d'un=20groupe=20de=20priorit=C3=A9=20:=20par=20quote-par?= =?UTF-8?q?t=20https://trello.com/c/JyNktdmx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/acc_priority_group.py | 24 ++++++++++++++ models/acc_priority_group_counter.py | 28 ++++++++++++++++ views/acc_priority_group_counter_views.xml | 37 +++++++++++++++++++--- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/models/acc_priority_group.py b/models/acc_priority_group.py index 2da6651..d657ced 100644 --- a/models/acc_priority_group.py +++ b/models/acc_priority_group.py @@ -16,6 +16,7 @@ class AccPriorityGroup(models.Model): type_algo = fields.Selection( [ ("prorata", "Répartition au prorata"), + ("quotepart", "Répartition par quote-part"), ], string="Répartition au sein du groupe", default="prorata", @@ -137,6 +138,7 @@ class AccPriorityGroup(models.Model): def compute(self, data=None): compute_algo = { "prorata": self._prorata, + "quotepart": self._quotepart, } if data is None: data = self.env["acc.enedis.raw.cdc"].get_repartition_data( @@ -184,3 +186,25 @@ class AccPriorityGroup(models.Model): data[slot]["prod_totale"] = prod - total_affecte return data + + def _quotepart(self, data): + total_share = sum(self.acc_priority_group_counter_ids.mapped("acc_counter_share")) + for slot in data: + remaining_prod = data.get(slot).get("prod_totale") + total_affecte = 0.0 + if not data.get(slot).get("affect"): + data[slot]["affect"] = {} + for priority_group_counter in self.acc_priority_group_counter_ids: + counter = priority_group_counter.acc_counter_id + counter_share = priority_group_counter.acc_counter_share + conso_k = data.get(slot).get("conso").get(counter.name) + if remaining_prod == 0 or total_share == 0 or conso_k is None or counter_share == 0: + part_a_affecter = 0.0 + else: + part_a_affecter = min( + conso_k, remaining_prod * (counter_share / total_share) + ) + data[slot]["affect"][counter] = part_a_affecter + total_affecte += part_a_affecter + data[slot]["prod_totale"] = remaining_prod - total_affecte + return data diff --git a/models/acc_priority_group_counter.py b/models/acc_priority_group_counter.py index 3df09d7..18e4e7d 100644 --- a/models/acc_priority_group_counter.py +++ b/models/acc_priority_group_counter.py @@ -39,6 +39,10 @@ class AccPriorityGroupCounter(models.Model): string="Counter domain", compute="_compute_acc_counter_id_domain" ) + acc_counter_share = fields.Float(string='Quote-part', help="Part de la production affectée à ce PRM, au prorata de l'ensemble des parts affectées aux autres PRMs") + is_group_type_quotepart = fields.Boolean(compute='_compute_is_group_type_quotepart', store=False) + acc_counter_percentage = fields.Float(compute="_compute_acc_counter_percentage", store=False) + # ------------------------------------------------------ # SQL Constraints # ------------------------------------------------------ @@ -50,6 +54,17 @@ class AccPriorityGroupCounter(models.Model): # ------------------------------------------------------ # Computed fields / Search Fields # ------------------------------------------------------ + @api.depends('acc_priority_group_id.type_algo') + def _compute_is_group_type_quotepart(self): + for record in self: + record.is_group_type_quotepart = record.acc_priority_group_id.type_algo == 'quotepart' + + @api.depends('acc_priority_group_id.acc_priority_group_counter_ids', "acc_counter_share") + def _compute_acc_counter_percentage(self): + for record in self: + total_share = sum(record.acc_priority_group_id.acc_priority_group_counter_ids.mapped("acc_counter_share")) + record.acc_counter_percentage = 100 * record.acc_counter_share / total_share if total_share else 0 + @api.depends("acc_counter_id", "acc_priority_group_id") def _compute_acc_counter_id_domain(self): for counter in self: @@ -108,3 +123,16 @@ class AccPriorityGroupCounter(models.Model): domain=[("acc_operation_id", "=", operation_id)], order="sequence, id" ) return group_ids + + def action_open_counter_share_form(self): + print('action_open_counter_share_form') + return { + 'type': 'ir.actions.act_window', + 'name': 'Modifier la quote-part du point de soutirage', + 'res_model': 'acc.priority.group.counter', + 'view_mode': 'form', + 'target': 'new', + 'res_id': self.id, + 'view_id': self.env.ref('oacc_repartition_keys.acc_operation_priority_group_counter_share_form').id, + #'context': {'default_field_name': 'Auto-filled Text'} + } diff --git a/views/acc_priority_group_counter_views.xml b/views/acc_priority_group_counter_views.xml index 5bf60f2..33b31ec 100644 --- a/views/acc_priority_group_counter_views.xml +++ b/views/acc_priority_group_counter_views.xml @@ -1,5 +1,21 @@ <?xml version="1.0" encoding="utf-8" ?> <odoo> + + <record id="acc_operation_priority_group_counter_share_form" model="ir.ui.view"> + <field name="name">acc_operation_priority_group_counter_share_form</field> + <field name="model">acc.priority.group.counter</field> + <field name="arch" type="xml"> + <form string="My Model"> + <div> + <group> + <field name="acc_counter_share" style="width: 15em;"/> + </group> + </div> + </form> + </field> + </record> + + <record id="acc_priority_group_counter_act_window" model="ir.actions.act_window"> <field name="name">Clé de répartition par groupes de priorité</field> <field name="res_model">acc.priority.group.counter</field> @@ -32,7 +48,7 @@ <field name="arch" type="xml"> <kanban default_group_by="acc_priority_group_id" - class="o_kanban_small_column" + class="o_kanban_medium_column" on_create="quick_create" quick_create_view="oacc_repartition_keys.acc_operation_priority_group_counter_form" archivable="false" @@ -47,7 +63,7 @@ <templates> <t t-name="kanban-box"> - <div t-attf-class="oe_kanban_global_click oe_kanban_card"> + <div t-attf-class="oe_kanban_card"> <div class="oe_kanban_content"> <div class="o_kanban_record_top"> <div class="o_kanban_record_headings"> @@ -64,9 +80,20 @@ </div> </div> <div class="o_kanban_record_body"> - <field name="counter_owner" /> - <br /> - <field name="counter_street" /> + <div><field name="counter_owner" /></div> + <div><field name="counter_street" /></div> + <field name="is_group_type_quotepart" invisible="1"/> + <group attrs="{'invisible': [('is_group_type_quotepart', '=', False)]}"> + <div class="d-flex justify-content-between"> + <div> + <div> + Quote-part: <field name="acc_counter_share" attrs="{'invisible': [('is_group_type_quotepart', '=', False)]}"/> + (<field name="acc_counter_percentage" widget="float" options="{'precision': 2}"/>%) + </div> + </div> + <div class="align-self-end"><button name="action_open_counter_share_form" type="object" class="btn btn-sm btn-primary text-end">Modifier</button></div> + </div> + </group> </div> </div> <div class="clearfix" /> -- GitLab From cd7257871c21e33fdba96e4b540bd9063ec47293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Laporte?= <stephane.laporte@enercoop.org> Date: Wed, 5 Mar 2025 17:29:52 +0100 Subject: [PATCH 2/4] =?UTF-8?q?[ADD]=20Nouvelle=20r=C3=A9partition=20au=20?= =?UTF-8?q?sein=20d'un=20groupe=20de=20priorit=C3=A9=20:=20par=20quote-par?= =?UTF-8?q?t=20https://trello.com/c/JyNktdmx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __manifest__.py | 1 + models/acc_operation.py | 64 +++++++++++++++------- models/acc_priority_group.py | 23 +++++--- models/acc_priority_group_counter.py | 10 +--- static/src/css/custom.css | 3 + views/acc_priority_group_counter_views.xml | 33 ++++++----- 6 files changed, 83 insertions(+), 51 deletions(-) create mode 100644 static/src/css/custom.css diff --git a/__manifest__.py b/__manifest__.py index f40cf05..75ba6c1 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -26,6 +26,7 @@ "web.assets_backend": [ "/oacc_repartition_keys/static/src/js/kanban_button.js", "/oacc_repartition_keys/static/src/xml/kanban_button.xml", + "/oacc_repartition_keys/static/src/css/custom.css", ], }, "installable": True, diff --git a/models/acc_operation.py b/models/acc_operation.py index b10c25b..b44a71d 100644 --- a/models/acc_operation.py +++ b/models/acc_operation.py @@ -2,6 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) import base64 import csv +import math from collections import OrderedDict from datetime import date, datetime from io import StringIO @@ -98,6 +99,14 @@ class AccOperation(models.Model): for repartition in repartitions: repartition.unlink() + def get_affect_sum(self, data_slot): + affect_sum = 0.0 + for counter in data_slot.get("affect"): + affect_counter = data_slot.get("affect").get(counter) + if affect_counter: + affect_sum += affect_counter + return affect_sum + def generate(self): """ generate repartition keys @@ -112,7 +121,13 @@ class AccOperation(models.Model): repartition = self.env["acc.repartition.keys"].create({"operation_id": self.id}) - data = None + data = self.env["acc.enedis.raw.cdc"].get_repartition_data(operation_id=self) + + # on sauvegarde prod_totale pour permettre en fin de traitement + # le calcul du pourcentage attribué à chaque compteur + for slot in data: + data[slot]["prod_initiale"] = data.get(slot).get("prod_totale") + for priority in self.acc_priority_group_ids: data = priority.compute(data) @@ -121,41 +136,48 @@ class AccOperation(models.Model): _("Pas de données brute pour le mois précedent présente") ) + # contrôle, l'ensemble des affectations ne doit pas dépasser la production à affecter for slot in data: - affect = data.get(slot).get("affect") + item = data.get(slot) + affect = item.get("affect") if affect: - total_affecte = sum(affect.values()) + total_production = item.get("prod_initiale") + remaining_prod = item.get("prod_totale") + affect_sum = self.get_affect_sum(item) + if abs(affect_sum + remaining_prod - total_production) > 1e-3: + raise ValidationError( + _( + "Une erreur s'est produite lors de l'affectation de l'auto-consommation" + ) + ) - slot_result = {} + # enregistrement en base + for slot in data: + item = data.get(slot) + affect = item.get("affect") + if affect: + total_production = item.get("prod_initiale") + + # calcul du pourcentage attribué à chaque compteur par rapport à la production totale + weights = {} for counter_id in affect: - if total_affecte == 0: + affecte_counter = affect.get(counter_id) + if total_production == 0: weight = 0.0 else: - weight = round( - (affect.get(counter_id) * 100) / total_affecte, 8 - ) - - slot_result[counter_id] = weight - - total_weight = sum(slot_result.values()) - max_value = None - if total_weight > 100.0: - max_value = max(slot_result.values()) + weight = math.floor((affecte_counter * 100 / total_production) * 1e+3) / 1e+3 + weights[counter_id] = weight slot_line = [] - for counter in slot_result: - if max_value is not None: - if slot_result[counter] == max_value: - slot_result[counter] -= 0.0000001 + for counter in weights: slot_line.append( { "acc_repartition_id": repartition.id, "time_slot": slot, - "weight": slot_result[counter], + "weight": weights[counter], "acc_counter_id": counter.id, } ) - self.env["acc.repartition.counter"].create(slot_line) # ------------------------------------------------------ diff --git a/models/acc_priority_group.py b/models/acc_priority_group.py index d657ced..cd20c06 100644 --- a/models/acc_priority_group.py +++ b/models/acc_priority_group.py @@ -33,6 +33,7 @@ class AccPriorityGroup(models.Model): ) counter_datas = fields.Json(compute="_compute_counter_datas") + total_share = fields.Float(compute="_compute_total_share", store=False) # ------------------------------------------------------ # SQL Constraints # ------------------------------------------------------ @@ -44,6 +45,12 @@ class AccPriorityGroup(models.Model): # ------------------------------------------------------ # Computed fields / Search Fields # ------------------------------------------------------ + @api.depends("acc_priority_group_counter_ids") + def _compute_total_share(self): + for record in self: + record.total_share = sum( + record.acc_priority_group_counter_ids.mapped("acc_counter_share") + ) @api.depends("type_algo", "sequence") def _compute_display_name(self): @@ -140,13 +147,7 @@ class AccPriorityGroup(models.Model): "prorata": self._prorata, "quotepart": self._quotepart, } - if data is None: - data = self.env["acc.enedis.raw.cdc"].get_repartition_data( - operation_id=self.acc_operation_id - ) - d = compute_algo[self.type_algo](data) - return d def get_conso_sum(self, data_slot): @@ -179,7 +180,6 @@ class AccPriorityGroup(models.Model): part_a_affecter = min( conso_k, prod * (conso_k / priority_counters_conso_sum) ) - data[slot]["affect"][counter] = part_a_affecter total_affecte += part_a_affecter @@ -188,7 +188,7 @@ class AccPriorityGroup(models.Model): return data def _quotepart(self, data): - total_share = sum(self.acc_priority_group_counter_ids.mapped("acc_counter_share")) + total_share = self.total_share for slot in data: remaining_prod = data.get(slot).get("prod_totale") total_affecte = 0.0 @@ -198,7 +198,12 @@ class AccPriorityGroup(models.Model): counter = priority_group_counter.acc_counter_id counter_share = priority_group_counter.acc_counter_share conso_k = data.get(slot).get("conso").get(counter.name) - if remaining_prod == 0 or total_share == 0 or conso_k is None or counter_share == 0: + if ( + remaining_prod == 0 + or total_share == 0 + or conso_k is None + or counter_share == 0 + ): part_a_affecter = 0.0 else: part_a_affecter = min( diff --git a/models/acc_priority_group_counter.py b/models/acc_priority_group_counter.py index 18e4e7d..426efd3 100644 --- a/models/acc_priority_group_counter.py +++ b/models/acc_priority_group_counter.py @@ -40,7 +40,7 @@ class AccPriorityGroupCounter(models.Model): ) acc_counter_share = fields.Float(string='Quote-part', help="Part de la production affectée à ce PRM, au prorata de l'ensemble des parts affectées aux autres PRMs") - is_group_type_quotepart = fields.Boolean(compute='_compute_is_group_type_quotepart', store=False) + group_type_algo = fields.Selection(related="acc_priority_group_id.type_algo", readonly=True) acc_counter_percentage = fields.Float(compute="_compute_acc_counter_percentage", store=False) # ------------------------------------------------------ @@ -54,16 +54,12 @@ class AccPriorityGroupCounter(models.Model): # ------------------------------------------------------ # Computed fields / Search Fields # ------------------------------------------------------ - @api.depends('acc_priority_group_id.type_algo') - def _compute_is_group_type_quotepart(self): - for record in self: - record.is_group_type_quotepart = record.acc_priority_group_id.type_algo == 'quotepart' @api.depends('acc_priority_group_id.acc_priority_group_counter_ids', "acc_counter_share") def _compute_acc_counter_percentage(self): for record in self: - total_share = sum(record.acc_priority_group_id.acc_priority_group_counter_ids.mapped("acc_counter_share")) - record.acc_counter_percentage = 100 * record.acc_counter_share / total_share if total_share else 0 + total_share = record.acc_priority_group_id.total_share + record.acc_counter_percentage = record.acc_counter_share / total_share if total_share else 0 @api.depends("acc_counter_id", "acc_priority_group_id") def _compute_acc_counter_id_domain(self): diff --git a/static/src/css/custom.css b/static/src/css/custom.css new file mode 100644 index 0000000..3585319 --- /dev/null +++ b/static/src/css/custom.css @@ -0,0 +1,3 @@ +.oacc_quick_create.o_form_editable .o_cell .o_form_label { + margin-bottom: 0 !important; +} diff --git a/views/acc_priority_group_counter_views.xml b/views/acc_priority_group_counter_views.xml index 33b31ec..3f6eb4e 100644 --- a/views/acc_priority_group_counter_views.xml +++ b/views/acc_priority_group_counter_views.xml @@ -28,20 +28,25 @@ <field name="name">acc.operation.priority.group.counter.form</field> <field name="model">acc.priority.group.counter</field> <field name="arch" type="xml"> - <form> - <field name="acc_priority_group_id" invisible="1" /> - <field name="acc_operation_id" invisible="1" /> - <field name="acc_counter_id_domain" invisible="1" /> + <form class="oacc_quick_create"> + <field name="acc_priority_group_id" invisible="1" /> + <field name="acc_operation_id" invisible="1" /> + <field name="acc_counter_id_domain" invisible="1" /> + <group> <field - name="acc_counter_id" - options="{'no_create_edit': True, 'no_create': True}" - domain="acc_counter_id_domain" - /> - </form> + name="acc_counter_id" + options="{'no_create_edit': True, 'no_create': True}" + domain="acc_counter_id_domain" + /> + </group> + <field name="group_type_algo" invisible="1"/> + <group attrs="{'invisible': [('group_type_algo', '!=', 'quotepart')]}"> + <field name="acc_counter_share" style="width: 15em;"/> + </group> + </form> </field> </record> - <record id="acc_operation_priority_group_counter_kanban" model="ir.ui.view"> <field name="name">acc.operation.priority.group.counter.kanban</field> <field name="model">acc.priority.group.counter</field> @@ -82,13 +87,13 @@ <div class="o_kanban_record_body"> <div><field name="counter_owner" /></div> <div><field name="counter_street" /></div> - <field name="is_group_type_quotepart" invisible="1"/> - <group attrs="{'invisible': [('is_group_type_quotepart', '=', False)]}"> + <field name="group_type_algo" invisible="1"/> + <group attrs="{'invisible': [('group_type_algo', '!=', 'quotepart')]}"> <div class="d-flex justify-content-between"> <div> <div> - Quote-part: <field name="acc_counter_share" attrs="{'invisible': [('is_group_type_quotepart', '=', False)]}"/> - (<field name="acc_counter_percentage" widget="float" options="{'precision': 2}"/>%) + Quote-part: <field name="acc_counter_share"/> + (<field name="acc_counter_percentage" widget="percentage"/>) </div> </div> <div class="align-self-end"><button name="action_open_counter_share_form" type="object" class="btn btn-sm btn-primary text-end">Modifier</button></div> -- GitLab From bfef633332dd0870886dc0131ef31241245cb896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Laporte?= <stephane.laporte@enercoop.org> Date: Wed, 12 Mar 2025 16:16:31 +0100 Subject: [PATCH 3/4] =?UTF-8?q?[ADD]=20Nouvelle=20r=C3=A9partition=20au=20?= =?UTF-8?q?sein=20d'un=20groupe=20de=20priorit=C3=A9=20:=20par=20quote-par?= =?UTF-8?q?t=20https://trello.com/c/JyNktdmx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/acc_priority_group.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/models/acc_priority_group.py b/models/acc_priority_group.py index cd20c06..f5ca34d 100644 --- a/models/acc_priority_group.py +++ b/models/acc_priority_group.py @@ -162,7 +162,7 @@ class AccPriorityGroup(models.Model): def _prorata(self, data): for slot in data: - prod = data.get(slot).get("prod_totale") + remaining_prod = data.get(slot).get("prod_totale") priority_counters_conso_sum = self.get_conso_sum(data.get(slot)) total_affecte = 0.0 @@ -174,16 +174,22 @@ class AccPriorityGroup(models.Model): # si le compteur rentre en cours de periode il figure dans le # groupe de repartition mais pas dans les données brutes conso_k = None # dans ce cas on affecte 0 - if prod == 0 or priority_counters_conso_sum == 0 or conso_k is None: + if ( + remaining_prod < 0 + or priority_counters_conso_sum == 0 + or conso_k is None + ): part_a_affecter = 0.0 else: - part_a_affecter = min( - conso_k, prod * (conso_k / priority_counters_conso_sum) - ) + part_a_affecter = ( + remaining_prod * conso_k + ) / priority_counters_conso_sum + part_a_affecter = min(conso_k, part_a_affecter) data[slot]["affect"][counter] = part_a_affecter total_affecte += part_a_affecter - data[slot]["prod_totale"] = prod - total_affecte + data[slot]["prod_totale"] = remaining_prod - total_affecte + # possiblement on peut avoir affecté plus que la production total_affecte=496.00000000000006 return data @@ -199,16 +205,15 @@ class AccPriorityGroup(models.Model): counter_share = priority_group_counter.acc_counter_share conso_k = data.get(slot).get("conso").get(counter.name) if ( - remaining_prod == 0 + remaining_prod < 0 or total_share == 0 or conso_k is None or counter_share == 0 ): part_a_affecter = 0.0 else: - part_a_affecter = min( - conso_k, remaining_prod * (counter_share / total_share) - ) + part_a_affecter = (remaining_prod * counter_share) / total_share + part_a_affecter = min(conso_k, part_a_affecter) data[slot]["affect"][counter] = part_a_affecter total_affecte += part_a_affecter data[slot]["prod_totale"] = remaining_prod - total_affecte -- GitLab From fa7958431b6fe54dfaf761c956135af06327c894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Laporte?= <stephane.laporte@enercoop.org> Date: Mon, 17 Mar 2025 10:08:29 +0100 Subject: [PATCH 4/4] =?UTF-8?q?[ADD]=20Nouvelle=20r=C3=A9partition=20au=20?= =?UTF-8?q?sein=20d'un=20groupe=20de=20priorit=C3=A9=20:=20par=20quote-par?= =?UTF-8?q?t=20https://trello.com/c/JyNktdmx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/acc_operation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/acc_operation.py b/models/acc_operation.py index b44a71d..a8ba360 100644 --- a/models/acc_operation.py +++ b/models/acc_operation.py @@ -165,7 +165,7 @@ class AccOperation(models.Model): if total_production == 0: weight = 0.0 else: - weight = math.floor((affecte_counter * 100 / total_production) * 1e+3) / 1e+3 + weight = math.floor((affecte_counter * 100 / total_production) * 1e+6) / 1e+6 weights[counter_id] = weight slot_line = [] -- GitLab