feat: RGPD 100% + UX improvements staff contrats

- Audit RGPD: confirmation PDFMonkey (100% services conformes EU)
- Staff contrats: reset tous filtres lors filtres rapides (recherche, org, type)
- Staff contrats: ajout colonne Production (production_name)
- Signature salarie: message info scroll pour signature
This commit is contained in:
odentas 2025-10-23 18:24:41 +02:00
parent 8cb366ee53
commit e1d89ab765
3 changed files with 108 additions and 29 deletions

View file

@ -2,7 +2,8 @@
> **Date de l'audit** : 23 octobre 2025 > **Date de l'audit** : 23 octobre 2025
> **Projet** : Nouvel Espace Paie Odentas > **Projet** : Nouvel Espace Paie Odentas
> **Objectif** : Vérifier que toutes les données clients restent dans l'Union Européenne > **Objectif** : Vérifier que toutes les données clients restent dans l'Union Européenne
> **Résultat** : ✅ **9/9 services conformes (100%)**
--- ---
@ -18,7 +19,7 @@
| **Docuseal** | 🇪🇺 api.docuseal.eu | Version EU | ✅ Conforme | Signatures électroniques | - | | **Docuseal** | 🇪🇺 api.docuseal.eu | Version EU | ✅ Conforme | Signatures électroniques | - |
| **PostHog** | 🇪🇺 eu.i.posthog.com | Instance EU | ✅ Conforme | Analytics utilisateurs | - | | **PostHog** | 🇪🇺 eu.i.posthog.com | Instance EU | ✅ Conforme | Analytics utilisateurs | - |
| **GoCardless** | 🇪🇺 EEE | Serveurs EEE + SCC UE | ✅ Conforme | Mandats SEPA, paiements | ✅ **Confirmé par support** | | **GoCardless** | 🇪🇺 EEE | Serveurs EEE + SCC UE | ✅ Conforme | Mandats SEPA, paiements | ✅ **Confirmé par support** |
| **PDFMonkey** | ⚠️ **En attente** | Heroku + AWS (région ?) | ⚠️ **À confirmer** | Contrats CDDU (données salariés) | **Mail envoyé - en attente réponse** | | **PDFMonkey** | 🇪🇺 EU | Heroku + AWS EU | ✅ Conforme | Contrats CDDU (données salariés) | ✅ **Confirmé par support** |
--- ---
@ -69,19 +70,7 @@
### ⚠️ Services à vérifier ### ⚠️ Services à vérifier
#### PDFMonkey - **Statut** : ✅ **100% conforme**
- **API** : `https://api.pdfmonkey.io`
- **Entreprise** : Française (Paris - 51 rue de Ponthieu, 75008)
- **Infrastructure déclarée** : Heroku + AWS (région non spécifiée)
- **Données envoyées** :
- Informations contrats CDDU (nom, prénom, dates, salaires)
- Informations structure employeur
- Informations production
- **Problème** : Heroku héberge par défaut aux USA (`us-east-1`)
- **Action** :
- ✅ Mail envoyé à `tinymonkey@pdfmonkey.io` le 23/10/2025
- ⏳ En attente de confirmation de la région de traitement
- **Statut** : ⚠️ **En attente de réponse**
#### GoCardless #### GoCardless
- **Environment** : `live` - **Environment** : `live`
@ -98,6 +87,49 @@
- **Confirmation** : ✅ Support GoCardless - 23 octobre 2025 - **Confirmation** : ✅ Support GoCardless - 23 octobre 2025
- **Statut** : ✅ **Conforme RGPD** - **Statut** : ✅ **Conforme RGPD**
#### PDFMonkey
- **API** : `https://api.pdfmonkey.io`
- **Entreprise** : Française (Paris - 51 rue de Ponthieu, 75008)
- **Infrastructure** : Heroku + AWS - Serveurs UE
- **Données envoyées** :
- Informations contrats CDDU (nom, prénom, dates, salaires)
- Informations structure employeur
- Informations production
- **Confirmation support** : ✅ 23 octobre 2025
> "Je confirme, les données (serveurs, base de données, stockage des fichiers) sont bien stockées en UE"
> — Simon, PDFMonkey (tinymonkey@pdfmonkey.io)
- **Statut** : ✅ **Conforme RGPD**
---
#### GoCardless
- **Environment** : `live`
- **API** : GoCardless UK/EU
- **Infrastructure** : Serveurs dans l'Espace Économique Européen (EEE)
- **Données** : Mandats SEPA, paiements, coordonnées bancaires
- **Mécanismes de protection** :
- Opérations principales de traitement des paiements : ✅ Serveurs EEE
- Prestataires tiers (hors EEE) : ✅ Clauses Contractuelles Types (SCC) de l'UE
- Contrôle préalable des fournisseurs avec mécanismes RGPD approuvés
- **Documentation** :
- https://gocardless.com/legal/data-protection/
- https://gocardless.com/fr-fr/privacy/fr-gdpr/
- **Confirmation** : ✅ Support GoCardless - 23 octobre 2025
- **Statut** : ✅ **Conforme RGPD**
#### PDFMonkey
- **API** : `https://api.pdfmonkey.io`
- **Entreprise** : Française (Paris - 51 rue de Ponthieu, 75008)
- **Infrastructure** : Heroku + AWS - Serveurs UE
- **Données envoyées** :
- Informations contrats CDDU (nom, prénom, dates, salaires)
- Informations structure employeur
- Informations production
- **Confirmation support** : ✅ 23 octobre 2025
> "Je confirme, les données (serveurs, base de données, stockage des fichiers) sont bien stockées en UE"
> — Simon, PDFMonkey (tinymonkey@pdfmonkey.io)
- **Statut** : ✅ **Conforme RGPD**
--- ---
## 📝 Notes sur les services retirés ## 📝 Notes sur les services retirés
@ -139,15 +171,20 @@ Ces services ont été **complètement retirés** du projet le 23 octobre 2025 :
## 🎯 Plan d'action ## 🎯 Plan d'action
### 🚨 Priorité CRITIQUE ### ✅ Tous les services confirmés !
1. **PDFMonkey - Attendre réponse** **Statut global** : 🎉 **9/9 services conformes RGPD (100%)**
- ✅ Mail envoyé le 23/10/2025
- ⏳ Attendre confirmation de la localisation des serveurs Tous les services utilisés par l'Espace Paie Odentas ont été vérifiés et **stockent les données dans l'Union Européenne** :
- Si réponse négative (serveurs hors EU) → **Migrer vers alternative** : - ✅ AWS (S3, SES, Lambda) - eu-west-3
- DocRaptor (option EU disponible) - ✅ Supabase - EU
- PDF.co (serveurs EU) - ✅ Vercel - cdg1 (Paris)
- Solution auto-hébergée (Puppeteer + AWS Lambda eu-west-3) - ✅ Docuseal - api.docuseal.eu
- ✅ PostHog - eu.i.posthog.com
- ✅ GoCardless - EEE + SCC (confirmé par support)
- ✅ PDFMonkey - EU (confirmé par support)
**Aucune action requise** - La plateforme est **100% conforme RGPD**.
--- ---
@ -194,6 +231,20 @@ Merci d'avance pour votre retour rapide.
**Citation** : **Citation** :
> "L'ensemble de nos principales opérations de traitement des paiements européens sont exécutées sur des serveurs situés dans l'Espace économique européen (EEE). [...] Dès lors que des données sont stockées dans ces services, nous veillons à ce qu'elles soient protégées selon les normes de l'Union européenne, en utilisant un mécanisme de transfert approuvé par le RGPD." > "L'ensemble de nos principales opérations de traitement des paiements européens sont exécutées sur des serveurs situés dans l'Espace économique européen (EEE). [...] Dès lors que des données sont stockées dans ces services, nous veillons à ce qu'elles soient protégées selon les normes de l'Union européenne, en utilisant un mécanisme de transfert approuvé par le RGPD."
### Réponse de PDFMonkey
**Date** : 23 octobre 2025
**Source** : Simon - PDFMonkey (tinymonkey@pdfmonkey.io)
**Statut** : ✅ **Conforme RGPD**
**Citation** :
> "Bonjour Renaud,
>
> Je confirme, les données (serveurs, base de données, stockage des fichiers) sont bien stockées en UE
>
> Bien à vous,
> Simon"
--- ---
## 📝 Notes importantes ## 📝 Notes importantes
@ -224,12 +275,13 @@ Ces CDNs peuvent servir depuis des serveurs hors UE, mais ils ne contiennent **a
| Statut | Nombre de services | | Statut | Nombre de services |
|--------|-------------------| |--------|-------------------|
| ✅ Conforme RGPD | 8 services | | ✅ Conforme RGPD | 9 services |
| ⚠️ À vérifier | 1 service | | ⚠️ À vérifier | 0 service |
| ❌ Non conforme | 0 service | | ❌ Non conforme | 0 service |
| ✅ Retiré | 2 services (Airtable, n8n) | | ✅ Retiré | 2 services (Airtable, n8n) |
### Taux de conformité actuel ### Taux de conformité final
**8 / 9 confirmés = 89%** **9 / 9 confirmés = 100% 🎉**
Tous les services utilisés par l'Espace Paie Odentas sont conformes RGPD et stockent les données dans l'Union Européenne.
Avec la vérification en cours (PDFMonkey), le taux devrait atteindre **100%**.

View file

@ -36,7 +36,7 @@ export default async function StaffContractsPage() {
const { data: contracts, error } = await sb const { data: contracts, error } = await sb
.from("cddu_contracts") .from("cddu_contracts")
.select( .select(
`id, contract_number, employee_name, employee_id, structure, type_de_contrat, start_date, end_date, created_at, etat_de_la_demande, etat_de_la_paie, dpae, gross_pay, org_id, contrat_signe_par_employeur, contrat_signe, last_employer_notification_at, last_employee_notification_at, `id, contract_number, employee_name, employee_id, structure, type_de_contrat, start_date, end_date, created_at, etat_de_la_demande, etat_de_la_paie, dpae, gross_pay, org_id, contrat_signe_par_employeur, contrat_signe, last_employer_notification_at, last_employee_notification_at, production_name,
salaries!employee_id(salarie, nom, prenom, adresse_mail)` salaries!employee_id(salarie, nom, prenom, adresse_mail)`
) )
.order("start_date", { ascending: false }) .order("start_date", { ascending: false })

View file

@ -128,6 +128,7 @@ type Contract = {
contrat_signe?: string | null; contrat_signe?: string | null;
last_employer_notification_at?: string | null; last_employer_notification_at?: string | null;
last_employee_notification_at?: string | null; last_employee_notification_at?: string | null;
production_name?: string | null;
salaries?: { salaries?: {
salarie?: string | null; salarie?: string | null;
nom?: string | null; nom?: string | null;
@ -294,6 +295,11 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
const applyQuickFilterDpaeAFaire = () => { const applyQuickFilterDpaeAFaire = () => {
const today = new Date(); const today = new Date();
const in7 = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7); const in7 = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7);
// Reset tous les filtres pour voir uniquement ce filtre rapide
setQ(""); // Reset recherche
setStructureFilter(null); // Reset organisation
setTypeFilter(null); // Reset type de contrat
setSignatureFilter(null); // Reset signature
setDpaeFilter('À faire'); setDpaeFilter('À faire');
setEtatContratFilters(new Set()); // Reset état contrat setEtatContratFilters(new Set()); // Reset état contrat
setEtatPaieFilter(null); // Reset état paie setEtatPaieFilter(null); // Reset état paie
@ -309,6 +315,11 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
const today = new Date(); const today = new Date();
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1); const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
const lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0); const lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0);
// Reset tous les filtres pour voir uniquement ce filtre rapide
setQ(""); // Reset recherche
setStructureFilter(null); // Reset organisation
setTypeFilter(null); // Reset type de contrat
setSignatureFilter(null); // Reset signature
setEtatContratFilters(new Set(['Reçue', 'En cours'])); // Multiple selections setEtatContratFilters(new Set(['Reçue', 'En cours'])); // Multiple selections
setDpaeFilter(null); // Reset DPAE setDpaeFilter(null); // Reset DPAE
setEtatPaieFilter(null); // Reset état paie setEtatPaieFilter(null); // Reset état paie
@ -329,6 +340,11 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
// Premier jour du mois dernier // Premier jour du mois dernier
const firstDayLastMonth = new Date(lastDayLastMonth.getFullYear(), lastDayLastMonth.getMonth(), 1); const firstDayLastMonth = new Date(lastDayLastMonth.getFullYear(), lastDayLastMonth.getMonth(), 1);
// Reset tous les filtres pour voir uniquement ce filtre rapide
setQ(""); // Reset recherche
setStructureFilter(null); // Reset organisation
setTypeFilter(null); // Reset type de contrat
setSignatureFilter(null); // Reset signature
setEtatPaieFilter('À traiter'); setEtatPaieFilter('À traiter');
setDpaeFilter(null); // Reset DPAE setDpaeFilter(null); // Reset DPAE
setEtatContratFilters(new Set()); // Reset état contrat setEtatContratFilters(new Set()); // Reset état contrat
@ -342,6 +358,11 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
}; };
const applyQuickFilterPaieATraiterToutes = () => { const applyQuickFilterPaieATraiterToutes = () => {
// Reset tous les filtres pour voir uniquement ce filtre rapide
setQ(""); // Reset recherche
setStructureFilter(null); // Reset organisation
setTypeFilter(null); // Reset type de contrat
setSignatureFilter(null); // Reset signature
setEtatPaieFilter('À traiter'); setEtatPaieFilter('À traiter');
setDpaeFilter(null); // Reset DPAE setDpaeFilter(null); // Reset DPAE
setEtatContratFilters(new Set(['Traitée'])); // Ajouter le filtre "Traitée" setEtatContratFilters(new Set(['Traitée'])); // Ajouter le filtre "Traitée"
@ -365,6 +386,10 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
const tomorrow = new Date(today); const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setDate(tomorrow.getDate() + 1);
// Reset tous les filtres pour voir uniquement ce filtre rapide
setQ(""); // Reset recherche
setStructureFilter(null); // Reset organisation
setTypeFilter(null); // Reset type de contrat
setSignatureFilter('non_signe'); // Filtre pour les contrats non complètement signés setSignatureFilter('non_signe'); // Filtre pour les contrats non complètement signés
setEtatPaieFilter(null); // Reset état paie setEtatPaieFilter(null); // Reset état paie
setEtatContratFilters(new Set()); // Reset état contrat setEtatContratFilters(new Set()); // Reset état contrat
@ -1957,6 +1982,7 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
Salarié {sortField === 'employee_name' ? (sortOrder === 'asc' ? '▲' : '▼') : ''} Salarié {sortField === 'employee_name' ? (sortOrder === 'asc' ? '▲' : '▼') : ''}
</th> </th>
<th className="text-left px-3 py-2">Structure</th> <th className="text-left px-3 py-2">Structure</th>
<th className="text-left px-3 py-2">Production</th>
<th className="text-left px-3 py-2">Type</th> <th className="text-left px-3 py-2">Type</th>
<th className="text-left px-3 py-2 cursor-pointer" onClick={() => { setSortField('start_date'); setSortOrder((o) => o === 'asc' ? 'desc' : 'asc'); }}> <th className="text-left px-3 py-2 cursor-pointer" onClick={() => { setSortField('start_date'); setSortOrder((o) => o === 'asc' ? 'desc' : 'asc'); }}>
Date début {sortField === 'start_date' ? (sortOrder === 'asc' ? '▲' : '▼') : ''} Date début {sortField === 'start_date' ? (sortOrder === 'asc' ? '▲' : '▼') : ''}
@ -2062,6 +2088,7 @@ function ContractsGridImpl({ initialData, activeOrgId }: { initialData: Contract
<td className="px-3 py-2">{formatEmployeeName(r)}</td> <td className="px-3 py-2">{formatEmployeeName(r)}</td>
<td className="px-3 py-2">{r.structure || "—"}</td> <td className="px-3 py-2">{r.structure || "—"}</td>
<td className="px-3 py-2">{r.production_name || "—"}</td>
<td className="px-3 py-2">{ <td className="px-3 py-2">{
r.type_de_contrat === "CDD d'usage" ? "CDDU" : (r.type_de_contrat || "—") r.type_de_contrat === "CDD d'usage" ? "CDDU" : (r.type_de_contrat || "—")
}</td> }</td>