"""
Ce module contient les Utilitaires d'extraction des données à partir des fichiers
"""
import datetime
import logging
from multiprocessing.connection import answer_challenge
import time
from enum import Enum, auto
from typing import Callable, Dict

import numpy as np
import pandas as pd
from django.core.files.base import File
from django.db.models import Q

from .models import FMOB, Administre
from .models import AvisPosteChoices as AvisPoste
from .models import (Diplome, Domaine, Filiere, Fonction, FormationEmploi,
                     Garnison, Grade, Marque, MarquesGroupe, Notation, Poste,
                     PreferencesListe, Administres_Pams, Postes_Pams)
from .models import StatutPamChoices as StatutPam
from .utils.alimentation import BOCols, FmobCols, InseeCols, ReoCols
from .utils.alimentation_decorators import (data_perf_logger_factory,
                                            get_data_logger)
from .utils.decorators import execution_time

logger = get_data_logger(__name__)

pd.options.mode.chained_assignment = None  # default='warn'

# valeur arbitraire pour symboliser np.nan. En effet un ensemble d'opérations comme
#   fillna(np.nan).astype(str).replace([np.nan, 'nan'], [None, None]))
# n'est pas pratique car 'nan' pourrait être une valeur du fichier (qui ne représente pas None)
APP_NAN = '__ogure_nan__'

# expression régulière pour représenter un caractère alphanumérique
REGEX_NON_ALPHANUM = '[^a-zA-Z0-9]'


class FileTypes(str, Enum):
    """ enum pour les types de fichiers chargés par l'utilisateur """

    # militaires à supprimer
    ADM_SUPPR = auto()

    # données BO
    BO = auto()

    # commentaires
    COMMENTS = auto()

    # diplômes
    DIPLOME = auto()

    # filières et domaines
    DOM_FIL = auto()

    # FMOB
    FMOB_FEMP = auto()

    # FMOB PAM A+1
    FMOB_FEMP_PAM_SUIVANT = auto()

    # FUD
    FUD = auto()

    # INSEE
    INSEE = auto()

    # référentiel FE
    REF_FE = auto()

    # référentiel de zones géographiques SHM
    REF_GEO = auto()

    # référentiel de gestionnaires
    REF_GEST = auto()

    # référentiel organique
    REF_ORG = auto()

    # référentiel de sous-viviers/filières
    REF_SV_FIL = auto()

    # REO
    REO = auto()

    # REO PAM A +1
    REO_PAM_SUIVANT = auto()

    # REO (OCV)
    REO_OCV = auto()


    def __repr__(self):
        return self.__str__()



class DataFrameTypes(Enum):
    """ enum pour les data frames issus de lectures de fichiers """

    # nom        (type de fichier,      libellé)
    ADM_SUPPR  = (FileTypes.ADM_SUPPR,  'militaires à supprimer')
    BO         = (FileTypes.BO,         'BO')
    COMMENTS   = (FileTypes.COMMENTS,   'commentaires')
    DIPLOME    = (FileTypes.DIPLOME,    'diplômes')
    DOM_FIL    = (FileTypes.DOM_FIL,    'filières / domaines')
    FEMP       = (FileTypes.FMOB_FEMP,  'FEMP')
    FMOB       = (FileTypes.FMOB_FEMP,  'FMOB')
    FUD        = (FileTypes.FUD,        'FUD')
    INSEE      = (FileTypes.INSEE,      'mapping INSEE')
    REF_FE     = (FileTypes.REF_FE,     'référentiel FE')
    REF_GEO    = (FileTypes.REF_GEO,    'référentiel de zones géographiques SHM')
    REF_GEST   = (FileTypes.REF_GEST,   'référentiel de gestionnaires')
    REF_ORG    = (FileTypes.REF_ORG,    'référentiel organique')
    REF_SV_FIL = (FileTypes.REF_SV_FIL, 'référentiel de sous-viviers/filières')
    REO        = (FileTypes.REO,        'REO')
    REO_OCV    = (FileTypes.REO_OCV,    'postes OCV')

    #Dataframe PAM A + 1
    FEMP_PAM_SUIVANT = (FileTypes.FMOB_FEMP_PAM_SUIVANT,  'FEMP_PAM_SUIVANT')
    FMOB_PAM_SUIVANT = (FileTypes.FMOB_FEMP_PAM_SUIVANT,  'FMOB_PAM_SUIVANT')
    REO_PAM_SUIVANT  = (FileTypes.REO_PAM_SUIVANT,        'REO_PAM_SUIVANT')


    def __repr__(self):
        return self.__str__()



class Files():
    """
    Regroupe les constantes de fichiers
    TODO combiner avec FileTypes ?
    """

    class AdmSuppr():
        """ Constantes pour les colonnes du fichier de militaires à supprimer """
        ID_SAP = 'Matricule SAP'                                   # A

    class DonneesBo():
        """ Constantes pour les colonnes du fichier de données BO """

        CREDO_FE = "CREDO FE act"                                  # X
        DATE_ARRIVEE_FE = "Date arrivée FE"
        DATE_DEBUT_GRADE = "Grade act DD"
        DATE_ENTREE_SERVICE = "Entrée en Service"
        DATE_FONCTION_1 = "Fonction -1 DD"
        DATE_FONCTION_2 = "Fonction -2 DD"
        DATE_FONCTION_3 = "Fonction -3 DD"
        DATE_FONCTION_4 = "Fonction -4 DD"
        DATE_FONCTION_5 = "Fonction -5 DD"
        DATE_FONCTION_6 = "Fonction -6 DD"
        DATE_FONCTION_7 = "Fonction -7 DD"
        DATE_FONCTION_8 = "Fonction -8 DD"
        DATE_FONCTION_9 = "Fonction -9 DD"
        DATE_LIEN_SERVICE = "Lien au service DF"
        DATE_NAISSANCE = "Naissance"
        DATE_POSITION_STATUAIRE = "Date Position statutaire"
        DATE_STATUT_CONCERTO = "Situation administrative act DD"   # DW
        DATE_STATUT_CONCERTO_FUTUR = "Date Position statu future"  # DY
        ID_SAP = 'Matricule SAP'                                   # A
        STATUT_CONCERTO = "Situation admi actuelle"                # DX
        STATUT_CONCERTO_FUTUR = "Position statutaire future"       # DZ


    class FMOB():
        """ Constantes pour les colonnes du fichier de FMOB """

        ID_SAP = 'Matricule SAP'


    class REO():
        """ Constantes pour les colonnes du fichier de FMOB """

        CODE_POSTAL = 'Code Postal /OB G'




def open_excel(io, sheetname, engine=None, header=0, usecols=None, skiprows=0, dtype=None, converters=None):
    """
    Ouvre un onglet spécifique d'un fichier excel

    :type io: chaine de caractères
    :param io: chemin du fichier excel

    :type sheetname: chaine de caractères
    :param sheetname: nom de l'onglet à ouvrir

    :type header: entier
    :param header: indice de la ligne comportant le nom des colonnes

    :return:    - **df** (*DataFrame*): tableau de l'onglet à ouvrir.
    """

    df = pd.read_excel(io, sheet_name=sheetname, engine=engine, header=header, usecols=usecols, skiprows=skiprows, dtype=dtype, converters=converters)

    return df


def read_files_by_type(files_by_type: Dict[FileTypes, File]) -> Dict[DataFrameTypes, pd.DataFrame]:
    """
    Lit les fichiers donnés et renvoie des data frames.

    :param files_by_type: fichiers indexés par type
    :type  files_by_type: Dict[FileTypes, File]

    :return: data frame indexés par type
    :rtype:  Dict[DataFrameTypes, pd.DataFrame]
    """
    df_by_type = {}

    # FIXME: (front) Forcer l'engine en fonction de l'extension du fichier
    logger.info('------------------ Start File Processing -----------------------')

    @execution_time(logger_factory=data_perf_logger_factory)
    def read_file(label: str, file: File, read_func: Callable[[None], pd.DataFrame]) -> pd.DataFrame:
        """ Utilise la fonction pour lire le fichier """
        logger.debug('Lecture du fichier %s', label)
        try:
            df = read_func(file)
            logger.info('Lecture du fichier %s ------> Succès', label.ljust(40))
        except BaseException:
            logger.error('Lecture du fichier %s ------>  Echec', label.ljust(40))
            raise
        return df

    def read_file_add_df(df_type: DataFrameTypes, read_func: Callable[[None], pd.DataFrame]) -> None:
        """
        Vérifie qu'il existe un fichier pour le type de DataFrame donné.
        Si oui, utilise la fonction pour le lire et ajoute le DataFrame au résultat.

        :param df_type: type de DataFrame
        :type  df_type: DataFrameTypes

        :param read_func: fonction de lecture du fichier
        :type  read_func: Callable[[None], pd.DataFrame]
        """
        file_type = df_type.value[0]
        file = files_by_type.get(file_type)
        if file:
            df_by_type[df_type] = read_file(df_type.value[1], file, read_func)

    read_file_add_df(DataFrameTypes.ADM_SUPPR, lambda f: open_excel(f, sheetname=0, engine='openpyxl'))
    read_file_add_df(DataFrameTypes.BO, lambda f: open_excel(f, sheetname=0, engine='openpyxl', usecols=BOCols.columns(), converters=BOCols.converters()))
    read_file_add_df(DataFrameTypes.COMMENTS, lambda f: open_excel(f, sheetname=0, engine='openpyxl'))
    read_file_add_df(DataFrameTypes.DIPLOME, lambda f: open_excel(f, sheetname=0, engine='openpyxl'))
    read_file_add_df(DataFrameTypes.DOM_FIL, lambda f: open_excel(f, sheetname='Feuil2', engine='openpyxl'))
    read_file_add_df(DataFrameTypes.FEMP, lambda f: open_excel(f, sheetname='FEMP', engine='openpyxl', converters=FmobCols.converters()))
    read_file_add_df(DataFrameTypes.FMOB, lambda f: open_excel(f, sheetname='FMOB', engine='openpyxl', converters=FmobCols.converters()))
    read_file_add_df(DataFrameTypes.FEMP_PAM_SUIVANT, lambda f: open_excel(f, sheetname='FEMP', engine='openpyxl', converters=FmobCols.converters()))
    read_file_add_df(DataFrameTypes.FMOB_PAM_SUIVANT, lambda f: open_excel(f, sheetname='FMOB', engine='openpyxl', converters=FmobCols.converters()))
    read_file_add_df(DataFrameTypes.FUD, lambda f: open_excel(f, sheetname=0, engine='openpyxl', skiprows=1))
    read_file_add_df(DataFrameTypes.INSEE, lambda f: open_excel(f, sheetname=0, engine='openpyxl', usecols=InseeCols.columns(), converters=InseeCols.converters()))
    read_file_add_df(DataFrameTypes.REF_FE, lambda f: open_excel(f, sheetname=0, engine='openpyxl'))
    read_file_add_df(DataFrameTypes.REF_GEO, lambda f: open_excel(f, sheetname=0, engine='openpyxl'))
    read_file_add_df(DataFrameTypes.REF_GEST, lambda f: open_excel(f, sheetname=0, engine='openpyxl'))
    read_file_add_df(DataFrameTypes.REF_ORG, lambda f: open_excel(f, sheetname=0, engine='openpyxl'))
    read_file_add_df(DataFrameTypes.REF_SV_FIL, lambda f: open_excel(f, sheetname=0, engine='openpyxl'))
    read_file_add_df(DataFrameTypes.REO, lambda f: open_excel(f, sheetname=0, engine='openpyxl', converters=ReoCols.converters()))
    read_file_add_df(DataFrameTypes.REO_PAM_SUIVANT, lambda f: open_excel(f, sheetname=0, engine='openpyxl', converters=ReoCols.converters()))
    read_file_add_df(DataFrameTypes.REO_OCV, lambda f: open_excel(f, sheetname=0, engine='openpyxl'))


    logger.info('------------------- End File Processing ------------------------')
    return df_by_type

#Fonction pour la table PAM
def to_table_pam(date):
    """
    Construit la table PAM qui servira à récuperer l'année du PAM et piloter l'affichage des données en fonction du bon pam pour chaque tables
    Cloture le PAM en le sauvegardant dans un fichier excel
    """
    annee_pam = str(date.year)
    annee_pam_suivant = str(date.year + 1)

    pam=pd.DataFrame(columns = ['pam_id','pam_date', 'pam_libelle','pam_statut'])
    pam_id = annee_pam
    pam_id_suivant = annee_pam_suivant
    pam_libelle = f"PAM de l'année {annee_pam}"
    pam_libelle_suivant = f"PAM de l'année {annee_pam_suivant}"
    pam_date = date
    pam_date_suivant = pam_date
    pam_statut = "PAM en cours"
    pam_statut_suivant = "PAM A+1"

    pam['pam_id'] = [pam_id,pam_id_suivant]
    pam['pam_date'] = [pam_date,pam_date_suivant]
    pam['pam_libelle'] = [pam_libelle,pam_libelle_suivant]
    pam['pam_statut'] = [pam_statut,pam_statut_suivant]

    return pam

# Fonction pour la table Domaine
def to_table_domaines(df_domaines):
    """
    Transformation de la table domaines. Sélection et renommage des champs.

    :param df_domaines: tableau domaines
    :type  df_domaines: class:`pandas.DataFrame`

    :return: tableau transformé contenant les information de domaines
    :rtype:  class:`pandas.DataFrame`
    """
    col_pk = Domaine.Cols.PK
    col_mapping = {'DOMAINE': col_pk}
    df = df_domaines[col_mapping.keys()]
    df = (df.rename(columns=col_mapping)
            .dropna(subset=[col_pk])
            .drop_duplicates(subset=[col_pk]))
    df[col_pk] = df[col_pk].str.replace(REGEX_NON_ALPHANUM, '_', regex=True)
    return df


# Fonction pour la table Filières
def to_table_filieres(df_filiere):
    """
    Transformation de la table filieres. Sélection et renommage des champs.


    :type df_filiere: dataframe
    :param df_filiere: tableau filieres

    :return:    - **filieres** (*DataFrame*): Tableau transformé contenant les information de filieres.

    """
    col_fil = ['DOMAINE', 'FILIERE']
    filieres = df_filiere[col_fil]
    filieres.rename(columns={'DOMAINE': 'domaine_id', 'FILIERE': 'f_code'}, inplace=True)
    filieres = (filieres.fillna(APP_NAN)
                        .replace({APP_NAN: None}))

    return filieres


# Fonction pour la table Garnison
def to_table_garnisons(df_insee, df_donneebo):
    """Création de la table Garnison à partir des tables insee et donnee bo. Sélection et renommage des champs.


    :type df_insee: dataframe
    :param df_insee: table insee

    :type df_donneebo: dataframe
    :param df_donneebo: table donnees BO


    :return:    - **garnisons** (*DataFrame*): Tableau créé contenant les information de garnisons.
.

    """
    garnisons = df_donneebo.drop_duplicates(subset="Garnison act", keep='first')
    garnisons = garnisons.merge(df_insee, how="left", left_on="Matricule SAP", right_on="Matricule SAP")
    garnisons = garnisons[['CODE INSEE', 'Garnison act', 'CODE POSTAL']]
    garnisons.drop_duplicates(subset="CODE INSEE", keep='first', inplace=True)
    garnisons.columns = ["gar_id", "gar_lieu", "gar_code_postal"]
    garnisons.reset_index(drop=True, inplace=True)

    return garnisons


# Fonction pour la table affectation
def to_table_affectation(df_donneebo):
    """Création de la table Affectation à partir de la table donnee bo. Sélection et renommage des champs.

    :type df_donneebo: dataframe
    :param df_donneebo: table donnees BO

    :type to_table_administre_df: dataframe
    :param to_table_administre_df: le retour de la fonction to_table_administre


    :return:    - **all_affectation** (*DataFrame*): Tableau créé contenant les information des affectations.
.

    """
    all_affec = []
    administres_fields = ('a_id_sap',)
    administres = pd.DataFrame.from_records(Administre.objects.all().values(*administres_fields))
    all_donnees = df_donneebo.merge(administres, how='inner', left_on='Matricule SAP', right_on='a_id_sap')
    for i in range(1, 10):
        frames = ['Matricule SAP', 'Affectation -' + str(i) + ' L', 'Affectation -' + str(i) + ' DD']
        df_temp = all_donnees[frames]
        df_temp.columns = ['Matricule SAP', 'Affectation L', 'Affectation D']
        df_temp.dropna(subset=['Affectation L'], inplace=True)
        all_affec.append(df_temp)

    all_affectation = pd.concat(all_affec, ignore_index=True)
    all_affectation.drop_duplicates(inplace=True, ignore_index=True)

    return all_affectation


# Fonction pour la table affectation
def to_table_diplome(df_diplome):
    """Création de la table Affectation à partir de la table donnee bo. Sélection et renommage des champs.

    :type df_diplome: dataframe
    :param df_diplome: table des diplomes


    :return:    - **all_diplome** (*DataFrame*): Tableau créé contenant les information des diplomes.
.

    """
    all_dip = []
    administres_fields = ('a_id_sap',)
    administres = pd.DataFrame.from_records(Administre.objects.all().values(*administres_fields))
    all_diplomes = df_diplome.merge(administres, how='inner', left_on='Matricule SAP', right_on='a_id_sap')
    for i in range(1, 11):
        frames = ['Matricule SAP', 'Diplôme militaire -' + str(i) + ' L', 'Diplôme militaire -' + str(i) + ' D', 'Diplôme militaire -' + str(i) + ' note',
                  'Diplôme militaire -' + str(i) + ' niveau']
        df_temp = all_diplomes[frames]
        df_temp.columns = ['Matricule SAP', 'Diplôme militaire L', 'Diplôme militaire D', 'Diplôme militaire note', 'Diplôme militaire niveau']
        df_temp.dropna(subset=['Diplôme militaire L'], inplace=True)
        all_dip.append(df_temp)

    all_diplome = pd.concat(all_dip, ignore_index=True)
    all_diplome.drop_duplicates(inplace=True, ignore_index=True)
    all_diplome = all_diplome.groupby(['Matricule SAP', 'Diplôme militaire L', 'Diplôme militaire D'])['Diplôme militaire note'].agg('max').reset_index()

    return all_diplome


def to_table_fud(df_fud):
    """Création de la table Affectation à partir de la table donnee bo. Sélection et renommage des champs.

    :type df_fud: dataframe
    :param df_fud: table fud

    :type to_table_administre_df: dataframe
    :param to_table_administre_df: table administre de la fonction to_table_administre dans extraction

    :return:    - **all_fuds** (*DataFrame*): Tableau créé contenant les information des FUDs.
.

    """
    all_fud = []
    administres_fields = ('a_id_sap',)
    administres = pd.DataFrame.from_records(Administre.objects.all().values(*administres_fields))
    all_fuds = df_fud.merge(administres, how='inner', left_on='Matricule SAP', right_on='a_id_sap')
    for i in range(1, 11):
        frames = ['Matricule SAP', 'FUD -' + str(i) + ' L', 'FUD -' + str(i) + ' DD', 'FUD -' + str(i) + ' DF']
        df_temp = all_fuds[frames]
        df_temp.columns = ['Matricule SAP', 'FUD L', 'FUD DD', 'FUD DF']
        df_temp.dropna(subset=['FUD L'], inplace=True)
        all_fud.append(df_temp)

    all_fud = pd.concat(all_fud, ignore_index=True)
    all_fud.drop_duplicates(inplace=True, ignore_index=True)

    return all_fud


def to_table_administre_notation(df_donne_bo):
    """Création de la table Notation à partir de la table donnee bo. Sélection et renommage des champs.

    :type df_donne_bo: dataframe
    :param df_donne_bo: table donnee bo

    :type to_table_administre_df: dataframe
    :param to_table_administre_df: table administre de la fonction to_table_administre dans extraction

    :return:    - **all_fuds** (*DataFrame*): Tableau créé contenant les information des FUDs.
.

    """

    donne_bo = df_donne_bo.copy()
    donne_bo.drop_duplicates(['Matricule SAP'], inplace=True, ignore_index=True)
    all_notation = []
    frames = ['Matricule SAP', 'Année notation A', 'IRIS / RAC retenu A', 'NR/NGC cumulé A',
              'QSR A', 'Apt resp / Emp sup A', 'Potentiel responsabilités catégorie sup A',
              'Age en années (au 31/12)']
    df_temp = donne_bo[frames]
    df_temp.columns = ['Matricule SAP', 'no_annne_de_notation', 'no_rac_ou_iris_cumule', 'no_nr_ou_iris', 'no_rf_qsr', 'no_aptitude_emploie_sup',
                       'no_potentiel_responsabilite_sup', 'no_age_annees']
    all_notation.append(df_temp)
    for i in range(1, 6):
        frames = ['Matricule SAP', 'Année notation A-' + str(i), 'IRIS / RAC retenu A-' + str(i), 'NR/NGC cumulé A-' + str(i),
                  'QSR A-' + str(i), 'Apt resp / Emp sup A-' + str(i), 'Potentiel responsabilités catégorie sup A-' + str(i),
                  'Age en années (au 31/12)']
        df_temp = donne_bo[frames]
        df_temp.columns = ['Matricule SAP', 'no_annne_de_notation', 'no_rac_ou_iris_cumule', 'no_nr_ou_iris', 'no_rf_qsr', 'no_aptitude_emploie_sup',
                           'no_potentiel_responsabilite_sup', 'no_age_annees']
        df_temp['no_age_annees'] = df_temp['no_age_annees'].astype(int) - i
        all_notation.append(df_temp)

    all_notations = pd.concat(all_notation, ignore_index=True)
    all_notations.drop_duplicates(inplace=True, ignore_index=True)
    all_notations = (all_notations.fillna(APP_NAN)
                                  .replace({APP_NAN: None}))

    administres_fields = ('a_id_sap',)
    administres = pd.DataFrame.from_records(Administre.objects.all().values(*administres_fields))
    all_notations = all_notations.merge(administres, how='inner', left_on='Matricule SAP', right_on='a_id_sap')
    all_notations = all_notations.reset_index(drop= True)

    all_notations = all_notations.fillna(np.NAN).replace('None',np.NAN)
    all_notations = all_notations.rename(columns = {'Matricule SAP':'administre_id'})
    all_notations['key'] = all_notations['administre_id'].astype(float).astype(str) + '_' + all_notations['no_age_annees'].astype(float).astype(str)

    return all_notations


# Fonction d'extraction du fichier contenant les militaires à supprimer
def to_table_suppression_administres(df_adm_suppr):
    """
    Création du dataframe adm_suppr.

    :return:    - **adm_suppr** (*DataFrame*): Tableau créé contenant les information des administrés à supprimer.
    """
    df_adm_suppr = (df_adm_suppr.drop_duplicates(subset=[Files.AdmSuppr.ID_SAP], keep='first')
                                .dropna(subset=[Files.AdmSuppr.ID_SAP]))
    administres = pd.DataFrame.from_records(Administre.objects.all().values('a_id_sap'))
    all_adm_suppr = df_adm_suppr.merge(administres, how='inner', left_on=Files.AdmSuppr.ID_SAP, right_on='a_id_sap')
    adm_suppr = pd.DataFrame(columns=['a_id_sap'])
    adm_suppr['a_id_sap'] = all_adm_suppr[Files.AdmSuppr.ID_SAP].astype(int)
    adm_suppr = adm_suppr.reset_index(drop=True)

    return adm_suppr


# Fonction pour la table Fonction
def to_table_fonctions(reo):
    """
    Création de la table Fonction à partir dela talbe REO. Sélection et renommage des champs.
    :type reo: dataframe
    :param reo: table reo
    :return:    - **fonctions** (*DataFrame*): Tableau créé contenant les information de fonction.
    """
    # sélection et renommage des champs
    fonctions = reo[[ReoCols.FONCTION_ID.value, ReoCols.FONCTION_LIBELLE.value]]
    fonctions.drop_duplicates(keep='first', inplace=True)
    fonctions = fonctions.rename(columns={ReoCols.FONCTION_ID.value: 'fon_id', ReoCols.FONCTION_LIBELLE.value: 'fon_libelle'})
    fonctions['fon_id'] = fonctions['fon_id'].astype(int, errors='ignore')
    fonctions = (fonctions.drop_duplicates(subset=['fon_id'], keep='first')
                          .dropna()
                          .fillna(APP_NAN)
                          .replace({APP_NAN: None})
                          .reset_index(drop=True))
    return fonctions



# Fonction pour la table RefGest
def to_table_ref_gest(df_ref_gest):
    """
    Création de la table RefGest à partir de la table Référentiel gestionnaires DRHAT anonymisé. Sélection et renommage des champs.

    :type df_ref_gest: dataframe
    :param df_ref_gest: table Référentiel gestionnaires DRHAT anonymisé

    :return:    - **ref_gest** (*DataFrame*): Tableau créé contenant les information du référentiel gestionnaire.

    """
    # sélection et renommage des champs
    ref_gest = df_ref_gest.iloc[:, 0:8]
    ref_gest.columns = ['ref_gest_sap',
                        'ref_gest_username',
                        'ref_gest_email',
                        'ref_gest_first_name',
                        'ref_gest_last_name',
                        'ref_gest_grade',
                        'ref_gest_niv_org',
                        'ref_gest_org_id']

    ref_gest = (ref_gest.drop_duplicates(subset=['ref_gest_sap'], keep='first')
                        .dropna(subset=['ref_gest_sap'])
                        .fillna(APP_NAN)
                        .replace({APP_NAN: None})
                        .reset_index(drop=True))

    return ref_gest


# Fonction pour la table RefOrg
def to_table_ref_org(df_ref_org):
    """
    Création de la table RefOrg à partir de la table Référentiel organique. Sélection et renommage des champs.

    :type df_ref_org: dataframe
    :param df_ref_org: table Référentiel organique

    :return:    - **ref_org** (*DataFrame*): Tableau créé contenant les information du référentiel organique.

    """
    # sélection et renommage des champs
    ref_org = df_ref_org.iloc[:, 0:16]
    ref_org.columns = ['ref_org_code_niv_org1',
                       'ref_org_lib_niv_org1',
                       'ref_org_code_niv_org2',
                       'ref_org_lib_niv_org2',
                       'ref_org_code_niv_org3',
                       'ref_org_lib_niv_org3',
                       'ref_org_code_niv_org4',
                       'ref_org_lib_niv_org4',
                       'ref_org_niv_org',
                       'ref_org_ref_fe',
                       'ref_org_ref_sv_fil',
                       'ref_org_droit_lect',
                       'ref_org_droit_ecr',
                       'ref_org_expert_hme',
                       'ref_org_bvt',
                       'ref_org_itd']

    # Création d'une clé primaire (inexistante dans le référentiel Excel importé)
    # Pour cela, on parcourt toutes les lignes du référentiel
    # A chaque ligne, la clé primaire sera le code_niv_org non vide d'indice le plus élevé

    list_pk = []

    for i in range(ref_org.shape[0]):

        code_niv_org4 = ref_org.loc[i, 'ref_org_code_niv_org4']  # code_niv_org d'indice 4
        code_niv_org3 = ref_org.loc[i, 'ref_org_code_niv_org3']  # code_niv_org d'indice 3
        code_niv_org2 = ref_org.loc[i, 'ref_org_code_niv_org2']  # code_niv_org d'indice 2
        code_niv_org1 = ref_org.loc[i, 'ref_org_code_niv_org1']  # code_niv_org d'indice 1

        if not pd.isnull(code_niv_org4):
            list_pk.append(code_niv_org4)
        elif not pd.isnull(code_niv_org3):
            list_pk.append(code_niv_org3)
        elif not pd.isnull(code_niv_org2):
            list_pk.append(code_niv_org2)
        else:
            list_pk.append(code_niv_org1)
    
    ref_org.insert(0, 'ref_org_code', list_pk)
    
    for c in ['ref_org_ref_fe',
              'ref_org_ref_sv_fil',
              'ref_org_droit_lect',
              'ref_org_droit_ecr',
              'ref_org_expert_hme',
              'ref_org_bvt',
              'ref_org_itd']:

        ref_org[c] = pd.notnull(ref_org[c])

    ref_org = (ref_org.drop_duplicates(subset=['ref_org_code'], keep='first')
                      .dropna(subset=['ref_org_code'])
                      .fillna(APP_NAN)
                      .replace({APP_NAN: None})
                      .reset_index(drop=True))

    return ref_org


# Fonction pour la table RefSvFil
def to_table_ref_sv_fil(df_ref_sv_fil):
    """
    Création de la table RefSvFil à partir de la table Référentiel sous-vivier filiere. Sélection et renommage des champs.

    :type df_ref_sv_fil: dataframe
    :param df_ref_sv_fil: table Référentiel sous-vivier filiere

    :return:    - **ref_sv_fil** (*DataFrame*): Tableau créé contenant les information du référentiel sous-vivier filiere.

    """
    # sélection et renommage des champs
    ref_sv_fil = df_ref_sv_fil.iloc[:, 0:12]
    ref_sv_fil.columns = ['ref_sv_fil_code_niv_org1',
                          'ref_sv_fil_lib_niv_org1',
                          'ref_sv_fil_code_niv_org2',
                          'ref_sv_fil_lib_niv_org2',
                          'ref_sv_fil_code_niv_org3',
                          'ref_sv_fil_lib_niv_org3',
                          'ref_sv_fil_code_niv_org4',
                          'ref_sv_fil_lib_niv_org4',
                          'ref_sv_fil_dom_gest',
                          'ref_sv_fil_dom',
                          'ref_sv_fil_fil',
                          'ref_sv_fil_cat']

    # On parcourt toutes les lignes du référentiel
    # A chaque ligne, la clé primaire sera le code_niv_org non vide d'indice le plus élevé

    list_pk = []
    for i in range(ref_sv_fil.shape[0]):

        code_niv_org4 = ref_sv_fil.loc[i, 'ref_sv_fil_code_niv_org4']  # code_niv_org d'indice 4
        code_niv_org3 = ref_sv_fil.loc[i, 'ref_sv_fil_code_niv_org3']  # code_niv_org d'indice 3
        code_niv_org2 = ref_sv_fil.loc[i, 'ref_sv_fil_code_niv_org2']  # code_niv_org d'indice 2
        code_niv_org1 = ref_sv_fil.loc[i, 'ref_sv_fil_code_niv_org1']  # code_niv_org d'indice 1

        if not pd.isnull(code_niv_org4):
            list_pk.append(code_niv_org4)
        elif not pd.isnull(code_niv_org3):
            list_pk.append(code_niv_org3)
        elif not pd.isnull(code_niv_org2):
            list_pk.append(code_niv_org2)
        else:
            list_pk.append(code_niv_org1)

    # Ici, ref_sv_fil_code n'est pas une clé primaire car pour un même code il peut y avoir plusieurs couples DOM/FIL
    # A la différence de ce qui est fait dans to_table_ref_org, on ne supprime donc pas les doublons de la colonne 'ref_sv_fil_code'
    ref_sv_fil.insert(0, 'ref_sv_fil_code', list_pk)
    ref_sv_fil = (ref_sv_fil.drop_duplicates(keep='first')
                            .dropna(subset=['ref_sv_fil_code'])
                            .fillna(APP_NAN)
                            .replace({APP_NAN: None})
                            .reset_index(drop=True)
                            .drop(ref_sv_fil.columns[1:9], axis=1)
                            .reset_index(drop=True))
    ref_sv_fil['sous_vivier_id'] = ref_sv_fil['ref_sv_fil_dom'].astype(str) + ", " + ref_sv_fil["ref_sv_fil_fil"].astype(str) + ", " + ref_sv_fil["ref_sv_fil_cat"].astype(str)

    return ref_sv_fil


# Fonction pour la table SousVivier
def to_table_sous_vivier(df_ref_sv_fil):
    """
    Création de la table SousVivier à partir de la table Référentiel sous-vivier filiere. Sélection et renommage des champs.

    :type df_ref_sv_fil: dataframe
    :param df_ref_sv_fil: table Référentiel sous-vivier filiere

    :return:    - **sous_vivier** (*DataFrame*): Tableau créé contenant les information de la table SousVivier.

    """
    # sélection et renommage des champs
    sous_vivier = df_ref_sv_fil.iloc[:, [9, 10, 11]]
    sous_vivier.columns = ['sv_dom', 'sv_fil', 'sv_cat']
    sous_vivier = (sous_vivier.drop_duplicates(keep='first')
                              .dropna()
                              .fillna(APP_NAN)
                              .replace({APP_NAN: None})
                              .reset_index(drop=True))

    sous_vivier.insert(0, 'sv_id', '')
    sous_vivier['sv_id'] = sous_vivier['sv_dom'].astype(str) + ", " + sous_vivier["sv_fil"].astype(str) + ", " + sous_vivier["sv_cat"].astype(str)
    sous_vivier.insert(1, 'sv_libelle', '')
    sous_vivier['sv_libelle'] = sous_vivier['sv_dom'].astype(str) + ", " + sous_vivier["sv_fil"].astype(str) + ", " + sous_vivier["sv_cat"].astype(str)
    return sous_vivier



# Fonction pour le référentiel FE
def to_table_ref_fe(df_ref_fe):
    """
    Adaptation du dataframe issu de la table Référentiel FE. Sélection et renommage des champs.

    :type df_ref_fe: dataframe
    :param df_ref_fe: table Référentiel FE

    :return:    - **ref_fe** (*DataFrame*): Tableau créé contenant les information du référentiel FE.

    """
    # sélection et renommage des champs
    ref_fe = df_ref_fe.iloc[:, [1, 3, 4, 5, 9, 12, 15, 16, 17, 18]]
    ref_fe.columns = ['fe_credo', 'fe_mere_credo', 'fe_mere_la', 'fe_fot',
                      'fe_abo_fe', 'fe_pilier_niv1', 'fe_code_niv_org4',
                      'fe_niv_org4', 'fe_code_niv_org4_mdr', 'fe_niv_org4_mdr']

    ref_fe = (ref_fe.drop_duplicates(subset=['fe_credo'], keep='first')
                    .dropna(subset=['fe_credo'])
                    .fillna(APP_NAN)
                    .replace({APP_NAN: None})
                    .reset_index(drop=True))

    return ref_fe


# Fonction pour la table Grade
def to_table_grades(bo):
    """
    Création de la table Grades à partir de la table donnee BO. Sélection et renommage des champs.


    :type bo: dataframe
    :param bo: table donnees BO

    :return:    - **grades** (*DataFrame*): Tableau créé contenant les information de grades.

    """
    # sélection et renommage des champs
    grades = pd.DataFrame(bo['GRADE TA'].value_counts().index)
    grades['gr_id'] = grades.index
    grades.columns = ["gr_code", "gr_id"]
    return grades


# Fonction pour la table FE avec les données REO et Donnes BO
def to_table_fe(df_ref_fe):

    ref_fe = df_ref_fe.iloc[:, [1, 2, 3, 4, 5, 9, 10, 11, 12, 15, 16, 17, 18]]
    ref_fe.columns = ['fe_credo', 'fe_libelle', 'fe_mere_credo', 'fe_mere_la', 'fe_fot',
                      'fe_abo_fe', 'fe_garnison_lieu', 'fe_code_postal', 'fe_pilier_niv1', 'fe_code_niv_org4',
                      'fe_niv_org4', 'fe_code_niv_org4_mdr', 'fe_niv_org4_mdr']  # vérifier que garnison_id_id existe dans table Garnison
    # Pour chaque garnison L, on véfie si il existe dans gar_lieu de la table Garnison, si oui on récupère l'id, si non =>erreur
    ref_fe['fe_nb_poste_reo_mdr'] = 0
    ref_fe['fe_nb_poste_reevalue_mdr'] = 0
    ref_fe['fe_nb_poste_vacant_mdr'] = 0
    ref_fe['fe_nb_poste_occupe_mdr'] = 0
    ref_fe['fe_nb_poste_reo_off'] = 0
    ref_fe['fe_nb_poste_reevalue_off'] = 0
    ref_fe['fe_nb_poste_vacant_off'] = 0
    ref_fe['fe_nb_poste_occupe_off'] = 0
    ref_fe['fe_nb_poste_reo_soff'] = 0
    ref_fe['fe_nb_poste_reevalue_soff'] = 0
    ref_fe['fe_nb_poste_vacant_soff'] = 0
    ref_fe['fe_nb_poste_occupe_soff'] = 0

    ref_fe = (ref_fe.drop_duplicates(subset=['fe_credo'], keep='first')
                    .dropna(subset=['fe_credo'])
                    .fillna(APP_NAN)
                    .replace({APP_NAN: None})
                    .reset_index(drop=True))

    return ref_fe


# Fonction pour la table MarquesGroupe
def to_table_groupesMarques():
    """
    Création de la table GroupesMarques en dur.


    :return:    - **groupesMarques** (*DataFrame*): Tableau créé contenant les information de groupesMarques.

    """

    groupesMarques = pd.DataFrame(
        columns=['gm_id', 'gm_type', 'gm_code', 'gm_libelle', 'gm_selectionMultiple', 'gm_ordre'])
    groupesMarques['gm_id'] = ['1', '2', '3', '4', '5']
    groupesMarques['gm_code'] = ['PRIO', 'GEN', 'ADM', 'POS', 'AFF']
    groupesMarques['gm_type'] = ['C', 'C', 'M', 'P', 'A']
    groupesMarques['gm_libelle'] = ['CIAT', 'Générique', 'Administrés', 'Poste', 'Affectations']
    groupesMarques['gm_selection_multiple'] = np.array([False, True, True, True, True], dtype=object)
    groupesMarques['gm_ordre'] = [20, 21, 10, 10, 10]
    return groupesMarques


# Fonction pour la table Marques
def to_table_marques():
    """
    Création de la table Marques en dur.


    :return:    - **marques** (*DataFrame*): Tableau créé contenant les information de marques.
    """
    marques = pd.DataFrame(columns=['groupe_marques_id', 'mar_id', 'mar_code', 'mar_libelle', 'mar_ordre'])
    marques['groupe_marques_id'] = ['1', '1', '1', '1', '2','2', '2', '2', '2', '2', '2', '2', '2', '3','3','3','3','4','4','4','4']
    marques['mar_code'] = ['P1', 'P2', 'P3', 'P4','AGS','ETOILE', 'ETOILE2','DRAPEAU', 'DRAPEAU2', 
                           'ALERTE', 'QUESTION','QUESTION2', 'COCHE', 'BDG','AGR','FDC','RDC','DRAPEAU3', 'DRAPEAU4', 'DRAPEAU5','DRAPEAU6',]
    marques['mar_libelle'] = ['1 Passage CIAT', '2 Passage CIAT','3 Passage CIAT','4 Passage CIAT',
                              'Agrément STATUT','Affectation poste ITD','Affectation poste CIAT','TRV1','TRV2', 'Attention particulière', 'Attente recrutement','Attente concours',
                              'PLS', 
                              'Blessé de guerre',
                              'Agrément REC part', 'Fin de carrière',
                              'Couple','TRV1','TRV2','TCOS','C2']
    marques['mar_ordre'] = [1, 2, 3, 4, 1, 2, 3, 4, 5,6,7, 8, 9, 4,3, 2, 1,1,2,3,4]
    marques['mar_id'] = ['1_P1', '1_P2', '1_P3', '1_P4', '2_AGS','2_E','2_E2', '2_D', '2_D2',
                         '2_ALERTE', '2_QUESTION','2_QUESTION2', '2_COCHE', '3_BDG','3_AGR','3_FDC', '3_RDC','4_D3','4_D4','4_D5','4_D6',]
    return marques


# Fonction pour la table Marques
def to_table_zone_geographique(df_zones):
    """
    Création de la table Zones Geographique en dur.


    :return:    - **zones** (*DataFrame*): Tableau créé contenant les information de zones geographiques.
    """
    df_zones.drop_duplicates(inplace=True)
    zones = pd.DataFrame(columns=['zone_id', 'zone_libelle'])
    zones['zone_id'] = df_zones['Zones géographiques SHM'].str.replace(' ', '')
    zones['zone_libelle'] = df_zones['Zones géographiques SHM']

    return zones


# Fonction pour la table Marques
def to_table_commentaires(df_com):
    """
    Création du dataframe commentaires.

    :return:    - **commentaires** (*DataFrame*): Tableau créé contenant les information des commentaires.
    """
    df_com.drop_duplicates(inplace=True)
    administres = pd.DataFrame.from_records(Administre.objects.all().values('a_id_sap'))
    all_commentaires = df_com.merge(administres, how='inner', left_on='IDSAP', right_on='a_id_sap')
    commentaires = pd.DataFrame(columns=['a_id_sap', 'a_notes_gestionnaire'])
    commentaires['a_id_sap'] = all_commentaires['IDSAP'].astype(int)
    commentaires['a_notes_gestionnaire'] = all_commentaires['COMMENTAIRES']
    commentaires = commentaires.reset_index(drop=True)

    return commentaires


# Fonction pour la table Poste
def to_table_postes(reo):
    """
    Création de la table postes à partir de la table REO. Sélection et renommage des champs.
    :type reo: dataframe
    :param reo: table REO
    :return:    - **postes** (*dataframe*): Tableau créé contenant les information de postes.
    """
    postes = reo.copy()
    # administres = pd.DataFrame.from_records(Administre.objects.all().values())
    # Ancien REO postes = postes[['ID du poste /MC', "Code Poste CREDO", "FE mère CREDO", "FE CREDO", "FE mère LA", "FE LA","Fonction du Poste L", "Domaine emploi LA", "Filière emploi LA", "NF emploi C", "Emploi L", "Catégorie du poste LA", "Marquant DUO/HDUO", "Origine du poste C", "Catégorie/regroupement grade L",  "Grade LA"]]
    postes = postes[[
        ReoCols.ANNEE_PROJET.value,
        ReoCols.FORMATION_EMPLOI.value,
        ReoCols.CODE_POSTAL.value,
        ReoCols.CATEGORIE.value, 
        ReoCols.EIP.value, 
        ReoCols.DOMAINE.value, 
        ReoCols.FILIERE.value,
        ReoCols.CODE_NF.value, 
        ReoCols.ID_POSTE.value,
        ReoCols.FONCTION_ID.value,
        ReoCols.FONCTION_LIBELLE.value,
        ReoCols.DOMAINE_GESTION.value,
    ]]
    postes = (postes.dropna(subset=[ReoCols.ID_POSTE.value])
                    .drop_duplicates(subset=[ReoCols.ID_POSTE.value], keep='first')
                    .rename(columns={
                            ReoCols.ID_POSTE.value: 'p_id',
                            ReoCols.FORMATION_EMPLOI.value: 'formation_emploi_id',
                            ReoCols.DOMAINE.value: 'p_domaine',
                            ReoCols.ANNEE_PROJET.value: 'p_annee',
                            ReoCols.FILIERE.value: 'p_filiere',
                            ReoCols.CODE_NF.value: 'p_nf',
                            ReoCols.FONCTION_ID.value: 'fonction_id',
                            ReoCols.FONCTION_LIBELLE.value: 'p_fonction',
                            ReoCols.CATEGORIE.value: 'p_categorie',
                            ReoCols.DOMAINE_GESTION.value: 'p_nfs',
                            ReoCols.CODE_POSTAL.value: 'p_dep',
                            ReoCols.EIP.value:'p_eip'}))
    postes['p_dep'] = postes['p_dep'].fillna('').str.slice(0, 2, 1).replace('', None)
    postes = (postes.fillna(APP_NAN)
                    .replace({APP_NAN: None})
                    .reset_index(drop=True))

    # initialisation des colonnes vides
    # traitement de departement
    postes['p_date_fin'] = None
    postes['p_notes_gestionnaire'] = None
    postes['p_notes_partagees'] = None
    postes['p_liste_id_marques'] = None
    postes['p_flag_particulier'] = None
    postes['statut_decision'] = None

    return postes

# Fonction qui permet de faire l'extraction de rèquete poste-ocv
def to_table_reo_ocv(reo_ocv_df):
    """
    Selection des colonnes des attributs . Sélection et renommage des champs.
    :type reo: dataframe
    :param reo: table REO
    :return:    - **postes** (*dataframe*): Tableau créé contenant les information de postes.
    """
    postes = pd.DataFrame.from_records(Poste.objects.all().values())
    postes_ocv = reo_ocv_df.copy()
    postes_ocv = postes_ocv[['Code Poste CREDO', 'Identifiant SAP', 'Marquant DUO/HDUO']]
    postes_ocv.dropna(subset=['Code Poste CREDO', 'Identifiant SAP'], inplace=True)
    postes_ocv = postes_ocv[postes_ocv['Marquant DUO/HDUO'] != "HDUO"]
    postes = postes.merge(postes_ocv, how='inner', left_on='p_id',  right_on='Code Poste CREDO')
    return postes

# Fonction qui concatène l'EIP et la formation d'emploi pour avoir un champ unique de poste
def concat_EIP_FE(x):
    """
    Construit la table des postes à partir du fichier REO homologué


    :type x: DataFrame
    :param x: contient les colonnes a_eip et formationsEmploi_id à concaténer

    :return:    - **dataframe** (*dataframe*): DataFrame avec un champ supplémentaire comportant l'information des deux colonnes.

    """
    str_a_eip = str(x["a_eip"])
    eip = str_a_eip[0:3] + str_a_eip[4:7] + str_a_eip[8:]
    fe = str(x["formation_emploi_id"])
    return eip + '_' + fe


# Fonction pour la table FMOB de fichier FMOB
def to_table_fmob_fmob(FMOB):
    """
    Construit la table FMOB à partir des tables FMOB.

    :type FMOB: DataFrame
    :param FMOB: table FMOB

    :return:    - **fmob** (*dataframe*): Tableau créé contenant les information de FMOB provenant de FMOB.
    """

    # import du fichier contenant les données
    logger.info("----- Début de l'extraction du fichier FMOB pour la table FMOB ----- ")
    logger.info('Nombre total de lignes du fichier FMOB : %s', FMOB.shape[0])
    FMOB.dropna(subset=["Matricule SAP", "Millésime FMOB"], axis=0, inplace=True)
    logger.info("Nombre total de lignes du fichier FMOB après la suppression des cellules vides dans les colonnes Matricule SAP et Millésime FMOB: %s", FMOB.shape[0])
    FMOB.drop_duplicates(subset=["Matricule SAP"], inplace=True)
    logger.info('Nombre total de lignes du fichier FMOB après suppression des doublons dans la colonne Matricule SAP : %s', FMOB.shape[0])
    FMOB.reset_index(inplace=True, drop=True)


    # sélection et renommage des champs
    try: # ADD 
        col_fmob = ["Matricule SAP", "Millésime FMOB", "Annulation FMOB","Avis CDC à la mutation de l'administré","Avis CDC Mobilité bassin externe","Avis CDC Mobilité bassin interne",
        "Avis CDC Mobilité centre intérêt ADT","Avis CDC Mobilité dans la spécialité","Avis CDC Mobilité hors métropole","Avis CDC Mobilité recrutement particulier administré","Sans Suite FMOB", "Date_Deb FMOB",
        "Date_Fin FMOB","Date de signature de l'administré", "Date de signature du chef de corps", "Date de VISA du militaire",
        "Départ de l'institution SOFF","Mobilité bassin externe","Mobilité bassin interne","Mobilité centre intérêt ADT","Mobilité dans la spécialité",
        "Mobilité hors métropole","Mobilité recrutement particulier administré","Motif d'édition LA", "Reconnaissance parcours pro administré",
        "Remarques éventuelles de l'intéressé","Avis du commandant de formation", 'Réception DRHAT FMOB']
        fmob = FMOB[col_fmob]
    except Exception as e:
        raise Exception("Erreur lors de l'extraction du fichier FMOB : les noms de colonnes du fichier ne sont pas corrects") from e

    fmob['fmob_id'] = fmob.index
    #changer le fmob_id # ADD & rename

    

    fmob.rename(columns={'Matricule SAP': 'administre_id', 'Millésime FMOB': 'fmob_millesime',
                         'Annulation FMOB': "fmob_annulation_fmob",
                         "Avis CDC à la mutation de l'administré":"fmob_avis_cdc_mutation_administre",
                         "Avis CDC Mobilité bassin externe":"fmob_avis_cdc_mobilite_externe",
                         "Avis CDC Mobilité bassin interne":"fmob_avis_cdc_mobilite_interne",
                         "Avis CDC Mobilité centre intérêt ADT":"fmob_avis_cdc_mobilite_centre_interet",
                         "Avis CDC Mobilité dans la spécialité":"fmob_avis_cdc_mobilite_specialite",
                         "Avis CDC Mobilité hors métropole":"fmob_avis_cdc_mobilite_hors_metropole",
                         "Avis CDC Mobilité recrutement particulier administré":"fmob_avis_cdc_mobilite_recrutement_particulier_admin",
                         'Sans Suite FMOB': "fmob_sans_suite_fmob",
                         'Date de VISA du militaire': "fmob_date_visa_militaire",
                         "Départ de l'institution SOFF": "fmob_depart_institution_soff",
                         "Mobilité bassin externe":"fmob_mobilite_bassin_externe",
                         "Mobilité bassin interne":"fmob_mobilite_bassin_interne",
                         "Mobilité centre intérêt ADT":"fmob_mobilite_centre_interet_adt",
                         "Mobilité dans la spécialité":"fmob_mobilite_dans_specialite",
                         "Mobilité hors métropole":"fmob_mobilite_hors_metropole",
                         "Mobilité recrutement particulier administré":"fmob_mobilite_recrutement_particulier_administre",
                         'Date_Deb FMOB': "fmob_date_deb_fmob",
                         'Date_Fin FMOB': "fmob_date_fin_fmob",
                         'Réception DRHAT FMOB' : "fmob_reception_drhat_fmob",
                         "Date de signature de l'administré": "fmob_date_signature_admin_fmob",
                         "Date de signature du chef de corps": "fmob_date_signature_chef_de_corps",
                         "Motif d'édition LA": "fmob_motif_edition_la",
                         "Reconnaissance parcours pro administré": "fmob_reconnaissance_parcours_pro_administre",
                         "Remarques éventuelles de l'intéressé" : "fmob_remarques_eventuelles_administres",
                         "Avis du commandant de formation" : "fmob_avis_commandant_formation",
                         }, inplace=True, errors='raise')
    # Check if O or N
    c_x = ['fmob_annulation_fmob', 'fmob_sans_suite_fmob','fmob_reconnaissance_parcours_pro_administre','fmob_reception_drhat_fmob']
       
    for c in c_x:
        fmob[c] = fmob[c].apply(lambda x: True if x == 'X  ' else False)

    #fmob['fmob_depart_institution_soff'] = fmob['fmob_depart_institution_soff'].apply(lambda x: True if x == 'O' else False)
    
    # ADD and check O or N : 
    c_o = ['fmob_depart_institution_soff','fmob_avis_cdc_mutation_administre','fmob_avis_cdc_mobilite_externe','fmob_avis_cdc_mobilite_interne',
    'fmob_avis_cdc_mobilite_centre_interet','fmob_avis_cdc_mobilite_specialite','fmob_avis_cdc_mobilite_hors_metropole',
    'fmob_avis_cdc_mobilite_recrutement_particulier_admin','fmob_mobilite_bassin_externe','fmob_mobilite_bassin_interne',
    'fmob_mobilite_centre_interet_adt','fmob_mobilite_dans_specialite','fmob_mobilite_hors_metropole',
    'fmob_mobilite_recrutement_particulier_administre']
       
    for c in c_o:
        fmob[c] = fmob[c].apply(lambda x: True if x == 'O  ' else False)

    #fmob['fmob_depart_institution_soff'] = fmob['fmob_depart_institution_soff'].apply(lambda x: True if x == 'O' else False)

    fmob = (fmob.fillna(APP_NAN)
                .replace({APP_NAN: None}))

    # Colonne Statut FMOB
    conditions = [
        (fmob['fmob_annulation_fmob'] == True),
        (fmob['fmob_sans_suite_fmob'] == True),
        (fmob['fmob_reception_drhat_fmob'] == False),
        (fmob['fmob_reception_drhat_fmob'] == True),
    ]

    values = ['Annulé', 'Classé sans suite', 'Non réceptionné', 'Réceptionné']

    fmob['fmob_statut'] = np.select(conditions, values)

    logger.info("----- Fin de l'extraction du fichier FMOB pour la table FMOB -----")

    return fmob

# Fonction pour la table FMOB du fichier FEMP
def to_table_fmob_femp(FEMP):
    """
    Construit la table FMOB à partir des tables FEMP.

    :type FEMP: DataFrame
    :param FEMP: table FEMP


    :return:    - **femp** (*dataframe*): Tableau créé contenant les information de FMOB provenant de FEMP.
    """

    # import du fichier contenant les données
    logger.info("----- Début de l'extraction du fichier FEMP pour la table FMOB ----- ")
    logger.info('Nombre total de lignes du fichier FEMP : %s', FEMP.shape[0])
    FEMP.dropna(subset=["Matricule SAP", "Date_Deb FEMP"], axis=0, inplace=True)
    logger.info("Nombre total de lignes du fichier FEMP après la suppression des cellules vides dans les colonnes Matricule SAP et Date_Deb FEMP: %s", FEMP.shape[0])
    FEMP.drop_duplicates(subset=["Matricule SAP"], inplace=True)
    logger.info('Nombre total de lignes du fichier FEMP après suppression des doublons dans la colonne Matricule SAP : %s', FEMP.shape[0])
    FEMP.reset_index(inplace=True, drop=True)

    # sélection et renommage des champs

    try:
        col_femp = ["Matricule SAP", "Annulation FEMP", "Sans Suite FEMP", "Date de signature de l'administré",
                    "Proposition d'affectation.Verrouillé","Commentaires AC","Millésime FEMP"]
        femp = FEMP[col_femp]
    except Exception as e:
        raise Exception("Erreur lors de l'extraction du fichier FEMP : les noms de colonnes du fichier ne sont pas corrects") from e

    femp.rename(columns={FmobCols.ID_SAP.value: "administre_id", "Annulation FEMP": "fmob_annulation_femp",
                         "Proposition d'affectation.Verrouillé": "fmob_proposition_affectation_verrouille",
                         "Sans Suite FEMP": "fmob_sans_suite_femp",
                         "Date de signature de l'administré": "fmob_date_signature_admin_femp",
                         "Commentaires AC" : "fmob_commentaire_ac", "Millésime FEMP":"fmob_millesime_femp"}, inplace=True, errors='raise')

    c_x = ['fmob_annulation_femp', 'fmob_sans_suite_femp', "fmob_proposition_affectation_verrouille"]

    for c in c_x:
        femp[c] = femp[c].apply(lambda x: True if x == 'X  ' else False)

    femp = (femp.fillna(APP_NAN)
                .replace({APP_NAN: None}))

    logger.info("----- Fin de l'extraction du fichier FEMP pour la table FMOB -----")

    return femp


# Fonction pour la table Preference list
def to_table_liste_preference(pam_id,sv_id):
    """
    Construit la table preferences en utilisant le sous-vivier id.

    :type sv_id: entier
    :param sv_id: ID du sous-vivier


    :return:    - **new_souhaits_formates** (*dataframe*): thTableau créé contenant les preferences des administres.
    """
    # Lire tous les administres avec les souhaits dans le sous_viviers et l'annee du Pam avec AVIS A_MUTER ou A Etudier 

    statut_administre = [StatutPam.A_MUTER, StatutPam.A_ETUDIER] 

    souhaits_columns = ('id','administre__a_id_sap', 'administre__a_nf_futur', 'administre__a_filiere_futur_id','a_liste_depts_souhaites_pam','pam_id')
    souhaits_columns_name = ('administre_pam_id','a_id_sap', 'a_nf_futur', 'a_filiere_futur_id','a_liste_depts_souhaites_pam','pam_id')

    souhaits_db = Administres_Pams.objects.filter(pam_id=pam_id, administre__sous_vivier_id=sv_id,
                                                a_statut_pam_annee__in=statut_administre,
                                                decision__de_decision__isnull=True).values_list(*souhaits_columns)
    if not souhaits_db.exists :
        logger.debug(f"Pas d'administrés avec le bon statut {statut_administre} dans le sous vivier {sv_id} et le pam {pam_id}.")
        raise Exception(f"Aucun administrés avec le bon statut {statut_administre} dans le sous vivier {sv_id} et le pam {pam_id}.")

    souhaits = pd.DataFrame.from_records(souhaits_db, columns = souhaits_columns_name)

    # Remplacement des None par un None connue
    souhaits = souhaits.replace('None', np.nan)
    # Remplacement du Vide par un None connue
    souhaits = souhaits.replace('', np.nan)

    # Lire tous les postes dans le sous_viviers et l'annee du Pam avec AVIS P1 ou P2 ou P3 ou P4

    statut_poste = [AvisPoste.P1,AvisPoste.P2,AvisPoste.P3,AvisPoste.P4]

    poste_columns = ('id','poste__p_id', 'poste__p_dep','poste__p_filiere_id','poste__p_nf','poste__p_eip','p_pam_id')
    poste_columns_name = ('poste_pam_id','poste_id', 'p_dep', 'p_filiere_id','p_nf','p_eip','pam_id')


    postes_db = Postes_Pams.objects.filter(Q(p_pam_id=pam_id) 
                                        & Q(poste__sous_viviers=sv_id) 
                                        & Q(decisions__de_decision__isnull=True)
                                        & Q(p_avis_pam__in=statut_poste)).values_list(*poste_columns)

    if not postes_db.exists :
        logger.debug(f"Pas de postes avec le bon statut {statut_poste} dans le sous vivier {sv_id} et le pam {pam_id}.")
        raise Exception(f"Aucun postes avec le bon statut {statut_administre} dans le sous vivier {sv_id} et le pam {pam_id}.")

        
    postes = pd.DataFrame.from_records(postes_db, columns=poste_columns_name)
        
    len_administres_traites = len(souhaits)
    len_postes_traites = len(postes)
    logger.debug('Nb admin traités : ' + str(len_administres_traites))
    logger.debug('Nb groupe_poste traités : ' + str(len_postes_traites))

    # Supprimer tous les administrateurs sans souhaits 
    souhaits_nonull = souhaits.dropna(subset=["a_liste_depts_souhaites_pam"], axis=0)

    souhaits_null = souhaits[~souhaits.index.isin(souhaits_nonull.index)]['a_id_sap']

    len_administres_restants = len(souhaits_nonull)

    logger.debug('Nb admin sans souhaits : ' + str(len_administres_traites - len_administres_restants))
    logger.debug('Les administrés sans souhaits sont : ' + str(list(souhaits_null)))

    if len_administres_restants == 0:
        logger.debug(f"Pas d'administrés avec des souhaits dans le sous vivier {sv_id} et le pam {pam_id}.")
        raise Exception(f"Aucun administrés n'a renseigné de souhaits - Veuillez vérifier que (Domaine/Filière/NF) et/ou (départements) entre Postes et Administrés soient les mêmes (pas de matching parfait).")
        
    souhaits_nonull = souhaits_nonull.reset_index(drop=True)

    souhaits_nonull['departement'] = souhaits_nonull['a_liste_depts_souhaites_pam'].apply(lambda x : x.strip().split(','))

    souhaits_nonull = souhaits_nonull.explode('departement', ignore_index = True)
    
    souhaits_nonull['departement'] = souhaits_nonull['departement'].replace('AUTRE_ETRANGER', '0')
    
    souhaits_poste = souhaits_nonull.merge(postes,
                                            how='inner', 
                                            left_on=['departement', 'a_filiere_futur_id', 'a_nf_futur'],
                                            right_on=['p_dep', 'p_filiere_id', 'p_nf'])
    
    if souhaits_poste.empty:
        logger.debug(f"Aucun poste ne correspond au souhait du département des administrés dans le sous vivier {sv_id} et le pam {pam_id}.")
        raise Exception(f"Aucun poste ne correspond au souhait des administrés dans le sous vivier - Veuillez vérifier que (Domaine/Filière/NF) et/ou (départements) entre Postes et Administrés soient les mêmes (pas de matching parfait).")

    souhaits_poste = souhaits_poste.dropna(subset=['p_eip'])

    souhaits_poste = souhaits_poste.reset_index(drop=True)
    
    a_id_sap_groupe = souhaits_poste['a_id_sap'].unique()
    
    new_souhaits_formates = pd.DataFrame(columns=['administre_pam_id', 'a_id_sap', 'departement', 'poste_id', 'poste_pam_id'])

    for a in a_id_sap_groupe:
        df = souhaits_poste[souhaits_poste['a_id_sap'] == a][
            ['administre_pam_id','a_id_sap', 'departement', 'poste_id', 'poste_pam_id']]
        df.reset_index(drop=False, inplace=True)
        df['lp_rang_poste'] = df.index + 1

        new_souhaits_formates = pd.concat([new_souhaits_formates, df], ignore_index=True)

        # comment choisir l'id ?
        new_souhaits_formates['lp_id'] = new_souhaits_formates.index
    
    new_souhaits_formates['pam_id'] = pam_id

    new_souhaits_formates = new_souhaits_formates.rename(columns={'a_id_sap': 'administre_id'})
    logger.debug('Ranking ending')
    return new_souhaits_formates
        




def to_table_liste_preference_selectif(sv_id,pam_id,l_a_id,l_p_id):
    """
    Construit la table preferences en utilisant le sous-vivier id.

    :type sv_id: entier
    :param sv_id: ID du sous-vivier


    :return:    - **new_souhaits_formates** (*dataframe*): thTableau créé contenant les preferences des administres.
    """
    # Lire tous les administres avec les souhaits dans le sous_viviers et l'annee du Pam avec AVIS A_MUTER ou A Etudier 

    statut_administre = [StatutPam.A_MUTER, StatutPam.A_ETUDIER] 

    souhaits_columns = ('id','administre__a_id_sap', 'administre__a_nf_futur', 'administre__a_filiere_futur_id','a_liste_depts_souhaites_pam','pam_id')
    souhaits_columns_name = ('administre_pam_id','a_id_sap', 'a_nf_futur', 'a_filiere_futur_id','a_liste_depts_souhaites_pam','pam_id')

    souhaits_db = Administres_Pams.objects.filter(pam_id=pam_id, administre__sous_vivier_id=sv_id,
                                                a_statut_pam_annee__in=statut_administre, administre_id__in=l_a_id,
                                                decision__de_decision__isnull=True).values_list(*souhaits_columns)
    if not souhaits_db.exists :
        logger.debug(f"Pas d'administrés avec le bon statut {statut_administre} dans le sous vivier {sv_id} et le pam {pam_id}.")
        raise Exception(f"Aucun administrés avec le bon statut {statut_administre} dans le sous vivier {sv_id} et le pam {pam_id}.")

    souhaits = pd.DataFrame.from_records(souhaits_db, columns = souhaits_columns_name)

    # Remplacement des None par un None connue
    souhaits = souhaits.replace('None', np.nan)
    # Remplacement du Vide par un None connue
    souhaits = souhaits.replace('', np.nan)

    # Lire tous les postes dans le sous_viviers et l'annee du Pam avec AVIS P1 ou P2 ou P3 ou P4

    statut_poste = [AvisPoste.P1,AvisPoste.P2,AvisPoste.P3,AvisPoste.P4]

    poste_columns = ('id','poste__p_id', 'poste__p_dep','poste__p_filiere_id','poste__p_nf','poste__p_eip','p_pam_id')
    poste_columns_name = ('poste_pam_id','poste_id', 'p_dep', 'p_filiere_id','p_nf','p_eip','pam_id')


    postes_db = Postes_Pams.objects.filter(Q(p_pam_id=pam_id) 
                                        & Q(poste__sous_viviers=sv_id) 
                                        & Q(decisions__de_decision__isnull=True)
                                        & Q(poste_id__in=l_p_id)
                                        & Q(p_avis_pam__in=statut_poste)).values_list(*poste_columns)

    if not postes_db.exists :
        logger.debug(f"Pas de postes avec le bon statut {statut_poste} dans le sous vivier {sv_id} et le pam {pam_id}.")
        raise Exception(f"Aucun postes avec le bon statut {statut_administre} dans le sous vivier {sv_id} et le pam {pam_id}.")

        
    postes = pd.DataFrame.from_records(postes_db, columns=poste_columns_name)
        
    len_administres_traites = len(souhaits)
    len_postes_traites = len(postes)
    logger.debug('Nb admin traités : ' + str(len_administres_traites))
    logger.debug('Nb groupe_poste traités : ' + str(len_postes_traites))

    # Supprimer tous les administrateurs sans souhaits 
    souhaits_nonull = souhaits.dropna(subset=["a_liste_depts_souhaites_pam"], axis=0)

    souhaits_null = souhaits[~souhaits.index.isin(souhaits_nonull.index)]['a_id_sap']

    len_administres_restants = len(souhaits_nonull)

    logger.debug('Nb admin sans souhaits : ' + str(len_administres_traites - len_administres_restants))
    logger.debug('Les administrés sans souhaits sont : ' + str(list(souhaits_null)))

    if len_administres_restants == 0:
        logger.debug(f"Pas d'administrés avec des souhaits dans le sous vivier {sv_id} et le pam {pam_id}.")
        raise Exception(f"Aucun administrés n'a renseigné de souhaits - Veuillez vérifier que (Domaine/Filière/NF) et/ou (départements) entre Postes et Administrés soient les mêmes (pas de matching parfait).")
        
    souhaits_nonull = souhaits_nonull.reset_index(drop=True)

    souhaits_nonull['departement'] = souhaits_nonull['a_liste_depts_souhaites_pam'].apply(lambda x : x.strip().split(','))

    souhaits_nonull = souhaits_nonull.explode('departement', ignore_index = True)
    
    souhaits_nonull['departement'] = souhaits_nonull['departement'].replace('AUTRE_ETRANGER', '0')
    
    souhaits_poste = souhaits_nonull.merge(postes,
                                            how='inner', 
                                            left_on=['departement', 'a_filiere_futur_id', 'a_nf_futur'],
                                            right_on=['p_dep', 'p_filiere_id', 'p_nf'])
    
    if souhaits_poste.empty:
        logger.debug(f"Aucun poste ne correspond au souhait du département des administrés dans le sous vivier {sv_id} et le pam {pam_id}.")
        raise Exception(f"Aucun poste ne correspond au souhait des administrés dans le sous vivier - Veuillez vérifier que (Domaine/Filière/NF) et/ou (départements) entre Postes et Administrés soient les mêmes (pas de matching parfait).")

    souhaits_poste = souhaits_poste.dropna(subset=['p_eip'])

    souhaits_poste = souhaits_poste.reset_index(drop=True)
    
    a_id_sap_groupe = souhaits_poste['a_id_sap'].unique()
    
    new_souhaits_formates = pd.DataFrame(columns=['administre_pam_id', 'a_id_sap', 'departement', 'poste_id', 'poste_pam_id'])

    for a in a_id_sap_groupe:
        df = souhaits_poste[souhaits_poste['a_id_sap'] == a][
            ['administre_pam_id','a_id_sap', 'departement', 'poste_id', 'poste_pam_id']]
        df.reset_index(drop=False, inplace=True)
        df['lp_rang_poste'] = df.index + 1

        new_souhaits_formates = pd.concat([new_souhaits_formates, df], ignore_index=True)

        # comment choisir l'id ?
        new_souhaits_formates['lp_id'] = new_souhaits_formates.index
    
    new_souhaits_formates['pam_id'] = pam_id

    new_souhaits_formates = new_souhaits_formates.rename(columns={'a_id_sap': 'administre_id'})
    logger.debug('Ranking ending')
    return new_souhaits_formates