from ast import For
import time
from datetime import date
#from tkinter.tix import Form

import numpy as np
import pandas as pd
from django.db.models import Case, IntegerField, Prefetch, Sum, When
from django.db.transaction import atomic
from django.forms import model_to_dict
from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import status, viewsets
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from django.db.models import Q



from .. import constants
from ..filters import AdministreFilter, AdministrePAMFilter, PosteFilter, PostePAMFilter, RelatedOrderingFilter
from ..models import (FMOB, Administre, Decision, DecisionChoices, Domaine,
                      Filiere, FormationEmploi, Marque, MarquesGroupe,
                      PcpFeGroupe, Poste, PreferencesListe, SousVivier,
                      SousVivierAssociation, SpecifiqueChoices, Administres_Pams, StatutPamChoices as StatutPam,
                      Postes_Pams,PAM)
from ..paginations import HeavyDataPagination
from ..reporting import (reporting_suivi_pam_admin, reporting_suivi_pam_poste,
                         reporting_taux_armement_gestionnaire,
                         reporting_taux_armement_pcp)
from ..serializers import (CTX_KEY_DECISIONS, CTX_KEY_PROFILES,
                           AdministreSerializer,
                           AdministresPamsSerializer,
                           AlimentationReferentielSerializer,
                           DomaineSerializer, FileSVSerializer,
                           FiliereSerializer, FmobSerializer, MarqueSerializer,
                           MarquesGroupeSerializer, PcpFeGroupeSerializer,
                           PosteSerializer, SousVivierAssociationSerializer,
                           PostesPamsSerializer)
from ..utils import sous_viviers_du_cellule, without_keys, generate_sv_id, nf2categorie
from ..utils.decisions import get_available_decisions
from ..utils.decorators import class_logger
from ..utils.permissions import get_profiles_by_adm
from ..utils_extraction import (DataFrameTypes, FileTypes, open_excel,
                                read_files_by_type)
from .commun import (GestionnairePermission, execution_time_viewset,
                     query_count_viewset)


# Vue de calcul des indicateurs
# Renvoie les indicateurs pour les vues :
#          - taux armement FE pour les PCP
#          - taux armement FE pour les gestionnaires
#          - suivi pam des gestionnaires pour les administrés
#          - suivi pam des gestionnaires pour les postes
@class_logger
@execution_time_viewset
@query_count_viewset
class ReportingView(APIView):
    """
    Cette classe est dédiée au vue du calcul des indicateurs
    - taux armement FE pour les PCP
    - taux armement FE pour les gestionnaires
    - suivi pam des gestionnaires pour les administrés
    - suivi pam des gestionnaires pour les postes

    """
    permission_classes = [IsAuthenticated, GestionnairePermission]

    def __str__(self):
        return 'ReportingView'

    def get(self, request):
        """La fonction get renvoie les indicateurs

        :type request: rest_framework.request.Request
        :param request: Request contenant le type, sous_vivier id, niveau fonctinonel et l'id de la formation emploi.

        :return:        - **return** (*json*): Si le type est SUIVI_PAM_GESTIONNAIRE le json contiendra l'information sur le suivi pam des
        gestionnaires pour les administrés et les postes. Si le type est TAUX_ARMEMENT_FE le json contiendra les indicateurs sur les taux d'armement.


        """
        type = request.query_params["type"]

        sv_id = []
        if 'sv_id' in request.query_params:
            sv_id = request.query_params["sv_id"]

        if 'vue' in request.query_params:
            vue = request.query_params["vue"]

        if "nf" in request.query_params:
            nf = request.query_params.getlist("nf")
        else:
            nf = ['1A', '1B', '1C', '2.', '3A', '3B', '3B NFS', '4.', '5A', '5B', '5C', '6A', '6B']

        if 'fe_id' in request.query_params:  # a voir
            fe_id = request.query_params.getlist('fe_id')
        else:
            fe_id = list(FormationEmploi.objects.values_list('fe_code', flat=True))

        # Vues pour les indicateurs sur le suivi du pam
        if (type == "SUIVI_PAM_GESTIONNAIRE"):
            if "d_code" in request.query_params:
                d_id = request.query_params.getlist("d_code")
            else:
                filieres = list(SousVivierAssociation.objects.filter(sous_vivier__sv_id=sv_id).values_list('filiere_id',
                                                                                                           flat=True).distinct())
                d_id = list(Filiere.objects.filter(f_code__in=filieres).values_list('domaine_id', flat=True).distinct())
            if "f_code" in request.query_params:
                f_id = request.query_params.getlist("f_code")
            else:
                f_id = list(SousVivierAssociation.objects.filter(sous_vivier__sv_id=sv_id).values_list('filiere_id',
                                                                                                       flat=True).distinct())
            if "categorie" in request.query_params:
                categorie = request.query_params.getlist("categorie")
            else:
                categorie = list(
                    SousVivierAssociation.objects.filter(sous_vivier__sv_id=sv_id).values_list('sva_categorie',
                                                                                               flat=True).distinct())
            # résultats pour la vue suivi pam des gestionnaires pour les administrés
            nb_a_etudier, nb_a_muter, nb_a_maintenir, nb_non_etudie_administres, nb_a_partant, nb_a_non_dispo, nb_prepos_administres, nb_pos_administres, nb_omi_active_administres, nb_omi_en_cours_administres, reste_a_realiser_administres, reste_a_realiser_a_etudier, reste_a_realiser_a_muter = reporting_suivi_pam_admin(
                sv_id, f_id, d_id, nf, categorie)
            # résultats pour la vue suivi pam des gestionnaires pour les postes
            nb_p1, nb_p2, nb_p3, nb_p4, nb_gele, nb_non_etudie_postes, reste_a_realiser_postes, reste_a_realiser_p1, reste_a_realiser_p2, reste_a_realiser_p3, reste_a_realiser_p4, nb_prepos_postes, nb_pos_postes, nb_omi_active_postes, nb_omi_en_cours_postes = reporting_suivi_pam_poste(
                sv_id, f_id, d_id, nf, categorie)
            return JsonResponse(
                {"nb_a_etudier": nb_a_etudier, "nb_a_muter": nb_a_muter, "nb_a_maintenir": nb_a_maintenir,
                 "nb_a_partant": nb_a_partant, "nb_a_non_dispo": nb_a_non_dispo,
                 "nb_non_etudie_administres": nb_non_etudie_administres,
                 "nb_prepos_administres": nb_prepos_administres, "nb_pos_administres": nb_pos_administres,
                 "nb_omi_en_cours_administres": nb_omi_en_cours_administres,
                 "nb_omi_active_administres": nb_omi_active_administres,
                 "reste_a_realiser_administres": reste_a_realiser_administres,
                 "reste_a_realiser_a_etudier": reste_a_realiser_a_etudier,
                 "reste_a_realiser_a_muter": reste_a_realiser_a_muter,
                 "nb_p1": nb_p1, "nb_p2": nb_p2, "nb_p3": nb_p3, "nb_p4": nb_p4, "nb_gele": nb_gele,
                 "nb_non_etudie_postes": nb_non_etudie_postes, "reste_a_realiser_postes": reste_a_realiser_postes,
                 "reste_a_realiser_p1": reste_a_realiser_p1, "reste_a_realiser_p2": reste_a_realiser_p2,
                 "reste_a_realiser_p3": reste_a_realiser_p3, "reste_a_realiser_p4": reste_a_realiser_p4,
                 "nb_prepos_postes": nb_prepos_postes, "nb_pos_postes": nb_pos_postes,
                 "nb_omi_en_cours_postes": nb_omi_en_cours_postes, "nb_omi_active_postes": nb_omi_active_postes},
                safe=False)
        # Vues pour les indicateurs sur les taux d'armement
        elif (type == 'TAUX_ARMEMENT_FE'):
            if "d_code" in request.query_params:
                d_id = request.query_params.getlist("d_code")
            else:
                d_id = list(Domaine.objects.all().values_list('d_code', flat=True))
            if "f_code" in request.query_params:
                f_id = request.query_params.getlist("f_code")
            else:
                f_id = list(Filiere.objects.all().values_list('f_code', flat=True))
            if "categorie" in request.query_params:
                categorie = request.query_params.getlist("categorie")
            else:
                categorie = ['MDR', 'SOFF', 'OFF', 'OGX']

            if len(sv_id) == 0:
                # résultats pour la vue taux armement FE pour les PCP
                nb_militaires_actuel, nb_postes_actuel, ecart_actuel, taux_armement_actuel, nb_militaires_entrants, nb_militaires_sortants, nb_militaires_projete, taux_armement_projete, taux_armement_cible = reporting_taux_armement_pcp(
                    fe_id, f_id, d_id, nf, categorie)
            else:
                # résultats pour la vue taux armement FE pour les gestionnaires
                nb_militaires_actuel, nb_postes_actuel, ecart_actuel, taux_armement_actuel, nb_militaires_entrants, nb_militaires_sortants, nb_militaires_projete, taux_armement_projete, taux_armement_cible = reporting_taux_armement_gestionnaire(
                    fe_id, f_id, d_id, nf, categorie, sv_id)
            return JsonResponse({'nb_militaires_actuel': nb_militaires_actuel, "nb_postes_actuel": nb_postes_actuel,
                                 'ecart_actuel': ecart_actuel, 'taux_armement_actuel': taux_armement_actuel,
                                 'nb_militaires_entrants': nb_militaires_entrants,
                                 'nb_militaires_sortants': nb_militaires_sortants,
                                 'nb_militaires_projete': nb_militaires_projete,
                                 'taux_armement_projete': taux_armement_projete,
                                 'taux_armement_cible': taux_armement_cible}, safe=False)
        else:
            return JsonResponse({"autre vue": "autres indicateurs demandes"})


# TODO : Supprimer cette vue qui n'est plus utilisée
# Vue de chargement des sous-viviers :
#       - Charge et traite le fichier de définition des sous-viviers
#       - Attribue les sous-viviers présents aux administrés et postes correspondants
@class_logger
@execution_time_viewset
@query_count_viewset
class ChargementSVView(APIView):
    """
    Cette classe est dédiée au vue de chargement des sous-viviers. Charge et traite le fichier de définition des sous-viviers et attribue les sous-viviers présents aux administrés et postes correspondants
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]

    serializer_class = FileSVSerializer

    def get(self, request):
        """La fonction get renvoie une reponse contenant ok

        :type request: rest_framework.request.Request
        :param request: Requset


        :return:    - **return** (*json*): json contenant "ok".
        """
        return Response({"get": "ok"})

    def post(self, request):
        """La fonction post charge les sous-viviers

        :type request: rest_framework.request.Request
        :param request: Request contenant le fichier SV

        :return:    - **Response** (*Response*): Reponse contient la liste des sous-viviers créés, ignorés et où il y a eu une erreur.
        """
        serializer = FileSVSerializer(data=request.data)

        if not serializer.is_valid():
            return Response(
                data=serializer.errors,
                status=status.HTTP_400_BAD_REQUEST
            )

        sv_file = request.data['SV']

        sv_df = open_excel(sv_file, sheetname="GESTIONNAIRES", engine='openpyxl')

        sv = pd.DataFrame(columns=['gestionnaire_id_sap', 'f_code', 'asso_sv_categorie', 'arme'])

        for i in range(len(sv_df)):
            sv.loc[i] = [sv_df.iloc[i, 0], sv_df.iloc[i, 3], sv_df.iloc[i, 4], sv_df.iloc[i, 5]]

        sv.dropna(subset=['f_code'], inplace=True)
        sv['arme'] = sv['arme'].replace({np.nan: None})
        sv.reset_index(drop=True, inplace=True)
        sv['asso_sv_categorie'].replace({"SOUS-OFFICIER": "SOFF", "OFFICIER": "OFF", "MILITAIRE DU RANG": "MDR"},
                                        inplace=True)
        sv['sv_id'] = sv.f_code + '_' + sv.asso_sv_categorie
        sv.f_code = sv.f_code.apply(lambda x: x.split(','))
        sv = sv.explode(column='f_code')
        sv.reset_index(drop=True, inplace=True)
        errors = []
        created = []
        ignored = []
        sv.fillna(np.nan, inplace=True)
        sv.replace([np.nan], [None], inplace=True)
        self.logger.debug(sv.head())

        for i in range(len(sv)):
            try:
                filiere = Filiere.objects.get(f_code=sv.at[i, "f_code"])
                categorie = sv.at[i, "asso_sv_categorie"]
                asso_sv = SousVivierAssociation.objects.filter(sva_categorie=categorie,
                                                               filiere__f_code=sv.at[i, "f_code"])

                # Vérifier que filiere et catégorie n'ont pas déjà de sous vivier
                if asso_sv.count() == 0 and categorie is not None:
                    # Créer le sous-vivier correspondant dans la table SousVivier
                    sous_vivier = SousVivier(sv_id=sv.at[i, "sv_id"],
                                             sv_libelle=str(sv.at[i, "sv_id"]))
                    self.logger.debug(model_to_dict(sous_vivier))
                    sous_vivier.save()

                    self.logger.debug(sv.at[i, "sv_id"])

                    # Insertion du nouveau sous-vivier dans la table d'association (sans le droit arme dans un premier temps)
                    sva = SousVivierAssociation(sva_id=i, sous_vivier_id=sous_vivier.sv_id, filiere_id=filiere.f_code,
                                                sva_categorie=categorie, sva_arme=sv.at[i, "arme"])
                    sva.save()

                    # Mise à jour du sous_vivier_id pour les postes et administres ayant la filiere et la categorie en cours
                    Administre.objects.filter(a_categorie=categorie, a_filiere_id=filiere.f_code).update(
                        sous_vivier_id=sous_vivier.sv_id)

                    # En fonction du fichier excel, changer la mise à jour du sous_vivier
                    Poste.objects.filter(p_categorie=categorie, p_filiere_id=filiere.f_code).sous_viviers.set([sous_vivier.sv_id])
                    created.append(str(sv.at[i, "asso_sv_categorie"]) + " " + str(sv.at[i, "f_code"]))

                else:
                    ignored.append(str(sv.at[i, "asso_sv_categorie"]) + " " + str(sv.at[i, "f_code"]))
                    # Si l'association appartient déjà à un sous_vivier, on ne fait rien
            except Filiere.DoesNotExist:
                errors.append("Filiere " + sv.at[i, 'f_code'] + "non retrouvée")

        return Response({"created": created, "ignored": ignored, "errors": errors})



@class_logger
@execution_time_viewset
@query_count_viewset
class AlimentationReferentielView(APIView):
    """ Vue pour alimenter la base à partir de référentiels """

    permission_classes = [IsAuthenticated, IsAdminUser]
    serializer_class = AlimentationReferentielSerializer

    def get(self, request):
        return Response("Formulaire d'alimentation d'OGURE NG (référentiel)")

    @atomic
    def post(self, request):
        """
        Charge le(s) fichier(s) et met à jour la base.

        :param request: requête, contient les fichiers
        :type  request: class:`rest_framework.request.Request`

        :raises: class:`rest_framework.exceptions.APIException`
        :return: réponse
        :rtype:  class:`rest_framework.response.Response`
        """

        try:
            validator = self.serializer_class(data=request.data)
            validator.is_valid(raise_exception=True)

            # récupération des fichiers
            referentiel_fe = validator.validated_data.get('referentiel_fe')

            if referentiel_fe:
                Cols = FormationEmploi.Cols
                col_pk           = Cols.PK
                col_pk_mere      = Cols.REL_MERE
                col_libelle_mere = Cols.LIBELLE
                col_zone_def     = Cols.ZONE_DEFENSE

                def process_referentiel_fe(referentiel) -> pd.DataFrame:
                    """
                    Fonction de lecture du référentiel de FE.

                    :param referentiel: Fichier du référentiel FE
                    :type  referentiel: XLSX

                    :return: DataFrame
                    :rtype:  class:`pandas.DataFrame`
                    """
                    return open_excel(referentiel, sheetname=0, engine='openpyxl')

                def convertir_fe(df: pd.DataFrame) -> pd.DataFrame:
                    """
                    Fonction de conversion du DataFrame de FE.

                    :param df: DataFrame du référentiel FE
                    :type  df: class:`pandas.DataFrame`

                    :return: DataFrame
                    :rtype:  class:`pandas.DataFrame`
                    """

                    col_pk_avant           = 'FE CREDO'         # B
                    col_pk_mere_avant      = 'FE mère CREDO'    # D
                    col_libelle_mere_avant = 'FE mère LA'       # E
                    col_zone_def_avant     = 'Zone de Défense'  # O

                    return (
                        df[[col_pk_avant, col_pk_mere_avant, col_libelle_mere_avant, col_zone_def_avant]]
                        .drop_duplicates(subset=col_pk_avant, keep='first')
                        .rename(columns={
                            col_pk_avant:           col_pk,
                            col_pk_mere_avant:      col_pk_mere,
                            col_libelle_mere_avant: col_libelle_mere,
                            col_zone_def_avant:     col_zone_def
                        })
                        .astype({col_pk: 'str', col_pk_mere: 'str'})
                    )

                def mettre_a_jour_fe(df: pd.DataFrame) -> None:
                    """
                    Met à jour les FE base à partir du DataFrame de FE.

                    :param df: DataFrame du référentiel FE
                    :type  df: class:`pandas.DataFrame`

                    :return: DataFrame
                    :rtype:  class:`pandas.DataFrame`
                    """

                    TypeModele = FormationEmploi
                    Cols = TypeModele.Cols
                    champs_maj = (Cols.REL_MERE, Cols.ZONE_DEFENSE)
                    modeles_en_base = {m.pk: m for m in TypeModele.objects.select_related(Cols.REL_MERE).only('pk', *champs_maj)}

                    taille_batch = 100
                    dict_update = {}
                    dict_mere_create = {}
                    error_count = 0
                    for idx, rec in enumerate(df.to_dict('records')):
                        pk = rec.get(col_pk)
                        try:
                            id_mere = rec.get(col_pk_mere)
                            zone_defense = rec.get(col_zone_def)

                            en_base = modeles_en_base.get(pk)

                            # TODO pas de création pour l'instant (ni de suppression) quand en_base est falsy
                            if en_base:
                                mere = None
                                if id_mere is not None:
                                    mere = modeles_en_base.get(id_mere) or dict_mere_create.get(id_mere)
                                    if not mere:
                                        try:
                                            # les FE mères manquantes seront créées
                                            mere = TypeModele(pk=id_mere, fe_libelle=rec.get(col_libelle_mere))
                                        except Exception as e:
                                            raise RuntimeError(f'la création d\'un modèle de type "{TypeModele.__name__}" (mère) a échoué') from e
                                        dict_mere_create.setdefault(id_mere, mere)

                                if mere != getattr(en_base, Cols.REL_MERE, None) or zone_defense != getattr(en_base, Cols.ZONE_DEFENSE, None):
                                    try:
                                        modele = TypeModele(pk=pk, mere=mere, zone_defense=zone_defense)
                                    except Exception as e:
                                        raise RuntimeError(f'la création d\'un modèle de type "{TypeModele.__name__}" a échoué') from e
                                    dict_update.setdefault(pk, modele)
                        except Exception:
                            error_count = error_count + 1
                            self.logger.exception('%s une erreur est survenue à la ligne : %s (pk=%s)', TypeModele.__name__, idx, pk)

                    if error_count:
                        self.logger.warning("%s(s) en erreur : %s", TypeModele.__name__, error_count)

                    if dict_mere_create:
                        TypeModele.objects.bulk_create(dict_mere_create.values(), batch_size=taille_batch)
                        self.logger.info('%s(s) mères créée(s) : %s', TypeModele.__name__, len(dict_mere_create))

                    if dict_update and champs_maj:
                        TypeModele.objects.bulk_update(dict_update.values(), batch_size=taille_batch, fields=champs_maj)
                        self.logger.info('%s(s) mise(s) à jour : %s', TypeModele.__name__, len(dict_update))

                df_referentiel_fe = process_referentiel_fe(referentiel_fe)
                self.logger.info('Lecture du référentiel FE                ------> Succès')

                df_referentiel_fe = convertir_fe(df_referentiel_fe)
                self.logger.info('Extraction des données du référentiel FE ------> Succès')

                mettre_a_jour_fe(df_referentiel_fe)
                self.logger.info('Mise à jour du référentiel FE            ------> Succès')

            return Response({'Insertion réussie'})
        except (Http404, APIException):
            raise
        except BaseException:
            message = "Impossible d'alimenter le(s) référentiel(s)"
            self.logger.exception(message)
            raise APIException(message)

# Vue API des administrés
# Pagination OK
# Ordering OK
# Search NOK
# Filtering NOK

@class_logger
@execution_time_viewset
@query_count_viewset
class AdministreView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue de l'administre.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    serializer_class = AdministreSerializer

    filter_backends = [DjangoFilterBackend, RelatedOrderingFilter]
    # filterset_fields = ['category', 'in_stock']
    filterset_class = AdministreFilter
    ordering_fields = Administre.Cols.PK
    ordering = [Administre.Cols.PK]
    pagination_class = HeavyDataPagination

    # important : mettre à jour quand le serializer change
    def get_queryset(self):
        Cols = Administre.Cols
        ColsFE = FormationEmploi.Cols
        return Administre.objects.select_related(
            Cols.REL_FONCTION, Cols.REL_GRADE, Cols.REL_SOUS_VIVIER
        ).prefetch_related(
            Cols.M2M_COMPETENCES,
            Prefetch(Cols.M2M_PAM, queryset=Administres_Pams.objects.select_related(Administres_Pams.Cols.O2M_FMOB)),
            # Prefetch(Cols.REL_DECISION, queryset=Decision.objects.select_related(
            #     f'{Decision.Cols.REL_POSTE}__{Poste.Cols.REL_FORMATION_EMPLOI}__{ColsFE.REL_MERE}',
            # )),
            Prefetch(Cols.REL_FORMATION_EMPLOI, queryset=FormationEmploi.objects.select_related(ColsFE.REL_MERE)),
        )

    def get_serializer(self, *args, **kwargs):
        ser = super().get_serializer(*args, **kwargs)
        arg = args[0] if args else None
        if arg and isinstance(arg, (list, tuple)) and self.request.method == 'GET':
            # calcule directement, si besoin, les profils et les décisions disponibles pour tous les résultats
            if CTX_KEY_PROFILES not in ser.context:
                ser.context[CTX_KEY_PROFILES] = get_profiles_by_adm(self.request.user, *arg)
            if CTX_KEY_DECISIONS not in ser.context:
                ser.context[CTX_KEY_DECISIONS] = get_available_decisions(arg, profiles_by_adm=ser.context[CTX_KEY_PROFILES],
                annee_pam = self.request.query_params['pam__in'])
        return ser

    @atomic
    def put(self, request):
        """La fonction put met à jour une liste d'administres.

        :type request: rest_framework.request.Request
        :param request: Request contenant la liste d'administres.

        :return:    - **Response** (*Response*): Reponse contient un message de la réussite de met à jour des administres .
        """
        try:
            req_data = request.data
            is_list = isinstance(req_data, list)
            validator = self.serializer_class(data=req_data, many=is_list, partial=True)
            validator.is_valid(raise_exception=True)

            today = date.today()
            Cols = Administre.Cols

            if 'annee_pam' in req_data:
                annee_pam = req_data.pop('annee_pam')
            else:
                annee_pam = ''


            def copy_data_item(input, pk):
                """Copie et complète si besoin le dictionnaire en entrée"""
                result = {**input}
                if pk and Cols.PK not in result:
                    result[Cols.PK] = pk
                return result

            def set_competences(administre_obj, a_comp):
                """Set competences to administre object"""
                a_comp_id = [competence.comp_id for competence in a_comp]
                administre_obj.a_liste_id_competences.set(a_comp_id)
                return administre_obj

            if is_list:  # Update multiple elements
                data = [copy_data_item(item, req_data[idx][Cols.PK]) for idx, item in enumerate(validator.validated_data)]
                fields = [key for key in data[0].keys() if key != Cols.PK]

                # Direct assignment to the forward side of a many-to-many set is prohibited. Use a_liste_id_competences.set() instead.
                if Cols.STATUT_PAM in fields:
                    fields_to_update_pam = ['a_statut_pam_annee']
                    data_statut_pam = [{'id' :str(o['a_id_sap'])+str(annee_pam), 'a_statut_pam_annee' : o[Cols.STATUT_PAM]} for o in data]
                    objs = [Administres_Pams(**data_item) for data_item in data_statut_pam]
                    Administres_Pams.objects.bulk_update(objs, fields=fields_to_update_pam)


                elif 'a_liste_id_competences' not in fields:
                    objs = [Administre(**data_item) for data_item in data]
                    if Cols.STATUT_PAM in fields:
                        list_error = []
                        for i in range(len(objs)):
                            old_administre = Administre.objects.get(a_id_sap=objs[i].a_id_sap)
                            administre = objs[i]
                            eip = administre.a_eip
                            fe_code = old_administre.formation_emploi_id
                            avis = administre.a_statut_pam
                            old_avis = old_administre.a_statut_pam
                            try:
                                categorie = constants.CATEGORIE_BY_NF[old_administre.a_nf.upper()]
                            except:
                                erreur = 'la catégorie de l\'administré n\'est pas reconnue dans ogure.'
                                raise Exception(erreur)

                        

                            # liste_error = impact_decisions(old_administre, administre, old_avis, avis, eip, fe_code, categorie)

                    Administre.objects.bulk_update(objs, fields=fields)
                else:
                    objs_w_comp = [set_competences(Administre(**without_keys(i, ['a_liste_id_competences'])), i['a_liste_id_competences']) for i in data]

            else:  # Update one element
                data = copy_data_item(validator.validated_data, validator.validated_data[Cols.PK])
                # c'est un bug la récupération de a_id_sap en-dessous, non ?
                a_id_sap = Administre.objects.update(data)
                db_instance = Administre.objects.filter(a_id_sap=a_id_sap).first()
                db_instance.tag.clear()

            return Response({'msg': 'updated'})
        except APIException:
            raise
        except Exception:
            message = 'échec de la mise à jour de N administrés'
            self.logger.exception(message)
            raise APIException(message)

    def partial_update(self, request, pk=None):
        """ La fonction put met à jour un administre.

        :type request: rest_framework.request.Request
        :param request: Request contenant l'administre.

        :type pk: integer
        :param pk: Primary Key de l'administre.

        :return:    - **Response** (*Response*): Reponse contient un message de la réussite de met à jour de l'administre .
        """
        try:
            a = get_object_or_404(Administre.objects, a_id_sap=pk) # Permet de déclancher la vérification des droits au niveau de l'objet
            self.check_object_permissions(self.request, a)
            req_data = request.data

            if 'annee_pam' in req_data:
                annee_pam = req_data.pop('annee_pam')


            # TODO valider les données (il faut d'abord corriger la MAJ des compétences)
            # validator = self.serializer_class(data=req_data, partial=True)
            # validator.is_valid(raise_exception=True)

            Cols = Administre.Cols

            # Copie et complète si besoin le dictionnaire en entrée
            # administre = {**validator.validated_data}
            administre = {**req_data}
            administre[Cols.PK] = pk

            fields = [key for key in administre.keys() if key != Cols.PK] # and key != 'a_liste_id_competences']

            if 'a_domaine_futur' in fields:
                administre['a_domaine_futur_id'] = administre['a_domaine_futur']
                del administre['a_domaine_futur']

            if 'a_filiere_futur' in fields:
                administre['a_filiere_futur_id'] = administre['a_filiere_futur']
                del administre['a_filiere_futur']

            if Cols.STATUT_PAM in fields:
                old_administre = Administre.objects.get(a_id_sap=pk)
                eip = old_administre.a_eip
                fe_code = old_administre.formation_emploi_id
                avis = administre[Cols.STATUT_PAM]
                old_avis = old_administre.a_statut_pam
                try:
                    categorie = constants.CATEGORIE_BY_NF[old_administre.a_nf.upper()]
                except:
                    erreur = 'la catégorie de l\'administré n\'est pas reconnue dans ogure.'
                    raise APIException(erreur)

                
                

                # liste_error = impact_decisions(old_administre, Administre(**administre), old_avis, avis, eip, fe_code, categorie)


            if 'a_liste_id_competences' in fields:
                adm=Administre(pk=pk)
                #adm=request.data['a_liste_id_competences']
                if administre['a_liste_id_competences']:
                    adm.a_liste_id_competences.set(administre['a_liste_id_competences'].split(','))
                else:
                    adm.a_liste_id_competences.set("")
            elif Cols.STATUT_PAM in fields:
                adm_pam = Administres_Pams.objects.get(id = str(pk)+str(annee_pam))
                adm_pam.a_statut_pam_annee = administre[Cols.STATUT_PAM]
                adm_pam.save()
            else:
                copy = { k: v for k, v in administre.items() if k != 'a_liste_id_competences'}
                adm = Administre(**copy)

                if fields:
                    Administre.objects.bulk_update([adm], fields=fields)

            return Response({'msg': 'updated'})

        except (Http404, APIException):
            raise
        except Exception:
            message = "échec de la mise à jour de l'administré"
            self.logger.exception(message)
            raise APIException(message)


# GET /api/administres => 0 administrés
# POST /api/administres => Crée 1 nouvel administres => id=1
# GET /api/administres/1 => 1 administre précédemment créé
# POST /api/administres/1 => 1 administre précédemment créé


@class_logger
@execution_time_viewset
@query_count_viewset
class AdministrePAMView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue de l'administre.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    serializer_class = AdministresPamsSerializer

    filter_backends = [DjangoFilterBackend, RelatedOrderingFilter]
    # filterset_fields = ['category', 'in_stock']
    filterset_class = AdministrePAMFilter
    ordering_fields = Administres_Pams.Cols.PK
    ordering = [Administres_Pams.Cols.PK]
    pagination_class = HeavyDataPagination
    # important : mettre à jour quand le serializer change
    def get_queryset(self):
        Cols = Administres_Pams.Cols
        Cols_Administre = Administre.Cols
        ColsFE = FormationEmploi.Cols

        return Administres_Pams.objects.select_related(
            Cols.REL_DECISION, Cols.REL_PAM, Cols.O2M_FMOB
        ).prefetch_related(
            Prefetch(Cols.REL_ADMINISTRE, queryset = Administre.objects.select_related(Cols_Administre.REL_FONCTION, Cols_Administre.REL_GRADE, Cols_Administre.REL_SOUS_VIVIER
                                                                        ).prefetch_related(
                                                                            Cols_Administre.M2M_COMPETENCES,
                                                                            Prefetch(Cols_Administre.REL_FORMATION_EMPLOI, queryset=FormationEmploi.objects.select_related(ColsFE.REL_MERE)),
                                                                        ))
        )

    def get_serializer(self, *args, **kwargs):
        ser = super().get_serializer(*args, **kwargs)
        arg = args[0] if args else None

        if arg and isinstance(arg, (list, tuple)) and self.request.method == 'GET':
            # calcule directement, si besoin, les profils et les décisions disponibles pour tous les résultats
            if CTX_KEY_PROFILES not in ser.context:
                ser.context[CTX_KEY_PROFILES] = get_profiles_by_adm(self.request.user, *arg)
            if CTX_KEY_DECISIONS not in ser.context:
                ser.context[CTX_KEY_DECISIONS] = get_available_decisions(arg, profiles_by_adm=ser.context[CTX_KEY_PROFILES])
        return ser

    @atomic
    def put(self, request):
        """La fonction put met à jour une liste d'administres.

        :type request: rest_framework.request.Request
        :param request: Request contenant la liste d'administres.

        :return:    - **Response** (*Response*): Reponse contient un message de la réussite de met à jour des administres .
        """
        try:
            req_data = request.data
            is_list = isinstance(req_data, list)
            # validator = AdministreSerializer(data=req_data, many=is_list, partial=True)
            # validator.is_valid(raise_exception=True)

            today = date.today()
            Cols = Administre.Cols

            # Extraire l'annee 
            if 'pam_id' in req_data[0]:
                annee_pam = req_data[0]['pam_id']


            def copy_data_item(input, pk):
                """Copie et complète si besoin le dictionnaire en entrée"""
                result = {**input}
                if pk and Cols.PK not in result:
                    result[Cols.PK] = pk
                return result

            def set_competences(administre_obj, a_comp):
                """Set competences to administre object"""
                a_comp_id = [competence.comp_id for competence in a_comp]
                administre_obj.a_liste_id_competences.set(a_comp_id)
                return administre_obj

            if is_list: 
                data = [copy_data_item(item, req_data[idx][Cols.PK]) for idx, item in enumerate(req_data)]
                fields = [key for key in data[0].keys() if key != Cols.PK]

                # Direct assignment to the forward side of a many-to-many set is prohibited. Use a_liste_id_competences.set() instead.

                # Update Administres Pams dans les if et elif et le else et elif est utilisé pour la mise a jour des Administres

                if Cols.STATUT_PAM in fields:
                    fields_to_update_pam = ['a_statut_pam_annee']
                    # Creation d'une liste qui contient l'id ( a_id_sap + annee_pam) avec le statut choisi
                    data_statut_pam = [{'id' :str(o['a_id_sap'])+str(annee_pam), 'a_statut_pam_annee' : o[Cols.STATUT_PAM]} for o in data]
                    # Creation des objets Administres Pams
                    objs = [Administres_Pams(**data_item) for data_item in data_statut_pam]
                    # Mise a jour des objets Administres Pams
                    Administres_Pams.objects.bulk_update(objs, fields=fields_to_update_pam)
                
                elif 'a_liste_depts_souhaites_pam' in fields:
                    fields_to_update_pam = ['a_liste_depts_souhaites_pam']
                    # Creation d'une liste qui contient l'id ( a_id_sap + annee_pam) avec le statut choisi
                    data_statut_pam = [{'id' :str(o['a_id_sap'])+str(annee_pam), 'a_liste_depts_souhaites_pam' : o['a_liste_depts_souhaites_pam']} for o in data]
                    # Creation des objets Administres Pams
                    objs = [Administres_Pams(**data_item) for data_item in data_statut_pam]
                    # Mise a jour des objets Administres Pams
                    Administres_Pams.objects.bulk_update(objs, fields=fields_to_update_pam)
        
                elif 'a_liste_id_competences' in fields:
                    objs_w_comp = [set_competences(Administre(**without_keys(i, ['a_liste_id_competences'])), i['a_liste_id_competences']) for i in data]
                
                elif 'a_liste_zones_geographiques_shm_pam' in fields:
                    fields_to_update_pam = ['a_liste_zones_geographiques_shm_pam']
                    data_statut_pam = [{'id' :str(o['a_id_sap'])+str(annee_pam), 'a_liste_zones_geographiques_shm_pam' :  o['a_liste_zones_geographiques_shm_pam']} for o in data]
                    objs = [Administres_Pams(**data_item) for data_item in data_statut_pam]
                    # Mise a jour des objets Administres Pams
                    Administres_Pams.objects.bulk_update(objs, fields=fields_to_update_pam)
                
                else:
                    objs = [Administre(**data_item) for data_item in data]
                    if Cols.STATUT_PAM in fields:
                        list_error = []
                        for i in range(len(objs)):
                            old_administre = Administre.objects.get(a_id_sap=objs[i].a_id_sap)
                            administre = objs[i]
                            eip = administre.a_eip
                            fe_code = old_administre.formation_emploi_id
                            avis = administre.a_statut_pam
                            old_avis = old_administre.a_statut_pam
                            try:
                                categorie = constants.CATEGORIE_BY_NF[old_administre.a_nf.upper()]
                            except:
                                erreur = 'la catégorie de l\'administré n\'est pas reconnue dans ogure.'
                                raise Exception(erreur)

                            # liste_error = impact_decisions(old_administre, administre, old_avis, avis, eip, fe_code, categorie)

                    elif 'a_domaine_futur_id' in fields or 'a_filiere_futur_id' in fields or 'a_nf_futur' in fields:
                        for i in range(len(objs)):
                            old_administre = Administre.objects.get(a_id_sap=objs[i].a_id_sap)
                            administre = objs[i]
                        
                            dom = administre.a_domaine_futur_id if 'a_domaine_futur_id' in fields else old_administre.a_domaine_futur_id
                            fil = administre.a_filiere_futur_id if 'a_filiere_futur_id' in fields else old_administre.a_filiere_futur_id
                            nf  = administre.a_nf_futur if 'a_nf_futur' in fields else old_administre.a_nf_futur
                            cat = nf2categorie(nf)
                            
                            sv_id = generate_sv_id(dom, fil, cat)
                            sv = SousVivier.objects.filter(sv_id=sv_id)

                            try:
                                sv.exists()
                                administre.sous_vivier_id = sv_id
                                if 'sous_vivier_id' not in fields:
                                    fields += ['sous_vivier_id']
                            except:
                                message = f"La mise à jour a échoué car la sous-vivier d'id : {sv_id} n'est pas en base"
                                self.logger.exception(message)
                                raise APIException(message)

                    Administre.objects.bulk_update(objs, fields=fields)

            return Response({'msg': 'updated'})
        except APIException:
            raise
        except Exception:
            message = 'échec de la mise à jour de N administrés'
            self.logger.exception(message)
            raise APIException(message)

    def partial_update(self, request, pk=None):
        """ La fonction put met à jour un administre.

        :type request: rest_framework.request.Request
        :param request: Request contenant l'administre.

        :type pk: integer
        :param pk: Primary Key de l'administre.

        :return:    - **Response** (*Response*): Reponse contient un message de la réussite de met à jour de l'administre .
        """
        try:
            a = get_object_or_404(Administre.objects, a_id_sap=pk) # Permet de déclancher la vérification des droits au niveau de l'objet
            self.check_object_permissions(self.request, a)
            req_data = request.data

            if 'annee_pam' in req_data:
                annee_pam = req_data.pop('annee_pam')


            # TODO valider les données (il faut d'abord corriger la MAJ des compétences)
            # validator = self.serializer_class(data=req_data, partial=True)
            # validator.is_valid(raise_exception=True)

            Cols = Administre.Cols

            # Copie et complète si besoin le dictionnaire en entrée
            # administre = {**validator.validated_data}
            administre = {**req_data}
            administre[Cols.PK] = pk

            fields = [key for key in administre.keys() if key != Cols.PK] # and key != 'a_liste_id_competences']

            if 'a_domaine_futur' in fields:
                administre['a_domaine_futur_id'] = administre['a_domaine_futur']
                del administre['a_domaine_futur']

            if 'a_filiere_futur' in fields:
                administre['a_filiere_futur_id'] = administre['a_filiere_futur']
                del administre['a_filiere_futur']

            if 'a_domaine_futur' in fields or 'a_filiere_futur' in fields or 'a_nf_futur' in fields:
                old_administre = Administre.objects.get(a_id_sap=pk)
                
                dom = administre['a_domaine_futur_id'] if 'a_domaine_futur' in fields else old_administre.a_domaine_futur_id
                fil = administre['a_filiere_futur_id'] if 'a_filiere_futur' in fields else old_administre.a_filiere_futur_id
                nf  = administre['a_nf_futur'] if 'a_nf_futur' in fields else old_administre.a_nf_futur
                cat = nf2categorie(nf)
                
                sv_id = generate_sv_id(dom, fil, cat)
                sv = SousVivier.objects.filter(sv_id=sv_id)

                try:
                    sv.exists()
                    administre['sous_vivier_id'] = sv_id
                    fields += ['sous_vivier_id']
                except:
                    message = f"La mise à jour a échoué car la sous-vivier d'id : {sv_id} n'est pas en base"
                    self.logger.exception(message)
                    raise APIException(message)

            if Cols.STATUT_PAM in fields:
                old_administre = Administre.objects.get(a_id_sap=pk)
                eip = old_administre.a_eip
                fe_code = old_administre.formation_emploi_id
                avis = administre[Cols.STATUT_PAM]
                old_avis = old_administre.a_statut_pam
                try:
                    categorie = constants.CATEGORIE_BY_NF[old_administre.a_nf.upper()]
                except:
                    erreur = 'la catégorie de l\'administré n\'est pas reconnue dans ogure.'
                    raise APIException(erreur)

            if 'a_liste_id_competences' in fields:
                adm=Administre(pk=pk)
                if administre['a_liste_id_competences']:
                    adm.a_liste_id_competences.set(administre['a_liste_id_competences'].split(','))
                else:
                    adm.a_liste_id_competences.set("")

            elif Cols.STATUT_PAM in fields:
                adm_pam = Administres_Pams.objects.get(id = str(pk)+str(annee_pam))
                adm_pam.a_statut_pam_annee = administre[Cols.STATUT_PAM]
                adm_pam.save()
            
            elif 'notes_pam' in fields:
                adm_pam = Administres_Pams.objects.get(id = str(pk)+str(annee_pam))
                adm_pam.notes_pam = administre['notes_pam']
                adm_pam.save()
            
            elif 'a_ciat_pam' in fields:
                adm_pam = Administres_Pams.objects.get(id = str(pk)+str(annee_pam))
                adm_pam.a_ciat_pam = administre['a_ciat_pam']
                adm_pam.save()

            elif 'a_specifique_pam' in fields:
                adm_pam = Administres_Pams.objects.get(id = str(pk)+str(annee_pam))
                adm_pam.a_specifique_pam = administre['a_specifique_pam']
                adm_pam.save()
            
            elif 'a_liste_depts_souhaites_pam' in fields:
                adm_pam = Administres_Pams.objects.get(id = str(pk)+str(annee_pam))
                adm_pam.a_liste_depts_souhaites_pam = administre['a_liste_depts_souhaites_pam']
                adm_pam.save()    

            elif 'a_liste_zones_geographiques_shm_pam' in fields:
                adm_pam = Administres_Pams.objects.get(id = str(pk)+str(annee_pam))
                adm_pam.a_liste_zones_geographiques_shm_pam = administre['a_liste_zones_geographiques_shm_pam']
                adm_pam.save()
            
            elif 'a_situationfuture_notes_fe' in fields:
                adm_pam = Administres_Pams.objects.get(id = str(pk)+str(annee_pam))
                adm_pam.a_situationfuture_notes_fe = administre['a_situationfuture_notes_fe']
                adm_pam.save()

            else:
                copy = {k: v for k, v in administre.items() if k != 'a_liste_id_competences'}
                adm = Administre(**copy)

                if fields:
                    Administre.objects.bulk_update([adm], fields=fields)

            return Response({'msg': 'updated'})

        except (Http404, APIException):
            raise
        except Exception:
            message = "échec de la mise à jour de l'administré"
            self.logger.exception(message)
            raise APIException(message)


@execution_time_viewset
@query_count_viewset
@class_logger
class PostePAMView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue des postes.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    lookup_value_regex = r"[\w.]+"
    serializer_class = PostesPamsSerializer
    pagination_class = HeavyDataPagination
    filter_backends = [DjangoFilterBackend, RelatedOrderingFilter]
    filterset_class = PostePAMFilter
    # ordering_fields = '__all_related__'
    ordering = ['poste_id','-p_pam']

    # important : mettre à jour quand le serializer change
    def get_queryset(self):
        """Cette fonction permet d'ajouter plus de logique à l'attribut queryset."""

        Cols = Postes_Pams.Cols
        Poste_Cols = Poste.Cols

        queryset = Postes_Pams.objects.select_related(
            Cols.REL_PAM
        ).prefetch_related(
            Prefetch(Cols.O2M_DECISION, queryset=Decision.objects.select_related(f'{Decision.Cols.REL_ADMINISTRE}__{Administre.Cols.REL_GRADE}')),
            Prefetch(Cols.REL_POSTE, queryset=Poste.objects.select_related(Poste_Cols.REL_FONCTION)
                                                           .prefetch_related(
                                                                            Poste_Cols.M2M_COMPETENCES,
                                                                            Poste_Cols.M2M_SOUS_VIVIERS,
                                                                            Prefetch(Poste_Cols.REL_FORMATION_EMPLOI, 
                                                                                        queryset=FormationEmploi.objects.select_related(FormationEmploi.Cols.REL_MERE))))
        )

        sous_viviers_name = self.request.query_params.get('sous_vivier')
        if sous_viviers_name is not None:
            queryset = queryset.filter(poste__sous_viviers__in=sous_viviers_name.split('-'))


        return queryset

    def put(self, request):
        """La fonction put met à jour une liste des postes.

        :type request: rest_framework.request.Request
        :param request: Request contenant la liste des postes.

        :return:    - **Response** (*Response*): Reponse contient un message de la réussite de met à jour des postes .
        """
        data = request.data
        self.logger.debug('------------------------- Modification Multiple de Poste verification ---------------------')
        self.logger.debug('Request data : %s', data)

        if 'pam_id' in data[0]:
            annee_pam = data[0]['pam_id']

        # serialized = self.serializer_class(data=data, many=isinstance(data, list), partial=True)
        # serialized.is_valid(raise_exception=True)

        if isinstance(data, list):  # Update multiple elements
            try:
                fields = [key for key in data[0].keys() if key != 'p_id' ]

      

                if 'p_avis_fe' in fields:
                    fields_to_update_pam = ['p_avis_fe_pam', 'p_avis_pam']
                    # Creation d'une liste qui contient l'id ( p_id + annee_pam) avec l'avis choisi
                    data_statut_pam = [{'id' :str(o['p_id'])+str(annee_pam), 'p_avis_fe_pam' : o['p_avis_fe'], 'p_avis_pam' :  o['p_avis_fe']} for o in data]
                    # Creation des objets Poste Pams
                    objs = [Postes_Pams(**data_item) for data_item in data_statut_pam]
                    # Mise a jour des objets Poste Pams
                    Postes_Pams.objects.bulk_update(objs, fields=fields_to_update_pam)
                
                elif 'p_avis' in fields:
                    fields_to_update_pam = ['p_avis_pam']
                    data_statut_pam = [{'id' :str(o['p_id'])+str(annee_pam), 'p_avis_pam' :  o['p_avis']} for o in data]
                    objs = [Postes_Pams(**data_item) for data_item in data_statut_pam]
                    Postes_Pams.objects.bulk_update(objs, fields=fields_to_update_pam)

                elif 'p_direct_commissionne' in fields:
                    fields_to_update_pam = ['p_direct_commissionne_pam']
                    data_statut_pam = [{'id' :str(o['p_id'])+str(annee_pam), 'p_direct_commissionne_pam' :  o['p_direct_commissionne']} for o in data]
                    objs = [Postes_Pams(**data_item) for data_item in data_statut_pam]
                    Postes_Pams.objects.bulk_update(objs, fields=fields_to_update_pam)
                
                elif 'p_itd_cellule' in fields:
                    sous_viviers = sous_viviers_du_cellule(data[0]['p_itd_cellule'])
                    self.logger.debug('Modification Multiple de p_itd_cellule, les sous_viviers de la cellule sont : %s', sous_viviers)
                    if sous_viviers:
                        objs = [Poste(**data[i]) for i in range(len(data))]
                        Poste.objects.bulk_update(objs, fields=fields)
                        objs_w_comp = [Poste(**without_keys(data[i], ['p_itd_cellule'])).sous_viviers.set(sous_viviers) for i in range(len(data))]
                    else:
                        return JsonResponse({'info': f"La mise à jour n'a pas réussi : la cellule '{data[0]['p_itd_cellule']}' n'a pas de sous viviers enregistrés dans l'application."})

                elif 'competence_id' not in fields:
                    if 'supression' in fields:
                        elem = data[0]
                        Postes_Pams.objects.filter(p_pam_id=elem['pam_id'],poste_id=elem['p_id']).delete()

                    if  'departement' and 'codeFe' and 'lieu' and 'zoneDeDefense' and 'codeFeMere' and 'etr' in fields:
                        elem = data[0]
                        #generate random id : create compteur de poste en sureff
                        no_sureff = str(Postes_Pams.objects.filter(p_pam_id=str(annee_pam),id__contains='_').count() + 1)

                        pam_actuel = PAM.objects.get(pam_statut='PAM en cours').pam_id

                        #on crée l'instance de formation d'emploi
                        fe = FormationEmploi.objects.get(fe_code=elem['codeFe'])
                        fe_poste_duplique = fe
                        fe_poste_duplique.zone_defense =elem['zoneDeDefense']
                        fe_poste_duplique.fe_garnison_lieu = elem['lieu']
                        fe_poste_duplique.save()
                        #créer une copie d'instance de poste qui existe déjà en base:
                        poste = Poste.objects.get(p_id=elem['p_id'])
                        poste_duplique = poste
                        poste_duplique.p_id = poste.p_id + '_'+ no_sureff
                        poste_duplique.p_dep= elem['departement']
                        poste_duplique.p_fonction = elem['etr']
                        poste_duplique.formation_emploi = fe_poste_duplique
                        poste_duplique.save()

                        if elem['pam_id'] == pam_actuel:
                            poste_sureffA = Postes_Pams.objects.get(id=elem['p_id'] + elem['pam_id']) 
                            poste_sureffA.id = elem['p_id']+ '_'+ no_sureff + elem['pam_id']
                            poste_sureffA.poste_id = elem['p_id']+ '_'+ no_sureff
                            poste_sureffA.poste = poste_duplique
                            poste_sureffA.info_reo = f"SUREFF {str(annee_pam)}"
                            poste_sureffA.save()

                            poste_sureffA1 = Postes_Pams.objects.get(id=elem['p_id'] + str(int(elem['pam_id'])+1)) 
                            poste_sureffA1.id = elem['p_id']+ '_'+ no_sureff +str(int(elem['pam_id'])+1)
                            poste_sureffA1.poste_id = elem['p_id']+ '_'+ no_sureff
                            poste_sureffA1.poste = poste_duplique
                            poste_sureffA1.info_reo = f"SUREFF {str(annee_pam)}"
                            poste_sureffA1.save()
                        
                        else:
                            poste_sureffA1 = Postes_Pams.objects.get(id=elem['p_id'] + elem['pam_id']) 
                            poste_sureffA1.id = elem['p_id']+ '_'+ no_sureff + elem['pam_id']
                            poste_sureffA1.poste_id = elem['p_id']+ '_'+ no_sureff
                            poste_sureffA1.poste = poste_duplique
                            poste_sureffA1.info_reo = f"SUREFF {str(annee_pam)}"
                            poste_sureffA1.save()                       

                    else:
                        objs = [Poste(**data[i]) for i in range(len(data))]
                        Poste.objects.bulk_update(objs, fields=fields)       
                else:
                    objs_w_comp = [Poste(**without_keys(data[i],['competence_id'])).competences.set(data[i]['competence_id'].split(',')) for i in range(len(data))]
            
            except:
                raise Exception("bulk update error")


        return Response({'msg': 'updated'})

    def partial_update(self, request, pk=None):
        """ La fonction put met à jour un poste.

        :type request: rest_framework.request.Request
        :param request: Request contenant le poste.

        :type pk: integer
        :param pk: Primary Key du poste.
        :return:    - **Response** (*Response*): Reponse contient un message de la réussite de met à jour du poste .
        """
        p = get_object_or_404(Poste.objects, p_id=pk) # Permet de déclancher la vérification des droits au niveau de l'objet
        self.check_object_permissions(self.request, p)
        poste = request.data
        self.logger.debug('------------------------- Modification Unitaire de Poste verification ---------------------')
        self.logger.debug('Request data : %s', poste)

        if 'annee_pam' in poste:
            annee_pam = poste.pop('annee_pam')

        fields_not_updated = ['p_id', 'p_poids_filiere', 'p_poids_nf', 'p_poids_competences']
        fields = [key for key in poste.keys() if key not in fields_not_updated]
        poste['p_id'] = pk



        if 'competence' in fields:
            pos=Poste(pk=pk)
            if poste['competence']:
                pos.competences.set(poste['competence'].split(','))
            else:
                pos.competences.set("")
        
        elif 'sous_vivier_id' in fields:
            pos=Poste(pk=pk)
            pos.sous_viviers.add(poste['sous_vivier_id'])

        elif 'p_avis_fe_pam' in fields:
            poste_pam = Postes_Pams.objects.get(id = str(pk)+str(annee_pam))
            poste_pam.p_avis_pam = poste['p_avis_fe_pam']
            poste_pam.p_avis_fe_pam = poste['p_avis_fe_pam']
            poste_pam.save()

        elif 'p_avis_pam' in fields:
            poste_pam = Postes_Pams.objects.get(id = str(pk)+str(annee_pam))
            poste_pam.p_avis_pam = poste['p_avis_pam']
            poste_pam.save()
        
        elif 'p_direct_commissionne_pam' in fields:
            poste_pam = Postes_Pams.objects.get(id = str(pk)+str(annee_pam))
            poste_pam.p_direct_commissionne_pam = poste['p_direct_commissionne_pam']
            poste_pam.save()

        elif 'p_priorisation_pcp_pam' in fields:
            poste_pam = Postes_Pams.objects.get(id = str(pk)+str(annee_pam))
            poste_pam.p_priorisation_pcp_pam = poste['p_priorisation_pcp_pam']
            poste_pam.save()

        elif 'p_notes_gestionnaire_pam' in fields:
            poste_pam = Postes_Pams.objects.get(id = str(pk)+str(annee_pam))
            poste_pam.p_notes_gestionnaire_pam = poste['p_notes_gestionnaire_pam']
            poste_pam.save()

        else:
            copy = {k: v for k, v in poste.items() if k != 'competence'}
            pos = Poste(**copy)

            # l'orde des if est tres important
            if 'p_specifique' in fields and poste['p_specifique'] == SpecifiqueChoices.ITD:
                pos.p_itd_affecte = False
                fields += ['p_itd_affecte']

            elif 'p_itd_cellule' in fields:
                sous_viviers = sous_viviers_du_cellule(poste['p_itd_cellule'])
                if sous_viviers:
                    pos.sous_viviers.set(sous_viviers)
                    pos.p_itd_affecte = True
                    fields += ['p_itd_affecte']
                else :
                    self.logger.debug(f"La mise à jour n'a pas réussi, La cellule '{poste['p_itd_cellule']}' n'a pas de sous viviers enregistrés dans l'application.")
                    return JsonResponse({'info': f"La mise à jour n'a pas réussi : la cellule '{poste['p_itd_cellule']}' n'a pas de sous viviers enregistrés dans l'application."})

            if fields:
                Poste.objects.bulk_update([pos], fields=fields)



        return Response({'msg': 'updated'})

@execution_time_viewset
@query_count_viewset
@class_logger
class PosteView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue des postes.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    lookup_value_regex = r"[\w.]+"
    serializer_class = PosteSerializer
    pagination_class = HeavyDataPagination
    filter_backends = [DjangoFilterBackend, RelatedOrderingFilter]
    filterset_class = PosteFilter
    ordering_fields = '__all_related__'
    ordering = ['p_id']

    # important : mettre à jour quand le serializer change
    def get_queryset(self):
        """Cette fonction permet d'ajouter plus de logique à l'attribut queryset."""

        Cols = Poste.Cols

        queryset = Poste.objects.select_related(
            Cols.REL_FONCTION
        ).prefetch_related(
            Cols.M2M_COMPETENCES,
            Cols.M2M_SOUS_VIVIERS,
            Prefetch(Cols.M2M_PAM, queryset=Postes_Pams.objects.select_related(Postes_Pams.Cols.O2M_DECISION)),
            Prefetch(Cols.O2M_DECISION, queryset=Decision.objects.select_related(f'{Decision.Cols.REL_ADMINISTRE}__{Administre.Cols.REL_GRADE}')),
            Prefetch(Cols.REL_FORMATION_EMPLOI, queryset=FormationEmploi.objects.select_related(FormationEmploi.Cols.REL_MERE)),
        ).annotate(
            p_nb_prepositionne=Sum(
                Case(When(decisions__de_decision=DecisionChoices.PREPOSITIONNE, then=1), default=0,
                     output_field=IntegerField())),
            p_nb_positionne=Sum(Case(When(decisions__de_decision=DecisionChoices.POSITIONNE, then=1), default=0,
                                     output_field=IntegerField())),
            p_nb_omi_en_cours=Sum(Case(When(decisions__de_decision=DecisionChoices.OMI_EN_COURS, then=1), default=0,
                                       output_field=IntegerField())),
            p_nb_omi_active=Sum(Case(When(decisions__de_decision=DecisionChoices.OMI_ACTIVE, then=1), default=0,
                                     output_field=IntegerField())),
        )

        sous_viviers_name = self.request.query_params.get('sous_vivier')
        if sous_viviers_name is not None:
            queryset = queryset.filter(sous_viviers=sous_viviers_name)


        return queryset


    def put(self, request):
        """La fonction put met à jour une liste des postes.

        :type request: rest_framework.request.Request
        :param request: Request contenant la liste des postes.

        :return:    - **Response** (*Response*): Reponse contient un message de la réussite de met à jour des postes .
        """
        data = request.data
        self.logger.debug('------------------------- Modification Multiple de Poste verification ---------------------')
        self.logger.debug('Request data : %s', data)
        serialized = self.serializer_class(data=data, many=isinstance(data, list), partial=True)
        serialized.is_valid(raise_exception=True)
        if isinstance(data, list):  # Update multiple elements
            try:
                fields = [key for key in data[0].keys() if key != 'p_id' ]

                if 'p_itd_cellule' in fields:
                    sous_viviers = sous_viviers_du_cellule(data[0]['p_itd_cellule'])
                    self.logger.debug('Modification Multiple de p_itd_cellule, les sous_viviers de la cellule sont : %s', sous_viviers)
                    if sous_viviers:
                        objs = [Poste(**data[i]) for i in range(len(data))]
                        Poste.objects.bulk_update(objs, fields=fields)
                        objs_w_comp = [Poste(**without_keys(data[i],['p_itd_cellule'])).sous_viviers.set(sous_viviers) for i in range(len(data))]
                    else :
                        return JsonResponse({'info': f"La mise à jour n'a pas réussi, La cellule '{data[0]['p_itd_cellule']}' n'a pas de sous viviers enregistrés dans l'application."})


                elif 'competence_id' not in fields:
                    objs = [Poste(**data[i]) for i in range(len(data))]
                    Poste.objects.bulk_update(objs, fields=fields)
                else:
                    objs_w_comp = [Poste(**without_keys(data[i],['competence_id'])).competences.set(data[i]['competence_id'].split(',')) for i in range(len(data))]
            except:
                raise Exception("bulk update error")
        else:  # Update one element
            a_id_sap = Poste.objects.update(serialized.validated_data)
            db_instance = Poste.objects.filter(
                a_id_sap=a_id_sap).first()
            db_instance.tag.clear()

        return Response({'msg': 'updated'})

    def partial_update(self, request, pk=None):
        """ La fonction put met à jour un poste.

        :type request: rest_framework.request.Request
        :param request: Request contenant le poste.

        :type pk: integer
        :param pk: Primary Key du poste.
        :return:    - **Response** (*Response*): Reponse contient un message de la réussite de met à jour du poste .
        """
        p = get_object_or_404(Poste.objects, p_id=pk) # Permet de déclancher la vérification des droits au niveau de l'objet
        self.check_object_permissions(self.request, p)
        poste = request.data
        self.logger.debug('------------------------- Modification Unitaire de Poste verification ---------------------')
        self.logger.debug('Request data : %s', poste)
        fields_not_updated = ['p_id', 'p_poids_filiere', 'p_poids_nf', 'p_poids_competences']
        fields = [key for key in poste.keys() if key not in fields_not_updated]
        poste['p_id'] = pk

        if 'competence' in fields:
            pos=Poste(pk=pk)
            if poste['competence']:
                pos.competences.set(poste['competence'].split(','))
            else:
                pos.competences.set("")
        elif 'sous_vivier_id' in fields:
            pos=Poste(pk=pk)
            pos.sous_viviers.add(poste['sous_vivier_id'])
        else:
            copy = { k: v for k, v in poste.items() if k != 'competence'}
            pos = Poste(**copy)

            if 'p_avis_fe' in fields:
                pos.p_avis = poste['p_avis_fe']
                fields += ['p_avis']

            # l'orde des if est tres important
            if 'p_specifique' in fields and poste['p_specifique'] == SpecifiqueChoices.ITD:
                pos.p_itd_affecte = False
                fields += ['p_itd_affecte']


            elif 'p_itd_cellule' in fields:
                sous_viviers = sous_viviers_du_cellule(poste['p_itd_cellule'])
                if sous_viviers:
                    pos.sous_viviers.set(sous_viviers)
                    pos.p_itd_affecte = True
                    fields += ['p_itd_affecte']
                else :
                    self.logger.debug(f"La mise à jour n'a pas réussi, La cellule '{poste['p_itd_cellule']}' n'a pas de sous viviers enregistrés dans l'application.")
                    return JsonResponse({'info': f"La mise à jour n'a pas réussi, La cellule '{poste['p_itd_cellule']}' n'a pas de sous viviers enregistrés dans l'application."})

            if fields:
                Poste.objects.bulk_update([pos], fields=fields)


        return Response({'msg': 'updated'})


@execution_time_viewset
@query_count_viewset
class ListesPreferencesView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue des ListesPreferences.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    serializer_class = PreferencesListe
    queryset = PreferencesListe.objects.all()


@execution_time_viewset
@query_count_viewset
class MarqueView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue des marques.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    serializer_class = MarqueSerializer
    queryset = Marque.objects.all()


@execution_time_viewset
@query_count_viewset
class MarquesGroupeView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue des marquegroupes.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    serializer_class = MarquesGroupeSerializer
    queryset = MarquesGroupe.objects.all()


@execution_time_viewset
@query_count_viewset
class FmobView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue des Fmob.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    serializer_class = FmobSerializer
    queryset = FMOB.objects.all()


@execution_time_viewset
@query_count_viewset
class FiliereView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue des filieres.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    serializer_class = FiliereSerializer
    queryset = Filiere.objects.all()


@execution_time_viewset
@query_count_viewset
class DomaineView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue des domaines.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    serializer_class = DomaineSerializer
    queryset = Domaine.objects.all()


@execution_time_viewset
@query_count_viewset
class SousVivierAssociationView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue du SousVivierAssociation.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    serializer_class = SousVivierAssociationSerializer
    queryset = SousVivierAssociation.objects.all()


@execution_time_viewset
@query_count_viewset
class PcpFeGroupeView(viewsets.ModelViewSet):
    """
    Cette classe est dédiée au vue du PcpFeGroupe.
    """
    permission_classes = [IsAuthenticated, GestionnairePermission]
    serializer_class = PcpFeGroupeSerializer
    queryset = PcpFeGroupe.objects.all()
