diff --git a/lemarche/conversations/admin.py b/lemarche/conversations/admin.py index cab8b4f50..2471292e3 100644 --- a/lemarche/conversations/admin.py +++ b/lemarche/conversations/admin.py @@ -150,7 +150,7 @@ class TemplateTransactionalAdmin(admin.ModelAdmin): ] search_fields = ["id", "name", "code", "mailjet_id", "brevo_id"] - readonly_fields = ["code", "template_transactional_send_log_count_with_link", "created_at", "updated_at"] + readonly_fields = ["template_transactional_send_log_count_with_link", "created_at", "updated_at"] fieldsets = ( (None, {"fields": ("name", "code", "description")}), diff --git a/lemarche/templates/tenders/_email_sent_for_modification.html b/lemarche/templates/tenders/_email_sent_for_modification.html new file mode 100644 index 000000000..089bf1181 --- /dev/null +++ b/lemarche/templates/tenders/_email_sent_for_modification.html @@ -0,0 +1,15 @@ +Bonjour, + +Merci d’avoir publié votre besoin sur le Marché de l’inclusion. Après vérification, nous vous informons que votre publication n’a pas pu être validée pour l'une des raisons suivantes : + +1. **Montant incohérent ou incorrect** : Le montant indiqué pourrait ne pas correspondre à la réalité de votre besoin ou présenter des incohérences. +2. **Informations incomplètes** : Certaines informations essentielles pourraient manquer, empêchant les prestataires de bien comprendre votre besoin et de formuler une réponse adaptée. +3. **Utilisateur non professionnel** : Nous n'acceptons actuellement que les demandes de devis provenant de professionnels. + +Nous vous encourageons à vérifier ces éléments et à republier votre besoin si vous remplissez les conditions requises. + +Merci pour votre engagement et votre envie de faire avancer l'inclusion avec nous ! + +À bientôt, + +L’équipe du Marché de l’inclusion diff --git a/lemarche/tenders/admin.py b/lemarche/tenders/admin.py index 9b518ef71..4eb2998a1 100644 --- a/lemarche/tenders/admin.py +++ b/lemarche/tenders/admin.py @@ -8,6 +8,7 @@ from django.db.models import Q from django.http import HttpResponseRedirect from django.urls import reverse +from django.utils import timezone from django.utils.html import format_html from django_admin_filters import MultiChoice from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin @@ -287,6 +288,7 @@ class TenderAdmin(FieldsetsInlineMixin, admin.ModelAdmin): "start_working_date_in_list", "siae_count_annotated_with_link_in_list", "siae_detail_contact_click_count_annotated_with_link_in_list", + "email_sent_for_modification", "is_validated_or_sent", "is_followed_by_us", ] @@ -294,6 +296,7 @@ class TenderAdmin(FieldsetsInlineMixin, admin.ModelAdmin): list_filter = [ AmountCustomFilter, ("kind", KindFilter), + "email_sent_for_modification", "is_followed_by_us", AuthorKindFilter, "status", @@ -498,6 +501,7 @@ class TenderAdmin(FieldsetsInlineMixin, admin.ModelAdmin): { "fields": ( "admins", + "email_sent_for_modification", "is_followed_by_us", "proj_resulted_in_reserved_tender", "proj_link_to_tender", @@ -571,6 +575,18 @@ def save_model(self, request, obj: Tender, form, change): """ if not obj.id and not obj.author_id: obj.author = request.user + + if obj.status == tender_constants.STATUS_PUBLISHED: + obj.published_at = timezone.now() + + if obj.published_at: + obj.logs.append( + { + "Statut": "published", + "Date de publication": obj.published_at.isoformat(), + "Détails": f"Le besoin a été publié par {request.user.full_name}.", + } + ) obj.save() def save_formset(self, request, form, formset, change): diff --git a/lemarche/tenders/migrations/0095_tender_email_sent_for_modification.py b/lemarche/tenders/migrations/0095_tender_email_sent_for_modification.py new file mode 100644 index 000000000..efc391d98 --- /dev/null +++ b/lemarche/tenders/migrations/0095_tender_email_sent_for_modification.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.15 on 2024-11-07 13:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("tenders", "0094_add_templatetransactional_tender_author_commercial_partners"), + ] + + operations = [ + migrations.AddField( + model_name="tender", + name="email_sent_for_modification", + field=models.BooleanField(default=False, help_text="E-mail envoyé pour modification"), + ), + ] diff --git a/lemarche/tenders/migrations/0096_alter_tender_email_sent_for_modification.py b/lemarche/tenders/migrations/0096_alter_tender_email_sent_for_modification.py new file mode 100644 index 000000000..93c4cd89c --- /dev/null +++ b/lemarche/tenders/migrations/0096_alter_tender_email_sent_for_modification.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.15 on 2024-11-23 13:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("tenders", "0095_tender_email_sent_for_modification"), + ] + + operations = [ + migrations.AlterField( + model_name="tender", + name="email_sent_for_modification", + field=models.BooleanField(default=False, verbose_name="Besoin non valide"), + ), + ] diff --git a/lemarche/tenders/migrations/0097_remove_tender_email_sent_for_modification_and_more.py b/lemarche/tenders/migrations/0097_remove_tender_email_sent_for_modification_and_more.py new file mode 100644 index 000000000..572285761 --- /dev/null +++ b/lemarche/tenders/migrations/0097_remove_tender_email_sent_for_modification_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.15 on 2024-11-27 21:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("tenders", "0096_alter_tender_email_sent_for_modification"), + ] + + operations = [ + migrations.RemoveField( + model_name="tender", + name="email_sent_for_modification", + ), + migrations.AddField( + model_name="tender", + name="is_data_valid", + field=models.BooleanField(null=True, verbose_name="Besoin valide"), + ), + ] diff --git a/lemarche/tenders/migrations/0098_remove_tender_is_data_valid_and_more.py b/lemarche/tenders/migrations/0098_remove_tender_is_data_valid_and_more.py new file mode 100644 index 000000000..b93aadf54 --- /dev/null +++ b/lemarche/tenders/migrations/0098_remove_tender_is_data_valid_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.15 on 2024-11-28 11:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("tenders", "0097_remove_tender_email_sent_for_modification_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="tender", + name="is_data_valid", + ), + migrations.AddField( + model_name="tender", + name="email_sent_for_modification", + field=models.BooleanField(default=False, verbose_name="Modifications requises"), + ), + ] diff --git a/lemarche/tenders/models.py b/lemarche/tenders/models.py index 582364653..71e14fe3d 100644 --- a/lemarche/tenders/models.py +++ b/lemarche/tenders/models.py @@ -624,6 +624,7 @@ class Tender(models.Model): ) # admins is_followed_by_us = models.BooleanField("Suivi par l'équipe", null=True) + email_sent_for_modification = models.BooleanField("Modifications requises", default=False) # Admin specific for proj proj_resulted_in_reserved_tender = models.BooleanField( "Abouti à un appel d’offre (uniquement sourcing)", null=True diff --git a/lemarche/www/pages/views.py b/lemarche/www/pages/views.py index a2aff8548..c8be63639 100644 --- a/lemarche/www/pages/views.py +++ b/lemarche/www/pages/views.py @@ -9,6 +9,7 @@ from django.utils import timezone from django.views.generic import FormView, ListView, TemplateView, View from django.views.generic.edit import FormMixin +from wagtail.models import Site as WagtailSite from lemarche.perimeters.models import Perimeter from lemarche.sectors.models import Sector @@ -29,8 +30,6 @@ from lemarche.www.tenders.utils import create_tender_from_dict, get_or_create_user_from_anonymous_content from lemarche.www.tenders.views import TenderCreateMultiStepView -from wagtail.models import Site as WagtailSite - class ContactView(SuccessMessageMixin, FormView): template_name = "pages/contact.html" @@ -281,6 +280,14 @@ def csrf_failure(request, reason=""): # noqa C901 elif key_cleaned == "is_draft": tender_dict["status"] = tender_constants.STATUS_DRAFT tender_dict["published_at"] = None + elif key_cleaned == "is_published": + tender_dict["logs"] = [ + { + "Statut": "published", + "Date de publication": tender_dict["published_at"].isoformat(), + "Détails": f"Le besoin a été publié par {request.user.full_name}.", + } + ] else: # response_kind, marche_benefits tender_dict[key_cleaned] = list() if value[0] == "" else value # get user diff --git a/lemarche/www/tenders/tasks.py b/lemarche/www/tenders/tasks.py index 0a0c561b8..8fb37a91f 100644 --- a/lemarche/www/tenders/tasks.py +++ b/lemarche/www/tenders/tasks.py @@ -537,6 +537,37 @@ def send_tenders_author_feedback_or_survey(tender: Tender, kind="feedback_30d"): ) +def send_tender_author_modification_request(tender: Tender): + """ + Send an email to the author of a Tender notifying them that their submission is invalid and requires modification. + """ + email_subject = f"Marché de l'inclusion : dépôt de besoin invalide : {tender.get_kind_display()}" + recipient_list = whitelist_recipient_list([tender.author.email]) + if len(recipient_list): + recipient_email = recipient_list[0] + recipient_name = tender.author.full_name + + variables = { + "TENDER_ID": tender.id, + "TENDER_TITLE": tender.title, + "TENDER_CREATED_AT": tender.created_at.strftime("%d %B %Y"), + "TENDER_AUTHOR_ID": tender.author.id, + "TENDER_AUTHOR_FIRST_NAME": tender.author.first_name, + } + + email_template = TemplateTransactional.objects.get(code="TENDER_AUTHOR_MODIFICATION_REQUEST") + + if not tender.contact_notifications_disabled: + email_template.send_transactional_email( + email_subject=email_subject, + recipient_email=recipient_email, + recipient_name=recipient_name, + variables=variables, + recipient_content_object=tender.author, + parent_content_object=tender, + ) + + def send_tenders_siaes_survey(tender: Tender, kind="transactioned_question_7d"): tendersiae_qs = TenderSiae.objects.filter(tender=tender) diff --git a/lemarche/www/tenders/views.py b/lemarche/www/tenders/views.py index 0aa63d0c7..ab26ce8cc 100644 --- a/lemarche/www/tenders/views.py +++ b/lemarche/www/tenders/views.py @@ -197,6 +197,13 @@ def save_instance_tender(self, tender_dict: dict, form_dict: dict, is_draft: boo # update self.instance.status = tender_status self.instance.published_at = tender_published_at + self.instance.logs.append( + { + "Statut": "published", + "Date de publication": self.instance.published_at.isoformat(), + "Détails": f"Le besoin a été publié par {self.request.user.full_name}.", + } + ) sectors = None for step, model_form in form_dict.items(): if model_form.has_changed():