Initial commit — décompte horaire
App web de suivi des heures de travail (FastAPI + HTMX + JSON plats). - Saisie inline par semaine avec bouton ✓ par ligne - Congés MA/AM/J avec coloration cellule et mise à jour live via OOB HTMX - Import fichier ODS badgeuse - Solde semaine et solde global recalculés à la volée - Design "ledger" écru, DM Mono, zéro border-radius - Déploiement Docker sur port 8000 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
80
app/calcul.py
Normal file
80
app/calcul.py
Normal file
@ -0,0 +1,80 @@
|
||||
"""Business logic: compute worked hours and expected hours per day/week."""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
def hhmm_to_minutes(val: str | None) -> int:
|
||||
if not val:
|
||||
return 0
|
||||
h, m = val.split(":")
|
||||
return int(h) * 60 + int(m)
|
||||
|
||||
|
||||
def minutes_to_hhmm(minutes: int) -> str:
|
||||
sign = "-" if minutes < 0 else ""
|
||||
minutes = abs(minutes)
|
||||
return f"{sign}{minutes // 60}h{minutes % 60:02d}"
|
||||
|
||||
|
||||
def heures_travaillees(p: dict) -> int:
|
||||
"""Return worked minutes for a pointage row."""
|
||||
matin = max(0, hhmm_to_minutes(p["matin_sortie"]) - hhmm_to_minutes(p["matin_entree"]))
|
||||
aprem = max(0, hhmm_to_minutes(p["aprem_sortie"]) - hhmm_to_minutes(p["aprem_entree"]))
|
||||
# only count if entry AND exit are present
|
||||
if not (p["matin_entree"] and p["matin_sortie"]):
|
||||
matin = 0
|
||||
if not (p["aprem_entree"] and p["aprem_sortie"]):
|
||||
aprem = 0
|
||||
return matin + aprem
|
||||
|
||||
|
||||
def heures_dues(date_str: str, conges: list[dict], heures_jour_min: int) -> int:
|
||||
"""Return expected minutes for a date, accounting for vacations."""
|
||||
d = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||
# Weekend = no hours due
|
||||
if d.weekday() >= 5:
|
||||
return 0
|
||||
|
||||
conges_date = {c["type"] for c in conges if c["date"] == date_str}
|
||||
|
||||
if "jour" in conges_date:
|
||||
return 0
|
||||
if "matin" in conges_date and "aprem" in conges_date:
|
||||
return 0
|
||||
|
||||
half = heures_jour_min // 2
|
||||
if "matin" in conges_date or "aprem" in conges_date:
|
||||
return half
|
||||
|
||||
return heures_jour_min
|
||||
|
||||
|
||||
def compute_week(pointages: list[dict], conges: list[dict], heures_jour_min: int) -> dict:
|
||||
"""Compute totals for a list of pointages (one week)."""
|
||||
total_travaille = 0
|
||||
total_du = 0
|
||||
jours = []
|
||||
|
||||
for p in pointages:
|
||||
travaille = heures_travaillees(p)
|
||||
du = heures_dues(p["date"], conges, heures_jour_min)
|
||||
delta = travaille - du
|
||||
total_travaille += travaille
|
||||
total_du += du
|
||||
jours.append({
|
||||
**p,
|
||||
"travaille_min": travaille,
|
||||
"du_min": du,
|
||||
"delta_min": delta,
|
||||
"travaille": minutes_to_hhmm(travaille),
|
||||
"du": minutes_to_hhmm(du),
|
||||
"delta": minutes_to_hhmm(delta),
|
||||
"conge": {c["type"] for c in conges if c["date"] == p["date"]},
|
||||
})
|
||||
|
||||
return {
|
||||
"jours": jours,
|
||||
"total_travaille": minutes_to_hhmm(total_travaille),
|
||||
"total_du": minutes_to_hhmm(total_du),
|
||||
"solde": minutes_to_hhmm(total_travaille - total_du),
|
||||
"solde_min": total_travaille - total_du,
|
||||
}
|
||||
Reference in New Issue
Block a user