diff --git a/__manifest__.py b/__manifest__.py index 21f087b048fe44e594e7a7f1c831dc2434068325..a4449faa26f6964efa3cdbe380f2d2bb86bea888 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -3,7 +3,7 @@ "summary": "Gestion de la facturation sur les opérations", "author": "Le Filament, Odoo SA", "website": "https://le-filament.com", - "version": "16.0.2.1.0", + "version": "16.0.2.2.0", "license": "AGPL-3", "depends": [ "base_strict_date_interval", diff --git a/migrations/16.0.2.2.0/post-migration.py b/migrations/16.0.2.2.0/post-migration.py new file mode 100644 index 0000000000000000000000000000000000000000..451188598a043189b010538d9534f5eeeadba4d8 --- /dev/null +++ b/migrations/16.0.2.2.0/post-migration.py @@ -0,0 +1,46 @@ +# Copyright 2023- Le Filament (https://le-filament.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from openupgradelib import openupgrade + +from odoo.exceptions import MissingError + + +def account_move_delivery_counter_to_many(env): + openupgrade.m2o_to_x2m( + cr=env.cr, + model=env["account.move"], + table="account_move", + field="acc_delivery_counter_ids", + source_field=openupgrade.get_legacy_name("acc_delivery_counter_id"), + ) + + openupgrade.drop_columns( + env.cr, + [("account_move", openupgrade.get_legacy_name("acc_delivery_counter_id"))], + ) + + +def populate_old_delivery_counter_id_to_account_move_line(env): + moves = env["account.move"].search([("acc_delivery_counter_ids", "!=", False)]) + + for move in moves: + for move_line in move.line_ids: + try: + if ( + not move_line.acc_delivery_counter_id + and len(move.acc_delivery_counter_ids) == 1 + ): + for counter in move.acc_delivery_counter_ids: + print(f"Assign counter {counter.name}") + move_line.acc_delivery_counter_id = counter + except MissingError: + continue + + +@openupgrade.migrate() +def migrate(env, version): + # account move acc_delivery_counter_id to acc_delivery_counter_ids + account_move_delivery_counter_to_many(env=env) + + # adapt new account template to old move + populate_old_delivery_counter_id_to_account_move_line(env=env) diff --git a/migrations/16.0.2.2.0/pre-migration.py b/migrations/16.0.2.2.0/pre-migration.py new file mode 100644 index 0000000000000000000000000000000000000000..5af59758f4949103cc31b8959872ec98cbe0b9d3 --- /dev/null +++ b/migrations/16.0.2.2.0/pre-migration.py @@ -0,0 +1,17 @@ +# Copyright 2023- Le Filament (https://le-filament.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from openupgradelib import openupgrade + +_columns_renames = { + "account_move": [ + ("acc_delivery_counter_id", None), + ], +} + + +@openupgrade.migrate() +def migrate(env, version): + # Update field names + + # Rename columns to preserve fields during upgrade + openupgrade.rename_columns(env.cr, _columns_renames) diff --git a/models/__init__.py b/models/__init__.py index 28635b70c9ba35e59eb8f90f45d7f9970bc83098..9c46795e48f2e6c1a4a439be6a1548c165f0a17f 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -7,3 +7,5 @@ from . import base_document_layout from . import res_company from . import res_config_settings from . import ir_actions_report +from . import res_partner +from . import acc_operation_role diff --git a/models/acc_operation.py b/models/acc_operation.py index cf7284174a6a67725f5b7238ac9a0d4b571f63ce..dfafbe23d007361b3df834bdb7912b7beb2167bd 100644 --- a/models/acc_operation.py +++ b/models/acc_operation.py @@ -17,6 +17,14 @@ class AccOperation(models.Model): "Facturation activée", ) + billing_agent_id = fields.Many2one( + comodel_name="res.partner", + compute="_compute_billing_agent_id", + string="Mandataire", + store=True, + readonly=True, + ) + acc_sale_price_conf_ids = fields.One2many( "acc.price.conf", "acc_operation_id", @@ -33,7 +41,7 @@ class AccOperation(models.Model): account_move_ids = fields.One2many( "account.move", "acc_operation_id", - domain=[("acc_delivery_counter_id", "!=", False)], + domain=[("acc_delivery_counter_ids", "!=", False)], string="Factures de consommation", ) account_move_count = fields.Integer( @@ -79,6 +87,20 @@ class AccOperation(models.Model): for res in self: res.account_move_count = len(res.account_move_ids) + @api.depends( + "partner_role_ids", "partner_role_ids.role", "partner_role_ids.partner_id" + ) + def _compute_billing_agent_id(self): + for operation in self: + billing_agent_ids = operation.partner_role_ids.filtered( + lambda r: r.role == "4_mand" + ) + operation.billing_agent_id = ( + billing_agent_ids[0].partner_id.id + if len(billing_agent_ids) > 0 + else False + ) + # ------------------------------------------------------ # Onchange / Constraints # ------------------------------------------------------ @@ -101,7 +123,7 @@ class AccOperation(models.Model): action["domain"] = [ ("move_type", "in", ("out_invoice", "out_refund")), ("acc_operation_id", "=", self.id), - ("acc_delivery_counter_id", "!=", False), + ("acc_delivery_counter_ids", "!=", False), ] action["context"] = { "default_move_type": "out_invoice", @@ -152,57 +174,30 @@ class AccOperation(models.Model): end_date=end_date, filter_date=True, ) - # Boucle sur chaque producteur du dictionnaire - for producer_id in energy_dict["prod"]["partner_ids"]: - producer = self.env["res.partner"].browse(producer_id) - # Vérification si une société existe pour le producteur, sinon on lève une - # erreur (impossible de facturer en son nom sans société liée) - if not producer.is_company_exist: - raise UserError( - _( - f"Le producteur {producer.name} n'a pas de société liéé.\n" - "Vérifiez d'avoir bien créé (et configuré) la société liée sur " - "chaque producteur sur sa fiche contact avant de facturer." - ) - ) - # Récupération de la liste de PRMs d'injection de ce producteur - inj_counter_ids = list( - energy_dict["prod"]["partner_ids"][producer_id][ - "acc_counter_ids" - ].keys() + + if self.billing_agent_id: + self.env["account.move"]._create_account_move_from_ope( + operation_id=self.id, + billing_partner_id=self.billing_agent_id, + is_billing_agent=True, + start_date=start_date, + end_date=end_date, + energy_dict=energy_dict, + contact_invoice_id=self.contact_invoice.id, ) + else: + # Boucle sur chaque producteur du dictionnaire + for producer_id in energy_dict["prod"]["partner_ids"]: + self.env["account.move"]._create_account_move_from_ope( + operation_id=self.id, + billing_partner_id=self.env["res.partner"].browse(producer_id), + is_billing_agent=False, + start_date=start_date, + end_date=end_date, + energy_dict=energy_dict, + contact_invoice_id=self.contact_invoice.id, + ) - # Pour chaque Consommateur / PRM, on génère une facture - for consumer_id in energy_dict["cons"]["partner_ids"]: - for counter_id in energy_dict["cons"]["partner_ids"][consumer_id][ - "acc_counter_ids" - ]: - counter_node = energy_dict["cons"]["partner_ids"][consumer_id][ - "acc_counter_ids" - ][counter_id] - # On calcule la consommation à facturer - # = somme de toutes les données journalières d'autoconsommation - # proratisée sur ce producteur - autocons_to_invoice = sum( - counter_node[date]["autocons_per_inj_partner"].get( - producer_id, 0.0 - ) - for date in counter_node - ) - if autocons_to_invoice > 0: - # Création de la facture - self.env["account.move"]._create_account_move_from_ope( - self.id, - producer, - inj_counter_ids, - consumer_id, - counter_id, - start_date, - end_date, - autocons_to_invoice, - counter_node, - self.contact_invoice.id, - ) return self.action_view_account_move() def _open_x2m_matrix(self, view_xmlid): diff --git a/models/acc_operation_role.py b/models/acc_operation_role.py new file mode 100644 index 0000000000000000000000000000000000000000..3b3d98eedaeb03a1b5550d2a9c3d892693cfab12 --- /dev/null +++ b/models/acc_operation_role.py @@ -0,0 +1,75 @@ +# Copyright 2024- Le Filament (https://le-filament.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError +from odoo.osv import expression + + +class AccOperationRole(models.Model): + _inherit = "acc.operation.role" + # ------------------------------------------------------ + # Fields declaration + # ------------------------------------------------------ + + role = fields.Selection( + selection_add=[ + ("4_mand", "Mandataire"), + ], + ondelete={"4_mand": "cascade"}, + ) + + # ------------------------------------------------------ + # SQL Constraints + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Default methods + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Computed fields / Search Fields + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Onchange / Constraints + # ------------------------------------------------------ + + @api.constrains("acc_operation_id", "role") + def _check_only_one_billing_agent(self): + """ + Vérification qu'il n'y a pas plusieurs rôles mandataire pour une même opération + """ + for role in self.filtered(lambda r: r.role == "4_mand"): + domain = expression.AND( + [ + [("id", "!=", role.id)], + [("acc_operation_id", "=", role.acc_operation_id.id)], + [("role", "=", "4_mand")], + ] + ) + conflicting_role = self.search( + domain, + limit=1, + ) + if conflicting_role: + raise ValidationError( + _( + "Impossible de créer un nouveau rôle mandataire, il y a déja un" + " défini pour cette opération et il ne peut y avoir qu'un " + "seul mandataire par opération.\n" + "Veuillez supprimer le mandataire existant avant d'en créer un " + "nouveau." + ) + ) + + # ------------------------------------------------------ + # CRUD methods (ORM overrides) + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Actions + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Business methods + # ------------------------------------------------------ diff --git a/models/account_move.py b/models/account_move.py index 3d099c2ec4687ad1810c228b152a0fdffb5aa087..366b9477db153e480de6d4303fb2e4b76e513c4f 100644 --- a/models/account_move.py +++ b/models/account_move.py @@ -26,12 +26,28 @@ class AccountMove(models.Model): string="PMO", related="acc_operation_id.pmo_id", ) - acc_delivery_counter_id = fields.Many2one( + + acc_delivery_counter_ids = fields.Many2many( "acc.counter", + relation="acc_account_delivery_counter_rel", string="PRM de soutirage", ondelete="restrict", ) + + acc_injection_counter_ids = fields.Many2many( + "acc.counter", + relation="acc_account_injection_counter_rel", + string="PRM d'injection", + ondelete="restrict", + ) + contact_invoice = fields.Many2one("res.partner", string="Une question ?") + + billing_agent_id = fields.Many2one( + "res.partner", + string="Mandataire de facturation", + ) + power_cons = fields.Float( "Consommation locale (index Enedis)", digits="Unité de Mesure" ) @@ -137,22 +153,90 @@ class AccountMove(models.Model): # ------------------------------------------------------ # Actions # ------------------------------------------------------ + def template_get_total_per_producer(self, producer, product_type): + total = 0 + for line in self.line_ids: + if ( + line.acc_injection_counter_id.partner_id.id == producer.id + and line.product_type == product_type + ): + total += line.price_subtotal + return total + + def template_get_total_per_delivery_prm(self, prm, product_type): + total_per_period = [] + for line in self.line_ids: + if ( + line.acc_delivery_counter_id.id == prm.id + and line.product_type == product_type + ): + found = False + for total in total_per_period: + if total.get("name") == line.name: + total["price_total"] += line.price_total + total["quantity"] += line.quantity + total["price_subtotal"] += line.price_subtotal + found = True + if found is False: + total_per_period.append( + { + "name": line.name, + "price_total": line.price_total, + "quantity": line.quantity, + "price_unit": line.price_unit, + "price_subtotal": line.price_subtotal, + } + ) + + for total in total_per_period: + total["tax"] = total.get("price_subtotal") * 0.2 + + return total_per_period + + def template_get_total_per_injection_prm(self, prmi, prms): + total_per_period = [] + for line in self.line_ids: + if line.product_type == "sale": + if ( + line.acc_injection_counter_id.id == prmi.id + and line.acc_delivery_counter_id.id == prms.id + ): + found = False + for total in total_per_period: + if total.get("name") == line.name: + total["price_total"] += line.price_total + total["quantity"] += line.quantity + total["price_subtotal"] += line.price_subtotal + found = True + if found is False: + total_per_period.append( + { + "name": line.name, + "price_total": line.price_total, + "quantity": line.quantity, + "price_unit": line.price_unit, + "price_subtotal": line.price_subtotal, + } + ) + + for total in total_per_period: + total["tax"] = total.get("price_subtotal") * 0.2 + + return total_per_period + def _create_account_move_from_ope( self, operation_id, - producer_id, - acc_injection_counter_ids, - partner_id, - acc_delivery_counter_id, + billing_partner_id, + is_billing_agent, start_date, end_date, - autocons_to_invoice, energy_dict, contact_invoice_id, ): """ Fonction permettant pour une période et une opération données : - - la création de la factures de vente par producteur/point + - la création de la facture de vente par producteur/point de soutirage. - la création des lignes de facture pour chaque point d'injection :param int operation_id : ID opération d'ACC (ID acc.operation) @@ -165,49 +249,188 @@ class AccountMove(models.Model): end_date : date de fin float autocons_to_invoice : auto-consommation du PRM de soutirage proratisée en fonction du producteur - dict energy_dict : détail de consommation journalier pour ce PRM + dict energy_dict : détail de consommation journalier int contact_invoice_id : ID à contacter si question (ID res.partner) """ company_id = self.env["res.company"].search( - [("partner_id", "=", producer_id.id)] - ) - account_move_id = self.create( - { - "acc_operation_id": operation_id, - "move_type": "out_invoice", - "company_id": company_id.id, - "contact_invoice": contact_invoice_id, - "partner_id": partner_id, - "acc_delivery_counter_id": acc_delivery_counter_id, - "power_cons": autocons_to_invoice, - "start_date": start_date, - "end_date": end_date, - "currency_id": company_id.currency_id.id, - "invoice_payment_term_id": ( - producer_id.property_payment_term_id.id or False - ), - } + [("partner_id", "=", billing_partner_id.id)], limit=1 ) + if not company_id: + user_type = "mandataire" if is_billing_agent else "producteur" + raise UserError( + _( + f"Le {user_type} {billing_partner_id.name} " + "n'a pas de société liéé.\n" + "Vérifiez d'avoir bien créé (et configuré) la société liée sur " + f"chaque {user_type} sur sa fiche contact avant de facturer." + ) + ) + acc_injection_counter_ids = [] - # Création des lignes de facture par PRM d'injection - for acc_injection_counter_id in acc_injection_counter_ids: - # Création des lignes avec prix de vente - account_move_id._process_create_account_move_line( - acc_injection_counter_id, - energy_dict, - "sale", + if is_billing_agent: + billing_agent_id = billing_partner_id + + for partner_id in energy_dict["prod"]["partner_ids"]: + acc_injection_counter_ids += list( + energy_dict["prod"]["partner_ids"][partner_id][ + "acc_counter_ids" + ].keys() + ) + else: + billing_agent_id = False + acc_injection_counter_ids += list( + energy_dict["prod"]["partner_ids"][billing_partner_id.id][ + "acc_counter_ids" + ].keys() ) - # Création des lignes avec taxe ACCISE - account_move_id._process_create_account_move_line( - acc_injection_counter_id, - energy_dict, - "accise", + account_move_id = [] + for consumer_id in energy_dict["cons"]["partner_ids"]: + # CAS les facture ne sont pas groupées par PRMs + if ( + self.env["res.partner"] + .browse(consumer_id) + .is_invoice_per_delivery_counter + ): + for delivery_counter_id in energy_dict["cons"]["partner_ids"][ + consumer_id + ]["acc_counter_ids"]: + counter_node = energy_dict["cons"]["partner_ids"][consumer_id][ + "acc_counter_ids" + ][delivery_counter_id] + if is_billing_agent: + autocons_to_invoice = sum( + counter_node[date]["autocons"] for date in counter_node + ) + else: + autocons_to_invoice = sum( + counter_node[date]["autocons_per_inj_partner"].get( + billing_partner_id.id, 0.0 + ) + for date in counter_node + ) + + account_move_id.append( + self.create( + { + "acc_operation_id": operation_id, + "move_type": "out_invoice", + "billing_agent_id": billing_agent_id.id + if is_billing_agent + else False, + "company_id": company_id.id, + "contact_invoice": contact_invoice_id, + "partner_id": consumer_id, + "power_cons": autocons_to_invoice, + "start_date": start_date, + "end_date": end_date, + "currency_id": company_id.currency_id.id, + "invoice_payment_term_id": ( + billing_partner_id.with_company( + company_id.id + ).property_payment_term_id.id + or False + ), + "acc_delivery_counter_ids": [ + fields.Command.set([delivery_counter_id]) + ], + "acc_injection_counter_ids": [ + fields.Command.set(acc_injection_counter_ids) + ], + } + ) + ) + # CAS ou les factures sont groupées + else: + consumer_node = energy_dict["cons"]["partner_ids"][consumer_id][ + "acc_counter_ids" + ] + if is_billing_agent: + autocons_to_invoice = sum( + consumer_node[delivery_counter_id][date]["autocons"] + for delivery_counter_id in consumer_node + for date in consumer_node[delivery_counter_id] + ) + else: + autocons_to_invoice = sum( + consumer_node[delivery_counter_id][date][ + "autocons_per_inj_partner" + ].get(billing_partner_id.id, 0.0) + for delivery_counter_id in consumer_node + for date in consumer_node[delivery_counter_id] + ) + ids = list( + energy_dict["cons"]["partner_ids"][consumer_id][ + "acc_counter_ids" + ].keys() + ) + account_move_id.append( + self.create( + { + "acc_operation_id": operation_id, + "billing_agent_id": billing_agent_id.id + if is_billing_agent + else False, + "move_type": "out_invoice", + "company_id": company_id.id, + "contact_invoice": contact_invoice_id, + "partner_id": consumer_id, + "power_cons": autocons_to_invoice, + "start_date": start_date, + "end_date": end_date, + "currency_id": company_id.currency_id.id, + "invoice_payment_term_id": ( + billing_partner_id.with_company( + company_id.id + ).property_payment_term_id.id + or False + ), + "acc_delivery_counter_ids": [fields.Command.set(ids)], + "acc_injection_counter_ids": [ + fields.Command.set(acc_injection_counter_ids) + ], + } + ) + ) + + if is_billing_agent: + acc_injection_counter_ids = [] + for partner_id in energy_dict["prod"]["partner_ids"]: + acc_injection_counter_ids += list( + energy_dict["prod"]["partner_ids"][partner_id][ + "acc_counter_ids" + ].keys() + ) + else: + acc_injection_counter_ids = list( + energy_dict["prod"]["partner_ids"][billing_partner_id.id][ + "acc_counter_ids" + ].keys() ) + # Création des lignes de facture par PRM de sous tirage + for move in account_move_id: + for delivery in move.acc_delivery_counter_ids: + for acc_injection_counter_id in acc_injection_counter_ids: + move._process_create_account_move_line( + delivery.id, + acc_injection_counter_id, + energy_dict, + "sale", + ) + + # Création des lignes avec taxe ACCISE + move._process_create_account_move_line( + delivery.id, + acc_injection_counter_id, + energy_dict, + "accise", + ) + def _process_create_account_move_line( self, + acc_delivery_counter_id, acc_injection_counter_id, energy_dict, type_line, @@ -216,7 +439,7 @@ class AccountMove(models.Model): Fonction permettant de créer une ligne de facture pour un point d'injection en fonction du type de ligne souhaité :param int acc_injection_counter_id : ID PRM d'injection (ID acc.counter) - dict energy_dict : détail de consommation journalier pour ce PRM + dict energy_dict : détail de consommation journalier char type_line : type de lignes ('sale' / 'accise') """ # Récupération des articles à utiliser sur la ligne de facture @@ -240,7 +463,7 @@ class AccountMove(models.Model): [ ("acc_operation_id", "=", self.acc_operation_id.id), ("acc_injection_counter_id", "=", injection_counter_id), - ("acc_delivery_counter_id", "=", self.acc_delivery_counter_id.id), + ("acc_delivery_counter_id", "=", acc_delivery_counter_id), ("type", "=", type_line), ], self.start_date, @@ -266,12 +489,17 @@ class AccountMove(models.Model): interval_end_datetime = local_to_utc( interval_end_date + relativedelta(days=1), "Europe/Paris" ) - # Calcul de la consommation à facturer sur les dates de l'intrvalle + + # Calcul de la consommation à facturer sur les dates de l'intervalle autocons_to_invoice = sum( - energy_dict[datetime]["autocons_per_inj_counter"].get( + energy_dict["cons"]["partner_ids"][self.partner_id.id][ + "acc_counter_ids" + ][acc_delivery_counter_id][datetime]["autocons_per_inj_counter"].get( acc_injection_counter_id, 0.0 ) - for datetime in energy_dict + for datetime in energy_dict["cons"]["partner_ids"][self.partner_id.id][ + "acc_counter_ids" + ][acc_delivery_counter_id] if datetime >= interval_start_datetime and datetime < interval_end_datetime ) @@ -284,6 +512,7 @@ class AccountMove(models.Model): "product_id": product_id.id, "product_type": type_line, "move_id": self.id, + "acc_delivery_counter_id": acc_delivery_counter_id, "acc_injection_counter_id": acc_injection_counter_id, "start_date": interval_start_date, "end_date": interval_end_date, @@ -301,7 +530,7 @@ class AccountMove(models.Model): "oacc_account.email_template_edi_invoice_oacc" if all( move.acc_operation_id - and move.acc_delivery_counter_id + and move.acc_delivery_counter_ids and move.move_type == "out_invoice" for move in self ) @@ -317,7 +546,7 @@ class AccountMove(models.Model): for invoice in self.filtered( lambda m: m.state == "posted" and m.acc_operation_id - and m.acc_delivery_counter_id + and m.acc_delivery_counter_ids and m.move_type in ("out_invoice", "out_refund") ): template_id.send_mail(invoice.id) diff --git a/models/account_move_line.py b/models/account_move_line.py index b7d4b7b0fa225296ba5f77832d661fbdbbe0b10c..0be1c37a57aa084de10756f9d3aefb6e05eb537c 100644 --- a/models/account_move_line.py +++ b/models/account_move_line.py @@ -16,12 +16,12 @@ class AccountMoveLine(models.Model): acc_injection_counter_id = fields.Many2one( "acc.counter", string="Point d'injection", - ondelete="cascade", + ondelete="restrict", ) acc_delivery_counter_id = fields.Many2one( "acc.counter", string="Point de soutirage", - related="move_id.acc_delivery_counter_id", + ondelete="restrict", ) start_date = fields.Date("Début de la période") end_date = fields.Date("Fin de la période") diff --git a/models/res_partner.py b/models/res_partner.py new file mode 100644 index 0000000000000000000000000000000000000000..6d8ae593c0020bcdc9c7bcf76e259946e605ea60 --- /dev/null +++ b/models/res_partner.py @@ -0,0 +1,73 @@ +# Copyright 2021- Le Filament (https://le-filament.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +from odoo import api, fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + # ------------------------------------------------------ + # Fields declaration + # ------------------------------------------------------ + + is_invoice_per_delivery_counter = fields.Boolean("Une facture par PRM-S") + is_billing_agent = fields.Boolean( + "Est un mandataire", compute="_compute_billing_agent_roles", store=True + ) + + # ------------------------------------------------------ + # SQL Constraints + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Default methods + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Computed fields / Search Fields + # ------------------------------------------------------ + @api.depends("acc_operation_role_ids", "acc_operation_role_ids.role") + def _compute_billing_agent_roles(self): + for partner in self: + roles = partner.acc_operation_role_ids.mapped("role") + partner.is_billing_agent = True if "4_mand" in roles else False + + # ------------------------------------------------------ + # Onchange / Constraints + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Business methods + # ------------------------------------------------------ + + # ------------------------------------------------------ + # CRUD methods (ORM overrides) + # ------------------------------------------------------ + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + # If billing agent, create a sequence + if any( + role and role[2] and role[2].get("role") == "4_mand" + for role in vals.get("acc_operation_role_ids", [False, False, []]) + ): + vals["ref_producer"] = self.env["ir.sequence"].next_by_code( + "res.partner" + ) + result = super().create(vals_list) + return result + + def write(self, vals): + """ + Overwrite write method to initialize ref_producer if empty + """ + if ( + any( + role and role[2] and role[2].get("role") == "4_mand" + for role in vals.get("acc_operation_role_ids", [False, False, []]) + ) + and not self.ref_producer + ): + vals["ref_producer"] = self.env["ir.sequence"].next_by_code("res.partner") + return super().write(vals) diff --git a/report/account_move_template.xml b/report/account_move_template.xml index fd434973db12ee782ae4b7275afb60bd05753875..870542985d07d8cdb3f69bce13747355a878113c 100644 --- a/report/account_move_template.xml +++ b/report/account_move_template.xml @@ -2,10 +2,9 @@ <!-- Copyright 2021- Le Filament (https://le-filament.com) License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> <odoo> - <template id="report_account_move_document_oacc"> <t t-call="web.external_layout"> - <t t-set="o" t-value="o.with_context(lang=lang)" /> + <t t-set="o" t-value="o.with_context(lang=lang)"/> <div class="page"> <div class="row mb-4"> <div class="col-4"> @@ -16,210 +15,292 @@ alt="Logo" /> </t> - <t t-else=""><h4 + <t t-else=""> + <h4 class="text-center" t-field="o.company_id.name" - /></t> + /> + </t> </div> <div class="col-8 text-center"> <p class="fw-bold"> <span class="text-uppercase mt0 " - >Votre facture d'électricité d'origine renouvelable et locale</span><br + >Votre facture d'électricité d'origine renouvelable et locale + </span> + <br /> - <span>Facture <t t-esc="o.name" /> du <span + <span>Facture + <t t-out="o.name"/> + du + <span t-field="o.date" t-options="{'format': 'dd/MM/yyyy'}" - /></span> + /> + </span> </p> - <span class="font-mini">Adressée par voie électronique<br /> - Facture de consommation</span> + <span class="font-mini">Adressée par voie électronique + <br/> + Facture de consommation + </span> </div> </div> + <!-- marge grise info reference--> <div class="row mt-4 mb-3"> <div class="col-4 grey-div"> <h5 class="fw-bold mb-3 mt-2 text-center" - >Vos références utiles</h5> + >Vos références utiles + </h5> <p> - <span class="fw-bold">Titulaire du contrat</span><br /> - <span t-field="o.partner_id.name" /><br /> - <span t-field="o.partner_id.street" /><br /> - <t t-if="o.partner_id.street2"><span + <span class="fw-bold">Titulaire du contrat</span> + <br/> + <span t-field="o.partner_id.name"/> + <br/> + <span t-field="o.partner_id.street"/> + <br/> + <t t-if="o.partner_id.street2"> + <span t-field="o.partner_id.street2" - /><br /></t> - <t t-if="o.partner_id.city"><span + /> + <br/> + </t> + <t t-if="o.partner_id.city"> + <span t-field="o.partner_id.zip" - /> - <span t-field="o.partner_id.city" /><br /></t> + /> + - + <span t-field="o.partner_id.city"/> + <br/> + </t> <t t-if="o.partner_id.email"> - <span t-field="o.partner_id.email" /><br /> + <span t-field="o.partner_id.email"/> + <br/> </t> <t t-if="o.partner_id.phone"> - <span t-field="o.partner_id.phone" /><br /> + <span t-field="o.partner_id.phone"/> + <br/> </t> <t t-if="o.partner_id.vat"><span class="fw-bold" >N°TVA:</span><br /> <span t-field="o.partner_id.vat" /><br /> </t> - <span - class="fw-bold" - >Point Référence Mesures (PRM-C):</span><br /> - <span t-field="o.acc_delivery_counter_id.name" /><br /> - <span class="fw-bold">Lieu de consommation:</span><br /> - <span t-field="o.acc_delivery_counter_id.street" /><br /> - <t t-if="o.acc_delivery_counter_id.street2"><span - t-field="o.acc_delivery_counter_id.street2" - /><br /></t> - <t t-if="o.acc_delivery_counter_id.zip"> - <span t-field="o.acc_delivery_counter_id.zip" /> - <span - t-field="o.acc_delivery_counter_id.city" - /><br /></t> + + <br/> + <br/> </p> <h5 class="fw-bold mb-3 mt-5 text-center" - >Référence de l'opération</h5> + >Référence de l'opération + </h5> <p> - <span class="fw-bold">Nom du producteur</span><br /> - <span t-field="o.company_id.name" /><br /> - <span class="fw-bold">Nom OACC⁽²⁾</span><br /> - <span t-field="o.acc_operation_id.description" /><br /> - <span class="fw-bold">Numéro OACC</span><br /> - <span t-field="o.acc_operation_id.name" /><br /> + <span class="fw-bold">Emetteur de la facture</span> + <br/> + <!-- Si mandataire de facturation--> + <t t-if="o.billing_agent_id"> + <span t-field="o.billing_agent_id.name"/> + <br/> + </t> + <t t-else=""> + <span t-field="o.company_id.name"/> + <br/> + </t> + <span class="fw-bold">Nom OACC⁽²⁾</span> + <br/> + <span t-field="o.acc_operation_id.description"/> + <br/> + <span class="fw-bold">Numéro OACC</span> + <br/> + <span t-field="o.acc_operation_id.name"/> + <br/> <span class="fw-bold" - >Références Points d'Injection (PRM-I)</span><br /> + >Références Points d'Injection (PRM-I) + </span> + <br/> <t t-set="inj_ids" t-value="o.acc_operation_id.acc_injection_period_ids.mapped('acc_counter_id')" /> <t t-foreach="inj_ids" t-as="inj"> - <span t-field="inj.name" /><br /> + <span t-field="inj.name"/> + <br/> </t> <t t-if="o.acc_operation_id.acc_origine_ids"> - <span class="fw-bold">Origines : </span> + <span class="fw-bold">Origines :</span> <t t-foreach="o.acc_operation_id.acc_origine_ids" t-as="origine" > <t t-if="origine_first"> - <span t-field="origine.name" /> + <span t-field="origine.name"/> </t> <t t-else=""> - - <span t-field="origine.name" /> + - + <span t-field="origine.name"/> </t> </t> </t> </p> <h5 class="fw-bold mb-3 mt-5 text-center" - >Une question sur votre facture ?</h5> + >Une question sur votre facture ? + </h5> <p class="mb-2"> - <span class="fw-bold" t-field="o.contact_invoice.name" /><br + <span class="fw-bold" t-field="o.contact_invoice.name"/> + <br /> - <span t-field="o.contact_invoice.street" /><br /> - <t t-if="o.contact_invoice.street2"><span + <span t-field="o.contact_invoice.street"/> + <br/> + <t t-if="o.contact_invoice.street2"> + <span t-field="o.contact_invoice.street2" - /><br /></t> - <t t-if="o.contact_invoice.zip"> - <span t-field="o.contact_invoice.zip" /> - <span + /> + <br/> + </t> + <t t-if="o.contact_invoice.zip"> + <span t-field="o.contact_invoice.zip"/> + - + <span t-field="o.contact_invoice.city" - /><br /></t> + /> + <br/> + </t> <t t-if="o.contact_invoice.phone"> - <span t-field="o.contact_invoice.phone" /><br /> + <span t-field="o.contact_invoice.phone"/> + <br/> </t> <t t-if="o.contact_invoice.email"> - <span t-field="o.contact_invoice.email" /> + <span t-field="o.contact_invoice.email"/> </t> </p> </div> - <div class="col-1" /> + <!-- haut de page--> + <div class="col-1"/> <div class="col-7"> <div class="text-end"> <h5 class="fw-bold">Coordonnées de facturation</h5> <p> - <span class="fw-bold" t-field="o.partner_id.name" /><br + <span class="fw-bold" t-field="o.partner_id.name"/> + <br /> - <span t-field="o.partner_id.street" /><br /> - <t t-if="o.partner_id.street2"><span + <span t-field="o.partner_id.street"/> + <br/> + <t t-if="o.partner_id.street2"> + <span t-field="o.partner_id.street2" - /><br /></t> - <span t-field="o.partner_id.zip" /> - <span + /> + <br/> + </t> + <span t-field="o.partner_id.zip"/> + - + <span t-field="o.partner_id.city" - /><br /> + /> + <br/> </p> </div> + <!-- synthese--> <div class="o_recap_table mt-3"> <div class="text-center"> <h3 class="text-uppercase" - >Synthèse de votre facture</h3> + >Synthèse de votre facture + </h3> <p class="fst-italic">Détails en pages suivantes</p> - <h4>Période de livraison concernée : <br /> - du <span + <h4>Période de livraison concernée : + <br/> + du + <span t-field="o.start_date" t-options="{'format': 'dd/MM/yyyy'}" - /> au <span + /> + au + <span t-field="o.end_date" t-options="{'format': 'dd/MM/yyyy'}" /> </h4> </div> + <!-- recap cadre oblongue--> <table class="table table-sm table-none o_main_table mb-4" name="account_move_line_table" > <tr> - <td><strong><h5 - >Electricité autoconsommée</h5></strong></td> + <td> + <strong> + <h5 + >Electricité autoconsommée + </h5> + </strong> + </td> <td class="text-end"> - <strong><h4><span + <strong> + <h4> + <span t-field="o.power_cons" - /> kWh</h4></strong> + /> + kWh + </h4> + </strong> </td> </tr> <tr> <td>Total électricité locale</td> <td class="text-end"> - <span t-field="o.amount_elec_tot" /> <span - >HT</span> + <span t-field="o.amount_elec_tot"/> + <span + >HT + </span> </td> </tr> <tr> <td>Total ACCISE</td> <td class="text-end"> - <span t-field="o.amount_accise_tot" /> <span - >HT</span> + <span t-field="o.amount_accise_tot"/> + <span + >HT + </span> </td> </tr> <tr t-if="o.amount_divers_tot > 0"> <td>Total Frais Divers</td> <td class="text-end"> - <span t-field="o.amount_divers_tot" /> <span - >HT</span> + <span t-field="o.amount_divers_tot"/> + <span + >HT + </span> </td> </tr> <tr> - <td /> - <td /> + <td/> + <td/> </tr> <tr> - <td /> - <td /> + <td/> + <td/> </tr> <tr> - <td><strong><h5>Total H.T.</h5></strong></td> + <td> + <strong> + <h5>Total H.T.</h5> + </strong> + </td> <td class="text-end"> - <strong><h4 + <strong> + <h4 class="text-nowrap" t-field="o.amount_untaxed_signed" - /></strong> + /> + </strong> </td> </tr> - <t t-set="tax_totals" t-value="o.tax_totals" /> + <t t-set="tax_totals" t-value="o.tax_totals"/> <t t-foreach="tax_totals['subtotals']" t-as="subtotal"> <t t-set="subtotal_to_show" @@ -233,30 +314,32 @@ <t t-if="tax_totals['display_tax_base']"> <td> <span - t-esc="amount_by_group['tax_group_name']" + t-out="amount_by_group['tax_group_name']" /> - <span class="text-nowrap"> on + <span class="text-nowrap">on <t - t-esc="amount_by_group['formatted_tax_group_base_amount']" + t-out="amount_by_group['formatted_tax_group_base_amount']" /> </span> </td> - <td class="text-end o_price_total"> + <td class="text-end"> <span class="text-nowrap" - t-esc="amount_by_group['formatted_tax_group_amount']" + t-out="amount_by_group['formatted_tax_group_amount']" /> </td> </t> <t t-else=""> - <td><span + <td> + <span class="text-nowrap" - t-esc="amount_by_group['tax_group_name']" - /></td> - <td class="text-end o_price_total"> + t-out="amount_by_group['tax_group_name']" + /> + </td> + <td class="text-end"> <span class="text-nowrap" - t-esc="amount_by_group['formatted_tax_group_amount']" + t-out="amount_by_group['formatted_tax_group_amount']" /> </td> </t> @@ -265,48 +348,62 @@ </t> <tr> - <td /> - <td /> + <td/> + <td/> </tr> <tr> - <td><strong><h5>Total TTC⁽¹⁾</h5></strong></td> + <td> + <strong> + <h5>Total TTC⁽¹⁾</h5> + </strong> + </td> <td class="text-end"> - <strong><h4 + <strong> + <h4 class="text-nowrap" t-field="o.amount_total_signed" - /></strong> + /> + </strong> </td> </tr> - + <!-- details facturation --> </table> <t t-if="o.company_id.is_collectivite"> - <div class="row mt-4"> + <div class="row mt-4"> <div class="col-12"> - <h6 t-field="o.company_id.collectivite_text" /> + <h6 t-field="o.company_id.collectivite_text"/> </div> - </div> + </div> </t> <t t-else=""> <div class="row mt-2"> <div class="col-12"> <h5 class="font-weight-bold" - >Modalités de paiement :</h5> - <h6>Référence facture : <t + >Modalités de paiement : + </h5> + <h6>Référence facture : + <t t-out="o.name" - /></h6> + /> + </h6> <div class="o_payment_terms"> <span t-field="o.invoice_payment_term_id.note" name="payment_term" /> </div> - <h6>Délai de règlement : à facturation<br /> - Date limite de règlement : <strong><span + <h6>Délai de règlement : à facturation + <br/> + Date limite de règlement : + <strong> + <span class="font-weight-bold" t-field="o.invoice_date_due" t-options="{'format': 'dd/MM/yyyy'}" - /></strong></h6> + /> + </strong> + </h6> </div> </div> </t> @@ -314,21 +411,161 @@ </div> </div> + + <!-- bas de page--> <div class="row mt-2"> <div class="col-12 fst-italic font-mini"> <p> - (1) Le total à payer correspond à l’électricité autoconsommée et à l'ACCISE - (2) OACC : Opération d'Auto-Consommation Collective - <br /><span t-if="o.narration" t-field="o.narration" /> + (1) Le total à payer correspond à l’électricité autoconsommée et à l'ACCISE - (2) OACC : + Opération d'Auto-Consommation Collective + <br/> + <span t-if="o.narration" t-field="o.narration"/> </p> </div> </div> + + <!-- page 2 (si mandataire) --> + <t t-if="o.billing_agent_id"> + <div class="row mt-4 mb-5" style="page-break-before:always;"> + <div class="col-12 text-center text-uppercase"> + <h4>Votre facture + <t t-out="o.name"/> + en détail + </h4> + <span class="text-uppercase ">Autoconsommation Collective + <t + t-out="o.acc_operation_id.name" + /> + </span> + <br/> + </div> + </div> + + <div class="row mt-4 mb-3"> + <div class="col-12"> + La présente facture est établie par + <t + t-out="o.billing_agent_id.name" + /> + au nom et pour le compte du/des + + mandant(s), dont les coordonnées figurent dans le tableau ci-dessous. + <br + /> + Sur la période du + <t t-out="o.start_date"/> + au <t + t-out="o.end_date" + />, voici la répartition des montants par producteur mandant. + <br/><br/> + + <table + class="table table-sm o_main_table" + name="account_move_line_table" + > + <thead> + <tr> + <th>Producteur</th> + <th class="text-center">Adresse</th> + <th class="text-center">N° TVA</th> + <th class="text-center">Electricité locale</th> + <th class="text-center">ACCISE</th> + <th class="text-center">TVA 20%</th> + <th class="text-center">TOTAL</th> + </tr> + </thead> + <tbody> + <t + t-set="producers" + t-value="o.acc_injection_counter_ids.mapped('partner_id')" + /> + <t t-foreach="producers" t-as="producer"> + <t + t-set="lines_sale" + t-value="o.line_ids.filtered(lambda line: line.acc_injection_counter_id.partner_id == producer and line.product_type == 'sale' and line.display_type == 'product')" + /> + <t t-set="current_total"/> + <t t-foreach="lines_sale" t-as="line"> + <t + t-value="current_total + line.price_total" + /> + </t> + <tr> + <td> + <span t-out="producer.name"/> + </td> + <td> + <span + t-out="producer.street + ' ' + producer.zip + ' ' + producer.city" + /> + </td> + <td> + <span t-out="producer.vat"/> + </td> + <td> + <t + t-set="total_sale_per_producer" + t-value="o.template_get_total_per_producer(producer, 'sale')" + /> + <span + t-out="total_sale_per_producer" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' + /> + + </td> + <td> + <t + t-set="total_accise_per_producer" + t-value="o.template_get_total_per_producer(producer, 'accise')" + /> + <span + t-out="total_accise_per_producer" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' + /> + </td> + <td> + <t + t-set="tva" + t-value="(total_sale_per_producer + total_accise_per_producer) * 0.2" + /> + <span + t-out="tva" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' + /> + </td> + <td> + <span + t-out="total_sale_per_producer + total_accise_per_producer + tva" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' + /> + </td> + + </tr> + </t> + + </tbody> + </table> + <br/> + <br/> + <br/> + <br/> + <br/> + <br/> + <br/> + </div> + </div> + </t> + + <!-- page 3--> + <!-- haut de page--> <t - t-set="prmi_ids" - t-value="o.line_ids.mapped('acc_injection_counter_id')" + t-set="prms_ids" + t-value="o.line_ids.mapped('acc_delivery_counter_id')" /> - <t t-foreach="prmi_ids" t-as="injection"> + <t t-foreach="prms_ids" t-as="delivery"> + <!-- Une page par PRMs--> <div class="row" style="page-break-before:always;"> - <div class="col-4"> + <div class="col-4 "> <t t-if="o.company_id.logo"> <img t-att-src="image_data_uri(o.company_id.logo)" @@ -336,102 +573,117 @@ alt="Logo" /> </t> - <t t-else=""><h4 + <t t-else=""> + <h4 class="text-center" t-field="o.company_id.name" - /></t> + /> + </t> </div> - <div class="col-8" /> + <div class="col-8"/> </div> - <div class="row mt-4 mb-5"> <div class="col-12 text-center text-uppercase"> - <h4>Votre facture <t t-esc="o.name" /> en détail</h4> - <span class="text-uppercase ">Autoconsommation Collective <t - t-esc="o.acc_operation_id.name" - /></span><br /> - <h5 class="fw-boldd ">PRM-I : <span + <h4>Votre facture + <t t-out="o.name"/> + en détail + </h4> + <span + class="text-uppercase " + >Autoconsommation Collective + <t + t-out="o.acc_operation_id.name" + /> + </span> + <br/> + <h5 class="fw-bold ">PRM-S : + <span class="fw-bold " - t-field="injection.name" - /></h5> + t-field="delivery.name" + /> + </h5> + <span class="fw-bold"> + <t + t-out="delivery.street + ' ' + delivery.zip + ' ' + delivery.city" + /> + </span> </div> </div> - <div class="row mt-5 "> - <div class="col-12 "> - <!-- Table pour l'électricité locale--> - <h6 class="text-uppercase "><strong> - Electricité locale</strong></h6> + + <div class="row mt-4 mb-5 per-prms-recap-table"> + <div class="col-12"> + <!-- Table pour l'électricité locale--> + <h6 class="text-uppercase "> + <strong> + Total Electricité locale du PRM-S + </strong> + </h6> <table - class="table table-sm o_main_table" + class="table table-sm o_main_table table-prm" name="account_move_line_table" > <thead> <tr> <th>Période</th> - <th class="text-center">Quantité consommée</th> - <th class="text-center">Prix unitaire</th> + <th + class="text-center" + >Quantité consommée + </th> <th class="text-center">Total HT</th> - <th class="text-center">TVA</th> + <th class="text-center">TVA 20%</th> <th class="text-center">Total TTC</th> </tr> </thead> <tbody> - <t t-set="current_total" t-value="0" /> + <t t-set="current_total" t-value="0"/> <t t-set="lines_sale" - t-value="o.line_ids.filtered(lambda line: line.acc_injection_counter_id == injection and line.product_type == 'sale' and line.display_type == 'product')" + t-value="o.template_get_total_per_delivery_prm(delivery, 'sale')" /> <t t-foreach="lines_sale" t-as="line"> <t t-set="current_total" - t-value="current_total + line.price_total" + t-value="current_total + line.get('price_total')" /> <tr class=""> <td> <span - t-field="line.name" + t-out="line.get('name')" t-options="{'widget': 'text'}" /> </td> <td class="text-center"> <span - class="text-nowrap" - t-field="line.quantity" - /><span> kWh</span> - </td> - <td class="text-center"> - <span - class="text-nowrap" - t-field="line.price_unit" - /><span> €/kWh</span> + t-out="line.get('quantity')" + t-options='{"widget": "float", "precision": 2}' + /> + <span>kWh</span> </td> <td class="text-center"> <span - class="text-nowrap" - t-field="line.price_subtotal" + t-out="line.get('price_subtotal')" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' /> </td> - <td - t-attf-class="text-start {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}" - > + <td class="text-center"> <span - t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_ids))" - id="line_tax_ids" + t-out="line.get('tax')" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' /> </td> <td class="text-center"> <span - class="text-nowrap" - t-field="line.price_total" + t-out="line.get('price_total')" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' /> </td> </tr> </t> <tr class="border-black o_total fw-bold"> - <td colspan="5">MONTANT TOTAL</td> + <td colspan="4">MONTANT TOTAL</td> <td class="text-center fw-bold"> <span - t-esc="current_total" + t-out="current_total" t-options='{"widget": "monetary", "display_currency": o.currency_id}' /> </td> @@ -439,84 +691,87 @@ </tbody> </table> - <!-- Table pour les taxes TCFE--> - <h6 class="mt-4 text-uppercase"><strong> - ACCISE*</strong></h6> + <!-- Table pour les taxes TCFE--> + <h6 class="mt-4 text-uppercase"> + <strong> + TOTAL ACCISE* du PRM-S + </strong> + </h6> <table - class="table table-sm o_main_table" + class="table table-sm o_main_table table-prm" name="account_move_line_table" > <thead> <tr> <th>Période</th> - <th class="text-center">Quantité consommée</th> + <th + class="text-center" + >Quantité consommée + </th> <th class="text-center">Prix unitaire</th> <th class="text-center">Total HT</th> - <th class="text-center">TVA</th> + <th class="text-center">TVA 20%</th> <th class="text-center">Total TTC</th> </tr> </thead> <tbody> - <t t-set="current_total" t-value="0" /> + <t t-set="current_total" t-value="0"/> <t - t-set="lines_accise" - t-value="o.line_ids.filtered(lambda line: line.acc_injection_counter_id == injection and line.product_type == 'accise')" + t-set="lines_sale" + t-value="o.template_get_total_per_delivery_prm(delivery, 'accise')" /> - <t t-foreach="lines_accise" t-as="line"> + <t t-foreach="lines_sale" t-as="line"> <t t-set="current_total" - t-value="current_total + line.price_total" + t-value="current_total + line.get('price_total')" /> - <t t-if="line.product_type == 'accise'"> - <tr class=""> - <td> - <span - t-field="line.name" - t-options="{'widget': 'text'}" - /> - </td> - <td class="text-center"> - <span - class="text-nowrap" - t-field="line.quantity" - /><span> kWh</span> - </td> - <td class="text-center"> - <span - class="text-nowrap" - t-field="line.price_unit" - /><span> €/kWh</span> - </td> - <td class="text-center"> + <tr class=""> + <td> <span - class="text-nowrap" - t-field="line.price_subtotal" - /> + t-out="line.get('name')" + t-options="{'widget': 'text'}" + /> </td> - <td - t-attf-class="text-start {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}" - > + <td class="text-center"> <span - t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_ids))" - id="line_tax_ids" - /> + t-out="line.get('quantity')" + t-options='{"widget": "float", "precision": 2}' + /> + <span>kWh</span> </td> <td class="text-center"> <span - class="text-nowrap" - t-field="line.price_total" - /> + t-out="line.get('price_unit')" + t-options='{"widget": "float", "precision": 5}' + /> + <span>€/kWh</span> </td> - </tr> - - </t> - <t t-else="" /> + <td class="text-center"> + <span + t-out="line.get('price_subtotal')" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' + /> + </td> + <td class="text-center"> + <span + t-out="line.get('tax')" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' + /> + </td> + <td class="text-center"> + <span + t-out="line.get('price_total')" + t-options='{"widget": "float", "precision": 2}' + /> + <span> €</span> + </td> + </tr> </t> <tr class="border-black o_total fw-bold"> <td colspan="5">MONTANT TOTAL</td> <td class="text-center"> <span - t-esc="current_total" + t-out="current_total" t-options='{"widget": "monetary", "display_currency": o.currency_id}' /> </td> @@ -524,100 +779,206 @@ </tbody> </table> <p class="mt-5 fst-italic"> - <span t-if="o.terms_tax" t-field="o.terms_tax" /> + <span t-if="o.terms_tax" t-field="o.terms_tax"/> </p> </div> </div> + + <!-- Boucle des prms --> + <t + t-set="prmi_ids" + t-value="o.line_ids.mapped('acc_injection_counter_id')" + /> + <t t-foreach="prmi_ids" t-as="injection"> + + <h6 class="mt-4 text-uppercase"> + <strong> + Origine : <t + t-out="injection.partner_id.name" + />, PRM-I n°<t t-out="injection.name"/>, + <t + t-out="injection.street" + /> + </strong> + </h6> + <table + class="table table-sm o_main_table" + name="account_move_line_table" + > + <thead> + <tr> + <th>Période</th> + <th + class="text-center" + >Quantité consommée + </th> + <th class="text-center">Prix unitaire</th> + <th class="text-center">Total HT</th> + <th class="text-center">TVA 20%</th> + <th class="text-center">Total TTC</th> + </tr> + </thead> + <tbody> + <t t-set="current_total" t-value="0"/> + <t + t-set="lines_sale" + t-value="o.template_get_total_per_injection_prm(injection, delivery)" + /> + <t t-foreach="lines_sale" t-as="line"> + <t + t-set="current_total" + t-value="current_total + line.get('price_total')" + /> + <tr class=""> + <td> + <span + t-out="line.get('name')" + t-options="{'widget': 'text'}" + /> + </td> + <td class="text-center"> + <span + t-out="line.get('quantity')" + t-options='{"widget": "float", "precision": 2}' + /> + <span>kWh</span> + </td> + <td class="text-center"> + <span + t-out="line.get('price_unit')" + t-options='{"widget": "float", "precision": 5}' + /> + <span>€/kWh</span> + </td> + <td class="text-center"> + <span + t-out="line.get('price_subtotal')" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' + /> + </td> + <td class="text-center"> + <span + t-out="line.get('tax')" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' + /> + </td> + <td class="text-center"> + <span + t-out="line.get('price_total')" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' + /> + </td> + </tr> + </t> + <tr class="border-black o_total fw-bold"> + <td colspan="5">MONTANT TOTAL</td> + <td class="text-center fw-bold"> + <span + t-out="current_total" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' + /> + </td> + </tr> + </tbody> + </table> + </t> + <!-- fin boucle prmS--> </t> -<!-- Partie pour les lignes de facture supplémentaires --> + <!-- Partie pour les lignes de facture supplémentaires --> <t t-set="lines_sale" t-value="o.line_ids.filtered(lambda line: not line.acc_injection_counter_id and not line.product_type and line.display_type == 'product')" /> - <t t-if="lines_sale"> - <div class="row" style="page-break-before:always;"> - <div class="col-4"> - <t t-if="o.company_id.logo"> - <img + <t t-if="lines_sale"> + <div class="row" style="page-break-before:always;"> + <div class="col-4"> + <t t-if="o.company_id.logo"> + <img t-att-src="image_data_uri(o.company_id.logo)" style="max-height: 80px;" alt="Logo" /> - </t> - <t t-else=""><h4 + </t> + <t t-else=""> + <h4 class="text-center" t-field="o.company_id.name" - /></t> - </div> - <div class="col-8" /> + /> + </t> </div> - <div class="row mt-4 mb-5"> - <div class="col-12 text-center text-uppercase"> - <h4>Votre facture <t t-esc="o.name" /> en détail</h4> - <span + <div class="col-8"/> + </div> + <div class="row mt-4 mb-5"> + <div class="col-12 text-center text-uppercase"> + <h4>Votre facture + <t t-out="o.name"/> + en détail + </h4> + <span class="text-uppercase " - >Autoconsommation Collective <t - t-esc="o.acc_operation_id.name" - /></span> - </div> + >Autoconsommation Collective + <t + t-out="o.acc_operation_id.name" + /> + </span> </div> + </div> - <div class="row mt-5 "> - <div class="col-12"> - <!-- Table les lignes spécifiques --> - <h6>Frais divers</h6> - <table + <div class="row mt-5 "> + <div class="col-12"> + <!-- Table les lignes spécifiques --> + <h6>Frais divers</h6> + <table class="table table-sm o_main_table" name="account_move_line_table" > - <thead> - <tr> - <th>Description</th> - <th class="text-center">Quantité</th> - <th class="text-center">Prix unitaire</th> - <th class="text-center">Total HT</th> - <th class="text-center">TVA</th> - <th class="text-center">Total TTC</th> - </tr> - </thead> - <tbody> - <t t-set="current_total" t-value="0" /> - <t t-foreach="lines_sale" t-as="line"> - <t + <thead> + <tr> + <th>Description</th> + <th class="text-center">Quantité</th> + <th class="text-center">Prix unitaire</th> + <th class="text-center">Total HT</th> + <th class="text-center">TVA 20%</th> + <th class="text-center">Total TTC</th> + </tr> + </thead> + <tbody> + <t t-set="current_total" t-value="0"/> + <t t-foreach="lines_sale" t-as="line"> + <t t-set="current_total" t-value="current_total + line.price_total" /> - <tr class=""> - <td> - <span + <tr class=""> + <td> + <span t-field="line.name" t-options="{'widget': 'text'}" /> - </td> - <td class="text-center"> - <span + </td> + <td class="text-center"> + <span class="text-nowrap" t-field="line.quantity" /> - </td> - <td class="text-center"> - <span + </td> + <td class="text-center"> + <span class="text-nowrap" t-field="line.price_unit" - /><span> €</span> - </td> - <td class="text-center"> + /> + <span>€</span> + </td> + <td class="text-center"> <span class="text-nowrap" t-field="line.price_subtotal" /> </td> - <td - t-attf-class="text-start {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}" - > + <td class="text-center"> <span - t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_ids))" - id="line_tax_ids" + t-field="line.price_subtotal * 0.2" + t-options='{"widget": "monetary", "display_currency": o.currency_id}' /> </td> <td class="text-center"> @@ -626,22 +987,22 @@ t-field="line.price_total" /> </td> - </tr> - </t> - <tr class="border-black o_total fw-bold"> - <td colspan="5">MONTANT TOTAL</td> - <td class="text-center"> - <span - t-esc="current_total" + </tr> + </t> + <tr class="border-black o_total fw-bold"> + <td colspan="5">MONTANT TOTAL</td> + <td class="text-center"> + <span + t-out="current_total" t-options='{"widget": "monetary", "display_currency": o.currency_id}' /> - </td> - </tr> - </tbody> - </table> - </div> + </td> + </tr> + </tbody> + </table> </div> - </t> + </div> + </t> </div> </t> </template> @@ -649,7 +1010,7 @@ <template id="report_invoice_oacc"> <t t-call="web.html_container"> <t t-foreach="docs" t-as="o"> - <t t-set="lang" t-value="o.partner_id.lang" /> + <t t-set="lang" t-value="o.partner_id.lang"/> <t t-call="oacc_account.report_account_move_document_oacc" t-lang="lang" diff --git a/static/src/scss/style.scss b/static/src/scss/style.scss index 6362f81186409ae6efc3e7d17956d100711d5e47..adb4f50b30aa00864b8abeb400a1cdd418e41e3b 100644 --- a/static/src/scss/style.scss +++ b/static/src/scss/style.scss @@ -54,6 +54,24 @@ td { margin-top: 35px !important; } +.o_report_layout_boxed .per-prms-recap-table{ + background-color: #eff0f1 !important; +} + +.o_report_layout_boxed .per-prms-recap-table table.table-prm + > tbody + > tr + > td, +.o_report_layout_boxed + .row:not(#total) + > div + > table.table-prm + tbody + tr:not(:last-child) + td:last-child { + background-color: transparent !important; +} + .o_report_layout_boxed .o_recap_table table td, .o_report_layout_boxed .o_recap_table table tr { border-style: none !important; diff --git a/views/acc_counter_period_views.xml b/views/acc_counter_period_views.xml index e73cab610be7ff65a18106bfd34cfadd478f3dcd..ed5127fa7e3d123f645bddd939396de5c794c8fc 100644 --- a/views/acc_counter_period_views.xml +++ b/views/acc_counter_period_views.xml @@ -9,7 +9,10 @@ <field name="arch" type="xml"> <group name="infos" position="inside"> <group> - <field name="accise_category" attrs="{'invisible': [('prm_type', '!=', 'delivery')]}" /> + <field + name="accise_category" + attrs="{'invisible': [('prm_type', '!=', 'delivery')]}" + /> </group> </group> <group name="dates" position="after"> diff --git a/views/account_move_views.xml b/views/account_move_views.xml index 35d0eb84b7e24bd2117b29f7ff4fc208e15f27fb..a8d853f447487291318d34d46e161ff6def06a35 100644 --- a/views/account_move_views.xml +++ b/views/account_move_views.xml @@ -10,9 +10,9 @@ <field name="made_sequence_hole" position="after"> <field name="acc_operation_id" optional="show" /> </field> - <field name="partner_id" position="after"> - <field name="acc_delivery_counter_id" optional="show" /> - </field> +<!-- <field name="partner_id" position="after">--> +<!-- <field name="acc_delivery_counter_id" optional="show" />--> +<!-- </field>--> </field> </record> @@ -23,7 +23,7 @@ <field name="arch" type="xml"> <field name="name" position="after"> <field name="acc_operation_id" optional="show" /> - <field name="acc_delivery_counter_id" optional="show" /> +<!-- <field name="acc_delivery_counter_id" optional="show" />--> </field> </field> </record> @@ -47,17 +47,16 @@ attrs="{'invisible': [('acc_operation_id', '=', False)]}" > <group> - <field - name="acc_delivery_counter_id" - options='{"always_reload": True, "no_quick_create": True}' - /> +<!-- <field--> +<!-- name="acc_delivery_counter_ids"--> +<!-- options='{"always_reload": True, "no_quick_create": True}'--> +<!-- />--> <field name="pmo_id" options='{"always_reload": True, "no_quick_create": True}' /> - <field - name="contact_invoice" - attrs="{'invisible': [('acc_delivery_counter_id', '=', False)]}" + <field name="contact_invoice" /> + attrs="{'invisible': [('acc_delivery_counter_ids', '=', False)]}" /> </group> <group> @@ -86,6 +85,7 @@ position="before" > <field name="acc_injection_counter_id" /> + <field name="acc_delivery_counter_id" /> </xpath> </field> </record> diff --git a/views/res_partner_views.xml b/views/res_partner_views.xml index 49e8d774006ccd520982af5a6b852573d1f7a45d..8d88a9f8c5dba380d607c6447b5bb26493b132e1 100644 --- a/views/res_partner_views.xml +++ b/views/res_partner_views.xml @@ -2,26 +2,74 @@ <!-- Copyright 2021- Le Filament (https://le-filament.com) License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> <odoo> - + <record id="view_res_partner_filter" model="ir.ui.view"> + <field name="name">res.partner.select</field> + <field name="model">res.partner</field> + <field name="inherit_id" ref="oacc.view_res_partner_filter" /> + <field name="arch" type="xml"> + <xpath expr="//field[@name='user_id']" position="after"> + <field name="acc_operation_ids" string="Opération liée" /> + </xpath> + <xpath expr="//filter[@name='type_producer']" position="after"> + <filter + string="Mandataires" + name="type_consumer" + domain="[('is_billing_agent', '=', True)]" + /> + </xpath> + </field> + </record> <record id="view_partner_form" model="ir.ui.view"> <field name="name">res.partner.form</field> <field name="model">res.partner</field> <field name="inherit_id" ref="l10n_fr_siret.res_partner_form_l10n_fr" /> <field name="arch" type="xml"> + <xpath expr="//field[@name='is_admin']" position="after"> + <field + name="is_invoice_per_delivery_counter" + groups="oacc.group_operation_superadmin" + /> + <field name="is_billing_agent" invisible="1" /> + </xpath> + + <xpath expr="//field[@name='ref_producer']" position="attributes"> + <attribute name="attrs"> + {'invisible': [ + ('is_billing_agent', '!=', True),('is_producer', '!=', True) + ]} + </attribute> + <attribute name="string"> + Référence + </attribute> + </xpath> + + <xpath expr="//button[@name='create_company']" position="attributes"> + <attribute name="attrs"> + {'invisible': [ + '|', '&', ('is_producer', '!=', True), + ('is_billing_agent', '!=', True), + ('is_company_exist', '=', True) + ]} + </attribute> + </xpath> + + <!-- sales and purchases page --> <xpath expr="//notebook//page[@name='sales_purchases']" position="attributes" > - <attribute - name="attrs" - >{'invisible': ['|', ('is_producer', '!=', True), ('is_company', '!=', True)]}</attribute> - + <attribute name="attrs">{'invisible': [ + '|', '&',('is_producer', '!=', True), + ('is_billing_agent', '!=', True), + ('is_company', '!=', True) + ]} + </attribute> </xpath> <xpath expr="//notebook//page[@name='sales_purchases']" position="inside"> - <field name="user_id" invisible="1"/> + <field name="user_id" invisible="1" /> </xpath> <xpath @@ -49,51 +97,40 @@ expr="//notebook//page[@name='sales_purchases']//group[@name='misc']//field[@name='siren']" position="attributes" > - <attribute - name="attrs" - >{'required': [('is_producer', '=', True), ('is_company', '=', True)]}</attribute> + <attribute name="invisible">1</attribute> </xpath> <xpath expr="//notebook//page[@name='sales_purchases']//group[@name='misc']//field[@name='company_registry']" position="attributes" > - <attribute - name="string" - >RCS</attribute> - <attribute - name="help" - >Registre du Commerce et des Sociétés</attribute> + <attribute name="string">RCS</attribute> + <attribute name="help">Registre du Commerce et des Sociétés</attribute> </xpath> <xpath expr="//notebook//page[@name='sales_purchases']//group[@name='misc']//field[@name='industry_id']" position="attributes" > - <attribute - name="invisible" - >1</attribute> + <attribute name="invisible">1</attribute> </xpath> <xpath expr="//notebook//page[@name='sales_purchases']//group[@name='misc']//field[@name='ref']" position="attributes" > - <attribute - name="invisible" - >1</attribute> + <attribute name="invisible">1</attribute> </xpath> <xpath expr="//notebook//page[@name='sales_purchases']//group[@name='misc']//field[@name='nic']" position="attributes" > - <attribute - name="attrs" - >{'required': [('is_producer', '=', True), ('is_company', '=', True)]}</attribute> + <attribute name="invisible">1</attribute> </xpath> <xpath expr="//notebook//page[@name='sales_purchases']//group[@name='misc']//field[@name='nic']" position="after" > <field - name="siret" attrs="{'required': [('is_producer', '=', True), ('is_company', '=', True)]}" + name="siret" + attrs="{'required': [('is_producer', '=', True), ('is_company', '=', True)]}" /> </xpath> @@ -101,13 +138,8 @@ expr="//notebook//page[@name='sales_purchases']//group[@name='misc']//field[@name='siret']" position="attributes" > - <attribute - name="invisible" - >1</attribute> + <attribute name="invisible">1</attribute> </xpath> - - </field> </record> - </odoo>