loko/streetup/common/management/commands/init_scheduled_tasks.py
2026-07-22 14:48:40 +02:00

299 lines
14 KiB
Python

"""
Script pour initialiser les tâches planifiées.
Ce script crée la tâche d'export mensuel des interventions.
Usage:
python manage.py init_scheduled_tasks
"""
from django.core.management.base import BaseCommand
from django.utils import timezone
from django.conf import settings
from common.models_scheduled_tasks import ScheduledCommand
import os
class Command(BaseCommand):
help = 'Initialise les tâches planifiées par défaut'
def handle(self, *args, **options):
self.stdout.write(self.style.WARNING('\n=== Initialisation des tâches planifiées ===\n'))
# Créer le répertoire exports s'il n'existe pas
exports_dir = os.path.join('exports')
if not os.path.exists(exports_dir):
os.makedirs(exports_dir)
self.stdout.write(self.style.SUCCESS(f'✓ Répertoire créé: {exports_dir}'))
# 1. Tâche d'export mensuel des interventions
task_name = "Export mensuel des interventions"
task, created = ScheduledCommand.objects.get_or_create(
name=task_name,
defaults={
'command_name': 'export_interventions_xlsx',
'command_args': {
'output': 'exports/interventions_export_%(year)s_%(month)s.xlsx'
},
'frequency': 'monthly',
'hour': 4,
'minute': 0,
'day_of_month': 3,
'is_active': True,
'send_email': True,
'email_recipients': getattr(settings, 'EMAIL_INTERNAL_FOLLOWUP', 'kdeterme@sprb.brussels'),
'email_subject': '[StreetUp] Export mensuel des interventions',
'description': '''
Export mensuel automatique des interventions du mois précédent.
La tâche s'exécute le 3 de chaque mois à 4h du matin.
Les interventions exportées sont celles du mois précédent (exemple: si nous sommes en février, exporte janvier).
Un email est automatiquement envoyé avec le fichier Excel en pièce jointe.
Le fichier est sauvegardé dans le répertoire exports/ avec le nom:
interventions_export_YYYY_MM.xlsx
'''.strip()
}
)
if created:
# Calculer la prochaine exécution
task.next_run = task.calculate_next_run()
task.save()
self.stdout.write(self.style.SUCCESS(f'\n✓ Tâche créée: {task_name}'))
self.stdout.write(f' - Fréquence: {task.get_frequency_display()}')
self.stdout.write(f' - Planning: Le {task.day_of_month} de chaque mois à {task.hour:02d}:{task.minute:02d}')
self.stdout.write(f' - Prochaine exécution: {task.next_run.strftime("%Y-%m-%d %H:%M")}')
self.stdout.write(f' - Email: {task.email_recipients}')
else:
self.stdout.write(self.style.WARNING(f'\n! Tâche déjà existante: {task_name}'))
self.stdout.write(f' - Statut: {"Active" if task.is_active else "Inactive"}')
if task.next_run:
self.stdout.write(f' - Prochaine exécution: {task.next_run.strftime("%Y-%m-%d %H:%M")}')
# 2. Tâche de nettoyage quotidien des analytics
task_name_analytics = "Nettoyage mensuel des analytics"
task2, created2 = ScheduledCommand.objects.get_or_create(
name=task_name_analytics,
defaults={
'command_name': 'cleanup_analytics',
'command_args': {
'days': 30
},
'frequency': 'daily',
'hour': 3,
'minute': 0,
'is_active': True,
'send_email': False,
'description': '''
Nettoyage quotidien des données analytics de plus de 30 jours.
La tâche s'exécute tous les jours à 3h du matin.
Avant de supprimer les PageView et UserSession anciennes,
un résumé quotidien (DailyPageViewSummary) est créé pour
conserver les statistiques d'utilisation agrégées par jour.
Données conservées dans le résumé:
- Nombre total de pages vues
- Utilisateurs et sessions uniques
- Temps de réponse moyen et max
- Top 20 des pages visitées
- Top 20 des utilisateurs actifs
- Répartition par méthode HTTP
'''.strip()
}
)
if created2:
task2.next_run = task2.calculate_next_run()
task2.save()
self.stdout.write(self.style.SUCCESS(f'\n✓ Tâche créée: {task_name_analytics}'))
self.stdout.write(f' - Fréquence: {task2.get_frequency_display()}')
self.stdout.write(f' - Planning: Tous les jours à {task2.hour:02d}:{task2.minute:02d}')
self.stdout.write(f' - Prochaine exécution: {task2.next_run.strftime("%Y-%m-%d %H:%M")}')
else:
self.stdout.write(self.style.WARNING(f'\n! Tâche déjà existante: {task_name_analytics}'))
self.stdout.write(f' - Statut: {"Active" if task2.is_active else "Inactive"}')
if task2.next_run:
self.stdout.write(f' - Prochaine exécution: {task2.next_run.strftime("%Y-%m-%d %H:%M")}')
# 3. Tâche d'envoi groupé journalier des interventions
task_name_daily = "Résumé journalier des interventions"
task3, created3 = ScheduledCommand.objects.get_or_create(
name=task_name_daily,
defaults={
'command_name': 'send_daily_intervention_summary',
'command_args': {},
'frequency': 'daily',
'hour': 18,
'minute': 0,
'is_active': False, # Désactivé par défaut, à activer si des contrats utilisent l'envoi groupé
'send_email': False,
'description': '''
Envoie un résumé journalier des interventions pour les contrats configurés en mode "envoi groupé".
Au lieu d'envoyer un email par intervention, toutes les fiches PDF du jour sont
regroupées dans un seul email par contrat.
Pour activer l'envoi groupé sur un contrat :
1. Aller dans Admin > Contracts > Contrats > [contrat]
2. Cocher "Envoi groupé journalier"
3. Définir l'heure d'envoi souhaitée dans "Heure d'envoi du résumé journalier"
4. Activer cette tâche planifiée et configurer son heure en conséquence
Arguments disponibles :
--date YYYY-MM-DD : traiter une date spécifique (défaut: aujourd'hui)
--contract ID : traiter un seul contrat
--dry-run : simulation sans envoi réel
'''.strip()
}
)
if created3:
task3.next_run = task3.calculate_next_run()
task3.save()
self.stdout.write(self.style.SUCCESS(f'\n✓ Tâche créée: {task_name_daily}'))
self.stdout.write(f' - Fréquence: {task3.get_frequency_display()}')
self.stdout.write(f' - Planning: Tous les jours à {task3.hour:02d}:{task3.minute:02d}')
self.stdout.write(f' - Statut: {"Active" if task3.is_active else "Inactive (à activer si nécessaire)"}')
else:
self.stdout.write(self.style.WARNING(f'\n! Tâche déjà existante: {task_name_daily}'))
self.stdout.write(f' - Statut: {"Active" if task3.is_active else "Inactive"}')
if task3.next_run:
self.stdout.write(f' - Prochaine exécution: {task3.next_run.strftime("%Y-%m-%d %H:%M")}')
# 4. Rafraîchissement des vues matérialisées de reporting
task_name_reporting = "Rafraîchissement des vues matérialisées"
task4, created4 = ScheduledCommand.objects.get_or_create(
name=task_name_reporting,
defaults={
'command_name': 'refresh_reporting_views',
'command_args': {},
'frequency': 'daily',
'hour': 3,
'minute': 0,
'is_active': True,
'send_email': False,
'description': (
'Rafraîchit quotidiennement les vues matérialisées du schéma reporting.\n\n'
'Vues concernées (dans l\'ordre) :\n'
' - reporting.mv_location_hierarchy\n'
' - reporting.mv_interventions\n\n'
'Utilise REFRESH MATERIALIZED VIEW CONCURRENTLY pour ne pas bloquer les '
'lectures en cours d\'exécution.\n'
'Lors de la première exécution (ou avec --force-recreate), les vues sont '
'créées à partir des fichiers SQL dans reporting/sql/.\n\n'
'Arguments disponibles :\n'
' --force-recreate : force le DROP + CREATE des vues (bloquant).'
),
}
)
if created4:
task4.next_run = task4.calculate_next_run()
task4.save()
self.stdout.write(self.style.SUCCESS(f'\n✓ Tâche créée: {task_name_reporting}'))
self.stdout.write(f' - Fréquence: {task4.get_frequency_display()}')
self.stdout.write(f' - Planning: Tous les jours à {task4.hour:02d}:{task4.minute:02d}')
self.stdout.write(f' - Prochaine exécution: {task4.next_run.strftime("%Y-%m-%d %H:%M")}')
else:
self.stdout.write(self.style.WARNING(f'\n! Tâche déjà existante: {task_name_reporting}'))
self.stdout.write(f' - Statut: {"Active" if task4.is_active else "Inactive"}')
if task4.next_run:
self.stdout.write(f' - Prochaine exécution: {task4.next_run.strftime("%Y-%m-%d %H:%M")}')
# 5. Clôture automatique des interventions
task_name_auto_close = "Clôture automatique des interventions"
task5, created5 = ScheduledCommand.objects.get_or_create(
name=task_name_auto_close,
defaults={
'command_name': 'close_expired_interventions',
'command_args': {},
'frequency': 'daily',
'hour': 2,
'minute': 0,
'is_active': True,
'send_email': False,
'description': '''
Clôture automatique des interventions après un certain délai défini par contrat.
La tâche s'exécute tous les jours à 2h00 du matin.
Les interventions concernées sont celles dont le statut est finalisé, traité, validé ou facturé, et dont le délai depuis la fin est supérieur au délai défini sur leur contrat.
Les observations liées sont également clôturées.
'''.strip()
}
)
if created5:
task5.next_run = task5.calculate_next_run()
task5.save()
self.stdout.write(self.style.SUCCESS(f'\n✓ Tâche créée: {task_name_auto_close}'))
self.stdout.write(f' - Fréquence: {task5.get_frequency_display()}')
self.stdout.write(f' - Planning: Tous les jours à {task5.hour:02d}:{task5.minute:02d}')
self.stdout.write(f' - Prochaine exécution: {task5.next_run.strftime("%Y-%m-%d %H:%M")}')
else:
self.stdout.write(self.style.WARNING(f'\n! Tâche déjà existante: {task_name_auto_close}'))
self.stdout.write(f' - Statut: {"Active" if task5.is_active else "Inactive"}')
if task5.next_run:
self.stdout.write(f' - Prochaine exécution: {task5.next_run.strftime("%Y-%m-%d %H:%M")}')
# 6. Sauvegarde de la base de données
task_name_backup = "Sauvegarde de la base de données"
task6, created6 = ScheduledCommand.objects.get_or_create(
name=task_name_backup,
defaults={
'command_name': 'backup_database',
'command_args': {},
'frequency': 'daily',
'hour': 4,
'minute': 0,
'is_active': True,
'send_email': True,
'email_recipients': getattr(settings, 'EMAIL_INTERNAL_FOLLOWUP', 'kdeterme@sprb.brussels'),
'email_subject': '[StreetUp] Sauvegarde de la base de données',
'description': '''
Sauvegarde quotidienne de la base de données PostgreSQL.
La tâche s'exécute tous les jours à 4h00 du matin.
Elle effectue les actions suivantes :
1. Crée un dump PostgreSQL (pg_dump) des schémas django et public.
2. Compresse le dump en fichier .sql.gz.
3. Téléverse le backup compressé vers le stockage distant MinIO (si BACKUP_MINIO_ENABLED est à true).
4. Synchronise le backup vers le serveur de test via Rsync (si BACKUP_REMOTE_ENABLED est à true).
5. Supprime les sauvegardes locales de plus de 30 jours (configuré via BACKUP_RETENTION_DAYS).
'''.strip()
}
)
if created6:
task6.next_run = task6.calculate_next_run()
task6.save()
self.stdout.write(self.style.SUCCESS(f'\n✓ Tâche créée: {task_name_backup}'))
self.stdout.write(f' - Fréquence: {task6.get_frequency_display()}')
self.stdout.write(f' - Planning: Tous les jours à {task6.hour:02d}:{task6.minute:02d}')
self.stdout.write(f' - Prochaine exécution: {task6.next_run.strftime("%Y-%m-%d %H:%M")}')
else:
self.stdout.write(self.style.WARNING(f'\n! Tâche déjà existante: {task_name_backup}'))
self.stdout.write(f' - Statut: {"Active" if task6.is_active else "Inactive"}')
if task6.next_run:
self.stdout.write(f' - Prochaine exécution: {task6.next_run.strftime("%Y-%m-%d %H:%M")}')
self.stdout.write(self.style.SUCCESS('\n=== Initialisation terminée ==='))
self.stdout.write('\nPour gérer les tâches planifiées:')
self.stdout.write(' 1. Accédez à l\'admin Django: /admin/')
self.stdout.write(' 2. Section: Common > Commandes planifiées')
self.stdout.write('\nPour tester l\'exécution:')
self.stdout.write(' python manage.py run_scheduled_commands')
self.stdout.write('\n')