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:
69
app/ods_import.py
Normal file
69
app/ods_import.py
Normal file
@ -0,0 +1,69 @@
|
||||
"""Parse badge-reader ODS files and return structured data."""
|
||||
from datetime import datetime, time
|
||||
from odf.opendocument import load
|
||||
from odf.table import Table, TableRow, TableCell
|
||||
from odf.text import P
|
||||
|
||||
|
||||
def _cell_text(cell) -> str:
|
||||
parts = []
|
||||
for p in cell.getElementsByType(P):
|
||||
for node in p.childNodes:
|
||||
if hasattr(node, "data"):
|
||||
parts.append(node.data)
|
||||
return "".join(parts).strip()
|
||||
|
||||
|
||||
def _parse_time(val: str) -> str | None:
|
||||
"""Return HH:MM or None. Ignores zero-times (formula placeholders)."""
|
||||
val = val.strip()
|
||||
if not val:
|
||||
return None
|
||||
for fmt in ("%H:%M:%S", "%H:%M"):
|
||||
try:
|
||||
t = datetime.strptime(val, fmt)
|
||||
hhmm = t.strftime("%H:%M")
|
||||
if hhmm == "00:00":
|
||||
return None
|
||||
return hhmm
|
||||
except ValueError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _parse_date(val: str) -> str | None:
|
||||
"""Return YYYY-MM-DD or None."""
|
||||
val = val.strip()
|
||||
for fmt in ("%d/%m/%Y", "%Y-%m-%d"):
|
||||
try:
|
||||
return datetime.strptime(val, fmt).strftime("%Y-%m-%d")
|
||||
except ValueError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def parse_ods(path: str) -> list[dict]:
|
||||
"""Return list of pointage dicts from all sheets in the ODS file."""
|
||||
doc = load(path)
|
||||
sheets = doc.spreadsheet.getElementsByType(Table)
|
||||
records = []
|
||||
|
||||
for sheet in sheets:
|
||||
rows = sheet.getElementsByType(TableRow)
|
||||
for row in rows:
|
||||
cells = row.getElementsByType(TableCell)
|
||||
if len(cells) < 5:
|
||||
continue
|
||||
date_val = _cell_text(cells[0])
|
||||
date = _parse_date(date_val)
|
||||
if not date:
|
||||
continue
|
||||
records.append({
|
||||
"date": date,
|
||||
"matin_entree": _parse_time(_cell_text(cells[1])),
|
||||
"matin_sortie": _parse_time(_cell_text(cells[2])),
|
||||
"aprem_entree": _parse_time(_cell_text(cells[3])),
|
||||
"aprem_sortie": _parse_time(_cell_text(cells[4])),
|
||||
})
|
||||
|
||||
return records
|
||||
Reference in New Issue
Block a user