-
+
{% if tender.deadline_date_is_outdated_annotated %}
Clôturé le {{ tender.deadline_date|default:"" }}
@@ -16,15 +15,29 @@
{% endif %}
Disponible jusqu'au : {{ tender.deadline_date|default:"" }}
- {% if not tender.tendersiae_set.first.detail_display_date %}
- Nouveau
- {% endif %}
+
{% endif %}
+
+ {% if tender.is_new_for_siaes %}
+ Nouveau
+ {% elif tender.deadline_date_is_outdated_annotated %}
+
+ {% endif %}
+
-
-
{{ tender.title }}
+
+ {{ tender.title }}
+
diff --git a/lemarche/tenders/migrations/0078_tendersiae_is_deleted_by_siae.py b/lemarche/tenders/migrations/0078_tendersiae_is_deleted_by_siae.py
new file mode 100644
index 000000000..4f81e2122
--- /dev/null
+++ b/lemarche/tenders/migrations/0078_tendersiae_is_deleted_by_siae.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.2.9 on 2024-02-22 13:01
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("tenders", "0077_alter_tender_siae_kind"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="tendersiae",
+ name="is_deleted_by_siae",
+ field=models.BooleanField(db_index=True, default=False, verbose_name="Supprimé par l'utilisateur ?"),
+ ),
+ ]
diff --git a/lemarche/tenders/models.py b/lemarche/tenders/models.py
index cdc966730..44ebade44 100644
--- a/lemarche/tenders/models.py
+++ b/lemarche/tenders/models.py
@@ -4,7 +4,20 @@
from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation
from django.db import IntegrityError, models, transaction
-from django.db.models import BooleanField, Case, Count, ExpressionWrapper, F, IntegerField, Q, Sum, Value, When
+from django.db.models import (
+ BooleanField,
+ Case,
+ Count,
+ Exists,
+ ExpressionWrapper,
+ F,
+ IntegerField,
+ OuterRef,
+ Q,
+ Sum,
+ Value,
+ When,
+)
from django.db.models.functions import Greatest
from django.urls import reverse
from django.utils import timezone
@@ -136,13 +149,34 @@ def in_sectors(self, sectors):
else:
return self
+ def with_is_new_for_siaes(self, siaes, limit_date=None):
+ # Set limit_date to today if not specified.
+ if limit_date is None:
+ limit_date = datetime.today()
+
+ # Subquery to find TenderSiae instances linked to the Tender, part of the specified Siae,
+ # and without a detailed display date.
+ tender_siae_subquery = TenderSiae.objects.filter(
+ tender=OuterRef("pk"),
+ siae__in=siaes,
+ detail_display_date__isnull=True,
+ )
+
+ # Annotates each Tender with a boolean 'is_new_for_siaes'. A Tender is considered new for the Siae
+ # if there's at least one associated TenderSiae (meeting subquery criteria) with a deadline_date
+ # beyond the limit_date. This implies the Tender was introduced after the limit_date, marking it as new.
+ return self.annotate(
+ is_new_for_siaes=Exists(tender_siae_subquery.filter(tender__deadline_date__gt=limit_date))
+ )
+
def filter_with_siaes(self, siaes):
"""
Return the list of tenders corresponding to the list of
- we return only sent tenders
- the tender-siae matching has already been done with filter_with_tender()
+ - with annotation to new if it's new for siaes
"""
- return self.sent().filter(tendersiae__siae__in=siaes).distinct()
+ return self.sent().filter(tendersiae__is_deleted_by_siae=False, tendersiae__siae__in=siaes).distinct()
def with_deadline_date_is_outdated(self, limit_date=datetime.today()):
return self.annotate(
@@ -948,6 +982,7 @@ class TenderSiae(models.Model):
default=tender_constants.TENDER_SIAE_SOURCE_EMAIL,
)
found_with_ai = models.BooleanField("Trouvé par l'IA", default=False)
+ is_deleted_by_siae = models.BooleanField("Supprimé par l'utilisateur ?", default=False, db_index=True)
# stats: relation
email_send_date = models.DateTimeField("Date d'envoi de l'e-mail", blank=True, null=True)
diff --git a/lemarche/www/tenders/urls.py b/lemarche/www/tenders/urls.py
index 93f7fff2c..00406573d 100644
--- a/lemarche/www/tenders/urls.py
+++ b/lemarche/www/tenders/urls.py
@@ -10,6 +10,7 @@
TenderDetailSurveyTransactionedView,
TenderDetailView,
TenderListView,
+ TenderSiaeHideView,
TenderSiaeListView,
)
@@ -45,6 +46,11 @@
TenderDetailNotInterestedClickView.as_view(),
name="detail-not-interested-click",
),
+ path(
+ "
/cacher-depot-de-besoin",
+ TenderSiaeHideView.as_view(),
+ name="hide-tender-siae",
+ ),
path(
"/sondage-transaction",
TenderDetailSurveyTransactionedView.as_view(),
diff --git a/lemarche/www/tenders/views.py b/lemarche/www/tenders/views.py
index 555c72ed6..35af0fd9c 100644
--- a/lemarche/www/tenders/views.py
+++ b/lemarche/www/tenders/views.py
@@ -2,13 +2,12 @@
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.paginator import Paginator
-from django.db.models import Prefetch
-from django.http import HttpResponseForbidden, HttpResponseRedirect
+from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.safestring import mark_safe
-from django.views.generic import DetailView, ListView, UpdateView
+from django.views.generic import DetailView, ListView, UpdateView, View
from django.views.generic.edit import FormMixin
from formtools.wizard.views import SessionWizardView
@@ -277,6 +276,7 @@ class TenderListView(LoginRequiredMixin, ListView):
paginate_by = 10
paginator_class = Paginator
status = None
+ siae: Siae = None
def get_queryset(self):
"""
@@ -288,11 +288,8 @@ def get_queryset(self):
if user.kind == User.KIND_SIAE and user.siaes:
siaes = user.siaes.all()
if siaes:
- # filtered prefetch to get detail_display_date on tendersiae_set related to user's siaes
- tendersiae_qs = TenderSiae.objects.filter(siae__in=siaes)
- qs = Tender.objects.filter_with_siaes(siaes).prefetch_related(
- Prefetch("tendersiae_set", queryset=tendersiae_qs)
- )
+ # we get the first siae by default
+ qs = Tender.objects.filter_with_siaes(siaes).with_is_new_for_siaes(siaes)
else:
qs = Tender.objects.by_user(user).with_siae_stats()
if self.status:
@@ -728,3 +725,18 @@ def get_success_message(self, already_answered=False):
if already_answered:
return "Votre réponse a déjà été prise en compte."
return "Merci pour votre réponse !"
+
+
+class TenderSiaeHideView(LoginRequiredMixin, View):
+ def post(self, request, *args, **kwargs):
+ tender = get_object_or_404(Tender, slug=kwargs["slug"])
+ user = self.request.user
+ if user.kind == User.KIND_SIAE:
+ tender.tendersiae_set.filter(siae__in=user.siaes.all()).update(is_deleted_by_siae=True)
+ if request.htmx:
+ # status code 204 doesn't work with htmx
+ return HttpResponse("", status=200)
+ return HttpResponseRedirect(reverse_lazy("home"))
+ else:
+ # if the user is not SIAE kind, the post is not allowed
+ return HttpResponse(status=401)