diff --git a/lemarche/static/itou_marche/admin.css b/lemarche/static/itou_marche/admin.css index 93d06ba66..488e2255d 100644 --- a/lemarche/static/itou_marche/admin.css +++ b/lemarche/static/itou_marche/admin.css @@ -23,3 +23,48 @@ div.custom-checkbox-select-multiple > div > label { width: 100%; /* 160px default ; to avoid line breaks */ } + +/* Modal style for tender change admin */ +.tender-send-modal-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; +} + +.tender-send-modal-content { + position: absolute; + top: 45%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #212121; + margin: auto; + max-width: 500px; + width: 90%; + padding: 20px; + border-radius: 4px; +} + +#submit-tender { + margin-bottom: 0; +} + +#submit-button.submit-button { + background-color: #ba2121; +} + +#submit-button.submit-button:hover { + background-color: #a41515; +} + +#cancel-button { + margin-left: auto; + padding: 10px 15px; +} + +#cancel-button:hover { + padding: 10px 15px; +} diff --git a/lemarche/static/js/admin_tender_confirmation.js b/lemarche/static/js/admin_tender_confirmation.js new file mode 100644 index 000000000..b12f5c673 --- /dev/null +++ b/lemarche/static/js/admin_tender_confirmation.js @@ -0,0 +1,55 @@ +document.addEventListener('DOMContentLoaded', function () { + const modal = document.getElementById('tender-send-confirmation-modal'); + const confirmBtn = document.getElementById('submit-button'); + const cancelBtn = document.getElementById('cancel-button'); + + function openModal(recipient, title) { + const messageElement = document.getElementById('tender-send-message'); + + // Set an attribute 'name' depending on the recipient + if (recipient === 'siaes') { + messageElement.textContent = "Le besoin « " + title + " » sera envoyé aux structures."; + confirmBtn.setAttribute('name', '_validate_send_to_siaes'); + } else if (recipient === 'partners') { + messageElement.textContent = "Le besoin « " + title + " » sera envoyé aux partenaires."; + confirmBtn.setAttribute('name', '_validate_send_to_commercial_partners'); + } + modal.style.display = 'block'; + } + + // data-recipent attribute determines the recipient of the tender + const buttons = document.querySelectorAll('input[data-recipient]'); + buttons.forEach(function(button) { + button.addEventListener('click', function(e) { + e.preventDefault(); // Prevent instant form submission + const recipient = button.getAttribute('data-recipient'); + const title = button.getAttribute('data-title'); + openModal(recipient, title); + }); + }); + + function closeModal() { + modal.style.display = 'none'; + } + + // Two different ways to close the modal: + // click on the cancel button + cancelBtn.addEventListener('click', function(e) { + e.preventDefault(); // Prevent page from scrolling up + closeModal(); + }); + + // click outside the modal + window.addEventListener('click', function (e) { + if (e.target === modal) { + closeModal(); + } + }); + + // Action confirmation + confirmBtn.addEventListener('click', function () { + if (formToSubmit) { + formToSubmit.submit(); + } + }); +}); diff --git a/lemarche/templates/tenders/admin_change_form.html b/lemarche/templates/tenders/admin_change_form.html index a9e8a778e..0823e794c 100644 --- a/lemarche/templates/tenders/admin_change_form.html +++ b/lemarche/templates/tenders/admin_change_form.html @@ -56,8 +56,21 @@ {% endif %} {% else %}

L'envoi des besoins 'validés' se fait toutes les 5 minutes, du Lundi au Vendredi, entre 9h et 17h

- + + {% endif %} {% endif %} + + + {% endblock %} diff --git a/lemarche/tenders/admin.py b/lemarche/tenders/admin.py index c5c06b594..81790de5e 100644 --- a/lemarche/tenders/admin.py +++ b/lemarche/tenders/admin.py @@ -526,6 +526,9 @@ class TenderAdmin(FieldsetsInlineMixin, admin.ModelAdmin): change_form_template = "tenders/admin_change_form.html" + class Media: + js = ["/static/js/admin_tender_confirmation.js"] + def get_queryset(self, request): qs = super().get_queryset(request) qs = qs.select_related("author") @@ -783,14 +786,25 @@ def response_change(self, request, obj: Tender): obj.set_siae_found_list() self.message_user(request, "Les structures concernées ont été mises à jour.") return HttpResponseRedirect("./#structures") # redirect to structures sections - if request.POST.get("_validate_tender"): + if request.POST.get("_validate_send_to_siaes"): + obj.set_validated() + if obj.amount_int > settings.BREVO_TENDERS_MIN_AMOUNT_TO_SEND: + api_brevo.create_deal(tender=obj, owner_email=request.user.email) + # we link deal(tender) with author contact + api_brevo.link_deal_with_contact_list(tender=obj) + self.message_user(request, "Ce dépôt de besoin a été synchronisé avec Brevo") + self.message_user(request, "Ce dépôt de besoin a été validé. Il sera envoyé en temps voulu :)") + return HttpResponseRedirect(".") + if request.POST.get("_validate_send_to_commercial_partners"): obj.set_validated() + obj.send_to_commercial_partners_only = True if obj.amount_int > settings.BREVO_TENDERS_MIN_AMOUNT_TO_SEND: api_brevo.create_deal(tender=obj, owner_email=request.user.email) # we link deal(tender) with author contact api_brevo.link_deal_with_contact_list(tender=obj) self.message_user(request, "Ce dépôt de besoin a été synchronisé avec Brevo") self.message_user(request, "Ce dépôt de besoin a été validé. Il sera envoyé en temps voulu :)") + obj.save() return HttpResponseRedirect(".") elif request.POST.get("_restart_tender"): restart_send_tender_task(tender=obj) diff --git a/lemarche/tenders/migrations/0092_tender_send_to_commercial_partners_only.py b/lemarche/tenders/migrations/0092_tender_send_to_commercial_partners_only.py new file mode 100644 index 000000000..66803fccc --- /dev/null +++ b/lemarche/tenders/migrations/0092_tender_send_to_commercial_partners_only.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.15 on 2024-09-27 12:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("tenders", "0091_tender_is_followed_by_us_tender_is_reserved_tender_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="tender", + name="send_to_commercial_partners_only", + field=models.BooleanField(default=False, verbose_name="Envoyer uniquement aux partenaires externes"), + ), + ] diff --git a/lemarche/tenders/models.py b/lemarche/tenders/models.py index 3960b74f3..89a563494 100644 --- a/lemarche/tenders/models.py +++ b/lemarche/tenders/models.py @@ -87,6 +87,7 @@ def validated_sent_batch(self): & Q(siae_detail_contact_click_count_annotated__lte=F("limit_nb_siae_interested")) & ~Q(siae_count_annotated=F("siae_email_send_count_annotated")) & Q(last_sent_at__lt=yesterday) + & Q(send_to_commercial_partners_only=False) ) ) @@ -632,6 +633,9 @@ class Tender(models.Model): ) # could become foreign key # Admin specific for tenders is_reserved_tender = models.BooleanField("Appel d'offre reservé", null=True) + send_to_commercial_partners_only = models.BooleanField( + "Envoyer uniquement aux partenaires externes", default=False + ) # partner data partner_approch_id = models.IntegerField("Partenaire APProch : ID", blank=True, null=True) diff --git a/lemarche/tenders/tests/test_models.py b/lemarche/tenders/tests/test_models.py index 73b62511b..97ee5d791 100644 --- a/lemarche/tenders/tests/test_models.py +++ b/lemarche/tenders/tests/test_models.py @@ -341,6 +341,7 @@ def test_validated_sent_batch(self): validated_at=None, first_sent_at=None, last_sent_at=None, + send_to_commercial_partners_only=False, ) TenderFactory( siaes=[siae], @@ -349,6 +350,7 @@ def test_validated_sent_batch(self): validated_at=timezone.now(), first_sent_at=None, last_sent_at=None, + send_to_commercial_partners_only=False, ) TenderFactory( siaes=[siae], @@ -357,6 +359,7 @@ def test_validated_sent_batch(self): validated_at=timezone.now(), first_sent_at=timezone.now(), last_sent_at=timezone.now(), + send_to_commercial_partners_only=False, ) TenderFactory( siaes=[siae], @@ -365,7 +368,9 @@ def test_validated_sent_batch(self): validated_at=one_hour_ago, first_sent_at=one_hour_ago, last_sent_at=one_hour_ago, + send_to_commercial_partners_only=False, ) + # This tender would be sent if send_to_commercial_partners_only was False TenderFactory( siaes=[siae], version=1, @@ -373,8 +378,9 @@ def test_validated_sent_batch(self): validated_at=two_days_ago, first_sent_at=two_days_ago, last_sent_at=two_days_ago, + send_to_commercial_partners_only=True, ) - self.assertEqual(Tender.objects.validated_sent_batch().count(), 1) + self.assertEqual(Tender.objects.validated_sent_batch().count(), 0) def test_is_not_outdated(self): TenderFactory(deadline_date=None) @@ -1085,17 +1091,36 @@ def test_edit_form_no_matching_on_validate_submission(self): self.form_data | { "title": "New title", - "_validate_tender": "Valider (sauvegarder) et envoyer aux structures", + "_validate_send_to_siaes": "Valider (sauvegarder) et envoyer aux structures", }, follow=True, ) tender_response = response.context_data["adminform"].form.instance self.assertEqual(tender_response.id, self.tender.id) self.assertContains(response, "Validé le ") + self.assertFalse(tender_response.send_to_commercial_partners_only) self.assertTrue(hasattr(tender_response, "siae_count_annotated")) self.assertEqual(tender_response.siae_count_annotated, 1) self.assertEqual(tender_response.siae_count_annotated, self.tender.tendersiae_set.count()) + def test_edit_form_validate_submission_to_commercial_partners(self): + self.client.force_login(self.user) + tender_update_post_url = get_admin_change_view_url(self.tender) + + response = self.client.post( + tender_update_post_url, + self.form_data + | { + "title": "New title", + "_validate_send_to_commercial_partners": "Valider (sauvegarder) et envoyer aux partenaires.", + }, + follow=True, + ) + tender_response = response.context_data["adminform"].form.instance + self.assertEqual(tender_response.id, self.tender.id) + self.assertContains(response, "Validé le ") + self.assertTrue(tender_response.send_to_commercial_partners_only) + class TenderUtilsTest(TestCase): @classmethod