1
0
forked from ScoDoc/ScoDoc

WIP: jury BUT: prise en compte des UE capitalisées dans les RCUEs

This commit is contained in:
Emmanuel Viennet 2022-11-24 00:11:59 +01:00
parent 06caacbb47
commit 386471a47f
10 changed files with 201 additions and 135 deletions

View File

@ -383,36 +383,39 @@ class DecisionsProposeesAnnee(DecisionsProposees):
# #
def infos(self) -> str: def infos(self) -> str:
"informations, for debugging purpose" """informations, for debugging purpose."""
return f"""<b>DecisionsProposeesAnnee</b> text = f"""<b>DecisionsProposeesAnnee</b>
<ul> <ul>
<li>Etudiant: <a href="{url_for("scolar.ficheEtud", <li>Etudiant: <a href="{url_for("scolar.ficheEtud",
scodoc_dept=g.scodoc_dept, etudid=self.etud.id) scodoc_dept=g.scodoc_dept, etudid=self.etud.id)
}">{self.etud.nomprenom}</a> }">{self.etud.nomprenom}</a>
</li> </li>
<li>formsemestre_impair: <a href="{url_for("notes.formsemestre_status", """
scodoc_dept=g.scodoc_dept, formsemestre_id=self.formsemestre_impair.id) for formsemestre, title in (
}">{html.escape(str(self.formsemestre_impair))}</a> (self.formsemestre_impair, "formsemestre_impair"),
(self.formsemestre_pair, "formsemestre_pair"),
):
text += f"<li>{title}:"
if formsemestre is not None:
text += f"""
<a href="{url_for("notes.formsemestre_status",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
}">{html.escape(str(formsemestre))}</a>
<ul> <ul>
<li>Formation: <a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept, <li>Formation: <a href="{url_for('notes.ue_table',
semestre_idx=self.formsemestre_impair.semestre_id, scodoc_dept=g.scodoc_dept,
formation_id=self.formsemestre_impair.formation.id)}"> semestre_idx=formsemestre.semestre_id,
{self.formsemestre_impair.formation.to_html()} ({self.formsemestre_impair.formation.id})</a> formation_id=formsemestre.formation.id)}">
{formsemestre.formation.to_html()} ({
formsemestre.formation.id})</a>
</li> </li>
</ul> </ul>
</li> """
<li>formsemestre_pair: <a href="{url_for("notes.formsemestre_status", else:
scodoc_dept=g.scodoc_dept, formsemestre_id=self.formsemestre_pair.id) text += " aucun."
}">{html.escape(str(self.formsemestre_pair))}</a> text += "</li>"
<ul>
<li>Formation: <a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
semestre_idx=self.formsemestre_pair.semestre_id,
formation_id=self.formsemestre_pair.formation.id)}">
{self.formsemestre_pair.formation.to_html()} ({self.formsemestre_pair.formation.id})</a>
</li>
</ul>
</li>
text += f"""
<li>RCUEs: {html.escape(str(self.rcues_annee))}</li> <li>RCUEs: {html.escape(str(self.rcues_annee))}</li>
<li>nb_competences: {getattr(self, "nb_competences", "-")}</li> <li>nb_competences: {getattr(self, "nb_competences", "-")}</li>
<li>nb_validables: {getattr(self, "nb_validables", "-")}</li> <li>nb_validables: {getattr(self, "nb_validables", "-")}</li>
@ -420,6 +423,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
<li>explanation: {self.explanation}</li> <li>explanation: {self.explanation}</li>
</ul> </ul>
""" """
return text
def annee_scolaire(self) -> int: def annee_scolaire(self) -> int:
"L'année de début de l'année scolaire" "L'année de début de l'année scolaire"
@ -434,14 +438,19 @@ class DecisionsProposeesAnnee(DecisionsProposees):
def comp_formsemestres( def comp_formsemestres(
self, formsemestre: FormSemestre self, formsemestre: FormSemestre
) -> tuple[FormSemestre, FormSemestre]: ) -> tuple[FormSemestre, FormSemestre]:
"""les deux formsemestres de l'année scolaire à laquelle appartient formsemestre.""" """Les deux formsemestres du niveau auquel appartient formsemestre.
Complète le niveau avec le formsemestre antérieur le plus récent.
L'"autre" formsemestre peut ainsi appartenir à l'année scolaire
antérieure (redoublants).
-> S_impair, S_pair
"""
if not formsemestre.formation.is_apc(): # garde fou if not formsemestre.formation.is_apc(): # garde fou
return None, None return None, None
if formsemestre.semestre_id % 2 == 0: if formsemestre.semestre_id % 2 == 0:
other_semestre_id = formsemestre.semestre_id - 1 other_semestre_id = formsemestre.semestre_id - 1
else: else:
other_semestre_id = formsemestre.semestre_id + 1 other_semestre_id = formsemestre.semestre_id + 1
annee_scolaire = formsemestre.annee_scolaire()
other_formsemestre = None other_formsemestre = None
for inscr in self.etud.formsemestre_inscriptions: for inscr in self.etud.formsemestre_inscriptions:
if ( if (
@ -452,8 +461,13 @@ class DecisionsProposeesAnnee(DecisionsProposees):
) )
# L'autre semestre # L'autre semestre
and (inscr.formsemestre.semestre_id == other_semestre_id) and (inscr.formsemestre.semestre_id == other_semestre_id)
# de la même année scolaire: # Antérieur
and (inscr.formsemestre.annee_scolaire() == annee_scolaire) and inscr.formsemestre.date_debut < formsemestre.date_debut
# Et plus le récent possible
and (
(other_formsemestre is None)
or (other_formsemestre.date_debut < inscr.formsemestre.date_debut)
)
): ):
other_formsemestre = inscr.formsemestre other_formsemestre = inscr.formsemestre
if formsemestre.semestre_id % 2 == 0: if formsemestre.semestre_id % 2 == 0:
@ -512,9 +526,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
rcue = RegroupementCoherentUE( rcue = RegroupementCoherentUE(
self.etud, self.etud,
self.formsemestre_impair, self.formsemestre_impair,
ue_impair, self.decisions_ues[ue_impair.id],
self.formsemestre_pair, self.formsemestre_pair,
ue_pair, self.decisions_ues[ue_pair.id],
self.inscription_etat, self.inscription_etat,
) )
ues_impair_sans_rcue.discard(ue_impair.id) ues_impair_sans_rcue.discard(ue_impair.id)
@ -976,18 +990,23 @@ class DecisionsProposeesUE(DecisionsProposees):
self.codes = [ self.codes = [
sco_codes.DEM if inscription_etat == scu.DEMISSION else sco_codes.DEF sco_codes.DEM if inscription_etat == scu.DEMISSION else sco_codes.DEF
] ]
self.moy_ue = np.NaN self.moy_ue = self.moy_ue_with_cap = np.NaN
self.ue_status = {}
return 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)
# Safety checks:
if not ue.id in res.etud_moy_ue: if not ue.id in res.etud_moy_ue:
self.explanation = "UE sans résultat" self.explanation = "UE sans résultat"
return return
if not etud.id in res.etud_moy_ue[ue.id]: if not etud.id in res.etud_moy_ue[ue.id]:
self.explanation = "Étudiant sans résultat dans cette UE" self.explanation = "Étudiant sans résultat dans cette UE"
return return
self.moy_ue = res.etud_moy_ue[ue.id][etud.id] ue_status = res.get_etud_ue_status(etud.id, ue.id)
self.moy_ue = ue_status["cur_moy_ue"]
self.moy_ue_with_cap = ue_status["moy"]
self.ue_status = ue_status
def set_rcue(self, rcue: RegroupementCoherentUE): def set_rcue(self, rcue: RegroupementCoherentUE):
"""Rattache cette UE à un RCUE. Cela peut modifier les codes """Rattache cette UE à un RCUE. Cela peut modifier les codes

View File

@ -58,8 +58,8 @@ def formsemestre_saisie_jury_but(
# DecisionsProposeesAnnee(etud, formsemestre2) # DecisionsProposeesAnnee(etud, formsemestre2)
# Pour le 1er etud, faire un check_ues_ready_jury(self) -> page d'erreur # Pour le 1er etud, faire un check_ues_ready_jury(self) -> page d'erreur
# -> rcue .ue_1, .ue_2 -> stroe moy ues, rcue.moy_rcue, etc # -> rcue .ue_1, .ue_2 -> stroe moy ues, rcue.moy_rcue, etc
if formsemestre2.semestre_id % 2 != 0: # XXX if formsemestre2.semestre_id % 2 != 0:
raise ScoValueError("Cette page ne fonctionne que sur les semestres pairs") # raise ScoValueError("Cette page ne fonctionne que sur les semestres pairs")
if formsemestre2.formation.referentiel_competence is None: if formsemestre2.formation.referentiel_competence is None:
raise ScoValueError( raise ScoValueError(
@ -262,7 +262,7 @@ class RowCollector:
# --- Codes (seront cachés, mais exportés en excel) # --- Codes (seront cachés, mais exportés en excel)
self.add_cell("etudid", "etudid", etud.id, "codes") self.add_cell("etudid", "etudid", etud.id, "codes")
self.add_cell("code_nip", "code_nip", etud.code_nip or "", "codes") self.add_cell("code_nip", "code_nip", etud.code_nip or "", "codes")
# --- Identité étudiant (adapté de res_comon/get_table_recap, à factoriser XXX TODO) # --- Identité étudiant (adapté de res_common/get_table_recap, à factoriser XXX TODO)
self.add_cell("civilite_str", "Civ.", etud.civilite_str, "identite_detail") self.add_cell("civilite_str", "Civ.", etud.civilite_str, "identite_detail")
self.add_cell("nom_disp", "Nom", etud.nom_disp(), "identite_detail") self.add_cell("nom_disp", "Nom", etud.nom_disp(), "identite_detail")
self["_nom_disp_order"] = etud.sort_key self["_nom_disp_order"] = etud.sort_key

View File

@ -82,8 +82,6 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
H.append( H.append(
_gen_but_niveau_ue( _gen_but_niveau_ue(
dec_rcue.rcue.ue_1, dec_rcue.rcue.ue_1,
deca.decisions_ues[dec_rcue.rcue.ue_1.id].moy_ue,
# dec_rcue.rcue.moy_ue_1,
deca.decisions_ues[dec_rcue.rcue.ue_1.id], deca.decisions_ues[dec_rcue.rcue.ue_1.id],
disabled=read_only, disabled=read_only,
) )
@ -92,8 +90,6 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
H.append( H.append(
_gen_but_niveau_ue( _gen_but_niveau_ue(
dec_rcue.rcue.ue_2, dec_rcue.rcue.ue_2,
deca.decisions_ues[dec_rcue.rcue.ue_2.id].moy_ue,
# dec_rcue.rcue.moy_ue_2,
deca.decisions_ues[dec_rcue.rcue.ue_2.id], deca.decisions_ues[dec_rcue.rcue.ue_2.id],
disabled=read_only, disabled=read_only,
) )
@ -142,20 +138,40 @@ def _gen_but_select(
""" """
def _gen_but_niveau_ue( def _gen_but_niveau_ue(ue: UniteEns, dec_ue: DecisionsProposeesUE, disabled=False):
ue: UniteEns, moy_ue: float, dec_ue: DecisionsProposeesUE, disabled=False if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
): moy_ue_str = f"""<span class="ue_cap">{
scu.fmt_note(dec_ue.moy_ue_with_cap)}</span>"""
scoplement = f"""<div class="scoplement">
<div>
<b>UE {ue.acronyme} capitalisée le
{dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")}
</b>
</div>
<div>UE en cours avec moyenne
{scu.fmt_note(dec_ue.moy_ue)}
</div>
</div>
"""
else:
moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>"""
scoplement = ""
return f"""<div class="but_niveau_ue { return f"""<div class="but_niveau_ue {
'recorded' if dec_ue.code_valide is not None else ''} 'recorded' if dec_ue.code_valide is not None else ''}
"> ">
<div title="{ue.titre}">{ue.acronyme}</div> <div title="{ue.titre}">{ue.acronyme}</div>
<div class="but_note">{scu.fmt_note(moy_ue)}</div> <div class="but_note with_scoplement">
<div>{moy_ue_str}</div>
{scoplement}
</div>
<div class="but_code">{ <div class="but_code">{
_gen_but_select("code_ue_"+str(ue.id), _gen_but_select("code_ue_"+str(ue.id),
dec_ue.codes, dec_ue.codes,
dec_ue.code_valide, disabled=disabled dec_ue.code_valide, disabled=disabled
) )
}</div> }</div>
</div>""" </div>"""
@ -295,7 +311,6 @@ def jury_but_semestriel(
H.append( H.append(
_gen_but_niveau_ue( _gen_but_niveau_ue(
ue, ue,
dec_ue.moy_ue,
dec_ue, dec_ue,
disabled=read_only, disabled=read_only,
) )

View File

@ -316,7 +316,7 @@ class ResultatsSemestre(ResultatsCache):
"""L'état de l'UE pour cet étudiant. """L'état de l'UE pour cet étudiant.
Result: dict, ou None si l'UE n'est pas dans ce semestre. Result: dict, ou None si l'UE n'est pas dans ce semestre.
""" """
ue = UniteEns.query.get(ue_id) # TODO cacher nos UEs ? ue = UniteEns.query.get(ue_id)
if ue.type == UE_SPORT: if ue.type == UE_SPORT:
return { return {
"is_capitalized": False, "is_capitalized": False,

View File

@ -84,28 +84,28 @@ class RegroupementCoherentUE:
"""Le regroupement cohérent d'UE, dans la terminologie du BUT, est le couple d'UEs """Le regroupement cohérent d'UE, dans la terminologie du BUT, est le couple d'UEs
de la même année (BUT1,2,3) liées au *même niveau de compétence*. de la même année (BUT1,2,3) liées au *même niveau de compétence*.
La moyenne (10/20) au RCU déclenche la compensation des UE. La moyenne (10/20) au RCUE déclenche la compensation des UE.
""" """
def __init__( def __init__(
self, self,
etud: Identite, etud: Identite,
formsemestre_1: FormSemestre, formsemestre_1: FormSemestre,
ue_1: UniteEns, dec_ue_1: "DecisionsProposeesUE",
formsemestre_2: FormSemestre, formsemestre_2: FormSemestre,
ue_2: UniteEns, dec_ue_2: "DecisionsProposeesUE",
inscription_etat: str, 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
# from app.but.jury_but import DecisionsProposeesUE
ue_1 = dec_ue_1.ue
ue_2 = dec_ue_2.ue
# Ordonne les UE dans le sens croissant (S1,S2) ou (S3,S4)... # Ordonne les UE dans le sens croissant (S1,S2) ou (S3,S4)...
if formsemestre_1.semestre_id > formsemestre_2.semestre_id: if formsemestre_1.semestre_id > formsemestre_2.semestre_id:
(ue_1, formsemestre_1), (ue_2, formsemestre_2) = ( (ue_1, formsemestre_1), (ue_2, formsemestre_2) = (
( (ue_2, formsemestre_2),
ue_2,
formsemestre_2,
),
(ue_1, formsemestre_1), (ue_1, formsemestre_1),
) )
assert formsemestre_1.semestre_id % 2 == 1 assert formsemestre_1.semestre_id % 2 == 1
@ -125,21 +125,12 @@ class RegroupementCoherentUE:
self.moy_ue_1 = self.moy_ue_2 = "-" self.moy_ue_1 = self.moy_ue_2 = "-"
self.moy_ue_1_val = self.moy_ue_2_val = 0.0 self.moy_ue_1_val = self.moy_ue_2_val = 0.0
return return
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre_1) self.moy_ue_1 = dec_ue_1.moy_ue_with_cap
if ue_1.id in res.etud_moy_ue and etud.id in res.etud_moy_ue[ue_1.id]: self.moy_ue_1_val = self.moy_ue_1 if self.moy_ue_1 is not None else 0.0
self.moy_ue_1 = res.etud_moy_ue[ue_1.id][etud.id] self.moy_ue_2 = dec_ue_2.moy_ue_with_cap
self.moy_ue_1_val = self.moy_ue_1 # toujours float, peut être NaN self.moy_ue_2_val = self.moy_ue_2 if self.moy_ue_2 is not None else 0.0
else:
self.moy_ue_1 = None # Calcul de la moyenne au RCUE (utilise les moy d'UE capitalisées)
self.moy_ue_1_val = 0.0
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre_2)
if ue_2.id in res.etud_moy_ue and etud.id in res.etud_moy_ue[ue_2.id]:
self.moy_ue_2 = res.etud_moy_ue[ue_2.id][etud.id]
self.moy_ue_2_val = self.moy_ue_2
else:
self.moy_ue_2 = None
self.moy_ue_2_val = 0.0
# Calcul de la moyenne au RCUE
if (self.moy_ue_1 is not None) and (self.moy_ue_2 is not None): if (self.moy_ue_1 is not None) and (self.moy_ue_2 is not None):
# Moyenne RCUE (les pondérations par défaut sont 1.) # Moyenne RCUE (les pondérations par défaut sont 1.)
self.moy_rcue = ( self.moy_rcue = (
@ -149,7 +140,9 @@ class RegroupementCoherentUE:
self.moy_rcue = None self.moy_rcue = None
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<{self.__class__.__name__} {self.ue_1.acronyme}({self.moy_ue_1}) {self.ue_2.acronyme}({self.moy_ue_2})>" return f"""<{self.__class__.__name__} {
self.ue_1.acronyme}({self.moy_ue_1}) {
self.ue_2.acronyme}({self.moy_ue_2})>"""
def query_validations( def query_validations(
self, self,
@ -218,62 +211,62 @@ class RegroupementCoherentUE:
# unused # unused
def find_rcues( # def find_rcues(
formsemestre: FormSemestre, ue: UniteEns, etud: Identite, inscription_etat: str # 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.
Cherche les UEs du même niveau de compétence auxquelles l'étudiant est inscrit. # Cherche les UEs du même niveau de compétence auxquelles l'étudiant est inscrit.
En cas de redoublement, il peut y en avoir plusieurs, donc plusieurs RCUEs. # En cas de redoublement, il peut y en avoir plusieurs, donc plusieurs RCUEs.
Résultat: la liste peut être vide. # Résultat: la liste peut être vide.
""" # """
if (ue.niveau_competence is None) or (ue.semestre_idx is None): # if (ue.niveau_competence is None) or (ue.semestre_idx is None):
return [] # return []
if ue.semestre_idx % 2: # S1, S3, S5 # if ue.semestre_idx % 2: # S1, S3, S5
other_semestre_idx = ue.semestre_idx + 1 # other_semestre_idx = ue.semestre_idx + 1
else: # else:
other_semestre_idx = ue.semestre_idx - 1 # other_semestre_idx = ue.semestre_idx - 1
cursor = db.session.execute( # cursor = db.session.execute(
text( # text(
"""SELECT # """SELECT
ue.id, formsemestre.id # ue.id, formsemestre.id
FROM # FROM
notes_ue ue, # notes_ue ue,
notes_formsemestre_inscription inscr, # notes_formsemestre_inscription inscr,
notes_formsemestre formsemestre # notes_formsemestre formsemestre
WHERE # WHERE
inscr.etudid = :etudid # inscr.etudid = :etudid
AND inscr.formsemestre_id = formsemestre.id # AND inscr.formsemestre_id = formsemestre.id
AND formsemestre.semestre_id = :other_semestre_idx # AND formsemestre.semestre_id = :other_semestre_idx
AND ue.formation_id = formsemestre.formation_id # AND ue.formation_id = formsemestre.formation_id
AND ue.niveau_competence_id = :ue_niveau_competence_id # AND ue.niveau_competence_id = :ue_niveau_competence_id
AND ue.semestre_idx = :other_semestre_idx # AND ue.semestre_idx = :other_semestre_idx
""" # """
), # ),
{ # {
"etudid": etud.id, # "etudid": etud.id,
"other_semestre_idx": other_semestre_idx, # "other_semestre_idx": other_semestre_idx,
"ue_niveau_competence_id": ue.niveau_competence_id, # "ue_niveau_competence_id": ue.niveau_competence_id,
}, # },
) # )
rcues = [] # rcues = []
for ue_id, formsemestre_id in cursor: # for ue_id, formsemestre_id in cursor:
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( # RegroupementCoherentUE(
etud, formsemestre, ue, other_formsemestre, other_ue, inscription_etat # 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
return rcues # return rcues
class ApcValidationAnnee(db.Model): class ApcValidationAnnee(db.Model):

View File

@ -150,7 +150,7 @@ class FormSemestre(db.Model):
self.modalite = FormationModalite.DEFAULT_MODALITE self.modalite = FormationModalite.DEFAULT_MODALITE
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>" return f"<{self.__class__.__name__} {self.id} {self.titre_annee()}>"
def to_dict(self, convert_objects=False) -> dict: def to_dict(self, convert_objects=False) -> dict:
"""dict (compatible ScoDoc7). """dict (compatible ScoDoc7).

View File

@ -25,7 +25,7 @@
# #
############################################################################## ##############################################################################
"""Feuille excel pour preparation des jurys """Feuille excel pour préparation des jurys classiques (non BUT)
""" """
import time import time
@ -51,7 +51,9 @@ import sco_version
def feuille_preparation_jury(formsemestre_id): def feuille_preparation_jury(formsemestre_id):
"Feuille excel pour preparation des jurys" """Feuille excel pour préparation des jurys classiques.
Non adaptée pour le BUT.
"""
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
etuds: Identite = nt.get_inscrits(order_by="moy") # tri par moy gen etuds: Identite = nt.get_inscrits(order_by="moy") # tri par moy gen

View File

@ -52,6 +52,7 @@
.but_note { .but_note {
border-right: 1px solid #aaa; border-right: 1px solid #aaa;
padding: 8px; padding: 8px;
position: relative;
} }
.but_annee select { .but_annee select {

View File

@ -12,7 +12,6 @@ body {
font-size: 12pt; font-size: 12pt;
} }
@media print { @media print {
.noprint { .noprint {
display: none; display: none;
@ -3168,7 +3167,8 @@ td.ue_cmp {
color: green; color: green;
} }
td.ue_capitalized { td.ue_capitalized,
.ue_cap {
text-decoration: underline; text-decoration: underline;
} }
@ -3330,6 +3330,42 @@ div.module_check_absences ul {
/* ----------------------------------------------- */ /* ----------------------------------------------- */
.scoplement {
pointer-events: none;
position: absolute;
bottom: 100%;
left: 0;
padding: 8px;
border-radius: 4px;
background: rgb(205, 228, 255);
color: #000;
border: 1px solid rgb(4, 16, 159);
opacity: 0;
display: grid !important;
grid-template-columns: 1fr;
gap: 0 !important;
column-gap: 4px !important;
}
.with_scoplement:hover .scoplement {
opacity: 1;
z-index: 1;
}
.scoplement>div {
text-align: left;
display: inline-block;
white-space: nowrap;
}
.scoplement>div:nth-child(1),
.scoplement>div:nth-child(7) {
margin-bottom: 8px;
}
/* ----------------------------------------------- */
/* ----------------------------- */ /* ----------------------------- */
/* TABLES generees par gen_table */ /* TABLES generees par gen_table */
/* ----------------------------- */ /* ----------------------------- */

View File

@ -2658,7 +2658,7 @@ def formsemestre_saisie_jury(formsemestre_id: int, selected_etudid: int = None):
""" """
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id) read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
if formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0: if formsemestre.formation.is_apc(): # and formsemestre.semestre_id % 2 == 0:
return jury_but_recap.formsemestre_saisie_jury_but( return jury_but_recap.formsemestre_saisie_jury_but(
formsemestre, read_only, selected_etudid=selected_etudid formsemestre, read_only, selected_etudid=selected_etudid
) )