feat: Release v3.1.6 - Amélioration complète des flux de passages
- Optimisation des listes de passages (user/admin) - Amélioration du flux de création avec validation temps réel - Amélioration du flux de consultation avec export multi-formats - Amélioration du flux de modification avec suivi des changements - Ajout de la génération PDF pour les reçus - Migration de la structure des uploads - Implémentation de la file d'attente d'emails - Ajout des permissions de suppression de passages - Corrections de bugs et optimisations performances 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
138
api/docs/CHK_USER_DELETE_PASS_INFO.md
Normal file
138
api/docs/CHK_USER_DELETE_PASS_INFO.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Gestion du champ chk_user_delete_pass
|
||||
|
||||
## 📋 Description
|
||||
Le champ `chk_user_delete_pass` permet de contrôler si les membres d'une amicale peuvent supprimer des passages.
|
||||
|
||||
## 🔄 Modifications API
|
||||
|
||||
### 1. Base de données
|
||||
- **Table** : `entites`
|
||||
- **Champ** : `chk_user_delete_pass` TINYINT(1) DEFAULT 0
|
||||
- **Valeurs** :
|
||||
- `0` : Les membres NE peuvent PAS supprimer de passages (par défaut)
|
||||
- `1` : Les membres PEUVENT supprimer des passages
|
||||
|
||||
### 2. Endpoints modifiés
|
||||
|
||||
#### POST /api/entites (Création)
|
||||
- Le champ est automatiquement initialisé à `0` (false) lors de la création
|
||||
- Non modifiable à la création
|
||||
|
||||
#### PUT /api/entites/{id} (Modification)
|
||||
**Entrée JSON :**
|
||||
```json
|
||||
{
|
||||
"chk_user_delete_pass": 1
|
||||
}
|
||||
```
|
||||
- **Type** : Boolean (0 ou 1)
|
||||
- **Obligatoire** : Non
|
||||
- **Accès** : Administrateurs uniquement (fk_role > 1)
|
||||
|
||||
#### GET /api/entites/{id} (Récupération)
|
||||
**Sortie JSON :**
|
||||
```json
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Amicale de Pompiers",
|
||||
"code_postal": "75001",
|
||||
"ville": "Paris",
|
||||
"chk_active": 1,
|
||||
"chk_user_delete_pass": 0
|
||||
}
|
||||
```
|
||||
|
||||
#### GET /api/entites (Liste)
|
||||
Retourne `chk_user_delete_pass` pour chaque entité dans la liste.
|
||||
|
||||
### 3. Route /api/login
|
||||
Le champ `chk_user_delete_pass` est maintenant inclus dans la réponse de login dans les objets `amicale` :
|
||||
|
||||
**Réponse JSON :**
|
||||
```json
|
||||
{
|
||||
"user": { ... },
|
||||
"amicale": {
|
||||
"id": 5,
|
||||
"name": "Amicale de Pompiers",
|
||||
"code_postal": "75001",
|
||||
"ville": "Paris",
|
||||
"chk_demo": 0,
|
||||
"chk_mdp_manuel": 0,
|
||||
"chk_username_manuel": 0,
|
||||
"chk_copie_mail_recu": 0,
|
||||
"chk_accept_sms": 0,
|
||||
"chk_active": 1,
|
||||
"chk_stripe": 0,
|
||||
"chk_user_delete_pass": 0 // ← NOUVEAU CHAMP
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 Utilisation côté client
|
||||
|
||||
### Flutter/Web
|
||||
Le client doit :
|
||||
1. **Récupérer** la valeur de `chk_user_delete_pass` depuis la réponse login
|
||||
2. **Stocker** cette valeur dans l'état de l'application
|
||||
3. **Conditionner** l'affichage du bouton de suppression selon cette valeur
|
||||
|
||||
**Exemple Flutter :**
|
||||
```dart
|
||||
// Dans le modèle Amicale
|
||||
class Amicale {
|
||||
final int id;
|
||||
final String name;
|
||||
final bool chkUserDeletePass; // Nouveau champ
|
||||
|
||||
bool get canUserDeletePassage => chkUserDeletePass;
|
||||
}
|
||||
|
||||
// Dans l'UI
|
||||
if (amicale.canUserDeletePassage) {
|
||||
// Afficher le bouton de suppression
|
||||
IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed: () => deletePassage(passageId),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## ⚠️ Points importants
|
||||
|
||||
1. **Valeur par défaut** : Toujours `0` (false) pour la sécurité
|
||||
2. **Modification** : Seuls les administrateurs (fk_role > 1) peuvent modifier ce champ
|
||||
3. **Rétrocompatibilité** : Les entités existantes ont la valeur `0` par défaut
|
||||
4. **Validation côté serveur** : L'API vérifiera également ce droit lors de la tentative de suppression
|
||||
|
||||
## 📝 Script SQL
|
||||
Le script de migration est disponible dans :
|
||||
```
|
||||
/scripts/sql/add_chk_user_delete_pass.sql
|
||||
```
|
||||
|
||||
## ✅ Checklist d'implémentation
|
||||
|
||||
### Côté API (déjà fait) :
|
||||
- [x] Ajout du champ en base de données
|
||||
- [x] Modification EntiteController (create, update, get)
|
||||
- [x] Modification LoginController (réponse login)
|
||||
- [x] Script SQL de migration
|
||||
|
||||
### Côté Client (à faire) :
|
||||
- [ ] Ajouter le champ dans le modèle Amicale
|
||||
- [ ] Parser le champ depuis la réponse login
|
||||
- [ ] Stocker dans l'état de l'application
|
||||
- [ ] Conditionner l'affichage du bouton suppression
|
||||
- [ ] Tester avec des valeurs 0 et 1
|
||||
|
||||
## 🔒 Sécurité
|
||||
Même si `chk_user_delete_pass = 1`, l'API devra vérifier :
|
||||
- L'authentification de l'utilisateur
|
||||
- L'appartenance à l'entité
|
||||
- Le droit de suppression sur le passage spécifique
|
||||
- Les règles métier (ex: pas de suppression après export)
|
||||
|
||||
---
|
||||
**Date :** 20/08/2025
|
||||
**Version API :** 3.1.4
|
||||
165
api/docs/DELETE_PASSAGE_PERMISSIONS.md
Normal file
165
api/docs/DELETE_PASSAGE_PERMISSIONS.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# API DELETE /passages/{id} - Documentation des permissions
|
||||
|
||||
## 📋 Endpoint
|
||||
```
|
||||
DELETE /api/passages/{id}
|
||||
```
|
||||
|
||||
## 🔒 Authentification
|
||||
- **Requise** : OUI (Bearer token)
|
||||
- **Session** : Doit être valide
|
||||
|
||||
## 📊 Logique de permissions
|
||||
|
||||
### Règles par rôle :
|
||||
|
||||
| fk_role | Description | Peut supprimer ? | Conditions |
|
||||
|---------|------------|------------------|------------|
|
||||
| 1 | Membre | ✅ Conditionnel | Si `entites.chk_user_delete_pass = 1` |
|
||||
| 2 | Admin amicale | ✅ OUI | Toujours autorisé |
|
||||
| 3+ | Super admin | ✅ OUI | Toujours autorisé |
|
||||
|
||||
### Détail du contrôle pour les membres (fk_role = 1) :
|
||||
|
||||
```sql
|
||||
-- L'API vérifie :
|
||||
SELECT chk_user_delete_pass
|
||||
FROM entites
|
||||
WHERE id = {user.fk_entite}
|
||||
|
||||
-- Si chk_user_delete_pass = 0 → Erreur 403
|
||||
-- Si chk_user_delete_pass = 1 → Continue
|
||||
```
|
||||
|
||||
## 🔄 Flux de vérification
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[DELETE /passages/{id}] --> B{Utilisateur authentifié ?}
|
||||
B -->|Non| C[Erreur 401]
|
||||
B -->|Oui| D{Récupérer fk_role}
|
||||
D --> E{fk_role = 1 ?}
|
||||
E -->|Non| F[Autorisé - Admin]
|
||||
E -->|Oui| G{Vérifier chk_user_delete_pass}
|
||||
G -->|= 0| H[Erreur 403 - Non autorisé]
|
||||
G -->|= 1| F
|
||||
F --> I{Passage existe ?}
|
||||
I -->|Non| J[Erreur 404]
|
||||
I -->|Oui| K{Passage appartient à l'entité ?}
|
||||
K -->|Non| L[Erreur 404]
|
||||
K -->|Oui| M[Soft delete : chk_active = 0]
|
||||
M --> N[Succès 200]
|
||||
```
|
||||
|
||||
## 📝 Réponses
|
||||
|
||||
### ✅ Succès (200)
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Passage supprimé avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Erreur 401 - Non authentifié
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Vous devez être connecté pour effectuer cette action"
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Erreur 403 - Permission refusée (membre sans autorisation)
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Vous n'avez pas l'autorisation de supprimer des passages"
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Erreur 404 - Passage non trouvé
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Passage non trouvé"
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Logging
|
||||
|
||||
L'API enregistre :
|
||||
|
||||
### En cas de tentative non autorisée :
|
||||
```php
|
||||
LogService::log('Tentative de suppression de passage non autorisée', [
|
||||
'level' => 'warning',
|
||||
'userId' => $userId,
|
||||
'userRole' => $userRole,
|
||||
'entiteId' => $entiteId,
|
||||
'passageId' => $passageId,
|
||||
'chk_user_delete_pass' => 0
|
||||
]);
|
||||
```
|
||||
|
||||
### En cas de succès :
|
||||
```php
|
||||
LogService::log('Suppression d\'un passage', [
|
||||
'level' => 'info',
|
||||
'userId' => $userId,
|
||||
'passageId' => $passageId
|
||||
]);
|
||||
```
|
||||
|
||||
## 🎯 Exemple d'utilisation
|
||||
|
||||
### Requête
|
||||
```bash
|
||||
curl -X DELETE https://api.geosector.fr/api/passages/19500576 \
|
||||
-H "Authorization: Bearer {session_token}" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
### Scénarios
|
||||
|
||||
#### Scénario 1 : Membre avec permission ✅
|
||||
- Utilisateur : fk_role = 1
|
||||
- Entité : chk_user_delete_pass = 1
|
||||
- **Résultat** : Suppression autorisée
|
||||
|
||||
#### Scénario 2 : Membre sans permission ❌
|
||||
- Utilisateur : fk_role = 1
|
||||
- Entité : chk_user_delete_pass = 0
|
||||
- **Résultat** : Erreur 403
|
||||
|
||||
#### Scénario 3 : Admin amicale ✅
|
||||
- Utilisateur : fk_role = 2
|
||||
- **Résultat** : Suppression autorisée (pas de vérification chk_user_delete_pass)
|
||||
|
||||
## ⚠️ Notes importantes
|
||||
|
||||
1. **Soft delete** : Le passage n'est pas supprimé physiquement, seulement `chk_active = 0`
|
||||
2. **Traçabilité** : `updated_at` et `fk_user_modif` sont mis à jour
|
||||
3. **Contrôle entité** : Un utilisateur ne peut supprimer que les passages de son entité
|
||||
4. **Log warning** : Toute tentative non autorisée est loggée en niveau WARNING
|
||||
|
||||
## 🔧 Configuration côté amicale
|
||||
|
||||
Pour autoriser les membres à supprimer des passages :
|
||||
|
||||
```sql
|
||||
UPDATE entites
|
||||
SET chk_user_delete_pass = 1
|
||||
WHERE id = {entite_id};
|
||||
```
|
||||
|
||||
Cette modification ne peut être faite que par un administrateur (fk_role > 1) via l'endpoint :
|
||||
```
|
||||
PUT /api/entites/{id}
|
||||
{
|
||||
"chk_user_delete_pass": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
**Version API** : 3.1.4
|
||||
**Date** : 20/08/2025
|
||||
90
api/docs/INSTALL_FPDF.md
Normal file
90
api/docs/INSTALL_FPDF.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Installation de FPDF pour la génération des reçus PDF avec logo
|
||||
|
||||
## Installation via Composer (RECOMMANDÉ)
|
||||
|
||||
Sur chaque serveur (DEV, REC, PROD), exécuter :
|
||||
|
||||
```bash
|
||||
cd /var/www/geosector/api
|
||||
composer require setasign/fpdf
|
||||
```
|
||||
|
||||
Ou si composer.json est déjà mis à jour :
|
||||
|
||||
```bash
|
||||
cd /var/www/geosector/api
|
||||
composer update
|
||||
```
|
||||
|
||||
## Fichiers à déployer
|
||||
|
||||
1. **Nouveaux fichiers** :
|
||||
- `/src/Services/ReceiptPDFGenerator.php` - Nouvelle classe de génération PDF avec FPDF
|
||||
- `/docs/_logo_recu.png` - Logo par défaut (casque de pompier)
|
||||
|
||||
2. **Fichiers modifiés** :
|
||||
- `/src/Services/ReceiptService.php` - Utilise maintenant ReceiptPDFGenerator
|
||||
- `/composer.json` - Ajout de la dépendance FPDF
|
||||
|
||||
## Vérification
|
||||
|
||||
Après installation, tester la génération d'un reçu :
|
||||
|
||||
```bash
|
||||
# Vérifier que FPDF est installé
|
||||
ls -la vendor/setasign/fpdf/
|
||||
|
||||
# Tester la génération d'un PDF
|
||||
php -r "
|
||||
require 'vendor/autoload.php';
|
||||
\$pdf = new FPDF();
|
||||
\$pdf->AddPage();
|
||||
\$pdf->SetFont('Arial','B',16);
|
||||
\$pdf->Cell(40,10,'Test FPDF OK');
|
||||
echo 'FPDF fonctionne' . PHP_EOL;
|
||||
"
|
||||
```
|
||||
|
||||
## Fonctionnalités du nouveau générateur
|
||||
|
||||
✅ **Support des vrais logos PNG/JPG**
|
||||
✅ **Logo par défaut** si l'entité n'a pas de logo
|
||||
✅ **Taille du logo** : 40x40mm
|
||||
✅ **Mise en page professionnelle** avec cadre pour le montant
|
||||
✅ **Conversion automatique** des caractères UTF-8
|
||||
✅ **PDF léger** (~20-30KB avec logo)
|
||||
|
||||
## Structure du reçu généré
|
||||
|
||||
1. **En-tête** :
|
||||
- Logo (40x40mm) à gauche
|
||||
- Nom et ville de l'entité à droite du logo
|
||||
|
||||
2. **Titre** :
|
||||
- "REÇU FISCAL DE DON"
|
||||
- Numéro du reçu
|
||||
- Article 200 CGI
|
||||
|
||||
3. **Corps** :
|
||||
- Informations du donateur
|
||||
- Montant en gros dans un cadre grisé
|
||||
- Date du don
|
||||
- Mode de règlement et campagne
|
||||
|
||||
4. **Pied de page** :
|
||||
- Mentions légales (réduction 66%)
|
||||
- Date et signature
|
||||
|
||||
## Résolution de problèmes
|
||||
|
||||
Si erreur "Class 'FPDF' not found" :
|
||||
```bash
|
||||
composer dump-autoload
|
||||
```
|
||||
|
||||
Si problème avec le logo :
|
||||
- Vérifier que `/docs/_logo_recu.png` existe
|
||||
- Vérifier les permissions : `chmod 644 docs/_logo_recu.png`
|
||||
|
||||
Si caractères accentués mal affichés :
|
||||
- FPDF utilise ISO-8859-1, la conversion est automatique dans ReceiptPDFGenerator
|
||||
237
api/docs/PREPA_PROD.md
Normal file
237
api/docs/PREPA_PROD.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# PRÉPARATION PRODUCTION - Process Email Queue + Permissions Suppression Passages
|
||||
|
||||
## 📅 Date de mise en production prévue : _____________
|
||||
|
||||
## 🎯 Objectif
|
||||
1. Mettre en place le système de traitement automatique de la queue d'emails pour l'envoi des reçus fiscaux de dons.
|
||||
2. Ajouter le champ de permission pour autoriser les membres à supprimer des passages.
|
||||
|
||||
## ✅ Prérequis
|
||||
- [ ] Backup de la base de données effectué
|
||||
- [ ] Accès SSH au serveur PROD
|
||||
- [ ] Accès à la base de données PROD
|
||||
- [ ] Droits pour éditer le crontab
|
||||
|
||||
## 📝 Fichiers à déployer
|
||||
Les fichiers suivants doivent être présents sur le serveur PROD :
|
||||
- `/scripts/cron/process_email_queue.php`
|
||||
- `/scripts/cron/process_email_queue_with_daily_log.sh`
|
||||
- `/scripts/cron/test_email_queue.php`
|
||||
- `/src/Services/ReceiptPDFGenerator.php` (nouveau)
|
||||
- `/src/Services/ReceiptService.php` (mis à jour)
|
||||
- `/src/Core/MonitoredDatabase.php` (mis à jour)
|
||||
- `/src/Controllers/EntiteController.php` (mis à jour)
|
||||
- `/src/Controllers/LoginController.php` (mis à jour)
|
||||
- `/scripts/sql/add_chk_user_delete_pass.sql` (nouveau)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 ÉTAPES DE MISE EN PRODUCTION
|
||||
|
||||
### 1️⃣ Mise à jour de la base de données
|
||||
|
||||
Se connecter à la base de données PROD et exécuter :
|
||||
|
||||
```sql
|
||||
-- Vérifier d'abord la structure actuelle de email_queue
|
||||
DESCRIBE email_queue;
|
||||
|
||||
-- Ajouter les champs manquants pour email_queue si nécessaire
|
||||
ALTER TABLE `email_queue`
|
||||
ADD COLUMN IF NOT EXISTS `sent_at` TIMESTAMP NULL DEFAULT NULL
|
||||
COMMENT 'Date/heure d\'envoi effectif de l\'email'
|
||||
AFTER `status`;
|
||||
|
||||
ALTER TABLE `email_queue`
|
||||
ADD COLUMN IF NOT EXISTS `error_message` TEXT NULL DEFAULT NULL
|
||||
COMMENT 'Message d\'erreur en cas d\'échec'
|
||||
AFTER `attempts`;
|
||||
|
||||
-- Ajouter les index pour optimiser les performances
|
||||
ALTER TABLE `email_queue`
|
||||
ADD INDEX IF NOT EXISTS `idx_status_attempts` (`status`, `attempts`);
|
||||
|
||||
ALTER TABLE `email_queue`
|
||||
ADD INDEX IF NOT EXISTS `idx_sent_at` (`sent_at`);
|
||||
|
||||
-- Vérifier les modifications email_queue
|
||||
DESCRIBE email_queue;
|
||||
|
||||
-- ⚠️ IMPORTANT : Ajouter le nouveau champ chk_user_delete_pass dans entites
|
||||
source /var/www/geosector/api/scripts/sql/add_chk_user_delete_pass.sql;
|
||||
```
|
||||
|
||||
### 2️⃣ Test du script avant mise en production
|
||||
|
||||
```bash
|
||||
# Se connecter au serveur PROD
|
||||
ssh user@prod-server
|
||||
|
||||
# Aller dans le répertoire de l'API
|
||||
cd /var/www/geosector/api
|
||||
|
||||
# Rendre les scripts exécutables
|
||||
chmod +x scripts/cron/process_email_queue.php
|
||||
chmod +x scripts/cron/test_email_queue.php
|
||||
|
||||
# Tester l'état de la queue (lecture seule)
|
||||
php scripts/cron/test_email_queue.php
|
||||
|
||||
# Si tout est OK, faire un test d'envoi sur 1 email
|
||||
# (modifier temporairement BATCH_SIZE à 1 dans le script si nécessaire)
|
||||
php scripts/cron/process_email_queue.php
|
||||
```
|
||||
|
||||
### 3️⃣ Configuration du CRON avec logs journaliers
|
||||
|
||||
```bash
|
||||
# Rendre le script wrapper exécutable
|
||||
chmod +x /var/www/geosector/api/scripts/cron/process_email_queue_with_daily_log.sh
|
||||
|
||||
# Éditer le crontab
|
||||
crontab -e
|
||||
|
||||
# Ajouter cette ligne pour exécution toutes les 5 minutes avec logs journaliers
|
||||
*/5 * * * * /var/www/geosector/api/scripts/cron/process_email_queue_with_daily_log.sh
|
||||
|
||||
# Sauvegarder et quitter (:wq sous vi/vim)
|
||||
|
||||
# Vérifier que le cron est bien enregistré
|
||||
crontab -l | grep email_queue
|
||||
|
||||
# Vérifier que le service cron est actif
|
||||
systemctl status cron
|
||||
```
|
||||
|
||||
**Note** : Les logs seront créés automatiquement dans `/var/www/geosector/api/logs/` avec le format : `email_queue_20250820.log`, `email_queue_20250821.log`, etc. Les logs de plus de 30 jours sont supprimés automatiquement.
|
||||
|
||||
### 4️⃣ Surveillance post-déploiement
|
||||
|
||||
Pendant les premières heures après la mise en production :
|
||||
|
||||
```bash
|
||||
# Surveiller les logs en temps réel (fichier du jour)
|
||||
tail -f /var/www/geosector/api/logs/email_queue_$(date +%Y%m%d).log
|
||||
|
||||
# Vérifier le statut de la queue
|
||||
php scripts/cron/test_email_queue.php
|
||||
|
||||
# Compter les emails traités
|
||||
mysql -u geo_app_user_prod -p geo_app -e "
|
||||
SELECT status, COUNT(*) as count
|
||||
FROM email_queue
|
||||
WHERE DATE(created_at) = CURDATE()
|
||||
GROUP BY status;"
|
||||
|
||||
# Vérifier les erreurs éventuelles
|
||||
mysql -u geo_app_user_prod -p geo_app -e "
|
||||
SELECT id, to_email, subject, attempts, error_message
|
||||
FROM email_queue
|
||||
WHERE status='failed'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 10;"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 ROLLBACK (si nécessaire)
|
||||
|
||||
En cas de problème, voici comment revenir en arrière :
|
||||
|
||||
```bash
|
||||
# 1. Stopper le cron
|
||||
crontab -e
|
||||
# Commenter la ligne du process_email_queue
|
||||
|
||||
# 2. Marquer les emails en attente pour traitement manuel
|
||||
mysql -u geo_app_user_prod -p geo_app -e "
|
||||
UPDATE email_queue
|
||||
SET status='pending', attempts=0
|
||||
WHERE status='failed' AND DATE(created_at) = CURDATE();"
|
||||
|
||||
# 3. Informer l'équipe pour traitement manuel si nécessaire
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 VALIDATION POST-DÉPLOIEMENT
|
||||
|
||||
### Critères de succès :
|
||||
- [ ] Aucune erreur dans les logs
|
||||
- [ ] Les emails sont envoyés dans les 5 minutes
|
||||
- [ ] Les reçus PDF sont correctement attachés
|
||||
- [ ] Le champ `date_sent_recu` est mis à jour dans `ope_pass`
|
||||
- [ ] Pas d'accumulation d'emails en status 'pending'
|
||||
|
||||
### Commandes de vérification :
|
||||
|
||||
```bash
|
||||
# Statistiques générales
|
||||
mysql -u geo_app_user_prod -p geo_app -e "
|
||||
SELECT
|
||||
status,
|
||||
COUNT(*) as count,
|
||||
MIN(created_at) as oldest,
|
||||
MAX(created_at) as newest
|
||||
FROM email_queue
|
||||
GROUP BY status;"
|
||||
|
||||
# Vérifier les passages avec reçus envoyés aujourd'hui
|
||||
mysql -u geo_app_user_prod -p geo_app -e "
|
||||
SELECT COUNT(*) as recus_envoyes_aujourdhui
|
||||
FROM ope_pass
|
||||
WHERE DATE(date_sent_recu) = CURDATE();"
|
||||
|
||||
# Performance du cron (dernières exécutions du jour)
|
||||
tail -20 /var/www/geosector/api/logs/email_queue_$(date +%Y%m%d).log | grep "Traitement terminé"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 CONTACTS EN CAS DE PROBLÈME
|
||||
|
||||
- **Responsable technique** : _____________
|
||||
- **DBA** : _____________
|
||||
- **Support O2Switch** : support@o2switch.fr
|
||||
|
||||
---
|
||||
|
||||
## 📋 NOTES IMPORTANTES
|
||||
|
||||
1. **Limite d'envoi** : 1500 emails/heure max (limite O2Switch)
|
||||
2. **Batch size** : 50 emails par exécution (toutes les 5 min = 600/heure max)
|
||||
3. **Lock file** : `/tmp/process_email_queue.lock` empêche l'exécution simultanée
|
||||
4. **Nettoyage auto** : Les emails envoyés > 30 jours sont supprimés automatiquement
|
||||
|
||||
## 🔒 SÉCURITÉ
|
||||
|
||||
- Les mots de passe SMTP ne sont jamais loggués
|
||||
- Les emails en erreur conservent le message d'erreur pour diagnostic
|
||||
- Le PDF est envoyé en pièce jointe encodée en base64
|
||||
|
||||
---
|
||||
|
||||
## ✅ CHECKLIST FINALE
|
||||
|
||||
### Email Queue :
|
||||
- [ ] Table email_queue mise à jour (sent_at, error_message, index)
|
||||
- [ ] Scripts cron testés avec succès
|
||||
- [ ] Cron configuré et actif
|
||||
- [ ] Logs accessibles et fonctionnels
|
||||
- [ ] Premier batch d'emails envoyé avec succès
|
||||
|
||||
### Permissions Suppression Passages :
|
||||
- [ ] Champ chk_user_delete_pass ajouté dans la table entites
|
||||
- [ ] EntiteController.php mis à jour pour gérer le nouveau champ
|
||||
- [ ] LoginController.php mis à jour pour retourner le champ dans amicale
|
||||
- [ ] Test de modification de permissions via l'interface admin
|
||||
|
||||
### Général :
|
||||
- [ ] Documentation mise à jour
|
||||
- [ ] Équipe informée de la mise en production
|
||||
|
||||
---
|
||||
|
||||
**Date de mise en production** : _______________
|
||||
**Validé par** : _______________
|
||||
**Signature** : _______________
|
||||
149
api/docs/SETUP_EMAIL_QUEUE_CRON.md
Normal file
149
api/docs/SETUP_EMAIL_QUEUE_CRON.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Instructions de mise en place du CRON pour la queue d'emails
|
||||
|
||||
## Problème résolu
|
||||
Les emails de reçus étaient insérés dans la table `email_queue` mais n'étaient jamais envoyés car il manquait le script de traitement.
|
||||
|
||||
## Fichiers créés
|
||||
1. `/scripts/cron/process_email_queue.php` - Script principal de traitement
|
||||
2. `/scripts/cron/test_email_queue.php` - Script de test/diagnostic
|
||||
3. `/scripts/sql/add_email_queue_fields.sql` - Migration SQL pour les champs manquants
|
||||
|
||||
## Installation sur les serveurs (DVA, REC, PROD)
|
||||
|
||||
### 1. Appliquer la migration SQL
|
||||
|
||||
Se connecter à la base de données et exécuter :
|
||||
|
||||
```bash
|
||||
mysql -u [user] -p [database] < /path/to/api/scripts/sql/add_email_queue_fields.sql
|
||||
```
|
||||
|
||||
Ou directement dans MySQL :
|
||||
```sql
|
||||
-- Ajouter les champs manquants
|
||||
ALTER TABLE `email_queue`
|
||||
ADD COLUMN IF NOT EXISTS `sent_at` TIMESTAMP NULL DEFAULT NULL AFTER `status`;
|
||||
|
||||
ALTER TABLE `email_queue`
|
||||
ADD COLUMN IF NOT EXISTS `error_message` TEXT NULL DEFAULT NULL AFTER `attempts`;
|
||||
|
||||
-- Ajouter les index pour les performances
|
||||
ALTER TABLE `email_queue`
|
||||
ADD INDEX IF NOT EXISTS `idx_status_attempts` (`status`, `attempts`);
|
||||
|
||||
ALTER TABLE `email_queue`
|
||||
ADD INDEX IF NOT EXISTS `idx_sent_at` (`sent_at`);
|
||||
```
|
||||
|
||||
### 2. Tester le script
|
||||
|
||||
Avant de mettre en place le cron, tester que tout fonctionne :
|
||||
|
||||
```bash
|
||||
# Vérifier l'état de la queue
|
||||
php /path/to/api/scripts/cron/test_email_queue.php
|
||||
|
||||
# Tester l'envoi (traite jusqu'à 50 emails)
|
||||
php /path/to/api/scripts/cron/process_email_queue.php
|
||||
```
|
||||
|
||||
### 3. Configurer le CRON
|
||||
|
||||
Ajouter la ligne suivante dans le crontab du serveur :
|
||||
|
||||
```bash
|
||||
# Éditer le crontab
|
||||
crontab -e
|
||||
|
||||
# Ajouter cette ligne (exécution toutes les 5 minutes)
|
||||
*/5 * * * * /usr/bin/php /path/to/api/scripts/cron/process_email_queue.php >> /var/log/email_queue.log 2>&1
|
||||
```
|
||||
|
||||
**Options de fréquence :**
|
||||
- `*/5 * * * *` - Toutes les 5 minutes (recommandé)
|
||||
- `*/10 * * * *` - Toutes les 10 minutes
|
||||
- `*/2 * * * *` - Toutes les 2 minutes (si volume important)
|
||||
|
||||
### 4. Monitoring
|
||||
|
||||
Le script génère des logs via `LogService`. Vérifier les logs dans :
|
||||
- `/path/to/api/logs/` (selon la configuration)
|
||||
|
||||
Points à surveiller :
|
||||
- Nombre d'emails traités
|
||||
- Emails en échec après 3 tentatives
|
||||
- Erreurs de connexion SMTP
|
||||
|
||||
### 5. Configuration SMTP
|
||||
|
||||
Vérifier que la configuration SMTP est correcte dans `AppConfig` :
|
||||
- Host SMTP
|
||||
- Port (587 pour TLS, 465 pour SSL)
|
||||
- Username/Password
|
||||
- Encryption (tls ou ssl)
|
||||
- From Email/Name
|
||||
|
||||
## Fonctionnement du script
|
||||
|
||||
### Caractéristiques
|
||||
- **Batch size** : 50 emails par exécution
|
||||
- **Max tentatives** : 3 essais par email
|
||||
- **Lock file** : Empêche l'exécution simultanée
|
||||
- **Nettoyage** : Supprime les emails envoyés > 30 jours
|
||||
- **Pause** : 0.5s entre chaque email (anti-spam)
|
||||
|
||||
### Workflow
|
||||
1. Récupère les emails avec `status = 'pending'` et `attempts < 3`
|
||||
2. Pour chaque email :
|
||||
- Incrémente le compteur de tentatives
|
||||
- Envoie via PHPMailer avec la config SMTP
|
||||
- Si succès : `status = 'sent'` + mise à jour du passage
|
||||
- Si échec : réessai à la prochaine exécution
|
||||
- Après 3 échecs : `status = 'failed'`
|
||||
|
||||
### Tables mises à jour
|
||||
- `email_queue` : status, attempts, sent_at, error_message
|
||||
- `ope_pass` : date_sent_recu, chk_email_sent
|
||||
|
||||
## Commandes utiles
|
||||
|
||||
```bash
|
||||
# Voir les emails en attente
|
||||
mysql -e "SELECT COUNT(*) FROM email_queue WHERE status='pending'" [database]
|
||||
|
||||
# Voir les emails échoués
|
||||
mysql -e "SELECT * FROM email_queue WHERE status='failed' ORDER BY created_at DESC LIMIT 10" [database]
|
||||
|
||||
# Réinitialiser un email échoué pour réessai
|
||||
mysql -e "UPDATE email_queue SET status='pending', attempts=0 WHERE id=[ID]" [database]
|
||||
|
||||
# Voir les logs du cron
|
||||
tail -f /var/log/email_queue.log
|
||||
|
||||
# Vérifier que le cron est actif
|
||||
crontab -l | grep process_email_queue
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Le cron ne s'exécute pas
|
||||
- Vérifier les permissions : `chmod +x process_email_queue.php`
|
||||
- Vérifier le chemin PHP : `which php`
|
||||
- Vérifier les logs système : `/var/log/syslog` ou `/var/log/cron`
|
||||
|
||||
### Emails en échec
|
||||
- Vérifier la config SMTP avec `test_email_queue.php`
|
||||
- Vérifier les logs pour les messages d'erreur
|
||||
- Tester la connexion SMTP : `telnet [smtp_host] [port]`
|
||||
|
||||
### Lock bloqué
|
||||
Si le message "Le processus est déjà en cours" persiste :
|
||||
```bash
|
||||
rm /tmp/process_email_queue.lock
|
||||
```
|
||||
|
||||
## Contact support
|
||||
En cas de problème, vérifier :
|
||||
1. Les logs de l'application
|
||||
2. La table `email_queue` pour les messages d'erreur
|
||||
3. La configuration SMTP dans AppConfig
|
||||
155
api/docs/UPLOAD-MIGRATION-RECAP.md
Normal file
155
api/docs/UPLOAD-MIGRATION-RECAP.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# 📋 RÉCAPITULATIF - Migration Arborescence Uploads
|
||||
|
||||
## ✅ Modifications effectuées
|
||||
|
||||
### 1. **EntiteController.php** (ligne 736)
|
||||
```php
|
||||
// Avant : "/entites/{$entiteId}/logo"
|
||||
// Après : "/{$entiteId}/logo"
|
||||
```
|
||||
✅ Les logos sont maintenant stockés dans : `uploads/{entite_id}/logo/`
|
||||
|
||||
### 2. **ReceiptService.php** (ligne 95)
|
||||
```php
|
||||
// Avant : "/entites/{$entiteId}/recus/{$operationId}"
|
||||
// Après : "/{$entiteId}/recus/{$operationId}"
|
||||
```
|
||||
✅ Les reçus PDF sont maintenant stockés dans : `uploads/{entite_id}/recus/{operation_id}/`
|
||||
|
||||
### 3. **ExportService.php** (lignes 40 et 141)
|
||||
```php
|
||||
// Avant Excel : "/{$entiteId}/operations/{$operationId}/exports/excel"
|
||||
// Après Excel : "/{$entiteId}/operations/{$operationId}"
|
||||
|
||||
// Avant JSON : "/{$entiteId}/operations/{$operationId}/exports/json"
|
||||
// Après JSON : "/{$entiteId}/operations/{$operationId}"
|
||||
```
|
||||
✅ Les exports sont maintenant stockés directement dans : `uploads/{entite_id}/operations/{operation_id}/`
|
||||
|
||||
## 📂 Nouvelle structure complète
|
||||
|
||||
```
|
||||
uploads/
|
||||
└── {entite_id}/ # Ex: 5, 1230, etc.
|
||||
├── logo/ # Logo de l'entité
|
||||
│ └── logo_{entite_id}_{timestamp}.{jpg|png}
|
||||
├── operations/ # Exports d'opérations
|
||||
│ └── {operation_id}/ # Ex: 1525, 3124
|
||||
│ ├── geosector-export-{operation_id}-{timestamp}.xlsx
|
||||
│ └── backup-{operation_id}-{timestamp}.json.enc
|
||||
└── recus/ # Reçus fiscaux
|
||||
└── {operation_id}/ # Ex: 3124
|
||||
└── recu_{passage_id}.pdf
|
||||
```
|
||||
|
||||
## 🔧 Script de migration
|
||||
|
||||
Un script a été créé pour migrer les fichiers existants :
|
||||
|
||||
**Fichier :** `/scripts/migrate_uploads_structure.php`
|
||||
|
||||
**Usage :**
|
||||
```bash
|
||||
# Mode simulation (voir ce qui sera fait sans modifier)
|
||||
php scripts/migrate_uploads_structure.php --dry-run
|
||||
|
||||
# Mode réel (effectue la migration)
|
||||
php scripts/migrate_uploads_structure.php
|
||||
```
|
||||
|
||||
**Ce que fait le script :**
|
||||
1. Déplace tout le contenu de `uploads/entites/*` vers `uploads/*`
|
||||
2. Fusionne les dossiers si nécessaire
|
||||
3. Simplifie la structure des exports (supprime `/documents/exports/excel/`)
|
||||
4. Applique les bonnes permissions (nginx:nobody 775/664)
|
||||
5. Crée un log détaillé dans `/logs/migration_uploads_YYYYMMDD_HHMMSS.log`
|
||||
|
||||
## 🚀 Procédure de déploiement
|
||||
|
||||
### Sur DEV (déjà fait)
|
||||
✅ Code modifié
|
||||
✅ Script de migration créé
|
||||
|
||||
### Sur REC
|
||||
```bash
|
||||
# 1. Déployer le nouveau code
|
||||
./livre-api.sh rec
|
||||
|
||||
# 2. Faire un backup des uploads actuels
|
||||
cd /var/www/geosector/api
|
||||
tar -czf uploads_backup_$(date +%Y%m%d).tar.gz uploads/
|
||||
|
||||
# 3. Tester en mode dry-run
|
||||
php scripts/migrate_uploads_structure.php --dry-run
|
||||
|
||||
# 4. Si OK, lancer la migration
|
||||
php scripts/migrate_uploads_structure.php
|
||||
|
||||
# 5. Vérifier la nouvelle structure
|
||||
ls -la uploads/
|
||||
ls -la uploads/*/
|
||||
```
|
||||
|
||||
### Sur PROD
|
||||
Même procédure que REC après validation
|
||||
|
||||
## ⚠️ Points d'attention
|
||||
|
||||
1. **Backup obligatoire** avant migration
|
||||
2. **Vérifier l'espace disque** disponible
|
||||
3. **Tester d'abord en dry-run**
|
||||
4. **Surveiller les logs** après migration
|
||||
5. **Tester** upload logo, génération reçu, et export Excel
|
||||
|
||||
## 📊 Gains obtenus
|
||||
|
||||
| Aspect | Avant | Après |
|
||||
|--------|-------|-------|
|
||||
| **Profondeur max** | 8 niveaux | 4 niveaux |
|
||||
| **Complexité** | 2 structures parallèles | 1 structure unique |
|
||||
| **Clarté** | Confus (entites + racine) | Simple et logique |
|
||||
| **Navigation** | Difficile | Intuitive |
|
||||
|
||||
## 🔍 Vérification post-migration
|
||||
|
||||
Après la migration, vérifier :
|
||||
|
||||
```bash
|
||||
# Structure attendue pour l'entité 5
|
||||
tree uploads/5/
|
||||
# Devrait afficher :
|
||||
# uploads/5/
|
||||
# ├── logo/
|
||||
# │ └── logo_5_*.png
|
||||
# ├── operations/
|
||||
# │ ├── 1525/
|
||||
# │ │ └── *.xlsx
|
||||
# │ └── 3124/
|
||||
# │ └── *.xlsx
|
||||
# └── recus/
|
||||
# └── 3124/
|
||||
# └── recu_*.pdf
|
||||
|
||||
# Vérifier les permissions
|
||||
ls -la uploads/*/
|
||||
# Devrait montrer : nginx:nobody avec 775 pour dossiers, 664 pour fichiers
|
||||
```
|
||||
|
||||
## ✅ Checklist finale
|
||||
|
||||
- [ ] Code modifié et testé en DEV
|
||||
- [ ] Script de migration créé
|
||||
- [ ] Documentation mise à jour
|
||||
- [ ] Backup effectué sur REC
|
||||
- [ ] Migration testée en dry-run sur REC
|
||||
- [ ] Migration exécutée sur REC
|
||||
- [ ] Tests fonctionnels sur REC
|
||||
- [ ] Backup effectué sur PROD
|
||||
- [ ] Migration exécutée sur PROD
|
||||
- [ ] Tests fonctionnels sur PROD
|
||||
|
||||
---
|
||||
|
||||
**Date de création :** 20/08/2025
|
||||
**Auteur :** Assistant Claude
|
||||
**Status :** Prêt pour déploiement
|
||||
93
api/docs/UPLOAD-REORGANIZATION.md
Normal file
93
api/docs/UPLOAD-REORGANIZATION.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Réorganisation de l'arborescence des uploads
|
||||
|
||||
## 📅 Date : 20/08/2025
|
||||
|
||||
## 🎯 Objectif
|
||||
Uniformiser et simplifier l'arborescence des fichiers uploads pour une meilleure organisation et maintenance.
|
||||
|
||||
## 📂 Arborescence actuelle (PROBLÈME)
|
||||
```
|
||||
uploads/
|
||||
├── entites/
|
||||
│ └── 5/
|
||||
│ ├── logo/
|
||||
│ ├── operations/
|
||||
│ │ └── 1525/
|
||||
│ │ └── documents/
|
||||
│ │ └── exports/
|
||||
│ │ └── excel/
|
||||
│ │ └── geosector-export-*.xlsx
|
||||
│ └── recus/
|
||||
│ └── 3124/
|
||||
│ └── recu_*.pdf
|
||||
└── 5/
|
||||
└── operations/
|
||||
├── 1525/
|
||||
└── 2021/
|
||||
```
|
||||
|
||||
**Problèmes identifiés :**
|
||||
- Duplication des structures (dossier `5` à la racine ET dans `entites/`)
|
||||
- Chemins trop profonds pour les exports Excel (6 niveaux)
|
||||
- Incohérence dans les chemins
|
||||
|
||||
## ✅ Nouvelle arborescence (SOLUTION)
|
||||
```
|
||||
uploads/
|
||||
└── {entite_id}/ # Un seul dossier par entité à la racine
|
||||
├── logo/ # Logo de l'entité
|
||||
│ └── logo_*.{jpg,png}
|
||||
├── operations/ # Exports par opération
|
||||
│ └── {operation_id}/
|
||||
│ └── *.xlsx # Exports Excel directement ici
|
||||
└── recus/ # Reçus par opération
|
||||
└── {operation_id}/
|
||||
└── recu_*.pdf
|
||||
```
|
||||
|
||||
## 📝 Fichiers à modifier
|
||||
|
||||
### 1. EntiteController.php (Upload logo)
|
||||
**Actuel :** `/entites/{$entiteId}/logo`
|
||||
**Nouveau :** `/{$entiteId}/logo`
|
||||
|
||||
### 2. ReceiptService.php (Stockage reçus PDF)
|
||||
**Actuel :** `/entites/{$entiteId}/recus/{$operationId}`
|
||||
**Nouveau :** `/{$entiteId}/recus/{$operationId}`
|
||||
|
||||
### 3. ExportService.php (Export Excel)
|
||||
**Actuel :** `/{$entiteId}/operations/{$operationId}/exports/excel`
|
||||
**Nouveau :** `/{$entiteId}/operations/{$operationId}`
|
||||
|
||||
### 4. ExportService.php (Export JSON)
|
||||
**Actuel :** `/{$entiteId}/operations/{$operationId}/exports/json`
|
||||
**Nouveau :** `/{$entiteId}/operations/{$operationId}` (ou supprimer si non utilisé)
|
||||
|
||||
## 🔄 Plan de migration
|
||||
|
||||
### Étape 1 : Modifier le code
|
||||
1. Mettre à jour tous les chemins dans les contrôleurs et services
|
||||
2. Tester en environnement DEV
|
||||
|
||||
### Étape 2 : Script de migration des fichiers existants
|
||||
Créer un script PHP pour :
|
||||
1. Lister tous les fichiers existants
|
||||
2. Les déplacer vers la nouvelle structure
|
||||
3. Supprimer les anciens dossiers vides
|
||||
|
||||
### Étape 3 : Déploiement
|
||||
1. Exécuter le script de migration sur REC
|
||||
2. Vérifier le bon fonctionnement
|
||||
3. Exécuter sur PROD
|
||||
|
||||
## 🚀 Avantages de la nouvelle structure
|
||||
- **Plus simple** : Chemins plus courts et plus logiques
|
||||
- **Plus cohérent** : Une seule structure pour toutes les entités
|
||||
- **Plus maintenable** : Facile de naviguer et comprendre
|
||||
- **Performance** : Moins de niveaux de dossiers à parcourir
|
||||
|
||||
## ⚠️ Points d'attention
|
||||
- Vérifier les permissions (nginx:nobody 775/664)
|
||||
- S'assurer que les anciens fichiers sont bien migrés
|
||||
- Mettre à jour la documentation
|
||||
- Informer l'équipe du changement
|
||||
19
api/docs/logrotate_email_queue.conf
Normal file
19
api/docs/logrotate_email_queue.conf
Normal file
@@ -0,0 +1,19 @@
|
||||
# Configuration logrotate pour email_queue.log
|
||||
# À placer dans /etc/logrotate.d/geosector-email-queue
|
||||
|
||||
/var/www/geosector/api/logs/email_queue.log {
|
||||
daily # Rotation journalière
|
||||
rotate 30 # Garder 30 jours d'historique
|
||||
compress # Compresser les anciens logs
|
||||
delaycompress # Compresser le jour suivant
|
||||
missingok # Pas d'erreur si le fichier n'existe pas
|
||||
notifempty # Ne pas tourner si vide
|
||||
create 664 www-data www-data # Créer nouveau fichier avec permissions
|
||||
dateext # Ajouter la date au nom du fichier
|
||||
dateformat -%Y%m%d # Format de date YYYYMMDD
|
||||
maxsize 100M # Rotation si dépasse 100MB même avant la fin du jour
|
||||
postrotate
|
||||
# Optionnel : envoyer un signal au process si nécessaire
|
||||
# /usr/bin/killall -SIGUSR1 php 2>/dev/null || true
|
||||
endscript
|
||||
}
|
||||
93
api/docs/recu_19500582.pdf
Normal file
93
api/docs/recu_19500582.pdf
Normal file
@@ -0,0 +1,93 @@
|
||||
%PDF-1.4
|
||||
%âãÏÓ
|
||||
1 0 obj
|
||||
<< /Type /Catalog /Pages 2 0 R >>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
|
||||
endobj
|
||||
3 0 obj
|
||||
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>
|
||||
endobj
|
||||
4 0 obj
|
||||
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>
|
||||
endobj
|
||||
5 0 obj
|
||||
<< /Length 599 >>
|
||||
stream
|
||||
BT
|
||||
/F1 14 Tf
|
||||
217 792 Td
|
||||
(AMICALE TEST DEV PIERRE) Tj
|
||||
ET
|
||||
BT
|
||||
/F1 11 Tf
|
||||
281 770 Td
|
||||
(RENNES) Tj
|
||||
ET
|
||||
BT
|
||||
/F1 14 Tf
|
||||
213.5 726 Td
|
||||
(RECU FISCAL N 19500582) Tj
|
||||
ET
|
||||
BT
|
||||
/F1 9 Tf
|
||||
263.75 704 Td
|
||||
(Article 200 CGI) Tj
|
||||
ET
|
||||
BT
|
||||
/F1 12 Tf
|
||||
50 657 Td
|
||||
(Dugues) Tj
|
||||
ET
|
||||
BT
|
||||
/F1 11 Tf
|
||||
50 637 Td
|
||||
(8 le Petit Monthelon Acigne) Tj
|
||||
ET
|
||||
BT
|
||||
/F1 16 Tf
|
||||
257.5 598 Td
|
||||
(8,00 euros) Tj
|
||||
ET
|
||||
BT
|
||||
/F1 12 Tf
|
||||
267.5 559 Td
|
||||
(20/08/2025) Tj
|
||||
ET
|
||||
BT
|
||||
/F1 10 Tf
|
||||
277.5 529 Td
|
||||
(OPE 2025) Tj
|
||||
ET
|
||||
BT
|
||||
/F1 9 Tf
|
||||
198.5 476 Td
|
||||
(Don ouvrant droit a reduction d'impot de 66%) Tj
|
||||
ET
|
||||
BT
|
||||
/F1 10 Tf
|
||||
50 419 Td
|
||||
(Le 20/08/2025) Tj
|
||||
ET
|
||||
BT
|
||||
/F1 10 Tf
|
||||
50 401 Td
|
||||
(Le President) Tj
|
||||
ET
|
||||
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 6
|
||||
0000000000 65535 f
|
||||
0000000019 00000 n
|
||||
0000000068 00000 n
|
||||
0000000125 00000 n
|
||||
0000000251 00000 n
|
||||
0000000353 00000 n
|
||||
trailer
|
||||
<< /Size 6 /Root 1 0 R >>
|
||||
startxref
|
||||
1003
|
||||
%%EOF
|
||||
75
api/docs/recu_19500586.pdf
Normal file
75
api/docs/recu_19500586.pdf
Normal file
@@ -0,0 +1,75 @@
|
||||
%PDF-1.3
|
||||
1 0 obj
|
||||
<< /Type /Catalog /Pages 2 0 R >>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
|
||||
endobj
|
||||
3 0 obj
|
||||
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>
|
||||
endobj
|
||||
4 0 obj
|
||||
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
|
||||
endobj
|
||||
5 0 obj
|
||||
<< /Length 767 >>
|
||||
stream
|
||||
BT
|
||||
/F1 12 Tf
|
||||
50 750 Td
|
||||
(AMICALE TEST DEV PIERRE) Tj
|
||||
0 -20 Td
|
||||
(17 place hoche 35000 RENNES) Tj
|
||||
/F1 16 Tf
|
||||
0 -40 Td
|
||||
(RECU DE DON N° 19500586) Tj
|
||||
/F1 10 Tf
|
||||
0 -15 Td
|
||||
(Article 200 du Code General des Impots) Tj
|
||||
/F1 12 Tf
|
||||
0 -45 Td
|
||||
(DONATEUR) Tj
|
||||
/F1 11 Tf
|
||||
0 -20 Td
|
||||
(Nom : M. Hermann) Tj
|
||||
0 -15 Td
|
||||
(Adresse : 12 le Petit Monthelon Acigne) Tj
|
||||
0 -15 Td
|
||||
(Email : pierre.vaissaire@gmail.com) Tj
|
||||
0 -30 Td
|
||||
/F1 12 Tf
|
||||
(DETAILS DU DON) Tj
|
||||
/F1 11 Tf
|
||||
0 -20 Td
|
||||
(Date : 19/08/2025) Tj
|
||||
0 -15 Td
|
||||
(Montant : 12,00 EUR) Tj
|
||||
0 -15 Td
|
||||
(Mode de reglement : Espece) Tj
|
||||
0 -15 Td
|
||||
(Campagne : OPE 2025) Tj
|
||||
/F1 9 Tf
|
||||
0 -40 Td
|
||||
(Reduction d'impot egale a 66% du montant verse dans la limite de 20% du revenu imposable) Tj
|
||||
/F1 11 Tf
|
||||
0 -30 Td
|
||||
(Fait a RENNES, le 19/08/2025) Tj
|
||||
0 -20 Td
|
||||
(Le President) Tj
|
||||
ET
|
||||
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 6
|
||||
0000000000 65535 f
|
||||
0000000009 00000 n
|
||||
0000000058 00000 n
|
||||
0000000115 00000 n
|
||||
0000000241 00000 n
|
||||
0000000311 00000 n
|
||||
trailer
|
||||
<< /Size 6 /Root 1 0 R >>
|
||||
startxref
|
||||
1129
|
||||
%%EOF
|
||||
Reference in New Issue
Block a user