Skip to content

Commit

Permalink
Merge branch 'staging' into 717-front
Browse files Browse the repository at this point in the history
  • Loading branch information
Perrine Letellier committed Jan 10, 2025
2 parents ff46c7c + 312a50c commit e1bd4e3
Show file tree
Hide file tree
Showing 20 changed files with 662 additions and 70 deletions.
1 change: 1 addition & 0 deletions api/serializers/declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ class Meta:
"id",
"file",
"type",
"type_display",
"name",
)
read_only_fields = ("file",)
Expand Down
52 changes: 51 additions & 1 deletion api/tests/test_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
SupervisorRoleFactory,
VisaRoleFactory,
)
from data.models import Attachment, Declaration, Snapshot, DeclaredMicroorganism, DeclaredPlant
from data.models import Attachment, Declaration, DeclaredMicroorganism, DeclaredPlant, Snapshot

from .utils import authenticate

Expand Down Expand Up @@ -1496,6 +1496,56 @@ def test_sort_declarations_by_instruction_limit(self):
self.assertEqual(results[1]["id"], declaration_middle.id)
self.assertEqual(results[2]["id"], declaration_last.id)

@authenticate
def test_sort_declarations_by_instruction_limit_with_visa(self):
"""
Le refus d'un visa ne doit pas compter pour le triage par date limite de réponse
"""
InstructionRoleFactory(user=authenticate.user)

today = timezone.now()

declaration_less_urgent = AwaitingInstructionDeclarationFactory()
snapshot_less_urgent = SnapshotFactory(
declaration=declaration_less_urgent, status=declaration_less_urgent.status
)
snapshot_less_urgent.creation_date = today - timedelta(days=1)
snapshot_less_urgent.save()

declaration_more_urgent = AwaitingInstructionDeclarationFactory()
snapshot_more_urgent = SnapshotFactory(
declaration=declaration_more_urgent, status=declaration_more_urgent.status
)
snapshot_more_urgent.creation_date = today - timedelta(days=10)
snapshot_more_urgent.save()

# Le snapshot du refus de visa ne doit pas affecter la date limite de réponse
snapshot_visa_refusal = SnapshotFactory(
declaration=declaration_more_urgent,
status=declaration_more_urgent.status,
action=Snapshot.SnapshotActions.REFUSE_VISA,
)
snapshot_visa_refusal.creation_date = today - timedelta(days=1)
snapshot_visa_refusal.save()

# Triage par date limite d'instruction
sort_url = f"{reverse('api:list_all_declarations')}?ordering=responseLimitDate"
response = self.client.get(sort_url, format="json")
results = response.json()["results"]
self.assertEqual(len(results), 2)

self.assertEqual(results[0]["id"], declaration_more_urgent.id)
self.assertEqual(results[1]["id"], declaration_less_urgent.id)

# Triage par date limite d'instruction inversé
reverse_sort_url = f"{reverse('api:list_all_declarations')}?ordering=-responseLimitDate"
response = self.client.get(reverse_sort_url, format="json")
results = response.json()["results"]
self.assertEqual(len(results), 2)

self.assertEqual(results[0]["id"], declaration_less_urgent.id)
self.assertEqual(results[1]["id"], declaration_more_urgent.id)

@authenticate
def test_update_article(self):
"""
Expand Down
4 changes: 4 additions & 0 deletions api/views/declaration/declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ def filter_queryset(self, request, queryset, view):
"""
Cette fonction vise à réproduire la property "response_limit_date" du modèle Declaration
mais dans la couche DB (avec des querysets) afin de pouvoir filtrer dessus.
⚠️ Attention : Tout changement effectué dans cette fonction doit aussi être reflété dans
data/models/declaration.py > response_limit_date
"""
order_by_response_limit, desc = self.order_by_response_limit(request)

Expand All @@ -302,6 +305,7 @@ def filter_queryset(self, request, queryset, view):
Snapshot.objects.filter(
declaration=OuterRef("pk"), status=Declaration.DeclarationStatus.AWAITING_INSTRUCTION
)
.exclude(action=Snapshot.SnapshotActions.REFUSE_VISA)
.order_by("-creation_date" if order_by_response_limit else "creation_date")
.values("creation_date")[:1]
)
Expand Down
12 changes: 11 additions & 1 deletion data/behaviours/auto_validable.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,15 @@ class Meta:
abstract = True

def save(self, *args, **kwargs):
self.full_clean()
"""
* fields_with_no_validation est un set
"""
fields_with_no_validation = kwargs.pop("fields_with_no_validation", ())
if fields_with_no_validation:
self.clean_fields(exclude=fields_with_no_validation)
self.clean()
self.validate_unique()
self.validate_constraints()
else:
self.full_clean()
super().save(*args, **kwargs)
11 changes: 6 additions & 5 deletions data/etl/teleicare_history/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def convert_phone_number(phone_number_to_parse):
if phone_number_to_parse:
phone_number = PhoneNumber.from_string(phone_number_to_parse, region="FR")
return phone_number
return None
return ""


def convert_activities(etab):
Expand Down Expand Up @@ -132,9 +132,8 @@ def match_companies_on_siret_or_vat(create_if_not_exist=False):
# la etab_date_adhesion n'est pas conservée
)
try:
new_company.save()
new_company.save(fields_with_no_validation=("phone_number"))
nb_created_companies += 1
logger.info(f"La company {etab.etab_raison_sociale} est créée via les infos TeleIcare.")
except ValidationError as e:
logger.error(f"Impossible de créer la Company à partir du siccrf_id = {etab.etab_ident}: {e}")

Expand Down Expand Up @@ -208,8 +207,10 @@ def create_declaration_from_teleicare_history():
if latest_ica_version_declaration:
try:
company = Company.objects.get(siccrf_id=ica_complement_alimentaire.etab_id)
except Company.DoesNotExist as e:
logger.error(e.message)
except Company.DoesNotExist:
logger.error(
f"Cette entreprise avec siccrf_id={ica_complement_alimentaire.etab_id} n'existe pas déjà en base"
)
continue
declaration_creation_date = (
convert_str_date(latest_ica_declaration.dcl_date, aware=True)
Expand Down
10 changes: 7 additions & 3 deletions data/models/company.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class CompanyContact(models.Model):
class Meta:
abstract = True

phone_number = PhoneNumberField("numéro de téléphone de contact")
phone_number = PhoneNumberField("numéro de téléphone de contact", blank=True)
email = models.EmailField("adresse e-mail de contact", blank=True)
website = models.CharField("site web de l'entreprise", blank=True)

Expand Down Expand Up @@ -97,7 +97,9 @@ class Meta:
validators=[validate_siret],
)
vat = models.CharField("n° TVA intracommunautaire", unique=True, blank=True, null=True, validators=[validate_vat])
activities = MultipleChoiceField(models.CharField(choices=ActivityChoices), verbose_name="activités", default=list)
activities = MultipleChoiceField(
models.CharField(choices=ActivityChoices), verbose_name="activités", default=list, blank=True
)

supervisors = models.ManyToManyField(
settings.AUTH_USER_MODEL,
Expand Down Expand Up @@ -127,7 +129,9 @@ def clean(self):
raise ValidationError(
"Une entreprise doit avoir un n° de SIRET ou un n°de TVA intracommunautaire (ou les deux)."
)

# Au minimum un point de contact nécessaire (hors None ou "")
if not ((self.phone_number and self.phone_number.is_valid()) or self.email):
raise ValidationError("Une entreprise doit avoir un n° de téléphone ou un e-mail (ou les deux).")
# Pas de duplication possible des activités
if len(self.activities) != len(set(self.activities)):
raise ValidationError("Une entreprise ne peut avoir plusieurs fois la même activité")
Expand Down
14 changes: 13 additions & 1 deletion data/models/declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,11 @@ def response_limit_date(self):

"""
La date limite d'instruction est fixée à deux mois à partir du dernier statut
"en attente d'instruction" sauf dans le cas d'un refus de visa
"en attente d'instruction" sauf dans le cas d'un refus de visa.
⚠️ Attention : Le filtre par date de réponse dans api/views/declaration/declaration.py
refait la même logique dans la couche DB. Tout changement effectué dans cette fonction
doit aussi être reflété dans InstructionDateOrderingFilter > filter_queryset.
"""
concerned_statuses = [
Declaration.DeclarationStatus.AWAITING_INSTRUCTION,
Expand Down Expand Up @@ -782,3 +786,11 @@ class AttachmentType(models.TextChoices):
null=True, blank=True, upload_to="declaration-attachments/%Y/%m/%d/", verbose_name="pièce jointe"
)
name = models.TextField("nom du fichier", blank=True)

@property
def has_pdf_extension(self):
return self.file and self.file.url.endswith(".pdf")

@property
def type_display(self):
return self.get_type_display() or "Type inconnu"
2 changes: 1 addition & 1 deletion frontend/src/components/DeclarationSummary/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
<SummaryInfoSegment label="Description" :value="payload.description" />
<SummaryInfoSegment label="Populations cibles" :value="populationNames" />
<SummaryInfoSegment label="Populations à consommation déconseillée" :value="conditionNames" />
<SummaryInfoSegment label="Mise en garde et avertissement" :value="payload.warning" />
<SummaryInfoSegment label="Forme galénique" :value="galenicFormulationsNames" />
<SummaryInfoSegment label="Mode d'emploi" :value="payload.instructions" />
<SummaryInfoSegment label="Unité de consommation" :value="unitInfo" />
<SummaryInfoSegment label="Dose journalière recommandée" :value="payload.dailyRecommendedDose" />
<SummaryInfoSegment label="Conditionnement" :value="payload.conditioning" />
<SummaryInfoSegment label="Durabilité minimale / DLUO (en mois)" :value="payload.minimumDuration" />
<SummaryInfoSegment label="Mise en garde et avertissement" :value="payload.warnings" />
<SummaryInfoSegment label="Objectifs / effets" :value="effectsNames" />
</div>

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/FilePreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
:required="true"
/>
</DsfrInputGroup>
<div v-else>{{ documentTypes.find((x) => x.value === file.type)?.text }}</div>
<div v-else>{{ file.typeDisplay }}</div>
<div class="flex gap-2">
<a :href="file.file" target="_blank" class="fr-btn fr-btn--secondary fr-btn--sm inline-flex">
Ouvrir {{ isPDF ? "PDF" : "image" }}
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/views/ProducerFormPage/SummaryTab.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<template>
<div>
<div class="text-right">
<a :href="`/declarations/${payload.id}/summary`" download class="text-sm font-medium">
<v-icon name="ri-printer-line"></v-icon>
Imprimer
</a>
</div>
<SectionTitle title="Votre démarche" sizeTag="h6" icon="ri-file-text-line" />
<DeclarationSummary v-model="payload" :readonly="readonly" />
<hr v-if="!readonly" />
Expand Down
Binary file added web/static/fonts/Marianne-Medium.ttf
Binary file not shown.
Binary file added web/static/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/static/images/pdf-file.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e1bd4e3

Please sign in to comment.