diff --git a/README.md b/README.md
index fbbdf7ddc..176e2901a 100644
--- a/README.md
+++ b/README.md
@@ -263,12 +263,14 @@ docker-compose exec web pip install -r requirements.txt
### Permissions
The user belonging to backoffice group can be granted specific permissions:
+
- `view_private_form`, "Voir les demandes restreintes": allows the user to make requests that are not visible by standard user. Typically during the setup stage of a new form configuration
-- `amend_submission`,"Traiter les demandes": allow the user the process (amend) the requests (fill the backoffice fields), require validation for other departments and print the documents
-- `validate_submission`,"Valider les demandes": allow the user to fill the validation form
-- `classify_submission`,"Classer les demandes" allow the user to accept/reject the requests if validations services have all accepted it
-- `edit_submission`, "Éditer les demandes": allow the user to edit de requests filled by another person
+- `amend_submission`, "Traiter les demandes": allow the user the process (amend) the requests (fill the backoffice fields), require validation for other departments and print the documents
+- `validate_submission`, "Valider les demandes": allow the user to fill the validation form
+- `classify_submission`, "Classer les demandes" allow the user to accept/reject the requests if validations services have all accepted it
+- `edit_submission`, "Modifier les demandes": allow the user to edit the requests filled by another person
- `read_submission`, "Consulter les demandes": allow a trusted user to only read submissions without any other permission
+- `edit_submission_validations`, "Modifier les validations": allow the pilot to edit the validation to correct spelling mistakes
### Two factor authentication
diff --git a/geocity/apps/accounts/permissions_groups.py b/geocity/apps/accounts/permissions_groups.py
index 81477d859..735f29dec 100644
--- a/geocity/apps/accounts/permissions_groups.py
+++ b/geocity/apps/accounts/permissions_groups.py
@@ -53,6 +53,7 @@
AVAILABLE_FOR_INTEGRATOR_PERMISSION_CODENAMES = [
"read_submission",
"amend_submission",
+ "edit_submission_validations",
"validate_submission",
"classify_submission",
"edit_submission",
diff --git a/geocity/apps/api/serializers.py b/geocity/apps/api/serializers.py
index 70509f8e0..dbd830e00 100644
--- a/geocity/apps/api/serializers.py
+++ b/geocity/apps/api/serializers.py
@@ -422,11 +422,10 @@ def to_representation(self, value):
values = {}
for field in validation._meta.fields:
values["validation_status"] = validation.get_validation_status_display()
- if field.name in [
- "comment_before",
- "comment_during",
- "comment_after",
- ]:
+ if (
+ field.name == "comment"
+ or field.name == "comment_is_visible_by_author"
+ ):
values[field.name] = getattr(validation, field.name)
rep[validation.department.description] = values
diff --git a/geocity/apps/core/static/js/submission_validations_edit.js b/geocity/apps/core/static/js/submission_validations_edit.js
new file mode 100644
index 000000000..0d8cbcd0f
--- /dev/null
+++ b/geocity/apps/core/static/js/submission_validations_edit.js
@@ -0,0 +1,13 @@
+// Create a label to replace "readonly hidden select" readonly
+window.addEventListener('load', function () {
+ var selects = document.querySelectorAll("select[readonly][hidden]");
+ for (select of selects) {
+ let elem = document.createElement('label');
+ let text = select.querySelector("option[selected]").text
+ let div = select.closest('.col-md-9');
+
+ elem.innerHTML = text;
+ elem.classList.add('col-form-label', 'bold');
+ div.appendChild(elem);
+ }
+});
diff --git a/geocity/apps/reports/migrations/0023_sectionhorizontalrule_padding_top.py b/geocity/apps/reports/migrations/0023_sectionhorizontalrule_padding_top.py
new file mode 100644
index 000000000..0ac72f194
--- /dev/null
+++ b/geocity/apps/reports/migrations/0023_sectionhorizontalrule_padding_top.py
@@ -0,0 +1,22 @@
+# Generated by Django 4.1.7 on 2023-03-23 12:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("reports", "0022_alter_sectionparagraph_content"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="sectionhorizontalrule",
+ name="padding_top",
+ field=models.PositiveIntegerField(
+ default=0,
+ help_text="Espace vide au dessus afin de placer le texte au bon endroit (en pixels). Augmenter la valeur fait descendre le texte",
+ verbose_name="Espace vide au dessus",
+ ),
+ ),
+ ]
diff --git a/geocity/apps/reports/models.py b/geocity/apps/reports/models.py
index af6765446..d1c08221b 100644
--- a/geocity/apps/reports/models.py
+++ b/geocity/apps/reports/models.py
@@ -541,6 +541,8 @@ class Meta:
class SectionHorizontalRule(Section):
+ padding_top = padding_top_field()
+
class Meta:
verbose_name = _("Ligne horizontale")
diff --git a/geocity/apps/reports/templates/reports/sections/sectionvalidation.html b/geocity/apps/reports/templates/reports/sections/sectionvalidation.html
index b6b44a566..ff8db6128 100644
--- a/geocity/apps/reports/templates/reports/sections/sectionvalidation.html
+++ b/geocity/apps/reports/templates/reports/sections/sectionvalidation.html
@@ -9,11 +9,11 @@
{% for service, validation in request_data.properties.validations.items %}
-
Validation: {{service}}
- Statut : {{validation.validation_status}}
- Commentaire (avant) : {{validation.comment_before}}
- Commentaire (pendant) : {{validation.comment_during}}
- Commentaire (après) : {{validation.comment_after}}
+ Validation: {{service}}
+ Statut : {{validation.validation_status}}
+ {% if validation.comment_is_visible_by_author %}
+ Commentaire : {{validation.comment}}
+ {% endif %}
{% endfor %}
diff --git a/geocity/apps/submissions/forms.py b/geocity/apps/submissions/forms.py
index c13425254..d8bb81ba5 100644
--- a/geocity/apps/submissions/forms.py
+++ b/geocity/apps/submissions/forms.py
@@ -1406,15 +1406,13 @@ class Meta:
model = models.SubmissionValidation
fields = [
"validation_status",
- "comment_before",
- "comment_during",
- "comment_after",
+ "comment",
+ "comment_is_visible_by_author",
]
widgets = {
"validation_status": forms.RadioSelect(),
- "comment_before": forms.Textarea(attrs={"rows": 3}),
- "comment_during": forms.Textarea(attrs={"rows": 3}),
- "comment_after": forms.Textarea(attrs={"rows": 3}),
+ "comment": forms.Textarea(attrs={"rows": 3}),
+ "comment_is_visible_by_author": forms.CheckboxInput(),
}
def __init__(self, *args, **kwargs):
@@ -1896,3 +1894,15 @@ def get_submission_forms(submission):
]
return forms_infos
+
+
+class SubmissionValidationsForm(forms.ModelForm):
+ class Meta:
+ model = models.SubmissionValidation
+ fields = ["department", "comment", "comment_is_visible_by_author"]
+
+ def __init__(self, *args, **kwargs):
+ super(SubmissionValidationsForm, self).__init__(*args, **kwargs)
+ if self.instance.id:
+ self.fields["department"].widget.attrs["readonly"] = True
+ self.fields["department"].widget.attrs["hidden"] = True
diff --git a/geocity/apps/submissions/management/commands/fixturize.py b/geocity/apps/submissions/management/commands/fixturize.py
index ee406a46a..f0c7f5c0b 100644
--- a/geocity/apps/submissions/management/commands/fixturize.py
+++ b/geocity/apps/submissions/management/commands/fixturize.py
@@ -447,11 +447,9 @@ def setup_submission(self, entity, user_iterations, administrative_entity, text)
another_department = PermitDepartment.objects.get(
group__name=f"{entity}-validator"
)
- comment_before_default = (
- "Ce projet n'est pas admissible, veuillez l'améliorer.",
- )
- comment_during_default = ("Les améliorations ont été prise en compte.",)
- comment_after_default = ("Excellent projet qui bénéficiera à la communauté.",)
+ comment = """Avant : Ce projet n'est pas admissible, veuillez l'améliorer.
+Pendant : Les améliorations ont été prise en compte.
+Après : Excellent projet qui bénéficiera à la communauté."""
for user_iteration in range(user_iterations):
username = f"{entity}-user-{user_iteration}"
@@ -473,9 +471,7 @@ def setup_submission(self, entity, user_iterations, administrative_entity, text)
submission,
department,
validation_status,
- comment_before_default,
- comment_during_default,
- comment_after_default,
+ comment,
)
# Submission to Classify with mixed objects requiring and not requiring validation document
@@ -490,9 +486,7 @@ def setup_submission(self, entity, user_iterations, administrative_entity, text)
submission,
department,
validation_status,
- comment_before_default,
- comment_during_default,
- comment_after_default,
+ comment,
)
# Submission to Classify with validation document required
@@ -506,9 +500,7 @@ def setup_submission(self, entity, user_iterations, administrative_entity, text)
submission,
department,
validation_status,
- comment_before_default,
- comment_during_default,
- comment_after_default,
+ comment,
)
# Submission to Classify with validation document required (with another Form)
@@ -522,9 +514,7 @@ def setup_submission(self, entity, user_iterations, administrative_entity, text)
submission,
department,
validation_status,
- comment_before_default,
- comment_during_default,
- comment_after_default,
+ comment,
)
# Submission with pending validations
@@ -550,16 +540,12 @@ def setup_submission(self, entity, user_iterations, administrative_entity, text)
department,
validation_status,
text,
- text,
- text,
)
self.create_submission_validation(
submission,
another_department,
validation_status,
text,
- text,
- text,
)
# Amend properties
@@ -831,6 +817,7 @@ def get_pilot_permissions(self):
secretariat_permissions = Permission.objects.filter(
codename__in=[
"amend_submission",
+ "edit_submission_validations",
"classify_submission",
],
content_type=self.submission_ct,
@@ -1018,17 +1005,13 @@ def create_submission_validation(
submission,
department,
validation_status=SubmissionValidation.STATUS_REQUESTED,
- comment_before="",
- comment_during="",
- comment_after="",
+ comment="",
):
SubmissionValidation.objects.get_or_create(
submission=submission,
department=department,
validation_status=validation_status,
- comment_before=comment_before,
- comment_during=comment_during,
- comment_after=comment_after,
+ comment=comment,
)
def create_submission_amend_field(
diff --git a/geocity/apps/submissions/migrations/0016_historicalsubmissionvalidation_comment_and_more.py b/geocity/apps/submissions/migrations/0016_historicalsubmissionvalidation_comment_and_more.py
new file mode 100644
index 000000000..be5971d60
--- /dev/null
+++ b/geocity/apps/submissions/migrations/0016_historicalsubmissionvalidation_comment_and_more.py
@@ -0,0 +1,53 @@
+# Generated by Django 4.1.7 on 2023-03-22 10:49
+
+from django.db import migrations, models
+
+
+def migrate_comments(apps, schema_editor):
+ """
+ Put comment_before, comment_during and comment_after in 1 comment
+ """
+ SubmissionValidation = apps.get_model("submissions", "SubmissionValidation")
+
+ validations = SubmissionValidation.objects.all().exclude(
+ comment_before__exact="", comment_during__exact="", comment_after__exact=""
+ )
+
+ # For each SubmissionValidation, squash comment_before comment_during comment_after in 1 comment
+ for validation in validations:
+ comment = f"""{validation.comment_before}
+ {validation.comment_during}
+ {validation.comment_after}"""
+
+ # save it in SubmissionValidation.comment
+ validation.comment = comment
+ validation.save()
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("submissions", "0015_alter_submission_options"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="historicalsubmissionvalidation",
+ name="comment",
+ field=models.TextField(
+ blank=True,
+ help_text="Information supplémentaire facultative transmise au requérant",
+ verbose_name="Commentaire",
+ ),
+ ),
+ migrations.AddField(
+ model_name="submissionvalidation",
+ name="comment",
+ field=models.TextField(
+ blank=True,
+ help_text="Information supplémentaire facultative transmise au requérant",
+ verbose_name="Commentaire",
+ ),
+ ),
+ migrations.RunPython(migrate_comments),
+ ]
diff --git a/geocity/apps/submissions/migrations/0017_alter_submission_options_and_more.py b/geocity/apps/submissions/migrations/0017_alter_submission_options_and_more.py
new file mode 100644
index 000000000..7e75b359f
--- /dev/null
+++ b/geocity/apps/submissions/migrations/0017_alter_submission_options_and_more.py
@@ -0,0 +1,131 @@
+# Generated by Django 4.1.7 on 2023-04-03 11:24
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ("accounts", "0011_add_signature_sheet_to_administrative_entity"),
+ ("submissions", "0016_historicalsubmissionvalidation_comment_and_more"),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name="submission",
+ options={
+ "permissions": [
+ ("read_submission", "Consulter les demandes"),
+ ("amend_submission", "Traiter les demandes"),
+ ("edit_submission_validations", "Modifier les validations"),
+ ("validate_submission", "Valider les demandes"),
+ ("classify_submission", "Classer les demandes"),
+ ("edit_submission", "Modifier les demandes"),
+ ("view_private_form", "Voir les demandes restreintes"),
+ ("can_refund_transactions", "Rembourser une transaction"),
+ ("can_revert_refund_transactions", "Revenir sur un remboursement"),
+ ],
+ "verbose_name": "2.2 Consultation de la demande",
+ "verbose_name_plural": "2.2 Consultation des demandes",
+ },
+ ),
+ migrations.RemoveField(
+ model_name="historicalsubmissionvalidation",
+ name="comment_after",
+ ),
+ migrations.RemoveField(
+ model_name="historicalsubmissionvalidation",
+ name="comment_before",
+ ),
+ migrations.RemoveField(
+ model_name="historicalsubmissionvalidation",
+ name="comment_during",
+ ),
+ migrations.RemoveField(
+ model_name="submissionvalidation",
+ name="comment_after",
+ ),
+ migrations.RemoveField(
+ model_name="submissionvalidation",
+ name="comment_before",
+ ),
+ migrations.RemoveField(
+ model_name="submissionvalidation",
+ name="comment_during",
+ ),
+ migrations.AddField(
+ model_name="historicalsubmissionvalidation",
+ name="comment_is_visible_by_author",
+ field=models.BooleanField(
+ default=True,
+ verbose_name="Commentaire visible par l'auteur de la demande",
+ ),
+ ),
+ migrations.AddField(
+ model_name="submissionvalidation",
+ name="comment_is_visible_by_author",
+ field=models.BooleanField(
+ default=True,
+ verbose_name="Commentaire visible par l'auteur de la demande",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="historicalsubmissionvalidation",
+ name="comment",
+ field=models.TextField(blank=True, verbose_name="Commentaire"),
+ ),
+ migrations.AlterField(
+ model_name="historicalsubmissionvalidation",
+ name="department",
+ field=models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to="accounts.permitdepartment",
+ verbose_name="Département",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="historicalsubmissionvalidation",
+ name="validated_by",
+ field=models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to=settings.AUTH_USER_MODEL,
+ verbose_name="Validé par",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="submissionvalidation",
+ name="comment",
+ field=models.TextField(blank=True, verbose_name="Commentaire"),
+ ),
+ migrations.AlterField(
+ model_name="submissionvalidation",
+ name="department",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="submission_validations",
+ to="accounts.permitdepartment",
+ verbose_name="Département",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="submissionvalidation",
+ name="validated_by",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to=settings.AUTH_USER_MODEL,
+ verbose_name="Validé par",
+ ),
+ ),
+ ]
diff --git a/geocity/apps/submissions/models.py b/geocity/apps/submissions/models.py
index e483097ca..e86eb91ef 100644
--- a/geocity/apps/submissions/models.py
+++ b/geocity/apps/submissions/models.py
@@ -81,14 +81,15 @@ def filter_for_user(self, user, form_filter=None, ignore_archived=True):
starts_at_min=Min("geo_time__starts_at"),
ends_at_max=Max("geo_time__ends_at"),
permit_duration_max=Max("forms__permit_duration"),
- remaining_validations=Count("validations")
+ remaining_validations=Count("validations__department", distinct=True)
- Count(
- "validations",
+ "validations__department",
+ distinct=True,
filter=~Q(
validations__validation_status=SubmissionValidation.STATUS_REQUESTED
),
),
- required_validations=Count("validations"),
+ required_validations=Count("validations__department", distinct=True),
author_fullname=Concat(
F("author__first_name"),
Value(" "),
@@ -271,9 +272,10 @@ class Meta:
permissions = [
("read_submission", _("Consulter les demandes")),
("amend_submission", _("Traiter les demandes")),
+ ("edit_submission_validations", _("Modifier les validations")),
("validate_submission", _("Valider les demandes")),
("classify_submission", _("Classer les demandes")),
- ("edit_submission", _("Éditer les demandes")),
+ ("edit_submission", _("Modifier les demandes")),
("view_private_form", _("Voir les demandes restreintes")),
("can_refund_transactions", _("Rembourser une transaction")),
("can_revert_refund_transactions", _("Revenir sur un remboursement")),
@@ -1782,28 +1784,23 @@ class SubmissionValidation(models.Model):
)
department = models.ForeignKey(
PermitDepartment,
+ verbose_name=_("Département"),
on_delete=models.CASCADE,
related_name="submission_validations",
)
validation_status = models.IntegerField(
_("Statut de validation"), choices=STATUS_CHOICES, default=STATUS_REQUESTED
)
- comment_before = models.TextField(
- _("Commentaire (avant)"),
+ comment = models.TextField(
+ _("Commentaire"),
blank=True,
- help_text=_("Information supplémentaire facultative transmise au requérant"),
)
- comment_during = models.TextField(
- _("Commentaire (pendant)"),
- blank=True,
- help_text=_("Information supplémentaire facultative transmise au requérant"),
+ comment_is_visible_by_author = models.BooleanField(
+ _("Commentaire visible par l'auteur de la demande"), default=True
)
- comment_after = models.TextField(
- _("Commentaire (après)"),
- blank=True,
- help_text=_("Information supplémentaire facultative transmise au requérant"),
+ validated_by = models.ForeignKey(
+ User, verbose_name=_("Validé par"), null=True, on_delete=models.SET_NULL
)
- validated_by = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
validated_at = models.DateTimeField(_("Validé le"), null=True)
history = HistoricalRecords()
diff --git a/geocity/apps/submissions/permissions.py b/geocity/apps/submissions/permissions.py
index 2c8f8d77b..757a96d57 100644
--- a/geocity/apps/submissions/permissions.py
+++ b/geocity/apps/submissions/permissions.py
@@ -16,6 +16,14 @@ def has_permission_to_amend_submission(user, submission):
)
+def has_permission_to_edit_submission_validations(user, submission):
+ return user.has_perm(
+ "submissions.edit_submission_validations"
+ ) and submission.administrative_entity in AdministrativeEntity.objects.associated_to_user(
+ user
+ )
+
+
def can_amend_submission(user, submission):
return submission.can_be_amended() and has_permission_to_amend_submission(
user, submission
diff --git a/geocity/apps/submissions/templates/submissions/_submission_actions.html b/geocity/apps/submissions/templates/submissions/_submission_actions.html
index eeaa872ba..08553ba5a 100644
--- a/geocity/apps/submissions/templates/submissions/_submission_actions.html
+++ b/geocity/apps/submissions/templates/submissions/_submission_actions.html
@@ -91,7 +91,6 @@ {% trans "" %}
{% trans "Informations générales" %}