Merge branch 'entreprises' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises
This commit is contained in:
commit
0140c404ea
@ -450,7 +450,7 @@ def etudiant_bulletin_semestre(
|
|||||||
|
|
||||||
app.set_sco_dept(dept.acronym)
|
app.set_sco_dept(dept.acronym)
|
||||||
return sco_bulletins.get_formsemestre_bulletin_etud_json(
|
return sco_bulletins.get_formsemestre_bulletin_etud_json(
|
||||||
formsemestre, etud, version
|
formsemestre, etud, version=version
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ from flask import g, url_for
|
|||||||
from app import db
|
from app import db
|
||||||
from app import log
|
from app import log
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.comp import res_sem
|
from app.comp import inscr_mod, res_sem
|
||||||
from app.models import formsemestre
|
from app.models import formsemestre
|
||||||
|
|
||||||
from app.models.but_refcomp import (
|
from app.models.but_refcomp import (
|
||||||
@ -219,15 +219,16 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
"le 1er semestre de l'année scolaire considérée (S1, S3, S5)"
|
"le 1er semestre de l'année scolaire considérée (S1, S3, S5)"
|
||||||
self.formsemestre_pair = formsemestre_pair
|
self.formsemestre_pair = formsemestre_pair
|
||||||
"le second formsemestre de la même année scolaire (S2, S4, S6)"
|
"le second formsemestre de la même année scolaire (S2, S4, S6)"
|
||||||
self.annee_but = (
|
formsemestre_last = formsemestre_pair or formsemestre_impair
|
||||||
(formsemestre_impair.semestre_id + 1) // 2
|
"le formsemestre le plus avancé dans cette année"
|
||||||
if formsemestre_impair
|
|
||||||
else (formsemestre_pair.semestre_id + 1) // 2
|
self.annee_but = (formsemestre_last.semestre_id + 1) // 2
|
||||||
)
|
|
||||||
"le rang de l'année dans le BUT: 1, 2, 3"
|
"le rang de l'année dans le BUT: 1, 2, 3"
|
||||||
assert self.annee_but in (1, 2, 3)
|
assert self.annee_but in (1, 2, 3)
|
||||||
self.rcues_annee = []
|
self.rcues_annee = []
|
||||||
"RCUEs de l'année"
|
"RCUEs de l'année"
|
||||||
|
self.inscription_etat = etud.inscription_etat(formsemestre_last.id)
|
||||||
|
|
||||||
if self.formsemestre_impair is not None:
|
if self.formsemestre_impair is not None:
|
||||||
self.validation = ApcValidationAnnee.query.filter_by(
|
self.validation = ApcValidationAnnee.query.filter_by(
|
||||||
etudid=self.etud.id,
|
etudid=self.etud.id,
|
||||||
@ -255,13 +256,17 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
|
|
||||||
self.ues_impair, self.ues_pair = self.compute_ues_annee() # pylint: disable=all
|
self.ues_impair, self.ues_pair = self.compute_ues_annee() # pylint: disable=all
|
||||||
self.decisions_ues = {
|
self.decisions_ues = {
|
||||||
ue.id: DecisionsProposeesUE(etud, formsemestre_impair, ue)
|
ue.id: DecisionsProposeesUE(
|
||||||
|
etud, formsemestre_impair, ue, self.inscription_etat
|
||||||
|
)
|
||||||
for ue in self.ues_impair
|
for ue in self.ues_impair
|
||||||
}
|
}
|
||||||
"{ue_id : DecisionsProposeesUE} pour toutes les UE de l'année"
|
"{ue_id : DecisionsProposeesUE} pour toutes les UE de l'année"
|
||||||
self.decisions_ues.update(
|
self.decisions_ues.update(
|
||||||
{
|
{
|
||||||
ue.id: DecisionsProposeesUE(etud, formsemestre_pair, ue)
|
ue.id: DecisionsProposeesUE(
|
||||||
|
etud, formsemestre_pair, ue, self.inscription_etat
|
||||||
|
)
|
||||||
for ue in self.ues_pair
|
for ue in self.ues_pair
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -291,8 +296,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
[rcue for rcue in rcues_avec_niveau if not rcue.est_suffisant()]
|
[rcue for rcue in rcues_avec_niveau if not rcue.est_suffisant()]
|
||||||
)
|
)
|
||||||
"le nb de comp. sous la barre de 8/20"
|
"le nb de comp. sous la barre de 8/20"
|
||||||
# année ADM si toutes RCUE validées (sinon PASD)
|
# année ADM si toutes RCUE validées (sinon PASD) et non DEM ou DEF
|
||||||
self.admis = self.nb_validables == self.nb_competences
|
self.admis = (self.nb_validables == self.nb_competences) and (
|
||||||
|
self.inscription_etat == scu.INSCRIT
|
||||||
|
)
|
||||||
"vrai si l'année est réussie, tous niveaux validables"
|
"vrai si l'année est réussie, tous niveaux validables"
|
||||||
self.valide_moitie_rcue = self.nb_validables > (self.nb_competences // 2)
|
self.valide_moitie_rcue = self.nb_validables > (self.nb_competences // 2)
|
||||||
# Peut passer si plus de la moitié validables et tous > 8
|
# Peut passer si plus de la moitié validables et tous > 8
|
||||||
@ -310,6 +317,19 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
if self.admis:
|
if self.admis:
|
||||||
self.codes = [sco_codes.ADM] + self.codes
|
self.codes = [sco_codes.ADM] + self.codes
|
||||||
self.explanation = expl_rcues
|
self.explanation = expl_rcues
|
||||||
|
elif self.inscription_etat != scu.INSCRIT:
|
||||||
|
self.codes = [
|
||||||
|
sco_codes.DEM
|
||||||
|
if self.inscription_etat == scu.DEMISSION
|
||||||
|
else sco_codes.DEF,
|
||||||
|
# propose aussi d'autres codes, au cas où...
|
||||||
|
sco_codes.DEM
|
||||||
|
if self.inscription_etat != scu.DEMISSION
|
||||||
|
else sco_codes.DEF,
|
||||||
|
sco_codes.ABAN,
|
||||||
|
sco_codes.ABL,
|
||||||
|
sco_codes.EXCLU,
|
||||||
|
]
|
||||||
elif self.passage_de_droit:
|
elif self.passage_de_droit:
|
||||||
self.codes = [sco_codes.PASD, sco_codes.ADJ] + self.codes
|
self.codes = [sco_codes.PASD, sco_codes.ADJ] + self.codes
|
||||||
self.explanation = expl_rcues
|
self.explanation = expl_rcues
|
||||||
@ -482,6 +502,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
ue_impair,
|
ue_impair,
|
||||||
self.formsemestre_pair,
|
self.formsemestre_pair,
|
||||||
ue_pair,
|
ue_pair,
|
||||||
|
self.inscription_etat,
|
||||||
)
|
)
|
||||||
ues_impair_sans_rcue.discard(ue_impair.id)
|
ues_impair_sans_rcue.discard(ue_impair.id)
|
||||||
break
|
break
|
||||||
@ -509,7 +530,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
rcue = rc
|
rcue = rc
|
||||||
break
|
break
|
||||||
if rcue is not None:
|
if rcue is not None:
|
||||||
dec_rcue = DecisionsProposeesRCUE(self, rcue)
|
dec_rcue = DecisionsProposeesRCUE(self, rcue, self.inscription_etat)
|
||||||
rc_niveaux.append((dec_rcue, niveau.id))
|
rc_niveaux.append((dec_rcue, niveau.id))
|
||||||
# prévient les UE concernées :-)
|
# prévient les UE concernées :-)
|
||||||
self.decisions_ues[dec_rcue.rcue.ue_1.id].set_rcue(dec_rcue.rcue)
|
self.decisions_ues[dec_rcue.rcue.ue_1.id].set_rcue(dec_rcue.rcue)
|
||||||
@ -724,14 +745,26 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, dec_prop_annee: DecisionsProposeesAnnee, rcue: RegroupementCoherentUE
|
self,
|
||||||
|
dec_prop_annee: DecisionsProposeesAnnee,
|
||||||
|
rcue: RegroupementCoherentUE,
|
||||||
|
inscription_etat: str = scu.INSCRIT,
|
||||||
):
|
):
|
||||||
super().__init__(etud=dec_prop_annee.etud)
|
super().__init__(etud=dec_prop_annee.etud)
|
||||||
self.rcue = rcue
|
self.rcue = rcue
|
||||||
if rcue is None: # RCUE non dispo, eg un seul semestre
|
if rcue is None: # RCUE non dispo, eg un seul semestre
|
||||||
self.codes = []
|
self.codes = []
|
||||||
return
|
return
|
||||||
|
self.inscription_etat = inscription_etat
|
||||||
|
"inscription: I, DEM, DEF"
|
||||||
self.parcour = dec_prop_annee.parcour
|
self.parcour = dec_prop_annee.parcour
|
||||||
|
if inscription_etat != scu.INSCRIT:
|
||||||
|
self.validation = None # cache toute validation
|
||||||
|
self.explanation = "non incrit (dem. ou déf.)"
|
||||||
|
self.codes = [
|
||||||
|
sco_codes.DEM if inscription_etat == scu.DEMISSION else sco_codes.DEF
|
||||||
|
]
|
||||||
|
return
|
||||||
self.validation = rcue.query_validations().first()
|
self.validation = rcue.query_validations().first()
|
||||||
if self.validation is not None:
|
if self.validation is not None:
|
||||||
self.code_valide = self.validation.code
|
self.code_valide = self.validation.code
|
||||||
@ -828,12 +861,27 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
etud: Identite,
|
etud: Identite,
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
ue: UniteEns,
|
ue: UniteEns,
|
||||||
|
inscription_etat: str = scu.INSCRIT,
|
||||||
):
|
):
|
||||||
super().__init__(etud=etud)
|
super().__init__(etud=etud)
|
||||||
self.formsemestre = formsemestre
|
self.formsemestre = formsemestre
|
||||||
self.ue: UniteEns = ue
|
self.ue: UniteEns = ue
|
||||||
self.rcue: RegroupementCoherentUE = None
|
self.rcue: RegroupementCoherentUE = None
|
||||||
"Le rcu auquel est rattaché cette UE, ou None"
|
"Le rcu auquel est rattaché cette UE, ou None"
|
||||||
|
self.inscription_etat = inscription_etat
|
||||||
|
"inscription: I, DEM, DEF"
|
||||||
|
if ue.type == sco_codes.UE_SPORT:
|
||||||
|
self.explanation = "UE bonus, pas de décision de jury"
|
||||||
|
self.codes = [] # aucun code proposé
|
||||||
|
return
|
||||||
|
if inscription_etat != scu.INSCRIT:
|
||||||
|
self.validation = None # cache toute validation
|
||||||
|
self.explanation = "non incrit (dem. ou déf.)"
|
||||||
|
self.codes = [
|
||||||
|
sco_codes.DEM if inscription_etat == scu.DEMISSION else sco_codes.DEF
|
||||||
|
]
|
||||||
|
self.moy_ue = "-"
|
||||||
|
return
|
||||||
# Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non)
|
# Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non)
|
||||||
# mais ici on a restreint au formsemestre donc une seule (prend la première)
|
# mais ici on a restreint au formsemestre donc une seule (prend la première)
|
||||||
self.validation = ScolarFormSemestreValidation.query.filter_by(
|
self.validation = ScolarFormSemestreValidation.query.filter_by(
|
||||||
@ -841,10 +889,6 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
).first()
|
).first()
|
||||||
if self.validation is not None:
|
if self.validation is not None:
|
||||||
self.code_valide = self.validation.code
|
self.code_valide = self.validation.code
|
||||||
if ue.type == sco_codes.UE_SPORT:
|
|
||||||
self.explanation = "UE bonus, pas de décision de jury"
|
|
||||||
self.codes = [] # aucun code proposé
|
|
||||||
return
|
|
||||||
|
|
||||||
# Moyenne de l'UE ?
|
# Moyenne de l'UE ?
|
||||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||||
@ -863,6 +907,8 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
|
|
||||||
def compute_codes(self):
|
def compute_codes(self):
|
||||||
"""Calcul des .codes attribuables et de l'explanation associée"""
|
"""Calcul des .codes attribuables et de l'explanation associée"""
|
||||||
|
if self.inscription_etat != scu.INSCRIT:
|
||||||
|
return
|
||||||
if self.moy_ue > (sco_codes.ParcoursBUT.BARRE_MOY - sco_codes.NOTES_TOLERANCE):
|
if self.moy_ue > (sco_codes.ParcoursBUT.BARRE_MOY - sco_codes.NOTES_TOLERANCE):
|
||||||
self.codes.insert(0, sco_codes.ADM)
|
self.codes.insert(0, sco_codes.ADM)
|
||||||
self.explanation = (f"Moyenne >= {sco_codes.ParcoursBUT.BARRE_MOY}/20",)
|
self.explanation = (f"Moyenne >= {sco_codes.ParcoursBUT.BARRE_MOY}/20",)
|
||||||
|
@ -409,7 +409,9 @@ def get_table_jury_but(
|
|||||||
)}" class="stdlink">
|
)}" class="stdlink">
|
||||||
{"voir" if read_only else ("modif." if deca.code_valide else "saisie")}
|
{"voir" if read_only else ("modif." if deca.code_valide else "saisie")}
|
||||||
décision</a>
|
décision</a>
|
||||||
""",
|
"""
|
||||||
|
if deca.inscription_etat == scu.INSCRIT
|
||||||
|
else deca.inscription_etat,
|
||||||
"col_lien_saisie_but",
|
"col_lien_saisie_but",
|
||||||
)
|
)
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
|
@ -650,7 +650,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
elif nb_ues_validables < len(ues_sans_bonus):
|
elif nb_ues_validables < len(ues_sans_bonus):
|
||||||
row["_ues_validables_class"] += " moy_inf"
|
row["_ues_validables_class"] += " moy_inf"
|
||||||
row["_ues_validables_order"] = nb_ues_validables # pour tri
|
row["_ues_validables_order"] = nb_ues_validables # pour tri
|
||||||
if mode_jury:
|
if mode_jury and self.validations:
|
||||||
dec_sem = self.validations.decisions_jury.get(etudid)
|
dec_sem = self.validations.decisions_jury.get(etudid)
|
||||||
jury_code_sem = dec_sem["code"] if dec_sem else ""
|
jury_code_sem = dec_sem["code"] if dec_sem else ""
|
||||||
idx = add_cell(
|
idx = add_cell(
|
||||||
|
@ -1419,7 +1419,7 @@ def get_import_donnees_file_sample():
|
|||||||
@permission_required(Permission.RelationsEntreprisesExport)
|
@permission_required(Permission.RelationsEntreprisesExport)
|
||||||
def import_donnees():
|
def import_donnees():
|
||||||
"""
|
"""
|
||||||
Permet d'importer des entreprises a l'aide d'un fichier excel (.xlsx)
|
Permet d'importer des entreprises à partir d'un fichier excel (.xlsx)
|
||||||
"""
|
"""
|
||||||
form = ImportForm()
|
form = ImportForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
@ -1428,7 +1428,7 @@ def import_donnees():
|
|||||||
Config.SCODOC_VAR_DIR, "tmp", secure_filename(file.filename)
|
Config.SCODOC_VAR_DIR, "tmp", secure_filename(file.filename)
|
||||||
)
|
)
|
||||||
file.save(file_path)
|
file.save(file_path)
|
||||||
diag, lm = sco_excel.excel_file_to_list_are(file_path)
|
diag, lm = sco_excel.excel_workbook_to_list(file_path)
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
if lm is None or len(lm) < 2:
|
if lm is None or len(lm) < 2:
|
||||||
flash("Veuillez utilisez la feuille excel à remplir")
|
flash("Veuillez utilisez la feuille excel à remplir")
|
||||||
|
@ -16,6 +16,7 @@ from app.models.ues import UniteEns
|
|||||||
from app.models.formations import Formation
|
from app.models.formations import Formation
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
from app.scodoc import sco_codes_parcours as sco_codes
|
from app.scodoc import sco_codes_parcours as sco_codes
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
class ApcValidationRCUE(db.Model):
|
class ApcValidationRCUE(db.Model):
|
||||||
@ -42,6 +43,7 @@ class ApcValidationRCUE(db.Model):
|
|||||||
formsemestre_id = db.Column(
|
formsemestre_id = db.Column(
|
||||||
db.Integer, db.ForeignKey("notes_formsemestre.id"), index=True, nullable=True
|
db.Integer, db.ForeignKey("notes_formsemestre.id"), index=True, nullable=True
|
||||||
)
|
)
|
||||||
|
"formsemestre pair du RCUE"
|
||||||
# Les deux UE associées à ce niveau:
|
# Les deux UE associées à ce niveau:
|
||||||
ue1_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
|
ue1_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
|
||||||
ue2_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
|
ue2_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
|
||||||
@ -84,6 +86,7 @@ class RegroupementCoherentUE:
|
|||||||
ue_1: UniteEns,
|
ue_1: UniteEns,
|
||||||
formsemestre_2: FormSemestre,
|
formsemestre_2: FormSemestre,
|
||||||
ue_2: UniteEns,
|
ue_2: UniteEns,
|
||||||
|
inscription_etat: str,
|
||||||
):
|
):
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
@ -109,6 +112,11 @@ class RegroupementCoherentUE:
|
|||||||
"semestre pair"
|
"semestre pair"
|
||||||
self.ue_2 = ue_2
|
self.ue_2 = ue_2
|
||||||
# Stocke les moyennes d'UE
|
# Stocke les moyennes d'UE
|
||||||
|
if inscription_etat != scu.INSCRIT:
|
||||||
|
self.moy_rcue = None
|
||||||
|
self.moy_ue_1 = self.moy_ue_2 = "-"
|
||||||
|
self.moy_ue_1_val = self.moy_ue_2_val = 0.0
|
||||||
|
return
|
||||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre_1)
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre_1)
|
||||||
if ue_1.id in res.etud_moy_ue and etud.id in res.etud_moy_ue[ue_1.id]:
|
if ue_1.id in res.etud_moy_ue and etud.id in res.etud_moy_ue[ue_1.id]:
|
||||||
self.moy_ue_1 = res.etud_moy_ue[ue_1.id][etud.id]
|
self.moy_ue_1 = res.etud_moy_ue[ue_1.id][etud.id]
|
||||||
@ -201,8 +209,9 @@ class RegroupementCoherentUE:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# unused
|
||||||
def find_rcues(
|
def find_rcues(
|
||||||
formsemestre: FormSemestre, ue: UniteEns, etud: Identite
|
formsemestre: FormSemestre, ue: UniteEns, etud: Identite, inscription_etat: str
|
||||||
) -> list[RegroupementCoherentUE]:
|
) -> list[RegroupementCoherentUE]:
|
||||||
"""Les RCUE (niveau de compétence) à considérer pour cet étudiant dans
|
"""Les RCUE (niveau de compétence) à considérer pour cet étudiant dans
|
||||||
ce semestre pour cette UE.
|
ce semestre pour cette UE.
|
||||||
@ -250,7 +259,9 @@ def find_rcues(
|
|||||||
other_ue = UniteEns.query.get(ue_id)
|
other_ue = UniteEns.query.get(ue_id)
|
||||||
other_formsemestre = FormSemestre.query.get(formsemestre_id)
|
other_formsemestre = FormSemestre.query.get(formsemestre_id)
|
||||||
rcues.append(
|
rcues.append(
|
||||||
RegroupementCoherentUE(etud, formsemestre, ue, other_formsemestre, other_ue)
|
RegroupementCoherentUE(
|
||||||
|
etud, formsemestre, ue, other_formsemestre, other_ue, inscription_etat
|
||||||
|
)
|
||||||
)
|
)
|
||||||
# safety check: 1 seul niveau de comp. concerné:
|
# safety check: 1 seul niveau de comp. concerné:
|
||||||
assert len({rcue.ue_1.niveau_competence_id for rcue in rcues}) == 1
|
assert len({rcue.ue_1.niveau_competence_id for rcue in rcues}) == 1
|
||||||
|
@ -58,7 +58,6 @@ from app.scodoc import sco_formations
|
|||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_permissions_check
|
from app.scodoc import sco_permissions_check
|
||||||
from app.scodoc import sco_photos
|
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_pvjury
|
from app.scodoc import sco_pvjury
|
||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
@ -66,15 +65,6 @@ import app.scodoc.sco_utils as scu
|
|||||||
from app.scodoc.sco_utils import ModuleType, fmt_note
|
from app.scodoc.sco_utils import ModuleType, fmt_note
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
|
||||||
# ----- CLASSES DE BULLETINS DE NOTES
|
|
||||||
from app.scodoc import sco_bulletins_standard
|
|
||||||
from app.scodoc import sco_bulletins_legacy
|
|
||||||
|
|
||||||
# import sco_bulletins_example # format exemple (à désactiver en production)
|
|
||||||
|
|
||||||
# ... ajouter ici vos modules ...
|
|
||||||
from app.scodoc import sco_bulletins_ucac # format expérimental UCAC Cameroun
|
|
||||||
|
|
||||||
|
|
||||||
def get_formsemestre_bulletin_etud_json(
|
def get_formsemestre_bulletin_etud_json(
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
|
@ -92,7 +92,6 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
d = {"type": "classic", "version": "0"}
|
d = {"type": "classic", "version": "0"}
|
||||||
|
|
||||||
if (not sem["bul_hide_xml"]) or force_publishing:
|
if (not sem["bul_hide_xml"]) or force_publishing:
|
||||||
published = True
|
published = True
|
||||||
else:
|
else:
|
||||||
@ -134,6 +133,7 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
)
|
)
|
||||||
d["etudiant"]["sexe"] = d["etudiant"]["civilite"] # backward compat for our clients
|
d["etudiant"]["sexe"] = d["etudiant"]["civilite"] # backward compat for our clients
|
||||||
# Disponible pour publication ?
|
# Disponible pour publication ?
|
||||||
|
d["publie"] = published
|
||||||
if not published:
|
if not published:
|
||||||
return d # stop !
|
return d # stop !
|
||||||
|
|
||||||
@ -364,8 +364,35 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def dict_decision_jury(etudid, formsemestre_id, with_decisions=False):
|
def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
|
||||||
"dict avec decision pour bulletins json"
|
"""dict avec decision pour bulletins json
|
||||||
|
- decision : décision semestre
|
||||||
|
- decision_ue : list des décisions UE
|
||||||
|
- situation
|
||||||
|
|
||||||
|
with_decision donne les décision même si bul_show_decision est faux.
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
{
|
||||||
|
'autorisation_inscription': [{'semestre_id': 4}],
|
||||||
|
'decision': {'code': 'ADM',
|
||||||
|
'compense_formsemestre_id': None,
|
||||||
|
'date': '2022-01-21',
|
||||||
|
'etat': 'I'},
|
||||||
|
'decision_ue': [
|
||||||
|
{
|
||||||
|
'acronyme': 'UE31',
|
||||||
|
'code': 'ADM',
|
||||||
|
'ects': 16.0,
|
||||||
|
'numero': 23,
|
||||||
|
'titre': 'Approfondissement métiers',
|
||||||
|
'ue_id': 1787
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
'situation': 'Inscrit le 25/06/2021. Décision jury: Validé. UE acquises: '
|
||||||
|
'UE31, UE32. Diplôme obtenu.'}
|
||||||
|
"""
|
||||||
from app.scodoc import sco_bulletins
|
from app.scodoc import sco_bulletins
|
||||||
|
|
||||||
d = {}
|
d = {}
|
||||||
|
@ -40,10 +40,9 @@ from openpyxl.comments import Comment
|
|||||||
from openpyxl import Workbook, load_workbook
|
from openpyxl import Workbook, load_workbook
|
||||||
from openpyxl.cell import WriteOnlyCell
|
from openpyxl.cell import WriteOnlyCell
|
||||||
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
||||||
|
from openpyxl.worksheet.worksheet import Worksheet
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import notesdb
|
|
||||||
from app.scodoc import sco_preferences
|
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
@ -593,71 +592,87 @@ def excel_feuille_saisie(e, titreannee, description, lines):
|
|||||||
def excel_bytes_to_list(bytes_content):
|
def excel_bytes_to_list(bytes_content):
|
||||||
try:
|
try:
|
||||||
filelike = io.BytesIO(bytes_content)
|
filelike = io.BytesIO(bytes_content)
|
||||||
return _excel_to_list(filelike)
|
except Exception as exc:
|
||||||
except:
|
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"""Le fichier xlsx attendu n'est pas lisible !
|
"""Le fichier xlsx attendu n'est pas lisible !
|
||||||
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ..)
|
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ..)
|
||||||
"""
|
"""
|
||||||
)
|
) from exc
|
||||||
|
return _excel_to_list(filelike)
|
||||||
|
|
||||||
|
|
||||||
def excel_file_to_list(filename):
|
def excel_file_to_list(filename):
|
||||||
try:
|
try:
|
||||||
return _excel_to_list(filename)
|
return _excel_to_list(filename)
|
||||||
except:
|
except Exception as exc:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"""Le fichier xlsx attendu n'est pas lisible !
|
"""Le fichier xlsx attendu n'est pas lisible !
|
||||||
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...)
|
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...)
|
||||||
"""
|
"""
|
||||||
)
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
def excel_file_to_list_are(filename):
|
def excel_workbook_to_list(filename):
|
||||||
try:
|
try:
|
||||||
return _excel_to_list_are(filename)
|
return _excel_workbook_to_list(filename)
|
||||||
except:
|
except Exception as exc:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"""Le fichier xlsx attendu n'est pas lisible !
|
"""Le fichier xlsx attendu n'est pas lisible !
|
||||||
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...)
|
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...)
|
||||||
"""
|
"""
|
||||||
)
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
|
def _open_workbook(filelike, dump_debug=False) -> Workbook:
|
||||||
|
"""Open document.
|
||||||
|
On error, if dump-debug is True, dump data in /tmp for debugging purpose
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
workbook = load_workbook(filename=filelike, read_only=True, data_only=True)
|
||||||
|
except Exception as exc:
|
||||||
|
log("Excel_to_list: failure to import document")
|
||||||
|
if dump_debug:
|
||||||
|
dump_filename = "/tmp/last_scodoc_import_failure" + scu.XLSX_SUFFIX
|
||||||
|
log(f"Dumping problemetic file on {dump_filename}")
|
||||||
|
with open(dump_filename, "wb") as f:
|
||||||
|
f.write(filelike)
|
||||||
|
raise ScoValueError(
|
||||||
|
"Fichier illisible: assurez-vous qu'il s'agit bien d'un document Excel xlsx !"
|
||||||
|
) from exc
|
||||||
|
return workbook
|
||||||
|
|
||||||
|
|
||||||
def _excel_to_list(filelike):
|
def _excel_to_list(filelike):
|
||||||
"""returns list of list
|
"""returns list of list"""
|
||||||
convert_to_string is a conversion function applied to all non-string values (ie numbers)
|
workbook = _open_workbook(filelike)
|
||||||
"""
|
|
||||||
try:
|
|
||||||
wb = load_workbook(filename=filelike, read_only=True, data_only=True)
|
|
||||||
except:
|
|
||||||
log("Excel_to_list: failure to import document")
|
|
||||||
with open("/tmp/last_scodoc_import_failure" + scu.XLSX_SUFFIX, "wb") as f:
|
|
||||||
f.write(filelike)
|
|
||||||
raise ScoValueError(
|
|
||||||
"Fichier illisible: assurez-vous qu'il s'agit bien d'un document Excel !"
|
|
||||||
)
|
|
||||||
diag = [] # liste de chaines pour former message d'erreur
|
diag = [] # liste de chaines pour former message d'erreur
|
||||||
# n'utilise que la première feuille
|
if len(workbook.get_sheet_names()) < 1:
|
||||||
if len(wb.get_sheet_names()) < 1:
|
|
||||||
diag.append("Aucune feuille trouvée dans le classeur !")
|
diag.append("Aucune feuille trouvée dans le classeur !")
|
||||||
return diag, None
|
return diag, None
|
||||||
if len(wb.get_sheet_names()) > 1:
|
# n'utilise que la première feuille:
|
||||||
|
if len(workbook.get_sheet_names()) > 1:
|
||||||
diag.append("Attention: n'utilise que la première feuille du classeur !")
|
diag.append("Attention: n'utilise que la première feuille du classeur !")
|
||||||
|
sheet_name = workbook.get_sheet_names()[0]
|
||||||
|
ws = workbook[sheet_name]
|
||||||
|
matrix, diag_sheet = _excel_sheet_to_list(ws, sheet_name)
|
||||||
|
diag += diag_sheet
|
||||||
|
return diag, matrix
|
||||||
|
|
||||||
|
|
||||||
|
def _excel_sheet_to_list(sheet: Worksheet, sheet_name: str) -> tuple[list, list]:
|
||||||
|
"""read a spreadsheet sheet, and returns:
|
||||||
|
- diag : a list of strings (error messages aimed at helping the user)
|
||||||
|
- a list of lists: the spreadsheet cells
|
||||||
|
"""
|
||||||
|
diag = []
|
||||||
# fill matrix
|
# fill matrix
|
||||||
sheet_name = wb.get_sheet_names()[0]
|
|
||||||
ws = wb.get_sheet_by_name(sheet_name)
|
|
||||||
sheet_name = sheet_name.encode(scu.SCO_ENCODING, "backslashreplace")
|
|
||||||
values = {}
|
values = {}
|
||||||
for row in ws.iter_rows():
|
for row in sheet.iter_rows():
|
||||||
for cell in row:
|
for cell in row:
|
||||||
if cell.value is not None:
|
if cell.value is not None:
|
||||||
values[(cell.row - 1, cell.column - 1)] = str(cell.value)
|
values[(cell.row - 1, cell.column - 1)] = str(cell.value)
|
||||||
if not values:
|
if not values:
|
||||||
diag.append(
|
diag.append(f"Aucune valeur trouvée dans la feuille {sheet_name} !")
|
||||||
"Aucune valeur trouvée dans la feuille %s !"
|
|
||||||
% sheet_name.decode(scu.SCO_ENCODING)
|
|
||||||
)
|
|
||||||
return diag, None
|
return diag, None
|
||||||
indexes = list(values.keys())
|
indexes = list(values.keys())
|
||||||
# search numbers of rows and cols
|
# search numbers of rows and cols
|
||||||
@ -665,76 +680,38 @@ def _excel_to_list(filelike):
|
|||||||
cols = [x[1] for x in indexes]
|
cols = [x[1] for x in indexes]
|
||||||
nbcols = max(cols) + 1
|
nbcols = max(cols) + 1
|
||||||
nbrows = max(rows) + 1
|
nbrows = max(rows) + 1
|
||||||
m = []
|
matrix = []
|
||||||
for _ in range(nbrows):
|
for _ in range(nbrows):
|
||||||
m.append([""] * nbcols)
|
matrix.append([""] * nbcols)
|
||||||
|
|
||||||
for row_idx, col_idx in indexes:
|
for row_idx, col_idx in indexes:
|
||||||
v = values[(row_idx, col_idx)]
|
v = values[(row_idx, col_idx)]
|
||||||
# if isinstance(v, six.text_type):
|
matrix[row_idx][col_idx] = v
|
||||||
# v = v.encode(scu.SCO_ENCODING, "backslashreplace")
|
diag.append(f'Feuille "{sheet_name}", {len(matrix)} lignes')
|
||||||
# elif convert_to_string:
|
|
||||||
# v = convert_to_string(v)
|
return diag, matrix
|
||||||
m[row_idx][col_idx] = v
|
|
||||||
diag.append(
|
|
||||||
'Feuille "%s", %d lignes' % (sheet_name.decode(scu.SCO_ENCODING), len(m))
|
|
||||||
)
|
|
||||||
# diag.append(str(M))
|
|
||||||
#
|
|
||||||
return diag, m
|
|
||||||
|
|
||||||
|
|
||||||
def _excel_to_list_are(filelike):
|
def _excel_workbook_to_list(filelike):
|
||||||
"""returns list of list
|
"""Lit un classeur (workbook): chaque feuille est lue
|
||||||
convert_to_string is a conversion function applied to all non-string values (ie numbers)
|
et est convertie en une liste de listes.
|
||||||
|
Returns:
|
||||||
|
- diag : a list of strings (error messages aimed at helping the user)
|
||||||
|
- a list of lists: the spreadsheet cells
|
||||||
"""
|
"""
|
||||||
try:
|
workbook = _open_workbook(filelike)
|
||||||
wb = load_workbook(filename=filelike, read_only=True, data_only=True)
|
|
||||||
except:
|
|
||||||
log("Excel_to_list: failure to import document")
|
|
||||||
with open("/tmp/last_scodoc_import_failure" + scu.XLSX_SUFFIX, "wb") as f:
|
|
||||||
f.write(filelike)
|
|
||||||
raise ScoValueError(
|
|
||||||
"Fichier illisible: assurez-vous qu'il s'agit bien d'un document Excel !"
|
|
||||||
)
|
|
||||||
diag = [] # liste de chaines pour former message d'erreur
|
diag = [] # liste de chaines pour former message d'erreur
|
||||||
if len(wb.get_sheet_names()) < 1:
|
if len(workbook.get_sheet_names()) < 1:
|
||||||
diag.append("Aucune feuille trouvée dans le classeur !")
|
diag.append("Aucune feuille trouvée dans le classeur !")
|
||||||
return diag, None
|
return diag, None
|
||||||
lm = []
|
matrix_list = []
|
||||||
for sheet_name in wb.get_sheet_names():
|
for sheet_name in workbook.get_sheet_names():
|
||||||
# fill matrix
|
# fill matrix
|
||||||
ws = wb.get_sheet_by_name(sheet_name)
|
sheet = workbook.get_sheet_by_name(sheet_name)
|
||||||
sheet_name = sheet_name.encode(scu.SCO_ENCODING, "backslashreplace")
|
matrix, diag_sheet = _excel_sheet_to_list(sheet, sheet_name)
|
||||||
values = {}
|
diag += diag_sheet
|
||||||
for row in ws.iter_rows():
|
matrix_list.append(matrix)
|
||||||
for cell in row:
|
return diag, matrix_list
|
||||||
if cell.value is not None:
|
|
||||||
values[(cell.row - 1, cell.column - 1)] = str(cell.value)
|
|
||||||
if not values:
|
|
||||||
diag.append(
|
|
||||||
"Aucune valeur trouvée dans la feuille %s !"
|
|
||||||
% sheet_name.decode(scu.SCO_ENCODING)
|
|
||||||
)
|
|
||||||
return diag, None
|
|
||||||
indexes = list(values.keys())
|
|
||||||
# search numbers of rows and cols
|
|
||||||
rows = [x[0] for x in indexes]
|
|
||||||
cols = [x[1] for x in indexes]
|
|
||||||
nbcols = max(cols) + 1
|
|
||||||
nbrows = max(rows) + 1
|
|
||||||
m = []
|
|
||||||
for _ in range(nbrows):
|
|
||||||
m.append([""] * nbcols)
|
|
||||||
|
|
||||||
for row_idx, col_idx in indexes:
|
|
||||||
v = values[(row_idx, col_idx)]
|
|
||||||
m[row_idx][col_idx] = v
|
|
||||||
diag.append(
|
|
||||||
'Feuille "%s", %d lignes' % (sheet_name.decode(scu.SCO_ENCODING), len(m))
|
|
||||||
)
|
|
||||||
lm.append(m)
|
|
||||||
return diag, lm
|
|
||||||
|
|
||||||
|
|
||||||
def excel_feuille_listeappel(
|
def excel_feuille_listeappel(
|
||||||
|
@ -35,13 +35,17 @@ from app.models.etudiants import Identite
|
|||||||
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import db, log
|
||||||
|
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import FormSemestre
|
from app.models import FormSemestre
|
||||||
from app.models.notes import etud_has_notes_attente
|
from app.models.notes import etud_has_notes_attente
|
||||||
|
from app.models.validations import (
|
||||||
|
ScolarAutorisationInscription,
|
||||||
|
ScolarFormSemestreValidation,
|
||||||
|
)
|
||||||
|
from app.models.but_validations import ApcValidationRCUE, ApcValidationAnnee
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
from app.scodoc.sco_codes_parcours import *
|
from app.scodoc.sco_codes_parcours import *
|
||||||
@ -989,28 +993,32 @@ def do_formsemestre_validation_auto(formsemestre_id):
|
|||||||
|
|
||||||
|
|
||||||
def formsemestre_validation_suppress_etud(formsemestre_id, etudid):
|
def formsemestre_validation_suppress_etud(formsemestre_id, etudid):
|
||||||
"""Suppression des decisions de jury pour un etudiant."""
|
"""Suppression des décisions de jury pour un étudiant/formsemestre.
|
||||||
log("formsemestre_validation_suppress_etud( %s, %s)" % (formsemestre_id, etudid))
|
Efface toutes les décisions enregistrées concernant ce formsemestre et cet étudiant:
|
||||||
cnx = ndb.GetDBConnexion()
|
code semestre, UEs, autorisations d'inscription
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
"""
|
||||||
args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
|
log(f"formsemestre_validation_suppress_etud( {formsemestre_id}, {etudid})")
|
||||||
try:
|
|
||||||
# -- Validation du semestre et des UEs
|
# Validations jury classiques (semestres, UEs, autorisations)
|
||||||
cursor.execute(
|
for v in ScolarFormSemestreValidation.query.filter_by(
|
||||||
"""delete from scolar_formsemestre_validation
|
etudid=etudid, formsemestre_id=formsemestre_id
|
||||||
where etudid = %(etudid)s and formsemestre_id=%(formsemestre_id)s""",
|
):
|
||||||
args,
|
db.session.delete(v)
|
||||||
)
|
for v in ScolarAutorisationInscription.query.filter_by(
|
||||||
# -- Autorisations d'inscription
|
etudid=etudid, origin_formsemestre_id=formsemestre_id
|
||||||
cursor.execute(
|
):
|
||||||
"""delete from scolar_autorisation_inscription
|
db.session.delete(v)
|
||||||
where etudid = %(etudid)s and origin_formsemestre_id=%(formsemestre_id)s""",
|
# Validations jury spécifiques BUT
|
||||||
args,
|
for v in ApcValidationRCUE.query.filter_by(
|
||||||
)
|
etudid=etudid, formsemestre_id=formsemestre_id
|
||||||
cnx.commit()
|
):
|
||||||
except:
|
db.session.delete(v)
|
||||||
cnx.rollback()
|
for v in ApcValidationAnnee.query.filter_by(
|
||||||
raise
|
etudid=etudid, formsemestre_id=formsemestre_id
|
||||||
|
):
|
||||||
|
db.session.delete(v)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
_invalidate_etud_formation_caches(
|
_invalidate_etud_formation_caches(
|
||||||
|
@ -224,9 +224,26 @@ class releveBUT extends HTMLElement {
|
|||||||
<div class=abs>Non justifiées</div>
|
<div class=abs>Non justifiées</div>
|
||||||
<div>${data.semestre.absences?.injustifie ?? "-"}</div>
|
<div>${data.semestre.absences?.injustifie ?? "-"}</div>
|
||||||
<div class=abs>Total</div><div>${data.semestre.absences?.total ?? "-"}</div>
|
<div class=abs>Total</div><div>${data.semestre.absences?.total ?? "-"}</div>
|
||||||
|
</div>`;
|
||||||
|
if(data.semestre.decision_rcue.length){
|
||||||
|
output += `
|
||||||
|
<div>
|
||||||
|
<div class=enteteSemestre>RCUE</div><div></div>
|
||||||
|
${(()=>{
|
||||||
|
let output = "";
|
||||||
|
data.semestre.decision_rcue.forEach(competence=>{
|
||||||
|
output += `<div class=rang>${competence.niveau.competence.titre}</div><div>${competence.code}</div>`;
|
||||||
|
})
|
||||||
|
return output;
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
<a class=photo href="${data.etudiant.fiche_url}"><img src="${data.etudiant.photo_url || "default_Student.svg"}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0"></a>
|
</div>`
|
||||||
`;
|
}
|
||||||
|
|
||||||
|
output += `
|
||||||
|
<a class=photo href="${data.etudiant.fiche_url}">
|
||||||
|
<img src="${data.etudiant.photo_url || "default_Student.svg"}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0">
|
||||||
|
</a>`;
|
||||||
/*${data.semestre.groupes.map(groupe => {
|
/*${data.semestre.groupes.map(groupe => {
|
||||||
return `
|
return `
|
||||||
<div>
|
<div>
|
||||||
@ -240,9 +257,11 @@ class releveBUT extends HTMLElement {
|
|||||||
}).join("")
|
}).join("")
|
||||||
}*/
|
}*/
|
||||||
this.shadow.querySelector(".infoSemestre").innerHTML = output;
|
this.shadow.querySelector(".infoSemestre").innerHTML = output;
|
||||||
if(data.semestre.decision_annee?.code){
|
|
||||||
|
|
||||||
|
/*if(data.semestre.decision_annee?.code){
|
||||||
this.shadow.querySelector(".decision_annee").innerHTML = "Décision année : " + data.semestre.decision_annee.code + " - " + correspondanceCodes[data.semestre.decision_annee.code];
|
this.shadow.querySelector(".decision_annee").innerHTML = "Décision année : " + data.semestre.decision_annee.code + " - " + correspondanceCodes[data.semestre.decision_annee.code];
|
||||||
}
|
}*/
|
||||||
|
|
||||||
this.shadow.querySelector(".decision").innerHTML = data.semestre.situation || "";
|
this.shadow.querySelector(".decision").innerHTML = data.semestre.situation || "";
|
||||||
/*if (data.semestre.decision?.code) {
|
/*if (data.semestre.decision?.code) {
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
<div class="but_doc_codes">
|
<div class="but_doc_codes">
|
||||||
<p><em>Ci-dessous la signification de chaque code est expliquée,
|
<p><em>Ci-dessous la signification de chaque code est expliquée,
|
||||||
ainsi que la correspondance avec les codes préconisés par
|
ainsi que la correspondance avec certains codes préconisés par
|
||||||
l'AMUE pour Apogée dans un document informel qui a circulé début
|
l'AMUE et l'ADIUT pour Apogée.
|
||||||
2022 (les éventuelles erreurs n'engagent personne).
|
</em>
|
||||||
</em></p>
|
On distingue les codes ScoDoc (utilisés ci-dessus et dans les différentes
|
||||||
|
tables générées par ScoDoc) et leur transcription vers Apogée lors des exports
|
||||||
|
(transcription paramétrable par votre administrateur ScoDoc).
|
||||||
|
</p>
|
||||||
<div class="but_doc_section">Codes d'année</div>
|
<div class="but_doc_section">Codes d'année</div>
|
||||||
<div class="but_doc">
|
<div class="but_doc">
|
||||||
<table>
|
<table>
|
||||||
@ -230,4 +233,34 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="but_doc_section">Rappels de l'arrêté BUT (extraits)</div>
|
||||||
|
<div class="but_doc">
|
||||||
|
<ul>
|
||||||
|
<li>Au sein de chaque regroupement cohérent d’UE, la compensation est intégrale.
|
||||||
|
Si une UE n’a pas été acquise en raison d’une moyenne inférieure à 10,
|
||||||
|
cette UE sera acquise par compensation si et seulement si l’étudiant
|
||||||
|
a obtenu la moyenne au regroupement cohérent auquel l’UE appartient.</li>
|
||||||
|
<li>La poursuite d'études dans un semestre pair d’une même année est de droit
|
||||||
|
pour tout étudiant.
|
||||||
|
La poursuite d’études dans un semestre impair est possible
|
||||||
|
<em>si et seulement si</em> l’étudiant a obtenu :
|
||||||
|
<ul>
|
||||||
|
<li>la moyenne à plus de la moitié des regroupements cohérents d’UE</li>
|
||||||
|
<li>et une moyenne égale ou supérieure à 8 sur 20 à chaque regroupement cohérent d’UE.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>La poursuite d'études dans le semestre 5 nécessite de plus la validation de toutes les UE des
|
||||||
|
semestres 1 et 2 dans les conditions de validation des points 4.3 et 4.4, ou par décision de jury.</li>
|
||||||
|
</ul>
|
||||||
|
<b>Textes de référence:</b>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://www.enseignementsup-recherche.gouv.fr/fr/bo/21/Special4/ESRS2114777A.htm">Bulletin
|
||||||
|
officiel spécial n°4 du 17 juin 2021</a></li>
|
||||||
|
<li><a
|
||||||
|
href="https://cache.media.enseignementsup-recherche.gouv.fr//file/SPE4-MESRI-17-6-2021/19/4/SP4_ESR_17_6_2021_1413194.pdf">Version
|
||||||
|
pdf complète</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
@ -57,7 +57,7 @@ from app.models.ues import UniteEns
|
|||||||
from app import api
|
from app import api
|
||||||
from app import db
|
from app import db
|
||||||
from app import models
|
from app import models
|
||||||
from app.models import ScolarNews
|
from app.models import ScolarNews, but_validations
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.but import apc_edit_ue, jury_but_recap
|
from app.but import apc_edit_ue, jury_but_recap
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
@ -71,7 +71,7 @@ from app.views import notes_bp as bp
|
|||||||
|
|
||||||
# ---------------
|
# ---------------
|
||||||
|
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_bulletins_json, sco_utils as scu
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app import log, send_scodoc_alarm
|
from app import log, send_scodoc_alarm
|
||||||
|
|
||||||
@ -2515,51 +2515,68 @@ def do_formsemestre_validation_auto(formsemestre_id):
|
|||||||
def formsemestre_validation_suppress_etud(
|
def formsemestre_validation_suppress_etud(
|
||||||
formsemestre_id, etudid, dialog_confirmed=False
|
formsemestre_id, etudid, dialog_confirmed=False
|
||||||
):
|
):
|
||||||
"""Suppression des decisions de jury pour un etudiant."""
|
"""Suppression des décisions de jury pour un étudiant."""
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
||||||
dest_url=scu.ScoURL(),
|
dest_url=scu.ScoURL(),
|
||||||
)
|
)
|
||||||
if not dialog_confirmed:
|
etud = Identite.query.get_or_404(etudid)
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
sem = formsemestre.to_dict()
|
if formsemestre.formation.is_apc():
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
next_url = url_for(
|
||||||
decision_jury = nt.get_etud_decision_sem(etudid)
|
"scolar.ficheEtud",
|
||||||
if decision_jury:
|
scodoc_dept=g.scodoc_dept,
|
||||||
existing = (
|
etudid=etudid,
|
||||||
"<p>Décision existante: %(code)s du %(event_date)s</p>" % decision_jury
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
existing = ""
|
next_url = url_for(
|
||||||
return scu.confirm_dialog(
|
"notes.formsemestre_validation_etud_form",
|
||||||
"""<h2>Confirmer la suppression des décisions du semestre %s (%s - %s) pour %s ?</h2>%s
|
scodoc_dept=g.scodoc_dept,
|
||||||
<p>Cette opération est irréversible.
|
formsemestre_id=formsemestre_id,
|
||||||
</p>
|
etudid=etudid,
|
||||||
|
)
|
||||||
|
if not dialog_confirmed:
|
||||||
|
d = sco_bulletins_json.dict_decision_jury(
|
||||||
|
etudid, formsemestre_id, with_decisions=True
|
||||||
|
)
|
||||||
|
d.update(but_validations.dict_decision_jury(etud, formsemestre))
|
||||||
|
|
||||||
|
descr_ues = [f"{u['acronyme']}: {u['code']}" for u in d.get("decision_ue", [])]
|
||||||
|
dec_annee = d.get("decision_annee")
|
||||||
|
if dec_annee:
|
||||||
|
descr_annee = dec_annee.get("code", "-")
|
||||||
|
else:
|
||||||
|
descr_annee = "-"
|
||||||
|
|
||||||
|
existing = f"""
|
||||||
|
<ul>
|
||||||
|
<li>Semestre : {d.get("decision", {"code":"-"})['code'] or "-"}</li>
|
||||||
|
<li>Année BUT: {descr_annee}</li>
|
||||||
|
<li>UEs : {", ".join(descr_ues)}</li>
|
||||||
|
<li>RCUEs: {len(d.get("decision_rcue", []))} décisions</li>
|
||||||
|
</ul>
|
||||||
"""
|
"""
|
||||||
% (
|
return scu.confirm_dialog(
|
||||||
sem["titre_num"],
|
f"""<h2>Confirmer la suppression des décisions du semestre
|
||||||
sem["date_debut"],
|
{formsemestre.titre_mois()} pour {etud.nomprenom}
|
||||||
sem["date_fin"],
|
</h2>
|
||||||
etud["nomprenom"],
|
<p>Cette opération est irréversible.</p>
|
||||||
existing,
|
<div>
|
||||||
),
|
{existing}
|
||||||
|
</div>
|
||||||
|
""",
|
||||||
OK="Supprimer",
|
OK="Supprimer",
|
||||||
dest_url="",
|
dest_url="",
|
||||||
cancel_url="formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s"
|
cancel_url=next_url,
|
||||||
% (formsemestre_id, etudid),
|
|
||||||
parameters={"etudid": etudid, "formsemestre_id": formsemestre_id},
|
parameters={"etudid": etudid, "formsemestre_id": formsemestre_id},
|
||||||
)
|
)
|
||||||
|
|
||||||
sco_formsemestre_validation.formsemestre_validation_suppress_etud(
|
sco_formsemestre_validation.formsemestre_validation_suppress_etud(
|
||||||
formsemestre_id, etudid
|
formsemestre_id, etudid
|
||||||
)
|
)
|
||||||
return flask.redirect(
|
flash("Décisions supprimées")
|
||||||
scu.ScoURL()
|
return flask.redirect(next_url)
|
||||||
+ "/Notes/formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&head_message=Décision%%20supprimée"
|
|
||||||
% (formsemestre_id, etudid)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ------------- PV de JURY et archives
|
# ------------- PV de JURY et archives
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.3.13"
|
SCOVERSION = "9.3.15"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ except NameError:
|
|||||||
|
|
||||||
load_dotenv(os.path.join(BASEDIR, ".env"))
|
load_dotenv(os.path.join(BASEDIR, ".env"))
|
||||||
CHK_CERT = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
|
CHK_CERT = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
|
||||||
SCODOC_URL = os.environ["SCODOC_URL"] or "http://localhost:5000"
|
SCODOC_URL = os.environ.get("SCODOC_URL") or "http://localhost:5000"
|
||||||
API_URL = SCODOC_URL + "/ScoDoc/api"
|
API_URL = SCODOC_URL + "/ScoDoc/api"
|
||||||
SCODOC_USER = os.environ["SCODOC_USER"]
|
SCODOC_USER = os.environ["SCODOC_USER"]
|
||||||
SCODOC_PASSWORD = os.environ["SCODOC_PASSWORD"]
|
SCODOC_PASSWORD = os.environ["SCODOC_PASSWORD"]
|
||||||
@ -85,13 +85,13 @@ if r.status_code != 200:
|
|||||||
print(f"{len(r.json())} étudiants courants")
|
print(f"{len(r.json())} étudiants courants")
|
||||||
|
|
||||||
# Bulletin d'un BUT
|
# Bulletin d'un BUT
|
||||||
formsemestre_id = 1052 # A adapter
|
formsemestre_id = 1063 # A adapter
|
||||||
etudid = 16400
|
etudid = 16450
|
||||||
bul = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
|
bul = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
|
||||||
|
|
||||||
# d'un DUT
|
# d'un DUT
|
||||||
formsemestre_id = 1028 # A adapter
|
formsemestre_id = 1062 # A adapter
|
||||||
etudid = 14721
|
etudid = 16309
|
||||||
bul_dut = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
|
bul_dut = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user