256 lines
No EOL
8.1 KiB
Python
256 lines
No EOL
8.1 KiB
Python
"""Service d'import des vacances scolaires depuis l'API gouvernementale."""
|
|
import requests
|
|
from datetime import datetime
|
|
from app_new.extensions import db
|
|
from app_new.core.models import CollegeClosure
|
|
|
|
|
|
# API du gouvernement pour les vacances scolaires
|
|
API_URL = "https://data.education.gouv.fr/api/v2/catalog/datasets/fr-en-calendrier-scolaire-scolaires/exports/json"
|
|
|
|
|
|
def fetch_vacances_scolaires(zone="A", annee=None):
|
|
"""
|
|
Récupère les vacances scolaires depuis l'API gouvernementale.
|
|
|
|
Args:
|
|
zone: Zone académique (A, B ou C)
|
|
annee: Année scolaire (ex: 2024 pour 2024-2025). Si None, année actuelle.
|
|
|
|
Returns:
|
|
Liste de dictionnaires avec les vacances
|
|
"""
|
|
if annee is None:
|
|
annee = datetime.now().year
|
|
|
|
# L'API du gouvernement filtre par zone et année
|
|
params = {
|
|
"where": f"zones='{zone}'",
|
|
"order_by": "start_date",
|
|
"limit": 50
|
|
}
|
|
|
|
try:
|
|
response = requests.get(API_URL, params=params, timeout=30)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
vacances = []
|
|
for item in data:
|
|
start = item.get("start_date")
|
|
end = item.get("end_date")
|
|
description = item.get("description", "Vacances scolaires")
|
|
|
|
if start and end:
|
|
# Filtrer pour l'année scolaire demandée
|
|
start_date = datetime.fromisoformat(start.replace("Z", "+00:00"))
|
|
if start_date.year >= annee - 1:
|
|
vacances.append({
|
|
"start_date": start_date.date(),
|
|
"end_date": datetime.fromisoformat(end.replace("Z", "+00:00")).date(),
|
|
"description": description,
|
|
"zone": zone
|
|
})
|
|
|
|
return vacances
|
|
except Exception as e:
|
|
print(f"Erreur lors de la récupération des vacances: {e}")
|
|
return []
|
|
|
|
|
|
def import_vacances_scolaires(zone="A", annee=None, user_id=None):
|
|
"""
|
|
Importe les vacances scolaires dans la base de données.
|
|
|
|
Args:
|
|
zone: Zone académique (A, B ou C)
|
|
annee: Année scolaire
|
|
user_id: ID de l'utilisateur qui importe
|
|
|
|
Returns:
|
|
(nombre_ajoutees, nombre_ignorees, erreurs)
|
|
"""
|
|
vacances = fetch_vacances_scolaires(zone, annee)
|
|
|
|
if not vacances:
|
|
# Données de fallback si l'API n'est pas accessible
|
|
vacances = get_vacances_fallback(zone, annee)
|
|
|
|
ajoutees = 0
|
|
ignorees = 0
|
|
erreurs = []
|
|
|
|
for vac in vacances:
|
|
# Vérifier si cette fermeture existe déjà
|
|
existing = CollegeClosure.query.filter(
|
|
CollegeClosure.type == "vacances_scolaires",
|
|
CollegeClosure.start_date == vac["start_date"],
|
|
CollegeClosure.end_date == vac["end_date"]
|
|
).first()
|
|
|
|
if existing:
|
|
ignorees += 1
|
|
continue
|
|
|
|
try:
|
|
closure = CollegeClosure(
|
|
type="vacances_scolaires",
|
|
label=vac["description"],
|
|
start_date=vac["start_date"],
|
|
end_date=vac["end_date"],
|
|
zone=zone,
|
|
is_exception=False,
|
|
created_by_id=user_id
|
|
)
|
|
db.session.add(closure)
|
|
ajoutees += 1
|
|
except Exception as e:
|
|
erreurs.append(f"{vac['description']}: {str(e)}")
|
|
|
|
if ajoutees > 0:
|
|
db.session.commit()
|
|
|
|
return ajoutees, ignorees, erreurs
|
|
|
|
|
|
def get_vacances_fallback(zone, annee):
|
|
"""
|
|
Retourne les vacances scolaires par défaut si l'API n'est pas accessible.
|
|
Basé sur le calendrier officiel français.
|
|
"""
|
|
from datetime import timedelta
|
|
|
|
if annee is None:
|
|
annee = datetime.now().year
|
|
|
|
# Calculer l'année scolaire (2024-2025 pour annee=2024)
|
|
annee_scolaire = f"{annee}-{annee + 1}"
|
|
|
|
# Vacances standardisées (approximatives, à ajuster selon la zone)
|
|
vacances = [
|
|
# Toussaint
|
|
{
|
|
"start_date": datetime(annee, 10, 19).date(),
|
|
"end_date": datetime(annee, 11, 3).date(),
|
|
"description": f"Vacances de la Toussaint {annee_scolaire}",
|
|
"zone": zone
|
|
},
|
|
# Noël
|
|
{
|
|
"start_date": datetime(annee, 12, 21).date(),
|
|
"end_date": datetime(annee + 1, 1, 6).date(),
|
|
"description": f"Vacances de Noël {annee_scolaire}",
|
|
"zone": zone
|
|
},
|
|
# Hiver (diffère selon la zone)
|
|
{
|
|
"start_date": datetime(annee + 1, 2, 15).date() if zone != "C" else datetime(annee + 1, 2, 22).date(),
|
|
"end_date": datetime(annee + 1, 3, 2).date() if zone != "C" else datetime(annee + 1, 3, 9).date(),
|
|
"description": f"Vacances d'hiver {annee_scolaire}",
|
|
"zone": zone
|
|
},
|
|
# Printemps (diffère selon la zone) - CORRIGÉ: annee + 1 au lieu de annee + 2
|
|
{
|
|
"start_date": datetime(annee + 1, 4, 12).date() if zone == "A" else (datetime(annee + 1, 4, 19).date() if zone == "B" else datetime(annee + 1, 4, 26).date()),
|
|
"end_date": datetime(annee + 1, 4, 27).date() if zone == "A" else (datetime(annee + 1, 5, 4).date() if zone == "B" else datetime(annee + 1, 5, 11).date()),
|
|
"description": f"Vacances de printemps {annee_scolaire}",
|
|
"zone": zone
|
|
},
|
|
# Grandes vacances
|
|
{
|
|
"start_date": datetime(annee + 1, 7, 5).date(),
|
|
"end_date": datetime(annee + 1, 8, 31).date(),
|
|
"description": f"Grandes vacances {annee_scolaire}",
|
|
"zone": zone
|
|
},
|
|
]
|
|
|
|
return vacances
|
|
|
|
|
|
def get_jours_feries(annee=None):
|
|
"""
|
|
Retourne les jours fériés français pour une année donnée.
|
|
Calculés dynamiquement (Pâques est calculé).
|
|
"""
|
|
from datetime import timedelta
|
|
|
|
if annee is None:
|
|
annee = datetime.now().year
|
|
|
|
# Calcul de Pâques (algorithme de Butcher-Meeus)
|
|
a = annee % 19
|
|
b = annee // 100
|
|
c = annee % 100
|
|
d = b // 4
|
|
e = b % 4
|
|
f = (b + 8) // 25
|
|
g = (b - f + 1) // 3
|
|
h = (19 * a + b - d - g + 15) % 30
|
|
i = c // 4
|
|
k = c % 4
|
|
l = (32 + 2 * e + 2 * i - h - k) % 7
|
|
m = (a + 11 * h + 22 * l) // 451
|
|
mois = (h + l - 7 * m + 114) // 31
|
|
jour = ((h + l - 7 * m + 114) % 31) + 1
|
|
|
|
paques = datetime(annee, mois, jour)
|
|
|
|
# Utiliser timedelta au lieu de replace pour éviter les jours invalides
|
|
jours_feries = [
|
|
(datetime(annee, 1, 1), "Jour de l'an"),
|
|
(paques, "Pâques"),
|
|
(paques + timedelta(days=1), "Lundi de Pâques"),
|
|
(datetime(annee, 5, 1), "Fête du travail"),
|
|
(datetime(annee, 5, 8), "Victoire 1945"),
|
|
(paques + timedelta(days=39), "Ascension"),
|
|
(paques + timedelta(days=49), "Pentecôte"),
|
|
(paques + timedelta(days=50), "Lundi de Pentecôte"),
|
|
(datetime(annee, 7, 14), "Fête nationale"),
|
|
(datetime(annee, 8, 15), "Assomption"),
|
|
(datetime(annee, 11, 1), "Toussaint"),
|
|
(datetime(annee, 11, 11), "Armistice 1918"),
|
|
(datetime(annee, 12, 25), "Noël"),
|
|
]
|
|
|
|
return [(d.date(), n) for d, n in jours_feries]
|
|
|
|
|
|
def import_jours_feries(annee=None, user_id=None):
|
|
"""
|
|
Importe les jours fériés dans la base de données.
|
|
|
|
Returns:
|
|
(nombre_ajoutees, nombre_ignorees)
|
|
"""
|
|
jours = get_jours_feries(annee)
|
|
ajoutees = 0
|
|
ignorees = 0
|
|
|
|
for date, nom in jours:
|
|
existing = CollegeClosure.query.filter(
|
|
CollegeClosure.type == "jour_ferie",
|
|
CollegeClosure.start_date == date,
|
|
CollegeClosure.end_date == date
|
|
).first()
|
|
|
|
if existing:
|
|
ignorees += 1
|
|
continue
|
|
|
|
closure = CollegeClosure(
|
|
type="jour_ferie",
|
|
label=nom,
|
|
start_date=date,
|
|
end_date=date,
|
|
zone=None,
|
|
is_exception=False,
|
|
created_by_id=user_id
|
|
)
|
|
db.session.add(closure)
|
|
ajoutees += 1
|
|
|
|
if ajoutees > 0:
|
|
db.session.commit()
|
|
|
|
return ajoutees, ignorees |