WIP: nouvelles gestion jury BUT.

This commit is contained in:
Emmanuel Viennet 2023-06-28 21:25:38 +02:00 committed by iziram
parent b4c10146a9
commit d38f233c21
16 changed files with 749 additions and 922 deletions

View File

@ -36,7 +36,6 @@ from app.models import Scolog, ScolarAutorisationInscription
from app.models.but_validations import (
ApcValidationAnnee,
ApcValidationRCUE,
RegroupementCoherentUE,
)
from app.models.etudiants import Identite
from app.models.formations import Formation

File diff suppressed because it is too large Load Diff

View File

@ -153,7 +153,7 @@ def pvjury_table_but(
etudid=etud.id,
),
"cursus": _descr_cursus_but(etud),
"ects": f"{deca.formsemestre_ects():g}",
"ects": f"{deca.ects_annee():g}",
"ues": deca.descr_ues_validation(line_sep=line_sep) if deca else "-",
"niveaux": deca.descr_niveaux_validation(line_sep=line_sep)
if deca

View File

@ -48,9 +48,9 @@ def _get_jury_but_etud_result(
# --- Les RCUEs
rcue_list = []
if deca:
for rcue in deca.rcues_annee:
dec_rcue = deca.dec_rcue_by_ue.get(rcue.ue_1.id)
if dec_rcue is not None: # None si l'UE n'est pas associée à un niveau
for dec_rcue in deca.get_decisions_rcues_annee():
rcue = dec_rcue.rcue
if rcue.complete: # n'exporte que les RCUEs complets
dec_ue1 = deca.decisions_ues[rcue.ue_1.id]
dec_ue2 = deca.decisions_ues[rcue.ue_2.id]
rcue_dict = {

View File

@ -93,35 +93,25 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
<div class="titre">RCUE</div>
"""
)
for niveau in deca.niveaux_competences:
for dec_rcue in deca.get_decisions_rcues_annee():
rcue = dec_rcue.rcue
niveau = rcue.niveau
H.append(
f"""<div class="but_niveau_titre">
<div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div>
</div>"""
)
dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id) # peut être None
ues = [
ue
for ue in deca.ues_impair
if ue.niveau_competence and ue.niveau_competence.id == niveau.id
]
ue_impair = ues[0] if ues else None
ues = [
ue
for ue in deca.ues_pair
if ue.niveau_competence and ue.niveau_competence.id == niveau.id
]
ue_pair = ues[0] if ues else None
ue_impair, ue_pair = rcue.ue_1, rcue.ue_2
# Les UEs à afficher,
# qui seront toujours en readonly sur le formsemestre de l'année précédente du redoublant
# qui
ues_ro = [
(
ue_impair,
(deca.a_cheval and deca.formsemestre_id != deca.formsemestre_impair.id),
rcue.ue_cur_impair is None,
),
(
ue_pair,
deca.a_cheval and deca.formsemestre_id != deca.formsemestre_pair.id,
rcue.ue_cur_pair is None,
),
]
# Ordonne selon les dates des 2 semestres considérés:
@ -155,12 +145,13 @@ def _gen_but_select(
code_valide: str,
disabled: bool = False,
klass: str = "",
data: dict = {},
data: dict = None,
code_valide_label: str = "",
) -> str:
"Le menu html select avec les codes"
# if disabled: # mauvaise idée car le disabled est traité en JS
# return f"""<div class="but_code {klass}">{code_valide}</div>"""
data = data or {}
options_htm = "\n".join(
[
f"""<option value="{code}"
@ -220,8 +211,14 @@ def _gen_but_niveau_ue(
else:
scoplement = ""
return f"""<div class="but_niveau_ue {
'recorded' if dec_ue.code_valide is not None else ''}
ue_class = "" # 'recorded' if dec_ue.code_valide is not None else ''
if dec_ue.code_valide is not None and dec_ue.codes:
if dec_ue.code_valide == dec_ue.codes[0]:
ue_class = "recorded"
else:
ue_class = "recorded_different"
return f"""<div class="but_niveau_ue {ue_class}
{'annee_prec' if annee_prec else ''}
">
<div title="{ue.titre}">{ue.acronyme}</div>
@ -242,7 +239,7 @@ def _gen_but_niveau_ue(
def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
if dec_rcue is None:
if dec_rcue is None or not dec_rcue.rcue.complete:
return """
<div class="but_niveau_rcue niveau_vide with_scoplement">
<div></div>

View File

@ -4,7 +4,6 @@
"""
from typing import Union
from flask_sqlalchemy.query import Query
from app import db
from app.models import CODE_STR_LEN
@ -13,8 +12,6 @@ from app.models.etudiants import Identite
from app.models.formations import Formation
from app.models.formsemestre import FormSemestre
from app.models.ues import UniteEns
from app.scodoc import codes_cursus as sco_codes
from app.scodoc import sco_utils as scu
class ApcValidationRCUE(db.Model):
@ -22,7 +19,7 @@ class ApcValidationRCUE(db.Model):
aka "regroupements cohérents d'UE" dans le jargon BUT.
Le formsemestre est celui du semestre PAIR du niveau de compétence
Le formsemestre est l'origine, utilisé pour effacer
"""
__tablename__ = "apc_validation_rcue"
@ -109,139 +106,6 @@ class ApcValidationRCUE(db.Model):
}
# Attention: ce n'est pas un modèle mais une classe ordinaire:
class RegroupementCoherentUE:
"""Le regroupement cohérent d'UE, dans la terminologie du BUT, est le couple d'UEs
de la même année (BUT1,2,3) liées au *même niveau de compétence*.
La moyenne (10/20) au RCUE déclenche la compensation des UE.
"""
def __init__(
self,
etud: Identite,
formsemestre_1: FormSemestre,
dec_ue_1: "DecisionsProposeesUE",
formsemestre_2: FormSemestre,
dec_ue_2: "DecisionsProposeesUE",
inscription_etat: str,
):
ue_1 = dec_ue_1.ue
ue_2 = dec_ue_2.ue
# Ordonne les UE dans le sens croissant (S1,S2) ou (S3,S4)...
if formsemestre_1.semestre_id > formsemestre_2.semestre_id:
(ue_1, formsemestre_1), (ue_2, formsemestre_2) = (
(ue_2, formsemestre_2),
(ue_1, formsemestre_1),
)
assert formsemestre_1.semestre_id % 2 == 1
assert formsemestre_2.semestre_id % 2 == 0
assert abs(formsemestre_1.semestre_id - formsemestre_2.semestre_id) == 1
assert ue_1.niveau_competence_id == ue_2.niveau_competence_id
self.etud = etud
self.formsemestre_1 = formsemestre_1
"semestre impair"
self.ue_1 = ue_1
self.formsemestre_2 = formsemestre_2
"semestre pair"
self.ue_2 = ue_2
# Stocke les moyennes d'UE
if inscription_etat != scu.INSCRIT:
self.moy_rcue = None
self.moy_ue_1 = self.moy_ue_2 = "-"
self.moy_ue_1_val = self.moy_ue_2_val = 0.0
return
self.moy_ue_1 = dec_ue_1.moy_ue_with_cap
self.moy_ue_1_val = self.moy_ue_1 if self.moy_ue_1 is not None else 0.0
self.moy_ue_2 = dec_ue_2.moy_ue_with_cap
self.moy_ue_2_val = self.moy_ue_2 if self.moy_ue_2 is not None else 0.0
# Calcul de la moyenne au RCUE (utilise les moy d'UE capitalisées)
if (self.moy_ue_1 is not None) and (self.moy_ue_2 is not None):
# Moyenne RCUE (les pondérations par défaut sont 1.)
self.moy_rcue = (
self.moy_ue_1 * ue_1.coef_rcue + self.moy_ue_2 * ue_2.coef_rcue
) / (ue_1.coef_rcue + ue_2.coef_rcue)
else:
self.moy_rcue = None
def __repr__(self) -> str:
return f"""<{self.__class__.__name__} {
self.ue_1.acronyme}({self.moy_ue_1}) {
self.ue_2.acronyme}({self.moy_ue_2})>"""
def __str__(self) -> str:
return f"""RCUE {
self.ue_1.acronyme}({self.moy_ue_1}) + {
self.ue_2.acronyme}({self.moy_ue_2})"""
def query_validations(
self,
) -> Query: # list[ApcValidationRCUE]
"""Les validations de jury enregistrées pour ce RCUE"""
niveau = self.ue_2.niveau_competence
return (
ApcValidationRCUE.query.filter_by(
etudid=self.etud.id,
)
.join(UniteEns, UniteEns.id == ApcValidationRCUE.ue2_id)
.join(ApcNiveau, UniteEns.niveau_competence_id == ApcNiveau.id)
.filter(ApcNiveau.id == niveau.id)
)
def other_ue(self, ue: UniteEns) -> UniteEns:
"""L'autre UE du regroupement. Si ue ne fait pas partie du regroupement, ValueError"""
if ue.id == self.ue_1.id:
return self.ue_2
elif ue.id == self.ue_2.id:
return self.ue_1
raise ValueError(f"ue {ue} hors RCUE {self}")
def est_enregistre(self) -> bool:
"""Vrai si ce RCUE, donc le niveau de compétences correspondant
a une décision jury enregistrée
"""
return self.query_validations().count() > 0
def est_compensable(self):
"""Vrai si ce RCUE est validable (uniquement) par compensation
c'est à dire que sa moyenne est > 10 avec une UE < 10.
Note: si ADM, est_compensable est faux.
"""
return (
(self.moy_rcue is not None)
and (self.moy_rcue > sco_codes.BUT_BARRE_RCUE)
and (
(self.moy_ue_1_val < sco_codes.NOTES_BARRE_GEN)
or (self.moy_ue_2_val < sco_codes.NOTES_BARRE_GEN)
)
)
def est_suffisant(self) -> bool:
"""Vrai si ce RCUE est > 8"""
return (self.moy_rcue is not None) and (
self.moy_rcue > sco_codes.BUT_RCUE_SUFFISANT
)
def est_validable(self) -> bool:
"""Vrai si ce RCUE satisfait les conditions pour être validé,
c'est à dire que la moyenne des UE qui le constituent soit > 10
"""
return (self.moy_rcue is not None) and (
self.moy_rcue > sco_codes.BUT_BARRE_RCUE
)
def code_valide(self) -> Union[ApcValidationRCUE, None]:
"Si ce RCUE est ADM, CMP ou ADJ, la validation. Sinon, None"
validation = self.query_validations().first()
if (validation is not None) and (
validation.code in sco_codes.CODES_RCUE_VALIDES
):
return validation
return None
# unused
# def find_rcues(
# formsemestre: FormSemestre, ue: UniteEns, etud: Identite, inscription_etat: str
@ -319,7 +183,7 @@ class ApcValidationAnnee(db.Model):
formsemestre_id = db.Column(
db.Integer, db.ForeignKey("notes_formsemestre.id"), nullable=True
)
"le semestre IMPAIR (le 1er) de l'année"
"le semestre origine, normalement l'IMPAIR (le 1er) de l'année"
formation_id = db.Column(
db.Integer,
db.ForeignKey("notes_formations.id"),

View File

@ -265,11 +265,8 @@ class ScolarNews(db.Model):
# Informations générales
H.append(
f"""<div>
Pour être informé des évolutions de ScoDoc,
vous pouvez vous
<a class="stdlink" href="{scu.SCO_ANNONCES_WEBSITE}">
abonner à la liste de diffusion</a>.
f"""<div><a class="discretelink" href="{scu.SCO_ANNONCES_WEBSITE}">
Pour en savoir plus sur ScoDoc voir le site scodoc.org</a>.
</div>
"""
)

View File

@ -68,7 +68,7 @@ class ScolarFormSemestreValidation(db.Model):
if self.ue_id:
# Note: si l'objet vient d'être créé, ue_id peut exister mais pas ue !
return f"""décision sur UE {self.ue.acronyme if self.ue else self.ue_id
}: {self.code}"""
} ({self.ue_id}): {self.code}"""
return f"""décision sur semestre {self.formsemestre.titre_mois()} du {
self.event_date.strftime("%d/%m/%Y")}"""

View File

@ -793,7 +793,13 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
{tf[1]}
"""
elif tf[0] == -1:
return "<h4>annulation</h4>"
return redirect(
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
)
)
else:
if tf[2]["gestion_compensation_lst"]:
tf[2]["gestion_compensation"] = True

View File

@ -520,7 +520,7 @@ SCO_DEFAULT_SQL_USERS_CNX = "dbname=SCOUSERS port=%s" % SCO_DEFAULT_SQL_PORT
# Valeurs utilisées pour affichage seulement, pas de requetes ni de mails envoyés:
SCO_WEBSITE = "https://scodoc.org"
SCO_USER_MANUAL = "https://scodoc.org/GuideUtilisateur"
SCO_ANNONCES_WEBSITE = "https://listes.univ-paris13.fr/mailman/listinfo/scodoc-annonces"
SCO_ANNONCES_WEBSITE = "https://scodoc.org/Contact"
SCO_DEVEL_LIST = "scodoc-devel@listes.univ-paris13.fr"
SCO_USERS_LIST = "notes@listes.univ-paris13.fr"
SCO_LISTS_URL = "https://scodoc.org/Contact"

View File

@ -1,291 +1,316 @@
// JS for all ScoDoc pages (using jQuery UI)
$(function () {
// Autocomplete recherche etudiants par nom
$(".in-expnom").autocomplete(
{
delay: 300, // wait 300ms before suggestions
minLength: 2, // min nb of chars before suggest
position: { collision: 'flip' }, // automatic menu position up/down
source: SCO_URL + "/search_etud_by_name",
select: function (event, ui) {
$(".in-expnom").val(ui.item.value);
$("#form-chercheetud").submit();
}
});
// Autocomplete recherche etudiants par nom
$(".in-expnom").autocomplete({
delay: 300, // wait 300ms before suggestions
minLength: 2, // min nb of chars before suggest
position: { collision: "flip" }, // automatic menu position up/down
source: SCO_URL + "/search_etud_by_name",
select: function (event, ui) {
$(".in-expnom").val(ui.item.value);
$("#form-chercheetud").submit();
},
});
// Date picker
$(".datepicker").datepicker({
showOn: 'button',
buttonImage: '/ScoDoc/static/icons/calendar_img.png',
buttonImageOnly: true,
dateFormat: 'dd/mm/yy',
duration: 'fast',
});
$('.datepicker').datepicker('option', $.extend({ showMonthAfterYear: false },
$.datepicker.regional['fr']));
// Date picker
$(".datepicker").datepicker({
showOn: "button",
buttonImage: "/ScoDoc/static/icons/calendar_img.png",
buttonImageOnly: true,
dateFormat: "dd/mm/yy",
duration: "fast",
});
$(".datepicker").datepicker(
"option",
$.extend({ showMonthAfterYear: false }, $.datepicker.regional["fr"])
);
/* Barre menu */
var sco_menu_position = { my: "left top", at: "left bottom" };
$("#sco_menu").menu({
position: sco_menu_position,
blur: function () {
$(this).menu("option", "position", sco_menu_position);
},
focus: function (e, ui) {
if ($("#sco_menu").get(0) !== $(ui).get(0).item.parent().get(0)) {
$(this).menu("option", "position", { my: "left top", at: "right top" });
}
/* Barre menu */
var sco_menu_position = { my: "left top", at: "left bottom" };
$("#sco_menu")
.menu({
position: sco_menu_position,
blur: function () {
$(this).menu("option", "position", sco_menu_position);
},
focus: function (e, ui) {
if ($("#sco_menu").get(0) !== $(ui).get(0).item.parent().get(0)) {
$(this).menu("option", "position", {
my: "left top",
at: "right top",
});
}
}).mouseleave(function (x, y) {
$("#sco_menu").menu('collapseAll');
},
})
.mouseleave(function (x, y) {
$("#sco_menu").menu("collapseAll");
});
$("#sco_menu > li > a > span").switchClass("ui-icon-carat-1-e", "ui-icon-carat-1-s");
$("#sco_menu > li > a > span").switchClass(
"ui-icon-carat-1-e",
"ui-icon-carat-1-s"
);
/* Les menus isoles dropdown */
$(".sco_dropdown_menu").menu({
position: sco_menu_position
}).mouseleave(function (x, y) {
$(".sco_dropdown_menu").menu('collapseAll');
}
);
$(".sco_dropdown_menu > li > a > span").switchClass("ui-icon-carat-1-e", "ui-icon-carat-1-s");
/* Les menus isoles dropdown */
$(".sco_dropdown_menu")
.menu({
position: sco_menu_position,
})
.mouseleave(function (x, y) {
$(".sco_dropdown_menu").menu("collapseAll");
});
$(".sco_dropdown_menu > li > a > span").switchClass(
"ui-icon-carat-1-e",
"ui-icon-carat-1-s"
);
/* up-to-date status */
var update_div = document.getElementById("update_warning");
if (update_div) {
fetch('install_info').then(
response => response.text()
).then(text => {
update_div.innerHTML = text;
if (text) {
update_div.style.display = "block";
}
});
}
/* up-to-date status */
var update_div = document.getElementById("update_warning");
if (update_div) {
fetch("install_info")
.then((response) => response.text())
.then((text) => {
update_div.innerHTML = text;
if (text) {
update_div.style.display = "block";
}
});
}
});
function sco_capitalize(string) {
return string[0].toUpperCase() + string.slice(1).toLowerCase();
return string[0].toUpperCase() + string.slice(1).toLowerCase();
}
// Affiche un message transitoire (duration milliseconds, 0 means infinity)
function sco_message(msg, className = "message_custom", duration = 0) {
var div = document.createElement("div");
div.className = className;
div.innerHTML = msg;
document.querySelector("body").appendChild(div);
if (duration) {
setTimeout(() => {
div.remove();
}, 3000);
}
var div = document.createElement("div");
div.className = className;
div.innerHTML = msg;
document.querySelector("body").appendChild(div);
if (duration) {
setTimeout(() => {
div.remove();
}, 8000);
}
}
function sco_error_message(msg) {
sco_message(msg, className = "message_error", duration = 0);
sco_message(msg, (className = "message_error"), (duration = 0));
}
function get_query_args() {
var s = window.location.search; // eg "?x=1&y=2"
var vars = {};
s.replace(
/[?&]+([^=&]+)=?([^&]*)?/gi, // regexp
function (m, key, value) { // callback
vars[key] = value !== undefined ? value : '';
}
);
return vars;
var s = window.location.search; // eg "?x=1&y=2"
var vars = {};
s.replace(
/[?&]+([^=&]+)=?([^&]*)?/gi, // regexp
function (m, key, value) {
// callback
vars[key] = value !== undefined ? value : "";
}
);
return vars;
}
// Tables (gen_tables)
$(function () {
if ($('table.gt_table').length > 0) {
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.gt_table').DataTable(table_options);
table_options["searching"] = true;
$('table.gt_table_searchable').DataTable(table_options);
}
if ($("table.gt_table").length > 0) {
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.gt_table").DataTable(table_options);
table_options["searching"] = true;
$("table.gt_table_searchable").DataTable(table_options);
}
});
// Show tags (readonly)
function readOnlyTags(nodes) {
// nodes are textareas, hide them and create a span showing tags
for (var i = 0; i < nodes.length; i++) {
var node = $(nodes[i]);
node.hide();
var tags = nodes[i].value.split(',');
node.after('<span class="ro_tags"><span class="ro_tag">' + tags.join('</span><span class="ro_tag">') + '</span></span>');
}
// nodes are textareas, hide them and create a span showing tags
for (var i = 0; i < nodes.length; i++) {
var node = $(nodes[i]);
node.hide();
var tags = nodes[i].value.split(",");
node.after(
'<span class="ro_tags"><span class="ro_tag">' +
tags.join('</span><span class="ro_tag">') +
"</span></span>"
);
}
}
/* Editeur pour champs
* Usage: créer un élément avec data-oid (object id)
* La méthode d'URL save sera appelée en POST avec deux arguments: oid et value,
* La méthode d'URL save sera appelée en POST avec deux arguments: oid et value,
* value contenant la valeur du champs.
* Inspiré par les codes et conseils de Seb. L.
*/
class ScoFieldEditor {
constructor(selector, save_url, read_only) {
this.save_url = save_url;
this.read_only = read_only;
this.selector = selector;
this.installListeners();
constructor(selector, save_url, read_only) {
this.save_url = save_url;
this.read_only = read_only;
this.selector = selector;
this.installListeners();
}
// Enregistre l'élément obj
save(obj) {
var value = obj.innerText.trim();
if (value.length == 0) {
value = "";
}
// Enregistre l'élément obj
save(obj) {
var value = obj.innerText.trim();
if (value.length == 0) {
value = "";
}
if (value == obj.dataset.value) {
return true; // Aucune modification, pas d'enregistrement mais on continue normalement
}
obj.classList.add("sco_wait");
// DEBUG
// console.log(`
// data : ${value},
// id: ${obj.dataset.oid}
// `);
if (value == obj.dataset.value) {
return true; // Aucune modification, pas d'enregistrement mais on continue normalement
}
obj.classList.add("sco_wait");
// DEBUG
// console.log(`
// data : ${value},
// id: ${obj.dataset.oid}
// `);
$.post(this.save_url,
{
oid: obj.dataset.oid,
value: value,
},
function (result) {
obj.classList.remove("sco_wait");
obj.classList.add("sco_modified");
}
);
return true;
$.post(
this.save_url,
{
oid: obj.dataset.oid,
value: value,
},
function (result) {
obj.classList.remove("sco_wait");
obj.classList.add("sco_modified");
}
);
return true;
}
/*****************************/
/* Gestion des évènements */
/*****************************/
installListeners() {
if (this.read_only) {
return;
}
/*****************************/
/* Gestion des évènements */
/*****************************/
installListeners() {
if (this.read_only) {
return;
document.body.addEventListener("keydown", this.key);
let editor = this;
this.handleSelectCell = (event) => {
editor.selectCell(event);
};
this.handleModifCell = (event) => {
editor.modifCell(event);
};
this.handleBlur = (event) => {
editor.blurCell(event);
};
this.handleKeyCell = (event) => {
editor.keyCell(event);
};
document.querySelectorAll(this.selector).forEach((cellule) => {
cellule.addEventListener("click", this.handleSelectCell);
cellule.addEventListener("dblclick", this.handleModifCell);
cellule.addEventListener("blur", this.handleBlur);
});
}
/*********************************/
/* Interaction avec les cellules */
/*********************************/
blurCell(event) {
let currentModif = document.querySelector(".sco_modifying");
if (currentModif) {
if (!this.save(currentModif)) {
return;
}
}
}
selectCell(event) {
let obj = event.currentTarget;
if (obj) {
if (obj.classList.contains("sco_modifying")) {
return; // Cellule en cours de modification, ne pas sélectionner.
}
let currentModif = document.querySelector(".sco_modifying");
if (currentModif) {
if (!this.save(currentModif)) {
return;
}
document.body.addEventListener("keydown", this.key);
let editor = this;
this.handleSelectCell = (event) => { editor.selectCell(event) };
this.handleModifCell = (event) => { editor.modifCell(event) };
this.handleBlur = (event) => { editor.blurCell(event) };
this.handleKeyCell = (event) => { editor.keyCell(event) };
document.querySelectorAll(this.selector).forEach(cellule => {
cellule.addEventListener("click", this.handleSelectCell);
cellule.addEventListener("dblclick", this.handleModifCell);
cellule.addEventListener("blur", this.handleBlur);
});
}
/*********************************/
/* Interaction avec les cellules */
/*********************************/
blurCell(event) {
let currentModif = document.querySelector(".sco_modifying");
if (currentModif) {
if (!this.save(currentModif)) {
return;
}
}
}
selectCell(event) {
let obj = event.currentTarget;
if (obj) {
if (obj.classList.contains("sco_modifying")) {
return; // Cellule en cours de modification, ne pas sélectionner.
}
let currentModif = document.querySelector(".sco_modifying");
if (currentModif) {
if (!this.save(currentModif)) {
return;
}
}
}
this.unselectCell();
obj.classList.add("sco_selected");
}
}
unselectCell() {
document.querySelectorAll(".sco_selected, .sco_modifying").forEach(cellule => {
cellule.classList.remove("sco_selected", "sco_modifying");
cellule.removeAttribute("contentEditable");
cellule.removeEventListener("keydown", this.handleKeyCell);
});
}
modifCell(event) {
let obj = event.currentTarget;
if (obj) {
obj.classList.add("sco_modifying");
obj.contentEditable = true;
obj.addEventListener("keydown", this.handleKeyCell);
obj.focus();
}
}
key(event) {
switch (event.key) {
case "Enter":
this.modifCell(document.querySelector(".sco_selected"));
event.preventDefault();
break;
}
}
keyCell(event) {
let obj = event.currentTarget;
if (obj) {
if (event.key == "Enter") {
event.preventDefault();
event.stopPropagation();
if (!this.save(obj)) {
return
}
obj.classList.remove("sco_modifying");
// ArrowMove(0, 1);
// modifCell(document.querySelector(".sco_selected"));
this.unselectCell();
}
this.unselectCell();
obj.classList.add("sco_selected");
}
}
unselectCell() {
document
.querySelectorAll(".sco_selected, .sco_modifying")
.forEach((cellule) => {
cellule.classList.remove("sco_selected", "sco_modifying");
cellule.removeAttribute("contentEditable");
cellule.removeEventListener("keydown", this.handleKeyCell);
});
}
modifCell(event) {
let obj = event.currentTarget;
if (obj) {
obj.classList.add("sco_modifying");
obj.contentEditable = true;
obj.addEventListener("keydown", this.handleKeyCell);
obj.focus();
}
}
key(event) {
switch (event.key) {
case "Enter":
this.modifCell(document.querySelector(".sco_selected"));
event.preventDefault();
break;
}
}
keyCell(event) {
let obj = event.currentTarget;
if (obj) {
if (event.key == "Enter") {
event.preventDefault();
event.stopPropagation();
if (!this.save(obj)) {
return;
}
obj.classList.remove("sco_modifying");
// ArrowMove(0, 1);
// modifCell(document.querySelector(".sco_selected"));
this.unselectCell();
}
}
}
}
function getCurrentScriptPath() {
// Get all the script elements on the page
var scripts = document.getElementsByTagName('script');
// Get all the script elements on the page
var scripts = document.getElementsByTagName("script");
// Find the last script element (which is the currently executing script)
var currentScript = scripts[scripts.length - 1];
// Find the last script element (which is the currently executing script)
var currentScript = scripts[scripts.length - 1];
// Retrieve the src attribute of the script element
var scriptPath = currentScript.src;
// Retrieve the src attribute of the script element
var scriptPath = currentScript.src;
return scriptPath;
return scriptPath;
}
function removeLastTwoComponents(path) {
// Split the path into individual components
var components = path.split('/');
// Split the path into individual components
var components = path.split("/");
// Remove the last two components (filename and enclosing directory)
components.splice(-2);
// Remove the last two components (filename and enclosing directory)
components.splice(-2);
// Join the remaining components back into a path
var newPath = components.join('/');
// Join the remaining components back into a path
var newPath = components.join("/");
return newPath;
return newPath;
}

View File

@ -74,9 +74,8 @@ class TableJury(TableRecap):
self.freq_codes_annuels[deca.code_valide] += 1
row.add_nb_rcues_cell()
# --- Les RCUEs
for rcue in deca.rcues_annee:
dec_rcue = deca.dec_rcue_by_ue.get(rcue.ue_1.id)
if dec_rcue is not None: # None si l'UE n'est pas associée à un niveau
for dec_rcue in deca.get_decisions_rcues_annee():
if dec_rcue.rcue.complete:
row.add_rcue_cols(dec_rcue)
self.freq_codes_annuels["total"] = len(self.rows)
@ -205,7 +204,7 @@ class RowJury(RowRecap):
else:
classes.append("moy_ue_valid")
if len(deca.rcues_annee) > 0:
if len(deca.get_decisions_rcues_annee()) > 0:
# permet un tri par nb de niveaux validables + moyenne gen indicative S_pair
if deca.res_pair and deca.etud.id in deca.res_pair.etud_moy_gen:
moy = deca.res_pair.etud_moy_gen[deca.etud.id]
@ -260,9 +259,11 @@ class RowJury(RowRecap):
def add_rcue_cols(self, dec_rcue: DecisionsProposeesRCUE):
"2 cells: moyenne du RCUE, code enregistré"
self.table.group_titles["rcue"] = "RCUEs en cours"
rcue = dec_rcue.rcue
col_id = f"moy_rcue_{rcue.ue_1.niveau_competence_id}" # le niveau_id
if not rcue.complete:
return
col_id = f"moy_rcue_{rcue.niveau.id}" # le niveau_id
self.table.group_titles["rcue"] = "RCUEs en cours"
note_class = ""
val = rcue.moy_rcue
if isinstance(val, float):

View File

@ -130,6 +130,12 @@
<td class="amue">CODJ</td>
<td>Acquis par décision du jury</td>
</tr>
<tr>
<td>ADSUP</td>
<td>{{codes["ADSUP"]}}</td>
<td class="amue"></td>
<td>Acquis parce que le niveau de compétence supérieur est acquis</td>
</tr>
<tr>
<td>AJ</td>
<td>{{codes["AJ"]}}</td>
@ -200,6 +206,12 @@
<td class="amue"></td>
<td>Acquis par décision de jury sur le RCUE (ECTS acquis)</td>
</tr>
<tr>
<td>ADSUP</td>
<td>{{codes["ADSUP"]}}</td>
<td class="amue"></td>
<td>Acquis parce que le niveau de compétence supérieur est acquis</td>
</tr>
<tr>
<td>AJ</td>
<td>{{codes["AJ"]}}</td>

View File

@ -13,6 +13,7 @@
<ul>
<li>Semestre: {{ue.semestre_idx}}</li>
<li>Code: <tt>{{ue.ue_code}}</tt></li>
<li>ECTS: <b>{{ue.ects or 0}}</b></li>
<li>Type: {{ue.type}}</li>
<li>Externe: {{ "oui" if ue.is_external else "non" }}</li>
<li>Code Apogée: {{ue.code_apogee or "aucun"}}</li>

View File

@ -2430,7 +2430,7 @@ def formsemestre_validation_but(
)
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
if len(deca.rcues_annee) == 0:
if len(deca.get_decisions_rcues_annee()) == 0:
return jury_but_view.jury_but_semestriel(
formsemestre, etud, read_only, navigation_div=navigation_div
)
@ -2459,22 +2459,25 @@ def formsemestre_validation_but(
warning = ""
if len(deca.niveaux_competences) != len(deca.decisions_rcue_by_niveau):
if deca.a_cheval:
warning += """<div class="warning">Attention: regroupements RCUE
entre années (redoublement).</div>"""
else:
warning += f"""<div class="warning">Attention: {len(deca.niveaux_competences)}
warning += f"""<div class="warning">Attention: {len(deca.niveaux_competences)}
niveaux mais {len(deca.decisions_rcue_by_niveau)} regroupements RCUE.</div>"""
if (deca.parcour is None) and len(formsemestre.parcours) > 0:
warning += (
"""<div class="warning">L'étudiant n'est pas inscrit à un parcours.</div>"""
)
if deca.formsemestre_impair and deca.inscription_etat_impair != scu.INSCRIT:
etat_ins = scu.ETATS_INSCRIPTION.get(deca.inscription_etat_impair, "inconnu?")
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_impair.semestre_id}</div>"""
if deca.formsemestre_pair and deca.inscription_etat_pair != scu.INSCRIT:
etat_ins = scu.ETATS_INSCRIPTION.get(deca.inscription_etat_pair, "inconnu?")
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_pair.semestre_id}</div>"""
if deca.formsemestre_impair:
inscription = deca.formsemestre_impair.etuds_inscriptions.get(etud.id)
if (not inscription) or inscription.etat != scu.INSCRIT:
etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_impair.semestre_id}</div>"""
if deca.formsemestre_pair:
inscription = deca.formsemestre_pair.etuds_inscriptions.get(etud.id)
if (not inscription) or inscription.etat != scu.INSCRIT:
etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_pair.semestre_id}</div>"""
if deca.has_notes_en_attente():
warning += f"""<div class="warning">{etud.nomprenom} a des notes en ATTente.
Vous devriez régler cela avant de statuer en jury !</div>"""
@ -2531,7 +2534,7 @@ def formsemestre_validation_but(
else:
erase_span = f"""<a href="{
url_for("notes.formsemestre_jury_but_erase",
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id,
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre.id,
etudid=deca.etud.id)}" class="stdlink"
title="efface décisions issues des jurys de cette année"
>effacer décisions de ce jury</a>
@ -2564,15 +2567,7 @@ def formsemestre_validation_but(
)
H.append(navigation_div)
H.append("</form>")
if deca.a_cheval:
H.append(
f"""<div class="but_doc_codes but_warning_rcue_cap">
{scu.EMO_WARNING} Rappel: pour les redoublants, seules les UE <b>capitalisées</b> (note > 10)
lors d'une année précédente peuvent être prise en compte pour former
un RCUE (associé à un niveau de compétence du BUT).
</div>
"""
)
# Affichage cursus BUT
but_cursus = cursus_but.EtudCursusBUT(etud, deca.formsemestre.formation)
H += [
@ -2595,7 +2590,14 @@ def formsemestre_validation_but(
codes=ScoDocSiteConfig.get_codes_apo_dict(),
)
)
H.append(
f"""<div class="but_doc_codes but_warning_rcue_cap">
{scu.EMO_WARNING} Rappel: pour les redoublants, seules les UE <b>capitalisées</b> (note > 10)
lors d'une année précédente peuvent être prise en compte pour former
un RCUE (associé à un niveau de compétence du BUT).
</div>
"""
)
return "\n".join(H) + html_sco_header.sco_footer()

View File

@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.4.93"
SCOVERSION = "9.4.94"
SCONAME = "ScoDoc"