forked from ScoDoc/ScoDoc
WIP: formulaire ajout justificatif
This commit is contained in:
parent
99b182e1c7
commit
bf4b69c9b2
@ -640,9 +640,9 @@ def justif_import(justif_id: int = None):
|
||||
db.session.commit()
|
||||
|
||||
return {"filename": fname}
|
||||
except ScoValueError as err:
|
||||
except ScoValueError as exc:
|
||||
# Si cela ne fonctionne pas on renvoie une erreur
|
||||
return json_error(404, err.args[0])
|
||||
return json_error(404, exc.args[0])
|
||||
|
||||
|
||||
@bp.route("/justificatif/<int:justif_id>/export/<filename>", methods=["GET", "POST"])
|
||||
|
@ -26,9 +26,11 @@
|
||||
|
||||
"""
|
||||
Formulaire ajout d'une "assiduité" sur un étudiant
|
||||
Formulaire ajout d'un justificatif sur un étudiant
|
||||
"""
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import MultipleFileField
|
||||
from wtforms import (
|
||||
SelectField,
|
||||
StringField,
|
||||
@ -37,20 +39,15 @@ from wtforms import (
|
||||
TextAreaField,
|
||||
validators,
|
||||
)
|
||||
from wtforms.validators import DataRequired
|
||||
|
||||
|
||||
class AjoutAssiduiteEtudForm(FlaskForm):
|
||||
"Formulaire de saisie d'une assiduité pour un étudiant"
|
||||
class AjoutAssiOrJustForm(FlaskForm):
|
||||
"""Elements communs aux deux formulaires ajout
|
||||
assiduité et justificatif
|
||||
"""
|
||||
|
||||
error_message = "" # used to report our errors
|
||||
|
||||
assi_etat = RadioField(
|
||||
"Signaler:",
|
||||
choices=[("absent", "absence"), ("retard", "retard"), ("present", "présence")],
|
||||
default="absent",
|
||||
validators=[
|
||||
validators.DataRequired("spécifiez le type d'évènement à signaler"),
|
||||
],
|
||||
)
|
||||
date_debut = StringField(
|
||||
"Date de début",
|
||||
validators=[validators.Length(max=10)],
|
||||
@ -89,14 +86,9 @@ class AjoutAssiduiteEtudForm(FlaskForm):
|
||||
"id": "assi_date_fin",
|
||||
},
|
||||
)
|
||||
modimpl = SelectField(
|
||||
"Module",
|
||||
choices={}, # will be populated dynamically
|
||||
)
|
||||
assi_raison = TextAreaField(
|
||||
"Raison",
|
||||
render_kw={
|
||||
# "name": "assi_raison",
|
||||
"id": "assi_raison",
|
||||
"cols": 75,
|
||||
"rows": 4,
|
||||
@ -114,3 +106,37 @@ class AjoutAssiduiteEtudForm(FlaskForm):
|
||||
)
|
||||
submit = SubmitField("Enregistrer")
|
||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
||||
|
||||
|
||||
class AjoutAssiduiteEtudForm(AjoutAssiOrJustForm):
|
||||
"Formulaire de saisie d'une assiduité pour un étudiant"
|
||||
|
||||
assi_etat = RadioField(
|
||||
"Signaler:",
|
||||
choices=[("absent", "absence"), ("retard", "retard"), ("present", "présence")],
|
||||
default="absent",
|
||||
validators=[
|
||||
validators.DataRequired("spécifiez le type d'évènement à signaler"),
|
||||
],
|
||||
)
|
||||
modimpl = SelectField(
|
||||
"Module",
|
||||
choices={}, # will be populated dynamically
|
||||
)
|
||||
|
||||
|
||||
class AjoutJustificatifEtudForm(AjoutAssiOrJustForm):
|
||||
"Formulaire de saisie d'un justificatif pour un étudiant"
|
||||
|
||||
etat = SelectField(
|
||||
"État du justificatif",
|
||||
choices=[
|
||||
("", "Choisir..."), # Placeholder
|
||||
("attente", "En attente de validation"),
|
||||
("non_valide", "Non valide"),
|
||||
("modifie", "Modifié"),
|
||||
("valide", "Valide"),
|
||||
],
|
||||
validators=[DataRequired(message="This field is required.")],
|
||||
)
|
||||
fichiers = MultipleFileField()
|
||||
|
@ -373,13 +373,13 @@ class Justificatif(db.Model):
|
||||
}
|
||||
return data
|
||||
|
||||
def __str__(self) -> str:
|
||||
def __repr__(self) -> str:
|
||||
"chaine pour journaux et debug (lisible par humain français)"
|
||||
try:
|
||||
etat_str = EtatJustificatif(self.etat).name
|
||||
except ValueError:
|
||||
etat_str = "Invalide"
|
||||
return f"""Justificatif {etat_str} de {
|
||||
return f"""Justificatif id={self.id} {etat_str} de {
|
||||
self.date_debut.strftime("%d/%m/%Y %Hh%M")
|
||||
} à {
|
||||
self.date_fin.strftime("%d/%m/%Y %Hh%M")
|
||||
@ -411,7 +411,7 @@ class Justificatif(db.Model):
|
||||
|
||||
db.session.add(nouv_justificatif)
|
||||
|
||||
log(f"create_justificatif: {etud.id} {nouv_justificatif}")
|
||||
log(f"create_justificatif: etudid={etud.id} {nouv_justificatif}")
|
||||
Scolog.logdb(
|
||||
method="create_justificatif",
|
||||
etudid=etud.id,
|
||||
@ -482,8 +482,6 @@ def compute_assiduites_justified(
|
||||
etudid: int, justificatifs: list[Justificatif] = None, reset: bool = False
|
||||
) -> list[int]:
|
||||
"""
|
||||
compute_assiduites_justified_faster
|
||||
|
||||
Args:
|
||||
etudid (int): l'identifiant de l'étudiant
|
||||
justificatifs (list[Justificatif]): La liste des justificatifs qui seront utilisés
|
||||
|
@ -122,26 +122,40 @@ def sidebar(etudid: int = None):
|
||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, cur_sem)
|
||||
nbabsnj = nbabs - nbabsjust
|
||||
H.append(
|
||||
f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">({sco_preferences.get_preference("assi_metrique", None)})
|
||||
f"""<span title="absences du { cur_sem["date_debut"] } au {
|
||||
cur_sem["date_fin"] }">({
|
||||
sco_preferences.get_preference("assi_metrique", None)})
|
||||
<br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
|
||||
)
|
||||
H.append("<ul>")
|
||||
if current_user.has_permission(Permission.AbsChange):
|
||||
H.append(
|
||||
f"""
|
||||
<li><a href="{ url_for('assiduites.ajout_assiduite_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Ajouter</a></li>
|
||||
<li><a href="{ url_for('assiduites.ajout_justificatif_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Justifier</a></li>
|
||||
<li><a href="{ url_for('assiduites.ajout_assiduite_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">Ajouter</a></li>
|
||||
<li><a href="{ url_for('assiduites.ajout_justificatif_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">Justifier</a></li>
|
||||
"""
|
||||
)
|
||||
if sco_preferences.get_preference("handle_billets_abs"):
|
||||
H.append(
|
||||
f"""<li><a href="{ url_for('absences.billets_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Billets</a></li>"""
|
||||
f"""<li><a href="{ url_for('absences.billets_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">Billets</a></li>"""
|
||||
)
|
||||
H.append(
|
||||
f"""
|
||||
<li><a href="{ url_for('assiduites.calendrier_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Calendrier</a></li>
|
||||
<li><a href="{ url_for('assiduites.liste_assiduites_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Liste</a></li>
|
||||
<li><a href="{ url_for('assiduites.bilan_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Bilan</a></li>
|
||||
<li><a href="{ url_for('assiduites.calendrier_assi_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">Calendrier</a></li>
|
||||
<li><a href="{ url_for('assiduites.liste_assiduites_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">Liste</a></li>
|
||||
<li><a href="{ url_for('assiduites.bilan_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">Bilan</a></li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
|
@ -122,10 +122,10 @@ class JustificatifArchiver(BaseArchiver):
|
||||
archive_name: str = None,
|
||||
description: str = "",
|
||||
user_id: str = None,
|
||||
) -> str:
|
||||
) -> tuple[str, str]:
|
||||
"""
|
||||
Ajoute un fichier dans une archive "justificatif" pour l'etudid donné
|
||||
Retourne l'archive_name utilisé
|
||||
Retourne l'archive_name utilisé et le filename
|
||||
"""
|
||||
if archive_name is None:
|
||||
archive_id: str = self.create_obj_archive(
|
||||
|
@ -316,7 +316,7 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
|
||||
# --- Absences
|
||||
H.append(
|
||||
f"""<p>
|
||||
<a href="{ url_for('assiduites.calendrier_etud',
|
||||
<a href="{ url_for('assiduites.calendrier_assi_etud',
|
||||
scodoc_dept=g.scodoc_dept, etudid=I['etudid']) }" class="bull_link">
|
||||
<b>Absences :</b>{I['nbabs']} demi-journées, dont {I['nbabsjust']} justifiées
|
||||
(pendant ce semestre).
|
||||
|
@ -134,8 +134,11 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||
if nbabs:
|
||||
H.append(
|
||||
f"""<p class="bul_abs">
|
||||
<a href="{ url_for('assiduites.calendrier_etud', scodoc_dept=g.scodoc_dept, etudid=self.infos["etudid"]) }" class="bull_link">
|
||||
<b>Absences :</b> {self.infos['nbabs']} demi-journées, dont {self.infos['nbabsjust']} justifiées
|
||||
<a href="{ url_for('assiduites.calendrier_assi_etud',
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=self.infos["etudid"]) }" class="bull_link">
|
||||
<b>Absences :</b> {self.infos['nbabs']} demi-journées, dont {
|
||||
self.infos['nbabsjust']} justifiées
|
||||
(pendant ce semestre).
|
||||
</a></p>
|
||||
"""
|
||||
|
@ -147,7 +147,7 @@ def evaluation_check_absences_html(
|
||||
H.append(': <span class="eval_check_absences_ok">ok</span>')
|
||||
H.append("</h2>")
|
||||
|
||||
def etudlist(etudids, linkabs=False):
|
||||
def etudlist(etudids: list[int], linkabs=False):
|
||||
H.append("<ul>")
|
||||
if not etudids and show_ok:
|
||||
H.append("<li>aucun</li>")
|
||||
|
@ -1,15 +1,15 @@
|
||||
:root {
|
||||
--color-present: #6bdb83;
|
||||
--color-absent: #e62a11;
|
||||
--color-absent-clair: #f25d4a;
|
||||
--color-absent-clair: rgb(252, 151, 50);
|
||||
--color-retard: #f0c865;
|
||||
--color-justi: #7059ff;
|
||||
--color-justi-clair: #6885e3;
|
||||
--color-justi: #29b1b9;
|
||||
--color-justi-clair: #48f6ff;
|
||||
--color-justi-invalide: #a84476;
|
||||
--color-nonwork: #badfff;
|
||||
|
||||
--color-absent-justi: #e65ab7;
|
||||
--color-retard-justi: #ffef7a;
|
||||
--color-absent-justi: #ff86d7;
|
||||
--color-retard-justi: #e8c6eb;
|
||||
|
||||
--color-error: #e62a11;
|
||||
--color-warning: #eec660;
|
||||
@ -657,19 +657,35 @@ section.assi-form {
|
||||
table.liste_assi td.date {
|
||||
width: 140px;
|
||||
}
|
||||
table.liste_assi.dataTable tbody td.date-debut {
|
||||
padding-left: 12px;
|
||||
}
|
||||
table.liste_assi td.actions {
|
||||
white-space: nowrap; /* boutons horizontalement */
|
||||
}
|
||||
table.liste_assi td.actions a:last-child {
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
tr.row-assiduite td {
|
||||
border-bottom: 1px solid grey;
|
||||
}
|
||||
table.liste_assi tbody tr td.assi-type {
|
||||
padding-left: 8px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
tr.row-assiduite.absent td.assi-type {
|
||||
background-color: var(--color-absent-clair);
|
||||
}
|
||||
tr.row-assiduite.absent.justifiee td.assi-type {
|
||||
background-color: var(--color-absent-justi);
|
||||
}
|
||||
tr.row-assiduite.retard td.assi-type {
|
||||
background-color: var(--color-retard);
|
||||
}
|
||||
tr.row-assiduite.present td.assi-type {
|
||||
background-color: var(--color-present);
|
||||
}
|
||||
tr.row-justificatif.valide td.assi-type {
|
||||
background-color: var(--color-justi);
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ class RowAssiJusti(tb.Row):
|
||||
self.ligne["date_debut"].strftime("%d/%m/%y à %H:%M"),
|
||||
data={"order": self.ligne["date_debut"]},
|
||||
raw_content=self.ligne["date_debut"],
|
||||
column_classes={"date"},
|
||||
column_classes={"date", "date-debut"},
|
||||
)
|
||||
# Date de fin
|
||||
self.add_cell(
|
||||
@ -256,7 +256,7 @@ class RowAssiJusti(tb.Row):
|
||||
self.ligne["date_fin"].strftime("%d/%m/%y à %H:%M"),
|
||||
raw_content=self.ligne["date_fin"],
|
||||
data={"order": self.ligne["date_fin"]},
|
||||
column_classes={"date"},
|
||||
column_classes={"date", "date-fin"},
|
||||
)
|
||||
|
||||
# Ajout des colonnes optionnelles
|
||||
@ -280,14 +280,16 @@ class RowAssiJusti(tb.Row):
|
||||
obj_type: str = ""
|
||||
is_assiduite: bool = self.ligne["type"] == "assiduite"
|
||||
if is_assiduite:
|
||||
justifiee: str = "Justifiée" if self.ligne["est_just"] else ""
|
||||
self.classes.append("row-assiduite")
|
||||
self.classes.append(EtatAssiduite(self.ligne["etat"]).name.lower())
|
||||
if self.ligne["est_just"]:
|
||||
self.classes.append("justifiee")
|
||||
etat: str = {
|
||||
EtatAssiduite.PRESENT: "Présence",
|
||||
EtatAssiduite.ABSENT: "Absence",
|
||||
EtatAssiduite.RETARD: "Retard",
|
||||
}.get(self.ligne["etat"])
|
||||
justifiee: str = "Justifiée" if self.ligne["est_just"] else ""
|
||||
obj_type = f"{etat} {justifiee}"
|
||||
else:
|
||||
self.classes.append("row-justificatif")
|
||||
@ -462,12 +464,12 @@ class AssiFiltre:
|
||||
type_filtrage, date = val_filtre
|
||||
|
||||
match (type_filtrage):
|
||||
# On garde uniquement les dates supérieur au filtre
|
||||
# On garde uniquement les dates supérieures au filtre
|
||||
case 2:
|
||||
query_filtree = query_filtree.filter(
|
||||
getattr(obj_class, cle_filtre) > date
|
||||
)
|
||||
# On garde uniquement les dates inférieur au filtre
|
||||
# On garde uniquement les dates inférieures au filtre
|
||||
case 1:
|
||||
query_filtree = query_filtree.filter(
|
||||
getattr(obj_class, cle_filtre) < date
|
||||
|
@ -1,245 +0,0 @@
|
||||
{% include "assiduites/widgets/toast.j2" %}
|
||||
{% block pageContent %}
|
||||
<div class="pageContent">
|
||||
<h3>Justifier des absences ou retards</h3>
|
||||
|
||||
<section class="justi-form page">
|
||||
|
||||
<fieldset>
|
||||
<div class="justi-row">
|
||||
<div class="justi-label">
|
||||
<legend for="justi_date_debut" required>Date de début</legend>
|
||||
<scodoc-datetime name="justi_date_debut" id="justi_date_debut"> </scodoc-datetime>
|
||||
<span>Journée entière</span> <input type="checkbox" name="justi_journee" id="justi_journee">
|
||||
</div>
|
||||
<div class="justi-label" id="date_fin">
|
||||
<legend for="justi_date_fin" required>Date de fin</legend>
|
||||
<scodoc-datetime name="justi_date_fin" id="justi_date_fin"></scodoc-datetime>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="justi-row">
|
||||
<div class="justi-label">
|
||||
<legend for="justi_etat" required>Etat du justificatif</legend>
|
||||
<select name="justi_etat" id="justi_etat">
|
||||
<option value="attente" selected>En Attente de validation</option>
|
||||
<option value="non_valide">Non Valide</option>
|
||||
<option value="modifie">Modifié</option>
|
||||
<option value="valide">Valide</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="justi-row">
|
||||
<div class="justi-label">
|
||||
<legend for="justi_raison">Raison</legend>
|
||||
<textarea name="justi_raison" id="justi_raison" cols="50" rows="10" maxlength="500"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="justi-row">
|
||||
<div class="justi-sect">
|
||||
|
||||
</div>
|
||||
<div class="justi-label">
|
||||
<legend for="justi_fich">Importer un fichier</legend>
|
||||
<input type="file" name="justi_fich" id="justi_fich" multiple>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="justi-row">
|
||||
<button onclick="validerFormulaire(this)">Créer le justificatif</button>
|
||||
<button onclick="effacerFormulaire()">Remettre à zero</button>
|
||||
</div>
|
||||
|
||||
|
||||
</fieldset>
|
||||
|
||||
</section>
|
||||
<section class="assi-liste">
|
||||
{{tableau | safe }}
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.justi-row {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.justi-form fieldset {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.pageContent {
|
||||
max-width: var(--sco-content-max-width);
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.justi-label {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
[required]::after {
|
||||
content: "*";
|
||||
color: var(--color-error);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
|
||||
function validateFields() {
|
||||
const field = document.querySelector('.justi-form')
|
||||
const { deb, fin } = getDates()
|
||||
const date_debut = new Date(deb);
|
||||
const date_fin = new Date(fin);
|
||||
|
||||
if (deb == "" || fin == "" || !date_debut.isValid() || !date_fin.isValid()) {
|
||||
openAlertModal("Erreur détéctée", document.createTextNode("Il faut indiquer une date de début et une date de fin valide."), "", color = "crimson");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (date_fin.isBefore(date_debut)) {
|
||||
openAlertModal("Erreur détéctée", document.createTextNode("La date de fin doit se trouver après la date de début."), "", color = "crimson");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function fieldsToJustificatif() {
|
||||
const field = document.querySelector('.justi-form.page')
|
||||
|
||||
const { deb, fin } = getDates()
|
||||
|
||||
const etat = field.querySelector('#justi_etat').value;
|
||||
const raison = field.querySelector('#justi_raison').value;
|
||||
|
||||
return {
|
||||
date_debut: new Date(deb).toFakeIso(),
|
||||
date_fin: new Date(fin).toFakeIso(),
|
||||
etat: etat,
|
||||
raison: raison,
|
||||
}
|
||||
}
|
||||
|
||||
function importFiles(justif_id) {
|
||||
const field = document.querySelector('.justi-form')
|
||||
|
||||
const in_files = field.querySelector('#justi_fich');
|
||||
const path = getUrl() + `/api/justificatif/${justif_id}/import`;
|
||||
|
||||
const requests = []
|
||||
Array.from(in_files.files).forEach((f) => {
|
||||
pushToast(generateToast(document.createTextNode(`Importation du fichier : ${f.name} commencée`), color = "var(--color-information)"));
|
||||
const fd = new FormData();
|
||||
fd.append('file', f);
|
||||
requests.push(
|
||||
$.ajax(
|
||||
{
|
||||
url: path,
|
||||
type: 'POST',
|
||||
data: fd,
|
||||
dateType: 'json',
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: () => {
|
||||
pushToast(generateToast(document.createTextNode(`Importation du fichier : ${f.name} finie`)));
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
});
|
||||
$.when(...requests).done(() => {
|
||||
location.reload();
|
||||
})
|
||||
}
|
||||
|
||||
function validerFormulaire(btn) {
|
||||
if (!validateFields()) return
|
||||
|
||||
const justificatif = fieldsToJustificatif();
|
||||
let justif_id = null;
|
||||
let couverture = null;
|
||||
|
||||
createJustificatif(justificatif, (data) => {
|
||||
if (Object.keys(data.errors).length > 0) {
|
||||
console.error(data.errors);
|
||||
errorAlert();
|
||||
}
|
||||
if (Object.keys(data.success).length > 0) {
|
||||
couverture = data.success[0].message.couverture
|
||||
justif_id = data.success[0].message.justif_id;
|
||||
importFiles(justif_id);
|
||||
return;
|
||||
}
|
||||
})
|
||||
|
||||
btn.disabled = true;
|
||||
setTimeout(() => {
|
||||
btn.disabled = false;
|
||||
}, 1000)
|
||||
|
||||
}
|
||||
|
||||
function effacerFormulaire() {
|
||||
const field = document.querySelector('.justi-form')
|
||||
|
||||
field.querySelector('#justi_date_debut').value = "";
|
||||
field.querySelector('#justi_date_fin').value = "";
|
||||
field.querySelector('#justi_etat').value = "attente";
|
||||
field.querySelector('#justi_raison').value = "";
|
||||
field.querySelector('#justi_fich').value = "";
|
||||
|
||||
}
|
||||
|
||||
function dayOnly() {
|
||||
const date_deb = document.getElementById("justi_date_debut");
|
||||
const date_fin = document.getElementById("justi_date_fin");
|
||||
|
||||
if (document.getElementById('justi_journee').checked) {
|
||||
date_deb.setAttribute("show", "date")
|
||||
date_fin.setAttribute("show", "date")
|
||||
document.querySelector(`legend[for="justi_date_fin"]`).removeAttribute("required")
|
||||
|
||||
} else {
|
||||
date_deb.removeAttribute("show")
|
||||
date_fin.removeAttribute("show")
|
||||
document.querySelector(`legend[for="justi_date_fin"]`).setAttribute("required", "")
|
||||
}
|
||||
}
|
||||
|
||||
function getDates() {
|
||||
const date_deb = document.querySelector(".page #justi_date_debut")
|
||||
const date_fin = document.querySelector(".page #justi_date_fin")
|
||||
const journee = document.querySelector('.page #justi_journee').checked
|
||||
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time)
|
||||
let fin = "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
|
||||
if (journee) {
|
||||
fin = (date_fin.valueAsObject.date || date_deb.valueAsObject.date) + fin
|
||||
} else {
|
||||
fin = date_fin.valueAsObject.date + fin
|
||||
}
|
||||
return {
|
||||
"deb": deb,
|
||||
"fin": fin,
|
||||
}
|
||||
}
|
||||
|
||||
const etudid = {{ sco.etud.id }};
|
||||
|
||||
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
||||
const assi_morning = '{{assi_morning}}';
|
||||
const assi_evening = '{{assi_evening}}';
|
||||
|
||||
window.onload = () => {
|
||||
document.getElementById('justi_journee').addEventListener('click', () => { dayOnly() });
|
||||
dayOnly()
|
||||
|
||||
document.getElementById("justi_date_debut").valueAsObject = { time: assi_morning }
|
||||
document.getElementById("justi_date_fin").valueAsObject = { time: assi_evening }
|
||||
}
|
||||
</script>
|
||||
{% endblock pageContent %}
|
105
app/templates/assiduites/pages/ajout_justificatif_etud.j2
Normal file
105
app/templates/assiduites/pages/ajout_justificatif_etud.j2
Normal file
@ -0,0 +1,105 @@
|
||||
{% extends "sco_page.j2" %}
|
||||
{% import 'wtf.j2' as wtf %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.css"/>
|
||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block app_content %}
|
||||
<style>
|
||||
form#ajout-justificatif-etud {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
form#ajout-justificatif-etud > div {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
div.submit {
|
||||
margin-top: 12px;
|
||||
}
|
||||
div.submit > input {
|
||||
margin-right: 16px;
|
||||
}
|
||||
</style>
|
||||
<div class="tab-content">
|
||||
<h2>Justifier des absences ou retards</h2>
|
||||
|
||||
<section class="justi-form page">
|
||||
|
||||
<form id="ajout-justificatif-etud" method="post">
|
||||
<fieldset>
|
||||
{{ form.hidden_tag() }}
|
||||
{# Dates et heures #}
|
||||
<div class="dates-heures">
|
||||
{{ form.date_debut.label }} : {{ form.date_debut }}
|
||||
de {{ form.heure_debut }} à {{ form.heure_fin }}
|
||||
<span class="help">laisser les heures vides pour indiquer la journée entière</span>
|
||||
{{ render_field_errors(form, 'date_debut') }}
|
||||
{{ render_field_errors(form, 'heure_debut') }}
|
||||
{{ render_field_errors(form, 'heure_fin') }}
|
||||
|
||||
<div>
|
||||
{{ form.date_fin.label }} : {{ form.date_fin }}
|
||||
<span class="help">si le jour de fin est différent,
|
||||
les heures seront ignorées (journées complètes)</span>
|
||||
{{ render_field_errors(form, 'date_fin') }}
|
||||
</div>
|
||||
</div>
|
||||
{# Etat #}
|
||||
<div>
|
||||
<div>{{ form.etat.label }}</div>
|
||||
{{ form.etat() }}
|
||||
{{ render_field_errors(form, 'etat') }}
|
||||
</div>
|
||||
{# Raison #}
|
||||
<div>
|
||||
<div>{{ form.assi_raison.label }}</div>
|
||||
{{ form.assi_raison() }}
|
||||
{{ render_field_errors(form, 'assi_raison') }}
|
||||
</div>
|
||||
{# Fichier(s) justificatif(s) #}
|
||||
<div>
|
||||
<div>{{ form.fichiers.label }}</div>
|
||||
{{ form.fichiers() }}
|
||||
{{ render_field_errors(form, 'fichiers') }}
|
||||
</div>
|
||||
{# Date dépot #}
|
||||
{{ form.entry_date.label }} : {{ form.entry_date }}
|
||||
<span class="help">laisser vide pour date courante</span>
|
||||
{{ render_field_errors(form, 'entry_date') }}
|
||||
{# Submit #}
|
||||
<div class="submit">
|
||||
{{ form.submit }} {{ form.cancel }}
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
</section>
|
||||
<section class="assi-liste">
|
||||
{{tableau | safe }}
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock app_content %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||
<script>
|
||||
$('.timepicker').timepicker({
|
||||
timeFormat: 'HH:mm',
|
||||
interval: {{ scu.get_assiduites_time_config("assi_tick_time") }},
|
||||
minTime: "{{ scu.get_assiduites_time_config("assi_morning_time") }}",
|
||||
maxTime: "{{ scu.get_assiduites_time_config("assi_afternoon_time") }}",
|
||||
startTime: "{{ scu.get_assiduites_time_config("assi_morning_time") }}",
|
||||
dynamic: false,
|
||||
dropdown: true,
|
||||
scrollbar: false
|
||||
});
|
||||
</script>
|
||||
{% endblock scripts %}
|
@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<div class="sco_box_title">Évènements enregistrés pour cet étudiant</div>
|
||||
<div class="sco_box_title">{{ titre }}</div>
|
||||
<div id="options-tableau">
|
||||
{% if afficher_options != false %}
|
||||
<input type="checkbox" id="show_pres" name="show_pres"
|
||||
|
@ -71,7 +71,7 @@
|
||||
etudid=sco.etud.id) }}">Billets</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li><a href="{{ url_for('assiduites.calendrier_etud', scodoc_dept=g.scodoc_dept,
|
||||
<li><a href="{{ url_for('assiduites.calendrier_assi_etud', scodoc_dept=g.scodoc_dept,
|
||||
etudid=sco.etud.id) }}">Calendrier</a></li>
|
||||
<li><a href="{{ url_for('assiduites.liste_assiduites_etud', scodoc_dept=g.scodoc_dept,
|
||||
etudid=sco.etud.id) }}">Liste</a></li>
|
||||
|
@ -32,14 +32,18 @@ from flask import g, request, render_template, flash
|
||||
from flask import abort, url_for, redirect, Response
|
||||
from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
from app import db, log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.decorators import (
|
||||
scodoc,
|
||||
permission_required,
|
||||
)
|
||||
from app.forms.assiduite.ajout_assiduite_etud import AjoutAssiduiteEtudForm
|
||||
from app.forms.assiduite.ajout_assiduite_etud import (
|
||||
AjoutAssiOrJustForm,
|
||||
AjoutAssiduiteEtudForm,
|
||||
AjoutJustificatifEtudForm,
|
||||
)
|
||||
from app.models import (
|
||||
Assiduite,
|
||||
Departement,
|
||||
@ -53,7 +57,7 @@ from app.models import (
|
||||
)
|
||||
from app.scodoc.codes_cursus import UE_STANDARD
|
||||
from app.auth.models import User
|
||||
from app.models.assiduites import get_assiduites_justif
|
||||
from app.models.assiduites import get_assiduites_justif, compute_assiduites_justified
|
||||
from app.tables.list_etuds import RowEtud, TableEtud
|
||||
import app.tables.liste_assiduites as liste_assi
|
||||
|
||||
@ -63,7 +67,6 @@ from app.views import ScoData
|
||||
# ---------------
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import safehtml
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_groups_view
|
||||
@ -254,7 +257,7 @@ def bilan_dept():
|
||||
@bp.route("/ajout_assiduite_etud", methods=["GET", "POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.AbsChange)
|
||||
def ajout_assiduite_etud():
|
||||
def ajout_assiduite_etud() -> str:
|
||||
"""
|
||||
ajout_assiduite_etud Saisie d'une assiduité d'un étudiant
|
||||
|
||||
@ -315,12 +318,11 @@ def ajout_assiduite_etud():
|
||||
liste_assi.AssiJustifData.from_etudiants(
|
||||
etud,
|
||||
),
|
||||
filename=f"assiduite-{etudid}",
|
||||
filename=f"assiduite-{etud.nom or ''}",
|
||||
afficher_etu=False,
|
||||
filtre=liste_assi.AssiFiltre(type_obj=1),
|
||||
options=liste_assi.AssiDisplayOptions(show_module=True),
|
||||
)
|
||||
#
|
||||
if not is_html:
|
||||
return tableau
|
||||
|
||||
@ -336,16 +338,13 @@ def ajout_assiduite_etud():
|
||||
)
|
||||
|
||||
|
||||
def _record_assiduite_etud(
|
||||
etud: Identite,
|
||||
form: AjoutAssiduiteEtudForm,
|
||||
) -> bool:
|
||||
"""Enregistre les données du formulaire de saisie assiduité.
|
||||
Returns ok if successfully recorded, else put error info in the form.
|
||||
Format attendu des données du formulaire:
|
||||
form.assi_etat.data : 'absent'
|
||||
form.date_debut.data : '05/12/2023'
|
||||
form.heure_debut.data : '09:06' (heure locale du serveur)
|
||||
def _get_dates_from_assi_form(
|
||||
form: AjoutAssiOrJustForm,
|
||||
) -> tuple[bool, datetime.datetime, datetime.datetime, datetime.datetime]:
|
||||
"""Prend les dates et heures du form, les vérifie
|
||||
puis converti en deux datetime, en timezone du serveur.
|
||||
Ramène ok=True si ok.
|
||||
Met des messages d'erreur dans le form.
|
||||
"""
|
||||
ok = True
|
||||
debut_jour = "00:00"
|
||||
@ -384,6 +383,44 @@ def _record_assiduite_etud(
|
||||
except ValueError:
|
||||
form.heure_fin.errors.append("heure fin invalide")
|
||||
ok = False
|
||||
# Vérifie cohérence des dates/heures
|
||||
dt_debut = datetime.datetime.combine(date_debut, heure_debut)
|
||||
dt_fin = datetime.datetime.combine(date_fin or date_debut, heure_fin)
|
||||
if dt_fin <= dt_debut:
|
||||
form.errors["general_errors"] = ["Erreur: dates début/fin incohérentes"]
|
||||
return False
|
||||
# La date de dépot (si vide, la date actuelle)
|
||||
dt_entry_date = (
|
||||
datetime.datetime.strptime(form.entry_date.data, "%d/%m/%Y")
|
||||
if form.entry_date.data
|
||||
else None
|
||||
)
|
||||
# Ajoute time zone serveur
|
||||
dt_debut_tz_server = scu.TIME_ZONE.localize(dt_debut)
|
||||
dt_fin_tz_server = scu.TIME_ZONE.localize(dt_fin)
|
||||
dt_entry_date_tz_server = (
|
||||
scu.TIME_ZONE.localize(dt_entry_date) if dt_entry_date else None
|
||||
)
|
||||
return ok, dt_debut_tz_server, dt_fin_tz_server, dt_entry_date_tz_server
|
||||
|
||||
|
||||
def _record_assiduite_etud(
|
||||
etud: Identite,
|
||||
form: AjoutAssiduiteEtudForm,
|
||||
) -> bool:
|
||||
"""Enregistre les données du formulaire de saisie assiduité.
|
||||
Returns ok if successfully recorded, else put error info in the form.
|
||||
Format attendu des données du formulaire:
|
||||
form.assi_etat.data : 'absent'
|
||||
form.date_debut.data : '05/12/2023'
|
||||
form.heure_debut.data : '09:06' (heure locale du serveur)
|
||||
"""
|
||||
(
|
||||
ok,
|
||||
dt_debut_tz_server,
|
||||
dt_fin_tz_server,
|
||||
dt_entry_date_tz_server,
|
||||
) = _get_dates_from_assi_form(form)
|
||||
# Le module (avec "autre")
|
||||
mod_data = form.modimpl.data
|
||||
if mod_data:
|
||||
@ -397,26 +434,10 @@ def _record_assiduite_etud(
|
||||
ok = False
|
||||
else:
|
||||
moduleimpl_id = None
|
||||
# La date de dépot (si viden date actuelle)
|
||||
dt_entry_date = (
|
||||
datetime.datetime.strptime(form.entry_date.data, "%d/%m/%Y")
|
||||
if form.entry_date.data
|
||||
else None
|
||||
)
|
||||
|
||||
if not ok:
|
||||
return False
|
||||
# Vérifie cohérence des dates/heures
|
||||
dt_debut = datetime.datetime.combine(date_debut, heure_debut)
|
||||
dt_fin = datetime.datetime.combine(date_fin or date_debut, heure_fin)
|
||||
if dt_fin <= dt_debut:
|
||||
form.errors["general_errors"] = ["Erreur: dates début/fin incohérentes"]
|
||||
return False
|
||||
# Ajoute time zone serveur
|
||||
dt_debut_tz_server = scu.TIME_ZONE.localize(dt_debut)
|
||||
dt_fin_tz_server = scu.TIME_ZONE.localize(dt_fin)
|
||||
dt_entry_date_tz_server = (
|
||||
scu.TIME_ZONE.localize(dt_entry_date) if dt_entry_date else None
|
||||
)
|
||||
|
||||
external_data = None
|
||||
moduleimpl: ModuleImpl | None = None
|
||||
match moduleimpl_id:
|
||||
@ -446,27 +467,6 @@ def _record_assiduite_etud(
|
||||
form.error_message = f"Erreur: {exc.args[0]}"
|
||||
return False
|
||||
|
||||
# # Génération de la page
|
||||
# return HTMLBuilder(
|
||||
# header,
|
||||
# _mini_timeline(),
|
||||
# render_template(
|
||||
# "assiduites/pages/ajout_assiduites.j2",
|
||||
# sco=ScoData(etud),
|
||||
# assi_limit_annee=sco_preferences.get_preference(
|
||||
# "assi_limit_annee",
|
||||
# dept_id=g.scodoc_dept_id,
|
||||
# ),
|
||||
# saisie_eval=saisie_eval,
|
||||
# date_deb=date_deb,
|
||||
# date_fin=date_fin,
|
||||
# etud=etud,
|
||||
# redirect_url=redirect_url,
|
||||
# moduleimpl_id=moduleimpl_id,
|
||||
# tableau=tableau,
|
||||
# scu=scu,
|
||||
# ),
|
||||
|
||||
|
||||
@bp.route("/liste_assiduites_etud")
|
||||
@scodoc
|
||||
@ -585,7 +585,9 @@ def bilan_etud():
|
||||
).build()
|
||||
|
||||
|
||||
@bp.route("/AjoutJustificatifEtud")
|
||||
@bp.route(
|
||||
"/ajout_justificatif_etud", methods=["GET", "POST"]
|
||||
) # was AjoutJustificatifEtud
|
||||
@scodoc
|
||||
@permission_required(Permission.AbsChange)
|
||||
def ajout_justificatif_etud():
|
||||
@ -597,63 +599,130 @@ def ajout_justificatif_etud():
|
||||
Returns:
|
||||
str: l'html généré
|
||||
"""
|
||||
|
||||
# Récupération de l'étudiant concerné
|
||||
etudid = request.args.get("etudid", -1)
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
if etud.dept_id != g.scodoc_dept_id:
|
||||
abort(404, "étudiant inexistant dans ce département")
|
||||
|
||||
# Préparation de la page (header)
|
||||
header: str = html_sco_header.sco_header(
|
||||
page_title="Justificatifs",
|
||||
init_qtip=True,
|
||||
javascripts=[
|
||||
"js/assiduites.js",
|
||||
"js/date_utils.js",
|
||||
],
|
||||
cssstyles=CSSSTYLES
|
||||
+ [
|
||||
"css/assiduites.css",
|
||||
],
|
||||
etud = Identite.get_etud(request.args.get("etudid"))
|
||||
redirect_url = url_for(
|
||||
"assiduites.calendrier_assi_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||
)
|
||||
|
||||
tableau = _prepare_tableau(
|
||||
form = AjoutJustificatifEtudForm()
|
||||
if form.validate_on_submit():
|
||||
if form.cancel.data: # cancel button
|
||||
return redirect(redirect_url)
|
||||
ok = _record_justificatif_etud(etud, form)
|
||||
if ok:
|
||||
return redirect(redirect_url)
|
||||
|
||||
is_html, tableau = _prepare_tableau(
|
||||
liste_assi.AssiJustifData.from_etudiants(
|
||||
etud,
|
||||
),
|
||||
filename=f"justificatifs-{etudid}",
|
||||
filename=f"justificatifs-{etud.nom or ''}",
|
||||
afficher_etu=False,
|
||||
filtre=liste_assi.AssiFiltre(type_obj=2),
|
||||
options=liste_assi.AssiDisplayOptions(show_module=False, show_desc=True),
|
||||
afficher_options=False,
|
||||
titre="Justificatifs enregistrés pour cet étudiant",
|
||||
)
|
||||
if not tableau[0]:
|
||||
return tableau[1]
|
||||
if not is_html:
|
||||
return tableau
|
||||
|
||||
# Peuplement du template jinja
|
||||
return HTMLBuilder(
|
||||
header,
|
||||
render_template(
|
||||
"assiduites/pages/ajout_justificatif.j2",
|
||||
sco=ScoData(etud),
|
||||
return render_template(
|
||||
"assiduites/pages/ajout_justificatif_etud.j2",
|
||||
assi_limit_annee=sco_preferences.get_preference(
|
||||
"assi_limit_annee",
|
||||
dept_id=g.scodoc_dept_id,
|
||||
),
|
||||
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
|
||||
assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"),
|
||||
tableau=tableau[1],
|
||||
),
|
||||
).build()
|
||||
etud=etud,
|
||||
form=form,
|
||||
page_title="Justificatifs",
|
||||
redirect_url=redirect_url,
|
||||
sco=ScoData(etud),
|
||||
scu=scu,
|
||||
tableau=tableau,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/CalendrierAssiduitesEtud")
|
||||
def _record_justificatif_etud(
|
||||
etud: Identite,
|
||||
form: AjoutJustificatifEtudForm,
|
||||
) -> bool:
|
||||
"""Enregistre les données du formulaire de saisie justificatif (et ses fichiers).
|
||||
Returns ok if successfully recorded, else put error info in the form.
|
||||
Format attendu des données du formulaire:
|
||||
form.assi_etat.data : 'absent'
|
||||
form.date_debut.data : '05/12/2023'
|
||||
form.heure_debut.data : '09:06' (heure locale du serveur)
|
||||
"""
|
||||
(
|
||||
ok,
|
||||
dt_debut_tz_server,
|
||||
dt_fin_tz_server,
|
||||
dt_entry_date_tz_server,
|
||||
) = _get_dates_from_assi_form(form)
|
||||
|
||||
if not ok:
|
||||
return False
|
||||
etat = scu.EtatJustificatif.get(form.etat.data)
|
||||
try:
|
||||
just = Justificatif.create_justificatif(
|
||||
etud,
|
||||
dt_debut_tz_server,
|
||||
dt_fin_tz_server,
|
||||
etat=etat,
|
||||
raison=form.assi_raison.data,
|
||||
entry_date=dt_entry_date_tz_server,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
db.session.add(just)
|
||||
if not _upload_justificatif_files(just, form):
|
||||
db.session.rollback()
|
||||
return False
|
||||
db.session.commit()
|
||||
compute_assiduites_justified(etud.id, [just])
|
||||
flash("Justificatif enregistré")
|
||||
return True
|
||||
except ScoValueError as exc:
|
||||
db.session.rollback()
|
||||
form.error_message = f"Erreur: {exc.args[0]}"
|
||||
return False
|
||||
|
||||
|
||||
def _upload_justificatif_files(
|
||||
just: Justificatif, form: AjoutJustificatifEtudForm
|
||||
) -> bool:
|
||||
"""Enregistre les fichiers du formulaire de création de justificatif"""
|
||||
# Utilisation de l'archiver de justificatifs
|
||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||
archive_name: str = just.fichier
|
||||
try:
|
||||
# On essaye de sauvegarder les fichiers
|
||||
for file in form.fichiers.data or []:
|
||||
archive_name, _ = archiver.save_justificatif(
|
||||
just.etudiant,
|
||||
filename=file.filename,
|
||||
data=file.stream.read(),
|
||||
archive_name=archive_name,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
flash(f"Fichier {file.filename} enregistré")
|
||||
if form.fichiers.data:
|
||||
# On actualise l'archive du justificatif
|
||||
just.fichier = archive_name
|
||||
db.session.add(just)
|
||||
|
||||
return True
|
||||
except ScoValueError as exc:
|
||||
log(f"_upload_justificatif_files: error on {file.filename} for etud {etud.id}")
|
||||
form.error_message = f"Erreur sur fichier justificatif: {exc.args[0]}"
|
||||
return False
|
||||
|
||||
|
||||
@bp.route("/calendrier_assi_etud")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def calendrier_etud():
|
||||
def calendrier_assi_etud():
|
||||
"""
|
||||
calendrier_etud : Affichage d'un calendrier des assiduités de l'étudiant
|
||||
Affichage d'un calendrier de l'assiduité de l'étudiant
|
||||
Args:
|
||||
etudid (int): l'identifiant de l'étudiant
|
||||
|
||||
@ -1028,7 +1097,7 @@ class RowEtudWithAssi(RowEtud):
|
||||
self.etat_assiduite = etat_assiduite
|
||||
# remplace lien vers fiche par lien vers calendrier
|
||||
self.target_url = url_for(
|
||||
"assiduites.calendrier_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||
"assiduites.calendrier_assi_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||
)
|
||||
self.target_title = f"Calendrier de {etud.nomprenom}"
|
||||
|
||||
@ -1206,6 +1275,7 @@ def _prepare_tableau(
|
||||
filtre: liste_assi.AssiFiltre = None,
|
||||
options: liste_assi.AssiDisplayOptions = None,
|
||||
afficher_options: bool = True,
|
||||
titre="Évènements enregistrés pour cet étudiant",
|
||||
) -> tuple[bool, Response | str]:
|
||||
"""
|
||||
Prépare un tableau d'assiduités / justificatifs
|
||||
@ -1274,6 +1344,7 @@ def _prepare_tableau(
|
||||
total_pages=table.total_pages,
|
||||
options=options,
|
||||
afficher_options=afficher_options,
|
||||
titre=titre,
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.67"
|
||||
SCOVERSION = "9.6.68"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user