This commit is contained in:
2022-11-08 21:19:51 +01:00
commit 4c456eafc3
160 changed files with 21472 additions and 0 deletions

View File

@@ -0,0 +1,448 @@
import logging
import numpy as np
import pandas as pd
from backend.models.administre import Administre, Administres_Pams
from backend.models.administre import StatutPamChoices as StatutPam
from backend.models.pam import PAM
from backend.models.domaine import Domaine
from backend.models.filiere import Filiere
from backend.models.formation_emploi import FormationEmploi
from backend.models.grade import Grade
from django.db.models import Q
from ..alimentation_decorators import data_perf_logger_factory, get_data_logger
from ..decorators import execution_time
from .commun import (InsertionCounters, batch_iterator, is_same_model,
is_same_value)
logger = get_data_logger(__name__)
@execution_time(level=logging.INFO, logger_factory=data_perf_logger_factory)
def update_administre_fmob(df) -> None:
"""
Met à jour le statut PAM des administrés quand un formulaire de mobilité est annulé.
"""
ModelType = Administres_Pams
Cols = ModelType.Cols
status = StatutPam.A_MAINTENIR
annee_pam = str(int(df['fmob_millesime'].iloc[0]))
updated = (ModelType.objects
.filter(**{f'{Cols.O2M_FMOB}__fmob_annulation_fmob': True})
.filter(~Q(**{Cols.STATUT_PAM: status}))
.filter(a_statut_pam_annee=StatutPam.NON_ETUDIE)
.filter(pam__pam_id=annee_pam)
.update(**{Cols.STATUT_PAM: status}))
if updated:
logger.info('%s | mis à jour car FMOB annulé : %s', ModelType.__name__, updated)
logger.info('Début de la mise à jour des FMOB en "A traiter"')
next_status = StatutPam.A_TRAITER
next_updated = (ModelType.objects
.filter(**{f'{Cols.O2M_FMOB}__isnull' : False})
.filter(~Q(**{Cols.STATUT_PAM: status}))
.filter(a_statut_pam_annee=StatutPam.NON_ETUDIE)
.filter(pam__pam_id=annee_pam)
.update(**{Cols.STATUT_PAM: next_status}))
if next_updated:
logger.info('%s | mis à jour car FMOB existe: %s administrés qui ont un FORMOB', ModelType.__name__, next_updated)
logger.info("%s[pk=%s] administré mis à jour", ModelType.__name__,next_updated)
return updated, next_updated
@execution_time(level=logging.INFO, logger_factory=data_perf_logger_factory)
def insert_administre_bo(df: pd.DataFrame) -> None:
"""
Insère ou met à jour des données de la table des administrés.
:param df: Dataframe contenant les données pretraités à inserer
:type df: class:`pandas.DataFrame`
"""
logger.info('start update_administre')
ModelType = Administre
ModelType_2 = Administres_Pams
Cols = ModelType.Cols
Cols_2 = ModelType_2.Cols
col_pk = Cols.PK
fields_to_update = [
'formation_emploi_id',
'a_code_fonction',
'grade_id',
'a_grade_date_debut',
'a_fonction',
'a_nom',
'a_prenom',
'a_sexe',
'a_id_def',
'a_eip',
'a_eip_fiche_detaille',
'a_eis',
Cols.REL_DOMAINE,
Cols.REL_FILIERE,
'a_nf',
'a_domaine_gestion',
'a_date_entree_service',
'a_arme',
'a_rg_origine_recrutement',
'a_date_rdc',
'a_date_dernier_acr',
'a_date_naissance',
'a_diplome_hl',
'a_dernier_diplome',
'a_credo_fe',
'a_date_arrivee_fe',
'a_date_pos_statuaire',
'a_pos_statuaire',
'a_interruption_service',
'a_situation_fam',
'a_date_mariage',
'a_nombre_enfants',
'a_enfants',
'a_sap_conjoint',
'a_fonction1',
'a_fonction2',
'a_fonction3',
'a_fonction4',
'a_fonction5',
'a_fonction6',
'a_fonction7',
'a_fonction8',
'a_fonction9',
'a_date_fonction1',
'a_date_fonction2',
'a_date_fonction3',
'a_date_fonction4',
'a_date_fonction5',
'a_date_fonction6',
'a_date_fonction7',
'a_date_fonction8',
'a_date_fonction9',
'a_pls_gb_max',
'a_marqueur_pn',
'a_profession_conjoint',
'a_id_def_conjoint',
'a_sexe_conjoint',
'a_origine_recrutement',
Cols.STATUT_CONCERTO,
Cols.DATE_STATUT_CONCERTO,
Cols.STATUT_CONCERTO_FUTUR,
Cols.DATE_STATUT_CONCERTO_FUTUR,
'a_fud',
'a_date_fud',
'a_lien_service',
'a_categorie',
]
fields_to_update_2 = [
'pam_id',
'administre_id',
'a_statut_pam_annee',
]
models_in_db = {m.pk: m for m in ModelType.objects.only(col_pk, *fields_to_update)}
fields_db = ['a_id_sap'] + fields_to_update
# Integer ou Float Colonnes à mettre à jour
fields_num = [
'a_sap_conjoint',
'a_pls_gb_max',
'a_nombre_enfants',
'a_id_sap'
]
# Dict pour convertir toutes les colonnes non Integer en chaîne de caractères.
dict_conv_str = {a: str for a in fields_db if a not in fields_num}
# Dict pour convertir toutes les colonnes Integer en Float.
dict_conv_float = {a: float for a in fields_num }
# Lire tous les administrés de la base de données
adm_in_db_df = pd.DataFrame.from_records(Administre.objects.all().values_list(*tuple(fields_db)), columns = fields_db)
df = df.drop(['_merge'],1)
if not adm_in_db_df.empty :
# Il va y avoir de modification de type donc c'est mieux de ne pas toucher df
df_comparaison = df.copy()
# Modification de type de quelque champs
logger.debug('Conversion des types pour la fusion')
df_comparaison = df_comparaison.fillna(np.nan)
df_comparaison = df_comparaison.replace('None', np.nan)
adm_in_db_df = adm_in_db_df.fillna(np.nan)
adm_in_db_df = adm_in_db_df.replace('None', np.nan)
adm_in_db_df = adm_in_db_df.astype(dict_conv_str)
adm_in_db_df = adm_in_db_df.astype(dict_conv_float)
df_comparaison = df_comparaison.astype(dict_conv_str)
df_comparaison = df_comparaison.astype(dict_conv_float)
compare = pd.DataFrame([df_comparaison[fields_db].dtypes,adm_in_db_df[fields_db].dtypes]).T
logger.debug('Comparaison des types pour la fusion')
# logger.debug(compare[compare[0]!=compare[1]].dropna())
logger.debug('------------------------------------')
# Comparaison pour savoir ce qui doit etre creer, mis a jour ou supprimer
comparing_adm_sap = pd.merge(df_comparaison, adm_in_db_df, how='outer', on = 'a_id_sap', suffixes=(None, "_x"), indicator=True)
same_rows = comparing_adm_sap[comparing_adm_sap['_merge'] == 'both'].drop('_merge', axis=1)
new_rows = comparing_adm_sap[comparing_adm_sap['_merge'] == 'left_only'].drop('_merge', axis=1)
delete_rows = comparing_adm_sap[comparing_adm_sap['_merge'] == 'right_only'].drop('_merge', axis=1)
# Comparaison pour savoir des ligne a mettre a jour, lequel est deja a jour et lequel doit se mettre a jour
comparing_adm_both = pd.merge(same_rows, adm_in_db_df, how='left', on=fields_db,
suffixes=(None, "_x"), indicator=True)
not_updated_rows = comparing_adm_both[comparing_adm_both['_merge'] == 'both'].drop(['_merge'], axis=1)
updated_rows = comparing_adm_both[comparing_adm_both['_merge'] == 'left_only'].drop(['_merge'], axis=1)
# Creation du df final avec une colonne db_create_status qui dis si la ligne doit etre creer ou mis a jour
update = df.loc[df['a_id_sap'].isin(list(updated_rows['a_id_sap']))]
update['db_create_status'] = 0
create = df.loc[df['a_id_sap'].isin(list(new_rows['a_id_sap']))]
create['db_create_status'] = 1
df = pd.concat([update,create])
else :
df['db_create_status'] = 1
not_updated_rows = pd.DataFrame([])
# IDs existants pour les clés étrangères
domaines_in_db = set(Domaine.objects.values_list('pk', flat=True))
filieres_in_db = set(Filiere.objects.values_list('pk', flat=True))
formations_in_db = set(FormationEmploi.objects.values_list('pk', flat=True))
grades_in_db = set(Grade.objects.values_list('pk', flat=True))
fields_not_validated = [f.name for f in ModelType._meta.get_fields() if f.is_relation]
fields_not_validated_2 = [f.name for f in ModelType_2._meta.get_fields() if f.is_relation]
dict_create = {}
dict_update = {}
dict_up_to_date = {}
dict_create_2 = {}
set_dom = set()
set_fil = set()
counters = InsertionCounters()
annee_pam = PAM.objects.filter(pam_statut='PAM en cours')[0].pam_id
annee_pam_suivant = PAM.objects.filter(pam_statut='PAM A+1')[0].pam_id
def process_row(row):
pk = str(row[col_pk])
pk_2 = pk + str(annee_pam)
pk_3 = pk + str(annee_pam_suivant)
try:
domaine = row['a_domaine']
if domaine is not None and domaine not in domaines_in_db:
# TODO déjà fait dans l'extraction, serait mieux placé ici
logger.warning("%s[pk=%s] domaine ignoré car absent du référentiel : %s", ModelType.__name__, pk, domaine)
set_dom.add(domaine)
counters.ignored += 1
return
filiere = row['a_filiere']
if filiere is not None and filiere not in filieres_in_db:
# TODO déjà fait dans l'extraction, serait mieux placé ici
logger.warning("%s[pk=%s] filière ignoré car absente du référentiel : %s", ModelType.__name__, pk, filiere)
set_fil.add(filiere)
counters.ignored += 1
return
grade = row['grade_id']
if grade is not None and grade not in grades_in_db:
# TODO déjà fait dans l'extraction, serait mieux placé ici
logger.warning("%s[pk=%s] ignoré car grade inconnu : %s", ModelType.__name__, pk, grade)
counters.ignored += 1
return
formation = row['formation_emploi_id']
if formation is not None and formation not in formations_in_db:
# TODO déjà fait dans l'extraction, serait mieux placé ici
logger.warning("%s[pk=%s] ignoré car formation-emploi inconnue : %s", ModelType.__name__, pk, formation)
counters.ignored += 1
return
model_2 = ModelType_2(**{
'id' :pk_2,
'pam_id' :annee_pam,
'administre_id' :pk,
'a_statut_pam_annee' :StatutPam.NON_ETUDIE,
})
model_3 = ModelType_2(**{
'id' :pk_3,
'pam_id' :annee_pam_suivant,
'administre_id' :pk,
'a_statut_pam_annee' :StatutPam.NON_ETUDIE,
})
in_db = models_in_db.get(pk)
model = ModelType(**{
'pk': pk,
'formation_emploi_id': formation,
'grade_id': grade,
'a_grade_date_debut': row['a_grade_date_debut'],
'a_liste_id_marques': row['a_liste_id_marques'],
'a_nom': row['a_nom'],
'a_prenom': row['a_prenom'],
'a_sexe': row['a_sexe'],
'a_id_def': row['a_id_def'],
'a_eip': row['a_eip'],
'a_eip_fiche_detaille': row['a_eip_fiche_detaille'],
f'{Cols.REL_DOMAINE}_id': domaine,
'a_domaine_futur_id': domaine,
f'{Cols.REL_FILIERE}_id': filiere,
'a_filiere_futur_id': filiere,
'a_fonction': row['a_fonction'],
'a_code_fonction': row['a_code_fonction'],
'a_nf': row['a_nf'],
'a_nf_futur': row['a_nf'],
'a_categorie': row['a_categorie'],
'a_domaine_gestion': row['a_domaine_gestion'],
'a_date_entree_service': row['a_date_entree_service'],
'a_arme': row['a_arme'],
'a_rg_origine_recrutement': row['a_rg_origine_recrutement'],
'a_date_naissance': row['a_date_naissance'],
'a_diplome_hl': row['a_diplome_hl'],
'a_dernier_diplome': row['a_dernier_diplome'],
'a_credo_fe': row['a_credo_fe'],
'a_date_arrivee_fe': row['a_date_arrivee_fe'],
'a_date_pos_statuaire': row['a_date_pos_statuaire'],
'a_pos_statuaire': row['a_pos_statuaire'],
'a_interruption_service': row['a_interruption_service'],
'a_situation_fam': row['a_situation_fam'],
'a_date_mariage': row['a_date_mariage'],
'a_nombre_enfants': row['a_nombre_enfants'],
'a_enfants': row['a_enfants'],
'a_date_rdc': row['a_date_rdc'],
'a_date_dernier_acr': row['a_date_dernier_acr'],
'a_eis': row['a_eis'],
'a_sap_conjoint': row['a_sap_conjoint'],
'a_flag_particulier': row['a_flag_particulier'],
'a_notes_gestionnaire': row['a_notes_gestionnaire'],
'a_notes_partagees': row['a_notes_partagees'],
'a_fonction1': row['a_fonction1'],
'a_fonction2': row['a_fonction2'],
'a_fonction3': row['a_fonction3'],
'a_fonction4': row['a_fonction4'],
'a_fonction5': row['a_fonction5'],
'a_fonction6': row['a_fonction6'],
'a_fonction7': row['a_fonction7'],
'a_fonction8': row['a_fonction8'],
'a_fonction9': row['a_fonction9'],
'a_date_fonction1': row['a_date_fonction1'],
'a_date_fonction2': row['a_date_fonction2'],
'a_date_fonction3': row['a_date_fonction3'],
'a_date_fonction4': row['a_date_fonction4'],
'a_date_fonction5': row['a_date_fonction5'],
'a_date_fonction6': row['a_date_fonction6'],
'a_date_fonction7': row['a_date_fonction7'],
'a_date_fonction8': row['a_date_fonction8'],
'a_date_fonction9': row['a_date_fonction9'],
'a_pls_gb_max': row['a_pls_gb_max'],
'a_marqueur_pn': row['a_marqueur_pn'],
'a_profession_conjoint': row['a_profession_conjoint'],
'a_id_def_conjoint': row['a_id_def_conjoint'],
'a_sexe_conjoint': row['a_sexe_conjoint'],
'a_origine_recrutement': row['a_origine_recrutement'],
'a_lien_service': row['a_lien_service'],
'a_statut_concerto': row[Cols.STATUT_CONCERTO],
'a_date_statut_concerto': row[Cols.DATE_STATUT_CONCERTO],
'a_statut_concerto_futur': row[Cols.STATUT_CONCERTO_FUTUR],
'a_date_statut_concerto_futur': row[Cols.DATE_STATUT_CONCERTO_FUTUR],
'a_fud': row['a_fud'],
'a_date_fud': row['a_date_fud'],
})
if row['db_create_status']:
model_3.full_clean(exclude=fields_not_validated, validate_unique=False)
dict_create_2.setdefault(pk_3, model_3)
model_2.full_clean(exclude=fields_not_validated, validate_unique=False)
dict_create_2.setdefault(pk_2, model_2)
model.full_clean(exclude=fields_not_validated, validate_unique=False)
dict_create.setdefault(pk, model)
else:
if not ModelType_2.objects.filter(Q(id=pk_2)).exists():
model_2.full_clean(exclude=fields_not_validated_2, validate_unique=False)
dict_create_2.setdefault(pk_2, model_2)
if not ModelType_2.objects.filter(Q(id=pk_3)).exists():
model_3.full_clean(exclude=fields_not_validated, validate_unique=False)
dict_create_2.setdefault(pk_3, model_3)
model.full_clean(exclude=fields_not_validated, validate_unique=False)
dict_update.setdefault(pk, model)
except Exception:
counters.errors += 1
logger.exception('%s une erreur est survenue à la ligne : %s (pk=%s)', ModelType.__name__, row['index'], pk)
df['index'] = df.index
df.apply(process_row, axis=1)
if counters.errors:
logger.warning("%s | en erreur : %s", ModelType.__name__, counters.errors)
if counters.ignored:
logger.warning('%s | ignoré(s) : %s', ModelType.__name__, counters.ignored)
if set_dom:
logger.warning('%s %s(s) ignoré(s) : %s', len(set_dom), Domaine.__name__, set_dom)
if set_fil:
logger.warning('%s %s(s) ignorée(s) : %s', len(set_fil), Filiere.__name__, set_fil)
if not not_updated_rows.empty and fields_to_update:
logger.info('%s | déjà à jour : %s', ModelType.__name__, len(not_updated_rows))
batch_size = 50
if dict_create:
logger.info('%s | à créer : %s', ModelType.__name__, len(dict_create))
for idx, data_batch in enumerate(batch_iterator(list(dict_create.values()), batch_size)):
ModelType.objects.bulk_create(data_batch)
logger.debug('créé(s) : %s (lot %s)', len(data_batch), idx + 1)
logger.info('%s | créé(s) : %s', ModelType.__name__, len(dict_create))
else :
logger.info('%s | rien à créer', ModelType.__name__)
if dict_create_2:
logger.info('%s | à créer dans la vue A+1 : (%s)', ModelType_2.__name__, len(dict_create_2))
for idx, data_batch in enumerate(batch_iterator(list(dict_create_2.values()), batch_size)):
ModelType_2.objects.bulk_create(data_batch)
logger.debug('créé(s) : %s (lot %s)', len(data_batch), idx + 1)
logger.info('%s | créé(s) : %s', ModelType_2.__name__, len(dict_create_2))
if dict_update and fields_to_update:
logger.info('%s | à mettre à jour : %s', ModelType.__name__, len(dict_update))
for idx, data_batch in enumerate(batch_iterator(list(dict_update.values()), batch_size)):
ModelType.objects.bulk_update(data_batch, fields=fields_to_update)
logger.debug('mis à jour : %s (lot %s)', len(data_batch), idx + 1)
logger.info('%s | mis à jour : %s', ModelType.__name__, len(dict_update))
else :
logger.info('%s | rien à mettre à jour', ModelType.__name__)
adm_cree = len(dict_create) + len(dict_create_2)
adm_modifie = len(dict_update)
return adm_cree, adm_modifie, len(not_updated_rows), counters.errors, counters.ignored, set_dom, set_fil
# pas de suppression ici, il est prévu de faire un upload de fichier spécifique