+ {% comment %} admin info {% endcomment %}
{% if is_admin %}
{% include "tenders/_detail_admin_extra_info.html" with tender=tender %}
{% endif %}
+ {% comment %} details author {% endcomment %}
{% if user == tender.author %}
- {% if tender.is_draft %}
-
-
-
-
-
-
-
Votre {{ tender_kind_display|default:tender.get_kind_display }} est encore en brouillon. Modifiez-le pour le publier.
-
-
-
-
-
- Modifier
-
- {% endif %}
- {% if tender.is_pending_validation %}
-
-
-
-
-
-
-
Votre {{ tender_kind_display|default:tender.get_kind_display }} est en cours de validation.
-
-
-
- {% endif %}
- {% if tender.is_validated %}
-
-
-
-
-
-
-
Votre {{ tender_kind_display|default:tender.get_kind_display }} est validé et envoyé !
-
-
-
-
-
- {{ tender.siae_email_send_date_count }} prestataire{{ tender.siae_email_send_date_count|pluralize }} ciblé{{ tender.siae_email_send_date_count|pluralize }}
-
-
-
- {{ tender.siae_email_link_click_date_or_detail_display_date_count }} prestataire{{ tender.siae_email_link_click_date_or_detail_display_date_count|pluralize }} qui {{ tender.siae_email_link_click_date_or_detail_display_date_count|pluralize:'a,ont' }} vu
-
-
-
- {{ tender.siae_detail_contact_click_date_count }} prestataire{{ tender.siae_detail_contact_click_date_count|pluralize }} intéressé{{ tender.siae_detail_contact_click_date_count|pluralize }}
-
- {% endif %}
+ {% include "tenders/_detail_side_infos_author.html" %}
{% else %}
{% if not tender.deadline_date_outdated %}
+ {% comment %} stats of views {% endcomment %}
{% if tender.siae_email_link_click_date_or_detail_display_date_count > 0 %}
{% endif %}
- {% if user.is_authenticated %}
+ {% if user.is_authenticated and not tender.response_is_anonymous %}
+ {% comment %} infos for partner {% endcomment %}
{% if user.kind == user.KIND_PARTNER %}
{% if not user_partner_can_display_tender_contact_details %}
@@ -139,6 +94,7 @@
{% endif %}
+ {% comment %} infos for siaes {% endcomment %}
{% elif user.kind == user.KIND_SIAE %}
{% if not user.has_siae %}
@@ -153,18 +109,26 @@
Besoin d'aide ? contacter le support via le chat en ligne qui se trouve en bas à droite.
- {% elif not user_siae_has_detail_contact_click_date %}
+ {% elif not siae_has_detail_contact_click_date %}
{% include "tenders/_detail_cta.html" with tender=tender user_can_click=True %}
{% include "tenders/_detail_cta_cocontracting.html" with tender=tender %}
{% endif %}
{% endif %}
- {% elif siae_id %}
+ {% comment %} if siae_id {% endcomment %}
+ {% elif siae_id and not tender.response_is_anonymous %}
{% if not siae_has_detail_contact_click_date %}
{% include "tenders/_detail_cta.html" with tender=tender user_can_click=True siae_id=siae_id %}
{% include "tenders/_detail_cta_cocontracting.html" with tender=tender siae_id=siae_id %}
{% else %}
{% include "tenders/_detail_contact.html" with tender=tender %}
{% endif %}
+ {% comment %} for anonymous tenders {% endcomment %}
+ {% elif tender.response_is_anonymous and siae_id or tender.response_is_anonymous and user.is_authenticated %}
+ {% if not siae_has_detail_contact_click_date %}
+ {% include "tenders/_detail_cta.html" with tender=tender user_can_click=True tender_is_anonymous=True %}
+ {% else %}
+ {% include "tenders/_detail_success_contact.html" %}
+ {% endif %}
{% else %}
{% include "tenders/_detail_cta.html" with tender=tender user_can_click=False %}
{% endif %}
diff --git a/lemarche/tenders/admin.py b/lemarche/tenders/admin.py
index 7580e8dd9..99197f88e 100644
--- a/lemarche/tenders/admin.py
+++ b/lemarche/tenders/admin.py
@@ -1,5 +1,6 @@
from ckeditor.widgets import CKEditorWidget
from django import forms
+from django.conf import settings
from django.contrib import admin
from django.contrib.contenttypes.admin import GenericTabularInline
from django.db import models
@@ -273,6 +274,7 @@ class TenderAdmin(FieldsetsInlineMixin, admin.ModelAdmin):
"contact_email",
"contact_phone",
"response_kind",
+ "response_is_anonymous",
),
},
),
@@ -487,7 +489,8 @@ def response_change(self, request, obj: Tender):
if request.POST.get("_validate_tender"):
update_and_send_tender_task(tender=obj)
self.message_user(request, "Ce dépôt de besoin a été validé et envoyé aux structures")
- api_hubspot.create_deal_from_tender(tender=obj)
+ if settings.BITOUBI_ENV == "prod":
+ api_hubspot.create_deal_from_tender(tender=obj)
return HttpResponseRedirect(".")
elif request.POST.get("_restart_tender"):
diff --git a/lemarche/tenders/migrations/0061_tender_response_is_anonymous.py b/lemarche/tenders/migrations/0061_tender_response_is_anonymous.py
new file mode 100644
index 000000000..040cadd15
--- /dev/null
+++ b/lemarche/tenders/migrations/0061_tender_response_is_anonymous.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.2.5 on 2023-10-17 14:20
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("tenders", "0060_tender_survey_transactioned_amount_and_feedback"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="tender",
+ name="response_is_anonymous",
+ field=models.BooleanField(default=False, verbose_name="Je souhaite rester anonyme"),
+ ),
+ ]
diff --git a/lemarche/tenders/models.py b/lemarche/tenders/models.py
index 4bafe953d..149d02be5 100644
--- a/lemarche/tenders/models.py
+++ b/lemarche/tenders/models.py
@@ -261,6 +261,8 @@ class Tender(models.Model):
blank=True,
default=list,
)
+
+ response_is_anonymous = models.BooleanField(verbose_name="Je souhaite rester anonyme", blank=False, default=False)
accept_cocontracting = models.BooleanField(
verbose_name="Ouvert à la co-traitance",
help_text="Ce besoin peut être répondu par plusieurs prestataires (co-traitance ou sous-traitance)",
@@ -543,6 +545,8 @@ def cta_card_paragraph_text(self):
return "Accéder à l'appel d'offres afin d'y répondre."
elif self.kind == tender_constants.KIND_QUOTE:
return "Accéder aux coordonnées du client afin de lui envoyer un devis."
+ elif self.kind == tender_constants.KIND_PROJECT and self.response_is_anonymous:
+ return "Manifestez votre intérêt au client. S’il est intéressé, le client vous recontactera via les coordonnées présentes sur votre fiche commerciale." # noqa
elif self.kind == tender_constants.KIND_PROJECT:
return "Accéder aux coordonnées du client afin de lui présenter vos services et produits."
# just in case
@@ -552,6 +556,8 @@ def cta_card_paragraph_text(self):
def cta_card_button_text(self):
if self.kind == tender_constants.KIND_TENDER:
return "Voir l'appel d'offres"
+ elif self.kind == tender_constants.KIND_PROJECT and self.response_is_anonymous:
+ return "Je suis intéressé !"
return "Accéder aux coordonnées"
@cached_property
diff --git a/lemarche/www/tenders/forms.py b/lemarche/www/tenders/forms.py
index 61cc5e349..dc4e57de9 100644
--- a/lemarche/www/tenders/forms.py
+++ b/lemarche/www/tenders/forms.py
@@ -107,6 +107,10 @@ def __init__(self, kind, questions_list=None, *args, **kwargs):
if self.kind != tender_constants.KIND_TENDER:
self.fields["external_link"].label = "Lien à partager"
self.fields["external_link"].help_text = None
+
+ if self.kind == tender_constants.KIND_TENDER:
+ self.fields["external_link"].required = True
+
self.fields["amount"].label = "Montant € estimé de votre besoin"
self.fields["accept_share_amount"].label = self.fields["accept_share_amount"].help_text
self.fields["external_link"].widget.attrs["placeholder"] = "https://www.example.fr"
@@ -116,10 +120,10 @@ def clean_questions_list(self):
questions = self.cleaned_data["questions_list"]
if questions is None:
return questions
- elif type(questions) is not list:
+ elif not isinstance(questions, list):
raise ValueError("It's not a list")
for index, question in enumerate(questions):
- if type(question) is not dict:
+ if not isinstance(question, dict):
raise ValueError("Bad format")
if not question.get("text"):
questions.pop(index)
@@ -157,8 +161,8 @@ class TenderCreateStepContactForm(forms.ModelForm):
label="Comment les prestataires doivent vous répondre",
choices=Tender._meta.get_field("response_kind").base_field.choices,
widget=forms.CheckboxSelectMultiple,
+ required=False,
)
-
contact_company_name = forms.CharField(label="Votre entreprise", widget=forms.HiddenInput(), required=False)
class Meta:
@@ -169,6 +173,7 @@ class Meta:
"contact_email",
"contact_phone",
"response_kind",
+ "response_is_anonymous",
]
labels = {
"contact_first_name": "Prénom",
@@ -183,8 +188,10 @@ def __init__(self, external_link, user: User, *args, **kwargs):
self.user = user
user_is_anonymous = not user.is_authenticated
+ # display response_is_anonymous if the tender is a project
+ if self.instance and self.instance.kind != tender_constants.KIND_PROJECT:
+ self.fields["response_is_anonymous"].widget = forms.HiddenInput()
# required fields
- self.fields["response_kind"].required = True
if user_is_anonymous:
self.fields["contact_first_name"].required = True
self.fields["contact_last_name"].required = True
@@ -205,6 +212,9 @@ def __init__(self, external_link, user: User, *args, **kwargs):
def clean(self):
super().clean()
+ if not self.cleaned_data.get("response_kind") and not self.cleaned_data.get("response_is_anonymous"):
+ self.add_error("response_kind", "Ce champ est obligatoire.")
+
if not self.user.is_authenticated:
# contact_email must be filled if RESPONSE_KIND_EMAIL
if self.cleaned_data.get("response_kind") and (
diff --git a/lemarche/www/tenders/tests.py b/lemarche/www/tenders/tests.py
index cc7082027..f09436549 100644
--- a/lemarche/www/tenders/tests.py
+++ b/lemarche/www/tenders/tests.py
@@ -142,6 +142,22 @@ def test_tender_wizard_form_not_created(self):
with self.assertRaises(AssertionError):
self._check_every_step(tenders_step_data, final_redirect_page=reverse("siae:search_results"))
+ def test_tender_wizard_form_external_link_required_for_tender(self):
+ self.client.force_login(self.user_buyer)
+ tenders_step_data = self._generate_fake_data_form(_step_1={"general-kind": tender_constants.KIND_TENDER})
+ # remove required field in survey
+ tenders_step_data[1].pop("detail-external_link")
+ with self.assertRaises(AssertionError):
+ self._check_every_step(tenders_step_data, final_redirect_page=reverse("siae:search_results"))
+
+ def test_tender_wizard_form_contact_response_required_for_project(self):
+ self.client.force_login(self.user_buyer)
+ tenders_step_data = self._generate_fake_data_form(_step_1={"general-kind": tender_constants.KIND_PROJECT})
+ # remove required field in survey
+ tenders_step_data[2].pop("contact-response_kind")
+ with self.assertRaises(AssertionError):
+ self._check_every_step(tenders_step_data, final_redirect_page=reverse("siae:search_results"))
+
def test_tender_wizard_form_all_good_anonymous(self):
tenders_step_data = self._generate_fake_data_form()
final_response = self._check_every_step(tenders_step_data, final_redirect_page=reverse("siae:search_results"))
diff --git a/lemarche/www/tenders/views.py b/lemarche/www/tenders/views.py
index d72129349..fa25fc823 100644
--- a/lemarche/www/tenders/views.py
+++ b/lemarche/www/tenders/views.py
@@ -376,7 +376,7 @@ def get_context_data(self, **kwargs):
).exists()
if user.is_authenticated:
if user.kind == User.KIND_SIAE:
- context["user_siae_has_detail_contact_click_date"] = TenderSiae.objects.filter(
+ context["siae_has_detail_contact_click_date"] = TenderSiae.objects.filter(
tender=self.object, siae__in=user.siaes.all(), detail_contact_click_date__isnull=False
).exists()
if show_nps: