Skip to content

Commit

Permalink
Merge branch 'staging' into 994-alphabetical-order
Browse files Browse the repository at this point in the history
  • Loading branch information
alemangui committed Jan 14, 2025
2 parents 8d40332 + a9e7694 commit e2cac58
Show file tree
Hide file tree
Showing 8 changed files with 639 additions and 26 deletions.
6 changes: 6 additions & 0 deletions data/etl/teleicare_history/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def create_declaration_from_teleicare_history():
* télédéclarante de la déclaration (cette relation n'est pour le moment pas conservée, car le BEPIAS ne sait pas ce qu'elle signifie)
"""
nb_created_declarations = 0

for ica_complement_alimentaire in IcaComplementAlimentaire.objects.all():
# retrouve la déclaration la plus à jour correspondant à ce complément alimentaire
all_ica_declarations = IcaDeclaration.objects.filter(cplalim_id=ica_complement_alimentaire.cplalim_ident)
Expand Down Expand Up @@ -207,10 +208,15 @@ def create_declaration_from_teleicare_history():
# other_conditions=
# effects=
# other_effects=
# address=
# postal_code=
# city=
# country=
status=Declaration.DeclarationStatus.WITHDRAWN
if latest_ica_declaration.dcl_date_fin_commercialisation
else DECLARATION_STATUS_MAPPING[latest_ica_version_declaration.stattdcl_ident],
)

try:
declaration.save()
nb_created_declarations += 1
Expand Down
86 changes: 86 additions & 0 deletions data/factories/teleicare_history/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,32 @@

import factory
import faker

from phonenumber_field.phonenumber import PhoneNumber
from datetime import datetime, timedelta
from random import randrange

from data.choices import CountryChoices
from data.models.teleicare_history.ica_etablissement import IcaEtablissement
from data.models.teleicare_history.ica_declaration import (
IcaComplementAlimentaire,
IcaDeclaration,
IcaVersionDeclaration,
)
from data.utils.string_utils import make_random_str
from data.factories.company import _make_siret, _make_vat, _make_phone_number


def random_date(start, end=datetime.now()):
"""
Retourne une date random entre une date de début et une date de fin
"""
delta = end - start
int_delta = (delta.days * 24 * 60 * 60) + delta.seconds
random_second = randrange(int_delta)
return start + timedelta(seconds=random_second)


class EtablissementFactory(factory.django.DjangoModelFactory):
class Meta:
model = IcaEtablissement
Expand All @@ -28,3 +46,71 @@ class Meta:
etab_adre_ville = factory.Faker("city", locale="FR")
etab_adre_cp = factory.Faker("postcode", locale="FR")
etab_adre_voie = factory.Faker("street_address", locale="FR")


class ComplementAlimentaireFactory(factory.django.DjangoModelFactory):
class Meta:
model = IcaComplementAlimentaire

cplalim_ident = factory.Sequence(lambda n: n + 1)
frmgal_ident = factory.Faker("pyint", min_value=0, max_value=20)
etab = factory.SubFactory(EtablissementFactory)
cplalim_marque = factory.Faker("text", max_nb_chars=20)
cplalim_gamme = factory.Faker("text", max_nb_chars=20)
cplalim_nom = factory.Faker("text", max_nb_chars=20)
dclencours_gout_arome_parfum = factory.Faker("text", max_nb_chars=20)
cplalim_forme_galenique_autre = factory.Faker("text", max_nb_chars=20)


class DeclarationFactory(factory.django.DjangoModelFactory):
class Meta:
model = IcaDeclaration

dcl_ident = factory.Sequence(lambda n: n + 1)
cplalim = factory.SubFactory(ComplementAlimentaireFactory)
tydcl_ident = factory.Faker("pyint", min_value=0, max_value=20)
etab = factory.SubFactory(EtablissementFactory)
etab_ident_rmm_declarant = factory.Faker("pyint", min_value=0, max_value=20)
dcl_date = datetime.strftime(random_date(start=datetime(2016, 1, 1)), "%m/%d/%Y %H:%M:%S %p")
dcl_date_fin_commercialisation = factory.LazyFunction(
lambda: datetime.strftime(random_date(start=datetime(2016, 1, 1)), "%m/%d/%Y %H:%M:%S %p")
if random.random() > 0.3
else None
)


class VersionDeclarationFactory(factory.django.DjangoModelFactory):
class Meta:
model = IcaVersionDeclaration

vrsdecl_ident = factory.Sequence(lambda n: n + 1)
ag_ident = factory.Faker("pyint", min_value=0, max_value=20)
typvrs_ident = factory.Faker("pyint", min_value=0, max_value=20)
unt_ident = factory.Faker("pyint", min_value=0, max_value=20)
pays_ident_adre = factory.Faker("pyint", min_value=0, max_value=8)
etab = factory.SubFactory(EtablissementFactory)
pays_ident_pays_de_reference = factory.Faker("pyint", min_value=0, max_value=8)
dcl = factory.SubFactory(DeclarationFactory)
stattdcl_ident = factory.Faker("pyint", min_value=0, max_value=8)
stadcl_ident = factory.Faker("pyint", min_value=0, max_value=8)
vrsdecl_numero = factory.Faker("pyint", min_value=0, max_value=20)
vrsdecl_commentaires = factory.Faker("text", max_nb_chars=20)
vrsdecl_mise_en_garde = factory.Faker("text", max_nb_chars=20)
vrsdecl_durabilite = factory.Faker("pyint", min_value=0, max_value=8)
vrsdecl_mode_emploi = factory.Faker("text", max_nb_chars=20)
vrsdecl_djr = factory.fuzzy.FuzzyText(length=4, chars=string.ascii_uppercase + string.digits)
vrsdecl_conditionnement = factory.Faker("text", max_nb_chars=20)
vrsdecl_poids_uc = factory.Faker("pyfloat")
vrsdecl_forme_galenique_autre = factory.Faker("text", max_nb_chars=20)
vrsdecl_date_limite_reponse_pro = factory.Faker("text", max_nb_chars=20)
vrsdecl_observations_ac = factory.Faker("text", max_nb_chars=20)
vrsdecl_observations_pro = factory.Faker("text", max_nb_chars=20)
vrsdecl_numero_dossiel = factory.Faker("text", max_nb_chars=20)
vrsdecl_adre_ville = factory.Faker("text", max_nb_chars=20)
vrsdecl_adre_cp = factory.Faker("text", max_nb_chars=20)
vrsdecl_adre_voie = factory.Faker("text", max_nb_chars=20)
vrsdecl_adre_comp = factory.Faker("text", max_nb_chars=20)
vrsdecl_adre_comp2 = factory.Faker("text", max_nb_chars=20)
vrsdecl_adre_dist = factory.Faker("text", max_nb_chars=20)
vrsdecl_adre_region = factory.Faker("text", max_nb_chars=20)
vrsdecl_adre_raison_sociale = factory.Faker("text", max_nb_chars=20)
18 changes: 9 additions & 9 deletions data/models/teleicare_history/ica_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ class IcaDeclaration(models.Model):
) # correspond à l'entreprise gestionnaire de la déclaration
etab_ident_rmm_declarant = models.IntegerField()
dcl_date = models.TextField()
dcl_saisie_administration = models.BooleanField()
dcl_annee = models.IntegerField()
dcl_mois = models.IntegerField()
dcl_numero = models.IntegerField()
dcl_saisie_administration = models.BooleanField(null=True) # rendu nullable pour simplifier les Factories
dcl_annee = models.IntegerField(null=True) # rendu nullable pour simplifier les Factories
dcl_mois = models.IntegerField(null=True) # rendu nullable pour simplifier les Factories
dcl_numero = models.IntegerField(null=True) # rendu nullable pour simplifier les Factories
dcl_date_fin_commercialisation = models.TextField(blank=True, null=True)

class Meta:
Expand All @@ -55,20 +55,20 @@ class Meta:
class IcaVersionDeclaration(models.Model):
vrsdecl_ident = models.IntegerField(primary_key=True)
ag_ident = models.IntegerField(blank=True, null=True)
typvrs_ident = models.IntegerField()
typvrs_ident = models.IntegerField(null=True) # rendu nullable pour simplifier les Factories
unt_ident = models.IntegerField(blank=True, null=True)
pays_ident_adre = models.IntegerField(blank=True, null=True)
etab = models.ForeignKey(
IcaEtablissement, on_delete=models.CASCADE, db_column="etab_ident"
) # correspond à l'entreprise télédéclarante
ex_ident = models.IntegerField()
ex_ident = models.IntegerField(null=True) # rendu nullable pour simplifier les Factories
pays_ident_pays_de_reference = models.IntegerField(blank=True, null=True)
dcl = models.ForeignKey(
IcaDeclaration, on_delete=models.CASCADE, db_column="dcl_ident"
) # dcl_ident est aussi une foreign_key vers IcaComplementAlimentaire
stattdcl_ident = models.IntegerField(blank=True, null=True)
stadcl_ident = models.IntegerField(blank=True, null=True)
vrsdecl_numero = models.IntegerField()
vrsdecl_numero = models.IntegerField(null=True)
vrsdecl_commentaires = models.TextField(blank=True, null=True)
vrsdecl_mise_en_garde = models.TextField(blank=True, null=True)
vrsdecl_durabilite = models.IntegerField(blank=True, null=True)
Expand All @@ -80,9 +80,9 @@ class IcaVersionDeclaration(models.Model):
vrsdecl_date_limite_reponse_pro = models.TextField(blank=True, null=True)
vrsdecl_observations_ac = models.TextField(blank=True, null=True)
vrsdecl_observations_pro = models.TextField(blank=True, null=True)
vrsdecl_mode_json = models.BooleanField()
vrsdecl_mode_json = models.BooleanField(null=True) # rendu nullable pour simplifier les Factories
vrsdecl_numero_dossiel = models.TextField(blank=True, null=True)
vrsdecl_mode_sans_verif = models.BooleanField()
vrsdecl_mode_sans_verif = models.BooleanField(null=True) # rendu nullable pour simplifier les Factories
vrsdecl_adre_ville = models.TextField(blank=True, null=True)
vrsdecl_adre_cp = models.TextField(blank=True, null=True)
vrsdecl_adre_voie = models.TextField(blank=True, null=True)
Expand Down
93 changes: 79 additions & 14 deletions data/tests/test_teleicare_history_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,26 @@
from django.db import connection
from django.test import TestCase

from data.etl.teleicare_history.extractor import match_companies_on_siret_or_vat
from data.etl.teleicare_history.extractor import (
create_declaration_from_teleicare_history,
match_companies_on_siret_or_vat,
)
from data.factories.company import CompanyFactory, _make_siret, _make_vat
from data.factories.teleicare_history import EtablissementFactory
from data.factories.galenic_formulation import GalenicFormulationFactory
from data.factories.teleicare_history import (
ComplementAlimentaireFactory,
DeclarationFactory,
EtablissementFactory,
VersionDeclarationFactory,
)
from data.factories.unit import SubstanceUnitFactory
from data.models.company import Company
from data.models.declaration import Declaration
from data.models.teleicare_history.ica_declaration import (
IcaComplementAlimentaire,
IcaDeclaration,
IcaVersionDeclaration,
)
from data.models.teleicare_history.ica_etablissement import IcaEtablissement


Expand All @@ -20,21 +36,24 @@ def setUp(self):
Adapted from: https://stackoverflow.com/a/49800437
"""
super().setUp()
with connection.schema_editor() as schema_editor:
schema_editor.create_model(IcaEtablissement)
for table in [IcaEtablissement, IcaComplementAlimentaire, IcaDeclaration, IcaVersionDeclaration]:
with connection.schema_editor() as schema_editor:
schema_editor.create_model(table)

if IcaEtablissement._meta.db_table not in connection.introspection.table_names():
raise ValueError(
"Table `{table_name}` is missing in test database.".format(
table_name=IcaEtablissement._meta.db_table
if table._meta.db_table not in connection.introspection.table_names():
raise ValueError(
"Table `{table_name}` is missing in test database.".format(table_name=table._meta.db_table)
)
)

def tearDown(self):
super().tearDown()

with connection.schema_editor() as schema_editor:
schema_editor.delete_model(IcaEtablissement)
for table in [IcaVersionDeclaration, IcaComplementAlimentaire, IcaDeclaration, IcaEtablissement]:
table.objects.all().delete()
# la suppression des modèles fail avec l'erreur
# django.db.utils.OperationalError: cannot DROP TABLE "ica_versiondeclaration" because it has pending trigger events
# même avec un sleep(15)
# with connection.schema_editor() as schema_editor:
# schema_editor.delete_model(table)

def test_match_companies_on_siret_or_vat(self):
"""
Expand Down Expand Up @@ -90,15 +109,61 @@ def test_create_new_companies(self):
"""

etablissement_to_create_as_company = EtablissementFactory(etab_siret=None, etab_ica_importateur=True)
# ne sera pas créé car le numéro de téléphone est mal formatté
# devrait être créée malgré le numéro de téléphone mal formaté
_ = EtablissementFactory(etab_siret=None, etab_ica_importateur=True, etab_telephone="0345")
self.assertEqual(Company.objects.filter(siccrf_id=etablissement_to_create_as_company.etab_ident).count(), 0)

match_companies_on_siret_or_vat(create_if_not_exist=True)
self.assertEqual(Company.objects.filter(siccrf_id=etablissement_to_create_as_company.etab_ident).count(), 1)
self.assertTrue(Company.objects.filter(siccrf_id=etablissement_to_create_as_company.etab_ident).exists())
self.assertEqual(Company.objects.exclude(siccrf_id=None).count(), 2)

created_company = Company.objects.get(siccrf_id=etablissement_to_create_as_company.etab_ident)
self.assertEqual(created_company.siccrf_id, etablissement_to_create_as_company.etab_ident)
self.assertEqual(created_company.address, etablissement_to_create_as_company.etab_adre_voie)
self.assertEqual(created_company.postal_code, etablissement_to_create_as_company.etab_adre_cp)
self.assertEqual(created_company.city, etablissement_to_create_as_company.etab_adre_ville)

def test_create_declaration_from_history(self):
"""
Les déclarations sont créées à partir d'object historiques des modèles Ica_
"""
galenic_formulation_id = 1
galenic_formulation = GalenicFormulationFactory(siccrf_id=galenic_formulation_id)
unit_id = 1
unit = SubstanceUnitFactory(siccrf_id=unit_id)
etablissement_to_create_as_company = EtablissementFactory(etab_siret=None, etab_ica_importateur=True)

CA_to_create_as_declaration = ComplementAlimentaireFactory(
etab=etablissement_to_create_as_company, frmgal_ident=galenic_formulation_id
)
declaration_to_create_as_declaration = DeclarationFactory(cplalim=CA_to_create_as_declaration)
version_declaration_to_create_as_declaration = VersionDeclarationFactory(
dcl=declaration_to_create_as_declaration,
stadcl_ident=8,
stattdcl_ident=2,
unt_ident=unit_id,
vrsdecl_djr="32 kg of ppo",
)

match_companies_on_siret_or_vat(create_if_not_exist=True)
create_declaration_from_teleicare_history()

version_declaration_to_create_as_declaration.refresh_from_db()
created_declaration = Declaration.objects.get(siccrf_id=CA_to_create_as_declaration.cplalim_ident)
self.assertEqual(created_declaration.name, CA_to_create_as_declaration.cplalim_nom)
self.assertEqual(created_declaration.brand, CA_to_create_as_declaration.cplalim_marque)
self.assertEqual(created_declaration.gamme, CA_to_create_as_declaration.cplalim_gamme)
self.assertEqual(created_declaration.flavor, CA_to_create_as_declaration.dclencours_gout_arome_parfum)
self.assertEqual(created_declaration.galenic_formulation, galenic_formulation)
self.assertEqual(created_declaration.unit_quantity, 32)
self.assertEqual(created_declaration.unit_measurement, unit)
self.assertEqual(
created_declaration.conditioning, version_declaration_to_create_as_declaration.vrsdecl_conditionnement
)
self.assertEqual(
created_declaration.daily_recommended_dose,
str(version_declaration_to_create_as_declaration.vrsdecl_poids_uc),
)
self.assertEqual(
created_declaration.minimum_duration, str(version_declaration_to_create_as_declaration.vrsdecl_durabilite)
)
4 changes: 4 additions & 0 deletions frontend/src/components/AppHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ const navItems = [
to: "/blog",
text: "Ressources",
},
{
to: "/faq",
text: "FAQ",
},
]
const loggedOnlyNavItems = [
{
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import ContactForm from "@/views/ContactForm"
import CompliancePage from "@/views/CompliancePage"
import DeclaredElementPage from "@/views/DeclaredElementPage"
import MandatedCompaniesPage from "@/views/MandatedCompaniesPage"
import FaqPage from "@/views/FaqPage"
import { ref } from "vue"

const routes = [
Expand Down Expand Up @@ -372,6 +373,14 @@ const routes = [
title: "Conformité au droit alimentaire",
},
},
{
path: "/faq",
name: "FaqPage",
component: FaqPage,
meta: {
title: "Foire aux questions",
},
},
{
path: "/:catchAll(.*)*", // https://stackoverflow.com/a/70343919/2255491
component: NotFound,
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/views/BlogPostPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
/>
</div>
<div v-if="blogPost" class="fr-container my-8">
<h1 class="fr-h4">{{ blogPost.title }}</h1>
<p class="my-0">{{ author }}</p>
<p class="fr-text--xs my-0">{{ date }}</p>
<div class="p-4 border mb-4">
<h1 class="fr-h4 !mb-2">{{ blogPost.title }}</h1>
<p class="!mb-0">{{ author }}</p>
<p class="fr-text--xs !mb-0">{{ date }}</p>
</div>
<div id="content" v-html="blogPost.content || blogPost.body" class="text-left"></div>
</div>
</template>
Expand Down
Loading

0 comments on commit e2cac58

Please sign in to comment.