diff --git a/noethysweb/core/decorators.py b/noethysweb/core/decorators.py
index 2a3d24f2..42585211 100644
--- a/noethysweb/core/decorators.py
+++ b/noethysweb/core/decorators.py
@@ -2,12 +2,12 @@
# Copyright (c) 2019-2021 Ivan LUCAS.
# Noethysweb, application de gestion multi-activités.
# Distribué sous licence GNU GPL.
-
+import logging
+logger = logging.getLogger(__name__)
from django.urls import reverse_lazy, reverse
from django.http import HttpResponseRedirect, HttpResponseBadRequest, HttpResponseForbidden
-from django.contrib.auth import REDIRECT_FIELD_NAME
-from django.contrib.auth.decorators import user_passes_test
from reglements.utils import utils_ventilation
+from core.models import PortailParametre
def Verifie_ventilation(function):
@@ -39,6 +39,10 @@ def _function(request, *args, **kwargs):
def secure_ajax_portail(function):
""" A associer aux requêtes AJAX """
def _function(request, *args, **kwargs):
+ compte_famille = PortailParametre.objects.filter(code="compte_famille").first()
+ compte_individu = PortailParametre.objects.filter(code="compte_individu").first()
+ # logger.debug("compte_famille: %s" % compte_famille.valeur)
+ # logger.debug("compte_individu: %s" % compte_individu.valeur)
# Vérifie que c'est une requête AJAX
if not request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest':
return HttpResponseBadRequest()
@@ -46,8 +50,11 @@ def _function(request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
# Vérifie que c'est un user de type utilisateur
- if request.user.categorie != "famille":
+ # et que si le paramètre de compte individu est true ( coché ) alors interdire l'accès ou l'utilisation des identifiants famille
+ if((request.user.categorie not in ["famille", "individu"]) # Vérification de la catégorie : Si l'utilisateur n'est ni "famille" ni "individu", l'accès est interdit.
+ or (compte_famille != "True" and request.user.categorie == "famille") #Compte famille non activé pour une famille: Si compte_famille n'est pas "True" et que la catégorie est "famille", l'accès est interdit.
+ or ( compte_individu == "True" and request.user.categorie == "famille")): #Compte individu activé pour une famille: Si compte_individu est "True" alors que l'utilisateur est de catégorie "famille", l'accès est interdit.
+ # if request.user.categorie != "individu":
return HttpResponseForbidden()
return function(request, *args, **kwargs)
return _function
-
diff --git a/noethysweb/core/models.py b/noethysweb/core/models.py
index 3075ab72..056ada44 100644
--- a/noethysweb/core/models.py
+++ b/noethysweb/core/models.py
@@ -1649,7 +1649,17 @@ class Meta:
def __str__(self):
return "Remplissage ID%d" % self.idremplissage if self.idremplissage else "Nouveau"
+class CategorieCompteInternet(models.Model):
+ idcategorie = models.AutoField(verbose_name="ID", db_column='IDcategorie', primary_key=True)
+ nom = models.CharField(verbose_name="Nom", max_length=200)
+ class Meta:
+ db_table = 'categories_compte_internet'
+ verbose_name = "catégorie de compte internet"
+ verbose_name_plural = "catégories de compte internet"
+
+ def __str__(self):
+ return self.nom
class Individu(models.Model):
idindividu = models.AutoField(verbose_name="ID", db_column='IDindividu', primary_key=True)
@@ -1696,6 +1706,17 @@ class Individu(models.Model):
type_garde_choix = [(1, "Mère"), (2, "Père"), (3, "Garde alternée"), (4, "Autre personne")]
type_garde = models.IntegerField(verbose_name=_("Type de garde"), choices=type_garde_choix, blank=True, null=True)
info_garde = models.TextField(verbose_name=_("Information sur la garde"), blank=True, null=True)
+ # new attributs
+ internet_categorie = models.ForeignKey(CategorieCompteInternet, verbose_name="Catégorie",related_name="internet_categori", on_delete=models.PROTECT, blank=True,null=True)
+ internet_actif = models.BooleanField(verbose_name="Compte internet activé", default=True)
+ internet_identifiant = encrypt(models.CharField(verbose_name="Identifiant", max_length=200, blank=True, null=True))
+ internet_mdp = encrypt(models.CharField(verbose_name="Mot de passe", max_length=200, blank=True, null=True))
+ internet_secquest = models.CharField(verbose_name="Question", max_length=200, blank=True, null=True)
+ internet_reservations = models.BooleanField(verbose_name="Autoriser les réservations sur le portail", default=True)
+ mobile = encrypt(models.CharField(verbose_name="Portable favori", max_length=100, blank=True, null=True))
+ utilisateur = models.OneToOneField(Utilisateur, on_delete=models.CASCADE, null=True)
+ certification_date = models.DateTimeField(verbose_name="Date de certification", blank=True, null=True)
+ blocage_impayes_off = models.BooleanField(verbose_name="Ne jamais appliquer le blocage des réservations si impayés",default=False,help_text="En cochant cette case, vous permettez à cette famille d'accéder aux réservations du portail même s'il y a des impayés et que le paramètre 'blocage si impayés' a été activé dans les paramètres généraux du portail.")
class Meta:
db_table = 'individus'
@@ -1767,6 +1788,9 @@ def Maj_infos(self):
self.ville_resid = dict_adresse["ville"]
self.secteur = dict_adresse["secteur"]
self.save()
+ def save_individu(sender, instance, **kwargs):
+ if hasattr(instance, 'individu'):
+ instance.individu.save()
class Scolarite(models.Model):
@@ -1787,19 +1811,6 @@ def __str__(self):
return "Etape de scolarité du %s au %s" % (self.date_debut.strftime('%d/%m/%Y'), self.date_fin.strftime('%d/%m/%Y'))
-class CategorieCompteInternet(models.Model):
- idcategorie = models.AutoField(verbose_name="ID", db_column='IDcategorie', primary_key=True)
- nom = models.CharField(verbose_name="Nom", max_length=200)
-
- class Meta:
- db_table = 'categories_compte_internet'
- verbose_name = "catégorie de compte internet"
- verbose_name_plural = "catégories de compte internet"
-
- def __str__(self):
- return self.nom
-
-
class Famille(models.Model):
idfamille = models.AutoField(verbose_name="ID", db_column='IDfamille', primary_key=True)
date_creation = models.DateTimeField(verbose_name="Date de création", auto_now_add=True)
@@ -1848,7 +1859,8 @@ class Famille(models.Model):
mobile_blocage = models.BooleanField(verbose_name="La famille ne souhaite pas recevoir de SMS groupés", default=False, help_text="L'éditeur de SMS groupés du menu Outils ne proposera pas cette famille dans les destinataires.")
individus_masques = models.ManyToManyField(Individu, verbose_name="Individus masqués", related_name="individus_masques", blank=True)
blocage_impayes_off = models.BooleanField(verbose_name="Ne jamais appliquer le blocage des réservations si impayés", default=False, help_text="En cochant cette case, vous permettez à cette famille d'accéder aux réservations du portail même s'il y a des impayés et que le paramètre 'blocage si impayés' a été activé dans les paramètres généraux du portail.")
-
+ contact_facturation = models.ForeignKey(Individu, verbose_name="Contact facturation", related_name="contact_facturation", on_delete=models.SET_NULL, blank=True, null=True)
+ utilisateur = models.OneToOneField(Utilisateur, on_delete=models.CASCADE, null=True)
class Meta:
db_table = 'familles'
verbose_name = "famille"
@@ -1907,18 +1919,25 @@ def Maj_infos(self, maj_adresse=True, maj_mail=True, maj_mobile=True, maj_titula
# Titulaire Hélios
if maj_titulaire_helios:
- if self.titulaire_helios:
- # recherche si le titulaire est toujours dans la famille
- found = False
- for rattachement in rattachements:
- if rattachement.individu == self.titulaire_helios:
- found = True
- if not found:
- self.titulaire_helios = None
- if not self.titulaire_helios:
- # Recherche un individu valide parmi les titulaires de la famille
- if rattachements:
- self.titulaire_helios = rattachements.first().individu
+ try:
+ if self.titulaire_helios:
+ # Recherche si le titulaire est toujours dans la famille
+ found = False
+ for rattachement in rattachements:
+ if rattachement.individu == self.titulaire_helios:
+ found = True
+ break # Si trouvé, on sort de la boucle
+ if not found:
+ # Si le titulaire n'est pas trouvé dans la famille, on le met à None
+ self.titulaire_helios = None
+ except Individu.DoesNotExist:
+ # Si le titulaire a été supprimé, on le met à None
+ self.titulaire_helios = None
+
+ # Assurez-vous qu'il y a un titulaire dans la famille si nécessaire
+ if not self.titulaire_helios and rattachements.exists():
+ # Recherche un individu valide parmi les rattachements
+ self.titulaire_helios = rattachements.first().individu
if maj_tiers_solidaire:
if self.tiers_solidaire:
@@ -1949,6 +1968,11 @@ def Maj_infos(self, maj_adresse=True, maj_mail=True, maj_mobile=True, maj_titula
def Get_rue_resid(self):
return self.rue_resid.replace("\n", "
") if self.rue_resid else None
+
+ def Get_nom(self):
+ texte = ""
+ texte = self.nom
+ return texte
def Get_infos(self, avec_civilite=False):
""" Renvoie le nom des titulaires """
@@ -3042,6 +3066,8 @@ class Destinataire(models.Model):
categorie = models.CharField(verbose_name="Catégorie", max_length=300, blank=True, null=True)
individu = models.ForeignKey(Individu, verbose_name="Individu", blank=True, null=True, on_delete=models.CASCADE)
famille = models.ForeignKey(Famille, verbose_name="Famille", blank=True, null=True, on_delete=models.CASCADE)
+ inscription = models.ForeignKey(Inscription, verbose_name="Inscription", blank=True, null=True,on_delete=models.CASCADE)
+ activites = models.ForeignKey(Activite, verbose_name="Activites", blank=True, null=True, on_delete=models.CASCADE)
collaborateur = models.ForeignKey("Collaborateur", verbose_name="Collaborateur", blank=True, null=True, on_delete=models.CASCADE)
contact = models.ForeignKey(Contact, verbose_name="Contact", blank=True, null=True, on_delete=models.CASCADE)
liste_diffusion = models.ForeignKey(ListeDiffusion, verbose_name="Liste de diffusion", blank=True, null=True, on_delete=models.CASCADE)
@@ -3144,7 +3170,7 @@ def Is_famille_authorized(self, famille=None):
class PortailParametre(models.Model):
idparametre = models.AutoField(verbose_name="ID", db_column='IDparametre', primary_key=True)
- code = models.CharField(verbose_name="Code", max_length=200, blank=True, null=True)
+ code = models.CharField(verbose_name="Code", max_length=200, blank=True, null=True, unique=True)
valeur = models.TextField(verbose_name="Valeur", blank=True, null=True)
class Meta:
@@ -3202,9 +3228,10 @@ def __str__(self):
class PortailMessage(models.Model):
idmessage = models.AutoField(verbose_name="ID", db_column='IDmessage', primary_key=True)
- famille = models.ForeignKey(Famille, verbose_name="Famille", on_delete=models.CASCADE)
- structure = models.ForeignKey(Structure, verbose_name="Structure", on_delete=models.CASCADE)
- utilisateur = models.ForeignKey(Utilisateur, verbose_name="Utilisateur", blank=True, null=True, on_delete=models.PROTECT)
+ famille = models.ForeignKey(Famille, verbose_name="Famille", on_delete=models.CASCADE, db_index=True)
+ individu = models.ForeignKey(Individu, verbose_name="Individu", on_delete=models.CASCADE, null=True, db_index=True)
+ structure = models.ForeignKey(Structure, verbose_name="Structure", on_delete=models.CASCADE, db_index=True)
+ utilisateur = models.ForeignKey(Utilisateur, verbose_name="Utilisateur", blank=True, null=True,on_delete=models.PROTECT)
texte = models.TextField(verbose_name="Texte")
date_creation = models.DateTimeField(verbose_name="Date de création", auto_now_add=True)
date_lecture = models.DateTimeField(verbose_name="Date de lecture", max_length=200, blank=True, null=True)
@@ -3213,6 +3240,14 @@ class Meta:
db_table = 'portail_messages'
verbose_name = "message"
verbose_name_plural = "messages"
+ indexes = [
+ models.Index(fields=["famille"]),
+ models.Index(fields=["individu"]),
+ models.Index(fields=["structure"]),
+ models.Index(fields=["utilisateur"]),
+ models.Index(fields=["date_creation"]),
+ models.Index(fields=["date_lecture"]),
+ ]
def __str__(self):
return "Message ID%d" % self.idmessage if self.idmessage else "Nouveau message"
diff --git a/noethysweb/core/templates/core/header.html b/noethysweb/core/templates/core/header.html
index 5b468fdf..3783812d 100644
--- a/noethysweb/core/templates/core/header.html
+++ b/noethysweb/core/templates/core/header.html
@@ -104,7 +104,7 @@
{{ message.texte|striptags|truncatechars:55 }}
-{{ message.date_creation|timesince }} | {{ message.structure }}
++ {{ message.date_creation|timesince }} + | {{ message.structure }} +
+
+ Si vous ne souhaitez plus recevoir nos mails groupés, cliquez sur le lien suivant : Désinscription
" % url_desinscription headers = {"List-Unsubscribe-Post": "List-Unsubscribe=One-Click", "List-Unsubscribe": url_desinscription} - # Création du message - message = EmailMultiAlternatives(subject=objet, body=utils_texte.Textify(html), from_email=mail.adresse_exp.adresse, to=[destinataire.adresse], connection=connection, headers=headers) + # Construction du message + message = EmailMultiAlternatives( + subject=objet, + body=utils_texte.Textify(html), + from_email=mail.adresse_exp.adresse, + to=[destinataire.adresse], + connection=connection, + headers=headers + ) message.mixed_subtype = 'related' message.attach_alternative(html, "text/html") @@ -203,19 +222,26 @@ def Envoyer_model_mail(idmail=None, request=None): if resultat == 1: liste_envois_succes.append(destinataire) - - # Mémorise l'envoi dans l'historique - utils_historique.Ajouter(titre="Envoi d'un email", detail=objet, utilisateur=request.user if request else None, famille=destinataire.famille_id, - individu=destinataire.individu_id, collaborateur=destinataire.collaborateur_id, objet="Email", idobjet=mail.pk, classe="Mail") - - # Pause si envoi par lot activé - if nbre_mails_lot and len(destinataires) > 1: + # Historique + utils_historique.Ajouter( + titre="Envoi d'un email", + detail=objet, + utilisateur=request.user if request else None, + famille=destinataire.famille_id, + individu=destinataire.individu_id, + collaborateur=destinataire.collaborateur_id, + objet="Email", + idobjet=mail.pk, + classe="Mail" + ) + + # Pause si envoi par lot + if nbre_mails_lot and len(unique_destinataires) > 1: if index_lot >= int(nbre_mails_lot): time.sleep(int(duree_pause)) index_lot = 0 index_lot += 1 - connection.close() return liste_envois_succes diff --git a/noethysweb/outils/views/editeur_emails.py b/noethysweb/outils/views/editeur_emails.py index 1f4072bd..516b90ee 100644 --- a/noethysweb/outils/views/editeur_emails.py +++ b/noethysweb/outils/views/editeur_emails.py @@ -1,40 +1,72 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2019-2021 Ivan LUCAS. -# Noethysweb, application de gestion multi-activités. -# Distribué sous licence GNU GPL. +# Copyright (c) 2019-2021 Ivan LUCAS. +# Noethysweb, application de gestion multi-activités. +# Distribué sous licence GNU GPL. import json, os, uuid +from collections import defaultdict + from django.urls import reverse_lazy from django.http import JsonResponse, HttpResponseRedirect from django.db.models import Q, Count from django.contrib import messages from django.conf import settings + from core.views import crud -from core.models import ModeleEmail, Mail, PieceJointe, Destinataire, Famille, Individu, Collaborateur, Contact, SignatureEmail +from core.models import ModeleEmail, Mail, PieceJointe, Destinataire, Famille, Individu, Activite, Inscription, Collaborateur, Contact, SignatureEmail, PortailParametre from core.utils import utils_texte from outils.forms.editeur_emails import Formulaire from outils.utils import utils_email +def RemoveDuplicates(mail): + """ + Supprime en base les doublons d'adresse pour un même Mail, + en ne gardant que la première occurrence pour chaque adresse. + """ + groups = defaultdict(list) + for d in mail.destinataires.all(): + addr_norm = (d.adresse or "").strip().lower() + groups[addr_norm].append(d) + + for addr_norm, items in groups.items(): + if len(items) > 1: + to_keep = items[0] # On garde le premier + for d in items[1:]: + d.delete() + def Get_modele_email(request): - """ Renvoie le contenu HTML d'un modèle d'emails """ + """Renvoie le contenu HTML d'un modèle d'emails""" idmodele = int(request.POST.get("idmodele")) modele = ModeleEmail.objects.filter(pk=idmodele).first() return JsonResponse({"objet": modele.objet, "html": modele.html}) def Get_signature_email(request): - """ Renvoie le contenu HTML d'une signature d'emails """ + """Renvoie le contenu HTML d'une signature d'emails""" idsignature = int(request.POST.get("idsignature")) signature = SignatureEmail.objects.filter(pk=idsignature).first() return JsonResponse({"html": signature.html}) def Exporter_excel(request): + """Exporte la liste des destinataires ou des envois vers un fichier Excel""" idmail = int(request.POST.get("idmail") or 0) mode = request.POST.get("mode") - destinataires = Destinataire.objects.select_related("famille", "individu", "contact", "collaborateur").filter(mail=idmail).order_by("adresse") - if not destinataires: + + # --- Fetch the Mail --- + try: + mail = Mail.objects.get(pk=idmail) + except Mail.DoesNotExist: + return JsonResponse({"erreur": "Mail inexistant."}, status=401) + + destinataires = ( + mail.destinataires + .select_related("famille", "individu", "activites", "inscription", "contact", "collaborateur") + .order_by("adresse") + ) + + if not destinataires.exists(): return JsonResponse({"erreur": "Aucun destinataire n'a été sélectionné"}, status=401) # Création du répertoire et du nom du fichier @@ -42,150 +74,245 @@ def Exporter_excel(request): rep_destination = os.path.join(settings.MEDIA_ROOT, rep_temp) if not os.path.isdir(rep_destination): os.makedirs(rep_destination) - nom_fichier = "export_%s.xlsx" % mode + nom_fichier = f"export_{mode}.xlsx" - # Création du classeur + # Création du classeur Excel (xlsxwriter) import xlsxwriter classeur = xlsxwriter.Workbook(os.path.join(rep_destination, nom_fichier)) feuille = classeur.add_worksheet(mode) - # Création des colonnes + # Colonnes si on exporte les "envois" if mode == "envois": - colonnes = ("Famille", "Individu", "Collaborateur", "Contact", "Email", "Résultat de l'envoi") + colonnes = ( + "Famille", "Individu", "Activité", "Inscription", + "Collaborateur", "Contact", "Email", "Résultat de l'envoi" + ) for num_colonne, label_colonne in enumerate(colonnes): feuille.set_column(num_colonne, num_colonne, 35) feuille.write(0, num_colonne, label_colonne) + # Remplir le classeur for num_ligne, destinataire in enumerate(destinataires): if mode == "destinataires": + # Juste la colonne avec l'adresse feuille.write(num_ligne, 0, destinataire.adresse) if mode == "envois": + # Remplir chaque colonne feuille.write(num_ligne+1, 0, destinataire.famille.nom if destinataire.famille else "") feuille.write(num_ligne+1, 1, destinataire.individu.Get_nom() if destinataire.individu else "") - feuille.write(num_ligne+1, 2, destinataire.collaborateur.Get_nom() if destinataire.collaborateur else "") - feuille.write(num_ligne+1, 3, destinataire.contact.Get_nom() if destinataire.contact else "") - feuille.write(num_ligne+1, 4, destinataire.adresse) - feuille.write(num_ligne+1, 5, destinataire.resultat_envoi) - classeur.close() + feuille.write(num_ligne+1, 2, destinataire.activites.nom if destinataire.activites else "") + feuille.write(num_ligne+1, 3, destinataire.inscription.Get_nom() if destinataire.inscription else "") + feuille.write(num_ligne+1, 4, destinataire.collaborateur.Get_nom() if destinataire.collaborateur else "") + feuille.write(num_ligne+1, 5, destinataire.contact.Get_nom() if destinataire.contact else "") + feuille.write(num_ligne+1, 6, destinataire.adresse) + feuille.write(num_ligne+1, 7, destinataire.resultat_envoi) + classeur.close() return JsonResponse({"nom_fichier": os.path.join(rep_temp, nom_fichier)}) class Page(crud.Page): + """Page principale de l'éditeur d'emails""" model = Mail menu_code = "editeur_emails" template_name = "outils/editeur_emails.html" + def Get_idmail(self): + return self.kwargs.get('pk', None) + def get_context_data(self, **kwargs): context = super(Page, self).get_context_data(**kwargs) - context['page_titre'] = "Editeur d'emails" - context['afficher_menu_brothers'] = True - context['categories'] = {} if not self.Get_idmail() else {item["categorie"]: item["nbre"] for item in Destinataire.objects.values('categorie').filter(mail=self.Get_idmail()).annotate(nbre=Count("pk"))} - mail = Mail.objects.get(pk=self.Get_idmail()) if self.Get_idmail() else None + context['page_titre'] = "Envoi d'emails par lot" + + mail = None + if self.Get_idmail(): + mail = Mail.objects.get(pk=self.Get_idmail()) + context['mail'] = mail + + # --- Supprimer les doublons si mail existe --- + if mail: + RemoveDuplicates(mail) + + # Charger la catégorie, les signatures, etc. categorie = mail.categorie if mail else "saisie_libre" context['modeles'] = ModeleEmail.objects.filter(categorie=categorie) - context['signatures'] = SignatureEmail.objects.filter(Q(structure__in=self.request.user.structures.all()) | Q(structure__isnull=True)) - context['mail'] = mail + context['signatures'] = SignatureEmail.objects.filter( + Q(structure__in=self.request.user.structures.all()) | Q(structure__isnull=True) + ) + + # Stats par catégorie + context['categories'] = {} + if mail: + categories_data = ( + Destinataire.objects + .values('categorie') + .filter(mail=mail) + .annotate(nbre=Count('pk', distinct=True)) + ) + for item in categories_data: + cat = item["categorie"] + if cat == "famille": + count_value = Destinataire.objects.filter(mail=mail, categorie=cat).values('famille').distinct().count() + elif cat == "activites": + count_value = Destinataire.objects.filter(mail=mail, categorie=cat).values('activite').distinct().count() + elif cat == "individu": + count_value = Destinataire.objects.filter(mail=mail, categorie=cat).values('individu').distinct().count() + else: + count_value = item["nbre"] + context['categories'][cat] = count_value + + # Récupérer tous les destinataires (il n'y a plus de doublons, thanks to RemoveDuplicates) + destinataires = [] + nbre_envois_attente = 0 + nbre_envois_reussis = 0 + nbre_envois_echec = 0 + + if mail: + for d in ( + mail.destinataires + .select_related("famille", "individu", "activites", "inscription", "contact", "collaborateur") + .prefetch_related("documents") + .order_by("adresse") + ): + destinataires.append(d) + if not d.resultat_envoi: + nbre_envois_attente += 1 + elif d.resultat_envoi == "ok": + nbre_envois_reussis += 1 + else: + nbre_envois_echec += 1 - # Importe la liste des destinataires (Elimine les doublons) - destinataires, adresses_temp = [], [] - nbre_envois_attente, nbre_envois_reussis, nbre_envois_echec = 0, 0, 0 - if self.Get_idmail(): - for destinataire in Destinataire.objects.select_related("famille", "individu", "contact", "collaborateur").prefetch_related("documents").filter(mail=self.Get_idmail()).order_by("adresse"): - if True:#destinataire.adresse not in adresses_temp: - destinataires.append(destinataire) - adresses_temp.append(destinataire.adresse) - if not destinataire.resultat_envoi: - nbre_envois_attente += 1 - elif destinataire.resultat_envoi == "ok": - nbre_envois_reussis += 1 - else: - nbre_envois_echec += 1 context['destinataires'] = destinataires context['nbre_envois_attente'] = nbre_envois_attente - context['nbre_envois_echec'] = nbre_envois_echec context['nbre_envois_reussis'] = nbre_envois_reussis + context['nbre_envois_echec'] = nbre_envois_echec - # Création du texte d'intro de la box Envois + # Intro envois intro_envois = [] - if not destinataires: intro_envois.append("Aucun envoi") - if nbre_envois_reussis: intro_envois.append("%d envois réussis" % nbre_envois_reussis) if nbre_envois_reussis > 1 else intro_envois.append("1 envoi réussi") - if nbre_envois_attente: intro_envois.append("%d envois en attente" % nbre_envois_attente) if nbre_envois_attente > 1 else intro_envois.append("1 envoi en attente") - if nbre_envois_echec: intro_envois.append("%d envois en échec" % nbre_envois_echec) if nbre_envois_echec > 1 else intro_envois.append("1 envoi en échec") + if not destinataires: + intro_envois.append("Aucun envoi") + if nbre_envois_reussis == 1: + intro_envois.append("1 envoi réussi") + elif nbre_envois_reussis > 1: + intro_envois.append(f"{nbre_envois_reussis} envois réussis") + + if nbre_envois_attente == 1: + intro_envois.append("1 envoi en attente") + elif nbre_envois_attente > 1: + intro_envois.append(f"{nbre_envois_attente} envois en attente") + + if nbre_envois_echec == 1: + intro_envois.append("1 envoi en échec") + elif nbre_envois_echec > 1: + intro_envois.append(f"{nbre_envois_echec} envois en échec") + context['intro_envoi'] = utils_texte.Convert_liste_to_texte_virgules(intro_envois) + # Parametres + parametres_db = PortailParametre.objects.filter( + code__in=[ + "emails_individus_afficher_page", + "emails_familles_afficher_page", + "emails_activites_afficher_page", + "emails_inscriptions_afficher_page", + "emails_collaborateurs_afficher_page", + "emails_liste_diffusion_afficher_page", + ] + ) + parametres = {} + for p in parametres_db: + if isinstance(p.valeur, str): + parametres[p.code] = p.valeur.strip().lower() in ["true", "1"] + + context["parametres"] = parametres return context - def Get_idmail(self): - return self.kwargs.get('pk', None) - def get_form_kwargs(self, **kwargs): - """ Envoie l'idactivite au formulaire """ - form_kwargs = super(Page, self).get_form_kwargs(**kwargs) + """Envoie l'idmail au formulaire""" + form_kwargs = super().get_form_kwargs(**kwargs) form_kwargs["idmail"] = self.Get_idmail() return form_kwargs def post(self, request, *args, **kwargs): - # Validation du formulaire + """Gère la sauvegarde du Mail ou la redirection vers la sélection de destinataires.""" form = Formulaire(request.POST, request=self.request) - if form.is_valid() == False: + if not form.is_valid(): return self.render_to_response(self.get_context_data(form=form)) - idmail = self.kwargs.get("pk") - - # Modification du mail + idmail = self.Get_idmail() if idmail: mail = Mail.objects.get(pk=idmail) - mail.objet = form.cleaned_data.get("objet") - mail.html = form.cleaned_data.get("html") - mail.adresse_exp = form.cleaned_data.get("adresse_exp") - mail.selection = form.cleaned_data.get("selection") + mail.objet = form.cleaned_data["objet"] + mail.html = form.cleaned_data["html"] + mail.adresse_exp = form.cleaned_data["adresse_exp"] + mail.selection = form.cleaned_data["selection"] mail.save() - - # Création du mail - if not idmail: + else: mail = Mail.objects.create( categorie="saisie_libre", - objet=form.cleaned_data.get("objet"), - html=form.cleaned_data.get("html"), - adresse_exp=form.cleaned_data.get("adresse_exp"), - selection=form.cleaned_data.get("selection"), + objet=form.cleaned_data["objet"], + html=form.cleaned_data["html"], + adresse_exp=form.cleaned_data["adresse_exp"], + selection=form.cleaned_data["selection"], utilisateur=request.user, ) - messages.add_message(request, messages.INFO, "Un brouillon du mail a été enregistré") + messages.info(request, "Un brouillon du mail a été enregistré") - # Enregistrement d'une nouvelle pièce jointe + # Gérer les pièces jointes piece = request.FILES.get('pieces_jointes') if piece: piece_jointe = PieceJointe.objects.create(nom=piece._name, fichier=piece) mail.pieces_jointes.add(piece_jointe) - # Suppression d'une pièce suppr_piece_jointe = request.POST.get("suppr_piece_jointe") if suppr_piece_jointe: - piece_jointe = PieceJointe.objects.get(pk=suppr_piece_jointe) - piece_jointe.delete() + PieceJointe.objects.filter(pk=suppr_piece_jointe).delete() - # Redirection vers pages de sélection des destinataires + # Gérer la redirection pour ajouter des destinataires action = request.POST.get("action") - if action == "ajouter_familles": return HttpResponseRedirect(reverse_lazy("editeur_emails_familles", kwargs={"idmail": mail.pk})) - if action == "ajouter_individus": return HttpResponseRedirect(reverse_lazy("editeur_emails_individus", kwargs={"idmail": mail.pk})) - if action == "ajouter_collaborateurs": return HttpResponseRedirect(reverse_lazy("editeur_emails_collaborateurs", kwargs={"idmail": mail.pk})) - if action == "ajouter_contacts": return HttpResponseRedirect(reverse_lazy("editeur_emails_contacts", kwargs={"idmail": mail.pk})) - if action == "ajouter_diffusion": return HttpResponseRedirect(reverse_lazy("editeur_emails_listes_diffusion", kwargs={"idmail": mail.pk})) - if action == "ajouter_saisie_libre": return HttpResponseRedirect(reverse_lazy("editeur_emails_saisie_libre", kwargs={"idmail": mail.pk})) - - # Envoyer + if action == "ajouter_familles": + return HttpResponseRedirect(reverse_lazy("editeur_emails_familles", kwargs={"idmail": mail.pk})) + if action == "ajouter_individus": + return HttpResponseRedirect(reverse_lazy("editeur_emails_individus", kwargs={"idmail": mail.pk})) + if action == "ajouter_activites": + return HttpResponseRedirect(reverse_lazy("editeur_emails_activites")) + if action == "ajouter_inscription": + return HttpResponseRedirect(reverse_lazy("editeur_emails_inscriptions")) + if action == "ajouter_collaborateurs": + return HttpResponseRedirect(reverse_lazy("editeur_emails_collaborateurs", kwargs={"idmail": mail.pk})) + if action == "ajouter_contacts": + return HttpResponseRedirect(reverse_lazy("editeur_emails_contacts", kwargs={"idmail": mail.pk})) + if action == "ajouter_diffusion": + return HttpResponseRedirect(reverse_lazy("editeur_emails_listes_diffusion", kwargs={"idmail": mail.pk})) + if action == "ajouter_saisie_libre": + return HttpResponseRedirect(reverse_lazy("editeur_emails_saisie_libre", kwargs={"idmail": mail.pk})) + + # Gérer l'envoi if action == "envoyer": - if not mail.destinataires: - messages.add_message(request, messages.ERROR, "Vous devez sélectionner au moins un destinataire") - elif not form.cleaned_data.get("adresse_exp"): - messages.add_message(request, messages.ERROR, "Vous devez sélectionner une adresse d'expédition dans la liste") - elif not form.cleaned_data.get("objet"): - messages.add_message(request, messages.ERROR, "Vous devez saisir un objet") - elif not form.cleaned_data.get("html"): - messages.add_message(request, messages.ERROR, "Vous devez saisir un texte") + # Vérifier la validité minimale + if mail.destinataires.count() == 0: + messages.error(request, "Vous devez sélectionner au moins un destinataire") + elif not form.cleaned_data["adresse_exp"]: + messages.error(request, "Vous devez sélectionner une adresse d'expédition dans la liste") + elif not form.cleaned_data["objet"]: + messages.error(request, "Vous devez saisir un objet") + elif not form.cleaned_data["html"]: + messages.error(request, "Vous devez saisir un texte") else: + # Définir la variable expediteur_email + expediteur_email = mail.adresse_exp + # Récupérer tous les destinataires existants de type "expediteur" + destinataires_expediteurs = list(mail.destinataires.filter(categorie="expediteur")) + # Comparaison en mémoire pour éviter l'erreur sur EncryptedEmailField + already_there = any(str(dest.adresse) == str(expediteur_email) for dest in destinataires_expediteurs) + + # Ajouter l'expéditeur seulement s'il n'existe pas déjà + if not already_there: + destinataire_expediteur = Destinataire.objects.create( + categorie="expediteur", + adresse=expediteur_email + ) + mail.destinataires.add(destinataire_expediteur) # Ajout au ManyToMany utils_email.Envoyer_model_mail(idmail=mail.pk, request=request) return HttpResponseRedirect(reverse_lazy("editeur_emails", kwargs={'pk': mail.pk})) @@ -199,66 +326,106 @@ class Modifier(Page, crud.Modifier): form_class = Formulaire - - -# ------------------------------------------------------------------------------------------- +# -------------------------------------------------------------------------- +# Pages d'ajout de destinataires (familles, individus, etc.) +# -------------------------------------------------------------------------- class Page_destinataires(crud.Page): menu_code = "editeur_emails" def get_context_data(self, **kwargs): - context = super(Page_destinataires, self).get_context_data(**kwargs) + context = super().get_context_data(**kwargs) context['page_titre'] = "Editeur d'emails" context['idmail'] = self.kwargs.get("idmail") context['categorie'] = self.categorie return context def post(self, request, **kwargs): - liste_selections = json.loads(request.POST.get("selections")) - - # Importe le mail + liste_selections = json.loads(request.POST.get("selections") or "[]") mail = Mail.objects.get(pk=self.kwargs.get("idmail")) - # Importe les adresses dict_adresses = {} if self.categorie == "famille": - dict_adresses = {famille.pk: famille.mail for famille in Famille.objects.filter(email_blocage=False)} - if self.categorie == "individu": - dict_adresses = {individu.pk: individu.mail for individu in Individu.objects.all()} - if self.categorie == "collaborateur": - dict_adresses = {collaborateur.pk: collaborateur.mail for collaborateur in Collaborateur.objects.all()} - if self.categorie == "contact": - dict_adresses = {contact.pk: contact.mail for contact in Contact.objects.all()} - - # Importe la liste des destinataires actuels - destinataires = Destinataire.objects.filter(categorie=self.categorie, mail=mail) - liste_id = [getattr(destinataire, "%s_id" % self.categorie) for destinataire in destinataires] - - # Recherche le dernier ID de la table Destinataires - dernier_destinataire = Destinataire.objects.last() - idmax = dernier_destinataire.pk if dernier_destinataire else 0 - - # Ajout des destinataires + dict_adresses = { + f.pk: f.mail + for f in Famille.objects.filter(email_blocage=False) + } + elif self.categorie == "individu": + dict_adresses = { + i.pk: i.mail + for i in Individu.objects.all() + } + elif self.categorie == "activites": + inscriptions = ( + Inscription.objects + .filter(activite_id__in=liste_selections, famille__isnull=False) + .select_related("famille") + ) + for ins in inscriptions: + if ins.famille and ins.famille.mail: + dict_adresses[ins.famille.pk] = ins.famille.mail + elif self.categorie == "collaborateur": + dict_adresses = { + c.pk: c.mail + for c in Collaborateur.objects.all() + } + elif self.categorie == "contact": + dict_adresses = { + c.pk: c.mail + for c in Contact.objects.all() + } + + # Destinataires déjà existants pour cette catégorie + destinataires_existants = mail.destinataires.filter(categorie=self.categorie) + liste_id_existants = [ + getattr(dest, f"{self.categorie}_id") for dest in destinataires_existants + ] + + # ### AJOUT : On construit un set des adresses déjà présentes (pour ce mail), + # afin d'éviter les doublons en base + existing_addresses = set( + str(d.adresse).strip().lower() + for d in mail.destinataires.all() + if d.adresse + ) + + # On repère la fin de la table pour faire le bulk_create + dernier = Destinataire.objects.last() + idmax = dernier.pk if dernier else 0 + liste_ajouts = [] - for id in liste_selections: - if id not in liste_id: - adresse = dict_adresses.get(id, None) + for ident in dict_adresses: + # Ne pas ajouter deux fois la même "famille_id"/"individu_id", etc. + if ident not in liste_id_existants: + adresse = dict_adresses[ident] if adresse: - kwargs = {"{0}_id".format(self.categorie): id, "categorie": self.categorie, "adresse": adresse} - liste_ajouts.append(Destinataire(**kwargs)) + addr_norm = adresse.strip().lower() + # Vérifier si cette adresse est déjà utilisée + if addr_norm not in existing_addresses: + existing_addresses.add(addr_norm) # Évite de l'ajouter encore + if self.categorie == "activites": + liste_ajouts.append(Destinataire( + categorie="activites", + famille_id=ident, + adresse=adresse + )) + else: + kwargs2 = { + f"{self.categorie}_id": ident, + "categorie": self.categorie, + "adresse": adresse, + } + liste_ajouts.append(Destinataire(**kwargs2)) + if liste_ajouts: - # Enregistre les destinataires Destinataire.objects.bulk_create(liste_ajouts) - # Associe les destinataires au mail - destinataires = Destinataire.objects.filter(pk__gt=idmax) - ThroughModel = Mail.destinataires.through - ThroughModel.objects.bulk_create([ThroughModel(mail_id=mail.pk, destinataire_id=destinataire.pk) for destinataire in destinataires]) - - # Suppression des destinataires - for id in liste_id: - if id not in liste_selections: - kwargs = {"{0}_id".format(self.categorie): id, "mail": mail} - destinataire = Destinataire.objects.get(**kwargs) - destinataire.delete() + nouveaux = Destinataire.objects.filter(pk__gt=idmax) + mail.destinataires.add(*nouveaux) + + # Suppression si décoché + for ident in liste_id_existants: + if ident not in dict_adresses: + qs_suppr = destinataires_existants.filter(**{f"{self.categorie}_id": ident}) + qs_suppr.delete() return HttpResponseRedirect(reverse_lazy("editeur_emails", kwargs={'pk': mail.pk})) diff --git a/noethysweb/outils/views/editeur_emails_activites.py b/noethysweb/outils/views/editeur_emails_activites.py new file mode 100644 index 00000000..bb89a123 --- /dev/null +++ b/noethysweb/outils/views/editeur_emails_activites.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2025 Faouzia TEKAYA. +# Noethysweb, application de gestion multi-activités. +# Distribué sous licence GNU GPL. + +import logging +logger = logging.getLogger(__name__) +from django.urls import reverse_lazy, reverse +from core.views.mydatatableview import MyDatatable, columns, helpers +from core.views import crud +from django.db.models import Count, F +from core.models import Mail, DocumentJoint, Inscription, Destinataire, Activite +from django.http import JsonResponse +from django.contrib import messages +import json +def Transferer_activites(request): + if request.method != "POST": + return JsonResponse({"erreur": "Méthode invalide."}, status=405) + + data = json.loads(request.POST.get("activites_selectionnees", "[]")) + if not data: + return JsonResponse({"erreur": "Aucune activité sélectionnée."}, status=401) + + # Créer le Mail + mail = Mail.objects.create( + categorie="activites", + objet="Informations sur l'activité", + html="", + adresse_exp=request.user.Get_adresse_exp_defaut(), + selection="NON_ENVOYE", + verrouillage_destinataires=False, + utilisateur=request.user, + ) + + # Trouver toutes les familles ayant une inscription pour ces activités + inscriptions = ( + Inscription.objects + .filter(activite_id__in=data, famille__isnull=False) + .select_related("famille") + ) + + # Ajouter un Destinataire par famille + familles_dict = {} + for insc in inscriptions: + if insc.famille and insc.famille.mail: + familles_dict[insc.famille.pk] = insc.famille.mail + + liste_anomalies = [] + for fam_id, email in familles_dict.items(): + if email: + destinataire = Destinataire.objects.create( + categorie="activites", + famille_id=fam_id, + adresse=email, + ) + mail.destinataires.add(destinataire) + else: + # Facultatif : signaler l'absence d'email + liste_anomalies.append(str(fam_id)) + + if liste_anomalies: + messages.error( + request, + "Familles sans email : %s" % ", ".join(liste_anomalies) + ) + + # Retourne l’URL pour l’ouvrir dans l’éditeur d’emails + url = reverse_lazy("editeur_emails", kwargs={"pk": mail.pk}) + return JsonResponse({"url": url}) + +def Impression_pdf(request): + # Récupération des activites cochées + activites_cochees = json.loads(request.POST.get("activites_cochees")) + if not activites_cochees: + return JsonResponse({"erreur": "Veuillez cocher au moins une activité dans la liste"}, status=401) + + # Création des PDF + from fiche_individu.utils import utils_activites + activite = utils_activites.Activites() + resultat = activite.Impression(liste_activites=activites_cochees, dict_options={},mode_email=True) # Mise à jour du paramètre + if not resultat: + return JsonResponse({"success": False}, status=401) + + # Création du mail + logger.debug("Création d'un nouveau mail...") + mail = Mail.objects.create( + categorie="activites", + objet="Notification d'activité", + html="", + adresse_exp=request.user.Get_adresse_exp_defaut(), + selection="NON_ENVOYE", + verrouillage_destinataires=True, + utilisateur=request.user, + ) + + # Création des destinataires et des documents joints + logger.debug("Enregistrement des destinataires et documents joints...") + liste_anomalies = [] + for IDcotisation, donnees in resultat["noms_fichiers"].items(): + inscriptions = Inscription.objects.select_related('famille').filter(activite_id=IDcotisation) + + for cotisation in inscriptions: + if cotisation.famille.mail: + destinataire = Destinataire.objects.create(categorie="famille", famille=cotisation.famille,adresse=cotisation.famille.mail) + document_joint = DocumentJoint.objects.create(nom="Activite", fichier=donnees["nom_fichier"]) + destinataire.documents.add(document_joint) + mail.destinataires.add(destinataire) + + else: + liste_anomalies.append(cotisation.famille.nom) + + if liste_anomalies: + messages.add_message(request, messages.ERROR, "Adresses mail manquantes : %s" % ", ".join(liste_anomalies)) + + # Création de l'URL pour ouvrir l'éditeur d'emails + logger.debug("Redirection vers l'éditeur d'emails...") + url = reverse_lazy("editeur_emails", kwargs={'pk': mail.pk}) + return JsonResponse({"url": url}) + +class Page(crud.Page): + model = Activite + url_liste = "editeur_emails_activites" + menu_code = "editeur_emails_activites" + +from outils.views.editeur_emails import Page_destinataires +class Liste(Page_destinataires, crud.Liste): + template_name = "outils/editeur_emails_activites.html" + model = Activite + categorie = "activites" + + def get_queryset(self): + queryset = ( + Inscription.objects + .filter(famille__mail__isnull=False) + .annotate( + id_activite=F("activite__idactivite"), # <-- ADDED + nom_activite=F("activite__nom"), + nom_groupe=F("groupe__nom"), + ) + .values("id_activite", "nom_activite", "nom_groupe") + .annotate(nombre_inscriptions=Count("idinscription")) + .order_by("nom_activite", "nom_groupe") + ) + return queryset + + def get_context_data(self, **kwargs): + + context = super(Liste, self).get_context_data(**kwargs) + context['page_titre'] = "Envoi des Inscrits aux Activites par Email" + context['box_titre'] = "Envoyer des Inscrits aux Activites par Email en lot" + context['box_introduction'] = "Cochez les activites puis cliquez sur le bouton Aperçu." + context['onglet_actif'] = "activites_email" + context['active_checkbox'] = True + context['bouton_supprimer'] = False + return context + + class datatable_class(MyDatatable): + filtres = ['id_activite','activite__nom', 'groupe__nom', 'nombre_inscriptions'] + model = None + check = columns.CheckBoxSelectColumn(label="") + c_activite_id = columns.TextColumn("Activité", sources=["id_activite"]) + + c_activite = columns.TextColumn("Activité", sources=["nom_activite"]) + c_groupes = columns.TextColumn("Groupes", sources=["nom_groupe"]) + c_nb = columns.TextColumn("Nombre d'inscriptions", sources=["nombre_inscriptions"]) + + class Meta: + structure_template = MyDatatable.structure_template + columns = ["check","c_activite_id" ,"c_activite", "c_groupes", "c_nb"] + + def get_object_pk(self, row): + # Return the real integer ID + row=row["id_activite"] + return row + + def prepare_results(self, queryset): + results = [] + for row in queryset: + results.append({ + "id_activite": row["id_activite"], + "nom_activite": row["nom_activite"], + "nom_groupe": row["nom_groupe"], + "nombre_inscriptions": row["nombre_inscriptions"], + }) + return results + + def Formate_statut(self, instance, *args, **kwargs): + if instance.statut == "attente": + return " Attente" + elif instance.statut == "refus": + return " Refus" + else: + return " Valide" \ No newline at end of file diff --git a/noethysweb/outils/views/editeur_emails_express.py b/noethysweb/outils/views/editeur_emails_express.py index 02d1e5fe..5adcb73c 100644 --- a/noethysweb/outils/views/editeur_emails_express.py +++ b/noethysweb/outils/views/editeur_emails_express.py @@ -3,13 +3,13 @@ # Noethysweb, application de gestion multi-activités. # Distribué sous licence GNU GPL. -import json -from email.utils import parseaddr from django.http import JsonResponse -from django.shortcuts import render -from core.models import ModeleEmail, Mail, Destinataire, Famille, DocumentJoint +from core.models import ModeleEmail, Mail, PieceJointe, Destinataire, Famille, Individu, Contact, AdresseMail, DocumentJoint from outils.utils import utils_email from outils.forms.editeur_emails_express import Formulaire +from django.shortcuts import render +import json, re +from email.utils import parseaddr def Envoyer_email(request): @@ -79,17 +79,6 @@ def Get_view_editeur_email(request): """ Renvoie l'éditeur d'emails dans un modal """ # Récupère d'éventuelles données donnees = json.loads(request.POST.get("donnees")) - - # Importation de la famille - if "idfamille" in donnees: - categorie = "famille" - famille = Famille.objects.get(pk=donnees["idfamille"]) - adresse = famille.mail - else: - categorie = "saisie_libre" - famille = None - adresse = donnees.get("adresse", "") - # Création du mail modele_email = ModeleEmail.objects.filter(categorie=donnees["categorie"], defaut=True).first() mail = Mail.objects.create( @@ -102,12 +91,25 @@ def Get_view_editeur_email(request): utilisateur=request.user, ) - # Création du destinataire et du document joint - destinataire = Destinataire.objects.create(categorie=categorie, famille=famille, adresse=adresse, valeurs=json.dumps(donnees["champs"])) - if "nom_fichier" in donnees: - document_joint = DocumentJoint.objects.create(nom=donnees["label_fichier"], fichier=donnees["nom_fichier"]) - destinataire.documents.add(document_joint) - mail.destinataires.add(destinataire) + if 'idfamille' in donnees: + + # Importation de la famille + famille = Famille.objects.get(pk=donnees["idfamille"]) + # Création du destinataire et du document joint + destinataire = Destinataire.objects.create(categorie="famille", famille=famille, adresse=famille.mail, valeurs=json.dumps(donnees["champs"])) + if "nom_fichier" in donnees: + document_joint = DocumentJoint.objects.create(nom=donnees["label_fichier"], fichier=donnees["nom_fichier"]) + destinataire.documents.add(document_joint) + mail.destinataires.add(destinataire) + + else : #individu + # Importation de l'individu + individu = Individu.objects.get(pk=donnees["idindividu"]) + destinataire = Destinataire.objects.create(categorie="individu", individu=individu, adresse=individu.mail, valeurs=json.dumps(donnees["champs"])) + if "nom_fichier" in donnees: + document_joint = DocumentJoint.objects.create(nom=donnees["label_fichier"], fichier=donnees["nom_fichier"]) + destinataire.documents.add(document_joint) + mail.destinataires.add(destinataire) # Prépare le context context = { @@ -115,4 +117,4 @@ def Get_view_editeur_email(request): "form": Formulaire(instance=mail, request=request), "modeles": ModeleEmail.objects.filter(categorie=request.POST.get("categorie", donnees["categorie"])), } - return render(request, 'outils/editeur_emails_express.html', context) + return render(request, 'outils/editeur_emails_express.html', context) \ No newline at end of file diff --git a/noethysweb/outils/views/editeur_emails_inscriptions.py b/noethysweb/outils/views/editeur_emails_inscriptions.py new file mode 100644 index 00000000..6dc9282f --- /dev/null +++ b/noethysweb/outils/views/editeur_emails_inscriptions.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2025 Faouzia TEKAYA. +# Noethysweb, application de gestion multi-activités. +# Distribué sous licence GNU GPL. + +import logging + +logger = logging.getLogger(__name__) +from django.urls import reverse_lazy, reverse +from core.views.mydatatableview import MyDatatable, columns, helpers +from core.views import crud +from django.db.models import Q +from core.models import Mail, DocumentJoint, Inscription, Destinataire +from django.http import JsonResponse +from django.contrib import messages +import json + +def Impression_pdf(request): + # Récupération des inscriptions cochées + inscriptions_cochees = json.loads(request.POST.get("inscriptions_cochees")) + if not inscriptions_cochees: + return JsonResponse({"erreur": "Veuillez cocher au moins une inscription dans la liste"}, status=401) + + # Création des PDF + from fiche_individu.utils import utils_inscriptions + inscription = utils_inscriptions.Inscriptions() + resultat = inscription.Impression(liste_inscriptions=inscriptions_cochees, dict_options={}, mode_email=True) + + if not resultat: + return JsonResponse({"success": False}, status=401) + + # Création du mail + logger.debug("Création d'un nouveau mail...") + mail = Mail.objects.create( + categorie="inscription", + objet="Notification d'inscription", + html="", + adresse_exp=request.user.Get_adresse_exp_defaut(), + selection="NON_ENVOYE", + verrouillage_destinataires=True, + utilisateur=request.user, + ) + + # Création des destinataires et des documents joints + logger.debug("Enregistrement des destinataires et documents joints...") + liste_anomalies = [] + for IDcotisation, donnees in resultat["noms_fichiers"].items(): + cotisation = Inscription.objects.select_related('famille').get(pk=IDcotisation) + + if cotisation.famille.mail: + destinataire = Destinataire.objects.create(categorie="famille", famille=cotisation.famille,adresse=cotisation.famille.mail) + document_joint = DocumentJoint.objects.create(nom="Inscription", fichier=donnees["nom_fichier"]) + destinataire.documents.add(document_joint) + mail.destinataires.add(destinataire) + else: + liste_anomalies.append(cotisation.famille.nom) + + if liste_anomalies: + messages.add_message(request, messages.ERROR, "Adresses mail manquantes : %s" % ", ".join(liste_anomalies)) + + # Création de l'URL pour ouvrir l'éditeur d'emails + logger.debug("Redirection vers l'éditeur d'emails...") + url = reverse_lazy("editeur_emails", kwargs={'pk': mail.pk}) + return JsonResponse({"url": url}) + +class Page(crud.Page): + model = Inscription + url_liste = "editeur_emails_inscriptions" + menu_code = "editeur_emails_inscriptions" + +from outils.views.editeur_emails import Page_destinataires +class Liste(Page_destinataires, crud.Liste): + template_name = "outils/editeur_emails_inscriptions.html" + model = Inscription + categorie = "inscriptions" + + def get_queryset(self): + condition = Q(activite__structure__in=self.request.user.structures.all()) + return Inscription.objects.select_related("famille", "individu", "groupe", "categorie_tarif", + "activite").filter(condition) + + def get_context_data(self, **kwargs): + context = super(Liste, self).get_context_data(**kwargs) + context['page_titre'] = "Envoi des Inscriptions par Email" + context['box_titre'] = "Envoyer des Inscriptions par Email en lot" + context['box_introduction'] = "Cochez des inscriptions puis cliquez sur le bouton Aperçu." + context['onglet_actif'] = "inscriptions_email" + context['active_checkbox'] = True + context['bouton_supprimer'] = False + return context + + class datatable_class(MyDatatable): + filtres = ["igenerique:individu", "fgenerique:famille", 'idinscription', 'date_debut', 'date_fin', + 'activite__nom', 'groupe__nom', 'statut', 'categorie_tarif__nom'] + check = columns.CheckBoxSelectColumn(label="") + activite = columns.TextColumn("Activité", sources=['activite__nom']) + groupe = columns.TextColumn("Groupe", sources=['groupe__nom']) + categorie_tarif = columns.TextColumn("Catégorie de tarif", sources=['categorie_tarif__nom']) + individu = columns.CompoundColumn("Individu", sources=['individu__nom', 'individu__prenom']) + famille = columns.TextColumn("Famille", sources=['famille__nom']) + + class Meta: + structure_template = MyDatatable.structure_template + columns = ["check", "idinscription", 'date_debut', 'date_fin', 'individu', 'famille', 'activite', 'groupe', + 'categorie_tarif', 'statut'] + processors = { + 'date_debut': helpers.format_date('%d/%m/%Y'), + 'date_fin': helpers.format_date('%d/%m/%Y'), + 'statut': 'Formate_statut', + } + ordering = ['date_debut'] + + def Formate_statut(self, instance, *args, **kwargs): + if instance.statut == "attente": + return " Attente" + elif instance.statut == "refus": + return " Refus" + else: + return " Valide" \ No newline at end of file diff --git a/noethysweb/outils/views/editeur_sms_express.py b/noethysweb/outils/views/editeur_sms_express.py index a728e5a8..30f05aa5 100644 --- a/noethysweb/outils/views/editeur_sms_express.py +++ b/noethysweb/outils/views/editeur_sms_express.py @@ -6,7 +6,7 @@ import json from django.http import JsonResponse from django.shortcuts import render -from core.models import ModeleSMS, SMS, DestinataireSMS, Famille +from core.models import ModeleSMS, SMS, DestinataireSMS, Famille , Individu from outils.forms.editeur_sms_express import Formulaire from outils.utils import utils_sms @@ -49,9 +49,6 @@ def Get_view_editeur_sms(request): # Récupère d'éventuelles données donnees = json.loads(request.POST.get("donnees")) - # Importation de la famille - famille = Famille.objects.get(pk=donnees["idfamille"]) - # Création du SMS modele_sms = ModeleSMS.objects.filter(categorie=donnees["categorie"], defaut=True).first() sms = SMS.objects.create( @@ -62,9 +59,20 @@ def Get_view_editeur_sms(request): utilisateur=request.user, ) - # Création du destinataire - destinataire = DestinataireSMS.objects.create(categorie="famille", famille=famille, mobile=famille.mobile, valeurs=json.dumps(donnees["champs"])) - sms.destinataires.add(destinataire) + if 'idfamille' in donnees: + + # Importation de la famille + famille = Famille.objects.get(pk=donnees["idfamille"]) + # Création du destinataire + destinataire = DestinataireSMS.objects.create(categorie="famille", famille=famille, mobile=famille.mobile, valeurs=json.dumps(donnees["champs"])) + sms.destinataires.add(destinataire) + + else : #individu + # Importation de l'individu + individu = Individu.objects.get(pk=donnees["idindividu"]) + # Création du destinataire + destinataire = DestinataireSMS.objects.create(categorie="individu", individu=individu, mobile=individu.mobile, valeurs=json.dumps(donnees["champs"])) + sms.destinataires.add(destinataire) # Prépare le context context = { @@ -72,4 +80,4 @@ def Get_view_editeur_sms(request): "form": Formulaire(instance=sms, request=request), "modeles": ModeleSMS.objects.filter(categorie=request.POST.get("categorie", donnees["categorie"])), } - return render(request, 'outils/editeur_sms_express.html', context) + return render(request, 'outils/editeur_sms_express.html', context) \ No newline at end of file diff --git a/noethysweb/outils/views/historique.py b/noethysweb/outils/views/historique.py index f36e4a17..af64c7c9 100644 --- a/noethysweb/outils/views/historique.py +++ b/noethysweb/outils/views/historique.py @@ -32,10 +32,13 @@ class Liste(Page, crud.Liste): template_name = "outils/historique.html" def get_queryset(self): - conditions = (Q(utilisateur__structures__in=self.request.user.structures.all()) | Q(utilisateur__categorie="famille")) + conditions = ( + Q(utilisateur__structures__in=self.request.user.structures.all()) | + Q(utilisateur__categorie__in=["famille", "individu"]) + ) self.afficher_dernier_mois = utils_parametres.Get(nom="afficher_dernier_mois", categorie="historique", utilisateur=self.request.user, valeur=True) if self.afficher_dernier_mois: - conditions &= Q(horodatage__date__gte=datetime.date.today() - datetime.timedelta(days=7)) + conditions &= Q(horodatage__date__gte=datetime.date.today() - datetime.timedelta(days=14)) return Historique.objects.select_related("famille", "individu", "collaborateur", "utilisateur").filter(conditions, self.Get_filtres("Q")) def get_context_data(self, **kwargs): @@ -71,7 +74,15 @@ def Formate_collaborateur(self, instance, **kwargs): return instance.collaborateur.Get_nom() if instance.collaborateur else "" def Formate_utilisateur(self, instance, **kwargs): - if instance.utilisateur.categorie == "utilisateur": - return instance.utilisateur.username if instance.utilisateur else "" - else: - return instance.utilisateur.famille + if instance.utilisateur: + # Vérifie si la catégorie est "utilisateur" + if instance.utilisateur.categorie == "utilisateur": + return instance.utilisateur.username + # Vérifie si la catégorie est "famille" + elif instance.utilisateur.categorie == "famille": + return instance.utilisateur.famille.nom if instance.utilisateur.famille else "" + # Vérifie si la catégorie est "individu" + elif instance.utilisateur.categorie == "individu": + return instance.utilisateur.individu.Get_nom() if instance.utilisateur.individu else "" + # Si aucune catégorie n'est définie, retourne une chaîne vide + return "" \ No newline at end of file diff --git a/noethysweb/parametrage/forms/outils_parametres_generaux.py b/noethysweb/parametrage/forms/outils_parametres_generaux.py new file mode 100644 index 00000000..3013552e --- /dev/null +++ b/noethysweb/parametrage/forms/outils_parametres_generaux.py @@ -0,0 +1,67 @@ +# Copyright (c) 2024 GIP RECIA. +# Noethysweb, application de gestion multi-activités. +# Distribué sous licence GNU GPL. + +from django import forms +from core.forms.base import FormulaireBase +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Layout, Hidden, Submit, HTML, Fieldset, Div, ButtonHolder +from crispy_forms.bootstrap import Field, InlineRadios +from core.utils.utils_commandes import Commandes +from core.models import PortailParametre +from core.utils.utils_parametres_generaux import LISTE_PARAMETRES + + +LISTE_RUBRIQUES = [ + ("Envoi emails par lots", ["emails_individus_afficher_page", "emails_familles_afficher_page", "emails_activites_afficher_page" + ,"emails_inscriptions_afficher_page","emails_collaborateurs_afficher_page","emails_liste_diffusion_afficher_page"]), +] + +class Formulaire(FormulaireBase, forms.Form): + def __init__(self, *args, **kwargs): + super(Formulaire, self).__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.form_id = 'emails_parametres_form' + self.helper.form_method = 'post' + self.helper.form_class = 'form-horizontal' + self.helper.label_class = 'col-md-2' + self.helper.field_class = 'col-md-10' + + # Initialisation du layout + self.helper.layout = Layout() + self.helper.layout.append(Commandes(annuler_url="{% url 'outils_parametres_generaux' %}", ajouter=False)) + + dict_parametres = {parametre.code: parametre for parametre in LISTE_PARAMETRES} + for parametre_db in PortailParametre.objects.all(): + if parametre_db.code in dict_parametres: + dict_parametres[parametre_db.code].From_db(parametre_db.valeur) + + # Création des fields + for titre_rubrique, liste_parametres in LISTE_RUBRIQUES: + liste_fields = [] + for code_parametre in liste_parametres: + self.fields[code_parametre] = dict_parametres[code_parametre].Get_ctrl() + self.fields[code_parametre].initial = dict_parametres[code_parametre].valeur + liste_fields.append(Field(code_parametre)) + self.helper.layout.append(Fieldset(titre_rubrique, *liste_fields)) + + self.helper.layout.append(HTML("| {% trans "Activité" %} | -{% trans "Groupe" %} | -
|---|
| {{ inscription.activite.nom }} | -{{ inscription.groupe.nom }} | +{% trans "Activité" %} | +{% trans "Groupe" %} |
|---|
| Famille d'individu | Individu à inscrire | Activité souhaitée | ||
|---|---|---|---|---|
| {{ demande.individu.prenom }} | +{{ demande.famille }} | +{{ demande.individu.nom }} {{ demande.individu.prenom }} | {{ demande.nom_activite }} | |
| {{ cotisation.date_debut|date:'d/m/Y' }} | -{{ cotisation.date_fin|date:'d/m/Y' }} | -{{ cotisation.prestation.label }} | -{{ cotisation.numero|default:"" }} | -{% if cotisation.individu %}{{ cotisation.individu.prenom }}{% else %}{% trans "Famille" %}{% endif %} | -
| {{ cotisation.date_debut|date:'d/m/Y' }} | +{{ cotisation.date_fin|date:'d/m/Y' }} | +{{ cotisation.prestation.label }} | +{{ cotisation.numero|default:"" }} | +{% if cotisation.individu %}{{ cotisation.individu.nom }}{{ cotisation.individu.prenom }}{% else %}{{ cotisation.famille.nom }}{% endif %} | +
- {{ request.user.famille }} -
+ {% if request.user.famille %} +{{ request.user.famille }}
+ {% else %} +{{ request.user.individu }}
+ {% endif %}| {{ reglement.date|date:'d/m/Y' }} | {{ reglement.mode }} | @@ -67,36 +74,18 @@ {% endfor %}
-
- {% trans "Famille" %}
- |
- - {% blocktrans %}Fiche famille{% endblocktrans %} - | -
-
-
|
- - - - - | -||||||||
|
- |
- - {% if rattachement.categorie == 1 %}{% blocktrans %}Fiche représentant{% endblocktrans %}{% endif %} - {% if rattachement.categorie == 2 %}{% blocktrans %}Fiche enfant{% endblocktrans %}{% endif %} - {% if rattachement.categorie == 3 %}{% blocktrans %}Fiche contact{% endblocktrans %}{% endif %} - | -
-
-
+
+
+
|