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