Skip to content
Extraits de code Groupes Projets
Sélectionner une révision Git
  • 488ec2357b70baee52d89a255c2246073bbbfb96
  • 12.0-evo-202003 par défaut
  • 14-RV-20250324
  • 14-RV-20240830
  • 14-RV-20231222
  • 12-RV-Bug_ecrasement_date_radiation
  • 12-RV-revision-staff
  • 12-RV-copadev
  • 12-RV-Correctif-open-instagram
  • 12-RV-Tree-Coop-Ajout-effectif
  • 12.0-RV-Instagram
  • 12.0-RV-segment_visibility
  • 12.0 protégée
  • 12.0-RV-Abonnements
14 résultats

scop_deces_wizard.py

Blame
  • Bifurcation depuis Le Filament / Confédération Générale des SCOP / cgscop_partner
    Le projet source a une visibilité limitée.
    scop_cotisation_cg.py 34,30 Kio
    # © 2021 Le Filament (<http://www.le-filament.com>)
    # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
    
    import base64
    import json
    import logging
    from datetime import datetime
    from io import BytesIO
    
    import xlsxwriter
    
    from odoo import _, api, exceptions, fields, models
    
    _logger = logging.getLogger(__name__)
    
    
    class ScopCotisation(models.Model):
        _inherit = "scop.cotisation"
        _name = "scop.cotisation.cg"
        _description = "Base des cotisations CG"
    
        name = fields.Char(string="Nom", compute="_compute_name", store=True)
    
        simul_ids = fields.One2many(
            comodel_name="scop.cotisation.cg.simulation",
            inverse_name="cotisation_id",
            string="Simulations",
        )
    
        invoice_ids = fields.One2many(
            comodel_name="account.move",
            inverse_name="cotisation_cg_id",
            string="Factures",
            domain=[
                ("move_type", "in", ("out_invoice", "out_refund")),
                ("is_contribution", "=", True),
            ],
        )
    
        bordereau_ids = fields.One2many(
            comodel_name="scop.bordereau",
            inverse_name="base_cotisation_cg",
            string="Bordereau",
        )
    
        state = fields.Selection(
            [("new", "Brouillon"), ("ongoing", "En cours"), ("end", "Clôturé")],
            string="Statut",
            default="new",
            compute="_compute_state",
            store=True,
        )
    
        invoice_count = fields.Integer(
            string="Appels de cotisations émis", compute="_compute_real"
        )
        invoice_valid_count = fields.Integer(
            string="Appels de cotisations validés", compute="_compute_real"
        )
    
        amount_called = fields.Monetary(
            "Montant appelé",
            compute="_compute_amount_called",
            currency_field="company_currency_id",
        )
        amount_residual = fields.Monetary(
            "Montant restant à réglé",
            compute="_compute_amount_residual",
            currency_field="company_currency_id",
        )
        amount_paid = fields.Monetary(
            "Montant payé",
            compute="_compute_amount_paid",
            currency_field="company_currency_id",
        )
    
        percent_cotiz_paid = fields.Float("% Payées", compute="_compute_percent_cotiz_paid")
    
        graph_values = fields.Text(compute="_compute_graph_values")
    
        batch_id = fields.Many2one(
            comodel_name="queue.job.batch", name="Files d'attente en cours"
        )
        batch_ids_str = fields.Char()
        batch_count = fields.Integer(compute="_compute_batch_count")
    
        # ------------------------------------------------------
        # Contraintes SQL
        # ------------------------------------------------------
        # Unicité d'année' (company - année)
        _sql_constraints = [
            (
                "contribution_unique",
                "unique(year, company_id)",
                "La gestion des cotisations pour cette année existe déjà",
            ),
        ]
    
        # ------------------------------------------------------
        # Compute fields
        # ------------------------------------------------------
        @api.depends("year", "state")
        def _compute_name(self):
            for cotiz in self:
                cotiz.name = "Cotisations %s - %s" % (
                    cotiz.year,
                    dict(self._fields["state"].selection).get(cotiz.state),
                )
    
        def _compute_amount_called(self):
            for cotiz in self:
                cotiz.amount_called = sum(
                    cotiz.invoice_ids.filtered(lambda i: i.state == "posted").mapped(
                        "amount_total_signed"
                    )
                )
    
        def _compute_amount_residual(self):
            for cotiz in self:
                cotiz.amount_residual = sum(
                    cotiz.invoice_ids.mapped("amount_residual_signed")
                )
    
        def _compute_amount_paid(self):
            """
            montant déjà payé = différence entre le reste à payer du total validé,
            le reste à payer étant égal à 0 pour les factures en brouillon
            """
            for cotiz in self:
                cotiz.amount_paid = cotiz.amount_called - cotiz.amount_residual
    
        def _compute_percent_cotiz_paid(self):
            for cotiz in self:
                if cotiz.amount_called != 0:
                    percent = cotiz.amount_paid / cotiz.amount_called * 100
                    if 0 < percent < 1:
                        cotiz.percent_cotiz_paid = 1
                    elif 99 < percent < 100:
                        cotiz.percent_cotiz_paid = 99
                    else:
                        cotiz.percent_cotiz_paid = percent
                else:
                    cotiz.percent_cotiz_paid = 0
    
        @api.depends(
            "invoice_ids",
            "invoice_ids.state",
            "invoice_ids.amount_total_signed",
        )
        def _compute_real(self):
            for cotiz in self:
                cotiz.invoice_count = len(cotiz.invoice_ids)
                cotiz.invoice_valid_count = len(
                    cotiz.invoice_ids.filtered(lambda i: i.state == "posted")
                )
    
        def _compute_graph_values(self):
            for cotiz in self:
                draft_count = len(cotiz.invoice_ids.filtered(lambda i: i.state == "draft"))
                opened_count = len(
                    cotiz.invoice_ids.filtered(
                        lambda i: i.state == "posted"
                        and i.payment_state not in ("paid", "reversed")
                    )
                )
                paid_count = len(
                    cotiz.invoice_ids.filtered(
                        lambda i: i.state == "posted" and i.payment_state == "paid"
                    )
                )
                cotiz.graph_values = json.dumps(
                    [
                        {
                            "values": [
                                {"label": "Brouillons", "value": draft_count},
                                {"label": "Ouverts", "value": opened_count},
                                {"label": "Payés", "value": paid_count},
                            ],
                            "area": True,
                            "title": "",
                            "key": "Cotisations",
                        }
                    ]
                )
    
        @api.depends("invoice_ids", "invoice_ids.state")
        def _compute_state(self):
            for cotiz in self:
                if len(cotiz.invoice_ids) == 0:
                    cotiz.state = "new"
                elif (
                    len(cotiz.invoice_ids)
                    == len(cotiz.invoice_ids.filtered(lambda i: i.state == "posted"))
                    and cotiz.state != "end"
                ):
                    cotiz.state = "end"
                elif cotiz.state != "ongoing":
                    cotiz.state = "ongoing"
    
        def _compute_batch_count(self):
            for base in self:
                if not base.batch_ids_str:
                    base.batch_count = 0
                else:
                    base.batch_count = len(base.batch_ids_str.split(","))
    
        # ------------------------------------------------------
        # Button functions
        # ------------------------------------------------------
        def cotiz_generate(self):
            """
            Open wizard to generate cotisations for renew members
            """
            if not self.env.company.is_contribution_cg:
                raise exceptions.UserError(_("La cotisation CG Scop n'est pas configurée."))
    
            if (
                not self.trimester_1
                or not self.trimester_2
                or not self.trimester_3
                or not self.trimester_4
            ):
                raise exceptions.UserError(
                    _("Les trimestres doivent être configurés pour lancer les cotisations.")
                )
    
            if not self.date_cotisation:
                raise exceptions.UserError(
                    _(
                        "La date de calcul des cotisations doit être renseignée pour "
                        "lancer les cotisations."
                    )
                )
    
            # List of members already invoiced
            member_invoiced = self.invoice_ids.mapped("partner_id")
    
            # List of members
            members = self.get_members()
    
            # List of not invoiced members
            members_to_invoice = members - member_invoiced
    
            if len(members_to_invoice) > 0:
                message = (
                    "<h3>Appels de cotisation "
                    + self.year
                    + "</h3> <hr/>"
                    + "Nombre d'adhérents renouvelés : "
                    + str(self.member_count)
                    + "<br/>Bordereaux déjà générées non vides : "
                    + str(self.invoiced_member_count)
                    + "<br/>Bordereaux à générer : "
                    + str(len(members_to_invoice))
                    + "<p>Les appels de cotisation vont être générés.</p> "
                )
                return {
                    "type": "ir.actions.act_window.message",
                    "title": _("Génération des appels de cotisation annuels"),
                    "is_html_message": True,
                    "message": _(message),
                    "close_button_title": False,
                    "buttons": [
                        {
                            "type": "method",
                            "name": "Lancer le calcul",
                            "model": self._name,
                            "method": "action_cotiz_generate",
                            "args": [self.id, members_to_invoice.ids],
                            "classes": "btn-primary",
                        },
                        {
                            "type": "ir.actions.act_window_close",
                            "name": "Fermer",
                        },
                    ],
                }
            else:
                message = (
                    "<p class='text-center'>Tous les appels de "
                    + "cotisations annuels ont déjà été créés !</p>"
                )
                return {
                    "type": "ir.actions.act_window.message",
                    "title": _("Génération des appels de cotisation annuels"),
                    "is_html_message": True,
                    "message": _(message),
                    "close_button_title": False,
                    "buttons": [
                        {
                            "type": "ir.actions.act_window_close",
                            "name": "Fermer",
                        },
                    ],
                }
    
        def action_cotiz_generate(self, member_ids):
            """
            Create contribution for members
            :params member_ids: list of member ids
            """
            # Vérifie que des tâches ne sont pas en cours
            self.ensure_one()
            if self.self.batch_ids_str:
                jobs = [int(x) for x in self.batch_ids_str.split(",")]
            else:
                jobs = []
            queue_ids = self.env["queue.job"].search(
                [
                    ("job_batch_id", "in", jobs),
                    ("state", "not in", ["done", "cancelled", "failed"]),
                ]
            )
            if queue_ids:
                raise exceptions.UserError(
                    _("Des tâches de création sont encore en cours.")
                )
    
            member_to_invoice = self.env["res.partner"].browse(member_ids)
            # Job queue
            batch_name = (
                fields.Datetime.to_string(fields.Datetime.now())
                + " Génération des bordereaux "
                + self.year
            )
            batch = self.env["queue.job.batch"].get_new_batch(batch_name)
            # Stocke le dernier batch en cours
            self.batch_id = batch
            # Stocke la liste des batchs au format texte
            if not self.batch_ids_str:
                self.batch_ids_str = str(batch.id)
            else:
                self.batch_ids_str += ", %s" % (str(batch.id),)
            # Ajoute les bordereaux au batch
            for member in member_to_invoice:
                liasse_id = self.get_liasse(member)
                description = "%s - Adh %s - %s" % (
                    self.year,
                    member.member_number,
                    member.name,
                )
                self.with_context(job_batch=batch).with_delay(
                    max_retries=3, description=description
                ).create_bordereau(
                    member=member, liasse=liasse_id, nb_quarter="4", date=False
                )
            batch.enqueue()
    
        def cotiz_generate_wizard(self):
            """
            Open wizard to generate cotisations for new members
            """
            if (
                not self.trimester_1
                or not self.trimester_2
                or not self.trimester_3
                or not self.trimester_4
            ):
                raise exceptions.UserError(
                    _("Les trimestres doivent être configurés pour lancer les cotisations.")
                )
    
            if not self.date_cotisation:
                raise exceptions.UserError(
                    _(
                        "La date de calcul des cotisations doit être renseignée pour lancer "
                        "les cotisations."
                    )
                )
    
            wizard = self.env["scop.cotisation.cg.wizard"].create(
                {
                    "year": self.year,
                    "cotisation_cg_id": self.id,
                }
            )
            return {
                "name": "Génération des appels de cotisation nouveaux adhérents",
                "type": "ir.actions.act_window",
                "view_type": "form",
                "view_mode": "form",
                "res_model": "scop.cotisation.cg.wizard",
                "res_id": wizard.id,
                "context": {
                    "id_cotisation_cg": self.id,
                },
                "target": "new",
            }
    
        def cotiz_view(self):
            """
            Button to open view for Appels de cotisations
            """
            tree_id = self.env.ref("cgscop_cotisation.account_move_tree_scop_inherited").id
            form_id = self.env.ref("cgscop_cotisation_cg.invoice_form_scop_cg_inherited").id
            search_id = (
                self.env.ref("cgscop_cotisation_cg.invoice_search_scop_cg_inherited").id,
                "view_account_invoice_cotiz_cg_search",
            )
            return {
                "name": "Appels de cotisation " + self.year,
                "type": "ir.actions.act_window",
                "res_model": "account.move",
                "view_mode": "tree,form",
                "view_type": "form",
                "search_view_id": search_id,
                "views": [
                    [tree_id, "tree"],
                    [form_id, "form"],
                    [False, "pivot"],
                    [False, "graph"],
                ],
                "domain": [("cotisation_cg_id", "=", self.id)],
                "context": {
                    "create": False,
                    "default_year": self.year,
                    "default_is_contribution": True,
                    "default_cotisation_cg_id": self.id,
                },
                "target": "current",
            }
    
        def bordereaux_view(self):
            """
            Button to open view for Bordereaux
            """
            return {
                "name": "Bordereaux de cotisation " + self.year,
                "type": "ir.actions.act_window",
                "res_model": "scop.bordereau",
                "view_mode": "tree,form",
                "view_type": "form",
                "domain": [("base_cotisation_cg", "=", self.id)],
                "target": "current",
            }
    
        def create_bordereau(self, member, nb_quarter, liasse=None, date=False):
            """
            Création du bordereau de cotisations
            :param member:
            :param nb_quarter:
            :param liasse:
            :param date:
            :return bordereau_id:
            """
            # Variables to calculate cotiz
            staff_id = self.get_last_staff_id(member)
            if staff_id:
                staff_shareholder_count = staff_id.staff_shareholder_count
                staff_count = staff_id.staff_count
                staff_average = staff_id.staff_average
            else:
                staff_shareholder_count = 0
                staff_count = 0
                staff_average = 0
            date_invoice = date if date else self.date_cotisation
    
            existing_bdx = self.env["scop.bordereau"].search(
                [
                    ("partner_id", "=", member.id),
                    ("base_cotisation_cg", "=", self.id),
                ]
            )
            if not existing_bdx:
                bordereau = self.env["scop.bordereau"].create(
                    {
                        "partner_id": member.id,
                        "payment_mode_id": member.customer_payment_mode_id.id,
                        "base_cotisation_cg": self.id,
                        "liasse_fiscale_id": liasse.id if liasse else None,
                        "year_liasse": liasse.year if liasse else 0,
                        "date_cotisation": date_invoice,
                        "nb_quarter": nb_quarter,
                        "staff_count": staff_count,
                        "staff_shareholder_count": staff_shareholder_count,
                        "staff_average": staff_average,
                    }
                )
                bordereau.create_cotiz_and_lines()
            else:
                raise exceptions.UserError(
                    _("Un bordereau existe déjà pour cette coopérative et cette campagne")
                )
            return bordereau.id
    
        def bordereau_validate(self):
            """
            Open wizard to validate all bordereaux from "base de cotisation"
            """
            bordereau_to_validate = self.bordereau_ids.filtered(lambda b: b.state == "new")
            bordereau_to_validate.validate_bordereau_multi()
            if len(bordereau_to_validate) > 0:
                message = "%s bordereaux sont en cours de validation" % len(
                    bordereau_to_validate
                )
            else:
                message = "Tous les bordereaux ont déjà été validés"
    
            return {
                "type": "ir.actions.act_window.message",
                "title": _("Validation des bordereaux"),
                "is_html_message": True,
                "message": _(message),
                "close_button_title": False,
                "buttons": [
                    {
                        "type": "ir.actions.act_window_close",
                        "name": "Fermer",
                    },
                ],
            }
    
        def add_simul(self):
            """
            Ajoute une simulation à la base de cotisation pour l'année donnée.
            Cette simulation est sous la forme d'un fichier Excel avec le détail
            des cotisations par coopératives.
    
            @return: object scop.cotisation.cg.simulation
            """
            # Get context
            type_simul = self.env.context.get("type_simul")
    
            # Get contribution type
            type_cotisation_cg = self.env.ref("cgscop_partner.riga_14397")
    
            # Get product type
            AccountMoveLine = self.env["account.move.line"]
            product_cg_id = self.company_id.contribution_cg_id
            product_com_id = self.company_id.contribution_fede_com_id
            product_cae_id = self.company_id.contribution_fede_cae_id
            product_indus_id = self.company_id.contribution_fede_indus_id
            product_hdf_id = self.company_id.contribution_hdf_id
            product_med_id = self.company_id.contribution_med_id
    
            # List of members
            members = self.get_members()
    
            header = [
                "N° Adhérent",
                "Union Régionale",
                "Forme Coopérative",
                "Organisme",
                "Type de cotisation",
                "Cotisation " + self.year,
                "Cotisation " + str(int(self.year) - 1),
                "Assiette",
                "Montant Assiette",
                "Année Liasse",
                "Durée Exercice",
                "CA (L2052 FL)",
                "CA + Subvention",
                "VA",
                "VABDF sens CG Scop",
                "Masse Salariale (L2052 FY)",
                "Salaires sens CG Scop",
                "Résultat",
                "Dernier effectif connu",
                "Dernier effectif associé connu",
                "Moyen de paiement",
            ]
    
            # Init variables
            datas = []
            total_cg = total_hdf = total_com = total_med = total_cae = total_indus = 0
            count_member = 0
    
            for m in self.web_progress_iter(members, msg="cotisations calculées"):
                liasse = self.get_liasse(m)
                line_ids = AccountMoveLine.search(
                    [
                        ("partner_id", "=", m.id),
                        ("move_id.state", "=", "posted"),
                        ("move_id.year", "=", int(self.year) - 1),
                        ("product_id", "!=", False),
                    ]
                )
    
                # Calcul Cotisation CG Scop
                net_results = 0
                if liasse.L2053_HN > 0:
                    net_results = liasse.L2053_HN
                elif liasse.L2051_DI > 0:
                    net_results = liasse.L2051_DI
                contrib_cg = (
                    self.round_to_closest_multiple(liasse.contribution_cg, 4)
                    if liasse
                    else liasse.get_values_for_cotiz_cg(m).get("plancher1")
                )
                contribution_amount = contrib_cg
                contribution_name = type_cotisation_cg.name
    
                # Calcul cotisation N-1
                contribution_last_year = sum(
                    line_ids.filtered(lambda l: l.product_id == product_cg_id).mapped(
                        lambda c: c.price_total if c.balance > 0 else -c.price_total
                    )
                )
    
                # Construction Tableau
                liasse.read(
                    ["L2052_FL", "av_cg", "av_lf", "revenue_cg", "L2052_FY", "wage_cg"]
                )
                datas_contrib = [
                    m.member_number,
                    m.ur_id.name,
                    m.cooperative_form_id.name,
                    m.name,
                    contribution_name,
                    contribution_amount,
                    contribution_last_year,
                    liasse.contribution_base_type.upper() if liasse else "CA",
                    liasse.contribution_base_amount,
                    liasse.year,
                    liasse.dureeExercice,
                    liasse.L2052_FL,
                    liasse.revenue_cg,
                    liasse.av_lf,
                    liasse.av_cg,
                    liasse.L2052_FY,
                    liasse.wage_cg,
                    net_results,
                    m.staff_last,
                    m.staff_shareholder_last,
                    m.customer_payment_mode_id.name
                    if m.customer_payment_mode_id
                    else "Virement/Chèque",
                ]
    
                # Ajout ligne CG Scop
                datas.append(datas_contrib)
    
                if type_simul == "all":
                    # Ajout ligne UR HDF
                    ur_hdf = self.env.ref("cgscop_partner.riga_14232")
                    if m.ur_id == ur_hdf:
                        datas_contrib_hdf = datas_contrib.copy()
                        # Calcul cotisation
                        contrib_hdf = (
                            self.round_to_closest_multiple(liasse.contribution_hdf, 4)
                            if liasse
                            else 40
                        )
                        # Calcul cotisation N-1
                        contribution_last_year = sum(
                            line_ids.filtered(
                                lambda l: l.product_id == product_hdf_id
                            ).mapped(
                                lambda c: c.price_total if c.balance > 0 else -c.price_total
                            )
                        )
                        datas_contrib_hdf[4] = "Cotisation UR HDF"
                        datas_contrib_hdf[5] = contrib_hdf
                        datas_contrib_hdf[6] = contribution_last_year
                        datas.append(datas_contrib_hdf)
                        total_hdf += contrib_hdf
    
                    # Ajout ligne UR Med
                    ur_med = self.env.ref("cgscop_partner.riga_14243")
                    if m.ur_id == ur_med:
                        datas_contrib_med = datas_contrib.copy()
                        # Calcul cotisation
                        contrib_med = (
                            self.round_to_closest_multiple(liasse.contribution_med, 4)
                            if liasse
                            else 0
                        )
                        # Calcul cotisation N-1
                        contribution_last_year = sum(
                            line_ids.filtered(
                                lambda l: l.product_id == product_med_id
                            ).mapped(
                                lambda c: c.price_total if c.balance > 0 else -c.price_total
                            )
                        )
                        datas_contrib_med[4] = "Cotisation UR Méditerranée"
                        datas_contrib_med[5] = contrib_med
                        datas_contrib_med[6] = contribution_last_year
                        datas.append(datas_contrib_med)
                        total_med += contrib_med
    
                    # Ajout ligne Fédération Com
                    if m.is_federation_com:
                        datas_contrib_com = datas_contrib.copy()
                        # Calcul cotisation
                        if liasse:
                            contrib_fede_com = self.round_to_closest_multiple(
                                liasse.contribution_com, 4
                            )
                        else:
                            staff_id = self.get_last_staff_id(liasse.partner_id)
                            if staff_id.staff_average > 0:
                                contrib_fede_com = staff_id.staff_average * 108
                            else:
                                contrib_fede_com = staff_id.staff_count * 108
                            if contrib_fede_com < 108:
                                contrib_fede_com = 108
                        # Calcul cotisation N-1
                        contribution_last_year = sum(
                            line_ids.filtered(
                                lambda l: l.product_id == product_com_id
                            ).mapped(
                                lambda c: c.price_total if c.balance > 0 else -c.price_total
                            )
                        )
                        datas_contrib_com[4] = "Cotisation Fédération de la Com"
                        datas_contrib_com[5] = contrib_fede_com
                        datas_contrib_com[6] = contribution_last_year
                        datas.append(datas_contrib_com)
                        total_com += contrib_fede_com
    
                    # Ajout ligne Fédération CAE
                    # TODO: Mettre à jour avec is_federation_cae après la maj des
                    #       périodes par la CG
                    if m.cae:
                        datas_contrib_cae = datas_contrib.copy()
                        if liasse:
                            contrib_fede_cae = self.round_to_closest_multiple(
                                liasse.contribution_cae, 4
                            )
                        else:
                            contrib_fede_cae = 300
                            # Calcul cotisation N-1
                        contribution_last_year = sum(
                            line_ids.filtered(
                                lambda l: l.product_id == product_cae_id
                            ).mapped(
                                lambda c: c.price_total if c.balance > 0 else -c.price_total
                            )
                        )
                        datas_contrib_cae[4] = "Cotisation Fédération des CAE"
                        datas_contrib_cae[5] = contrib_fede_cae
                        datas_contrib_cae[6] = contribution_last_year
                        datas.append(datas_contrib_cae)
                        total_cae += contrib_fede_cae
    
                    if m.is_federation_indus:
                        datas_contrib_indus = datas_contrib.copy()
                        if liasse:
                            contrib_fede_indus = self.round_to_closest_multiple(
                                liasse.contribution_indus, 4
                            )
                        else:
                            contrib_fede_indus = 100
                            # Calcul cotisation N-1
                        contribution_last_year = sum(
                            line_ids.filtered(
                                lambda l: l.product_id == product_indus_id
                            ).mapped(
                                lambda c: c.price_total if c.balance > 0 else -c.price_total
                            )
                        )
                        datas_contrib_indus[4] = "Cotisation Fédération Industrie"
                        datas_contrib_indus[5] = contrib_fede_indus
                        datas_contrib_indus[6] = contribution_last_year
                        datas.append(datas_contrib_indus)
                        total_indus += contrib_fede_indus
    
                count_member += 1
                total_cg += contrib_cg
    
            # Génération du ficher Excel
            file_name = (
                datetime.strftime(datetime.now(), "%Y-%m-%d_%Hh%M")
                + " Simulation cotisations "
                + self.year
                + ".xlsx"
            )
            output = BytesIO()
            workbook = xlsxwriter.Workbook(output, {"in_memory": True})
            worksheet1 = workbook.add_worksheet("Données brutes")
    
            # Intégration Header
            format_header = workbook.add_format(
                {"bg_color": "#eeeeee", "font_color": "#333333", "bold": True}
            )
            for col, h in enumerate(header):
                worksheet1.write(0, col, h, format_header)
    
            # Intégration datas
            format_monetary = workbook.add_format({"num_format": "#,##0 [$€-407]"})
    
            for row, contrib in enumerate(datas):
                for col, value in enumerate(contrib):
                    worksheet1.write(row + 1, col, value)
    
            # Format columns
            worksheet1.set_column(0, 0, 10, None)
            worksheet1.set_column(1, 1, 30, None)
            worksheet1.set_column(2, 2, 15, None)
            worksheet1.set_column(3, 3, 50, None)
            worksheet1.set_column(4, 4, 25, None)
            worksheet1.set_column(5, 6, 20, format_monetary)
            worksheet1.set_column(8, 8, 20, format_monetary)
            worksheet1.set_column(11, 17, 20, format_monetary)
            worksheet1.set_column(20, 20, 20, None)
    
            # Enregistrement du fichier dans la base
            workbook.close()
            output.seek(0)
            mem_bytes = output.read()
            file_base64 = base64.encodebytes(mem_bytes)
    
            simul_id = self.simul_ids.create(
                {
                    "cotisation_id": self.id,
                    "file": file_base64,
                    "filename": file_name,
                    "type_simul": type_simul,
                    "total_member": count_member,
                    "total_cg": total_cg,
                    "total_hdf": total_hdf,
                    "total_com": total_com,
                    "total_cae": total_cae,
                    "total_indus": total_indus,
                    "total_med": total_med,
                }
            )
    
            return {
                "name": "Simulation cotisation",
                "type": "ir.actions.act_window",
                "view_mode": "form",
                "res_model": "scop.cotisation.cg.simulation",
                "res_id": simul_id.id,
                "target": "new",
                "flags": {"mode": "readonly", "default_buttons": False},
            }
    
        def show_batch(self):
            self.ensure_one()
            batch_ids = self.env["queue.job.batch"].browse(
                [int(x) for x in self.batch_ids_str.split(",")]
            )
            return {
                "name": "Files d'attente",
                "type": "ir.actions.act_window",
                "view_type": "form",
                "view_mode": "tree,form",
                "res_model": "queue.job.batch",
                "domain": [("id", "in", batch_ids.ids)],
            }
    
        # ------------------------------------------------------
        # Global functions
        # ------------------------------------------------------
        def get_liasse(self, member):
            self.ensure_one()
            Liasse = self.env["scop.liasse.fiscale"]
            # Recherche bordereau précédent
            bdx = self.env["scop.bordereau"].search(
                [("partner_id", "=", member.id), ("year", "=", str(int(self.year) - 1))]
            )
            liasse_ids = None
            if bdx:
                # Recherche liasse année suivante
                liasse_ids = Liasse.search(
                    [
                        ("partner_id", "=", member.id),
                        ("is_qualified", "=", True),
                        ("year", "=", bdx.liasse_fiscale_id.year + 1),
                    ],
                    order="type_id",
                )
            # Si pas de liasse année suivante, on cherche la dernière liasse éligible
            if not liasse_ids:
                liasse_ids = Liasse.search(
                    [
                        ("partner_id", "=", member.id),
                        ("is_qualified", "=", True),
                        ("year", ">", 0),
                        "|",
                        "|",
                        ("L2052_FL", ">", 0),
                        ("av_lf", "!=", 0),
                        ("av_cg", "!=", 0),
                    ],
                    order="year desc",
                )
            # Si pas de liasse cette année, on cherche les liasses sans année
            if not liasse_ids:
                liasse_ids = Liasse.search(
                    [
                        ("partner_id", "=", member.id),
                        ("is_qualified", "=", True),
                        "|",
                        "|",
                        ("L2052_FL", ">", 0),
                        ("av_lf", "!=", 0),
                        ("av_cg", "!=", 0),
                    ],
                    order="id desc",
                )
    
            if liasse_ids:
                last_liasse = liasse_ids[0]
                if last_liasse.year > 0:
                    # Filtre les liasses de la dernière année disponible
                    last_liasse_all = liasse_ids.filtered(
                        lambda l: l.year == last_liasse.year
                    )
                    # Si une seule liasse de cette année => on retourne cette liasse
                    if len(last_liasse_all) == 1:
                        return last_liasse
                    # Priorité ensuite sur les liasses type LM
                    elif last_liasse_all.filtered(lambda l: l.type_id == "lm"):
                        return last_liasse_all.filtered(lambda l: l.type_id == "lm")[0]
                    else:
                        return last_liasse
                # Sinon on retourne la première liasse
                return liasse_ids[0]
            else:
                return Liasse
    
        # ------------------------------------------------------
        # Calcul des cotisations
        # ------------------------------------------------------
        def get_va(self, liasse):
            """
            VA saisie ou VA au sens CGSCOP
            si pas de VA renseignée sur la liasse fiscale
            :param liasse:
            :return:
            """
            liasse.read(["av_lf", "av_cg"])
            if liasse.av_lf != 0:
                if liasse.av_lf > liasse.av_cg:
                    va = liasse.av_cg
                else:
                    va = liasse.av_lf
            else:
                va = liasse.av_cg
            return va
    
        def get_last_staff_id(self, partner):
            """
            Return last known staff_id line
            :param partner:
            :return:
            """
            staff_id = partner.staff_ids.sorted(key=lambda l: l.effective_date)
            return staff_id[-1] if len(staff_id) > 1 else staff_id
    
        def get_type_assiette(self, ca, va):
            """
            Find type assiette for bordereau
            :param ca:
            :param va:
            :return: type
            """
            if ca > 0 and va > 0:
                if ca <= va * 7 / 3:
                    type_cotiz = "ca"
                else:
                    type_cotiz = "va"
            else:
                if va > 0:
                    type_cotiz = "va"
                else:
                    type_cotiz = "ca"
            return type_cotiz