diff --git a/app/api/assiduites.py b/app/api/assiduites.py
index ec47f518a..bbd6142f9 100644
--- a/app/api/assiduites.py
+++ b/app/api/assiduites.py
@@ -601,9 +601,12 @@ def _create_singular(
     moduleimpl_id = data.get("moduleimpl_id", False)
     moduleimpl: ModuleImpl = None
 
-    if moduleimpl_id not in [False, None]:
+    if moduleimpl_id not in [False, None, "", "-1"]:
         if moduleimpl_id != "autre":
-            moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
+            try:
+                moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
+            except ValueError:
+                moduleimpl = None
             if moduleimpl is None:
                 errors.append("param 'moduleimpl_id': invalide")
         else:
@@ -810,7 +813,7 @@ def _edit_singular(assiduite_unique, data):
     moduleimpl: ModuleImpl = None
 
     if moduleimpl_id is not False:
-        if moduleimpl_id is not None:
+        if moduleimpl_id not in [None, "", "-1"]:
             if moduleimpl_id == "autre":
                 assiduite_unique.moduleimpl_id = None
                 external_data = (
@@ -823,7 +826,13 @@ def _edit_singular(assiduite_unique, data):
                 assiduite_unique.external_data = external_data
 
             else:
-                moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
+                try:
+                    moduleimpl = ModuleImpl.query.filter_by(
+                        id=int(moduleimpl_id)
+                    ).first()
+                except ValueError:
+                    moduleimpl = None
+
                 if moduleimpl is None:
                     errors.append("param 'moduleimpl_id': invalide")
                 else:
@@ -834,7 +843,7 @@ def _edit_singular(assiduite_unique, data):
                     else:
                         assiduite_unique.moduleimpl_id = moduleimpl_id
         else:
-            assiduite_unique.moduleimpl_id = moduleimpl_id
+            assiduite_unique.moduleimpl_id = None
 
     # Cas 3 : desc
     desc = data.get("desc", False)
diff --git a/app/models/assiduites.py b/app/models/assiduites.py
index 7f5520df7..0f8933e73 100644
--- a/app/models/assiduites.py
+++ b/app/models/assiduites.py
@@ -350,11 +350,15 @@ def compute_assiduites_justified(
     if justificatifs is None:
         justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid).all()
 
+    justificatifs = [j for j in justificatifs if j.etat == EtatJustificatif.VALIDE]
+
     assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
 
     assiduites_justifiees: list[int] = []
 
     for assi in assiduites:
+        if assi.etat == EtatAssiduite.PRESENT:
+            continue
         if any(
             assi.date_debut >= j.date_debut and assi.date_fin <= j.date_fin
             for j in justificatifs
diff --git a/app/models/etudiants.py b/app/models/etudiants.py
index e00f84650..a451526f7 100644
--- a/app/models/etudiants.py
+++ b/app/models/etudiants.py
@@ -74,9 +74,11 @@ class Identite(db.Model):
     )
 
     # Relations avec les assiduites et les justificatifs
-    assiduites = db.relationship("Assiduite", back_populates="etudiant", lazy="dynamic")
+    assiduites = db.relationship(
+        "Assiduite", back_populates="etudiant", lazy="dynamic", cascade="all, delete"
+    )
     justificatifs = db.relationship(
-        "Justificatif", back_populates="etudiant", lazy="dynamic"
+        "Justificatif", back_populates="etudiant", lazy="dynamic", cascade="all, delete"
     )
 
     def __repr__(self):
diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py
index 2a478843b..07e778cf3 100644
--- a/app/scodoc/sco_assiduites.py
+++ b/app/scodoc/sco_assiduites.py
@@ -212,7 +212,7 @@ def get_assiduites_stats(
     output: dict = {}
     calculator: CountCalculator = CountCalculator()
 
-    if "split" not in filtered:
+    if filtered is None or "split" not in filtered:
         calculator.compute_assiduites(assiduites)
         count: dict = calculator.to_dict()
 
@@ -382,7 +382,10 @@ def justifies(justi: Justificatif, obj: bool = False) -> list[int] or Query:
 
 
 def get_all_justified(
-    etudid: int, date_deb: datetime = None, date_fin: datetime = None
+    etudid: int,
+    date_deb: datetime = None,
+    date_fin: datetime = None,
+    moduleimpl_id: int = None,
 ) -> Query:
     """Retourne toutes les assiduités justifiées sur une période"""
 
@@ -393,7 +396,9 @@ def get_all_justified(
 
     date_deb = scu.localize_datetime(date_deb)
     date_fin = scu.localize_datetime(date_fin)
-    justified = Assiduite.query.filter_by(est_just=True, etudid=etudid)
+    justified: Query = Assiduite.query.filter_by(est_just=True, etudid=etudid)
+    if moduleimpl_id is not None:
+        justified = justified.filter_by(moduleimpl_id=moduleimpl_id)
     after = filter_by_date(
         justified,
         Assiduite,
@@ -419,7 +424,7 @@ def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int]:
 
 
 def formsemestre_get_assiduites_count(
-    etudid: int, formsemestre: FormSemestre
+    etudid: int, formsemestre: FormSemestre, moduleimpl_id: int = None
 ) -> tuple[int, int]:
     """Les comptes d'absences de cet étudiant dans ce semestre:
     tuple (nb abs non justifiées, nb abs justifiées)
@@ -428,9 +433,14 @@ def formsemestre_get_assiduites_count(
     metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id)
     return get_assiduites_count_in_interval(
         etudid,
-        date_debut=formsemestre.date_debut,
-        date_fin=formsemestre.date_fin,
+        date_debut=scu.localize_datetime(
+            datetime.combine(formsemestre.date_debut, time(8, 0))
+        ),
+        date_fin=scu.localize_datetime(
+            datetime.combine(formsemestre.date_fin, time(18, 0))
+        ),
         metrique=scu.translate_assiduites_metric(metrique),
+        moduleimpl_id=moduleimpl_id,
     )
 
 
@@ -441,6 +451,7 @@ def get_assiduites_count_in_interval(
     metrique="demi",
     date_debut: datetime = None,
     date_fin: datetime = None,
+    moduleimpl_id: int = None,
 ):
     """Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
     tuple (nb abs, nb abs justifiées)
@@ -452,33 +463,39 @@ def get_assiduites_count_in_interval(
     key = f"{etudid}_{date_debut_iso}_{date_fin_iso}{metrique}_assiduites"
 
     r = sco_cache.AbsSemEtudCache.get(key)
-    if not r:
+    if not r or moduleimpl_id is not None:
         date_debut: datetime = date_debut or datetime.fromisoformat(date_debut_iso)
         date_fin: datetime = date_fin or datetime.fromisoformat(date_fin_iso)
 
-        assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
+        assiduites: Query = Assiduite.query.filter_by(etudid=etudid)
         assiduites = assiduites.filter(Assiduite.etat == scu.EtatAssiduite.ABSENT)
         justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid)
 
         assiduites = filter_by_date(assiduites, Assiduite, date_debut, date_fin)
+
+        if moduleimpl_id is not None:
+            assiduites = assiduites.filter_by(moduleimpl_id=moduleimpl_id)
+
         justificatifs = filter_by_date(
             justificatifs, Justificatif, date_debut, date_fin
         )
-
         calculator: CountCalculator = CountCalculator()
         calculator.compute_assiduites(assiduites)
         nb_abs: dict = calculator.to_dict()[metrique]
 
-        abs_just: list[Assiduite] = get_all_justified(etudid, date_debut, date_fin)
+        abs_just: list[Assiduite] = get_all_justified(
+            etudid, date_debut, date_fin, moduleimpl_id
+        )
 
         calculator.reset()
         calculator.compute_assiduites(abs_just)
         nb_abs_just: dict = calculator.to_dict()[metrique]
 
         r = (nb_abs, nb_abs_just)
-        ans = sco_cache.AbsSemEtudCache.set(key, r)
-        if not ans:
-            log("warning: get_assiduites_count failed to cache")
+        if moduleimpl_id is None:
+            ans = sco_cache.AbsSemEtudCache.set(key, r)
+            if not ans:
+                log("warning: get_assiduites_count failed to cache")
     return r
 
 
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index ee8417f3b..00f0604ef 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -237,7 +237,7 @@ def localize_datetime(date: datetime.datetime or str) -> datetime.datetime:
     new_date: datetime.datetime = date
     if new_date.tzinfo is None:
         try:
-            new_date = timezone("Europe/Paris").localize(date)
+            new_date = TIME_ZONE.localize(date)
         except OverflowError:
             new_date = timezone("UTC").localize(date)
     return new_date
diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js
index 2ee935efc..73d872ec6 100644
--- a/app/static/js/assiduites.js
+++ b/app/static/js/assiduites.js
@@ -811,11 +811,8 @@ function numberTimeToDate(nb) {
  * @param {boolean} clear vidage de l'objet "assiduites" ou non
  * @returns {object} l'objets Assiduités {<etudid:str> : [<assiduite>,]}
  */
-function getAssiduitesFromEtuds(clear, has_formsemestre = true, deb, fin) {
+function getAssiduitesFromEtuds(clear, deb, fin) {
   const etudIds = Object.keys(etuds).join(",");
-  const formsemestre_id = has_formsemestre
-    ? `formsemestre_id=${getFormSemestreId()}&`
-    : "";
 
   const date_debut = deb ? deb : toIsoString(getPrevDate());
   const date_fin = fin ? fin : toIsoString(getNextDate());
@@ -826,7 +823,7 @@ function getAssiduitesFromEtuds(clear, has_formsemestre = true, deb, fin) {
 
   const url_api =
     getUrl() +
-    `/api/assiduites/group/query?date_debut=${date_debut}&${formsemestre_id}&date_fin=${date_fin}&etudids=${etudIds}`;
+    `/api/assiduites/group/query?date_debut=${date_debut}&date_fin=${date_fin}&etudids=${etudIds}`;
   sync_get(url_api, (data, status) => {
     if (status === "success") {
       const dataKeys = Object.keys(data);
@@ -924,14 +921,11 @@ function deleteAssiduite(assiduite_id) {
 
 function hasModuleImpl(assiduite) {
   if (assiduite.moduleimpl_id != null) return true;
-  if (
-    "external_data" in assiduite &&
-    assiduite.external_data instanceof Object &&
-    "module" in assiduite.external_data
-  )
-    return true;
-
-  return false;
+  return (
+    assiduite.hasOwnProperty("external_data") &&
+    assiduite.external_data != null &&
+    assiduite.external_data.hasOwnProperty("module")
+  );
 }
 
 /**
@@ -1057,16 +1051,13 @@ function getAssiduiteValue(field) {
  * Mise à jour des assiduités d'un étudiant
  * @param {String | Number} etudid identifiant de l'étudiant
  */
-function actualizeEtudAssiduite(etudid, has_formsemestre = true) {
-  const formsemestre_id = has_formsemestre
-    ? `formsemestre_id=${getFormSemestreId()}&`
-    : "";
+function actualizeEtudAssiduite(etudid) {
   const date_debut = toIsoString(getPrevDate());
   const date_fin = toIsoString(getNextDate());
 
   const url_api =
     getUrl() +
-    `/api/assiduites/${etudid}/query?${formsemestre_id}date_debut=${date_debut}&date_fin=${date_fin}`;
+    `/api/assiduites/${etudid}/query?date_debut=${date_debut}&date_fin=${date_fin}`;
   sync_get(url_api, (data, status) => {
     if (status === "success") {
       assiduites[etudid] = data;
@@ -1331,7 +1322,7 @@ function insertEtudRow(etud, index, output = false) {
  * @param {String | Number} etudid l'identifiant de l'étudiant
  */
 function actualizeEtud(etudid) {
-  actualizeEtudAssiduite(etudid, !isSingleEtud());
+  actualizeEtudAssiduite(etudid);
   //Actualize row
   const etudHolder = document.querySelector(".etud_holder");
   const ancient_row = document.getElementById(`etud_row_${etudid}`);
@@ -1412,10 +1403,10 @@ function setModuleImplId(assiduite, module = null) {
   const moduleimpl = module == null ? getModuleImplId() : module;
   if (moduleimpl === "autre") {
     if (
-      "external_data" in assiduite &&
-      assiduite.external_data instanceof Object
+      assiduite.hasOwnProperty("external_data") &&
+      assiduite.external_data != null
     ) {
-      if ("module" in assiduite.external_data) {
+      if (assiduite.external_data.hasOwnProperty("module")) {
         assiduite.external_data.module = "Autre";
       } else {
         assiduite["external_data"] = { module: "Autre" };
@@ -1427,10 +1418,10 @@ function setModuleImplId(assiduite, module = null) {
   } else {
     assiduite["moduleimpl_id"] = moduleimpl;
     if (
-      "external_data" in assiduite &&
-      assiduite.external_data instanceof Object
+      assiduite.hasOwnProperty("external_data") &&
+      assiduite.external_data != null
     ) {
-      if ("module" in assiduite.external_data) {
+      if (assiduite.external_data.hasOwnProperty("module")) {
         delete assiduite.external_data.module;
       }
     }
@@ -1482,9 +1473,9 @@ function getCurrentAssiduiteModuleImplId() {
     let mod = currentAssiduites[0].moduleimpl_id;
     if (
       mod == null &&
-      "external_data" in currentAssiduites[0] &&
-      currentAssiduites[0].external_data instanceof Object &&
-      "module" in currentAssiduites[0].external_data
+      currentAssiduites[0].hasOwnProperty("external_data") &&
+      currentAssiduites[0].external_data != null &&
+      currentAssiduites[0].external_data.hasOwnProperty("module")
     ) {
       mod = currentAssiduites[0].external_data.module;
     }
@@ -1696,9 +1687,9 @@ function getModuleImpl(assiduite) {
 
   if (id == null || id == undefined) {
     if (
-      "external_data" in assiduite &&
-      assiduite.external_data instanceof Object &&
-      "module" in assiduite.external_data
+      assiduite.hasOwnProperty("external_data") &&
+      assiduite.external_data != null &&
+      assiduite.external_data.hasOwnProperty("module")
     ) {
       return assiduite.external_data.module;
     } else {
@@ -1724,10 +1715,12 @@ function getModuleImpl(assiduite) {
 }
 
 function getUser(obj) {
-  if ("external_data" in obj && obj.external_data != null) {
-    if ("enseignant" in obj.external_data) {
-      return obj.external_data.enseignant;
-    }
+  if (
+    obj.hasOwnProperty("external_data") &&
+    obj.external_data != null &&
+    obj.external_data.hasOwnProperty("enseignant")
+  ) {
+    return obj.external_data.enseignant;
   }
 
   return obj.user_id;
diff --git a/app/templates/assiduites/widgets/differee.j2 b/app/templates/assiduites/widgets/differee.j2
index aa42a3e5a..8d96c5bc4 100644
--- a/app/templates/assiduites/widgets/differee.j2
+++ b/app/templates/assiduites/widgets/differee.j2
@@ -533,7 +533,7 @@
         }
 
         if (get) {
-            getAssiduitesFromEtuds(false, false, d_debut.format(), d_fin.format())
+            getAssiduitesFromEtuds(false, d_debut.format(), d_fin.format())
             return 0x0;
         }
 
diff --git a/app/templates/assiduites/widgets/tableau_assi.j2 b/app/templates/assiduites/widgets/tableau_assi.j2
index c40d211b0..78c34cf00 100644
--- a/app/templates/assiduites/widgets/tableau_assi.j2
+++ b/app/templates/assiduites/widgets/tableau_assi.j2
@@ -184,8 +184,11 @@
             path,
             (data) => {
                 let module = data.moduleimpl_id;
-
-                if (module == null && "external_data" in data && "module" in data.external_data) {
+                if (
+                    module == null && data.hasOwnProperty("external_data") &&
+                    data.external_data != null &&
+                    data.external_data.hasOwnProperty('module')
+                ) {
                     module = data.external_data.module.toLowerCase();
                 }
 
diff --git a/app/views/notes.py b/app/views/notes.py
index 9f69cbd97..f3ad9e440 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -1174,22 +1174,9 @@ def view_module_abs(moduleimpl_id, fmt="html"):
 
     rows = []
     for etud in inscrits:
-        # TODO-ASSIDUITE  ne va pas car ne filtre pas sur le moduleimpl
-        # nb_abs, nb_abs_just = sco_assiduites.formsemestre_get_assiduites_count(etud.id, modimpl.formsemestre)
-        nb_abs, nb_abs_just = 0, 0  # XXX TODO-ASSIDUITE
-        # nb_abs = sco_abs.count_abs(
-        #     etudid=etud.id,
-        #     debut=debut_sem,
-        #     fin=fin_sem,
-        #     moduleimpl_id=moduleimpl_id,
-        # )
-        # if nb_abs:
-        #     nb_abs_just = sco_abs.count_abs_just(
-        #         etudid=etud.id,
-        #         debut=debut_sem,
-        #         fin=fin_sem,
-        #         moduleimpl_id=moduleimpl_id,
-        #     )
+        nb_abs, nb_abs_just = sco_assiduites.formsemestre_get_assiduites_count(
+            etud.id, modimpl.formsemestre, moduleimpl_id=modimpl.id
+        )
         rows.append(
             {
                 "nomprenom": etud.nomprenom,
diff --git a/tests/unit/test_assiduites.py b/tests/unit/test_assiduites.py
index 725be9491..50f92c649 100644
--- a/tests/unit/test_assiduites.py
+++ b/tests/unit/test_assiduites.py
@@ -13,8 +13,7 @@ import app.scodoc.sco_assiduites as scass
 import app.scodoc.sco_utils as scu
 from app import db
 from app.models import Assiduite, FormSemestre, Identite, Justificatif, ModuleImpl
-
-# from app.scodoc import sco_abs_views, sco_formsemestre TODO-ASSIDUITE
+from app.models.assiduites import compute_assiduites_justified
 
 from app.scodoc.sco_exceptions import ScoValueError
 from tests.unit import sco_fake_gen
@@ -39,7 +38,6 @@ def test_bi_directional_enum(test_client):
     assert BiInt.inverse()[1] == BiInt.A and BiInt.inverse()[2] == BiInt.B
 
 
-@pytest.mark.skip  # XXX TODO-ASSIDUITE (issue #690)
 def test_general(test_client):
     """tests général du modèle assiduite"""
 
@@ -80,11 +78,9 @@ def test_general(test_client):
         date_fin="31/07/2024",
     )
 
-    formsemestre_1 = sco_formsemestre.get_formsemestre(
-        formsemestre_id_1
-    )  # Utiliser plutot FormSemestre de nos jours  TODO-ASSIDUITE
-    formsemestre_2 = sco_formsemestre.get_formsemestre(formsemestre_id_2)
-    formsemestre_3 = sco_formsemestre.get_formsemestre(formsemestre_id_3)
+    formsemestre_1 = FormSemestre.get_formsemestre(formsemestre_id_1)
+    formsemestre_2 = FormSemestre.get_formsemestre(formsemestre_id_2)
+    formsemestre_3 = FormSemestre.get_formsemestre(formsemestre_id_3)
 
     # Création des modulesimpls (4, 2 par semestre)
 
@@ -137,7 +133,7 @@ def test_general(test_client):
     etud_faux_dict = g_fake.create_etud(code_nip=None, prenom="etudfaux")
     etud_faux = Identite.query.filter_by(id=etud_faux_dict["id"]).first()
 
-    # verif_migration_abs_assiduites() // Test à revoir TODO-ASSIDUITE
+    # verif_migration_abs_assiduites() // Test à revoir TODO-ASSIDUITE (issue #696)
 
     ajouter_assiduites(etuds, moduleimpls, etud_faux)
     justificatifs: list[Justificatif] = ajouter_justificatifs(etuds[0])
@@ -145,11 +141,14 @@ def test_general(test_client):
         etuds, moduleimpls, (formsemestre_1, formsemestre_2, formsemestre_3)
     )
     verifier_filtrage_justificatifs(etuds[0], justificatifs)
+
+    essais_cache(etuds[0].etudid, (formsemestre_1, formsemestre_2), moduleimpls)
+
     editer_supprimer_assiduites(etuds, moduleimpls)
     editer_supprimer_justificatif(etuds[0])
 
 
-@pytest.mark.skip  # XXX TODO-ASSIDUITE (issue #696)
+# XXX TODO-ASSIDUITE (issue #696)
 def verif_migration_abs_assiduites():
     """Vérification que le script de migration fonctionne correctement"""
     downgrade_module(assiduites=True, justificatifs=True)
@@ -476,11 +475,11 @@ def _get_justi(
     ).first()
 
 
-def essais_cache(etudid):
+def essais_cache(etudid, sems: tuple[FormSemestre], moduleimpls: list[ModuleImpl]):
     """Vérification des fonctionnalités du cache"""
 
-    date_deb: str = "2023-01-01T07:00"
-    date_fin: str = "2023-03-31T19:00"
+    date_deb: str = "2022-09-01T07:00"
+    date_fin: str = "2023-01-31T19:00"
 
     assiduites_count_no_cache = scass.get_assiduites_count_in_interval(
         etudid, date_deb, date_fin
@@ -490,8 +489,36 @@ def essais_cache(etudid):
     )
 
     assert (
-        assiduites_count_cache == assiduites_count_no_cache == (34, 15)
-    ), "Erreur cache"
+        assiduites_count_cache == assiduites_count_no_cache == (2, 1)
+    ), "Erreur cache classique"
+
+    assert scass.formsemestre_get_assiduites_count(etudid, sems[0]) == (
+        2,
+        1,
+    ), "Erreur formsemestre_get_assiduites_count (sans module) A"
+    assert scass.formsemestre_get_assiduites_count(etudid, sems[1]) == (
+        0,
+        0,
+    ), "Erreur formsemestre_get_assiduites_count (sans module) B"
+
+    assert scass.formsemestre_get_assiduites_count(
+        etudid, sems[0], moduleimpl_id=moduleimpls[0].id
+    ) == (
+        1,
+        1,
+    ), "Erreur formsemestre_get_assiduites_count (avec module) A"
+    assert scass.formsemestre_get_assiduites_count(
+        etudid, sems[0], moduleimpl_id=moduleimpls[1].id
+    ) == (
+        1,
+        0,
+    ), "Erreur formsemestre_get_assiduites_count (avec module) A"
+    assert scass.formsemestre_get_assiduites_count(
+        etudid, sems[0], moduleimpl_id=moduleimpls[2].id
+    ) == (
+        0,
+        0,
+    ), "Erreur formsemestre_get_assiduites_count (avec module) A"
 
 
 def ajouter_justificatifs(etud):
@@ -543,6 +570,8 @@ def ajouter_justificatifs(etud):
         db.session.commit()
         justificatifs.append(just_obj)
 
+    compute_assiduites_justified(etud.etudid, justificatifs)
+
     # Vérification de la création des justificatifs
     assert [
         justi for justi in justificatifs if not isinstance(justi, Justificatif)
@@ -858,7 +887,7 @@ def ajouter_assiduites(
 
 
 def verifier_comptage_et_filtrage_assiduites(
-    etuds: list[Identite], moduleimpls: list[int], formsemestres: tuple[int]
+    etuds: list[Identite], moduleimpls: list[int], formsemestres: tuple[FormSemestre]
 ):
     """
     Deuxième partie:
@@ -931,9 +960,6 @@ def verifier_comptage_et_filtrage_assiduites(
     ), "Filtrage par 'Moduleimpl' mauvais"
 
     # Formsemestre
-    formsemestres = [
-        FormSemestre.query.filter_by(id=fms["id"]).first() for fms in formsemestres
-    ]
     assert (
         scass.filter_by_formsemestre(
             etu1.assiduites, Assiduite, formsemestres[0]
diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py
index 030207e5e..be078c921 100644
--- a/tests/unit/test_sco_basic.py
+++ b/tests/unit/test_sco_basic.py
@@ -23,15 +23,18 @@ import app
 from app import db
 from app.comp import res_sem
 from app.comp.res_compat import NotesTableCompat
-from app.models import FormSemestre
+from app.models import FormSemestre, Assiduite, Justificatif
 from app.scodoc import sco_formsemestre
 from app.scodoc import sco_bulletins
 from app.scodoc import codes_cursus
+from app.scodoc import sco_assiduites as scass
 from app.scodoc import sco_evaluations
 from app.scodoc import sco_evaluation_db
 from app.scodoc import sco_formsemestre_validation
 from app.scodoc import sco_cursus_dut
 from app.scodoc import sco_saisie_notes
+from app.scodoc.sco_utils import EtatAssiduite, EtatJustificatif, localize_datetime
+from app.models.assiduites import compute_assiduites_justified
 
 DEPT = TestConfig.DEPT_TEST
 
@@ -186,21 +189,10 @@ def run_sco_basic(verbose=False) -> FormSemestre:
     # -----------------------
     etudid = etuds[0]["etudid"]
 
-    # XXX TODO-ASSIDUITE
-    # _ = sco_abs_views.doSignaleAbsence(
-    #     "15/01/2020", "18/01/2020", demijournee=2, etudid=etudid
-    # )
-
-    # _ = sco_abs_views.doJustifAbsence(
-    #     "17/01/2020",
-    #     "18/01/2020",
-    #     demijournee=2,
-    #     etudid=etudid,
-    # )
-
-    # nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
-    # assert nbabs == 6, f"incorrect nbabs ({nbabs})"
-    # assert nbabsjust == 2, f"incorrect nbabsjust ({nbabsjust})"
+    _signal_absences_justificatifs(etudid)
+    nbabs, nbabsjust = scass.get_assiduites_count(etudid, sem)
+    assert nbabs == 6, f"incorrect nbabs ({nbabs})"
+    assert nbabsjust == 2, f"incorrect nbabsjust ({nbabsjust})"
 
     # --- Permission saisie notes et décisions de jury, avec ou sans démission ou défaillance
     # on n'a pas encore saisi de décisions
@@ -251,3 +243,30 @@ def run_sco_basic(verbose=False) -> FormSemestre:
     )
     assert q.count() == 0
     return formsemestre
+
+
+def _signal_absences_justificatifs(etudid: int):
+    etud: Identite = Identite.query.get(etudid)
+    db.session.commit()
+    for i in range(15, 18):
+        db.session.add(
+            Assiduite.create_assiduite(
+                etud=etud,
+                date_debut=localize_datetime(datetime.datetime(2020, 1, i, 8, 0)),
+                date_fin=localize_datetime(datetime.datetime(2020, 1, i, 18, 0)),
+                etat=EtatAssiduite.ABSENT,
+            )
+        )
+    db.session.commit()
+    justif: Justificatif = Justificatif.create_justificatif(
+        etud=etud,
+        date_debut=localize_datetime(datetime.datetime(2020, 1, 17, 8, 0)),
+        date_fin=localize_datetime(datetime.datetime(2020, 1, 17, 18, 0)),
+        etat=EtatJustificatif.VALIDE,
+    )
+    db.session.add(justif)
+    compute_assiduites_justified(
+        etud.etudid,
+        [justif],
+    )
+    db.session.commit()