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 "
Paramètres manquants pour le formulaire de tâche liée.
" return render_template( "partials/task_quick_add_form.html", entity_type=entity_type, entity_id=entity_id, today=date.today().isoformat(), )