From ce541d18708dd1a689a6611e3b7d3716d2cb6ebe Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 9 Nov 2022 12:50:10 +0100 Subject: [PATCH] =?UTF-8?q?WIP:=20param=C3=A9trage=20dates=20antipodiques?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/formsemestre.py | 28 ++++++---- app/scodoc/sco_etape_bilan.py | 33 ++++++------ app/scodoc/sco_formsemestre.py | 53 ++++++++++--------- app/scodoc/sco_moduleimpl_status.py | 12 +++-- app/scodoc/sco_semset.py | 12 ++--- app/scodoc/sco_utils.py | 26 ++++----- .../versions/5542cac8c34a_semset_periode.py | 36 +++++++++++++ .../b9aadc10227f_module_type_non_null.py | 1 - 8 files changed, 122 insertions(+), 79 deletions(-) create mode 100644 migrations/versions/5542cac8c34a_semset_periode.py diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index ecfc246c12..6d60bd5dee 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -358,29 +358,39 @@ class FormSemestre(db.Model): """Test si sem est entièrement sur la même année scolaire. (ce n'est pas obligatoire mais si ce n'est pas le cas les exports Apogée risquent de mal fonctionner) - Pivot au 1er août. + Pivot au 1er août par défaut. """ if self.date_debut > self.date_fin: log(f"Warning: semestre {self.id} begins after ending !") annee_debut = self.date_debut.year - if self.date_debut.month < 8: # août + if self.date_debut.month <= scu.MONTH_FIN_ANNEE_SCOLAIRE: # juillet # considere que debut sur l'anne scolaire precedente annee_debut -= 1 annee_fin = self.date_fin.year - if self.date_fin.month < 9: + if self.date_fin.month <= (scu.MONTH_FIN_ANNEE_SCOLAIRE + 1): # 9 (sept) pour autoriser un début en sept et une fin en aout annee_fin -= 1 return annee_debut == annee_fin def est_decale(self): """Vrai si semestre "décalé" - c'est à dire semestres impairs commençant entre janvier et juin - et les pairs entre juillet et decembre + c'est à dire semestres impairs commençant (par défaut) + entre janvier et juin et les pairs entre juillet et décembre. """ if self.semestre_id <= 0: return False # formations sans semestres - return (self.semestre_id % 2 and self.date_debut.month <= 6) or ( - not self.semestre_id % 2 and self.date_debut.month > 6 + return ( + # impair + ( + self.semestre_id % 2 + and self.date_debut.month < scu.MONTH_FIN_ANNEE_SCOLAIRE + ) + or + # pair + ( + (not self.semestre_id % 2) + and self.date_debut.month >= scu.MONTH_FIN_ANNEE_SCOLAIRE + ) ) def etapes_apo_vdi(self) -> list[ApoEtapeVDI]: @@ -936,8 +946,8 @@ class NotesSemSet(db.Model): title = db.Column(db.Text) annee_scolaire = db.Column(db.Integer, nullable=True, default=None) - # periode: 0 (année), 1 (Simpair), 2 (Spair) - sem_id = db.Column(db.Integer, nullable=True, default=None) + sem_id = db.Column(db.Integer, nullable=False, default=0) + "période: 0 (année), 1 (Simpair), 2 (Spair)" # Association: many to many diff --git a/app/scodoc/sco_etape_bilan.py b/app/scodoc/sco_etape_bilan.py index a2dd56bbff..294443627c 100644 --- a/app/scodoc/sco_etape_bilan.py +++ b/app/scodoc/sco_etape_bilan.py @@ -31,20 +31,21 @@ ## fonctionalités Le menu 'synchronisation avec Apogée' ne permet pas de traiter facilement les cas -où un même code étape est implementé dans des semestres (au sens ScoDoc) différents. +où un même code étape est implementé dans des formsemestres différents. -La proposition est d'ajouter à la page de description des ensembles de semestres +On ajoute à la page de description des ensembles de semestres une section permettant de faire le point sur les cas particuliers. Cette section est composée de deux parties: -* Une partie effectif où figurent le nombre d'étudiants selon un répartition par + + * Une partie effectif où figurent le nombre d'étudiants selon une répartition par semestre (en ligne) et par code étape (en colonne). On ajoute également des colonnes/lignes correspondant à des anomalies (étudiant sans code étape, sans semestre, avec deux semestres, sans NIP, etc.). - * La seconde partie présente la liste des étudiants. Il est possible qu'un + * Une seconde partie présente la liste des étudiants. Il est possible qu'un même nom figure deux fois dans la liste (si on a pas pu faire la correspondance - entre une inscription apogée et un étudiant d'un semestre, par exemple). + entre une inscription Apogée et un étudiant d'un semestre, par exemple). L'activation d'un des nombres du tableau 'effectifs' restreint l'affichage de la liste aux étudiants qui contribuent à ce nombre. @@ -66,28 +67,26 @@ Cette classe compile la totalité des données: ** constitution des listes d'anomalies Cette classe explore la suite semestres du semset. -Pour chaque semestre, elle recense les étudiants du semestre et -les codes étapes concernés. +Pour chaque semestre, elle recense les étudiants du semestre et +les codes étapes concernés, puis tous les codes étapes (toujours +en important les étudiants de l'étape via le portail). -puis tous les codes étapes (toujours en important les étudiants de l'étape -via le portail) - -enfin on dispatch chaque étudiant dans une case - soit ordinaire, soit +Enfin on dispatch chaque étudiant dans une case - soit ordinaire, soit correspondant à une anomalie. ### Modification de sco_etape_apogee_view.py -Pour insertion de l'affichage ajouté +Pour insertion de l'affichage ajouté. ### Modification de sco_semset.py -Affichage proprement dit +Affichage proprement dit. -### Modification de scp_formsemestre.py +### Modification de sco_formsemestre.py -Modification/ajout de la méthode sem_in_semestre_scolaire pour permettre -l'inscrition de semestres décalés (S1 en septembre, ...). -Le filtrage s'effctue sur la date et non plus sur la parité du semestre (1-3/2-4). +Modification/ajout de la méthode sem_in_semestre_scolaire pour permettre +l'inscription de semestres décalés (S1 en septembre, ...). +Le filtrage s'effectue sur la date et non plus sur la parité du semestre (1-3/2-4). """ import json diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index 50da1edd82..42e6bd46e0 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -28,6 +28,7 @@ """Operations de base sur les formsemestres """ from operator import itemgetter +import datetime import time from flask import g, request @@ -421,6 +422,22 @@ def sem_set_responsable_name(sem): ) +def debut_in_semestre_scolaire( + date_debut: datetime.date, year: int = False, saison: int = 0 +) -> bool: + """Vrai si date_debut est dans l'année scolaire ou le semestre + indiquée par year et periode + (par défaut, l'année scolaire en cours). + periode: + 1 = sept, + 0 = janvier + None = année complète + """ + if not year: + year = scu.AnneeScolaire() + # XXX WIP à voir selon ce que fait réellement sem_in_semestre_scolaire + + def sem_in_semestre_scolaire(sem, year=False, saison=0): """n'utilise que la date de debut, pivot au 1er aout si annee non specifiée, année scolaire courante @@ -436,13 +453,13 @@ def sem_in_semestre_scolaire(sem, year=False, saison=0): if not year: year = scu.AnneeScolaire() # est-on dans la même année universitaire ? - if sem["mois_debut_ord"] > 7: + if sem["mois_debut_ord"] > 7: # XXX if sem["annee_debut"] != str(year): return False else: if sem["annee_debut"] != str(year + 1): return False - # rafinement éventuel sur le semestre + # raffinement éventuel sur le semestre # saison is None => pas de rafinement => True if saison == 0: return True @@ -454,36 +471,20 @@ def sem_in_semestre_scolaire(sem, year=False, saison=0): def sem_in_annee_scolaire(sem, year=False): """Test si sem appartient à l'année scolaire year (int). - N'utilise que la date de debut, pivot au 1er août. - Si annee non specifiée, année scolaire courante + N'utilise que la date de début, pivot au 1er août. + Si année non specifiée, année scolaire courante """ if not year: year = scu.AnneeScolaire() - return ((sem["annee_debut"] == str(year)) and (sem["mois_debut_ord"] > 7)) or ( - (sem["annee_debut"] == str(year + 1)) and (sem["mois_debut_ord"] <= 7) + return ( + (sem["annee_debut"] == str(year)) + and (sem["mois_debut_ord"] > scu.MONTH_FIN_ANNEE_SCOLAIRE) + ) or ( + (sem["annee_debut"] == str(year + 1)) + and (sem["mois_debut_ord"] <= scu.MONTH_FIN_ANNEE_SCOLAIRE) ) -def sem_une_annee(sem): # XXX deprecated: use FormSemestre.est_sur_une_annee() - """Test si sem est entièrement sur la même année scolaire. - (ce n'est pas obligatoire mais si ce n'est pas le cas les exports Apogée ne vont pas fonctionner) - pivot au 1er août. - """ - if sem["date_debut_iso"] > sem["date_fin_iso"]: - log("Warning: semestre %(formsemestre_id)s begins after ending !" % sem) - return False - - debut = int(sem["annee_debut"]) - if sem["mois_debut_ord"] < 8: # considere que debut sur l'anne scolaire precedente - debut -= 1 - fin = int(sem["annee_fin"]) - if ( - sem["mois_fin_ord"] < 9 - ): # 9 (sept) pour autoriser un début en sept et une fin en aout - fin -= 1 - return debut == fin - - def sem_est_courant(sem): # -> FormSemestre.est_courant """Vrai si la date actuelle (now) est dans le semestre (les dates de début et fin sont incluses)""" now = time.strftime("%Y-%m-%d") diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index 5720e6ea96..3ab89db7ea 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -37,7 +37,7 @@ from app import db from app.auth.models import User from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import ModuleImpl +from app.models import FormSemestre, ModuleImpl from app.models.evaluations import Evaluation from app.models.ues import UniteEns import app.scodoc.sco_utils as scu @@ -198,6 +198,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id) M = modimpl.to_dict() formsemestre_id = modimpl.formsemestre_id + formsemestre: FormSemestre = modimpl.formsemestre Mod = sco_edit_module.module_list(args={"module_id": modimpl.module_id})[0] sem = sco_formsemestre.get_formsemestre(formsemestre_id) F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0] @@ -205,7 +206,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): moduleimpl_id=M["moduleimpl_id"] ) - nt: NotesTableCompat = res_sem.load_formsemestre_results(modimpl.formsemestre) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) mod_evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id}) mod_evals.sort( @@ -333,9 +334,10 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): ) # Adapté à partir d'une suggestion de DS (Le Havre) # Liens saisies absences seulement si permission et date courante dans le semestre - if current_user.has_permission( - Permission.ScoAbsChange - ) and sco_formsemestre.sem_est_courant(sem): + if ( + current_user.has_permission(Permission.ScoAbsChange) + and formsemestre.est_courant() + ): datelundi = sco_abs.ddmmyyyy(time.strftime("%d/%m/%Y")).prev_monday() group_id = sco_groups.get_default_group(formsemestre_id) H.append( diff --git a/app/scodoc/sco_semset.py b/app/scodoc/sco_semset.py index b32ee32c05..4eeff7cff9 100644 --- a/app/scodoc/sco_semset.py +++ b/app/scodoc/sco_semset.py @@ -104,9 +104,9 @@ class SemSet(dict): cnx, {"title": title, "annee_scolaire": annee_scolaire, "sem_id": sem_id}, ) - log("created new semset_id=%s" % self.semset_id) + log(f"created new semset_id={self.semset_id}") self.load_sems() - # analyse des semestres pour construire le bilan par semestre et par étape + # Analyse des semestres pour construire le bilan par semestre et par étape self.bilan = EtapeBilan() for sem in self.sems: self.bilan.add_sem(sem) @@ -148,6 +148,7 @@ class SemSet(dict): sem["etapes_apo_str"] = sco_formsemestre.etapes_apo_str(sorted(list(ets))) def add(self, formsemestre_id): + "Ajoute ce semestre à l'ensemble" # check if formsemestre_id in self.formsemestre_ids: return # already there @@ -155,13 +156,12 @@ class SemSet(dict): sem["formsemestre_id"] for sem in self.list_possible_sems() ]: raise ValueError( - "can't add %s to set %s: incompatible sem_id" - % (formsemestre_id, self.semset_id) + f"can't add {formsemestre_id} to set {self.semset_id}: incompatible sem_id" ) ndb.SimpleQuery( - """INSERT INTO notes_semset_formsemestre - (formsemestre_id, semset_id) + """INSERT INTO notes_semset_formsemestre + (formsemestre_id, semset_id) VALUES (%(formsemestre_id)s, %(semset_id)s) """, { diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index d00c493945..81b3fb9ee3 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -160,6 +160,10 @@ EVALUATION_NORMALE = 0 EVALUATION_RATTRAPAGE = 1 EVALUATION_SESSION2 = 2 +# Dates et années scolaires +MONTH_FIN_ANNEE_SCOLAIRE = 7 # juillet (TODO: passer en paramètre config.) +DAY_FIN_ANNEE_SCOLAIRE = 31 # TODO calculer en fct du mois + MONTH_NAMES_ABBREV = ( "Jan ", "Fév ", @@ -461,18 +465,6 @@ def NotesURL(): return url_for("notes.index_html", scodoc_dept=g.scodoc_dept)[: -len("/index_html")] -def EntreprisesURL(): - """URL of Enterprises - e.g. https://scodoc.xxx.fr/ScoDoc/DEPT/Scolarite/Entreprises - = url de base des requêtes de ZEntreprises - et page accueil Entreprises - """ - return "NotImplemented" - # url_for("entreprises.index_html", scodoc_dept=g.scodoc_dept)[ - # : -len("/index_html") - # ] - - def AbsencesURL(): """URL of Absences""" return url_for("absences.index_html", scodoc_dept=g.scodoc_dept)[ @@ -918,7 +910,7 @@ def annee_scolaire_repr(year, month): """representation de l'annee scolaire : '2009 - 2010' à partir d'une date. """ - if month > 7: # apres le 1er aout + if month > MONTH_FIN_ANNEE_SCOLAIRE: # apres le 1er aout return "%s - %s" % (year, year + 1) else: return "%s - %s" % (year - 1, year) @@ -926,7 +918,7 @@ def annee_scolaire_repr(year, month): def annee_scolaire_debut(year, month) -> int: """Annee scolaire de debut (septembre): heuristique pour l'hémisphère nord...""" - if int(month) > 7: + if int(month) > MONTH_FIN_ANNEE_SCOLAIRE: return int(year) else: return int(year) - 1 @@ -943,7 +935,11 @@ def date_fin_anne_scolaire(annee_scolaire: int) -> datetime: """La date de fin de l'année scolaire = 31 juillet de l'année suivante """ - return datetime.datetime(year=annee_scolaire + 1, month=7, day=31) + return datetime.datetime( + year=annee_scolaire + 1, + month=MONTH_FIN_ANNEE_SCOLAIRE, + day=DAY_FIN_ANNEE_SCOLAIRE, + ) def sem_decale_str(sem): diff --git a/migrations/versions/5542cac8c34a_semset_periode.py b/migrations/versions/5542cac8c34a_semset_periode.py new file mode 100644 index 0000000000..4b5a0ff729 --- /dev/null +++ b/migrations/versions/5542cac8c34a_semset_periode.py @@ -0,0 +1,36 @@ +"""semset_periode + +Revision ID: 5542cac8c34a +Revises: 52f5f35c077f +Create Date: 2022-11-08 01:17:51.983042 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.orm import sessionmaker # added by ev + + +# revision identifiers, used by Alembic. +revision = "5542cac8c34a" +down_revision = "52f5f35c077f" +branch_labels = None +depends_on = None + +Session = sessionmaker() + + +def upgrade(): + # + bind = op.get_bind() + session = Session(bind=bind) + session.execute("""UPDATE notes_semset SET sem_id=0 WHERE sem_id IS NULL;""") + op.alter_column( + "notes_semset", "sem_id", existing_type=sa.INTEGER(), nullable=False + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column("notes_semset", "sem_id", existing_type=sa.INTEGER(), nullable=True) + # ### end Alembic commands ### diff --git a/migrations/versions/b9aadc10227f_module_type_non_null.py b/migrations/versions/b9aadc10227f_module_type_non_null.py index 1758c3d235..1b5d4548ce 100644 --- a/migrations/versions/b9aadc10227f_module_type_non_null.py +++ b/migrations/versions/b9aadc10227f_module_type_non_null.py @@ -7,7 +7,6 @@ Create Date: 2022-02-15 21:47:29.212329 """ from alembic import op import sqlalchemy as sa -from sqlalchemy.dialects import postgresql from sqlalchemy.orm import sessionmaker # added by ev # revision identifiers, used by Alembic.