-
Notifications
You must be signed in to change notification settings - Fork 7
Haie / L'historique des dossiers inclut la mise en attente + champs de l'état du dossier (Attente PC 3/3) + analytics #918
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
base: main
Are you sure you want to change the base?
Changes from 15 commits
dfd19a1
2675ec9
4aac306
a3a712d
a70ed24
040cf9c
b51491d
08ed666
95ef96b
67becdc
668067f
97bef13
a5503c7
f5c951c
409ee80
b9d7eeb
6acb287
63386b4
c396e8d
5e02c97
669d1f3
06925d7
cdfab6f
7b667e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Generated by Django 4.2.23 on 2025-12-18 05:27 | ||
|
|
||
| from django.conf import settings | ||
| from django.db import migrations, models | ||
| import django.db.models.deletion | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
| ("petitions", "0028_alter_statuslog_options_and_more"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="statuslog", | ||
| name="resumed_by", | ||
| field=models.ForeignKey( | ||
| null=True, | ||
| on_delete=django.db.models.deletion.SET_NULL, | ||
| related_name="resumed_logs", | ||
| to=settings.AUTH_USER_MODEL, | ||
| verbose_name="Auteur de la reprise de la procédure suite à la réception d'informations complémentaires", | ||
| ), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Generated by Django 4.2.23 on 2026-01-07 05:33 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("petitions", "0029_statuslog_resumed_by_and_more"), | ||
| ("petitions", "0033_merge_20251211_1513"), | ||
| ] | ||
|
|
||
| operations = [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # Generated by Django 4.2.23 on 2026-01-07 05:34 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| def fix_missing_resumed_by(apps, schema_editor): | ||
| """Set resumed_by for logs that have info_receipt_date but no resumed_by.""" | ||
| StatusLog = apps.get_model("petitions", "StatusLog") | ||
| User = apps.get_model("users", "User") | ||
| qs = StatusLog.objects.filter( | ||
| info_receipt_date__isnull=False, | ||
| resumed_by__isnull=True, | ||
| ) | ||
| if qs.count() == 0: | ||
| return | ||
|
|
||
| user = User.objects.get(id=1) | ||
| qs.update(resumed_by=user) | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| atomic = False | ||
|
|
||
| dependencies = [ | ||
| ("petitions", "0034_merge_20260107_0633"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.RemoveConstraint( | ||
| model_name="statuslog", | ||
| name="receipt_date_data_is_consistent", | ||
| ), | ||
| migrations.RunPython(fix_missing_resumed_by, migrations.RunPython.noop), | ||
| migrations.AddConstraint( | ||
| model_name="statuslog", | ||
| constraint=models.CheckConstraint( | ||
| check=models.Q( | ||
| models.Q( | ||
| ("info_receipt_date__isnull", True), | ||
| ("resumed_by__isnull", True), | ||
| ), | ||
| models.Q( | ||
| ("info_receipt_date__isnull", False), | ||
| ("resumed_by__isnull", False), | ||
| ("suspension_date__isnull", False), | ||
| ("response_due_date__isnull", False), | ||
| ), | ||
| _connector="OR", | ||
| ), | ||
| name="receipt_date_data_is_consistent", | ||
| ), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| import shutil | ||
| import tempfile | ||
| from urllib.parse import parse_qs, urlparse | ||
| from zoneinfo import ZoneInfo | ||
|
|
||
| import fiona | ||
| import requests | ||
|
|
@@ -13,7 +14,6 @@ | |
| from django.contrib.auth.mixins import LoginRequiredMixin | ||
| from django.contrib.postgres.expressions import ArraySubquery | ||
| from django.contrib.sites.models import Site | ||
| from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator | ||
| from django.db import transaction | ||
| from django.db.models import Exists, OuterRef, Prefetch, Q, Subquery | ||
| from django.db.models.functions import Coalesce | ||
|
|
@@ -35,6 +35,7 @@ | |
| from django.views.decorators.http import require_POST | ||
| from django.views.generic import DetailView, FormView, ListView, UpdateView | ||
| from django.views.generic.detail import SingleObjectMixin | ||
| from django.views.generic.list import MultipleObjectMixin | ||
| from fiona import Feature, Geometry, Properties | ||
| from pyproj import Transformer | ||
| from shapely.ops import transform | ||
|
|
@@ -61,6 +62,7 @@ | |
| SimulationForm, | ||
| ) | ||
| from envergo.petitions.models import ( | ||
| DECISIONS, | ||
| DOSSIER_STATES, | ||
| STAGES, | ||
| InvitationToken, | ||
|
|
@@ -1251,14 +1253,28 @@ def get_success_url(self): | |
|
|
||
|
|
||
| class PetitionProjectInstructorProcedureView( | ||
| BasePetitionProjectInstructorView, FormView | ||
| BasePetitionProjectInstructorView, MultipleObjectMixin, FormView | ||
| ): | ||
| """View for display and edit the petition project procedure by the instructor""" | ||
|
|
||
| form_class = ProcedureForm | ||
| template_name = "haie/petitions/instructor_view_procedure.html" | ||
| paginate_by = 10 | ||
|
|
||
| def get(self, request, *args, **kwargs): | ||
| self.object = self.get_object() | ||
| self.object_list = self.object.status_history.select_related( | ||
| "created_by" | ||
| ).order_by("-created_at") | ||
| return super().get(request, *args, **kwargs) | ||
|
|
||
| def post(self, request, *args, **kwargs): | ||
| self.object = self.get_object() | ||
| self.object_list = self.object.status_history.select_related( | ||
| "created_by" | ||
| ).order_by("-created_at") | ||
| return super().post(request, *args, **kwargs) | ||
|
|
||
| def get_initial(self): | ||
| initial = super().get_initial() | ||
| initial["stage"] = self.object.current_stage | ||
|
|
@@ -1267,49 +1283,109 @@ def get_initial(self): | |
| return initial | ||
|
|
||
| def get_context_data(self, **kwargs): | ||
| context = super().get_context_data(**kwargs) | ||
| obj = self.object | ||
| history_qs = obj.status_history.select_related("created_by").order_by( | ||
| "-created_at" | ||
| ) | ||
| paginator = Paginator(history_qs, self.paginate_by) | ||
|
|
||
| page_number = self.request.GET.get("page") | ||
| try: | ||
| page_obj = paginator.page(page_number) | ||
| except PageNotAnInteger: | ||
| page_obj = paginator.page(1) | ||
| except EmptyPage: | ||
| page_obj = paginator.page(paginator.num_pages) | ||
|
|
||
| # Prefetch previous log in the current page | ||
| start = max( | ||
| page_obj.start_index() - 2, 0 | ||
| ) # -1 for 0 index, -1 to get previous log | ||
| end = page_obj.end_index() | ||
| logs = list(history_qs[start:end]) | ||
| for i, log in enumerate(logs): | ||
| log.previous_log = logs[i + 1] if i + 1 < len(logs) else None | ||
|
|
||
| # Drop the extra item (first one) if it's not part of this page | ||
| if page_obj.number > 1: | ||
| logs = logs[1:] | ||
| def extract_entries(log): | ||
pyDez marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| """The history table display an entry for each status change, suspension and resumption for requesting info | ||
|
|
||
| As the suspension and resumption are not a single StatusLog model but attributes of it, this method will | ||
| map a StatusLog into one or more entries for the history table. | ||
| """ | ||
| TYPE_CHOICES = [ | ||
| ("status_change", "Changement d'état"), | ||
| ("suspension", "Demande de compléments"), | ||
| ("resumption", "Complément reçus"), | ||
| ] | ||
| TYPE_LABELS = dict(TYPE_CHOICES) | ||
|
|
||
| entries = [ | ||
| { | ||
| "type": "status_change", | ||
| "type_display": TYPE_LABELS["status_change"], | ||
| "created_at": log.created_at, | ||
| "status_date": log.status_date, | ||
| "created_by": ( | ||
| "" | ||
| if not log.created_by | ||
| else ( | ||
| log.created_by.email | ||
| if not log.created_by.is_staff | ||
| else "Administrateur" | ||
| ) | ||
| ), | ||
| "update_comment": log.update_comment, | ||
| "stage": log.stage, | ||
| "decision": log.decision, | ||
| "due_date": log.due_date, | ||
| } | ||
| ] | ||
| if log.suspension_date: | ||
pyDez marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| # this log object is storing a suspension, we add an entry | ||
| entries.insert( | ||
| 0, | ||
| { | ||
| "type": "suspension", | ||
| "type_display": TYPE_LABELS["suspension"], | ||
| "created_by": ( | ||
| "" | ||
| if not log.suspended_by | ||
| else ( | ||
| log.suspended_by.email | ||
| if not log.suspended_by.is_staff | ||
| else "Administrateur" | ||
| ) | ||
| ), | ||
| "created_at": datetime.datetime.combine( | ||
| log.suspension_date, datetime.time.min | ||
| ).replace(tzinfo=ZoneInfo("UTC")), | ||
| "response_due_date": log.response_due_date, | ||
| "update_comment": "Suspension de l’instruction, message envoyé au demandeur.", | ||
| }, | ||
| ) | ||
| if log.info_receipt_date: | ||
pyDez marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| # this log object is storing a resumption, we add an entry | ||
| entries.insert( | ||
| 0, | ||
| { | ||
| "type": "resumption", | ||
| "type_display": TYPE_LABELS["resumption"], | ||
| "created_by": ( | ||
| "" | ||
| if not log.resumed_by | ||
| else ( | ||
| log.resumed_by.email | ||
| if not log.resumed_by.is_staff | ||
| else "Administrateur" | ||
| ) | ||
| ), | ||
| "created_at": datetime.datetime.combine( | ||
| log.info_receipt_date, datetime.time.min | ||
| ).replace(tzinfo=ZoneInfo("UTC")), | ||
| "due_date": log.due_date, | ||
| "update_comment": "Reprise de l’instruction, date d'échéance ajustée.", | ||
| }, | ||
| ) | ||
| entries[0]["due_date"] = log.original_due_date | ||
|
|
||
| return entries | ||
|
|
||
| context = super().get_context_data(**kwargs) | ||
| paginator, page_obj, page_qs, has_other_pages = self.paginate_queryset( | ||
| self.object_list, self.paginate_by | ||
| ) | ||
| logs = [entry for log in page_qs for entry in extract_entries(log)] | ||
| context.update( | ||
| { | ||
| "object_list": logs, | ||
| "paginator": paginator, | ||
| "page_obj": page_obj, | ||
| "is_paginated": paginator.num_pages > 1, | ||
| "STAGES": STAGES, | ||
| "DECISIONS": DECISIONS, | ||
| } | ||
| ) | ||
|
|
||
| # Request for additional information is only relevant when the project is | ||
| # in the "instruction" phase | ||
| if self.has_change_permission( | ||
| self.request, self.object | ||
| ) and obj.current_stage.startswith("instruction"): | ||
| ) and self.object.current_stage.startswith("instruction"): | ||
| request_info_form = RequestAdditionalInfoForm() | ||
| resume_processing_form = ResumeProcessingForm() | ||
| context.update( | ||
|
|
@@ -1378,6 +1454,7 @@ def notify_admin(): | |
| self.request, | ||
| reference=self.object.reference, | ||
| etape_i=previous_stage, | ||
| department=self.object.get_department_code(), | ||
| etape_f=log.stage, | ||
| decision_i=previous_decision, | ||
| decision_f=log.decision, | ||
|
|
@@ -1511,6 +1588,7 @@ def resume_form_valid(self, form): | |
| status.due_date = status.original_due_date + interruption_days | ||
| else: | ||
| status.due_date = None | ||
| status.resumed_by = self.request.user | ||
| status.save() | ||
|
|
||
| # Send Mattermost notification | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.