-
Notifications
You must be signed in to change notification settings - Fork 2
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(Admin des Besoins): Demande de modification ou clôture d'un besoin #1596
Open
chloend
wants to merge
24
commits into
master
Choose a base branch
from
chloend/tender-hs
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
5741fe0
Add two attributes in Tender :
chloend 77915ea
Create 'add_log_entry' method in Tender
chloend 151faab
Create 'reset_modification_request' method in Tender
chloend a23fdf8
Test 'add_log_entry' Tender method
chloend f02c725
Test 'reset_modification_request' Tender method
chloend 01d578a
Add two Tender methods in Tender creation :
chloend b54f445
Add rejected status
chloend df44ee0
Tests for 'email_sent_for_modification' and 'changes_information' in …
chloend c784b94
Create email tasks and templates :
chloend 8e0e7d4
Add 'email_sent_for_modification' to tender admin
chloend 937c3d6
Add 'changes_information' to tender admin
chloend a495bc2
Add 'email_sent_for_modification' and 'changes_information' in clean …
chloend d2cfd36
Admin : send emails if tender needs modification or is rejected
chloend 9b646c9
Data persistance if 'email_sent_for_modification' and 'changes_inform…
chloend 69d541b
'email_sent_for_modification' is readonly while tender is not published
chloend 70b31d6
Add 'get_object_update_url' to retrieve updated urls from any app
chloend 1b97dda
Test 'get_object_update_url' function
chloend 6b5dd6d
Replace email domain for non prod envs and tests
chloend ac6c5ac
Use 'get_object_update_url' instead of 'get_object_share_url'
chloend 4e205e9
Add an additional message in the mail if changes_information is not e…
chloend 69b0d9c
Store the date of email sent in tender.logs
chloend 3a8dcf9
Create 'update_tender_status_to_rejected' command with its tests
chloend 302ba09
Create 'tenders_update_status_to_rejected' script and add it to cron
chloend 9a3db2b
final commit
chloend File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
#!/bin/bash -l | ||
|
||
# Update Tenders' status to rejected if no changes whitin 10 days since modification request | ||
|
||
# Do not run if this env var is not set: | ||
if [[ -z "$CRON_TENDER_UPDATE_STATUS_TO_REJECTED_ENABLED" ]]; then | ||
echo "CRON_TENDER_UPDATE_STATUS_TO_REJECTED_ENABLED not set. Exiting..." | ||
exit 0 | ||
fi | ||
|
||
# About clever cloud cronjobs: | ||
# https://developers.clever-cloud.com/doc/administrate/cron/ | ||
|
||
if [[ "$INSTANCE_NUMBER" != "0" ]]; then | ||
echo "Instance number is ${INSTANCE_NUMBER}. Stop here." | ||
exit 0 | ||
fi | ||
|
||
# $APP_HOME is set by default by clever cloud. | ||
cd $APP_HOME | ||
|
||
django-admin update_tender_status_to_rejected |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
...s/0019_add_templatetransactional_tender_author_modification_request_and_reject_message.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
from django.db import migrations | ||
|
||
|
||
def create_template(apps, schema_editor): | ||
TemplateTransactional = apps.get_model("conversations", "TemplateTransactional") | ||
TemplateTransactional.objects.create( | ||
name="Dépôt de besoin : auteur : modifications requises", | ||
code="TENDERS_AUTHOR_MODIFICATION_REQUEST", | ||
description="Envoyé à l'auteur du besoin pour lui demander de le modifier ou de prendre rendez-vous avec les admins", | ||
) | ||
TemplateTransactional.objects.create( | ||
name="Dépôt de besoin : auteur : dépôt de besoin rejeté", | ||
code="TENDERS_AUTHOR_REJECT_MESSAGE", | ||
description="Envoyé à l'auteur du besoin pour l'informer du rejet de son dépôt de besoin", | ||
) | ||
|
||
|
||
def delete_template(apps, schema_editor): | ||
TemplateTransactional = apps.get_model("conversations", "TemplateTransactional") | ||
TemplateTransactional.objects.filter(code="TENDERS_AUTHOR_MODIFICATION_REQUEST").delete() | ||
TemplateTransactional.objects.filter(code="TENDERS_AUTHOR_REJECT_MESSAGE").delete() | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("conversations", "0018_conversation_is_anonymized"), | ||
] | ||
|
||
operations = [ | ||
migrations.RunPython(create_template, reverse_code=delete_template), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import logging | ||
|
||
from ckeditor.widgets import CKEditorWidget | ||
from django import forms | ||
from django.contrib import admin | ||
|
@@ -24,7 +26,14 @@ | |
from lemarche.utils.admin.admin_site import admin_site | ||
from lemarche.utils.apis import api_brevo | ||
from lemarche.utils.fields import ChoiceArrayField, pretty_print_readonly_jsonfield | ||
from lemarche.www.tenders.tasks import restart_send_tender_task | ||
from lemarche.www.tenders.tasks import ( | ||
restart_send_tender_task, | ||
send_tender_author_modification_request, | ||
send_tender_author_reject_message, | ||
) | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class KindFilter(MultiChoice): | ||
|
@@ -278,6 +287,7 @@ def clean(self): | |
""" | ||
cleaned_data = super().clean() | ||
distance_location = cleaned_data.get("distance_location") | ||
|
||
if distance_location: | ||
location = cleaned_data.get("location") | ||
if not location: | ||
|
@@ -302,13 +312,15 @@ 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", | ||
"status", | ||
"is_validated_or_sent", | ||
"is_followed_by_us", | ||
] | ||
|
||
list_filter = [ | ||
AmountCustomFilter, | ||
("kind", KindFilter), | ||
"email_sent_for_modification", | ||
"is_followed_by_us", | ||
AuthorKindFilter, | ||
"status", | ||
|
@@ -542,6 +554,33 @@ class TenderAdmin(FieldsetsInlineMixin, admin.ModelAdmin): | |
class Media: | ||
js = ["/static/js/admin_tender_confirmation.js"] | ||
|
||
def handle_email_sent_for_modification(self, request, obj): | ||
""" | ||
Send an email to the author and set some fields with 'set_modification_request' | ||
Display an error message if the email can't be sent | ||
""" | ||
try: | ||
send_tender_author_modification_request(tender=obj) | ||
obj.set_modification_request() | ||
self.message_user(request, "Une demande de modification a été envoyée à l'auteur du besoin") | ||
except Exception as e: | ||
self.message_user( | ||
request, | ||
"Erreur lors de l'envoi de la demande de modification : veuillez contacter le support.", | ||
level="error", | ||
) | ||
logger.error(f"Exception when sending mail {e}") | ||
finally: | ||
return HttpResponseRedirect(".") | ||
|
||
def handle_rejected_status(self, request, obj): | ||
""" | ||
If tender status is REJECTED, send an email to the author and redirect to the same page. | ||
""" | ||
send_tender_author_reject_message(tender=obj) | ||
self.message_user(request, "Un email a été envoyé à l'auteur du besoin") | ||
return HttpResponseRedirect(".") | ||
|
||
def get_queryset(self, request): | ||
qs = super().get_queryset(request) | ||
qs = qs.select_related("author") | ||
|
@@ -583,7 +622,6 @@ def save_model(self, request, obj: Tender, form, change): | |
""" | ||
if not obj.id and not obj.author_id: | ||
obj.author = request.user | ||
obj.save() | ||
|
||
def save_formset(self, request, form, formset, change): | ||
""" | ||
|
@@ -802,6 +840,10 @@ def response_change(self, request, obj: Tender): | |
# we don't need to send it in the crm, parteners manage them | ||
self.message_user(request, "Ce dépôt de besoin a été validé. Il sera envoyé aux partenaires :)") | ||
return HttpResponseRedirect(".") | ||
if request.POST.get("_send_modification_request"): | ||
return self.handle_email_sent_for_modification(request, obj) | ||
if obj.status == tender_constants.STATUS_REJECTED: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pour moi, le |
||
return self.handle_rejected_status(request, obj) | ||
elif request.POST.get("_restart_tender"): | ||
restart_send_tender_task(tender=obj) | ||
self.message_user(request, "Ce dépôt de besoin a été renvoyé aux structures") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
lemarche/tenders/management/commands/update_tender_status_to_rejected.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
from datetime import timedelta | ||
|
||
from django.core.management.base import BaseCommand | ||
from django.utils import timezone | ||
|
||
from lemarche.tenders import constants as tender_constants | ||
from lemarche.tenders.models import Tender | ||
|
||
|
||
class Command(BaseCommand): | ||
help = "Si aucune modification n'est apportée dans les 10 jours suivant la demande, le Besoin est rejeté." | ||
|
||
def handle(self, *args, **options): | ||
threshold_date = timezone.now() - timedelta(days=10) | ||
tenders_to_update = [] | ||
|
||
tenders_draft = Tender.objects.filter(status=tender_constants.STATUS_DRAFT) | ||
tenders_draft_count = tenders_draft.count() | ||
|
||
self.stdout.write(f"Besoin(s) à traiter : {tenders_draft_count}") | ||
|
||
for tender in tenders_draft: | ||
email_sent_at = None | ||
for log_entry in tender.logs: | ||
if log_entry.get("action") == "send tender author modification request": | ||
email_sent_at = log_entry.get("date") | ||
break | ||
|
||
if email_sent_at: | ||
email_sent_at_date = timezone.datetime.fromisoformat(email_sent_at) | ||
if email_sent_at_date <= threshold_date: | ||
tenders_to_update.append(tender) | ||
|
||
for tender in tenders_to_update: | ||
tender.status = tender_constants.STATUS_REJECTED | ||
tender.save(update_fields=["status"]) | ||
|
||
if not tenders_to_update: | ||
self.stdout.write("Aucun besoin rejeté") | ||
elif len(tenders_to_update) == 1: | ||
self.stdout.write("1 besoin rejeté") | ||
else: | ||
self.stdout.write(f"{len(tenders_to_update)} besoins rejetés") |
37 changes: 37 additions & 0 deletions
37
lemarche/tenders/migrations/0096_tender_email_sent_for_modification_and_more.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Generated by Django 4.2.15 on 2025-01-16 04:01 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("tenders", "0095_remove_tender_accept_cocontracting_and_more"), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name="tender", | ||
name="email_sent_for_modification", | ||
field=models.BooleanField( | ||
default=False, | ||
help_text="Envoyer un e-mail pour demander des modifications", | ||
verbose_name="Modifications requises", | ||
), | ||
), | ||
migrations.AlterField( | ||
model_name="tender", | ||
name="status", | ||
field=models.CharField( | ||
choices=[ | ||
("DRAFT", "Brouillon"), | ||
("PUBLISHED", "Publié"), | ||
("VALIDATED", "Validé"), | ||
("SENT", "Envoyé"), | ||
("REJECTED", "Rejeté"), | ||
], | ||
default="DRAFT", | ||
max_length=10, | ||
verbose_name="Statut", | ||
), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Je ne suis pas sûr de l'utilité de ce
if
vu questatus
est affecté juste au dessus.