WIP: paramétrage dates antipodiques

This commit is contained in:
Emmanuel Viennet 2022-11-09 12:50:10 +01:00
parent 836c57ec98
commit ce541d1870
8 changed files with 122 additions and 79 deletions

View File

@ -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

View File

@ -31,20 +31,21 @@
## fonctionalités
Le menu 'synchronisation avec Apogée' ne permet pas de traiter facilement les cas
un même code étape est implementé dans des semestres (au sens ScoDoc) différents.
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 figurent le nombre d'étudiants selon un répartition par
* Une partie effectif 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

View File

@ -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")

View File

@ -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(

View File

@ -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)
""",
{

View File

@ -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):

View File

@ -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 ###

View File

@ -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.