diff --git a/app/api/assiduites.py b/app/api/assiduites.py index 44dae142..fe21e606 100644 --- a/app/api/assiduites.py +++ b/app/api/assiduites.py @@ -526,7 +526,7 @@ def _count_manager(requested) -> tuple[str, dict]: return (metric, filtered) -def _filter_manager(requested, assiduites_query): +def _filter_manager(requested, assiduites_query: Assiduite): """ Retourne les assiduites entrées filtrées en fonction de la request """ @@ -538,19 +538,14 @@ def _filter_manager(requested, assiduites_query): # cas 2 : date de début deb = requested.args.get("date_debut") deb: datetime = scu.is_iso_formated(deb, True) - if deb is not None: - - assiduites_query = scass.filter_assiduites_by_date( - assiduites_query, deb, sup=True - ) # cas 3 : date de fin fin = requested.args.get("date_fin") fin = scu.is_iso_formated(fin, True) - if fin is not None: - assiduites_query = scass.filter_assiduites_by_date( - assiduites_query, fin, sup=False + if (deb, fin) != (None, None): + assiduites_query: Assiduite = scass.filter_by_date( + assiduites_query, Assiduite, deb, fin ) # cas 4 : moduleimpl_id diff --git a/app/api/justificatifs.py b/app/api/justificatifs.py index 9af0c955..3cebc4d8 100644 --- a/app/api/justificatifs.py +++ b/app/api/justificatifs.py @@ -384,7 +384,8 @@ def justif_import(justif_id: int = None): archiver: JustificatifArchiver = JustificatifArchiver() try: - archive_name: str = archiver.save_justificatif( + fname: str + archive_name, fname = archiver.save_justificatif( etudid=justificatif_unique.etudid, filename=file.filename, data=file.stream.read(), @@ -396,7 +397,7 @@ def justif_import(justif_id: int = None): db.session.add(justificatif_unique) db.session.commit() - return jsonify({"response": "imported"}) + return jsonify({"filename": fname}) except ScoValueError as err: return json_error(404, err.args[0]) @@ -527,7 +528,6 @@ def justif_list(justif_id: int = None): # Partie justification -# TODO: justificatif-justified @bp.route("/justificatif/justified/", methods=["GET"]) @api_web_bp.route("/justificatif/justified/", methods=["GET"]) @scodoc @@ -567,19 +567,14 @@ def _filter_manager(requested, justificatifs_query): # cas 2 : date de début deb = requested.args.get("date_debut") deb: datetime = scu.is_iso_formated(deb, True) - if deb is not None: - - justificatifs_query = scass.filter_justificatifs_by_date( - justificatifs_query, deb, sup=True - ) # cas 3 : date de fin fin = requested.args.get("date_fin") fin = scu.is_iso_formated(fin, True) - if fin is not None: - justificatifs_query = scass.filter_justificatifs_by_date( - justificatifs_query, fin, sup=False + if (deb, fin) != (None, None): + justificatifs_query: Justificatif = scass.filter_by_date( + justificatifs_query, Justificatif, deb, fin ) return justificatifs_query diff --git a/app/scodoc/sco_archives_justificatifs.py b/app/scodoc/sco_archives_justificatifs.py index 7202f5e1..2f5b6a5f 100644 --- a/app/scodoc/sco_archives_justificatifs.py +++ b/app/scodoc/sco_archives_justificatifs.py @@ -35,7 +35,6 @@ class JustificatifArchiver(BaseArchiver): """ Ajoute un fichier dans une archive "justificatif" pour l'etudid donné Retourne l'archive_name utilisé - TODO: renvoie archive_name + filename """ self._set_dept(etudid) if archive_name is None: @@ -45,9 +44,9 @@ class JustificatifArchiver(BaseArchiver): else: archive_id: str = self.get_id_from_name(etudid, archive_name) - self.store(archive_id, filename, data) + fname: str = self.store(archive_id, filename, data) - return self.get_archive_name(archive_id) + return self.get_archive_name(archive_id), fname def delete_justificatif(self, etudid: int, archive_name: str, filename: str = None): """ diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index 0bdbfbaa..8d31def5 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -13,22 +13,20 @@ def get_assiduites_stats( ) -> Assiduite: if filtered is not None: + deb, fin = None, None for key in filtered: if key == "etat": assiduites = filter_assiduites_by_etat(assiduites, filtered[key]) elif key == "date_fin": - assiduites = filter_assiduites_by_date( - assiduites, filtered[key], sup=False - ) + fin = filtered[key] elif key == "date_debut": - assiduites = filter_assiduites_by_date( - assiduites, filtered[key], sup=True - ) + deb = filtered[key] elif key == "moduleimpl_id": assiduites = filter_by_module_impl(assiduites, filtered[key]) elif key == "formsemestre": assiduites = filter_by_formsemestre(assiduites, filtered[key]) - + if (deb, fin) != (None, None): + assiduites = filter_by_date(assiduites, Assiduite, deb, fin) count: dict = get_count(assiduites) metrics: list[str] = metric.split(",") @@ -41,8 +39,56 @@ def get_assiduites_stats( return output if output else count -def get_count(assiduites: Assiduite) -> dict[str, int or float]: +def big_counter( + interval: tuple[datetime], + pref_time: time = time(12, 0), +): + curr_date: datetime + if interval[0].time() >= pref_time: + curr_date = scu.localize_datetime( + datetime.combine(interval[0].date(), pref_time) + ) + else: + curr_date = scu.localize_datetime( + datetime.combine(interval[0].date(), time(0, 0)) + ) + + def next_(curr: datetime, journee): + if curr.time() != pref_time: + next_time = scu.localize_datetime(datetime.combine(curr.date(), pref_time)) + else: + next_time = scu.localize_datetime( + datetime.combine(curr.date() + timedelta(days=1), time(0, 0)) + ) + journee += 1 + return next_time, journee + + demi: int = 0 + j: int = 0 + while curr_date <= interval[1]: + next_time: datetime + next_time, j = next_(curr_date, j) + if scu.is_period_overlapping((curr_date, next_time), interval, True): + demi += 1 + curr_date = next_time + + delta: timedelta = interval[1] - interval[0] + heures: float = delta.total_seconds() / 3600 + + if delta.days >= 1: + heures -= delta.days * 16 + + return (demi, j, heures) + + +def get_count( + assiduites: Assiduite, noon: time = time(hour=12) +) -> dict[str, int or float]: + """Fonction permettant de compter les assiduites + -> seul "compte" est correcte lorsque les assiduites viennent de plusieurs étudiants + """ + # TODO: Comptage demi journée / journée d'assiduité longue output: dict[str, int or float] = {} compte: int = assiduites.count() heure: float = 0.0 @@ -55,13 +101,26 @@ def get_count(assiduites: Assiduite) -> dict[str, int or float]: current_time: str = None midnight: time = time(hour=0) - noon: time = time(hour=12) def time_check(dtime): return midnight <= dtime.time() <= noon for ass in all_assiduites: delta: timedelta = ass.date_fin - ass.date_debut + + if delta.days > 0: + + computed_values: tuple[int, int, float] = big_counter( + (ass.date_debut, ass.date_fin), noon + ) + + demi += computed_values[0] - 1 + journee += computed_values[1] - 1 + heure += computed_values[2] + + current_day = ass.date_fin.date() + continue + heure += delta.total_seconds() / 3600 ass_time: str = time_check(ass.date_debut) @@ -89,25 +148,30 @@ def filter_assiduites_by_etat(assiduites: Assiduite, etat: str) -> Assiduite: return assiduites.filter(Assiduite.etat.in_(etats)) -def filter_assiduites_by_date( - assiduites: Assiduite, date_: datetime, sup: bool = True -) -> Assiduite: +def filter_by_date( + collection: Assiduite or Justificatif, + collection_cls: Assiduite or Justificatif, + date_deb: datetime = None, + date_fin: datetime = None, + strict: bool = False, +): """ Filtrage d'une collection d'assiduites en fonction d'une date - - Sup == True -> les assiduites doivent débuter après 'date'\n - Sup == False -> les assiduites doivent finir avant 'date' """ + if date_deb is None: + date_deb = datetime.min + if date_fin is None: + date_fin = datetime.max - if date_.tzinfo is None: - first_assiduite: Assiduite = assiduites.first() - if first_assiduite is not None: - date_: datetime = date_.replace(tzinfo=first_assiduite.date_debut.tzinfo) - - if sup: - return assiduites.filter(Assiduite.date_debut >= date_) - - return assiduites.filter(Assiduite.date_fin <= date_) + date_deb = scu.localize_datetime(date_deb) + date_fin = scu.localize_datetime(date_fin) + if not strict: + return collection.filter( + collection_cls.date_debut <= date_fin, collection_cls.date_fin >= date_deb + ) + return collection.filter( + collection_cls.date_debut < date_fin, collection_cls.date_fin > date_deb + ) def filter_justificatifs_by_etat( @@ -190,11 +254,8 @@ def justifies(justi: Justificatif) -> list[int]: Justificatif, Assiduite.etudid == Justificatif.etudid ).filter(Assiduite.etat != scu.EtatAssiduite.PRESENT) - assiduites_query = filter_assiduites_by_date( - assiduites_query, justi.date_debut, True - ) - assiduites_query = filter_assiduites_by_date( - assiduites_query, justi.date_fin, False + assiduites_query = filter_by_date( + assiduites_query, Assiduite, justi.date_debut, justi.date_fin ) justified = [assi.id for assi in assiduites_query.all()] diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 7d7ec436..4d56ad3c 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -158,7 +158,7 @@ def localize_datetime(date: datetime.datetime or str) -> datetime.datetime: date = is_iso_formated(date, convert=True) new_date: datetime.datetime = date - if date.tzinfo is None: + if date is not None and date.tzinfo is None: from app.models.assiduites import Assiduite first_assiduite = Assiduite.query.first() @@ -174,27 +174,19 @@ def localize_datetime(date: datetime.datetime or str) -> datetime.datetime: def is_period_overlapping( periode: tuple[datetime.datetime, datetime.datetime], interval: tuple[datetime.datetime, datetime.datetime], + strict: bool = True, ) -> bool: """ Vérifie si la période et l'interval s'intersectent - + si strict == True : les extrémitées ne comptes pas Retourne Vrai si c'est le cas, faux sinon """ p_deb, p_fin = periode i_deb, i_fin = interval - # i = intervalmap() - # p = intervalmap() - # i[:] = 0 - # p[:] = 0 - # i[i_deb:i_fin] = 1 - # p[p_deb:p_fin] = 1 - - # # TOTALK: Vérification des bornes de la période dans l'interval et inversement - # res: int = sum((i[p_deb], i[p_fin], p[i_deb], p[i_fin])) - - # return res > 0 - return p_deb <= i_fin and p_fin >= i_deb + if not strict: + return p_deb <= i_fin and p_fin >= i_deb + return p_deb < i_fin and p_fin > i_deb # Types de modules diff --git a/tests/api/test_api_justificatifs.py b/tests/api/test_api_justificatifs.py index 8e5b16e3..48867c38 100644 --- a/tests/api/test_api_justificatifs.py +++ b/tests/api/test_api_justificatifs.py @@ -310,13 +310,13 @@ def test_import_justificatif(api_headers): filename: str = "tests/api/test_api_justificatif.txt" resp: dict = send_file(1, filename, api_headers) - assert "response" in resp - assert resp["response"] == "imported" + assert "filename" in resp + assert resp["filename"] == "test_api_justificatif.txt" filename: str = "tests/api/test_api_justificatif2.txt" resp: dict = send_file(1, filename, api_headers) - assert "response" in resp - assert resp["response"] == "imported" + assert "filename" in resp + assert resp["filename"] == "test_api_justificatif2.txt" # Mauvais fonctionnement diff --git a/tests/unit/test_assiduites.py b/tests/unit/test_assiduites.py index bdbf930e..3c84fa8a 100644 --- a/tests/unit/test_assiduites.py +++ b/tests/unit/test_assiduites.py @@ -258,54 +258,59 @@ def verifier_filtrage_justificatifs(etud: Identite, justificatifs: list[Justific scass.filter_justificatifs_by_etat(etud.justificatifs, "autre").count() == 0 ), "Filtrage de l'état 'autre' mauvais" - # Date début - date = scu.localize_datetime("2022-09-01T10:00+01:00") - assert ( - scass.filter_justificatifs_by_date(etud.justificatifs, date, sup=True).count() - == 5 - ), "Filtrage 'Date début' mauvais 1" - date = scu.localize_datetime("2022-09-03T08:00:00+01:00") - assert ( - scass.filter_justificatifs_by_date(etud.justificatifs, date, sup=True).count() - == 5 - ), "Filtrage 'Date début' mauvais 2" - date = scu.localize_datetime("2022-09-03T09:00:00+01:00") - assert ( - scass.filter_justificatifs_by_date(etud.justificatifs, date, sup=True).count() - == 4 - ), "Filtrage 'Date début' mauvais 3" - date = scu.localize_datetime("2022-09-03T09:00:02+01:00") - assert ( - scass.filter_justificatifs_by_date(etud.justificatifs, date, sup=True).count() - == 4 - ), "Filtrage 'Date début' mauvais 4" + # Dates + + assert ( + scass.filter_by_date(etud.justificatifs, Justificatif).count() == 5 + ), "Filtrage 'Toute Date' mauvais 1" - # Date fin date = scu.localize_datetime("2022-09-01T10:00+01:00") assert ( - scass.filter_justificatifs_by_date(etud.justificatifs, date, sup=False).count() - == 0 - ), "Filtrage 'Date fin' mauvais 1" - date = scu.localize_datetime("2022-09-03T10:00:00+01:00") + scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count() + == 5 + ), "Filtrage 'Toute Date' mauvais 2" + + date = scu.localize_datetime("2022-09-03T08:00+01:00") assert ( - scass.filter_justificatifs_by_date(etud.justificatifs, date, sup=False).count() + scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count() + == 5 + ), "Filtrage 'date début' mauvais 3" + + date = scu.localize_datetime("2022-09-03T08:00:01+01:00") + assert ( + scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count() + == 5 + ), "Filtrage 'date début' mauvais 4" + + date = scu.localize_datetime("2022-09-03T10:00+01:00") + assert ( + scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count() + == 4 + ), "Filtrage 'date début' mauvais 5" + + date = scu.localize_datetime("2022-09-01T10:00+01:00") + assert ( + scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count() + == 0 + ), "Filtrage 'Toute Date' mauvais 6" + + date = scu.localize_datetime("2022-09-03T08:00+01:00") + assert ( + scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count() == 1 - ), "Filtrage 'Date fin' mauvais 2" + ), "Filtrage 'date début' mauvais 7" + date = scu.localize_datetime("2022-09-03T10:00:01+01:00") assert ( - scass.filter_justificatifs_by_date(etud.justificatifs, date, sup=False).count() - == 1 - ), "Filtrage 'Date fin' mauvais 3" - date = scu.localize_datetime("2023-01-04T13:00:01+01:00") + scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count() + == 2 + ), "Filtrage 'date début' mauvais 8" + + date = scu.localize_datetime("2023-01-03T12:00+01:00") assert ( - scass.filter_justificatifs_by_date(etud.justificatifs, date, sup=False).count() + scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count() == 5 - ), "Filtrage 'Date fin' mauvais 4" - date = scu.localize_datetime("2023-01-03T11:00:01+01:00") - assert ( - scass.filter_justificatifs_by_date(etud.justificatifs, date, sup=False).count() - == 4 - ), "Filtrage 'Date fin' mauvais 5" + ), "Filtrage 'date début' mauvais 9" # Justifications des assiduites @@ -371,7 +376,7 @@ def editer_supprimer_assiduites(etuds: list[Identite], moduleimpls: list[int]): # Vérification du changement assert ( - scass.filter_assiduites_by_etat(etuds[0].assiduites, "retard").count() == 3 + scass.filter_assiduites_by_etat(etuds[0].assiduites, "retard").count() == 4 ), "Edition d'assiduité mauvais" assert ( scass.filter_by_module_impl(etuds[1].assiduites, moduleimpls[0].id).count() == 2 @@ -382,7 +387,7 @@ def editer_supprimer_assiduites(etuds: list[Identite], moduleimpls: list[int]): db.session.delete(ass3) db.session.commit() - assert etuds[2].assiduites.count() == 5, "Supression d'assiduité mauvais" + assert etuds[2].assiduites.count() == 6, "Supression d'assiduité mauvais" def ajouter_assiduites( @@ -441,6 +446,13 @@ def ajouter_assiduites( "moduleimpl": moduleimpls[3], "desc": "Description", }, + { + "etat": scu.EtatAssiduite.RETARD, + "deb": "2022-11-04T11:00:01+01:00", + "fin": "2022-12-04T12:00+01:00", + "moduleimpl": None, + "desc": "Description", + }, ] assiduites = [ @@ -514,10 +526,14 @@ def verifier_comptage_et_filtrage_assiduites( # Vérification du comptage classique comptage = scass.get_assiduites_stats(etu1.assiduites) - assert comptage["compte"] == 6, "la métrique 'Comptage' n'est pas bien calculée" - assert comptage["journee"] == 3, "la métrique 'Journée' n'est pas bien calculée" - assert comptage["demi"] == 4, "la métrique 'Demi-Journée' n'est pas bien calculée" - assert comptage["heure"] == 8, "la métrique 'Heure' n'est pas bien calculée" + assert comptage["compte"] == 6 + 1, "la métrique 'Comptage' n'est pas bien calculée" + assert ( + comptage["journee"] == 3 + 30 + ), "la métrique 'Journée' n'est pas bien calculée" + assert ( + comptage["demi"] == 4 + 60 + ), "la métrique 'Demi-Journée' n'est pas bien calculée" + assert comptage["heure"] == 8 + 241, "la métrique 'Heure' n'est pas bien calculée" # Vérification du filtrage classique @@ -526,19 +542,19 @@ def verifier_comptage_et_filtrage_assiduites( scass.filter_assiduites_by_etat(etu2.assiduites, "present").count() == 2 ), "Filtrage de l'état 'présent' mauvais" assert ( - scass.filter_assiduites_by_etat(etu2.assiduites, "retard").count() == 2 + scass.filter_assiduites_by_etat(etu2.assiduites, "retard").count() == 3 ), "Filtrage de l'état 'retard' mauvais" assert ( scass.filter_assiduites_by_etat(etu2.assiduites, "absent").count() == 2 ), "Filtrage de l'état 'absent' mauvais" assert ( - scass.filter_assiduites_by_etat(etu2.assiduites, "absent,retard").count() == 4 + scass.filter_assiduites_by_etat(etu2.assiduites, "absent,retard").count() == 5 ), "Filtrage de l'état 'absent,retard' mauvais" assert ( scass.filter_assiduites_by_etat( etu2.assiduites, "absent,retard,present" ).count() - == 6 + == 7 ), "Filtrage de l'état 'absent,retard,present' mauvais" assert ( scass.filter_assiduites_by_etat(etu2.assiduites, "autre").count() == 0 @@ -558,7 +574,7 @@ def verifier_comptage_et_filtrage_assiduites( scass.filter_by_module_impl(etu3.assiduites, mod22.id).count() == 2 ), "Filtrage par 'Moduleimpl' mauvais" assert ( - scass.filter_by_module_impl(etu3.assiduites, None).count() == 1 + scass.filter_by_module_impl(etu3.assiduites, None).count() == 2 ), "Filtrage par 'Moduleimpl' mauvais" assert ( scass.filter_by_module_impl(etu3.assiduites, 152).count() == 0 @@ -569,7 +585,7 @@ def verifier_comptage_et_filtrage_assiduites( FormSemestre.query.filter_by(id=fms["id"]).first() for fms in formsemestres ] assert ( - scass.filter_by_formsemestre(etu1.assiduites, formsemestres[0]).count() == 3 + scass.filter_by_formsemestre(etu1.assiduites, formsemestres[0]).count() == 4 ), "Filtrage 'Formsemestre' mauvais" assert ( scass.filter_by_formsemestre(etu1.assiduites, formsemestres[1]).count() == 3 @@ -579,41 +595,48 @@ def verifier_comptage_et_filtrage_assiduites( ), "Filtrage 'Formsemestre' mauvais" # Date début - date = scu.localize_datetime("2022-09-01T10:00+01:00") assert ( - scass.filter_assiduites_by_date(etu2.assiduites, date, sup=True).count() == 6 - ), "Filtrage 'Date début' mauvais" - date = scu.localize_datetime("2022-09-03T10:00:00+01:00") - assert ( - scass.filter_assiduites_by_date(etu2.assiduites, date, sup=True).count() == 5 - ), "Filtrage 'Date début' mauvais" - date = scu.localize_datetime("2022-09-03T10:00:01+01:00") - assert ( - scass.filter_assiduites_by_date(etu2.assiduites, date, sup=True).count() == 5 - ), "Filtrage 'Date début' mauvais" - date = scu.localize_datetime("2022-09-03T10:00:02+01:00") - assert ( - scass.filter_assiduites_by_date(etu2.assiduites, date, sup=True).count() == 4 - ), "Filtrage 'Date début' mauvais" + scass.filter_by_date(etu2.assiduites, Assiduite).count() == 7 + ), "Filtrage 'Date début' mauvais 1" - # Date fin date = scu.localize_datetime("2022-09-01T10:00+01:00") assert ( - scass.filter_assiduites_by_date(etu2.assiduites, date, sup=False).count() == 0 - ), "Filtrage 'Date fin' mauvais" - date = scu.localize_datetime("2022-09-03T10:00:00+01:00") + scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7 + ), "Filtrage 'Date début' mauvais 2" + + date = scu.localize_datetime("2022-09-03T10:00+01:00") assert ( - scass.filter_assiduites_by_date(etu2.assiduites, date, sup=False).count() == 1 - ), "Filtrage 'Date fin' mauvais" + scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7 + ), "Filtrage 'Date début' mauvais 3" + + date = scu.localize_datetime("2022-09-03T16:00+01:00") + assert ( + scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 4 + ), "Filtrage 'Date début' mauvais 4" + + # Date Fin + + date = scu.localize_datetime("2022-09-01T10:00+01:00") + assert ( + scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 0 + ), "Filtrage 'Date fin' mauvais 1" + + date = scu.localize_datetime("2022-09-03T10:00+01:00") + assert ( + scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 1 + ), "Filtrage 'Date fin' mauvais 2" + date = scu.localize_datetime("2022-09-03T10:00:01+01:00") assert ( - scass.filter_assiduites_by_date(etu2.assiduites, date, sup=False).count() == 1 - ), "Filtrage 'Date fin' mauvais" - date = scu.localize_datetime("2023-01-04T13:00:01+01:00") + scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 2 + ), "Filtrage 'Date fin' mauvais 3" + + date = scu.localize_datetime("2022-09-03T16:00+01:00") assert ( - scass.filter_assiduites_by_date(etu2.assiduites, date, sup=False).count() == 6 - ), "Filtrage 'Date fin' mauvais" - date = scu.localize_datetime("2023-01-03T11:00:01+01:00") + scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 3 + ), "Filtrage 'Date fin' mauvais 4" + + date = scu.localize_datetime("2023-01-04T16:00+01:00") assert ( - scass.filter_assiduites_by_date(etu2.assiduites, date, sup=False).count() == 4 - ), "Filtrage 'Date fin' mauvais" + scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 7 + ), "Filtrage 'Date fin' mauvais 5"