diff --git a/app/api/assiduites.py b/app/api/assiduites.py index ff2cff4c3..3c02c7e75 100644 --- a/app/api/assiduites.py +++ b/app/api/assiduites.py @@ -480,7 +480,7 @@ def _create_singular( moduleimpl_id = data.get("moduleimpl_id", False) moduleimpl: ModuleImpl = None - if moduleimpl_id is not False: + if moduleimpl_id not in [False, None]: moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first() if moduleimpl is None: errors.append("param 'moduleimpl_id': invalide") diff --git a/app/api/justificatifs.py b/app/api/justificatifs.py index 693c08f49..1aa418fe2 100644 --- a/app/api/justificatifs.py +++ b/app/api/justificatifs.py @@ -384,7 +384,10 @@ def _delete_singular(justif_id: int, database): if archive_name is not None: archiver: JustificatifArchiver = JustificatifArchiver() - archiver.delete_justificatif(justificatif_unique.etudid, archive_name) + try: + archiver.delete_justificatif(justificatif_unique.etudid, archive_name) + except ValueError: + pass database.session.delete(justificatif_unique) compute_assiduites_justified( @@ -430,6 +433,7 @@ def justif_import(justif_id: int = None): filename=file.filename, data=file.stream.read(), archive_name=archive_name, + user_id=current_user.id, ) justificatif_unique.fichier = archive_name @@ -446,7 +450,7 @@ def justif_import(justif_id: int = None): @api_web_bp.route("/justificatif//export/", methods=["POST"]) @scodoc @login_required -@permission_required(Permission.ScoJustifView) +@permission_required(Permission.ScoJustifChange) def justif_export(justif_id: int = None, filename: str = None): """ Retourne un fichier d'une archive d'un justificatif @@ -541,7 +545,7 @@ def justif_remove(justif_id: int = None): @scodoc @login_required @as_json -@permission_required(Permission.ScoJustifView) +@permission_required(Permission.ScoView) def justif_list(justif_id: int = None): """ Liste les fichiers du justificatif @@ -563,7 +567,14 @@ def justif_list(justif_id: int = None): archive_name, justificatif_unique.etudid ) - return filenames + retour = {"total": len(filenames), "filenames": []} + + for fi in filenames: + if int(fi[1]) == current_user.id or current_user.has_permission( + Permission.ScoJustifView + ): + retour["filenames"].append(fi[0]) + return retour # Partie justification diff --git a/app/scodoc/sco_archives_justificatifs.py b/app/scodoc/sco_archives_justificatifs.py index 576b39473..a121ded85 100644 --- a/app/scodoc/sco_archives_justificatifs.py +++ b/app/scodoc/sco_archives_justificatifs.py @@ -18,7 +18,7 @@ class Trace: def __init__(self, path: str) -> None: self.path: str = path + "/_trace.csv" - self.content: dict[str, list[datetime, datetime]] = {} + self.content: dict[str, list[datetime, datetime, str]] = {} self.import_from_file() def import_from_file(self): @@ -27,26 +27,31 @@ class Trace: with open(self.path, "r", encoding="utf-8") as file: for line in file.readlines(): csv = line.split(",") + if len(csv) < 4: + continue fname: str = csv[0] entry_date: datetime = is_iso_formated(csv[1], True) delete_date: datetime = is_iso_formated(csv[2], True) + user_id = csv[3] - self.content[fname] = [entry_date, delete_date] + self.content[fname] = [entry_date, delete_date, user_id] - def set_trace(self, *fnames: str, mode: str = "entry"): + def set_trace(self, *fnames: str, mode: str = "entry", current_user: str = None): """Ajoute une trace du fichier donné mode : entry / delete """ - modes: list[str] = ["entry", "delete"] + modes: list[str] = ["entry", "delete", "user_id"] for fname in fnames: if fname in modes: continue - traced: list[datetime, datetime] = self.content.get(fname, False) + traced: list[datetime, datetime, str] = self.content.get(fname, False) if not traced: - self.content[fname] = [None, None] + self.content[fname] = [None, None, None] traced = self.content[fname] - traced[modes.index(mode)] = datetime.now() + traced[modes.index(mode)] = ( + datetime.now() if mode != "user_id" else current_user + ) self.save_trace() def save_trace(self): @@ -55,11 +60,13 @@ class Trace: for fname, traced in self.content.items(): date_fin: datetime or None = traced[1].isoformat() if traced[1] else "None" if traced[0] is not None: - lines.append(f"{fname},{traced[0].isoformat()},{date_fin}") + lines.append(f"{fname},{traced[0].isoformat()},{date_fin}, {traced[2]}") with open(self.path, "w", encoding="utf-8") as file: file.write("\n".join(lines)) - def get_trace(self, fnames: list[str] = ()) -> dict[str, list[datetime, datetime]]: + def get_trace( + self, fnames: list[str] = () + ) -> dict[str, list[datetime, datetime, str]]: """Récupère la trace pour les noms de fichiers. si aucun nom n'est donné, récupère tous les fichiers""" @@ -100,6 +107,7 @@ class JustificatifArchiver(BaseArchiver): data: bytes or str, archive_name: str = None, description: str = "", + user_id: str = None, ) -> str: """ Ajoute un fichier dans une archive "justificatif" pour l'etudid donné @@ -116,7 +124,9 @@ class JustificatifArchiver(BaseArchiver): fname: str = self.store(archive_id, filename, data) trace = Trace(self.get_obj_dir(etudid)) - trace.set_trace(fname, "entry") + trace.set_trace(fname, mode="entry") + if user_id is not None: + trace.set_trace(fname, mode="user_id", current_user=user_id) return self.get_archive_name(archive_id), fname @@ -149,7 +159,7 @@ class JustificatifArchiver(BaseArchiver): if os.path.isfile(path): if has_trace: trace = Trace(self.get_obj_dir(etudid)) - trace.set_trace(filename, "delete") + trace.set_trace(filename, mode="delete") os.remove(path) else: @@ -164,7 +174,9 @@ class JustificatifArchiver(BaseArchiver): ) ) - def list_justificatifs(self, archive_name: str, etudid: int) -> list[str]: + def list_justificatifs( + self, archive_name: str, etudid: int + ) -> list[tuple[str, int]]: """ Retourne la liste des noms de fichiers dans l'archive donnée """ @@ -173,7 +185,10 @@ class JustificatifArchiver(BaseArchiver): archive_id = self.get_id_from_name(etudid, archive_name) filenames = self.list_archive(archive_id) - return filenames + trace: Trace = Trace(self.get_obj_dir(etudid)) + traced = trace.get_trace(filenames) + + return [(key, value[2]) for key, value in traced.items()] def get_justificatif_file(self, archive_name: str, etudid: int, filename: str): """ diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index c9cb30054..96d128658 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -305,10 +305,12 @@ def filter_by_formsemestre(assiduites_query: Assiduite, formsemestre: FormSemest .filter(FormSemestreInscription.formsemestre_id == formsemestre.id) ) - assiduites_query = assiduites_query.filter( - Assiduite.date_debut >= formsemestre.date_debut - ) - return assiduites_query.filter(Assiduite.date_fin <= formsemestre.date_fin) + form_date_debut = formsemestre.date_debut + timedelta(days=1) + form_date_fin = formsemestre.date_fin + timedelta(days=1) + + assiduites_query = assiduites_query.filter(Assiduite.date_debut >= form_date_debut) + + return assiduites_query.filter(Assiduite.date_fin <= form_date_fin) def justifies(justi: Justificatif, obj: bool = False) -> list[int]: @@ -446,7 +448,11 @@ def invalidate_assiduites_etud_date(etudid, date: datetime): from app.scodoc import sco_compute_moy # Semestres a cette date: - etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] + etud = sco_etud.get_etud_info(etudid=etudid, filled=True) + if len(etud) == 0: + return + else: + etud = etud[0] sems = [ sem for sem in etud["sems"] diff --git a/app/static/css/assiduites.css b/app/static/css/assiduites.css index 45b5bcc20..72b651888 100644 --- a/app/static/css/assiduites.css +++ b/app/static/css/assiduites.css @@ -541,4 +541,12 @@ .icon:focus { outline: none; border: none; +} + +#forcemodule { + border-radius: 8px; + background: crimson; + max-width: fit-content; + padding: 5px; + color: white; } \ No newline at end of file diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js index 7de091019..2b7fa5395 100644 --- a/app/static/js/assiduites.js +++ b/app/static/js/assiduites.js @@ -263,15 +263,12 @@ function executeMassActionQueue() { * } */ const tlTimes = getTimeLineTimes(); - const assiduite = { + let assiduite = { date_debut: tlTimes.deb.format(), date_fin: tlTimes.fin.format(), }; - const moduleimpl = getModuleImplId(); - if (moduleimpl !== null) { - assiduite["moduleimpl_id"] = moduleimpl; - } + assiduite = setModuleImplId(assiduite); const createQueue = []; //liste des assiduités qui seront créées. @@ -309,10 +306,7 @@ function executeMassActionQueue() { const edit = () => { //On ajoute le moduleimpl (s'il existe) aux assiduités à modifier const editQueue = toEdit.map((assiduite) => { - const moduleimpl = getModuleImplId(); - if (moduleimpl !== null) { - assiduite["moduleimpl_id"] = moduleimpl; - } + assiduite = setModuleImplId(assiduite); return assiduite; }); @@ -844,17 +838,13 @@ function getAssiduitesFromEtuds(clear, has_formsemestre = true, deb, fin) { */ function createAssiduite(etat, etudid) { const tlTimes = getTimeLineTimes(); - const assiduite = { + let assiduite = { date_debut: tlTimes.deb.format(), date_fin: tlTimes.fin.format(), etat: etat, }; - const moduleimpl = getModuleImplId(); - - if (moduleimpl !== null) { - assiduite["moduleimpl_id"] = moduleimpl; - } + assiduite = setModuleImplId(assiduite); const path = getUrl() + `/api/assiduite/${etudid}/create`; sync_post( @@ -904,10 +894,12 @@ function deleteAssiduite(assiduite_id) { * TODO : Rendre asynchrone */ function editAssiduite(assiduite_id, etat) { - const assiduite = { + let assiduite = { etat: etat, moduleimpl_id: getModuleImplId(), }; + + assiduite = setModuleImplId(assiduite); const path = getUrl() + `/api/assiduite/${assiduite_id}/edit`; let bool = false; sync_post( @@ -1340,6 +1332,23 @@ function getModuleImplId() { return ["", undefined, null].includes(val) ? null : val; } +function setModuleImplId(assiduite, module = null) { + const moduleimpl = module == null ? getModuleImplId() : module; + if (moduleimpl === "autre") { + if ("desc" in assiduite && assiduite["desc"] != null) { + if (assiduite["desc"].indexOf("Module:Autre") == -1) { + assiduite["desc"] = "Module:Autre\n" + assiduite["desc"]; + } + } else { + assiduite["desc"] = "Module:Autre"; + } + assiduite["moduleimpl_id"] = null; + } else { + assiduite["moduleimpl_id"] = moduleimpl; + } + return assiduite; +} + /** * Récupération de l'id du formsemestre * @returns {String} l'identifiant du formsemestre @@ -1381,7 +1390,15 @@ function isSingleEtud() { function getCurrentAssiduiteModuleImplId() { const currentAssiduites = getAssiduitesConflict(etudid); if (currentAssiduites.length > 0) { - const mod = currentAssiduites[0].moduleimpl_id; + let mod = currentAssiduites[0].moduleimpl_id; + if ( + mod == null && + "desc" in currentAssiduites[0] && + currentAssiduites[0].desc != null && + currentAssiduites[0].desc.indexOf("Module:Autre") != -1 + ) { + mod = "autre"; + } return mod == null ? "" : mod; } return ""; diff --git a/app/templates/assiduites/pages/signal_assiduites_group.j2 b/app/templates/assiduites/pages/signal_assiduites_group.j2 index f0da36b0e..16d8c0059 100644 --- a/app/templates/assiduites/pages/signal_assiduites_group.j2 +++ b/app/templates/assiduites/pages/signal_assiduites_group.j2 @@ -15,7 +15,7 @@
Groupes : {{grp|safe}}
- +
Module :{{moduleimpl_select|safe}}
@@ -84,13 +84,16 @@ if (select.value == "") { btn.disabled = true; + document.getElementById('forcemodule').style.display = "block"; } select.addEventListener('change', (e) => { if (e.target.value != "") { btn.disabled = false; + document.getElementById('forcemodule').style.display = "none"; } else { btn.disabled = true; + document.getElementById('forcemodule').style.display = "block"; } }); } diff --git a/app/templates/assiduites/widgets/differee.j2 b/app/templates/assiduites/widgets/differee.j2 index e1a356ae9..92b0bc190 100644 --- a/app/templates/assiduites/widgets/differee.j2 +++ b/app/templates/assiduites/widgets/differee.j2 @@ -245,6 +245,13 @@ .rbtn:disabled { opacity: 0.7; } + + .td.etat { + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + border: 10px solid white; + }