Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Dépôt de besoins): Permettre aux structures de supprimer les dépôts de besoins expirés #1110

Merged
merged 9 commits into from
Mar 13, 2024
29 changes: 21 additions & 8 deletions lemarche/templates/tenders/_list_item_siae.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
{% load static humanize %}

<div class="card c-card c-card--marche c-card--marche-{{ tender.kind|lower }}{% if tender.deadline_date_is_outdated_annotated %} c-card--marche-outdated{% endif %} c-card--link siae-card" role="button" data-url="{% url 'tenders:detail' tender.slug %}">
<div id="card-{{tender.slug}}" class="card c-card c-card--marche-{{ tender.kind|lower }}{% if tender.deadline_date_is_outdated_annotated %} c-card--marche-outdated{% endif %} mb-2">
<div class="card-body">
<div class="row">
<div class="col-md-12">
<div class="col-md-9">
<p class="mb-1">
{% if tender.deadline_date_is_outdated_annotated %}
<span class="badge badge-sm badge-base badge-pill badge-nuance-08">Clôturé le {{ tender.deadline_date|default:"" }}</span>
Expand All @@ -16,15 +15,29 @@
{% endif %}
</span>
Disponible <strong>jusqu'au : {{ tender.deadline_date|default:"" }}</strong>
{% if not tender.tendersiae_set.first.detail_display_date %}
<span class="float-right badge badge-sm badge-pill badge-new">Nouveau</span>
{% endif %}

{% endif %}
</p>
</div>
<div class="col-md-3">
{% if tender.is_new_for_siaes %}
<span class="float-right badge badge-sm badge-pill badge-new">Nouveau</span>
{% elif tender.deadline_date_is_outdated_annotated %}
<form action="{% url 'tenders:delete-tender-siae' tender.slug %}"
method="post" hx-post="{% url 'tenders:delete-tender-siae' tender.slug %}"
hx-target="#card-{{tender.slug}}" hx-swap="outerHTML"
>
{% csrf_token %}
<button type="submit" class="btn btn-light btn-ico btn-sm float-right" aria-label="Supprimer">
<i class="ri-delete-bin-2-line"></i>
</button>
</form>
{% endif %}
</div>
</div>

<h2 class="py-2{% if tender.deadline_date_is_outdated_annotated %} text-nuance-03{% endif %}">{{ tender.title }}</h2>
<a href="{% url 'tenders:detail' tender.slug %}">
<h2 class="py-2{% if tender.deadline_date_is_outdated_annotated %} text-nuance-03{% endif %}">{{ tender.title }}</h2>
</a>

<hr />

Expand Down
17 changes: 17 additions & 0 deletions lemarche/tenders/migrations/0078_tendersiae_is_deleted_by_siae.py
Original file line number Diff line number Diff line change
@@ -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",
madjid-asa marked this conversation as resolved.
Show resolved Hide resolved
field=models.BooleanField(db_index=True, default=False, verbose_name="Supprimé par l'utilisateur ?"),
),
]
44 changes: 42 additions & 2 deletions lemarche/tenders/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -136,13 +149,39 @@ def in_sectors(self, sectors):
else:
return self

def with_is_new_for_siaes(self, siaes, limit_date=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

peut-être rajouter un test ?

# 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()
.with_is_new_for_siaes(siaes)
madjid-asa marked this conversation as resolved.
Show resolved Hide resolved
)

def with_deadline_date_is_outdated(self, limit_date=datetime.today()):
return self.annotate(
Expand Down Expand Up @@ -948,6 +987,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)
Expand Down
6 changes: 6 additions & 0 deletions lemarche/www/tenders/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
TenderDetailSurveyTransactionedView,
TenderDetailView,
TenderListView,
TenderSiaeDeleteView,
TenderSiaeListView,
)

Expand Down Expand Up @@ -45,6 +46,11 @@
TenderDetailNotInterestedClickView.as_view(),
name="detail-not-interested-click",
),
path(
"<str:slug>/supprimer-depot-de-besoin",
TenderSiaeDeleteView.as_view(),
name="delete-tender-siae",
),
path(
"<str:slug>/sondage-transaction",
TenderDetailSurveyTransactionedView.as_view(),
Expand Down
28 changes: 20 additions & 8 deletions lemarche/www/tenders/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -277,6 +276,7 @@ class TenderListView(LoginRequiredMixin, ListView):
paginate_by = 10
paginator_class = Paginator
status = None
siae: Siae = None

def get_queryset(self):
"""
Expand All @@ -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)
else:
qs = Tender.objects.by_user(user).with_siae_stats()
if self.status:
Expand Down Expand Up @@ -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 TenderSiaeDeleteView(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
madjid-asa marked this conversation as resolved.
Show resolved Hide resolved
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)
Loading