forked from ScoDoc/ScoDoc
Compare commits
62 Commits
79b79ddc76
...
fd597b87d4
Author | SHA1 | Date | |
---|---|---|---|
|
fd597b87d4 | ||
|
b383c378f6 | ||
dcddec1ee6 | |||
664fa4eeb3 | |||
ca3336e9be | |||
bae33cf86b | |||
2c98f47d49 | |||
64eccde6bc | |||
7f80b18990 | |||
a9f7980000 | |||
b596a3820c | |||
7ab130499b | |||
59b94c9a45 | |||
75f8c3b500 | |||
9212f72581 | |||
b4f5634f2b | |||
14aabab746 | |||
adbe466392 | |||
ee8cfc6191 | |||
eb5b8d69da | |||
16f97b17be | |||
27c6f18c29 | |||
545c04968f | |||
13a8184601 | |||
b653cb0218 | |||
a52bd6e7fe | |||
97d306d9d0 | |||
98c3a7f740 | |||
34bbfec443 | |||
4fea9701cb | |||
557c5eec4f | |||
dd9351ca6e | |||
41254e06da | |||
bc6c220f70 | |||
|
9ad21c6a38 | ||
|
3b0a42435b | ||
94e13d0c1d | |||
a06854eb12 | |||
5ba99a1b9e | |||
24b6d1f4cc | |||
d4d84fcb1e | |||
44893acb4a | |||
0fe10a383e | |||
f63c1fadb8 | |||
2e6e7675bf | |||
eb288fa6a1 | |||
2dbaacf460 | |||
21cfe4ebf8 | |||
0275054510 | |||
|
dab0f78279 | ||
954a8c8e81 | |||
edd9fde616 | |||
37832c5bb1 | |||
77eefbe483 | |||
73b46b11dc | |||
8d64bf3b87 | |||
9af424d356 | |||
efe48582bf | |||
a3cbea5dcd | |||
dc0212b725 | |||
c52bb4e31c | |||
5f5c43ec08 |
@ -5,11 +5,16 @@ from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.auth import token_auth
|
||||
from app.api.errors import error_response
|
||||
from app.decorators import permission_required
|
||||
from app.api.auth import token_auth, token_permission_required
|
||||
from app.scodoc.sco_abs import add_absence, add_justif, annule_absence, annule_justif, list_abs_date
|
||||
from app.api.auth import token_permission_required
|
||||
from app.api.tools import get_etu_from_request
|
||||
from app.scodoc.sco_abs import (
|
||||
add_absence,
|
||||
add_justif,
|
||||
annule_absence,
|
||||
annule_justif,
|
||||
list_abs_date,
|
||||
)
|
||||
from app.scodoc.sco_groups import get_group_members
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
@ -17,7 +22,6 @@ from app.scodoc.sco_permissions import Permission
|
||||
@bp.route("/absences/etudid/<int:etudid>", methods=["GET"])
|
||||
@bp.route("/absences/nip/<int:nip>", methods=["GET"])
|
||||
@bp.route("/absences/ine/<int:ine>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def absences(etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
@ -27,35 +31,34 @@ def absences(etudid: int = None, nip: int = None, ine: int = None):
|
||||
nip: le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
"""
|
||||
abs = None
|
||||
if etudid is not None: # Si route etudid
|
||||
if etudid is None:
|
||||
# Récupération de l'étudiant
|
||||
try:
|
||||
etu = get_etu_from_request(etudid, nip, ine)
|
||||
etudid = etu.etudid
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
|
||||
)
|
||||
|
||||
# Récupération des absences de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etudid).all()
|
||||
else:
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
# Récupération des absences de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etu.etudid).all()
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
# Récupération des absences de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etu.etudid).all()
|
||||
|
||||
if abs is not None: # Si des absences ont bien été trouvé
|
||||
# Si l'étudiant a au minimum une absence
|
||||
if len(abs) > 0:
|
||||
# Mise en forme des données
|
||||
data = [d.to_dict() for d in abs]
|
||||
|
||||
return jsonify(data)
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
return error_response(200, message="Aucune absence trouvé pour cet étudiant")
|
||||
|
||||
|
||||
@bp.route("/absences/etudid/<int:etudid>/abs_just_only", methods=["GET"])
|
||||
@bp.route("/absences/nip/<int:nip>/abs_just_only", methods=["GET"])
|
||||
@bp.route("/absences/ine/<int:ine>/abs_just_only", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def absences_justify(etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
@ -65,43 +68,65 @@ def absences_justify(etudid: int = None, nip: int = None, ine: int = None):
|
||||
nip: le code nip d'un étudiant
|
||||
ine : le code ine d'un étudiant
|
||||
"""
|
||||
abs = None
|
||||
if etudid is not None: # Si route etudid
|
||||
if etudid is None:
|
||||
# Récupération de l'étudiant
|
||||
try:
|
||||
etu = get_etu_from_request(etudid, nip, ine)
|
||||
etudid = etu.etudid
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
|
||||
)
|
||||
|
||||
# Récupération des absences justifiées de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etudid, estjust=True).all()
|
||||
else:
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
# Récupération des absences justifiées de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etu.etudid, estjust=True).all()
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
# Récupération des absences justifiées de l'étudiant
|
||||
abs = models.Absence.query.filter_by(etudid=etu.etudid, estjust=True).all()
|
||||
|
||||
if abs is not None: # Si des absences ont bien été trouvé
|
||||
# Si l'étudiant a au minimum une absence justifiées
|
||||
if len(abs) > 0:
|
||||
# Mise en forme des données
|
||||
data = [d.to_dict() for d in abs]
|
||||
|
||||
return jsonify(data)
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
return error_response(
|
||||
200, message="Aucune absence justifié trouvé pour cet étudiant"
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/absences/abs_signale?etudid=<int:etudid>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>", methods=["POST"])
|
||||
@bp.route("/absences/abs_signale?nip=<int:nip>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>", methods=["POST"])
|
||||
@bp.route("/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>", methods=["POST"])
|
||||
@bp.route("/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>&moduleimpl_id=<int:moduleimpl_id>", methods=["POST"])
|
||||
@token_auth.login_required
|
||||
### Inutil en définitif ###
|
||||
@bp.route(
|
||||
"/absences/abs_signale?etudid=<int:etudid>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@bp.route(
|
||||
"/absences/abs_signale?nip=<int:nip>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@bp.route(
|
||||
"/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@bp.route(
|
||||
"/absences/abs_signale?ine=<int:ine>&date=<string:date>&matin=<string:matin>&justif=<string:justif>"
|
||||
"&description=<string:description>&moduleimpl_id=<int:moduleimpl_id>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@token_permission_required(Permission.APIAbsChange)
|
||||
def abs_signale(date: datetime, matin: bool, justif: bool, etudid: int = None, nip: int = None, ine: int = None,
|
||||
description: str = None, moduleimpl_id: int = None):
|
||||
def abs_signale(
|
||||
date: datetime,
|
||||
matin: bool,
|
||||
justif: bool,
|
||||
etudid: int = None,
|
||||
nip: int = None,
|
||||
ine: int = None, ### Inutil en définitif
|
||||
description: str = None,
|
||||
moduleimpl_id: int = None,
|
||||
):
|
||||
"""
|
||||
Permet d'ajouter une absence en base
|
||||
|
||||
@ -116,114 +141,45 @@ def abs_signale(date: datetime, matin: bool, justif: bool, etudid: int = None, n
|
||||
"""
|
||||
# Fonctions utilisées : app.scodoc.sco_abs.add_absence() et app.scodoc.sco_abs.add_justif()
|
||||
|
||||
if description is not None: # Si la description a été renseignée
|
||||
if moduleimpl_id is not None: # Si le moduleimpl a été renseigné
|
||||
if etudid is not None: # Si route etudid
|
||||
if etudid is None:
|
||||
# Récupération de l'étudiant
|
||||
try:
|
||||
etu = get_etu_from_request(etudid, nip, ine)
|
||||
etudid = etu.etudid
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
|
||||
)
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etudid, date, matin, justif, description, moduleimpl_id)
|
||||
if justif == True:
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif, description, moduleimpl_id)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif, description, moduleimpl_id)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
else: # Si le moduleimpl n'a pas été renseigné
|
||||
if etudid is not None: # Si route etudid
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etudid, date, matin, justif, description)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif, description)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif, description)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin, description)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
else:
|
||||
if etudid is not None: # Si route etudid
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etudid, date, matin, justif)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etudid, date, matin)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
try:
|
||||
# Utilisation de la fonction add_absence
|
||||
add_absence(etu.etudid, date, matin, justif)
|
||||
# Utilisation de la fonction add_justif
|
||||
add_justif(etu.etudid, date, matin)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
return error_response(200, message="OK")
|
||||
return error_response(
|
||||
409, message="La requête ne peut être traitée en l’état actuel"
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/absences/abs_annule?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@bp.route("/absences/abs_annule?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@bp.route("/absences/abs_annule?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@token_auth.login_required
|
||||
@bp.route(
|
||||
"/absences/abs_annule?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@bp.route(
|
||||
"/absences/abs_annule?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@bp.route(
|
||||
"/absences/abs_annule?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@token_permission_required(Permission.APIAbsChange)
|
||||
def abs_annule(jour: datetime, matin: str, etudid: int = None, nip: int = None, ine: int = None):
|
||||
def abs_annule(
|
||||
jour: datetime, matin: str, etudid: int = None, nip: int = None, ine: int = None
|
||||
):
|
||||
"""
|
||||
Retourne un html
|
||||
|
||||
@ -236,32 +192,46 @@ def abs_annule(jour: datetime, matin: str, etudid: int = None, nip: int = None,
|
||||
# Fonction utilisée : app.scodoc.sco_abs.annule_absence()
|
||||
|
||||
if etudid is None:
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
# Récupération de l'etudid de l'étudiant
|
||||
try:
|
||||
etu = get_etu_from_request(etudid, nip, ine)
|
||||
etudid = etu.etudid
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
|
||||
)
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
# Récupération de l'etudid de l'étudiant
|
||||
etudid = etu.etudid
|
||||
try:
|
||||
# Utilisation de la fonction annule_absence
|
||||
annule_absence(etudid, jour, matin)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que le 'jour' et le 'matin' sont valides",
|
||||
)
|
||||
|
||||
return error_response(200, message="OK")
|
||||
|
||||
|
||||
@bp.route("/absences/abs_annule_justif?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@bp.route("/absences/abs_annule_justif?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@bp.route("/absences/abs_annule_justif?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>", methods=["POST"])
|
||||
@token_auth.login_required
|
||||
@bp.route(
|
||||
"/absences/abs_annule_justif?etudid=<int:etudid>&jour=<string:jour>&matin=<string:matin>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@bp.route(
|
||||
"/absences/abs_annule_justif?nip=<int:nip>&jour=<string:jour>&matin=<string:matin>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@bp.route(
|
||||
"/absences/abs_annule_justif?ine=<int:ine>&jour=<string:jour>&matin=<string:matin>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@token_permission_required(Permission.APIAbsChange)
|
||||
def abs_annule_justif(jour: datetime, matin: str, etudid: int = None, nip: int = None, ine: int = None):
|
||||
def abs_annule_justif(
|
||||
jour: datetime, matin: str, etudid: int = None, nip: int = None, ine: int = None
|
||||
):
|
||||
"""
|
||||
Retourne un html
|
||||
|
||||
@ -274,30 +244,37 @@ def abs_annule_justif(jour: datetime, matin: str, etudid: int = None, nip: int =
|
||||
# Fonction utilisée : app.scodoc.sco_abs.annule_justif()
|
||||
|
||||
if etudid is None:
|
||||
if nip is not None: # Si route nip
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_nip=nip).first()
|
||||
# Récupération de l'etudid de l'étudiant
|
||||
etudid = etu.etudid
|
||||
|
||||
if ine is not None: # Si route ine
|
||||
# Récupération de l'étudiant
|
||||
etu = models.Identite.query.filter_by(code_ine=ine).first()
|
||||
# Récupération de l'etudid de l'étudiant
|
||||
try:
|
||||
etu = get_etu_from_request(etudid, nip, ine)
|
||||
etudid = etu.etudid
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
|
||||
)
|
||||
try:
|
||||
# Utilisation de la fonction annule_justif
|
||||
annule_justif(etudid, jour, matin)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que le 'jour' et le 'matin' sont valides",
|
||||
)
|
||||
|
||||
return error_response(200, message="OK")
|
||||
|
||||
|
||||
@bp.route("/absences/abs_group_etat/?group_id=<int:group_id>&date_debut=date_debut&date_fin=date_fin", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@bp.route(
|
||||
"/absences/abs_group_etat/?group_id=<int:group_id>&date_debut=date_debut&date_fin=date_fin",
|
||||
methods=["GET"],
|
||||
)
|
||||
@token_permission_required(Permission.APIView)
|
||||
def abs_groupe_etat(group_id: int, date_debut, date_fin, with_boursier=True, format="html"):
|
||||
def abs_groupe_etat(
|
||||
group_id: int, date_debut, date_fin, with_boursier=True, format="html"
|
||||
):
|
||||
"""
|
||||
Retoune la liste des absences d'un ou plusieurs groupes entre deux dates
|
||||
"""
|
||||
@ -307,7 +284,9 @@ def abs_groupe_etat(group_id: int, date_debut, date_fin, with_boursier=True, for
|
||||
# Utilisation de la fonction get_group_members
|
||||
members = get_group_members(group_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
return error_response(
|
||||
409, message="La requête ne peut être traitée en l’état actuel"
|
||||
)
|
||||
|
||||
data = []
|
||||
# Filtre entre les deux dates renseignées
|
||||
|
@ -1,6 +1,5 @@
|
||||
############################################### Departements ##########################################################
|
||||
import app
|
||||
from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
@ -8,9 +7,10 @@ from app.api.auth import token_auth, token_permission_required
|
||||
from app.api.errors import error_response
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
from flask import jsonify
|
||||
|
||||
|
||||
@bp.route("/departements", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def departements():
|
||||
"""
|
||||
@ -31,7 +31,6 @@ def departements():
|
||||
@bp.route(
|
||||
"/departements/<string:dept>/etudiants/liste/<int:formsemestre_id>", methods=["GET"]
|
||||
)
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def liste_etudiants(dept: str, formsemestre_id=None):
|
||||
"""
|
||||
@ -87,7 +86,6 @@ def liste_etudiants(dept: str, formsemestre_id=None):
|
||||
|
||||
|
||||
@bp.route("/departements/<string:dept>/semestres_courants", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def liste_semestres_courant(dept: str):
|
||||
"""
|
||||
@ -150,7 +148,6 @@ def liste_semestres_courant(dept: str):
|
||||
"/departements/<string:dept>/formations/<int:formation_id>/referentiel_competences",
|
||||
methods=["GET"],
|
||||
)
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def referenciel_competences(dept: str, formation_id: int):
|
||||
"""
|
||||
@ -185,7 +182,6 @@ def referenciel_competences(dept: str, formation_id: int):
|
||||
"/departements/<string:dept>/formsemestre/<string:formsemestre_id>/programme",
|
||||
methods=["GET"],
|
||||
)
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def semestre_index(dept: str, formsemestre_id: int):
|
||||
"""
|
||||
|
@ -13,9 +13,7 @@ from app.scodoc.sco_groups import get_etud_groups
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
|
||||
@bp.route("/etudiants", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def etudiants():
|
||||
"""
|
||||
@ -56,7 +54,6 @@ def etudiants():
|
||||
|
||||
|
||||
@bp.route("/etudiants/courant", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def etudiants_courant():
|
||||
"""
|
||||
@ -102,7 +99,6 @@ def etudiants_courant():
|
||||
@bp.route("/etudiant/etudid/<int:etudid>", methods=["GET"])
|
||||
@bp.route("/etudiant/nip/<int:nip>", methods=["GET"])
|
||||
@bp.route("/etudiant/ine/<int:ine>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def etudiant(etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
@ -137,7 +133,6 @@ def etudiant(etudid: int = None, nip: int = None, ine: int = None):
|
||||
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
||||
@bp.route("/etudiant/nip/<int:nip>/formsemestres")
|
||||
@bp.route("/etudiant/ine/<int:ine>/formsemestres")
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
@ -182,39 +177,46 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
||||
...
|
||||
]
|
||||
"""
|
||||
# Récupération de toutes les inscriptions
|
||||
inscriptions = models.FormSemestreInscription.query.all()
|
||||
|
||||
# Récupération de l'étudiant
|
||||
etu = get_etu_from_request(etudid, nip, ine)
|
||||
|
||||
sems = []
|
||||
# Filtre les inscriptions contenant l'étudiant
|
||||
for sem in inscriptions:
|
||||
if sem.etudid == etu.etudid:
|
||||
sems.append(sem)
|
||||
# Récupération de toutes les inscriptions de l'étudiant
|
||||
formsemestres_inscriptions = models.FormSemestreInscription.query.filter_by(
|
||||
etud=etu
|
||||
).all()
|
||||
|
||||
formsemestres = []
|
||||
|
||||
# Filtre les formsemestre contenant les inscriptions de l'étudiant
|
||||
for sem in sems: #data_inscriptions:
|
||||
res = models.FormSemestre.query.filter_by(id=sem.formsemestre_id).first()
|
||||
# Récupération des formsemestres contenant les inscriptions de l'étudiant
|
||||
for formsemestre_inscription in formsemestres_inscriptions:
|
||||
res = models.FormSemestre.query.filter_by(
|
||||
id=formsemestre_inscription.formsemestre_id
|
||||
).first()
|
||||
formsemestres.append(res)
|
||||
|
||||
data = []
|
||||
# Mise en forme des données
|
||||
for formsem in formsemestres:
|
||||
data.append(formsem.to_dict())
|
||||
for formsemestre in formsemestres:
|
||||
data.append(formsemestre.to_dict())
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin", methods=["GET"])
|
||||
@bp.route("/etudiant/nip/<int:nip>/formsemestre/<int:formsemestre_id>/bulletin", methods=["GET"])
|
||||
@bp.route("/etudiant/ine/<int:ine>/formsemestre/<int:formsemestre_id>/bulletin", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@bp.route(
|
||||
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/bulletin",
|
||||
methods=["GET"],
|
||||
)
|
||||
@bp.route(
|
||||
"/etudiant/nip/<int:nip>/formsemestre/<int:formsemestre_id>/bulletin",
|
||||
methods=["GET"],
|
||||
)
|
||||
@bp.route(
|
||||
"/etudiant/ine/<int:ine>/formsemestre/<int:formsemestre_id>/bulletin",
|
||||
methods=["GET"],
|
||||
)
|
||||
@token_permission_required(Permission.APIView)
|
||||
def etudiant_bulletin_semestre(formsemestre_id, etudid: int = None, nip: int = None, ine: int = None):
|
||||
def etudiant_bulletin_semestre(
|
||||
formsemestre_id, etudid: int = None, nip: int = None, ine: int = None
|
||||
):
|
||||
"""
|
||||
Retourne le bulletin d'un étudiant en fonction de son id et d'un semestre donné
|
||||
|
||||
@ -233,18 +235,33 @@ def etudiant_bulletin_semestre(formsemestre_id, etudid: int = None, nip: int = N
|
||||
|
||||
if etudid is None:
|
||||
# Récupération de l'étudiant
|
||||
try:
|
||||
etu = get_etu_from_request(etudid, nip, ine)
|
||||
etudid = etu.etudid
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
|
||||
)
|
||||
|
||||
return make_json_formsemestre_bulletinetud(formsemestre_id, etudid)
|
||||
|
||||
|
||||
@bp.route("/etudiant/etudid/<int:etudid>/semestre/<int:formsemestre_id>/groups", methods=["GET"])
|
||||
@bp.route("/etudiant/nip/<int:nip>/semestre/<int:formsemestre_id>/groups", methods=["GET"])
|
||||
@bp.route("/etudiant/ine/<int:ine>/semestre/<int:formsemestre_id>/groups", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@bp.route(
|
||||
"/etudiant/etudid/<int:etudid>/semestre/<int:formsemestre_id>/groups",
|
||||
methods=["GET"],
|
||||
)
|
||||
@bp.route(
|
||||
"/etudiant/nip/<int:nip>/semestre/<int:formsemestre_id>/groups", methods=["GET"]
|
||||
)
|
||||
@bp.route(
|
||||
"/etudiant/ine/<int:ine>/semestre/<int:formsemestre_id>/groups", methods=["GET"]
|
||||
)
|
||||
@token_permission_required(Permission.APIView)
|
||||
def etudiant_groups(formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None):
|
||||
def etudiant_groups(
|
||||
formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None
|
||||
):
|
||||
"""
|
||||
Retourne la liste des groupes auxquels appartient l'étudiant dans le semestre indiqué
|
||||
|
||||
@ -283,16 +300,27 @@ def etudiant_groups(formsemestre_id: int, etudid: int = None, nip: int = None, i
|
||||
|
||||
if etudid is None:
|
||||
# Récupération de l'étudiant
|
||||
try:
|
||||
etu = get_etu_from_request(etudid, nip, ine)
|
||||
etudid = etu.etudid
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
|
||||
)
|
||||
|
||||
# Récupération du formsemestre
|
||||
sem = models.FormSemestre.query.filter_by(id=formsemestre_id).first()
|
||||
#breakpoint()
|
||||
try:
|
||||
# Utilisation de la fonction get_etud_groups
|
||||
data = get_etud_groups(etudid, sem.id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide"
|
||||
" et le formsemestre_id sont valides",
|
||||
)
|
||||
|
||||
return jsonify(data)
|
||||
|
@ -3,14 +3,13 @@ from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.auth import token_auth, token_permission_required
|
||||
from app.api.auth import token_permission_required
|
||||
from app.api.errors import error_response
|
||||
from app.scodoc.sco_evaluation_db import do_evaluation_get_all_notes
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/evaluations/<int:moduleimpl_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def evaluations(moduleimpl_id: int):
|
||||
"""
|
||||
@ -29,7 +28,6 @@ def evaluations(moduleimpl_id: int):
|
||||
|
||||
|
||||
@bp.route("/evaluations/eval_notes/<int:evaluation_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def evaluation_notes(evaluation_id: int):
|
||||
"""
|
||||
@ -42,18 +40,32 @@ def evaluation_notes(evaluation_id: int):
|
||||
try:
|
||||
# Utilisation de la fonction do_evaluation_get_all_notes
|
||||
data = do_evaluation_get_all_notes(evaluation_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel. \n"
|
||||
"Veillez vérifier la conformité du 'evaluation_id'",
|
||||
)
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/evaluations/eval_set_notes?eval_id=<int:eval_id>&etudid=<int:etudid>¬e=<float:note>", methods=["POST"])
|
||||
@bp.route("/evaluations/eval_set_notes?eval_id=<int:eval_id>&nip=<int:nip>¬e=<float:note>", methods=["POST"])
|
||||
@bp.route("/evaluations/eval_set_notes?eval_id=<int:eval_id>&ine=<int:ine>¬e=<float:note>", methods=["POST"])
|
||||
@token_auth.login_required
|
||||
@bp.route(
|
||||
"/evaluations/eval_set_notes?eval_id=<int:eval_id>&etudid=<int:etudid>¬e=<float:note>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@bp.route(
|
||||
"/evaluations/eval_set_notes?eval_id=<int:eval_id>&nip=<int:nip>¬e=<float:note>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@bp.route(
|
||||
"/evaluations/eval_set_notes?eval_id=<int:eval_id>&ine=<int:ine>¬e=<float:note>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@token_permission_required(Permission.APIEditAllNotes)
|
||||
def evaluation_set_notes(eval_id: int, note: float, etudid: int = None, nip: int = None, ine: int = None):
|
||||
def evaluation_set_notes(
|
||||
eval_id: int, note: float, etudid: int = None, nip: int = None, ine: int = None
|
||||
):
|
||||
"""
|
||||
Set les notes d'une évaluation pour un étudiant donnée
|
||||
|
||||
|
@ -5,14 +5,12 @@ from app import models
|
||||
from app.api import bp
|
||||
from app.api.errors import error_response
|
||||
from app.api.auth import token_auth, token_permission_required
|
||||
from app.decorators import permission_required
|
||||
from app.scodoc.sco_formations import formation_export
|
||||
from app.scodoc.sco_moduleimpl import moduleimpl_list
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/formations", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def formations():
|
||||
"""
|
||||
@ -28,7 +26,6 @@ def formations():
|
||||
|
||||
|
||||
@bp.route("/formations/<int:formation_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def formations_by_id(formation_id: int):
|
||||
"""
|
||||
@ -46,7 +43,6 @@ def formations_by_id(formation_id: int):
|
||||
|
||||
|
||||
@bp.route("/formations/formation_export/<int:formation_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def formation_export_by_formation_id(formation_id: int, export_ids=False):
|
||||
"""
|
||||
@ -58,13 +54,16 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False):
|
||||
# Utilisation de la fonction formation_export
|
||||
data = formation_export(formation_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel. \n"
|
||||
"Veillez vérifier la conformité du 'formation_id'",
|
||||
)
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/formations/apo/<string:etape_apo>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def formsemestre_apo(etape_apo: int):
|
||||
"""
|
||||
@ -86,7 +85,6 @@ def formsemestre_apo(etape_apo: int):
|
||||
|
||||
|
||||
@bp.route("/formations/moduleimpl/<int:moduleimpl_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def moduleimpls(moduleimpl_id: int):
|
||||
"""
|
||||
@ -103,8 +101,10 @@ def moduleimpls(moduleimpl_id: int):
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/formations/moduleimpl/<int:moduleimpl_id>/formsemestre/<int:formsemestre_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@bp.route(
|
||||
"/formations/moduleimpl/<int:moduleimpl_id>/formsemestre/<int:formsemestre_id>",
|
||||
methods=["GET"],
|
||||
)
|
||||
@token_permission_required(Permission.APIView)
|
||||
def moduleimpls_sem(moduleimpl_id: int, formsemestre_id: int):
|
||||
"""
|
||||
@ -115,10 +115,15 @@ def moduleimpls_sem(moduleimpl_id: int, formsemestre_id: int):
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_moduleimpl.moduleimpl_list()
|
||||
|
||||
try:
|
||||
# Utilisation de la fonction moduleimpl_list
|
||||
data = moduleimpl_list(moduleimpl_id, formsemestre_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if len(data) == 0:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel. \n"
|
||||
"Aucun moduleimpl ne correspond au 'moduleimpl_id' ou "
|
||||
"'formsemestre_id' renseigné",
|
||||
)
|
||||
|
||||
return jsonify(data)
|
@ -10,11 +10,13 @@ from app.api.tools import get_etu_from_request
|
||||
from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_pvjury import formsemestre_pvjury
|
||||
from app.scodoc.sco_recapcomplet import formsemestre_recapcomplet
|
||||
from app.scodoc.sco_recapcomplet import (
|
||||
formsemestre_recapcomplet,
|
||||
gen_formsemestre_recapcomplet_json,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/formations/formsemestre/<int:formsemestre_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def formsemestre(formsemestre_id: int):
|
||||
"""
|
||||
@ -44,9 +46,15 @@ def formsemestre(formsemestre_id: int):
|
||||
"/formsemestre/<int:formsemestre_id>/departements/<string:dept>/etudiant/ine/<int:ine>/bulletin",
|
||||
methods=["GET"],
|
||||
)
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def etudiant_bulletin(formsemestre_id, dept, format="json", etudid: int = None, nip: int = None, ine: int = None, ):#*args, size
|
||||
def etudiant_bulletin(
|
||||
formsemestre_id,
|
||||
dept,
|
||||
format="json",
|
||||
etudid: int = None,
|
||||
nip: int = None,
|
||||
ine: int = None,
|
||||
): # *args, size
|
||||
"""
|
||||
Retourne le bulletin de note d'un étudiant
|
||||
|
||||
@ -59,18 +67,30 @@ def etudiant_bulletin(formsemestre_id, dept, format="json", etudid: int = None,
|
||||
|
||||
# make_json_formsemestre_bulletinetud
|
||||
|
||||
try:
|
||||
app.set_sco_dept(dept)
|
||||
|
||||
except:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que le nom de département est valide",
|
||||
)
|
||||
if etudid is None:
|
||||
# Récupération de l'étudiant
|
||||
try:
|
||||
etu = get_etu_from_request(etudid, nip, ine)
|
||||
etudid = etu.etudid
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
|
||||
)
|
||||
|
||||
data = make_json_formsemestre_bulletinetud(formsemestre_id, etudid)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
# if args[0] == "short":
|
||||
# data = formsemestre_bulletinetud_dict(formsemestre_id, etudid, version=args[0])
|
||||
# elif args[0] == "selectevals":
|
||||
@ -83,8 +103,9 @@ def etudiant_bulletin(formsemestre_id, dept, format="json", etudid: int = None,
|
||||
# return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@bp.route(
|
||||
"/formsemestre/<int:formsemestre_id>/bulletins", methods=["GET"]
|
||||
) # XXX TODO à revoir
|
||||
@token_permission_required(Permission.APIView)
|
||||
def bulletins(formsemestre_id: int):
|
||||
"""
|
||||
@ -93,18 +114,23 @@ def bulletins(formsemestre_id: int):
|
||||
formsemestre_id : l'id d'un formesemestre
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_recapcomplet.formsemestre_recapcomplet()
|
||||
# gen_formsemestre_recapcomplet_json ??
|
||||
|
||||
try:
|
||||
# Utilisation de la fonction formsemestre_recapcomplet
|
||||
data = formsemestre_recapcomplet(formsemestre_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
# data = formsemestre_recapcomplet(formsemestre_id)
|
||||
data = gen_formsemestre_recapcomplet_json(formsemestre_id)
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel. \n"
|
||||
"Veillez vérifier la conformité du 'formation_id'",
|
||||
)
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/jury", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def jury(formsemestre_id: int):
|
||||
"""
|
||||
@ -117,7 +143,11 @@ def jury(formsemestre_id: int):
|
||||
try:
|
||||
# Utilisation de la fonction formsemestre_pvjury
|
||||
data = formsemestre_pvjury(formsemestre_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
except AttributeError:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel. \n"
|
||||
"Veillez vérifier la conformité du 'formation_id'",
|
||||
)
|
||||
|
||||
return jsonify(data)
|
@ -4,13 +4,14 @@ from flask import jsonify
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.errors import error_response
|
||||
from app.api.auth import token_auth, token_permission_required
|
||||
from app.api.auth import token_permission_required
|
||||
from app.scodoc.sco_prepajury import feuille_preparation_jury
|
||||
from app.scodoc.sco_pvjury import formsemestre_pvjury
|
||||
|
||||
|
||||
@bp.route("/jury/formsemestre/<int:formsemestre_id>/preparation_jury", methods=["GET"])
|
||||
def jury_preparation(formsemestre_id: int): # XXX TODO check à quoi resemble le retour de la fonction
|
||||
# @token_permission_required(Permission.)
|
||||
def jury_preparation(formsemestre_id: int):
|
||||
"""
|
||||
Retourne la feuille de préparation du jury
|
||||
|
||||
@ -25,7 +26,8 @@ def jury_preparation(formsemestre_id: int): # XXX TODO check à quoi resemble le
|
||||
|
||||
|
||||
@bp.route("/jury/formsemestre/<int:formsemestre_id>/decisions_jury", methods=["GET"])
|
||||
def jury_decisions(formsemestre_id: int): # XXX TODO check à quoi resemble le retour de la fonction
|
||||
# @token_permission_required(Permission.)
|
||||
def jury_decisions(formsemestre_id: int):
|
||||
"""
|
||||
Retourne les décisions du jury suivant un formsemestre donné
|
||||
|
||||
@ -39,14 +41,31 @@ def jury_decisions(formsemestre_id: int): # XXX TODO check à quoi resemble le r
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/jury/set_decision/etudid?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
|
||||
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>", methods=["POST"])
|
||||
@bp.route("/jury/set_decision/nip?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
|
||||
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>", methods=["POST"])
|
||||
@bp.route("/jury/set_decision/ine?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
|
||||
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>", methods=["POST"])
|
||||
def set_decision_jury(formsemestre_id: int, decision_jury: str, devenir_jury: str, assiduite: bool,
|
||||
etudid: int = None, nip: int = None, ine: int = None):
|
||||
@bp.route(
|
||||
"/jury/set_decision/etudid?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
|
||||
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@bp.route(
|
||||
"/jury/set_decision/nip?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
|
||||
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@bp.route(
|
||||
"/jury/set_decision/ine?etudid=<int:etudid>&formsemestre_id=<int:formesemestre_id>"
|
||||
"&jury=<string:decision_jury>&devenir=<string:devenir_jury>&assiduite=<bool>",
|
||||
methods=["POST"],
|
||||
)
|
||||
# @token_permission_required(Permission.)
|
||||
def set_decision_jury(
|
||||
formsemestre_id: int,
|
||||
decision_jury: str,
|
||||
devenir_jury: str,
|
||||
assiduite: bool,
|
||||
etudid: int = None,
|
||||
nip: int = None,
|
||||
ine: int = None,
|
||||
):
|
||||
"""
|
||||
Attribuer la décision du jury et le devenir à un etudiant
|
||||
|
||||
@ -61,10 +80,22 @@ def set_decision_jury(formsemestre_id: int, decision_jury: str, devenir_jury: st
|
||||
return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
@bp.route("/jury/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/annule_decision", methods=["DELETE"])
|
||||
@bp.route("/jury/nip/<int:nip>/formsemestre/<int:formsemestre_id>/annule_decision", methods=["DELETE"])
|
||||
@bp.route("/jury/ine/<int:ine>/formsemestre/<int:formsemestre_id>/annule_decision", methods=["DELETE"])
|
||||
def annule_decision_jury(formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None):
|
||||
@bp.route(
|
||||
"/jury/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/annule_decision",
|
||||
methods=["DELETE"],
|
||||
)
|
||||
@bp.route(
|
||||
"/jury/nip/<int:nip>/formsemestre/<int:formsemestre_id>/annule_decision",
|
||||
methods=["DELETE"],
|
||||
)
|
||||
@bp.route(
|
||||
"/jury/ine/<int:ine>/formsemestre/<int:formsemestre_id>/annule_decision",
|
||||
methods=["DELETE"],
|
||||
)
|
||||
# @token_permission_required(Permission.)
|
||||
def annule_decision_jury(
|
||||
formsemestre_id: int, etudid: int = None, nip: int = None, ine: int = None
|
||||
):
|
||||
"""
|
||||
Supprime la déciosion du jury pour un étudiant donné
|
||||
|
||||
|
@ -44,7 +44,6 @@ from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/logos", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def api_get_glob_logos():
|
||||
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
|
||||
@ -57,7 +56,6 @@ def api_get_glob_logos():
|
||||
|
||||
|
||||
@bp.route("/logos/<string:logoname>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def api_get_glob_logo(logoname):
|
||||
if not g.current_user.has_permission(Permission.ScoSuperAdmin, None):
|
||||
@ -74,7 +72,6 @@ def api_get_glob_logo(logoname):
|
||||
|
||||
|
||||
@bp.route("/departements/<string:departement>/logos", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def api_get_local_logos(departement):
|
||||
dept_id = Departement.from_acronym(departement).id
|
||||
@ -85,7 +82,6 @@ def api_get_local_logos(departement):
|
||||
|
||||
|
||||
@bp.route("/departements/<string:departement>/logos/<string:logoname>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def api_get_local_logo(departement, logoname):
|
||||
# format = requested_format("jpg", ['png', 'jpg']) XXX ?
|
||||
|
@ -3,16 +3,14 @@ from flask import jsonify
|
||||
|
||||
from app import models
|
||||
from app.api import bp
|
||||
from app.api.auth import token_auth
|
||||
|
||||
from app.api.errors import error_response
|
||||
from app.decorators import permission_required
|
||||
from app.api.auth import token_auth, token_permission_required
|
||||
from app.api.auth import token_permission_required
|
||||
from app.scodoc.sco_groups import get_group_members, setGroups
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/partitions/<int:formsemestre_id>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def partition(formsemestre_id: int):
|
||||
"""
|
||||
@ -30,13 +28,8 @@ def partition(formsemestre_id: int):
|
||||
# return error_response(501, message="Not implemented")
|
||||
|
||||
|
||||
# @bp.route(
|
||||
# "/partitions/formsemestre/<int:formsemestre_id>/groups/group_ids?with_codes=&all_groups=&etat=",
|
||||
# methods=["GET"],
|
||||
# )
|
||||
@bp.route("/partitions/groups/<int:group_id>", methods=["GET"])
|
||||
@bp.route("/partitions/groups/<int:group_id>/etat/<string:etat>", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIView)
|
||||
def etud_in_group(group_id: int, etat=None):
|
||||
"""
|
||||
@ -47,29 +40,30 @@ def etud_in_group(group_id: int, etat=None):
|
||||
"""
|
||||
# Fonction utilisée : app.scodoc.sco_groups.get_group_members()
|
||||
|
||||
if etat is None: # Si l'état n'est pas renseigné
|
||||
try:
|
||||
# Utilisation de la fonction get_group_members
|
||||
if etat is None:
|
||||
data = get_group_members(group_id)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
else: # Si l'état est renseigné
|
||||
try:
|
||||
# Utilisation de la fonction get_group_members
|
||||
else:
|
||||
data = get_group_members(group_id, etat)
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
|
||||
if len(data) == 0:
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel. \n"
|
||||
"Aucun groupe ne correspond au 'group_id' renseigné",
|
||||
)
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/partitions/set_groups?partition_id=<int:partition_id>&groups_lists=<int:groups_lists>&"
|
||||
"groups_to_create=<int:groups_to_create>&groups_to_delete=<int:groups_to_delete>", methods=["POST"],
|
||||
"groups_to_create=<int:groups_to_create>&groups_to_delete=<int:groups_to_delete>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@token_auth.login_required
|
||||
@token_permission_required(Permission.APIEtudChangeGroups)
|
||||
def set_groups(partition_id: int, groups_lists: int, groups_to_delete: int, groups_to_create: int):
|
||||
def set_groups(
|
||||
partition_id: int, groups_lists: int, groups_to_delete: int, groups_to_create: int
|
||||
):
|
||||
"""
|
||||
Set les groups
|
||||
|
||||
@ -84,4 +78,8 @@ def set_groups(partition_id: int, groups_lists: int, groups_to_delete: int, grou
|
||||
setGroups(partition_id, groups_lists, groups_to_create, groups_to_delete)
|
||||
return error_response(200, message="Groups set")
|
||||
except ValueError:
|
||||
return error_response(409, message="La requête ne peut être traitée en l’état actuel")
|
||||
return error_response(
|
||||
409,
|
||||
message="La requête ne peut être traitée en l’état actuel. \n"
|
||||
"Veillez vérifier la conformité des éléments passé en paramètres",
|
||||
)
|
||||
|
@ -1,5 +1,6 @@
|
||||
from app import models
|
||||
|
||||
|
||||
def get_etu_from_request(etudid, nip, ine):
|
||||
"""
|
||||
Fonction qui retourne un etudiant en fonction de l'etudid, code nip et code ine rentré en paramètres
|
||||
@ -10,7 +11,6 @@ def get_etu_from_request(etudid, nip, ine):
|
||||
|
||||
Exemple de résultat: <Itendite>
|
||||
"""
|
||||
|
||||
if etudid is None:
|
||||
if nip is None: # si ine
|
||||
etu = models.Identite.query.filter_by(code_ine=str(ine)).first()
|
||||
|
@ -1072,6 +1072,29 @@ class BonusTours(BonusDirect):
|
||||
)
|
||||
|
||||
|
||||
class BonusIUTvannes(BonusSportAdditif):
|
||||
"""Calcul bonus modules optionels (sport, culture), règle IUT Vannes
|
||||
|
||||
<p><b>Ne concerne actuellement que les DUT et LP</b></p>
|
||||
<p>Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||
de l'U.B.S. (sports, musique, deuxième langue, culture, etc) non
|
||||
rattachés à une unité d'enseignement.
|
||||
</p><p>
|
||||
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||
optionnelles sont cumulés.
|
||||
</p><p>
|
||||
3% de ces points cumulés s'ajoutent à la moyenne générale du semestre
|
||||
déjà obtenue par l'étudiant.
|
||||
</p>
|
||||
"""
|
||||
|
||||
name = "bonus_iutvannes"
|
||||
displayed_name = "IUT de Vannes"
|
||||
seuil_moy_gen = 10.0
|
||||
proportion_point = 0.03 # 3%
|
||||
classic_use_bonus_ues = False # seulement sur moy gen.
|
||||
|
||||
|
||||
class BonusVilleAvray(BonusSport):
|
||||
"""Bonus modules optionnels (sport, culture), règle IUT Ville d'Avray.
|
||||
|
||||
|
@ -34,7 +34,7 @@ def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
|
||||
)
|
||||
df = df.merge(ins_df, how="left", left_index=True, right_index=True)
|
||||
# Force columns names to integers (moduleimpl ids)
|
||||
df.columns = pd.Int64Index([int(x) for x in df.columns], dtype="int")
|
||||
df.columns = pd.Index([int(x) for x in df.columns], dtype=int)
|
||||
# les colonnes de df sont en float (Nan) quand il n'y a
|
||||
# aucun inscrit au module.
|
||||
df.fillna(0, inplace=True) # les non-inscrits
|
||||
|
@ -169,9 +169,7 @@ class ModuleImplResults:
|
||||
self.en_attente = True
|
||||
|
||||
# Force columns names to integers (evaluation ids)
|
||||
evals_notes.columns = pd.Int64Index(
|
||||
[int(x) for x in evals_notes.columns], dtype="int"
|
||||
)
|
||||
evals_notes.columns = pd.Index([int(x) for x in evals_notes.columns], dtype=int)
|
||||
self.evals_notes = evals_notes
|
||||
|
||||
def _load_evaluation_notes(self, evaluation: Evaluation) -> pd.DataFrame:
|
||||
|
@ -100,8 +100,9 @@ def comp_ranks_series(notes: pd.Series) -> (pd.Series, pd.Series):
|
||||
if (notes is None) or (len(notes) == 0):
|
||||
return (pd.Series([], dtype=object), pd.Series([], dtype=int))
|
||||
notes = notes.sort_values(ascending=False) # Serie, tri par ordre décroissant
|
||||
rangs_str = pd.Series(index=notes.index, dtype=str) # le rang est une chaîne
|
||||
rangs_int = pd.Series(index=notes.index, dtype=int) # le rang numérique pour tris
|
||||
rangs_str = pd.Series("", index=notes.index, dtype=str) # le rang est une chaîne
|
||||
# le rang numérique pour tris:
|
||||
rangs_int = pd.Series(0, index=notes.index, dtype=int)
|
||||
N = len(notes)
|
||||
nb_ex = 0 # nb d'ex-aequo consécutifs en cours
|
||||
notes_i = notes.iat
|
||||
@ -128,4 +129,5 @@ def comp_ranks_series(notes: pd.Series) -> (pd.Series, pd.Series):
|
||||
rangs_int[etudid] = i + 1
|
||||
srang = "%d" % (i + 1)
|
||||
rangs_str[etudid] = srang
|
||||
assert rangs_int.dtype == int
|
||||
return rangs_str, rangs_int
|
||||
|
@ -271,7 +271,7 @@ def compute_ue_moys_apc(
|
||||
)
|
||||
# Annule les coefs des modules NaN
|
||||
modimpl_coefs_etuds_no_nan = np.where(np.isnan(sem_cube), 0.0, modimpl_coefs_etuds)
|
||||
if modimpl_coefs_etuds_no_nan.dtype == np.object: # arrive sur des tableaux vides
|
||||
if modimpl_coefs_etuds_no_nan.dtype == object: # arrive sur des tableaux vides
|
||||
modimpl_coefs_etuds_no_nan = modimpl_coefs_etuds_no_nan.astype(np.float)
|
||||
#
|
||||
# Version vectorisée
|
||||
@ -356,7 +356,7 @@ def compute_ue_moys_classic(
|
||||
modimpl_coefs_etuds_no_nan = np.where(
|
||||
np.isnan(sem_matrix), 0.0, modimpl_coefs_etuds
|
||||
)
|
||||
if modimpl_coefs_etuds_no_nan.dtype == np.object: # arrive sur des tableaux vides
|
||||
if modimpl_coefs_etuds_no_nan.dtype == object: # arrive sur des tableaux vides
|
||||
modimpl_coefs_etuds_no_nan = modimpl_coefs_etuds_no_nan.astype(np.float)
|
||||
# --------------------- Calcul des moyennes d'UE
|
||||
ue_modules = np.array(
|
||||
@ -367,7 +367,7 @@ def compute_ue_moys_classic(
|
||||
)
|
||||
# nb_ue x nb_etuds x nb_mods : coefs prenant en compte NaN et inscriptions:
|
||||
coefs = (modimpl_coefs_etuds_no_nan_stacked * ue_modules).swapaxes(1, 2)
|
||||
if coefs.dtype == np.object: # arrive sur des tableaux vides
|
||||
if coefs.dtype == object: # arrive sur des tableaux vides
|
||||
coefs = coefs.astype(np.float)
|
||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||
etud_moy_ue = (
|
||||
@ -462,7 +462,7 @@ def compute_mat_moys_classic(
|
||||
modimpl_coefs_etuds_no_nan = np.where(
|
||||
np.isnan(sem_matrix), 0.0, modimpl_coefs_etuds
|
||||
)
|
||||
if modimpl_coefs_etuds_no_nan.dtype == np.object: # arrive sur des tableaux vides
|
||||
if modimpl_coefs_etuds_no_nan.dtype == object: # arrive sur des tableaux vides
|
||||
modimpl_coefs_etuds_no_nan = modimpl_coefs_etuds_no_nan.astype(np.float)
|
||||
|
||||
etud_moy_mat = (modimpl_coefs_etuds_no_nan * sem_matrix_inscrits).sum(
|
||||
|
@ -34,7 +34,7 @@ from flask_login import current_user
|
||||
|
||||
from app.entreprises.models import (
|
||||
Entreprise,
|
||||
EntrepriseContact,
|
||||
EntrepriseCorrespondant,
|
||||
EntrepriseOffre,
|
||||
EntrepriseOffreDepartement,
|
||||
EntreprisePreferences,
|
||||
@ -85,6 +85,9 @@ def get_offre_files_and_depts(offre: EntrepriseOffre, depts: list):
|
||||
Retourne l'offre, les fichiers attachés a l'offre et les département liés
|
||||
"""
|
||||
offre_depts = EntrepriseOffreDepartement.query.filter_by(offre_id=offre.id).all()
|
||||
correspondant = EntrepriseCorrespondant.query.filter_by(
|
||||
id=offre.correspondant_id
|
||||
).first()
|
||||
if not offre_depts or check_offre_depts(depts, offre_depts):
|
||||
files = []
|
||||
path = os.path.join(
|
||||
@ -100,13 +103,11 @@ def get_offre_files_and_depts(offre: EntrepriseOffre, depts: list):
|
||||
for _file in glob.glob(f"{dir}/*"):
|
||||
file = [os.path.basename(dir), os.path.basename(_file)]
|
||||
files.append(file)
|
||||
return [offre, files, offre_depts]
|
||||
return [offre, files, offre_depts, correspondant]
|
||||
return None
|
||||
|
||||
|
||||
def send_email_notifications_entreprise(
|
||||
subject, entreprise: Entreprise, contact: EntrepriseContact
|
||||
):
|
||||
def send_email_notifications_entreprise(subject, entreprise: Entreprise):
|
||||
txt = [
|
||||
"Une entreprise est en attente de validation",
|
||||
"Entreprise:",
|
||||
@ -116,14 +117,6 @@ def send_email_notifications_entreprise(
|
||||
f"\tcode postal: {entreprise.codepostal}",
|
||||
f"\tville: {entreprise.ville}",
|
||||
f"\tpays: {entreprise.pays}",
|
||||
"",
|
||||
"Contact:",
|
||||
f"nom: {contact.nom}",
|
||||
f"prenom: {contact.prenom}",
|
||||
f"telephone: {contact.telephone}",
|
||||
f"mail: {contact.mail}",
|
||||
f"poste: {contact.poste}",
|
||||
f"service: {contact.service}",
|
||||
]
|
||||
txt = "\n".join(txt)
|
||||
email.send_email(
|
||||
@ -135,34 +128,42 @@ def send_email_notifications_entreprise(
|
||||
return txt
|
||||
|
||||
|
||||
def verif_contact_data(contact_data):
|
||||
def verif_correspondant_data(correspondant_data):
|
||||
"""
|
||||
Verifie les données d'une ligne Excel (contact)
|
||||
contact_data[0]: nom
|
||||
contact_data[1]: prenom
|
||||
contact_data[2]: telephone
|
||||
contact_data[3]: mail
|
||||
contact_data[4]: poste
|
||||
contact_data[5]: service
|
||||
contact_data[6]: entreprise_id
|
||||
Verifie les données d'une ligne Excel (correspondant)
|
||||
correspondant_data[0]: nom
|
||||
correspondant_data[1]: prenom
|
||||
correspondant_data[2]: telephone
|
||||
correspondant_data[3]: mail
|
||||
correspondant_data[4]: poste
|
||||
correspondant_data[5]: service
|
||||
correspondant_data[6]: entreprise_id
|
||||
"""
|
||||
# champs obligatoires
|
||||
if contact_data[0] == "" or contact_data[1] == "" or contact_data[6] == "":
|
||||
if (
|
||||
correspondant_data[0].strip() == ""
|
||||
or correspondant_data[1].strip() == ""
|
||||
or correspondant_data[6].strip() == ""
|
||||
):
|
||||
return False
|
||||
|
||||
# entreprise_id existant
|
||||
entreprise = Entreprise.query.filter_by(siret=contact_data[6]).first()
|
||||
entreprise = Entreprise.query.filter_by(siret=correspondant_data[6].strip()).first()
|
||||
if entreprise is None:
|
||||
return False
|
||||
|
||||
# contact possède le meme nom et prénom dans la meme entreprise
|
||||
contact = EntrepriseContact.query.filter_by(
|
||||
nom=contact_data[0], prenom=contact_data[1], entreprise_id=entreprise.id
|
||||
# correspondant possède le meme nom et prénom dans la meme entreprise
|
||||
correspondant = EntrepriseCorrespondant.query.filter_by(
|
||||
nom=correspondant_data[0].strip(),
|
||||
prenom=correspondant_data[1].strip(),
|
||||
entreprise_id=entreprise.id,
|
||||
).first()
|
||||
if contact is not None:
|
||||
if correspondant is not None:
|
||||
return False
|
||||
|
||||
if contact_data[2] == "" and contact_data[3] == "": # 1 moyen de contact
|
||||
if (
|
||||
correspondant_data[2].strip() == "" and correspondant_data[3].strip() == ""
|
||||
): # 1 moyen de contact
|
||||
return False
|
||||
|
||||
return True
|
||||
@ -174,24 +175,24 @@ def verif_entreprise_data(entreprise_data):
|
||||
"""
|
||||
if EntreprisePreferences.get_check_siret():
|
||||
for data in entreprise_data: # champs obligatoires
|
||||
if data == "":
|
||||
if data.strip() == "":
|
||||
return False
|
||||
else:
|
||||
for data in entreprise_data[1:]: # champs obligatoires
|
||||
if data == "":
|
||||
if data.strip() == "":
|
||||
return False
|
||||
if EntreprisePreferences.get_check_siret():
|
||||
siret = entreprise_data[0].strip() # vérification sur le siret
|
||||
siret = entreprise_data[0].replace(" ", "") # vérification sur le siret
|
||||
if re.match("^\d{14}$", siret) is None:
|
||||
return False
|
||||
try:
|
||||
req = requests.get(
|
||||
f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret}"
|
||||
)
|
||||
except requests.ConnectionError:
|
||||
print("no internet")
|
||||
if req.status_code != 200:
|
||||
return False
|
||||
except requests.ConnectionError:
|
||||
return False
|
||||
entreprise = Entreprise.query.filter_by(siret=siret).first()
|
||||
if entreprise is not None:
|
||||
return False
|
||||
|
@ -40,11 +40,17 @@ from wtforms import (
|
||||
SelectMultipleField,
|
||||
DateField,
|
||||
BooleanField,
|
||||
FieldList,
|
||||
FormField,
|
||||
)
|
||||
from wtforms.validators import ValidationError, DataRequired, Email, Optional
|
||||
from wtforms.widgets import ListWidget, CheckboxInput
|
||||
|
||||
from app.entreprises.models import Entreprise, EntrepriseContact, EntreprisePreferences
|
||||
from app.entreprises.models import (
|
||||
Entreprise,
|
||||
EntrepriseCorrespondant,
|
||||
EntreprisePreferences,
|
||||
)
|
||||
from app.models import Identite, Departement
|
||||
from app.auth.models import User
|
||||
|
||||
@ -66,7 +72,7 @@ def _build_string_field(label, required=True, render_kw=None):
|
||||
class EntrepriseCreationForm(FlaskForm):
|
||||
siret = _build_string_field(
|
||||
"SIRET (*)",
|
||||
render_kw={"placeholder": "Numéro composé de 14 chiffres", "maxlength": "14"},
|
||||
render_kw={"placeholder": "Numéro composé de 14 chiffres"},
|
||||
)
|
||||
nom_entreprise = _build_string_field("Nom de l'entreprise (*)")
|
||||
adresse = _build_string_field("Adresse de l'entreprise (*)")
|
||||
@ -74,15 +80,18 @@ class EntrepriseCreationForm(FlaskForm):
|
||||
ville = _build_string_field("Ville de l'entreprise (*)")
|
||||
pays = _build_string_field("Pays de l'entreprise", required=False)
|
||||
|
||||
nom_contact = _build_string_field("Nom du contact (*)")
|
||||
prenom_contact = _build_string_field("Prénom du contact (*)")
|
||||
telephone = _build_string_field("Téléphone du contact (*)", required=False)
|
||||
nom_correspondant = _build_string_field("Nom du correspondant", required=False)
|
||||
prenom_correspondant = _build_string_field(
|
||||
"Prénom du correspondant", required=False
|
||||
)
|
||||
telephone = _build_string_field("Téléphone du correspondant", required=False)
|
||||
mail = StringField(
|
||||
"Mail du contact (*)",
|
||||
"Mail du correspondant",
|
||||
validators=[Optional(), Email(message="Adresse e-mail invalide")],
|
||||
)
|
||||
poste = _build_string_field("Poste du contact", required=False)
|
||||
service = _build_string_field("Service du contact", required=False)
|
||||
poste = _build_string_field("Poste du correspondant", required=False)
|
||||
service = _build_string_field("Service du correspondant", required=False)
|
||||
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate(self):
|
||||
@ -90,29 +99,46 @@ class EntrepriseCreationForm(FlaskForm):
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
|
||||
if not self.telephone.data and not self.mail.data:
|
||||
if (
|
||||
self.nom_correspondant.data.strip()
|
||||
or self.prenom_correspondant.data.strip()
|
||||
or self.telephone.data.strip()
|
||||
or self.mail.data.strip()
|
||||
or self.poste.data.strip()
|
||||
or self.service.data.strip()
|
||||
):
|
||||
if not self.nom_correspondant.data.strip():
|
||||
self.nom_correspondant.errors.append("Ce champ est requis")
|
||||
validate = False
|
||||
if not self.prenom_correspondant.data.strip():
|
||||
self.prenom_correspondant.errors.append("Ce champ est requis")
|
||||
validate = False
|
||||
if not self.telephone.data.strip() and not self.mail.data.strip():
|
||||
self.telephone.errors.append(
|
||||
"Saisir un moyen de contact (mail ou téléphone)"
|
||||
)
|
||||
self.mail.errors.append("Saisir un moyen de contact (mail ou téléphone)")
|
||||
self.mail.errors.append(
|
||||
"Saisir un moyen de contact (mail ou téléphone)"
|
||||
)
|
||||
validate = False
|
||||
|
||||
return validate
|
||||
|
||||
def validate_siret(self, siret):
|
||||
if EntreprisePreferences.get_check_siret():
|
||||
siret = siret.data.strip()
|
||||
if re.match("^\d{14}$", siret) is None:
|
||||
siret_data = siret.data.replace(" ", "")
|
||||
self.siret.data = siret_data
|
||||
if re.match("^\d{14}$", siret_data) is None:
|
||||
raise ValidationError("Format incorrect")
|
||||
try:
|
||||
req = requests.get(
|
||||
f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret}"
|
||||
f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret_data}"
|
||||
)
|
||||
except requests.ConnectionError:
|
||||
print("no internet")
|
||||
if req.status_code != 200:
|
||||
raise ValidationError("SIRET inexistant")
|
||||
entreprise = Entreprise.query.filter_by(siret=siret).first()
|
||||
except requests.ConnectionError:
|
||||
raise ValidationError("Impossible de vérifier l'existance du SIRET")
|
||||
entreprise = Entreprise.query.filter_by(siret=siret_data).first()
|
||||
if entreprise is not None:
|
||||
lien = f'<a href="/ScoDoc/entreprises/fiche_entreprise/{entreprise.id}">ici</a>'
|
||||
raise ValidationError(
|
||||
@ -144,6 +170,7 @@ class MultiCheckboxField(SelectMultipleField):
|
||||
|
||||
|
||||
class OffreCreationForm(FlaskForm):
|
||||
hidden_entreprise_id = HiddenField()
|
||||
intitule = _build_string_field("Intitulé (*)")
|
||||
description = TextAreaField(
|
||||
"Description (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||
@ -159,17 +186,44 @@ class OffreCreationForm(FlaskForm):
|
||||
duree = _build_string_field("Durée (*)")
|
||||
depts = MultiCheckboxField("Départements", validators=[Optional()], coerce=int)
|
||||
expiration_date = DateField("Date expiration", validators=[Optional()])
|
||||
correspondant = SelectField("Correspondant à contacté", validators=[Optional()])
|
||||
fichier = FileField(
|
||||
"Fichier (*)",
|
||||
validators=[
|
||||
Optional(),
|
||||
FileAllowed(["pdf", "docx"], "Fichier .pdf ou .docx uniquement"),
|
||||
],
|
||||
)
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.correspondant.choices = [
|
||||
(correspondant.id, f"{correspondant.nom} {correspondant.prenom}")
|
||||
for correspondant in EntrepriseCorrespondant.query.filter_by(
|
||||
entreprise_id=self.hidden_entreprise_id.data
|
||||
)
|
||||
]
|
||||
|
||||
self.depts.choices = [
|
||||
(dept.id, dept.acronym) for dept in Departement.query.all()
|
||||
]
|
||||
|
||||
def validate(self):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
|
||||
if len(self.depts.data) < 1:
|
||||
self.depts.errors.append("Choisir au moins un département")
|
||||
validate = False
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
class OffreModificationForm(FlaskForm):
|
||||
hidden_entreprise_id = HiddenField()
|
||||
intitule = _build_string_field("Intitulé (*)")
|
||||
description = TextAreaField(
|
||||
"Description (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||
@ -185,27 +239,79 @@ class OffreModificationForm(FlaskForm):
|
||||
duree = _build_string_field("Durée (*)")
|
||||
depts = MultiCheckboxField("Départements", validators=[Optional()], coerce=int)
|
||||
expiration_date = DateField("Date expiration", validators=[Optional()])
|
||||
correspondant = SelectField("Correspondant à contacté", validators=[Optional()])
|
||||
submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.correspondant.choices = [
|
||||
(correspondant.id, f"{correspondant.nom} {correspondant.prenom}")
|
||||
for correspondant in EntrepriseCorrespondant.query.filter_by(
|
||||
entreprise_id=self.hidden_entreprise_id.data
|
||||
)
|
||||
]
|
||||
|
||||
self.depts.choices = [
|
||||
(dept.id, dept.acronym) for dept in Departement.query.all()
|
||||
]
|
||||
|
||||
def validate(self):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
|
||||
class ContactCreationForm(FlaskForm):
|
||||
hidden_entreprise_id = HiddenField()
|
||||
nom = _build_string_field("Nom (*)")
|
||||
prenom = _build_string_field("Prénom (*)")
|
||||
telephone = _build_string_field("Téléphone (*)", required=False)
|
||||
if len(self.depts.data) < 1:
|
||||
self.depts.errors.append("Choisir au moins un département")
|
||||
validate = False
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
class CorrespondantCreationForm(FlaskForm):
|
||||
nom = _build_string_field("Nom (*)", render_kw={"class": "form-control"})
|
||||
prenom = _build_string_field("Prénom (*)", render_kw={"class": "form-control"})
|
||||
telephone = _build_string_field(
|
||||
"Téléphone (*)", required=False, render_kw={"class": "form-control"}
|
||||
)
|
||||
mail = StringField(
|
||||
"Mail (*)",
|
||||
validators=[Optional(), Email(message="Adresse e-mail invalide")],
|
||||
render_kw={"class": "form-control"},
|
||||
)
|
||||
poste = _build_string_field("Poste", required=False)
|
||||
service = _build_string_field("Service", required=False)
|
||||
poste = _build_string_field(
|
||||
"Poste", required=False, render_kw={"class": "form-control"}
|
||||
)
|
||||
service = _build_string_field(
|
||||
"Service", required=False, render_kw={"class": "form-control"}
|
||||
)
|
||||
# depts = MultiCheckboxField("Départements", validators=[Optional()], coerce=int)
|
||||
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
|
||||
# self.depts.choices = [
|
||||
# (dept.id, dept.acronym) for dept in Departement.query.all()
|
||||
# ]
|
||||
|
||||
def validate(self):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
|
||||
if not self.telephone.data and not self.mail.data:
|
||||
self.telephone.errors.append(
|
||||
"Saisir un moyen de contact (mail ou téléphone)"
|
||||
)
|
||||
self.mail.errors.append("Saisir un moyen de contact (mail ou téléphone)")
|
||||
validate = False
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
class CorrespondantsCreationForm(FlaskForm):
|
||||
hidden_entreprise_id = HiddenField()
|
||||
correspondants = FieldList(FormField(CorrespondantCreationForm), min_entries=1)
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate(self):
|
||||
@ -213,28 +319,37 @@ class ContactCreationForm(FlaskForm):
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
|
||||
contact = EntrepriseContact.query.filter_by(
|
||||
entreprise_id=self.hidden_entreprise_id.data,
|
||||
nom=self.nom.data,
|
||||
prenom=self.prenom.data,
|
||||
).first()
|
||||
if contact is not None:
|
||||
self.nom.errors.append("Ce contact existe déjà (même nom et prénom)")
|
||||
self.prenom.errors.append("")
|
||||
validate = False
|
||||
|
||||
if not self.telephone.data and not self.mail.data:
|
||||
self.telephone.errors.append(
|
||||
"Saisir un moyen de contact (mail ou téléphone)"
|
||||
correspondant_list = []
|
||||
for entry in self.correspondants.entries:
|
||||
if entry.nom.data.strip() and entry.prenom.data.strip():
|
||||
if (
|
||||
entry.nom.data.strip(),
|
||||
entry.prenom.data.strip(),
|
||||
) in correspondant_list:
|
||||
entry.nom.errors.append(
|
||||
"Vous avez saisi 2 fois le même nom et prenom"
|
||||
)
|
||||
self.mail.errors.append("Saisir un moyen de contact (mail ou téléphone)")
|
||||
entry.prenom.errors.append("")
|
||||
validate = False
|
||||
correspondant_list.append(
|
||||
(entry.nom.data.strip(), entry.prenom.data.strip())
|
||||
)
|
||||
correspondant = EntrepriseCorrespondant.query.filter_by(
|
||||
entreprise_id=self.hidden_entreprise_id.data,
|
||||
nom=entry.nom.data,
|
||||
prenom=entry.prenom.data,
|
||||
).first()
|
||||
if correspondant is not None:
|
||||
entry.nom.errors.append(
|
||||
"Ce correspondant existe déjà (même nom et prénom)"
|
||||
)
|
||||
entry.prenom.errors.append("")
|
||||
validate = False
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
class ContactModificationForm(FlaskForm):
|
||||
hidden_contact_id = HiddenField()
|
||||
class CorrespondantModificationForm(FlaskForm):
|
||||
hidden_correspondant_id = HiddenField()
|
||||
hidden_entreprise_id = HiddenField()
|
||||
nom = _build_string_field("Nom (*)")
|
||||
prenom = _build_string_field("Prénom (*)")
|
||||
@ -245,21 +360,29 @@ class ContactModificationForm(FlaskForm):
|
||||
)
|
||||
poste = _build_string_field("Poste", required=False)
|
||||
service = _build_string_field("Service", required=False)
|
||||
# depts = MultiCheckboxField("Départements", validators=[Optional()], coerce=int)
|
||||
submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
|
||||
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
|
||||
# self.depts.choices = [
|
||||
# (dept.id, dept.acronym) for dept in Departement.query.all()
|
||||
# ]
|
||||
|
||||
def validate(self):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
|
||||
contact = EntrepriseContact.query.filter(
|
||||
EntrepriseContact.id != self.hidden_contact_id.data,
|
||||
EntrepriseContact.entreprise_id == self.hidden_entreprise_id.data,
|
||||
EntrepriseContact.nom == self.nom.data,
|
||||
EntrepriseContact.prenom == self.prenom.data,
|
||||
correspondant = EntrepriseCorrespondant.query.filter(
|
||||
EntrepriseCorrespondant.id != self.hidden_correspondant_id.data,
|
||||
EntrepriseCorrespondant.entreprise_id == self.hidden_entreprise_id.data,
|
||||
EntrepriseCorrespondant.nom == self.nom.data,
|
||||
EntrepriseCorrespondant.prenom == self.prenom.data,
|
||||
).first()
|
||||
if contact is not None:
|
||||
self.nom.errors.append("Ce contact existe déjà (même nom et prénom)")
|
||||
if correspondant is not None:
|
||||
self.nom.errors.append("Ce correspondant existe déjà (même nom et prénom)")
|
||||
self.prenom.errors.append("")
|
||||
validate = False
|
||||
|
||||
@ -273,7 +396,59 @@ class ContactModificationForm(FlaskForm):
|
||||
return validate
|
||||
|
||||
|
||||
class HistoriqueCreationForm(FlaskForm):
|
||||
class ContactCreationForm(FlaskForm):
|
||||
date = _build_string_field(
|
||||
"Date (*)",
|
||||
render_kw={"type": "datetime-local"},
|
||||
)
|
||||
utilisateur = _build_string_field(
|
||||
"Utilisateur (*)",
|
||||
render_kw={"placeholder": "Tapez le nom de l'utilisateur"},
|
||||
)
|
||||
notes = TextAreaField("Notes (*)", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate_utilisateur(self, utilisateur):
|
||||
utilisateur_data = self.utilisateur.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')')) FROM \"user\" WHERE UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')'))=:utilisateur_data"
|
||||
)
|
||||
utilisateur = (
|
||||
User.query.from_statement(stm)
|
||||
.params(utilisateur_data=utilisateur_data)
|
||||
.first()
|
||||
)
|
||||
if utilisateur is None:
|
||||
raise ValidationError("Champ incorrect (selectionnez dans la liste)")
|
||||
|
||||
|
||||
class ContactModificationForm(FlaskForm):
|
||||
date = _build_string_field(
|
||||
"Date (*)",
|
||||
render_kw={"type": "datetime-local"},
|
||||
)
|
||||
utilisateur = _build_string_field(
|
||||
"Utilisateur (*)",
|
||||
render_kw={"placeholder": "Tapez le nom de l'utilisateur"},
|
||||
)
|
||||
notes = TextAreaField("Notes (*)", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||
submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate_utilisateur(self, utilisateur):
|
||||
utilisateur_data = self.utilisateur.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')')) FROM \"user\" WHERE UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')'))=:utilisateur_data"
|
||||
)
|
||||
utilisateur = (
|
||||
User.query.from_statement(stm)
|
||||
.params(utilisateur_data=utilisateur_data)
|
||||
.first()
|
||||
)
|
||||
if utilisateur is None:
|
||||
raise ValidationError("Champ incorrect (selectionnez dans la liste)")
|
||||
|
||||
|
||||
class StageApprentissageCreationForm(FlaskForm):
|
||||
etudiant = _build_string_field(
|
||||
"Étudiant (*)",
|
||||
render_kw={"placeholder": "Tapez le nom de l'étudiant"},
|
||||
@ -289,6 +464,7 @@ class HistoriqueCreationForm(FlaskForm):
|
||||
date_fin = DateField(
|
||||
"Date fin (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||
)
|
||||
notes = TextAreaField("Notes")
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate(self):
|
||||
@ -319,15 +495,74 @@ class HistoriqueCreationForm(FlaskForm):
|
||||
raise ValidationError("Champ incorrect (selectionnez dans la liste)")
|
||||
|
||||
|
||||
class StageApprentissageModificationForm(FlaskForm):
|
||||
etudiant = _build_string_field(
|
||||
"Étudiant (*)",
|
||||
render_kw={"placeholder": "Tapez le nom de l'étudiant"},
|
||||
)
|
||||
type_offre = SelectField(
|
||||
"Type de l'offre (*)",
|
||||
choices=[("Stage"), ("Alternance")],
|
||||
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||
)
|
||||
date_debut = DateField(
|
||||
"Date début (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||
)
|
||||
date_fin = DateField(
|
||||
"Date fin (*)", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||
)
|
||||
notes = TextAreaField("Notes")
|
||||
submit = SubmitField("Modifier", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate(self):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
|
||||
if (
|
||||
self.date_debut.data
|
||||
and self.date_fin.data
|
||||
and self.date_debut.data > self.date_fin.data
|
||||
):
|
||||
self.date_debut.errors.append("Les dates sont incompatibles")
|
||||
self.date_fin.errors.append("Les dates sont incompatibles")
|
||||
validate = False
|
||||
|
||||
return validate
|
||||
|
||||
def validate_etudiant(self, etudiant):
|
||||
etudiant_data = etudiant.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, CONCAT(nom, ' ', prenom) as nom_prenom FROM Identite WHERE CONCAT(nom, ' ', prenom)=:nom_prenom"
|
||||
)
|
||||
etudiant = (
|
||||
Identite.query.from_statement(stm).params(nom_prenom=etudiant_data).first()
|
||||
)
|
||||
if etudiant is None:
|
||||
raise ValidationError("Champ incorrect (selectionnez dans la liste)")
|
||||
|
||||
|
||||
class EnvoiOffreForm(FlaskForm):
|
||||
responsable = _build_string_field(
|
||||
"Responsable de formation (*)",
|
||||
render_kw={"placeholder": "Tapez le nom du responsable de formation"},
|
||||
responsables = FieldList(
|
||||
_build_string_field(
|
||||
"Responsable (*)",
|
||||
render_kw={
|
||||
"placeholder": "Tapez le nom du responsable de formation",
|
||||
"class": "form-control",
|
||||
},
|
||||
),
|
||||
min_entries=1,
|
||||
)
|
||||
submit = SubmitField("Envoyer", render_kw=SUBMIT_MARGE)
|
||||
|
||||
def validate_responsable(self, responsable):
|
||||
responsable_data = responsable.data.upper().strip()
|
||||
def validate(self):
|
||||
validate = True
|
||||
if not FlaskForm.validate(self):
|
||||
validate = False
|
||||
|
||||
for entry in self.responsables.entries:
|
||||
if entry.data:
|
||||
responsable_data = entry.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')')) FROM \"user\" WHERE UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')'))=:responsable_data"
|
||||
)
|
||||
@ -337,7 +572,10 @@ class EnvoiOffreForm(FlaskForm):
|
||||
.first()
|
||||
)
|
||||
if responsable is None:
|
||||
raise ValidationError("Champ incorrect (selectionnez dans la liste)")
|
||||
entry.errors.append("Champ incorrect (selectionnez dans la liste)")
|
||||
validate = False
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
class AjoutFichierForm(FlaskForm):
|
||||
|
@ -11,8 +11,8 @@ class Entreprise(db.Model):
|
||||
ville = db.Column(db.Text)
|
||||
pays = db.Column(db.Text, default="FRANCE")
|
||||
visible = db.Column(db.Boolean, default=False)
|
||||
contacts = db.relationship(
|
||||
"EntrepriseContact",
|
||||
correspondants = db.relationship(
|
||||
"EntrepriseCorrespondant",
|
||||
backref="entreprise",
|
||||
lazy="dynamic",
|
||||
cascade="all, delete-orphan",
|
||||
@ -35,12 +35,22 @@ class Entreprise(db.Model):
|
||||
}
|
||||
|
||||
|
||||
class EntrepriseContact(db.Model):
|
||||
__tablename__ = "are_contacts"
|
||||
# class EntrepriseSite(db.Model):
|
||||
# __tablename__ = "are_sites"
|
||||
# id = db.Column(db.Integer, primary_key=True)
|
||||
# entreprise_id = db.Column(
|
||||
# db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
|
||||
# )
|
||||
# nom = db.Column(db.Text)
|
||||
|
||||
|
||||
class EntrepriseCorrespondant(db.Model):
|
||||
__tablename__ = "are_correspondants"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
entreprise_id = db.Column(
|
||||
db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
|
||||
)
|
||||
# site_id = db.Column(db.Integer, db.ForeignKey("are_sites.id", ondelete="cascade"))
|
||||
nom = db.Column(db.Text)
|
||||
prenom = db.Column(db.Text)
|
||||
telephone = db.Column(db.Text)
|
||||
@ -61,6 +71,17 @@ class EntrepriseContact(db.Model):
|
||||
}
|
||||
|
||||
|
||||
class EntrepriseContact(db.Model):
|
||||
__tablename__ = "are_contacts"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
date = db.Column(db.DateTime(timezone=True))
|
||||
user = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="cascade"))
|
||||
entreprise = db.Column(
|
||||
db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
|
||||
)
|
||||
notes = db.Column(db.Text)
|
||||
|
||||
|
||||
class EntrepriseOffre(db.Model):
|
||||
__tablename__ = "are_offres"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
@ -75,6 +96,9 @@ class EntrepriseOffre(db.Model):
|
||||
duree = db.Column(db.Text)
|
||||
expiration_date = db.Column(db.Date)
|
||||
expired = db.Column(db.Boolean, default=False)
|
||||
correspondant_id = db.Column(
|
||||
db.Integer, db.ForeignKey("are_correspondants.id", ondelete="cascade")
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
@ -95,8 +119,8 @@ class EntrepriseLog(db.Model):
|
||||
text = db.Column(db.Text)
|
||||
|
||||
|
||||
class EntrepriseEtudiant(db.Model):
|
||||
__tablename__ = "are_etudiants"
|
||||
class EntrepriseStageApprentissage(db.Model):
|
||||
__tablename__ = "are_stages_apprentissages"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
entreprise_id = db.Column(
|
||||
db.Integer, db.ForeignKey("are_entreprises.id", ondelete="cascade")
|
||||
@ -107,6 +131,7 @@ class EntrepriseEtudiant(db.Model):
|
||||
date_fin = db.Column(db.Date)
|
||||
formation_text = db.Column(db.Text)
|
||||
formation_scodoc = db.Column(db.Integer)
|
||||
notes = db.Column(db.Text)
|
||||
|
||||
|
||||
class EntrepriseEnvoiOffre(db.Model):
|
||||
@ -136,6 +161,15 @@ class EntrepriseOffreDepartement(db.Model):
|
||||
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id", ondelete="cascade"))
|
||||
|
||||
|
||||
# class EntrepriseCorrespondantDepartement(db.Model):
|
||||
# __tablename__ = "are_correspondant_departement"
|
||||
# id = db.Column(db.Integer, primary_key=True)
|
||||
# correspondant_id = db.Column(
|
||||
# db.Integer, db.ForeignKey("are_correspondants.id", ondelete="cascade")
|
||||
# )
|
||||
# dept_id = db.Column(db.Integer, db.ForeignKey("departement.id", ondelete="cascade"))
|
||||
|
||||
|
||||
class EntreprisePreferences(db.Model):
|
||||
__tablename__ = "are_preferences"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
@ -12,14 +12,17 @@ from app.decorators import permission_required
|
||||
|
||||
from app.entreprises import LOGS_LEN
|
||||
from app.entreprises.forms import (
|
||||
CorrespondantsCreationForm,
|
||||
EntrepriseCreationForm,
|
||||
EntrepriseModificationForm,
|
||||
SuppressionConfirmationForm,
|
||||
OffreCreationForm,
|
||||
OffreModificationForm,
|
||||
CorrespondantModificationForm,
|
||||
ContactCreationForm,
|
||||
ContactModificationForm,
|
||||
HistoriqueCreationForm,
|
||||
StageApprentissageCreationForm,
|
||||
StageApprentissageModificationForm,
|
||||
EnvoiOffreForm,
|
||||
AjoutFichierForm,
|
||||
ValidationConfirmationForm,
|
||||
@ -30,9 +33,10 @@ from app.entreprises import bp
|
||||
from app.entreprises.models import (
|
||||
Entreprise,
|
||||
EntrepriseOffre,
|
||||
EntrepriseContact,
|
||||
EntrepriseCorrespondant,
|
||||
EntrepriseLog,
|
||||
EntrepriseEtudiant,
|
||||
EntrepriseContact,
|
||||
EntrepriseStageApprentissage,
|
||||
EntrepriseEnvoiOffre,
|
||||
EntrepriseOffreDepartement,
|
||||
EntreprisePreferences,
|
||||
@ -96,22 +100,22 @@ def validation():
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/contacts", methods=["GET"])
|
||||
@bp.route("/correspondants", methods=["GET"])
|
||||
@permission_required(Permission.RelationsEntreprisesView)
|
||||
def contacts():
|
||||
def correspondants():
|
||||
"""
|
||||
Permet d'afficher une page avec la liste des contacts des entreprises visibles et une liste des dernières opérations
|
||||
Permet d'afficher une page avec la liste des correspondants des entreprises visibles et une liste des dernières opérations
|
||||
"""
|
||||
contacts = (
|
||||
db.session.query(EntrepriseContact, Entreprise)
|
||||
.join(Entreprise, EntrepriseContact.entreprise_id == Entreprise.id)
|
||||
correspondants = (
|
||||
db.session.query(EntrepriseCorrespondant, Entreprise)
|
||||
.join(Entreprise, EntrepriseCorrespondant.entreprise_id == Entreprise.id)
|
||||
.filter_by(visible=True)
|
||||
)
|
||||
logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all()
|
||||
return render_template(
|
||||
"entreprises/contacts.html",
|
||||
title="Contacts",
|
||||
contacts=contacts,
|
||||
"entreprises/correspondants.html",
|
||||
title="Correspondants",
|
||||
correspondants=correspondants,
|
||||
logs=logs,
|
||||
)
|
||||
|
||||
@ -122,7 +126,7 @@ def fiche_entreprise(id):
|
||||
"""
|
||||
Permet d'afficher la fiche entreprise d'une entreprise avec une liste des dernières opérations et
|
||||
l'historique des étudiants ayant réaliser un stage ou une alternance dans cette entreprise.
|
||||
La fiche entreprise comporte les informations de l'entreprise, les contacts de l'entreprise et
|
||||
La fiche entreprise comporte les informations de l'entreprise, les correspondants de l'entreprise et
|
||||
les offres de l'entreprise.
|
||||
"""
|
||||
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404(
|
||||
@ -141,32 +145,32 @@ def fiche_entreprise(id):
|
||||
offre_with_files = are.get_offre_files_and_depts(offre, depts)
|
||||
if offre_with_files is not None:
|
||||
offres_with_files.append(offre_with_files)
|
||||
contacts = entreprise.contacts[:]
|
||||
correspondants = entreprise.correspondants[:]
|
||||
logs = (
|
||||
EntrepriseLog.query.order_by(EntrepriseLog.date.desc())
|
||||
.filter_by(object=id)
|
||||
.limit(LOGS_LEN)
|
||||
.all()
|
||||
)
|
||||
historique = (
|
||||
db.session.query(EntrepriseEtudiant, Identite)
|
||||
.order_by(EntrepriseEtudiant.date_debut.desc())
|
||||
.filter(EntrepriseEtudiant.entreprise_id == id)
|
||||
.join(Identite, Identite.id == EntrepriseEtudiant.etudid)
|
||||
stages_apprentissages = (
|
||||
db.session.query(EntrepriseStageApprentissage, Identite)
|
||||
.order_by(EntrepriseStageApprentissage.date_debut.desc())
|
||||
.filter(EntrepriseStageApprentissage.entreprise_id == id)
|
||||
.join(Identite, Identite.id == EntrepriseStageApprentissage.etudid)
|
||||
.all()
|
||||
)
|
||||
return render_template(
|
||||
"entreprises/fiche_entreprise.html",
|
||||
title="Fiche entreprise",
|
||||
entreprise=entreprise,
|
||||
contacts=contacts,
|
||||
correspondants=correspondants,
|
||||
offres=offres_with_files,
|
||||
logs=logs,
|
||||
historique=historique,
|
||||
stages_apprentissages=stages_apprentissages,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/logs/<int:id>", methods=["GET"])
|
||||
@bp.route("/fiche_entreprise/<int:id>/logs", methods=["GET"])
|
||||
@permission_required(Permission.RelationsEntreprisesView)
|
||||
def logs_entreprise(id):
|
||||
"""
|
||||
@ -198,12 +202,12 @@ def fiche_entreprise_validation(id):
|
||||
entreprise = Entreprise.query.filter_by(id=id, visible=False).first_or_404(
|
||||
description=f"fiche entreprise (validation) {id} inconnue"
|
||||
)
|
||||
contacts = entreprise.contacts
|
||||
correspondants = entreprise.correspondants
|
||||
return render_template(
|
||||
"entreprises/fiche_entreprise_validation.html",
|
||||
title="Validation fiche entreprise",
|
||||
entreprise=entreprise,
|
||||
contacts=contacts,
|
||||
correspondants=correspondants,
|
||||
)
|
||||
|
||||
|
||||
@ -221,6 +225,9 @@ def offres_recues():
|
||||
)
|
||||
offres_recues_with_files = []
|
||||
for offre in offres_recues:
|
||||
correspondant = EntrepriseCorrespondant.query.filter_by(
|
||||
id=offre[1].correspondant_id
|
||||
).first()
|
||||
files = []
|
||||
path = os.path.join(
|
||||
Config.SCODOC_VAR_DIR,
|
||||
@ -235,7 +242,7 @@ def offres_recues():
|
||||
for file in glob.glob(f"{dir}/*"):
|
||||
file = [os.path.basename(dir), os.path.basename(file)]
|
||||
files.append(file)
|
||||
offres_recues_with_files.append([offre[0], offre[1], files])
|
||||
offres_recues_with_files.append([offre[0], offre[1], files, correspondant])
|
||||
return render_template(
|
||||
"entreprises/offres_recues.html",
|
||||
title="Offres reçues",
|
||||
@ -287,23 +294,24 @@ def add_entreprise():
|
||||
)
|
||||
db.session.add(entreprise)
|
||||
db.session.commit()
|
||||
if form.nom_correspondant.data.strip():
|
||||
db.session.refresh(entreprise)
|
||||
contact = EntrepriseContact(
|
||||
correspondant = EntrepriseCorrespondant(
|
||||
entreprise_id=entreprise.id,
|
||||
nom=form.nom_contact.data.strip(),
|
||||
prenom=form.prenom_contact.data.strip(),
|
||||
nom=form.nom_correspondant.data.strip(),
|
||||
prenom=form.prenom_correspondant.data.strip(),
|
||||
telephone=form.telephone.data.strip(),
|
||||
mail=form.mail.data.strip(),
|
||||
poste=form.poste.data.strip(),
|
||||
service=form.service.data.strip(),
|
||||
)
|
||||
db.session.add(contact)
|
||||
db.session.add(correspondant)
|
||||
if current_user.has_permission(Permission.RelationsEntreprisesValidate, None):
|
||||
entreprise.visible = True
|
||||
nom_entreprise = f"<a href=/ScoDoc/entreprises/fiche_entreprise/{entreprise.id}>{entreprise.nom}</a>"
|
||||
log = EntrepriseLog(
|
||||
authenticated_user=current_user.user_name,
|
||||
text=f"{nom_entreprise} - Création de la fiche entreprise ({entreprise.nom}) avec un contact",
|
||||
text=f"{nom_entreprise} - Création de la fiche entreprise ({entreprise.nom})",
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
@ -314,18 +322,18 @@ def add_entreprise():
|
||||
db.session.commit()
|
||||
if EntreprisePreferences.get_email_notifications():
|
||||
are.send_email_notifications_entreprise(
|
||||
"entreprise en attente de validation", entreprise, contact
|
||||
"entreprise en attente de validation", entreprise
|
||||
)
|
||||
flash("L'entreprise a été ajouté à la liste pour la validation.")
|
||||
return redirect(url_for("entreprises.index"))
|
||||
return render_template(
|
||||
"entreprises/ajout_entreprise.html",
|
||||
title="Ajout entreprise avec contact",
|
||||
title="Ajout entreprise avec correspondant",
|
||||
form=form,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/edit_entreprise/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route("/fiche_entreprise/edit_entreprise/<int:id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def edit_entreprise(id):
|
||||
"""
|
||||
@ -396,7 +404,7 @@ def edit_entreprise(id):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/delete_entreprise/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route("/fiche_entreprise/delete_entreprise/<int:id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def delete_entreprise(id):
|
||||
"""
|
||||
@ -432,7 +440,9 @@ def delete_entreprise(id):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/validate_entreprise/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route(
|
||||
"/fiche_entreprise_validation/<int:id>/validate_entreprise", methods=["GET", "POST"]
|
||||
)
|
||||
@permission_required(Permission.RelationsEntreprisesValidate)
|
||||
def validate_entreprise(id):
|
||||
"""
|
||||
@ -447,7 +457,7 @@ def validate_entreprise(id):
|
||||
nom_entreprise = f"<a href=/ScoDoc/entreprises/fiche_entreprise/{entreprise.id}>{entreprise.nom}</a>"
|
||||
log = EntrepriseLog(
|
||||
authenticated_user=current_user.user_name,
|
||||
text=f"{nom_entreprise} - Validation de la fiche entreprise ({entreprise.nom}) avec un contact",
|
||||
text=f"{nom_entreprise} - Validation de la fiche entreprise ({entreprise.nom}) avec un correspondant",
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
@ -460,7 +470,10 @@ def validate_entreprise(id):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/delete_validation_entreprise/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route(
|
||||
"/fiche_entreprise_validation/<int:id>/delete_validation_entreprise",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@permission_required(Permission.RelationsEntreprisesValidate)
|
||||
def delete_validation_entreprise(id):
|
||||
"""
|
||||
@ -482,7 +495,7 @@ def delete_validation_entreprise(id):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/add_offre/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route("/fiche_entreprise/<int:id>/add_offre", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def add_offre(id):
|
||||
"""
|
||||
@ -491,7 +504,7 @@ def add_offre(id):
|
||||
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404(
|
||||
description=f"entreprise {id} inconnue"
|
||||
)
|
||||
form = OffreCreationForm()
|
||||
form = OffreCreationForm(hidden_entreprise_id=id)
|
||||
if form.validate_on_submit():
|
||||
offre = EntrepriseOffre(
|
||||
entreprise_id=entreprise.id,
|
||||
@ -501,6 +514,7 @@ def add_offre(id):
|
||||
missions=form.missions.data.strip(),
|
||||
duree=form.duree.data.strip(),
|
||||
expiration_date=form.expiration_date.data,
|
||||
correspondant_id=form.correspondant.data,
|
||||
)
|
||||
db.session.add(offre)
|
||||
db.session.commit()
|
||||
@ -511,6 +525,19 @@ def add_offre(id):
|
||||
dept_id=dept,
|
||||
)
|
||||
db.session.add(offre_dept)
|
||||
if form.fichier.data:
|
||||
date = f"{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}"
|
||||
path = os.path.join(
|
||||
Config.SCODOC_VAR_DIR,
|
||||
"entreprises",
|
||||
f"{offre.entreprise_id}",
|
||||
f"{offre.id}",
|
||||
f"{date}",
|
||||
)
|
||||
os.makedirs(path)
|
||||
file = form.fichier.data
|
||||
filename = secure_filename(file.filename)
|
||||
file.save(os.path.join(path, filename))
|
||||
log = EntrepriseLog(
|
||||
authenticated_user=current_user.user_name,
|
||||
object=entreprise.id,
|
||||
@ -527,7 +554,7 @@ def add_offre(id):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/edit_offre/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route("/fiche_entreprise/edit_offre/<int:id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def edit_offre(id):
|
||||
"""
|
||||
@ -537,7 +564,9 @@ def edit_offre(id):
|
||||
description=f"offre {id} inconnue"
|
||||
)
|
||||
offre_depts = EntrepriseOffreDepartement.query.filter_by(offre_id=offre.id).all()
|
||||
form = OffreModificationForm()
|
||||
form = OffreModificationForm(
|
||||
hidden_entreprise_id=offre.entreprise_id, correspondant=offre.correspondant_id
|
||||
)
|
||||
offre_depts_list = [(offre_dept.dept_id) for offre_dept in offre_depts]
|
||||
if form.validate_on_submit():
|
||||
offre.intitule = form.intitule.data.strip()
|
||||
@ -546,6 +575,7 @@ def edit_offre(id):
|
||||
offre.missions = form.missions.data.strip()
|
||||
offre.duree = form.duree.data.strip()
|
||||
offre.expiration_date = form.expiration_date.data
|
||||
offre.correspondant_id = form.correspondant.data
|
||||
if offre_depts_list != form.depts.data:
|
||||
for dept in form.depts.data:
|
||||
if dept not in offre_depts_list:
|
||||
@ -584,7 +614,7 @@ def edit_offre(id):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/delete_offre/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route("/fiche_entreprise/delete_offre/<int:id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def delete_offre(id):
|
||||
"""
|
||||
@ -621,7 +651,7 @@ def delete_offre(id):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/delete_offre_recue/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route("/offres_recues/delete_offre_recue/<int:id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesView)
|
||||
def delete_offre_recue(id):
|
||||
"""
|
||||
@ -635,7 +665,7 @@ def delete_offre_recue(id):
|
||||
return redirect(url_for("entreprises.offres_recues"))
|
||||
|
||||
|
||||
@bp.route("/expired/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route("/fiche_entreprise/expired/<int:id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def expired(id):
|
||||
"""
|
||||
@ -653,36 +683,153 @@ def expired(id):
|
||||
return redirect(url_for("entreprises.fiche_entreprise", id=offre.entreprise_id))
|
||||
|
||||
|
||||
@bp.route("/add_contact/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route("/fiche_entreprise/<int:id>/add_correspondant", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def add_contact(id):
|
||||
def add_correspondant(id):
|
||||
"""
|
||||
Permet d'ajouter un contact a une entreprise
|
||||
Permet d'ajouter un correspondant a une entreprise
|
||||
"""
|
||||
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404(
|
||||
description=f"entreprise {id} inconnue"
|
||||
)
|
||||
form = ContactCreationForm(hidden_entreprise_id=entreprise.id)
|
||||
form = CorrespondantsCreationForm(hidden_entreprise_id=entreprise.id)
|
||||
if form.validate_on_submit():
|
||||
contact = EntrepriseContact(
|
||||
for correspondant_entry in form.correspondants.entries:
|
||||
correspondant = EntrepriseCorrespondant(
|
||||
entreprise_id=entreprise.id,
|
||||
nom=form.nom.data.strip(),
|
||||
prenom=form.prenom.data.strip(),
|
||||
telephone=form.telephone.data.strip(),
|
||||
mail=form.mail.data.strip(),
|
||||
poste=form.poste.data.strip(),
|
||||
service=form.service.data.strip(),
|
||||
nom=correspondant_entry.nom.data.strip(),
|
||||
prenom=correspondant_entry.prenom.data.strip(),
|
||||
telephone=correspondant_entry.telephone.data.strip(),
|
||||
mail=correspondant_entry.mail.data.strip(),
|
||||
poste=correspondant_entry.poste.data.strip(),
|
||||
service=correspondant_entry.service.data.strip(),
|
||||
)
|
||||
log = EntrepriseLog(
|
||||
authenticated_user=current_user.user_name,
|
||||
object=entreprise.id,
|
||||
text="Création d'un contact",
|
||||
text="Création d'un correspondant",
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.add(correspondant)
|
||||
db.session.commit()
|
||||
flash("Le correspondant a été ajouté à la fiche entreprise.")
|
||||
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id))
|
||||
return render_template(
|
||||
"entreprises/ajout_correspondants.html",
|
||||
title="Ajout correspondant",
|
||||
form=form,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/fiche_entreprise/edit_correspondant/<int:id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def edit_correspondant(id):
|
||||
"""
|
||||
Permet de modifier un correspondant
|
||||
"""
|
||||
correspondant = EntrepriseCorrespondant.query.filter_by(id=id).first_or_404(
|
||||
description=f"correspondant {id} inconnu"
|
||||
)
|
||||
form = CorrespondantModificationForm(
|
||||
hidden_entreprise_id=correspondant.entreprise_id,
|
||||
hidden_correspondant_id=correspondant.id,
|
||||
)
|
||||
if form.validate_on_submit():
|
||||
correspondant.nom = form.nom.data.strip()
|
||||
correspondant.prenom = form.prenom.data.strip()
|
||||
correspondant.telephone = form.telephone.data.strip()
|
||||
correspondant.mail = form.mail.data.strip()
|
||||
correspondant.poste = form.poste.data.strip()
|
||||
correspondant.service = form.service.data.strip()
|
||||
log = EntrepriseLog(
|
||||
authenticated_user=current_user.user_name,
|
||||
object=correspondant.entreprise_id,
|
||||
text="Modification d'un correspondant",
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
flash("Le correspondant a été modifié.")
|
||||
return redirect(
|
||||
url_for("entreprises.fiche_entreprise", id=correspondant.entreprise.id)
|
||||
)
|
||||
elif request.method == "GET":
|
||||
form.nom.data = correspondant.nom
|
||||
form.prenom.data = correspondant.prenom
|
||||
form.telephone.data = correspondant.telephone
|
||||
form.mail.data = correspondant.mail
|
||||
form.poste.data = correspondant.poste
|
||||
form.service.data = correspondant.service
|
||||
return render_template(
|
||||
"entreprises/form.html",
|
||||
title="Modification correspondant",
|
||||
form=form,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/fiche_entreprise/delete_correspondant/<int:id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def delete_correspondant(id):
|
||||
"""
|
||||
Permet de supprimer un correspondant
|
||||
"""
|
||||
correspondant = EntrepriseCorrespondant.query.filter_by(id=id).first_or_404(
|
||||
description=f"correspondant {id} inconnu"
|
||||
)
|
||||
form = SuppressionConfirmationForm()
|
||||
if form.validate_on_submit():
|
||||
db.session.delete(correspondant)
|
||||
log = EntrepriseLog(
|
||||
authenticated_user=current_user.user_name,
|
||||
object=correspondant.entreprise_id,
|
||||
text="Suppression d'un correspondant",
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
flash("Le correspondant a été supprimé de la fiche entreprise.")
|
||||
return redirect(
|
||||
url_for("entreprises.fiche_entreprise", id=correspondant.entreprise_id)
|
||||
)
|
||||
return render_template(
|
||||
"entreprises/delete_confirmation.html",
|
||||
title="Supression correspondant",
|
||||
form=form,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/fiche_entreprise/<int:id>/add_contact", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def add_contact(id):
|
||||
"""
|
||||
Permet d'ajouter un contact avec une entreprise
|
||||
"""
|
||||
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404(
|
||||
description=f"entreprise {id} inconnue"
|
||||
)
|
||||
form = ContactCreationForm(
|
||||
date=f"{datetime.now().strftime('%Y-%m-%dT%H:%M')}",
|
||||
utilisateur=f"{current_user.nom} {current_user.prenom} ({current_user.user_name})"
|
||||
if current_user.nom and current_user.prenom
|
||||
else "",
|
||||
)
|
||||
if form.validate_on_submit():
|
||||
utilisateur_data = form.utilisateur.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')')) FROM \"user\" WHERE UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')'))=:utilisateur_data"
|
||||
)
|
||||
utilisateur = (
|
||||
User.query.from_statement(stm)
|
||||
.params(utilisateur_data=utilisateur_data)
|
||||
.first()
|
||||
)
|
||||
contact = EntrepriseContact(
|
||||
date=form.date.data,
|
||||
user=utilisateur.id,
|
||||
entreprise=entreprise.id,
|
||||
notes=form.notes.data.strip(),
|
||||
)
|
||||
db.session.add(contact)
|
||||
db.session.commit()
|
||||
flash("Le contact a été ajouté à la fiche entreprise.")
|
||||
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id))
|
||||
return redirect(url_for("entreprises.contacts", id=entreprise.id))
|
||||
return render_template(
|
||||
"entreprises/form.html",
|
||||
title="Ajout contact",
|
||||
@ -690,44 +837,38 @@ def add_contact(id):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/edit_contact/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route("/fiche_entreprise/edit_contact/<int:id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def edit_contact(id):
|
||||
"""
|
||||
Permet de modifier un contact
|
||||
Permet d'editer un contact avec une entreprise
|
||||
"""
|
||||
contact = EntrepriseContact.query.filter_by(id=id).first_or_404(
|
||||
description=f"contact {id} inconnu"
|
||||
)
|
||||
form = ContactModificationForm(
|
||||
hidden_entreprise_id=contact.entreprise_id,
|
||||
hidden_contact_id=contact.id,
|
||||
)
|
||||
form = ContactModificationForm()
|
||||
if form.validate_on_submit():
|
||||
contact.nom = form.nom.data.strip()
|
||||
contact.prenom = form.prenom.data.strip()
|
||||
contact.telephone = form.telephone.data.strip()
|
||||
contact.mail = form.mail.data.strip()
|
||||
contact.poste = form.poste.data.strip()
|
||||
contact.service = form.service.data.strip()
|
||||
log = EntrepriseLog(
|
||||
authenticated_user=current_user.user_name,
|
||||
object=contact.entreprise_id,
|
||||
text="Modification d'un contact",
|
||||
utilisateur_data = form.utilisateur.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')')) FROM \"user\" WHERE UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')'))=:utilisateur_data"
|
||||
)
|
||||
db.session.add(log)
|
||||
utilisateur = (
|
||||
User.query.from_statement(stm)
|
||||
.params(utilisateur_data=utilisateur_data)
|
||||
.first()
|
||||
)
|
||||
contact.date = form.date.data
|
||||
contact.user = utilisateur.id
|
||||
contact.notes = form.notes.data
|
||||
db.session.commit()
|
||||
flash("Le contact a été modifié.")
|
||||
return redirect(
|
||||
url_for("entreprises.fiche_entreprise", id=contact.entreprise.id)
|
||||
)
|
||||
return redirect(url_for("entreprises.contacts", id=contact.entreprise))
|
||||
elif request.method == "GET":
|
||||
form.nom.data = contact.nom
|
||||
form.prenom.data = contact.prenom
|
||||
form.telephone.data = contact.telephone
|
||||
form.mail.data = contact.mail
|
||||
form.poste.data = contact.poste
|
||||
form.service.data = contact.service
|
||||
utilisateur = User.query.filter_by(id=contact.user).first()
|
||||
form.date.data = contact.date.strftime("%Y-%m-%dT%H:%M")
|
||||
form.utilisateur.data = (
|
||||
f"{utilisateur.nom} {utilisateur.prenom} ({utilisateur.user_name})"
|
||||
)
|
||||
form.notes.data = contact.notes
|
||||
return render_template(
|
||||
"entreprises/form.html",
|
||||
title="Modification contact",
|
||||
@ -735,57 +876,31 @@ def edit_contact(id):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/delete_contact/<int:id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def delete_contact(id):
|
||||
@bp.route("/fiche_entreprise/<int:id>/contacts")
|
||||
@permission_required(Permission.RelationsEntreprisesView)
|
||||
def contacts(id):
|
||||
"""
|
||||
Permet de supprimer un contact
|
||||
Permet d'afficher une page avec la liste des contacts d'une entreprise
|
||||
"""
|
||||
contact = EntrepriseContact.query.filter_by(id=id).first_or_404(
|
||||
description=f"contact {id} inconnu"
|
||||
)
|
||||
form = SuppressionConfirmationForm()
|
||||
if form.validate_on_submit():
|
||||
contact_count = EntrepriseContact.query.filter_by(
|
||||
entreprise_id=contact.entreprise_id
|
||||
).count()
|
||||
if contact_count == 1:
|
||||
flash(
|
||||
"Le contact n'a pas été supprimé de la fiche entreprise. (1 contact minimum)"
|
||||
)
|
||||
return redirect(
|
||||
url_for("entreprises.fiche_entreprise", id=contact.entreprise_id)
|
||||
)
|
||||
else:
|
||||
db.session.delete(contact)
|
||||
log = EntrepriseLog(
|
||||
authenticated_user=current_user.user_name,
|
||||
object=contact.entreprise_id,
|
||||
text="Suppression d'un contact",
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
flash("Le contact a été supprimé de la fiche entreprise.")
|
||||
return redirect(
|
||||
url_for("entreprises.fiche_entreprise", id=contact.entreprise_id)
|
||||
)
|
||||
contacts = EntrepriseContact.query.filter_by(entreprise=id).all()
|
||||
return render_template(
|
||||
"entreprises/delete_confirmation.html",
|
||||
title="Supression contact",
|
||||
form=form,
|
||||
"entreprises/contacts.html",
|
||||
title="Liste des contacts",
|
||||
contacts=contacts,
|
||||
entreprise_id=id,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/add_historique/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route("/fiche_entreprise/<int:id>/add_stage_apprentissage", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def add_historique(id):
|
||||
def add_stage_apprentissage(id):
|
||||
"""
|
||||
Permet d'ajouter un étudiant ayant réalisé un stage ou une alternance sur la fiche entreprise de l'entreprise
|
||||
"""
|
||||
entreprise = Entreprise.query.filter_by(id=id, visible=True).first_or_404(
|
||||
description=f"entreprise {id} inconnue"
|
||||
)
|
||||
form = HistoriqueCreationForm()
|
||||
form = StageApprentissageCreationForm()
|
||||
if form.validate_on_submit():
|
||||
etudiant_nomcomplet = form.etudiant.data.upper().strip()
|
||||
stm = text(
|
||||
@ -799,7 +914,7 @@ def add_historique(id):
|
||||
formation = etudiant.inscription_courante_date(
|
||||
form.date_debut.data, form.date_fin.data
|
||||
)
|
||||
historique = EntrepriseEtudiant(
|
||||
stage_apprentissage = EntrepriseStageApprentissage(
|
||||
entreprise_id=entreprise.id,
|
||||
etudid=etudiant.id,
|
||||
type_offre=form.type_offre.data.strip(),
|
||||
@ -809,19 +924,105 @@ def add_historique(id):
|
||||
formation_scodoc=formation.formsemestre.formsemestre_id
|
||||
if formation
|
||||
else None,
|
||||
notes=form.notes.data.strip(),
|
||||
)
|
||||
db.session.add(historique)
|
||||
db.session.add(stage_apprentissage)
|
||||
db.session.commit()
|
||||
flash("L'étudiant a été ajouté sur la fiche entreprise.")
|
||||
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id))
|
||||
return render_template(
|
||||
"entreprises/ajout_historique.html",
|
||||
title="Ajout historique",
|
||||
"entreprises/ajout_stage_apprentissage.html",
|
||||
title="Ajout stage / apprentissage",
|
||||
form=form,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/envoyer_offre/<int:id>", methods=["GET", "POST"])
|
||||
@bp.route(
|
||||
"/fiche_entreprise/edit_stage_apprentissage/<int:id>", methods=["GET", "POST"]
|
||||
)
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def edit_stage_apprentissage(id):
|
||||
"""
|
||||
Permet de modifier un étudiant ayant réalisé un stage ou une alternance sur la fiche entreprise de l'entreprise
|
||||
"""
|
||||
stage_apprentissage = EntrepriseStageApprentissage.query.filter_by(
|
||||
id=id
|
||||
).first_or_404(description=f"stage_apprentissage {id} inconnue")
|
||||
etudiant = Identite.query.filter_by(id=stage_apprentissage.etudid).first_or_404(
|
||||
description=f"etudiant {id} inconnue"
|
||||
)
|
||||
form = StageApprentissageModificationForm()
|
||||
if form.validate_on_submit():
|
||||
etudiant_nomcomplet = form.etudiant.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, CONCAT(nom, ' ', prenom) as nom_prenom FROM Identite WHERE CONCAT(nom, ' ', prenom)=:nom_prenom"
|
||||
)
|
||||
etudiant = (
|
||||
Identite.query.from_statement(stm)
|
||||
.params(nom_prenom=etudiant_nomcomplet)
|
||||
.first()
|
||||
)
|
||||
formation = etudiant.inscription_courante_date(
|
||||
form.date_debut.data, form.date_fin.data
|
||||
)
|
||||
stage_apprentissage.etudid = etudiant.id
|
||||
stage_apprentissage.type_offre = form.type_offre.data.strip()
|
||||
stage_apprentissage.date_debut = form.date_debut.data
|
||||
stage_apprentissage.date_fin = form.date_fin.data
|
||||
stage_apprentissage.formation_text = (
|
||||
formation.formsemestre.titre if formation else None,
|
||||
)
|
||||
stage_apprentissage.formation_scodoc = (
|
||||
formation.formsemestre.formsemestre_id if formation else None,
|
||||
)
|
||||
stage_apprentissage.notes = form.notes.data.strip()
|
||||
db.session.commit()
|
||||
return redirect(
|
||||
url_for(
|
||||
"entreprises.fiche_entreprise", id=stage_apprentissage.entreprise_id
|
||||
)
|
||||
)
|
||||
elif request.method == "GET":
|
||||
form.etudiant.data = f"{sco_etud.format_nom(etudiant.nom)} {sco_etud.format_prenom(etudiant.prenom)}"
|
||||
form.type_offre.data = stage_apprentissage.type_offre
|
||||
form.date_debut.data = stage_apprentissage.date_debut
|
||||
form.date_fin.data = stage_apprentissage.date_fin
|
||||
form.notes.data = stage_apprentissage.notes
|
||||
return render_template(
|
||||
"entreprises/ajout_stage_apprentissage.html",
|
||||
title="Modification stage / apprentissage",
|
||||
form=form,
|
||||
)
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/fiche_entreprise/delete_stage_apprentissage/<int:id>", methods=["GET", "POST"]
|
||||
)
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def delete_stage_apprentissage(id):
|
||||
"""
|
||||
Permet de supprimer un étudiant ayant réalisé un stage ou une alternance sur la fiche entreprise de l'entreprise
|
||||
"""
|
||||
stage_apprentissage = EntrepriseStageApprentissage.query.filter_by(
|
||||
id=id
|
||||
).first_or_404(description=f"stage_apprentissage {id} inconnu")
|
||||
form = SuppressionConfirmationForm()
|
||||
if form.validate_on_submit():
|
||||
db.session.delete(stage_apprentissage)
|
||||
db.session.commit()
|
||||
return redirect(
|
||||
url_for(
|
||||
"entreprises.fiche_entreprise", id=stage_apprentissage.entreprise_id
|
||||
)
|
||||
)
|
||||
return render_template(
|
||||
"entreprises/delete_confirmation.html",
|
||||
title="Supression stage/apprentissage",
|
||||
form=form,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/fiche_entreprise/envoyer_offre/<int:id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesSend)
|
||||
def envoyer_offre(id):
|
||||
"""
|
||||
@ -832,7 +1033,9 @@ def envoyer_offre(id):
|
||||
)
|
||||
form = EnvoiOffreForm()
|
||||
if form.validate_on_submit():
|
||||
responsable_data = form.responsable.data.upper().strip()
|
||||
for responsable in form.responsables.entries:
|
||||
if responsable.data.strip():
|
||||
responsable_data = responsable.data.upper().strip()
|
||||
stm = text(
|
||||
"SELECT id, UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')')) FROM \"user\" WHERE UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')'))=:responsable_data"
|
||||
)
|
||||
@ -924,10 +1127,11 @@ def export_entreprises():
|
||||
filename = title
|
||||
return scu.send_file(xlsx, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
|
||||
else:
|
||||
abort(404)
|
||||
flash("Aucune entreprise dans la base.")
|
||||
return redirect(url_for("entreprises.index"))
|
||||
|
||||
|
||||
@bp.route("/get_import_entreprises_file_sample")
|
||||
@bp.route("/import_entreprises/get_import_entreprises_file_sample")
|
||||
@permission_required(Permission.RelationsEntreprisesExport)
|
||||
def get_import_entreprises_file_sample():
|
||||
"""
|
||||
@ -978,20 +1182,19 @@ def import_entreprises():
|
||||
ligne += 1
|
||||
if (
|
||||
are.verif_entreprise_data(entreprise_data)
|
||||
and entreprise_data[0] not in siret_list
|
||||
and entreprise_data[0].replace(" ", "") not in siret_list
|
||||
):
|
||||
siret_list.append(entreprise_data[0])
|
||||
siret_list.append(entreprise_data[0].replace(" ", ""))
|
||||
entreprise = Entreprise(
|
||||
siret=entreprise_data[0],
|
||||
nom=entreprise_data[1],
|
||||
adresse=entreprise_data[2],
|
||||
ville=entreprise_data[3],
|
||||
codepostal=entreprise_data[4],
|
||||
pays=entreprise_data[5],
|
||||
siret=entreprise_data[0].replace(" ", ""),
|
||||
nom=entreprise_data[1].strip(),
|
||||
adresse=entreprise_data[2].strip(),
|
||||
ville=entreprise_data[3].strip(),
|
||||
codepostal=entreprise_data[4].strip(),
|
||||
pays=entreprise_data[5].strip(),
|
||||
visible=True,
|
||||
)
|
||||
entreprises_import.append(entreprise)
|
||||
|
||||
else:
|
||||
flash(f"Erreur lors de l'importation (ligne {ligne})")
|
||||
return render_template(
|
||||
@ -1026,19 +1229,19 @@ def import_entreprises():
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/export_contacts")
|
||||
@bp.route("/export_correspondants")
|
||||
@permission_required(Permission.RelationsEntreprisesExport)
|
||||
def export_contacts():
|
||||
def export_correspondants():
|
||||
"""
|
||||
Permet d'exporter la liste des contacts sous format excel (.xlsx)
|
||||
Permet d'exporter la liste des correspondants sous format excel (.xlsx)
|
||||
"""
|
||||
contacts = (
|
||||
db.session.query(EntrepriseContact)
|
||||
.join(Entreprise, EntrepriseContact.entreprise_id == Entreprise.id)
|
||||
correspondants = (
|
||||
db.session.query(EntrepriseCorrespondant)
|
||||
.join(Entreprise, EntrepriseCorrespondant.entreprise_id == Entreprise.id)
|
||||
.filter_by(visible=True)
|
||||
.all()
|
||||
)
|
||||
if contacts:
|
||||
if correspondants:
|
||||
keys = [
|
||||
"nom",
|
||||
"prenom",
|
||||
@ -1049,20 +1252,24 @@ def export_contacts():
|
||||
"entreprise_siret",
|
||||
]
|
||||
titles = keys[:]
|
||||
L = [[contact.to_dict().get(k, "") for k in keys] for contact in contacts]
|
||||
title = "Contacts"
|
||||
L = [
|
||||
[correspondant.to_dict().get(k, "") for k in keys]
|
||||
for correspondant in correspondants
|
||||
]
|
||||
title = "Correspondants"
|
||||
xlsx = sco_excel.excel_simple_table(titles=titles, lines=L, sheet_name=title)
|
||||
filename = title
|
||||
return scu.send_file(xlsx, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
|
||||
else:
|
||||
abort(404)
|
||||
flash("Aucun correspondant dans la base.")
|
||||
return redirect(url_for("entreprises.correspondants"))
|
||||
|
||||
|
||||
@bp.route("/get_import_contacts_file_sample")
|
||||
@bp.route("/import_correspondants/get_import_correspondants_file_sample")
|
||||
@permission_required(Permission.RelationsEntreprisesExport)
|
||||
def get_import_contacts_file_sample():
|
||||
def get_import_correspondants_file_sample():
|
||||
"""
|
||||
Permet de récupérer un fichier exemple vide pour pouvoir importer des contacts
|
||||
Permet de récupérer un fichier exemple vide pour pouvoir importer des correspondants
|
||||
"""
|
||||
keys = [
|
||||
"nom",
|
||||
@ -1074,17 +1281,17 @@ def get_import_contacts_file_sample():
|
||||
"entreprise_siret",
|
||||
]
|
||||
titles = keys[:]
|
||||
title = "ImportContacts"
|
||||
xlsx = sco_excel.excel_simple_table(titles=titles, sheet_name="Contacts")
|
||||
title = "ImportCorrespondants"
|
||||
xlsx = sco_excel.excel_simple_table(titles=titles, sheet_name="Correspondants")
|
||||
filename = title
|
||||
return scu.send_file(xlsx, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
|
||||
|
||||
|
||||
@bp.route("/import_contacts", methods=["GET", "POST"])
|
||||
@bp.route("/import_correspondants", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesExport)
|
||||
def import_contacts():
|
||||
def import_correspondants():
|
||||
"""
|
||||
Permet d'importer des contacts a l'aide d'un fichier excel (.xlsx)
|
||||
Permet d'importer des correspondants a l'aide d'un fichier excel (.xlsx)
|
||||
"""
|
||||
form = ImportForm()
|
||||
if form.validate_on_submit():
|
||||
@ -1095,8 +1302,8 @@ def import_contacts():
|
||||
file.save(file_path)
|
||||
data = sco_excel.excel_file_to_list(file_path)
|
||||
os.remove(file_path)
|
||||
contacts_import = []
|
||||
contact_list = []
|
||||
correspondants_import = []
|
||||
correspondant_list = []
|
||||
ligne = 0
|
||||
titles = [
|
||||
"nom",
|
||||
@ -1110,57 +1317,72 @@ def import_contacts():
|
||||
if data[1][0] != titles:
|
||||
flash("Veuillez utilisez la feuille excel à remplir")
|
||||
return render_template(
|
||||
"entreprises/import_contacts.html",
|
||||
title="Importation contacts",
|
||||
"entreprises/import_correspondants.html",
|
||||
title="Importation correspondants",
|
||||
form=form,
|
||||
)
|
||||
for contact_data in data[1][1:]:
|
||||
for correspondant_data in data[1][1:]:
|
||||
ligne += 1
|
||||
if (
|
||||
are.verif_contact_data(contact_data)
|
||||
and (contact_data[0], contact_data[1], contact_data[6])
|
||||
not in contact_list
|
||||
):
|
||||
contact_list.append((contact_data[0], contact_data[1], contact_data[6]))
|
||||
contact = EntrepriseContact(
|
||||
nom=contact_data[0],
|
||||
prenom=contact_data[1],
|
||||
telephone=contact_data[2],
|
||||
mail=contact_data[3],
|
||||
poste=contact_data[4],
|
||||
service=contact_data[5],
|
||||
entreprise_id=contact_data[6],
|
||||
are.verif_correspondant_data(correspondant_data)
|
||||
and (
|
||||
correspondant_data[0].strip(),
|
||||
correspondant_data[1].strip(),
|
||||
correspondant_data[6].strip(),
|
||||
)
|
||||
contacts_import.append(contact)
|
||||
not in correspondant_list
|
||||
):
|
||||
correspondant_list.append(
|
||||
(
|
||||
correspondant_data[0].strip(),
|
||||
correspondant_data[1].strip(),
|
||||
correspondant_data[6].strip(),
|
||||
)
|
||||
)
|
||||
entreprise = Entreprise.query.filter_by(
|
||||
siret=correspondant_data[6].strip()
|
||||
).first()
|
||||
correspondant = EntrepriseCorrespondant(
|
||||
nom=correspondant_data[0].strip(),
|
||||
prenom=correspondant_data[1].strip(),
|
||||
telephone=correspondant_data[2].strip(),
|
||||
mail=correspondant_data[3].strip(),
|
||||
poste=correspondant_data[4].strip(),
|
||||
service=correspondant_data[5].strip(),
|
||||
entreprise_id=entreprise.id,
|
||||
)
|
||||
correspondants_import.append(correspondant)
|
||||
else:
|
||||
flash(f"Erreur lors de l'importation (ligne {ligne})")
|
||||
return render_template(
|
||||
"entreprises/import_contacts.html",
|
||||
title="Importation contacts",
|
||||
"entreprises/import_correspondants.html",
|
||||
title="Importation correspondants",
|
||||
form=form,
|
||||
)
|
||||
|
||||
if len(contacts_import) > 0:
|
||||
for contact in contacts_import:
|
||||
db.session.add(contact)
|
||||
if len(correspondants_import) > 0:
|
||||
for correspondant in correspondants_import:
|
||||
db.session.add(correspondant)
|
||||
log = EntrepriseLog(
|
||||
authenticated_user=current_user.user_name,
|
||||
text=f"Importation de {len(contacts_import)} contact(s)",
|
||||
text=f"Importation de {len(correspondants_import)} correspondant(s)",
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
flash(f"Importation réussie de {len(contacts_import)} contact(s)")
|
||||
flash(
|
||||
f"Importation réussie de {len(correspondants_import)} correspondant(s)"
|
||||
)
|
||||
return render_template(
|
||||
"entreprises/import_contacts.html",
|
||||
title="Importation Contacts",
|
||||
"entreprises/import_correspondants.html",
|
||||
title="Importation correspondants",
|
||||
form=form,
|
||||
contacts_import=contacts_import,
|
||||
correspondants_import=correspondants_import,
|
||||
)
|
||||
else:
|
||||
flash('Feuille "Contacts" vide')
|
||||
flash('Feuille "Correspondants" vide')
|
||||
return render_template(
|
||||
"entreprises/import_contacts.html",
|
||||
title="Importation contacts",
|
||||
"entreprises/import_correspondants.html",
|
||||
title="Importation correspondants",
|
||||
form=form,
|
||||
)
|
||||
|
||||
@ -1198,7 +1420,7 @@ def get_offre_file(entreprise_id, offre_id, filedir, filename):
|
||||
abort(404, description=f"fichier {filename} inconnu")
|
||||
|
||||
|
||||
@bp.route("/add_offre_file/<int:offre_id>", methods=["GET", "POST"])
|
||||
@bp.route("/fiche_entreprise/add_offre_file/<int:offre_id>", methods=["GET", "POST"])
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def add_offre_file(offre_id):
|
||||
"""
|
||||
@ -1230,7 +1452,10 @@ def add_offre_file(offre_id):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/delete_offre_file/<int:offre_id>/<string:filedir>", methods=["GET", "POST"])
|
||||
@bp.route(
|
||||
"/fiche_entreprise/delete_offre_file/<int:offre_id>/<string:filedir>",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@permission_required(Permission.RelationsEntreprisesChange)
|
||||
def delete_offre_file(offre_id, filedir):
|
||||
"""
|
||||
|
@ -41,11 +41,8 @@ from wtforms.fields.simple import StringField, HiddenField
|
||||
from app.models import Departement
|
||||
from app.scodoc import sco_logos, html_sco_header
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_config_actions import (
|
||||
LogoDelete,
|
||||
LogoUpdate,
|
||||
LogoInsert,
|
||||
)
|
||||
|
||||
from app.scodoc.sco_config_actions import LogoInsert
|
||||
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
|
||||
@ -120,6 +117,8 @@ def logo_name_validator(message=None):
|
||||
class AddLogoForm(FlaskForm):
|
||||
"""Formulaire permettant l'ajout d'un logo (dans un département)"""
|
||||
|
||||
from app.scodoc.sco_config_actions import LogoInsert
|
||||
|
||||
dept_key = HiddenField()
|
||||
name = StringField(
|
||||
label="Nom",
|
||||
@ -151,7 +150,7 @@ class AddLogoForm(FlaskForm):
|
||||
dept_id = dept_key_to_id(self.dept_key.data)
|
||||
if dept_id == GLOBAL:
|
||||
dept_id = None
|
||||
if find_logo(logoname=name.data, dept_id=dept_id) is not None:
|
||||
if find_logo(logoname=name.data, dept_id=dept_id, strict=True) is not None:
|
||||
raise validators.ValidationError("Un logo de même nom existe déjà")
|
||||
|
||||
def select_action(self):
|
||||
@ -160,6 +159,14 @@ class AddLogoForm(FlaskForm):
|
||||
return LogoInsert.build_action(self.data)
|
||||
return None
|
||||
|
||||
def opened(self):
|
||||
if self.do_insert.data:
|
||||
if self.name.errors:
|
||||
return "open"
|
||||
if self.upload.errors:
|
||||
return "open"
|
||||
return ""
|
||||
|
||||
|
||||
class LogoForm(FlaskForm):
|
||||
"""Embed both presentation of a logo (cf. template file configuration.html)
|
||||
@ -176,7 +183,18 @@ class LogoForm(FlaskForm):
|
||||
)
|
||||
],
|
||||
)
|
||||
do_delete = SubmitField("Supprimer l'image")
|
||||
do_delete = SubmitField("Supprimer")
|
||||
do_rename = SubmitField("Renommer")
|
||||
new_name = StringField(
|
||||
label="Nom",
|
||||
validators=[
|
||||
logo_name_validator("Nom de logo invalide (alphanumérique, _)"),
|
||||
validators.Length(
|
||||
max=20, message="Un nom ne doit pas dépasser 20 caractères"
|
||||
),
|
||||
validators.DataRequired("Nom de logo requis (alphanumériques ou '-')"),
|
||||
],
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["meta"] = {"csrf": False}
|
||||
@ -205,12 +223,25 @@ class LogoForm(FlaskForm):
|
||||
self.titre = "Logo pied de page"
|
||||
|
||||
def select_action(self):
|
||||
from app.scodoc.sco_config_actions import LogoRename
|
||||
from app.scodoc.sco_config_actions import LogoUpdate
|
||||
from app.scodoc.sco_config_actions import LogoDelete
|
||||
|
||||
if self.do_delete.data and self.can_delete:
|
||||
return LogoDelete.build_action(self.data)
|
||||
if self.upload.data and self.validate():
|
||||
return LogoUpdate.build_action(self.data)
|
||||
if self.do_rename.data and self.validate():
|
||||
return LogoRename.build_action(self.data)
|
||||
return None
|
||||
|
||||
def opened(self):
|
||||
if self.upload.data and self.upload.errors:
|
||||
return "open"
|
||||
if self.new_name.data and self.new_name.errors:
|
||||
return "open"
|
||||
return ""
|
||||
|
||||
|
||||
class DeptForm(FlaskForm):
|
||||
dept_key = HiddenField()
|
||||
@ -244,6 +275,23 @@ class DeptForm(FlaskForm):
|
||||
return self
|
||||
return self.index.get(logoname, None)
|
||||
|
||||
def opened(self):
|
||||
if self.add_logo.opened():
|
||||
return "open"
|
||||
for logo_form in self.logos:
|
||||
if logo_form.opened():
|
||||
return "open"
|
||||
return ""
|
||||
|
||||
def count(self):
|
||||
compte = len(self.logos.entries)
|
||||
if compte == 0:
|
||||
return "vide"
|
||||
elif compte == 1:
|
||||
return "1 élément"
|
||||
else:
|
||||
return f"{compte} éléments"
|
||||
|
||||
|
||||
def _make_dept_id_name():
|
||||
"""Cette section assure que tous les départements sont traités (y compris ceux qu'ont pas de logo au départ)
|
||||
|
@ -27,6 +27,20 @@ class Absence(db.Model):
|
||||
# XXX TODO: contrainte ajoutée: vérifier suppression du module
|
||||
# (mettre à NULL sans supprimer)
|
||||
|
||||
def to_dict(self):
|
||||
data = {
|
||||
"id": self.id,
|
||||
"etudid": self.etudid,
|
||||
"jour": self.jour,
|
||||
"estabs": self.estabs,
|
||||
"estjust": self.estjust,
|
||||
"matin": self.matin,
|
||||
"description": self.description,
|
||||
"entry_date": self.entry_date,
|
||||
"moduleimpl_id": self.moduleimpl_id,
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
class AbsenceNotification(db.Model):
|
||||
"""Notification d'absence émise"""
|
||||
|
@ -302,21 +302,45 @@ class Identite(db.Model):
|
||||
else:
|
||||
date_ins = events[0].event_date
|
||||
situation += date_ins.strftime(" le %d/%m/%Y")
|
||||
elif inscr.etat == scu.DEF:
|
||||
situation = f"défaillant en {inscr.formsemestre.titre_mois()}"
|
||||
event = (
|
||||
models.ScolarEvent.query.filter_by(
|
||||
etudid=self.id,
|
||||
formsemestre_id=inscr.formsemestre.id,
|
||||
event_type="DEFAILLANCE",
|
||||
)
|
||||
.order_by(models.ScolarEvent.event_date)
|
||||
.first()
|
||||
)
|
||||
if not event:
|
||||
log(
|
||||
f"*** situation inconsistante pour {self} (def mais pas d'event)"
|
||||
)
|
||||
situation += "???" # ???
|
||||
else:
|
||||
date_def = event.event_date
|
||||
situation += date_def.strftime(" le %d/%m/%Y")
|
||||
|
||||
else:
|
||||
situation = f"démission de {inscr.formsemestre.titre_mois()}"
|
||||
# Cherche la date de demission dans scolar_events:
|
||||
events = models.ScolarEvent.query.filter_by(
|
||||
event = (
|
||||
models.ScolarEvent.query.filter_by(
|
||||
etudid=self.id,
|
||||
formsemestre_id=inscr.formsemestre.id,
|
||||
event_type="DEMISSION",
|
||||
).all()
|
||||
if not events:
|
||||
)
|
||||
.order_by(models.ScolarEvent.event_date)
|
||||
.first()
|
||||
)
|
||||
if not event:
|
||||
log(
|
||||
f"*** situation inconsistante pour {self} (demission mais pas d'event)"
|
||||
)
|
||||
date_dem = "???" # ???
|
||||
situation += "???" # ???
|
||||
else:
|
||||
date_dem = events[0].event_date
|
||||
date_dem = event.event_date
|
||||
situation += date_dem.strftime(" le %d/%m/%Y")
|
||||
else:
|
||||
situation = "non inscrit" + self.e
|
||||
|
@ -36,18 +36,21 @@ class Scolog(db.Model):
|
||||
class ScolarNews(db.Model):
|
||||
"""Nouvelles pour page d'accueil"""
|
||||
|
||||
NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id)
|
||||
NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id)
|
||||
NEWS_FORM = "FORM" # modification formation (object=formation_id)
|
||||
NEWS_SEM = "SEM" # creation semestre (object=None)
|
||||
NEWS_ABS = "ABS" # saisie absence
|
||||
NEWS_APO = "APO" # changements de codes APO
|
||||
NEWS_FORM = "FORM" # modification formation (object=formation_id)
|
||||
NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id)
|
||||
NEWS_MISC = "MISC" # unused
|
||||
NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id)
|
||||
NEWS_SEM = "SEM" # creation semestre (object=None)
|
||||
NEWS_MAP = {
|
||||
NEWS_INSCR: "inscription d'étudiants",
|
||||
NEWS_NOTE: "saisie note",
|
||||
NEWS_ABS: "saisie absence",
|
||||
NEWS_APO: "modif. code Apogée",
|
||||
NEWS_FORM: "modification formation",
|
||||
NEWS_SEM: "création semestre",
|
||||
NEWS_INSCR: "inscription d'étudiants",
|
||||
NEWS_MISC: "opération", # unused
|
||||
NEWS_NOTE: "saisie note",
|
||||
NEWS_SEM: "création semestre",
|
||||
}
|
||||
NEWS_TYPES = list(NEWS_MAP.keys())
|
||||
|
||||
|
@ -146,6 +146,7 @@ class Formation(db.Model):
|
||||
db.session.add(ue)
|
||||
|
||||
db.session.commit()
|
||||
if change:
|
||||
app.clear_scodoc_cache()
|
||||
|
||||
|
||||
|
@ -286,7 +286,7 @@ class FormSemestre(db.Model):
|
||||
"""
|
||||
if not self.etapes:
|
||||
return ""
|
||||
return ", ".join([str(x.etape_apo) for x in self.etapes])
|
||||
return ", ".join(sorted([str(x.etape_apo) for x in self.etapes]))
|
||||
|
||||
def responsables_str(self, abbrev_prenom=True) -> str:
|
||||
"""chaîne "J. Dupond, X. Martin"
|
||||
@ -375,7 +375,7 @@ class FormSemestre(db.Model):
|
||||
return f"{self.titre} {self.formation.get_parcours().SESSION_NAME} {self.semestre_id}"
|
||||
|
||||
def sem_modalite(self) -> str:
|
||||
"""Le semestre et la modialité, ex "S2 FI" ou "S3 APP" """
|
||||
"""Le semestre et la modalité, ex "S2 FI" ou "S3 APP" """
|
||||
if self.semestre_id > 0:
|
||||
descr_sem = f"S{self.semestre_id}"
|
||||
else:
|
||||
@ -433,7 +433,7 @@ notes_formsemestre_responsables = db.Table(
|
||||
|
||||
|
||||
class FormSemestreEtape(db.Model):
|
||||
"""Étape Apogée associées au semestre"""
|
||||
"""Étape Apogée associée au semestre"""
|
||||
|
||||
__tablename__ = "notes_formsemestre_etapes"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
@ -486,7 +486,10 @@ class JuryPE(object):
|
||||
sesdates = [
|
||||
pe_tagtable.conversionDate_StrToDate(sem["date_fin"]) for sem in sessems
|
||||
] # association 1 date -> 1 semestrePE pour les semestres de l'étudiant
|
||||
if sesdates:
|
||||
lastdate = max(sesdates) # date de fin de l'inscription la plus récente
|
||||
else:
|
||||
return False
|
||||
|
||||
# if PETable.AFFICHAGE_DEBUG_PE == True : pe_tools.pe_print(" derniere inscription = ", lastDateSem)
|
||||
|
||||
@ -585,7 +588,7 @@ class JuryPE(object):
|
||||
for (i, fid) in enumerate(lesFids):
|
||||
if pe_tools.PE_DEBUG:
|
||||
pe_tools.pe_print(
|
||||
u"%d) Semestre taggué %s (avec classement dans groupe)"
|
||||
"%d) Semestre taggué %s (avec classement dans groupe)"
|
||||
% (i + 1, fid)
|
||||
)
|
||||
self.add_semtags_in_jury(fid)
|
||||
@ -620,7 +623,7 @@ class JuryPE(object):
|
||||
nbinscrit = self.semTagDict[fid].get_nbinscrits()
|
||||
if pe_tools.PE_DEBUG:
|
||||
pe_tools.pe_print(
|
||||
u" - %d étudiants classés " % (nbinscrit)
|
||||
" - %d étudiants classés " % (nbinscrit)
|
||||
+ ": "
|
||||
+ ",".join(
|
||||
[etudid for etudid in self.semTagDict[fid].get_etudids()]
|
||||
@ -628,12 +631,12 @@ class JuryPE(object):
|
||||
)
|
||||
if lesEtudidsManquants:
|
||||
pe_tools.pe_print(
|
||||
u" - dont %d étudiants manquants ajoutés aux données du jury"
|
||||
" - dont %d étudiants manquants ajoutés aux données du jury"
|
||||
% (len(lesEtudidsManquants))
|
||||
+ ": "
|
||||
+ ", ".join(lesEtudidsManquants)
|
||||
)
|
||||
pe_tools.pe_print(u" - Export csv")
|
||||
pe_tools.pe_print(" - Export csv")
|
||||
filename = self.NOM_EXPORT_ZIP + self.semTagDict[fid].nom + ".csv"
|
||||
self.zipfile.writestr(filename, self.semTagDict[fid].str_tagtable())
|
||||
|
||||
@ -742,7 +745,7 @@ class JuryPE(object):
|
||||
|
||||
for fid in fids_finaux:
|
||||
if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 1:
|
||||
pe_tools.pe_print(u" - semestre final %s" % (fid))
|
||||
pe_tools.pe_print(" - semestre final %s" % (fid))
|
||||
settag = pe_settag.SetTag(
|
||||
nom, parcours=parcours
|
||||
) # Le set tag fusionnant les données
|
||||
@ -762,7 +765,7 @@ class JuryPE(object):
|
||||
for ffid in settag.get_Fids_in_settag():
|
||||
if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 1:
|
||||
pe_tools.pe_print(
|
||||
u" -> ajout du semestre tagué %s" % (ffid)
|
||||
" -> ajout du semestre tagué %s" % (ffid)
|
||||
)
|
||||
self.add_semtags_in_jury(ffid)
|
||||
settag.set_SemTagDict(
|
||||
@ -791,7 +794,7 @@ class JuryPE(object):
|
||||
if nbreEtudInscrits > 0:
|
||||
if pe_tools.PE_DEBUG:
|
||||
pe_tools.pe_print(
|
||||
u"%d) %s avec interclassement sur la promo" % (i + 1, nom)
|
||||
"%d) %s avec interclassement sur la promo" % (i + 1, nom)
|
||||
)
|
||||
if nom in ["S1", "S2", "S3", "S4"]:
|
||||
settag.set_SetTagDict(self.semTagDict)
|
||||
@ -802,7 +805,7 @@ class JuryPE(object):
|
||||
else:
|
||||
if pe_tools.PE_DEBUG:
|
||||
pe_tools.pe_print(
|
||||
u"%d) Pas d'interclassement %s sur la promo faute de notes"
|
||||
"%d) Pas d'interclassement %s sur la promo faute de notes"
|
||||
% (i + 1, nom)
|
||||
)
|
||||
|
||||
@ -1152,11 +1155,14 @@ class JuryPE(object):
|
||||
return sesSems
|
||||
|
||||
# **********************************************
|
||||
def calcul_anneePromoDUT_d_un_etudiant(self, etudid):
|
||||
def calcul_anneePromoDUT_d_un_etudiant(self, etudid) -> int:
|
||||
"""Calcule et renvoie la date de diplome prévue pour un étudiant fourni avec son etudid
|
||||
en fonction de sesSemestres de scolarisation"""
|
||||
sesSemestres = self.get_semestresDUT_d_un_etudiant(etudid)
|
||||
return max([get_annee_diplome_semestre(sem) for sem in sesSemestres])
|
||||
en fonction de ses semestres de scolarisation"""
|
||||
semestres = self.get_semestresDUT_d_un_etudiant(etudid)
|
||||
if semestres:
|
||||
return max([get_annee_diplome_semestre(sem) for sem in semestres])
|
||||
else:
|
||||
return None
|
||||
|
||||
# *********************************************
|
||||
# Fonctions d'affichage pour debug
|
||||
@ -1184,18 +1190,21 @@ class JuryPE(object):
|
||||
chaine += "\n"
|
||||
return chaine
|
||||
|
||||
def get_date_entree_etudiant(self, etudid):
|
||||
"""Renvoie la date d'entree d'un étudiant"""
|
||||
return str(
|
||||
min([int(sem["annee_debut"]) for sem in self.ETUDINFO_DICT[etudid]["sems"]])
|
||||
)
|
||||
def get_date_entree_etudiant(self, etudid) -> str:
|
||||
"""Renvoie la date d'entree d'un étudiant: "1996" """
|
||||
annees_debut = [
|
||||
int(sem["annee_debut"]) for sem in self.ETUDINFO_DICT[etudid]["sems"]
|
||||
]
|
||||
if annees_debut:
|
||||
return str(min(annees_debut))
|
||||
return ""
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
# Fonctions
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
def get_annee_diplome_semestre(sem):
|
||||
def get_annee_diplome_semestre(sem) -> int:
|
||||
"""Pour un semestre donne, décrit par le biais du dictionnaire sem usuel :
|
||||
sem = {'formestre_id': ..., 'semestre_id': ..., 'annee_debut': ...},
|
||||
à condition qu'il soit un semestre de formation DUT,
|
||||
|
@ -121,6 +121,7 @@ class GenTable(object):
|
||||
html_with_td_classes=False, # put class=column_id in each <td>
|
||||
html_before_table="", # html snippet to put before the <table> in the page
|
||||
html_empty_element="", # replace table when empty
|
||||
html_table_attrs="", # for html
|
||||
base_url=None,
|
||||
origin=None, # string added to excel and xml versions
|
||||
filename="table", # filename, without extension
|
||||
@ -146,6 +147,7 @@ class GenTable(object):
|
||||
self.html_header = html_header
|
||||
self.html_before_table = html_before_table
|
||||
self.html_empty_element = html_empty_element
|
||||
self.html_table_attrs = html_table_attrs
|
||||
self.page_title = page_title
|
||||
self.pdf_link = pdf_link
|
||||
self.xls_link = xls_link
|
||||
@ -383,12 +385,16 @@ class GenTable(object):
|
||||
colspan_count = colspan
|
||||
else:
|
||||
colspan_txt = ""
|
||||
attrs = row.get("_%s_td_attrs" % cid, "")
|
||||
order = row.get(f"_{cid}_order")
|
||||
if order:
|
||||
attrs += f' data-order="{order}"'
|
||||
r.append(
|
||||
"<%s%s %s%s%s>%s</%s>"
|
||||
% (
|
||||
elem,
|
||||
std,
|
||||
row.get("_%s_td_attrs" % cid, ""),
|
||||
attrs,
|
||||
klass,
|
||||
colspan_txt,
|
||||
content,
|
||||
@ -413,8 +419,7 @@ class GenTable(object):
|
||||
cls = ' class="%s"' % " ".join(tablclasses)
|
||||
else:
|
||||
cls = ""
|
||||
|
||||
H = [self.html_before_table, "<table%s%s>" % (hid, cls)]
|
||||
H = [self.html_before_table, f"<table{hid}{cls} {self.html_table_attrs}>"]
|
||||
|
||||
line_num = 0
|
||||
# thead
|
||||
|
@ -57,7 +57,6 @@ def sidebar_common():
|
||||
<a href="{scu.AbsencesURL()}" class="sidebar">Absences</a> <br/>
|
||||
"""
|
||||
]
|
||||
|
||||
if current_user.has_permission(
|
||||
Permission.ScoUsersAdmin
|
||||
) or current_user.has_permission(Permission.ScoUsersView):
|
||||
|
@ -272,9 +272,15 @@ def _build_etud_res(e, apo_data):
|
||||
r = {}
|
||||
for elt_code in apo_data.apo_elts:
|
||||
elt = apo_data.apo_elts[elt_code]
|
||||
try:
|
||||
# les colonnes de cet élément
|
||||
col_ids_type = [
|
||||
(ec["apoL_a01_code"], ec["Type R\xc3\xa9s."]) for ec in elt.cols
|
||||
] # les colonnes de cet élément
|
||||
]
|
||||
except KeyError as exc:
|
||||
raise ScoValueError(
|
||||
"Erreur: un élément sans 'Type R\xc3\xa9s.'. Vérifiez l'encodage de vos fichiers."
|
||||
) from exc
|
||||
r[elt_code] = {}
|
||||
for (col_id, type_res) in col_ids_type:
|
||||
r[elt_code][type_res] = e.cols[col_id]
|
||||
|
@ -396,7 +396,7 @@ class ApoEtud(dict):
|
||||
|
||||
# Element etape (annuel ou non):
|
||||
if sco_formsemestre.sem_has_etape(sem, code) or (
|
||||
code in sem["elt_annee_apo"].split(",")
|
||||
code in {x.strip() for x in sem["elt_annee_apo"].split(",")}
|
||||
):
|
||||
export_res_etape = self.export_res_etape
|
||||
if (not export_res_etape) and cur_sem:
|
||||
@ -412,7 +412,7 @@ class ApoEtud(dict):
|
||||
return VOID_APO_RES
|
||||
|
||||
# Element semestre:
|
||||
if code in sem["elt_sem_apo"].split(","):
|
||||
if code in {x.strip() for x in sem["elt_sem_apo"].split(",")}:
|
||||
if self.export_res_sem:
|
||||
return self.comp_elt_semestre(nt, decision, etudid)
|
||||
else:
|
||||
@ -421,7 +421,9 @@ class ApoEtud(dict):
|
||||
# Elements UE
|
||||
decisions_ue = nt.get_etud_decision_ues(etudid)
|
||||
for ue in nt.get_ues_stat_dict():
|
||||
if ue["code_apogee"] and code in ue["code_apogee"].split(","):
|
||||
if ue["code_apogee"] and code in {
|
||||
x.strip() for x in ue["code_apogee"].split(",")
|
||||
}:
|
||||
if self.export_res_ues:
|
||||
if decisions_ue and ue["ue_id"] in decisions_ue:
|
||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||
@ -442,9 +444,10 @@ class ApoEtud(dict):
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
module_code_found = False
|
||||
for modimpl in modimpls:
|
||||
if modimpl["module"]["code_apogee"] and code in modimpl["module"][
|
||||
"code_apogee"
|
||||
].split(","):
|
||||
module = modimpl["module"]
|
||||
if module["code_apogee"] and code in {
|
||||
x.strip() for x in module["code_apogee"].split(",")
|
||||
}:
|
||||
n = nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
|
||||
if n != "NI" and self.export_res_modules:
|
||||
return dict(N=self.fmt_note(n), B=20, J="", R="")
|
||||
@ -949,8 +952,9 @@ class ApoData(object):
|
||||
return maq_elems, sem_elems
|
||||
|
||||
def get_codes_by_sem(self):
|
||||
"""Pour chaque semestre associé, donne l'ensemble des codes Apogée qui s'y trouvent
|
||||
(dans le semestre, les UE et les modules)
|
||||
"""Pour chaque semestre associé, donne l'ensemble des codes de cette maquette Apogée
|
||||
qui s'y trouvent (dans le semestre, les UE ou les modules).
|
||||
Return: { formsemestre_id : { 'code1', 'code2', ... }}
|
||||
"""
|
||||
codes_by_sem = {}
|
||||
for sem in self.sems_etape:
|
||||
@ -961,8 +965,8 @@ class ApoData(object):
|
||||
# associé à l'étape, l'année ou les semestre:
|
||||
if (
|
||||
sco_formsemestre.sem_has_etape(sem, code)
|
||||
or (code in sem["elt_sem_apo"].split(","))
|
||||
or (code in sem["elt_annee_apo"].split(","))
|
||||
or (code in {x.strip() for x in sem["elt_sem_apo"].split(",")})
|
||||
or (code in {x.strip() for x in sem["elt_annee_apo"].split(",")})
|
||||
):
|
||||
s.add(code)
|
||||
continue
|
||||
@ -970,15 +974,18 @@ class ApoData(object):
|
||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
for ue in nt.get_ues_stat_dict():
|
||||
if ue["code_apogee"] and code in ue["code_apogee"].split(","):
|
||||
if ue["code_apogee"]:
|
||||
codes = {x.strip() for x in ue["code_apogee"].split(",")}
|
||||
if code in codes:
|
||||
s.add(code)
|
||||
continue
|
||||
# associé à un module:
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
for modimpl in modimpls:
|
||||
if modimpl["module"]["code_apogee"] and code in modimpl["module"][
|
||||
"code_apogee"
|
||||
].split(","):
|
||||
module = modimpl["module"]
|
||||
if module["code_apogee"]:
|
||||
codes = {x.strip() for x in module["code_apogee"].split(",")}
|
||||
if code in codes:
|
||||
s.add(code)
|
||||
continue
|
||||
# log('codes_by_sem=%s' % pprint.pformat(codes_by_sem))
|
||||
|
@ -28,11 +28,10 @@
|
||||
"""
|
||||
|
||||
"""
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.scodoc.sco_logos import write_logo, find_logo, delete_logo
|
||||
import app
|
||||
from flask import current_app
|
||||
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
|
||||
|
||||
class Action:
|
||||
"""Base class for all classes describing an action from from config form."""
|
||||
@ -42,9 +41,9 @@ class Action:
|
||||
self.parameters = parameters
|
||||
|
||||
@staticmethod
|
||||
def build_action(parameters, stream=None):
|
||||
def build_action(parameters):
|
||||
"""Check (from parameters) if some action has to be done and
|
||||
then return list of action (or else return empty list)."""
|
||||
then return list of action (or else return None)."""
|
||||
raise NotImplementedError
|
||||
|
||||
def display(self):
|
||||
@ -59,6 +58,45 @@ class Action:
|
||||
GLOBAL = "_"
|
||||
|
||||
|
||||
class LogoRename(Action):
|
||||
"""Action: rename a logo
|
||||
dept_id: dept_id or '-'
|
||||
logo_id: logo_id (old name)
|
||||
new_name: new_name
|
||||
"""
|
||||
|
||||
def __init__(self, parameters):
|
||||
super().__init__(
|
||||
f"Renommage du logo {parameters['logo_id']} en {parameters['new_name']}",
|
||||
parameters,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_action(parameters):
|
||||
dept_id = parameters["dept_key"]
|
||||
if dept_id == GLOBAL:
|
||||
dept_id = None
|
||||
parameters["dept_id"] = dept_id
|
||||
if parameters["new_name"]:
|
||||
logo = find_logo(
|
||||
logoname=parameters["new_name"],
|
||||
dept_id=parameters["dept_key"],
|
||||
strict=True,
|
||||
)
|
||||
if logo is None:
|
||||
return LogoRename(parameters)
|
||||
|
||||
def execute(self):
|
||||
from app.scodoc.sco_logos import rename_logo
|
||||
|
||||
current_app.logger.info(self.message)
|
||||
rename_logo(
|
||||
old_name=self.parameters["logo_id"],
|
||||
new_name=self.parameters["new_name"],
|
||||
dept_id=self.parameters["dept_id"],
|
||||
)
|
||||
|
||||
|
||||
class LogoUpdate(Action):
|
||||
"""Action: change a logo
|
||||
dept_id: dept_id or '_',
|
||||
@ -83,6 +121,8 @@ class LogoUpdate(Action):
|
||||
return None
|
||||
|
||||
def execute(self):
|
||||
from app.scodoc.sco_logos import write_logo
|
||||
|
||||
current_app.logger.info(self.message)
|
||||
write_logo(
|
||||
stream=self.parameters["upload"],
|
||||
@ -113,6 +153,8 @@ class LogoDelete(Action):
|
||||
return None
|
||||
|
||||
def execute(self):
|
||||
from app.scodoc.sco_logos import delete_logo
|
||||
|
||||
current_app.logger.info(self.message)
|
||||
delete_logo(name=self.parameters["logo_id"], dept_id=self.parameters["dept_id"])
|
||||
|
||||
@ -136,13 +178,15 @@ class LogoInsert(Action):
|
||||
parameters["dept_id"] = None
|
||||
if parameters["upload"] and parameters["name"]:
|
||||
logo = find_logo(
|
||||
logoname=parameters["name"], dept_id=parameters["dept_key"]
|
||||
logoname=parameters["name"], dept_id=parameters["dept_key"], strict=True
|
||||
)
|
||||
if logo is None:
|
||||
return LogoInsert(parameters)
|
||||
return None
|
||||
|
||||
def execute(self):
|
||||
from app.scodoc.sco_logos import write_logo
|
||||
|
||||
dept_id = self.parameters["dept_key"]
|
||||
if dept_id == GLOBAL:
|
||||
dept_id = None
|
||||
|
@ -29,6 +29,7 @@
|
||||
"""
|
||||
|
||||
from flask import g, request
|
||||
from flask import url_for
|
||||
from flask_login import current_user
|
||||
|
||||
import app
|
||||
@ -79,7 +80,7 @@ def index_html(showcodes=0, showsemtable=0):
|
||||
sco_formsemestre.sem_set_responsable_name(sem)
|
||||
|
||||
if showcodes:
|
||||
sem["tmpcode"] = "<td><tt>%s</tt></td>" % sem["formsemestre_id"]
|
||||
sem["tmpcode"] = f"<td><tt>{sem['formsemestre_id']}</tt></td>"
|
||||
else:
|
||||
sem["tmpcode"] = ""
|
||||
# Nombre d'inscrits:
|
||||
@ -121,26 +122,27 @@ def index_html(showcodes=0, showsemtable=0):
|
||||
|
||||
if showsemtable:
|
||||
H.append(
|
||||
"""<hr/>
|
||||
<h2>Semestres de %s</h2>
|
||||
f"""<hr>
|
||||
<h2>Semestres de {sco_preferences.get_preference("DeptName")}</h2>
|
||||
"""
|
||||
% sco_preferences.get_preference("DeptName")
|
||||
)
|
||||
H.append(_sem_table_gt(sems, showcodes=showcodes).html())
|
||||
H.append("</table>")
|
||||
if not showsemtable:
|
||||
H.append(
|
||||
'<hr/><p><a href="%s?showsemtable=1">Voir tous les semestres</a></p>'
|
||||
% request.base_url
|
||||
f"""<hr>
|
||||
<p><a class="stdlink" href="{url_for('scolar.index_html', scodoc_dept=g.scodoc_dept, showsemtable=1)
|
||||
}">Voir tous les semestres ({len(othersems)} verrouillés)</a>
|
||||
</p>"""
|
||||
)
|
||||
|
||||
H.append(
|
||||
"""<p><form action="%s/view_formsemestre_by_etape">
|
||||
Chercher étape courante: <input name="etape_apo" type="text" size="8" spellcheck="false"></input>
|
||||
</form
|
||||
</p>
|
||||
"""
|
||||
% scu.NotesURL()
|
||||
f"""<p>
|
||||
<form action="{url_for('notes.view_formsemestre_by_etape', scodoc_dept=g.scodoc_dept)}">
|
||||
Chercher étape courante:
|
||||
<input name="etape_apo" type="text" size="8" spellcheck="false"></input>
|
||||
</form>
|
||||
</p>"""
|
||||
)
|
||||
#
|
||||
if current_user.has_permission(Permission.ScoEtudInscrit):
|
||||
@ -148,23 +150,26 @@ Chercher étape courante: <input name="etape_apo" type="text" size="8" spellchec
|
||||
"""<hr>
|
||||
<h3>Gestion des étudiants</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="etudident_create_form">créer <em>un</em> nouvel étudiant</a></li>
|
||||
<li><a class="stdlink" href="form_students_import_excel">importer de nouveaux étudiants</a> (ne pas utiliser sauf cas particulier, utilisez plutôt le lien dans
|
||||
<li><a class="stdlink" href="etudident_create_form">créer <em>un</em> nouvel étudiant</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="form_students_import_excel">importer de nouveaux étudiants</a>
|
||||
(ne pas utiliser sauf cas particulier, utilisez plutôt le lien dans
|
||||
le tableau de bord semestre si vous souhaitez inscrire les
|
||||
étudiants importés à un semestre)</li>
|
||||
étudiants importés à un semestre)
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
#
|
||||
if current_user.has_permission(Permission.ScoEditApo):
|
||||
H.append(
|
||||
"""<hr>
|
||||
f"""<hr>
|
||||
<h3>Exports Apogée</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="%s/semset_page">Années scolaires / exports Apogée</a></li>
|
||||
<li><a class="stdlink" href="{url_for('notes.semset_page', scodoc_dept=g.scodoc_dept)
|
||||
}">Années scolaires / exports Apogée</a></li>
|
||||
</ul>
|
||||
"""
|
||||
% scu.NotesURL()
|
||||
)
|
||||
#
|
||||
H.append(
|
||||
@ -176,7 +181,13 @@ Chercher étape courante: <input name="etape_apo" type="text" size="8" spellchec
|
||||
"""
|
||||
)
|
||||
#
|
||||
return html_sco_header.sco_header() + "\n".join(H) + html_sco_header.sco_footer()
|
||||
return (
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"ScoDoc {g.scodoc_dept}", javascripts=["js/scolar_index.js"]
|
||||
)
|
||||
+ "\n".join(H)
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
|
||||
|
||||
def _sem_table(sems):
|
||||
@ -213,7 +224,9 @@ def _sem_table(sems):
|
||||
|
||||
|
||||
def _sem_table_gt(sems, showcodes=False):
|
||||
"""Nouvelle version de la table des semestres"""
|
||||
"""Nouvelle version de la table des semestres
|
||||
Utilise une datatables.
|
||||
"""
|
||||
_style_sems(sems)
|
||||
columns_ids = (
|
||||
"lockimg",
|
||||
@ -224,10 +237,15 @@ def _sem_table_gt(sems, showcodes=False):
|
||||
"titre_resp",
|
||||
"nb_inscrits",
|
||||
"etapes_apo_str",
|
||||
"elt_annee_apo",
|
||||
"elt_sem_apo",
|
||||
)
|
||||
if showcodes:
|
||||
columns_ids = ("formsemestre_id",) + columns_ids
|
||||
|
||||
html_class = "stripe cell-border compact hover order-column table_leftalign semlist"
|
||||
if current_user.has_permission(Permission.ScoEditApo):
|
||||
html_class += " apo_editable"
|
||||
tab = GenTable(
|
||||
titles={
|
||||
"formsemestre_id": "id",
|
||||
@ -236,14 +254,23 @@ def _sem_table_gt(sems, showcodes=False):
|
||||
"mois_debut": "Début",
|
||||
"dash_mois_fin": "Année",
|
||||
"titre_resp": "Semestre",
|
||||
"nb_inscrits": "N", # groupicon,
|
||||
"nb_inscrits": "N",
|
||||
"etapes_apo_str": "Étape Apo.",
|
||||
"elt_annee_apo": "Elt. année Apo.",
|
||||
"elt_sem_apo": "Elt. sem. Apo.",
|
||||
},
|
||||
columns_ids=columns_ids,
|
||||
rows=sems,
|
||||
html_class="table_leftalign semlist",
|
||||
table_id="semlist",
|
||||
html_class_ignore_default=True,
|
||||
html_class=html_class,
|
||||
html_sortable=True,
|
||||
# base_url = '%s?formsemestre_id=%s' % (request.base_url, formsemestre_id),
|
||||
# caption='Maquettes enregistrées',
|
||||
html_table_attrs=f"""
|
||||
data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}"
|
||||
data-elt_annee_apo_save_url="{url_for('notes.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept)}"
|
||||
data-elt_sem_apo_save_url="{url_for('notes.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept)}"
|
||||
""",
|
||||
html_with_td_classes=True,
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
|
||||
@ -276,6 +303,16 @@ def _style_sems(sems):
|
||||
sem["semestre_id_n"] = ""
|
||||
else:
|
||||
sem["semestre_id_n"] = sem["semestre_id"]
|
||||
# pour édition codes Apogée:
|
||||
sem[
|
||||
"_etapes_apo_str_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['etapes_apo_str']}" """
|
||||
sem[
|
||||
"_elt_annee_apo_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_annee_apo']}" """
|
||||
sem[
|
||||
"_elt_sem_apo_td_attrs"
|
||||
] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """
|
||||
|
||||
|
||||
def delete_dept(dept_id: int):
|
||||
|
@ -43,10 +43,8 @@ import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import (
|
||||
ScoGenError,
|
||||
ScoValueError,
|
||||
ScoLockedFormError,
|
||||
ScoNonEmptyFormationObject,
|
||||
@ -61,7 +59,6 @@ from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_tag_module
|
||||
|
||||
_ueEditor = ndb.EditableTable(
|
||||
@ -1355,93 +1352,6 @@ def ue_is_locked(ue_id):
|
||||
return len(r) > 0
|
||||
|
||||
|
||||
# ---- Table recap formation
|
||||
def formation_table_recap(formation_id, format="html"):
|
||||
"""Table recapitulant formation."""
|
||||
from app.scodoc import sco_formations
|
||||
|
||||
F = sco_formations.formation_list(args={"formation_id": formation_id})
|
||||
if not F:
|
||||
raise ScoValueError("invalid formation_id")
|
||||
F = F[0]
|
||||
T = []
|
||||
ues = ue_list(args={"formation_id": formation_id})
|
||||
for ue in ues:
|
||||
Matlist = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
|
||||
for Mat in Matlist:
|
||||
Modlist = sco_edit_module.module_list(
|
||||
args={"matiere_id": Mat["matiere_id"]}
|
||||
)
|
||||
for Mod in Modlist:
|
||||
Mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls(
|
||||
Mod["module_id"]
|
||||
)
|
||||
#
|
||||
T.append(
|
||||
{
|
||||
"UE_acro": ue["acronyme"],
|
||||
"Mat_tit": Mat["titre"],
|
||||
"Mod_tit": Mod["abbrev"] or Mod["titre"],
|
||||
"Mod_code": Mod["code"],
|
||||
"Mod_coef": Mod["coefficient"],
|
||||
"Mod_sem": Mod["semestre_id"],
|
||||
"nb_moduleimpls": Mod["nb_moduleimpls"],
|
||||
"heures_cours": Mod["heures_cours"],
|
||||
"heures_td": Mod["heures_td"],
|
||||
"heures_tp": Mod["heures_tp"],
|
||||
"ects": Mod["ects"],
|
||||
}
|
||||
)
|
||||
columns_ids = [
|
||||
"UE_acro",
|
||||
"Mat_tit",
|
||||
"Mod_tit",
|
||||
"Mod_code",
|
||||
"Mod_coef",
|
||||
"Mod_sem",
|
||||
"nb_moduleimpls",
|
||||
"heures_cours",
|
||||
"heures_td",
|
||||
"heures_tp",
|
||||
"ects",
|
||||
]
|
||||
titles = {
|
||||
"UE_acro": "UE",
|
||||
"Mat_tit": "Matière",
|
||||
"Mod_tit": "Module",
|
||||
"Mod_code": "Code",
|
||||
"Mod_coef": "Coef.",
|
||||
"Mod_sem": "Sem.",
|
||||
"nb_moduleimpls": "Nb utilisé",
|
||||
"heures_cours": "Cours (h)",
|
||||
"heures_td": "TD (h)",
|
||||
"heures_tp": "TP (h)",
|
||||
"ects": "ECTS",
|
||||
}
|
||||
|
||||
title = (
|
||||
"""Formation %(titre)s (%(acronyme)s) [version %(version)s] code %(formation_code)s"""
|
||||
% F
|
||||
)
|
||||
tab = GenTable(
|
||||
columns_ids=columns_ids,
|
||||
rows=T,
|
||||
titles=titles,
|
||||
origin="Généré par %s le " % scu.sco_version.SCONAME
|
||||
+ scu.timedate_human_repr()
|
||||
+ "",
|
||||
caption=title,
|
||||
html_caption=title,
|
||||
html_class="table_leftalign",
|
||||
base_url="%s?formation_id=%s" % (request.base_url, formation_id),
|
||||
page_title=title,
|
||||
html_title="<h2>" + title + "</h2>",
|
||||
pdf_title=title,
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
|
||||
def ue_list_semestre_ids(ue: dict):
|
||||
"""Liste triée des numeros de semestres des modules dans cette UE
|
||||
Il est recommandable que tous les modules d'une UE aient le même indice de semestre.
|
||||
|
@ -32,11 +32,8 @@
|
||||
Voir sco_apogee_csv.py pour la structure du fichier Apogée.
|
||||
|
||||
Stockage: utilise sco_archive.py
|
||||
=> /opt/scodoc/var/scodoc/archives/apo_csv/<dept_id>/2016-1/2016-07-03-16-12-19/V3ASR.csv
|
||||
pour une maquette de l'année scolaire 2016, semestre 1, etape V3ASR
|
||||
|
||||
ou bien (à partir de ScoDoc 1678) :
|
||||
/opt/scodoc/var/scodoc/archives/apo_csv/<dept_id>/2016-1/2016-07-03-16-12-19/V3ASR!111.csv
|
||||
exemple:
|
||||
/opt/scodoc-data/archives/apo_csv/<dept_id>/2016-1/2016-07-03-16-12-19/V3ASR!111.csv
|
||||
pour une maquette de l'étape V3ASR version VDI 111.
|
||||
|
||||
La version VDI sera ignorée sauf si elle est indiquée dans l'étape du semestre.
|
||||
|
@ -34,8 +34,6 @@ from zipfile import ZipFile
|
||||
import flask
|
||||
from flask import url_for, g, send_file, request
|
||||
|
||||
# from werkzeug.utils import send_file
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc import html_sco_header
|
||||
|
193
app/scodoc/sco_formation_recap.py
Normal file
193
app/scodoc/sco_formation_recap.py
Normal file
@ -0,0 +1,193 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Table recap formation (avec champs éditables)
|
||||
"""
|
||||
import io
|
||||
from zipfile import ZipFile, BadZipfile
|
||||
|
||||
from flask import Response
|
||||
from flask import send_file, url_for
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app.models import Formation, FormSemestre, UniteEns, Module
|
||||
from app.models.formations import Matiere
|
||||
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_preferences
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
|
||||
# ---- Table recap formation
|
||||
def formation_table_recap(formation_id, format="html") -> Response:
|
||||
"""Table recapitulant formation."""
|
||||
T = []
|
||||
formation = Formation.query.get_or_404(formation_id)
|
||||
ues = formation.ues.order_by(UniteEns.semestre_idx, UniteEns.numero)
|
||||
can_edit = current_user.has_permission(Permission.ScoChangeFormation)
|
||||
li = 0
|
||||
for ue in ues:
|
||||
# L'UE
|
||||
T.append(
|
||||
{
|
||||
"sem": f"S{ue.semestre_idx}" if ue.semestre_idx is not None else "-",
|
||||
"_sem_order": f"{li:04d}",
|
||||
"code": ue.acronyme,
|
||||
"titre": ue.titre or "",
|
||||
"_titre_target": url_for(
|
||||
"notes.ue_edit",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
ue_id=ue.id,
|
||||
)
|
||||
if can_edit
|
||||
else None,
|
||||
"apo": ue.code_apogee or "",
|
||||
"_apo_td_attrs": f""" data-oid="{ue.id}" data-value="{ue.code_apogee or ''}" """,
|
||||
"coef": ue.coefficient or "",
|
||||
"ects": ue.ects,
|
||||
"_css_row_class": "ue",
|
||||
}
|
||||
)
|
||||
li += 1
|
||||
matieres = ue.matieres.order_by(Matiere.numero)
|
||||
for mat in matieres:
|
||||
modules = mat.modules.order_by(Module.numero)
|
||||
for mod in modules:
|
||||
nb_moduleimpls = mod.modimpls.count()
|
||||
# le module (ou ressource ou sae)
|
||||
T.append(
|
||||
{
|
||||
"sem": f"S{mod.semestre_id}"
|
||||
if mod.semestre_id is not None
|
||||
else "-",
|
||||
"_sem_order": f"{li:04d}",
|
||||
"code": mod.code,
|
||||
"titre": mod.abbrev or mod.titre,
|
||||
"_titre_target": url_for(
|
||||
"notes.module_edit",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
module_id=mod.id,
|
||||
)
|
||||
if can_edit
|
||||
else None,
|
||||
"apo": mod.code_apogee,
|
||||
"_apo_td_attrs": f""" data-oid="{mod.id}" data-value="{mod.code_apogee or ''}" """,
|
||||
"coef": mod.coefficient,
|
||||
"nb_moduleimpls": nb_moduleimpls,
|
||||
"heures_cours": mod.heures_cours,
|
||||
"heures_td": mod.heures_td,
|
||||
"heures_tp": mod.heures_tp,
|
||||
"_css_row_class": f"mod {mod.type_abbrv()}",
|
||||
}
|
||||
)
|
||||
columns_ids = [
|
||||
"sem",
|
||||
"code",
|
||||
"apo",
|
||||
# "mat", inutile d'afficher la matière
|
||||
"titre",
|
||||
"coef",
|
||||
"ects",
|
||||
"nb_moduleimpls",
|
||||
"heures_cours",
|
||||
"heures_td",
|
||||
"heures_tp",
|
||||
]
|
||||
titles = {
|
||||
"ue": "UE",
|
||||
"mat": "Matière",
|
||||
"titre": "Titre",
|
||||
"code": "Code",
|
||||
"apo": "Apo",
|
||||
"coef": "Coef.",
|
||||
"sem": "Sem.",
|
||||
"nb_moduleimpls": "Nb utilisé",
|
||||
"heures_cours": "Cours (h)",
|
||||
"heures_td": "TD (h)",
|
||||
"heures_tp": "TP (h)",
|
||||
"ects": "ECTS",
|
||||
}
|
||||
|
||||
title = f"""Formation {formation.titre} ({formation.acronyme})
|
||||
[version {formation.version}] code {formation.formation_code}"""
|
||||
html_class = "stripe cell-border compact hover order-column formation_table_recap"
|
||||
if current_user.has_permission(Permission.ScoEditApo):
|
||||
html_class += " apo_editable"
|
||||
|
||||
tab = GenTable(
|
||||
columns_ids=columns_ids,
|
||||
rows=T,
|
||||
titles=titles,
|
||||
origin=f"Généré par {scu.sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||
caption=title,
|
||||
html_caption=title,
|
||||
html_class=html_class,
|
||||
html_class_ignore_default=True,
|
||||
html_table_attrs=f"""
|
||||
data-apo_ue_save_url="{url_for('notes.ue_set_apo', scodoc_dept=g.scodoc_dept)}"
|
||||
data-apo_mod_save_url="{url_for('notes.module_set_apo', scodoc_dept=g.scodoc_dept)}"
|
||||
""",
|
||||
html_with_td_classes=True,
|
||||
base_url=f"{request.base_url}?formation_id={formation_id}",
|
||||
page_title=title,
|
||||
html_title=f"<h2>{title}</h2>",
|
||||
pdf_title=title,
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
table_id="formation_table_recap",
|
||||
)
|
||||
return tab.make_page(format=format, javascripts=["js/formation_recap.js"])
|
||||
|
||||
|
||||
def export_recap_formations_annee_scolaire(annee_scolaire):
|
||||
"""Exporte un zip des recap (excel) des formatons de tous les semestres
|
||||
de l'année scolaire indiquée.
|
||||
"""
|
||||
annee_scolaire = int(annee_scolaire)
|
||||
data = io.BytesIO()
|
||||
zip_file = ZipFile(data, "w")
|
||||
formsemestres = FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id).filter(
|
||||
FormSemestre.date_debut >= scu.date_debut_anne_scolaire(annee_scolaire),
|
||||
FormSemestre.date_debut <= scu.date_fin_anne_scolaire(annee_scolaire),
|
||||
)
|
||||
formation_ids = {formsemestre.formation.id for formsemestre in formsemestres}
|
||||
for formation_id in formation_ids:
|
||||
formation = Formation.query.get(formation_id)
|
||||
xls = formation_table_recap(formation_id, format="xlsx").data
|
||||
filename = (
|
||||
scu.sanitize_filename(formation.get_titre_version()) + scu.XLSX_SUFFIX
|
||||
)
|
||||
zip_file.writestr(filename, xls)
|
||||
zip_file.close()
|
||||
data.seek(0)
|
||||
return send_file(
|
||||
data,
|
||||
mimetype="application/zip",
|
||||
download_name=f"formations-{g.scodoc_dept}-{annee_scolaire}-{annee_scolaire+1}.zip",
|
||||
as_attachment=True,
|
||||
)
|
@ -95,9 +95,12 @@ _formsemestreEditor = ndb.EditableTable(
|
||||
|
||||
def get_formsemestre(formsemestre_id, raise_soft_exc=False):
|
||||
"list ONE formsemestre"
|
||||
if formsemestre_id is None:
|
||||
raise ValueError(f"get_formsemestre: id manquant")
|
||||
if formsemestre_id in g.stored_get_formsemestre:
|
||||
return g.stored_get_formsemestre[formsemestre_id]
|
||||
if not isinstance(formsemestre_id, int):
|
||||
log(f"get_formsemestre: invalid id '{formsemestre_id}'")
|
||||
raise ScoInvalidIdType("formsemestre_id must be an integer !")
|
||||
sems = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})
|
||||
if not sems:
|
||||
@ -141,7 +144,6 @@ def _formsemestre_enrich(sem):
|
||||
"""Ajoute champs souvent utiles: titre + annee et dateord (pour tris)"""
|
||||
# imports ici pour eviter refs circulaires
|
||||
from app.scodoc import sco_formsemestre_edit
|
||||
from app.scodoc import sco_etud
|
||||
|
||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
||||
@ -350,6 +352,7 @@ def read_formsemestre_etapes(formsemestre_id): # OBSOLETE
|
||||
"""SELECT etape_apo
|
||||
FROM notes_formsemestre_etapes
|
||||
WHERE formsemestre_id = %(formsemestre_id)s
|
||||
ORDER BY etape_apo
|
||||
""",
|
||||
{"formsemestre_id": formsemestre_id},
|
||||
)
|
||||
|
@ -28,6 +28,7 @@
|
||||
"""Tableau de bord semestre
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
@ -760,8 +761,7 @@ def _make_listes_sem(sem, with_absences=True):
|
||||
)
|
||||
|
||||
formsemestre_id = sem["formsemestre_id"]
|
||||
|
||||
# calcule dates 1er jour semaine pour absences
|
||||
weekday = datetime.datetime.today().weekday()
|
||||
try:
|
||||
if with_absences:
|
||||
first_monday = sco_abs.ddmmyyyy(sem["date_debut"]).prev_monday()
|
||||
@ -780,8 +780,8 @@ def _make_listes_sem(sem, with_absences=True):
|
||||
<select name="datedebut" class="noprint">
|
||||
"""
|
||||
date = first_monday
|
||||
for jour in sco_abs.day_names():
|
||||
form_abs_tmpl += f'<option value="{date}">{jour}s</option>'
|
||||
for idx, jour in enumerate(sco_abs.day_names()):
|
||||
form_abs_tmpl += f"""<option value="{date}" {'selected' if idx == weekday else ''}>{jour}s</option>"""
|
||||
date = date.next_day()
|
||||
form_abs_tmpl += f"""
|
||||
</select>
|
||||
@ -966,6 +966,7 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
|
||||
def formsemestre_status(formsemestre_id=None):
|
||||
"""Tableau de bord semestre HTML"""
|
||||
# porté du DTML
|
||||
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||
formsemestre_id=formsemestre_id
|
||||
@ -987,7 +988,9 @@ def formsemestre_status(formsemestre_id=None):
|
||||
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Semestre %s" % sem["titreannee"]),
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"{formsemestre.sem_modalite()} {formsemestre.titre_annee()}"
|
||||
),
|
||||
'<div class="formsemestre_status">',
|
||||
formsemestre_status_head(
|
||||
formsemestre_id=formsemestre_id, page_title="Tableau de bord"
|
||||
|
@ -89,6 +89,11 @@ def write_logo(stream, name, dept_id=None):
|
||||
Logo(logoname=name, dept_id=dept_id).create(stream)
|
||||
|
||||
|
||||
def rename_logo(old_name, new_name, dept_id):
|
||||
logo = find_logo(old_name, dept_id, True)
|
||||
logo.rename(new_name)
|
||||
|
||||
|
||||
def list_logos():
|
||||
"""Crée l'inventaire de tous les logos existants.
|
||||
L'inventaire se présente comme un dictionnaire de dictionnaire de Logo:
|
||||
@ -285,6 +290,20 @@ class Logo:
|
||||
dt = path.stat().st_mtime
|
||||
return path.stat().st_mtime
|
||||
|
||||
def rename(self, new_name):
|
||||
"""Change le nom (pas le département)
|
||||
Les éléments non utiles ne sont pas recalculés (car rechargés lors des accès ultérieurs)
|
||||
"""
|
||||
old_path = Path(self.filepath)
|
||||
self.logoname = secure_filename(new_name)
|
||||
if not self.logoname:
|
||||
self.logoname = "*** *** nom de logo invalide *** à changer ! *** ***"
|
||||
else:
|
||||
new_path = os.path.sep.join(
|
||||
[self.dirpath, self.prefix + self.logoname + "." + self.suffix]
|
||||
)
|
||||
old_path.rename(new_path)
|
||||
|
||||
|
||||
def guess_image_type(stream) -> str:
|
||||
"guess image type from header in stream"
|
||||
|
@ -37,12 +37,20 @@ import xml.dom.minidom
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
SCO_CACHE_ETAPE_FILENAME = os.path.join(scu.SCO_TMP_DIR, "last_etapes.xml")
|
||||
|
||||
|
||||
class ApoInscritsEtapeCache(sco_cache.ScoDocCache):
|
||||
"""Cache liste des inscrits à une étape Apogée"""
|
||||
|
||||
timeout = 10 * 60 # 10 minutes
|
||||
prefix = "APOINSCRETAP"
|
||||
|
||||
|
||||
def has_portal():
|
||||
"True if we are connected to a portal"
|
||||
return get_portal_url()
|
||||
@ -139,14 +147,20 @@ get_maquette_url = _PI.get_maquette_url
|
||||
get_portal_api_version = _PI.get_portal_api_version
|
||||
|
||||
|
||||
def get_inscrits_etape(code_etape, anneeapogee=None, ntrials=2):
|
||||
def get_inscrits_etape(code_etape, anneeapogee=None, ntrials=4, use_cache=True):
|
||||
"""Liste des inscrits à une étape Apogée
|
||||
Result = list of dicts
|
||||
ntrials: try several time the same request, useful for some bad web services
|
||||
use_cache: use (redis) cache
|
||||
"""
|
||||
log("get_inscrits_etape: code=%s anneeapogee=%s" % (code_etape, anneeapogee))
|
||||
if anneeapogee is None:
|
||||
anneeapogee = str(time.localtime()[0])
|
||||
if use_cache:
|
||||
obj = ApoInscritsEtapeCache.get((code_etape, anneeapogee))
|
||||
if obj:
|
||||
log("get_inscrits_etape: using cached data")
|
||||
return obj
|
||||
|
||||
etud_url = get_etud_url()
|
||||
api_ver = get_portal_api_version()
|
||||
@ -189,6 +203,8 @@ def get_inscrits_etape(code_etape, anneeapogee=None, ntrials=2):
|
||||
return False # ??? pas d'annee d'inscription dans la réponse
|
||||
|
||||
etuds = [e for e in etuds if check_inscription(e)]
|
||||
if use_cache and etuds:
|
||||
ApoInscritsEtapeCache.set((code_etape, anneeapogee), etuds)
|
||||
return etuds
|
||||
|
||||
|
||||
|
@ -103,7 +103,7 @@ def formsemestre_recapcomplet(
|
||||
return data
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Récapitulatif",
|
||||
page_title=f"{formsemestre.sem_modalite()}: moyennes",
|
||||
no_side_bar=True,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js", "js/table_recap.js"],
|
||||
|
@ -704,6 +704,7 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
|
||||
typ=ScolarNews.NEWS_INSCR,
|
||||
text="Import Apogée de %d étudiants en " % len(created_etudids),
|
||||
obj=sem["formsemestre_id"],
|
||||
max_frequency=10 * 60, # 10'
|
||||
)
|
||||
|
||||
|
||||
|
@ -728,15 +728,13 @@ def sendResult(
|
||||
|
||||
def send_file(data, filename="", suffix="", mime=None, attached=None):
|
||||
"""Build Flask Response for file download of given type
|
||||
By default (attached is None), json and xml are inlined and otrher types are attached.
|
||||
By default (attached is None), json and xml are inlined and other types are attached.
|
||||
"""
|
||||
if attached is None:
|
||||
if mime == XML_MIMETYPE or mime == JSON_MIMETYPE:
|
||||
attached = False
|
||||
else:
|
||||
attached = True
|
||||
# if attached and not filename:
|
||||
# raise ValueError("send_file: missing attachement filename")
|
||||
if filename:
|
||||
if suffix:
|
||||
filename += suffix
|
||||
@ -755,7 +753,7 @@ def send_docx(document, filename):
|
||||
buffer.seek(0)
|
||||
return flask.send_file(
|
||||
buffer,
|
||||
attachment_filename=sanitize_filename(filename),
|
||||
download_name=sanitize_filename(filename),
|
||||
mimetype=DOCX_MIMETYPE,
|
||||
)
|
||||
|
||||
@ -873,6 +871,20 @@ def annee_scolaire_debut(year, month):
|
||||
return int(year) - 1
|
||||
|
||||
|
||||
def date_debut_anne_scolaire(annee_scolaire: int) -> datetime:
|
||||
"""La date de début de l'année scolaire
|
||||
= 1er aout
|
||||
"""
|
||||
return datetime.datetime(year=annee_scolaire, month=8, day=1)
|
||||
|
||||
|
||||
def date_fin_anne_scolaire(annee_scolaire: int) -> datetime:
|
||||
"""La date de fin de l'année scolaire
|
||||
= 31 juillet de l'année suivante
|
||||
"""
|
||||
return datetime.datetime(year=annee_scolaire + 1, month=7, day=31)
|
||||
|
||||
|
||||
def sem_decale_str(sem):
|
||||
"""'D' si semestre decalé, ou ''"""
|
||||
# considère "décalé" les semestre impairs commençant entre janvier et juin
|
||||
|
@ -15,6 +15,10 @@
|
||||
|
||||
}
|
||||
|
||||
.form-error {
|
||||
color: #a94442;
|
||||
}
|
||||
|
||||
.nav-entreprise>ul>li>a:hover {
|
||||
color: red;
|
||||
}
|
||||
@ -50,23 +54,23 @@
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
|
||||
.entreprise, .contact, .offre {
|
||||
.entreprise, .correspondant, .offre {
|
||||
border: solid 2px;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.contacts-et-offres {
|
||||
.correspondants-et-offres {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.contacts-et-offres > div {
|
||||
.correspondants-et-offres > div {
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.contacts-et-offres > div:nth-child(2) {
|
||||
.correspondants-et-offres > div:nth-child(2) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
|
@ -427,8 +427,8 @@ table.semlist tr td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
table.semlist tr a.stdlink,
|
||||
table.semlist tr a.stdlink:visited {
|
||||
table.semlist tbody tr a.stdlink,
|
||||
table.semlist tbody tr a.stdlink:visited {
|
||||
color: navy;
|
||||
text-decoration: none;
|
||||
}
|
||||
@ -442,32 +442,86 @@ table.semlist tr td.semestre_id {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table.semlist tr td.modalite {
|
||||
table.semlist tbody tr td.modalite {
|
||||
text-align: left;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_S-1 {
|
||||
/***************************/
|
||||
/* Statut des cellules */
|
||||
/***************************/
|
||||
.sco_selected {
|
||||
outline: 1px solid #c09;
|
||||
}
|
||||
|
||||
.sco_modifying {
|
||||
outline: 2px dashed #c09;
|
||||
background-color: white !important;
|
||||
}
|
||||
|
||||
.sco_wait {
|
||||
outline: 2px solid #c90;
|
||||
}
|
||||
|
||||
.sco_good {
|
||||
outline: 2px solid #9c0;
|
||||
}
|
||||
|
||||
.sco_modified {
|
||||
font-weight: bold;
|
||||
color: indigo
|
||||
}
|
||||
|
||||
/***************************/
|
||||
/* Message */
|
||||
/***************************/
|
||||
.message {
|
||||
position: fixed;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
z-index: 10;
|
||||
padding: 20px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
background: #ec7068;
|
||||
background: #90c;
|
||||
color: #FFF;
|
||||
font-size: 24px;
|
||||
animation: message 3s;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
@keyframes message {
|
||||
20% {
|
||||
transform: translate(-50%, 100%)
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: translate(-50%, 100%)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S-1 td {
|
||||
background-color: rgb(251, 250, 216);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_S1 {
|
||||
div#gtrcontent table.semlist tbody tr.css_S1 td {
|
||||
background-color: rgb(92%, 95%, 94%);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_S2 {
|
||||
div#gtrcontent table.semlist tbody tr.css_S2 td {
|
||||
background-color: rgb(214, 223, 236);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_S3 {
|
||||
div#gtrcontent table.semlist tbody tr.css_S3 td {
|
||||
background-color: rgb(167, 216, 201);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_S4 {
|
||||
div#gtrcontent table.semlist tbody tr.css_S4 td {
|
||||
background-color: rgb(131, 225, 140);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr.css_MEXT {
|
||||
div#gtrcontent table.semlist tbody tr.css_MEXT td {
|
||||
color: #0b6e08;
|
||||
}
|
||||
|
||||
@ -1001,6 +1055,14 @@ span.wtf-field ul.errors li {
|
||||
display: list-item !important;
|
||||
}
|
||||
|
||||
.configuration_logo entete_dept {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.configuration_logo .effectifs {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.configuration_logo h1 {
|
||||
display: inline-block;
|
||||
}
|
||||
@ -3910,3 +3972,17 @@ table.evaluations_recap td.nb_att,
|
||||
table.evaluations_recap td.nb_exc {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ------------- Tableau récap formation ------------ */
|
||||
table.formation_table_recap tr.ue td {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.formation_table_recap td.coef,
|
||||
table.formation_table_recap td.ects,
|
||||
table.formation_table_recap td.nb_moduleimpls,
|
||||
table.formation_table_recap td.heures_cours,
|
||||
table.formation_table_recap td.heures_td,
|
||||
table.formation_table_recap td.heures_tp {
|
||||
text-align: right;
|
||||
}
|
@ -52,6 +52,9 @@ div.title_STANDARD, .champs_STANDARD {
|
||||
div.title_MALUS {
|
||||
background-color: #ff4700;
|
||||
}
|
||||
.sums {
|
||||
background: #ddd;
|
||||
}
|
||||
/***************************/
|
||||
/* Statut des cellules */
|
||||
/***************************/
|
||||
|
28
app/static/js/formation_recap.js
Normal file
28
app/static/js/formation_recap.js
Normal file
@ -0,0 +1,28 @@
|
||||
/* Page accueil département */
|
||||
var apo_ue_editor = null;
|
||||
var apo_mod_editor = null;
|
||||
|
||||
$(document).ready(function () {
|
||||
var table_options = {
|
||||
"paging": false,
|
||||
"searching": false,
|
||||
"info": false,
|
||||
/* "autoWidth" : false, */
|
||||
"fixedHeader": {
|
||||
"header": true,
|
||||
"footer": true
|
||||
},
|
||||
"orderCellsTop": true, // cellules ligne 1 pour tri
|
||||
"aaSorting": [], // Prevent initial sorting
|
||||
};
|
||||
$('table#formation_table_recap').DataTable(table_options);
|
||||
let table_editable = document.querySelector("table#formation_table_recap.apo_editable");
|
||||
if (table_editable) {
|
||||
let apo_ue_save_url = document.querySelector("table#formation_table_recap.apo_editable").dataset.apo_ue_save_url;
|
||||
apo_ue_editor = new ScoFieldEditor("table#formation_table_recap tr.ue td.apo", apo_ue_save_url, false);
|
||||
let apo_mod_save_url = document.querySelector("table#formation_table_recap.apo_editable").dataset.apo_mod_save_url;
|
||||
apo_mod_editor = new ScoFieldEditor("table#formation_table_recap tr.mod td.apo", apo_mod_save_url, false);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -133,3 +133,134 @@ function readOnlyTags(nodes) {
|
||||
node.after('<span class="ro_tags"><span class="ro_tag">' + tags.join('</span><span class="ro_tag">') + '</span></span>');
|
||||
}
|
||||
}
|
||||
|
||||
/* Editeur pour champs
|
||||
* Usage: créer un élément avec data-oid (object id)
|
||||
* La méthode d'URL save sera appelée en POST avec deux arguments: oid et value,
|
||||
* value contenant la valeur du champs.
|
||||
* Inspiré par les codes et conseils de Seb. L.
|
||||
*/
|
||||
class ScoFieldEditor {
|
||||
constructor(selector, save_url, read_only) {
|
||||
this.save_url = save_url;
|
||||
this.read_only = read_only;
|
||||
this.selector = selector;
|
||||
this.installListeners();
|
||||
}
|
||||
// Enregistre l'élément obj
|
||||
save(obj) {
|
||||
var value = obj.innerText.trim();
|
||||
if (value.length == 0) {
|
||||
value = "";
|
||||
}
|
||||
if (value == obj.dataset.value) {
|
||||
return true; // Aucune modification, pas d'enregistrement mais on continue normalement
|
||||
}
|
||||
obj.classList.add("sco_wait");
|
||||
// DEBUG
|
||||
// console.log(`
|
||||
// data : ${value},
|
||||
// id: ${obj.dataset.oid}
|
||||
// `);
|
||||
|
||||
$.post(this.save_url,
|
||||
{
|
||||
oid: obj.dataset.oid,
|
||||
value: value,
|
||||
},
|
||||
function (result) {
|
||||
obj.classList.remove("sco_wait");
|
||||
obj.classList.add("sco_modified");
|
||||
}
|
||||
);
|
||||
return true;
|
||||
}
|
||||
/*****************************/
|
||||
/* Gestion des évènements */
|
||||
/*****************************/
|
||||
installListeners() {
|
||||
if (this.read_only) {
|
||||
return;
|
||||
}
|
||||
document.body.addEventListener("keydown", this.key);
|
||||
let editor = this;
|
||||
this.handleSelectCell = (event) => { editor.selectCell(event) };
|
||||
this.handleModifCell = (event) => { editor.modifCell(event) };
|
||||
this.handleBlur = (event) => { editor.blurCell(event) };
|
||||
this.handleKeyCell = (event) => { editor.keyCell(event) };
|
||||
document.querySelectorAll(this.selector).forEach(cellule => {
|
||||
cellule.addEventListener("click", this.handleSelectCell);
|
||||
cellule.addEventListener("dblclick", this.handleModifCell);
|
||||
cellule.addEventListener("blur", this.handleBlur);
|
||||
});
|
||||
}
|
||||
/*********************************/
|
||||
/* Interaction avec les cellules */
|
||||
/*********************************/
|
||||
blurCell(event) {
|
||||
let currentModif = document.querySelector(".sco_modifying");
|
||||
if (currentModif) {
|
||||
if (!this.save(currentModif)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
selectCell(event) {
|
||||
let obj = event.currentTarget;
|
||||
if (obj) {
|
||||
if (obj.classList.contains("sco_modifying")) {
|
||||
return; // Cellule en cours de modification, ne pas sélectionner.
|
||||
}
|
||||
let currentModif = document.querySelector(".sco_modifying");
|
||||
if (currentModif) {
|
||||
if (!this.save(currentModif)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.unselectCell();
|
||||
obj.classList.add("sco_selected");
|
||||
}
|
||||
}
|
||||
unselectCell() {
|
||||
document.querySelectorAll(".sco_selected, .sco_modifying").forEach(cellule => {
|
||||
cellule.classList.remove("sco_selected", "sco_modifying");
|
||||
cellule.removeAttribute("contentEditable");
|
||||
cellule.removeEventListener("keydown", this.handleKeyCell);
|
||||
});
|
||||
}
|
||||
modifCell(event) {
|
||||
let obj = event.currentTarget;
|
||||
if (obj) {
|
||||
obj.classList.add("sco_modifying");
|
||||
obj.contentEditable = true;
|
||||
obj.addEventListener("keydown", this.handleKeyCell);
|
||||
obj.focus();
|
||||
}
|
||||
}
|
||||
key(event) {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
this.modifCell(document.querySelector(".sco_selected"));
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
keyCell(event) {
|
||||
let obj = event.currentTarget;
|
||||
if (obj) {
|
||||
if (event.key == "Enter") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (!this.save(obj)) {
|
||||
return
|
||||
}
|
||||
obj.classList.remove("sco_modifying");
|
||||
// ArrowMove(0, 1);
|
||||
// modifCell(document.querySelector(".sco_selected"));
|
||||
this.unselectCell();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
33
app/static/js/scolar_index.js
Normal file
33
app/static/js/scolar_index.js
Normal file
@ -0,0 +1,33 @@
|
||||
/* Page accueil département */
|
||||
var apo_editor = null;
|
||||
var elt_annee_apo_editor = null;
|
||||
var elt_sem_apo_editor = null;
|
||||
|
||||
$(document).ready(function () {
|
||||
var table_options = {
|
||||
"paging": false,
|
||||
"searching": false,
|
||||
"info": false,
|
||||
/* "autoWidth" : false, */
|
||||
"fixedHeader": {
|
||||
"header": true,
|
||||
"footer": true
|
||||
},
|
||||
"orderCellsTop": true, // cellules ligne 1 pour tri
|
||||
"aaSorting": [], // Prevent initial sorting
|
||||
};
|
||||
$('table.semlist').DataTable(table_options);
|
||||
let table_editable = document.querySelector("table#semlist.apo_editable");
|
||||
if (table_editable) {
|
||||
let save_url = document.querySelector("table#semlist.apo_editable").dataset.apo_save_url;
|
||||
apo_editor = new ScoFieldEditor(".etapes_apo_str", save_url, false);
|
||||
|
||||
save_url = document.querySelector("table#semlist.apo_editable").dataset.elt_annee_apo_save_url;
|
||||
elt_annee_apo_editor = new ScoFieldEditor(".elt_annee_apo", save_url, false);
|
||||
|
||||
save_url = document.querySelector("table#semlist.apo_editable").dataset.elt_sem_apo_save_url;
|
||||
elt_sem_apo_editor = new ScoFieldEditor(".elt_sem_apo", save_url, false);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -4,8 +4,13 @@
|
||||
/*****************************/
|
||||
/* Mise en place des données */
|
||||
/*****************************/
|
||||
let lastX;
|
||||
let lastY;
|
||||
|
||||
function build_table(data) {
|
||||
let output = "";
|
||||
let sumsUE = {};
|
||||
let sumsRessources = {};
|
||||
|
||||
data.forEach((cellule) => {
|
||||
output += `
|
||||
@ -29,11 +34,61 @@ function build_table(data) {
|
||||
">
|
||||
${cellule.data}
|
||||
</div>`;
|
||||
|
||||
if (cellule.editable) {
|
||||
sumsRessources[cellule.y] = (sumsRessources[cellule.y] ?? 0) + (parseFloat(cellule.data) || 0);
|
||||
sumsUE[cellule.x] = (sumsUE[cellule.x] ?? 0) + (parseFloat(cellule.data) || 0);
|
||||
}
|
||||
})
|
||||
|
||||
output += showSums(sumsRessources, sumsUE);
|
||||
document.querySelector(".tableau").innerHTML = output;
|
||||
installListeners();
|
||||
}
|
||||
|
||||
function showSums(sumsRessources, sumsUE) {
|
||||
lastX = Object.keys(sumsUE).length + 2;
|
||||
lastY = Object.keys(sumsRessources).length + 2;
|
||||
|
||||
let output = "";
|
||||
|
||||
Object.entries(sumsUE).forEach(([num, value]) => {
|
||||
output += `
|
||||
<div
|
||||
class="sums"
|
||||
data-editable="false"
|
||||
data-x="${num}"
|
||||
data-y="${lastY}"
|
||||
style="
|
||||
--x:${num};
|
||||
--y:${lastY};
|
||||
--nbX:1;
|
||||
--nbY:1;
|
||||
">
|
||||
${value}
|
||||
</div>`;
|
||||
})
|
||||
|
||||
Object.entries(sumsRessources).forEach(([num, value]) => {
|
||||
output += `
|
||||
<div
|
||||
class="sums"
|
||||
data-editable="false"
|
||||
data-x="${lastX}"
|
||||
data-y="${num}"
|
||||
style="
|
||||
--x:${lastX};
|
||||
--y:${num};
|
||||
--nbX:1;
|
||||
--nbY:1;
|
||||
">
|
||||
${value}
|
||||
</div>`;
|
||||
})
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*****************************/
|
||||
/* Gestion des évènements */
|
||||
/*****************************/
|
||||
@ -54,6 +109,7 @@ function installListeners() {
|
||||
}
|
||||
}
|
||||
});
|
||||
cellule.addEventListener("input", processSums);
|
||||
});
|
||||
}
|
||||
|
||||
@ -120,10 +176,27 @@ function keyCell(event) {
|
||||
return
|
||||
}
|
||||
this.classList.remove("modifying");
|
||||
let selected = document.querySelector(".selected");
|
||||
ArrowMove(0, 1);
|
||||
if (selected != document.querySelector(".selected")) {
|
||||
modifCell(document.querySelector(".selected"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processSums() {
|
||||
let sum = 0;
|
||||
document.querySelectorAll(`[data-editable="true"][data-x="${this.dataset.x}"]`).forEach(e => {
|
||||
sum += parseFloat(e.innerText) || 0;
|
||||
})
|
||||
document.querySelector(`.sums[data-x="${this.dataset.x}"][data-y="${lastY}"]`).innerText = sum;
|
||||
|
||||
sum = 0;
|
||||
document.querySelectorAll(`[data-editable="true"][data-y="${this.dataset.y}"]`).forEach(e => {
|
||||
sum += parseFloat(e.innerText) || 0;
|
||||
})
|
||||
document.querySelector(`.sums[data-x="${lastX}"][data-y="${this.dataset.y}"]`).innerText = sum;
|
||||
}
|
||||
|
||||
/******************************/
|
||||
/* Affichage d'un message */
|
||||
|
@ -31,6 +31,18 @@
|
||||
display: none;
|
||||
}`;
|
||||
releve.shadowRoot.appendChild(style);
|
||||
})
|
||||
.catch(error => {
|
||||
let div = document.createElement("div");
|
||||
div.innerText = "Une erreur s'est produite lors du transfère des données.";
|
||||
div.style.fontSize = "24px";
|
||||
div.style.color = "#d93030";
|
||||
|
||||
let releve = document.querySelector("releve-but");
|
||||
releve.after(div);
|
||||
releve.remove();
|
||||
|
||||
throw 'Fin du script - données invalides';
|
||||
});
|
||||
document.querySelector("html").style.scrollBehavior = "smooth";
|
||||
</script>
|
||||
|
@ -20,7 +20,7 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_add_logo(add_logo_form) %}
|
||||
<details>
|
||||
<details {{ add_logo_form.opened() }}>
|
||||
<summary>
|
||||
<h3>Ajouter un logo</h3>
|
||||
</summary>
|
||||
@ -33,7 +33,7 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_logo(dept_form, logo_form) %}
|
||||
<details>
|
||||
<details {{ logo_form.opened() }}>
|
||||
{{ logo_form.hidden_tag() }}
|
||||
<summary>
|
||||
{% if logo_form.titre %}
|
||||
@ -65,6 +65,11 @@
|
||||
<span class="wtf-field">{{ render_field(logo_form.upload, False, onchange="submit_form()") }}</span>
|
||||
</div>
|
||||
{% if logo_form.can_delete %}
|
||||
<div class="action_label">Renommer</div>
|
||||
<div class="action_button">
|
||||
{{ render_field(logo_form.new_name, False) }}
|
||||
{{ render_field(logo_form.do_rename, False, onSubmit="submit_form()") }}
|
||||
</div>
|
||||
<div class="action_label">Supprimer l'image</div>
|
||||
<div class="action_button">
|
||||
{{ render_field(logo_form.do_delete, False, onSubmit="submit_form()") }}
|
||||
@ -97,20 +102,24 @@
|
||||
<div class="configuration_logo">
|
||||
<h1>Bibliothèque de logos</h1>
|
||||
{% for dept_entry in form.depts.entries %}
|
||||
<details>
|
||||
{% set dept_form = dept_entry.form %}
|
||||
{{ dept_entry.form.hidden_tag() }}
|
||||
<details {{ dept_form.opened() }}>
|
||||
<summary>
|
||||
<span class="entete_dept">
|
||||
{% if dept_entry.form.is_local() %}
|
||||
<h2>Département {{ dept_form.dept_name.data }}</h2>
|
||||
<h3 class="effectifs">{{ dept_form.count() }}</h3>
|
||||
<div class="sco_help">Les paramètres donnés sont spécifiques à ce département.<br />
|
||||
Les logos du département se substituent aux logos de même nom définis globalement:</div>
|
||||
{% else %}
|
||||
<h2>Logos généraux</h2>
|
||||
<h3 class="effectifs">{{ dept_form.count() }}</h3>
|
||||
<div class="sco_help">Les images de cette section sont utilisé pour tous les départements,
|
||||
mais peuvent être redéfinies localement au niveau de chaque département
|
||||
(il suffit de définir un logo local de même nom)</div>
|
||||
{% endif %}
|
||||
</span>
|
||||
</summary>
|
||||
<div>
|
||||
{{ render_logos(dept_form) }}
|
||||
|
@ -1,26 +0,0 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
<div class="contact">
|
||||
<div>
|
||||
Nom : {{ contact.nom }}<br>
|
||||
Prénom : {{ contact.prenom }}<br>
|
||||
{% if contact.telephone %}
|
||||
Téléphone : {{ contact.telephone }}<br>
|
||||
{% endif %}
|
||||
{% if contact.mail %}
|
||||
Mail : {{ contact.mail }}<br>
|
||||
{% endif %}
|
||||
{% if contact.poste %}
|
||||
Poste : {{ contact.poste }}<br>
|
||||
{% endif %}
|
||||
{% if contact.service %}
|
||||
Service : {{ contact.service }}<br>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
<div class="parent-btn">
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.edit_contact', id=contact.id) }}">Modifier contact</a>
|
||||
<a class="btn btn-danger" href="{{ url_for('entreprises.delete_contact', id=contact.id) }}">Supprimer contact</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
26
app/templates/entreprises/_correspondant.html
Normal file
26
app/templates/entreprises/_correspondant.html
Normal file
@ -0,0 +1,26 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
<div class="correspondant">
|
||||
<div>
|
||||
Nom : {{ correspondant.nom }}<br>
|
||||
Prénom : {{ correspondant.prenom }}<br>
|
||||
{% if correspondant.telephone %}
|
||||
Téléphone : {{ correspondant.telephone }}<br>
|
||||
{% endif %}
|
||||
{% if correspondant.mail %}
|
||||
Mail : {{ correspondant.mail }}<br>
|
||||
{% endif %}
|
||||
{% if correspondant.poste %}
|
||||
Poste : {{ correspondant.poste }}<br>
|
||||
{% endif %}
|
||||
{% if correspondant.service %}
|
||||
Service : {{ correspondant.service }}<br>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
<div class="parent-btn">
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.edit_correspondant', id=correspondant.id) }}">Modifier correspondant</a>
|
||||
<a class="btn btn-danger" href="{{ url_for('entreprises.delete_correspondant', id=correspondant.id) }}">Supprimer correspondant</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
@ -1,6 +1,7 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
<div class="offre">
|
||||
<div>
|
||||
Ajouté le {{ offre[0].date_ajout.strftime('%d/%m/%y') }} à {{ offre[0].date_ajout.strftime('%Hh%M') }}<br>
|
||||
Intitulé : {{ offre[0].intitule }}<br>
|
||||
Description : {{ offre[0].description }}<br>
|
||||
Type de l'offre : {{ offre[0].type_offre }}<br>
|
||||
@ -9,6 +10,16 @@
|
||||
{% if offre[2] %}
|
||||
Département(s) : {% for offre_dept in offre[2] %} <div class="offre-depts">{{ offre_dept.dept_id|get_dept_acronym }}</div> {% endfor %}<br>
|
||||
{% endif %}
|
||||
|
||||
{% if offre[0].correspondant_id %}
|
||||
Contacté {{ offre[3].nom }} {{ offre[3].prenom }}
|
||||
{% if offre[3].mail and offre[3].telephone %}
|
||||
({{ offre[3].mail }} - {{ offre[3].telephone }})<br>
|
||||
{% else %}
|
||||
({{ offre[3].mail }}{{offre[3].telephone}})<br>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% for fichier in offre[1] %}
|
||||
<a href="{{ url_for('entreprises.get_offre_file', entreprise_id=entreprise.id, offre_id=offre[0].id, filedir=fichier[0], filename=fichier[1] )}}">{{ fichier[1] }}</a>
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
@ -16,6 +27,7 @@
|
||||
{% endif %}
|
||||
<br>
|
||||
{% endfor %}
|
||||
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
<a href="{{ url_for('entreprises.add_offre_file', offre_id=offre[0].id) }}">Ajoutez un fichier</a>
|
||||
{% endif %}
|
||||
@ -26,9 +38,11 @@
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.edit_offre', id=offre[0].id) }}">Modifier l'offre</a>
|
||||
<a class="btn btn-danger" href="{{ url_for('entreprises.delete_offre', id=offre[0].id) }}">Supprimer l'offre</a>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesSend, None) %}
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.envoyer_offre', id=offre[0].id) }}">Envoyer l'offre</a>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
{% if not offre[0].expired %}
|
||||
<a class="btn btn-danger" href="{{ url_for('entreprises.expired', id=offre[0].id) }}">Rendre expirée</a>
|
||||
|
56
app/templates/entreprises/ajout_correspondants.html
Normal file
56
app/templates/entreprises/ajout_correspondants.html
Normal file
@ -0,0 +1,56 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
{% extends 'base.html' %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>{{ title }}</h1>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<p>
|
||||
(*) champs requis
|
||||
</p>
|
||||
<form method="POST" action="" novalidate>
|
||||
{{ form.hidden_tag() }}
|
||||
{% for subfield in form.correspondants %}
|
||||
{% for subsubfield in subfield %}
|
||||
{% if subsubfield.errors %}
|
||||
{% for error in subsubfield.errors %}
|
||||
<p class="help-block form-error">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{{ form.correspondants }}
|
||||
<div style="margin-bottom: 10px;">
|
||||
<button class="btn btn-default" id="add-correspondant-field">Ajouter un correspondant</button>
|
||||
<input class="btn btn-default" type="submit" value="Envoyer">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onload = function(e) {
|
||||
let addCorrespondantFieldBtn = document.getElementById('add-correspondant-field');
|
||||
addCorrespondantFieldBtn.addEventListener('click', function(e){
|
||||
e.preventDefault();
|
||||
let allCorrepondantsFieldWrapper = document.getElementById('correspondants');
|
||||
let allCorrepondantsField = allCorrepondantsFieldWrapper.getElementsByTagName('input');
|
||||
let correspondantInputIds = []
|
||||
let csrf_token = document.getElementById('csrf_token').value;
|
||||
for(let i = 0; i < allCorrepondantsField.length; i++) {
|
||||
correspondantInputIds.push(parseInt(allCorrepondantsField[i].name.split('-')[1]));
|
||||
}
|
||||
let newFieldName = `correspondants-${Math.max(...correspondantInputIds) + 1}`;
|
||||
allCorrepondantsFieldWrapper.insertAdjacentHTML('beforeend',`
|
||||
<li><label for="${newFieldName}">Correspondants-${Math.max(...correspondantInputIds) + 1}</label> <table id="${newFieldName}"><tr><th><label for="${newFieldName}-nom">Nom (*)</label></th><td><input class="form-control" id="${newFieldName}-nom" name="${newFieldName}-nom" required type="text" value=""></td></tr><tr><th><label for="${newFieldName}-prenom">Prénom (*)</label></th><td><input class="form-control" id="${newFieldName}-prenom" name="${newFieldName}-prenom" required type="text" value=""></td></tr><tr><th><label for="${newFieldName}-telephone">Téléphone (*)</label></th><td><input class="form-control" id="${newFieldName}-telephone" name="${newFieldName}-telephone" type="text" value=""></td></tr><tr><th><label for="${newFieldName}-mail">Mail (*)</label></th><td><input class="form-control" id="${newFieldName}-mail" name="${newFieldName}-mail" type="text" value=""></td></tr><tr><th><label for="${newFieldName}-poste">Poste</label></th><td><input class="form-control" id="${newFieldName}-poste" name="${newFieldName}-poste" type="text" value=""></td></tr><tr><th><label for="${newFieldName}-service">Service</label></th><td><input class="form-control" id="${newFieldName}-service" name="${newFieldName}-service" type="text" value=""></td></tr></table><input id="${newFieldName}-csrf_token" name="${newFieldName}-csrf_token" type="hidden" value=${csrf_token}></li>
|
||||
`);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
@ -3,7 +3,7 @@
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Ajout entreprise avec contact</h1>
|
||||
<h1>Ajout entreprise</h1>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
@ -16,12 +16,18 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.onload = function(e){
|
||||
{# ajout margin-bottom sur le champ pays #}
|
||||
var champ_pays = document.getElementById("pays")
|
||||
if (champ_pays !== null) {
|
||||
var closest_form_group = champ_pays.closest(".form-group")
|
||||
closest_form_group.style.marginBottom = "50px"
|
||||
}
|
||||
|
||||
document.getElementById("siret").addEventListener("keyup", autocomplete);
|
||||
|
||||
function autocomplete() {
|
||||
var input = document.getElementById("siret").value;
|
||||
if(input.length == 14) {
|
||||
var input = document.getElementById("siret").value.replaceAll(" ", "")
|
||||
if(input.length >= 14) {
|
||||
fetch("https://entreprise.data.gouv.fr/api/sirene/v1/siret/" + input)
|
||||
.then(response => {
|
||||
if(response.ok)
|
||||
@ -48,13 +54,5 @@
|
||||
document.getElementById("codepostal").value = ''
|
||||
document.getElementById("ville").value = ''
|
||||
}
|
||||
}
|
||||
|
||||
{# ajout margin-bottom sur le champ pays #}
|
||||
var champ_pays = document.getElementById("pays")
|
||||
if (champ_pays !== null) {
|
||||
var closest_form_group = champ_pays.closest(".form-group")
|
||||
closest_form_group.style.marginBottom = "50px"
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
@ -9,7 +9,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Ajout historique</h1>
|
||||
<h1>{{ title }}</h1>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
@ -9,64 +9,51 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
{% include 'entreprises/nav.html' %}
|
||||
|
||||
{% if logs %}
|
||||
<div class="container">
|
||||
<h3>Dernières opérations <a href="{{ url_for('entreprises.logs') }}">Voir tout</a></h3>
|
||||
<ul>
|
||||
{% for log in logs %}
|
||||
<li><span style="margin-right: 10px;">{{ log.date.strftime('%d %b %Hh%M') }}</span><span>{{ log.text|safe }} par {{ log.authenticated_user|get_nomcomplet_by_username }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="container boutons">
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesExport, None) %}
|
||||
<a class="btn btn-default" href="{{ url_for('entreprises.import_contacts') }}">Importer des contacts</a>
|
||||
{% endif %}
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesExport, None) and contacts %}
|
||||
<a class="btn btn-default" href="{{ url_for('entreprises.export_contacts') }}">Exporter la liste des contacts</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="container" style="margin-bottom: 10px;">
|
||||
<h1>Liste des contacts</h1>
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
<a class="btn btn-primary" style="margin-bottom:10px;" href="{{ url_for('entreprises.add_contact', id=entreprise_id) }}">Ajouter contact</a>
|
||||
{% endif %}
|
||||
<table id="table-contacts">
|
||||
<thead>
|
||||
<tr>
|
||||
<td data-priority="1">Nom</td>
|
||||
<td data-priority="3">Prenom</td>
|
||||
<td data-priority="4">Telephone</td>
|
||||
<td data-priority="5">Mail</td>
|
||||
<td data-priority="6">Poste</td>
|
||||
<td data-priority="7">Service</td>
|
||||
<td data-priority="2">Entreprise</td>
|
||||
<td data-priority="">Date</td>
|
||||
<td data-priority="">Utilisateur</td>
|
||||
<td data-priority="">Notes</td>
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
<td data-priority="">Action</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for contact in contacts %}
|
||||
<tr>
|
||||
<td>{{ contact[0].nom }}</td>
|
||||
<td>{{ contact[0].prenom }}</td>
|
||||
<td>{{ contact[0].telephone }}</td>
|
||||
<td>{{ contact[0].mail }}</td>
|
||||
<td>{{ contact[0].poste}}</td>
|
||||
<td>{{ contact[0].service}}</td>
|
||||
<td><a href="{{ url_for('entreprises.fiche_entreprise', id=contact[1].id) }}">{{ contact[1].nom }}</a></td>
|
||||
<td>{{ contact.date.strftime('%d/%m/%Y %Hh%M') }}</td>
|
||||
<td>{{ contact.user|get_nomcomplet_by_id }}</td>
|
||||
<td>{{ contact.notes }}</td>
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-default dropdown-toggle" data-toggle="dropdown" href="#">Action
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-left">
|
||||
<li><a href="{{ url_for('entreprises.edit_contact', id=contact.id) }}">Modifier</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>Nom</td>
|
||||
<td>Prenom</td>
|
||||
<td>Telephone</td>
|
||||
<td>Mail</td>
|
||||
<td>Poste</td>
|
||||
<td>Service</td>
|
||||
<td>Entreprise</td>
|
||||
<td>Date</td>
|
||||
<td>Utilisateur</td>
|
||||
<td>Notes</td>
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
<td>Action</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
102
app/templates/entreprises/correspondants.html
Normal file
102
app/templates/entreprises/correspondants.html
Normal file
@ -0,0 +1,102 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<script src="/ScoDoc/static/jQuery/jquery-1.12.4.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/ScoDoc/static/DataTables/datatables.min.css">
|
||||
<script type="text/javascript" charset="utf8" src="/ScoDoc/static/DataTables/datatables.min.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
{% include 'entreprises/nav.html' %}
|
||||
|
||||
{% if logs %}
|
||||
<div class="container">
|
||||
<h3>Dernières opérations <a href="{{ url_for('entreprises.logs') }}">Voir tout</a></h3>
|
||||
<ul>
|
||||
{% for log in logs %}
|
||||
<li><span style="margin-right: 10px;">{{ log.date.strftime('%d %b %Hh%M') }}</span><span>{{ log.text|safe }} par {{ log.authenticated_user|get_nomcomplet_by_username }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="container boutons">
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesExport, None) %}
|
||||
<a class="btn btn-default" href="{{ url_for('entreprises.import_correspondants') }}">Importer des correspondants</a>
|
||||
{% endif %}
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesExport, None) and correspondants %}
|
||||
<a class="btn btn-default" href="{{ url_for('entreprises.export_correspondants') }}">Exporter la liste des correspondants</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="container" style="margin-bottom: 10px;">
|
||||
<h1>Liste des correspondants</h1>
|
||||
<table id="table-correspondants">
|
||||
<thead>
|
||||
<tr>
|
||||
<td data-priority="1">Nom</td>
|
||||
<td data-priority="3">Prenom</td>
|
||||
<td data-priority="4">Téléphone</td>
|
||||
<td data-priority="5">Mail</td>
|
||||
<td data-priority="6">Poste</td>
|
||||
<td data-priority="7">Service</td>
|
||||
<td data-priority="2">Entreprise</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for correspondant in correspondants %}
|
||||
<tr>
|
||||
<td>{{ correspondant[0].nom }}</td>
|
||||
<td>{{ correspondant[0].prenom }}</td>
|
||||
<td>{{ correspondant[0].telephone }}</td>
|
||||
<td>{{ correspondant[0].mail }}</td>
|
||||
<td>{{ correspondant[0].poste}}</td>
|
||||
<td>{{ correspondant[0].service}}</td>
|
||||
<td><a href="{{ url_for('entreprises.fiche_entreprise', id=correspondant[1].id) }}">{{ correspondant[1].nom }}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>Nom</td>
|
||||
<td>Prenom</td>
|
||||
<td>Téléphone</td>
|
||||
<td>Mail</td>
|
||||
<td>Poste</td>
|
||||
<td>Service</td>
|
||||
<td>Entreprise</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let table = new DataTable('#table-correspondants',
|
||||
{
|
||||
"autoWidth": false,
|
||||
"responsive": {
|
||||
"details": true
|
||||
},
|
||||
"pageLength": 10,
|
||||
"language": {
|
||||
"emptyTable": "Aucune donnée disponible dans le tableau",
|
||||
"info": "Affichage de _START_ à _END_ sur _TOTAL_ entrées",
|
||||
"infoEmpty": "Affichage de 0 à 0 sur 0 entrées",
|
||||
"infoFiltered": "(filtrées depuis un total de _MAX_ entrées)",
|
||||
"lengthMenu": "Afficher _MENU_ entrées",
|
||||
"loadingRecords": "Chargement...",
|
||||
"processing": "Traitement...",
|
||||
"search": "Rechercher:",
|
||||
"zeroRecords": "Aucune entrée correspondante trouvée",
|
||||
"paginate": {
|
||||
"next": "Suivante",
|
||||
"previous": "Précédente"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -16,7 +16,22 @@
|
||||
<p>
|
||||
(*) champs requis
|
||||
</p>
|
||||
{{ wtf.quick_form(form, novalidate=True) }}
|
||||
<form method="POST" action="" novalidate>
|
||||
{{ form.hidden_tag() }}
|
||||
{{ form.responsables.label }}<br>
|
||||
{% for subfield in form.responsables %}
|
||||
{% if subfield.errors %}
|
||||
{% for error in subfield.errors %}
|
||||
<p class="help-block form-error">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ form.responsables }}
|
||||
<div style="margin-bottom: 10px;">
|
||||
<button class="btn btn-default" id="add-responsable-field">Ajouter un responsable</button>
|
||||
<input class="btn btn-default" type="submit" value="Envoyer">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -30,7 +45,28 @@
|
||||
minchars: 2,
|
||||
timeout: 60000
|
||||
};
|
||||
var as_responsables = new bsn.AutoSuggest('responsable', responsables_options);
|
||||
|
||||
let allResponsablesFieldWrapper = document.getElementById('responsables');
|
||||
let allResponsablesField = allResponsablesFieldWrapper.getElementsByTagName('input');
|
||||
for(let i = 0; i < allResponsablesField.length; i++) {
|
||||
new bsn.AutoSuggest(allResponsablesField[i].id, responsables_options);
|
||||
}
|
||||
|
||||
let addResponsableFieldBtn = document.getElementById('add-responsable-field');
|
||||
addResponsableFieldBtn.addEventListener('click', function(e){
|
||||
e.preventDefault();
|
||||
let allResponsablesFieldWrapper = document.getElementById('responsables');
|
||||
let allResponsablesField = allResponsablesFieldWrapper.getElementsByTagName('input');
|
||||
let responsableInputIds = []
|
||||
for(let i = 0; i < allResponsablesField.length; i++) {
|
||||
responsableInputIds.push(parseInt(allResponsablesField[i].name.split('-')[1]));
|
||||
}
|
||||
let newFieldName = `responsables-${Math.max(...responsableInputIds) + 1}`;
|
||||
allResponsablesFieldWrapper.insertAdjacentHTML('beforeend',`
|
||||
<li><label for="${newFieldName}">Responsable (*)</label> <input class="form-control" id="${newFieldName}" name="${newFieldName}" type="text" value="" placeholder="Tapez le nom du responsable de formation"></li>
|
||||
`);
|
||||
var as_r = new bsn.AutoSuggest(newFieldName, responsables_options);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,6 +1,13 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<script src="/ScoDoc/static/jQuery/jquery-1.12.4.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/ScoDoc/static/DataTables/datatables.min.css">
|
||||
<script type="text/javascript" charset="utf8" src="/ScoDoc/static/DataTables/datatables.min.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
{% if logs %}
|
||||
<div class="container">
|
||||
@ -16,24 +23,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if historique %}
|
||||
<div class="container">
|
||||
<h3>Historique</h3>
|
||||
<ul>
|
||||
{% for data in historique %}
|
||||
<li>
|
||||
<span style="margin-right: 10px;">{{ data[0].date_debut.strftime('%d/%m/%Y') }} - {{
|
||||
data[0].date_fin.strftime('%d/%m/%Y') }}</span>
|
||||
<span style="margin-right: 10px;">
|
||||
{{ data[0].type_offre }} réalisé par {{ data[1].nom|format_nom }} {{ data[1].prenom|format_prenom }}
|
||||
{% if data[0].formation_text %} en {{ data[0].formation_text }}{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="container fiche-entreprise">
|
||||
<h2>Fiche entreprise - {{ entreprise.nom }} ({{ entreprise.siret }})</h2>
|
||||
|
||||
@ -53,20 +42,19 @@
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.edit_entreprise', id=entreprise.id) }}">Modifier</a>
|
||||
<a class="btn btn-danger" href="{{ url_for('entreprises.delete_entreprise', id=entreprise.id) }}">Supprimer</a>
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.add_offre', id=entreprise.id) }}">Ajouter offre</a>
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.add_contact', id=entreprise.id) }}">Ajouter contact</a>
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.add_historique', id=entreprise.id) }}">Ajouter
|
||||
historique</a>
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.add_correspondant', id=entreprise.id) }}">Ajouter correspondant</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.contacts', id=entreprise.id) }}">Liste contacts</a>
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.offres_expirees', id=entreprise.id) }}">Voir les offres expirées</a>
|
||||
<div>
|
||||
|
||||
|
||||
<div class="contacts-et-offres">
|
||||
{% if contacts %}
|
||||
<div class="correspondants-et-offres">
|
||||
{% if correspondants %}
|
||||
<div>
|
||||
<h3>Contacts</h3>
|
||||
{% for contact in contacts %}
|
||||
{% include 'entreprises/_contact.html' %}
|
||||
<h3>Correspondants</h3>
|
||||
{% for correspondant in correspondants %}
|
||||
{% include 'entreprises/_correspondant.html' %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -81,4 +69,95 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
<a class="btn btn-primary" href="{{ url_for('entreprises.add_stage_apprentissage', id=entreprise.id) }}">Ajouter stage ou apprentissage</a>
|
||||
{% endif %}
|
||||
<h3>Liste des stages et apprentissages réalisés au sein de l'entreprise</h3>
|
||||
<table id="table-stages-apprentissages">
|
||||
<thead>
|
||||
<tr>
|
||||
<td data-priority="">Date début</td>
|
||||
<td data-priority="">Date fin</td>
|
||||
<td data-priority="">Durée</td>
|
||||
<td data-priority="">Type</td>
|
||||
<td data-priority="">Étudiant</td>
|
||||
<td data-priority="">Formation</td>
|
||||
<td data-priority="">Notes</td>
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
<td data-priority="3">Action</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for data in stages_apprentissages %}
|
||||
<tr>
|
||||
<td>{{ data[0].date_debut.strftime('%d/%m/%Y') }}</td>
|
||||
<td>{{ data[0].date_fin.strftime('%d/%m/%Y') }}</td>
|
||||
<td>{{ (data[0].date_fin-data[0].date_debut).days//7 }} semaines</td>
|
||||
<td>{{ data[0].type_offre }}</td>
|
||||
<td>{{ data[1].nom|format_nom }} {{ data[1].prenom|format_prenom }}</td>
|
||||
<td>{% if data[0].formation_text %}{{ data[0].formation_text }}{% endif %}</td>
|
||||
<td>{{ data[0].notes }}</td>
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-default dropdown-toggle" data-toggle="dropdown" href="#">Action
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-left">
|
||||
<li><a href="{{ url_for('entreprises.edit_stage_apprentissage', id=data[0].id) }}">Modifier</a></li>
|
||||
<li><a href="{{ url_for('entreprises.delete_stage_apprentissage', id=data[0].id) }}" style="color:red">Supprimer</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>Date début</td>
|
||||
<td>Date fin</td>
|
||||
<td>Durée</td>
|
||||
<td>Type</td>
|
||||
<td>Étudiant</td>
|
||||
<td>Formation</td>
|
||||
<td>Notes</td>
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesChange, None) %}
|
||||
<td>Action</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let table = new DataTable('#table-stages-apprentissages',
|
||||
{
|
||||
"autoWidth": false,
|
||||
"responsive": {
|
||||
"details": true
|
||||
},
|
||||
"pageLength": 10,
|
||||
"language": {
|
||||
"emptyTable": "Aucune donnée disponible dans le tableau",
|
||||
"info": "Affichage de _START_ à _END_ sur _TOTAL_ entrées",
|
||||
"infoEmpty": "Affichage de 0 à 0 sur 0 entrées",
|
||||
"infoFiltered": "(filtrées depuis un total de _MAX_ entrées)",
|
||||
"lengthMenu": "Afficher _MENU_ entrées",
|
||||
"loadingRecords": "Chargement...",
|
||||
"processing": "Traitement...",
|
||||
"search": "Rechercher:",
|
||||
"zeroRecords": "Aucune entrée correspondante trouvée",
|
||||
"paginate": {
|
||||
"next": "Suivante",
|
||||
"previous": "Précédente"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -16,25 +16,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if contacts %}
|
||||
{% if correspondants %}
|
||||
<div>
|
||||
{% for contact in contacts %}
|
||||
{% for correspondant in correspondants %}
|
||||
<div>
|
||||
<h3>Contact</h3>
|
||||
<div class="contact">
|
||||
Nom : {{ contact.nom }}<br>
|
||||
Prénom : {{ contact.prenom }}<br>
|
||||
{% if contact.telephone %}
|
||||
Téléphone : {{ contact.telephone }}<br>
|
||||
<h3>Correspondant</h3>
|
||||
<div class="correspondant">
|
||||
Nom : {{ correspondant.nom }}<br>
|
||||
Prénom : {{ correspondant.prenom }}<br>
|
||||
{% if correspondant.telephone %}
|
||||
Téléphone : {{ correspondant.telephone }}<br>
|
||||
{% endif %}
|
||||
{% if contact.mail %}
|
||||
Mail : {{ contact.mail }}<br>
|
||||
{% if correspondant.mail %}
|
||||
Mail : {{ correspondant.mail }}<br>
|
||||
{% endif %}
|
||||
{% if contact.poste %}
|
||||
Poste : {{ contact.poste }}<br>
|
||||
{% if correspondant.poste %}
|
||||
Poste : {{ correspondant.poste }}<br>
|
||||
{% endif %}
|
||||
{% if contact.service %}
|
||||
Service : {{ contact.service }}<br>
|
||||
{% if correspondant.service %}
|
||||
Service : {{ correspondant.service }}<br>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/css/autosuggest_inquisitor.css" />
|
||||
<script src="/ScoDoc/static/libjs/AutoSuggest.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
@ -25,5 +27,36 @@
|
||||
var closest_form_control = champ_depts.closest(".form-control")
|
||||
closest_form_control.classList.remove("form-control")
|
||||
}
|
||||
|
||||
if(document.getElementById("expiration_date") !== null && document.getElementById("expiration_date").value === "")
|
||||
expiration()
|
||||
|
||||
if(document.getElementById("type_offre") !== null)
|
||||
document.getElementById("type_offre").addEventListener("change", expiration);
|
||||
|
||||
function expiration() {
|
||||
var date = new Date()
|
||||
var expiration = document.getElementById("expiration_date")
|
||||
var type_offre = document.getElementById("type_offre").value
|
||||
if (type_offre === "Alternance") {
|
||||
expiration.value = `${date.getFullYear() + 1}-01-01`
|
||||
} else {
|
||||
if(date.getMonth() + 1 < 8)
|
||||
expiration.value = `${date.getFullYear()}-08-01`
|
||||
else
|
||||
expiration.value = `${date.getFullYear() + 1}-08-01`
|
||||
}
|
||||
}
|
||||
|
||||
var responsables_options = {
|
||||
script: "/ScoDoc/entreprises/responsables?",
|
||||
varname: "term",
|
||||
json: true,
|
||||
noresults: "Valeur invalide !",
|
||||
minchars: 2,
|
||||
timeout: 60000
|
||||
};
|
||||
|
||||
var as_utilisateurs = new bsn.AutoSuggest('utilisateur', responsables_options);
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,62 +0,0 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
{% extends 'base.html' %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Importation contacts</h1>
|
||||
<br>
|
||||
<div>
|
||||
<a href="{{ url_for('entreprises.get_import_contacts_file_sample') }}">Obtenir la feuille excel à remplir</a>
|
||||
</div>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<p>
|
||||
(*) champs requis
|
||||
</p>
|
||||
{{ wtf.quick_form(form, novalidate=True) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not contacts_import %}
|
||||
<table class="table">
|
||||
<thead><tr><td><b>Attribut</b></td><td><b>Type</b></td><td><b>Description</b></td></tr></thead>
|
||||
<tr><td>nom</td><td>text</td><td>nom du contact</td></tr>
|
||||
<tr><td>prenom</td><td>text</td><td>prenom du contact</td></tr>
|
||||
<tr><td>telephone</td><td>text</td><td>telephone du contact</td></tr>
|
||||
<tr><td>mail</td><td>text</td><td>mail du contact</td></tr>
|
||||
<tr><td>poste</td><td>text</td><td>poste du contact</td></tr>
|
||||
<tr><td>service</td><td>text</td><td>service dans lequel travaille le contact</td></tr>
|
||||
<tr><td>entreprise_siret</td><td>integer</td><td>SIRET de l'entreprise</td></tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if contacts_import %}
|
||||
<br><div>Importation de {{ contacts_import|length }} contact(s)</div>
|
||||
{% for contact in contacts_import %}
|
||||
<div class="contact">
|
||||
<div>
|
||||
Nom : {{ contact.nom }}<br>
|
||||
Prénom : {{ contact.prenom }}<br>
|
||||
{% if contact.telephone %}
|
||||
Téléphone : {{ contact.telephone }}<br>
|
||||
{% endif %}
|
||||
{% if contact.mail %}
|
||||
Mail : {{ contact.mail }}<br>
|
||||
{% endif %}
|
||||
{% if contact.poste %}
|
||||
Poste : {{ contact.poste }}<br>
|
||||
{% endif %}
|
||||
{% if contact.service %}
|
||||
Service : {{ contact.service }}<br>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('entreprises.fiche_entreprise', id=contact.entreprise_id )}}">lien vers l'entreprise</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
62
app/templates/entreprises/import_correspondants.html
Normal file
62
app/templates/entreprises/import_correspondants.html
Normal file
@ -0,0 +1,62 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
{% extends 'base.html' %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Importation correspondants</h1>
|
||||
<br>
|
||||
<div>
|
||||
<a href="{{ url_for('entreprises.get_import_correspondants_file_sample') }}">Obtenir la feuille excel à remplir</a>
|
||||
</div>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<p>
|
||||
(*) champs requis
|
||||
</p>
|
||||
{{ wtf.quick_form(form, novalidate=True) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not correspondants_import %}
|
||||
<table class="table">
|
||||
<thead><tr><td><b>Attribut</b></td><td><b>Type</b></td><td><b>Description</b></td></tr></thead>
|
||||
<tr><td>nom</td><td>text</td><td>nom du correspondant</td></tr>
|
||||
<tr><td>prenom</td><td>text</td><td>prenom du correspondant</td></tr>
|
||||
<tr><td>telephone</td><td>text</td><td>telephone du correspondant</td></tr>
|
||||
<tr><td>mail</td><td>text</td><td>mail du correspondant</td></tr>
|
||||
<tr><td>poste</td><td>text</td><td>poste du correspondant</td></tr>
|
||||
<tr><td>service</td><td>text</td><td>service dans lequel travaille le correspondant</td></tr>
|
||||
<tr><td>entreprise_siret</td><td>integer</td><td>SIRET de l'entreprise</td></tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if correspondants_import %}
|
||||
<br><div>Importation de {{ correspondants_import|length }} correspondant(s)</div>
|
||||
{% for correspondant in correspondants_import %}
|
||||
<div class="correspondant">
|
||||
<div>
|
||||
Nom : {{ correspondant.nom }}<br>
|
||||
Prénom : {{ correspondant.prenom }}<br>
|
||||
{% if correspondant.telephone %}
|
||||
Téléphone : {{ correspondant.telephone }}<br>
|
||||
{% endif %}
|
||||
{% if correspondant.mail %}
|
||||
Mail : {{ correspondant.mail }}<br>
|
||||
{% endif %}
|
||||
{% if correspondant.poste %}
|
||||
Poste : {{ correspondant.poste }}<br>
|
||||
{% endif %}
|
||||
{% if correspondant.service %}
|
||||
Service : {{ correspondant.service }}<br>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('entreprises.fiche_entreprise', id=correspondant.entreprise_id )}}">lien vers l'entreprise</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -2,7 +2,7 @@
|
||||
<nav class="nav-entreprise">
|
||||
<ul>
|
||||
<li><a href="{{ url_for('entreprises.index') }}">Entreprises</a></li>
|
||||
<li><a href="{{ url_for('entreprises.contacts') }}">Contacts</a></li>
|
||||
<li><a href="{{ url_for('entreprises.correspondants') }}">Correspondants</a></li>
|
||||
<li><a href="{{ url_for('entreprises.offres_recues') }}">Offres reçues</a></li>
|
||||
{% if current_user.has_permission(current_user.Permission.RelationsEntreprisesValidate, None) %}
|
||||
<li><a href="{{ url_for('entreprises.validation') }}">Entreprises à valider</a></li>
|
||||
|
@ -10,12 +10,22 @@
|
||||
{% for offre in offres_recues %}
|
||||
<div class="offre offre-recue">
|
||||
<div>
|
||||
Envoyé le {{ offre[0].date_envoi.strftime('%d %B %Y à %H:%M') }} par {{ offre[0].sender_id|get_nomcomplet_by_id }}<br>
|
||||
Envoyé le {{ offre[0].date_envoi.strftime('%d/%m/%Y') }} à {{ offre[0].date_envoi.strftime('%Hh%M') }} par {{ offre[0].sender_id|get_nomcomplet_by_id }}<br>
|
||||
Intitulé : {{ offre[1].intitule }}<br>
|
||||
Description : {{ offre[1].description }}<br>
|
||||
Type de l'offre : {{ offre[1].type_offre }}<br>
|
||||
Missions : {{ offre[1].missions }}<br>
|
||||
Durée : {{ offre[1].duree }}<br>
|
||||
|
||||
{% if offre[1].correspondant_id %}
|
||||
Contacté {{ offre[3].nom }} {{ offre[3].prenom }}
|
||||
{% if offre[3].mail and offre[3].telephone %}
|
||||
({{ offre[3].mail }} - {{ offre[3].telephone }})<br>
|
||||
{% else %}
|
||||
({{ offre[3].mail }}{{offre[3].telephone}})<br>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ url_for('entreprises.fiche_entreprise', id=offre[1].entreprise_id) }}">lien vers l'entreprise</a><br>
|
||||
|
||||
{% for fichier in offre[2] %}
|
||||
|
@ -1,6 +1,5 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
{% extends 'base.html' %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block app_content %}
|
||||
|
||||
|
@ -45,11 +45,13 @@ from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.models.formsemestre import FormSemestreUEComputationExpr
|
||||
from app.models.modules import Module
|
||||
from app.models.ues import UniteEns
|
||||
|
||||
from app import api
|
||||
from app import db
|
||||
from app import models
|
||||
from app.models import ScolarNews
|
||||
from app.auth.models import User
|
||||
from app.but import bulletin_but
|
||||
from app.decorators import (
|
||||
@ -86,7 +88,6 @@ from app.scodoc import sco_archives
|
||||
from app.scodoc import sco_bulletins
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_compute_moy
|
||||
from app.scodoc import sco_cost_formation
|
||||
from app.scodoc import sco_debouche
|
||||
from app.scodoc import sco_edit_apc
|
||||
@ -103,6 +104,7 @@ from app.scodoc import sco_evaluation_edit
|
||||
from app.scodoc import sco_evaluation_recap
|
||||
from app.scodoc import sco_export_results
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_formation_recap
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_custommenu
|
||||
from app.scodoc import sco_formsemestre_edit
|
||||
@ -288,10 +290,9 @@ def formsemestre_bulletinetud(
|
||||
code_ine=None,
|
||||
):
|
||||
format = format or "html"
|
||||
if not formsemestre_id:
|
||||
flask.abort(404, "argument manquant: formsemestre_id")
|
||||
|
||||
if not isinstance(formsemestre_id, int):
|
||||
raise ScoInvalidIdType("formsemestre_id must be an integer !")
|
||||
raise ValueError("formsemestre_id must be an integer !")
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
if etudid:
|
||||
etud = models.Identite.query.get_or_404(etudid)
|
||||
@ -472,6 +473,7 @@ sco_publish(
|
||||
"/edit_ue_set_code_apogee",
|
||||
sco_edit_ue.edit_ue_set_code_apogee,
|
||||
Permission.ScoChangeFormation,
|
||||
methods=["POST"],
|
||||
)
|
||||
sco_publish(
|
||||
"/formsemestre_edit_uecoefs",
|
||||
@ -479,8 +481,20 @@ sco_publish(
|
||||
Permission.ScoView,
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/formation_table_recap")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def formation_table_recap(formation_id, format="html"):
|
||||
return sco_formation_recap.formation_table_recap(formation_id, format="html")
|
||||
|
||||
|
||||
sco_publish(
|
||||
"/formation_table_recap", sco_edit_ue.formation_table_recap, Permission.ScoView
|
||||
"/export_recap_formations_annee_scolaire",
|
||||
sco_formation_recap.export_recap_formations_annee_scolaire,
|
||||
Permission.ScoView,
|
||||
)
|
||||
sco_publish(
|
||||
"/formation_add_malus_modules",
|
||||
@ -571,6 +585,20 @@ def index_html():
|
||||
</li>
|
||||
<li><a class="stdlink" href="formation_import_xml_form">Importer une formation (xml)</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for("notes.export_recap_formations_annee_scolaire",
|
||||
scodoc_dept=g.scodoc_dept, annee_scolaire=scu.AnneeScolaire()-1)
|
||||
}">exporter les formations de l'année scolaire
|
||||
{scu.AnneeScolaire()-1} - {scu.AnneeScolaire()}
|
||||
</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for("notes.export_recap_formations_annee_scolaire",
|
||||
scodoc_dept=g.scodoc_dept, annee_scolaire=scu.AnneeScolaire())
|
||||
}">exporter les formations de l'année scolaire
|
||||
{scu.AnneeScolaire()} - {scu.AnneeScolaire()+1}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Référentiels de compétences</h3>
|
||||
<ul>
|
||||
@ -2410,6 +2438,125 @@ sco_publish(
|
||||
Permission.ScoEditApo,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/formsemestre_set_apo_etapes", methods=["POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoEditApo)
|
||||
def formsemestre_set_apo_etapes():
|
||||
"""Change les codes étapes du semestre indiqué.
|
||||
Args: oid=formsemestre_id, value=chaine "V1RT, V1RT2", codes séparés par des virgules
|
||||
"""
|
||||
formsemestre_id = int(request.form.get("oid"))
|
||||
etapes_apo_str = request.form.get("value")
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
current_etapes = {e.etape_apo for e in formsemestre.etapes}
|
||||
new_etapes = {s.strip() for s in etapes_apo_str.split(",")}
|
||||
|
||||
if new_etapes != current_etapes:
|
||||
formsemestre.etapes = []
|
||||
for etape_apo in new_etapes:
|
||||
etape = models.FormSemestreEtape(
|
||||
formsemestre_id=formsemestre_id, etape_apo=etape_apo
|
||||
)
|
||||
formsemestre.etapes.append(etape)
|
||||
db.session.add(formsemestre)
|
||||
db.session.commit()
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_APO,
|
||||
text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})",
|
||||
max_frequency=10 * 60,
|
||||
)
|
||||
return ("", 204)
|
||||
|
||||
|
||||
@bp.route("/formsemestre_set_elt_annee_apo", methods=["POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoEditApo)
|
||||
def formsemestre_set_elt_annee_apo():
|
||||
"""Change les codes étapes du semestre indiqué.
|
||||
Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
||||
"""
|
||||
oid = int(request.form.get("oid"))
|
||||
value = (request.form.get("value") or "").strip()
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid)
|
||||
if value != formsemestre.elt_annee_apo:
|
||||
formsemestre.elt_annee_apo = value
|
||||
db.session.add(formsemestre)
|
||||
db.session.commit()
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_APO,
|
||||
text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})",
|
||||
max_frequency=10 * 60,
|
||||
)
|
||||
return ("", 204)
|
||||
|
||||
|
||||
@bp.route("/formsemestre_set_elt_sem_apo", methods=["POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoEditApo)
|
||||
def formsemestre_set_elt_sem_apo():
|
||||
"""Change les codes étapes du semestre indiqué.
|
||||
Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
||||
"""
|
||||
oid = int(request.form.get("oid"))
|
||||
value = (request.form.get("value") or "").strip()
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid)
|
||||
if value != formsemestre.elt_sem_apo:
|
||||
formsemestre.elt_sem_apo = value
|
||||
db.session.add(formsemestre)
|
||||
db.session.commit()
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_APO,
|
||||
text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})",
|
||||
max_frequency=10 * 60,
|
||||
)
|
||||
return ("", 204)
|
||||
|
||||
|
||||
@bp.route("/ue_set_apo", methods=["POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoEditApo)
|
||||
def ue_set_apo():
|
||||
"""Change le code APO de l'UE
|
||||
Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE)
|
||||
"""
|
||||
ue_id = int(request.form.get("oid"))
|
||||
code_apo = (request.form.get("value") or "").strip()
|
||||
ue = UniteEns.query.get_or_404(ue_id)
|
||||
if code_apo != ue.code_apogee:
|
||||
ue.code_apogee = code_apo
|
||||
db.session.add(ue)
|
||||
db.session.commit()
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_FORM,
|
||||
text=f"Modification code Apogée d'UE dans la formation {ue.formation.titre} ({ue.formation.acronyme})",
|
||||
max_frequency=10 * 60,
|
||||
)
|
||||
return ("", 204)
|
||||
|
||||
|
||||
@bp.route("/module_set_apo", methods=["POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoEditApo)
|
||||
def module_set_apo():
|
||||
"""Change le code APO du module
|
||||
Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE)
|
||||
"""
|
||||
oid = int(request.form.get("oid"))
|
||||
code_apo = (request.form.get("value") or "").strip()
|
||||
mod = Module.query.get_or_404(oid)
|
||||
if code_apo != mod.code_apogee:
|
||||
mod.code_apogee = code_apo
|
||||
db.session.add(mod)
|
||||
db.session.commit()
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_FORM,
|
||||
text=f"Modification code Apogée d'UE dans la formation {mod.formation.titre} ({mod.formation.acronyme})",
|
||||
max_frequency=10 * 60,
|
||||
)
|
||||
return ("", 204)
|
||||
|
||||
|
||||
# sco_semset
|
||||
sco_publish("/semset_page", sco_semset.semset_page, Permission.ScoEditApo)
|
||||
sco_publish(
|
||||
|
@ -325,7 +325,7 @@ def showEtudLog(etudid, format="html"):
|
||||
# ---------- PAGE ACCUEIL (listes) --------------
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
@bp.route("/", alias=True)
|
||||
@bp.route("/index_html")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
|
@ -0,0 +1,158 @@
|
||||
"""tables module gestions relations entreprises suite
|
||||
|
||||
Revision ID: e97b2a10f86c
|
||||
Revises: af05f03b81be
|
||||
Create Date: 2022-04-19 17:39:08.197835
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "e97b2a10f86c"
|
||||
down_revision = "af05f03b81be"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"are_correspondants",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("entreprise_id", sa.Integer(), nullable=True),
|
||||
sa.Column("nom", sa.Text(), nullable=True),
|
||||
sa.Column("prenom", sa.Text(), nullable=True),
|
||||
sa.Column("telephone", sa.Text(), nullable=True),
|
||||
sa.Column("mail", sa.Text(), nullable=True),
|
||||
sa.Column("poste", sa.Text(), nullable=True),
|
||||
sa.Column("service", sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["entreprise_id"], ["are_entreprises.id"], ondelete="cascade"
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"are_stages_apprentissages",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("entreprise_id", sa.Integer(), nullable=True),
|
||||
sa.Column("etudid", sa.Integer(), nullable=True),
|
||||
sa.Column("type_offre", sa.Text(), nullable=True),
|
||||
sa.Column("date_debut", sa.Date(), nullable=True),
|
||||
sa.Column("date_fin", sa.Date(), nullable=True),
|
||||
sa.Column("formation_text", sa.Text(), nullable=True),
|
||||
sa.Column("formation_scodoc", sa.Integer(), nullable=True),
|
||||
sa.Column("notes", sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["entreprise_id"], ["are_entreprises.id"], ondelete="cascade"
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.drop_table("are_etudiants")
|
||||
op.add_column(
|
||||
"are_contacts", sa.Column("date", sa.DateTime(timezone=True), nullable=True)
|
||||
)
|
||||
op.add_column("are_contacts", sa.Column("user", sa.Integer(), nullable=True))
|
||||
op.add_column("are_contacts", sa.Column("entreprise", sa.Integer(), nullable=True))
|
||||
op.add_column("are_contacts", sa.Column("notes", sa.Text(), nullable=True))
|
||||
op.drop_constraint(
|
||||
"are_contacts_entreprise_id_fkey", "are_contacts", type_="foreignkey"
|
||||
)
|
||||
op.create_foreign_key(
|
||||
None,
|
||||
"are_contacts",
|
||||
"are_entreprises",
|
||||
["entreprise"],
|
||||
["id"],
|
||||
ondelete="cascade",
|
||||
)
|
||||
op.create_foreign_key(
|
||||
None, "are_contacts", "user", ["user"], ["id"], ondelete="cascade"
|
||||
)
|
||||
op.drop_column("are_contacts", "nom")
|
||||
op.drop_column("are_contacts", "telephone")
|
||||
op.drop_column("are_contacts", "service")
|
||||
op.drop_column("are_contacts", "entreprise_id")
|
||||
op.drop_column("are_contacts", "mail")
|
||||
op.drop_column("are_contacts", "poste")
|
||||
op.drop_column("are_contacts", "prenom")
|
||||
op.add_column(
|
||||
"are_offres", sa.Column("correspondant_id", sa.Integer(), nullable=True)
|
||||
)
|
||||
op.create_foreign_key(
|
||||
None,
|
||||
"are_offres",
|
||||
"are_correspondants",
|
||||
["correspondant_id"],
|
||||
["id"],
|
||||
ondelete="cascade",
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, "are_offres", type_="foreignkey")
|
||||
op.drop_column("are_offres", "correspondant_id")
|
||||
op.add_column(
|
||||
"are_contacts",
|
||||
sa.Column("prenom", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"are_contacts",
|
||||
sa.Column("poste", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"are_contacts", sa.Column("mail", sa.TEXT(), autoincrement=False, nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
"are_contacts",
|
||||
sa.Column("entreprise_id", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"are_contacts",
|
||||
sa.Column("service", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"are_contacts",
|
||||
sa.Column("telephone", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"are_contacts", sa.Column("nom", sa.TEXT(), autoincrement=False, nullable=True)
|
||||
)
|
||||
op.drop_constraint(None, "are_contacts", type_="foreignkey")
|
||||
op.drop_constraint(None, "are_contacts", type_="foreignkey")
|
||||
op.create_foreign_key(
|
||||
"are_contacts_entreprise_id_fkey",
|
||||
"are_contacts",
|
||||
"are_entreprises",
|
||||
["entreprise_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
op.drop_column("are_contacts", "notes")
|
||||
op.drop_column("are_contacts", "entreprise")
|
||||
op.drop_column("are_contacts", "user")
|
||||
op.drop_column("are_contacts", "date")
|
||||
op.create_table(
|
||||
"are_etudiants",
|
||||
sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False),
|
||||
sa.Column("entreprise_id", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.Column("etudid", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.Column("type_offre", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("date_debut", sa.DATE(), autoincrement=False, nullable=True),
|
||||
sa.Column("date_fin", sa.DATE(), autoincrement=False, nullable=True),
|
||||
sa.Column("formation_text", sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.Column("formation_scodoc", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["entreprise_id"],
|
||||
["are_entreprises.id"],
|
||||
name="are_etudiants_entreprise_id_fkey",
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name="are_etudiants_pkey"),
|
||||
)
|
||||
op.drop_table("are_stages_apprentissages")
|
||||
op.drop_table("are_correspondants")
|
||||
# ### end Alembic commands ###
|
@ -1,74 +1,87 @@
|
||||
alembic==1.7.5
|
||||
alembic==1.7.7
|
||||
astroid==2.11.2
|
||||
async-timeout==4.0.2
|
||||
attrs==21.4.0
|
||||
Babel==2.9.1
|
||||
black==22.3.0
|
||||
blinker==1.4
|
||||
certifi==2021.10.8
|
||||
cffi==1.15.0
|
||||
chardet==4.0.0
|
||||
charset-normalizer==2.0.9
|
||||
click==8.0.3
|
||||
charset-normalizer==2.0.12
|
||||
click==8.1.2
|
||||
cracklib==2.9.3
|
||||
cryptography==36.0.1
|
||||
cryptography==36.0.2
|
||||
Deprecated==1.2.13
|
||||
dnspython==2.1.0
|
||||
dill==0.3.4
|
||||
dominate==2.6.0
|
||||
email-validator==1.1.3
|
||||
et-xmlfile==1.1.0
|
||||
Flask==2.0.2
|
||||
Flask==2.1.1
|
||||
Flask-Babel==2.0.0
|
||||
Flask-Bootstrap==3.3.7.1
|
||||
Flask-Caching==1.10.1
|
||||
Flask-HTTPAuth==4.5.0
|
||||
Flask-Login==0.5.0
|
||||
Flask-Login==0.6.0
|
||||
Flask-Mail==0.9.1
|
||||
Flask-Migrate==3.1.0
|
||||
Flask-Moment==1.0.2
|
||||
Flask-SQLAlchemy==2.5.1
|
||||
Flask-WTF==1.0.0
|
||||
Flask-WTF==1.0.1
|
||||
greenlet==1.1.2
|
||||
gunicorn==20.1.0
|
||||
icalendar==4.0.9
|
||||
idna==3.3
|
||||
importlib-metadata==4.11.3
|
||||
iniconfig==1.1.1
|
||||
isort==5.10.1
|
||||
itsdangerous==2.0.1
|
||||
Jinja2==3.0.3
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.1
|
||||
lazy-object-proxy==1.7.1
|
||||
lxml==4.8.0
|
||||
Mako==1.1.6
|
||||
MarkupSafe==2.0.1
|
||||
mccabe==0.6.1
|
||||
numpy==1.22.0
|
||||
Mako==1.2.0
|
||||
MarkupSafe==2.1.1
|
||||
mccabe==0.7.0
|
||||
mypy==0.942
|
||||
mypy-extensions==0.4.3
|
||||
numpy==1.22.3
|
||||
openpyxl==3.0.9
|
||||
packaging==21.3
|
||||
pandas==1.3.5
|
||||
Pillow==8.4.0
|
||||
pandas==1.4.2
|
||||
pathspec==0.9.0
|
||||
Pillow==9.1.0
|
||||
pkg_resources==0.0.0
|
||||
platformdirs==2.5.1
|
||||
pluggy==1.0.0
|
||||
psycopg2==2.9.3
|
||||
py==1.11.0
|
||||
pycparser==2.21
|
||||
pydot==1.4.2
|
||||
PyJWT==2.3.0
|
||||
pyOpenSSL==21.0.0
|
||||
pyparsing==3.0.6
|
||||
pytest==6.2.5
|
||||
pylint==2.13.5
|
||||
pylint-flask==0.6
|
||||
pylint-flask-sqlalchemy==0.2.0
|
||||
pylint-plugin-utils==0.7
|
||||
pyOpenSSL==22.0.0
|
||||
pyparsing==3.0.8
|
||||
pytest==7.1.1
|
||||
python-dateutil==2.8.2
|
||||
python-docx==0.8.11
|
||||
python-dotenv==0.19.2
|
||||
python-dotenv==0.20.0
|
||||
python-editor==1.0.4
|
||||
pytz==2021.3
|
||||
redis==4.1.0
|
||||
reportlab==3.6.5
|
||||
requests==2.26.0
|
||||
pytz==2022.1
|
||||
redis==4.2.2
|
||||
reportlab==3.6.9
|
||||
requests==2.27.1
|
||||
rq==1.10.1
|
||||
six==1.16.0
|
||||
SQLAlchemy==1.4.29
|
||||
SQLAlchemy==1.4.35
|
||||
toml==0.10.2
|
||||
tornado==6.1
|
||||
typing-extensions==4.0.1
|
||||
urllib3==1.26.7
|
||||
typing_extensions==4.1.1
|
||||
urllib3==1.26.9
|
||||
visitor==0.1.3
|
||||
Werkzeug==2.0.2
|
||||
wrapt==1.13.3
|
||||
Werkzeug==2.1.1
|
||||
wrapt==1.14.0
|
||||
WTForms==3.0.1
|
||||
zipp==3.8.0
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.2.2"
|
||||
SCOVERSION = "9.2.11"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
@ -204,7 +204,7 @@ class ScoFake(object):
|
||||
abbrev=None,
|
||||
ects=None,
|
||||
code_apogee=None,
|
||||
module_type=None,
|
||||
module_type=scu.ModuleType.STANDARD,
|
||||
) -> int:
|
||||
oid = sco_edit_module.do_module_create(locals())
|
||||
oids = sco_edit_module.module_list(args={"module_id": oid})
|
||||
|
@ -2,20 +2,15 @@
|
||||
Test calcul moyennes UE
|
||||
"""
|
||||
import numpy as np
|
||||
from numpy.lib.nanfunctions import _nanquantile_1d
|
||||
import pandas as pd
|
||||
from tests.unit import setup
|
||||
|
||||
from tests.unit import sco_fake_gen
|
||||
from app import db
|
||||
from app import models
|
||||
from app.comp import moy_mod
|
||||
from app.comp import moy_ue
|
||||
from app.comp import inscr_mod
|
||||
from app.models import FormSemestre, Evaluation, ModuleImplInscription
|
||||
from app.models.etudiants import Identite
|
||||
from app.scodoc import sco_codes_parcours, sco_saisie_notes
|
||||
from app.scodoc.sco_utils import NOTES_ATTENTE, NOTES_NEUTRALISE
|
||||
from app.scodoc import sco_saisie_notes
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
from app.scodoc.sco_utils import NOTES_NEUTRALISE
|
||||
from app.scodoc import sco_exceptions
|
||||
|
||||
|
||||
@ -69,9 +64,20 @@ def test_ue_moy(test_client):
|
||||
_ = sco_saisie_notes.notes_add(G.default_user, evaluation2.id, [(etudid, n2)])
|
||||
# Recalcul des moyennes
|
||||
sem_cube, _, _ = moy_ue.notes_sem_load_cube(formsemestre)
|
||||
# Masque de tous les modules _sauf_ les bonus (sport)
|
||||
modimpl_mask = [
|
||||
modimpl.module.ue.type != UE_SPORT
|
||||
for modimpl in formsemestre.modimpls_sorted
|
||||
]
|
||||
etuds = formsemestre.etuds.all()
|
||||
etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df
|
||||
sem_cube,
|
||||
etuds,
|
||||
modimpls,
|
||||
ues,
|
||||
modimpl_inscr_df,
|
||||
modimpl_coefs_df,
|
||||
modimpl_mask,
|
||||
)
|
||||
return etud_moy_ue
|
||||
|
||||
@ -113,8 +119,11 @@ def test_ue_moy(test_client):
|
||||
# Recalcule les notes:
|
||||
sem_cube, _, _ = moy_ue.notes_sem_load_cube(formsemestre)
|
||||
etuds = formsemestre.etuds.all()
|
||||
modimpl_mask = [
|
||||
modimpl.module.ue.type != UE_SPORT for modimpl in formsemestre.modimpls_sorted
|
||||
]
|
||||
etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df
|
||||
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df, modimpl_mask
|
||||
)
|
||||
assert etud_moy_ue[ue1.id][etudid] == n1
|
||||
assert etud_moy_ue[ue2.id][etudid] == n1
|
||||
|
Loading…
Reference in New Issue
Block a user