diff --git a/app/models/events.py b/app/models/events.py
index 9725f3c8a..b94549e76 100644
--- a/app/models/events.py
+++ b/app/models/events.py
@@ -36,18 +36,21 @@ class Scolog(db.Model):
class ScolarNews(db.Model):
"""Nouvelles pour page d'accueil"""
- NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id)
- NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id)
- NEWS_FORM = "FORM" # modification formation (object=formation_id)
- NEWS_SEM = "SEM" # creation semestre (object=None)
NEWS_ABS = "ABS" # saisie absence
+ NEWS_APO = "APO" # changements de codes APO
+ NEWS_FORM = "FORM" # modification formation (object=formation_id)
+ NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id)
NEWS_MISC = "MISC" # unused
+ NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id)
+ NEWS_SEM = "SEM" # creation semestre (object=None)
NEWS_MAP = {
- NEWS_INSCR: "inscription d'étudiants",
- NEWS_NOTE: "saisie note",
+ NEWS_ABS: "saisie absence",
+ NEWS_APO: "modif. code Apogée",
NEWS_FORM: "modification formation",
- NEWS_SEM: "création semestre",
+ NEWS_INSCR: "inscription d'étudiants",
NEWS_MISC: "opération", # unused
+ NEWS_NOTE: "saisie note",
+ NEWS_SEM: "création semestre",
}
NEWS_TYPES = list(NEWS_MAP.keys())
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index 0d126fc7f..fda723839 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -375,7 +375,7 @@ class FormSemestre(db.Model):
return f"{self.titre} {self.formation.get_parcours().SESSION_NAME} {self.semestre_id}"
def sem_modalite(self) -> str:
- """Le semestre et la modialité, ex "S2 FI" ou "S3 APP" """
+ """Le semestre et la modalité, ex "S2 FI" ou "S3 APP" """
if self.semestre_id > 0:
descr_sem = f"S{self.semestre_id}"
else:
diff --git a/app/pe/pe_jurype.py b/app/pe/pe_jurype.py
index 0943649cf..48795edfc 100644
--- a/app/pe/pe_jurype.py
+++ b/app/pe/pe_jurype.py
@@ -486,7 +486,10 @@ class JuryPE(object):
sesdates = [
pe_tagtable.conversionDate_StrToDate(sem["date_fin"]) for sem in sessems
] # association 1 date -> 1 semestrePE pour les semestres de l'étudiant
- lastdate = max(sesdates) # date de fin de l'inscription la plus récente
+ if sesdates:
+ lastdate = max(sesdates) # date de fin de l'inscription la plus récente
+ else:
+ return False
# if PETable.AFFICHAGE_DEBUG_PE == True : pe_tools.pe_print(" derniere inscription = ", lastDateSem)
@@ -585,7 +588,7 @@ class JuryPE(object):
for (i, fid) in enumerate(lesFids):
if pe_tools.PE_DEBUG:
pe_tools.pe_print(
- u"%d) Semestre taggué %s (avec classement dans groupe)"
+ "%d) Semestre taggué %s (avec classement dans groupe)"
% (i + 1, fid)
)
self.add_semtags_in_jury(fid)
@@ -620,7 +623,7 @@ class JuryPE(object):
nbinscrit = self.semTagDict[fid].get_nbinscrits()
if pe_tools.PE_DEBUG:
pe_tools.pe_print(
- u" - %d étudiants classés " % (nbinscrit)
+ " - %d étudiants classés " % (nbinscrit)
+ ": "
+ ",".join(
[etudid for etudid in self.semTagDict[fid].get_etudids()]
@@ -628,12 +631,12 @@ class JuryPE(object):
)
if lesEtudidsManquants:
pe_tools.pe_print(
- u" - dont %d étudiants manquants ajoutés aux données du jury"
+ " - dont %d étudiants manquants ajoutés aux données du jury"
% (len(lesEtudidsManquants))
+ ": "
+ ", ".join(lesEtudidsManquants)
)
- pe_tools.pe_print(u" - Export csv")
+ pe_tools.pe_print(" - Export csv")
filename = self.NOM_EXPORT_ZIP + self.semTagDict[fid].nom + ".csv"
self.zipfile.writestr(filename, self.semTagDict[fid].str_tagtable())
@@ -742,7 +745,7 @@ class JuryPE(object):
for fid in fids_finaux:
if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 1:
- pe_tools.pe_print(u" - semestre final %s" % (fid))
+ pe_tools.pe_print(" - semestre final %s" % (fid))
settag = pe_settag.SetTag(
nom, parcours=parcours
) # Le set tag fusionnant les données
@@ -762,7 +765,7 @@ class JuryPE(object):
for ffid in settag.get_Fids_in_settag():
if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 1:
pe_tools.pe_print(
- u" -> ajout du semestre tagué %s" % (ffid)
+ " -> ajout du semestre tagué %s" % (ffid)
)
self.add_semtags_in_jury(ffid)
settag.set_SemTagDict(
@@ -791,7 +794,7 @@ class JuryPE(object):
if nbreEtudInscrits > 0:
if pe_tools.PE_DEBUG:
pe_tools.pe_print(
- u"%d) %s avec interclassement sur la promo" % (i + 1, nom)
+ "%d) %s avec interclassement sur la promo" % (i + 1, nom)
)
if nom in ["S1", "S2", "S3", "S4"]:
settag.set_SetTagDict(self.semTagDict)
@@ -802,7 +805,7 @@ class JuryPE(object):
else:
if pe_tools.PE_DEBUG:
pe_tools.pe_print(
- u"%d) Pas d'interclassement %s sur la promo faute de notes"
+ "%d) Pas d'interclassement %s sur la promo faute de notes"
% (i + 1, nom)
)
@@ -1152,11 +1155,14 @@ class JuryPE(object):
return sesSems
# **********************************************
- def calcul_anneePromoDUT_d_un_etudiant(self, etudid):
+ def calcul_anneePromoDUT_d_un_etudiant(self, etudid) -> int:
"""Calcule et renvoie la date de diplome prévue pour un étudiant fourni avec son etudid
- en fonction de sesSemestres de scolarisation"""
- sesSemestres = self.get_semestresDUT_d_un_etudiant(etudid)
- return max([get_annee_diplome_semestre(sem) for sem in sesSemestres])
+ en fonction de ses semestres de scolarisation"""
+ semestres = self.get_semestresDUT_d_un_etudiant(etudid)
+ if semestres:
+ return max([get_annee_diplome_semestre(sem) for sem in semestres])
+ else:
+ return None
# *********************************************
# Fonctions d'affichage pour debug
@@ -1184,18 +1190,21 @@ class JuryPE(object):
chaine += "\n"
return chaine
- def get_date_entree_etudiant(self, etudid):
- """Renvoie la date d'entree d'un étudiant"""
- return str(
- min([int(sem["annee_debut"]) for sem in self.ETUDINFO_DICT[etudid]["sems"]])
- )
+ def get_date_entree_etudiant(self, etudid) -> str:
+ """Renvoie la date d'entree d'un étudiant: "1996" """
+ annees_debut = [
+ int(sem["annee_debut"]) for sem in self.ETUDINFO_DICT[etudid]["sems"]
+ ]
+ if annees_debut:
+ return str(min(annees_debut))
+ return ""
# ----------------------------------------------------------------------------------------
# Fonctions
# ----------------------------------------------------------------------------------------
-def get_annee_diplome_semestre(sem):
+def get_annee_diplome_semestre(sem) -> int:
"""Pour un semestre donne, décrit par le biais du dictionnaire sem usuel :
sem = {'formestre_id': ..., 'semestre_id': ..., 'annee_debut': ...},
à condition qu'il soit un semestre de formation DUT,
diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py
index 01b94c064..1c708ca5c 100644
--- a/app/scodoc/gen_tables.py
+++ b/app/scodoc/gen_tables.py
@@ -385,12 +385,16 @@ class GenTable(object):
colspan_count = colspan
else:
colspan_txt = ""
+ attrs = row.get("_%s_td_attrs" % cid, "")
+ order = row.get(f"_{cid}_order")
+ if order:
+ attrs += f' data-order="{order}"'
r.append(
"<%s%s %s%s%s>%s%s>"
% (
elem,
std,
- row.get("_%s_td_attrs" % cid, ""),
+ attrs,
klass,
colspan_txt,
content,
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index c6811d454..fd25e5da3 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -43,10 +43,8 @@ import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
from app.scodoc.TrivialFormulator import TrivialFormulator
-from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import (
- ScoGenError,
ScoValueError,
ScoLockedFormError,
ScoNonEmptyFormationObject,
@@ -61,7 +59,6 @@ from app.scodoc import sco_edit_module
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
-from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module
_ueEditor = ndb.EditableTable(
@@ -1355,93 +1352,6 @@ def ue_is_locked(ue_id):
return len(r) > 0
-# ---- Table recap formation
-def formation_table_recap(formation_id, format="html"):
- """Table recapitulant formation."""
- from app.scodoc import sco_formations
-
- F = sco_formations.formation_list(args={"formation_id": formation_id})
- if not F:
- raise ScoValueError("invalid formation_id")
- F = F[0]
- T = []
- ues = ue_list(args={"formation_id": formation_id})
- for ue in ues:
- Matlist = sco_edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]})
- for Mat in Matlist:
- Modlist = sco_edit_module.module_list(
- args={"matiere_id": Mat["matiere_id"]}
- )
- for Mod in Modlist:
- Mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls(
- Mod["module_id"]
- )
- #
- T.append(
- {
- "UE_acro": ue["acronyme"],
- "Mat_tit": Mat["titre"],
- "Mod_tit": Mod["abbrev"] or Mod["titre"],
- "Mod_code": Mod["code"],
- "Mod_coef": Mod["coefficient"],
- "Mod_sem": Mod["semestre_id"],
- "nb_moduleimpls": Mod["nb_moduleimpls"],
- "heures_cours": Mod["heures_cours"],
- "heures_td": Mod["heures_td"],
- "heures_tp": Mod["heures_tp"],
- "ects": Mod["ects"],
- }
- )
- columns_ids = [
- "UE_acro",
- "Mat_tit",
- "Mod_tit",
- "Mod_code",
- "Mod_coef",
- "Mod_sem",
- "nb_moduleimpls",
- "heures_cours",
- "heures_td",
- "heures_tp",
- "ects",
- ]
- titles = {
- "UE_acro": "UE",
- "Mat_tit": "Matière",
- "Mod_tit": "Module",
- "Mod_code": "Code",
- "Mod_coef": "Coef.",
- "Mod_sem": "Sem.",
- "nb_moduleimpls": "Nb utilisé",
- "heures_cours": "Cours (h)",
- "heures_td": "TD (h)",
- "heures_tp": "TP (h)",
- "ects": "ECTS",
- }
-
- title = (
- """Formation %(titre)s (%(acronyme)s) [version %(version)s] code %(formation_code)s"""
- % F
- )
- tab = GenTable(
- columns_ids=columns_ids,
- rows=T,
- titles=titles,
- origin="Généré par %s le " % scu.sco_version.SCONAME
- + scu.timedate_human_repr()
- + "",
- caption=title,
- html_caption=title,
- html_class="table_leftalign",
- base_url="%s?formation_id=%s" % (request.base_url, formation_id),
- page_title=title,
- html_title="
" + title + "
",
- pdf_title=title,
- preferences=sco_preferences.SemPreferences(),
- )
- return tab.make_page(format=format)
-
-
def ue_list_semestre_ids(ue: dict):
"""Liste triée des numeros de semestres des modules dans cette UE
Il est recommandable que tous les modules d'une UE aient le même indice de semestre.
diff --git a/app/scodoc/sco_formation_recap.py b/app/scodoc/sco_formation_recap.py
new file mode 100644
index 000000000..16aa336d2
--- /dev/null
+++ b/app/scodoc/sco_formation_recap.py
@@ -0,0 +1,192 @@
+# -*- mode: python -*-
+# -*- coding: utf-8 -*-
+
+##############################################################################
+#
+# Gestion scolarite IUT
+#
+# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Emmanuel Viennet emmanuel.viennet@viennet.net
+#
+##############################################################################
+
+"""Table recap formation (avec champs éditables)
+"""
+import io
+from zipfile import ZipFile, BadZipfile
+
+from flask import send_file, url_for
+from flask import g, request
+from flask_login import current_user
+
+from app.models import Formation, FormSemestre, UniteEns, Module
+from app.models.formations import Matiere
+
+from app.scodoc.gen_tables import GenTable
+from app.scodoc.sco_permissions import Permission
+from app.scodoc import sco_preferences
+import app.scodoc.sco_utils as scu
+
+
+# ---- Table recap formation
+def formation_table_recap(formation_id, format="html"):
+ """Table recapitulant formation."""
+ T = []
+ formation = Formation.query.get_or_404(formation_id)
+ ues = formation.ues.order_by(UniteEns.semestre_idx, UniteEns.numero)
+ can_edit = current_user.has_permission(Permission.ScoChangeFormation)
+ li = 0
+ for ue in ues:
+ # L'UE
+ T.append(
+ {
+ "sem": f"S{ue.semestre_idx}" if ue.semestre_idx is not None else "-",
+ "_sem_order": f"{li:04d}",
+ "code": ue.acronyme,
+ "titre": ue.titre or "",
+ "_titre_target": url_for(
+ "notes.ue_edit",
+ scodoc_dept=g.scodoc_dept,
+ ue_id=ue.id,
+ )
+ if can_edit
+ else None,
+ "apo": ue.code_apogee or "",
+ "_apo_td_attrs": f""" data-oid="{ue.id}" data-value="{ue.code_apogee or ''}" """,
+ "coef": ue.coefficient or "",
+ "ects": ue.ects,
+ "_css_row_class": f"ue ue_",
+ }
+ )
+ li += 1
+ matieres = ue.matieres.order_by(Matiere.numero)
+ for mat in matieres:
+ modules = mat.modules.order_by(Module.numero)
+ for mod in modules:
+ nb_moduleimpls = mod.modimpls.count()
+ # le module (ou ressource ou sae)
+ T.append(
+ {
+ "sem": f"S{mod.semestre_id}"
+ if mod.semestre_id is not None
+ else "-",
+ "_sem_order": f"{li:04d}",
+ "code": mod.code,
+ "titre": mod.abbrev or mod.titre,
+ "_titre_target": url_for(
+ "notes.module_edit",
+ scodoc_dept=g.scodoc_dept,
+ module_id=mod.id,
+ )
+ if can_edit
+ else None,
+ "apo": mod.code_apogee,
+ "_apo_td_attrs": f""" data-oid="{mod.id}" data-value="{mod.code_apogee or ''}" """,
+ "coef": mod.coefficient,
+ "nb_moduleimpls": nb_moduleimpls,
+ "heures_cours": mod.heures_cours,
+ "heures_td": mod.heures_td,
+ "heures_tp": mod.heures_tp,
+ "_css_row_class": f"mod {mod.type_abbrv()}",
+ }
+ )
+ columns_ids = [
+ "sem",
+ "code",
+ "apo",
+ # "mat", inutile d'afficher la matière
+ "titre",
+ "coef",
+ "ects",
+ "nb_moduleimpls",
+ "heures_cours",
+ "heures_td",
+ "heures_tp",
+ ]
+ titles = {
+ "ue": "UE",
+ "mat": "Matière",
+ "titre": "Titre",
+ "code": "Code",
+ "apo": "Apo",
+ "coef": "Coef.",
+ "sem": "Sem.",
+ "nb_moduleimpls": "Nb utilisé",
+ "heures_cours": "Cours (h)",
+ "heures_td": "TD (h)",
+ "heures_tp": "TP (h)",
+ "ects": "ECTS",
+ }
+
+ title = f"""Formation {formation.titre} ({formation.acronyme})
+ [version {formation.version}] code {formation.formation_code}"""
+ html_class = "stripe cell-border compact hover order-column formation_table_recap"
+ if current_user.has_permission(Permission.ScoEditApo):
+ html_class += " apo_editable"
+
+ tab = GenTable(
+ columns_ids=columns_ids,
+ rows=T,
+ titles=titles,
+ origin=f"Généré par {scu.sco_version.SCONAME} le {scu.timedate_human_repr()}",
+ caption=title,
+ html_caption=title,
+ html_class=html_class,
+ html_class_ignore_default=True,
+ html_table_attrs=f"""
+ data-apo_ue_save_url="{url_for('notes.ue_set_apo', scodoc_dept=g.scodoc_dept)}"
+ data-apo_mod_save_url="{url_for('notes.module_set_apo', scodoc_dept=g.scodoc_dept)}"
+ """,
+ html_with_td_classes=True,
+ base_url=f"{request.base_url}?formation_id={formation_id}",
+ page_title=title,
+ html_title=f"{title}
",
+ pdf_title=title,
+ preferences=sco_preferences.SemPreferences(),
+ table_id="formation_table_recap",
+ )
+ return tab.make_page(format=format, javascripts=["js/formation_recap.js"])
+
+
+def export_recap_formations_annee_scolaire(annee_scolaire):
+ """Exporte un zip des recap (excel) des formatons de tous les semestres
+ de l'année scolaire indiquée.
+ """
+ annee_scolaire = int(annee_scolaire)
+ data = io.BytesIO()
+ zip_file = ZipFile(data, "w")
+ formsemestres = FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id).filter(
+ FormSemestre.date_debut >= scu.date_debut_anne_scolaire(annee_scolaire),
+ FormSemestre.date_debut <= scu.date_fin_anne_scolaire(annee_scolaire),
+ )
+ formation_ids = {formsemestre.formation.id for formsemestre in formsemestres}
+ for formation_id in formation_ids:
+ formation = Formation.query.get(formation_id)
+ xls = formation_table_recap(formation_id, format="xlsx").data
+ filename = (
+ scu.sanitize_filename(formation.get_titre_version()) + scu.XLSX_SUFFIX
+ )
+ zip_file.writestr(filename, xls)
+ zip_file.close()
+ data.seek(0)
+ return send_file(
+ data,
+ mimetype="application/zip",
+ download_name=f"formations-{g.scodoc_dept}-{annee_scolaire}-{annee_scolaire+1}.zip",
+ as_attachment=True,
+ )
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index d47892019..ff91c1495 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -871,6 +871,20 @@ def annee_scolaire_debut(year, month):
return int(year) - 1
+def date_debut_anne_scolaire(annee_scolaire: int) -> datetime:
+ """La date de début de l'année scolaire
+ = 1er aout
+ """
+ return datetime.datetime(year=annee_scolaire, month=8, day=1)
+
+
+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)
+
+
def sem_decale_str(sem):
"""'D' si semestre decalé, ou ''"""
# considère "décalé" les semestre impairs commençant entre janvier et juin
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index 24548b28c..6a0d9e73d 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -1056,7 +1056,7 @@ span.wtf-field ul.errors li {
}
.configuration_logo entete_dept {
- display: inline-block;
+ display: inline-block;
}
.configuration_logo .effectifs {
@@ -3971,4 +3971,18 @@ table.evaluations_recap td.nb_abs,
table.evaluations_recap td.nb_att,
table.evaluations_recap td.nb_exc {
text-align: center;
+}
+
+/* ------------- Tableau récap formation ------------ */
+table.formation_table_recap tr.ue td {
+ font-weight: bold;
+}
+
+table.formation_table_recap td.coef,
+table.formation_table_recap td.ects,
+table.formation_table_recap td.nb_moduleimpls,
+table.formation_table_recap td.heures_cours,
+table.formation_table_recap td.heures_td,
+table.formation_table_recap td.heures_tp {
+ text-align: right;
}
\ No newline at end of file
diff --git a/app/static/js/formation_recap.js b/app/static/js/formation_recap.js
new file mode 100644
index 000000000..0bcb009f4
--- /dev/null
+++ b/app/static/js/formation_recap.js
@@ -0,0 +1,28 @@
+/* Page accueil département */
+var apo_ue_editor = null;
+var apo_mod_editor = null;
+
+$(document).ready(function () {
+ var table_options = {
+ "paging": false,
+ "searching": false,
+ "info": false,
+ /* "autoWidth" : false, */
+ "fixedHeader": {
+ "header": true,
+ "footer": true
+ },
+ "orderCellsTop": true, // cellules ligne 1 pour tri
+ "aaSorting": [], // Prevent initial sorting
+ };
+ $('table#formation_table_recap').DataTable(table_options);
+ let table_editable = document.querySelector("table#formation_table_recap.apo_editable");
+ if (table_editable) {
+ let apo_ue_save_url = document.querySelector("table#formation_table_recap.apo_editable").dataset.apo_ue_save_url;
+ apo_ue_editor = new ScoFieldEditor("table#formation_table_recap tr.ue td.apo", apo_ue_save_url, false);
+ let apo_mod_save_url = document.querySelector("table#formation_table_recap.apo_editable").dataset.apo_mod_save_url;
+ apo_mod_editor = new ScoFieldEditor("table#formation_table_recap tr.mod td.apo", apo_mod_save_url, false);
+ }
+});
+
+
diff --git a/app/static/js/scolar_index.js b/app/static/js/scolar_index.js
index 4718e467e..600a7cf5a 100644
--- a/app/static/js/scolar_index.js
+++ b/app/static/js/scolar_index.js
@@ -15,8 +15,11 @@ $(document).ready(function () {
"aaSorting": [], // Prevent initial sorting
};
$('table.semlist').DataTable(table_options);
- let apo_save_url = document.querySelector("table#semlist.apo_editable").dataset.apo_save_url;
- apo_editor = new ScoFieldEditor(".etapes_apo_str", apo_save_url, false);
+ let table_editable = document.querySelector("table#semlist.apo_editable");
+ if (table_editable) {
+ let apo_save_url = document.querySelector("table#semlist.apo_editable").dataset.apo_save_url;
+ apo_editor = new ScoFieldEditor(".etapes_apo_str", apo_save_url, false);
+ }
});
diff --git a/app/views/notes.py b/app/views/notes.py
index ba0ca78e5..4fc69bdc8 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -45,11 +45,13 @@ from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models.formsemestre import FormSemestre
from app.models.formsemestre import FormSemestreUEComputationExpr
+from app.models.modules import Module
from app.models.ues import UniteEns
from app import api
from app import db
from app import models
+from app.models import ScolarNews
from app.auth.models import User
from app.but import bulletin_but
from app.decorators import (
@@ -86,7 +88,6 @@ from app.scodoc import sco_archives
from app.scodoc import sco_bulletins
from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_cache
-from app.scodoc import sco_compute_moy
from app.scodoc import sco_cost_formation
from app.scodoc import sco_debouche
from app.scodoc import sco_edit_apc
@@ -103,6 +104,7 @@ from app.scodoc import sco_evaluation_edit
from app.scodoc import sco_evaluation_recap
from app.scodoc import sco_export_results
from app.scodoc import sco_formations
+from app.scodoc import sco_formation_recap
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_custommenu
from app.scodoc import sco_formsemestre_edit
@@ -480,7 +482,14 @@ sco_publish(
methods=["GET", "POST"],
)
sco_publish(
- "/formation_table_recap", sco_edit_ue.formation_table_recap, Permission.ScoView
+ "/formation_table_recap",
+ sco_formation_recap.formation_table_recap,
+ Permission.ScoView,
+)
+sco_publish(
+ "/export_recap_formations_annee_scolaire",
+ sco_formation_recap.export_recap_formations_annee_scolaire,
+ Permission.ScoView,
)
sco_publish(
"/formation_add_malus_modules",
@@ -571,6 +580,20 @@ def index_html():
Importer une formation (xml)
+ exporter les formations de l'année scolaire
+ {scu.AnneeScolaire()-1} - {scu.AnneeScolaire()}
+
+
+ exporter les formations de l'année scolaire
+ {scu.AnneeScolaire()} - {scu.AnneeScolaire()+1}
+
+
Référentiels de compétences
@@ -2433,7 +2456,52 @@ def formsemestre_set_apo_etapes():
formsemestre.etapes.append(etape)
db.session.add(formsemestre)
db.session.commit()
+ ScolarNews.add(
+ typ=ScolarNews.NEWS_APO,
+ text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})",
+ )
+ return ("", 204)
+
+@bp.route("/ue_set_apo", methods=["POST"])
+@scodoc
+@permission_required(Permission.ScoEditApo)
+def ue_set_apo():
+ """Change le code APO de l'UE
+ Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE)
+ """
+ ue_id = int(request.form.get("oid"))
+ code_apo = (request.form.get("value") or "").strip()
+ ue = UniteEns.query.get_or_404(ue_id)
+ if code_apo != ue.code_apogee:
+ ue.code_apogee = code_apo
+ db.session.add(ue)
+ db.session.commit()
+ ScolarNews.add(
+ typ=ScolarNews.NEWS_FORM,
+ text=f"Modification code Apogée d'UE dans la formation {ue.formation.titre} ({ue.formation.acronyme})",
+ )
+ return ("", 204)
+
+
+@bp.route("/module_set_apo", methods=["POST"])
+@scodoc
+@permission_required(Permission.ScoEditApo)
+def module_set_apo():
+ """Change le code APO du module
+ Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE)
+ """
+ oid = int(request.form.get("oid"))
+ code_apo = (request.form.get("value") or "").strip()
+ mod = Module.query.get_or_404(oid)
+ if code_apo != mod.code_apogee:
+ mod.code_apogee = code_apo
+ db.session.add(mod)
+ db.session.commit()
+ ScolarNews.add(
+ typ=ScolarNews.NEWS_FORM,
+ text=f"Modification code Apogée d'UE dans la formation {mod.formation.titre} ({mod.formation.acronyme})",
+ )
return ("", 204)
diff --git a/sco_version.py b/sco_version.py
index 9136c351b..a8d3aae11 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.2.5"
+SCOVERSION = "9.2.6"
SCONAME = "ScoDoc"