diff --git a/app/scodoc/notesdb.py b/app/scodoc/notesdb.py index b03b5427d..9be12f395 100644 --- a/app/scodoc/notesdb.py +++ b/app/scodoc/notesdb.py @@ -602,15 +602,15 @@ BOOL_STR = { "false": False, "0": False, "1": True, - "true": "true", + "true": True, } -def bool_or_str(x): +def bool_or_str(x) -> bool: """a boolean, may also be encoded as a string "0", "False", "1", "True" """ if isinstance(x, str): return BOOL_STR[x.lower()] - return x + return bool(x) # post filtering diff --git a/app/scodoc/sco_abs_views.py b/app/scodoc/sco_abs_views.py index 50984e04e..b21cb8c0b 100644 --- a/app/scodoc/sco_abs_views.py +++ b/app/scodoc/sco_abs_views.py @@ -790,7 +790,8 @@ def ListeAbsEtud( absjust_only: si vrai, renvoie table absences justifiées sco_year: année scolaire à utiliser. Si non spécifier, utilie l'année en cours. e.g. "2005" """ - absjust_only = int(absjust_only) # si vrai, table absjust seule (export xls ou pdf) + # si absjust_only, table absjust seule (export xls ou pdf) + absjust_only = ndb.bool_or_str(absjust_only) datedebut = "%s-08-01" % scu.AnneeScolaire(sco_year=sco_year) etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py index bbfb81b95..20ffa7a90 100644 --- a/app/scodoc/sco_formsemestre_inscriptions.py +++ b/app/scodoc/sco_formsemestre_inscriptions.py @@ -35,7 +35,7 @@ from flask import url_for, g, request import app.scodoc.sco_utils as scu from app import log from app.scodoc.scolog import logdb -from app.scodoc.sco_exceptions import ScoValueError +from app.scodoc.sco_exceptions import ScoException, ScoValueError from app.scodoc.sco_permissions import Permission from app.scodoc.sco_codes_parcours import UE_STANDARD, UE_SPORT, UE_TYPE_NAME import app.scodoc.notesdb as ndb @@ -127,6 +127,42 @@ def do_formsemestre_inscription_delete(oid, formsemestre_id=None): ) # > desinscription du semestre +def do_formsemestre_demission( + etudid, + formsemestre_id, + event_date=None, + etat_new="D", # 'D' or DEF + operation_method="demEtudiant", + event_type="DEMISSION", +): + "Démission ou défaillance d'un étudiant" + # marque 'D' ou DEF dans l'inscription au semestre et ajoute + # un "evenement" scolarite + cnx = ndb.GetDBConnexion() + # check lock + sem = sco_formsemestre.get_formsemestre(formsemestre_id) + if not sem["etat"]: + raise ScoValueError("Modification impossible: semestre verrouille") + # + ins = do_formsemestre_inscription_list( + {"etudid": etudid, "formsemestre_id": formsemestre_id} + )[0] + if not ins: + raise ScoException("etudiant non inscrit ?!") + ins["etat"] = etat_new + do_formsemestre_inscription_edit(args=ins, formsemestre_id=formsemestre_id) + logdb(cnx, method=operation_method, etudid=etudid) + sco_etud.scolar_events_create( + cnx, + args={ + "etudid": etudid, + "event_date": event_date, + "formsemestre_id": formsemestre_id, + "event_type": event_type, + }, + ) + + def do_formsemestre_inscription_edit(args=None, formsemestre_id=None): "edit a formsemestre_inscription" cnx = ndb.GetDBConnexion() diff --git a/app/views/notes.py b/app/views/notes.py index b3b2535de..e4162a07b 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -360,6 +360,8 @@ def ue_set_internal(ue_id): ue.is_external = False db.session.add(ue) db.session.commit() + # Invalide les semestres de cette formation + sco_edit_formation.invalidate_sems_in_formation(ue.formation_id) return redirect( url_for( "notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=ue.formation_id diff --git a/app/views/scolar.py b/app/views/scolar.py index 79e9d1986..ee6b69693 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -34,7 +34,7 @@ import os import time import flask -from flask import jsonify, url_for, flash, redirect, render_template, make_response +from flask import jsonify, url_for, flash, render_template, make_response from flask import current_app, g, request from flask_login import current_user from flask_wtf import FlaskForm @@ -215,7 +215,7 @@ def config_logos(scodoc_dept): ) app.clear_scodoc_cache() flash(f"Logos enregistrés") - return redirect(url_for("scolar.index_html", scodoc_dept=scodoc_dept)) + return flask.redirect(url_for("scolar.index_html", scodoc_dept=scodoc_dept)) return render_template( "configuration.html", @@ -1017,32 +1017,13 @@ def _do_dem_or_def_etud( redirect=True, ): "Démission ou défaillance d'un étudiant" - # marque 'D' ou DEF dans l'inscription au semestre et ajoute - # un "evenement" scolarite - cnx = ndb.GetDBConnexion() - # check lock - sem = sco_formsemestre.get_formsemestre(formsemestre_id) - if not sem["etat"]: - raise ScoValueError("Modification impossible: semestre verrouille") - # - ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( - {"etudid": etudid, "formsemestre_id": formsemestre_id} - )[0] - if not ins: - raise ScoException("etudiant non inscrit ?!") - ins["etat"] = etat_new - sco_formsemestre_inscriptions.do_formsemestre_inscription_edit( - args=ins, formsemestre_id=formsemestre_id - ) - logdb(cnx, method=operation_method, etudid=etudid) - sco_etud.scolar_events_create( - cnx, - args={ - "etudid": etudid, - "event_date": event_date, - "formsemestre_id": formsemestre_id, - "event_type": event_type, - }, + sco_formsemestre_inscriptions.do_formsemestre_demission( + etudid, + formsemestre_id, + event_date=event_date, + etat_new=etat_new, # 'D' or DEF + operation_method=operation_method, + event_type=event_type, ) if redirect: return flask.redirect( diff --git a/tests/unit/test_notes_modules.py b/tests/unit/test_notes_modules.py index c997cfd15..fbd7bdeea 100644 --- a/tests/unit/test_notes_modules.py +++ b/tests/unit/test_notes_modules.py @@ -3,6 +3,7 @@ et aussi moyennes modules et UE internes (via nt) """ +from re import X from config import TestConfig from tests.unit import sco_fake_gen @@ -11,8 +12,10 @@ from flask import g import app from app.scodoc import sco_bulletins from app.scodoc import sco_cache +from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_moduleimpl from app.scodoc import sco_utils as scu +from app.views import scolar DEPT = TestConfig.DEPT_TEST @@ -40,6 +43,7 @@ def check_nt( if expected_sum_coefs_ue is not False: ue_status = nt.get_etud_ue_status(etudid, ue_id) assert expected_sum_coefs_ue == ue_status["sum_coefs"] + return nt def test_notes_modules(test_client): @@ -149,7 +153,7 @@ def test_notes_modules(test_client): moduleimpl_id, expected_mod_moy=0.0, expected_moy_ue=0.0, - expected_sum_coefs_ue=0.0, + expected_sum_coefs_ue=coef_mod_1, # absences, donc zéros et on garde le coef ) # Note excusée EXC <-> scu.NOTES_NEUTRALISE @@ -241,6 +245,7 @@ def test_notes_modules(test_client): expected_sum_coefs_ue=0.0, ) # --- Maintenant avec 2 modules dans l'UE + coef_mod_2 = 2.1 mod2 = G.create_module( matiere_id=mat["matiere_id"], code="TSM2", @@ -305,3 +310,103 @@ def test_notes_modules(test_client): ) assert b2["ues"][0]["ue_status"]["cur_moy_ue"] == 11.0 assert b2["ues"][0]["ue_status"]["moy"] == 11 + + +def test_notes_modules_att_dem(test_client): + """Scénario dit "lyonnais": + Des étudiants, des notes, plusieurs étudiants avec notes ATT (ici notes éval en "attente"), + démission d'un étudiant qui avait ATT. Passage des autres ATT en EXC ou ABS. + On va tester avec un module, une éval, deux étudiants + """ + app.set_sco_dept(DEPT) + + G = sco_fake_gen.ScoFake(verbose=False) + etuds = [G.create_etud(code_nip=None) for i in range(2)] # 2 étudiants + + f = G.create_formation(acronyme="") + ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test") + ue_id = ue["ue_id"] + mat = G.create_matiere(ue_id=ue_id, titre="matière test") + coef_mod_1 = 1.5 + mod = G.create_module( + matiere_id=mat["matiere_id"], + code="TSM1", + coefficient=coef_mod_1, + titre="module test", + ue_id=ue["ue_id"], + formation_id=f["formation_id"], + ) + # + # -------------------------------- + # + sem = G.create_formsemestre( + formation_id=f["formation_id"], + semestre_id=1, + date_debut="01/01/2020", + date_fin="30/06/2020", + ) + formsemestre_id = sem["formsemestre_id"] + mi = G.create_moduleimpl( + module_id=mod["module_id"], + formsemestre_id=formsemestre_id, + ) + moduleimpl_id = mi["moduleimpl_id"] + # --- Inscription des étudiants + for etud in etuds: + G.inscrit_etudiant(sem, etud) + # --- Creation évaluation: e1 + coef_1 = 1.0 + e1 = G.create_evaluation( + moduleimpl_id=moduleimpl_id, + jour="01/01/2020", + description="evaluation 1", + coefficient=coef_1, + ) + # Attente (ATT) sur les 2 evals + _, _, _ = G.create_note(evaluation=e1, etud=etuds[0], note=scu.NOTES_ATTENTE) # ATT + _, _, _ = G.create_note(evaluation=e1, etud=etuds[1], note=scu.NOTES_ATTENTE) # ATT + # Démission du premier étudiant + sco_formsemestre_inscriptions.do_formsemestre_demission( + etuds[0]["etudid"], + sem["formsemestre_id"], + event_date="02/01/2020", + ) + b = sco_bulletins.formsemestre_bulletinetud_dict( + sem["formsemestre_id"], etuds[0]["etudid"] + ) + assert b["etud_etat"] == "D" + assert b["nb_demissions"] == 1 + assert b["ues"] == [] # inscrit à aucune UE ! + # bulletin de l'étudiant non demissionnaire: + b = sco_bulletins.formsemestre_bulletinetud_dict( + sem["formsemestre_id"], etuds[1]["etudid"] + ) + assert b["etud_etat"] == "I" + assert b["nb_demissions"] == 1 + assert len(b["ues"]) == 1 + nt = check_nt( + etuds[1]["etudid"], + sem["formsemestre_id"], + ue_id, + moduleimpl_id, + expected_mod_moy="NA0", + expected_moy_ue=0.0, + expected_sum_coefs_ue=0.0, + ) + note_e1 = nt.get_etud_eval_note(etuds[1]["etudid"], e1["evaluation_id"]) + assert note_e1["value"] == scu.NOTES_ATTENTE + note_e1 = nt.get_etud_eval_note(etuds[0]["etudid"], e1["evaluation_id"]) + assert note_e1["value"] == scu.NOTES_ATTENTE # XXXX un peu contestable + # Saisie note ABS pour le deuxième etud + _, _, _ = G.create_note(evaluation=e1, etud=etuds[1], note=None) # ABS + nt = check_nt( + etuds[1]["etudid"], + sem["formsemestre_id"], + ue_id, + moduleimpl_id, + expected_mod_moy=0.0, + expected_moy_ue=0.0, + expected_sum_coefs_ue=coef_mod_1, + ) + note_e1 = nt.get_etud_eval_note(etuds[1]["etudid"], e1["evaluation_id"]) + assert note_e1["value"] is None