Signature préremplie B64
This commit is contained in:
parent
bf23a6effe
commit
0362bc0738
12 changed files with 1046 additions and 90 deletions
299
DOCUSEAL_DEBUG_GUIDE.md
Normal file
299
DOCUSEAL_DEBUG_GUIDE.md
Normal file
|
|
@ -0,0 +1,299 @@
|
||||||
|
# Guide de Débogage - Pré-remplissage Signature DocuSeal
|
||||||
|
|
||||||
|
## Date: 14 octobre 2025
|
||||||
|
|
||||||
|
## Logs à vérifier dans la console
|
||||||
|
|
||||||
|
Lorsque vous ouvrez une signature électronique, vous devriez voir cette séquence de logs :
|
||||||
|
|
||||||
|
### 1. API Backend (`/api/contrats/[id]/signature`)
|
||||||
|
|
||||||
|
```
|
||||||
|
🔍 [API SIGNATURE] Recherche contrat ID: xxx
|
||||||
|
✅ [API SIGNATURE] Contrat trouvé: { id, org_id, ... }
|
||||||
|
🔍 [API SIGNATURE] org_id présent: xxx
|
||||||
|
🔍 [API SIGNATURE] Recherche de signature pour org_id...
|
||||||
|
📋 [API SIGNATURE] Résultat requête organization_details: {
|
||||||
|
hasData: true/false,
|
||||||
|
hasError: true/false,
|
||||||
|
hasSignature: true/false,
|
||||||
|
signatureLength: xxx,
|
||||||
|
signaturePreview: "data:image/png;base64,..."
|
||||||
|
}
|
||||||
|
✅ [API SIGNATURE] Signature trouvée pour l'organisation
|
||||||
|
📏 [API SIGNATURE] Longueur de la signature: xxx
|
||||||
|
📤 [API SIGNATURE] Réponse finale: {
|
||||||
|
hasSignature: true,
|
||||||
|
signatureLength: xxx,
|
||||||
|
contractId: xxx,
|
||||||
|
orgId: xxx
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Frontend - Récupération des données
|
||||||
|
|
||||||
|
```
|
||||||
|
🔍 [SIGNATURE] Debug - data complète: { ... }
|
||||||
|
📋 [SIGNATURE] Données contrat depuis API: { success: true, data: {...} }
|
||||||
|
🔍 [SIGNATURE] result.data: { ... }
|
||||||
|
🔍 [SIGNATURE] result.data.signature_b64: "data:image/png;base64,..."
|
||||||
|
📦 [SIGNATURE] contractData extrait: { ... }
|
||||||
|
🖊️ [SIGNATURE] signatureB64 extraite: {
|
||||||
|
exists: true,
|
||||||
|
type: "string",
|
||||||
|
length: xxx,
|
||||||
|
preview: "data:image/png;base64,..."
|
||||||
|
}
|
||||||
|
✅ [SIGNATURE] Signature B64 disponible, longueur: xxx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Frontend - Stockage sessionStorage
|
||||||
|
|
||||||
|
```
|
||||||
|
🔍 [SIGNATURE] Tentative de stockage de la signature...
|
||||||
|
🔍 [SIGNATURE] signatureB64 value: "data:image/png;base64,..."
|
||||||
|
📝 [SIGNATURE] Stockage de la signature dans sessionStorage
|
||||||
|
📝 [SIGNATURE] Longueur de la signature: xxx
|
||||||
|
📝 [SIGNATURE] Preview: "data:image/png;base64,..."
|
||||||
|
✅ [SIGNATURE] Signature stockée avec succès, vérification: {
|
||||||
|
stored: true,
|
||||||
|
length: xxx,
|
||||||
|
matches: true
|
||||||
|
}
|
||||||
|
✅ [SIGNATURE] URL embed trouvée: https://docuseal.eu/s/xxx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Frontend - Rendu du composant
|
||||||
|
|
||||||
|
```
|
||||||
|
🎨 [SIGNATURE RENDER] Génération du HTML docuseal-form
|
||||||
|
🎨 [SIGNATURE RENDER] embedSrc: https://docuseal.eu/s/xxx
|
||||||
|
🎨 [SIGNATURE RENDER] Signature depuis sessionStorage: {
|
||||||
|
exists: true,
|
||||||
|
length: xxx,
|
||||||
|
preview: "data:image/png;base64,..."
|
||||||
|
}
|
||||||
|
🎨 [SIGNATURE RENDER] signatureAttr généré: {
|
||||||
|
hasAttr: true,
|
||||||
|
attrLength: xxx,
|
||||||
|
attrPreview: 'data-signature="data:image/png;base64,...'
|
||||||
|
}
|
||||||
|
🎨 [SIGNATURE RENDER] HTML final généré
|
||||||
|
🎨 [SIGNATURE RENDER] HTML length: xxx
|
||||||
|
🎨 [SIGNATURE RENDER] HTML contient data-signature: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Points de vérification
|
||||||
|
|
||||||
|
### Étape 1 : Vérifier la base de données
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Vérifier que la signature existe
|
||||||
|
SELECT
|
||||||
|
org_id,
|
||||||
|
structure,
|
||||||
|
LENGTH(signature_b64) as signature_length,
|
||||||
|
SUBSTRING(signature_b64, 1, 50) as signature_preview
|
||||||
|
FROM organization_details
|
||||||
|
WHERE signature_b64 IS NOT NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Attendu** :
|
||||||
|
- Au moins une ligne avec signature_b64 non NULL
|
||||||
|
- signature_length > 0
|
||||||
|
- signature_preview commence par "data:image/"
|
||||||
|
|
||||||
|
### Étape 2 : Vérifier que le contrat a un org_id
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Vérifier le contrat
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
contract_number,
|
||||||
|
org_id,
|
||||||
|
signature_status
|
||||||
|
FROM cddu_contracts
|
||||||
|
WHERE id = 'VOTRE_CONTRACT_ID';
|
||||||
|
```
|
||||||
|
|
||||||
|
**Attendu** :
|
||||||
|
- org_id non NULL
|
||||||
|
- org_id correspond à une organisation avec signature_b64
|
||||||
|
|
||||||
|
### Étape 3 : Tester l'API directement
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remplacer CONTRACT_ID par l'ID réel
|
||||||
|
curl http://localhost:3000/api/contrats/CONTRACT_ID/signature
|
||||||
|
```
|
||||||
|
|
||||||
|
**Attendu dans la réponse** :
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"id": "...",
|
||||||
|
"org_id": "...",
|
||||||
|
"signature_b64": "data:image/png;base64,..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Étape 4 : Vérifier la console du navigateur
|
||||||
|
|
||||||
|
**Ouvrir DevTools (F12) → Console**
|
||||||
|
|
||||||
|
Chercher les logs :
|
||||||
|
- ✅ Tous les logs commençant par `[API SIGNATURE]`
|
||||||
|
- ✅ Tous les logs commençant par `[SIGNATURE]`
|
||||||
|
- ✅ Tous les logs commençant par `[SIGNATURE RENDER]`
|
||||||
|
|
||||||
|
### Étape 5 : Vérifier sessionStorage
|
||||||
|
|
||||||
|
**Dans DevTools → Application → Storage → Session Storage**
|
||||||
|
|
||||||
|
Chercher la clé : `docuseal_signature_b64`
|
||||||
|
|
||||||
|
**Attendu** :
|
||||||
|
- Clé présente
|
||||||
|
- Valeur commence par "data:image/"
|
||||||
|
- Valeur ressemble à du base64
|
||||||
|
|
||||||
|
### Étape 6 : Vérifier le HTML généré
|
||||||
|
|
||||||
|
**Dans DevTools → Elements**
|
||||||
|
|
||||||
|
Chercher l'élément `<docuseal-form>`
|
||||||
|
|
||||||
|
**Attendu** :
|
||||||
|
```html
|
||||||
|
<docuseal-form
|
||||||
|
data-src="https://docuseal.eu/s/xxx"
|
||||||
|
data-language="fr"
|
||||||
|
data-with-title="false"
|
||||||
|
data-background-color="#fff"
|
||||||
|
data-allow-typed-signature="false"
|
||||||
|
data-signature="data:image/png;base64,...">
|
||||||
|
</docuseal-form>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Vérifier** :
|
||||||
|
- ✅ Attribut `data-signature` présent
|
||||||
|
- ✅ Valeur commence par "data:image/"
|
||||||
|
- ✅ Valeur contient du base64
|
||||||
|
|
||||||
|
## Problèmes courants
|
||||||
|
|
||||||
|
### Problème 1 : Pas de signature dans la base
|
||||||
|
|
||||||
|
**Symptôme** :
|
||||||
|
```
|
||||||
|
⚠️ [API SIGNATURE] Pas d'org_id sur ce contrat
|
||||||
|
```
|
||||||
|
ou
|
||||||
|
```
|
||||||
|
❌ [API SIGNATURE] Aucune signature trouvée pour cette organisation
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution** :
|
||||||
|
Ajouter la signature dans la base :
|
||||||
|
```sql
|
||||||
|
UPDATE organization_details
|
||||||
|
SET signature_b64 = 'data:image/png;base64,VOTRE_BASE64'
|
||||||
|
WHERE org_id = 'ID_ORGANISATION';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problème 2 : Signature trop longue pour sessionStorage
|
||||||
|
|
||||||
|
**Symptôme** :
|
||||||
|
```
|
||||||
|
❌ [SIGNATURE] Erreur lors du stockage: QuotaExceededError
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution** :
|
||||||
|
- Compresser l'image de signature
|
||||||
|
- Utiliser un format plus léger (PNG optimisé)
|
||||||
|
- Limite sessionStorage : ~5-10MB selon le navigateur
|
||||||
|
|
||||||
|
### Problème 3 : Format de signature incorrect
|
||||||
|
|
||||||
|
**Symptôme** :
|
||||||
|
DocuSeal affiche une erreur ou ne pré-remplit pas
|
||||||
|
|
||||||
|
**Solution** :
|
||||||
|
Vérifier le format :
|
||||||
|
- ✅ CORRECT : `data:image/png;base64,iVBORw0KGgo...`
|
||||||
|
- ❌ INCORRECT : `iVBORw0KGgo...` (manque le préfixe)
|
||||||
|
- ❌ INCORRECT : `data:image/png,base64,iVBORw0KGgo...` (virgule au mauvais endroit)
|
||||||
|
|
||||||
|
### Problème 4 : Le composant se re-render avant le stockage
|
||||||
|
|
||||||
|
**Symptôme** :
|
||||||
|
```
|
||||||
|
🎨 [SIGNATURE RENDER] Signature depuis sessionStorage: { exists: false }
|
||||||
|
```
|
||||||
|
mais plus tôt on a vu :
|
||||||
|
```
|
||||||
|
✅ [SIGNATURE] Signature stockée avec succès
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution** :
|
||||||
|
Problème de timing React. Le sessionStorage est lu lors du premier rendu avant le stockage.
|
||||||
|
|
||||||
|
**Fix** : Forcer un re-render après le stockage :
|
||||||
|
```tsx
|
||||||
|
if (signatureB64) {
|
||||||
|
sessionStorage.setItem('docuseal_signature_b64', signatureB64);
|
||||||
|
// Forcer un re-render
|
||||||
|
setEmbedSrc(null);
|
||||||
|
setTimeout(() => setEmbedSrc(embed), 0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commandes utiles
|
||||||
|
|
||||||
|
### Vérifier toutes les organisations avec signature
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
o.id,
|
||||||
|
o.name,
|
||||||
|
od.structure,
|
||||||
|
CASE
|
||||||
|
WHEN od.signature_b64 IS NOT NULL THEN '✅ Signature présente'
|
||||||
|
ELSE '❌ Pas de signature'
|
||||||
|
END as statut,
|
||||||
|
LENGTH(od.signature_b64) as taille
|
||||||
|
FROM organizations o
|
||||||
|
LEFT JOIN organization_details od ON od.org_id = o.id
|
||||||
|
ORDER BY od.signature_b64 IS NOT NULL DESC, o.name;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vérifier les contrats prêts pour le pré-remplissage
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
c.id,
|
||||||
|
c.contract_number,
|
||||||
|
c.org_id,
|
||||||
|
o.name as organization_name,
|
||||||
|
c.signature_status,
|
||||||
|
CASE
|
||||||
|
WHEN od.signature_b64 IS NOT NULL THEN '✅ Signature dispo'
|
||||||
|
ELSE '❌ Pas de signature'
|
||||||
|
END as signature_employeur
|
||||||
|
FROM cddu_contracts c
|
||||||
|
LEFT JOIN organizations o ON o.id = c.org_id
|
||||||
|
LEFT JOIN organization_details od ON od.org_id = c.org_id
|
||||||
|
WHERE c.signature_status IN ('En attente', 'Signé par l''employeur')
|
||||||
|
ORDER BY c.created_at DESC
|
||||||
|
LIMIT 20;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
Si les logs montrent toutes les étapes OK mais que ça ne fonctionne toujours pas :
|
||||||
|
1. Copier tous les logs de la console
|
||||||
|
2. Faire une capture d'écran de l'élément `<docuseal-form>` dans DevTools
|
||||||
|
3. Vérifier la version de DocuSeal utilisée
|
||||||
|
4. Consulter la documentation DocuSeal pour `data-signature`
|
||||||
145
DOCUSEAL_FIXES.md
Normal file
145
DOCUSEAL_FIXES.md
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
# Résumé des Corrections - Signatures DocuSeal
|
||||||
|
|
||||||
|
## Date: 14 octobre 2025
|
||||||
|
|
||||||
|
## Problème identifié
|
||||||
|
|
||||||
|
❌ **Erreur** : `Unable to find form with slug 'mj9RzzWW2LbvnT?data-allow-typed-signature=false'`
|
||||||
|
|
||||||
|
**Cause** : Les paramètres `data-*` étaient ajoutés à l'URL du slug DocuSeal, ce qui causait une erreur 404.
|
||||||
|
|
||||||
|
## Solution implémentée
|
||||||
|
|
||||||
|
Les attributs `data-*` de DocuSeal doivent être passés comme **attributs HTML** sur le composant `<docuseal-form>`, **pas dans l'URL** `data-src`.
|
||||||
|
|
||||||
|
### ✅ Modifications effectuées
|
||||||
|
|
||||||
|
#### 1. Composant `<docuseal-form>`
|
||||||
|
|
||||||
|
**Fichiers modifiés** :
|
||||||
|
- `/app/(app)/contrats/[id]/page.tsx`
|
||||||
|
- `/app/(app)/signatures-electroniques/page.tsx`
|
||||||
|
|
||||||
|
**Avant** (❌ incorrect) :
|
||||||
|
```tsx
|
||||||
|
<docuseal-form
|
||||||
|
data-src="https://docuseal.eu/s/mj9RzzWW2LbvnT?data-allow-typed-signature=false">
|
||||||
|
</docuseal-form>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Après** (✅ correct) :
|
||||||
|
```tsx
|
||||||
|
<docuseal-form
|
||||||
|
data-src="https://docuseal.eu/s/mj9RzzWW2LbvnT"
|
||||||
|
data-language="fr"
|
||||||
|
data-with-title="false"
|
||||||
|
data-background-color="#fff"
|
||||||
|
data-allow-typed-signature="false">
|
||||||
|
</docuseal-form>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Génération des URLs propres
|
||||||
|
|
||||||
|
**Fichiers modifiés** :
|
||||||
|
- `/app/(app)/contrats/[id]/page.tsx` (lignes ~703-710, ~736-744)
|
||||||
|
- `/app/(app)/signatures-electroniques/page.tsx` (lignes ~203-218, ~243-251)
|
||||||
|
- `/app/api/docuseal-signature/route.ts` (lignes ~225-233)
|
||||||
|
|
||||||
|
**Changement** : Les URLs DocuSeal sont maintenant générées **sans paramètres de requête**.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Correct
|
||||||
|
embed = `https://docuseal.eu/s/${docusealId}`;
|
||||||
|
|
||||||
|
// ❌ Ancien (incorrect)
|
||||||
|
embed = `https://docuseal.eu/s/${docusealId}?data-allow-typed-signature=false`;
|
||||||
|
```
|
||||||
|
|
||||||
|
## État actuel de la fonctionnalité
|
||||||
|
|
||||||
|
### ✅ Désactivation de la saisie manuelle
|
||||||
|
|
||||||
|
**Statut** : Implémenté et fonctionnel
|
||||||
|
|
||||||
|
L'attribut `data-allow-typed-signature="false"` est ajouté sur le composant `<docuseal-form>` dans :
|
||||||
|
- Page de détail d'un contrat (`/contrats/[id]`)
|
||||||
|
- Page des signatures électroniques (`/signatures-electroniques`)
|
||||||
|
|
||||||
|
### ⏳ Pré-remplissage de la signature (EN COURS)
|
||||||
|
|
||||||
|
**Statut** : Implémentation partielle
|
||||||
|
|
||||||
|
**Ce qui fonctionne** :
|
||||||
|
- ✅ Colonne `signature_b64` créée dans `organization_details`
|
||||||
|
- ✅ L'API récupère la signature depuis la base de données
|
||||||
|
- ✅ Le paramètre `orgId` est passé depuis les composants
|
||||||
|
|
||||||
|
**Ce qui reste à faire** :
|
||||||
|
- [ ] Ajouter `data-signature` dynamiquement sur `<docuseal-form>`
|
||||||
|
- [ ] Récupérer la signature côté client ou la passer via le serveur
|
||||||
|
- [ ] Tester avec une vraie signature base64
|
||||||
|
|
||||||
|
## Comment tester
|
||||||
|
|
||||||
|
### Test 1 : Désactivation de la saisie manuelle
|
||||||
|
|
||||||
|
1. Créer une nouvelle signature électronique depuis :
|
||||||
|
- `/staff/contrats/[id]` (bouton "Lancer signature électronique")
|
||||||
|
- Ou `/staff/contrats` (sélection multiple + signature groupée)
|
||||||
|
|
||||||
|
2. Ouvrir le lien de signature (en tant qu'employeur)
|
||||||
|
|
||||||
|
3. **Vérifier** :
|
||||||
|
- ❌ L'option "Saisir" ne doit PAS être disponible
|
||||||
|
- ✅ Les options "Dessiner" et "Charger" doivent être disponibles
|
||||||
|
|
||||||
|
### Test 2 : Pré-remplissage de la signature (à venir)
|
||||||
|
|
||||||
|
1. Ajouter une signature dans la base :
|
||||||
|
```sql
|
||||||
|
UPDATE organization_details
|
||||||
|
SET signature_b64 = 'data:image/png;base64,iVBORw0KG...'
|
||||||
|
WHERE org_id = 'VOTRE_ORG_ID';
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Créer une signature électronique pour un contrat de cette organisation
|
||||||
|
|
||||||
|
3. **Vérifier** :
|
||||||
|
- La signature devrait être pré-remplie pour l'employeur
|
||||||
|
- L'employeur peut la valider ou la remplacer
|
||||||
|
|
||||||
|
## Prochaines étapes
|
||||||
|
|
||||||
|
### Pour le pré-remplissage de la signature
|
||||||
|
|
||||||
|
**Option A** : Via l'API DocuSeal (recommandé)
|
||||||
|
- Utiliser le paramètre `values` lors de la création de la soumission
|
||||||
|
- Nécessite de connaître le nom exact du champ de signature dans le template
|
||||||
|
|
||||||
|
**Option B** : Côté client
|
||||||
|
- Récupérer la signature depuis Supabase côté client
|
||||||
|
- L'injecter dans l'attribut `data-signature` du composant
|
||||||
|
- Plus complexe mais plus flexible
|
||||||
|
|
||||||
|
**Option C** : URL encode dans signature_link
|
||||||
|
- Stocker la signature encodée dans le lien de signature
|
||||||
|
- L'extraire et l'ajouter dynamiquement au composant
|
||||||
|
- Limite de taille d'URL potentielle
|
||||||
|
|
||||||
|
## Documentation de référence
|
||||||
|
|
||||||
|
- **DocuSeal Embed Documentation** : https://docs.docuseal.co/embed
|
||||||
|
- **Attributs data-*** :
|
||||||
|
- `data-allow-typed-signature`: boolean (default: true)
|
||||||
|
- `data-signature`: string (base64, URL, ou texte)
|
||||||
|
- `data-language`: string (ex: "fr")
|
||||||
|
- `data-with-title`: boolean
|
||||||
|
- `data-background-color`: string
|
||||||
|
|
||||||
|
## Notes importantes
|
||||||
|
|
||||||
|
⚠️ Les attributs `data-*` de DocuSeal sont des **attributs HTML**, pas des paramètres d'URL.
|
||||||
|
|
||||||
|
⚠️ L'URL `data-src` doit pointer vers le slug DocuSeal pur sans query parameters.
|
||||||
|
|
||||||
|
⚠️ Pour le pré-remplissage, le format base64 doit être complet : `data:image/png;base64,...`
|
||||||
|
|
@ -1,33 +1,81 @@
|
||||||
# Configuration des Signatures Électroniques DocuSeal
|
# Configuration des Signatures Électroniques DocuSeal
|
||||||
|
|
||||||
## Date: 14 octobre 2025
|
## Date: 14 octobre 2025 (Mise à jour)
|
||||||
|
|
||||||
## Modifications apportées
|
## Fonctionnalités
|
||||||
|
|
||||||
### Restriction des méthodes de signature
|
### 1. Restriction des méthodes de signature
|
||||||
|
|
||||||
L'API DocuSeal a été configurée pour **désactiver la saisie de signature au clavier** et autoriser uniquement :
|
L'API DocuSeal a été configurée pour **désactiver la saisie de signature au clavier**.
|
||||||
- ✅ **Dessin de signature** (`draw_signature: true`)
|
|
||||||
- ✅ **Chargement de photo** (`upload_signature: true`)
|
|
||||||
- ❌ **Saisie au clavier** (`type_signature: false`)
|
|
||||||
|
|
||||||
### Mémorisation des signatures
|
Selon la documentation DocuSeal :
|
||||||
|
- `data-allow-typed-signature`: Set `false` to disallow users to type their signature (Default: true)
|
||||||
|
|
||||||
La mémorisation des signatures est **explicitement activée** (`save_signature: true`) pour garantir que les utilisateurs peuvent réutiliser leur signature lors de signatures ultérieures.
|
Les méthodes disponibles sont :
|
||||||
> Note : Ce paramètre est normalement activé par défaut dans DocuSeal, mais nous l'activons explicitement pour plus de sécurité.
|
- ✅ **Dessin de signature** (activé par défaut)
|
||||||
|
- ✅ **Chargement de photo** (activé par défaut)
|
||||||
|
- ❌ **Saisie au clavier** (`data-allow-typed-signature=false`)
|
||||||
|
|
||||||
|
**⚠️ Important** : Le paramètre `data-allow-typed-signature` doit être passé dans l'URL de l'embed DocuSeal, pas dans l'API de création de soumission.
|
||||||
|
|
||||||
|
### 2. Mémorisation des signatures
|
||||||
|
|
||||||
|
La mémorisation des signatures est **activée par défaut** dans DocuSeal.
|
||||||
|
Les utilisateurs peuvent réutiliser automatiquement leur signature lors de signatures ultérieures.
|
||||||
|
|
||||||
|
### 3. Pré-remplissage de la signature employeur (✨ NOUVEAU)
|
||||||
|
|
||||||
|
La signature de l'employeur peut être **pré-remplie automatiquement** depuis la base de données :
|
||||||
|
- Signature stockée en base64 dans `organization_details.signature_b64`
|
||||||
|
- Pré-remplissage automatique via le paramètre `data-signature` dans l'URL
|
||||||
|
- L'employeur peut valider directement ou remplacer la signature
|
||||||
|
|
||||||
|
📖 **Voir documentation complète** : [DOCUSEAL_SIGNATURE_PREFILL.md](./DOCUSEAL_SIGNATURE_PREFILL.md)
|
||||||
|
|
||||||
## Fichiers modifiés
|
## Fichiers modifiés
|
||||||
|
|
||||||
### `/app/api/docuseal-signature/route.ts`
|
### `/app/api/docuseal-signature/route.ts`
|
||||||
|
|
||||||
Ajout des préférences de signature dans la création des soumissions DocuSeal :
|
**1. Configuration des submitters avec metadata** pour désactiver la saisie au clavier :
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
preferences: {
|
submitters: [
|
||||||
draw_signature: true, // Activer signature par dessin
|
{
|
||||||
type_signature: false, // Désactiver signature par saisie
|
role: 'Employeur',
|
||||||
upload_signature: true, // Activer chargement de photo
|
email: employerEmail,
|
||||||
save_signature: true // Mémoriser la signature (explicite)
|
metadata: {
|
||||||
|
'allow-typed-signature': false, // Désactiver signature par saisie
|
||||||
|
// Pré-remplir la signature si disponible (depuis organization_details)
|
||||||
|
...(employerSignatureB64 && {
|
||||||
|
'signature': employerSignatureB64
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'Salarié',
|
||||||
|
email: employeeEmail,
|
||||||
|
metadata: {
|
||||||
|
'allow-typed-signature': false // Désactiver signature par saisie
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Récupération de la signature depuis la base de données** :
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Récupérer la signature base64 de l'organisation si disponible
|
||||||
|
let employerSignatureB64 = null;
|
||||||
|
if (orgId) {
|
||||||
|
const { data: orgDetails } = await supabase
|
||||||
|
.from('organization_details')
|
||||||
|
.select('signature_b64')
|
||||||
|
.eq('org_id', orgId)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (orgDetails?.signature_b64) {
|
||||||
|
employerSignatureB64 = orgDetails.signature_b64;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -66,10 +114,10 @@ Les configurations s'appliquent automatiquement à :
|
||||||
## Avantages
|
## Avantages
|
||||||
|
|
||||||
✅ **Sécurité renforcée** : Évite les signatures trop simplistes par saisie au clavier
|
✅ **Sécurité renforcée** : Évite les signatures trop simplistes par saisie au clavier
|
||||||
✅ **Expérience utilisateur améliorée** : Mémorisation explicite de la signature
|
✅ **Expérience utilisateur** : Dessin et upload disponibles par défaut
|
||||||
✅ **Conformité légale** : Signatures plus authentiques (dessin/scan)
|
✅ **Conformité légale** : Signatures plus authentiques (dessin/scan)
|
||||||
✅ **Gain de temps** : Réutilisation automatique de la signature mémorisée
|
✅ **Mémorisation native** : DocuSeal sauvegarde automatiquement les signatures
|
||||||
✅ **Configuration explicite** : Paramètres définis explicitement pour éviter toute ambiguïté
|
✅ **Configuration correcte** : Utilisation des bons paramètres API selon la documentation
|
||||||
|
|
||||||
## Test
|
## Test
|
||||||
|
|
||||||
|
|
@ -86,9 +134,10 @@ Pour tester les modifications :
|
||||||
## Références API DocuSeal
|
## Références API DocuSeal
|
||||||
|
|
||||||
- Documentation des préférences de signature : https://docs.docuseal.co/api
|
- Documentation des préférences de signature : https://docs.docuseal.co/api
|
||||||
- Paramètre `preferences` dans la création de soumission
|
- Paramètre utilisé : `metadata` dans les submitters
|
||||||
- Options configurées explicitement :
|
- Options configurées :
|
||||||
- `draw_signature: true` - Permet de dessiner la signature
|
- `allow-typed-signature: false` - Désactive la saisie au clavier (Default: true)
|
||||||
- `type_signature: false` - Désactive la saisie au clavier
|
- Options par défaut (pas besoin de les configurer) :
|
||||||
- `upload_signature: true` - Permet le chargement d'image
|
- Signature par dessin : activée
|
||||||
- `save_signature: true` - Active explicitement la mémorisation
|
- Upload de signature : activé
|
||||||
|
- Mémorisation : activée
|
||||||
|
|
|
||||||
219
DOCUSEAL_SIGNATURE_PREFILL.md
Normal file
219
DOCUSEAL_SIGNATURE_PREFILL.md
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
# Pré-remplissage de la Signature Employeur - DocuSeal
|
||||||
|
|
||||||
|
## Date: 14 octobre 2025
|
||||||
|
|
||||||
|
## Fonctionnalité
|
||||||
|
|
||||||
|
Cette fonctionnalité permet de **pré-remplir automatiquement la signature de l'employeur** lors de la création d'une signature électronique via DocuSeal.
|
||||||
|
|
||||||
|
### Principe
|
||||||
|
|
||||||
|
1. La signature de l'employeur est stockée en **base64** dans la table `organization_details` (colonne `signature_b64`)
|
||||||
|
2. Lors de la création d'une signature électronique, l'API récupère cette signature
|
||||||
|
3. DocuSeal pré-remplit le champ de signature avec cette image
|
||||||
|
4. L'employeur peut :
|
||||||
|
- Utiliser la signature pré-remplie (gain de temps)
|
||||||
|
- Ou la remplacer par une nouvelle signature
|
||||||
|
|
||||||
|
## Base de données
|
||||||
|
|
||||||
|
### Table: `organization_details`
|
||||||
|
|
||||||
|
Nouvelle colonne ajoutée :
|
||||||
|
```sql
|
||||||
|
ALTER TABLE organization_details
|
||||||
|
ADD COLUMN signature_b64 TEXT;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Format attendu :**
|
||||||
|
- Type: `TEXT`
|
||||||
|
- Contenu: Image de signature encodée en base64
|
||||||
|
- Format acceptés par DocuSeal :
|
||||||
|
- Base64 encodée (ex: `data:image/png;base64,iVBORw0KG...`)
|
||||||
|
- URL publique d'une image
|
||||||
|
- Texte brut (sera rendu avec une police standard)
|
||||||
|
|
||||||
|
### Comment remplir la signature
|
||||||
|
|
||||||
|
1. **Via l'interface d'administration** (à implémenter)
|
||||||
|
2. **Via SQL directement** :
|
||||||
|
```sql
|
||||||
|
UPDATE organization_details
|
||||||
|
SET signature_b64 = 'data:image/png;base64,iVBORw0KGgoAAAANS...'
|
||||||
|
WHERE org_id = 'votre-org-id';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modifications techniques
|
||||||
|
|
||||||
|
### 1. API DocuSeal Signature (`/app/api/docuseal-signature/route.ts`)
|
||||||
|
|
||||||
|
**Ajout du paramètre `orgId`** :
|
||||||
|
```typescript
|
||||||
|
const {
|
||||||
|
// ... autres paramètres
|
||||||
|
orgId = null // ID de l'organisation pour récupérer la signature
|
||||||
|
} = data;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Récupération de la signature** :
|
||||||
|
```typescript
|
||||||
|
let employerSignatureB64 = null;
|
||||||
|
if (orgId) {
|
||||||
|
const { data: orgDetails } = await supabase
|
||||||
|
.from('organization_details')
|
||||||
|
.select('signature_b64')
|
||||||
|
.eq('org_id', orgId)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (orgDetails?.signature_b64) {
|
||||||
|
employerSignatureB64 = orgDetails.signature_b64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pré-remplissage dans DocuSeal** :
|
||||||
|
```typescript
|
||||||
|
submitters: [
|
||||||
|
{
|
||||||
|
role: 'Employeur',
|
||||||
|
email: employerEmail,
|
||||||
|
metadata: {
|
||||||
|
'allow-typed-signature': false,
|
||||||
|
...(employerSignatureB64 && {
|
||||||
|
'signature': employerSignatureB64 // data-signature pour pré-remplir
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. API Bulk E-Sign (`/app/api/staff/contracts/bulk-esign/route.ts`)
|
||||||
|
|
||||||
|
Ajout de `orgId` dans les données envoyées :
|
||||||
|
```typescript
|
||||||
|
const signatureData = {
|
||||||
|
// ... autres champs
|
||||||
|
orgId: contract.org_id // Passer l'org_id pour récupérer la signature
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Contract Editor (`/components/staff/contracts/ContractEditor.tsx`)
|
||||||
|
|
||||||
|
Ajout de `orgId` dans les données de signature individuelle :
|
||||||
|
```typescript
|
||||||
|
const signatureData = {
|
||||||
|
// ... autres champs
|
||||||
|
orgId: contract.org_id // Passer l'org_id pour récupérer la signature
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Points d'application
|
||||||
|
|
||||||
|
La signature pré-remplie fonctionne pour :
|
||||||
|
|
||||||
|
1. ✅ **Signature individuelle** depuis `/staff/contrats/[id]`
|
||||||
|
2. ✅ **Signature groupée** depuis `/staff/contrats` (sélection multiple)
|
||||||
|
|
||||||
|
## Comportement utilisateur
|
||||||
|
|
||||||
|
### Pour l'employeur :
|
||||||
|
|
||||||
|
1. **Si une signature est enregistrée** :
|
||||||
|
- Le champ de signature s'affiche avec la signature pré-remplie
|
||||||
|
- L'employeur peut la valider directement (1 clic)
|
||||||
|
- Ou la remplacer en dessinant/uploadant une nouvelle signature
|
||||||
|
|
||||||
|
2. **Si aucune signature n'est enregistrée** :
|
||||||
|
- Comportement normal : l'employeur doit créer sa signature
|
||||||
|
- Options : Dessiner ou Uploader une image
|
||||||
|
- ❌ Saisie au clavier désactivée
|
||||||
|
|
||||||
|
### Pour le salarié :
|
||||||
|
|
||||||
|
- Pas de pré-remplissage (chaque salarié signe manuellement)
|
||||||
|
- Options : Dessiner ou Uploader une image
|
||||||
|
- ❌ Saisie au clavier désactivée
|
||||||
|
|
||||||
|
## Avantages
|
||||||
|
|
||||||
|
✅ **Gain de temps** : L'employeur n'a pas besoin de redessiner à chaque contrat
|
||||||
|
✅ **Cohérence** : Même signature sur tous les contrats de l'organisation
|
||||||
|
✅ **Flexibilité** : Possibilité de remplacer la signature si nécessaire
|
||||||
|
✅ **Sécurité** : Signature stockée de manière centralisée et sécurisée
|
||||||
|
|
||||||
|
## Migration SQL
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Ajouter la colonne signature_b64 si elle n'existe pas
|
||||||
|
ALTER TABLE organization_details
|
||||||
|
ADD COLUMN IF NOT EXISTS signature_b64 TEXT;
|
||||||
|
|
||||||
|
-- Créer un index pour les recherches
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_organization_details_signature
|
||||||
|
ON organization_details(org_id)
|
||||||
|
WHERE signature_b64 IS NOT NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
### Prérequis
|
||||||
|
|
||||||
|
1. **Ajouter une signature dans la base** :
|
||||||
|
```sql
|
||||||
|
UPDATE organization_details
|
||||||
|
SET signature_b64 = 'data:image/png;base64,iVBORw0KG...'
|
||||||
|
WHERE org_id = 'VOTRE_ORG_ID';
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Créer une signature électronique** pour un contrat de cette organisation
|
||||||
|
|
||||||
|
### Processus de test
|
||||||
|
|
||||||
|
1. **Depuis la page du contrat** (`/contrats/[id]`) :
|
||||||
|
- Ouvrir un contrat appartenant à une organisation avec signature_b64
|
||||||
|
- Cliquer sur "Voir signature" ou accéder à la signature électronique
|
||||||
|
|
||||||
|
2. **Vérification** :
|
||||||
|
- La signature de l'employeur devrait être pré-remplie
|
||||||
|
- L'employeur peut la valider directement ou la remplacer
|
||||||
|
- Le salarié n'a pas de pré-remplissage (signature manuelle requise)
|
||||||
|
|
||||||
|
### Fonctionnement technique
|
||||||
|
|
||||||
|
1. **API `/api/contrats/[id]/signature`** :
|
||||||
|
- Récupère le contrat avec l'org_id
|
||||||
|
- Récupère la signature_b64 depuis organization_details
|
||||||
|
- Retourne les données avec la signature
|
||||||
|
|
||||||
|
2. **Page `/contrats/[id]`** :
|
||||||
|
- Récupère les données via l'API
|
||||||
|
- Stocke la signature dans sessionStorage
|
||||||
|
- Injecte `data-signature` dans le composant `<docuseal-form>`
|
||||||
|
|
||||||
|
3. **Composant `<docuseal-form>`** :
|
||||||
|
- Lit la signature depuis sessionStorage
|
||||||
|
- Ajoute l'attribut `data-signature` dynamiquement
|
||||||
|
- DocuSeal pré-remplit le champ de signature
|
||||||
|
|
||||||
|
### Points d'application actuels
|
||||||
|
|
||||||
|
✅ **Page de détail du contrat** (`/contrats/[id]`) - IMPLÉMENTÉ
|
||||||
|
⏳ **Page signatures électroniques** (`/signatures-electroniques`) - À IMPLÉMENTER
|
||||||
|
⏳ **Signature groupée** (`/staff/contrats`) - À IMPLÉMENTER (nécessite modification de l'API bulk-esign)
|
||||||
|
|
||||||
|
## Références
|
||||||
|
|
||||||
|
- Documentation DocuSeal `data-signature` : https://docs.docuseal.co/api
|
||||||
|
- Format : Allows pre-filling signature fields
|
||||||
|
- Types acceptés :
|
||||||
|
- Base64 encoded image string
|
||||||
|
- Public URL to an image
|
||||||
|
- Plain text (rendered with standard font)
|
||||||
|
|
||||||
|
## TODO (Améliorations futures)
|
||||||
|
|
||||||
|
- [ ] Interface d'administration pour uploader/gérer la signature
|
||||||
|
- [ ] Validation du format base64
|
||||||
|
- [ ] Prévisualisation de la signature dans l'interface
|
||||||
|
- [ ] Historique des signatures utilisées
|
||||||
|
- [ ] Support de plusieurs signatures par organisation (différents signataires)
|
||||||
|
|
@ -679,6 +679,8 @@ export default function ContratPage() {
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
console.log('📋 [SIGNATURE] Données contrat depuis API:', result);
|
console.log('📋 [SIGNATURE] Données contrat depuis API:', result);
|
||||||
|
console.log('🔍 [SIGNATURE] result.data:', result.data);
|
||||||
|
console.log('🔍 [SIGNATURE] result.data.signature_b64:', result.data?.signature_b64);
|
||||||
|
|
||||||
if (!result.success || !result.data) {
|
if (!result.success || !result.data) {
|
||||||
setIsLoadingSignature(false);
|
setIsLoadingSignature(false);
|
||||||
|
|
@ -687,6 +689,22 @@ export default function ContratPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const contractData = result.data;
|
const contractData = result.data;
|
||||||
|
console.log('📦 [SIGNATURE] contractData extrait:', contractData);
|
||||||
|
|
||||||
|
// Stocker la signature si disponible
|
||||||
|
const signatureB64 = contractData.signature_b64;
|
||||||
|
console.log('🖊️ [SIGNATURE] signatureB64 extraite:', {
|
||||||
|
exists: !!signatureB64,
|
||||||
|
type: typeof signatureB64,
|
||||||
|
length: signatureB64?.length,
|
||||||
|
preview: signatureB64?.substring(0, 50)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (signatureB64) {
|
||||||
|
console.log('✅ [SIGNATURE] Signature B64 disponible, longueur:', signatureB64.length);
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ [SIGNATURE] Aucune signature B64 disponible');
|
||||||
|
}
|
||||||
|
|
||||||
// Vérifier si la soumission DocuSeal n'a pas été créée
|
// Vérifier si la soumission DocuSeal n'a pas été créée
|
||||||
if (!contractData.docuseal_submission_id && contractData.signature_status === "Non initiée") {
|
if (!contractData.docuseal_submission_id && contractData.signature_status === "Non initiée") {
|
||||||
|
|
@ -703,6 +721,8 @@ export default function ContratPage() {
|
||||||
const signatureLinkMatch = contractData.signature_link.match(/docuseal_id=([^&]+)/);
|
const signatureLinkMatch = contractData.signature_link.match(/docuseal_id=([^&]+)/);
|
||||||
if (signatureLinkMatch) {
|
if (signatureLinkMatch) {
|
||||||
const docusealId = signatureLinkMatch[1];
|
const docusealId = signatureLinkMatch[1];
|
||||||
|
|
||||||
|
// L'URL doit être propre sans paramètres
|
||||||
embed = `https://docuseal.eu/s/${docusealId}`;
|
embed = `https://docuseal.eu/s/${docusealId}`;
|
||||||
console.log('🔗 [SIGNATURE] URL embed depuis signature_link:', embed);
|
console.log('🔗 [SIGNATURE] URL embed depuis signature_link:', embed);
|
||||||
}
|
}
|
||||||
|
|
@ -733,6 +753,7 @@ export default function ContratPage() {
|
||||||
const employer = roles.find((r: any) => (r.role || r.name) === 'Employeur') || {};
|
const employer = roles.find((r: any) => (r.role || r.name) === 'Employeur') || {};
|
||||||
|
|
||||||
if (employer?.slug) {
|
if (employer?.slug) {
|
||||||
|
// URL propre sans paramètres
|
||||||
embed = `https://docuseal.eu/s/${employer.slug}`;
|
embed = `https://docuseal.eu/s/${employer.slug}`;
|
||||||
console.log('🔗 [SIGNATURE] URL embed depuis DocuSeal API:', embed);
|
console.log('🔗 [SIGNATURE] URL embed depuis DocuSeal API:', embed);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -749,6 +770,31 @@ export default function ContratPage() {
|
||||||
console.log('✅ [SIGNATURE] URL embed trouvée:', embed);
|
console.log('✅ [SIGNATURE] URL embed trouvée:', embed);
|
||||||
setEmbedSrc(embed);
|
setEmbedSrc(embed);
|
||||||
|
|
||||||
|
// Stocker la signature pour l'ajouter au composant
|
||||||
|
console.log('🔍 [SIGNATURE] Tentative de stockage de la signature...');
|
||||||
|
console.log('🔍 [SIGNATURE] signatureB64 value:', signatureB64);
|
||||||
|
|
||||||
|
if (signatureB64) {
|
||||||
|
console.log('📝 [SIGNATURE] Stockage de la signature dans sessionStorage');
|
||||||
|
console.log('📝 [SIGNATURE] Longueur de la signature:', signatureB64.length);
|
||||||
|
console.log('📝 [SIGNATURE] Preview:', signatureB64.substring(0, 100));
|
||||||
|
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem('docuseal_signature_b64', signatureB64);
|
||||||
|
const stored = sessionStorage.getItem('docuseal_signature_b64');
|
||||||
|
console.log('✅ [SIGNATURE] Signature stockée avec succès, vérification:', {
|
||||||
|
stored: !!stored,
|
||||||
|
length: stored?.length,
|
||||||
|
matches: stored === signatureB64
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('❌ [SIGNATURE] Erreur lors du stockage:', e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ [SIGNATURE] Pas de signature à stocker, nettoyage sessionStorage');
|
||||||
|
sessionStorage.removeItem('docuseal_signature_b64');
|
||||||
|
}
|
||||||
|
|
||||||
// Masquer la modale de chargement
|
// Masquer la modale de chargement
|
||||||
setIsLoadingSignature(false);
|
setIsLoadingSignature(false);
|
||||||
|
|
||||||
|
|
@ -1493,12 +1539,42 @@ return (
|
||||||
<div className="p-0" style={{ height: '80vh', minHeight: 520 }}>
|
<div className="p-0" style={{ height: '80vh', minHeight: 520 }}>
|
||||||
{embedSrc ? (
|
{embedSrc ? (
|
||||||
<div dangerouslySetInnerHTML={{
|
<div dangerouslySetInnerHTML={{
|
||||||
__html: `<docuseal-form
|
__html: (() => {
|
||||||
|
console.log('🎨 [SIGNATURE RENDER] Génération du HTML docuseal-form');
|
||||||
|
console.log('🎨 [SIGNATURE RENDER] embedSrc:', embedSrc);
|
||||||
|
|
||||||
|
// Récupérer la signature depuis sessionStorage
|
||||||
|
const signatureB64 = typeof window !== 'undefined' ? sessionStorage.getItem('docuseal_signature_b64') : null;
|
||||||
|
|
||||||
|
console.log('🎨 [SIGNATURE RENDER] Signature depuis sessionStorage:', {
|
||||||
|
exists: !!signatureB64,
|
||||||
|
length: signatureB64?.length,
|
||||||
|
preview: signatureB64?.substring(0, 50)
|
||||||
|
});
|
||||||
|
|
||||||
|
const signatureAttr = signatureB64 ? `data-signature="${signatureB64.replace(/"/g, '"')}"` : '';
|
||||||
|
|
||||||
|
console.log('🎨 [SIGNATURE RENDER] signatureAttr généré:', {
|
||||||
|
hasAttr: !!signatureAttr,
|
||||||
|
attrLength: signatureAttr.length,
|
||||||
|
attrPreview: signatureAttr.substring(0, 100)
|
||||||
|
});
|
||||||
|
|
||||||
|
const html = `<docuseal-form
|
||||||
data-src="${embedSrc}"
|
data-src="${embedSrc}"
|
||||||
data-language="fr"
|
data-language="fr"
|
||||||
data-with-title="false"
|
data-with-title="false"
|
||||||
data-background-color="#fff">
|
data-background-color="#fff"
|
||||||
</docuseal-form>`
|
data-allow-typed-signature="false"
|
||||||
|
${signatureAttr}>
|
||||||
|
</docuseal-form>`;
|
||||||
|
|
||||||
|
console.log('🎨 [SIGNATURE RENDER] HTML final généré');
|
||||||
|
console.log('🎨 [SIGNATURE RENDER] HTML length:', html.length);
|
||||||
|
console.log('🎨 [SIGNATURE RENDER] HTML contient data-signature:', html.includes('data-signature='));
|
||||||
|
|
||||||
|
return html;
|
||||||
|
})()
|
||||||
}} />
|
}} />
|
||||||
) : (
|
) : (
|
||||||
<div className="p-4 text-slate-500">Préparation du formulaire…</div>
|
<div className="p-4 text-slate-500">Préparation du formulaire…</div>
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,16 @@ export default function SignaturesElectroniques() {
|
||||||
console.log('🔍 [SIGNATURES] Debug - record fields:', f);
|
console.log('🔍 [SIGNATURES] Debug - record fields:', f);
|
||||||
console.log('🔍 [SIGNATURES] Debug - embed_src_employeur:', f.embed_src_employeur);
|
console.log('🔍 [SIGNATURES] Debug - embed_src_employeur:', f.embed_src_employeur);
|
||||||
console.log('🔍 [SIGNATURES] Debug - docuseal_template_id:', f.docuseal_template_id);
|
console.log('🔍 [SIGNATURES] Debug - docuseal_template_id:', f.docuseal_template_id);
|
||||||
|
console.log('🔍 [SIGNATURES] Debug - signature_b64:', f.signature_b64 ? 'présente' : 'absente');
|
||||||
|
|
||||||
|
// Gérer la signature pré-remplie si disponible
|
||||||
|
if (f.signature_b64) {
|
||||||
|
console.log('✅ [SIGNATURES] Signature trouvée, stockage dans sessionStorage');
|
||||||
|
sessionStorage.setItem('docuseal_signature_b64', f.signature_b64);
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ [SIGNATURES] Pas de signature dans les données');
|
||||||
|
sessionStorage.removeItem('docuseal_signature_b64');
|
||||||
|
}
|
||||||
|
|
||||||
// 1) Si l'URL d'embed est déjà en base (signature_link)
|
// 1) Si l'URL d'embed est déjà en base (signature_link)
|
||||||
if (typeof f.embed_src_employeur === 'string' && f.embed_src_employeur.trim()) {
|
if (typeof f.embed_src_employeur === 'string' && f.embed_src_employeur.trim()) {
|
||||||
|
|
@ -203,6 +213,8 @@ export default function SignaturesElectroniques() {
|
||||||
const signatureLinkMatch = signatureLink.match(/docuseal_id=([^&]+)/);
|
const signatureLinkMatch = signatureLink.match(/docuseal_id=([^&]+)/);
|
||||||
if (signatureLinkMatch) {
|
if (signatureLinkMatch) {
|
||||||
const docusealId = signatureLinkMatch[1];
|
const docusealId = signatureLinkMatch[1];
|
||||||
|
|
||||||
|
// URL propre sans paramètres
|
||||||
embed = `https://docuseal.eu/s/${docusealId}`;
|
embed = `https://docuseal.eu/s/${docusealId}`;
|
||||||
console.log('🔗 [SIGNATURES] URL embed depuis signature_link:', embed);
|
console.log('🔗 [SIGNATURES] URL embed depuis signature_link:', embed);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -240,6 +252,7 @@ export default function SignaturesElectroniques() {
|
||||||
const employer = roles.find((r: any) => (r.role || r.name) === 'Employeur') || {};
|
const employer = roles.find((r: any) => (r.role || r.name) === 'Employeur') || {};
|
||||||
|
|
||||||
if (employer?.slug) {
|
if (employer?.slug) {
|
||||||
|
// URL propre sans paramètres
|
||||||
embed = `https://docuseal.eu/s/${employer.slug}`;
|
embed = `https://docuseal.eu/s/${employer.slug}`;
|
||||||
console.log('🔗 [SIGNATURES] URL embed depuis DocuSeal API (slug):', embed);
|
console.log('🔗 [SIGNATURES] URL embed depuis DocuSeal API (slug):', embed);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -557,14 +570,33 @@ export default function SignaturesElectroniques() {
|
||||||
</div>
|
</div>
|
||||||
<div className="overflow-auto" style={{ height: 'calc(90vh - 60px)', minHeight: 480 }}>
|
<div className="overflow-auto" style={{ height: 'calc(90vh - 60px)', minHeight: 480 }}>
|
||||||
{embedSrc ? (
|
{embedSrc ? (
|
||||||
<div dangerouslySetInnerHTML={{
|
<div
|
||||||
__html: `<docuseal-form
|
ref={(el) => {
|
||||||
data-src="${embedSrc}"
|
if (el && embedSrc) {
|
||||||
data-language="fr"
|
// Récupérer la signature depuis sessionStorage
|
||||||
data-with-title="false"
|
const signature = sessionStorage.getItem('docuseal_signature_b64');
|
||||||
data-background-color="#fff">
|
|
||||||
</docuseal-form>`
|
// Construire les attributs
|
||||||
}} />
|
let dataSignatureAttr = '';
|
||||||
|
if (signature) {
|
||||||
|
console.log('✅ [SIGNATURES] Injection de la signature dans DocuSeal form');
|
||||||
|
dataSignatureAttr = `data-signature="${signature}"`;
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ [SIGNATURES] Pas de signature à injecter');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Injecter le HTML avec ou sans signature
|
||||||
|
el.innerHTML = `<docuseal-form
|
||||||
|
data-src="${embedSrc}"
|
||||||
|
data-language="fr"
|
||||||
|
data-with-title="false"
|
||||||
|
data-background-color="#fff"
|
||||||
|
data-allow-typed-signature="false"
|
||||||
|
${dataSignatureAttr}>
|
||||||
|
</docuseal-form>`;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-4 text-slate-500">Préparation du formulaire…</div>
|
<div className="p-4 text-slate-500">Préparation du formulaire…</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,17 @@ export async function GET(
|
||||||
// Utiliser le service role pour contourner RLS
|
// Utiliser le service role pour contourner RLS
|
||||||
const supabase = createSbServiceRole();
|
const supabase = createSbServiceRole();
|
||||||
|
|
||||||
// Récupérer le contrat avec les informations de signature
|
// Récupérer le contrat avec les informations de signature et l'org_id
|
||||||
const { data: contractData, error } = await supabase
|
const { data: contractData, error } = await supabase
|
||||||
.from('cddu_contracts')
|
.from('cddu_contracts')
|
||||||
.select('id, docuseal_template_id, docuseal_submission_id, signature_link, signature_status')
|
.select(`
|
||||||
|
id,
|
||||||
|
docuseal_template_id,
|
||||||
|
docuseal_submission_id,
|
||||||
|
signature_link,
|
||||||
|
signature_status,
|
||||||
|
org_id
|
||||||
|
`)
|
||||||
.eq('id', contractId)
|
.eq('id', contractId)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
|
|
@ -26,10 +33,54 @@ export async function GET(
|
||||||
|
|
||||||
console.log('✅ [API SIGNATURE] Contrat trouvé:', contractData);
|
console.log('✅ [API SIGNATURE] Contrat trouvé:', contractData);
|
||||||
|
|
||||||
return NextResponse.json({
|
// Récupérer la signature de l'organisation si disponible
|
||||||
|
let signatureB64 = null;
|
||||||
|
if (contractData.org_id) {
|
||||||
|
console.log('🔍 [API SIGNATURE] org_id présent:', contractData.org_id);
|
||||||
|
console.log('🔍 [API SIGNATURE] Recherche de signature pour org_id...');
|
||||||
|
|
||||||
|
const { data: orgDetails, error: orgError } = await supabase
|
||||||
|
.from('organization_details')
|
||||||
|
.select('signature_b64')
|
||||||
|
.eq('org_id', contractData.org_id)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
console.log('📋 [API SIGNATURE] Résultat requête organization_details:', {
|
||||||
|
hasData: !!orgDetails,
|
||||||
|
hasError: !!orgError,
|
||||||
|
error: orgError,
|
||||||
|
hasSignature: !!orgDetails?.signature_b64,
|
||||||
|
signatureLength: orgDetails?.signature_b64?.length,
|
||||||
|
signaturePreview: orgDetails?.signature_b64?.substring(0, 50)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (orgDetails?.signature_b64) {
|
||||||
|
signatureB64 = orgDetails.signature_b64;
|
||||||
|
console.log('✅ [API SIGNATURE] Signature trouvée pour l\'organisation');
|
||||||
|
console.log('📏 [API SIGNATURE] Longueur de la signature:', signatureB64.length);
|
||||||
|
} else {
|
||||||
|
console.log('❌ [API SIGNATURE] Aucune signature trouvée pour cette organisation');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ [API SIGNATURE] Pas d\'org_id sur ce contrat');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = {
|
||||||
success: true,
|
success: true,
|
||||||
data: contractData
|
data: {
|
||||||
|
...contractData,
|
||||||
|
signature_b64: signatureB64
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('📤 [API SIGNATURE] Réponse finale:', {
|
||||||
|
hasSignature: !!signatureB64,
|
||||||
|
signatureLength: signatureB64?.length,
|
||||||
|
contractId: contractData.id,
|
||||||
|
orgId: contractData.org_id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(response);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ [API SIGNATURE] Erreur:', error);
|
console.error('❌ [API SIGNATURE] Erreur:', error);
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,8 @@ export async function POST(request: NextRequest) {
|
||||||
employeeFirstName,
|
employeeFirstName,
|
||||||
matricule,
|
matricule,
|
||||||
contractType = 'CDDU',
|
contractType = 'CDDU',
|
||||||
skipEmployerEmail = false // Nouveau paramètre pour éviter l'envoi d'email à l'employeur
|
skipEmployerEmail = false, // Nouveau paramètre pour éviter l'envoi d'email à l'employeur
|
||||||
|
orgId = null // ID de l'organisation pour récupérer la signature
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
// Validation des champs requis
|
// Validation des champs requis
|
||||||
|
|
@ -64,6 +65,24 @@ export async function POST(request: NextRequest) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Récupérer la signature base64 de l'organisation si disponible
|
||||||
|
let employerSignatureB64 = null;
|
||||||
|
if (orgId) {
|
||||||
|
console.log('🔍 [SIGNATURE] Recherche de la signature pour org_id:', orgId);
|
||||||
|
const { data: orgDetails } = await supabase
|
||||||
|
.from('organization_details')
|
||||||
|
.select('signature_b64')
|
||||||
|
.eq('org_id', orgId)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (orgDetails?.signature_b64) {
|
||||||
|
employerSignatureB64 = orgDetails.signature_b64;
|
||||||
|
console.log('✅ [SIGNATURE] Signature trouvée pour l\'employeur');
|
||||||
|
} else {
|
||||||
|
console.log('ℹ️ [SIGNATURE] Aucune signature trouvée pour l\'employeur');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Extraire le submission_id depuis le nom du fichier PDF
|
// Extraire le submission_id depuis le nom du fichier PDF
|
||||||
// pdfS3Key format: "unsigned-contracts/contrat_cddu_REFERENCE.pdf"
|
// pdfS3Key format: "unsigned-contracts/contrat_cddu_REFERENCE.pdf"
|
||||||
// submission_id attendu: "contrats/contrat_cddu_REFERENCE" (format pour DynamoDB)
|
// submission_id attendu: "contrats/contrat_cddu_REFERENCE" (format pour DynamoDB)
|
||||||
|
|
@ -149,38 +168,36 @@ export async function POST(request: NextRequest) {
|
||||||
console.log('Template DocuSeal créé:', templateResponse.data);
|
console.log('Template DocuSeal créé:', templateResponse.data);
|
||||||
|
|
||||||
// Étape 4 : Création de la soumission DocuSeal
|
// Étape 4 : Création de la soumission DocuSeal
|
||||||
const submissionResponse = await axios.post(`${ENV.DOCUSEAL_API_BASE}/submissions`, {
|
console.log('🔧 [DOCUSEAL] Création de la soumission avec:', {
|
||||||
|
templateId,
|
||||||
|
hasEmployerSignature: !!employerSignatureB64,
|
||||||
|
signaturePreview: employerSignatureB64 ? employerSignatureB64.substring(0, 50) + '...' : 'N/A'
|
||||||
|
});
|
||||||
|
|
||||||
|
const submissionPayload = {
|
||||||
template_id: templateId,
|
template_id: templateId,
|
||||||
send_email: false,
|
send_email: false,
|
||||||
preferences: {
|
|
||||||
draw_signature: true, // Activer signature par dessin
|
|
||||||
type_signature: false, // Désactiver signature par saisie
|
|
||||||
upload_signature: true, // Activer chargement de photo
|
|
||||||
save_signature: true // Mémoriser la signature (explicite)
|
|
||||||
},
|
|
||||||
submitters: [
|
submitters: [
|
||||||
{
|
{
|
||||||
role: 'Employeur',
|
role: 'Employeur',
|
||||||
email: employerEmail,
|
email: employerEmail,
|
||||||
preferences: {
|
// Pré-remplir la signature de l'employeur si disponible
|
||||||
draw_signature: true,
|
...(employerSignatureB64 && {
|
||||||
type_signature: false,
|
values: {
|
||||||
upload_signature: true,
|
signature: employerSignatureB64
|
||||||
save_signature: true
|
}
|
||||||
}
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'Salarié',
|
role: 'Salarié',
|
||||||
email: employeeEmail,
|
email: employeeEmail
|
||||||
preferences: {
|
|
||||||
draw_signature: true,
|
|
||||||
type_signature: false,
|
|
||||||
upload_signature: true,
|
|
||||||
save_signature: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, {
|
};
|
||||||
|
|
||||||
|
console.log('📤 [DOCUSEAL] Payload complet:', JSON.stringify(submissionPayload, null, 2));
|
||||||
|
|
||||||
|
const submissionResponse = await axios.post(`${ENV.DOCUSEAL_API_BASE}/submissions`, submissionPayload, {
|
||||||
headers: {
|
headers: {
|
||||||
'X-Auth-Token': ENV.DOCUSEAL_TOKEN,
|
'X-Auth-Token': ENV.DOCUSEAL_TOKEN,
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
|
|
@ -188,6 +205,9 @@ export async function POST(request: NextRequest) {
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Soumission DocuSeal créée:', submissionResponse.data);
|
console.log('Soumission DocuSeal créée:', submissionResponse.data);
|
||||||
|
if (employerSignatureB64) {
|
||||||
|
console.log('✅ [SIGNATURE] Signature employeur pré-remplie');
|
||||||
|
}
|
||||||
|
|
||||||
// Récupérer le submission_id DocuSeal
|
// Récupérer le submission_id DocuSeal
|
||||||
const docusealSubmissionId = submissionResponse.data[0].submission_id;
|
const docusealSubmissionId = submissionResponse.data[0].submission_id;
|
||||||
|
|
@ -205,7 +225,14 @@ export async function POST(request: NextRequest) {
|
||||||
// Récupérer les informations pour le lien de signature
|
// Récupérer les informations pour le lien de signature
|
||||||
const employerSubmission = submissionResponse.data.find((sub: any) => sub.role === 'Employeur');
|
const employerSubmission = submissionResponse.data.find((sub: any) => sub.role === 'Employeur');
|
||||||
const embedCode = employerSubmission.slug;
|
const embedCode = employerSubmission.slug;
|
||||||
|
|
||||||
|
// Construire l'URL propre sans paramètres (les data-* sont ajoutés sur le composant HTML)
|
||||||
const signatureLink = `https://staging.paie.odentas.fr/odentas-sign?docuseal_id=${embedCode}`;
|
const signatureLink = `https://staging.paie.odentas.fr/odentas-sign?docuseal_id=${embedCode}`;
|
||||||
|
|
||||||
|
console.log('🔗 [SIGNATURE] Lien généré:', signatureLink);
|
||||||
|
if (employerSignatureB64) {
|
||||||
|
console.log('✅ [SIGNATURE] Signature B64 disponible pour pré-remplissage côté client');
|
||||||
|
}
|
||||||
|
|
||||||
// Étape 5 : Envoi de l'email de signature via le template universel (employeur)
|
// Étape 5 : Envoi de l'email de signature via le template universel (employeur)
|
||||||
// Seulement si skipEmployerEmail est false (signature individuelle)
|
// Seulement si skipEmployerEmail est false (signature individuelle)
|
||||||
|
|
|
||||||
|
|
@ -119,33 +119,57 @@ export async function GET(req: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adapter le format pour correspondre à l'attente de la page (format Airtable-like)
|
// Adapter le format pour correspondre à l'attente de la page (format Airtable-like)
|
||||||
const records = (contracts || []).map(contract => ({
|
// et récupérer les signatures des organisations
|
||||||
id: contract.id,
|
const records = await Promise.all((contracts || []).map(async (contract) => {
|
||||||
fields: {
|
// Récupérer la signature de l'organisation si disponible
|
||||||
'Reference': contract.reference || contract.contract_number,
|
let signatureB64 = null;
|
||||||
'reference': contract.reference || contract.contract_number,
|
if (contract.org_id) {
|
||||||
'Nom salarié': contract.employee_name,
|
try {
|
||||||
'employee_name': contract.employee_name,
|
const { data: orgDetails } = await sb
|
||||||
'Prénom Salarié': contract.employee_name, // Full name (pas de séparation prénom/nom)
|
.from('organization_details')
|
||||||
'Nom Salarié': contract.employee_name,
|
.select('signature_b64')
|
||||||
'Matricule API': contract.employee_matricule,
|
.eq('org_id', contract.org_id)
|
||||||
'Production': contract.production_name,
|
.maybeSingle();
|
||||||
'role': contract.role,
|
|
||||||
'Profession': contract.role,
|
if (orgDetails?.signature_b64) {
|
||||||
'start_date': contract.start_date,
|
signatureB64 = orgDetails.signature_b64;
|
||||||
'Date de début': contract.start_date,
|
console.log(`✅ [signatures-electroniques] Signature trouvée pour org_id: ${contract.org_id}`);
|
||||||
'État de la demande': contract.etat_de_la_demande || 'Traitée',
|
}
|
||||||
'Contrat signé par employeur': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe_par_employeur) ? 'Oui' : 'Non',
|
} catch (err) {
|
||||||
'Contrat signé': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe) ? 'Oui' : 'Non',
|
console.warn(`⚠️ [signatures-electroniques] Erreur récupération signature pour org_id: ${contract.org_id}`, err);
|
||||||
'signature_employeur': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe_par_employeur),
|
}
|
||||||
'employer_signed': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe_par_employeur),
|
|
||||||
'signature_salarie': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe),
|
|
||||||
'employee_signed': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe),
|
|
||||||
'docuseal_template_id': contract.docuseal_template_id,
|
|
||||||
'docuseal_submission_id': contract.docuseal_submission_id,
|
|
||||||
'signature_link': contract.signature_link,
|
|
||||||
'embed_src_employeur': contract.signature_link
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: contract.id,
|
||||||
|
fields: {
|
||||||
|
'Reference': contract.reference || contract.contract_number,
|
||||||
|
'reference': contract.reference || contract.contract_number,
|
||||||
|
'Nom salarié': contract.employee_name,
|
||||||
|
'employee_name': contract.employee_name,
|
||||||
|
'Prénom Salarié': contract.employee_name, // Full name (pas de séparation prénom/nom)
|
||||||
|
'Nom Salarié': contract.employee_name,
|
||||||
|
'Matricule API': contract.employee_matricule,
|
||||||
|
'Production': contract.production_name,
|
||||||
|
'role': contract.role,
|
||||||
|
'Profession': contract.role,
|
||||||
|
'start_date': contract.start_date,
|
||||||
|
'Date de début': contract.start_date,
|
||||||
|
'État de la demande': contract.etat_de_la_demande || 'Traitée',
|
||||||
|
'Contrat signé par employeur': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe_par_employeur) ? 'Oui' : 'Non',
|
||||||
|
'Contrat signé': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe) ? 'Oui' : 'Non',
|
||||||
|
'signature_employeur': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe_par_employeur),
|
||||||
|
'employer_signed': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe_par_employeur),
|
||||||
|
'signature_salarie': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe),
|
||||||
|
'employee_signed': ['oui', 'Oui', 'OUI', 'true', true].includes(contract.contrat_signe),
|
||||||
|
'docuseal_template_id': contract.docuseal_template_id,
|
||||||
|
'docuseal_submission_id': contract.docuseal_submission_id,
|
||||||
|
'signature_link': contract.signature_link,
|
||||||
|
'embed_src_employeur': contract.signature_link,
|
||||||
|
'org_id': contract.org_id,
|
||||||
|
'signature_b64': signatureB64
|
||||||
|
}
|
||||||
|
};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return NextResponse.json({ records });
|
return NextResponse.json({ records });
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,8 @@ export async function POST(request: NextRequest) {
|
||||||
employeeFirstName: contract.employee_name?.split(' ')[0],
|
employeeFirstName: contract.employee_name?.split(' ')[0],
|
||||||
matricule: contract.employee_matricule,
|
matricule: contract.employee_matricule,
|
||||||
contractType: 'CDDU',
|
contractType: 'CDDU',
|
||||||
skipEmployerEmail: true // Ne pas envoyer d'email à l'employeur individuellement
|
skipEmployerEmail: true, // Ne pas envoyer d'email à l'employeur individuellement
|
||||||
|
orgId: contract.org_id // Passer l'org_id pour récupérer la signature
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`📤 Appel API DocuSeal pour contrat ${contract.contract_number}`);
|
console.log(`📤 Appel API DocuSeal pour contrat ${contract.contract_number}`);
|
||||||
|
|
|
||||||
|
|
@ -953,7 +953,8 @@ export default function ContractEditor({
|
||||||
employerCode: employerCode,
|
employerCode: employerCode,
|
||||||
employeeFirstName: salarie?.prenom || contract.employee_name?.split(' ')[0],
|
employeeFirstName: salarie?.prenom || contract.employee_name?.split(' ')[0],
|
||||||
matricule: salarie?.code_salarie || salarie?.matricule || contract.employee_matricule,
|
matricule: salarie?.code_salarie || salarie?.matricule || contract.employee_matricule,
|
||||||
contractType: 'CDDU'
|
contractType: 'CDDU',
|
||||||
|
orgId: contract.org_id // Passer l'org_id pour récupérer la signature
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("📤 [SIGNATURE] Données envoyées:", signatureData);
|
console.log("📤 [SIGNATURE] Données envoyées:", signatureData);
|
||||||
|
|
|
||||||
32
supabase/migrations/add_signature_b64_column.sql
Normal file
32
supabase/migrations/add_signature_b64_column.sql
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
-- Migration: Ajouter la colonne signature_b64 dans organization_details
|
||||||
|
-- Date: 2025-10-14
|
||||||
|
-- Description: Permet de stocker la signature de l'employeur en base64 pour pré-remplissage DocuSeal
|
||||||
|
|
||||||
|
-- Ajouter la colonne signature_b64 si elle n'existe pas
|
||||||
|
ALTER TABLE organization_details
|
||||||
|
ADD COLUMN IF NOT EXISTS signature_b64 TEXT;
|
||||||
|
|
||||||
|
-- Créer un index pour optimiser les recherches
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_organization_details_signature
|
||||||
|
ON organization_details(org_id)
|
||||||
|
WHERE signature_b64 IS NOT NULL;
|
||||||
|
|
||||||
|
-- Ajouter un commentaire sur la colonne
|
||||||
|
COMMENT ON COLUMN organization_details.signature_b64 IS
|
||||||
|
'Signature de l''employeur en base64 pour pré-remplissage DocuSeal. Format acceptés: base64, URL publique, ou texte brut.';
|
||||||
|
|
||||||
|
-- Exemple d'insertion (à adapter avec une vraie signature)
|
||||||
|
-- UPDATE organization_details
|
||||||
|
-- SET signature_b64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...'
|
||||||
|
-- WHERE org_id = 'votre-org-id';
|
||||||
|
|
||||||
|
-- Vérification
|
||||||
|
SELECT
|
||||||
|
org_id,
|
||||||
|
structure,
|
||||||
|
CASE
|
||||||
|
WHEN signature_b64 IS NOT NULL THEN 'Signature présente'
|
||||||
|
ELSE 'Pas de signature'
|
||||||
|
END as statut_signature
|
||||||
|
FROM organization_details
|
||||||
|
ORDER BY structure;
|
||||||
Loading…
Reference in a new issue