Skip to content
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

(PC-30007)[API] feat: add pdf to beneficiary extract #12936

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Meewan
Copy link
Contributor

@Meewan Meewan commented Jun 25, 2024

But de la pull request

Ticket Jira (ou description si BSR) : https://passculture.atlassian.net/browse/PC-30007

Note le rendu du pdf final a été validé par le DPO (le client de la feature)

Vérifications

  • J'ai écrit les tests nécessaires
  • J'ai relu attentivement les migrations, en particulier pour éviter les locks, et je préviens les équipes Shérif et Data
  • J'ai ajouté des screenshots pour d'éventuels changements graphiques

@Meewan Meewan force-pushed the pc-30007-rpa-generate-pdf-html-extraction branch 2 times, most recently from a603d49 to fb6aa7b Compare June 26, 2024 10:00
Copy link
Contributor

@prouzet prouzet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J'ai pas mal de commentaires mais sur la seule lecture du code.
Je n'ai pas testé ce que ça rend visuellement.

api/src/pcapi/tasks/gdpr_tasks.py Show resolved Hide resolved
api/src/pcapi/tasks/gdpr_tasks.py Show resolved Hide resolved
pdf_bytes = generate_pdf_from_html(html_content=html_content)
file_info = zipfile.ZipInfo(
filename=f"{container.internal.user.firstName} {container.internal.user.lastName}.pdf",
date_time=datetime.utcnow().timetuple()[:6],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il n'y a pas de notion de fuseau horaire dans ce champ.
Ne voudrait-on pas la date locale dans le fichier ?
Cela n'a cependant pas une extrême importance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tu veux dire en se basant sur le département du user ? cette méthode étant appelée avec une cloud task potentiellement des heures/jours après la demande je en sais pas si ça a du sens.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oui ça pourrait être tout simplement :
date_time=utc_datetime_to_department_timezone(datetime.utcnow(), container.internal.user.departementCode).timetuple()[:6]
voire même sans le second paramètre si on considère que c'est généré à Paris.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mais c'est assez secondaire, c'est juste une date de fichier.

<meta charset="utf-8">
<title>Réponse à ta demande d'accès</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,400;0,600;0,700;0,800;1,400');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il me semblait qu'on avait souhaité avoir toutes les ressources locales, et ne pas importer des URL externes dans le code. Cela ne serait-il pas le cas ici aussi ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est repris de la template utilisée en Finance. Je pars du principe que si c'est ok pour eux ça l'est aussi pour nous (si l'appel échoue ça ne fait pas échouer le rendu, ça met juste pas la bonne police)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je ne crois pas que ce soit une question de disponibilité de la ressource, mais plus une question de sécurité.

margin: 2cm 1cm;
@bottom-left {
font-size: 6pt;
content: "Pass Culture - SAS au capital de 1,000,000 € - 87/89 rue de la Boétie 75008 PARIS - RCS Paris B 853 318 459 – Siren 853318459 – NAF 7021 Z - N° TVA FR65853318459";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1,000,000 € est une écriture anglophone.

Suggested change
content: "Pass Culture - SAS au capital de 1,000,000 € - 87/89 rue de la Boétie 75008 PARIS - RCS Paris B 853 318 459 – Siren 853318459 – NAF 7021 Z - N° TVA FR65853318459";
content: "Pass Culture - SAS au capital de 1 000 000 € - 87/89 rue de la Boétie 75008 PARIS - RCS Paris B 853 318 459 – Siren 853318459 – NAF 7021 Z - N° TVA FR65853318459";

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c'est le pieds de page de la Finance, donc je ne pense pas le modifier.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ou alors modifier aussi dans la page de finance ? 🙂
Qui est responsable du modèle ?

api/src/pcapi/templates/extracts/beneficiary_extract.html Outdated Show resolved Hide resolved
api/src/pcapi/templates/extracts/beneficiary_extract.html Outdated Show resolved Hide resolved
api/src/pcapi/templates/extracts/beneficiary_extract.html Outdated Show resolved Hide resolved
api/src/pcapi/templates/extracts/beneficiary_extract.html Outdated Show resolved Hide resolved
api/tests/tasks/gdpr_tasks_test.py Show resolved Hide resolved
@Meewan Meewan force-pushed the pc-30007-rpa-generate-pdf-html-extraction branch 2 times, most recently from b1b94aa to 1156d48 Compare June 26, 2024 14:06
@Meewan Meewan requested a review from prouzet June 26, 2024 14:06
@Meewan Meewan force-pushed the pc-30007-rpa-generate-pdf-html-extraction branch from 1156d48 to 9d5997e Compare June 27, 2024 14:10
Copy link
Contributor

@prouzet prouzet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Voici mes remarques en testant la branche dans mon environnement de développement.
Même s'il y a des petites choses à corriger dans le formatage, je trouve le rendu super propre !

@@ -1263,6 +1262,9 @@ def create_extract_user_gdpr_data(user_id: int) -> utils.BackofficeResponse:
if not user:
raise NotFound()

if not (user.is_beneficiary or user.roles == []):
raise NotFound()

if has_gdpr_extract(user=user):
flash("Une extraction de données est déjà en cours pour cet utilisateur.", "error")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Par homogénéité et parce qu'en testant, "error" n'affiche aucun fond :

Suggested change
flash("Une extraction de données est déjà en cours pour cet utilisateur.", "error")
flash("Une extraction de données est déjà en cours pour cet utilisateur.", "warning")

@@ -175,16 +180,27 @@ def _extract_brevo_data(user: users_models.User) -> dict:
def _dump_as_json_bytes(container: serializers.DataContainer) -> tuple[zipfile.ZipInfo, bytes]:
json_bytes = container.json(indent=4).encode("utf-8")
file_info = zipfile.ZipInfo(
filename=f"{container.internal.user.firstName}_{container.internal.user.lastName}.json",
filename=f"{container.internal.user.firstName} {container.internal.user.lastName}.json",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pour un utilisateur sans nom ni prénom (avant de remplir ses infos), je vois des fichiers None None dans le zip, est-ce souhaitable ? devrait-on avoir l'adresse mail comme le nom du zip ?
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

je ne suis aps fan d'avoir un @ dans un nom de fichier mais je vais chercher une solution a ce probleme

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu peux mettre le User ID, je proposais l'email car c'est le nom du fichier zip au moment du téléchargement, donc je pensais que ça te convenait 🙂

Comment on lines +103 to +104
<tr><td>Nom</td><td>{{container.internal.user.firstName}}</td></tr>
<tr><td>Prénom</td><td>{{container.internal.user.lastName}}</td></tr>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nom et prénom peuvent être vides si l'utilisateur n'a pas encore rempli ses infos personnelles :
image

<tr><td>Valeur</td><td>{{('%0.2f' % deposit.amount|float) | replace('.', ',')}}€</td></tr>
<tr><td>Source</td><td>{{deposit.source}}</td></tr>
{% if deposit.dateUpdated %}<tr><td>Dernière modification</td><td>{{deposit.dateUpdated.strftime('%d/%m/%Y %H:%M:%S')}}</td></tr>{% endif %}
<tr><td>Type de crédit</td><td>{{deposit.type.value}}</td></tr>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il manque un formatage vers "Pass 15-17" ou "Pass 18" car GRANT_15_17 est un nom technique
image

<table class="borderless">
<tr><td>Date d'obtention</td><td>{{deposit.dateCreated.strftime('%d/%m/%Y %H:%M:%S')}}</td></tr>
{% if deposit.expirationDate %}<tr><td>Date d'expiration</td><td>{{deposit.expirationDate.strftime('%d/%m/%Y %H:%M:%S')}}</td></tr>{% endif %}
<tr><td>Valeur</td><td>{{('%0.2f' % deposit.amount|float) | replace('.', ',')}}€</td></tr>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<tr><td>Valeur</td><td>{{('%0.2f' % deposit.amount|float) | replace('.', ',')}}€</td></tr>
<tr><td>Valeur</td><td>{{('%0.2f' % deposit.amount|float) | replace('.', ',')}} </td></tr>

<table class="borderless">
<tr><td>Nom</td><td>{{booking.name}}</td></tr>
<tr><td>Quantité</td><td>{{booking.quantity}}</td></tr>
<tr><td>Prix unitaire</td><td>{{'%0.2f' % booking.amount|float }}€</td></tr>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<tr><td>Prix unitaire</td><td>{{'%0.2f' % booking.amount|float }}</td></tr>
<tr><td>Prix unitaire</td><td>{{ ('%0.2f' % deposit.amount|float) | replace('.', ',') }} </td></tr>

image

<tr><td>Date de réservation</td><td>{{booking.dateCreated.strftime('%d/%m/%Y %H:%M:%S')}}</td></tr>
{% if booking.dateUsed %}<tr><td>Date de retrait</td><td>{{booking.dateUsed.strftime('%d/%m/%Y %H:%M:%S')}}</td></tr>{% endif %}
{% if booking.cancellationDate %}<tr><td>Date d'annulation</td><td>{{booking.cancellationDate.strftime('%d/%m/%Y %H:%M:%S')}}</td></tr>{% endif %}
<tr><td>État</td><td>{{booking.status.value}}</td></tr>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ici aussi il manque un formatage de la valeur technique vers un libellé d'affichage, probablement "Validée" dans le cas de cette capture d'écran :
image
Il faut probablement utiliser les mêmes mots que dans l'app.

<table class="borderless">
<tr><td>Date de la validation</td><td>{{validation.dateCreated.strftime('%d/%m/%Y %H:%M:%S')}}</td></tr>
<tr><td>Moyen de la validation</td><td>{{validation.type.value}}</td></tr>
{% if validation.status %}<tr><td>Résultat</td><td>{{validation.status.value}}</td></tr>{%endif%}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il manque un formatage vers le libellé en français, peut-être le même que dans l'app si c'est affiché.
Ici probablement « en attente »
image

{% endif %}

{% if history in container.internal.emailsHistory %}
<h3>📧 <span class=underline>Historique des changements d'adresse de messagerie</span></h3>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J'ai testé sur un utilisateur pour lequel j'ai changé deux fois l'email depuis le backoffice : cet historique n'apparaît pas. Dans la table user_email_history, l'eventType est ADMIN_UPDATE, qui n'est pas dans la liste de la fonction _extract_email_history. Cela mériterait d'étoffer le test auto pour s'assurer que ce cas fonctionne, car il s'agit d'une information personnelle importante.

De plus, dans cette même fonction, ne devrait-on pas se baser sur CONFIRMATION plutôt que UPDATE_REQUEST ? À tester de bout en bout.

{% for action in container.internal.actionsHistory %}
<table class="borderless">
<tr><td>Date</td><td>{{action.actionDate.strftime('%d/%m/%Y %H:%M:%S')}}</td></tr>
<tr><td>Action</td><td>{{action.actionType.value}}</td></tr>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Peut-être souhaite-t-on garder la raison la suspension confidentielle, cependant je pense qu'on devrait au moins préciser si la suspension est à la demande de l'utilisateur ou par le pass Culture. Qu'en pense le DPO ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants