275 lines
No EOL
9.6 KiB
Python
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(),
|
|
) |