Jury BUT: modification validation année: unqiue sur ref. comp.

This commit is contained in:
Emmanuel Viennet 2023-06-30 09:34:29 +02:00
commit 61b46db4dd
8 changed files with 174 additions and 143 deletions

View File

@ -284,15 +284,11 @@ class DecisionsProposeesAnnee(DecisionsProposees):
# ---- Décision année et autorisation
self.autorisations_recorded = False
"vrai si on a enregistré l'autorisation de passage"
self.validation = (
ApcValidationAnnee.query.filter_by(
etudid=self.etud.id,
ordre=self.annee_but,
)
.join(Formation)
.filter_by(formation_code=self.formsemestre.formation.formation_code)
.first()
)
self.validation = ApcValidationAnnee.query.filter_by(
etudid=self.etud.id,
ordre=self.annee_but,
referentiel_competence_id=self.formsemestre.formation.referentiel_competence_id,
).first()
"Validation actuellement enregistrée pour cette année BUT"
self.code_valide = self.validation.code if self.validation is not None else None
"Le code jury annuel enregistré, ou None"
@ -689,13 +685,13 @@ class DecisionsProposeesAnnee(DecisionsProposees):
self.validation = ApcValidationAnnee(
etudid=self.etud.id,
formsemestre=self.formsemestre_impair,
formation_id=self.formsemestre.formation_id,
referentiel_competence_id=self.formsemestre.formation.referentiel_competence_id,
ordre=self.annee_but,
annee_scolaire=self.annee_scolaire(),
code=code,
)
else: # Update validation année BUT
self.validation.etud = self.etud
assert self.validation.etudid == self.etud.id
self.validation.formsemestre = self.formsemestre_impair
self.validation.formation_id = self.formsemestre.formation_id
self.validation.ordre = self.annee_but
@ -852,13 +848,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
# Efface les validations concernant l'année BUT
# de ce semestre
validations = (
ApcValidationAnnee.query.filter_by(
etudid=self.etud.id,
ordre=self.annee_but,
)
.join(Formation)
.filter_by(formation_code=self.formsemestre.formation.formation_code)
validations = ApcValidationAnnee.query.filter_by(
etudid=self.etud.id,
ordre=self.annee_but,
referentiel_competence_id=self.formsemestre.formation.referentiel_competence_id,
)
for validation in validations:
db.session.delete(validation)
@ -1287,15 +1280,11 @@ class DecisionsProposeesRCUE(DecisionsProposees):
if annee_inferieure < 1:
return
# Garde-fou: Année déjà validée ?
validations_annee: ApcValidationAnnee = (
ApcValidationAnnee.query.filter_by(
etudid=self.etud.id,
ordre=annee_inferieure,
)
.join(Formation)
.filter_by(formation_code=self.deca.formsemestre.formation.formation_code)
.all()
)
validations_annee: ApcValidationAnnee = ApcValidationAnnee.query.filter_by(
etudid=self.etud.id,
ordre=annee_inferieure,
referentiel_competence_id=self.deca.formsemestre.formation.referentiel_competence_id,
).all()
if len(validations_annee) > 1:
log(
f"warning: {len(validations_annee)} validations d'année\n{validations_annee}"
@ -1332,8 +1321,8 @@ class DecisionsProposeesRCUE(DecisionsProposees):
validation_annee = ApcValidationAnnee(
etudid=self.etud.id,
ordre=annee_inferieure,
referentiel_competence_id=self.deca.formsemestre.formation.referentiel_competence_id,
code=sco_codes.ADSUP,
formation_id=self.deca.formsemestre.formation_id,
# met cette validation sur l'année scolaire actuelle, pas la précédente
annee_scolaire=self.deca.formsemestre.annee_scolaire(),
)
@ -1575,16 +1564,11 @@ class DecisionsProposeesUE(DecisionsProposees):
# def est_annee_validee(self, ordre: int) -> bool:
# """Vrai si l'année BUT ordre est validée"""
# # On cherche les validations d'annee avec le même
# # code formation que nous.
# return (
# ApcValidationAnnee.query.filter_by(
# etudid=self.etud.id,
# ordre=ordre,
# )
# .join(Formation)
# .filter(
# Formation.formation_code == self.formsemestre.formation.formation_code
# referentiel_competence_id=self.formsemestre.formation.referentiel_competence_id
# )
# .count()
# > 0

View File

@ -231,12 +231,11 @@ def erase_decisions_annee_formation(
.all()
)
# Année BUT
validations += (
ApcValidationAnnee.query.filter_by(etudid=etud.id, ordre=annee)
.join(Formation)
.filter_by(formation_code=formation.formation_code)
.all()
)
validations += ApcValidationAnnee.query.filter_by(
etudid=etud.id,
ordre=annee,
referentiel_competence_id=formation.referentiel_competence_id,
).all()
# Autorisations vers les semestres suivants ceux de l'année:
validations += (
ScolarAutorisationInscription.query.filter_by(

View File

@ -337,17 +337,15 @@ class ResultatsSemestreBUT(NotesTableCompat):
if self.validations_annee:
return self.validations_annee
annee_but = (self.formsemestre.semestre_id + 1) // 2
validations = (
ApcValidationAnnee.query.filter_by(ordre=annee_but)
.join(Formation)
.filter_by(formation_code=self.formsemestre.formation.formation_code)
.join(
FormSemestreInscription,
db.and_(
FormSemestreInscription.etudid == ApcValidationAnnee.etudid,
FormSemestreInscription.formsemestre_id == self.formsemestre.id,
),
)
validations = ApcValidationAnnee.query.filter_by(
ordre=annee_but,
referentiel_competence_id=self.formsemestre.formation.referentiel_competence_id,
).join(
FormSemestreInscription,
db.and_(
FormSemestreInscription.etudid == ApcValidationAnnee.etudid,
FormSemestreInscription.formsemestre_id == self.formsemestre.id,
),
)
validation_by_etud = {}
for validation in validations:

View File

@ -94,6 +94,11 @@ class ApcReferentielCompetences(db.Model, XMLModel):
backref="referentiel_competence",
order_by="Formation.acronyme, Formation.version",
)
validations_annee = db.relationship(
"ApcValidationAnnee",
backref="referentiel_competence",
lazy="dynamic",
)
def __repr__(self):
return f"<ApcReferentielCompetences {self.id} {self.specialite!r} {self.departement!r}>"

View File

@ -2,8 +2,6 @@
"""Décisions de jury (validations) des RCUE et années du BUT
"""
from typing import Union
from app import db
from app.models import CODE_STR_LEN
@ -106,71 +104,14 @@ class ApcValidationRCUE(db.Model):
}
# unused
# def find_rcues(
# formsemestre: FormSemestre, ue: UniteEns, etud: Identite, inscription_etat: str
# ) -> list[RegroupementCoherentUE]:
# """Les RCUE (niveau de compétence) à considérer pour cet étudiant dans
# ce semestre pour cette UE.
# 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.
# Résultat: la liste peut être vide.
# """
# if (ue.niveau_competence is None) or (ue.semestre_idx is None):
# return []
# if ue.semestre_idx % 2: # S1, S3, S5
# other_semestre_idx = ue.semestre_idx + 1
# else:
# other_semestre_idx = ue.semestre_idx - 1
# cursor = db.session.execute(
# text(
# """SELECT
# ue.id, formsemestre.id
# FROM
# notes_ue ue,
# notes_formsemestre_inscription inscr,
# notes_formsemestre formsemestre
# WHERE
# inscr.etudid = :etudid
# AND inscr.formsemestre_id = formsemestre.id
# AND formsemestre.semestre_id = :other_semestre_idx
# AND ue.formation_id = formsemestre.formation_id
# AND ue.niveau_competence_id = :ue_niveau_competence_id
# AND ue.semestre_idx = :other_semestre_idx
# """
# ),
# {
# "etudid": etud.id,
# "other_semestre_idx": other_semestre_idx,
# "ue_niveau_competence_id": ue.niveau_competence_id,
# },
# )
# rcues = []
# for ue_id, formsemestre_id in cursor:
# other_ue = UniteEns.query.get(ue_id)
# other_formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# rcues.append(
# RegroupementCoherentUE(
# etud, formsemestre, ue, other_formsemestre, other_ue, inscription_etat
# )
# )
# # safety check: 1 seul niveau de comp. concerné:
# assert len({rcue.ue_1.niveau_competence_id for rcue in rcues}) == 1
# return rcues
class ApcValidationAnnee(db.Model):
"""Validation des années du BUT"""
__tablename__ = "apc_validation_annee"
# Assure unicité de la décision:
__table_args__ = (db.UniqueConstraint("etudid", "annee_scolaire", "ordre"),)
__table_args__ = (
db.UniqueConstraint("etudid", "ordre", "referentiel_competence_id"),
)
id = db.Column(db.Integer, primary_key=True)
etudid = db.Column(
db.Integer,
@ -184,10 +125,8 @@ class ApcValidationAnnee(db.Model):
db.Integer, db.ForeignKey("notes_formsemestre.id"), nullable=True
)
"le semestre origine, normalement l'IMPAIR (le 1er) de l'année"
formation_id = db.Column(
db.Integer,
db.ForeignKey("notes_formations.id"),
nullable=False,
referentiel_competence_id = db.Column(
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
)
annee_scolaire = db.Column(db.Integer, nullable=False) # eg 2021
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
@ -207,17 +146,22 @@ class ApcValidationAnnee(db.Model):
"dict pour bulletins"
return {
"annee_scolaire": self.annee_scolaire,
"date": self.date.isoformat(),
"date": self.date.isoformat() if self.date else "",
"code": self.code,
"ordre": self.ordre,
}
def html(self) -> str:
"Affichage html"
date_str = (
f"""le {self.date.strftime("%d/%m/%Y")} à {self.date.strftime("%Hh%M")}"""
if self.date
else "(sans date)"
)
return f"""Validation <b>année BUT{self.ordre}</b> émise par
{self.formsemestre.html_link_status() if self.formsemestre else "-"}
: <b>{self.code}</b>
le {self.date.strftime("%d/%m/%Y")} à {self.date.strftime("%Hh%M")}
{date_str}
"""
@ -259,15 +203,11 @@ def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
decisions["descr_decisions_rcue"] = ""
decisions["descr_decisions_niveaux"] = ""
# --- Année: prend la validation pour l'année scolaire de ce semestre
validation = (
ApcValidationAnnee.query.filter_by(
etudid=etud.id,
annee_scolaire=formsemestre.annee_scolaire(),
)
.join(Formation)
.filter(Formation.formation_code == formsemestre.formation.formation_code)
.first()
)
validation = ApcValidationAnnee.query.filter_by(
etudid=etud.id,
annee_scolaire=formsemestre.annee_scolaire(),
referentiel_competence_id=formsemestre.formation.referentiel_competence_id,
).first()
if validation:
decisions["decision_annee"] = validation.to_dict_bul()
else:

View File

@ -867,15 +867,12 @@ class FormSemestre(db.Model):
.order_by(UniteEns.numero)
.all()
)
vals_annee = ( # issues de ce formsemestre seulement
vals_annee = ( # issues de cette année scolaire seulement
ApcValidationAnnee.query.filter_by(
etudid=etudid,
annee_scolaire=self.annee_scolaire(),
)
.join(ApcValidationAnnee.formsemestre)
.join(FormSemestre.formation)
.filter(Formation.formation_code == self.formation.formation_code)
.all()
referentiel_competence_id=self.formation.referentiel_competence_id,
).all()
)
H = []
for vals in (vals_sem, vals_ues, vals_rcues, vals_annee):

View File

@ -491,15 +491,11 @@ class ApoEtud(dict):
# ne trouve pas de semestre impair
self.validation_annee_but = None
return
self.validation_annee_but: ApcValidationAnnee = (
ApcValidationAnnee.query.filter_by(
formsemestre_id=formsemestre.id,
etudid=self.etud["etudid"],
formation_id=self.cur_sem[
"formation_id"
], # XXX utiliser formation_code
).first()
)
self.validation_annee_but: ApcValidationAnnee = ApcValidationAnnee.query.filter_by(
formsemestre_id=formsemestre.id,
etudid=self.etud["etudid"],
referentiel_competence_id=self.cur_res.formsemestre.formation.referentiel_competence_id,
).first()
self.is_nar = (
self.validation_annee_but and self.validation_annee_but.code == NAR
)

View File

@ -0,0 +1,112 @@
"""Change ApcValidationAnnee
Revision ID: 829683efddc4
Revises: c701224fa255
Create Date: 2023-06-28 09:47:16.591028
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.orm import sessionmaker # added by ev
# revision identifiers, used by Alembic.
revision = "829683efddc4"
down_revision = "c701224fa255"
branch_labels = None
depends_on = None
Session = sessionmaker()
# Voir https://stackoverflow.com/questions/24082542/check-if-a-table-column-exists-in-the-database-using-sqlalchemy-and-alembic
from sqlalchemy import inspect
def column_exists(table_name, column_name):
bind = op.get_context().bind
insp = inspect(bind)
columns = insp.get_columns(table_name)
return any(c["name"] == column_name for c in columns)
def upgrade():
if column_exists("apc_validation_annee", "referentiel_competence_id"):
return # utile durant developpement
# Enleve la contrainte erronée
with op.batch_alter_table("apc_validation_annee", schema=None) as batch_op:
batch_op.drop_constraint(
"apc_validation_annee_etudid_annee_scolaire_ordre_key", type_="unique"
)
# Ajoute colonne referentiel, nullable pour l'instant
batch_op.add_column(
sa.Column("referentiel_competence_id", sa.Integer(), nullable=True)
)
# Affecte le referentiel des anciennes validations
bind = op.get_bind()
session = Session(bind=bind)
session.execute(
sa.text(
"""
UPDATE apc_validation_annee AS a
SET referentiel_competence_id = (
SELECT f.referentiel_competence_id
FROM notes_formations f
WHERE f.id = a.formation_id
)
"""
)
)
# En principe, on n'a pas pu entrer de validation sur des formations sans referentiel
# par prudence, on les supprime avant d'ajouter la contrainte
session.execute(
sa.text(
"DELETE FROM apc_validation_annee WHERE referentiel_competence_id is NULL"
)
)
op.alter_column(
"apc_validation_annee",
"referentiel_competence_id",
nullable=False,
)
op.create_foreign_key(
"apc_validation_annee_refcomp_fkey",
"apc_validation_annee",
"apc_referentiel_competences",
["referentiel_competence_id"],
["id"],
)
# Efface les validations d'année dupliquées
# (garde le premier code dans l'ordre alphabétique... mieux que rien)
session.execute(
sa.text(
"""
DELETE FROM apc_validation_annee t1
WHERE EXISTS (
SELECT 1
FROM apc_validation_annee t2
WHERE t1.etudid = t2.etudid
AND t1.referentiel_competence_id = t2.referentiel_competence_id
AND t1.ordre = t2.ordre
AND t1.code > t2.code
);
"""
)
)
# Et ajoute la contrainte unicité de décision année par étudiant/ref. comp.:
op.create_unique_constraint(
"apc_validation_annee_etudid_ordre_refcomp_key",
"apc_validation_annee",
["etudid", "ordre", "referentiel_competence_id"],
)
op.drop_column("apc_validation_annee", "formation_id")
def downgrade():
# Se contente de ré-ajouter la colonne formation_id sans re-générer son contenu
with op.batch_alter_table("apc_validation_annee", schema=None) as batch_op:
# batch_op.drop_constraint(
# "apc_validation_annee_etudid_ordre_refcomp_key", type_="unique"
# )
# batch_op.drop_column("referentiel_competence_id")
batch_op.add_column(sa.Column("formation_id", sa.Integer(), nullable=True))