Suppressions de décisions de jury

This commit is contained in:
Emmanuel Viennet 2023-06-18 09:37:13 +02:00 committed by iziram
parent 9ee36f5eba
commit fbca147d7e
17 changed files with 276 additions and 49 deletions

View File

@ -75,11 +75,9 @@ from app.comp.res_but import ResultatsSemestreBUT
from app.comp import res_sem from app.comp import res_sem
from app.models.but_refcomp import ( from app.models.but_refcomp import (
ApcAnneeParcours,
ApcCompetence, ApcCompetence,
ApcNiveau, ApcNiveau,
ApcParcours, ApcParcours,
ApcParcoursNiveauCompetence,
) )
from app.models import Scolog, ScolarAutorisationInscription from app.models import Scolog, ScolarAutorisationInscription
from app.models.but_validations import ( from app.models.but_validations import (
@ -89,7 +87,7 @@ from app.models.but_validations import (
) )
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.formations import Formation from app.models.formations import Formation
from app.models.formsemestre import FormSemestre, FormSemestreInscription from app.models.formsemestre import FormSemestre
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.models.validations import ScolarFormSemestreValidation from app.models.validations import ScolarFormSemestreValidation
from app.scodoc import sco_cache from app.scodoc import sco_cache
@ -473,7 +471,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
semestre_idx=formsemestre.semestre_id, semestre_idx=formsemestre.semestre_id,
formation_id=formsemestre.formation.id)}"> formation_id=formsemestre.formation.id)}">
{formsemestre.formation.to_html()} ({ {formsemestre.formation.html()} ({
formsemestre.formation.id})</a> formsemestre.formation.id})</a>
</li> </li>
</ul> </ul>
@ -902,7 +900,8 @@ class DecisionsProposeesAnnee(DecisionsProposees):
pour cette année: décisions d'UE, de RCUE, d'année, pour cette année: décisions d'UE, de RCUE, d'année,
et autorisations d'inscription émises. et autorisations d'inscription émises.
Efface même si étudiant DEM ou DEF. Efface même si étudiant DEM ou DEF.
Si à cheval, n'efface que pour le semestre d'origine du deca. Si à cheval ou only_one_sem, n'efface que les décisions UE et les
autorisations de passage du semestre d'origine du deca.
(commite la session.) (commite la session.)
""" """
if only_one_sem or self.a_cheval: if only_one_sem or self.a_cheval:

View File

@ -246,7 +246,7 @@ def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
scoplement = ( scoplement = (
f"""<div class="scoplement">{ f"""<div class="scoplement">{
dec_rcue.validation.to_html() dec_rcue.validation.html()
}</div>""" }</div>"""
if dec_rcue.validation if dec_rcue.validation
else "" else ""

View File

@ -10,8 +10,17 @@ import pandas as pd
import sqlalchemy as sa import sqlalchemy as sa
from app import db from app import db
from app.models import FormSemestre, Identite, ScolarFormSemestreValidation, UniteEns
from app.comp.res_cache import ResultatsCache from app.comp.res_cache import ResultatsCache
from app.models import (
ApcValidationAnnee,
ApcValidationRCUE,
Formation,
FormSemestre,
Identite,
ScolarAutorisationInscription,
ScolarFormSemestreValidation,
UniteEns,
)
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
@ -81,7 +90,7 @@ class ValidationsSemestre(ResultatsCache):
# UEs: { etudid : { ue_id : {"code":, "ects":, "event_date":} }} # UEs: { etudid : { ue_id : {"code":, "ects":, "event_date":} }}
decisions_jury_ues = {} decisions_jury_ues = {}
# Parcours les décisions d'UE: # Parcoure les décisions d'UE:
for decision in ( for decision in (
decisions_jury_q.filter(db.text("ue_id is not NULL")) decisions_jury_q.filter(db.text("ue_id is not NULL"))
.join(UniteEns) .join(UniteEns)
@ -172,3 +181,80 @@ def formsemestre_get_ue_capitalisees(formsemestre: FormSemestre) -> pd.DataFrame
with db.engine.begin() as connection: with db.engine.begin() as connection:
df = pd.read_sql_query(query, connection, params=params, index_col="etudid") df = pd.read_sql_query(query, connection, params=params, index_col="etudid")
return df return df
def erase_decisions_annee_formation(
etud: Identite, formation: Formation, annee: int, delete=False
) -> list:
"""Efface toutes les décisions de jury de l'étudiant dans les formations de même code
que celle donnée pour cette année de la formation:
UEs, RCUEs de l'année BUT, année BUT, passage vers l'année suivante.
Ne considère pas l'origine de la décision.
annee: entier, 1, 2, 3, ...
Si delete est faux, renvoie la liste des validations qu'il faudrait effacer, sans y toucher.
"""
sem1, sem2 = annee * 2 - 1, annee * 2
# UEs
validations = (
ScolarFormSemestreValidation.query.filter_by(etudid=etud.id)
.join(UniteEns)
.filter(db.or_(UniteEns.semestre_idx == sem1, UniteEns.semestre_idx == sem2))
.join(Formation)
.filter_by(formation_code=formation.formation_code)
.order_by(
UniteEns.acronyme, UniteEns.numero
) # acronyme d'abord car 2 semestres
.all()
)
# RCUEs (a priori inutile de matcher sur l'ue2_id)
validations += (
ApcValidationRCUE.query.filter_by(etudid=etud.id)
.join(UniteEns, UniteEns.id == ApcValidationRCUE.ue1_id)
.filter_by(semestre_idx=sem1)
.join(Formation)
.filter_by(formation_code=formation.formation_code)
.order_by(UniteEns.acronyme, UniteEns.numero)
.all()
)
# Validation de semestres classiques
validations += (
ScolarFormSemestreValidation.query.filter_by(etudid=etud.id, ue_id=None)
.join(
FormSemestre,
FormSemestre.id == ScolarFormSemestreValidation.formsemestre_id,
)
.filter(
db.or_(FormSemestre.semestre_id == sem1, FormSemestre.semestre_id == sem2)
)
.join(Formation)
.filter_by(formation_code=formation.formation_code)
.all()
)
# Année BUT
validations += (
ApcValidationAnnee.query.filter_by(etudid=etud.id, ordre=annee)
.join(Formation)
.filter_by(formation_code=formation.formation_code)
.all()
)
# Autorisations vers les semestres suivants ceux de l'année:
validations += (
ScolarAutorisationInscription.query.filter_by(
etudid=etud.id, formation_code=formation.formation_code
)
.filter(
db.or_(
ScolarAutorisationInscription.semestre_id == sem1 + 1,
ScolarAutorisationInscription.semestre_id == sem2 + 1,
)
)
.all()
)
if delete:
for validation in validations:
db.session.delete(validation)
db.session.commit()
sco_cache.invalidate_formsemestre_etud(etud)
return []
return validations

View File

@ -66,7 +66,7 @@ class ApcValidationRCUE(db.Model):
return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: { return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: {
self.code} enregistrée le {self.date.strftime("%d/%m/%Y")}""" self.code} enregistrée le {self.date.strftime("%d/%m/%Y")}"""
def to_html(self) -> str: def html(self) -> str:
"description en HTML" "description en HTML"
return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}:
<b>{self.code}</b> <b>{self.code}</b>
@ -348,6 +348,13 @@ class ApcValidationAnnee(db.Model):
"ordre": self.ordre, "ordre": self.ordre,
} }
def html(self) -> str:
"Affichage html"
return f"""Validation <b>année BUT{self.ordre}</b> émise par
{self.formsemestre.html_link_status() if self.formsemestre else "-"}
le {self.date.strftime("%d/%m/%Y")} à {self.date.strftime("%Hh%M")}
"""
def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict: def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
""" """

View File

@ -60,7 +60,7 @@ class Formation(db.Model):
return f"""<{self.__class__.__name__}(id={self.id}, dept_id={ return f"""<{self.__class__.__name__}(id={self.id}, dept_id={
self.dept_id}, acronyme={self.acronyme!r}, version={self.version})>""" self.dept_id}, acronyme={self.acronyme!r}, version={self.version})>"""
def to_html(self) -> str: def html(self) -> str:
"titre complet pour affichage" "titre complet pour affichage"
return f"""Formation {self.titre} ({self.acronyme}) [version {self.version}] code {self.formation_code}""" return f"""Formation {self.titre} ({self.acronyme}) [version {self.version}] code {self.formation_code}"""

View File

@ -16,7 +16,7 @@ from operator import attrgetter
from flask_login import current_user from flask_login import current_user
from flask import flash, g from flask import flash, g, url_for
from sqlalchemy.sql import text from sqlalchemy.sql import text
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -165,6 +165,14 @@ class FormSemestre(db.Model):
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} {self.id} {self.titre_annee()}>" return f"<{self.__class__.__name__} {self.id} {self.titre_annee()}>"
def html_link_status(self) -> str:
"html link to status page"
return f"""<a class="stdlink" href="{
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept,
formsemestre_id=self.id,)
}">{self.titre_mois()}</a>
"""
@classmethod @classmethod
def get_formsemestre(cls, formsemestre_id: int) -> "FormSemestre": def get_formsemestre(cls, formsemestre_id: int) -> "FormSemestre":
""" "FormSemestre ou 404, cherche uniquement dans le département courant""" """ "FormSemestre ou 404, cherche uniquement dans le département courant"""

View File

@ -59,13 +59,16 @@ class ScolarFormSemestreValidation(db.Model):
) )
def __repr__(self): def __repr__(self):
return f"{self.__class__.__name__}(sem={self.formsemestre_id}, etuid={self.etudid}, code={self.code}, ue={self.ue}, moy_ue={self.moy_ue})" return f"""{self.__class__.__name__}(sem={self.formsemestre_id}, etuid={
self.etudid}, code={self.code}, ue={self.ue}, moy_ue={self.moy_ue})"""
def __str__(self): def __str__(self):
if self.ue_id: if self.ue_id:
# Note: si l'objet vient d'être créé, ue_id peut exister mais pas ue ! # Note: si l'objet vient d'être créé, ue_id peut exister mais pas ue !
return f"""décision sur UE {self.ue.acronyme if self.ue else self.ue_id}: {self.code}""" return f"""décision sur UE {self.ue.acronyme if self.ue else self.ue_id
return f"""décision sur semestre {self.formsemestre.titre_mois()} du {self.event_date.strftime("%d/%m/%Y")}""" }: {self.code}"""
return f"""décision sur semestre {self.formsemestre.titre_mois()} du {
self.event_date.strftime("%d/%m/%Y")}"""
def to_dict(self) -> dict: def to_dict(self) -> dict:
"as a dict" "as a dict"
@ -73,6 +76,20 @@ class ScolarFormSemestreValidation(db.Model):
d.pop("_sa_instance_state", None) d.pop("_sa_instance_state", None)
return d return d
def html(self) -> str:
"Affichage html"
if self.ue_id is not None:
return f"""Validation de l'UE {self.ue.acronyme}
(<b>{self.code}</b>
le {self.event_date.strftime("%d/%m/%Y")} à {self.event_date.strftime("%Hh%M")})
"""
else:
return f"""Validation du semestre S{
self.formsemestre.semestre_id if self.formsemestre else "?"}
(<b>{self.code}</b>
le {self.event_date.strftime("%d/%m/%Y")} à {self.event_date.strftime("%Hh%M")})
"""
class ScolarAutorisationInscription(db.Model): class ScolarAutorisationInscription(db.Model):
"""Autorisation d'inscription dans un semestre""" """Autorisation d'inscription dans un semestre"""
@ -93,6 +110,7 @@ class ScolarAutorisationInscription(db.Model):
db.Integer, db.Integer,
db.ForeignKey("notes_formsemestre.id"), db.ForeignKey("notes_formsemestre.id"),
) )
origin_formsemestre = db.relationship("FormSemestre", lazy="select", uselist=False)
def __repr__(self) -> str: def __repr__(self) -> str:
return f"""{self.__class__.__name__}(id={self.id}, etudid={ return f"""{self.__class__.__name__}(id={self.id}, etudid={
@ -104,6 +122,15 @@ class ScolarAutorisationInscription(db.Model):
d.pop("_sa_instance_state", None) d.pop("_sa_instance_state", None)
return d return d
def html(self) -> str:
"Affichage html"
return f"""Autorisation de passage vers <b>S{self.semestre_id}</b> émise par
{self.origin_formsemestre.html_link_status()
if self.origin_formsemestre
else "-"}
le {self.date.strftime("%d/%m/%Y")} à {self.date.strftime("%Hh%M")}
"""
@classmethod @classmethod
def autorise_etud( def autorise_etud(
cls, cls,

View File

@ -315,6 +315,19 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids) SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
def invalidate_formsemestre_etud(etud: "Identite"):
"""Invalide tous les formsemestres auxquels l'étudiant est inscrit"""
from app.models import FormSemestre, FormSemestreInscription
inscriptions = (
FormSemestreInscription.query.filter_by(etudid=etud.id)
.join(FormSemestre)
.filter_by(dept_id=g.scodoc_dept_id)
)
for inscription in inscriptions:
invalidate_formsemestre(inscription.formsemestre_id)
class DeferredSemCacheManager: class DeferredSemCacheManager:
"""Contexte pour effectuer des opérations indépendantes dans la """Contexte pour effectuer des opérations indépendantes dans la
même requete qui invalident le cache. Par exemple, quand on inscrit même requete qui invalident le cache. Par exemple, quand on inscrit

View File

@ -757,7 +757,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
], ],
page_title=f"Programme {formation.acronyme} v{formation.version}", page_title=f"Programme {formation.acronyme} v{formation.version}",
), ),
f"""<h2>{formation.to_html()} {lockicon} f"""<h2>{formation.html()} {lockicon}
</h2> </h2>
""", """,
] ]
@ -1010,12 +1010,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
<p><ul>""" <p><ul>"""
) )
for formsemestre in formsemestres: for formsemestre in formsemestres:
H.append( H.append(f"""<li>{formsemestre.html_link_status()}""")
f"""<li><a class="stdlink" href="{
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id
)}">{formsemestre.titre_mois()}</a>"""
)
if not formsemestre.etat: if not formsemestre.etat:
H.append(" [verrouillé]") H.append(" [verrouillé]")
else: else:

View File

@ -142,7 +142,7 @@ def formsemestre_recapcomplet(
H.append( H.append(
'<select name="tabformat" onchange="document.f.submit()" class="noprint">' '<select name="tabformat" onchange="document.f.submit()" class="noprint">'
) )
for (fmt, label) in ( for fmt, label in (
("html", "Tableau"), ("html", "Tableau"),
("evals", "Avec toutes les évaluations"), ("evals", "Avec toutes les évaluations"),
("xlsx", "Excel (non formaté)"), ("xlsx", "Excel (non formaté)"),
@ -186,7 +186,7 @@ def formsemestre_recapcomplet(
</li> </li>
<li><a class="stdlink" href="{url_for('notes.formsemestre_jury_but_erase', <li><a class="stdlink" href="{url_for('notes.formsemestre_jury_but_erase',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, only_one_sem=1) scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, only_one_sem=1)
}">Effacer <em>toutes</em> les décisions de jury (BUT) du semestre</a> }">Effacer <em>toutes</em> les décisions de jury BUT issues de ce semestre</a>
</li> </li>
""" """
) )

View File

@ -145,12 +145,7 @@ class SemSet(dict):
# Construction du ou des lien(s) vers le semestre # Construction du ou des lien(s) vers le semestre
self["semlinks"] = [ self["semlinks"] = [
f"""<a class="stdlink" href="{ formsemestre.html_link_status() for formsemestre in self.formsemestres
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id)
}">{formsemestre.titre_annee()}</a>
"""
for formsemestre in self.formsemestres
] ]
self["semtitles_str"] = "<br>".join(self["semlinks"]) self["semtitles_str"] = "<br>".join(self["semlinks"])

View File

@ -15,7 +15,6 @@
padding-bottom: 0px; padding-bottom: 0px;
padding-left: 16px; padding-left: 16px;
padding-right: 0px; padding-right: 0px;
background: #FFF; background: #FFF;
border: 1px solid #aaa; border: 1px solid #aaa;
border-radius: 8px; border-radius: 8px;
@ -40,3 +39,9 @@ div.code_rcue {
padding-bottom: 8px; padding-bottom: 8px;
position: relative; position: relative;
} }
div.code_jury {
padding-right: 4px;
padding-left: 4px;
width: 64px;
}

View File

@ -15,11 +15,9 @@
<input type="hidden" name="etudid" value="{{etud.id}}"></input> <input type="hidden" name="etudid" value="{{etud.id}}"></input>
<input type="hidden" name="format" value="{{format}}"></input> <input type="hidden" name="format" value="{{format}}"></input>
Bulletin Bulletin
<span class="bull_liensemestre"><a href="{{ <span class="bull_liensemestre">
url_for("notes.formsemestre_status", {{formsemestre.html_link_status() | safe}}
scodoc_dept=g.scodoc_dept, </span>
formsemestre_id=formsemestre.id)}}">{{formsemestre.titre_mois()
}}</a></span>
<div> <div>
<em>établi le {{time.strftime("%d/%m/%Y à %Hh%M")}} (notes sur 20)</em> <em>établi le {{time.strftime("%d/%m/%Y à %Hh%M")}} (notes sur 20)</em>

View File

@ -15,14 +15,16 @@
<div class="code_jury">{{validation.code}}</div> <div class="code_jury">{{validation.code}}</div>
<div class="scoplement"> <div class="scoplement">
<div>{{validation.ue1.acronyme}} - {{validation.ue2.acronyme}}</div> <div>{{validation.ue1.acronyme}} - {{validation.ue2.acronyme}}</div>
<div>Jury de {{validation.formsemestre.titre_annee()}}</div> <div>Jury de {{validation.formsemestre.titre_annee() if validation.formsemestre else "-"}}</div>
<div>enregistré le {{ <div>enregistré le {{
validation.date.strftime("%d/%m/%Y à %H:%M") validation.date.strftime("%d/%m/%Y à %H:%M")
}}</div> }}</div>
</div> </div>
</div> </div>
{% else %} {% else %}
- <div class="code_rcue">
<div class="code_jury">-</div>
</div>
{%endif%} {%endif%}
</div> </div>
{% endfor %} {% endfor %}

View File

@ -44,7 +44,7 @@
{%- endmacro %} {%- endmacro %}
{% block app_content %} {% block app_content %}
<h2>{{formation.to_html()}}</h2> <h2>{{formation.html()}}</h2>
{# Liens vers les différents parcours #} {# Liens vers les différents parcours #}
<div class="les_parcours"> <div class="les_parcours">
@ -127,7 +127,7 @@ Choisissez un parcours...
d'associer à chaque semestre d'un niveau de compétence une UE de la formation d'associer à chaque semestre d'un niveau de compétence une UE de la formation
<a class="stdlink" <a class="stdlink"
href="{{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept, formation_id=formation.id ) href="{{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept, formation_id=formation.id )
}}">{{formation.to_html()}} }}">{{formation.html()}}
</a>.</p> </a>.</p>
<p>Le symbole <span class="parc">TC</span> désigne un niveau du tronc commun <p>Le symbole <span class="parc">TC</span> désigne un niveau du tronc commun

View File

@ -0,0 +1,41 @@
{% extends 'base.j2' %}
{% block app_content %}
{% if not validations %}
<p>Aucune validation de jury enregistrée pour <b>{{etud.nom_disp()}}</b> sur
<b>l'année {{annee}}</b>
de la formation <em>{{ formation.html() }}</em>
</p>
<div style="margin-top: 16px;">
<a class="stdlink" href="{{ cancel_url }}">continuer</a>
</div>
{% else %}
<h2>Effacer les décisions de jury pour l'année {{annee}} de {{etud.nom_disp()}} ?</h2>
<p class="help">Affectera toutes les décisions concernant l'année {{annee}} de la formation,
quelle que soit leur origine.</p>
<p>Les décisions concernées sont:</p>
<ul>
{% for validation in validations %}
<li>{{ validation.html() | safe}}
</li>
{% endfor %}
</ul>
<div style="margin-top: 16px;">
<form method="post">
<input type="submit" value="Effacer ces décisions" />
{% if cancel_url %}
<input type="button" value="Annuler" style="margin-left: 16px;"
onClick="document.location='{{ cancel_url }}';" />
{% endif %}
</form>
</div>
{% endif %}
{% endblock %}

View File

@ -48,9 +48,9 @@ from app.but.forms import jury_but_forms
from app.but import jury_but_pv from app.but import jury_but_pv
from app.but import jury_but_view from app.but import jury_but_view
from app.comp import res_sem from app.comp import jury, res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import ScolarAutorisationInscription, ScolarNews, Scolog from app.models import Formation, ScolarAutorisationInscription, ScolarNews, Scolog
from app.models.but_refcomp import ApcNiveau, ApcParcours from app.models.but_refcomp import ApcNiveau, ApcParcours
from app.models.config import ScoDocSiteConfig from app.models.config import ScoDocSiteConfig
from app.models.etudiants import Identite from app.models.etudiants import Identite
@ -2494,7 +2494,19 @@ def formsemestre_validation_but(
erase_span = f"""<a href="{ erase_span = f"""<a href="{
url_for("notes.formsemestre_jury_but_erase", url_for("notes.formsemestre_jury_but_erase",
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id, scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id,
etudid=deca.etud.id)}" class="stdlink">effacer décisions</a>""" etudid=deca.etud.id)}" class="stdlink"
title="efface décisions issues des jurys de cette année"
>effacer décisions</a>
<a style="margin-left: 16px;" class="stdlink"
href="{
url_for("notes.erase_decisions_annee_formation",
scodoc_dept=g.scodoc_dept, formation_id=deca.formsemestre.formation.id,
etudid=deca.etud.id, annee=deca.annee_but)}"
title="efface toutes décisions concernant le BUT{deca.annee_but}
de cet étudiant (même extérieures ou issues d'un redoublement)"
>effacer toutes ses décisions de BUT{deca.annee_but}</a>
"""
H.append( H.append(
f"""<div class="but_settings"> f"""<div class="but_settings">
<input type="checkbox" onchange="enable_manual_codes(this)"> <input type="checkbox" onchange="enable_manual_codes(this)">
@ -2815,15 +2827,15 @@ def formsemestre_saisie_jury(formsemestre_id: int, selected_etudid: int = None):
) )
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
def formsemestre_jury_but_erase( def formsemestre_jury_but_erase(formsemestre_id: int, etudid: int = None):
formsemestre_id: int, etudid: int = None, only_one_sem=False
):
"""Supprime la décision de jury BUT pour cette année. """Supprime la décision de jury BUT pour cette année.
Si only_one_sem, n'efface que pour le formsemestre indiqué, pas les deux de l'année.
Si l'étudiant n'est pas spécifié, efface les décisions de tous les inscrits. Si l'étudiant n'est pas spécifié, efface les décisions de tous les inscrits.
Si only_one_sem, n'efface que pour le formsemestre indiqué, pas les deux de l'année.
""" """
only_one_sem = int(request.args.get("only_one_sem") or False) only_one_sem = int(request.args.get("only_one_sem") or False)
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre: FormSemestre = FormSemestre.query.filter_by(
id=formsemestre_id, dept_id=g.scodoc_dept_id
).first_or_404()
if not formsemestre.can_edit_jury(): if not formsemestre.can_edit_jury():
raise ScoPermissionDenied( raise ScoPermissionDenied(
dest_url=url_for( dest_url=url_for(
@ -2881,14 +2893,53 @@ def formsemestre_jury_but_erase(
if only_one_sem if only_one_sem
else """Les validations de toutes les UE, RCUE (compétences) et année else """Les validations de toutes les UE, RCUE (compétences) et année
issues de cette année scolaire seront effacées. issues de cette année scolaire seront effacées.
Les décisions des années scolaires précédentes ne seront pas modifiées.
""" """
) )
+ """<div class="warning">Cette opération est irréversible !</div>""", + """
<p>Les décisions des années scolaires précédentes ne seront pas modifiées.</p>
<div class="warning">Cette opération est irréversible !</div>
""",
cancel_url=dest_url, cancel_url=dest_url,
) )
@bp.route(
"/erase_decisions_annee_formation/<int:etudid>/<int:formation_id>/<int:annee>",
methods=["GET", "POST"],
)
@scodoc
@permission_required(Permission.ScoEtudInscrit)
def erase_decisions_annee_formation(etudid: int, formation_id: int, annee: int):
"""Efface toute les décisions d'une année pour cet étudiant"""
etud: Identite = Identite.query.get_or_404(etudid)
formation: Formation = Formation.query.filter_by(
id=formation_id, dept_id=g.scodoc_dept_id
).first_or_404()
if request.method == "POST":
jury.erase_decisions_annee_formation(etud, formation, annee, delete=True)
flash("Décisions de jury effacées")
return redirect(
url_for(
"scolar.ficheEtud",
scodoc_dept=g.scodoc_dept,
etudid=etud.id,
)
)
validations = jury.erase_decisions_annee_formation(etud, formation, annee)
return render_template(
"jury/erase_decisions_annee_formation.j2",
annee=annee,
cancel_url=url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id
),
etud=etud,
formation=formation,
validations=validations,
sco=ScoData(),
title=f"Effacer décisions de jury {etud.nom} - année {annee}",
)
sco_publish( sco_publish(
"/formsemestre_lettres_individuelles", "/formsemestre_lettres_individuelles",
sco_pv_forms.formsemestre_lettres_individuelles, sco_pv_forms.formsemestre_lettres_individuelles,