1658 lines
75 KiB
Python
1658 lines
75 KiB
Python
import numpy as np
|
|
from asgiref.sync import sync_to_async
|
|
from django.db.models import ManyToOneRel, Window, F, Q, Sum, Case, When, IntegerField, Prefetch
|
|
from django.db.models.functions import Rank
|
|
from django.forms import model_to_dict
|
|
from django.http import JsonResponse
|
|
from django.utils import timezone
|
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
from rest_framework import generics, viewsets, serializers, status
|
|
from rest_framework import viewsets, status
|
|
from rest_framework.views import APIView
|
|
from rest_framework.response import Response
|
|
import pandas as pd
|
|
|
|
from . import constants
|
|
from django.db.models import Q
|
|
from .filters import AdministreFilter, RelatedOrderingFilter, PosteFilter
|
|
from .paginations import HeavyDataPagination
|
|
from .serializers import AdministreSerializer, FileCompetenceSerializer, FmobSerializer, FormationEmploiSerializer, \
|
|
PcpFeGroupeSerializer, \
|
|
PosteSerializer, \
|
|
NotationSerializer, MarqueSerializer, MarquesGroupeSerializer, FileSerializer, \
|
|
FiliereSerializer, DomaineSerializer, FileSVSerializer, DecisionSerializer, \
|
|
SousVivierAssociationSerializer, ScoringValidator, AlimentationReferentielSerializer
|
|
from .models import Administre, Competence, FMOB, FormationEmploi, PcpFeGroupe, Poste, Notation, \
|
|
PreferencesListe, Marque, MarquesGroupe, \
|
|
Filiere, Domaine, SousVivier, Grade, CustomUser, Decision, Calcul, SousVivierAssociation, \
|
|
StatutPamChoices as StatutPam
|
|
from .utils import cleanString, impact_decisions
|
|
from .utils_extraction import to_table_administres, to_table_domaines, to_table_fe, to_table_filieres, \
|
|
to_table_fmob, to_table_fonctions, to_table_garnisons, to_table_grades, \
|
|
to_table_fmob, to_table_fonctions, to_table_garnisons, to_table_grades, \
|
|
to_table_liste_preference, \
|
|
to_table_groupesMarques, to_table_marques, to_table_postes, process_files, open_excel
|
|
from .utils_insertion import insert_Administre, insert_Domaine, insert_FMOB, insert_Filiere, insert_Fonction, \
|
|
insert_FormationEmploi, insert_Garnison, insert_Grade, insert_MarquesGroupe, insert_Notation, \
|
|
insert_Marque, insert_Poste, insert_liste_preference, insert_matching
|
|
from .utils_matching import matching_parfait, preprocess_matching
|
|
from .utils_calcul import lancer_calculs, notations
|
|
from .reporting import poste_vacant_vivier, reporting_taux_armement_pcp, reporting_taux_armement_gestionnaire, \
|
|
reporting_suivi_pam_admin, \
|
|
reporting_suivi_pam_poste
|
|
import json
|
|
|
|
from django.shortcuts import render
|
|
from django.contrib.auth.decorators import login_required
|
|
|
|
|
|
from datetime import datetime, date
|
|
from django.conf import settings
|
|
from django.db.transaction import atomic
|
|
from django.http import Http404
|
|
from rest_framework import serializers, status
|
|
from rest_framework.exceptions import APIException
|
|
from typing import Optional, Tuple
|
|
import logging
|
|
import requests
|
|
import time
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def api_exception(status_code: int, message: Optional[str] = None) -> APIException:
|
|
return type('MyException', (APIException,), {'status_code': status_code, 'default_detail': message})
|
|
|
|
# Vue de lancement pour le scoring
|
|
# Calcul les scores des administrés à muter pour les postes à pourvoir et stocke les résultats dans la table Calcul
|
|
class ScoringView(APIView):
|
|
"""
|
|
Cette classe est dédiée au vue de lancement pour le scoring et au calcul des scores des administrés à muter avec les postes à pourvoir.
|
|
"""
|
|
def __recuperer_chemin_requete(self, request) -> str:
|
|
""" récupère et reconstruit le chemin de la requête (mais pas forcément tel que le client la connaît)
|
|
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant le port et l'adrresse ip du serveur
|
|
|
|
:return: - **return** (*string*): une chaine de caractère contenant le statut modifié du calcul.
|
|
|
|
"""
|
|
|
|
port = request.get_port()
|
|
return f"{request.scheme}://localhost{':' + port if port else ''}{request.path}"
|
|
|
|
|
|
def __appeler_api_depiler_calculs(self, request):
|
|
""" appelle l'API pour lancer le prochain calcul en attente, ce qui permet d'attribuer l'opération à un worker indéfini
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant le chemin de la requète
|
|
"""
|
|
url = self.__recuperer_chemin_requete(request)
|
|
headers = request.headers
|
|
cookies = request.COOKIES
|
|
try:
|
|
requests.patch(url, headers=request.headers, cookies=request.COOKIES, timeout=0.001)
|
|
except requests.exceptions.ReadTimeout:
|
|
pass
|
|
except Exception as e:
|
|
logger.debug("échec de l'appel à %s: %s", url, e)
|
|
|
|
@atomic
|
|
def __creer_calcul(self, sv_id: str) -> Calcul:
|
|
""" crée un calcul en base sauf s'il existe déjà un calcul en cours ou en attente pour le même sous-vivier
|
|
Raises
|
|
------
|
|
ApiException:
|
|
un calcul est déjà en cours ou en attente pour le même sous-vivier
|
|
IntegrityError:
|
|
la création a échoué
|
|
|
|
:type request: rest_framework.request.Request
|
|
:type sv_id: str
|
|
:param sv_id: clé primaire du sous vivier
|
|
:return: - **Object** (*Object*): retourne l'objet calcul crée
|
|
"""
|
|
|
|
Statut = Calcul.Statut
|
|
COL_ID = 'sous_vivier_id'
|
|
COL_STATUT = 'ca_statut'
|
|
COL_DEBUT = 'ca_date_debut'
|
|
|
|
colonnes = (COL_ID, COL_DEBUT, COL_STATUT)
|
|
df = pd.DataFrame.from_records(Calcul.objects.values_list(*colonnes), columns=colonnes)
|
|
|
|
en_cours = df[df[COL_STATUT] == Statut.EN_COURS][COL_ID].values
|
|
if sv_id in en_cours:
|
|
raise api_exception(status.HTTP_400_BAD_REQUEST, 'un calcul est déjà en cours pour ce sous-vivier')
|
|
|
|
en_attente = df[df[COL_STATUT] == Statut.EN_ATTENTE][COL_ID].values
|
|
if sv_id in en_attente:
|
|
raise api_exception(status.HTTP_400_BAD_REQUEST, 'un calcul est déjà en attente pour ce sous-vivier')
|
|
|
|
if en_attente.size:
|
|
calcul_autorise = False
|
|
else:
|
|
nb_max = getattr(settings, 'MAX_CALCULS', None)
|
|
calcul_autorise = not isinstance(nb_max, int) or len(en_cours) < nb_max
|
|
|
|
date_debut = timezone.now()
|
|
Calcul.objects.filter(Q(sous_vivier_id=sv_id) & ~Q(ca_statut__in=(Statut.EN_COURS, Statut.EN_ATTENTE))).delete()
|
|
if calcul_autorise:
|
|
return Calcul.objects.create(sous_vivier_id=sv_id, ca_date_debut=date_debut,ca_statut_pourcentage =0 , ca_statut=Statut.EN_COURS)
|
|
return Calcul.objects.create(sous_vivier_id=sv_id, ca_date_debut=date_debut,ca_statut_pourcentage =0 ,ca_statut=Statut.EN_ATTENTE)
|
|
|
|
|
|
@atomic
|
|
def __modifier_statut_premier_calcul_en_attente(self) -> Optional[ Tuple[str, datetime] ]:
|
|
""" lance le calcul en attente le plus ancien (si possible). Il ne s'agit que d'une modification en base.
|
|
(ID de sous-vivier, date de début) du calcul lancé ou None si aucun calcul ne peut être lancé
|
|
:type request: rest_framework.request.Request
|
|
:type sv_id: str
|
|
:param sv_id: clé primaire du sous vivier
|
|
:type ca_date_debut : date
|
|
:param ca_date_debut: date de début calcul en attente
|
|
:return: - **Object** (*Object*): retourne l'objet calcul crée
|
|
Raises
|
|
------
|
|
ApiException:
|
|
un calcul est déjà en cours ou en attente pour le même sous-vivier
|
|
IntegrityError:
|
|
la création a échoué
|
|
"""
|
|
|
|
Statut = Calcul.Statut
|
|
COL_ID = 'sous_vivier_id'
|
|
COL_STATUT = 'ca_statut'
|
|
COL_DEBUT = 'ca_date_debut'
|
|
|
|
colonnes = (COL_ID, COL_DEBUT, COL_STATUT)
|
|
df = pd.DataFrame.from_records(Calcul.objects.order_by(COL_DEBUT).values_list(*colonnes), columns=colonnes)
|
|
|
|
en_attente = df[df[COL_STATUT] == Statut.EN_ATTENTE]
|
|
if en_attente.empty:
|
|
return None
|
|
|
|
en_cours = df[df[COL_STATUT] == Statut.EN_COURS][COL_ID].values
|
|
nb_max = getattr(settings, 'MAX_CALCULS', None)
|
|
calcul_autorise = not isinstance(nb_max, int) or en_cours.size < nb_max
|
|
if not calcul_autorise:
|
|
return None
|
|
|
|
a_lancer = en_attente.iloc[0]
|
|
sv_id = a_lancer[COL_ID]
|
|
date_attente = a_lancer[COL_DEBUT]
|
|
date_debut = timezone.now()
|
|
|
|
# la date de début est mise à jour aussi pour éviter de compter le temps d'attente
|
|
mis_a_jour = (
|
|
Calcul.objects
|
|
.filter(sous_vivier_id=sv_id, ca_date_debut=date_attente, ca_statut=Statut.EN_ATTENTE)
|
|
.update(ca_date_debut=date_debut, ca_statut=Statut.EN_COURS)
|
|
)
|
|
return (sv_id, date_debut) if mis_a_jour else None
|
|
|
|
|
|
def __executer_calcul(self, sv_id: str, date_debut: datetime) -> Calcul:
|
|
""" exécute un calcul (ID de sous-vivier, date de début) du calcul lancé ou None si aucun calcul ne peut être lancé
|
|
:type request: rest_framework.request.Request
|
|
:type sv_id: str
|
|
:param sv_id: clé primaire du sous vivier
|
|
:type ca_date_debut : date
|
|
:param ca_date_debut: date de début calcul en attente
|
|
:return: - **Object** (*Object*): retourne l'objet calcul ainsi que son statut
|
|
"""
|
|
|
|
Statut = Calcul.Statut
|
|
COL_ID = 'sous_vivier_id'
|
|
COL_STATUT = 'ca_statut'
|
|
COL_DEBUT = 'ca_date_debut'
|
|
|
|
qs_calcul = Calcul.objects.filter(sous_vivier_id=sv_id, ca_date_debut=date_debut)
|
|
qs_en_cours = qs_calcul.filter(ca_statut=Statut.EN_COURS)
|
|
try:
|
|
lancer_calculs(sv_id)
|
|
except SystemExit:
|
|
logger.info("OGURE M'A TUER")
|
|
qs_en_cours.update(ca_date_fin=timezone.now(), ca_statut=Statut.TERMINE_DE_FORCE)
|
|
raise
|
|
except Exception as e:
|
|
qs_calcul = Calcul.objects.filter(sous_vivier_id=sv_id, ca_date_debut=date_debut)
|
|
if str(e) == "Arret du calcul":
|
|
qs_calcul.update(ca_date_fin=timezone.now(), ca_statut=Statut.ARRETER)
|
|
raise
|
|
else :
|
|
qs_calcul.update(ca_date_fin=timezone.now(), ca_statut=Statut.ERREUR)
|
|
raise
|
|
qs_en_cours.update(ca_date_fin=timezone.now(), ca_statut=Statut.TERMINE)
|
|
return qs_calcul.first()
|
|
|
|
|
|
def get(self, request):
|
|
"""La fonction get vérifie s'il y a un calcul en cours et renvoie des informations sur le calcul.
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant l'identifiant du sous vivier
|
|
:return: - **return** (*JsonResponse*): json contenant les informations sur le calcul comme statut, date_debut, date_fin, administres et postes.
|
|
"""
|
|
debut = None
|
|
fin = None
|
|
administres = 0
|
|
postes = 0
|
|
try:
|
|
sv_id = request.query_params['sous_vivier_id']
|
|
calcul = Calcul.objects.get(sous_vivier_id=sv_id)
|
|
debut = calcul.ca_date_debut
|
|
fin = calcul.ca_date_fin
|
|
administres_statuts = ['A_MUTER']
|
|
administres = Administre.objects.filter(sous_vivier_id=sv_id, a_statut_pam__in=administres_statuts).count()
|
|
postes = Poste.objects.filter(Q(sous_vivier_id=sv_id) & (
|
|
Q(p_nb_p1__gt=0) | Q(p_nb_p2__gt=0) | Q(p_nb_p3__gt=0) | Q(p_nb_p4__gt=0) | Q(
|
|
p_nb_vacant__gt=0))).count()
|
|
statut = calcul.ca_statut
|
|
statut_pourcentage = calcul.ca_statut_pourcentage
|
|
except:
|
|
statut = "AUCUN"
|
|
statut_pourcentage = 0
|
|
|
|
return JsonResponse(
|
|
{"statut": statut, "statut_pourcentage": statut_pourcentage, "date_debut": debut, "date_fin": fin, "administres": administres, "postes": postes})
|
|
|
|
def patch(self, request, id=None):
|
|
"""La fonction patch est une méthode privée qui execute des méthodes privées qui exécute le calcul et le dépile.
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant l'identifiant du sous vivier et la date de dbut du calcul
|
|
"""
|
|
Statut = Calcul.Statut
|
|
try:
|
|
id_et_debut = self.__modifier_statut_premier_calcul_en_attente()
|
|
if id_et_debut is None:
|
|
return Response({'message': "aucun calcul n'a été lancé"})
|
|
|
|
try:
|
|
calcul = self.__executer_calcul(id_et_debut[0], id_et_debut[1])
|
|
return Response({'statut': calcul.ca_statut if calcul else Statut.AUCUN})
|
|
finally:
|
|
self.__appeler_api_depiler_calculs(request)
|
|
except (Http404, APIException):
|
|
raise
|
|
except SystemExit:
|
|
raise api_exception(status.HTTP_500_INTERNAL_SERVER_ERROR, 'lancement du prochain calcul arrêté')
|
|
except:
|
|
message = 'impossible de lancer le prochain calcul en attente'
|
|
logger.exception(message)
|
|
raise api_exception(status.HTTP_500_INTERNAL_SERVER_ERROR, message)
|
|
|
|
|
|
def post(self, request):
|
|
""" lance le calcul sur un sous vivier
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant l'identifiant du sous vivier
|
|
:type sv_id: str
|
|
:param sv_id: clé primaire du sous vivier
|
|
:return: - **return** (*Response*): retourne le statut des postes
|
|
"""
|
|
Statut = Calcul.Statut
|
|
try:
|
|
# validation
|
|
validator = ScoringValidator(data=request.data)
|
|
validator.is_valid(raise_exception=True)
|
|
sv_id = validator.validated_data.get('sous_vivier_id')
|
|
generics.get_object_or_404(SousVivier.objects, sv_id=sv_id)
|
|
|
|
# calcul
|
|
calcul = self.__creer_calcul(sv_id)
|
|
if calcul.ca_statut == Statut.EN_COURS:
|
|
try:
|
|
calcul = self.__executer_calcul(sv_id, calcul.ca_date_debut)
|
|
finally:
|
|
self.__appeler_api_depiler_calculs(request)
|
|
|
|
return Response({'statut': calcul.ca_statut if calcul else Statut.AUCUN})
|
|
except (Http404, APIException):
|
|
raise
|
|
except SystemExit:
|
|
raise api_exception(status.HTTP_500_INTERNAL_SERVER_ERROR, 'calcul arrêté')
|
|
except:
|
|
message = 'impossible de lancer le calcul'
|
|
logger.exception(message)
|
|
raise api_exception(status.HTTP_500_INTERNAL_SERVER_ERROR, message)
|
|
|
|
class ArretCalul(APIView):
|
|
"""
|
|
Cette classe est dédiée a l'arret du calcul.
|
|
"""
|
|
def post(self, request):
|
|
"""La fonction post change le statut d'un calcul à EN_ATTENTE_ARRET
|
|
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant l'identifiant du sous vivier
|
|
|
|
:return: - **return** (*json*): json contenant le statut modifié du calcul.
|
|
"""
|
|
sv_id = request.data['sous_vivier_id']
|
|
calcul = Calcul.objects.get(sous_vivier_id=sv_id)
|
|
if Calcul.objects.filter(sous_vivier_id=sv_id).exists() and calcul.ca_statut == 'EN_COURS':
|
|
calcul.ca_statut = 'EN_ATTENTE_ARRET'
|
|
calcul.save()
|
|
elif Calcul.objects.filter(sous_vivier_id=sv_id).exists() and calcul.ca_statut == 'EN_ATTENTE':
|
|
calcul.ca_statut = 'ARRETER'
|
|
calcul.save()
|
|
return JsonResponse(
|
|
{"statut": calcul.ca_statut})
|
|
|
|
# Vue pour la fiche détaillée des administrés
|
|
class FicheDetailleeView(APIView):
|
|
|
|
"""
|
|
Cette classe est dédiée au vue de la fiche détaillée des administrés
|
|
"""
|
|
def get(self, request):
|
|
"""La fonction get recupére les infos administrés
|
|
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant l'identifiant de l'administré
|
|
|
|
:return: - **Response** (*objet*): informations concernant l'administré, informations sur le formulaire de mobilité.
|
|
"""
|
|
res = {"result": "error"}
|
|
if 'administre_id' in request.query_params:
|
|
administre_id = request.query_params['administre_id']
|
|
try:
|
|
administre = Administre.objects.get(a_id_sap=administre_id)
|
|
res = model_to_dict(administre)
|
|
|
|
fmobs = FMOB.objects.filter(administre_id=administre_id).order_by('-fmob_millesime')
|
|
|
|
res["fmob"] = None
|
|
|
|
if fmobs.exists():
|
|
fmob_dict = model_to_dict(fmobs[0])
|
|
res["fmob"] = fmob_dict
|
|
|
|
res["fe"] = None
|
|
fe = list(FormationEmploi.objects.filter(fe_code=administre.formation_emploi_id).values(
|
|
*('fe_code', 'garnison__gar_code_postal', 'garnison__gar_lieu', 'fe_libelle')))
|
|
# if fe.exists():
|
|
# fe = fe[0]
|
|
# garnison_dict = model_to_dict()
|
|
# res["garnison"] = garnison_dict
|
|
if len(fe):
|
|
res["fe"] = fe[0]
|
|
res["conjoint"] = None
|
|
conjoint = Administre.objects.filter(a_id_sap=administre.a_sap_conjoint)
|
|
if conjoint.exists():
|
|
print('has conjoint')
|
|
conjoint_dict = model_to_dict(conjoint[0])
|
|
conjoint_dict["fe"] = None
|
|
fe_conjoint = list(FormationEmploi.objects.filter(fe_code=conjoint[0].formation_emploi_id).values(
|
|
*('fe_code', 'garnison__gar_code_postal', 'garnison__gar_lieu', 'fe_libelle')))
|
|
if len(fe):
|
|
conjoint_dict["fe"] = fe_conjoint[0]
|
|
res["conjoint"] = conjoint_dict
|
|
|
|
except Exception as e:
|
|
print(e)
|
|
print(e.args)
|
|
print(res)
|
|
# TODO: return error
|
|
|
|
return Response(res)
|
|
|
|
# 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 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
|
|
|
|
"""
|
|
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"})
|
|
|
|
|
|
# Vue pour afficher le nombre de postes vacants par fe et sous-vivier
|
|
class PosteVacantView(APIView):
|
|
"""
|
|
Cette classe est dédiée au vue pour afficher le nombre de postes vacants par fe et sous-vivier
|
|
"""
|
|
|
|
def get(self, request):
|
|
"""La fonction get renvoie les postes vaccants
|
|
|
|
:type request: rest_framework.request.Request
|
|
:param request: Requset contenant l'identifiant de la formation emploi et l'identifiant du sous-vivier.
|
|
|
|
|
|
:return: - **return** (*json*): json contenant le nombre de poste vaccant du sous vivier.
|
|
"""
|
|
|
|
fe_id = request.query_params["fe_id"]
|
|
sv_id = request.query_params["sv_id"]
|
|
nb_poste_vacant_vivier = poste_vacant_vivier(sv_id, fe_id)
|
|
return JsonResponse({'nb_poste_vacant_vivier': nb_poste_vacant_vivier}, safe=False)
|
|
|
|
|
|
# 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 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
|
|
"""
|
|
|
|
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)
|
|
print(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"]))
|
|
print(model_to_dict(sous_vivier))
|
|
sous_vivier.save()
|
|
|
|
print(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)
|
|
Poste.objects.filter(p_categorie=categorie, p_filiere_id=filiere.f_code).update(
|
|
sous_vivier_id=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})
|
|
|
|
|
|
# Vue de chargement des competences :
|
|
# - Charge et traite les fichiers de compétences
|
|
# - Attribue les compétences présentes aux administrés et postes correspondants
|
|
class ChargementCompetenceView(APIView):
|
|
"""
|
|
Cette classe est dédiée au vue de chargement des competences. Charge et traite les fichiers de compétences.
|
|
Attribue les compétences présentes aux administrés et postes correspondants
|
|
|
|
"""
|
|
serializer_class = FileCompetenceSerializer
|
|
|
|
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 competences.
|
|
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant le fichier de competence
|
|
|
|
:return: - **Response** (*Response*): Reponse contient un message de la réussite pour charger la compétence .
|
|
"""
|
|
serializer = FileCompetenceSerializer(data=request.data)
|
|
|
|
if not serializer.is_valid():
|
|
return Response(
|
|
data=serializer.errors,
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
competence_file_v1 = request.data['competence_v1']
|
|
competence_file_v2 = request.data['competence_v2']
|
|
competence_poste_v1 = open_excel(competence_file_v1, sheetname='Feuil1', engine='openpyxl')
|
|
competence_poste_v1.drop_duplicates(subset=['Macro compétence (0 à n par officier)'], inplace=True)
|
|
competence_mili = open_excel(competence_file_v2, sheetname="MILITAIRE", engine='openpyxl')
|
|
competence_mili.reset_index(drop=True, inplace=True)
|
|
competence_poste = open_excel(competence_file_v2, sheetname='REO', engine='openpyxl')
|
|
competence_poste.dropna(subset=['COMPETENCE 1'], inplace=True)
|
|
competence_poste.reset_index(drop=True, inplace=True)
|
|
|
|
print("Preprocessing OK")
|
|
|
|
# Ajout des compétences du fichier version 1, dans la table Compétence
|
|
for i in range(len(competence_poste_v1)):
|
|
comp = Competence(comp_id=cleanString(competence_poste_v1.at[i, 'Macro compétence (0 à n par officier)']),
|
|
comp_libelle=competence_poste_v1.at[i, 'Macro compétence (0 à n par officier)'])
|
|
comp.save()
|
|
|
|
print("Ajout des compétences du fichier 1 dans la table Competence --> OK")
|
|
|
|
# Ajout des compétences du fichier version 2, dans la table Compétence
|
|
competence_mili_1 = competence_mili[['COMPETENCE 1']].drop_duplicates().reset_index(drop=True)
|
|
competence_mili_2 = competence_mili[['COMPETENCE 2']].drop_duplicates().reset_index(drop=True).rename(
|
|
columns={'COMPETENCE 2': "COMPETENCE 1"})
|
|
competence_mili_3 = competence_mili[['COMPETENCE 2.1']].drop_duplicates().reset_index(drop=True).rename(
|
|
columns={'COMPETENCE 2.1': "COMPETENCE 1"})
|
|
competence_poste_1 = competence_poste[['COMPETENCE 1']].drop_duplicates(subset=["COMPETENCE 1"]).reset_index(
|
|
drop=True)
|
|
total_competences = pd.concat([competence_mili_1, competence_mili_2, competence_mili_3,
|
|
competence_poste_1]).drop_duplicates().reset_index(drop=True)
|
|
total_competences.dropna(inplace=True)
|
|
total_competences.reset_index(drop=True, inplace=True)
|
|
|
|
for i in range(len(total_competences)):
|
|
competence = Competence(comp_id=cleanString(total_competences.at[i, "COMPETENCE 1"]),
|
|
comp_libelle=total_competences.at[i, 'COMPETENCE 1'])
|
|
competence.save()
|
|
|
|
print('Ajout des compétences du fichier 2 dans la table Competence --> OK')
|
|
|
|
# Ajout des compétences du fichier 2 dans la table Poste
|
|
print(competence_poste)
|
|
for i in range(len(competence_poste)):
|
|
postes = Poste.objects.filter(p_code_fonction=competence_poste.at[i, 'CODE FONCTION'],
|
|
formation_emploi_id=competence_poste.at[i, 'CODE FE'],
|
|
p_domaine=competence_poste.at[i, 'DOM EIP'],
|
|
p_filiere=competence_poste.at[i, "FIL EIP"],
|
|
p_nf=competence_poste.at[i, 'NR EIP'])
|
|
|
|
postes.update(competence_id=cleanString(competence_poste.at[i, "COMPETENCE 1"]))
|
|
print('Ajout des compétences du fichier 2 dans la table Poste --> OK')
|
|
# Ajout des compétences du fichier 2 dans la table d'association Administrés et Compétences
|
|
liste_admin = []
|
|
for i in range(len(competence_mili)):
|
|
sap = competence_mili.at[i, 'N SAP']
|
|
admin_competences = ""
|
|
|
|
if not pd.isna(competence_mili.at[i, 'COMPETENCE 1']):
|
|
ca1_id = cleanString(competence_mili.at[i, 'COMPETENCE 1'])
|
|
admin_competences = admin_competences + ca1_id + ","
|
|
|
|
if not pd.isna(competence_mili.at[i, 'COMPETENCE 2']):
|
|
ca2_id = cleanString(competence_mili.at[i, 'COMPETENCE 2'])
|
|
admin_competences = admin_competences + ca2_id + ","
|
|
|
|
if not pd.isna(competence_mili.at[i, 'COMPETENCE 2.1']):
|
|
ca3_id = cleanString(competence_mili.at[i, 'COMPETENCE 2.1'])
|
|
admin_competences = admin_competences + ca3_id + ","
|
|
|
|
liste_admin.append(Administre(a_id_sap=sap, a_liste_id_competences=admin_competences))
|
|
|
|
Administre.objects.bulk_update(liste_admin, fields=['a_liste_id_competences'])
|
|
print('Ajout des compétences du fichier 2 dans la table CompetenceAdministre --> OK')
|
|
|
|
return JsonResponse({"msg": "Chargement competence ok"})
|
|
|
|
|
|
class CurrentUserView(APIView):
|
|
"""
|
|
Cette classe est dédiée au vue de l'utilisateur courant:
|
|
"""
|
|
|
|
def get(self, request):
|
|
"""La fonction get verifie si l'utilisateur est authentifié
|
|
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant l'identifiant de l'utilisateur
|
|
|
|
|
|
:return: - **Response** (*json*): json contenant l'identité et le role de l'utilisateur. En plus de la formation emploi group et le sous-vivier specifique à cet utilisateur.
|
|
"""
|
|
if request.user.is_authenticated:
|
|
current_user = CustomUser.objects.get(id=request.user.id)
|
|
try:
|
|
administre = model_to_dict(current_user.administre)
|
|
except:
|
|
administre = None
|
|
current_user = model_to_dict(current_user, fields=[field.name for field in CustomUser._meta.get_fields() if
|
|
field.name not in ["password", "logentry", "groups",
|
|
"user_permissions"]])
|
|
current_user['sous_viviers'] = list(SousVivier.objects.filter(gestionnaire_id=request.user.id).values())
|
|
current_user['identite'] = administre
|
|
try:
|
|
pcpFeGroupe = model_to_dict(PcpFeGroupe.objects.get(gestionnaire_id=request.user.id))
|
|
except:
|
|
pcpFeGroupe = None
|
|
|
|
current_user['fe_groupe'] = pcpFeGroupe
|
|
current_user['role'] = "ADMINISTRATEUR" if request.user.is_superuser else "GESTIONNAIRE"
|
|
return Response(current_user)
|
|
return Response({"msg": "Authentification requise"})
|
|
|
|
|
|
class ReferencesView(APIView):
|
|
|
|
"""
|
|
Cette classe est dédiée au vue de la reference.
|
|
"""
|
|
def get(self, request):
|
|
"""
|
|
La fonction get envoie les references
|
|
|
|
:return: - **JsonReponse** (*json*): json contenant les groupes marques, les marques, les competences, les domaines et les filieres.
|
|
"""
|
|
groupesMarques = [obj.as_dict() for obj in MarquesGroupe.objects.all().order_by('gm_code')]
|
|
marques = [obj.as_dict() for obj in Marque.objects.all().order_by('mar_code')]
|
|
competences = [obj.as_dict() for obj in Competence.objects.all().order_by('comp_id')]
|
|
domaines = [obj.as_dict() for obj in Domaine.objects.all().order_by('d_code')]
|
|
filieres = [obj.as_dict() for obj in Filiere.objects.all().order_by('domaine__d_code', 'f_code')]
|
|
# TODO: Vérifier la présence de l'ordre sur les postes'
|
|
grades = [obj.as_dict() for obj in Grade.objects.all()]
|
|
FEs = [obj.as_dict() for obj in FormationEmploi.objects.all().order_by('fe_code')]
|
|
|
|
return JsonResponse(
|
|
{"groupesMarques": groupesMarques, "marques": marques,
|
|
"domaines": domaines, "filieres": filieres,
|
|
"grades": grades, "competences": competences, "FEs": FEs}, safe=False)
|
|
|
|
|
|
# Vue pour l'alimentation et l'update en BDD :
|
|
# - Charge et traite les fichiers de données
|
|
# - Remplit la base de données pour les modèles Domaines, Filieres, Grade, Fonction, Garnison, FormationEmploi, Marque, MarquesGroupe, Administre, Poste, FMOB,
|
|
class AlimentationView(APIView):
|
|
"""
|
|
Cette classe est dédiée au vue de l'alimentation et l'update en BDD. Charge et traite les fichiers de données et remplit la base de données pour les modèles Domaines, Filieres, Grade, Fonction, Garnison, FormationEmploi, Marque, MarquesGroupe, Administre, Poste, FMOB.
|
|
"""
|
|
serializer_class = FileSerializer
|
|
|
|
def get(self, request):
|
|
"""La fonction get renvoie une reponse contenant Formulaire d'alimentation d'OGURE NG
|
|
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request
|
|
|
|
|
|
:return: - **return** (*json*): json contenant "Formulaire d'alimentation d'OGURE NG".
|
|
"""
|
|
return Response("Formulaire d'alimentation d'OGURE NG")
|
|
|
|
def post(self, request):
|
|
"""La fonction post charge les fichiers.
|
|
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant les fichiers
|
|
|
|
:return: - **Response** (*Response*): Reponse contient les erreurs de chargement des données .
|
|
"""
|
|
# TODO: préparer une variable qui renvoie les erreurs sous 3 axes : données référentielles, administrés, postes
|
|
serializer = FileSerializer(data=request.data)
|
|
if not serializer.is_valid():
|
|
return Response(
|
|
data=serializer.errors,
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
logger.info('---------------------Upload begining---------------------------')
|
|
start_time = time.time()
|
|
|
|
file_donnees_bo = request.data['Donnees_BO_ADT']
|
|
file_zpropaf = request.data['ZPROPAF']
|
|
file_reo = request.data['REO']
|
|
file_fmob = request.data['FMOB']
|
|
file_filiere_domaine = request.data['domaine_filiere']
|
|
file_insee = request.data['insee_maping']
|
|
file_diplomes = request.data['diplomes']
|
|
# file_suivi_pam = request.data['suivi_pam']
|
|
|
|
logger.info('---------------------Upload ending-----------------------------')
|
|
logger.debug("--------------upload time -- %d seconds ------------------------", time.time() - start_time)
|
|
|
|
# ETL from xlsx source : Ordre : domaines, filieres, garnisons, fonctions, grades, fe, groupesMarques, marques, gestionnaires, administres, flag_pam_admnistres, postes, fmob, listePreferences
|
|
filiere_domaine_df, donnees_bo_df, zpropaf_df, reo_df, fmob_df, femp_df, insee_df, diplomes_df = process_files(
|
|
file_filiere_domaine,
|
|
file_donnees_bo,
|
|
file_zpropaf,
|
|
file_reo,
|
|
file_fmob,
|
|
file_insee,
|
|
file_diplomes)
|
|
|
|
logger.info('---------------------Insert beginning---------------------------')
|
|
start_time_insert = time.time()
|
|
|
|
insert_Domaine(to_table_domaines(filiere_domaine_df))
|
|
logger.debug('1. Domaine -----------> Success')
|
|
insert_Garnison(to_table_garnisons(insee_df, donnees_bo_df))
|
|
logger.debug('2. Garnison ----------> Success')
|
|
insert_Fonction(to_table_fonctions(reo_df))
|
|
logger.debug('3. Fonction ----------> Success')
|
|
insert_Grade()
|
|
logger.debug('4. Grade -------------> Success')
|
|
insert_Filiere(to_table_filieres(filiere_domaine_df))
|
|
logger.debug('5. Filières -----------> Success')
|
|
insert_MarquesGroupe(to_table_groupesMarques())
|
|
logger.debug('6. MarquesGroupe -----> Success')
|
|
insert_Marque(to_table_marques())
|
|
logger.debug('7. Marque ------------> Success')
|
|
insert_FormationEmploi(to_table_fe(reo_df, donnees_bo_df))
|
|
logger.debug('8. FormationEmplois ---> Success')
|
|
administres_list, administres_error = insert_Administre(
|
|
to_table_administres(donnees_bo_df, zpropaf_df, insee_df, diplomes_df))
|
|
logger.debug('9. Administre --------> Success')
|
|
|
|
postes_list = insert_Poste(to_table_postes(reo_df))
|
|
logger.debug('10. Poste --------------> Success')
|
|
|
|
logger.debug('time: %d s', time.time() - start_time_insert)
|
|
fmob_error = insert_FMOB(to_table_fmob(fmob_df, femp_df, zpropaf_df))
|
|
logger.debug('11. Fmob ----------------> Success')
|
|
logger.info('---------------------Insert ending -----------------------------')
|
|
|
|
logger.info('-------------------- Début des mises à jour --------------------')
|
|
# administre : le statut PAM par défaut est A_TRAITER s'il y a un FMOB
|
|
(Administre.objects
|
|
.filter(a_statut_pam=StatutPam.A_ETUDIER, fmobs__isnull=False)
|
|
.update(a_statut_pam=StatutPam.A_TRAITER))
|
|
logger.info('--------------------- Fin des mises à jour ---------------------')
|
|
|
|
logger.debug("------------------insert time -- %d seconds --------------------", time.time() - start_time_insert)
|
|
# # result = {"administres_error": administres_error, "fmob_error": fmob_error}
|
|
# result = {"administres_error": "", "fmob_error": fmob_error}
|
|
return Response({"administres_error": ""})
|
|
|
|
|
|
class AlimentationReferentielView(APIView):
|
|
""" Vue pour alimenter la base à partir de référentiels """
|
|
|
|
serializer_class = AlimentationReferentielSerializer
|
|
|
|
def get(self, request):
|
|
return Response("Formulaire d'alimentation d'OGURE NG (référentiel")
|
|
|
|
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 = AlimentationReferentielSerializer(data=request.data)
|
|
validator.is_valid(raise_exception=True)
|
|
|
|
# récupération des fichiers
|
|
referentiel_fe = request.data.get('referentiel_fe', None)
|
|
|
|
if referentiel_fe:
|
|
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'
|
|
col_mere_avant = 'FE mère CREDO'
|
|
col_zone_def_avant = 'Zone de Défense'
|
|
|
|
Cols = FormationEmploi.Cols
|
|
col_pk_apres = Cols.PK
|
|
col_mere_apres = Cols.REL_MERE
|
|
col_zone_def_apres = Cols.ZONE_DEFENSE
|
|
|
|
return (df[[col_pk_avant, col_mere_avant, col_zone_def_avant]]
|
|
.drop_duplicates(subset=col_pk_avant, keep='first')
|
|
.rename(columns={
|
|
col_pk_avant: col_pk_apres,
|
|
col_mere_avant: col_mere_apres,
|
|
col_zone_def_avant: col_zone_def_apres
|
|
}))
|
|
|
|
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_create = {}
|
|
dict_update = {}
|
|
for rec in df.to_dict('records'):
|
|
pk = str(rec.get(Cols.PK, None))
|
|
id_mere = rec.get(Cols.REL_MERE, None)
|
|
mere = modeles_en_base.get(str(id_mere), None) if id_mere else None
|
|
zone_defense = rec.get(Cols.ZONE_DEFENSE, None)
|
|
|
|
en_base = modeles_en_base.get(pk, None)
|
|
if not en_base or (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_create if not en_base else dict_update).setdefault(pk, modele)
|
|
|
|
# TODO pas de création pour l'instant (ni de suppression)
|
|
# if dict_create:
|
|
# FormationEmploi.objects.bulk_create(dict_create.values(), batch_size=taille_batch)
|
|
if dict_update and champs_maj:
|
|
FormationEmploi.objects.bulk_update(dict_update.values(), batch_size=taille_batch, fields=champs_maj)
|
|
logger.debug(f"MAJ: %s", len(dict_update))
|
|
|
|
df_referentiel_fe = process_referentiel_fe(referentiel_fe)
|
|
df_referentiel_fe = convertir_fe(df_referentiel_fe)
|
|
mettre_a_jour_fe(df_referentiel_fe)
|
|
|
|
return Response({'message':'OK'})
|
|
except (Http404, APIException):
|
|
raise
|
|
except:
|
|
message = "impossible d'alimenter le référentiel"
|
|
logger.exception(message)
|
|
raise api_exception(status.HTTP_500_INTERNAL_SERVER_ERROR, message)
|
|
|
|
# Vue API des administrés
|
|
# Pagination OK
|
|
# Ordering OK
|
|
# Search NOK
|
|
# Filtering NOK
|
|
|
|
class AdministreView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue de l'administre.
|
|
"""
|
|
serializer_class = AdministreSerializer
|
|
|
|
filter_backends = [DjangoFilterBackend, RelatedOrderingFilter]
|
|
# filterset_fields = ['category', 'in_stock']
|
|
filterset_class = AdministreFilter
|
|
ordering_fields = 'a_id_sap'
|
|
ordering = ['a_id_sap']
|
|
pagination_class = HeavyDataPagination
|
|
|
|
# important : mettre à jour quand le serializer change
|
|
def get_queryset(self):
|
|
Cols = Administre.Cols
|
|
return Administre.objects.select_related(
|
|
Cols.REL_FONCTION, Cols.REL_GRADE,
|
|
).prefetch_related(
|
|
Cols.M2M_COMPETENCES,
|
|
Cols.O2M_FMOB,
|
|
Prefetch(Cols.REL_DECISION, queryset=Decision.objects.select_related('poste__formation_emploi__garnison')),
|
|
Prefetch(Cols.REL_FORMATION_EMPLOI, queryset=FormationEmploi.objects.select_related(FormationEmploi.Cols.REL_GARNISON, FormationEmploi.Cols.REL_MERE)),
|
|
Prefetch(Cols.REL_SOUS_VIVIER, queryset=SousVivier.objects.select_related(SousVivier.Cols.REL_GESTIONNAIRE)),
|
|
)
|
|
|
|
@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
|
|
|
|
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
|
|
|
|
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]
|
|
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.'
|
|
print(erreur)
|
|
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: # 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'
|
|
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:
|
|
logger.debug("Mise à jour administré")
|
|
req_data = request.data
|
|
|
|
# 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.'
|
|
print(erreur)
|
|
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("")
|
|
|
|
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é"
|
|
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 PosteView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue des postes.
|
|
"""
|
|
lookup_value_regex = r"[\w.]+"
|
|
serializer_class = PosteSerializer
|
|
pagination_class = HeavyDataPagination
|
|
filter_backends = [DjangoFilterBackend, RelatedOrderingFilter]
|
|
# filterset_fields = ['category', 'in_stock']
|
|
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
|
|
return Poste.objects.select_related(
|
|
Cols.REL_FONCTION
|
|
).prefetch_related(
|
|
Cols.M2M_COMPETENCES,
|
|
Cols.O2M_DECISION,
|
|
Prefetch(Cols.REL_FORMATION_EMPLOI, queryset=FormationEmploi.objects.select_related(FormationEmploi.Cols.REL_GARNISON, FormationEmploi.Cols.REL_MERE)),
|
|
Prefetch(Cols.REL_SOUS_VIVIER, queryset=SousVivier.objects.select_related(SousVivier.Cols.REL_GESTIONNAIRE)),
|
|
).annotate(
|
|
p_nb_prepositionne=Sum(
|
|
Case(When(decisions__de_decision=constants.DECISION_PREPOSITIONNE, then=1), default=0,
|
|
output_field=IntegerField())),
|
|
p_nb_positionne=Sum(Case(When(decisions__de_decision=constants.DECISION_POSITIONNE, then=1), default=0,
|
|
output_field=IntegerField())),
|
|
p_nb_omi_en_cours=Sum(Case(When(decisions__de_decision=constants.DECISION_OMI_EN_COURS, then=1), default=0,
|
|
output_field=IntegerField())),
|
|
p_nb_omi_active=Sum(Case(When(decisions__de_decision=constants.DECISION_OMI_ACTIVE, then=1), default=0,
|
|
output_field=IntegerField())),
|
|
)
|
|
|
|
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
|
|
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' ]
|
|
objs = [Poste(**data[i]) for i in range(len(data))]
|
|
|
|
except:
|
|
raise Exception("bulk update error")
|
|
|
|
Poste.objects.bulk_update(objs, fields=fields)
|
|
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 .
|
|
"""
|
|
poste = request.data
|
|
fields = [key for key in poste.keys() if key != 'p_id']# and key != 'competence']
|
|
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("")
|
|
else:
|
|
copy = { k: v for k, v in poste.items() if k != 'competence'}
|
|
pos = Poste(**copy)
|
|
|
|
if fields:
|
|
Poste.objects.bulk_update([pos], fields=fields)
|
|
|
|
|
|
# copy = { k: v for k, v in poste.items() if k != 'competence'}
|
|
# pos = Poste(**copy)
|
|
|
|
# if fields:
|
|
# Poste.objects.bulk_update([copy], fields=fields)
|
|
|
|
# if 'competence' in poste.items():
|
|
# pos.competence.set(poste['competence'].split(','))
|
|
# pos.save()
|
|
|
|
|
|
return Response({'msg': 'updated'})
|
|
|
|
|
|
class NotationView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue des notations.
|
|
"""
|
|
serializer_class = NotationSerializer
|
|
queryset = Notation.objects.all()
|
|
pagination_class = HeavyDataPagination
|
|
|
|
def list(self, request, pk=None):
|
|
"""La fonction list envoie le classement des postes pour un administré particulier et inversement.
|
|
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant l'administre ou le poste.
|
|
|
|
:return: - **JsonResponse** (*JsonResponse*): Json contenant le classement.
|
|
"""
|
|
notations_list = []
|
|
q = 'poste'
|
|
if 'administre_id' in request.query_params:
|
|
administre_id = request.query_params['administre_id']
|
|
administres_keys = (
|
|
'poste__p_id', 'poste__p_nf', 'poste__p_domaine', 'poste__p_filiere', 'poste__p_eip',
|
|
'poste__formation_emploi__fe_code', 'poste__competences',
|
|
'poste__p_notes_gestionnaire', 'poste__p_liste_id_marques', 'poste__p_code_fonction',
|
|
'poste__p_avis',
|
|
'poste__p_nb_p1',
|
|
'poste__p_nb_p2',
|
|
'poste__p_nb_p3',
|
|
'poste__p_nb_p4',
|
|
'poste__p_dep',
|
|
'poste__formation_emploi__fe_libelle', 'poste__competences', 'poste__p_fonction',
|
|
'poste__formation_emploi__garnison',
|
|
'poste__p_notes_gestionnaire', 'poste__p_liste_id_marques',
|
|
'poste__formation_emploi__garnison__gar_lieu', 'no_score_administre',
|
|
'no_flag_cple_ideal')
|
|
|
|
counter = 10
|
|
|
|
notation_qs = Notation.objects.filter(administre_id=administre_id).order_by('-no_score_administre')[
|
|
:counter].select_related('poste')
|
|
|
|
notation_qs_matching_parfait = Notation.objects.filter(administre_id=administre_id,
|
|
no_flag_cple_ideal=True).select_related('poste')
|
|
|
|
notation_qs.union(notation_qs_matching_parfait)
|
|
notation_qs = notation_qs | notation_qs_matching_parfait
|
|
notations_list = list(notation_qs.values(*administres_keys))
|
|
|
|
for notation in notations_list:
|
|
poste_id = notation['poste__p_id']
|
|
topCounter = 0
|
|
allNotationsInvolved = Notation.objects.filter(poste_id=poste_id)
|
|
for note in allNotationsInvolved:
|
|
topList = list(
|
|
Notation.objects.filter(no_id=note.no_id).order_by('-no_score_administre')[:10].values(
|
|
'poste_id'))
|
|
topPostes = [poste['poste_id'] for poste in topList]
|
|
if poste_id in topPostes:
|
|
topCounter += 1
|
|
|
|
notation['poste__nb_top'] = topCounter
|
|
|
|
if 'poste_id' in request.query_params:
|
|
q = "administre"
|
|
poste_id = request.query_params['poste_id']
|
|
poste_unit = Poste.objects.get(p_id=poste_id)
|
|
try:
|
|
counter = poste_unit.p_nb_p1 + poste_unit.p_nb_p2 + poste_unit.p_nb_p3 + poste_unit.p_nb_p4
|
|
if counter < 10:
|
|
counter = 10
|
|
except:
|
|
counter = 10
|
|
postes_keys = (
|
|
'administre__a_id_sap', 'administre__a_nom', 'administre__a_prenom', 'administre__a_statut_pam',
|
|
'administre__grade_id', 'administre__a_liste_id_marques', 'administre__decision__de_decision',
|
|
'administre__decision__de_date_decision', 'no_score_administre', 'no_flag_cple_ideal', 'administre__a_notes_gestionnaire',
|
|
'administre__a_fonction', 'administre__a_code_fonction',
|
|
'administre__a_liste_id_marques',
|
|
'administre__a_liste_id_competences',
|
|
'administre__decision__poste_id')
|
|
notation_qs = Notation.objects.filter(poste_id=poste_id).order_by('-no_score_administre')[
|
|
:counter].select_related(
|
|
'administre')
|
|
notation_qs_matching_parfait = Notation.objects.filter(poste_id=poste_id,
|
|
no_flag_cple_ideal=True).select_related('administre')
|
|
notation_qs.union(notation_qs_matching_parfait)
|
|
notations_list = list(
|
|
notation_qs.values(*postes_keys))
|
|
|
|
for notation in notations_list:
|
|
administre_id = notation['administre__a_id_sap']
|
|
topCounter = 0
|
|
allNotationsInvolved = Notation.objects.filter(administre_id=administre_id)
|
|
for note in allNotationsInvolved:
|
|
topList = list(
|
|
Notation.objects.filter(no_id=note.no_id).order_by('-no_score_administre')[:10].values(
|
|
'administre_id'))
|
|
topAdministres = [administre['administre_id'] for administre in topList]
|
|
if administre_id in topAdministres:
|
|
topCounter += 1
|
|
|
|
notation['administre__nb_top'] = topCounter
|
|
|
|
result = []
|
|
for notation in notations_list:
|
|
res_notation = {q: {}}
|
|
for key in notation:
|
|
if (q + "__") in key:
|
|
res_notation[q][key.replace(q + '__', '')] = notation[key]
|
|
else:
|
|
res_notation[key] = notation[key]
|
|
if q == "poste":
|
|
res_notation[q]['p_nb_prepositionne'] = Decision.objects.filter(
|
|
de_decision=constants.DECISION_PREPOSITIONNE).count()
|
|
res_notation[q]['p_nb_positionne'] = Decision.objects.filter(
|
|
de_decision=constants.DECISION_POSITIONNE).count()
|
|
res_notation[q]['p_nb_omi_active'] = Decision.objects.filter(
|
|
de_decision=constants.DECISION_OMI_ACTIVE).count()
|
|
res_notation[q]['p_nb_omi_en_cours'] = Decision.objects.filter(
|
|
de_decision=constants.DECISION_OMI_EN_COURS).count()
|
|
result.append(res_notation)
|
|
return Response(result)
|
|
|
|
|
|
class DecisionView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue des decisions.
|
|
"""
|
|
serializer_class = DecisionSerializer
|
|
queryset = Decision.objects.all()
|
|
|
|
def list(self, request, pk=None):
|
|
"""Cette fonction envoie les informations du poste lié à l'administre dans une décision avec le score de l'administre et inversement.
|
|
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant l'administre ou le poste.
|
|
|
|
:return: - **res_decision** (*JsonResponse*): Json contenant le classement.
|
|
"""
|
|
decision = []
|
|
q = 'poste'
|
|
administre_id = None
|
|
poste_id = None
|
|
if 'administre_id' in request.query_params:
|
|
administre_id = request.query_params['administre_id']
|
|
administres_keys = (
|
|
'poste__p_id', 'poste__p_nf', 'poste__p_domaine', 'poste__p_filiere',
|
|
'poste__p_eip','poste__p_avis',
|
|
'poste__formation_emploi__fe_code', 'poste__competences',
|
|
'poste__p_notes_gestionnaire', 'poste__p_liste_id_marques',
|
|
'poste__formation_emploi__fe_libelle', 'poste__formation_emploi__garnison__gar_lieu',
|
|
'poste__p_dep', 'poste__p_code_fonction', 'poste__competences',
|
|
'poste__p_fonction',
|
|
# 'poste__decision__de_decision',
|
|
'poste__p_nb_p1',
|
|
'poste__p_nb_p2',
|
|
'poste__p_nb_p3',
|
|
'poste__p_nb_p4',
|
|
'poste__p_notes_gestionnaire', 'poste__p_liste_id_marques',
|
|
'de_decision', 'de_date_decision', 'de_notes_gestionnaire')
|
|
decision = Decision.objects.filter(administre_id=administre_id).select_related('poste')
|
|
decision = list(decision.values(*administres_keys))
|
|
|
|
if 'poste_id' in request.query_params:
|
|
q = "administre"
|
|
poste_id = request.query_params['poste_id']
|
|
postes_keys = (
|
|
'administre__a_id_sap', 'administre__a_nom', 'administre__a_prenom', 'administre__a_statut_pam',
|
|
'administre__a_fonction', 'administre__a_code_fonction', 'administre__a_liste_id_competences',
|
|
'administre__grade_id', 'administre__a_liste_id_marques', 'administre__decision__de_decision',
|
|
'administre__decision__de_date_decision', 'de_decision', 'de_date_decision',
|
|
'de_notes_gestionnaire', 'administre__a_notes_gestionnaire', 'administre__a_liste_id_competences',
|
|
'administre__decision__poste_id')
|
|
|
|
decision = Decision.objects.filter(poste_id=poste_id).select_related('administre')
|
|
|
|
decision = list(decision.values(*postes_keys))
|
|
res_decision = []
|
|
if len(decision) > 0:
|
|
for k in range(len(decision)):
|
|
decision_unit = decision[k]
|
|
try:
|
|
administre_id = decision_unit.administre_id
|
|
except:
|
|
administre_id = None
|
|
|
|
try:
|
|
poste_id = decision_unit.poste_id
|
|
except:
|
|
poste_id = None
|
|
|
|
try:
|
|
res_decision_unit = {q: {}, 'no_score_administre': Notation.objects.get(
|
|
administre_id=administre_id,
|
|
poste_id=poste_id).no_score_administre}
|
|
except:
|
|
res_decision_unit = {q: {}, 'no_score_administre': None}
|
|
for key in decision_unit:
|
|
if (q + "__") in key:
|
|
res_decision_unit[q][key.replace(q + '__', '')] = decision_unit[key]
|
|
else:
|
|
res_decision_unit[key] = decision_unit[key]
|
|
# Ajout du relevé des décisions sur le poste (cas q = "poste")
|
|
if q == "poste":
|
|
res_decision_unit[q]['p_nb_prepositionne'] = Decision.objects.filter(
|
|
de_decision=constants.DECISION_PREPOSITIONNE).count()
|
|
res_decision_unit[q]['p_nb_positionne'] = Decision.objects.filter(
|
|
de_decision=constants.DECISION_POSITIONNE).count()
|
|
res_decision_unit[q]['p_nb_omi_active'] = Decision.objects.filter(
|
|
de_decision=constants.DECISION_OMI_ACTIVE).count()
|
|
res_decision_unit[q]['p_nb_omi_en_cours'] = Decision.objects.filter(
|
|
de_decision=constants.DECISION_OMI_EN_COURS).count()
|
|
res_decision.append(res_decision_unit)
|
|
return Response(res_decision)
|
|
|
|
def create(self, request): # équivalent post
|
|
"""Cette fonction crée une decision.
|
|
|
|
:type request: rest_framework.request.Request
|
|
:param request: Request contenant l'administre, le poste et la decision.
|
|
|
|
:return: - **response** (*JsonResponse*): Json contenant le message "Décision changée".
|
|
"""
|
|
result = {}
|
|
# TODO : try except
|
|
administre_id = request.data['administre_id']
|
|
poste_id = request.data['poste_id']
|
|
de_decision = request.data['de_decision']
|
|
decision = Decision(administre_id=administre_id, poste_id=poste_id)
|
|
administre_decision = Decision.objects.filter(administre_id=administre_id)
|
|
# Lorsqu'on crée une décision on supprime toute décision précédente sur l'administré
|
|
# poste_decision = Decision.objects.filter(poste_id=poste_id)
|
|
if administre_decision.exists():
|
|
administre_decision.delete()
|
|
|
|
decision.de_decision = de_decision
|
|
decision.de_date_decision = timezone.now()
|
|
decision.save()
|
|
|
|
poste = Poste.objects.get(p_id=poste_id)
|
|
poste.p_nb_vacant = poste.p_nb_vacant - 1
|
|
poste.save()
|
|
|
|
response = JsonResponse({'status': 'success', 'message': 'Décision changée'})
|
|
|
|
return response
|
|
|
|
|
|
class FormationEmploiView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue des FormationEmplois.
|
|
"""
|
|
serializer_class = FormationEmploiSerializer
|
|
|
|
# important : mettre à jour quand le serializer change
|
|
def get_queryset(self):
|
|
Cols = FormationEmploi.Cols
|
|
return FormationEmploi.objects.select_related(Cols.REL_GARNISON, Cols.REL_MERE)
|
|
|
|
|
|
class ListesPreferencesView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue des ListesPreferences.
|
|
"""
|
|
serializer_class = PreferencesListe
|
|
queryset = PreferencesListe.objects.all()
|
|
|
|
|
|
class MarqueView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue des marques.
|
|
"""
|
|
serializer_class = MarqueSerializer
|
|
queryset = Marque.objects.all()
|
|
|
|
|
|
class MarquesGroupeView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue des marquegroupes.
|
|
"""
|
|
serializer_class = MarquesGroupeSerializer
|
|
queryset = MarquesGroupe.objects.all()
|
|
|
|
|
|
class FmobView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue des Fmob.
|
|
"""
|
|
serializer_class = FmobSerializer
|
|
queryset = FMOB.objects.all()
|
|
|
|
|
|
class FiliereView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue des filieres.
|
|
"""
|
|
serializer_class = FiliereSerializer
|
|
queryset = Filiere.objects.all()
|
|
|
|
|
|
class DomaineView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue des domaines.
|
|
"""
|
|
serializer_class = DomaineSerializer
|
|
queryset = Domaine.objects.all()
|
|
|
|
|
|
class SousVivierAssociationView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue du SousVivierAssociation.
|
|
"""
|
|
serializer_class = SousVivierAssociationSerializer
|
|
queryset = SousVivierAssociation.objects.all()
|
|
|
|
|
|
class PcpFeGroupeView(viewsets.ModelViewSet):
|
|
"""
|
|
Cette classe est dédiée au vue du PcpFeGroupe.
|
|
"""
|
|
serializer_class = PcpFeGroupeSerializer
|
|
queryset = PcpFeGroupe.objects.all()
|