SuiteConsultance/modules/tasks/web.py
2025-09-20 13:18:04 +02:00

275 lines
No EOL
9.6 KiB
Python

from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
from datetime import date, datetime
from modules.tasks.task_handler import TaskHandler
from modules.tasks.task import Task
from modules.email.draft_handler import DraftHandler
from modules.email.email_manager import EmailSender
from jobs.daily_reminder_job import main as generate_email_drafts_today
from html import escape
tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks")
@tasks_bp.get("/")
def tasks_index():
"""
Page de gestion des tâches: liste + formulaire de création.
Filtres optionnels via query string: status, entity_type, entity_id
"""
handler = TaskHandler()
status = request.args.get("status")
entity_type = request.args.get("entity_type")
entity_id = request.args.get("entity_id")
tasks = handler.list_tasks(status=status) if status else handler.list_tasks()
if entity_type and entity_id:
tasks = [t for t in tasks if t.entity_type == entity_type and t.entity_id == entity_id]
return render_template(
"tasks/manage.html",
tasks=tasks,
status=status,
entity_type=entity_type,
entity_id=entity_id,
today=date.today().isoformat(),
)
@tasks_bp.get("/today/fragment")
def today_fragment():
"""
Renvoie le fragment HTML du bloc 'tâches du jour'
"""
handler = TaskHandler()
today_tasks = handler.list_today_tasks(status="todo")
return render_template("partials/tasks_today_block.html", today_tasks=today_tasks)
@tasks_bp.get('/tomorrow/fragment')
def tomorrow_fragment():
"""
Renvoie le fragment HTML du bloc 'tâches du lendemain'
"""
handler = TaskHandler()
tomorrow_tasks = handler.list_tomorrow_tasks(status="todo")
return render_template("partials/tasks_today_block.html", today_tasks=tomorrow_tasks)
@tasks_bp.get("/today/count")
def today_count():
"""
Renvoie le nombre de tâches du jour (todo) au format JSON, pour affichage dans un badge.
"""
handler = TaskHandler()
count = len(handler.list_today_tasks(status="todo"))
return jsonify({"count": count})
@tasks_bp.post("/create")
def create_task():
"""
Création d'une tâche depuis un formulaire.
Champs attendus: title, due_date (YYYY-MM-DD), description, priority, entity_type, entity_id
"""
title = (request.form.get("title") or "").strip()
due_date = (request.form.get("due_date") or "").strip()
description = (request.form.get("description") or "").strip()
priority = (request.form.get("priority") or "normale").strip()
entity_type = (request.form.get("entity_type") or "").strip() or None
entity_id = (request.form.get("entity_id") or "").strip() or None
if not title or not due_date:
flash("Titre et échéance sont obligatoires.", "warning")
return redirect(request.referrer or url_for("tasks.tasks_index"))
handler = TaskHandler()
task = Task(
title=title,
due_date=due_date,
description=description,
priority=priority,
entity_type=entity_type,
entity_id=entity_id,
)
handler.add_task(task)
flash("Tâche créée avec succès.", "success")
return redirect(request.referrer or url_for("tasks.tasks_index"))
@tasks_bp.post("/status")
def set_status():
"""
Met à jour le statut d'une tâche (todo|done|canceled).
Form data: task_id, status
"""
task_id = (request.form.get("task_id") or "").strip()
status = (request.form.get("status") or "todo").strip()
if not task_id or status not in ("todo", "done", "canceled"):
flash("Requête invalide pour le changement de statut.", "warning")
return redirect(request.referrer or url_for("tasks.tasks_index"))
handler = TaskHandler()
task = handler.get_task(task_id)
if not task:
flash("Tâche introuvable.", "danger")
return redirect(request.referrer or url_for("tasks.tasks_index"))
task.status = status
task.completed_at = datetime.utcnow().isoformat() if status == "done" else None
if handler.update_task(task):
flash("Statut de la tâche mis à jour.", "success")
else:
flash("Échec de la mise à jour du statut.", "danger")
return redirect(request.referrer or url_for("tasks.tasks_index"))
@tasks_bp.post("/delete")
def delete_task():
"""
Supprime une tâche.
Form data: task_id
"""
task_id = (request.form.get("task_id") or "").strip()
if not task_id:
flash("Requête invalide pour la suppression.", "warning")
return redirect(request.referrer or url_for("tasks.tasks_index"))
handler = TaskHandler()
if handler.delete_task(task_id):
flash("Tâche supprimée.", "success")
else:
flash("Échec de la suppression.", "danger")
return redirect(request.referrer or url_for("tasks.tasks_index"))
@tasks_bp.post("/update")
def update_task():
"""
Met à jour les champs d'une tâche.
Form data: task_id, title, due_date, description, priority, entity_type, entity_id
"""
task_id = (request.form.get("task_id") or "").strip()
if not task_id:
flash("Requête invalide pour l'édition.", "warning")
return redirect(request.referrer or url_for("tasks.tasks_index"))
handler = TaskHandler()
task = handler.get_task(task_id)
if not task:
flash("Tâche introuvable.", "danger")
return redirect(request.referrer or url_for("tasks.tasks_index"))
# Mise à jour des champs
task.title = (request.form.get("title") or task.title).strip()
task.due_date = (request.form.get("due_date") or task.due_date).strip()
task.description = (request.form.get("description") or task.description).strip()
task.priority = (request.form.get("priority") or task.priority).strip()
task.entity_type = (request.form.get("entity_type") or task.entity_type or "").strip() or None
task.entity_id = (request.form.get("entity_id") or task.entity_id or "").strip() or None
if handler.update_task(task):
flash("Tâche modifiée.", "success")
else:
flash("Échec de la modification.", "danger")
return redirect(request.referrer or url_for("tasks.tasks_index"))
@tasks_bp.get("/email-drafts")
def email_drafts_list():
"""
Liste des brouillons d'emails à envoyer + actions UI (via template).
"""
dh = DraftHandler()
drafts = dh.list_pending()
return render_template("tasks/email_drafts.html", drafts=drafts)
@tasks_bp.post("/email-drafts/generate")
def email_drafts_generate():
"""
Déclenche la génération des brouillons d'aujourd'hui (équivalent au job quotidien).
"""
try:
count = generate_email_drafts_today()
if count:
flash(f"{count} brouillon(s) généré(s) pour aujourd'hui.", "success")
else:
flash("Aucun brouillon créé. Vérifiez: tâches 'todo' dues aujourd'hui, liées à des prospects avec un email.", "info")
except Exception as e:
flash(f"Erreur lors de la génération: {e}", "danger")
return redirect(url_for("tasks.email_drafts_list"))
@tasks_bp.post("/email-drafts/send")
def email_drafts_send():
"""
Envoie un brouillon d'email sélectionné puis met à jour son statut.
"""
draft_id = (request.form.get("draft_id") or "").strip()
if not draft_id:
flash("Brouillon invalide", "warning")
return redirect(url_for("tasks.email_drafts_list"))
dh = DraftHandler()
draft = dh.get_draft(draft_id)
if not draft:
flash("Brouillon introuvable", "danger")
return redirect(url_for("tasks.email_drafts_list"))
sender = EmailSender()
try:
res = sender.send_tracked_email(
to_email=draft.to_email,
subject=draft.subject,
body=draft.content,
prospect_id=draft.prospect_id,
template_id=draft.template_id,
)
if res.get("success"):
dh.mark_sent(draft.id, success=True)
flash("Email envoyé.", "success")
else:
dh.mark_sent(draft.id, success=False, error_message=res.get("error"))
flash("Échec de l'envoi de l'email.", "danger")
except Exception as e:
dh.mark_sent(draft.id, success=False, error_message=str(e))
flash("Erreur lors de l'envoi de l'email.", "danger")
return redirect(url_for("tasks.email_drafts_list"))
@tasks_bp.get("/quick-add")
def quick_add_page():
"""
Page complète 'Quick Add Task' pour une entité donnée.
Paramètres query: entity_type (client|prospect|project), entity_id
"""
entity_type = (request.args.get("entity_type") or "").strip()
entity_id = (request.args.get("entity_id") or "").strip()
if not entity_type or not entity_id:
flash("Paramètres manquants pour lier la tâche à une entité.", "warning")
return redirect(url_for("tasks.tasks_index"))
return render_template(
"tasks/quick_add.html",
entity_type=entity_type,
entity_id=entity_id,
today=date.today().isoformat(),
)
@tasks_bp.get("/quick-add/fragment")
def quick_add_fragment():
"""
Fragment HTML du formulaire 'Quick Add Task' intégré dans d'autres pages.
Paramètres query: entity_type, entity_id
"""
entity_type = (request.args.get("entity_type") or "").strip()
entity_id = (request.args.get("entity_id") or "").strip()
if not entity_type or not entity_id:
return "<p class='text-warning'>Paramètres manquants pour le formulaire de tâche liée.</p>"
return render_template(
"partials/task_quick_add_form.html",
entity_type=entity_type,
entity_id=entity_id,
today=date.today().isoformat(),
)