forked from ScoDoc/ScoDoc
Compare commits
10 Commits
30781ba9aa
...
d2a17ffdfb
Author | SHA1 | Date | |
---|---|---|---|
|
d2a17ffdfb | ||
|
5be9d711a7 | ||
|
d5f01e0628 | ||
|
36bc67fffc | ||
|
825dc6ecb1 | ||
|
238b6b10d4 | ||
|
cfa209a24b | ||
|
54db0d70d5 | ||
|
fe80051573 | ||
|
4d5c1a84c3 |
@ -1,8 +1,8 @@
|
|||||||
"""api.__init__
|
"""api.__init__
|
||||||
"""
|
"""
|
||||||
|
from flask_json import as_json
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import request, g, jsonify
|
from flask import request, g
|
||||||
from app import db
|
from app import db
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import ScoException
|
from app.scodoc.sco_exceptions import ScoException
|
||||||
@ -35,6 +35,7 @@ def requested_format(default_format="json", allowed_formats=None):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@as_json
|
||||||
def get_model_api_object(model_cls: db.Model, model_id: int, join_cls: db.Model = None):
|
def get_model_api_object(model_cls: db.Model, model_id: int, join_cls: db.Model = None):
|
||||||
"""
|
"""
|
||||||
Retourne une réponse contenant la représentation api de l'objet "Model[model_id]"
|
Retourne une réponse contenant la représentation api de l'objet "Model[model_id]"
|
||||||
@ -48,7 +49,7 @@ def get_model_api_object(model_cls: db.Model, model_id: int, join_cls: db.Model
|
|||||||
query = query.join(join_cls).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(join_cls).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
unique: model_cls = query.first_or_404()
|
unique: model_cls = query.first_or_404()
|
||||||
|
|
||||||
return jsonify(unique.to_dict(format_api=True))
|
return unique.to_dict(format_api=True)
|
||||||
|
|
||||||
|
|
||||||
from app.api import tokens
|
from app.api import tokens
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
"""ScoDoc 9 API : Assiduités
|
"""ScoDoc 9 API : Assiduités
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import g, jsonify, request
|
from flask_json import as_json
|
||||||
|
from flask import g, request
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
import app.scodoc.sco_assiduites as scass
|
import app.scodoc.sco_assiduites as scass
|
||||||
@ -52,6 +53,7 @@ def assiduite(assiduite_id: int = None):
|
|||||||
@api_web_bp.route("/assiduites/<int:etudid>/count/query", defaults={"with_query": True})
|
@api_web_bp.route("/assiduites/<int:etudid>/count/query", defaults={"with_query": True})
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def count_assiduites(etudid: int = None, with_query: bool = False):
|
def count_assiduites(etudid: int = None, with_query: bool = False):
|
||||||
"""
|
"""
|
||||||
@ -109,10 +111,8 @@ def count_assiduites(etudid: int = None, with_query: bool = False):
|
|||||||
if with_query:
|
if with_query:
|
||||||
metric, filtered = _count_manager(request)
|
metric, filtered = _count_manager(request)
|
||||||
|
|
||||||
return jsonify(
|
return scass.get_assiduites_stats(
|
||||||
scass.get_assiduites_stats(
|
assiduites=etud.assiduites, metric=metric, filtered=filtered
|
||||||
assiduites=etud.assiduites, metric=metric, filtered=filtered
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -122,6 +122,7 @@ def count_assiduites(etudid: int = None, with_query: bool = False):
|
|||||||
@api_web_bp.route("/assiduites/<int:etudid>/query", defaults={"with_query": True})
|
@api_web_bp.route("/assiduites/<int:etudid>/query", defaults={"with_query": True})
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def assiduites(etudid: int = None, with_query: bool = False):
|
def assiduites(etudid: int = None, with_query: bool = False):
|
||||||
"""
|
"""
|
||||||
@ -178,13 +179,14 @@ def assiduites(etudid: int = None, with_query: bool = False):
|
|||||||
data = ass.to_dict(format_api=True)
|
data = ass.to_dict(format_api=True)
|
||||||
data_set.append(data)
|
data_set.append(data)
|
||||||
|
|
||||||
return jsonify(data_set)
|
return data_set
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/assiduites/group/query", defaults={"with_query": True})
|
@bp.route("/assiduites/group/query", defaults={"with_query": True})
|
||||||
@api_web_bp.route("/assiduites/group/query", defaults={"with_query": True})
|
@api_web_bp.route("/assiduites/group/query", defaults={"with_query": True})
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def assiduites_group(with_query: bool = False):
|
def assiduites_group(with_query: bool = False):
|
||||||
"""
|
"""
|
||||||
@ -247,12 +249,11 @@ def assiduites_group(with_query: bool = False):
|
|||||||
if with_query:
|
if with_query:
|
||||||
assiduites_query = _filter_manager(request, assiduites_query)
|
assiduites_query = _filter_manager(request, assiduites_query)
|
||||||
|
|
||||||
data_set: dict[list[dict]] = {key: [] for key in etuds}
|
data_set: dict[list[dict]] = {str(key): [] for key in etuds}
|
||||||
for ass in assiduites_query.all():
|
for ass in assiduites_query.all():
|
||||||
data = ass.to_dict(format_api=True)
|
data = ass.to_dict(format_api=True)
|
||||||
data_set.get(data["etudid"]).append(data)
|
data_set.get(str(data["etudid"])).append(data)
|
||||||
|
return data_set
|
||||||
return jsonify(data_set)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
@ -271,6 +272,7 @@ def assiduites_group(with_query: bool = False):
|
|||||||
)
|
)
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||||
"""Retourne toutes les assiduités du formsemestre"""
|
"""Retourne toutes les assiduités du formsemestre"""
|
||||||
@ -291,7 +293,7 @@ def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
|||||||
data = ass.to_dict(format_api=True)
|
data = ass.to_dict(format_api=True)
|
||||||
data_set.append(data)
|
data_set.append(data)
|
||||||
|
|
||||||
return jsonify(data_set)
|
return data_set
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
@ -312,6 +314,7 @@ def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
|||||||
)
|
)
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def count_assiduites_formsemestre(
|
def count_assiduites_formsemestre(
|
||||||
formsemestre_id: int = None, with_query: bool = False
|
formsemestre_id: int = None, with_query: bool = False
|
||||||
@ -334,12 +337,13 @@ def count_assiduites_formsemestre(
|
|||||||
if with_query:
|
if with_query:
|
||||||
metric, filtered = _count_manager(request)
|
metric, filtered = _count_manager(request)
|
||||||
|
|
||||||
return jsonify(scass.get_assiduites_stats(assiduites_query, metric, filtered))
|
return scass.get_assiduites_stats(assiduites_query, metric, filtered)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/assiduite/<int:etudid>/create", methods=["POST"])
|
@bp.route("/assiduite/<int:etudid>/create", methods=["POST"])
|
||||||
@api_web_bp.route("/assiduite/<int:etudid>/create", methods=["POST"])
|
@api_web_bp.route("/assiduite/<int:etudid>/create", methods=["POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
@ -382,12 +386,13 @@ def assiduite_create(etudid: int = None):
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify({"errors": errors, "success": success})
|
return {"errors": errors, "success": success}
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/assiduites/create", methods=["POST"])
|
@bp.route("/assiduites/create", methods=["POST"])
|
||||||
@api_web_bp.route("/assiduites/create", methods=["POST"])
|
@api_web_bp.route("/assiduites/create", methods=["POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
@ -435,7 +440,7 @@ def assiduites_create():
|
|||||||
else:
|
else:
|
||||||
success[i] = obj
|
success[i] = obj
|
||||||
|
|
||||||
return jsonify({"errors": errors, "success": success})
|
return {"errors": errors, "success": success}
|
||||||
|
|
||||||
|
|
||||||
def _create_singular(
|
def _create_singular(
|
||||||
@ -515,6 +520,7 @@ def _create_singular(
|
|||||||
@api_web_bp.route("/assiduite/delete", methods=["POST"])
|
@api_web_bp.route("/assiduite/delete", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def assiduite_delete():
|
def assiduite_delete():
|
||||||
@ -543,7 +549,7 @@ def assiduite_delete():
|
|||||||
else:
|
else:
|
||||||
output["success"][f"{i}"] = {"OK": True}
|
output["success"][f"{i}"] = {"OK": True}
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return jsonify(output)
|
return output
|
||||||
|
|
||||||
|
|
||||||
def _delete_singular(assiduite_id: int, database):
|
def _delete_singular(assiduite_id: int, database):
|
||||||
@ -558,6 +564,7 @@ def _delete_singular(assiduite_id: int, database):
|
|||||||
@api_web_bp.route("/assiduite/<int:assiduite_id>/edit", methods=["POST"])
|
@api_web_bp.route("/assiduite/<int:assiduite_id>/edit", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def assiduite_edit(assiduite_id: int):
|
def assiduite_edit(assiduite_id: int):
|
||||||
@ -625,13 +632,14 @@ def assiduite_edit(assiduite_id: int):
|
|||||||
|
|
||||||
db.session.add(assiduite_unique)
|
db.session.add(assiduite_unique)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return jsonify({"OK": True})
|
return {"OK": True}
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/assiduites/edit", methods=["POST"])
|
@bp.route("/assiduites/edit", methods=["POST"])
|
||||||
@api_web_bp.route("/assiduites/edit", methods=["POST"])
|
@api_web_bp.route("/assiduites/edit", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def assiduites_edit():
|
def assiduites_edit():
|
||||||
@ -666,7 +674,7 @@ def assiduites_edit():
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify({"errors": errors, "success": success})
|
return {"errors": errors, "success": success}
|
||||||
|
|
||||||
|
|
||||||
def _edit_singular(assiduite_unique, data):
|
def _edit_singular(assiduite_unique, data):
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"""
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from flask_json import as_json
|
||||||
from flask import g, jsonify, request
|
from flask import g, jsonify, request
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
@ -57,6 +58,7 @@ def justificatif(justif_id: int = None):
|
|||||||
@api_web_bp.route("/justificatifs/<int:etudid>/query", defaults={"with_query": True})
|
@api_web_bp.route("/justificatifs/<int:etudid>/query", defaults={"with_query": True})
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def justificatifs(etudid: int = None, with_query: bool = False):
|
def justificatifs(etudid: int = None, with_query: bool = False):
|
||||||
"""
|
"""
|
||||||
@ -100,13 +102,14 @@ def justificatifs(etudid: int = None, with_query: bool = False):
|
|||||||
data = just.to_dict(format_api=True)
|
data = just.to_dict(format_api=True)
|
||||||
data_set.append(data)
|
data_set.append(data)
|
||||||
|
|
||||||
return jsonify(data_set)
|
return data_set
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
|
@bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
|
||||||
@api_web_bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
|
@api_web_bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def justif_create(etudid: int = None):
|
def justif_create(etudid: int = None):
|
||||||
@ -145,7 +148,7 @@ def justif_create(etudid: int = None):
|
|||||||
else:
|
else:
|
||||||
success[i] = obj
|
success[i] = obj
|
||||||
compute_assiduites_justified(Justificatif.query.filter_by(etudid=etudid), True)
|
compute_assiduites_justified(Justificatif.query.filter_by(etudid=etudid), True)
|
||||||
return jsonify({"errors": errors, "success": success})
|
return {"errors": errors, "success": success}
|
||||||
|
|
||||||
|
|
||||||
def _create_singular(
|
def _create_singular(
|
||||||
@ -221,6 +224,7 @@ def _create_singular(
|
|||||||
@api_web_bp.route("/justificatif/<int:justif_id>/edit", methods=["POST"])
|
@api_web_bp.route("/justificatif/<int:justif_id>/edit", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def justif_edit(justif_id: int):
|
def justif_edit(justif_id: int):
|
||||||
@ -296,23 +300,22 @@ def justif_edit(justif_id: int):
|
|||||||
db.session.add(justificatif_unique)
|
db.session.add(justificatif_unique)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify(
|
return {
|
||||||
{
|
"couverture": {
|
||||||
"couverture": {
|
"avant": avant_ids,
|
||||||
"avant": avant_ids,
|
"après": compute_assiduites_justified(
|
||||||
"après": compute_assiduites_justified(
|
Justificatif.query.filter_by(etudid=justificatif_unique.etudid),
|
||||||
Justificatif.query.filter_by(etudid=justificatif_unique.etudid),
|
True,
|
||||||
True,
|
),
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/justificatif/delete", methods=["POST"])
|
@bp.route("/justificatif/delete", methods=["POST"])
|
||||||
@api_web_bp.route("/justificatif/delete", methods=["POST"])
|
@api_web_bp.route("/justificatif/delete", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def justif_delete():
|
def justif_delete():
|
||||||
@ -342,7 +345,7 @@ def justif_delete():
|
|||||||
output["success"][f"{i}"] = {"OK": True}
|
output["success"][f"{i}"] = {"OK": True}
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify(output)
|
return output
|
||||||
|
|
||||||
|
|
||||||
def _delete_singular(justif_id: int, database):
|
def _delete_singular(justif_id: int, database):
|
||||||
@ -371,6 +374,7 @@ def _delete_singular(justif_id: int, database):
|
|||||||
@api_web_bp.route("/justificatif/<int:justif_id>/import", methods=["POST"])
|
@api_web_bp.route("/justificatif/<int:justif_id>/import", methods=["POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def justif_import(justif_id: int = None):
|
def justif_import(justif_id: int = None):
|
||||||
@ -407,7 +411,7 @@ def justif_import(justif_id: int = None):
|
|||||||
db.session.add(justificatif_unique)
|
db.session.add(justificatif_unique)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify({"filename": fname})
|
return {"filename": fname}
|
||||||
except ScoValueError as err:
|
except ScoValueError as err:
|
||||||
return json_error(404, err.args[0])
|
return json_error(404, err.args[0])
|
||||||
|
|
||||||
@ -447,6 +451,7 @@ def justif_export(justif_id: int = None, filename: str = None):
|
|||||||
@api_web_bp.route("/justificatif/<int:justif_id>/remove", methods=["POST"])
|
@api_web_bp.route("/justificatif/<int:justif_id>/remove", methods=["POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def justif_remove(justif_id: int = None):
|
def justif_remove(justif_id: int = None):
|
||||||
@ -504,13 +509,14 @@ def justif_remove(justif_id: int = None):
|
|||||||
except ScoValueError as err:
|
except ScoValueError as err:
|
||||||
return json_error(404, err.args[0])
|
return json_error(404, err.args[0])
|
||||||
|
|
||||||
return jsonify({"response": "removed"})
|
return {"response": "removed"}
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/justificatif/<int:justif_id>/list", methods=["GET"])
|
@bp.route("/justificatif/<int:justif_id>/list", methods=["GET"])
|
||||||
@api_web_bp.route("/justificatif/<int:justif_id>/list", methods=["GET"])
|
@api_web_bp.route("/justificatif/<int:justif_id>/list", methods=["GET"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def justif_list(justif_id: int = None):
|
def justif_list(justif_id: int = None):
|
||||||
@ -534,7 +540,7 @@ def justif_list(justif_id: int = None):
|
|||||||
archive_name, justificatif_unique.etudid
|
archive_name, justificatif_unique.etudid
|
||||||
)
|
)
|
||||||
|
|
||||||
return jsonify(filenames)
|
return filenames
|
||||||
|
|
||||||
|
|
||||||
# Partie justification
|
# Partie justification
|
||||||
@ -542,6 +548,7 @@ def justif_list(justif_id: int = None):
|
|||||||
@api_web_bp.route("/justificatif/<int:justif_id>/justifies", methods=["GET"])
|
@api_web_bp.route("/justificatif/<int:justif_id>/justifies", methods=["GET"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def justif_justifies(justif_id: int = None):
|
def justif_justifies(justif_id: int = None):
|
||||||
@ -557,7 +564,7 @@ def justif_justifies(justif_id: int = None):
|
|||||||
|
|
||||||
assiduites_list: list[int] = scass.justifies(justificatif_unique)
|
assiduites_list: list[int] = scass.justifies(justificatif_unique)
|
||||||
|
|
||||||
return jsonify(assiduites_list)
|
return assiduites_list
|
||||||
|
|
||||||
|
|
||||||
# -- Utils --
|
# -- Utils --
|
||||||
|
@ -386,6 +386,11 @@ class BulletinBUT:
|
|||||||
semestre_infos["absences"] = {
|
semestre_infos["absences"] = {
|
||||||
"injustifie": nbabs - nbabsjust,
|
"injustifie": nbabs - nbabsjust,
|
||||||
"total": nbabs,
|
"total": nbabs,
|
||||||
|
"metrique": {
|
||||||
|
"H.": "Heure(s)",
|
||||||
|
"J.": "Journée(s)",
|
||||||
|
"1/2 J.": "1/2 Jour.",
|
||||||
|
}.get(sco_preferences.get_preference("assi_metrique")),
|
||||||
}
|
}
|
||||||
decisions_ues = self.res.get_etud_decisions_ue(etud.id) or {}
|
decisions_ues = self.res.get_etud_decisions_ue(etud.id) or {}
|
||||||
if self.prefs["bul_show_ects"]:
|
if self.prefs["bul_show_ects"]:
|
||||||
|
@ -30,7 +30,7 @@ Formulaire configuration Module Assiduités
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import SubmitField
|
from wtforms import SubmitField, DecimalField
|
||||||
from wtforms.fields.simple import StringField
|
from wtforms.fields.simple import StringField
|
||||||
from wtforms.widgets import TimeInput
|
from wtforms.widgets import TimeInput
|
||||||
import datetime
|
import datetime
|
||||||
@ -82,5 +82,7 @@ class ConfigAssiduitesForm(FlaskForm):
|
|||||||
lunch_time = TimeField("Heure de midi (date pivot entre Matin et Après Midi)")
|
lunch_time = TimeField("Heure de midi (date pivot entre Matin et Après Midi)")
|
||||||
afternoon_time = TimeField("Fin de la journée")
|
afternoon_time = TimeField("Fin de la journée")
|
||||||
|
|
||||||
|
tick_time = DecimalField("Granularité de la Time Line (temps en minutes)", places=0)
|
||||||
|
|
||||||
submit = SubmitField("Valider")
|
submit = SubmitField("Valider")
|
||||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
||||||
|
@ -39,9 +39,11 @@ from app.models.validations import ScolarFormSemestreValidation
|
|||||||
from app.scodoc import codes_cursus, sco_preferences
|
from app.scodoc import codes_cursus, sco_preferences
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_utils import MONTH_NAMES_ABBREV
|
from app.scodoc.sco_utils import MONTH_NAMES_ABBREV, translate_assiduites_metric
|
||||||
from app.scodoc.sco_vdi import ApoEtapeVDI
|
from app.scodoc.sco_vdi import ApoEtapeVDI
|
||||||
|
|
||||||
|
from app.scodoc.sco_utils import translate_assiduites_metric
|
||||||
|
|
||||||
GROUPS_AUTO_ASSIGNMENT_DATA_MAX = 1024 * 1024 # bytes
|
GROUPS_AUTO_ASSIGNMENT_DATA_MAX = 1024 * 1024 # bytes
|
||||||
|
|
||||||
|
|
||||||
@ -678,8 +680,12 @@ class FormSemestre(db.Model):
|
|||||||
"""
|
"""
|
||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_abs
|
||||||
|
|
||||||
return sco_abs.get_abs_count_in_interval(
|
metrique = sco_preferences.get_preference("assi_metrique", self.id)
|
||||||
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
|
return sco_abs.get_assiduites_count_in_interval(
|
||||||
|
etudid,
|
||||||
|
self.date_debut.isoformat(),
|
||||||
|
self.date_fin.isoformat(),
|
||||||
|
translate_assiduites_metric(metrique),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_codes_apogee(self, category=None) -> set[str]:
|
def get_codes_apogee(self, category=None) -> set[str]:
|
||||||
|
@ -116,10 +116,10 @@ def sidebar(etudid: int = None):
|
|||||||
)
|
)
|
||||||
if etud["cursem"]:
|
if etud["cursem"]:
|
||||||
cur_sem = etud["cursem"]
|
cur_sem = etud["cursem"]
|
||||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, cur_sem)
|
nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, cur_sem)
|
||||||
nbabsnj = nbabs - nbabsjust
|
nbabsnj = nbabs - nbabsjust
|
||||||
H.append(
|
H.append(
|
||||||
f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">(1/2 j.)
|
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>"""
|
<br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
|
||||||
)
|
)
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
|
@ -1054,19 +1054,42 @@ def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso):
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def get_assiduites_count_in_interval(etudid, date_debut_iso, date_fin_iso):
|
def get_assiduites_count(etudid, sem):
|
||||||
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
|
tuple (nb abs non justifiées, nb abs justifiées)
|
||||||
|
Utilise un cache.
|
||||||
|
"""
|
||||||
|
metrique = sco_preferences.get_preference("assi_metrique", sem["formsemestre_id"])
|
||||||
|
return get_assiduites_count_in_interval(
|
||||||
|
etudid,
|
||||||
|
sem["date_debut_iso"],
|
||||||
|
sem["date_fin_iso"],
|
||||||
|
scu.translate_assiduites_metric(metrique),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_assiduites_count_in_interval(
|
||||||
|
etudid, date_debut_iso, date_fin_iso, metrique="demi"
|
||||||
|
):
|
||||||
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
||||||
tuple (nb abs, nb abs justifiées)
|
tuple (nb abs, nb abs justifiées)
|
||||||
Utilise un cache.
|
Utilise un cache.
|
||||||
"""
|
"""
|
||||||
key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso + "_assiduites"
|
key = (
|
||||||
|
str(etudid)
|
||||||
|
+ "_"
|
||||||
|
+ date_debut_iso
|
||||||
|
+ "_"
|
||||||
|
+ date_fin_iso
|
||||||
|
+ f"{metrique}_assiduites"
|
||||||
|
)
|
||||||
r = sco_cache.AbsSemEtudCache.get(key)
|
r = sco_cache.AbsSemEtudCache.get(key)
|
||||||
if not r:
|
if not r:
|
||||||
|
|
||||||
date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True)
|
date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True)
|
||||||
date_fin: datetime.datetime = scu.is_iso_formated(date_fin_iso, True)
|
date_fin: datetime.datetime = scu.is_iso_formated(date_fin_iso, True)
|
||||||
|
|
||||||
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
|
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
|
||||||
|
assiduites = assiduites.filter(Assiduite.etat != 0)
|
||||||
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid)
|
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid)
|
||||||
|
|
||||||
assiduites = scass.filter_by_date(assiduites, Assiduite, date_debut, date_fin)
|
assiduites = scass.filter_by_date(assiduites, Assiduite, date_debut, date_fin)
|
||||||
@ -1076,7 +1099,7 @@ def get_assiduites_count_in_interval(etudid, date_debut_iso, date_fin_iso):
|
|||||||
|
|
||||||
calculator: scass.CountCalculator = scass.CountCalculator()
|
calculator: scass.CountCalculator = scass.CountCalculator()
|
||||||
calculator.compute_assiduites(assiduites)
|
calculator.compute_assiduites(assiduites)
|
||||||
nb_abs: dict = calculator.to_dict()["demi"]
|
nb_abs: dict = calculator.to_dict()[metrique]
|
||||||
|
|
||||||
abs_just: list[Assiduite] = scass.get_all_justified(
|
abs_just: list[Assiduite] = scass.get_all_justified(
|
||||||
etudid, date_debut, date_fin
|
etudid, date_debut, date_fin
|
||||||
@ -1084,7 +1107,7 @@ def get_assiduites_count_in_interval(etudid, date_debut_iso, date_fin_iso):
|
|||||||
|
|
||||||
calculator.reset()
|
calculator.reset()
|
||||||
calculator.compute_assiduites(abs_just)
|
calculator.compute_assiduites(abs_just)
|
||||||
nb_abs_just: dict = calculator.to_dict()["demi"]
|
nb_abs_just: dict = calculator.to_dict()[metrique]
|
||||||
|
|
||||||
r = (nb_abs, nb_abs_just)
|
r = (nb_abs, nb_abs_just)
|
||||||
ans = sco_cache.AbsSemEtudCache.set(key, r)
|
ans = sco_cache.AbsSemEtudCache.set(key, r)
|
||||||
@ -1101,6 +1124,15 @@ def invalidate_abs_count(etudid, sem):
|
|||||||
sco_cache.AbsSemEtudCache.delete(key)
|
sco_cache.AbsSemEtudCache.delete(key)
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_assiduites_count(etudid, sem):
|
||||||
|
"""Invalidate (clear) cached counts"""
|
||||||
|
date_debut = sem["date_debut_iso"]
|
||||||
|
date_fin = sem["date_fin_iso"]
|
||||||
|
for met in ["demi", "journee", "compte", "heure"]:
|
||||||
|
key = str(etudid) + "_" + date_debut + "_" + date_fin + f"{met}_assiduites"
|
||||||
|
sco_cache.AbsSemEtudCache.delete(key)
|
||||||
|
|
||||||
|
|
||||||
def invalidate_abs_count_sem(sem):
|
def invalidate_abs_count_sem(sem):
|
||||||
"""Invalidate (clear) cached abs counts for all the students of this semestre"""
|
"""Invalidate (clear) cached abs counts for all the students of this semestre"""
|
||||||
inscriptions = (
|
inscriptions = (
|
||||||
@ -1112,6 +1144,17 @@ def invalidate_abs_count_sem(sem):
|
|||||||
invalidate_abs_count(ins["etudid"], sem)
|
invalidate_abs_count(ins["etudid"], sem)
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_assiduites_count_sem(sem):
|
||||||
|
"""Invalidate (clear) cached abs counts for all the students of this semestre"""
|
||||||
|
inscriptions = (
|
||||||
|
sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
|
||||||
|
sem["formsemestre_id"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for ins in inscriptions:
|
||||||
|
invalidate_assiduites_count(ins["etudid"], sem)
|
||||||
|
|
||||||
|
|
||||||
def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate
|
def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate
|
||||||
"""Doit etre appelé à chaque modification des absences pour cet étudiant et cette date.
|
"""Doit etre appelé à chaque modification des absences pour cet étudiant et cette date.
|
||||||
Invalide cache absence et caches semestre
|
Invalide cache absence et caches semestre
|
||||||
@ -1145,3 +1188,38 @@ def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate
|
|||||||
|
|
||||||
# Inval cache compteurs absences:
|
# Inval cache compteurs absences:
|
||||||
invalidate_abs_count_sem(sem)
|
invalidate_abs_count_sem(sem)
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_assiduites_etud_date(etudid, date):
|
||||||
|
"""Doit etre appelé à chaque modification des assiduites pour cet étudiant et cette date.
|
||||||
|
Invalide cache absence et caches semestre
|
||||||
|
date: date au format ISO
|
||||||
|
"""
|
||||||
|
from app.scodoc import sco_compute_moy
|
||||||
|
|
||||||
|
# Semestres a cette date:
|
||||||
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
|
sems = [
|
||||||
|
sem
|
||||||
|
for sem in etud["sems"]
|
||||||
|
if sem["date_debut_iso"] <= date and sem["date_fin_iso"] >= date
|
||||||
|
]
|
||||||
|
|
||||||
|
# Invalide les PDF et les absences:
|
||||||
|
for sem in sems:
|
||||||
|
# Inval cache bulletin et/ou note_table
|
||||||
|
if sco_compute_moy.formsemestre_expressions_use_abscounts(
|
||||||
|
sem["formsemestre_id"]
|
||||||
|
):
|
||||||
|
# certaines formules utilisent les absences
|
||||||
|
pdfonly = False
|
||||||
|
else:
|
||||||
|
# efface toujours le PDF car il affiche en général les absences
|
||||||
|
pdfonly = True
|
||||||
|
|
||||||
|
sco_cache.invalidate_formsemestre(
|
||||||
|
formsemestre_id=sem["formsemestre_id"], pdfonly=pdfonly
|
||||||
|
)
|
||||||
|
|
||||||
|
# Inval cache compteurs absences:
|
||||||
|
invalidate_assiduites_count(etudid, sem)
|
||||||
|
@ -47,6 +47,7 @@ import app.scodoc.notesdb as ndb
|
|||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
def abs_notify(etudid, date):
|
def abs_notify(etudid, date):
|
||||||
@ -61,8 +62,15 @@ def abs_notify(etudid, date):
|
|||||||
if not formsemestre:
|
if not formsemestre:
|
||||||
return # non inscrit a la date, pas de notification
|
return # non inscrit a la date, pas de notification
|
||||||
|
|
||||||
nbabs, nbabsjust = sco_abs.get_abs_count_in_interval(
|
nbabs, nbabsjust = sco_abs.get_assiduites_count_in_interval(
|
||||||
etudid, formsemestre.date_debut.isoformat(), formsemestre.date_fin.isoformat()
|
etudid,
|
||||||
|
formsemestre.date_debut.isoformat(),
|
||||||
|
formsemestre.date_fin.isoformat(),
|
||||||
|
scu.translate_assiduites_metric(
|
||||||
|
sco_preferences.get_preference(
|
||||||
|
"assi_metrique", formsemestre.formsemestre_id
|
||||||
|
)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
do_abs_notify(formsemestre, etudid, date, nbabs, nbabsjust)
|
do_abs_notify(formsemestre, etudid, date, nbabs, nbabsjust)
|
||||||
|
|
||||||
@ -85,6 +93,7 @@ def do_abs_notify(formsemestre: FormSemestre, etudid, date, nbabs, nbabsjust):
|
|||||||
return # abort
|
return # abort
|
||||||
|
|
||||||
# Vérification fréquence (pour ne pas envoyer de mails trop souvent)
|
# Vérification fréquence (pour ne pas envoyer de mails trop souvent)
|
||||||
|
# TODO Mettre la fréquence dans les préférences assiduités
|
||||||
abs_notify_max_freq = sco_preferences.get_preference("abs_notify_max_freq")
|
abs_notify_max_freq = sco_preferences.get_preference("abs_notify_max_freq")
|
||||||
destinations_filtered = []
|
destinations_filtered = []
|
||||||
for email_addr in destinations:
|
for email_addr in destinations:
|
||||||
@ -174,6 +183,8 @@ def abs_notify_is_above_threshold(etudid, nbabs, nbabsjust, formsemestre_id):
|
|||||||
|
|
||||||
(nbabs > abs_notify_abs_threshold)
|
(nbabs > abs_notify_abs_threshold)
|
||||||
(nbabs - nbabs_last_notified) > abs_notify_abs_increment
|
(nbabs - nbabs_last_notified) > abs_notify_abs_increment
|
||||||
|
|
||||||
|
TODO Mettre à jour avec le module assiduité + fonctionnement métrique
|
||||||
"""
|
"""
|
||||||
abs_notify_abs_threshold = sco_preferences.get_preference(
|
abs_notify_abs_threshold = sco_preferences.get_preference(
|
||||||
"abs_notify_abs_threshold", formsemestre_id
|
"abs_notify_abs_threshold", formsemestre_id
|
||||||
|
@ -197,7 +197,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
pid = partition["partition_id"]
|
pid = partition["partition_id"]
|
||||||
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
||||||
# --- Absences
|
# --- Absences
|
||||||
I["nbabs"], I["nbabsjust"] = sco_abs.get_abs_count(etudid, nt.sem)
|
I["nbabs"], I["nbabsjust"] = sco_abs.get_assiduites_count(etudid, nt.sem)
|
||||||
|
|
||||||
# --- Decision Jury
|
# --- Decision Jury
|
||||||
infos, dpv = etud_descr_situation_semestre(
|
infos, dpv = etud_descr_situation_semestre(
|
||||||
@ -487,7 +487,7 @@ def _ue_mod_bulletin(
|
|||||||
) # peut etre 'NI'
|
) # peut etre 'NI'
|
||||||
is_malus = mod["module"]["module_type"] == ModuleType.MALUS
|
is_malus = mod["module"]["module_type"] == ModuleType.MALUS
|
||||||
if bul_show_abs_modules:
|
if bul_show_abs_modules:
|
||||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
|
nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
|
||||||
mod_abs = [nbabs, nbabsjust]
|
mod_abs = [nbabs, nbabsjust]
|
||||||
mod["mod_abs_txt"] = scu.fmt_abs(mod_abs)
|
mod["mod_abs_txt"] = scu.fmt_abs(mod_abs)
|
||||||
else:
|
else:
|
||||||
|
@ -297,7 +297,7 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
|
|
||||||
# --- Absences
|
# --- Absences
|
||||||
if prefs["bul_show_abs"]:
|
if prefs["bul_show_abs"]:
|
||||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
|
nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
|
||||||
d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)
|
d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)
|
||||||
|
|
||||||
# --- Décision Jury
|
# --- Décision Jury
|
||||||
|
@ -63,6 +63,7 @@ from app.scodoc import sco_etud
|
|||||||
from app.scodoc import sco_xml
|
from app.scodoc import sco_xml
|
||||||
from app.scodoc.sco_xml import quote_xml_attr
|
from app.scodoc.sco_xml import quote_xml_attr
|
||||||
|
|
||||||
|
|
||||||
# -------- Bulletin en XML
|
# -------- Bulletin en XML
|
||||||
# (fonction séparée: n'utilise pas formsemestre_bulletinetud_dict()
|
# (fonction séparée: n'utilise pas formsemestre_bulletinetud_dict()
|
||||||
# pour simplifier le code, mais attention a la maintenance !)
|
# pour simplifier le code, mais attention a la maintenance !)
|
||||||
@ -369,7 +370,7 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
|
|
||||||
# --- Absences
|
# --- Absences
|
||||||
if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
|
if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
|
||||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
|
nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
|
||||||
doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust)))
|
doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust)))
|
||||||
# --- Decision Jury
|
# --- Decision Jury
|
||||||
if (
|
if (
|
||||||
|
@ -668,10 +668,13 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True):
|
|||||||
group_id = sco_groups.get_default_group(formsemestre_id)
|
group_id = sco_groups.get_default_group(formsemestre_id)
|
||||||
H.append(
|
H.append(
|
||||||
f"""<span class="noprint"><a href="{url_for(
|
f"""<span class="noprint"><a href="{url_for(
|
||||||
'absences.EtatAbsencesDate',
|
'assiduites.get_etat_abs_date',
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
group_ids=group_id,
|
group_ids=group_id,
|
||||||
date=E["jour"]
|
desc=E["description"],
|
||||||
|
jour=E["jour"],
|
||||||
|
heure_debut=E["heure_debut"],
|
||||||
|
heure_fin=E["heure_fin"],
|
||||||
)
|
)
|
||||||
}">(absences ce jour)</a></span>"""
|
}">(absences ce jour)</a></span>"""
|
||||||
)
|
)
|
||||||
|
@ -823,34 +823,20 @@ def _make_listes_sem(formsemestre: FormSemestre, with_absences=True):
|
|||||||
first_monday = sco_abs.ddmmyyyy(
|
first_monday = sco_abs.ddmmyyyy(
|
||||||
formsemestre.date_debut.strftime("%d/%m/%Y")
|
formsemestre.date_debut.strftime("%d/%m/%Y")
|
||||||
).prev_monday()
|
).prev_monday()
|
||||||
form_abs_tmpl = f"""
|
form_abs_tmpl = """
|
||||||
<td>
|
<td>
|
||||||
<a href="%(url_etat)s">absences</a>
|
<a class="btn" href="%(url_etat)s"><button>Voir l'assiduité</button></a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<form action="{url_for(
|
|
||||||
"assiduites.signal_assiduites_group", scodoc_dept=g.scodoc_dept
|
|
||||||
)}" method="get">
|
|
||||||
<input type="hidden" name="date" value="{
|
|
||||||
formsemestre.date_fin.strftime("%d/%m/%Y")}"/>
|
|
||||||
<input type="hidden" name="group_ids" value="%(group_id)s"/>
|
|
||||||
<input type="hidden" name="destination" value="{destination}"/>
|
|
||||||
<input type="submit" value="Saisir abs des" />
|
|
||||||
<select name="datedebut" class="noprint">
|
|
||||||
"""
|
"""
|
||||||
date = first_monday
|
|
||||||
for idx, jour in enumerate(sco_abs.day_names()):
|
|
||||||
form_abs_tmpl += f"""<option value="{date}" {
|
|
||||||
'selected' if idx == weekday else ''
|
|
||||||
}>{jour}s</option>"""
|
|
||||||
date = date.next_day()
|
|
||||||
form_abs_tmpl += f"""
|
form_abs_tmpl += f"""
|
||||||
</select>
|
<a class="btn" href="{
|
||||||
|
|
||||||
<a href="{
|
|
||||||
url_for("assiduites.signal_assiduites_group", scodoc_dept=g.scodoc_dept)
|
url_for("assiduites.signal_assiduites_group", scodoc_dept=g.scodoc_dept)
|
||||||
}?group_ids=%(group_id)s&jour={datetime.date.today().isoformat()}&formsemestre_id={formsemestre.id}">saisie par semaine</a>
|
}?group_ids=%(group_id)s&jour={datetime.date.today().isoformat()}&formsemestre_id={formsemestre.id}"><button>Saisie Journalière</button></a>
|
||||||
</form></td>
|
<a class="btn" href="{
|
||||||
|
url_for("assiduites.signal_assiduites_diff", scodoc_dept=g.scodoc_dept)
|
||||||
|
}?group_ids=%(group_id)s&formsemestre_id={formsemestre.formsemestre_id}"><button>Saisie Différée</button></a>
|
||||||
|
</td>
|
||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
form_abs_tmpl = ""
|
form_abs_tmpl = ""
|
||||||
|
@ -696,7 +696,7 @@ def formsemestre_recap_parcours_table(
|
|||||||
f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>"""
|
f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>"""
|
||||||
)
|
)
|
||||||
# Absences (nb d'abs non just. dans ce semestre)
|
# Absences (nb d'abs non just. dans ce semestre)
|
||||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
|
nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
|
||||||
H.append(f"""<td class="rcp_abs">{nbabs - nbabsjust}</td>""")
|
H.append(f"""<td class="rcp_abs">{nbabs - nbabsjust}</td>""")
|
||||||
|
|
||||||
# UEs
|
# UEs
|
||||||
|
@ -57,6 +57,7 @@ from app.scodoc import sco_moduleimpl
|
|||||||
from app.scodoc import sco_permissions_check
|
from app.scodoc import sco_permissions_check
|
||||||
from app.tables import list_etuds
|
from app.tables import list_etuds
|
||||||
|
|
||||||
|
|
||||||
# menu evaluation dans moduleimpl
|
# menu evaluation dans moduleimpl
|
||||||
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
|
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
|
||||||
"Menu avec actions sur une evaluation"
|
"Menu avec actions sur une evaluation"
|
||||||
@ -139,8 +140,11 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
|
|||||||
"title": "Absences ce jour",
|
"title": "Absences ce jour",
|
||||||
"endpoint": "absences.EtatAbsencesDate",
|
"endpoint": "absences.EtatAbsencesDate",
|
||||||
"args": {
|
"args": {
|
||||||
"date": E["jour"],
|
|
||||||
"group_ids": group_id,
|
"group_ids": group_id,
|
||||||
|
"desc": E["description"],
|
||||||
|
"jour": E["jour"],
|
||||||
|
"heure_debut": E["heure_debut"],
|
||||||
|
"heure_fin": E["heure_fin"],
|
||||||
},
|
},
|
||||||
"enabled": E["jour"],
|
"enabled": E["jour"],
|
||||||
},
|
},
|
||||||
|
@ -107,7 +107,7 @@ def etud_get_poursuite_info(sem, etud):
|
|||||||
rangs.append(["rang_" + codeModule, rangModule])
|
rangs.append(["rang_" + codeModule, rangModule])
|
||||||
|
|
||||||
# Absences
|
# Absences
|
||||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, nt.sem)
|
nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, nt.sem)
|
||||||
if (
|
if (
|
||||||
dec
|
dec
|
||||||
and not sem_descr # not sem_descr pour ne prendre que le semestre validé le plus récent
|
and not sem_descr # not sem_descr pour ne prendre que le semestre validé le plus récent
|
||||||
|
@ -621,7 +621,18 @@ class BasePreferences(object):
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"etat_defaut",
|
"periode_defaut",
|
||||||
|
{
|
||||||
|
"initvalue": 2.0,
|
||||||
|
"size": 10,
|
||||||
|
"title": "Durée par défaut d'un créneau",
|
||||||
|
"type": "float",
|
||||||
|
"category": "assi",
|
||||||
|
"only_global": True,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"assi_etat_defaut",
|
||||||
{
|
{
|
||||||
"initvalue": "aucun",
|
"initvalue": "aucun",
|
||||||
"input_type": "menu",
|
"input_type": "menu",
|
||||||
@ -631,6 +642,30 @@ class BasePreferences(object):
|
|||||||
"category": "assi",
|
"category": "assi",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"non_travail",
|
||||||
|
{
|
||||||
|
"initvalue": "sam, dim",
|
||||||
|
"title": "Jours non travaillés",
|
||||||
|
"size": 40,
|
||||||
|
"category": "assi",
|
||||||
|
"only_global": True,
|
||||||
|
"explanation": "Liste des jours (lun,mar,mer,jeu,ven,sam,dim)",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"assi_metrique",
|
||||||
|
{
|
||||||
|
"initvalue": "1/2 J.",
|
||||||
|
"input_type": "menu",
|
||||||
|
"labels": ["1/2 J.", "J.", "H."],
|
||||||
|
"allowed_values": ["1/2 J.", "J.", "H."],
|
||||||
|
"title": "Métrique de l'assiduité",
|
||||||
|
"explanation": "Unité affichée dans la fiche étudiante et le bilan\n(J. = journée, H. = heure)",
|
||||||
|
"category": "assi",
|
||||||
|
"only_global": True,
|
||||||
|
},
|
||||||
|
),
|
||||||
# portal
|
# portal
|
||||||
(
|
(
|
||||||
"portal_url",
|
"portal_url",
|
||||||
|
@ -139,7 +139,7 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
main_partition_id, ""
|
main_partition_id, ""
|
||||||
)
|
)
|
||||||
# absences:
|
# absences:
|
||||||
e_nbabs, e_nbabsjust = sco_abs.get_abs_count(etud.id, sem)
|
e_nbabs, e_nbabsjust = sco_abs.get_assiduites_count(etud.id, sem)
|
||||||
nbabs[etud.id] = e_nbabs
|
nbabs[etud.id] = e_nbabs
|
||||||
nbabsjust[etud.id] = e_nbabs - e_nbabsjust
|
nbabsjust[etud.id] = e_nbabs - e_nbabsjust
|
||||||
|
|
||||||
|
@ -251,6 +251,17 @@ def is_period_overlapping(
|
|||||||
return p_deb < i_fin and p_fin > i_deb
|
return p_deb < i_fin and p_fin > i_deb
|
||||||
|
|
||||||
|
|
||||||
|
def translate_assiduites_metric(hr_metric) -> str:
|
||||||
|
if hr_metric == "1/2 J.":
|
||||||
|
return "demi"
|
||||||
|
if hr_metric == "J.":
|
||||||
|
return "journee"
|
||||||
|
if hr_metric == "N.":
|
||||||
|
return "compte"
|
||||||
|
if hr_metric == "H.":
|
||||||
|
return "heure"
|
||||||
|
|
||||||
|
|
||||||
# Types de modules
|
# Types de modules
|
||||||
class ModuleType(IntEnum):
|
class ModuleType(IntEnum):
|
||||||
"""Code des types de module."""
|
"""Code des types de module."""
|
||||||
|
@ -28,6 +28,9 @@
|
|||||||
.infos {
|
.infos {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#datestr {
|
#datestr {
|
||||||
@ -36,6 +39,9 @@
|
|||||||
border: 1px #444 solid;
|
border: 1px #444 solid;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
min-width: 100px;
|
||||||
|
display: inline-block;
|
||||||
|
min-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tl_slider {
|
#tl_slider {
|
||||||
|
@ -439,9 +439,9 @@ function hideLoader() {
|
|||||||
*/
|
*/
|
||||||
function toTime(time) {
|
function toTime(time) {
|
||||||
let heure = Math.floor(time);
|
let heure = Math.floor(time);
|
||||||
let minutes = (time - heure) * 60;
|
let minutes = Math.round((time - heure) * 60);
|
||||||
if (minutes < 1) {
|
if (minutes < 10) {
|
||||||
minutes = "00";
|
minutes = `0${minutes}`;
|
||||||
}
|
}
|
||||||
if (heure < 10) {
|
if (heure < 10) {
|
||||||
heure = `0${heure}`;
|
heure = `0${heure}`;
|
||||||
@ -467,7 +467,18 @@ function updateDate() {
|
|||||||
|
|
||||||
const date = dateInput.valueAsDate;
|
const date = dateInput.valueAsDate;
|
||||||
|
|
||||||
$("#datestr").text(formatDate(date).capitalize());
|
if (!verifyNonWorkDays(date.getDay(), nonWorkDays)) {
|
||||||
|
$("#datestr").text(formatDate(date).capitalize());
|
||||||
|
dateInput.setAttribute("value", date.toISOString().split("T")[0]);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
const att = document.createTextNode(
|
||||||
|
"Le jour sélectionné n'est pas un jour travaillé."
|
||||||
|
);
|
||||||
|
openAlertModal("Erreur", att, "", "crimson");
|
||||||
|
dateInput.value = dateInput.getAttribute("value");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function verifyDateInSemester() {
|
function verifyDateInSemester() {
|
||||||
@ -519,6 +530,39 @@ function formatDateModal(str, separator = "·") {
|
|||||||
return new moment.tz(str, TIMEZONE).format(`DD/MM/Y ${separator} HH:mm`);
|
return new moment.tz(str, TIMEZONE).format(`DD/MM/Y ${separator} HH:mm`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si la date sélectionnée n'est pas un jour non travaillé
|
||||||
|
* Renvoie Vrai si le jour est non travaillé
|
||||||
|
*/
|
||||||
|
function verifyNonWorkDays(day, nonWorkdays) {
|
||||||
|
let d = "";
|
||||||
|
switch (day) {
|
||||||
|
case 0:
|
||||||
|
d = "dim";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
d = "lun";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
d = "mar";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
d = "mer";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
d = "jeu";
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
d = "ven";
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
d = "sam";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nonWorkdays.indexOf(d) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fonction qui vérifie si une période est dans un interval
|
* Fonction qui vérifie si une période est dans un interval
|
||||||
* Objet période / interval
|
* Objet période / interval
|
||||||
@ -573,7 +617,9 @@ function isConflictSameAsTimeLine(conflict) {
|
|||||||
* @returns {Date} la date sélectionnée
|
* @returns {Date} la date sélectionnée
|
||||||
*/
|
*/
|
||||||
function getDate() {
|
function getDate() {
|
||||||
const date = document.querySelector("#tl_date").valueAsDate;
|
const date = new Date(
|
||||||
|
document.querySelector("#tl_date").getAttribute("value")
|
||||||
|
);
|
||||||
date.setHours(0, 0, 0, 0);
|
date.setHours(0, 0, 0, 0);
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
@ -997,7 +1043,6 @@ function generateEtudRow(
|
|||||||
</div>
|
</div>
|
||||||
<div class="assiduites_bar">
|
<div class="assiduites_bar">
|
||||||
<div id="prevDateAssi" class="${assiduite.prevAssiduites?.etat?.toLowerCase()}">
|
<div id="prevDateAssi" class="${assiduite.prevAssiduites?.etat?.toLowerCase()}">
|
||||||
<span class="mini_tick">13h</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="btns_field single" etudid="${etud.id}" assiduite_id="${
|
<fieldset class="btns_field single" etudid="${etud.id}" assiduite_id="${
|
||||||
@ -1065,172 +1110,6 @@ function insertEtudRow(etud, index, output = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Création de la minitiline d'un étudiant
|
|
||||||
* @param {Array[Assiduité]} assiduitesArray
|
|
||||||
* @returns {HTMLElement} l'élément correspondant à la mini timeline
|
|
||||||
*/
|
|
||||||
function createMiniTimeline(assiduitesArray) {
|
|
||||||
const array = [...assiduitesArray];
|
|
||||||
const dateiso = document.getElementById("tl_date").value;
|
|
||||||
const timeline = document.createElement("div");
|
|
||||||
timeline.className = "mini-timeline";
|
|
||||||
if (isSingleEtud()) {
|
|
||||||
timeline.classList.add("single");
|
|
||||||
}
|
|
||||||
const timelineDate = moment(dateiso).startOf("day");
|
|
||||||
const dayStart = timelineDate.clone().add(8, "hours");
|
|
||||||
const dayEnd = timelineDate.clone().add(18, "hours");
|
|
||||||
const dayDuration = moment.duration(dayEnd.diff(dayStart)).asMinutes();
|
|
||||||
|
|
||||||
const tlTimes = getTimeLineTimes();
|
|
||||||
|
|
||||||
const period_assi = {
|
|
||||||
date_debut: tlTimes.deb.format(),
|
|
||||||
date_fin: tlTimes.fin.format(),
|
|
||||||
etat: "CRENEAU",
|
|
||||||
};
|
|
||||||
|
|
||||||
array.push(period_assi);
|
|
||||||
|
|
||||||
array.forEach((assiduité) => {
|
|
||||||
const startDate = moment(assiduité.date_debut);
|
|
||||||
const endDate = moment(assiduité.date_fin);
|
|
||||||
|
|
||||||
if (startDate.isBefore(dayStart)) {
|
|
||||||
startDate.startOf("day").add(8, "hours");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endDate.isAfter(dayEnd)) {
|
|
||||||
endDate.startOf("day").add(18, "hours");
|
|
||||||
}
|
|
||||||
|
|
||||||
const block = document.createElement("div");
|
|
||||||
block.className = "mini-timeline-block";
|
|
||||||
|
|
||||||
const startOffset = moment.duration(startDate.diff(dayStart)).asMinutes();
|
|
||||||
const duration = moment.duration(endDate.diff(startDate)).asMinutes();
|
|
||||||
const leftPercentage = (startOffset / dayDuration) * 100;
|
|
||||||
const widthPercentage = (duration / dayDuration) * 100;
|
|
||||||
|
|
||||||
block.style.left = `${leftPercentage}%`;
|
|
||||||
block.style.width = `${widthPercentage}%`;
|
|
||||||
|
|
||||||
if (assiduité.etat != "CRENEAU") {
|
|
||||||
if (isSingleEtud()) {
|
|
||||||
block.addEventListener("click", () => {
|
|
||||||
let deb = startDate.hours() + startDate.minutes() / 60;
|
|
||||||
let fin = endDate.hours() + endDate.minutes() / 60;
|
|
||||||
deb = Math.max(8, deb);
|
|
||||||
fin = Math.min(18, fin);
|
|
||||||
|
|
||||||
setPeriodValues(deb, fin);
|
|
||||||
updateSelectedSelect(getCurrentAssiduiteModuleImplId());
|
|
||||||
updateJustifyBtn();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//ajouter affichage assiduites on over
|
|
||||||
setupAssiduiteBuble(block, assiduité);
|
|
||||||
}
|
|
||||||
|
|
||||||
const action = (justificatifs) => {
|
|
||||||
if (justificatifs.length > 0) {
|
|
||||||
let j = "invalid_justified";
|
|
||||||
|
|
||||||
justificatifs.forEach((ju) => {
|
|
||||||
if (ju.etat == "VALIDE") {
|
|
||||||
j = "justified";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
block.classList.add(j);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (assiduité.etudid) {
|
|
||||||
getJustificatifFromPeriod(
|
|
||||||
{
|
|
||||||
deb: new moment.tz(assiduité.date_debut, TIMEZONE),
|
|
||||||
fin: new moment.tz(assiduité.date_fin, TIMEZONE),
|
|
||||||
},
|
|
||||||
assiduité.etudid,
|
|
||||||
action
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (assiduité.etat) {
|
|
||||||
case "PRESENT":
|
|
||||||
block.classList.add("present");
|
|
||||||
break;
|
|
||||||
case "RETARD":
|
|
||||||
block.classList.add("retard");
|
|
||||||
break;
|
|
||||||
case "ABSENT":
|
|
||||||
block.classList.add("absent");
|
|
||||||
break;
|
|
||||||
case "CRENEAU":
|
|
||||||
block.classList.add("creneau");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
block.style.backgroundColor = "white";
|
|
||||||
}
|
|
||||||
|
|
||||||
timeline.appendChild(block);
|
|
||||||
});
|
|
||||||
|
|
||||||
return timeline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ajout de la visualisation des assiduités de la mini timeline
|
|
||||||
* @param {HTMLElement} el l'élément survollé
|
|
||||||
* @param {Assiduité} assiduite l'assiduité représentée par l'élément
|
|
||||||
*/
|
|
||||||
function setupAssiduiteBuble(el, assiduite) {
|
|
||||||
if (!assiduite) return;
|
|
||||||
el.addEventListener("mouseenter", (event) => {
|
|
||||||
const bubble = document.querySelector(".assiduite-bubble");
|
|
||||||
bubble.className = "assiduite-bubble";
|
|
||||||
bubble.classList.add("is-active", assiduite.etat.toLowerCase());
|
|
||||||
|
|
||||||
bubble.innerHTML = "";
|
|
||||||
|
|
||||||
const idDiv = document.createElement("div");
|
|
||||||
idDiv.className = "assiduite-id";
|
|
||||||
idDiv.textContent = `ID: ${assiduite.assiduite_id}`;
|
|
||||||
bubble.appendChild(idDiv);
|
|
||||||
|
|
||||||
const periodDivDeb = document.createElement("div");
|
|
||||||
periodDivDeb.className = "assiduite-period";
|
|
||||||
periodDivDeb.textContent = `${formatDateModal(assiduite.date_debut)}`;
|
|
||||||
bubble.appendChild(periodDivDeb);
|
|
||||||
const periodDivFin = document.createElement("div");
|
|
||||||
periodDivFin.className = "assiduite-period";
|
|
||||||
periodDivFin.textContent = `${formatDateModal(assiduite.date_fin)}`;
|
|
||||||
bubble.appendChild(periodDivFin);
|
|
||||||
|
|
||||||
const stateDiv = document.createElement("div");
|
|
||||||
stateDiv.className = "assiduite-state";
|
|
||||||
stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
|
|
||||||
bubble.appendChild(stateDiv);
|
|
||||||
|
|
||||||
const userIdDiv = document.createElement("div");
|
|
||||||
userIdDiv.className = "assiduite-user_id";
|
|
||||||
userIdDiv.textContent = `saisi le ${formatDateModal(
|
|
||||||
assiduite.entry_date,
|
|
||||||
"à"
|
|
||||||
)} \npar ${getUserFromId(assiduite.user_id)}`;
|
|
||||||
bubble.appendChild(userIdDiv);
|
|
||||||
|
|
||||||
bubble.style.left = `${event.clientX - bubble.offsetWidth / 2}px`;
|
|
||||||
bubble.style.top = `${event.clientY + 20}px`;
|
|
||||||
});
|
|
||||||
el.addEventListener("mouseout", () => {
|
|
||||||
const bubble = document.querySelector(".assiduite-bubble");
|
|
||||||
bubble.classList.remove("is-active");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mise à jour d'une ligne étudiant
|
* Mise à jour d'une ligne étudiant
|
||||||
* @param {String | Number} etudid l'identifiant de l'étudiant
|
* @param {String | Number} etudid l'identifiant de l'étudiant
|
||||||
@ -1263,7 +1142,9 @@ function actualizeEtud(etudid) {
|
|||||||
*/
|
*/
|
||||||
function generateAllEtudRow() {
|
function generateAllEtudRow() {
|
||||||
if (isSingleEtud()) {
|
if (isSingleEtud()) {
|
||||||
actualizeEtud(etudid);
|
try {
|
||||||
|
actualizeEtud(etudid);
|
||||||
|
} catch (ignored) {}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,70 +1,79 @@
|
|||||||
/* Module par Seb. L. */
|
/* Module par Seb. L. */
|
||||||
class releveBUT extends HTMLElement {
|
class releveBUT extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.shadow = this.attachShadow({ mode: 'open' });
|
this.shadow = this.attachShadow({ mode: "open" });
|
||||||
|
|
||||||
/* Config par defaut */
|
/* Config par defaut */
|
||||||
this.config = {
|
this.config = {
|
||||||
showURL: true
|
showURL: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Template du module */
|
/* Template du module */
|
||||||
this.shadow.innerHTML = this.template();
|
this.shadow.innerHTML = this.template();
|
||||||
|
|
||||||
/* Style du module */
|
/* Style du module */
|
||||||
const styles = document.createElement('link');
|
const styles = document.createElement("link");
|
||||||
styles.setAttribute('rel', 'stylesheet');
|
styles.setAttribute("rel", "stylesheet");
|
||||||
if (location.href.includes("ScoDoc")) {
|
if (location.href.includes("ScoDoc")) {
|
||||||
styles.setAttribute('href', removeLastTwoComponents(getCurrentScriptPath()) + '/css/releve-but.css'); // Scodoc
|
styles.setAttribute(
|
||||||
} else {
|
"href",
|
||||||
styles.setAttribute('href', '/assets/styles/releve-but.css'); // Passerelle
|
removeLastTwoComponents(getCurrentScriptPath()) + "/css/releve-but.css"
|
||||||
}
|
); // Scodoc
|
||||||
this.shadow.appendChild(styles);
|
} else {
|
||||||
}
|
styles.setAttribute("href", "/assets/styles/releve-but.css"); // Passerelle
|
||||||
listeOnOff() {
|
}
|
||||||
this.parentElement.parentElement.classList.toggle("listeOff");
|
this.shadow.appendChild(styles);
|
||||||
this.parentElement.parentElement.querySelectorAll(".moduleOnOff").forEach(e => {
|
}
|
||||||
e.classList.remove("moduleOnOff")
|
listeOnOff() {
|
||||||
})
|
this.parentElement.parentElement.classList.toggle("listeOff");
|
||||||
}
|
this.parentElement.parentElement
|
||||||
moduleOnOff() {
|
.querySelectorAll(".moduleOnOff")
|
||||||
this.parentElement.classList.toggle("moduleOnOff");
|
.forEach((e) => {
|
||||||
}
|
e.classList.remove("moduleOnOff");
|
||||||
goTo() {
|
});
|
||||||
let module = this.dataset.module;
|
}
|
||||||
this.parentElement.parentElement.parentElement.parentElement.querySelector("#Module_" + module).scrollIntoView();
|
moduleOnOff() {
|
||||||
}
|
this.parentElement.classList.toggle("moduleOnOff");
|
||||||
|
}
|
||||||
|
goTo() {
|
||||||
|
let module = this.dataset.module;
|
||||||
|
this.parentElement.parentElement.parentElement.parentElement
|
||||||
|
.querySelector("#Module_" + module)
|
||||||
|
.scrollIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
set setConfig(config) {
|
set setConfig(config) {
|
||||||
this.config.showURL = config.showURL ?? this.config.showURL;
|
this.config.showURL = config.showURL ?? this.config.showURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
set showData(data) {
|
set showData(data) {
|
||||||
// this.showInformations(data);
|
// this.showInformations(data);
|
||||||
this.showSemestre(data);
|
this.showSemestre(data);
|
||||||
this.showSynthese(data);
|
this.showSynthese(data);
|
||||||
this.showEvaluations(data);
|
this.showEvaluations(data);
|
||||||
|
|
||||||
this.showCustom(data);
|
this.showCustom(data);
|
||||||
|
|
||||||
this.setOptions(data.options);
|
this.setOptions(data.options);
|
||||||
|
|
||||||
this.shadow.querySelectorAll(".CTA_Liste").forEach(e => {
|
this.shadow.querySelectorAll(".CTA_Liste").forEach((e) => {
|
||||||
e.addEventListener("click", this.listeOnOff)
|
e.addEventListener("click", this.listeOnOff);
|
||||||
})
|
});
|
||||||
this.shadow.querySelectorAll(".ue, .module").forEach(e => {
|
this.shadow.querySelectorAll(".ue, .module").forEach((e) => {
|
||||||
e.addEventListener("click", this.moduleOnOff)
|
e.addEventListener("click", this.moduleOnOff);
|
||||||
})
|
});
|
||||||
this.shadow.querySelectorAll(":not(.ueBonus)+.syntheseModule").forEach(e => {
|
this.shadow
|
||||||
e.addEventListener("click", this.goTo)
|
.querySelectorAll(":not(.ueBonus)+.syntheseModule")
|
||||||
})
|
.forEach((e) => {
|
||||||
|
e.addEventListener("click", this.goTo);
|
||||||
|
});
|
||||||
|
|
||||||
this.shadow.children[0].classList.add("ready");
|
this.shadow.children[0].classList.add("ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
template() {
|
template() {
|
||||||
return `
|
return `
|
||||||
<div>
|
<div>
|
||||||
<div class="wait"></div>
|
<div class="wait"></div>
|
||||||
<main class="releve">
|
<main class="releve">
|
||||||
@ -140,33 +149,36 @@ class releveBUT extends HTMLElement {
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************/
|
/********************************/
|
||||||
/* Informations sur l'étudiant */
|
/* Informations sur l'étudiant */
|
||||||
/********************************/
|
/********************************/
|
||||||
showInformations(data) {
|
showInformations(data) {
|
||||||
this.shadow.querySelector(".studentPic").src = data.etudiant.photo_url || "default_Student.svg";
|
this.shadow.querySelector(".studentPic").src =
|
||||||
|
data.etudiant.photo_url || "default_Student.svg";
|
||||||
|
|
||||||
let output = '';
|
let output = "";
|
||||||
|
|
||||||
if (this.config.showURL) {
|
if (this.config.showURL) {
|
||||||
output += `<a href="${data.etudiant.fiche_url}" class=info_etudiant>`;
|
output += `<a href="${data.etudiant.fiche_url}" class=info_etudiant>`;
|
||||||
} else {
|
} else {
|
||||||
output += `<div class=info_etudiant>`;
|
output += `<div class=info_etudiant>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
output += `
|
output += `
|
||||||
<div class=civilite>
|
<div class=civilite>
|
||||||
${this.civilite(data.etudiant.civilite)}
|
${this.civilite(data.etudiant.civilite)}
|
||||||
${data.etudiant.nom}
|
${data.etudiant.nom}
|
||||||
${data.etudiant.prenom}`;
|
${data.etudiant.prenom}`;
|
||||||
|
|
||||||
if (data.etudiant.date_naissance) {
|
if (data.etudiant.date_naissance) {
|
||||||
output += ` <div class=dateNaissance>né${(data.etudiant.civilite == "F") ? "e" : ""} le ${this.ISOToDate(data.etudiant.date_naissance)}</div>`;
|
output += ` <div class=dateNaissance>né${
|
||||||
}
|
data.etudiant.civilite == "F" ? "e" : ""
|
||||||
|
} le ${this.ISOToDate(data.etudiant.date_naissance)}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
output += `
|
output += `
|
||||||
</div>
|
</div>
|
||||||
<div class=numerosEtudiant>
|
<div class=numerosEtudiant>
|
||||||
Numéro étudiant : ${data.etudiant.code_nip || "~"} -
|
Numéro étudiant : ${data.etudiant.code_nip || "~"} -
|
||||||
@ -174,46 +186,51 @@ class releveBUT extends HTMLElement {
|
|||||||
</div>
|
</div>
|
||||||
<div>${data.formation.titre}</div>
|
<div>${data.formation.titre}</div>
|
||||||
`;
|
`;
|
||||||
if (this.config.showURL) {
|
if (this.config.showURL) {
|
||||||
output += `</a>`;
|
output += `</a>`;
|
||||||
} else {
|
} else {
|
||||||
output += `</div>`;
|
output += `</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shadow.querySelector(".infoEtudiant").innerHTML = output;
|
this.shadow.querySelector(".infoEtudiant").innerHTML = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************/
|
/*******************************/
|
||||||
/* Affichage local */
|
/* Affichage local */
|
||||||
/*******************************/
|
/*******************************/
|
||||||
showCustom(data) {
|
showCustom(data) {
|
||||||
this.shadow.querySelector(".custom").innerHTML = data.custom || "";
|
this.shadow.querySelector(".custom").innerHTML = data.custom || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************/
|
/*******************************/
|
||||||
/* Information sur le semestre */
|
/* Information sur le semestre */
|
||||||
/*******************************/
|
/*******************************/
|
||||||
showSemestre(data) {
|
showSemestre(data) {
|
||||||
let correspondanceCodes = {
|
let correspondanceCodes = {
|
||||||
"ADM": "Admis",
|
ADM: "Admis",
|
||||||
"AJD": "Admis par décision de jury",
|
AJD: "Admis par décision de jury",
|
||||||
"PASD": "Passage de droit : tout n'est pas validé, mais d'après les règles du BUT, vous passez",
|
PASD: "Passage de droit : tout n'est pas validé, mais d'après les règles du BUT, vous passez",
|
||||||
"PAS1NCI": "Vous passez par décision de jury mais attention, vous n'avez pas partout le niveau suffisant",
|
PAS1NCI:
|
||||||
"RED": "Ajourné mais autorisé à redoubler",
|
"Vous passez par décision de jury mais attention, vous n'avez pas partout le niveau suffisant",
|
||||||
"NAR": "Non admis et non autorisé à redoubler : réorientation",
|
RED: "Ajourné mais autorisé à redoubler",
|
||||||
"DEM": "Démission",
|
NAR: "Non admis et non autorisé à redoubler : réorientation",
|
||||||
"ABAN": "Abandon constaté sans lettre de démission",
|
DEM: "Démission",
|
||||||
"RAT": "En attente d'un rattrapage",
|
ABAN: "Abandon constaté sans lettre de démission",
|
||||||
"EXCLU": "Exclusion dans le cadre d'une décision disciplinaire",
|
RAT: "En attente d'un rattrapage",
|
||||||
"DEF": "Défaillance : non évalué par manque d'assiduité",
|
EXCLU: "Exclusion dans le cadre d'une décision disciplinaire",
|
||||||
"ABL": "Année blanche"
|
DEF: "Défaillance : non évalué par manque d'assiduité",
|
||||||
}
|
ABL: "Année blanche",
|
||||||
|
};
|
||||||
|
|
||||||
this.shadow.querySelector("#identite_etudiant").innerHTML = ` <a href="${data.etudiant.fiche_url}">${data.etudiant.nomprenom}</a> `;
|
this.shadow.querySelector(
|
||||||
this.shadow.querySelector(".dateInscription").innerHTML += this.ISOToDate(data.semestre.inscription);
|
"#identite_etudiant"
|
||||||
let output = '';
|
).innerHTML = ` <a href="${data.etudiant.fiche_url}">${data.etudiant.nomprenom}</a> `;
|
||||||
if (!data.options.block_moyenne_generale) {
|
this.shadow.querySelector(".dateInscription").innerHTML += this.ISOToDate(
|
||||||
output += `
|
data.semestre.inscription
|
||||||
|
);
|
||||||
|
let output = "";
|
||||||
|
if (!data.options.block_moyenne_generale) {
|
||||||
|
output += `
|
||||||
<div>
|
<div>
|
||||||
<div class=enteteSemestre>Moyenne</div><div class=enteteSemestre>${data.semestre.notes.value}</div>
|
<div class=enteteSemestre>Moyenne</div><div class=enteteSemestre>${data.semestre.notes.value}</div>
|
||||||
<div class=rang>Rang :</div><div class=rang>${data.semestre.rang.value} / ${data.semestre.rang.total}</div>
|
<div class=rang>Rang :</div><div class=rang>${data.semestre.rang.value} / ${data.semestre.rang.total}</div>
|
||||||
@ -222,64 +239,72 @@ class releveBUT extends HTMLElement {
|
|||||||
<div>Min. promo. :</div><div>${data.semestre.notes.min}</div>
|
<div>Min. promo. :</div><div>${data.semestre.notes.min}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
output += `
|
output += `
|
||||||
${(() => {
|
${(() => {
|
||||||
if ((!data.semestre.rang.groupes) ||
|
if (
|
||||||
(Object.keys(data.semestre.rang.groupes).length == 0)) {
|
!data.semestre.rang.groupes ||
|
||||||
return "";
|
Object.keys(data.semestre.rang.groupes).length == 0
|
||||||
}
|
) {
|
||||||
let output = "";
|
return "";
|
||||||
let [idGroupe, dataGroupe] = Object.entries(data.semestre.rang.groupes)[0];
|
}
|
||||||
output += `<div>
|
let output = "";
|
||||||
|
let [idGroupe, dataGroupe] = Object.entries(
|
||||||
|
data.semestre.rang.groupes
|
||||||
|
)[0];
|
||||||
|
output += `<div>
|
||||||
<div class=enteteSemestre>${data.semestre.groupes[0]?.group_name}</div><div></div>
|
<div class=enteteSemestre>${data.semestre.groupes[0]?.group_name}</div><div></div>
|
||||||
<div class=rang>Rang :</div><div class=rang>${dataGroupe.value} / ${dataGroupe.total}</div>
|
<div class=rang>Rang :</div><div class=rang>${dataGroupe.value} / ${dataGroupe.total}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
// <div>Max. promo. :</div><div>${dataGroupe.max || "-"}</div>
|
// <div>Max. promo. :</div><div>${dataGroupe.max || "-"}</div>
|
||||||
// <div>Moy. promo. :</div><div>${dataGroupe.moy || "-"}</div>
|
// <div>Moy. promo. :</div><div>${dataGroupe.moy || "-"}</div>
|
||||||
// <div>Min. promo. :</div><div>${dataGroupe.min || "-"}</div>
|
// <div>Min. promo. :</div><div>${dataGroupe.min || "-"}</div>
|
||||||
return output;
|
return output;
|
||||||
})()}
|
})()}
|
||||||
<div class=absencesRecap>
|
<div class=absencesRecap>
|
||||||
<div class=enteteSemestre>Absences</div><div class=enteteSemestre>1/2 jour.</div>
|
<div class=enteteSemestre>Absences</div><div class=enteteSemestre>${
|
||||||
|
data.semestre.absences?.metrique ?? "1/2 jour."
|
||||||
|
}</div>
|
||||||
<div class=abs>Non justifiées</div>
|
<div class=abs>Non justifiées</div>
|
||||||
<div>${data.semestre.absences?.injustifie ?? "-"}</div>
|
<div>${data.semestre.absences?.injustifie ?? "-"}</div>
|
||||||
<div class=abs>Total</div><div>${data.semestre.absences?.total ?? "-"}</div>
|
<div class=abs>Total</div><div>${data.semestre.absences?.total ?? "-"}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
if (data.semestre.decision_rcue?.length) {
|
if (data.semestre.decision_rcue?.length) {
|
||||||
output += `
|
output += `
|
||||||
<div>
|
<div>
|
||||||
<div class=enteteSemestre>RCUE</div><div></div>
|
<div class=enteteSemestre>RCUE</div><div></div>
|
||||||
${(() => {
|
${(() => {
|
||||||
let output = "";
|
let output = "";
|
||||||
data.semestre.decision_rcue.forEach(competence => {
|
data.semestre.decision_rcue.forEach((competence) => {
|
||||||
output += `<div class=competence>${competence.niveau.competence.titre}</div><div>${competence.code}</div>`;
|
output += `<div class=competence>${competence.niveau.competence.titre}</div><div>${competence.code}</div>`;
|
||||||
})
|
});
|
||||||
return output;
|
return output;
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`;
|
||||||
}
|
}
|
||||||
if (data.semestre.decision_ue?.length) {
|
if (data.semestre.decision_ue?.length) {
|
||||||
output += `
|
output += `
|
||||||
<div>
|
<div>
|
||||||
<div class=enteteSemestre>UE</div><div></div>
|
<div class=enteteSemestre>UE</div><div></div>
|
||||||
${(() => {
|
${(() => {
|
||||||
let output = "";
|
let output = "";
|
||||||
data.semestre.decision_ue.forEach(ue => {
|
data.semestre.decision_ue.forEach((ue) => {
|
||||||
output += `<div class=competence>${ue.acronyme}</div><div>${ue.code}</div>`;
|
output += `<div class=competence>${ue.acronyme}</div><div>${ue.code}</div>`;
|
||||||
})
|
});
|
||||||
return output;
|
return output;
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
output += `
|
output += `
|
||||||
<a class=photo href="${data.etudiant.fiche_url}">
|
<a class=photo href="${data.etudiant.fiche_url}">
|
||||||
<img src="${data.etudiant.photo_url || "default_Student.svg"}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0">
|
<img src="${
|
||||||
|
data.etudiant.photo_url || "default_Student.svg"
|
||||||
|
}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0">
|
||||||
</a>`;
|
</a>`;
|
||||||
/*${data.semestre.groupes.map(groupe => {
|
/*${data.semestre.groupes.map(groupe => {
|
||||||
return `
|
return `
|
||||||
<div>
|
<div>
|
||||||
<div class=enteteSemestre>Groupe</div><div class=enteteSemestre>${groupe.nom}</div>
|
<div class=enteteSemestre>Groupe</div><div class=enteteSemestre>${groupe.nom}</div>
|
||||||
@ -291,37 +316,42 @@ class releveBUT extends HTMLElement {
|
|||||||
`;
|
`;
|
||||||
}).join("")
|
}).join("")
|
||||||
}*/
|
}*/
|
||||||
this.shadow.querySelector(".infoSemestre").innerHTML = output;
|
this.shadow.querySelector(".infoSemestre").innerHTML = output;
|
||||||
|
|
||||||
|
/*if(data.semestre.decision_annee?.code){
|
||||||
/*if(data.semestre.decision_annee?.code){
|
|
||||||
this.shadow.querySelector(".decision_annee").innerHTML = "Décision année : " + data.semestre.decision_annee.code + " - " + correspondanceCodes[data.semestre.decision_annee.code];
|
this.shadow.querySelector(".decision_annee").innerHTML = "Décision année : " + data.semestre.decision_annee.code + " - " + correspondanceCodes[data.semestre.decision_annee.code];
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
this.shadow.querySelector(".decision").innerHTML = data.semestre.situation || "";
|
this.shadow.querySelector(".decision").innerHTML =
|
||||||
/*if (data.semestre.decision?.code) {
|
data.semestre.situation || "";
|
||||||
|
/*if (data.semestre.decision?.code) {
|
||||||
this.shadow.querySelector(".decision").innerHTML = "Décision jury: " + (data.semestre.decision?.code || "");
|
this.shadow.querySelector(".decision").innerHTML = "Décision jury: " + (data.semestre.decision?.code || "");
|
||||||
}*/
|
}*/
|
||||||
this.shadow.querySelector("#ects_tot").innerHTML = "ECTS : " + (data.semestre.ECTS?.acquis ?? "-") + " / " + (data.semestre.ECTS?.total ?? "-");
|
this.shadow.querySelector("#ects_tot").innerHTML =
|
||||||
}
|
"ECTS : " +
|
||||||
|
(data.semestre.ECTS?.acquis ?? "-") +
|
||||||
|
" / " +
|
||||||
|
(data.semestre.ECTS?.total ?? "-");
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************/
|
/*******************************/
|
||||||
/* Synthèse */
|
/* Synthèse */
|
||||||
/*******************************/
|
/*******************************/
|
||||||
showSynthese(data) {
|
showSynthese(data) {
|
||||||
let output = ``;
|
let output = ``;
|
||||||
/* Fusion et tri des UE et UE capitalisées */
|
/* Fusion et tri des UE et UE capitalisées */
|
||||||
let fusionUE = [
|
let fusionUE = [
|
||||||
...Object.entries(data.ues),
|
...Object.entries(data.ues),
|
||||||
...Object.entries(data.ues_capitalisees)
|
...Object.entries(data.ues_capitalisees),
|
||||||
].sort((a, b) => {
|
].sort((a, b) => {
|
||||||
return a[1].numero - b[1].numero
|
return a[1].numero - b[1].numero;
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Affichage */
|
/* Affichage */
|
||||||
fusionUE.forEach(([ue, dataUE]) => {
|
fusionUE.forEach(([ue, dataUE]) => {
|
||||||
if (dataUE.type == 1) { // UE Sport / Bonus
|
if (dataUE.type == 1) {
|
||||||
output += `
|
// UE Sport / Bonus
|
||||||
|
output += `
|
||||||
<div>
|
<div>
|
||||||
<div class="ue ueBonus">
|
<div class="ue ueBonus">
|
||||||
<h3>Bonus</h3>
|
<h3>Bonus</h3>
|
||||||
@ -330,52 +360,60 @@ class releveBUT extends HTMLElement {
|
|||||||
${this.ueSport(dataUE.modules)}
|
${this.ueSport(dataUE.modules)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
output += `
|
output += `
|
||||||
<div>
|
<div>
|
||||||
<div class="ue ${dataUE.date_capitalisation ? "capitalisee" : ""}">
|
<div class="ue ${dataUE.date_capitalisation ? "capitalisee" : ""}">
|
||||||
<h3>
|
<h3>
|
||||||
${ue}${(dataUE.titre) ? " - " + dataUE.titre : ""}
|
${ue}${dataUE.titre ? " - " + dataUE.titre : ""}
|
||||||
</h3>
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
<div class=moyenne>Moyenne : ${dataUE.moyenne?.value || dataUE.moyenne || "-"}</div>
|
<div class=moyenne>Moyenne : ${
|
||||||
<div class=ue_rang>Rang : ${dataUE.moyenne?.rang} / ${dataUE.moyenne?.total}</div>
|
dataUE.moyenne?.value || dataUE.moyenne || "-"
|
||||||
|
}</div>
|
||||||
|
<div class=ue_rang>Rang : ${dataUE.moyenne?.rang} / ${
|
||||||
|
dataUE.moyenne?.total
|
||||||
|
}</div>
|
||||||
<div class=info>`;
|
<div class=info>`;
|
||||||
if (!dataUE.date_capitalisation) {
|
if (!dataUE.date_capitalisation) {
|
||||||
output += ` Bonus : ${dataUE.bonus || 0} -
|
output += ` Bonus : ${dataUE.bonus || 0} -
|
||||||
Malus : ${dataUE.malus || 0}`;
|
Malus : ${dataUE.malus || 0}`;
|
||||||
} else {
|
} else {
|
||||||
output += ` le ${this.ISOToDate(dataUE.date_capitalisation.split("T")[0])} <a href="${dataUE.bul_orig_url}">dans ce semestre</a>`;
|
output += ` le ${this.ISOToDate(
|
||||||
}
|
dataUE.date_capitalisation.split("T")[0]
|
||||||
|
)} <a href="${dataUE.bul_orig_url}">dans ce semestre</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
output += ` <span class=ects> -
|
output += ` <span class=ects> -
|
||||||
ECTS : ${dataUE.ECTS?.acquis ?? "-"} / ${dataUE.ECTS?.total ?? "-"}
|
ECTS : ${dataUE.ECTS?.acquis ?? "-"} / ${
|
||||||
|
dataUE.ECTS?.total ?? "-"
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
/*<div class=absences>
|
/*<div class=absences>
|
||||||
<div>Abs N.J.</div><div>${dataUE.absences?.injustifie || 0}</div>
|
<div>Abs N.J.</div><div>${dataUE.absences?.injustifie || 0}</div>
|
||||||
<div>Total</div><div>${dataUE.absences?.total || 0}</div>
|
<div>Total</div><div>${dataUE.absences?.total || 0}</div>
|
||||||
</div>*/
|
</div>*/
|
||||||
output += "</div>";
|
output += "</div>";
|
||||||
|
|
||||||
if (!dataUE.date_capitalisation) {
|
if (!dataUE.date_capitalisation) {
|
||||||
output +=
|
output +=
|
||||||
this.synthese(data, dataUE.ressources) +
|
this.synthese(data, dataUE.ressources) +
|
||||||
this.synthese(data, dataUE.saes);
|
this.synthese(data, dataUE.saes);
|
||||||
}
|
}
|
||||||
|
|
||||||
output += "</div>";
|
output += "</div>";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.shadow.querySelector(".synthese").innerHTML = output;
|
this.shadow.querySelector(".synthese").innerHTML = output;
|
||||||
}
|
}
|
||||||
synthese(data, modules) {
|
synthese(data, modules) {
|
||||||
let output = "";
|
let output = "";
|
||||||
Object.entries(modules).forEach(([module, dataModule]) => {
|
Object.entries(modules).forEach(([module, dataModule]) => {
|
||||||
let titre = data.ressources[module]?.titre || data.saes[module]?.titre;
|
let titre = data.ressources[module]?.titre || data.saes[module]?.titre;
|
||||||
//let url = data.ressources[module]?.url || data.saes[module]?.url;
|
//let url = data.ressources[module]?.url || data.saes[module]?.url;
|
||||||
output += `
|
output += `
|
||||||
<div class=syntheseModule data-module="${module.replace(/[^a-zA-Z0-9]/g, "")}">
|
<div class=syntheseModule data-module="${module.replace(/[^a-zA-Z0-9]/g, "")}">
|
||||||
<div>${module} - ${titre}</div>
|
<div>${module} - ${titre}</div>
|
||||||
<div>
|
<div>
|
||||||
@ -384,14 +422,14 @@ class releveBUT extends HTMLElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})
|
});
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
ueSport(modules) {
|
ueSport(modules) {
|
||||||
let output = "";
|
let output = "";
|
||||||
Object.values(modules).forEach((module) => {
|
Object.values(modules).forEach((module) => {
|
||||||
Object.values(module.evaluations).forEach((evaluation) => {
|
Object.values(module.evaluations).forEach((evaluation) => {
|
||||||
output += `
|
output += `
|
||||||
<div class=syntheseModule>
|
<div class=syntheseModule>
|
||||||
<div>${module.titre} - ${evaluation.description || "Note"}</div>
|
<div>${module.titre} - ${evaluation.description || "Note"}</div>
|
||||||
<div>
|
<div>
|
||||||
@ -400,27 +438,31 @@ class releveBUT extends HTMLElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************/
|
/*******************************/
|
||||||
/* Evaluations */
|
/* Evaluations */
|
||||||
/*******************************/
|
/*******************************/
|
||||||
showEvaluations(data) {
|
showEvaluations(data) {
|
||||||
this.shadow.querySelector(".evaluations").innerHTML = this.module(data.ressources);
|
this.shadow.querySelector(".evaluations").innerHTML = this.module(
|
||||||
this.shadow.querySelector(".sae").innerHTML += this.module(data.saes);
|
data.ressources
|
||||||
}
|
);
|
||||||
module(module) {
|
this.shadow.querySelector(".sae").innerHTML += this.module(data.saes);
|
||||||
let output = "";
|
}
|
||||||
Object.entries(module).forEach(([numero, content]) => {
|
module(module) {
|
||||||
output += `
|
let output = "";
|
||||||
|
Object.entries(module).forEach(([numero, content]) => {
|
||||||
|
output += `
|
||||||
<div id="Module_${numero.replace(/[^a-zA-Z0-9]/g, "")}">
|
<div id="Module_${numero.replace(/[^a-zA-Z0-9]/g, "")}">
|
||||||
<div class=module>
|
<div class=module>
|
||||||
<h3>${this.URL(content.url, `${numero} - ${content.titre}`)}</h3>
|
<h3>${this.URL(content.url, `${numero} - ${content.titre}`)}</h3>
|
||||||
<div>
|
<div>
|
||||||
<div class=moyenne>Moyenne indicative : ${content.moyenne.value}</div>
|
<div class=moyenne>Moyenne indicative : ${
|
||||||
|
content.moyenne.value
|
||||||
|
}</div>
|
||||||
<div class=info>
|
<div class=info>
|
||||||
Classe : ${content.moyenne.moy} -
|
Classe : ${content.moyenne.moy} -
|
||||||
Max : ${content.moyenne.max} -
|
Max : ${content.moyenne.max} -
|
||||||
@ -435,14 +477,14 @@ class releveBUT extends HTMLElement {
|
|||||||
${this.evaluation(content.evaluations)}
|
${this.evaluation(content.evaluations)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})
|
});
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluation(evaluations) {
|
evaluation(evaluations) {
|
||||||
let output = "";
|
let output = "";
|
||||||
evaluations.forEach((evaluation) => {
|
evaluations.forEach((evaluation) => {
|
||||||
output += `
|
output += `
|
||||||
<div class=eval>
|
<div class=eval>
|
||||||
<div>${this.URL(evaluation.url, evaluation.description || "Évaluation")}</div>
|
<div>${this.URL(evaluation.url, evaluation.description || "Évaluation")}</div>
|
||||||
<div>
|
<div>
|
||||||
@ -454,52 +496,55 @@ class releveBUT extends HTMLElement {
|
|||||||
<div>Max. promo.</div><div>${evaluation.note.max}</div>
|
<div>Max. promo.</div><div>${evaluation.note.max}</div>
|
||||||
<div>Moy. promo.</div><div>${evaluation.note.moy}</div>
|
<div>Moy. promo.</div><div>${evaluation.note.moy}</div>
|
||||||
<div>Min. promo.</div><div>${evaluation.note.min}</div>
|
<div>Min. promo.</div><div>${evaluation.note.min}</div>
|
||||||
${Object.entries(evaluation.poids).map(([UE, poids]) => {
|
${Object.entries(evaluation.poids)
|
||||||
return `
|
.map(([UE, poids]) => {
|
||||||
|
return `
|
||||||
<div>Poids ${UE}</div>
|
<div>Poids ${UE}</div>
|
||||||
<div>${poids}</div>
|
<div>${poids}</div>
|
||||||
`;
|
`;
|
||||||
}).join("")}
|
})
|
||||||
|
.join("")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})
|
});
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************/
|
/********************/
|
||||||
/* Options */
|
/* Options */
|
||||||
/********************/
|
/********************/
|
||||||
setOptions(options) {
|
setOptions(options) {
|
||||||
Object.entries(options).forEach(([option, value]) => {
|
Object.entries(options).forEach(([option, value]) => {
|
||||||
if (value === false) {
|
if (value === false) {
|
||||||
this.shadow.children[0].classList.add(option.replace("show", "hide"));
|
this.shadow.children[0].classList.add(option.replace("show", "hide"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/********************/
|
||||||
|
/* Fonctions d'aide */
|
||||||
|
/********************/
|
||||||
|
URL(href, content) {
|
||||||
|
if (this.config.showURL) {
|
||||||
|
return `<a href=${href}>${content}</a>`;
|
||||||
|
} else {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
civilite(txt) {
|
||||||
|
switch (txt) {
|
||||||
|
case "M":
|
||||||
|
return "M.";
|
||||||
|
case "F":
|
||||||
|
return "Mme";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/********************/
|
ISOToDate(ISO) {
|
||||||
/* Fonctions d'aide */
|
return ISO.split("-").reverse().join("/");
|
||||||
/********************/
|
}
|
||||||
URL(href, content) {
|
|
||||||
if (this.config.showURL) {
|
|
||||||
return `<a href=${href}>${content}</a>`;
|
|
||||||
} else {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
civilite(txt) {
|
|
||||||
switch (txt) {
|
|
||||||
case "M": return "M.";
|
|
||||||
case "F": return "Mme";
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ISOToDate(ISO) {
|
|
||||||
return ISO.split("-").reverse().join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
customElements.define('releve-but', releveBUT);
|
customElements.define("releve-but", releveBUT);
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
{{ wtf.form_field(form.morning_time) }}
|
{{ wtf.form_field(form.morning_time) }}
|
||||||
{{ wtf.form_field(form.lunch_time) }}
|
{{ wtf.form_field(form.lunch_time) }}
|
||||||
{{ wtf.form_field(form.afternoon_time) }}
|
{{ wtf.form_field(form.afternoon_time) }}
|
||||||
|
{{ wtf.form_field(form.tick_time) }}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{{ wtf.form_field(form.submit) }}
|
{{ wtf.form_field(form.submit) }}
|
||||||
{{ wtf.form_field(form.cancel) }}
|
{{ wtf.form_field(form.cancel) }}
|
||||||
|
36
app/templates/assiduites/etat_absence_date.j2
Normal file
36
app/templates/assiduites/etat_absence_date.j2
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<h2>Présence lors de l'évaluation {{eval.title}} </h2>
|
||||||
|
<h3>Réalisé le {{eval.jour}} de {{eval.heure_debut}} à {{eval.heure_fin}}</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Nom
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Assiduité
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{% for etud in etudiants %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{etud.nom | safe}}
|
||||||
|
</td>
|
||||||
|
<td style="text-align: center;">
|
||||||
|
{{etud.etat}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
tr,
|
||||||
|
td {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
@ -3,6 +3,189 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
const mt_start = {{ t_start }};
|
||||||
|
const mt_end = {{ t_end }};
|
||||||
|
/**
|
||||||
|
* Création de la minitiline d'un étudiant
|
||||||
|
* @param {Array[Assiduité]} assiduitesArray
|
||||||
|
* @returns {HTMLElement} l'élément correspondant à la mini timeline
|
||||||
|
*/
|
||||||
|
function createMiniTimeline(assiduitesArray) {
|
||||||
|
const array = [...assiduitesArray];
|
||||||
|
const dateiso = document.getElementById("tl_date").value;
|
||||||
|
const timeline = document.createElement("div");
|
||||||
|
timeline.className = "mini-timeline";
|
||||||
|
if (isSingleEtud()) {
|
||||||
|
timeline.classList.add("single");
|
||||||
|
}
|
||||||
|
const timelineDate = moment(dateiso).startOf("day");
|
||||||
|
const dayStart = timelineDate.clone().add(mt_start, "hours");
|
||||||
|
const dayEnd = timelineDate.clone().add(mt_end, "hours");
|
||||||
|
const dayDuration = moment.duration(dayEnd.diff(dayStart)).asMinutes();
|
||||||
|
|
||||||
|
timeline.appendChild(setMiniTick(timelineDate, dayStart, dayDuration));
|
||||||
|
|
||||||
|
const tlTimes = getTimeLineTimes();
|
||||||
|
|
||||||
|
const period_assi = {
|
||||||
|
date_debut: tlTimes.deb.format(),
|
||||||
|
date_fin: tlTimes.fin.format(),
|
||||||
|
etat: "CRENEAU",
|
||||||
|
};
|
||||||
|
|
||||||
|
array.push(period_assi);
|
||||||
|
|
||||||
|
array.forEach((assiduité) => {
|
||||||
|
const startDate = moment(assiduité.date_debut);
|
||||||
|
const endDate = moment(assiduité.date_fin);
|
||||||
|
|
||||||
|
if (startDate.isBefore(dayStart)) {
|
||||||
|
startDate.startOf("day").add(mt_start, "hours");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate.isAfter(dayEnd)) {
|
||||||
|
endDate.startOf("day").add(mt_end, "hours");
|
||||||
|
}
|
||||||
|
|
||||||
|
const block = document.createElement("div");
|
||||||
|
block.className = "mini-timeline-block";
|
||||||
|
const duration = moment.duration(endDate.diff(startDate)).asMinutes();
|
||||||
|
const startOffset = moment.duration(startDate.diff(dayStart)).asMinutes();
|
||||||
|
const leftPercentage = (startOffset / dayDuration) * 100;
|
||||||
|
const widthPercentage = (duration / dayDuration) * 100;
|
||||||
|
|
||||||
|
block.style.left = `${leftPercentage}%`;
|
||||||
|
block.style.width = `${widthPercentage}%`;
|
||||||
|
|
||||||
|
if (assiduité.etat != "CRENEAU") {
|
||||||
|
if (isSingleEtud()) {
|
||||||
|
block.addEventListener("click", () => {
|
||||||
|
let deb = startDate.hours() + startDate.minutes() / 60;
|
||||||
|
let fin = endDate.hours() + endDate.minutes() / 60;
|
||||||
|
deb = Math.max(mt_start, deb);
|
||||||
|
fin = Math.min(mt_end, fin);
|
||||||
|
|
||||||
|
setPeriodValues(deb, fin);
|
||||||
|
updateSelectedSelect(getCurrentAssiduiteModuleImplId());
|
||||||
|
updateJustifyBtn();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//ajouter affichage assiduites on over
|
||||||
|
setupAssiduiteBuble(block, assiduité);
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = (justificatifs) => {
|
||||||
|
if (justificatifs.length > 0) {
|
||||||
|
let j = "invalid_justified";
|
||||||
|
|
||||||
|
justificatifs.forEach((ju) => {
|
||||||
|
if (ju.etat == "VALIDE") {
|
||||||
|
j = "justified";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
block.classList.add(j);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (assiduité.etudid) {
|
||||||
|
getJustificatifFromPeriod(
|
||||||
|
{
|
||||||
|
deb: new moment.tz(assiduité.date_debut, TIMEZONE),
|
||||||
|
fin: new moment.tz(assiduité.date_fin, TIMEZONE),
|
||||||
|
},
|
||||||
|
assiduité.etudid,
|
||||||
|
action
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (assiduité.etat) {
|
||||||
|
case "PRESENT":
|
||||||
|
block.classList.add("present");
|
||||||
|
break;
|
||||||
|
case "RETARD":
|
||||||
|
block.classList.add("retard");
|
||||||
|
break;
|
||||||
|
case "ABSENT":
|
||||||
|
block.classList.add("absent");
|
||||||
|
break;
|
||||||
|
case "CRENEAU":
|
||||||
|
block.classList.add("creneau");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
block.style.backgroundColor = "white";
|
||||||
|
}
|
||||||
|
|
||||||
|
timeline.appendChild(block);
|
||||||
|
});
|
||||||
|
|
||||||
|
return timeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajout de la visualisation des assiduités de la mini timeline
|
||||||
|
* @param {HTMLElement} el l'élément survollé
|
||||||
|
* @param {Assiduité} assiduite l'assiduité représentée par l'élément
|
||||||
|
*/
|
||||||
|
function setupAssiduiteBuble(el, assiduite) {
|
||||||
|
if (!assiduite) return;
|
||||||
|
el.addEventListener("mouseenter", (event) => {
|
||||||
|
const bubble = document.querySelector(".assiduite-bubble");
|
||||||
|
bubble.className = "assiduite-bubble";
|
||||||
|
bubble.classList.add("is-active", assiduite.etat.toLowerCase());
|
||||||
|
|
||||||
|
bubble.innerHTML = "";
|
||||||
|
|
||||||
|
const idDiv = document.createElement("div");
|
||||||
|
idDiv.className = "assiduite-id";
|
||||||
|
idDiv.textContent = `ID: ${assiduite.assiduite_id}`;
|
||||||
|
bubble.appendChild(idDiv);
|
||||||
|
|
||||||
|
const periodDivDeb = document.createElement("div");
|
||||||
|
periodDivDeb.className = "assiduite-period";
|
||||||
|
periodDivDeb.textContent = `${formatDateModal(assiduite.date_debut)}`;
|
||||||
|
bubble.appendChild(periodDivDeb);
|
||||||
|
const periodDivFin = document.createElement("div");
|
||||||
|
periodDivFin.className = "assiduite-period";
|
||||||
|
periodDivFin.textContent = `${formatDateModal(assiduite.date_fin)}`;
|
||||||
|
bubble.appendChild(periodDivFin);
|
||||||
|
|
||||||
|
const stateDiv = document.createElement("div");
|
||||||
|
stateDiv.className = "assiduite-state";
|
||||||
|
stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
|
||||||
|
bubble.appendChild(stateDiv);
|
||||||
|
|
||||||
|
const userIdDiv = document.createElement("div");
|
||||||
|
userIdDiv.className = "assiduite-user_id";
|
||||||
|
userIdDiv.textContent = `saisi le ${formatDateModal(
|
||||||
|
assiduite.entry_date,
|
||||||
|
"à"
|
||||||
|
)} \npar ${getUserFromId(assiduite.user_id)}`;
|
||||||
|
bubble.appendChild(userIdDiv);
|
||||||
|
|
||||||
|
bubble.style.left = `${event.clientX - bubble.offsetWidth / 2}px`;
|
||||||
|
bubble.style.top = `${event.clientY + 20}px`;
|
||||||
|
});
|
||||||
|
el.addEventListener("mouseout", () => {
|
||||||
|
const bubble = document.querySelector(".assiduite-bubble");
|
||||||
|
bubble.classList.remove("is-active");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMiniTick(timelineDate, dayStart, dayDuration) {
|
||||||
|
const endDate = timelineDate.clone().set({ 'hour': 13, 'minute': 0 });
|
||||||
|
const duration = moment.duration(endDate.diff(dayStart)).asMinutes();
|
||||||
|
const widthPercentage = (duration / dayDuration) * 100;
|
||||||
|
console.log(endDate, duration, widthPercentage)
|
||||||
|
const tick = document.createElement('span');
|
||||||
|
tick.className = "mini_tick"
|
||||||
|
tick.textContent = "13h"
|
||||||
|
tick.style.left = `${widthPercentage}%`
|
||||||
|
|
||||||
|
return tick
|
||||||
|
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -87,10 +270,9 @@
|
|||||||
|
|
||||||
.mini_tick {
|
.mini_tick {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: start;
|
||||||
top: -16px;
|
top: -40px;
|
||||||
left: 50%;
|
transform: translateX(-50%)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mini_tick::after {
|
.mini_tick::after {
|
||||||
|
372
app/templates/assiduites/signal_assiduites_diff.j2
Normal file
372
app/templates/assiduites/signal_assiduites_diff.j2
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
<h2>Signalement différé des assiduités {{gr |safe}}</h2>
|
||||||
|
<h3>{{sem | safe }}</h3>
|
||||||
|
<button onclick="getAndVerify()">Valider les assiduités</button>
|
||||||
|
<div id="studentTable">
|
||||||
|
<div class="thead">
|
||||||
|
<div class="tr">
|
||||||
|
<div class="th sticky">Noms</div>
|
||||||
|
<button id="addColumn" class="floating-button">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tbody">
|
||||||
|
|
||||||
|
{% for etud in etudiants %}
|
||||||
|
<div class="tr" etudid="{{etud.etudid}}">
|
||||||
|
<div class="td sticky">{{etud.nomprenom}}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include "assiduites/alert.j2" %}
|
||||||
|
{% include "assiduites/prompt.j2" %}
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.err-assi {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-shadow: 0 0 1rem 0 rgba(0, 0, 0, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thead .tr {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thead .tr .th {
|
||||||
|
height: 125px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th.sticky {
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th,
|
||||||
|
.td {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
width: 200px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tr {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-form {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-form input,
|
||||||
|
.mini-form select {
|
||||||
|
display: block;
|
||||||
|
margin: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addColumn {
|
||||||
|
font-size: 24px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
right: -60px;
|
||||||
|
top: calc(50% - 50px /2);
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addColumn:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th {
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tbody .tr:nth-child(even) {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tbody .tr:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.etat {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 33% 33% 33%;
|
||||||
|
grid-template-rows: 50% 50%;
|
||||||
|
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
#moduleimpl_select {
|
||||||
|
max-width: 175px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
let verified = false;
|
||||||
|
const etatDef = "{{etat_def}}";
|
||||||
|
moment.tz.setDefault("Etc/UTC");
|
||||||
|
function createColumn() {
|
||||||
|
let table = document.getElementById("studentTable");
|
||||||
|
let th = document.createElement("div");
|
||||||
|
th.classList.add("th");
|
||||||
|
const col_id = `${document.querySelectorAll("[col]").length + 1}`;
|
||||||
|
th.setAttribute("col", col_id);
|
||||||
|
th.innerHTML = `
|
||||||
|
<div class="mini-form">
|
||||||
|
<input type="datetime-local" id="dateStart">
|
||||||
|
<input type="datetime-local" id="dateEnd">
|
||||||
|
{{moduleimpl_select|safe}}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
table
|
||||||
|
.querySelector(".thead .tr")
|
||||||
|
.insertBefore(th, document.querySelector("#addColumn"));
|
||||||
|
|
||||||
|
const last = [...document.querySelectorAll("#dateStart")].pop();
|
||||||
|
defaultDate(last);
|
||||||
|
|
||||||
|
let rows = table.querySelector(".tbody").querySelectorAll(".tr");
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
let td = document.createElement("div");
|
||||||
|
td.setAttribute("colid", col_id)
|
||||||
|
td.classList.add("td", "etat");
|
||||||
|
const etudid = rows[i].getAttribute("etudid");
|
||||||
|
td.innerHTML = `
|
||||||
|
<input type="radio" name="etat_${col_id}_${etudid}" value="present">
|
||||||
|
<input type="radio" name="etat_${col_id}_${etudid}" value="retard">
|
||||||
|
<input type="radio" name="etat_${col_id}_${etudid}" value="absent">
|
||||||
|
<span>Present</span>
|
||||||
|
<span>Retard</span>
|
||||||
|
<span>Absent</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
rows[i].appendChild(td);
|
||||||
|
if (etatDef != "" && etatDef != "aucun") {
|
||||||
|
const inp = td.querySelector(`[value='${etatDef}']`).checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultDate(element) {
|
||||||
|
const num = element.parentElement.parentElement.getAttribute("col") - 1;
|
||||||
|
const last = [...document.querySelectorAll(`[col='${num}'] #dateEnd`)].pop();
|
||||||
|
let date = undefined;
|
||||||
|
if (last == undefined) {
|
||||||
|
date = moment().tz("Europe/Paris").format("YYYY-MM-DDTHH:mm");
|
||||||
|
} else {
|
||||||
|
date = last.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.value = date;
|
||||||
|
|
||||||
|
element.addEventListener(
|
||||||
|
"focusout",
|
||||||
|
() => {
|
||||||
|
const el = element.parentElement.querySelector("#dateEnd");
|
||||||
|
const el2 = element.parentElement.querySelector("#dateStart");
|
||||||
|
el.value = moment(el2.valueAsDate)
|
||||||
|
.add(2, "hours")
|
||||||
|
.format("YYYY-MM-DDTHH:mm");
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEtatCol(colId) {
|
||||||
|
const etats = {};
|
||||||
|
const tds = [...document.querySelectorAll(`.td[colid='${colId}']`)]
|
||||||
|
tds.forEach((td) => {
|
||||||
|
const tr = td.parentElement
|
||||||
|
const etudid = tr.getAttribute("etudid");
|
||||||
|
let inputs = [...td.querySelectorAll("input")]
|
||||||
|
|
||||||
|
etatInput = inputs.filter((e) => e.checked).pop()
|
||||||
|
|
||||||
|
if (etatInput == undefined) {
|
||||||
|
etats[etudid] = "";
|
||||||
|
} else {
|
||||||
|
etats[etudid] = etatInput.value;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return etats;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _createAssiduites(inputDeb, inputFin, moduleSelect, etudid, etat, colId) {
|
||||||
|
if (moduleSelect == "") {
|
||||||
|
return {
|
||||||
|
"date_debut": inputDeb,
|
||||||
|
"date_fin": inputFin,
|
||||||
|
"etudid": etudid,
|
||||||
|
"etat": etat,
|
||||||
|
"colid": colId,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
"date_debut": inputDeb,
|
||||||
|
"date_fin": inputFin,
|
||||||
|
"etudid": etudid,
|
||||||
|
"moduleimpl_id": moduleSelect,
|
||||||
|
"etat": etat,
|
||||||
|
"colid": colId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAndVerify() {
|
||||||
|
const assiduites = [];
|
||||||
|
const cols = [...document.querySelectorAll("[col]")];
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
cols.forEach((col) => {
|
||||||
|
const col_id = col.getAttribute("col");
|
||||||
|
const etats = getEtatCol(col_id);
|
||||||
|
|
||||||
|
const inputDeb = col.querySelector("#dateStart").value;
|
||||||
|
const inputFin = col.querySelector("#dateEnd").value;
|
||||||
|
const moduleSelect = col.querySelector("#moduleimpl_select").value;
|
||||||
|
|
||||||
|
if (inputDeb == "" || inputFin == "") {
|
||||||
|
errors.push(`La colonne n°${col_id} n'est pas valide`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO Mettre une erreur lorsque moduleimpl forcé (pref)
|
||||||
|
// TODO Mettre une erreur lorsque assiduité forcé (pref)
|
||||||
|
|
||||||
|
Object.keys(etats).forEach((key) => {
|
||||||
|
const etat = etats[key];
|
||||||
|
|
||||||
|
if (etat != "") {
|
||||||
|
assiduites.push(_createAssiduites(inputDeb, inputFin, moduleSelect, key, etat, col_id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
const texte = document.createElement("div");
|
||||||
|
errors.map((err) => document.createTextNode(err)).forEach((err) => {
|
||||||
|
texte.appendChild(err);
|
||||||
|
texte.appendChild(document.createElement('br'));
|
||||||
|
})
|
||||||
|
|
||||||
|
openAlertModal("Erreur(s) détéctée(s)", texte)
|
||||||
|
} else {
|
||||||
|
createAllAssiduites(assiduites);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAllAssiduites(createQueue) {
|
||||||
|
if (createQueue.length < 0)
|
||||||
|
return;
|
||||||
|
const path = getUrl() + `/api/assiduites/create`;
|
||||||
|
sync_post(
|
||||||
|
path,
|
||||||
|
createQueue,
|
||||||
|
(data, status) => {
|
||||||
|
verified = true;
|
||||||
|
const { success, errors } = data;
|
||||||
|
|
||||||
|
const indexes = [...Object.keys(errors)];
|
||||||
|
if (indexes.length > 0) {
|
||||||
|
const incriminated = indexes.map((i) => {
|
||||||
|
return createQueue[Number.parseInt(i)];
|
||||||
|
})
|
||||||
|
|
||||||
|
const error_message = document.createElement('div');
|
||||||
|
|
||||||
|
for (let i = 0; i < incriminated.length; i++) {
|
||||||
|
const err = errors[indexes[i]];
|
||||||
|
const crimi = incriminated[i];
|
||||||
|
const nom = document.querySelector(`[etudid='${crimi.etudid}']`).firstElementChild.textContent.trim();
|
||||||
|
const col = crimi.colid;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.classList.add("err-assi")
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.setAttribute("title", err);
|
||||||
|
span.textContent = "ℹ️"
|
||||||
|
|
||||||
|
const span2 = document.createElement("span");
|
||||||
|
span2.textContent = `L'assiduité (Colonne n°${col}) de ${nom} n'a pas pu être enregistrée`;
|
||||||
|
|
||||||
|
div.appendChild(span2);
|
||||||
|
div.appendChild(span);
|
||||||
|
|
||||||
|
error_message.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
openAlertModal("Certaines assiduités non pas été enregistrées", error_message)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
openAlertModal("Tous les assiduités ont bien été enregistrée", document.createTextNode(""), null, "#09AD2A")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(data, status) => {
|
||||||
|
//error
|
||||||
|
console.error(data, status);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById("addColumn").addEventListener("click", () => {
|
||||||
|
createColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onConfirmRefresh = function (event) {
|
||||||
|
if (!verified)
|
||||||
|
return event.returnValue = "Attention, certaines données n'ont pas été enregistrées";
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", onConfirmRefresh, { capture: true });
|
||||||
|
createColumn();
|
||||||
|
|
||||||
|
</script>
|
@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
<div class="infos">
|
<div class="infos">
|
||||||
Date: <span id="datestr"></span>
|
Date: <span id="datestr"></span>
|
||||||
<input type="date" name="tl_date" id="tl_date" value="{{ date }}" onchange="updateDate()">
|
<input type="date" name="tl_date" id="tl_date" value="{{ date }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{timeline|safe}}
|
{{timeline|safe}}
|
||||||
@ -70,11 +70,16 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
const etudid = {{ sco.etud.id }};
|
const etudid = {{ sco.etud.id }};
|
||||||
|
const nonWorkDays = [{{ nonworkdays| safe }}];
|
||||||
|
|
||||||
setupDate(() => {
|
setupDate(() => {
|
||||||
actualizeEtud(etudid);
|
if (updateDate()) {
|
||||||
updateSelect()
|
actualizeEtud(etudid);
|
||||||
|
updateSelect()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
setupTimeLine(() => {
|
setupTimeLine(() => {
|
||||||
updateJustifyBtn();
|
updateJustifyBtn();
|
||||||
});
|
});
|
||||||
|
@ -70,6 +70,8 @@
|
|||||||
{% include "assiduites/prompt.j2" %}
|
{% include "assiduites/prompt.j2" %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const nonWorkDays = [{{ nonworkdays| safe }}];
|
||||||
|
|
||||||
updateDate();
|
updateDate();
|
||||||
setupDate();
|
setupDate();
|
||||||
setupTimeLine();
|
setupTimeLine();
|
||||||
|
@ -9,11 +9,16 @@
|
|||||||
|
|
||||||
const timelineContainer = document.querySelector(".timeline-container");
|
const timelineContainer = document.querySelector(".timeline-container");
|
||||||
const periodTimeLine = document.querySelector(".period");
|
const periodTimeLine = document.querySelector(".period");
|
||||||
const t_start = {{ t_start }}
|
const t_start = {{ t_start }};
|
||||||
const t_end = {{ t_end }}
|
const t_end = {{ t_end }};
|
||||||
|
|
||||||
|
const tick_time = 60 / {{ tick_time }};
|
||||||
|
const tick_delay = 1 / tick_time;
|
||||||
|
|
||||||
|
const period_default = {{ periode_defaut }};
|
||||||
|
|
||||||
function createTicks() {
|
function createTicks() {
|
||||||
let i = t_start
|
let i = t_start;
|
||||||
|
|
||||||
while (i <= t_end) {
|
while (i <= t_end) {
|
||||||
const hourTick = document.createElement("div");
|
const hourTick = document.createElement("div");
|
||||||
@ -28,10 +33,10 @@
|
|||||||
timelineContainer.appendChild(tickLabel);
|
timelineContainer.appendChild(tickLabel);
|
||||||
|
|
||||||
if (i < t_end) {
|
if (i < t_end) {
|
||||||
let j = Math.floor(i + 1)
|
let j = Math.floor(i + 1);
|
||||||
|
|
||||||
while (i < j) {
|
while (i < j) {
|
||||||
i += 0.25;
|
i += tick_delay;
|
||||||
|
|
||||||
if (i <= t_end) {
|
if (i <= t_end) {
|
||||||
const quarterTick = document.createElement("div");
|
const quarterTick = document.createElement("div");
|
||||||
@ -41,34 +46,39 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
i = j;
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function numberToTime(num) {
|
function numberToTime(num) {
|
||||||
const integer = Math.floor(num)
|
const integer = Math.floor(num);
|
||||||
const decimal = (num % 1) * 60
|
const decimal = (num % 1) * 60;
|
||||||
|
|
||||||
let dec = `:${decimal}`
|
let dec = `:${decimal}`;
|
||||||
if (decimal < 10) {
|
if (decimal < 10) {
|
||||||
dec = `:0${decimal}`
|
dec = `:0${decimal}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let int = `${integer}`
|
let int = `${integer}`;
|
||||||
if (integer < 10) {
|
if (integer < 10) {
|
||||||
int = `0${integer}`
|
int = `0${integer}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return int + dec
|
return int + dec;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function snapToQuarter(value) {
|
function snapToQuarter(value) {
|
||||||
return Math.round(value * 4) / 4;
|
|
||||||
|
|
||||||
|
return Math.round(value * tick_time) / tick_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupTimeLine(callback) {
|
function setupTimeLine(callback) {
|
||||||
const func_call = callback ? callback : () => { }
|
const func_call = callback ? callback : () => { };
|
||||||
timelineContainer.addEventListener("mousedown", (event) => {
|
timelineContainer.addEventListener("mousedown", (event) => {
|
||||||
const startX = event.clientX;
|
const startX = event.clientX;
|
||||||
|
|
||||||
@ -88,9 +98,9 @@
|
|||||||
"mouseup",
|
"mouseup",
|
||||||
() => {
|
() => {
|
||||||
generateAllEtudRow();
|
generateAllEtudRow();
|
||||||
snapHandlesToQuarters()
|
snapHandlesToQuarters();
|
||||||
document.removeEventListener("mousemove", onMouseMove);
|
document.removeEventListener("mousemove", onMouseMove);
|
||||||
func_call()
|
func_call();
|
||||||
},
|
},
|
||||||
{ once: true }
|
{ once: true }
|
||||||
);
|
);
|
||||||
@ -117,12 +127,12 @@
|
|||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"mouseup",
|
"mouseup",
|
||||||
() => {
|
() => {
|
||||||
snapHandlesToQuarters()
|
snapHandlesToQuarters();
|
||||||
generateAllEtudRow();
|
generateAllEtudRow();
|
||||||
|
|
||||||
document.removeEventListener("mousemove", onMouseMove);
|
document.removeEventListener("mousemove", onMouseMove);
|
||||||
|
|
||||||
func_call()
|
func_call();
|
||||||
|
|
||||||
},
|
},
|
||||||
{ once: true }
|
{ once: true }
|
||||||
@ -151,41 +161,43 @@
|
|||||||
const startHour = (leftPercentage / 100) * (t_end - t_start) + t_start;
|
const startHour = (leftPercentage / 100) * (t_end - t_start) + t_start;
|
||||||
const endHour = ((leftPercentage + widthPercentage) / 100) * (t_end - t_start) + t_start;
|
const endHour = ((leftPercentage + widthPercentage) / 100) * (t_end - t_start) + t_start;
|
||||||
|
|
||||||
const startValue = Math.round(startHour * 4) / 4;
|
const startValue = snapToQuarter(startHour);
|
||||||
const endValue = Math.round(endHour * 4) / 4;
|
const endValue = snapToQuarter(endHour);
|
||||||
|
|
||||||
const computedValues = [Math.max(startValue, t_start), Math.min(t_end, endValue)]
|
const computedValues = [Math.max(startValue, t_start), Math.min(t_end, endValue)];
|
||||||
|
|
||||||
if (computedValues[0] > t_end || computedValues[1] < t_start) {
|
if (computedValues[0] > t_end || computedValues[1] < t_start) {
|
||||||
return [8, 10]
|
return [t_start, min(t_end, t_start + period_default)];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (computedValues[1] - computedValues[0] <= 0.25 && computedValues[1] < t_end - 0.25) {
|
if (computedValues[1] - computedValues[0] <= tick_delay && computedValues[1] < t_end - tick_delay) {
|
||||||
computedValues[1] += 0.25;
|
computedValues[1] += tick_delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
return computedValues
|
return computedValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPeriodValues(deb, fin) {
|
function setPeriodValues(deb, fin) {
|
||||||
let leftPercentage = (deb - t_start) / (t_end - t_start) * 100
|
deb = snapToQuarter(deb);
|
||||||
let widthPercentage = (fin - deb) / (t_end - t_start) * 100
|
fin = snapToQuarter(fin);
|
||||||
periodTimeLine.style.left = `${leftPercentage}%`
|
let leftPercentage = (deb - t_start) / (t_end - t_start) * 100;
|
||||||
periodTimeLine.style.width = `${widthPercentage}%`
|
let widthPercentage = (fin - deb) / (t_end - t_start) * 100;
|
||||||
|
periodTimeLine.style.left = `${leftPercentage}%`;
|
||||||
|
periodTimeLine.style.width = `${widthPercentage}%`;
|
||||||
|
|
||||||
snapHandlesToQuarters()
|
snapHandlesToQuarters();
|
||||||
generateAllEtudRow();
|
generateAllEtudRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
function snapHandlesToQuarters() {
|
function snapHandlesToQuarters() {
|
||||||
const periodValues = getPeriodValues();
|
const periodValues = getPeriodValues();
|
||||||
let lef = Math.min(computePercentage(periodValues[0], t_start), computePercentage(t_end, 0.25))
|
let lef = Math.min(computePercentage(periodValues[0], t_start), computePercentage(t_end, tick_delay));
|
||||||
if (lef < 0) {
|
if (lef < 0) {
|
||||||
lef = 0;
|
lef = 0;
|
||||||
}
|
}
|
||||||
const left = `${lef}%`
|
const left = `${lef}%`;
|
||||||
|
|
||||||
let wid = Math.max(computePercentage(periodValues[1], periodValues[0]), computePercentage(0.25, 0))
|
let wid = Math.max(computePercentage(periodValues[1], periodValues[0]), computePercentage(tick_delay, 0));
|
||||||
if (wid > 100) {
|
if (wid > 100) {
|
||||||
wid = 100;
|
wid = 100;
|
||||||
}
|
}
|
||||||
@ -195,11 +207,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function computePercentage(a, b) {
|
function computePercentage(a, b) {
|
||||||
return ((a - b) / (t_end - t_start)) * 100
|
return ((a - b) / (t_end - t_start)) * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
createTicks();
|
createTicks();
|
||||||
setPeriodValues(8, 9)
|
setPeriodValues(t_start, t_start + period_default);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
<b>Absences</b>
|
<b>Absences</b>
|
||||||
{% if sco.etud_cur_sem %}
|
{% if sco.etud_cur_sem %}
|
||||||
<span title="absences du {{ sco.etud_cur_sem['date_debut'] }}
|
<span title="absences du {{ sco.etud_cur_sem['date_debut'] }}
|
||||||
au {{ sco.etud_cur_sem['date_fin'] }}">(1/2 j.)
|
au {{ sco.etud_cur_sem['date_fin'] }}">({{sco.prefs["assi_metrique"]}})
|
||||||
<br />{{sco.nbabsjust}} J., {{sco.nbabsnj}} N.J.</span>
|
<br />{{sco.nbabsjust}} J., {{sco.nbabsnj}} N.J.</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -72,10 +72,13 @@ class ScoData:
|
|||||||
ins = self.etud.inscription_courante()
|
ins = self.etud.inscription_courante()
|
||||||
if ins:
|
if ins:
|
||||||
self.etud_cur_sem = ins.formsemestre
|
self.etud_cur_sem = ins.formsemestre
|
||||||
self.nbabs, self.nbabsjust = sco_abs.get_abs_count_in_interval(
|
self.nbabs, self.nbabsjust = sco_abs.get_assiduites_count_in_interval(
|
||||||
etud.id,
|
etud.id,
|
||||||
self.etud_cur_sem.date_debut.isoformat(),
|
self.etud_cur_sem.date_debut.isoformat(),
|
||||||
self.etud_cur_sem.date_fin.isoformat(),
|
self.etud_cur_sem.date_fin.isoformat(),
|
||||||
|
scu.translate_assiduites_metric(
|
||||||
|
sco_preferences.get_preference("assi_metrique")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
self.nbabsnj = self.nbabs - self.nbabsjust
|
self.nbabsnj = self.nbabs - self.nbabsjust
|
||||||
else:
|
else:
|
||||||
|
@ -10,7 +10,7 @@ from app.decorators import (
|
|||||||
scodoc,
|
scodoc,
|
||||||
permission_required,
|
permission_required,
|
||||||
)
|
)
|
||||||
from app.models import FormSemestre, Identite, ScoDocSiteConfig
|
from app.models import FormSemestre, Identite, ScoDocSiteConfig, Assiduite
|
||||||
from app.views import assiduites_bp as bp
|
from app.views import assiduites_bp as bp
|
||||||
from app.views import ScoData
|
from app.views import ScoData
|
||||||
|
|
||||||
@ -23,6 +23,8 @@ from app.scodoc import sco_groups_view
|
|||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_find_etud
|
from app.scodoc import sco_find_etud
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.scodoc import sco_assiduites as scass
|
||||||
|
|
||||||
|
|
||||||
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
||||||
@ -198,7 +200,7 @@ def signal_assiduites_etud():
|
|||||||
|
|
||||||
return HTMLBuilder(
|
return HTMLBuilder(
|
||||||
header,
|
header,
|
||||||
render_template("assiduites/minitimeline.j2"),
|
_mini_timeline(),
|
||||||
render_template(
|
render_template(
|
||||||
"assiduites/signal_assiduites_etud.j2",
|
"assiduites/signal_assiduites_etud.j2",
|
||||||
sco=ScoData(etud),
|
sco=ScoData(etud),
|
||||||
@ -207,6 +209,7 @@ def signal_assiduites_etud():
|
|||||||
lunch=lunch,
|
lunch=lunch,
|
||||||
timeline=_timeline(),
|
timeline=_timeline(),
|
||||||
afternoon=afternoon,
|
afternoon=afternoon,
|
||||||
|
nonworkdays=_non_work_days(),
|
||||||
forcer_module=sco_preferences.get_preference(
|
forcer_module=sco_preferences.get_preference(
|
||||||
"forcer_module", dept_id=g.scodoc_dept_id
|
"forcer_module", dept_id=g.scodoc_dept_id
|
||||||
),
|
),
|
||||||
@ -214,6 +217,12 @@ def signal_assiduites_etud():
|
|||||||
).build()
|
).build()
|
||||||
|
|
||||||
|
|
||||||
|
def _non_work_days():
|
||||||
|
non_travail = sco_preferences.get_preference("non_travail", None)
|
||||||
|
non_travail = non_travail.replace(" ", "").split(",")
|
||||||
|
return ",".join([f"'{i.lower()}'" for i in non_travail])
|
||||||
|
|
||||||
|
|
||||||
def _str_to_num(string: str):
|
def _str_to_num(string: str):
|
||||||
parts = [*map(float, string.split(":"))]
|
parts = [*map(float, string.split(":"))]
|
||||||
hour = parts[0]
|
hour = parts[0]
|
||||||
@ -277,10 +286,6 @@ def signal_assiduites_group():
|
|||||||
Returns:
|
Returns:
|
||||||
str: l'html généré
|
str: l'html généré
|
||||||
"""
|
"""
|
||||||
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
|
||||||
moduleimpl_id: int = request.args.get("moduleimpl_id")
|
|
||||||
group_ids: list[int] = request.args.get("group_ids", None)
|
|
||||||
|
|
||||||
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
||||||
moduleimpl_id: int = request.args.get("moduleimpl_id")
|
moduleimpl_id: int = request.args.get("moduleimpl_id")
|
||||||
date: str = request.args.get("jour", datetime.date.today().isoformat())
|
date: str = request.args.get("jour", datetime.date.today().isoformat())
|
||||||
@ -307,7 +312,6 @@ def signal_assiduites_group():
|
|||||||
group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id
|
group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Aucun étudiant WIP
|
|
||||||
if not groups_infos.members:
|
if not groups_infos.members:
|
||||||
return (
|
return (
|
||||||
html_sco_header.sco_header(page_title="Saisie journalière des Assiduités")
|
html_sco_header.sco_header(page_title="Saisie journalière des Assiduités")
|
||||||
@ -321,6 +325,7 @@ def signal_assiduites_group():
|
|||||||
|
|
||||||
# --- Filtrage par formsemestre ---
|
# --- Filtrage par formsemestre ---
|
||||||
formsemestre_id = groups_infos.formsemestre_id
|
formsemestre_id = groups_infos.formsemestre_id
|
||||||
|
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
if formsemestre.dept_id != g.scodoc_dept_id:
|
if formsemestre.dept_id != g.scodoc_dept_id:
|
||||||
abort(404, "groupes inexistants dans ce département")
|
abort(404, "groupes inexistants dans ce département")
|
||||||
@ -385,7 +390,7 @@ def signal_assiduites_group():
|
|||||||
|
|
||||||
return HTMLBuilder(
|
return HTMLBuilder(
|
||||||
header,
|
header,
|
||||||
render_template("assiduites/minitimeline.j2"),
|
_mini_timeline(),
|
||||||
render_template(
|
render_template(
|
||||||
"assiduites/signal_assiduites_group.j2",
|
"assiduites/signal_assiduites_group.j2",
|
||||||
gr_tit=gr_tit,
|
gr_tit=gr_tit,
|
||||||
@ -395,6 +400,7 @@ def signal_assiduites_group():
|
|||||||
grp=sco_groups_view.menu_groups_choice(groups_infos),
|
grp=sco_groups_view.menu_groups_choice(groups_infos),
|
||||||
moduleimpl_select=_module_selector(formsemestre, moduleimpl_id),
|
moduleimpl_select=_module_selector(formsemestre, moduleimpl_id),
|
||||||
timeline=_timeline(),
|
timeline=_timeline(),
|
||||||
|
nonworkdays=_non_work_days(),
|
||||||
formsemestre_date_debut=str(formsemestre.date_debut),
|
formsemestre_date_debut=str(formsemestre.date_debut),
|
||||||
formsemestre_date_fin=str(formsemestre.date_fin),
|
formsemestre_date_fin=str(formsemestre.date_fin),
|
||||||
forcer_module=sco_preferences.get_preference(
|
forcer_module=sco_preferences.get_preference(
|
||||||
@ -407,6 +413,154 @@ def signal_assiduites_group():
|
|||||||
).build()
|
).build()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/EtatAbsencesDate")
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def get_etat_abs_date():
|
||||||
|
evaluation = {
|
||||||
|
"jour": request.args.get("jour"),
|
||||||
|
"heure_debut": request.args.get("heure_debut"),
|
||||||
|
"heure_fin": request.args.get("heure_fin"),
|
||||||
|
"title": request.args.get("desc"),
|
||||||
|
}
|
||||||
|
date: str = evaluation["jour"]
|
||||||
|
group_ids: list[int] = request.args.get("group_ids", None)
|
||||||
|
etudiants: list[dict] = []
|
||||||
|
|
||||||
|
if group_ids is None:
|
||||||
|
group_ids = []
|
||||||
|
else:
|
||||||
|
group_ids = group_ids.split(",")
|
||||||
|
map(str, group_ids)
|
||||||
|
|
||||||
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||||
|
|
||||||
|
etuds = [
|
||||||
|
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||||
|
for m in groups_infos.members
|
||||||
|
]
|
||||||
|
|
||||||
|
date_debut = scu.is_iso_formated(
|
||||||
|
f"{evaluation['jour']}T{evaluation['heure_debut'].replace('h',':')}", True
|
||||||
|
)
|
||||||
|
date_fin = scu.is_iso_formated(
|
||||||
|
f"{evaluation['jour']}T{evaluation['heure_fin'].replace('h',':')}", True
|
||||||
|
)
|
||||||
|
|
||||||
|
assiduites: Assiduite = Assiduite.query.filter(
|
||||||
|
Assiduite.etudid.in_([e["etudid"] for e in etuds])
|
||||||
|
)
|
||||||
|
assiduites = scass.filter_by_date(
|
||||||
|
assiduites, Assiduite, date_debut, date_fin, False
|
||||||
|
)
|
||||||
|
|
||||||
|
for etud in etuds:
|
||||||
|
assi = assiduites.filter_by(etudid=etud["etudid"]).first()
|
||||||
|
|
||||||
|
etat = ""
|
||||||
|
if assi != None and assi.etat != 0:
|
||||||
|
etat = scu.EtatAssiduite.inverse().get(assi.etat).name
|
||||||
|
|
||||||
|
etudiant = {
|
||||||
|
"nom": f'<a href="{url_for("absences.CalAbs", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"])}"><font color="#A00000">{etud["nomprenom"]}</font></a>',
|
||||||
|
"etat": etat,
|
||||||
|
}
|
||||||
|
|
||||||
|
etudiants.append(etudiant)
|
||||||
|
|
||||||
|
etudiants = list(sorted(etudiants, key=lambda x: x["nom"]))
|
||||||
|
|
||||||
|
header: str = html_sco_header.sco_header(
|
||||||
|
page_title=evaluation["title"],
|
||||||
|
init_qtip=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return HTMLBuilder(
|
||||||
|
header,
|
||||||
|
render_template(
|
||||||
|
"assiduites/etat_absence_date.j2", etudiants=etudiants, eval=evaluation
|
||||||
|
),
|
||||||
|
html_sco_header.sco_footer(),
|
||||||
|
).build()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/SignalAssiduiteDifferee")
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoAbsChange)
|
||||||
|
def signal_assiduites_diff():
|
||||||
|
group_ids: list[int] = request.args.get("group_ids", None)
|
||||||
|
etudid: int = request.args.get("etudid", None)
|
||||||
|
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
||||||
|
etudiants: list[dict] = []
|
||||||
|
|
||||||
|
titre = None
|
||||||
|
|
||||||
|
# Vérification du formsemestre_id
|
||||||
|
try:
|
||||||
|
formsemestre_id = int(formsemestre_id)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
formsemestre_id = None
|
||||||
|
|
||||||
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
|
||||||
|
if etudid is not None:
|
||||||
|
etudiants.append(sco_etud.get_etud_info(etudid=int(etudid), filled=True)[0])
|
||||||
|
|
||||||
|
if group_ids is None:
|
||||||
|
group_ids = []
|
||||||
|
else:
|
||||||
|
group_ids = group_ids.split(",")
|
||||||
|
map(str, group_ids)
|
||||||
|
|
||||||
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||||
|
|
||||||
|
etudiants.extend(
|
||||||
|
[
|
||||||
|
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||||
|
for m in groups_infos.members
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
etudiants = list(sorted(etudiants, key=lambda x: x["nom"]))
|
||||||
|
|
||||||
|
header: str = html_sco_header.sco_header(
|
||||||
|
page_title="Assiduités Différées",
|
||||||
|
init_qtip=True,
|
||||||
|
javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS
|
||||||
|
+ [
|
||||||
|
"js/assiduites.js",
|
||||||
|
"libjs/moment.new.min.js",
|
||||||
|
"libjs/moment-timezone.js",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
sem = formsemestre.to_dict()
|
||||||
|
|
||||||
|
if groups_infos.tous_les_etuds_du_sem:
|
||||||
|
gr_tit = "en"
|
||||||
|
else:
|
||||||
|
if len(groups_infos.group_ids) > 1:
|
||||||
|
grp = "des groupes"
|
||||||
|
else:
|
||||||
|
grp = "du groupe"
|
||||||
|
gr_tit = (
|
||||||
|
grp + ' <span class="fontred">' + groups_infos.groups_titles + "</span>"
|
||||||
|
)
|
||||||
|
|
||||||
|
return HTMLBuilder(
|
||||||
|
header,
|
||||||
|
render_template(
|
||||||
|
"assiduites/signal_assiduites_diff.j2",
|
||||||
|
etudiants=etudiants,
|
||||||
|
etat_def=sco_preferences.get_preference("assi_etat_defaut"),
|
||||||
|
moduleimpl_select=_module_selector(formsemestre),
|
||||||
|
gr=gr_tit,
|
||||||
|
sem=sem["titre_num"],
|
||||||
|
),
|
||||||
|
html_sco_header.sco_footer(),
|
||||||
|
).build()
|
||||||
|
|
||||||
|
|
||||||
def _module_selector(
|
def _module_selector(
|
||||||
formsemestre: FormSemestre, moduleimpl_id: int = None
|
formsemestre: FormSemestre, moduleimpl_id: int = None
|
||||||
) -> HTMLElement:
|
) -> HTMLElement:
|
||||||
@ -444,9 +598,21 @@ def _module_selector(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _timeline() -> HTMLElement:
|
def _timeline(formsemestre_id=None) -> HTMLElement:
|
||||||
return render_template(
|
return render_template(
|
||||||
"assiduites/timeline.j2",
|
"assiduites/timeline.j2",
|
||||||
t_start=get_time("assi_morning_time", "08:00:00"),
|
t_start=get_time("assi_morning_time", "08:00:00"),
|
||||||
t_end=get_time("assi_afternoon_time", "18:00:00"),
|
t_end=get_time("assi_afternoon_time", "18:00:00"),
|
||||||
|
tick_time=ScoDocSiteConfig.get("assi_tick_time", 15),
|
||||||
|
periode_defaut=sco_preferences.get_preference(
|
||||||
|
"periode_defaut", formsemestre_id
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _mini_timeline() -> HTMLElement:
|
||||||
|
return render_template(
|
||||||
|
"assiduites/minitimeline.j2",
|
||||||
|
t_start=get_time("assi_morning_time", "08:00:00"),
|
||||||
|
t_end=get_time("assi_afternoon_time", "18:00:00"),
|
||||||
)
|
)
|
||||||
|
@ -203,6 +203,8 @@ def config_assiduites():
|
|||||||
flash("Heure de midi enregistrée")
|
flash("Heure de midi enregistrée")
|
||||||
if ScoDocSiteConfig.set("assi_afternoon_time", form.data["afternoon_time"]):
|
if ScoDocSiteConfig.set("assi_afternoon_time", form.data["afternoon_time"]):
|
||||||
flash("Heure de fin de la journée enregistrée")
|
flash("Heure de fin de la journée enregistrée")
|
||||||
|
if ScoDocSiteConfig.set("assi_tick_time", form.data["tick_time"]):
|
||||||
|
flash("Granularité de la timeline enregistrée")
|
||||||
return redirect(url_for("scodoc.configuration"))
|
return redirect(url_for("scodoc.configuration"))
|
||||||
|
|
||||||
elif request.method == "GET":
|
elif request.method == "GET":
|
||||||
@ -215,6 +217,7 @@ def config_assiduites():
|
|||||||
form.afternoon_time.data = ScoDocSiteConfig.get(
|
form.afternoon_time.data = ScoDocSiteConfig.get(
|
||||||
"assi_afternoon_time", datetime.time(18, 0, 0)
|
"assi_afternoon_time", datetime.time(18, 0, 0)
|
||||||
)
|
)
|
||||||
|
form.tick_time.data = float(ScoDocSiteConfig.get("assi_tick_time", 15))
|
||||||
return render_template(
|
return render_template(
|
||||||
"assiduites/config_assiduites.j2",
|
"assiduites/config_assiduites.j2",
|
||||||
form=form,
|
form=form,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""modèles assiduites justificatifs
|
"""modèles assiduites justificatifs
|
||||||
|
|
||||||
Revision ID: dbcf2175e87f
|
Revision ID: dbcf2175e87f
|
||||||
Revises: 5c7b208355df
|
Revises: d84bc592584e
|
||||||
Create Date: 2023-02-01 14:21:06.989190
|
Create Date: 2023-02-01 14:21:06.989190
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -11,7 +11,7 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = "dbcf2175e87f"
|
revision = "dbcf2175e87f"
|
||||||
down_revision = "6520faf67508"
|
down_revision = "d84bc592584e"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
@ -395,7 +395,7 @@ def verifier_filtrage_justificatifs(etud: Identite, justificatifs: list[Justific
|
|||||||
|
|
||||||
# Justifications des assiduites
|
# Justifications des assiduites
|
||||||
|
|
||||||
assert len(scass.justifies(justificatifs[2])) == 1, "Justifications mauvais"
|
assert len(scass.justifies(justificatifs[2])) == 2, "Justifications mauvais"
|
||||||
assert len(scass.justifies(justificatifs[0])) == 0, "Justifications mauvais"
|
assert len(scass.justifies(justificatifs[0])) == 0, "Justifications mauvais"
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user