feat: Flask routes and Jinja2/HTMX/Tailwind templates
This commit is contained in:
@@ -1,3 +1,64 @@
|
||||
from flask import Blueprint
|
||||
from flask import Blueprint, render_template
|
||||
from datetime import date, timedelta
|
||||
import sqlalchemy as sa
|
||||
from app import db
|
||||
from app.models import WorkEntry
|
||||
from app.business.time_calc import minutes_to_str, work_minutes_reference
|
||||
from app.business.travel_calc import compute_km_for_entry, compute_co2_grams
|
||||
from app.business.leave_calc import compute_leave_used, get_or_create_balance
|
||||
from app.config_loader import get_vehicles, get_journeys
|
||||
|
||||
bp = Blueprint("dashboard", __name__)
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
def index():
|
||||
today = date.today()
|
||||
year = today.year
|
||||
|
||||
monday = today - timedelta(days=today.weekday())
|
||||
sunday = monday + timedelta(days=6)
|
||||
week_entries = db.session.scalars(
|
||||
sa.select(WorkEntry).where(WorkEntry.date.between(monday, sunday))
|
||||
).all()
|
||||
|
||||
week_actual = sum(e.total_minutes() for e in week_entries)
|
||||
week_ref = sum(work_minutes_reference(e.day_type) for e in week_entries)
|
||||
week_balance = week_actual - week_ref
|
||||
|
||||
month_start = today.replace(day=1)
|
||||
month_entries = db.session.scalars(
|
||||
sa.select(WorkEntry).where(WorkEntry.date.between(month_start, today))
|
||||
).all()
|
||||
|
||||
vehicles = get_vehicles()
|
||||
journeys = get_journeys()
|
||||
|
||||
month_km = {}
|
||||
month_co2 = 0.0
|
||||
for entry in month_entries:
|
||||
km = compute_km_for_entry(entry.journey_profile_id, journeys)
|
||||
for v, d in km.items():
|
||||
month_km[v] = month_km.get(v, 0) + d
|
||||
month_co2 += compute_co2_grams(km, vehicles)
|
||||
|
||||
balance = get_or_create_balance(year)
|
||||
used = compute_leave_used(year)
|
||||
|
||||
today_entry = db.session.scalar(
|
||||
sa.select(WorkEntry).where(WorkEntry.date == today)
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"dashboard.html",
|
||||
today=today,
|
||||
today_entry=today_entry,
|
||||
journeys=journeys,
|
||||
week_actual_str=minutes_to_str(week_actual),
|
||||
week_balance=week_balance,
|
||||
week_balance_str=minutes_to_str(abs(week_balance)),
|
||||
month_km=month_km,
|
||||
month_co2_kg=round(month_co2 / 1000, 2),
|
||||
balance=balance,
|
||||
used=used,
|
||||
)
|
||||
|
||||
@@ -1,3 +1,99 @@
|
||||
from flask import Blueprint
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
||||
from datetime import date, time
|
||||
import sqlalchemy as sa
|
||||
from app import db
|
||||
from app.models import WorkEntry, TimeSlot
|
||||
from app.config_loader import get_journeys, day_types_without_journey
|
||||
|
||||
bp = Blueprint("entries", __name__)
|
||||
bp = Blueprint("entries", __name__, url_prefix="/entries")
|
||||
|
||||
DAY_TYPES = [
|
||||
("WORK", "Travail"),
|
||||
("TT", "Télétravail"),
|
||||
("GARDE", "Garde"),
|
||||
("ASTREINTE", "Astreinte"),
|
||||
("FORMATION", "Formation"),
|
||||
("RTT", "RTT"),
|
||||
("CONGE", "Congé"),
|
||||
("MALADE", "Maladie"),
|
||||
("FERIE", "Férié"),
|
||||
]
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
def list_entries():
|
||||
entries = db.session.scalars(
|
||||
sa.select(WorkEntry).order_by(WorkEntry.date.desc())
|
||||
).all()
|
||||
return render_template("entry_list.html", entries=entries)
|
||||
|
||||
|
||||
@bp.route("/new", methods=["GET", "POST"])
|
||||
@bp.route("/<int:entry_id>/edit", methods=["GET", "POST"])
|
||||
def entry_form(entry_id=None):
|
||||
entry = None
|
||||
if entry_id:
|
||||
entry = db.session.get(WorkEntry, entry_id)
|
||||
if not entry:
|
||||
flash("Entrée introuvable.", "error")
|
||||
return redirect(url_for("entries.list_entries"))
|
||||
|
||||
if request.method == "POST":
|
||||
entry_date = date.fromisoformat(request.form["date"])
|
||||
day_type = request.form["day_type"]
|
||||
journey_profile_id = request.form.get("journey_profile_id") or None
|
||||
comment = request.form.get("comment") or None
|
||||
|
||||
if day_type in day_types_without_journey():
|
||||
journey_profile_id = None
|
||||
|
||||
if entry is None:
|
||||
existing = db.session.scalar(
|
||||
sa.select(WorkEntry).where(WorkEntry.date == entry_date)
|
||||
)
|
||||
if existing:
|
||||
flash(f"Une entrée existe déjà pour le {entry_date}.", "error")
|
||||
return redirect(url_for("entries.entry_form"))
|
||||
entry = WorkEntry(date=entry_date)
|
||||
db.session.add(entry)
|
||||
|
||||
entry.day_type = day_type
|
||||
entry.journey_profile_id = journey_profile_id
|
||||
entry.comment = comment
|
||||
|
||||
for slot in list(entry.time_slots):
|
||||
db.session.delete(slot)
|
||||
|
||||
starts = request.form.getlist("start_time")
|
||||
ends = request.form.getlist("end_time")
|
||||
for s, e in zip(starts, ends):
|
||||
if s and e:
|
||||
db.session.add(TimeSlot(
|
||||
entry=entry,
|
||||
start_time=time.fromisoformat(s),
|
||||
end_time=time.fromisoformat(e),
|
||||
))
|
||||
|
||||
db.session.commit()
|
||||
flash("Entrée enregistrée.", "success")
|
||||
return redirect(url_for("dashboard.index"))
|
||||
|
||||
journeys = get_journeys()
|
||||
return render_template(
|
||||
"entry_form.html",
|
||||
entry=entry,
|
||||
day_types=DAY_TYPES,
|
||||
journeys=journeys,
|
||||
day_types_without_journey=day_types_without_journey(),
|
||||
today=date.today().isoformat(),
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/<int:entry_id>/delete", methods=["POST"])
|
||||
def delete_entry(entry_id):
|
||||
entry = db.session.get(WorkEntry, entry_id)
|
||||
if entry:
|
||||
db.session.delete(entry)
|
||||
db.session.commit()
|
||||
flash("Entrée supprimée.", "success")
|
||||
return redirect(url_for("entries.list_entries"))
|
||||
|
||||
@@ -1,3 +1,48 @@
|
||||
from flask import Blueprint
|
||||
from flask import Blueprint, render_template, request
|
||||
from datetime import date
|
||||
import sqlalchemy as sa
|
||||
from app import db
|
||||
from app.models import WorkEntry
|
||||
from app.business.travel_calc import compute_km_for_entry, compute_co2_grams, compute_frais_reels
|
||||
from app.config_loader import get_vehicles, get_journeys, get_bareme
|
||||
|
||||
bp = Blueprint("reports", __name__)
|
||||
bp = Blueprint("reports", __name__, url_prefix="/reports")
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
def index():
|
||||
year = request.args.get("year", date.today().year, type=int)
|
||||
start = date(year, 1, 1)
|
||||
end = date(year, 12, 31)
|
||||
|
||||
entries = db.session.scalars(
|
||||
sa.select(WorkEntry).where(WorkEntry.date.between(start, end))
|
||||
).all()
|
||||
|
||||
vehicles = get_vehicles()
|
||||
journeys = get_journeys()
|
||||
|
||||
total_km = {}
|
||||
total_co2 = 0.0
|
||||
for entry in entries:
|
||||
km = compute_km_for_entry(entry.journey_profile_id, journeys)
|
||||
for v, d in km.items():
|
||||
total_km[v] = total_km.get(v, 0) + d
|
||||
total_co2 += compute_co2_grams(km, vehicles)
|
||||
|
||||
frais_reels = {}
|
||||
for vehicle_id, km in total_km.items():
|
||||
vehicle = vehicles.get(vehicle_id, {})
|
||||
cv = vehicle.get("cv")
|
||||
if cv:
|
||||
tranches = get_bareme(year, cv)
|
||||
frais_reels[vehicle_id] = round(compute_frais_reels(km, tranches), 2)
|
||||
|
||||
return render_template(
|
||||
"reports.html",
|
||||
year=year,
|
||||
total_km=total_km,
|
||||
total_co2_kg=round(total_co2 / 1000, 2),
|
||||
frais_reels=frais_reels,
|
||||
vehicles=vehicles,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user