Rend les plages horaires et les rappels ntfy configurables, supprime le
classement, ajoute une suite de tests pytest - calcul.py: matin_debut/fin, aprem_debut/fin et pause_dejeuner_fin sont paramétrables via config.json > plages (moteur de calcul de l'heure de sortie optimale inclus, pas seulement l'affichage). Défauts identiques à l'ancien comportement, validé par la suite de tests. - stats.py: les seuils de conformité utilisent la même config. - main.py/templates: warn de saisie et suffixes de cible dynamiques. - Rappels ntfy: serveur, topic et plage horaire (rappel_debut_h/fin_h) configurables par utilisateur depuis /settings, plus en dur dans le code. - Page classement supprimée (pas de compétition entre utilisateurs). - Nouvelle suite pytest (app/tests/), à lancer via `docker compose run --rm pointeuse pytest -v`.
This commit is contained in:
67
app/main.py
67
app/main.py
@ -19,9 +19,8 @@ from models import (
|
||||
load_notif_config, save_notif_config, find_user_by_token, save_presence,
|
||||
load_auth, save_auth, find_user_by_reset_token,
|
||||
)
|
||||
from calcul import compute_week, minutes_to_hhmm, hhmm_to_minutes, heures_dues
|
||||
from calcul import compute_week, minutes_to_hhmm, hhmm_to_minutes, heures_dues, DEFAULT_PLAGES
|
||||
from stats import compute_all_stats
|
||||
from classement import compute_classement
|
||||
from notifications import reminder_loop
|
||||
|
||||
logging.basicConfig(
|
||||
@ -97,6 +96,10 @@ def heures_jour_min() -> int:
|
||||
return int(float(load_config().get("heures_jour", 7.8)) * 60)
|
||||
|
||||
|
||||
def plages_config() -> dict:
|
||||
return {**DEFAULT_PLAGES, **load_config().get("plages", {})}
|
||||
|
||||
|
||||
def _weeks_by_year(all_weeks: list) -> dict:
|
||||
result = {}
|
||||
for yr, wks in groupby(sorted(all_weeks, reverse=True), key=lambda x: x[0]):
|
||||
@ -113,7 +116,7 @@ def _build_soldes_ctx(year, week, conges, h_jour, user_id):
|
||||
"aprem_entree": None, "aprem_sortie": None})
|
||||
for d in dates
|
||||
]
|
||||
return {"result": compute_week(pointages, conges, h_jour)}
|
||||
return {"result": compute_week(pointages, conges, h_jour, plages_config())}
|
||||
|
||||
|
||||
def _build_jour_ctx(date, conges, h_jour, user_id):
|
||||
@ -151,7 +154,7 @@ def _oob_calc(date, jour):
|
||||
)
|
||||
|
||||
|
||||
def _oob_week_extras(result):
|
||||
def _oob_week_extras(result, plages):
|
||||
html = ""
|
||||
for jour in result["jours"]:
|
||||
date = jour["date"]
|
||||
@ -169,7 +172,7 @@ def _oob_week_extras(result):
|
||||
cible = None
|
||||
if not jour.get("is_complete"):
|
||||
if jour.get("entree_cible"):
|
||||
suffix = "→12:00" if "aprem" in jour.get("conge", set()) else "→17:00"
|
||||
suffix = f'→{plages["matin_fin"]}' if "aprem" in jour.get("conge", set()) else f'→{plages["aprem_fin"]}'
|
||||
cible = f'{jour["entree_cible"]}{suffix}'
|
||||
elif jour.get("sortie_cible"):
|
||||
cible = jour.get("sortie_cible")
|
||||
@ -178,17 +181,17 @@ def _oob_week_extras(result):
|
||||
return html
|
||||
|
||||
|
||||
def _oob_time_cells(date, jour):
|
||||
def _oob_time_cells(date, jour, plages):
|
||||
cg = jour["conge"]
|
||||
matin_cg = "matin" in cg or "jour" in cg
|
||||
aprem_cg = "aprem" in cg or "jour" in cg
|
||||
matin_warn = not matin_cg and jour["du_min"] > 0 and (
|
||||
(jour.get("matin_entree") and jour["matin_entree"] > "09:00") or
|
||||
(jour.get("matin_sortie") and jour["matin_sortie"] < "12:00")
|
||||
(jour.get("matin_entree") and jour["matin_entree"] > plages["matin_debut"]) or
|
||||
(jour.get("matin_sortie") and jour["matin_sortie"] < plages["matin_fin"])
|
||||
)
|
||||
aprem_warn = not aprem_cg and jour["du_min"] > 0 and (
|
||||
(jour.get("aprem_entree") and jour["aprem_entree"] > "14:00") or
|
||||
(jour.get("aprem_sortie") and jour["aprem_sortie"] < "17:00")
|
||||
(jour.get("aprem_entree") and jour["aprem_entree"] > plages["aprem_debut"]) or
|
||||
(jour.get("aprem_sortie") and jour["aprem_sortie"] < plages["aprem_fin"])
|
||||
)
|
||||
matin_cls = "td-fill cg" if matin_cg else ("td-fill warn" if matin_warn else "td-fill")
|
||||
aprem_cls = "td-fill cg" if aprem_cg else ("td-fill warn" if aprem_warn else "td-fill")
|
||||
@ -213,6 +216,7 @@ def _oob_time_cells(date, jour):
|
||||
|
||||
def _htmx_conge_and_soldes(request, date, d, conges, h_jour, user_id):
|
||||
iso = d.isocalendar()
|
||||
plages = plages_config()
|
||||
jour = _build_jour_ctx(date, conges, h_jour, user_id)
|
||||
cg = jour["conge"]
|
||||
btn_ma_on = " on" if "matin" in cg else ""
|
||||
@ -229,23 +233,24 @@ def _htmx_conge_and_soldes(request, date, d, conges, h_jour, user_id):
|
||||
soldes_ctx = _build_soldes_ctx(iso.year, iso.week, conges, h_jour, user_id)
|
||||
soldes_html = templates.get_template("_soldes.html").render({"request": request, **soldes_ctx})
|
||||
return HTMLResponse(
|
||||
_oob_time_cells(date, jour)
|
||||
_oob_time_cells(date, jour, plages)
|
||||
+ btns_html
|
||||
+ _oob_calc(date, jour)
|
||||
+ _oob_week_extras(soldes_ctx["result"])
|
||||
+ _oob_week_extras(soldes_ctx["result"], plages)
|
||||
+ soldes_html
|
||||
)
|
||||
|
||||
|
||||
def _htmx_calc_and_soldes(request, date, d, conges, h_jour, user_id):
|
||||
iso = d.isocalendar()
|
||||
plages = plages_config()
|
||||
jour = _build_jour_ctx(date, conges, h_jour, user_id)
|
||||
soldes_ctx = _build_soldes_ctx(iso.year, iso.week, conges, h_jour, user_id)
|
||||
soldes_html = templates.get_template("_soldes.html").render({"request": request, **soldes_ctx})
|
||||
return HTMLResponse(
|
||||
_oob_calc(date, jour)
|
||||
+ _oob_time_cells(date, jour)
|
||||
+ _oob_week_extras(soldes_ctx["result"])
|
||||
+ _oob_time_cells(date, jour, plages)
|
||||
+ _oob_week_extras(soldes_ctx["result"], plages)
|
||||
+ soldes_html
|
||||
)
|
||||
|
||||
@ -403,6 +408,7 @@ def semaine(request: Request, year: int, week: int):
|
||||
return RedirectResponse("/login", status_code=302)
|
||||
|
||||
h_jour = heures_jour_min()
|
||||
plages = plages_config()
|
||||
conges = load_conges(user_id)
|
||||
soldes_ctx = _build_soldes_ctx(year, week, conges, h_jour, user_id)
|
||||
|
||||
@ -414,7 +420,7 @@ def semaine(request: Request, year: int, week: int):
|
||||
"aprem_entree": None, "aprem_sortie": None})
|
||||
for d in dates
|
||||
]
|
||||
result = compute_week(pointages, conges, h_jour)
|
||||
result = compute_week(pointages, conges, h_jour, plages)
|
||||
|
||||
prev_d = datetime.strptime(f"{year}-W{week:02d}-1", "%G-W%V-%u").date() - timedelta(weeks=1)
|
||||
next_d = datetime.strptime(f"{year}-W{week:02d}-1", "%G-W%V-%u").date() + timedelta(weeks=1)
|
||||
@ -425,6 +431,7 @@ def semaine(request: Request, year: int, week: int):
|
||||
"year": year, "week": week,
|
||||
"result": result,
|
||||
**soldes_ctx,
|
||||
"plages": plages,
|
||||
"prev_year": prev_d.isocalendar().year, "prev_week": prev_d.isocalendar().week,
|
||||
"next_year": next_d.isocalendar().year, "next_week": next_d.isocalendar().week,
|
||||
"jours_fr": JOURS_FR,
|
||||
@ -527,6 +534,9 @@ def settings_page(request: Request):
|
||||
"request": request,
|
||||
"current_user": user_id,
|
||||
"ntfy_topic": notif.get("ntfy_topic") or "",
|
||||
"ntfy_server": notif.get("ntfy_server") or "https://ntfy.sh",
|
||||
"rappel_debut_h": notif.get("rappel_debut_h", 7),
|
||||
"rappel_fin_h": notif.get("rappel_fin_h", 20),
|
||||
"arrivee_url": f"{base_url}/presence/{notif['token']}/arrivee",
|
||||
"depart_url": f"{base_url}/presence/{notif['token']}/depart",
|
||||
"saved": request.query_params.get("saved") == "1",
|
||||
@ -534,13 +544,22 @@ def settings_page(request: Request):
|
||||
|
||||
|
||||
@app.post("/settings", response_class=HTMLResponse)
|
||||
def settings_save(request: Request, ntfy_topic: str = Form("")):
|
||||
def settings_save(
|
||||
request: Request, ntfy_topic: str = Form(""), ntfy_server: str = Form(""),
|
||||
rappel_debut_h: int = Form(7), rappel_fin_h: int = Form(20),
|
||||
):
|
||||
user_id = _get_user(request)
|
||||
if not user_id:
|
||||
return RedirectResponse("/login", status_code=302)
|
||||
|
||||
logger.info("réglages notif mis à jour: user_id=%r ntfy_topic=%r", user_id, ntfy_topic)
|
||||
save_notif_config(user_id, ntfy_topic)
|
||||
rappel_debut_h = max(0, min(23, rappel_debut_h))
|
||||
rappel_fin_h = max(rappel_debut_h + 1, min(24, rappel_fin_h))
|
||||
|
||||
logger.info(
|
||||
"réglages notif mis à jour: user_id=%r ntfy_topic=%r ntfy_server=%r plage=%sh-%sh",
|
||||
user_id, ntfy_topic, ntfy_server, rappel_debut_h, rappel_fin_h,
|
||||
)
|
||||
save_notif_config(user_id, ntfy_topic, ntfy_server, rappel_debut_h, rappel_fin_h)
|
||||
return RedirectResponse("/settings?saved=1", status_code=303)
|
||||
|
||||
|
||||
@ -566,15 +585,3 @@ def presence_depart(token: str):
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.get("/classement", response_class=HTMLResponse)
|
||||
def classement_page(request: Request):
|
||||
user_id = _get_user(request)
|
||||
if not user_id:
|
||||
return RedirectResponse("/login", status_code=302)
|
||||
|
||||
ranking = compute_classement()
|
||||
return templates.TemplateResponse("classement.html", {
|
||||
"request": request,
|
||||
"ranking": ranking,
|
||||
"current_user": user_id,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user