Skip to content
Extraits de code Groupes Projets
Sélectionner une révision Git
  • d3bb8af908d1eb1b43650dfbc8b798617ce784de
  • 14.0 par défaut protégée
  • 14.0-ahp12 protégée
  • 14.0-double-paillage protégée
  • 14.0-accessory protégée
5 résultats

sale_intervention.py

Blame
  • sale_intervention.py 36,25 Kio
    # Copyright 2021-2022 Le Filament (https://le-filament.com)
    # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
    
    import math
    
    from odoo import _, api, fields, models
    from odoo.exceptions import UserError
    
    
    class SaleIntervention(models.Model):
        _name = "sale.intervention"
        _description = "Sale Intervention"
        _inherit = ["mail.thread", "mail.activity.mixin"]
    
        @api.model
        def _default_city(self):
            project_id = self.env.context.get("default_project_id")
            if project_id:
                return (
                    self.env["sale.project"]
                    .browse(project_id)
                    .sale_order_id.partner_id.city
                )
    
        # ------------------------------------------------------
        # Fields declaration
        # ------------------------------------------------------
        name = fields.Char("Nom", group_operator="count")
        sequence = fields.Integer("Sequence", default=10)
        project_id = fields.Many2one(
            comodel_name="sale.project", string="Projet", ondelete="cascade", required=True
        )
        project_subvention_id = fields.Many2one(
            related="project_id.project_subvention_id", string="Subventions"
        )
        intervention_type_id = fields.Many2one(
            comodel_name="product.template",
            string="Type d'intervention",
            domain=lambda self: [
                (
                    "categ_id",
                    "=",
                    self.env.ref("ap_sale_project.product_category_forfait").id,
                )
            ],
            ondelete="restrict",
            required=True,
            group_operator="count_distinct",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        intervention_uom_name = fields.Char(
            related="intervention_type_id.uom_name", string="Unité de mesure intervention"
        )
    
        latitude = fields.Float(string="Geo Latitude", digits=(16, 6))
        longitude = fields.Float(string="Geo Longitude", digits=(16, 6))
        financial_help_ids = fields.Many2many(
            comodel_name="sale.financial.help",
            relation="sale_financial_help_rel",
            column1="financial_id",
            column2="sale_order_id",
            string="Aides financières",
        )
    
        # If calculated by meters
        intervention_length = fields.Float(
            string="Longueur de Haie (en m)",
            group_operator="sum",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        plant_interval = fields.Float(
            "Intervalle entre les plants (en m)",
            group_operator="sum",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        # If calculated by units
        plant_qty = fields.Integer(
            string="Nombre de Plants",
            group_operator="sum",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
    
        surface = fields.Integer(
            "Surface (en m2)",
            help="Information ne rentrant pas dans les calculs",
            group_operator="sum",
        )
        city = fields.Char(
            string="Commune intervention",
            default=_default_city,
        )
    
        # FOURNITURES
        is_collarette = fields.Boolean(
            string="Collerettes",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        collarette_qty = fields.Integer(
            string="Nombre de collerettes",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        collarette_calc = fields.Boolean(
            string="Collerette Haie",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
    
        # Mulch Fields
        mulch_id = fields.Many2one(
            comodel_name="product.template",
            string="Paillage",
            domain=lambda self: [
                ("categ_id", "=", self.env.ref("ap_sale_project.product_category_mulch").id)
            ],
            ondelete="restrict",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        mulch_qty = fields.Float(
            string="Qté paillage 1",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        mulch_unit = fields.Many2one(
            "uom.uom",
            related="mulch_id.uom_id",
            readonly=True,
            string="Unité de mesure paillage 1",
        )
        mulch_has_staples = fields.Boolean(
            string="Paillage 1 avec agrafes",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
    
        mulch2_id = fields.Many2one(
            comodel_name="product.template",
            string="Paillage 2",
            domain=lambda self: [
                ("categ_id", "=", self.env.ref("ap_sale_project.product_category_mulch").id)
            ],
            ondelete="restrict",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        mulch2_qty = fields.Float(
            string="Qté paillage 2",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        mulch2_unit = fields.Many2one(
            "uom.uom",
            related="mulch2_id.uom_id",
            readonly=True,
            string="Unité de mesure paillage 2",
        )
        mulch2_has_staples = fields.Boolean(
            string="Paillage 2 avec agrafes",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
    
        # High protection
        high_protection_id = fields.Many2one(
            comodel_name="product.template",
            string="Protection haute",
            domain=lambda self: [
                (
                    "categ_id",
                    "=",
                    self.env.ref("ap_sale_project.product_category_protection_high").id,
                )
            ],
            ondelete="restrict",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        high_protection_qty = fields.Integer(
            string="Qté protégée haut",
            help="Qté de la plantation que l’on veut protéger par le haut \
                (avec protections entrées au dessus)",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        stake_id = fields.Many2one(
            comodel_name="product.template",
            string="Piquets",
            domain=lambda self: [
                ("categ_id", "=", self.env.ref("ap_sale_project.product_category_stake").id)
            ],
            ondelete="restrict",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        stake_qty = fields.Integer(
            string="Qté Piquets",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
    
        # Low protection
        low_protection_id = fields.Many2one(
            comodel_name="product.template",
            string="Protection basse",
            domain=lambda self: [
                (
                    "categ_id",
                    "=",
                    self.env.ref("ap_sale_project.product_category_protection_low").id,
                )
            ],
            ondelete="restrict",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        low_protection_qty = fields.Integer(
            string="Qté protégée bas",
            help="Qté de la plantation que l’on veut protéger par le bas \
                (avec protections entrées au dessus)",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
    
        bamboo_qty = fields.Integer(
            string="Qté bambous",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
    
        # Markers (Jalons)
        marker_id = fields.Many2one(
            comodel_name="product.template",
            string="Jalons",
            domain=lambda self: [
                (
                    "categ_id",
                    "=",
                    self.env.ref("ap_sale_project.product_category_marker").id,
                )
            ],
            ondelete="restrict",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        marker_qty = fields.Integer(
            string="Qté jalons",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
    
        sequence_type = fields.Selection(
            [("sequence", "Constuction en séquence"), ("list", "Construction en liste")],
            string="Type de séquence",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        plant_sequence_ids = fields.One2many(
            comodel_name="sale.intervention.plant.sequence",
            inverse_name="intervention_id",
            domain="[('is_list', '!=', True)]",
            string="Séquences",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
        plant_list_ids = fields.One2many(
            comodel_name="sale.intervention.plant.sequence",
            inverse_name="intervention_id",
            domain="[('is_list', '=', True)]",
            string="Listes",
            readonly=True,
            states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
        )
    
        # Computed fields
        plant_qty_per_seq = fields.Integer(
            "Nombre de plants par séquence",
            compute="_compute_quantities",
            default=0,
            store=True,
        )
        full_seq_qty = fields.Integer(
            "Nombre de séquences entières",
            compute="_compute_quantities",
            default=0,
            store=True,
        )
        extra_plants_qty = fields.Integer(
            "Nombre de plants supplémentaires",
            compute="_compute_quantities",
            default=0,
            store=True,
        )
    
        plants_qty = fields.Integer(
            "Nombre total de plants (calculé)",
            compute="_compute_quantities",
            default=0,
            group_operator="sum",
            store=True,
        )
        plants_type_qty = fields.Integer(
            "Nombre d'espèces différentes",
            compute="_compute_quantities",
            default=0,
            store=True,
        )
        plants_local_qty = fields.Integer(
            "Nombre de végétal local",
            compute="_compute_quantities",
            default=0,
            group_operator="sum",
            store=True,
        )
    
        plant_price = fields.Float(
            "Prix d'un plant",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
        )
        collarette_price = fields.Float(
            "Prix d'une collerette",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
        )
        mulch_price = fields.Float(
            "Prix du paillage 1",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
        )
        mulch2_price = fields.Float(
            "Prix du paillage 2",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
        )
        staple1_price = fields.Float(
            "Prix des agrafes pour paillage 1",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
        )
        staple2_price = fields.Float(
            "Prix des agrafes pour paillage 2",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
        )
        high_protection_price = fields.Float(
            "Prix d'une protection haute",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
        )
        stake_price = fields.Float(
            "Prix d'un piquet",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
        )
        low_protection_price = fields.Float(
            "Prix d'une protection basse",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
        )
        marker_price = fields.Float(
            "Prix d'un jalon",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
        )
        service_price = fields.Float(
            "Prix du service",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
        )
        price = fields.Float(
            "Prix de l'intervention",
            compute="_compute_interventions_price",
            store=True,
            readonly=True,
            default=0.0,
            digits="Product Price",
            group_operator="sum",
        )
    
        sale_order_id = fields.Many2one(
            related="project_id.sale_order_id",
            string="Devis/Commande",
        )
        partner_id = fields.Many2one(
            related="project_id.partner_id", string="Client", store=True
        )
        state = fields.Selection(related="sale_order_id.state", store=True)
        sale_intervention_stock_ids = fields.One2many(
            comodel_name="sale.intervention.stock",
            inverse_name="sale_intervention_id",
            string="Articles à déstocker",
        )
    
        # ------------------------------------------------------
        # SQL Constraints
        # ------------------------------------------------------
    
        # ------------------------------------------------------
        # Default methods
        # ------------------------------------------------------
    
        # ------------------------------------------------------
        # Computed fields / Search Fields
        # ------------------------------------------------------
        @api.depends(
            "sequence_type",
            "intervention_uom_name",
            "intervention_length",
            "plant_interval",
            "plant_qty",
            "plant_sequence_ids",
            "plant_sequence_ids.product_id",
            "plant_sequence_ids.product_alternance_id",
            "plant_sequence_ids.is_local",
            "plant_list_ids",
            "plant_list_ids.product_alternance_id",
            "plant_list_ids.is_local",
        )
        def _compute_quantities(self):
            for rec in self:
                plant_qty_per_seq = 0
                full_seq_qty = 0
                extra_plants_qty = 0
                plants_qty = 0
                plants_type_qty = 0
                plants_local_qty = 0
    
                if rec.sequence_type == "sequence":
                    if rec.intervention_uom_name == "m" and rec.plant_interval != 0.0:
                        plants_qty = (
                            math.ceil(rec.intervention_length / rec.plant_interval) + 1
                        )
                    elif rec.intervention_uom_name == "Unité(s)":
                        plants_qty = rec.plant_qty
                    plant_qty_per_seq = len(rec.plant_sequence_ids)
                    if plant_qty_per_seq != 0:
                        full_seq_qty = plants_qty // plant_qty_per_seq
                        extra_plants_qty = plants_qty % plant_qty_per_seq
                        plants_type_qty = len(
                            set(
                                rec.plant_sequence_ids.product_id
                                + rec.plant_sequence_ids.product_alternance_id
                            )
                        )
                        local_ratio = (
                            len(rec.plant_sequence_ids.filtered("is_local"))
                            / plant_qty_per_seq
                        )
                        plants_local_qty = math.ceil(local_ratio * plants_qty)
                else:
                    plants_qty = sum(plant.qty for plant in rec.plant_list_ids)
                    plants_type_qty = len(set(rec.plant_list_ids.product_id))
                    plants_local_qty = sum(
                        plant.qty for plant in rec.plant_list_ids if plant.is_local
                    )
    
                rec.plants_qty = plants_qty
                rec.plant_qty_per_seq = plant_qty_per_seq
                rec.full_seq_qty = full_seq_qty
                rec.extra_plants_qty = extra_plants_qty
                rec.plants_type_qty = plants_type_qty
                rec.plants_local_qty = plants_local_qty
    
        # ------------------------------------------------------
        # Onchange / Constraints
        # ------------------------------------------------------
        @api.onchange("intervention_length", "plant_interval")
        def _onchange_length(self):
            if self.intervention_uom_name == "m" and self.plant_interval > 0:
                plant_qty = math.ceil(self.intervention_length / self.plant_interval) + 1
                self.plant_qty = plant_qty
                self.collarette_qty = plant_qty
    
        @api.onchange("plant_qty")
        def _onchange_plant_qty(self):
            if self.intervention_uom_name == "Unité(s)":
                self.collarette_qty = self.plant_qty
    
        @api.onchange("plant_qty", "intervention_length", "mulch_id")
        def _onchange_mulch_id(self):
            self.mulch_has_staples = False
            if self.mulch_unit == self.env.ref("uom.product_uom_meter"):
                self.mulch_qty = self.intervention_length
            elif self.mulch_unit == self.env.ref("uom.product_uom_unit"):
                self.mulch_qty = self.plant_qty
            else:
                self.mulch_qty = 0.0
    
        @api.onchange("plant_qty", "intervention_length", "mulch2_id")
        def _onchange_mulch2_id(self):
            self.mulch2_has_staples = False
            if self.mulch2_unit == self.env.ref("uom.product_uom_meter"):
                self.mulch2_qty = self.intervention_length
            elif self.mulch2_unit == self.env.ref("uom.product_uom_unit"):
                self.mulch2_qty = self.plant_qty
            else:
                self.mulch2_qty = 0.0
    
        @api.onchange("high_protection_qty")
        def _onchange_high_protection_qty(self):
            self.stake_qty = self.high_protection_qty
    
        @api.onchange("low_protection_qty")
        def _onchange_low_protection_qty(self):
            self.bamboo_qty = self.low_protection_qty * 2
    
        @api.constrains("plant_interval")
        def _check_plant_interval(self):
            for rec in self:
                if rec.intervention_uom_name == "m" and (
                    rec.plant_interval < 0.7 or rec.plant_interval > 1.2
                ):
                    raise UserError(
                        _(
                            "L'intervalle entre 2 plants '%f' est incorrect: il doit être "
                            "entre 0,7m et 1,2m."
                        )
                        % rec.plant_interval
                    )
    
        @api.constrains("plant_qty", "plants_qty")
        def _check_plant_qty(self):
            for rec in self:
                if (
                    rec.plant_qty != rec.plants_qty
                    and rec.sequence_type == "list"
                    and rec.plant_list_ids
                ):
                    raise UserError(
                        _(
                            "La quantité de plants renseignée '%d' est différente de"
                            "la quantité de plants calculée '%d'.\n "
                            "Les quantités doivent être identiques, vérifier votre compositon."
                        )
                        % (rec.plant_qty, rec.plants_qty)
                    )
    
        @api.depends(
            "project_id.intervention_ids.intervention_length",
            "project_subvention_id",
            "intervention_length",
            "plant_qty",
            "plant_interval",
            "intervention_type_id",
            "intervention_uom_name",
            "is_collarette",
            "collarette_qty",
            "mulch_id",
            "mulch_qty",
            "mulch_has_staples",
            "mulch2_id",
            "mulch2_qty",
            "mulch2_has_staples",
            "high_protection_id",
            "high_protection_qty",
            "stake_id",
            "stake_qty",
            "low_protection_id",
            "low_protection_qty",
            "bamboo_qty",
            "marker_id",
            "marker_qty",
        )
        def _compute_interventions_price(self):
            plant_categ_id = self.env.ref("ap_sale_project.product_category_plant")
            collarette_product_id = self.env.ref("ap_sale_project.ap_product_other_coll")
            staples_product_id = self.env.ref("ap_sale_project.ap_product_other_staples")
            service_product_id = self.env.ref("ap_sale_project.ap_product_service_service")
            total_price = 0.0
            for rec in self:
                # multiplicator is the quantity to be used for computation
                # either : length if computation is per meter
                # or : number of plants if computation is per plant
                multiplicator = (
                    rec.intervention_length
                    if rec.intervention_uom_name == "m"
                    else rec.plant_qty
                )
                partner = rec.sale_order_id.partner_id
                forfait = rec.intervention_type_id
                # CASE for computing based on forfait
                if (
                    rec.project_subvention_id.type == "forfait"
                    and rec.project_subvention_id.product_pricelist_ids
                ):
                    # We retrieve the first pricelist linked to this subvention
                    pricelist = rec.project_subvention_id.product_pricelist_ids[0]
                    # We retrieve from pricelist the unit price of our forfait
                    unit_price = pricelist.get_product_price(
                        forfait, multiplicator, partner
                    )
                    # and we multiply by quantity
                    rec.price = multiplicator * unit_price
                # CASE for computing based on grid
                elif (
                    rec.project_subvention_id.type == "grid"
                    and rec.project_subvention_id.product_pricelist_ids
                ):
                    # We retrieve pricelist from subvention where
                    # intervention_type_id = our type of intervention
                    pricelist = rec.project_subvention_id.product_pricelist_ids.filtered(
                        lambda i: i.intervention_type_id == forfait
                    )[0]
                    if pricelist and pricelist.item_ids.filtered(
                        lambda i: i.categ_id == plant_categ_id
                    ):
                        # First we retrieve price of a plant (since these are
                        # per categories we make something different here)
                        plant_pricelist_item = pricelist.item_ids.filtered(
                            lambda i: i.categ_id == plant_categ_id
                        )[0]
                        rec.plant_price = (
                            plant_pricelist_item.fixed_price
                            if plant_pricelist_item
                            else 0.0
                        )
    
                        # Then we create ordered lists with :
                        # products, quantities and partners
    
                        # Service
                        # For service we need to take into account the sumed length
                        # of all interventions of the same type
    
                        # Get all interventions of same type in the same project
                        interventions = self.project_id.intervention_ids.filtered(
                            lambda r: r.intervention_type_id == rec.intervention_type_id
                        )
                        service_qty = (
                            sum(interventions.mapped("intervention_length"))
                            if rec.intervention_uom_name == "m"
                            else multiplicator
                        )
                        products = [
                            service_product_id,
                        ]
                        quantities = [
                            service_qty,
                        ]
                        partners = [
                            partner,
                        ]
    
                        # Collarette
                        coll_qty = rec.collarette_qty or 0
                        if rec.is_collarette:
                            # In case collarette price is calculated per meter iso unit
                            if rec.intervention_uom_name == "m" and rec.collarette_calc:
                                coll_qty = rec.intervention_length
                            products.append(collarette_product_id)
                            quantities.append(coll_qty or 0)
                            partners.append(partner)
    
                        # Mulch
                        if rec.mulch_id:
                            products.append(rec.mulch_id)
                            quantities.append(rec.mulch_qty or 0)
                            partners.append(partner)
    
                        # Mulch 2
                        if rec.mulch2_id:
                            products.append(rec.mulch2_id)
                            quantities.append(rec.mulch2_qty or 0)
                            partners.append(partner)
    
                        # Staples
                        if rec.mulch_has_staples or rec.mulch2_has_staples:
                            products.append(staples_product_id)
                            quantities.append(rec.mulch_qty * 2 or 0)
                            partners.append(partner)
    
                        # High Protections
                        if rec.high_protection_id:
                            products.append(rec.high_protection_id)
                            quantities.append(rec.high_protection_qty or 0)
                            partners.append(partner)
    
                        # Stakes
                        if rec.stake_id:
                            products.append(rec.stake_id)
                            quantities.append(rec.stake_qty or 0)
                            partners.append(partner)
    
                        # Low Protections
                        if rec.low_protection_id:
                            products.append(rec.low_protection_id)
                            quantities.append(rec.low_protection_qty or 0)
                            partners.append(partner)
    
                        # Markers
                        if rec.marker_id:
                            products.append(rec.marker_id)
                            quantities.append(rec.marker_qty or 0)
                            partners.append(partner)
    
                        # We retrieve all prices here
                        prices = pricelist.get_products_price(
                            products, quantities, partners
                        )
    
                        rec.collarette_price = (
                            prices[collarette_product_id.id] if rec.is_collarette else 0.0
                        )
                        rec.mulch_price = prices[rec.mulch_id.id] if rec.mulch_id else 0.0
                        rec.staple1_price = (
                            prices[staples_product_id.id] if rec.mulch_has_staples else 0.0
                        )
                        rec.mulch2_price = (
                            prices[rec.mulch2_id.id] if rec.mulch2_id else 0.0
                        )
                        rec.staple2_price = (
                            prices[staples_product_id.id] if rec.mulch2_has_staples else 0.0
                        )
                        rec.high_protection_price = (
                            prices[rec.high_protection_id.id]
                            if rec.high_protection_id
                            else 0.0
                        )
                        rec.stake_price = prices[rec.stake_id.id] if rec.stake_id else 0.0
                        rec.low_protection_price = (
                            prices[rec.low_protection_id.id]
                            if rec.low_protection_id
                            else 0.0
                        )
                        rec.marker_price = (
                            prices[rec.marker_id.id] if rec.marker_id else 0.0
                        )
                        rec.service_price = prices[service_product_id.id]
    
                        rec.price = (
                            multiplicator * (rec.plant_price + rec.service_price)
                            + coll_qty * rec.collarette_price
                            + rec.mulch_qty * (rec.mulch_price + 2 * rec.staple1_price)
                            + rec.mulch2_qty * (rec.mulch2_price + 2 * rec.staple2_price)
                            + rec.high_protection_qty * rec.high_protection_price
                            + rec.stake_qty * rec.stake_price
                            + rec.low_protection_qty * rec.low_protection_price
                            + rec.marker_qty * rec.marker_price
                        )
                    else:
                        raise UserError(
                            _(
                                "Impossible de trouver la liste de prix correspondant "
                                "à ce programme de subvention : %s \n"
                                "et ce type d'intervention : %s"
                            )
                            % (rec.project_subvention_id.name, forfait.name)
                        )
                else:
                    raise UserError(
                        _(
                            "Impossible de trouver la liste de prix correspondant "
                            "à ce programme de subvention %s"
                        )
                        % rec.project_subvention_id.name
                    )
                total_price += rec.price
                rec.project_id.update_order_lines()
            return total_price
    
        def _get_plants_qties(self):
            # Method to get list of plants and quantities
            self.ensure_one()
            products = []
            qty = []
            if self.sequence_type == "sequence":
                iteration = 0
                for sequence in self.plant_sequence_ids:
                    products.append(sequence.product_id)
                    # add calculation for plants out of sequence, based on iteration
                    extra_plant = 0
                    extra_alter_plant = 0
                    if self.extra_plants_qty and iteration < self.extra_plants_qty:
                        # if the full number of sequence is odd
                        # (or no alternance plant is defined),
                        # we add the main plant
                        if self.full_seq_qty % 2 == 0 or not sequence.product_alternance_id:
                            extra_plant = 1
                        # otherwise, we add the alternance plant
                        else:
                            extra_alter_plant = 1
    
                    if sequence.product_alternance_id:
                        qty.append(math.ceil(self.full_seq_qty / 2) + extra_plant)
                        products.append(sequence.product_alternance_id)
                        qty.append(math.floor(self.full_seq_qty / 2) + extra_alter_plant)
                    else:
                        qty.append(self.full_seq_qty + extra_plant)
                    iteration += 1
    
            elif self.sequence_type == "list":
                products = self.plant_list_ids.mapped("product_id")
                qty = self.plant_list_ids.mapped("qty")
    
            return list(zip(products, qty))
    
        def _create_sale_intervention_stock_lines(self):
            self.ensure_one()
            # Only perform calculation if quantities are set
            if self.plants_qty > 0:
                data = []
                for product, qty in self._get_plants_qties():
                    if product and qty > 0:
                        data_vals = {
                            "sale_intervention_id": self.id,
                            "product_id": product.product_variant_id.id,
                            "product_uom_qty": qty,
                            "price_unit": self.plant_price,
                        }
                        data.append(data_vals)
                staples_product_id = self.env.ref(
                    "ap_sale_project.ap_product_other_staples"
                )
                if self.mulch_id and self.mulch_qty > 0:
                    data.append(
                        {
                            "sale_intervention_id": self.id,
                            "product_id": self.mulch_id.product_variant_id.id,
                            "product_uom_qty": self.mulch_qty,
                            "price_unit": self.mulch_price,
                        }
                    )
                    if self.mulch_has_staples:
                        data.append(
                            {
                                "sale_intervention_id": self.id,
                                "product_id": staples_product_id.product_variant_id.id,
                                "product_uom_qty": self.mulch_qty * 2,
                                "price_unit": self.staple1_price,
                            }
                        )
                if self.mulch2_id and self.mulch2_qty > 0:
                    data.append(
                        {
                            "sale_intervention_id": self.id,
                            "product_id": self.mulch2_id.product_variant_id.id,
                            "product_uom_qty": self.mulch2_qty,
                            "price_unit": self.mulch2_price,
                        }
                    )
                    if self.mulch2_has_staples:
                        data.append(
                            {
                                "sale_intervention_id": self.id,
                                "product_id": staples_product_id.product_variant_id.id,
                                "product_uom_qty": self.mulch2_qty * 2,
                                "price_unit": self.staple2_price,
                            }
                        )
                if self.is_collarette and self.collarette_qty > 0:
                    collarette_product_id = self.env.ref(
                        "ap_sale_project.ap_product_other_coll"
                    )
                    data.append(
                        {
                            "sale_intervention_id": self.id,
                            "product_id": collarette_product_id.product_variant_id.id,
                            "product_uom_qty": self.collarette_qty,
                            "price_unit": self.collarette_price,
                        }
                    )
                if self.high_protection_id and self.high_protection_qty > 0:
                    data.append(
                        {
                            "sale_intervention_id": self.id,
                            "product_id": self.high_protection_id.product_variant_id.id,
                            "product_uom_qty": self.high_protection_qty,
                            "price_unit": self.high_protection_price,
                        }
                    )
                if self.stake_id and self.stake_qty > 0:
                    data.append(
                        {
                            "sale_intervention_id": self.id,
                            "product_id": self.stake_id.product_variant_id.id,
                            "product_uom_qty": self.stake_qty,
                            "price_unit": self.stake_price,
                        }
                    )
                if self.low_protection_id and self.low_protection_qty > 0:
                    data.append(
                        {
                            "sale_intervention_id": self.id,
                            "product_id": self.low_protection_id.product_variant_id.id,
                            "product_uom_qty": self.low_protection_qty,
                            "price_unit": self.low_protection_price,
                        }
                    )
                if self.bamboo_qty > 0:
                    bamboo_product_id = self.env.ref(
                        "ap_sale_project.ap_product_other_bamboo"
                    )
                    data.append(
                        {
                            "sale_intervention_id": self.id,
                            "product_id": bamboo_product_id.product_variant_id.id,
                            "product_uom_qty": self.bamboo_qty,
                            "price_unit": 0.0,
                        }
                    )
                if self.marker_id and self.marker_qty > 0:
                    data.append(
                        {
                            "sale_intervention_id": self.id,
                            "product_id": self.marker_id.product_variant_id.id,
                            "product_uom_qty": self.marker_qty,
                            "price_unit": self.marker_price,
                        }
                    )
                StockLine = self.env["sale.intervention.stock"]
                StockLine.create(data)
    
        # ------------------------------------------------------
        # CRUD methods (ORM overrides)
        # ------------------------------------------------------
        @api.model_create_multi
        def create(self, vals_list):
            res = super().create(vals_list)
            for rec in res:
                rec.project_id.intervention_sequence += 1
                rec.name = "Séquence " + str(rec.project_id.intervention_sequence)
            return res
    
        def write(self, values):
            res = super().write(values)
            if self.sale_intervention_stock_ids:
                self.sale_intervention_stock_ids.unlink()
            self._create_sale_intervention_stock_lines()
            return res
    
        # ------------------------------------------------------
        # Actions
        # ------------------------------------------------------
    
        # ------------------------------------------------------
        # CRUD methods (ORM overrides)
        # ------------------------------------------------------
        def unlink(self):
            project_id = self.project_id
            res = super().unlink()
            project_id.update_order_lines()
            return res