export
This commit is contained in:
178
app/export.py
Normal file
178
app/export.py
Normal file
@ -0,0 +1,178 @@
|
||||
"""Export des pointages au format ODS (tableur), par semaine ou par année."""
|
||||
import io
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odf.opendocument import OpenDocumentSpreadsheet
|
||||
from odf.style import Style, TableColumnProperties, TextProperties
|
||||
from odf.table import Table, TableColumn, TableRow, TableCell
|
||||
from odf.text import P
|
||||
|
||||
from calcul import compute_week, minutes_to_hhmm
|
||||
from models import load_week_pointages, load_conges, list_all_weeks, load_plages, heures_jour_min
|
||||
|
||||
JOURS_FR = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi"]
|
||||
HEADERS = [
|
||||
"Date", "Jour", "Matin entrée", "Matin sortie", "Aprem entrée", "Aprem sortie",
|
||||
"Travaillé", "Dû", "Δ jour", "Congé",
|
||||
]
|
||||
|
||||
|
||||
def _week_dates(year: int, week: int) -> list[str]:
|
||||
first = datetime.strptime(f"{year}-W{week:02d}-1", "%G-W%V-%u").date()
|
||||
return [(first + timedelta(days=i)).strftime("%Y-%m-%d") for i in range(5)]
|
||||
|
||||
|
||||
def _new_doc():
|
||||
doc = OpenDocumentSpreadsheet()
|
||||
bold = Style(name="Bold", family="table-cell")
|
||||
bold.addElement(TextProperties(fontweight="bold"))
|
||||
doc.styles.addElement(bold)
|
||||
wide_col = Style(name="WideCol", family="table-column")
|
||||
wide_col.addElement(TableColumnProperties(columnwidth="2.4cm"))
|
||||
doc.styles.addElement(wide_col)
|
||||
return doc, bold, wide_col
|
||||
|
||||
|
||||
def _cell(text, style=None) -> TableCell:
|
||||
c = TableCell(stylename=style) if style else TableCell()
|
||||
c.addElement(P(text="" if text is None else str(text)))
|
||||
return c
|
||||
|
||||
|
||||
def _header_row(bold: Style) -> TableRow:
|
||||
row = TableRow()
|
||||
for h in HEADERS:
|
||||
row.addElement(_cell(h, bold))
|
||||
return row
|
||||
|
||||
|
||||
def _congé_label(cg: set) -> str:
|
||||
if "jour" in cg:
|
||||
return "Journée"
|
||||
parts = []
|
||||
if "matin" in cg:
|
||||
parts.append("Matin")
|
||||
if "aprem" in cg:
|
||||
parts.append("Après-midi")
|
||||
return " + ".join(parts)
|
||||
|
||||
|
||||
def _jour_row(jour: dict, nom: str) -> TableRow:
|
||||
row = TableRow()
|
||||
for v in (
|
||||
jour["date"], nom,
|
||||
jour.get("matin_entree"), jour.get("matin_sortie"),
|
||||
jour.get("aprem_entree"), jour.get("aprem_sortie"),
|
||||
jour["travaille"], jour["du"],
|
||||
("+" if jour["delta_min"] > 0 else "") + jour["delta"],
|
||||
_congé_label(jour["conge"]),
|
||||
):
|
||||
row.addElement(_cell(v))
|
||||
return row
|
||||
|
||||
|
||||
def _totals_row(bold: Style, travaille_min: int, du_min: int) -> TableRow:
|
||||
row = TableRow()
|
||||
for v in ("Total", "", "", "", "", "",
|
||||
minutes_to_hhmm(travaille_min), minutes_to_hhmm(du_min),
|
||||
("+" if travaille_min - du_min > 0 else "") + minutes_to_hhmm(travaille_min - du_min),
|
||||
""):
|
||||
row.addElement(_cell(v, bold))
|
||||
return row
|
||||
|
||||
|
||||
def _size_columns(table: Table, wide_col: Style, n: int = len(HEADERS)):
|
||||
for _ in range(n):
|
||||
table.addElement(TableColumn(stylename=wide_col))
|
||||
|
||||
|
||||
def export_semaine_ods(year: int, week: int, user_id: str) -> bytes:
|
||||
"""Build an .ods workbook with the daily detail for a single week."""
|
||||
doc, bold, wide_col = _new_doc()
|
||||
h_jour = heures_jour_min()
|
||||
plages = load_plages(user_id)
|
||||
conges = load_conges(user_id)
|
||||
|
||||
dates = _week_dates(year, week)
|
||||
raw = load_week_pointages(year, week, user_id)
|
||||
p_by_date = {p["date"]: p for p in raw}
|
||||
pointages = [
|
||||
p_by_date.get(d, {"date": d, "matin_entree": None, "matin_sortie": None,
|
||||
"aprem_entree": None, "aprem_sortie": None})
|
||||
for d in dates
|
||||
]
|
||||
result = compute_week(pointages, conges, h_jour, plages)
|
||||
|
||||
table = Table(name=f"Semaine {week:02d}-{year}")
|
||||
_size_columns(table, wide_col)
|
||||
table.addElement(_header_row(bold))
|
||||
for jour, nom in zip(result["jours"], JOURS_FR):
|
||||
table.addElement(_jour_row(jour, nom))
|
||||
table.addElement(_totals_row(bold, sum(j["travaille_min"] for j in result["jours"]),
|
||||
sum(j["du_min"] for j in result["jours"])))
|
||||
doc.spreadsheet.addElement(table)
|
||||
|
||||
buf = io.BytesIO()
|
||||
doc.save(buf)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def export_annee_ods(year: int, user_id: str) -> bytes:
|
||||
"""Build an .ods workbook with one sheet per week of the year, plus a yearly total."""
|
||||
doc, bold, wide_col = _new_doc()
|
||||
h_jour = heures_jour_min()
|
||||
plages = load_plages(user_id)
|
||||
conges = load_conges(user_id)
|
||||
|
||||
weeks = sorted(wk for yr, wk in list_all_weeks(user_id) if yr == year)
|
||||
|
||||
par_semaine = [] # (week, result, travaille_min, du_min)
|
||||
for week in weeks:
|
||||
dates = _week_dates(year, week)
|
||||
raw = load_week_pointages(year, week, user_id)
|
||||
p_by_date = {p["date"]: p for p in raw}
|
||||
pointages = [
|
||||
p_by_date.get(d, {"date": d, "matin_entree": None, "matin_sortie": None,
|
||||
"aprem_entree": None, "aprem_sortie": None})
|
||||
for d in dates
|
||||
]
|
||||
result = compute_week(pointages, conges, h_jour, plages)
|
||||
travaille_min = sum(j["travaille_min"] for j in result["jours"])
|
||||
du_min = sum(j["du_min"] for j in result["jours"])
|
||||
par_semaine.append((week, result, travaille_min, du_min))
|
||||
|
||||
year_travaille = sum(t for _, _, t, _ in par_semaine)
|
||||
year_du = sum(d for _, _, _, d in par_semaine)
|
||||
|
||||
recap = Table(name="Récapitulatif")
|
||||
_size_columns(recap, wide_col, n=4)
|
||||
header = TableRow()
|
||||
for h in ("Semaine", "Travaillé", "Dû", "Δ"):
|
||||
header.addElement(_cell(h, bold))
|
||||
recap.addElement(header)
|
||||
for week, _, travaille_min, du_min in par_semaine:
|
||||
row = TableRow()
|
||||
for v in (f"S{week:02d}", minutes_to_hhmm(travaille_min), minutes_to_hhmm(du_min),
|
||||
("+" if travaille_min - du_min > 0 else "") + minutes_to_hhmm(travaille_min - du_min)):
|
||||
row.addElement(_cell(v))
|
||||
recap.addElement(row)
|
||||
total_row = TableRow()
|
||||
for v in ("Total", minutes_to_hhmm(year_travaille), minutes_to_hhmm(year_du),
|
||||
("+" if year_travaille - year_du > 0 else "") + minutes_to_hhmm(year_travaille - year_du)):
|
||||
total_row.addElement(_cell(v, bold))
|
||||
recap.addElement(total_row)
|
||||
# Le récapitulatif en première feuille pour une vue d'ensemble immédiate à l'ouverture.
|
||||
doc.spreadsheet.addElement(recap)
|
||||
|
||||
for week, result, travaille_min, du_min in par_semaine:
|
||||
table = Table(name=f"S{week:02d}")
|
||||
_size_columns(table, wide_col)
|
||||
table.addElement(_header_row(bold))
|
||||
for jour, nom in zip(result["jours"], JOURS_FR):
|
||||
table.addElement(_jour_row(jour, nom))
|
||||
table.addElement(_totals_row(bold, travaille_min, du_min))
|
||||
doc.spreadsheet.addElement(table)
|
||||
|
||||
buf = io.BytesIO()
|
||||
doc.save(buf)
|
||||
return buf.getvalue()
|
||||
Reference in New Issue
Block a user