chore: initialisation du projet ul_tresorerie et ajout du sous-module documentation Nextcloud
This commit is contained in:
commit
f3ff8f5f74
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
# Exports CoGétise et fichiers de données sensibles
|
||||
*.asp
|
||||
*.csv
|
||||
|
||||
# Archives de packaging
|
||||
*.zip
|
||||
*.tar.gz
|
||||
|
||||
# Dépendances et outils de build éventuels
|
||||
vendor/
|
||||
node_modules/
|
||||
|
||||
# Fichiers système et éditeurs
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Logs et fichiers temporaires
|
||||
*.log
|
||||
tmp/
|
||||
temp/
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "documentation"]
|
||||
path = documentation
|
||||
url = https://github.com/nextcloud/documentation.git
|
||||
82
README.md
Normal file
82
README.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Cotisations-Nextcloud — Trésorerie UL CGT (`ul_tresorerie`)
|
||||
|
||||
Application native **Nextcloud** (versions 33 à 35) conçue pour l'**Union Locale CGT de Foix et de la Haute-Ariège** permettant le suivi rapide des cotisations mensuelles des syndiqués isolés (*INDIVIDUELS UL FOIX HAUTE-ARIEGE* / *Multipros*), l'import depuis CoGétise (`.asp`) et le publipostage intégré.
|
||||
|
||||
---
|
||||
|
||||
## Fonctionnalités principales
|
||||
|
||||
- **Grille annuelle de saisie rapide** : Suivi mois par mois (Janvier à Décembre) des cotisations par adhérent, filtrage en temps réel, édition groupée et totaux mensuels/annuels.
|
||||
- **Système d'alertes à deux niveaux** :
|
||||
- **Niveau 1 (Orange)** : Retard modéré (configurable, par défaut 3 mois impayés).
|
||||
- **Niveau 2 (Rouge)** : Retard critique (configurable, par défaut 6 mois impayés).
|
||||
- **Import CoGétise (`.asp`)** : Importation et synchronisation des syndiqués depuis un fichier d'export tabulé CoGétise avec détection d'encodage (UTF-8 / Windows-1252) et aperçu des créations/mises à jour.
|
||||
- **Publipostage & Reçus fiscaux (66 %)** :
|
||||
- Génération de **reçus fiscaux** (déduction fiscale de 66 % des cotisations syndicales versées sur l'année).
|
||||
- Lettres de **rappel de mois manquants** et de **rappel annuel**.
|
||||
- Modèles personnalisables avec variables dynamiques et impression directe.
|
||||
- **Contrôle d'accès** : Gestion des administrateurs, utilisateurs et groupes Nextcloud autorisés à consulter et modifier la trésorerie.
|
||||
|
||||
---
|
||||
|
||||
## Structure du dépôt
|
||||
|
||||
```text
|
||||
Cotisations-Nextcloud/
|
||||
├── documentation/ # Sous-module Git officiel Nextcloud Documentation
|
||||
├── ul_tresorerie/ # Code source de l'application Nextcloud
|
||||
│ ├── appinfo/ # Métadonnées (info.xml) et routes (routes.php)
|
||||
│ ├── css/ # Feuilles de style de l'interface
|
||||
│ ├── img/ # Logos et bannières UL CGT
|
||||
│ ├── js/ # Logique front-end de l'application
|
||||
│ ├── lib/ # Contrôleurs, Services et Migrations PHP
|
||||
│ └── templates/ # Template principal Nextcloud (main.php)
|
||||
├── .gitignore # Exclusion des exports sensibles (*.asp) et archives (*.zip)
|
||||
├── .gitmodules # Configuration du sous-module de documentation Nextcloud
|
||||
└── README.md # Documentation du projet
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gestion de la documentation officielle Nextcloud
|
||||
|
||||
La documentation Nextcloud est gérée directement depuis le dépôt officiel (`https://github.com/nextcloud/documentation.git`) via un **sous-module Git** situé dans le dossier `documentation/`.
|
||||
|
||||
### Cloner le projet avec la documentation
|
||||
```bash
|
||||
git clone --recurse-submodules <url-du-depot>
|
||||
```
|
||||
|
||||
Si le dépôt a déjà été cloné sans l'option `--recurse-submodules` :
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
### Mettre à jour la documentation depuis le dépôt officiel Nextcloud
|
||||
```bash
|
||||
git submodule update --remote documentation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installation de l'application dans Nextcloud
|
||||
|
||||
### Prérequis
|
||||
- **Nextcloud** : 33 à 35
|
||||
- **PHP** : 8.3 minimum
|
||||
|
||||
### Déploiement
|
||||
1. Copier ou lier le dossier `ul_tresorerie` dans le répertoire `custom_apps/` (ou `apps/`) de votre instance Nextcloud :
|
||||
```bash
|
||||
cp -r ul_tresorerie /var/www/nextcloud/custom_apps/
|
||||
chown -R www-data:www-data /var/www/nextcloud/custom_apps/ul_tresorerie
|
||||
```
|
||||
2. Activer l'application via `occ` ou depuis l'interface d'administration Nextcloud :
|
||||
```bash
|
||||
sudo -u www-data php /var/www/nextcloud/occ app:enable ul_tresorerie
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Licence
|
||||
Distribué sous licence **AGPL-3.0-or-later**.
|
||||
1
documentation
Submodule
1
documentation
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit d89f524273c99edd5e1034422fa33228fde47646
|
||||
27
ul_tresorerie/appinfo/info.xml
Normal file
27
ul_tresorerie/appinfo/info.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0"?>
|
||||
<info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
|
||||
<id>ul_tresorerie</id>
|
||||
<name>Trésorerie UL CGT</name>
|
||||
<summary>Suivi des cotisations mensuelles et publipostage — UL CGT Foix Haute-Ariège</summary>
|
||||
<description><![CDATA[Application native de suivi rapide des cotisations mensuelles des syndiqués isolés (INDIVIDUELS UL FOIX HAUTE-ARIEGE / Multipros), avec système d'alertes à deux niveaux, import CoGétise (.asp) et outil de publipostage intégré (reçus fiscaux 66 %, rappels de mois manquants, rappels annuels).]]></description>
|
||||
<version>1.0.0</version>
|
||||
<licence>AGPL-3.0-or-later</licence>
|
||||
<author>Union Locale CGT de Foix et de la Haute-Ariège</author>
|
||||
<namespace>ULTresorerie</namespace>
|
||||
<category>organization</category>
|
||||
<category>office</category>
|
||||
<bugs>https://github.com/ul-cgt-foix/ul_tresorerie/issues</bugs>
|
||||
<dependencies>
|
||||
<php min-version="8.3"/>
|
||||
<nextcloud min-version="33" max-version="35"/>
|
||||
</dependencies>
|
||||
<navigations>
|
||||
<navigation>
|
||||
<name>Trésorerie UL CGT</name>
|
||||
<route>ul_tresorerie.page.index</route>
|
||||
<icon>app.svg</icon>
|
||||
<order>10</order>
|
||||
</navigation>
|
||||
</navigations>
|
||||
</info>
|
||||
21
ul_tresorerie/appinfo/routes.php
Normal file
21
ul_tresorerie/appinfo/routes.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'routes' => [
|
||||
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
|
||||
['name' => 'api#getState', 'url' => '/api/state', 'verb' => 'GET'],
|
||||
['name' => 'api#createMember', 'url' => '/api/members', 'verb' => 'POST'],
|
||||
['name' => 'api#updateMember', 'url' => '/api/members/{id}', 'verb' => 'PUT'],
|
||||
['name' => 'api#deleteMember', 'url' => '/api/members/{id}', 'verb' => 'DELETE'],
|
||||
['name' => 'api#importAsp', 'url' => '/api/import', 'verb' => 'POST'],
|
||||
['name' => 'api#saveDue', 'url' => '/api/dues', 'verb' => 'POST'],
|
||||
['name' => 'api#deleteDue', 'url' => '/api/dues/delete', 'verb' => 'POST'],
|
||||
['name' => 'api#saveTemplate', 'url' => '/api/templates/{slug}', 'verb' => 'PUT'],
|
||||
['name' => 'api#resetTemplates', 'url' => '/api/templates/reset', 'verb' => 'POST'],
|
||||
['name' => 'api#sendEmails', 'url' => '/api/mailing/send', 'verb' => 'POST'],
|
||||
['name' => 'api#getPermissions', 'url' => '/api/permissions', 'verb' => 'GET'],
|
||||
['name' => 'api#savePermissions', 'url' => '/api/permissions', 'verb' => 'PUT'],
|
||||
],
|
||||
];
|
||||
1823
ul_tresorerie/css/app_v2.css
Normal file
1823
ul_tresorerie/css/app_v2.css
Normal file
File diff suppressed because it is too large
Load Diff
1823
ul_tresorerie/css/style.css
Normal file
1823
ul_tresorerie/css/style.css
Normal file
File diff suppressed because it is too large
Load Diff
187
ul_tresorerie/img/app.svg
Normal file
187
ul_tresorerie/img/app.svg
Normal file
@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24" height="31" viewBox="0 0 612 792" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F03E3E;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#FAD43A;}
|
||||
.st3{fill:#FAB005;}
|
||||
</style>
|
||||
<rect x="9.8" y="6.3" class="st0" width="591.6" height="779.5"/>
|
||||
<path class="st1" d="M9.8,533.9c9.8-53.7,47.7-94.8,113.3-94.8c25.7,0,69.4,7.1,98.6,55.3l-50.4,28.7c-4-8.4-8.4-16.4-19.5-24.3
|
||||
c-6.6-4.4-15-8-25.6-8c-36.7,0-49.1,42-49.1,69.9c0,30.5,15,69,51.3,69c3.5,0,20.3,0,34.5-15.9c5.7-6.6,8-11.9,10.2-18.6l51.3,26.5
|
||||
c-8.4,13.3-17.7,27.9-36.3,41.1c-17.7,12.4-40.2,20.8-66.3,20.8c-44.2,0-114.5-26.5-114.5-120.7C7.2,552.9,8.1,543.2,9.8,533.9"/>
|
||||
<path class="st1" d="M380.4,645.6c-13.3,8.8-29.6,19.5-61,19.5c-19,0-37.6-4.9-52.6-15c-31-19.9-42.9-55.7-42.9-95.5
|
||||
c0-46.4,21.7-113.2,98.2-113.2c35.4,0,51.7,15.5,61.9,25.2v-21.7h63.7v222c-0.9,25.2-1.8,54.8-35.4,74.3
|
||||
c-23.4,14.2-57.9,17.7-78.7,17.7c-46,0-76.1-15.9-88.4-28.7c-18.1-18.1-17.7-41.6-17.7-53.9h70.3c0.9,10.2,1.8,24.8,19.9,33.2
|
||||
c4.4,2.2,11.1,4.4,21.7,4.4c41.1,0,41.1-24.3,41.1-43.3V645.6z M294.2,554c0,19.5,1.3,59.7,43.3,59.7c34.5,0,42.5-27,42.5-59.7
|
||||
c0-11.1-0.9-34-8.4-45.1c-5.3-8.4-17.7-17.7-37.1-16.8C302.2,493.5,294.2,521.3,294.2,554z"/>
|
||||
<path class="st1" d="M494.9,375.8h67.7v69h42.2v47.3h-42.6V596c-0.4,14.2-0.4,26.1,17.7,26.1l24.3-0.9v53.1
|
||||
c-14.6,2.2-23.4,3.5-35.8,3.5c-23.4,0-49.1-1.3-63.7-22.6c-9.3-13.7-9.7-30.1-9.7-52.2V491.7h-33.6v-46.9h33.6V375.8z"/>
|
||||
<g>
|
||||
<path class="st2" d="M252.5,260C252.5,260,252.5,260.1,252.5,260C252.7,259.9,252.6,259.9,252.5,260z"/>
|
||||
<path class="st2" d="M250.8,262.1C250.8,262.4,251.2,262.1,250.8,262.1L250.8,262.1z"/>
|
||||
<path class="st2" d="M41.1,417.7C41.1,417.7,41.1,417.7,41.1,417.7C41,417.5,41,417.5,41.1,417.7z"/>
|
||||
<path class="st2" d="M452.6,424.5L452.6,424.5C452.5,424.5,452.5,424.5,452.6,424.5z"/>
|
||||
<path class="st2" d="M453.2,424.5C453.2,424.5,453.2,424.5,453.2,424.5C453.3,424.5,453.3,424.5,453.2,424.5z"/>
|
||||
<path class="st3" d="M601.4,390.8c-0.7,0-1.4,0.1-2.1,0.2c-0.2,0-0.5,0.9-0.6,1c-1,0.4-5,0.9-5.5,1c-2.5,0.2-4.7-1-7.7-1
|
||||
c0.2,1.4,0,2.1,1,3.5c-0.5-0.1-1.5,0.2-1.9,0c-0.6-0.3-0.7-2.4-2.9-1c-0.3,0.2-0.3,1.2-0.6,1.3c-0.7,0.2,0-0.1-0.3-0.3l-7.1-1
|
||||
l-0.6-0.6c-0.6,0.1-0.7,0.2-1,0.3c-1.2,0.4-0.3,0.6-1,1.3h-0.6c-0.2-0.1,0-1.3,0-1.3c-1.1-0.3-1.9,1.1-2.6,1.3
|
||||
c-1.1,0.3-2.3-0.5-4.2-0.3c-2.5,0.2-6,1.2-8,2.3c-0.5-0.4-1-0.8-1.6-1.3c-0.1,0.1-2.3,2.9-2.3,2.9c-0.6,0.3-1.4-0.5-0.6-0.3
|
||||
c0-0.2,0.5-1.9,0-1.9c-0.7,0-0.7,0.9-1,1.3c-1.2-0.6-2.5-1.2-3.9,0c-0.2,0.1-0.1,0.9-0.3,1c-1.4,0.4,0.2-1.7-1.6-1.3
|
||||
c-0.9,0.2-1.8,1.6-2.6,1.9c-1.3,0.6-2.4,0.6-3.9,1c0-0.1,0.6-0.9,0.3-1c-0.3-0.1-0.7,0.1-1,0c-0.6,0.7-0.7,1.3-1.3,2.2
|
||||
c-0.7-0.1-1.6,0-2.3,0c0-0.6,0-1.3,0-1.9c1.1,0-0.7-0.5-1-0.6c-0.7,1.5-0.6,1.3-0.3,2.9c-0.2,0-0.8,0.1-1,0c0,0-1.8,0.3-2.9,0.3
|
||||
c1.1-0.7,0.4-0.5,0.6-1.3l1-0.6c-0.1,0-1.1-0.6-1.3-0.6c-0.4,1.3-1,3.4-3.5,3.2c-2.9-0.3,0.2-2.5-3.9-0.6v1.9
|
||||
c-0.1,0-4.5-0.6-4.5-0.6c-0.3-0.3-0.2-0.9-0.6-1.3c-0.6,0.4-1.5,2-2.3,2.3c-2.4,0.7-3.8-0.6-5.8,0c-0.1,0,0.3,0.5,0.3,0.6
|
||||
c-0.9,0.3-1.7,0.9-2.9,0.6c-0.5-0.1,0-1.3,0-1.3c-0.5-0.2-3.1-0.3-3.2-0.3c0,0.1,0,0.2,0,0.3c0,0-1.8,0.8-2.9,1l-0.3,1.6
|
||||
c-0.9,0.5-2.6-1-3.9-1c-0.5,0-1,1-1,1c-4.2,1.2-7.4,0.8-11.6,1.3c-0.9,0.1-3.8,1.5-4.8,1.6c-2,0.2-4.4,0.4-5.5,0
|
||||
c-0.7-0.3-0.4-1.4-2.3-1.3c-1.4,0.1-2.6,1.7-3.5,1.9c-1.5,0.4-3.9,0.6-4.8,0.6c-2,0.2-3-0.5-4.2-1.3c-0.1,0.1-0.3,1.6-0.3,1.6
|
||||
c-0.8,0.2-1.8-1.2-3.2-1c0,0-0.8,0.9-1,1c-1.5,0.4-4.2,0.6-5.5,0.6c-2.9,0-5.7-0.6-8.7-0.3c-3.4,0.4-5.1,1.7-8.4,1.9
|
||||
c-0.9,0-3.9-0.8-5.5-0.6c-1.1,0.1-2.2,1.8-3.2,1.9c-2.3,0.4-1.8-1.3-3.9-1c-0.4,0-0.2,0.9-0.3,1.3c-2.5-0.2-7.3,1.3-8.7-1
|
||||
c-0.2,0.1-0.3,1.1,0,1.3c-2.7,0-4.6,0.8-6.8,0.6c-1.5-0.1-3.3-0.1-5.5-0.6c-0.5-0.1-1.1-1.2-1.3-1.3c-2.1-0.5-4.6,0.9-6.8,1
|
||||
c-0.1,0-3.2,0-3.2,0s-0.5-1.5-1-1.6c-1.1-0.3-2.3,0.7-3.2,0.6c-0.5,0-1.1-0.9-1.3-1c-3.4-0.6-2.2,1.8-4.5,1.6c0,0-3.7-1.6-1.9-1.3
|
||||
c0-0.1-0.6-0.8-0.3-1c-1.3,0.2-1.9,0.1-3.2,0.3c0,0.1,0.3,1.6,0.3,1.6c-0.8,0.1-2.6-1.3-2.6-1.3c-1.9-0.4-0.3,0.7-1.9,0.6
|
||||
c-2-0.1-3.8-2-5.1-2.9c-0.6,0.4-0.8,0.5-1.6,1c-0.5-1.2-1.1-2.2-1.6-3.2c-2,0.5-3.5-0.4-5.8-0.6c-0.1-0.4,0-1.2-0.3-1.3
|
||||
c-1.4-0.3-1.1,0.9-2.6,0.3c-0.4-0.2-1.3-1.8-1.6-1.9c-0.6-0.2-1.3,0.5-2.3,0.3c-1-0.2-0.5-1.2-0.6-1.3c-0.4-0.2-1.2,0.2-1.6,0
|
||||
c-0.1-0.1,0-1.2-0.3-1.3c-0.3-0.1-1.5,0.8-2.6-0.6c-0.2-0.3-0.2-0.5-0.3-1c0,0-0.3-0.8-0.6-1.3c0.2,0.4,0.6-0.8,0-1.3
|
||||
c-1-0.9-1.7-0.3-2.9-0.3c0.3-0.2-0.3-0.8-0.3-1c0.3-0.1,0.6,0,1,0c-0.5-2.3-1.2-2.1-1.6-2.9c-1.1-2.3-1.4-5.8-1.6-8
|
||||
c-0.6,0-1.3-0.2-1.9-0.3c0.4-1.6,0.6-2,1.6-2.9c-0.5-1.5-2.7-4.4-2.3-6.1c0.2-0.9,1-0.8,1.6-1.3c-1-2.2-1-2.8,0.6-3.9
|
||||
c-0.1-0.3-0.8-1.5-0.6-1.9c0-0.2,1.5-1.3,1.6-1.6c0.3-1.1-0.5-1.3-0.3-2.2c0.1-0.5,1-2.1,1.3-2.9c-0.3-0.3-2.3-2.9-2.3-2.9
|
||||
c-0.6-0.2-1.7,0.3-2.2,0c-0.1,0,0-0.8,0-1c-0.5-1.3-2.2-1.1-3.5-1c-0.3-0.4-0.5-1.8-1-1.9c-0.9-0.3-0.3,2.8-0.3,2.9
|
||||
c-2.5,0.4-3.7,0.6-5.2,1.9c-2.5,2.4-2.7,8.3-3.9,11.7l-2.9,3.4c-0.2,0.1-1,0.4-1.3,0.6c0.3,1.9,0.4,3-1,3.9c-2.9,1.8-2.5-0.8-2.9-1
|
||||
c-0.4-0.1-0.9,0.2-1.3,0c0,0-0.5,1-0.6,1.3c0.5,0.4,0.7,0.1,1.3,0.3c-0.1,0.2-0.3,1-0.3,1c-0.3-0.2-1,0.1-1.3,0
|
||||
c-0.1-0.1-0.2-1-0.3-1.3c-0.2,0.1-1.7,0.6-1.3,0.6c-0.6,0.6-1.6,2.2-2.3,2.6c-0.7,0.4-0.5-0.5-1.3-0.3c-1.5,0.3-2.5,3.5-2.9,5.1
|
||||
c-0.5-0.2-1.4,0.2-1.9,0c-0.1,0,0-0.9,0-1c0,0-0.1-1.7-2.3-1.3c-0.3,0-1.3,1.2-1.6,1.3c-0.8,0.3-2.2-0.1-3.2,0.3
|
||||
c-0.9,0.4-2,2.5-2.9,3.2c-1.5-2.8-2.2-1.5-3.9,0c-3.5-2.8-9.7,1.1-13.5-1.6c-1.1-0.7-1-4.2-1-4.2c0-0.2,0.1-1.3-0.3-1
|
||||
c1.4-1.2,1.2-2.3,2.2-3.9c0.3-0.5,1.6-0.8,1.9-1.3c0.5-0.8,0.1-1.8,0.6-2.6c0.2-0.3,1.2-0.3,1.3-0.6c0.2-0.5-0.2-1.4,0-1.9
|
||||
c0.3,0,1.1,0.4,1.6,0.6c1.4-2,2.9-2.4,4.8-3.9c1.2-0.9,2.1-2.6,3.2-3.5c1.7-1.5,3.8-2.3,5.8-3.5c1-0.7,1.9-2.3,2.9-2.9
|
||||
c0.3-0.2,0.9,0.1,1.3,0c1.3-0.5,2.7-2.3,3.9-2.9c3.2-1.7,6.1-2,10-3.2c2.1-0.7,6.2-2.8,8.7-3.2c3-0.6,6.3-0.2,8.4-0.6
|
||||
c0.8-0.2,1.6-1.2,2.9-1c0.5,0.1,0.7,1.2,1,1.3c0.5,0.1,1.6-0.7,2.3-0.6c1.2,0.1,2.8,1.7,3.9,1.9c0.7,0.2,1.6-0.4,2.6,0
|
||||
c0,0.1,0,0.2,0,0.3c0.9,0.5,1.6,0.8,2.6,1.3c0,0.1-0.1,0.9,0,1c1.7,0.5,2.7-0.7,5.5,0.3c-0.2-0.1-1.1-1.2-1.3-1.3
|
||||
c-0.3,0.5-0.3-0.6-0.3-0.6c0.3-0.3,1.5-1,1.6-1.3c0.3-1.2-0.6-4.3-1-5.8c-0.5-1.9-3.5-4.2-3.5-4.2c0-0.2,0.1-1.2,0-1.3
|
||||
c-0.3-0.3-1.3,0.2-1.6,0c-0.5-0.2,0-1-0.3-1.3c-0.6-0.5-4.6-3.1-5.1-3.2c-2.5-0.8-4.2,0.2-7.1,0c-1.1-0.1-2.6-1.3-3.9-1.3
|
||||
c-0.6,0-1.8,0.9-2.3,1c-3,0.3-5.9,0-9.3,1c-0.5,0.1-1.3,1.2-1.6,1.3c-1.5,0.5-2.8-0.2-4.2,0.3c-0.7,0.3-1.2,1.8-0.3,1.9
|
||||
c-2.7,0.9-3.9,2-6.8,2.9c-0.1-0.5,0.1-1.1-0.3-1.3h-0.6c-0.4,1-1.8,4-2.9,4.2c-0.3,0.1-3.9-1.4-4.5-1.6c0.4,0.8,0.5,1.2,1,1.9
|
||||
c-2.7,0.9-5.9,1.4-7.7,0.3c-0.3,2.7,0.3,3.8-0.3,6.4c-2.6-0.5-6.4,3-7.1-1.6c-0.3,1.7-0.6,2-2.6,2.6c-0.2,2.7-0.2,6.7-2.9,7.7
|
||||
c-0.3,0.1-1-0.2-1.3,0c-1.6,1.4,0.4,1.7-2.3,1.3c-0.1,0.3-0.1,0.9-0.3,1c0,0,0.8,0.6,0.6,0.6c-1.7,0-1.1-1.3-1.9-1.9
|
||||
c-0.4,0.2-1.2,0-1.6,0l-0.3,1.6c-1,0.4-6.9-0.2-7.7-0.3c-2.1-0.3-6.4,0.1-8.7,1c-0.2,0.1-0.5,0.9-0.6,1c-0.9,0.2-1.2-0.4-2.6,0
|
||||
c0,0.1,0,0.2,0,0.3c-1,0.5-3.9,0.8-4.5,1.6c-0.2,0.3-0.1,1.1-0.3,1.3c-0.8,0.6-2,0.4-2.9,1c0,0-0.3,1-0.3,1
|
||||
c-2.2,1.3-5.9,1.7-7.7,3.2c-0.4,0.4-0.7,1.4-1,1.9c-3.5-0.9-2.4,0.5-4.5,1.3c-1.4,0.5-2.9,0.1-4.5,1c-0.2,0.1-0.5,0.9-0.6,1
|
||||
c-2.2,0.6-3.1,0.1-5.5,1l0.3,2.6l-1-0.3c0,0-0.3-1.8-0.3-1.9c0,0-1,0.3-1,0.3c-0.2,2.4,0.4,2.5-1.9,1.9c0.1,0.2,0.5,1.2,0.3,1.3
|
||||
c-1.6,0.5-3.8-0.7-4.5-0.6c-0.6,0-0.6,0.8-1,1c-0.9,0.3-2-0.2-2.9,0c-0.4,0.1-1.7,1.2-2.2,1.3c-0.7,0.1-1.2-0.5-2.3-0.3
|
||||
c-2.5,0.5-5.6,3-8.4,3.5c-1.5,0.3-3.1-0.4-5.2,0c-1.9,0.4-4.8,2.2-6.4,2.3c-1,0-1.1-0.8-2.3-0.6c-0.1,0-0.8,0.9-1,1
|
||||
c-1.1,0.3-6.2,0.1-7.1,0c-0.2,0-1.1-1-1.3-1c-1.9-0.2-3,1-4.8,1c-0.3,0-1.6-0.9-1.9-1c-2.5-0.4-3.3,0.1-6.1-0.6c0-0.1,0.1-1,0-1
|
||||
c-0.9-0.3-2.3,0.2-3.2,0c-2.2-0.4-4-2.6-5.5-3.9c-0.4-0.3-1.4-0.5-1.6-1c-0.5-0.9,0.1-1.9-0.3-2.9c-0.3-0.7-2.1-2-2.6-3.2
|
||||
c0.5-0.4,0-1.6,0-1.6c0.3,0,0.3-3.2,0.3-3.2c1.3-2.8,0.2-4.4,2.6-5.5c-0.4-1.6-0.5-1.7,0-3.5c-0.4,0.1-0.5,0.5-1.3,0.3
|
||||
c0.3-1,2.5-5.9,3.2-6.4c0.4-0.3,1.5,0,1.9-0.3l0.3-1.6c3-1,2.8-2.6,5.1-4.5c0.6-0.5,1.3-0.1,1.9-0.6c0.6-0.5,0.6-1.8,1.3-2.3
|
||||
c1.1-0.7,2.3-0.4,3.2-1c1.1-0.7,1-2,1.9-2.9c1-1,3-1.1,4.5-1.6c0-0.7-0.2-1.6,0-2.3c1.2-0.4,2.9-0.4,3.2-0.6c0.1-0.1,0-1.1,0-1.3
|
||||
c2-1.1,3.1-1.9,5.1-2.9c-0.1-0.2-0.3-1-0.3-1c1-1.3,3.2-1,1.6-2.3c-0.4-0.3-0.3-1-0.3-1c1.6-0.5,2.5-0.7,3.9-1.3
|
||||
c0-0.1-0.4-0.6-0.3-0.6c1.3-0.5,2.7,0.3,4.2-1c0.9-0.7,1.3-2.3,2.3-2.9c0.3-0.2,1.1-0.1,1.3-0.3c0.5-0.6,0-1.5,0.6-1.9
|
||||
c1.6-1.2,3.5-1.2,5.1-2.3c1.1-0.7,2-1.8,3.2-2.6c-0.1-0.2-0.3-1-0.3-1c0.8-0.8,1.8-0.3,2.9-1c0.4-0.2-0.3-0.6-0.3-0.6
|
||||
c1.1-0.8,1.3-1,1.9-2.3c0.8,0,0.8,0.5,1.6,0c0,0-1-1.2,0.3-1.6c3.9-1.3-0.8,3.4-1.6,4.2c2.7-1.3,3.2-2.3,6.4-2.6
|
||||
c1.2-2.8,1.6-4,3.5-5.5c-0.1-0.2-0.5-0.4-0.3-0.6c0.1-0.1,1.2-0.5,1.3-0.6c1.1-2.1,0.3-2.3,2.6-3.2c0,0-1.2-0.5-1-0.6
|
||||
c0.4-0.3,1.2,0.2,1.6,0c0.1-0.1,0.3-1.5,0.3-1.6c1.3-0.2,1.7,0.2,2.6-0.6c0.3-0.3,0-1.2,0.3-1.6c0.3-0.4,1.4-0.5,1.6-1
|
||||
c0.9-1.6,0.2-2.6,1.3-3.5c0.8-0.7,1.9-0.3,2.6-1c0.1-0.2-0.1-0.8,0-1c0.3-0.7,3.9-3.4,4.8-4.5c1,0.2,1.2,0.2,2.3,0.3
|
||||
c0.1-0.5-0.2-1.4,0-1.9l0.3-0.3c2-0.7,1.7-2.4,2.6-2.9c0.9-0.5,2.5,0,2.9,0c0.7-1.8,1.2-2.2,2.9-2.9c-1.4-1.1,1.5-4.2,2.3-5.5
|
||||
c0.7,0.5,0.7,0.5,1.6,1l0.6-1.3c0.5-2.1-0.1-1.9,1.9-2.6c-0.3-2.6,1.2-3.5,4.2-4.2c0.1-0.7-0.3-1.6,0-2.3c0.2-0.4,1.3-0.4,1.6-0.6
|
||||
c0.8-0.7,2.3-4.5,2.3-4.5l-0.3-0.6c0.7-0.4,1.1-0.5,1.3-0.5c0-0.2,0.2-0.6,0.7-1.4c0,0,0.7-0.5,1-0.7c0-0.1,0-0.2,0-0.3
|
||||
c0.4-0.4,2.2-0.8,2.3-1c0.7-1.4-0.2-2.2-0.3-2.9c0.2-0.1,0.8,0.1,1,0c0.1-0.1-0.1-0.8,0-1c0.6-1.2,2.6-2,3.5-2.3
|
||||
c-0.2-0.6,0.2-0.6,0.3-1c-0.3-0.2-1.3-1.3-1.3-1.3c1.1,0.3,3.9-6,4.5-8c-0.6-0.3-0.7,0-1-0.6c1.8,0.1,4.2,0.2,3.5-2.6l-0.3-0.6
|
||||
c0.6-0.7,2.8-1.6,3.2-2.3c1.5-2.3,0.4-7.7,0.3-10c-0.1-2,1-6.2,0.6-7.4c-0.2-0.5-1.5-1.3-1.6-1.6c-0.5-1.6,1-2.5-0.3-3.2
|
||||
c-0.5-0.3-1.7-0.6-1.9-0.6c-0.3-2.5-0.3-3-2.3-3.2c0.5-1.3,0.5-1.6,1-2.6c-2.4-0.9-1.5-1.8-2.6-2.6c-0.6-0.4-0.5,0.4-1,0.3
|
||||
c-0.5-0.1-1.4-1.2-1.9-1.3c-2.3-0.5-5.3-0.6-7.7-0.3c-2.9,0.4-7,2.2-10.3,3.5c-0.9,0.4-2.7,2.1-3.5,2.3c-1,0.2-1.4-0.7-2.6-0.3
|
||||
c-1.7,0.5-4.9,6.9-6.8,8c-0.7,0.4-1.3-0.1-1.6,0c-1.2,0.4-1.1,2.1-1.9,2.9c-0.8,0.7-2,0.3-2.9,1c-0.6,0.4-0.8,1.5-1.3,1.9
|
||||
c-0.7,0.7-1.8,0.6-2.6,1.3c-1.1,0.9-1.2,2.9-2.3,3.9c-1,0.8-2.2,0.8-3.2,1.6c-1.9,1.5-3.3,3.9-5.1,5.5c-0.4,0.3-1.4,0.6-1.6,1
|
||||
c-0.2,0.3-0.1,1.6-0.6,2.3c-0.6,0.7-2.9,1.7-3.2,2.6c-0.2,0.4,0.2,1.3,0,1.6c-0.4,0.6-1-0.1-1.6,0.3c-0.2,0.1-0.2,1.4-0.3,1.6
|
||||
c-0.8,1.2-3.2,1.9-4.2,2.9c-0.7,0.7-0.3,1.6-1,2.6c-0.5,0.7-1.2,0.1-1.9,0.6c-0.1,0.1,0.1,0.8,0,1c-0.4,1-3,1.6-3.5,2.6
|
||||
c-0.5,0.8,0,1.2-0.3,1.6c-0.4,0.5-0.5,0.2-1.3,0.3c-0.7,2.2-3.2,3.9-1,5.1c-2.1,0.3-2.8,0.9-4.8,1c-0.6,1.3-0.5,2.5-1.3,3.5
|
||||
c-0.7,0.8-1.4,0.3-2.3,1c-0.9,0.7-0.7,1.9-1.3,2.9c-1.2,1.9-3.7,3.6-4.8,5.5c-0.2,0.3-0.9,3.8-1,3.9c-0.2,0.2-1.3,0-1.6,0
|
||||
c-0.8,1.8-0.6,2.3-2.6,2.6c-0.1,0.6,0.2,0.8,0.3,1.3c-1.4,1-1.8,1.9-2.9,3.2c-0.5,0.6-2.3,1.3-2.6,1.9c-0.5,1.1,0.3,2.2,0.6,3.9
|
||||
c-0.8,0.3-1.4,0.4-2.3,0.6c0,0.1,0.1,0.9,0,1c-0.6,0.2-1.9-0.9-2.6,1c-0.6,1.5,0.1,2.7,0.3,3.9c-3,0.9-3.9,0.4-4.5,3.9
|
||||
c-0.3,0-1.5,0.3-1.9,0.3c0,0.2,0,2.6,0,2.6s-1.5,1.5-1.6,1.6c0,0-1.1,1.8,0.3,1.3c-0.1,0.3-0.5,1.8-0.3,1.9
|
||||
c-3.7-1.9-6.4,6.4-8.4,9.6c-0.1,0.2-1.2,0.3-1.3,0.6c-0.1,0.4,0.2,1.3,0,1.6c-0.3,0.5-1.1,0.2-1.6,0.6c-0.4,0.4-1.7,2.8-1.9,3.2
|
||||
c-1,1.9-0.6,3.3,0.6,4.5c-2-0.6-2.7-0.4-3.9,1.6c-1.7,3-2.6,6-4.2,9.3c-0.6,1.2-2.1,2.4-2.6,3.9c-0.4,1.4,0.1,2.5-0.3,3.9
|
||||
c-0.3,0.7-2.8,2.5-2.9,2.6c1.4-0.5,1.9-0.6,2.9-1.3c0.2-0.2,0.7-0.9,1-1c1.7-0.8,0.2,0.6,1,1c-1.3,0.5-3.7,1.3-1.3,1.9
|
||||
c-1.7,1.1-3,2.2-4.8,3.2c0,0.2,0.1,1.2,0,1.3c-0.2,0.2-1.3,0-1.6,0c-0.4,0.8-0.2,1-0.6,1.6c-0.3-0.1-0.9-0.4-1-0.3
|
||||
c0.4,0.3-0.5,1.2,0.6,1c-0.1,0.1-0.7,0.9-0.6,1c-0.8-0.1-0.9-0.3-1.6-0.6c-0.3,0.7-0.3,0.3-1,0.6c0,0.3,0.2,1.8,0,1.9
|
||||
c-0.3,0.3-0.6-0.3-0.6-0.3c-1,0-4.9,3-5.8,3.5c-0.5,0.3-1.2,0-1.6,0.3c-0.2,0.2-0.1,1.1-0.3,1.3c-1,0.7-2,0.3-2.9,1
|
||||
c-0.5,0.4-1.7,2.3-1.6,2.3v0.6c0.4,0.1,1.9,0.6,1.9,0.6c-1.6,0.7-8.4,3-9,3.9c-0.1,0.1-0.4,1.9-0.6,2.6c-5.2,0.1-2.5,1.6-5.5,2.9
|
||||
c-1.7,0.7-2.1,0.4-3.2,1.3c0.6,1,0.4,1.3,1,2.2c-5.3,0.8-4.8,3.7-8.4,4.2c0.4,1.3,0.2,1.3,0.6,2.6c-0.7,0.3-1.3,0-1.9,0.3
|
||||
c-0.3,0.1-0.8,1.5-1,1.6c-0.3,0.1-4-0.7-2.2,0.6c0.3,0.3,0.3,0,0.3,0c1.4,0.5,2.8,0.5,1,1.6c-1.6,1-3.8,0.9-5.5,1.6
|
||||
c3.8,2.2-1.7,1.6-1.9,1.6c-0.1,0-0.2,0-0.3,0c-1,0.4-1.6,2.6-2.6,3.2c-0.1,0-4.6,2.2-4.8,2.2c-0.8,0.2-1.9-0.3-2.6,0
|
||||
c-0.6,0.2,0.5,1.4-0.6,1.9c0.5-0.1,1.1-0.5,1.6-0.6c-1.4,2.8-0.4,3.4-4.5,4.8c0.8,0,0-3.2,0-3.2c-0.4-0.3-0.1-0.5-0.3-1
|
||||
c-0.6,0.2-2.1,1.2-2.6,1.3c-2,0.1-0.5-2.1-3.5,0.6c-2.1,1.9-4.2,4.9-6.4,6.4c0.2,0.5,0.5,0.7,0.3,1.3c-1.7,0.3-1.5,0-1,0.3
|
||||
c0.6,0.3,0,0.8,0.6,1c0,0-0.5,1.1-0.6,1.2c0.2,0.3,0.6,1,0.6,1c0.1,0.6-0.4,0.9-0.6,1.3c2.7,0.2,2.3,1.2,1.3,3.2
|
||||
c-0.9-0.4,0,1.4,0.3,0.6c1.9,0.9,1.6,1.5,0.6,3.5c1,0.9,1.1,0.5,1.9,1.3c0,0-0.2,0.6-0.6,0.3c0,0.1-0.3,1-0.3,1
|
||||
c0.7,0.3,1.6,0.6,1.9,0.6c0,0-0.7,0.7-0.3,1c0.8,0.6,1.8,0.3,2.9,0.6c0.4-0.8,0.5-1.8,0.6-2.3c3.2-0.4,3.6,0,3.9-2.6
|
||||
c0.5,0,0.9,0.3,1,0.3c-0.7-0.3-0.7,1.3,1,1c0.9-0.2,4.9-3.1,5.8-3.9c0,0-0.4-0.6-0.3-0.6c1.2-0.6,1.5,0,2.2-0.6
|
||||
c0.5-0.5-0.1-0.8,0-1c-0.9,0.2-1,0.3-1.9,0.3c0.1-0.3,0.3-1,0.3-1c1.1,0.2,1.7,0.5,2.9,0.3c1.3-0.2,1.4-1.6,2.3-2.3
|
||||
c2.1-1.6,4.8-1.9,6.8-3.5c0.2-0.2,0.1-1.1,0.3-1.3c0.8-0.6,1.8-0.4,2.6-1c1.1-0.8,1.7-2.5,2.9-3.2c0.9-0.6,2.2-0.3,3.2-1
|
||||
c0.7-0.5,1.1-2.4,1.9-2.9c1.2-0.8,2.4-0.7,3.5-1.6c2.3-1.9,2.4-3.4,5.1-4.8c0,0-0.1-1.6,0-1.9c0.9,0.2,0.5,0.6,1.6,0.6
|
||||
c0.2,0-0.6-1.3-0.6-1.3c1.8-0.7,2.3-1.4,3.5-2.3c1.5-1,3.4-0.9,4.8-1.9l1.6-1.3c0.1-0.2-0.2-1.1,0-1.3c0.6-0.5,1.3-0.2,1.9-0.6
|
||||
c1.7-1.2,1.4-2.6,4.2-3.2c-0.1,0,0-1.1,0-1.3c-1,0-1.9,0-2.9,0c0.1-0.2,0.3-1,0.3-1c2-0.3,2.5,0,3.9,0.3c0.3-0.9,1.1-2.2,2.6-2.6
|
||||
c0.4-0.1,1.3,0.2,1.6,0c0.2-0.1-0.5-1.1-0.6-1.3c2-0.8,3-1.4,5.1-2.3c-0.1,0,0,0.9,0,1c0.4-0.4,0.3,0.3,0.3,0.3
|
||||
c-0.3,0.2-1.1,1-1.3,1.3l-0.3,1.3c0.5-0.1,0.8-0.4,1.3-0.6c-0.1,1.1-1.1,6.3-0.3,8.7l1.3,0.3c0,0.7,0.3,2.3,0.3,2.6
|
||||
c0,0.6-0.8,1.2-0.6,1.6c0.2,0.6,1.7,0.8,1.9,1.6c0.4,1.4-0.5,3.2,0,4.5c0.4,1.2,2.7,1,3.9,1.6c1.3,0.7,1.9,2.9,3.2,3.5
|
||||
c0.8,0.4,0.9-0.6,1.6-0.3c0,0,0.3,1,0.3,1c6,0,11.9,2.4,18.3,1.6c1.7-0.2,3.4-1,5.8-1.3c0,0,0.3-1,0.3-1c2.7-0.9,3.6-0.3,6.8-1
|
||||
c0.9-0.2,2.5-1.7,3.2-1.9c0.5-0.1,1.5,0.2,1.9,0c0.3-0.2,1.1-2.1,2.9-2.3c3.6-0.4,6.7,0.9,10,0.3c0.6-0.1,2.9-1.4,3.5-1.3
|
||||
c0.2,0,1.3,1.1,1.6,1.3c0.3-1.1,0.6-1.9,1-2.9c1.6,0.2,3,0.8,5.1,0c0.1,0,0.5-0.9,0.6-1c0.8-0.2,2.2,0.6,3.9,0
|
||||
c0.7-0.3,1.4-1.7,2.3-1.9c0.8-0.3,2.9,0.3,4.5-0.3c0.3-0.1,0.8-1.4,1.3-1.6c1-0.4,5.1,0.7,6.4,0.3c0.4-0.1,1.1-2.1,1.6-2.3
|
||||
c1.2-0.4,4.2,0,5.5-0.3c0.6-0.1,2.4-1.9,2.9-1.9c0.3,0,1.8,0.7,2.6,1c0.2-0.4,0.7-1.8,1-1.9c0.9-0.4,2.3,0.2,3.2,0
|
||||
c0.5-0.1,1.4-1.1,1.9-1.3c0.9-0.3,1.3,0.5,1.9,0.3c0,0,0-0.9,0-1c1.3-0.4,3.1-0.3,4.8-0.6c0,0,0.8-0.9,1-1c1.7-0.6,2.2,0.2,3.9-0.3
|
||||
c0.9-0.3,2-2.4,2.9-2.6c1.7-0.4,2.9,0.5,5.1,0c2.8,5.1,0.4,9.1,3.9,13.2c1,1.2,2.6,1,3.5,1.6c0.4,0.2,0.2,0.8,0.3,1.3
|
||||
c2.2,0.6,3.7,1.9,5.5,2.3l7.4,1.9c1.2,0.3,1.1-1.5,2.3-1.6c0.7-0.1-0.4,0.8,0.3,1c1,0.3,1.8-0.2,2.9-0.3c0,0.1-0.7,0.8-0.3,1h0.6
|
||||
l2.6-0.3l0.6-1.6c2.2-0.1,6.5,0.5,9.7,0c0.6-0.1,3-1.2,3.5-1.3c1.3-0.2,3.1,0.3,3.9,0.3c1,0,3.4-1.1,4.5-1.3
|
||||
c0.9-0.2,0.9,0.5,1.6,0.3c0.1,0,0-0.9,0-1c4.6-0.6,2.8-1.9,6.1-3.9c0.8,1.1,1.7,2.3,3.5,1.6c0.6-0.2,0.6-0.9,1-1.3
|
||||
c-0.9-0.1-1.1,0-1.6,0c1.9-0.8,3.8-2.7,6.8-2.9c-1.3-0.8-1-1.2-0.6-2.9c0.3,0,1.1,0.1,1.3,0c0,0.9,0.1,1.9,0,3.5c-0.1,0-1,0-1,0
|
||||
c1.5,1.4-1.3,2.8,1.3,4.2c-0.4,0.7-0.8,1.3-1.3,2.2c0.4,0.2,1.2,0,1.6,0c1.3,2.3-0.1,0.8,2.3,1.9c0,0-0.4,0.6-0.3,0.6
|
||||
c0.6,0.2-1.1-0.1-1.9-0.3c0.2,0.3,0.6,1.3,0.6,1.3c1.2,0.1,3.2-0.1,3.5,0l-0.6,1.7c0,0,2.7,3.1,2.5,3.2c-0.6,0.2-1.2,0.2-1.6,0.3
|
||||
c1.9,0.4,1.9,0.9,1.6,2.6c0.7-0.4,1.4-0.4,1.9-0.6c0,0,0-0.9,0-1c-2.3-2.2,0.7-0.5,1.3,1.9c0.3,0.1,1.3,0.1,1.6,0.3
|
||||
c0.1,0.1-0.1,0.8,0,1c0.2,0.6,2.6,2,3.2,2.3c1,0.4,2.3,0,3.2,0.6c0.1,0.1,0,1.1,0,1.3c1,0.3,1.4,0.3,2.6,0.6c0,0-0.4,0.6-0.3,0.6
|
||||
c1.2,0.2,0.8-1.1,2.9-0.6c0.2,0-1,0.6-1,0.6c2.8,1.9,3,0.9,6.1,0.3c-2.2,0.6-0.4,1.4,1,1.9c0.8,0.3,2.3,0.4,2.9,1
|
||||
c0.1,0.1,0.3,1.3,0.3,1.3c2.4,0.9,3.6-1.1,5.5-0.3c0,0-0.6,0.9-0.6,1c0.4,0.3,0.6,0.6,1,1c0.5-0.4,0.9-1,1.3-1.3l5.6,2l8.3,0.5
|
||||
c0.1-0.3,0.3-1.1,0.6-1.3c0.5,0.5,0.3,0.9,0.6,1.6c3.1,0.2,3.5,0.5,6.4,0.6c2,0.1,4.6-0.6,6.4,0c0,0,0.6,1.3,0.6,1.3
|
||||
c0.2,0.1,0.6-0.6,2.6,0c0.1-0.4,0-1.2,0.3-1.3c0,0,2.2,0.6,3.2,0.6c-0.4-0.1,1.1-0.4,1.3-0.3v0.6c2.3-0.2,3.4-0.5,4.8-1
|
||||
c0.5,1.1,0.7,1.5,1,2.3c1.2-0.3,2.8-0.4,4.2-1c0,0,0.7-1.2,1.6-1.3c0.8-0.1,2.5,1.2,3.2,1.3c1.5,0.2,4.9-0.9,7.1-0.6
|
||||
c0.3,0,1.4,0.8,1.9,0.6c0.5-0.1,0.6-1.2,1.3-1.3c0.3,0,3.2,1.3,3.5,1.3c0.2,0,1.1-1.6,1.3-1.6c1.6-0.4,1.4,1.1,2.9,0.6
|
||||
c0.4-0.1-0.3-1.3-0.3-1.3c0.3-0.1,0.9,0,1-0.3c1,0.3,1.9,1.5,3.5,1.6c1.4,0.1,2.7-0.9,3.5-1c0.7-0.1,2.7,0.6,2.9,0.6
|
||||
c0.9,0,2.8-0.9,3.5-1c0.9,0,2.5,0.7,3.9,0.6c1.4,0,4.3-0.5,5.8-0.6l0.3-1c0.2,0,1.2,0.5,1.6,0.6c0.1,0,0.5,0,0.7,0
|
||||
c2.9-0.4,4.4-1,7.4-0.6l0.3-1.3c0.8-0.1,1.4,1.5,2.6,1.3c0.3-0.1-0.3-1.3-0.3-1.3c2.9,0.8,3.5,0.3,5.1-1.3c0.4,0.2,1.2-0.2,1.6,0
|
||||
c0.1,0,0,1.1,0,1.3c0,0,1,0.3,1,0.3c0.3-0.2,0.5-1.4,1.3-1.6l4.2,0.3c0.3,0,0.8,0.7,1.6,0.6l0.6-1c0.6-0.2,3,0,4.5,0
|
||||
c0-0.2-0.1-1.6,0-1.6c0.5-0.2,1.3,0.3,1.3,0.3s0.4,1.1,1.9,1c2.2-0.2,3.4-1.3,5.8-1c-0.4-0.5-0.8-1.1-1-1.6
|
||||
c0.2-0.1,0.5,0.1,0.3-0.3c0.5,0.2,1.4,0,1.9,0c0,0.2-0.1,1.5,0,1.6c0.3-0.2,1,0.2,1.3,0c0.1-0.1,0-1.1,0-1.3c1.3,0.4,2.8,1,3.5,1
|
||||
c1.9-0.2,1.9-1.7,2.9-1.9c2.1-0.4-0.2,1,2.9-0.6c2.6,1.7,2.5,0.6,4.8,0.3c1.5-0.1,2.3,0.8,4.2,0.6c3.1-0.3,3.9-1.3,7.4-1.6v-1
|
||||
c1.3-0.3,1.1,0.8,1.9,0.6l0.3-1c2.5-0.2,2.8,0,4.2,0.6c0.1-0.2,0.5-1.2,0.6-1.3c3.1-0.8,5.5,0.6,7.1,0.3v-0.6c0.7,0,1.8,0,2.6,0
|
||||
l0.6-1.3c1.3-0.3,1.4,0.9,1.6,1c0.6,0.1,3.7-0.2,4.8-0.3c2.9-0.4,8.7-1.1,11.3-2.3c0.3-0.2,0.8-1.5,1-1.6c1.5-0.5,3,0.8,3.9,1.3
|
||||
c-0.3-1.5-0.7-2,1.9-2.6c0,0.1,0.4,2.2,0.3,1.9c0.6,0,1.1,0.7,2.6,0.3c0.2-0.1-0.6-0.6-0.6-0.6c0.8-0.2,1.1-0.2,1.6-0.3
|
||||
c-0.2-0.4-0.6-1.3-0.6-1.3c2.3-0.8,2.9,1.2,3.2,1.3c0.6,0.2,1.6,0,2.3,0c-0.2-0.1,0.5-0.3,0-0.6c-2.7-2,3.3-0.6,5.1-0.6
|
||||
c-0.7-0.6-0.4-0.5-0.6-1.3c0.5,0,1.2,0.2,1.6,0c0,0.3-0.2,1.8,0,1.9c0.2-0.1,0.7,0,1,0l-0.3-2.3c0,0,1.3,1,1.6,1.6
|
||||
c-0.6,0.1-0.2,1,0.6,0.6c1.3-0.5,0.8-0.9,2.6,0c0.9-1.3,1.4-2,1.9-2.9c0.3,0.6,3,2.6,5.8,1.6c0,0,2.4-2.8,2.6-2.9
|
||||
c2.7-0.9,5.4,0.1,7.7-1c0.5,0.7,0.6,1.3,1,1.9L601.4,390.8z M151.8,326.3c0.3-0.9,0.5-1.7,0.6-3.2l1-0.3c0,0,1.1,0.5,1.6,0.3
|
||||
c0.1-0.1-0.1-0.8,0-1c0.1-0.1,1.2-0.5,1.3-0.6c0.2-0.4,0.1-1.6,0-1.3l3.1-5.5c0-0.1,1.6-2.1,1.6-2.2l0.8-1.6c1.6-2.1,0.1-2.8,1-5.5
|
||||
c0.2-0.7,1.9-1.5,2.6-1.9l0.6,0.6c-0.3-2.3-0.4-3.1,1.3-4.2c0,0.1,0.3,1.6,0.3,1.6c0.7,0.3,1-0.5,1.3-0.3c0.1-1.3,1-2.8,1.4-3.4
|
||||
c-0.1,0.1-0.1,0.1-0.2,0.3c2.5-3.3,2.1-5.4,2.3-7.5c1.6,0,1.3,0.7,1.9-1.6c3.3,1,3.1-0.3,1.9-2.9c1.3-0.6,0.3-1.6,0.3-1.6
|
||||
c1-0.2,1.8,0.9,2.9,0.6c0.5-0.1,0-1,0-1c-0.2,0.7-0.6-0.6-0.6-0.6s1.3-1.9,1.3-1.9c0.2,0.1,0.5,1,0.6,1.3c0.1,0,1.3-1,1.3-1
|
||||
c0-0.1,1-1.3,1-1.3c0.5-2.1,0.6-4.4,1-5.8c0,0,0.5,0.7,0.6,0.6c0.3,0.7,0.6,1.4,1,2.2c0.2-0.1,1-0.3,1-0.3c0.4-1.9,0.6-3.8,1-6.4
|
||||
c0.7,0,0.4-0.8,1.3-0.6c0,0,1.1,0.8,1.3,1c0.1-0.4,0-0.8,0.3-1.6c0.3,0,1.4,0.2,1.6,0c0.2-0.2,0-1.3,0-1.6c1-0.4,0.5,0.3,1-0.6
|
||||
l-2.3-1.3c1.1-0.6,2.7-0.8,3.9-1.6c1.2-0.8,1.4-3.3,2.6-4.2c0.8-0.6,1.7-0.2,2.6-0.6c1.2-0.6,1.8-2.6,2.6-3.2
|
||||
c1.2-0.9,2.7-0.5,3.5-1.6c0.8-1.1-0.1-1.9,1-2.9c0.8-0.8,1.8-0.4,2.9-1c1.7-1,2.5-4,4.8-5.5c1.7-1.1,0.5-0.2,1-2.3
|
||||
c0,0,0.8-0.6,0.3-0.6c1.3-0.7,2.3-0.2,3.2-0.6c0.6-0.3,0.7-1.8,1.3-2.3c0.3-0.2,1,0.1,1.3,0c0.2-0.1,0.1-1.1,0.3-1.3
|
||||
c0.4-0.4,1.2,0,1.6-0.3c0.5-0.4,0.4-1.6,1-1.9c0.8-0.4,1.9,0,2.6-0.3c0.6-0.3,2.5-2.5,3.5-3.2c-0.1-0.1-0.5-0.5-0.3-0.6
|
||||
c0.3-0.3,1.2,0.2,1.6,0c0.4-0.2,0.8-1.9,1.3-2.3c1.1-0.8,2.4-0.5,3.5-1.3c0.5-0.3,0.2-1.4,1-1.6c0.4-0.1,1.2,0.2,1.6,0
|
||||
c0.7-0.4,0.7-1.8,1.3-2.3c2.2-1.8,3.8,0,6.4-3.2c1.9,0.5,1.2,0.6,3.2,0c2.8,3.9-0.2,8.1-1.6,11.9c-0.7,1.7-0.8,3-1.6,4.5
|
||||
c-0.6,1.1-2,2.1-2.6,3.2c-0.8,1.8-0.6,2.9-1.6,4.5c-0.5,0.8-1.8,1.5-2.2,2.2c-0.5,0.8-0.4,1.9-1,2.6c-0.6,0.7-2.1,0.8-2.6,1.6
|
||||
c-0.3,0.6,0.1,1.6-0.3,2.3c-0.5,0.8-1.2,0.1-1.9,0.6c-0.5,0.4,0,1-0.3,1.6c-0.2,0.3-1.1,0.4-1.3,0.6c-0.7,0.9-0.6,2-1.3,2.9
|
||||
c-0.6,0.7-2.6,1.4-2.9,2.3c-0.2,0.6,0.1,1.3,0,1.9c-0.4,0-1.7-0.3-2.3,0c-0.4,0.2-0.4,1.5-0.6,1.9c-0.2,0.3-2.8,2.1-2.9,2.3
|
||||
c-0.8,1.4-0.1,2.4-1.3,3.5c-1.1,1-1.4,0-2.6,0.6c-1.2,0.7-1.7,3.6-2.9,4.5c-1,0.7-2.5,0.5-3.5,1.3c-1,0.8-0.9,3.1-1.9,3.9
|
||||
c-0.5,0.4-1.5,0-1.9,0.3c-1.3,0.8-1.6,3.4-2.9,4.2c-1.2,0.7-2.6,0-3.9,0.6c-0.4,0.2-0.3,1.4-0.6,1.6c-0.3,0.1-1.4-0.2-1.6,0
|
||||
c-0.3,0.3,0.1,1.2,0,1.6c0,0-2.2,2.2-2.3,2.3c-1.4,0.7-2.2,0.1-3.9,1c-0.5,0.3-1.2,1.4-1.6,1.6c-1.5,0.8-2.6,0.3-3.5,1.6
|
||||
c-0.8,1.2-0.7,2.5-1.6,3.5c-0.5,0.6-1.8,1-2.3,1.6c-0.2,0.3-0.1,1.1-0.3,1.3c-2.5,1.9-2.4-1.2-3.5,2.9c-0.2,0-1.5-0.2-1.6-0.3
|
||||
c-0.4,0.5-1.4,1.7-1.9,2.2c-1.9,1.9-3.7,3-5.2,5.5c1.3-0.7,2.1-0.8,3.2-1.6l-1,1.3l-0.3,0.3c-0.1,0.1-0.4,1.4-0.6,1.6
|
||||
c-1.2,0.9-3.1,0.7-4.2,1.6c-0.1,0.1,0,0.8,0,1c-1.8,0.9-3.2,2.8-4.8,3.5c-1.9,0.8,0.9-1.8,1.9-2.3l0.3-1.3
|
||||
c-1.1,0.7-4.7,1.5-5.1,2.3c-0.7,1.2,0.2,1.6-0.3,2.6c-0.2,0.3-0.8,0.5-1.1,0.6c0,0-0.1,0-0.2,0c0,0,0.1,0,0.2,0
|
||||
c0.1-0.2-1-0.7-1.8-0.3c-0.4,0.2-0.4,1.1-0.6,1.3c-1.8,1.2-2.9,1.5-5.5,2.6c0,0.2,0.1,1.5,0,1.6c-0.2-0.2-0.7-0.7-1-0.6
|
||||
C151.6,327.4,151.5,326.9,151.8,326.3z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 20 KiB |
BIN
ul_tresorerie/img/banniere_ul.png
Normal file
BIN
ul_tresorerie/img/banniere_ul.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
187
ul_tresorerie/img/cgt_logo.svg
Normal file
187
ul_tresorerie/img/cgt_logo.svg
Normal file
@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="32" height="41" viewBox="0 0 612 792" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F03E3E;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#FAD43A;}
|
||||
.st3{fill:#FAB005;}
|
||||
</style>
|
||||
<rect x="9.8" y="6.3" class="st0" width="591.6" height="779.5"/>
|
||||
<path class="st1" d="M9.8,533.9c9.8-53.7,47.7-94.8,113.3-94.8c25.7,0,69.4,7.1,98.6,55.3l-50.4,28.7c-4-8.4-8.4-16.4-19.5-24.3
|
||||
c-6.6-4.4-15-8-25.6-8c-36.7,0-49.1,42-49.1,69.9c0,30.5,15,69,51.3,69c3.5,0,20.3,0,34.5-15.9c5.7-6.6,8-11.9,10.2-18.6l51.3,26.5
|
||||
c-8.4,13.3-17.7,27.9-36.3,41.1c-17.7,12.4-40.2,20.8-66.3,20.8c-44.2,0-114.5-26.5-114.5-120.7C7.2,552.9,8.1,543.2,9.8,533.9"/>
|
||||
<path class="st1" d="M380.4,645.6c-13.3,8.8-29.6,19.5-61,19.5c-19,0-37.6-4.9-52.6-15c-31-19.9-42.9-55.7-42.9-95.5
|
||||
c0-46.4,21.7-113.2,98.2-113.2c35.4,0,51.7,15.5,61.9,25.2v-21.7h63.7v222c-0.9,25.2-1.8,54.8-35.4,74.3
|
||||
c-23.4,14.2-57.9,17.7-78.7,17.7c-46,0-76.1-15.9-88.4-28.7c-18.1-18.1-17.7-41.6-17.7-53.9h70.3c0.9,10.2,1.8,24.8,19.9,33.2
|
||||
c4.4,2.2,11.1,4.4,21.7,4.4c41.1,0,41.1-24.3,41.1-43.3V645.6z M294.2,554c0,19.5,1.3,59.7,43.3,59.7c34.5,0,42.5-27,42.5-59.7
|
||||
c0-11.1-0.9-34-8.4-45.1c-5.3-8.4-17.7-17.7-37.1-16.8C302.2,493.5,294.2,521.3,294.2,554z"/>
|
||||
<path class="st1" d="M494.9,375.8h67.7v69h42.2v47.3h-42.6V596c-0.4,14.2-0.4,26.1,17.7,26.1l24.3-0.9v53.1
|
||||
c-14.6,2.2-23.4,3.5-35.8,3.5c-23.4,0-49.1-1.3-63.7-22.6c-9.3-13.7-9.7-30.1-9.7-52.2V491.7h-33.6v-46.9h33.6V375.8z"/>
|
||||
<g>
|
||||
<path class="st2" d="M252.5,260C252.5,260,252.5,260.1,252.5,260C252.7,259.9,252.6,259.9,252.5,260z"/>
|
||||
<path class="st2" d="M250.8,262.1C250.8,262.4,251.2,262.1,250.8,262.1L250.8,262.1z"/>
|
||||
<path class="st2" d="M41.1,417.7C41.1,417.7,41.1,417.7,41.1,417.7C41,417.5,41,417.5,41.1,417.7z"/>
|
||||
<path class="st2" d="M452.6,424.5L452.6,424.5C452.5,424.5,452.5,424.5,452.6,424.5z"/>
|
||||
<path class="st2" d="M453.2,424.5C453.2,424.5,453.2,424.5,453.2,424.5C453.3,424.5,453.3,424.5,453.2,424.5z"/>
|
||||
<path class="st3" d="M601.4,390.8c-0.7,0-1.4,0.1-2.1,0.2c-0.2,0-0.5,0.9-0.6,1c-1,0.4-5,0.9-5.5,1c-2.5,0.2-4.7-1-7.7-1
|
||||
c0.2,1.4,0,2.1,1,3.5c-0.5-0.1-1.5,0.2-1.9,0c-0.6-0.3-0.7-2.4-2.9-1c-0.3,0.2-0.3,1.2-0.6,1.3c-0.7,0.2,0-0.1-0.3-0.3l-7.1-1
|
||||
l-0.6-0.6c-0.6,0.1-0.7,0.2-1,0.3c-1.2,0.4-0.3,0.6-1,1.3h-0.6c-0.2-0.1,0-1.3,0-1.3c-1.1-0.3-1.9,1.1-2.6,1.3
|
||||
c-1.1,0.3-2.3-0.5-4.2-0.3c-2.5,0.2-6,1.2-8,2.3c-0.5-0.4-1-0.8-1.6-1.3c-0.1,0.1-2.3,2.9-2.3,2.9c-0.6,0.3-1.4-0.5-0.6-0.3
|
||||
c0-0.2,0.5-1.9,0-1.9c-0.7,0-0.7,0.9-1,1.3c-1.2-0.6-2.5-1.2-3.9,0c-0.2,0.1-0.1,0.9-0.3,1c-1.4,0.4,0.2-1.7-1.6-1.3
|
||||
c-0.9,0.2-1.8,1.6-2.6,1.9c-1.3,0.6-2.4,0.6-3.9,1c0-0.1,0.6-0.9,0.3-1c-0.3-0.1-0.7,0.1-1,0c-0.6,0.7-0.7,1.3-1.3,2.2
|
||||
c-0.7-0.1-1.6,0-2.3,0c0-0.6,0-1.3,0-1.9c1.1,0-0.7-0.5-1-0.6c-0.7,1.5-0.6,1.3-0.3,2.9c-0.2,0-0.8,0.1-1,0c0,0-1.8,0.3-2.9,0.3
|
||||
c1.1-0.7,0.4-0.5,0.6-1.3l1-0.6c-0.1,0-1.1-0.6-1.3-0.6c-0.4,1.3-1,3.4-3.5,3.2c-2.9-0.3,0.2-2.5-3.9-0.6v1.9
|
||||
c-0.1,0-4.5-0.6-4.5-0.6c-0.3-0.3-0.2-0.9-0.6-1.3c-0.6,0.4-1.5,2-2.3,2.3c-2.4,0.7-3.8-0.6-5.8,0c-0.1,0,0.3,0.5,0.3,0.6
|
||||
c-0.9,0.3-1.7,0.9-2.9,0.6c-0.5-0.1,0-1.3,0-1.3c-0.5-0.2-3.1-0.3-3.2-0.3c0,0.1,0,0.2,0,0.3c0,0-1.8,0.8-2.9,1l-0.3,1.6
|
||||
c-0.9,0.5-2.6-1-3.9-1c-0.5,0-1,1-1,1c-4.2,1.2-7.4,0.8-11.6,1.3c-0.9,0.1-3.8,1.5-4.8,1.6c-2,0.2-4.4,0.4-5.5,0
|
||||
c-0.7-0.3-0.4-1.4-2.3-1.3c-1.4,0.1-2.6,1.7-3.5,1.9c-1.5,0.4-3.9,0.6-4.8,0.6c-2,0.2-3-0.5-4.2-1.3c-0.1,0.1-0.3,1.6-0.3,1.6
|
||||
c-0.8,0.2-1.8-1.2-3.2-1c0,0-0.8,0.9-1,1c-1.5,0.4-4.2,0.6-5.5,0.6c-2.9,0-5.7-0.6-8.7-0.3c-3.4,0.4-5.1,1.7-8.4,1.9
|
||||
c-0.9,0-3.9-0.8-5.5-0.6c-1.1,0.1-2.2,1.8-3.2,1.9c-2.3,0.4-1.8-1.3-3.9-1c-0.4,0-0.2,0.9-0.3,1.3c-2.5-0.2-7.3,1.3-8.7-1
|
||||
c-0.2,0.1-0.3,1.1,0,1.3c-2.7,0-4.6,0.8-6.8,0.6c-1.5-0.1-3.3-0.1-5.5-0.6c-0.5-0.1-1.1-1.2-1.3-1.3c-2.1-0.5-4.6,0.9-6.8,1
|
||||
c-0.1,0-3.2,0-3.2,0s-0.5-1.5-1-1.6c-1.1-0.3-2.3,0.7-3.2,0.6c-0.5,0-1.1-0.9-1.3-1c-3.4-0.6-2.2,1.8-4.5,1.6c0,0-3.7-1.6-1.9-1.3
|
||||
c0-0.1-0.6-0.8-0.3-1c-1.3,0.2-1.9,0.1-3.2,0.3c0,0.1,0.3,1.6,0.3,1.6c-0.8,0.1-2.6-1.3-2.6-1.3c-1.9-0.4-0.3,0.7-1.9,0.6
|
||||
c-2-0.1-3.8-2-5.1-2.9c-0.6,0.4-0.8,0.5-1.6,1c-0.5-1.2-1.1-2.2-1.6-3.2c-2,0.5-3.5-0.4-5.8-0.6c-0.1-0.4,0-1.2-0.3-1.3
|
||||
c-1.4-0.3-1.1,0.9-2.6,0.3c-0.4-0.2-1.3-1.8-1.6-1.9c-0.6-0.2-1.3,0.5-2.3,0.3c-1-0.2-0.5-1.2-0.6-1.3c-0.4-0.2-1.2,0.2-1.6,0
|
||||
c-0.1-0.1,0-1.2-0.3-1.3c-0.3-0.1-1.5,0.8-2.6-0.6c-0.2-0.3-0.2-0.5-0.3-1c0,0-0.3-0.8-0.6-1.3c0.2,0.4,0.6-0.8,0-1.3
|
||||
c-1-0.9-1.7-0.3-2.9-0.3c0.3-0.2-0.3-0.8-0.3-1c0.3-0.1,0.6,0,1,0c-0.5-2.3-1.2-2.1-1.6-2.9c-1.1-2.3-1.4-5.8-1.6-8
|
||||
c-0.6,0-1.3-0.2-1.9-0.3c0.4-1.6,0.6-2,1.6-2.9c-0.5-1.5-2.7-4.4-2.3-6.1c0.2-0.9,1-0.8,1.6-1.3c-1-2.2-1-2.8,0.6-3.9
|
||||
c-0.1-0.3-0.8-1.5-0.6-1.9c0-0.2,1.5-1.3,1.6-1.6c0.3-1.1-0.5-1.3-0.3-2.2c0.1-0.5,1-2.1,1.3-2.9c-0.3-0.3-2.3-2.9-2.3-2.9
|
||||
c-0.6-0.2-1.7,0.3-2.2,0c-0.1,0,0-0.8,0-1c-0.5-1.3-2.2-1.1-3.5-1c-0.3-0.4-0.5-1.8-1-1.9c-0.9-0.3-0.3,2.8-0.3,2.9
|
||||
c-2.5,0.4-3.7,0.6-5.2,1.9c-2.5,2.4-2.7,8.3-3.9,11.7l-2.9,3.4c-0.2,0.1-1,0.4-1.3,0.6c0.3,1.9,0.4,3-1,3.9c-2.9,1.8-2.5-0.8-2.9-1
|
||||
c-0.4-0.1-0.9,0.2-1.3,0c0,0-0.5,1-0.6,1.3c0.5,0.4,0.7,0.1,1.3,0.3c-0.1,0.2-0.3,1-0.3,1c-0.3-0.2-1,0.1-1.3,0
|
||||
c-0.1-0.1-0.2-1-0.3-1.3c-0.2,0.1-1.7,0.6-1.3,0.6c-0.6,0.6-1.6,2.2-2.3,2.6c-0.7,0.4-0.5-0.5-1.3-0.3c-1.5,0.3-2.5,3.5-2.9,5.1
|
||||
c-0.5-0.2-1.4,0.2-1.9,0c-0.1,0,0-0.9,0-1c0,0-0.1-1.7-2.3-1.3c-0.3,0-1.3,1.2-1.6,1.3c-0.8,0.3-2.2-0.1-3.2,0.3
|
||||
c-0.9,0.4-2,2.5-2.9,3.2c-1.5-2.8-2.2-1.5-3.9,0c-3.5-2.8-9.7,1.1-13.5-1.6c-1.1-0.7-1-4.2-1-4.2c0-0.2,0.1-1.3-0.3-1
|
||||
c1.4-1.2,1.2-2.3,2.2-3.9c0.3-0.5,1.6-0.8,1.9-1.3c0.5-0.8,0.1-1.8,0.6-2.6c0.2-0.3,1.2-0.3,1.3-0.6c0.2-0.5-0.2-1.4,0-1.9
|
||||
c0.3,0,1.1,0.4,1.6,0.6c1.4-2,2.9-2.4,4.8-3.9c1.2-0.9,2.1-2.6,3.2-3.5c1.7-1.5,3.8-2.3,5.8-3.5c1-0.7,1.9-2.3,2.9-2.9
|
||||
c0.3-0.2,0.9,0.1,1.3,0c1.3-0.5,2.7-2.3,3.9-2.9c3.2-1.7,6.1-2,10-3.2c2.1-0.7,6.2-2.8,8.7-3.2c3-0.6,6.3-0.2,8.4-0.6
|
||||
c0.8-0.2,1.6-1.2,2.9-1c0.5,0.1,0.7,1.2,1,1.3c0.5,0.1,1.6-0.7,2.3-0.6c1.2,0.1,2.8,1.7,3.9,1.9c0.7,0.2,1.6-0.4,2.6,0
|
||||
c0,0.1,0,0.2,0,0.3c0.9,0.5,1.6,0.8,2.6,1.3c0,0.1-0.1,0.9,0,1c1.7,0.5,2.7-0.7,5.5,0.3c-0.2-0.1-1.1-1.2-1.3-1.3
|
||||
c-0.3,0.5-0.3-0.6-0.3-0.6c0.3-0.3,1.5-1,1.6-1.3c0.3-1.2-0.6-4.3-1-5.8c-0.5-1.9-3.5-4.2-3.5-4.2c0-0.2,0.1-1.2,0-1.3
|
||||
c-0.3-0.3-1.3,0.2-1.6,0c-0.5-0.2,0-1-0.3-1.3c-0.6-0.5-4.6-3.1-5.1-3.2c-2.5-0.8-4.2,0.2-7.1,0c-1.1-0.1-2.6-1.3-3.9-1.3
|
||||
c-0.6,0-1.8,0.9-2.3,1c-3,0.3-5.9,0-9.3,1c-0.5,0.1-1.3,1.2-1.6,1.3c-1.5,0.5-2.8-0.2-4.2,0.3c-0.7,0.3-1.2,1.8-0.3,1.9
|
||||
c-2.7,0.9-3.9,2-6.8,2.9c-0.1-0.5,0.1-1.1-0.3-1.3h-0.6c-0.4,1-1.8,4-2.9,4.2c-0.3,0.1-3.9-1.4-4.5-1.6c0.4,0.8,0.5,1.2,1,1.9
|
||||
c-2.7,0.9-5.9,1.4-7.7,0.3c-0.3,2.7,0.3,3.8-0.3,6.4c-2.6-0.5-6.4,3-7.1-1.6c-0.3,1.7-0.6,2-2.6,2.6c-0.2,2.7-0.2,6.7-2.9,7.7
|
||||
c-0.3,0.1-1-0.2-1.3,0c-1.6,1.4,0.4,1.7-2.3,1.3c-0.1,0.3-0.1,0.9-0.3,1c0,0,0.8,0.6,0.6,0.6c-1.7,0-1.1-1.3-1.9-1.9
|
||||
c-0.4,0.2-1.2,0-1.6,0l-0.3,1.6c-1,0.4-6.9-0.2-7.7-0.3c-2.1-0.3-6.4,0.1-8.7,1c-0.2,0.1-0.5,0.9-0.6,1c-0.9,0.2-1.2-0.4-2.6,0
|
||||
c0,0.1,0,0.2,0,0.3c-1,0.5-3.9,0.8-4.5,1.6c-0.2,0.3-0.1,1.1-0.3,1.3c-0.8,0.6-2,0.4-2.9,1c0,0-0.3,1-0.3,1
|
||||
c-2.2,1.3-5.9,1.7-7.7,3.2c-0.4,0.4-0.7,1.4-1,1.9c-3.5-0.9-2.4,0.5-4.5,1.3c-1.4,0.5-2.9,0.1-4.5,1c-0.2,0.1-0.5,0.9-0.6,1
|
||||
c-2.2,0.6-3.1,0.1-5.5,1l0.3,2.6l-1-0.3c0,0-0.3-1.8-0.3-1.9c0,0-1,0.3-1,0.3c-0.2,2.4,0.4,2.5-1.9,1.9c0.1,0.2,0.5,1.2,0.3,1.3
|
||||
c-1.6,0.5-3.8-0.7-4.5-0.6c-0.6,0-0.6,0.8-1,1c-0.9,0.3-2-0.2-2.9,0c-0.4,0.1-1.7,1.2-2.2,1.3c-0.7,0.1-1.2-0.5-2.3-0.3
|
||||
c-2.5,0.5-5.6,3-8.4,3.5c-1.5,0.3-3.1-0.4-5.2,0c-1.9,0.4-4.8,2.2-6.4,2.3c-1,0-1.1-0.8-2.3-0.6c-0.1,0-0.8,0.9-1,1
|
||||
c-1.1,0.3-6.2,0.1-7.1,0c-0.2,0-1.1-1-1.3-1c-1.9-0.2-3,1-4.8,1c-0.3,0-1.6-0.9-1.9-1c-2.5-0.4-3.3,0.1-6.1-0.6c0-0.1,0.1-1,0-1
|
||||
c-0.9-0.3-2.3,0.2-3.2,0c-2.2-0.4-4-2.6-5.5-3.9c-0.4-0.3-1.4-0.5-1.6-1c-0.5-0.9,0.1-1.9-0.3-2.9c-0.3-0.7-2.1-2-2.6-3.2
|
||||
c0.5-0.4,0-1.6,0-1.6c0.3,0,0.3-3.2,0.3-3.2c1.3-2.8,0.2-4.4,2.6-5.5c-0.4-1.6-0.5-1.7,0-3.5c-0.4,0.1-0.5,0.5-1.3,0.3
|
||||
c0.3-1,2.5-5.9,3.2-6.4c0.4-0.3,1.5,0,1.9-0.3l0.3-1.6c3-1,2.8-2.6,5.1-4.5c0.6-0.5,1.3-0.1,1.9-0.6c0.6-0.5,0.6-1.8,1.3-2.3
|
||||
c1.1-0.7,2.3-0.4,3.2-1c1.1-0.7,1-2,1.9-2.9c1-1,3-1.1,4.5-1.6c0-0.7-0.2-1.6,0-2.3c1.2-0.4,2.9-0.4,3.2-0.6c0.1-0.1,0-1.1,0-1.3
|
||||
c2-1.1,3.1-1.9,5.1-2.9c-0.1-0.2-0.3-1-0.3-1c1-1.3,3.2-1,1.6-2.3c-0.4-0.3-0.3-1-0.3-1c1.6-0.5,2.5-0.7,3.9-1.3
|
||||
c0-0.1-0.4-0.6-0.3-0.6c1.3-0.5,2.7,0.3,4.2-1c0.9-0.7,1.3-2.3,2.3-2.9c0.3-0.2,1.1-0.1,1.3-0.3c0.5-0.6,0-1.5,0.6-1.9
|
||||
c1.6-1.2,3.5-1.2,5.1-2.3c1.1-0.7,2-1.8,3.2-2.6c-0.1-0.2-0.3-1-0.3-1c0.8-0.8,1.8-0.3,2.9-1c0.4-0.2-0.3-0.6-0.3-0.6
|
||||
c1.1-0.8,1.3-1,1.9-2.3c0.8,0,0.8,0.5,1.6,0c0,0-1-1.2,0.3-1.6c3.9-1.3-0.8,3.4-1.6,4.2c2.7-1.3,3.2-2.3,6.4-2.6
|
||||
c1.2-2.8,1.6-4,3.5-5.5c-0.1-0.2-0.5-0.4-0.3-0.6c0.1-0.1,1.2-0.5,1.3-0.6c1.1-2.1,0.3-2.3,2.6-3.2c0,0-1.2-0.5-1-0.6
|
||||
c0.4-0.3,1.2,0.2,1.6,0c0.1-0.1,0.3-1.5,0.3-1.6c1.3-0.2,1.7,0.2,2.6-0.6c0.3-0.3,0-1.2,0.3-1.6c0.3-0.4,1.4-0.5,1.6-1
|
||||
c0.9-1.6,0.2-2.6,1.3-3.5c0.8-0.7,1.9-0.3,2.6-1c0.1-0.2-0.1-0.8,0-1c0.3-0.7,3.9-3.4,4.8-4.5c1,0.2,1.2,0.2,2.3,0.3
|
||||
c0.1-0.5-0.2-1.4,0-1.9l0.3-0.3c2-0.7,1.7-2.4,2.6-2.9c0.9-0.5,2.5,0,2.9,0c0.7-1.8,1.2-2.2,2.9-2.9c-1.4-1.1,1.5-4.2,2.3-5.5
|
||||
c0.7,0.5,0.7,0.5,1.6,1l0.6-1.3c0.5-2.1-0.1-1.9,1.9-2.6c-0.3-2.6,1.2-3.5,4.2-4.2c0.1-0.7-0.3-1.6,0-2.3c0.2-0.4,1.3-0.4,1.6-0.6
|
||||
c0.8-0.7,2.3-4.5,2.3-4.5l-0.3-0.6c0.7-0.4,1.1-0.5,1.3-0.5c0-0.2,0.2-0.6,0.7-1.4c0,0,0.7-0.5,1-0.7c0-0.1,0-0.2,0-0.3
|
||||
c0.4-0.4,2.2-0.8,2.3-1c0.7-1.4-0.2-2.2-0.3-2.9c0.2-0.1,0.8,0.1,1,0c0.1-0.1-0.1-0.8,0-1c0.6-1.2,2.6-2,3.5-2.3
|
||||
c-0.2-0.6,0.2-0.6,0.3-1c-0.3-0.2-1.3-1.3-1.3-1.3c1.1,0.3,3.9-6,4.5-8c-0.6-0.3-0.7,0-1-0.6c1.8,0.1,4.2,0.2,3.5-2.6l-0.3-0.6
|
||||
c0.6-0.7,2.8-1.6,3.2-2.3c1.5-2.3,0.4-7.7,0.3-10c-0.1-2,1-6.2,0.6-7.4c-0.2-0.5-1.5-1.3-1.6-1.6c-0.5-1.6,1-2.5-0.3-3.2
|
||||
c-0.5-0.3-1.7-0.6-1.9-0.6c-0.3-2.5-0.3-3-2.3-3.2c0.5-1.3,0.5-1.6,1-2.6c-2.4-0.9-1.5-1.8-2.6-2.6c-0.6-0.4-0.5,0.4-1,0.3
|
||||
c-0.5-0.1-1.4-1.2-1.9-1.3c-2.3-0.5-5.3-0.6-7.7-0.3c-2.9,0.4-7,2.2-10.3,3.5c-0.9,0.4-2.7,2.1-3.5,2.3c-1,0.2-1.4-0.7-2.6-0.3
|
||||
c-1.7,0.5-4.9,6.9-6.8,8c-0.7,0.4-1.3-0.1-1.6,0c-1.2,0.4-1.1,2.1-1.9,2.9c-0.8,0.7-2,0.3-2.9,1c-0.6,0.4-0.8,1.5-1.3,1.9
|
||||
c-0.7,0.7-1.8,0.6-2.6,1.3c-1.1,0.9-1.2,2.9-2.3,3.9c-1,0.8-2.2,0.8-3.2,1.6c-1.9,1.5-3.3,3.9-5.1,5.5c-0.4,0.3-1.4,0.6-1.6,1
|
||||
c-0.2,0.3-0.1,1.6-0.6,2.3c-0.6,0.7-2.9,1.7-3.2,2.6c-0.2,0.4,0.2,1.3,0,1.6c-0.4,0.6-1-0.1-1.6,0.3c-0.2,0.1-0.2,1.4-0.3,1.6
|
||||
c-0.8,1.2-3.2,1.9-4.2,2.9c-0.7,0.7-0.3,1.6-1,2.6c-0.5,0.7-1.2,0.1-1.9,0.6c-0.1,0.1,0.1,0.8,0,1c-0.4,1-3,1.6-3.5,2.6
|
||||
c-0.5,0.8,0,1.2-0.3,1.6c-0.4,0.5-0.5,0.2-1.3,0.3c-0.7,2.2-3.2,3.9-1,5.1c-2.1,0.3-2.8,0.9-4.8,1c-0.6,1.3-0.5,2.5-1.3,3.5
|
||||
c-0.7,0.8-1.4,0.3-2.3,1c-0.9,0.7-0.7,1.9-1.3,2.9c-1.2,1.9-3.7,3.6-4.8,5.5c-0.2,0.3-0.9,3.8-1,3.9c-0.2,0.2-1.3,0-1.6,0
|
||||
c-0.8,1.8-0.6,2.3-2.6,2.6c-0.1,0.6,0.2,0.8,0.3,1.3c-1.4,1-1.8,1.9-2.9,3.2c-0.5,0.6-2.3,1.3-2.6,1.9c-0.5,1.1,0.3,2.2,0.6,3.9
|
||||
c-0.8,0.3-1.4,0.4-2.3,0.6c0,0.1,0.1,0.9,0,1c-0.6,0.2-1.9-0.9-2.6,1c-0.6,1.5,0.1,2.7,0.3,3.9c-3,0.9-3.9,0.4-4.5,3.9
|
||||
c-0.3,0-1.5,0.3-1.9,0.3c0,0.2,0,2.6,0,2.6s-1.5,1.5-1.6,1.6c0,0-1.1,1.8,0.3,1.3c-0.1,0.3-0.5,1.8-0.3,1.9
|
||||
c-3.7-1.9-6.4,6.4-8.4,9.6c-0.1,0.2-1.2,0.3-1.3,0.6c-0.1,0.4,0.2,1.3,0,1.6c-0.3,0.5-1.1,0.2-1.6,0.6c-0.4,0.4-1.7,2.8-1.9,3.2
|
||||
c-1,1.9-0.6,3.3,0.6,4.5c-2-0.6-2.7-0.4-3.9,1.6c-1.7,3-2.6,6-4.2,9.3c-0.6,1.2-2.1,2.4-2.6,3.9c-0.4,1.4,0.1,2.5-0.3,3.9
|
||||
c-0.3,0.7-2.8,2.5-2.9,2.6c1.4-0.5,1.9-0.6,2.9-1.3c0.2-0.2,0.7-0.9,1-1c1.7-0.8,0.2,0.6,1,1c-1.3,0.5-3.7,1.3-1.3,1.9
|
||||
c-1.7,1.1-3,2.2-4.8,3.2c0,0.2,0.1,1.2,0,1.3c-0.2,0.2-1.3,0-1.6,0c-0.4,0.8-0.2,1-0.6,1.6c-0.3-0.1-0.9-0.4-1-0.3
|
||||
c0.4,0.3-0.5,1.2,0.6,1c-0.1,0.1-0.7,0.9-0.6,1c-0.8-0.1-0.9-0.3-1.6-0.6c-0.3,0.7-0.3,0.3-1,0.6c0,0.3,0.2,1.8,0,1.9
|
||||
c-0.3,0.3-0.6-0.3-0.6-0.3c-1,0-4.9,3-5.8,3.5c-0.5,0.3-1.2,0-1.6,0.3c-0.2,0.2-0.1,1.1-0.3,1.3c-1,0.7-2,0.3-2.9,1
|
||||
c-0.5,0.4-1.7,2.3-1.6,2.3v0.6c0.4,0.1,1.9,0.6,1.9,0.6c-1.6,0.7-8.4,3-9,3.9c-0.1,0.1-0.4,1.9-0.6,2.6c-5.2,0.1-2.5,1.6-5.5,2.9
|
||||
c-1.7,0.7-2.1,0.4-3.2,1.3c0.6,1,0.4,1.3,1,2.2c-5.3,0.8-4.8,3.7-8.4,4.2c0.4,1.3,0.2,1.3,0.6,2.6c-0.7,0.3-1.3,0-1.9,0.3
|
||||
c-0.3,0.1-0.8,1.5-1,1.6c-0.3,0.1-4-0.7-2.2,0.6c0.3,0.3,0.3,0,0.3,0c1.4,0.5,2.8,0.5,1,1.6c-1.6,1-3.8,0.9-5.5,1.6
|
||||
c3.8,2.2-1.7,1.6-1.9,1.6c-0.1,0-0.2,0-0.3,0c-1,0.4-1.6,2.6-2.6,3.2c-0.1,0-4.6,2.2-4.8,2.2c-0.8,0.2-1.9-0.3-2.6,0
|
||||
c-0.6,0.2,0.5,1.4-0.6,1.9c0.5-0.1,1.1-0.5,1.6-0.6c-1.4,2.8-0.4,3.4-4.5,4.8c0.8,0,0-3.2,0-3.2c-0.4-0.3-0.1-0.5-0.3-1
|
||||
c-0.6,0.2-2.1,1.2-2.6,1.3c-2,0.1-0.5-2.1-3.5,0.6c-2.1,1.9-4.2,4.9-6.4,6.4c0.2,0.5,0.5,0.7,0.3,1.3c-1.7,0.3-1.5,0-1,0.3
|
||||
c0.6,0.3,0,0.8,0.6,1c0,0-0.5,1.1-0.6,1.2c0.2,0.3,0.6,1,0.6,1c0.1,0.6-0.4,0.9-0.6,1.3c2.7,0.2,2.3,1.2,1.3,3.2
|
||||
c-0.9-0.4,0,1.4,0.3,0.6c1.9,0.9,1.6,1.5,0.6,3.5c1,0.9,1.1,0.5,1.9,1.3c0,0-0.2,0.6-0.6,0.3c0,0.1-0.3,1-0.3,1
|
||||
c0.7,0.3,1.6,0.6,1.9,0.6c0,0-0.7,0.7-0.3,1c0.8,0.6,1.8,0.3,2.9,0.6c0.4-0.8,0.5-1.8,0.6-2.3c3.2-0.4,3.6,0,3.9-2.6
|
||||
c0.5,0,0.9,0.3,1,0.3c-0.7-0.3-0.7,1.3,1,1c0.9-0.2,4.9-3.1,5.8-3.9c0,0-0.4-0.6-0.3-0.6c1.2-0.6,1.5,0,2.2-0.6
|
||||
c0.5-0.5-0.1-0.8,0-1c-0.9,0.2-1,0.3-1.9,0.3c0.1-0.3,0.3-1,0.3-1c1.1,0.2,1.7,0.5,2.9,0.3c1.3-0.2,1.4-1.6,2.3-2.3
|
||||
c2.1-1.6,4.8-1.9,6.8-3.5c0.2-0.2,0.1-1.1,0.3-1.3c0.8-0.6,1.8-0.4,2.6-1c1.1-0.8,1.7-2.5,2.9-3.2c0.9-0.6,2.2-0.3,3.2-1
|
||||
c0.7-0.5,1.1-2.4,1.9-2.9c1.2-0.8,2.4-0.7,3.5-1.6c2.3-1.9,2.4-3.4,5.1-4.8c0,0-0.1-1.6,0-1.9c0.9,0.2,0.5,0.6,1.6,0.6
|
||||
c0.2,0-0.6-1.3-0.6-1.3c1.8-0.7,2.3-1.4,3.5-2.3c1.5-1,3.4-0.9,4.8-1.9l1.6-1.3c0.1-0.2-0.2-1.1,0-1.3c0.6-0.5,1.3-0.2,1.9-0.6
|
||||
c1.7-1.2,1.4-2.6,4.2-3.2c-0.1,0,0-1.1,0-1.3c-1,0-1.9,0-2.9,0c0.1-0.2,0.3-1,0.3-1c2-0.3,2.5,0,3.9,0.3c0.3-0.9,1.1-2.2,2.6-2.6
|
||||
c0.4-0.1,1.3,0.2,1.6,0c0.2-0.1-0.5-1.1-0.6-1.3c2-0.8,3-1.4,5.1-2.3c-0.1,0,0,0.9,0,1c0.4-0.4,0.3,0.3,0.3,0.3
|
||||
c-0.3,0.2-1.1,1-1.3,1.3l-0.3,1.3c0.5-0.1,0.8-0.4,1.3-0.6c-0.1,1.1-1.1,6.3-0.3,8.7l1.3,0.3c0,0.7,0.3,2.3,0.3,2.6
|
||||
c0,0.6-0.8,1.2-0.6,1.6c0.2,0.6,1.7,0.8,1.9,1.6c0.4,1.4-0.5,3.2,0,4.5c0.4,1.2,2.7,1,3.9,1.6c1.3,0.7,1.9,2.9,3.2,3.5
|
||||
c0.8,0.4,0.9-0.6,1.6-0.3c0,0,0.3,1,0.3,1c6,0,11.9,2.4,18.3,1.6c1.7-0.2,3.4-1,5.8-1.3c0,0,0.3-1,0.3-1c2.7-0.9,3.6-0.3,6.8-1
|
||||
c0.9-0.2,2.5-1.7,3.2-1.9c0.5-0.1,1.5,0.2,1.9,0c0.3-0.2,1.1-2.1,2.9-2.3c3.6-0.4,6.7,0.9,10,0.3c0.6-0.1,2.9-1.4,3.5-1.3
|
||||
c0.2,0,1.3,1.1,1.6,1.3c0.3-1.1,0.6-1.9,1-2.9c1.6,0.2,3,0.8,5.1,0c0.1,0,0.5-0.9,0.6-1c0.8-0.2,2.2,0.6,3.9,0
|
||||
c0.7-0.3,1.4-1.7,2.3-1.9c0.8-0.3,2.9,0.3,4.5-0.3c0.3-0.1,0.8-1.4,1.3-1.6c1-0.4,5.1,0.7,6.4,0.3c0.4-0.1,1.1-2.1,1.6-2.3
|
||||
c1.2-0.4,4.2,0,5.5-0.3c0.6-0.1,2.4-1.9,2.9-1.9c0.3,0,1.8,0.7,2.6,1c0.2-0.4,0.7-1.8,1-1.9c0.9-0.4,2.3,0.2,3.2,0
|
||||
c0.5-0.1,1.4-1.1,1.9-1.3c0.9-0.3,1.3,0.5,1.9,0.3c0,0,0-0.9,0-1c1.3-0.4,3.1-0.3,4.8-0.6c0,0,0.8-0.9,1-1c1.7-0.6,2.2,0.2,3.9-0.3
|
||||
c0.9-0.3,2-2.4,2.9-2.6c1.7-0.4,2.9,0.5,5.1,0c2.8,5.1,0.4,9.1,3.9,13.2c1,1.2,2.6,1,3.5,1.6c0.4,0.2,0.2,0.8,0.3,1.3
|
||||
c2.2,0.6,3.7,1.9,5.5,2.3l7.4,1.9c1.2,0.3,1.1-1.5,2.3-1.6c0.7-0.1-0.4,0.8,0.3,1c1,0.3,1.8-0.2,2.9-0.3c0,0.1-0.7,0.8-0.3,1h0.6
|
||||
l2.6-0.3l0.6-1.6c2.2-0.1,6.5,0.5,9.7,0c0.6-0.1,3-1.2,3.5-1.3c1.3-0.2,3.1,0.3,3.9,0.3c1,0,3.4-1.1,4.5-1.3
|
||||
c0.9-0.2,0.9,0.5,1.6,0.3c0.1,0,0-0.9,0-1c4.6-0.6,2.8-1.9,6.1-3.9c0.8,1.1,1.7,2.3,3.5,1.6c0.6-0.2,0.6-0.9,1-1.3
|
||||
c-0.9-0.1-1.1,0-1.6,0c1.9-0.8,3.8-2.7,6.8-2.9c-1.3-0.8-1-1.2-0.6-2.9c0.3,0,1.1,0.1,1.3,0c0,0.9,0.1,1.9,0,3.5c-0.1,0-1,0-1,0
|
||||
c1.5,1.4-1.3,2.8,1.3,4.2c-0.4,0.7-0.8,1.3-1.3,2.2c0.4,0.2,1.2,0,1.6,0c1.3,2.3-0.1,0.8,2.3,1.9c0,0-0.4,0.6-0.3,0.6
|
||||
c0.6,0.2-1.1-0.1-1.9-0.3c0.2,0.3,0.6,1.3,0.6,1.3c1.2,0.1,3.2-0.1,3.5,0l-0.6,1.7c0,0,2.7,3.1,2.5,3.2c-0.6,0.2-1.2,0.2-1.6,0.3
|
||||
c1.9,0.4,1.9,0.9,1.6,2.6c0.7-0.4,1.4-0.4,1.9-0.6c0,0,0-0.9,0-1c-2.3-2.2,0.7-0.5,1.3,1.9c0.3,0.1,1.3,0.1,1.6,0.3
|
||||
c0.1,0.1-0.1,0.8,0,1c0.2,0.6,2.6,2,3.2,2.3c1,0.4,2.3,0,3.2,0.6c0.1,0.1,0,1.1,0,1.3c1,0.3,1.4,0.3,2.6,0.6c0,0-0.4,0.6-0.3,0.6
|
||||
c1.2,0.2,0.8-1.1,2.9-0.6c0.2,0-1,0.6-1,0.6c2.8,1.9,3,0.9,6.1,0.3c-2.2,0.6-0.4,1.4,1,1.9c0.8,0.3,2.3,0.4,2.9,1
|
||||
c0.1,0.1,0.3,1.3,0.3,1.3c2.4,0.9,3.6-1.1,5.5-0.3c0,0-0.6,0.9-0.6,1c0.4,0.3,0.6,0.6,1,1c0.5-0.4,0.9-1,1.3-1.3l5.6,2l8.3,0.5
|
||||
c0.1-0.3,0.3-1.1,0.6-1.3c0.5,0.5,0.3,0.9,0.6,1.6c3.1,0.2,3.5,0.5,6.4,0.6c2,0.1,4.6-0.6,6.4,0c0,0,0.6,1.3,0.6,1.3
|
||||
c0.2,0.1,0.6-0.6,2.6,0c0.1-0.4,0-1.2,0.3-1.3c0,0,2.2,0.6,3.2,0.6c-0.4-0.1,1.1-0.4,1.3-0.3v0.6c2.3-0.2,3.4-0.5,4.8-1
|
||||
c0.5,1.1,0.7,1.5,1,2.3c1.2-0.3,2.8-0.4,4.2-1c0,0,0.7-1.2,1.6-1.3c0.8-0.1,2.5,1.2,3.2,1.3c1.5,0.2,4.9-0.9,7.1-0.6
|
||||
c0.3,0,1.4,0.8,1.9,0.6c0.5-0.1,0.6-1.2,1.3-1.3c0.3,0,3.2,1.3,3.5,1.3c0.2,0,1.1-1.6,1.3-1.6c1.6-0.4,1.4,1.1,2.9,0.6
|
||||
c0.4-0.1-0.3-1.3-0.3-1.3c0.3-0.1,0.9,0,1-0.3c1,0.3,1.9,1.5,3.5,1.6c1.4,0.1,2.7-0.9,3.5-1c0.7-0.1,2.7,0.6,2.9,0.6
|
||||
c0.9,0,2.8-0.9,3.5-1c0.9,0,2.5,0.7,3.9,0.6c1.4,0,4.3-0.5,5.8-0.6l0.3-1c0.2,0,1.2,0.5,1.6,0.6c0.1,0,0.5,0,0.7,0
|
||||
c2.9-0.4,4.4-1,7.4-0.6l0.3-1.3c0.8-0.1,1.4,1.5,2.6,1.3c0.3-0.1-0.3-1.3-0.3-1.3c2.9,0.8,3.5,0.3,5.1-1.3c0.4,0.2,1.2-0.2,1.6,0
|
||||
c0.1,0,0,1.1,0,1.3c0,0,1,0.3,1,0.3c0.3-0.2,0.5-1.4,1.3-1.6l4.2,0.3c0.3,0,0.8,0.7,1.6,0.6l0.6-1c0.6-0.2,3,0,4.5,0
|
||||
c0-0.2-0.1-1.6,0-1.6c0.5-0.2,1.3,0.3,1.3,0.3s0.4,1.1,1.9,1c2.2-0.2,3.4-1.3,5.8-1c-0.4-0.5-0.8-1.1-1-1.6
|
||||
c0.2-0.1,0.5,0.1,0.3-0.3c0.5,0.2,1.4,0,1.9,0c0,0.2-0.1,1.5,0,1.6c0.3-0.2,1,0.2,1.3,0c0.1-0.1,0-1.1,0-1.3c1.3,0.4,2.8,1,3.5,1
|
||||
c1.9-0.2,1.9-1.7,2.9-1.9c2.1-0.4-0.2,1,2.9-0.6c2.6,1.7,2.5,0.6,4.8,0.3c1.5-0.1,2.3,0.8,4.2,0.6c3.1-0.3,3.9-1.3,7.4-1.6v-1
|
||||
c1.3-0.3,1.1,0.8,1.9,0.6l0.3-1c2.5-0.2,2.8,0,4.2,0.6c0.1-0.2,0.5-1.2,0.6-1.3c3.1-0.8,5.5,0.6,7.1,0.3v-0.6c0.7,0,1.8,0,2.6,0
|
||||
l0.6-1.3c1.3-0.3,1.4,0.9,1.6,1c0.6,0.1,3.7-0.2,4.8-0.3c2.9-0.4,8.7-1.1,11.3-2.3c0.3-0.2,0.8-1.5,1-1.6c1.5-0.5,3,0.8,3.9,1.3
|
||||
c-0.3-1.5-0.7-2,1.9-2.6c0,0.1,0.4,2.2,0.3,1.9c0.6,0,1.1,0.7,2.6,0.3c0.2-0.1-0.6-0.6-0.6-0.6c0.8-0.2,1.1-0.2,1.6-0.3
|
||||
c-0.2-0.4-0.6-1.3-0.6-1.3c2.3-0.8,2.9,1.2,3.2,1.3c0.6,0.2,1.6,0,2.3,0c-0.2-0.1,0.5-0.3,0-0.6c-2.7-2,3.3-0.6,5.1-0.6
|
||||
c-0.7-0.6-0.4-0.5-0.6-1.3c0.5,0,1.2,0.2,1.6,0c0,0.3-0.2,1.8,0,1.9c0.2-0.1,0.7,0,1,0l-0.3-2.3c0,0,1.3,1,1.6,1.6
|
||||
c-0.6,0.1-0.2,1,0.6,0.6c1.3-0.5,0.8-0.9,2.6,0c0.9-1.3,1.4-2,1.9-2.9c0.3,0.6,3,2.6,5.8,1.6c0,0,2.4-2.8,2.6-2.9
|
||||
c2.7-0.9,5.4,0.1,7.7-1c0.5,0.7,0.6,1.3,1,1.9L601.4,390.8z M151.8,326.3c0.3-0.9,0.5-1.7,0.6-3.2l1-0.3c0,0,1.1,0.5,1.6,0.3
|
||||
c0.1-0.1-0.1-0.8,0-1c0.1-0.1,1.2-0.5,1.3-0.6c0.2-0.4,0.1-1.6,0-1.3l3.1-5.5c0-0.1,1.6-2.1,1.6-2.2l0.8-1.6c1.6-2.1,0.1-2.8,1-5.5
|
||||
c0.2-0.7,1.9-1.5,2.6-1.9l0.6,0.6c-0.3-2.3-0.4-3.1,1.3-4.2c0,0.1,0.3,1.6,0.3,1.6c0.7,0.3,1-0.5,1.3-0.3c0.1-1.3,1-2.8,1.4-3.4
|
||||
c-0.1,0.1-0.1,0.1-0.2,0.3c2.5-3.3,2.1-5.4,2.3-7.5c1.6,0,1.3,0.7,1.9-1.6c3.3,1,3.1-0.3,1.9-2.9c1.3-0.6,0.3-1.6,0.3-1.6
|
||||
c1-0.2,1.8,0.9,2.9,0.6c0.5-0.1,0-1,0-1c-0.2,0.7-0.6-0.6-0.6-0.6s1.3-1.9,1.3-1.9c0.2,0.1,0.5,1,0.6,1.3c0.1,0,1.3-1,1.3-1
|
||||
c0-0.1,1-1.3,1-1.3c0.5-2.1,0.6-4.4,1-5.8c0,0,0.5,0.7,0.6,0.6c0.3,0.7,0.6,1.4,1,2.2c0.2-0.1,1-0.3,1-0.3c0.4-1.9,0.6-3.8,1-6.4
|
||||
c0.7,0,0.4-0.8,1.3-0.6c0,0,1.1,0.8,1.3,1c0.1-0.4,0-0.8,0.3-1.6c0.3,0,1.4,0.2,1.6,0c0.2-0.2,0-1.3,0-1.6c1-0.4,0.5,0.3,1-0.6
|
||||
l-2.3-1.3c1.1-0.6,2.7-0.8,3.9-1.6c1.2-0.8,1.4-3.3,2.6-4.2c0.8-0.6,1.7-0.2,2.6-0.6c1.2-0.6,1.8-2.6,2.6-3.2
|
||||
c1.2-0.9,2.7-0.5,3.5-1.6c0.8-1.1-0.1-1.9,1-2.9c0.8-0.8,1.8-0.4,2.9-1c1.7-1,2.5-4,4.8-5.5c1.7-1.1,0.5-0.2,1-2.3
|
||||
c0,0,0.8-0.6,0.3-0.6c1.3-0.7,2.3-0.2,3.2-0.6c0.6-0.3,0.7-1.8,1.3-2.3c0.3-0.2,1,0.1,1.3,0c0.2-0.1,0.1-1.1,0.3-1.3
|
||||
c0.4-0.4,1.2,0,1.6-0.3c0.5-0.4,0.4-1.6,1-1.9c0.8-0.4,1.9,0,2.6-0.3c0.6-0.3,2.5-2.5,3.5-3.2c-0.1-0.1-0.5-0.5-0.3-0.6
|
||||
c0.3-0.3,1.2,0.2,1.6,0c0.4-0.2,0.8-1.9,1.3-2.3c1.1-0.8,2.4-0.5,3.5-1.3c0.5-0.3,0.2-1.4,1-1.6c0.4-0.1,1.2,0.2,1.6,0
|
||||
c0.7-0.4,0.7-1.8,1.3-2.3c2.2-1.8,3.8,0,6.4-3.2c1.9,0.5,1.2,0.6,3.2,0c2.8,3.9-0.2,8.1-1.6,11.9c-0.7,1.7-0.8,3-1.6,4.5
|
||||
c-0.6,1.1-2,2.1-2.6,3.2c-0.8,1.8-0.6,2.9-1.6,4.5c-0.5,0.8-1.8,1.5-2.2,2.2c-0.5,0.8-0.4,1.9-1,2.6c-0.6,0.7-2.1,0.8-2.6,1.6
|
||||
c-0.3,0.6,0.1,1.6-0.3,2.3c-0.5,0.8-1.2,0.1-1.9,0.6c-0.5,0.4,0,1-0.3,1.6c-0.2,0.3-1.1,0.4-1.3,0.6c-0.7,0.9-0.6,2-1.3,2.9
|
||||
c-0.6,0.7-2.6,1.4-2.9,2.3c-0.2,0.6,0.1,1.3,0,1.9c-0.4,0-1.7-0.3-2.3,0c-0.4,0.2-0.4,1.5-0.6,1.9c-0.2,0.3-2.8,2.1-2.9,2.3
|
||||
c-0.8,1.4-0.1,2.4-1.3,3.5c-1.1,1-1.4,0-2.6,0.6c-1.2,0.7-1.7,3.6-2.9,4.5c-1,0.7-2.5,0.5-3.5,1.3c-1,0.8-0.9,3.1-1.9,3.9
|
||||
c-0.5,0.4-1.5,0-1.9,0.3c-1.3,0.8-1.6,3.4-2.9,4.2c-1.2,0.7-2.6,0-3.9,0.6c-0.4,0.2-0.3,1.4-0.6,1.6c-0.3,0.1-1.4-0.2-1.6,0
|
||||
c-0.3,0.3,0.1,1.2,0,1.6c0,0-2.2,2.2-2.3,2.3c-1.4,0.7-2.2,0.1-3.9,1c-0.5,0.3-1.2,1.4-1.6,1.6c-1.5,0.8-2.6,0.3-3.5,1.6
|
||||
c-0.8,1.2-0.7,2.5-1.6,3.5c-0.5,0.6-1.8,1-2.3,1.6c-0.2,0.3-0.1,1.1-0.3,1.3c-2.5,1.9-2.4-1.2-3.5,2.9c-0.2,0-1.5-0.2-1.6-0.3
|
||||
c-0.4,0.5-1.4,1.7-1.9,2.2c-1.9,1.9-3.7,3-5.2,5.5c1.3-0.7,2.1-0.8,3.2-1.6l-1,1.3l-0.3,0.3c-0.1,0.1-0.4,1.4-0.6,1.6
|
||||
c-1.2,0.9-3.1,0.7-4.2,1.6c-0.1,0.1,0,0.8,0,1c-1.8,0.9-3.2,2.8-4.8,3.5c-1.9,0.8,0.9-1.8,1.9-2.3l0.3-1.3
|
||||
c-1.1,0.7-4.7,1.5-5.1,2.3c-0.7,1.2,0.2,1.6-0.3,2.6c-0.2,0.3-0.8,0.5-1.1,0.6c0,0-0.1,0-0.2,0c0,0,0.1,0,0.2,0
|
||||
c0.1-0.2-1-0.7-1.8-0.3c-0.4,0.2-0.4,1.1-0.6,1.3c-1.8,1.2-2.9,1.5-5.5,2.6c0,0.2,0.1,1.5,0,1.6c-0.2-0.2-0.7-0.7-1-0.6
|
||||
C151.6,327.4,151.5,326.9,151.8,326.3z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 20 KiB |
2617
ul_tresorerie/js/app_v2.js
Normal file
2617
ul_tresorerie/js/app_v2.js
Normal file
File diff suppressed because it is too large
Load Diff
2617
ul_tresorerie/js/main.js
Normal file
2617
ul_tresorerie/js/main.js
Normal file
File diff suppressed because it is too large
Load Diff
26
ul_tresorerie/lib/AppInfo/Application.php
Normal file
26
ul_tresorerie/lib/AppInfo/Application.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\ULTresorerie\AppInfo;
|
||||
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||
|
||||
class Application extends App implements IBootstrap {
|
||||
public const string APP_ID = 'ul_tresorerie';
|
||||
|
||||
public function __construct(array $urlParams = []) {
|
||||
parent::__construct(self::APP_ID, $urlParams);
|
||||
}
|
||||
|
||||
public function register(IRegistrationContext $context): void {
|
||||
// Services and controllers are autowired via Nextcloud DI container
|
||||
}
|
||||
|
||||
public function boot(IBootContext $context): void {
|
||||
// Boot stage hook as required by Nextcloud 34 IBootstrap
|
||||
}
|
||||
}
|
||||
450
ul_tresorerie/lib/Controller/ApiController.php
Normal file
450
ul_tresorerie/lib/Controller/ApiController.php
Normal file
@ -0,0 +1,450 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\ULTresorerie\Controller;
|
||||
|
||||
use OCA\ULTresorerie\AppInfo\Application;
|
||||
use OCA\ULTresorerie\Service\PermissionService;
|
||||
use OCA\ULTresorerie\Service\TreasuryService;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\IRequest;
|
||||
use Throwable;
|
||||
|
||||
class ApiController extends Controller {
|
||||
public function __construct(
|
||||
IRequest $request,
|
||||
private readonly TreasuryService $treasuryService,
|
||||
private readonly PermissionService $permissionService,
|
||||
) {
|
||||
parent::__construct(Application::APP_ID, $request);
|
||||
}
|
||||
|
||||
private function ensureAuthorized(): ?JSONResponse {
|
||||
if (!$this->permissionService->canAccessCurrentUser()) {
|
||||
return new JSONResponse(
|
||||
['error' => 'Accès refusé : vous ne disposez pas des droits nécessaires pour accéder à la Trésorerie UL CGT.'],
|
||||
Http::STATUS_FORBIDDEN,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $year
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function getState(?int $year = null): JSONResponse {
|
||||
if (($denied = $this->ensureAuthorized()) !== null) {
|
||||
return $denied;
|
||||
}
|
||||
|
||||
try {
|
||||
$targetYear = ($year !== null && $year >= 2000 && $year <= 2100)
|
||||
? $year
|
||||
: (int)date('Y');
|
||||
$state = $this->treasuryService->getFullState($targetYear);
|
||||
$userContext = $this->permissionService->getCurrentUserContext();
|
||||
$state['userContext'] = $userContext;
|
||||
if ($userContext['isAdmin']) {
|
||||
$state['permissions'] = $this->permissionService->getPermissionsData();
|
||||
}
|
||||
return new JSONResponse($state);
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cogitiel_id
|
||||
* @param string $civility
|
||||
* @param string $last_name
|
||||
* @param string $first_name
|
||||
* @param string $phone_personal
|
||||
* @param string $phone_pro
|
||||
* @param string $address
|
||||
* @param string $postal_code
|
||||
* @param string $city
|
||||
* @param string $email
|
||||
* @param string $base_code
|
||||
* @param string $base_name
|
||||
* @param float $default_amount
|
||||
* @param string $default_method
|
||||
* @param int $is_archived
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function createMember(
|
||||
string $cogitiel_id = '',
|
||||
string $civility = '',
|
||||
string $last_name = '',
|
||||
string $first_name = '',
|
||||
string $phone_personal = '',
|
||||
string $phone_pro = '',
|
||||
string $address = '',
|
||||
string $postal_code = '',
|
||||
string $city = '',
|
||||
string $email = '',
|
||||
string $base_code = '90045110520251341',
|
||||
string $base_name = 'INDIVIDUELS UL FOIX HAUTE-ARIEGE',
|
||||
float $default_amount = 0.0,
|
||||
string $default_method = 'Virement',
|
||||
int $is_archived = 0,
|
||||
): JSONResponse {
|
||||
if (($denied = $this->ensureAuthorized()) !== null) {
|
||||
return $denied;
|
||||
}
|
||||
|
||||
if (trim($last_name) === '' || trim($first_name) === '') {
|
||||
return new JSONResponse(
|
||||
['error' => 'Le nom et le prénom du syndiqué sont obligatoires.'],
|
||||
Http::STATUS_BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$member = $this->treasuryService->createMember([
|
||||
'cogitiel_id' => $cogitiel_id,
|
||||
'civility' => $civility,
|
||||
'last_name' => $last_name,
|
||||
'first_name' => $first_name,
|
||||
'phone_personal' => $phone_personal,
|
||||
'phone_pro' => $phone_pro,
|
||||
'address' => $address,
|
||||
'postal_code' => $postal_code,
|
||||
'city' => $city,
|
||||
'email' => $email,
|
||||
'base_code' => $base_code,
|
||||
'base_name' => $base_name,
|
||||
'default_amount' => $default_amount,
|
||||
'default_method' => $default_method,
|
||||
'is_archived' => $is_archived,
|
||||
]);
|
||||
|
||||
return new JSONResponse(['member' => $member], Http::STATUS_CREATED);
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param string $cogitiel_id
|
||||
* @param string $civility
|
||||
* @param string $last_name
|
||||
* @param string $first_name
|
||||
* @param string $phone_personal
|
||||
* @param string $phone_pro
|
||||
* @param string $address
|
||||
* @param string $postal_code
|
||||
* @param string $city
|
||||
* @param string $email
|
||||
* @param string $base_code
|
||||
* @param string $base_name
|
||||
* @param float $default_amount
|
||||
* @param string $default_method
|
||||
* @param int $is_archived
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function updateMember(
|
||||
int $id,
|
||||
string $cogitiel_id = '',
|
||||
string $civility = '',
|
||||
string $last_name = '',
|
||||
string $first_name = '',
|
||||
string $phone_personal = '',
|
||||
string $phone_pro = '',
|
||||
string $address = '',
|
||||
string $postal_code = '',
|
||||
string $city = '',
|
||||
string $email = '',
|
||||
string $base_code = '90045110520251341',
|
||||
string $base_name = 'INDIVIDUELS UL FOIX HAUTE-ARIEGE',
|
||||
float $default_amount = 0.0,
|
||||
string $default_method = 'Virement',
|
||||
int $is_archived = 0,
|
||||
): JSONResponse {
|
||||
if (($denied = $this->ensureAuthorized()) !== null) {
|
||||
return $denied;
|
||||
}
|
||||
|
||||
if (trim($last_name) === '' || trim($first_name) === '') {
|
||||
return new JSONResponse(
|
||||
['error' => 'Le nom et le prénom du syndiqué sont obligatoires.'],
|
||||
Http::STATUS_BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$member = $this->treasuryService->updateMember($id, [
|
||||
'cogitiel_id' => $cogitiel_id,
|
||||
'civility' => $civility,
|
||||
'last_name' => $last_name,
|
||||
'first_name' => $first_name,
|
||||
'phone_personal' => $phone_personal,
|
||||
'phone_pro' => $phone_pro,
|
||||
'address' => $address,
|
||||
'postal_code' => $postal_code,
|
||||
'city' => $city,
|
||||
'email' => $email,
|
||||
'base_code' => $base_code,
|
||||
'base_name' => $base_name,
|
||||
'default_amount' => $default_amount,
|
||||
'default_method' => $default_method,
|
||||
'is_archived' => $is_archived,
|
||||
]);
|
||||
|
||||
return new JSONResponse(['member' => $member]);
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function deleteMember(int $id): JSONResponse {
|
||||
if (($denied = $this->ensureAuthorized()) !== null) {
|
||||
return $denied;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->treasuryService->deleteMember($id);
|
||||
return new JSONResponse([
|
||||
'deleted' => true,
|
||||
'id' => $id,
|
||||
'multiYearStats' => $this->treasuryService->getMultiYearStats((int)date('Y')),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $content
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function importAsp(?string $content = null): JSONResponse {
|
||||
if (($denied = $this->ensureAuthorized()) !== null) {
|
||||
return $denied;
|
||||
}
|
||||
|
||||
try {
|
||||
$rawContent = '';
|
||||
$uploadedFile = $this->request->getUploadedFile('file');
|
||||
|
||||
if (is_array($uploadedFile) && isset($uploadedFile['tmp_name']) && is_uploaded_file((string)$uploadedFile['tmp_name'])) {
|
||||
$rawContent = (string)file_get_contents((string)$uploadedFile['tmp_name']);
|
||||
} elseif ($content !== null && trim($content) !== '') {
|
||||
$rawContent = $content;
|
||||
} else {
|
||||
return new JSONResponse(
|
||||
['error' => 'Aucun fichier .asp ou contenu valide reçu pour l\'importation.'],
|
||||
Http::STATUS_BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
$summary = $this->treasuryService->importAspContent($rawContent);
|
||||
return new JSONResponse($summary);
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $member_id
|
||||
* @param int $year
|
||||
* @param int $month_start
|
||||
* @param int $month_end
|
||||
* @param float $amount
|
||||
* @param string $payment_method
|
||||
* @param string|null $note
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function saveDue(
|
||||
int $member_id,
|
||||
int $year,
|
||||
int $month_start,
|
||||
int $month_end,
|
||||
float $amount,
|
||||
string $payment_method = 'Virement',
|
||||
?string $note = null,
|
||||
): JSONResponse {
|
||||
if (($denied = $this->ensureAuthorized()) !== null) {
|
||||
return $denied;
|
||||
}
|
||||
|
||||
if ($amount <= 0.0) {
|
||||
return new JSONResponse(
|
||||
['error' => 'Veuillez saisir un montant supérieur à 0,00 € (ou utilisez « Effacer ce mois »).'],
|
||||
Http::STATUS_BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$savedDues = $this->treasuryService->saveDues(
|
||||
$member_id,
|
||||
$year,
|
||||
$month_start,
|
||||
$month_end,
|
||||
$amount,
|
||||
$payment_method,
|
||||
$note,
|
||||
);
|
||||
|
||||
return new JSONResponse([
|
||||
'member_id' => $member_id,
|
||||
'year' => $year,
|
||||
'dues' => $savedDues,
|
||||
'multiYearStats' => $this->treasuryService->getMultiYearStats($year),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $member_id
|
||||
* @param int $year
|
||||
* @param int $month
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function deleteDue(int $member_id, int $year, int $month): JSONResponse {
|
||||
if (($denied = $this->ensureAuthorized()) !== null) {
|
||||
return $denied;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->treasuryService->deleteDue($member_id, $year, $month);
|
||||
return new JSONResponse([
|
||||
'deleted' => true,
|
||||
'member_id' => $member_id,
|
||||
'year' => $year,
|
||||
'month' => $month,
|
||||
'multiYearStats' => $this->treasuryService->getMultiYearStats($year),
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $slug
|
||||
* @param string $title
|
||||
* @param string $subject
|
||||
* @param string $body
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function saveTemplate(
|
||||
string $slug,
|
||||
string $title,
|
||||
string $subject,
|
||||
string $body,
|
||||
): JSONResponse {
|
||||
if (($denied = $this->ensureAuthorized()) !== null) {
|
||||
return $denied;
|
||||
}
|
||||
|
||||
try {
|
||||
$template = $this->treasuryService->saveTemplate($slug, $title, $subject, $body);
|
||||
return new JSONResponse(['template' => $template]);
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
#[NoAdminRequired]
|
||||
public function resetTemplates(): JSONResponse {
|
||||
if (($denied = $this->ensureAuthorized()) !== null) {
|
||||
return $denied;
|
||||
}
|
||||
|
||||
try {
|
||||
$templates = $this->treasuryService->resetTemplates();
|
||||
return new JSONResponse(['templates' => $templates]);
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $year
|
||||
* @param array<int, int> $member_ids
|
||||
* @param string $subject
|
||||
* @param string $body
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function sendEmails(
|
||||
int $year,
|
||||
array $member_ids,
|
||||
string $subject,
|
||||
string $body,
|
||||
): JSONResponse {
|
||||
if (($denied = $this->ensureAuthorized()) !== null) {
|
||||
return $denied;
|
||||
}
|
||||
|
||||
if ($member_ids === []) {
|
||||
return new JSONResponse(
|
||||
['error' => 'Veuillez sélectionner au moins un destinataire.'],
|
||||
Http::STATUS_BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->treasuryService->sendMailingEmails($year, $member_ids, $subject, $body);
|
||||
return new JSONResponse($result);
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
#[NoAdminRequired]
|
||||
public function getPermissions(): JSONResponse {
|
||||
if (!$this->permissionService->isCurrentUserAdmin()) {
|
||||
return new JSONResponse(
|
||||
['error' => 'Seuls les administrateurs Nextcloud peuvent gérer les permissions de l\'application.'],
|
||||
Http::STATUS_FORBIDDEN,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return new JSONResponse($this->permissionService->getPermissionsData());
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $access_mode
|
||||
* @param array<int, string> $allowed_groups
|
||||
* @param array<int, string> $allowed_users
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
public function savePermissions(
|
||||
string $access_mode = 'restricted',
|
||||
array $allowed_groups = [],
|
||||
array $allowed_users = [],
|
||||
): JSONResponse {
|
||||
if (!$this->permissionService->isCurrentUserAdmin()) {
|
||||
return new JSONResponse(
|
||||
['error' => 'Seuls les administrateurs Nextcloud peuvent modifier les permissions de l\'application.'],
|
||||
Http::STATUS_FORBIDDEN,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$updated = $this->permissionService->savePermissionsData(
|
||||
$access_mode,
|
||||
$allowed_groups,
|
||||
$allowed_users,
|
||||
);
|
||||
return new JSONResponse($updated);
|
||||
} catch (Throwable $e) {
|
||||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
41
ul_tresorerie/lib/Controller/PageController.php
Normal file
41
ul_tresorerie/lib/Controller/PageController.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\ULTresorerie\Controller;
|
||||
|
||||
use OCA\ULTresorerie\AppInfo\Application;
|
||||
use OCA\ULTresorerie\Service\PermissionService;
|
||||
use OCA\ULTresorerie\Service\TreasuryService;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IRequest;
|
||||
|
||||
class PageController extends Controller {
|
||||
public function __construct(
|
||||
IRequest $request,
|
||||
private readonly TreasuryService $treasuryService,
|
||||
private readonly PermissionService $permissionService,
|
||||
) {
|
||||
parent::__construct(Application::APP_ID, $request);
|
||||
}
|
||||
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
public function index(): TemplateResponse {
|
||||
$userContext = $this->permissionService->getCurrentUserContext();
|
||||
if ($userContext['canAccess']) {
|
||||
$this->treasuryService->ensureDefaultTemplates();
|
||||
}
|
||||
|
||||
return new TemplateResponse(Application::APP_ID, 'main', [
|
||||
'currentYear' => (int)date('Y'),
|
||||
'canAccess' => $userContext['canAccess'],
|
||||
'isAdmin' => $userContext['isAdmin'],
|
||||
'userUid' => $userContext['uid'],
|
||||
'userDisplayName' => $userContext['displayName'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
209
ul_tresorerie/lib/Migration/Version1000Date20261031.php
Normal file
209
ul_tresorerie/lib/Migration/Version1000Date20261031.php
Normal file
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\ULTresorerie\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCA\ULTresorerie\Service\DefaultTemplates;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\DB\Types;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
class Version1000Date20261031 extends SimpleMigrationStep {
|
||||
public function __construct(
|
||||
private readonly IDBConnection $db,
|
||||
) {
|
||||
}
|
||||
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
// 3.1. Table ul_cgt_members (Syndiqués)
|
||||
if (!$schema->hasTable('ul_cgt_members')) {
|
||||
$table = $schema->createTable('ul_cgt_members');
|
||||
$table->addColumn('id', Types::BIGINT, [
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
'length' => 20,
|
||||
'unsigned' => true,
|
||||
]);
|
||||
$table->addColumn('cogitiel_id', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 64,
|
||||
]);
|
||||
$table->addColumn('civility', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 16,
|
||||
'default' => '',
|
||||
]);
|
||||
$table->addColumn('last_name', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 128,
|
||||
]);
|
||||
$table->addColumn('first_name', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 128,
|
||||
]);
|
||||
$table->addColumn('phone_personal', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 32,
|
||||
'default' => '',
|
||||
]);
|
||||
$table->addColumn('phone_pro', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 32,
|
||||
'default' => '',
|
||||
]);
|
||||
$table->addColumn('address', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 255,
|
||||
'default' => '',
|
||||
]);
|
||||
$table->addColumn('postal_code', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 16,
|
||||
'default' => '',
|
||||
]);
|
||||
$table->addColumn('city', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 128,
|
||||
'default' => '',
|
||||
]);
|
||||
$table->addColumn('email', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 190,
|
||||
'default' => '',
|
||||
]);
|
||||
$table->addColumn('base_code', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 64,
|
||||
'default' => '',
|
||||
]);
|
||||
$table->addColumn('base_name', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 190,
|
||||
'default' => '',
|
||||
]);
|
||||
$table->addColumn('default_amount', Types::DECIMAL, [
|
||||
'notnull' => true,
|
||||
'precision' => 10,
|
||||
'scale' => 2,
|
||||
'default' => '0.00',
|
||||
]);
|
||||
$table->addColumn('default_method', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 32,
|
||||
'default' => 'Virement',
|
||||
]);
|
||||
$table->addColumn('is_archived', Types::SMALLINT, [
|
||||
'notnull' => true,
|
||||
'default' => 0,
|
||||
]);
|
||||
|
||||
$table->setPrimaryKey(['id']);
|
||||
$table->addUniqueIndex(['cogitiel_id'], 'ul_cgt_mbr_cogitiel_uq');
|
||||
$table->addIndex(['last_name', 'first_name'], 'ul_cgt_mbr_name_idx');
|
||||
$table->addIndex(['is_archived'], 'ul_cgt_mbr_arch_idx');
|
||||
}
|
||||
|
||||
// 3.2. Table ul_cgt_dues (Cotisations mensuelles)
|
||||
if (!$schema->hasTable('ul_cgt_dues')) {
|
||||
$table = $schema->createTable('ul_cgt_dues');
|
||||
$table->addColumn('id', Types::BIGINT, [
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
'length' => 20,
|
||||
'unsigned' => true,
|
||||
]);
|
||||
$table->addColumn('member_id', Types::BIGINT, [
|
||||
'notnull' => true,
|
||||
'length' => 20,
|
||||
'unsigned' => true,
|
||||
]);
|
||||
$table->addColumn('year', Types::INTEGER, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('month', Types::INTEGER, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('amount', Types::DECIMAL, [
|
||||
'notnull' => true,
|
||||
'precision' => 10,
|
||||
'scale' => 2,
|
||||
]);
|
||||
$table->addColumn('payment_method', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 32,
|
||||
'default' => 'Virement',
|
||||
]);
|
||||
$table->addColumn('note', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 255,
|
||||
]);
|
||||
|
||||
$table->setPrimaryKey(['id']);
|
||||
$table->addUniqueIndex(['member_id', 'year', 'month'], 'ul_cgt_dues_mbr_ym_uq');
|
||||
$table->addIndex(['year'], 'ul_cgt_dues_year_idx');
|
||||
}
|
||||
|
||||
// 3.3. Table ul_cgt_templates (Modèles de publipostage)
|
||||
if (!$schema->hasTable('ul_cgt_templates')) {
|
||||
$table = $schema->createTable('ul_cgt_templates');
|
||||
$table->addColumn('id', Types::BIGINT, [
|
||||
'autoincrement' => true,
|
||||
'notnull' => true,
|
||||
'length' => 20,
|
||||
'unsigned' => true,
|
||||
]);
|
||||
$table->addColumn('slug', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 64,
|
||||
]);
|
||||
$table->addColumn('title', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 190,
|
||||
]);
|
||||
$table->addColumn('subject', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 255,
|
||||
]);
|
||||
$table->addColumn('body', Types::TEXT, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
|
||||
$table->setPrimaryKey(['id']);
|
||||
$table->addUniqueIndex(['slug'], 'ul_cgt_tpl_slug_uq');
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
|
||||
foreach (DefaultTemplates::getAll() as $template) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id')
|
||||
->from('ul_cgt_templates')
|
||||
->where($qb->expr()->eq('slug', $qb->createNamedParameter($template['slug'], IQueryBuilder::PARAM_STR)));
|
||||
$result = $qb->executeQuery();
|
||||
$existing = $result->fetchAssociative();
|
||||
$result->closeCursor();
|
||||
|
||||
if ($existing === false) {
|
||||
$insertQb = $this->db->getQueryBuilder();
|
||||
$insertQb->insert('ul_cgt_templates')
|
||||
->values([
|
||||
'slug' => $insertQb->createNamedParameter($template['slug'], IQueryBuilder::PARAM_STR),
|
||||
'title' => $insertQb->createNamedParameter($template['title'], IQueryBuilder::PARAM_STR),
|
||||
'subject' => $insertQb->createNamedParameter($template['subject'], IQueryBuilder::PARAM_STR),
|
||||
'body' => $insertQb->createNamedParameter($template['body'], IQueryBuilder::PARAM_STR),
|
||||
])
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
ul_tresorerie/lib/Service/AspImportParser.php
Normal file
110
ul_tresorerie/lib/Service/AspImportParser.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\ULTresorerie\Service;
|
||||
|
||||
final class AspImportParser {
|
||||
/**
|
||||
* Parse le contenu brut d'un fichier d'export CoGétise (.asp / CSV Windows-1252).
|
||||
*
|
||||
* @param string $rawContent Contenu brut du fichier .asp
|
||||
* @return array<int, array{
|
||||
* cogitiel_id: string,
|
||||
* civility: string,
|
||||
* last_name: string,
|
||||
* first_name: string,
|
||||
* phone_personal: string,
|
||||
* phone_pro: string,
|
||||
* address: string,
|
||||
* postal_code: string,
|
||||
* city: string,
|
||||
* email: string,
|
||||
* base_code: string,
|
||||
* base_name: string,
|
||||
* is_archived: int
|
||||
* }>
|
||||
*/
|
||||
public function parse(string $rawContent): array {
|
||||
// Suppression éventuelle du BOM UTF-8
|
||||
if (str_starts_with($rawContent, "\xEF\xBB\xBF")) {
|
||||
$rawContent = substr($rawContent, 3);
|
||||
}
|
||||
|
||||
$rawLines = preg_split('/\r\n|\r|\n/', $rawContent) ?: [];
|
||||
$parsedMembers = [];
|
||||
$isHeaderSkipped = false;
|
||||
|
||||
foreach ($rawLines as $rawLine) {
|
||||
// 1. Supprimer les espaces/tabulations de fin et convertir Windows-1252 -> UTF-8
|
||||
$line = rtrim($rawLine, "\r\n\t");
|
||||
if (trim($line) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mb_check_encoding($line, 'UTF-8')) {
|
||||
$line = mb_convert_encoding($line, 'UTF-8', 'Windows-1252');
|
||||
}
|
||||
|
||||
// 2. Parser avec str_getcsv($line, ',', '"') et ignorer la première ligne d'en-tête
|
||||
$columns = str_getcsv($line, ',', '"');
|
||||
if (!$isHeaderSkipped) {
|
||||
$isHeaderSkipped = true;
|
||||
$firstCol = trim((string)($columns[0] ?? ''));
|
||||
if ($firstCol === 'Code Responsable' || !ctype_digit($firstCol)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$cogitielId = trim((string)($columns[0] ?? ''));
|
||||
if ($cogitielId === '' || $cogitielId === 'Code Responsable') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$civility = trim((string)($columns[1] ?? ''));
|
||||
$lastName = mb_strtoupper(trim((string)($columns[2] ?? '')), 'UTF-8');
|
||||
$firstName = trim((string)($columns[3] ?? ''));
|
||||
$phonePersonal = trim((string)($columns[4] ?? ''));
|
||||
$phonePro = trim((string)($columns[5] ?? ''));
|
||||
|
||||
// 3. Concaténer proprement les colonnes 6, 7 et 8 non vides dans le champ address
|
||||
$addressParts = [];
|
||||
foreach ([6, 7, 8] as $colIndex) {
|
||||
$part = trim((string)($columns[$colIndex] ?? ''));
|
||||
if ($part !== '') {
|
||||
$addressParts[] = $part;
|
||||
}
|
||||
}
|
||||
$address = implode(', ', $addressParts);
|
||||
|
||||
$postalCode = trim((string)($columns[10] ?? ''));
|
||||
$city = trim((string)($columns[11] ?? ''));
|
||||
$email = trim((string)($columns[12] ?? ''));
|
||||
$baseCode = trim((string)($columns[13] ?? ''));
|
||||
$baseName = trim((string)($columns[14] ?? ''));
|
||||
$isArchived = ((int)trim((string)($columns[15] ?? '0')) !== 0) ? 1 : 0;
|
||||
|
||||
if ($lastName === '' && $firstName === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parsedMembers[] = [
|
||||
'cogitiel_id' => $cogitielId,
|
||||
'civility' => $civility,
|
||||
'last_name' => $lastName,
|
||||
'first_name' => $firstName,
|
||||
'phone_personal' => $phonePersonal,
|
||||
'phone_pro' => $phonePro,
|
||||
'address' => $address,
|
||||
'postal_code' => $postalCode,
|
||||
'city' => $city,
|
||||
'email' => $email,
|
||||
'base_code' => $baseCode,
|
||||
'base_name' => $baseName !== '' ? $baseName : 'INDIVIDUELS UL FOIX HAUTE-ARIEGE',
|
||||
'is_archived' => $isArchived,
|
||||
];
|
||||
}
|
||||
|
||||
return $parsedMembers;
|
||||
}
|
||||
}
|
||||
111
ul_tresorerie/lib/Service/DefaultTemplates.php
Normal file
111
ul_tresorerie/lib/Service/DefaultTemplates.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\ULTresorerie\Service;
|
||||
|
||||
final class DefaultTemplates {
|
||||
/**
|
||||
* Retourne les 3 modèles de publipostage pré-installés.
|
||||
*
|
||||
* @return array<int, array{slug: string, title: string, subject: string, body: string}>
|
||||
*/
|
||||
public static function getAll(): array {
|
||||
return [
|
||||
[
|
||||
'slug' => 'tax_receipt',
|
||||
'title' => '1. Reçu fiscal / Crédit d\'impôt 66 %',
|
||||
'subject' => 'Attestation fiscale {{annee}} — Cotisations syndicales CGT ({{prenom}} {{nom}})',
|
||||
'body' => <<<'EOT'
|
||||
Foix, le {{date_du_jour}}
|
||||
|
||||
Destinataire :
|
||||
{{civilite}} {{prenom}} {{nom}}
|
||||
{{adresse_complete}}
|
||||
Code syndiqué (CoGétise) : {{code_responsable}}
|
||||
|
||||
Objet : Attestation annuelle de versement de cotisations syndicales — Année {{annee}}
|
||||
|
||||
Chère camarade, cher camarade,
|
||||
|
||||
L'Union Locale CGT de Foix et de la Haute-Ariège atteste par la présente que {{civilite}} {{prenom}} {{nom}} (identifiant {{code_responsable}}), rattaché(e) à la base des Isolés / Multipros de l'UL CGT Foix Haute-Ariège, a acquitté au titre de l'année civile {{annee}} ({{nb_mois_payes}} mois réglé(s)) un montant total de cotisations syndicales de :
|
||||
|
||||
Montant total versé en {{annee}} : {{total_paye}}
|
||||
|
||||
Conformément aux dispositions de l'article 199 quater C du Code général des impôts, les cotisations versées aux organisations syndicales représentatives ouvrent droit à un crédit d'impôt sur le revenu égal à 66 % des sommes versées :
|
||||
|
||||
Montant de votre crédit d'impôt (66 %) : {{credit_impot}}
|
||||
|
||||
Conservez précieusement cette attestation afin de pouvoir la produire, le cas échéant, sur demande de l'administration fiscale lors de votre déclaration de revenus (case 7AC / 7AE / 7AG).
|
||||
|
||||
En te remerciant pour ta fidélité et ton engagement syndical à la CGT, nous t'adressons, chère camarade, cher camarade, nos salutations fraternelles et syndicalistes.
|
||||
|
||||
Pour la Trésorerie de l'Union Locale CGT de Foix et de la Haute-Ariège
|
||||
Bourse du Travail — Foix (09000)
|
||||
EOT,
|
||||
],
|
||||
[
|
||||
'slug' => 'missing_months',
|
||||
'title' => '2. Rappel des mois manquants (Alerte Orange)',
|
||||
'subject' => 'Suivi de tes cotisations syndicales CGT {{annee}} — {{nb_mois_manquants}} mois en attente',
|
||||
'body' => <<<'EOT'
|
||||
Foix, le {{date_du_jour}}
|
||||
|
||||
Destinataire :
|
||||
{{civilite}} {{prenom}} {{nom}}
|
||||
{{adresse_complete}}
|
||||
Code syndiqué (CoGétise) : {{code_responsable}}
|
||||
|
||||
Objet : Point sur tes cotisations syndicales CGT — Année {{annee}}
|
||||
|
||||
Chère camarade, cher camarade,
|
||||
|
||||
Lors du pointage de la trésorerie des syndiqués isolés (Multipros) de l'Union Locale CGT de Foix et de la Haute-Ariège pour l'année {{annee}}, nous avons bien enregistré tes règlements pour {{nb_mois_payes}} mois (soit un total perçu à ce jour de {{total_paye}}), et nous t'en remercions.
|
||||
|
||||
Sauf erreur ou omission de notre part (ou décalage de pointage bancaire récent), il apparaît toutefois que {{nb_mois_manquants}} échéance(s) mensuelle(s) ne figurent pas encore dans notre suivi pour l'année {{annee}} :
|
||||
|
||||
Mois non pointés : {{mois_manquants_liste}}
|
||||
Cotisation mensuelle habituelle : {{cotisation_habituelle}}
|
||||
Montant estimé à régulariser : {{montant_estime_retard}}
|
||||
|
||||
Les cotisations des syndiqués constituent la seule ressource financière garantissant l'indépendance de la CGT et les moyens d'action et de défense des salariés sur notre territoire de Foix et de la Haute-Ariège.
|
||||
|
||||
Si ton règlement a été effectué tout récemment ou si ta situation professionnelle ou personnelle a évolué (changement d'emploi, retraite, perte d'emploi), n'hésite pas à nous contacter afin que nous mettions à jour ta fiche et le montant de ta cotisation.
|
||||
|
||||
Restant à ton écoute, nous t'adressons, chère camarade, cher camarade, nos salutations fraternelles.
|
||||
|
||||
La Trésorerie de l'Union Locale CGT de Foix et de la Haute-Ariège
|
||||
EOT,
|
||||
],
|
||||
[
|
||||
'slug' => 'annual_zero_warning',
|
||||
'title' => '3. Rappel annuel d\'absence de versement (Alerte Rouge — 0 €)',
|
||||
'subject' => 'IMPORTANT : Régularisation de ton adhésion CGT pour l\'année {{annee}}',
|
||||
'body' => <<<'EOT'
|
||||
Foix, le {{date_du_jour}}
|
||||
|
||||
Destinataire :
|
||||
{{civilite}} {{prenom}} {{nom}}
|
||||
{{adresse_complete}}
|
||||
Code syndiqué (CoGétise) : {{code_responsable}}
|
||||
|
||||
Objet : Absence de versement de cotisation enregistrée sur l'année {{annee}}
|
||||
|
||||
Chère camarade, cher camarade,
|
||||
|
||||
Sauf erreur de notre part, lors de la vérification des comptes de l'Union Locale CGT de Foix et de la Haute-Ariège pour l'année {{annee}}, nous constatons qu'aucun versement de cotisation syndicale (0,00 €) n'a été enregistré sur ton dossier depuis le début de l'année ({{nb_mois_manquants}} mois échus : {{mois_manquants_liste}}).
|
||||
|
||||
À titre indicatif, sur la base de ta cotisation mensuelle de référence de {{cotisation_habituelle}}, le montant cumulé représente {{montant_estime_retard}} (ouvrant droit à 66 % de crédit d'impôt).
|
||||
|
||||
Nous tenons à attirer fraternellement ton attention sur le fait que le paiement régulier des cotisations conditionne le maintien de ton statut de syndiqué(e) CGT ainsi que l'accès à l'accompagnement juridique et syndical de l'Union Locale. À défaut de régularisation ou de nouvelles de ta part, nous serions malheureusement contraints de procéder à l'archivage de ton dossier d'adhérent(e).
|
||||
|
||||
Si tu rencontres des difficultés financières passagères, un changement de situation (chômage, retraite, maladie, changement d'entreprise) ou un problème technique de virement/prélèvement, prends rapidement contact avec l'Union Locale : nous trouverons ensemble une solution adaptée.
|
||||
|
||||
Dans l'attente de ton retour, nous te prions de croire, chère camarade, cher camarade, en nos salutations syndicalistes et fraternelles.
|
||||
|
||||
La Trésorerie de l'Union Locale CGT de Foix et de la Haute-Ariège
|
||||
EOT,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
209
ul_tresorerie/lib/Service/PermissionService.php
Normal file
209
ul_tresorerie/lib/Service/PermissionService.php
Normal file
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\ULTresorerie\Service;
|
||||
|
||||
use OCA\ULTresorerie\AppInfo\Application;
|
||||
use OCP\IConfig;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
|
||||
class PermissionService {
|
||||
private const string KEY_ACCESS_MODE = 'access_mode';
|
||||
private const string KEY_ALLOWED_GROUPS = 'allowed_groups';
|
||||
private const string KEY_ALLOWED_USERS = 'allowed_users';
|
||||
|
||||
public function __construct(
|
||||
private readonly IConfig $config,
|
||||
private readonly IUserSession $userSession,
|
||||
private readonly IGroupManager $groupManager,
|
||||
private readonly IUserManager $userManager,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne les informations de l'utilisateur courant et ses droits d'accès.
|
||||
*
|
||||
* @return array{uid: string, displayName: string, isAdmin: bool, canAccess: bool}
|
||||
*/
|
||||
public function getCurrentUserContext(): array {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
return [
|
||||
'uid' => '',
|
||||
'displayName' => 'Invité',
|
||||
'isAdmin' => false,
|
||||
'canAccess' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$uid = $user->getUID();
|
||||
$isAdmin = $this->groupManager->isAdmin($uid);
|
||||
|
||||
return [
|
||||
'uid' => $uid,
|
||||
'displayName' => $user->getDisplayName(),
|
||||
'isAdmin' => $isAdmin,
|
||||
'canAccess' => $this->canAccessCurrentUser(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuellement connecté est administrateur Nextcloud.
|
||||
*/
|
||||
public function isCurrentUserAdmin(): bool {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
return false;
|
||||
}
|
||||
return $this->groupManager->isAdmin($user->getUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur actuellement connecté est autorisé à accéder à l'application.
|
||||
*/
|
||||
public function canAccessCurrentUser(): bool {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$uid = $user->getUID();
|
||||
|
||||
// Les administrateurs Nextcloud ont toujours accès
|
||||
if ($this->groupManager->isAdmin($uid)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Si le mode est ouvert à tous les utilisateurs connectés
|
||||
if ($this->getAccessMode() === 'all_users') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Vérification dans la liste des utilisateurs explicitement autorisés
|
||||
$allowedUsers = $this->getAllowedUsers();
|
||||
if (in_array($uid, $allowedUsers, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Vérification dans la liste des groupes autorisés
|
||||
$allowedGroups = $this->getAllowedGroups();
|
||||
foreach ($allowedGroups as $gid) {
|
||||
if ($this->groupManager->isInGroup($uid, $gid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getAccessMode(): string {
|
||||
$mode = $this->config->getAppValue(Application::APP_ID, self::KEY_ACCESS_MODE, 'restricted');
|
||||
return $mode === 'all_users' ? 'all_users' : 'restricted';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getAllowedGroups(): array {
|
||||
$raw = $this->config->getAppValue(Application::APP_ID, self::KEY_ALLOWED_GROUPS, '[]');
|
||||
$decoded = json_decode($raw, true);
|
||||
if (!is_array($decoded)) {
|
||||
return [];
|
||||
}
|
||||
return array_values(array_unique(array_filter(array_map('strval', $decoded), static fn (string $v): bool => trim($v) !== '')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getAllowedUsers(): array {
|
||||
$raw = $this->config->getAppValue(Application::APP_ID, self::KEY_ALLOWED_USERS, '[]');
|
||||
$decoded = json_decode($raw, true);
|
||||
if (!is_array($decoded)) {
|
||||
return [];
|
||||
}
|
||||
return array_values(array_unique(array_filter(array_map('strval', $decoded), static fn (string $v): bool => trim($v) !== '')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère la configuration complète des permissions ainsi que la liste des groupes et utilisateurs Nextcloud.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getPermissionsData(): array {
|
||||
$groups = [];
|
||||
foreach ($this->groupManager->search('', 500, 0) as $group) {
|
||||
$gid = $group->getGID();
|
||||
$groups[] = [
|
||||
'gid' => $gid,
|
||||
'displayName' => method_exists($group, 'getDisplayName') ? $group->getDisplayName() : $gid,
|
||||
];
|
||||
}
|
||||
usort($groups, static fn (array $a, array $b): int => strcasecmp($a['displayName'], $b['displayName']));
|
||||
|
||||
$users = [];
|
||||
foreach ($this->userManager->search('', 500, 0) as $user) {
|
||||
$uid = $user->getUID();
|
||||
$users[] = [
|
||||
'uid' => $uid,
|
||||
'displayName' => $user->getDisplayName(),
|
||||
'email' => (string)($user->getEMailAddress() ?? ''),
|
||||
'isAdmin' => $this->groupManager->isAdmin($uid),
|
||||
'groups' => $this->groupManager->getUserGroupIds($user),
|
||||
];
|
||||
}
|
||||
usort($users, static fn (array $a, array $b): int => strcasecmp($a['displayName'], $b['displayName']));
|
||||
|
||||
return [
|
||||
'access_mode' => $this->getAccessMode(),
|
||||
'allowed_groups' => $this->getAllowedGroups(),
|
||||
'allowed_users' => $this->getAllowedUsers(),
|
||||
'available_groups' => $groups,
|
||||
'available_users' => $users,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre la configuration des permissions d'accès à l'application.
|
||||
*
|
||||
* @param array<int, string> $allowedGroups
|
||||
* @param array<int, string> $allowedUsers
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function savePermissionsData(
|
||||
string $accessMode,
|
||||
array $allowedGroups,
|
||||
array $allowedUsers,
|
||||
): array {
|
||||
$cleanMode = ($accessMode === 'all_users') ? 'all_users' : 'restricted';
|
||||
$cleanGroups = array_values(array_unique(array_filter(
|
||||
array_map(static fn ($g): string => trim((string)$g), $allowedGroups),
|
||||
static fn (string $v): bool => $v !== '',
|
||||
)));
|
||||
$cleanUsers = array_values(array_unique(array_filter(
|
||||
array_map(static fn ($u): string => trim((string)$u), $allowedUsers),
|
||||
static fn (string $v): bool => $v !== '',
|
||||
)));
|
||||
|
||||
$this->config->setAppValue(
|
||||
Application::APP_ID,
|
||||
self::KEY_ACCESS_MODE,
|
||||
$cleanMode,
|
||||
);
|
||||
$this->config->setAppValue(
|
||||
Application::APP_ID,
|
||||
self::KEY_ALLOWED_GROUPS,
|
||||
(string)json_encode($cleanGroups, JSON_UNESCAPED_UNICODE),
|
||||
);
|
||||
$this->config->setAppValue(
|
||||
Application::APP_ID,
|
||||
self::KEY_ALLOWED_USERS,
|
||||
(string)json_encode($cleanUsers, JSON_UNESCAPED_UNICODE),
|
||||
);
|
||||
|
||||
return $this->getPermissionsData();
|
||||
}
|
||||
}
|
||||
832
ul_tresorerie/lib/Service/TreasuryService.php
Normal file
832
ul_tresorerie/lib/Service/TreasuryService.php
Normal file
@ -0,0 +1,832 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\ULTresorerie\Service;
|
||||
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Mail\IMailer;
|
||||
use Throwable;
|
||||
|
||||
class TreasuryService {
|
||||
private const array MONTH_NAMES_FR = [
|
||||
1 => 'Janvier',
|
||||
2 => 'Février',
|
||||
3 => 'Mars',
|
||||
4 => 'Avril',
|
||||
5 => 'Mai',
|
||||
6 => 'Juin',
|
||||
7 => 'Juillet',
|
||||
8 => 'Août',
|
||||
9 => 'Septembre',
|
||||
10 => 'Octobre',
|
||||
11 => 'Novembre',
|
||||
12 => 'Décembre',
|
||||
];
|
||||
|
||||
private const array ALLOWED_METHODS = [
|
||||
'Virement',
|
||||
'Prélèvement',
|
||||
'Chèque',
|
||||
'Espèces',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly IDBConnection $db,
|
||||
private readonly IMailer $mailer,
|
||||
private readonly AspImportParser $importParser,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* S'assure que les 3 modèles de publipostage par défaut existent en base.
|
||||
*/
|
||||
public function ensureDefaultTemplates(): void {
|
||||
foreach (DefaultTemplates::getAll() as $tpl) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id')
|
||||
->from('ul_cgt_templates')
|
||||
->where($qb->expr()->eq('slug', $qb->createNamedParameter($tpl['slug'], IQueryBuilder::PARAM_STR)));
|
||||
$res = $qb->executeQuery();
|
||||
$row = $res->fetchAssociative();
|
||||
$res->closeCursor();
|
||||
|
||||
if ($row === false) {
|
||||
$ins = $this->db->getQueryBuilder();
|
||||
$ins->insert('ul_cgt_templates')
|
||||
->values([
|
||||
'slug' => $ins->createNamedParameter($tpl['slug'], IQueryBuilder::PARAM_STR),
|
||||
'title' => $ins->createNamedParameter($tpl['title'], IQueryBuilder::PARAM_STR),
|
||||
'subject' => $ins->createNamedParameter($tpl['subject'], IQueryBuilder::PARAM_STR),
|
||||
'body' => $ins->createNamedParameter($tpl['body'], IQueryBuilder::PARAM_STR),
|
||||
])
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère l'état complet pour une année donnée (syndiqués, cotisations, statistiques multi-années, modèles).
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getFullState(int $year): array {
|
||||
$this->ensureDefaultTemplates();
|
||||
|
||||
// 1. Syndiqués
|
||||
$qbMembers = $this->db->getQueryBuilder();
|
||||
$qbMembers->select('*')
|
||||
->from('ul_cgt_members')
|
||||
->orderBy('last_name', 'ASC')
|
||||
->addOrderBy('first_name', 'ASC');
|
||||
$resMembers = $qbMembers->executeQuery();
|
||||
$rawMembers = $resMembers->fetchAllAssociative();
|
||||
$resMembers->closeCursor();
|
||||
|
||||
$members = array_map([$this, 'normalizeMemberRow'], $rawMembers);
|
||||
|
||||
// 2. Cotisations de l'année sélectionnée
|
||||
$qbDues = $this->db->getQueryBuilder();
|
||||
$qbDues->select('*')
|
||||
->from('ul_cgt_dues')
|
||||
->where($qbDues->expr()->eq('year', $qbDues->createNamedParameter($year, IQueryBuilder::PARAM_INT)))
|
||||
->orderBy('month', 'ASC');
|
||||
$resDues = $qbDues->executeQuery();
|
||||
$rawDues = $resDues->fetchAllAssociative();
|
||||
$resDues->closeCursor();
|
||||
|
||||
$duesByMember = [];
|
||||
foreach ($rawDues as $dueRow) {
|
||||
$memberId = (int)$dueRow['member_id'];
|
||||
$month = (int)$dueRow['month'];
|
||||
if (!isset($duesByMember[$memberId])) {
|
||||
$duesByMember[$memberId] = [];
|
||||
}
|
||||
$duesByMember[$memberId][$month] = [
|
||||
'id' => (int)$dueRow['id'],
|
||||
'member_id' => $memberId,
|
||||
'year' => (int)$dueRow['year'],
|
||||
'month' => $month,
|
||||
'amount' => round((float)$dueRow['amount'], 2),
|
||||
'payment_method' => (string)$dueRow['payment_method'],
|
||||
'note' => (string)($dueRow['note'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
// 3. Modèles de publipostage
|
||||
$templates = $this->getTemplates();
|
||||
|
||||
// 4. Statistiques multi-années pour le Tableau de Bord
|
||||
$multiYearStats = $this->getMultiYearStats($year);
|
||||
|
||||
return [
|
||||
'year' => $year,
|
||||
'currentYear' => (int)date('Y'),
|
||||
'currentMonth' => (int)date('n'),
|
||||
'currentDay' => (int)date('j'),
|
||||
'members' => $members,
|
||||
'dues' => (object)$duesByMember,
|
||||
'templates' => $templates,
|
||||
'multiYearStats' => $multiYearStats,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule les agrégats mensuels, annuels et par mode de paiement sur toutes les années.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getMultiYearStats(int $focusYear): array {
|
||||
$currentYear = (int)date('Y');
|
||||
$qbAll = $this->db->getQueryBuilder();
|
||||
$qbAll->select('member_id', 'year', 'month', 'amount', 'payment_method')
|
||||
->from('ul_cgt_dues');
|
||||
$resAll = $qbAll->executeQuery();
|
||||
$allDues = $resAll->fetchAllAssociative();
|
||||
$resAll->closeCursor();
|
||||
|
||||
$yearsSet = [
|
||||
$currentYear - 2 => true,
|
||||
$currentYear - 1 => true,
|
||||
$currentYear => true,
|
||||
$focusYear - 1 => true,
|
||||
$focusYear => true,
|
||||
];
|
||||
|
||||
foreach ($allDues as $row) {
|
||||
$y = (int)$row['year'];
|
||||
if ($y >= 2000 && $y <= 2100) {
|
||||
$yearsSet[$y] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$availableYears = array_keys($yearsSet);
|
||||
sort($availableYears);
|
||||
|
||||
$byYear = [];
|
||||
$payingMembersByYear = [];
|
||||
|
||||
foreach ($availableYears as $y) {
|
||||
$monthlyTotals = [];
|
||||
$monthlyCounts = [];
|
||||
for ($m = 1; $m <= 12; $m++) {
|
||||
$monthlyTotals[$m] = 0.0;
|
||||
$monthlyCounts[$m] = 0;
|
||||
}
|
||||
|
||||
$byMethod = [];
|
||||
foreach (self::ALLOWED_METHODS as $methodName) {
|
||||
$byMethod[$methodName] = [
|
||||
'amount' => 0.0,
|
||||
'count' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$byYear[$y] = [
|
||||
'year' => $y,
|
||||
'totalAmount' => 0.0,
|
||||
'duesCount' => 0,
|
||||
'payingMembersCount' => 0,
|
||||
'monthlyTotals' => $monthlyTotals,
|
||||
'monthlyCounts' => $monthlyCounts,
|
||||
'byMethod' => $byMethod,
|
||||
];
|
||||
$payingMembersByYear[$y] = [];
|
||||
}
|
||||
|
||||
foreach ($allDues as $row) {
|
||||
$y = (int)$row['year'];
|
||||
$m = (int)$row['month'];
|
||||
$amt = round((float)$row['amount'], 2);
|
||||
$memberId = (int)$row['member_id'];
|
||||
$method = $this->sanitizeMethod((string)($row['payment_method'] ?? 'Virement'));
|
||||
|
||||
if (!isset($byYear[$y]) || $m < 1 || $m > 12 || $amt <= 0.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$byYear[$y]['totalAmount'] = round($byYear[$y]['totalAmount'] + $amt, 2);
|
||||
$byYear[$y]['duesCount']++;
|
||||
$byYear[$y]['monthlyTotals'][$m] = round($byYear[$y]['monthlyTotals'][$m] + $amt, 2);
|
||||
$byYear[$y]['monthlyCounts'][$m]++;
|
||||
$payingMembersByYear[$y][$memberId] = true;
|
||||
|
||||
$byYear[$y]['byMethod'][$method]['amount'] = round($byYear[$y]['byMethod'][$method]['amount'] + $amt, 2);
|
||||
$byYear[$y]['byMethod'][$method]['count']++;
|
||||
}
|
||||
|
||||
foreach ($availableYears as $y) {
|
||||
$byYear[$y]['payingMembersCount'] = count($payingMembersByYear[$y]);
|
||||
}
|
||||
|
||||
return [
|
||||
'availableYears' => $availableYears,
|
||||
'byYear' => (object)$byYear,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function getTemplates(): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('ul_cgt_templates')
|
||||
->orderBy('id', 'ASC');
|
||||
$res = $qb->executeQuery();
|
||||
$rows = $res->fetchAllAssociative();
|
||||
$res->closeCursor();
|
||||
|
||||
return array_map(static fn (array $r): array => [
|
||||
'id' => (int)$r['id'],
|
||||
'slug' => (string)$r['slug'],
|
||||
'title' => (string)$r['title'],
|
||||
'subject' => (string)$r['subject'],
|
||||
'body' => (string)$r['body'],
|
||||
], $rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un nouveau syndiqué.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function createMember(array $data): array {
|
||||
$cogitielId = trim((string)($data['cogitiel_id'] ?? ''));
|
||||
if ($cogitielId === '') {
|
||||
$cogitielId = 'UL09-' . date('YmdHis') . '-' . random_int(100, 999);
|
||||
}
|
||||
|
||||
$civility = trim((string)($data['civility'] ?? ''));
|
||||
$lastName = mb_strtoupper(trim((string)($data['last_name'] ?? '')), 'UTF-8');
|
||||
$firstName = trim((string)($data['first_name'] ?? ''));
|
||||
$phonePersonal = trim((string)($data['phone_personal'] ?? ''));
|
||||
$phonePro = trim((string)($data['phone_pro'] ?? ''));
|
||||
$address = trim((string)($data['address'] ?? ''));
|
||||
$postalCode = trim((string)($data['postal_code'] ?? ''));
|
||||
$city = mb_strtoupper(trim((string)($data['city'] ?? '')), 'UTF-8');
|
||||
$email = trim((string)($data['email'] ?? ''));
|
||||
$baseCode = trim((string)($data['base_code'] ?? '90045110520251341'));
|
||||
$baseName = trim((string)($data['base_name'] ?? 'INDIVIDUELS UL FOIX HAUTE-ARIEGE'));
|
||||
$defaultAmount = round(max(0.0, (float)($data['default_amount'] ?? 0.0)), 2);
|
||||
$defaultMethod = $this->sanitizeMethod((string)($data['default_method'] ?? 'Virement'));
|
||||
$isArchived = !empty($data['is_archived']) ? 1 : 0;
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->insert('ul_cgt_members')
|
||||
->values([
|
||||
'cogitiel_id' => $qb->createNamedParameter($cogitielId, IQueryBuilder::PARAM_STR),
|
||||
'civility' => $qb->createNamedParameter($civility, IQueryBuilder::PARAM_STR),
|
||||
'last_name' => $qb->createNamedParameter($lastName, IQueryBuilder::PARAM_STR),
|
||||
'first_name' => $qb->createNamedParameter($firstName, IQueryBuilder::PARAM_STR),
|
||||
'phone_personal' => $qb->createNamedParameter($phonePersonal, IQueryBuilder::PARAM_STR),
|
||||
'phone_pro' => $qb->createNamedParameter($phonePro, IQueryBuilder::PARAM_STR),
|
||||
'address' => $qb->createNamedParameter($address, IQueryBuilder::PARAM_STR),
|
||||
'postal_code' => $qb->createNamedParameter($postalCode, IQueryBuilder::PARAM_STR),
|
||||
'city' => $qb->createNamedParameter($city, IQueryBuilder::PARAM_STR),
|
||||
'email' => $qb->createNamedParameter($email, IQueryBuilder::PARAM_STR),
|
||||
'base_code' => $qb->createNamedParameter($baseCode, IQueryBuilder::PARAM_STR),
|
||||
'base_name' => $qb->createNamedParameter($baseName, IQueryBuilder::PARAM_STR),
|
||||
'default_amount' => $qb->createNamedParameter(number_format($defaultAmount, 2, '.', ''), IQueryBuilder::PARAM_STR),
|
||||
'default_method' => $qb->createNamedParameter($defaultMethod, IQueryBuilder::PARAM_STR),
|
||||
'is_archived' => $qb->createNamedParameter($isArchived, IQueryBuilder::PARAM_INT),
|
||||
])
|
||||
->executeStatement();
|
||||
|
||||
$newId = (int)$this->db->lastInsertId('*PREFIX*ul_cgt_members');
|
||||
return $this->getMemberById($newId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour la fiche d'un syndiqué existant.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function updateMember(int $id, array $data): array {
|
||||
$cogitielId = trim((string)($data['cogitiel_id'] ?? ''));
|
||||
$civility = trim((string)($data['civility'] ?? ''));
|
||||
$lastName = mb_strtoupper(trim((string)($data['last_name'] ?? '')), 'UTF-8');
|
||||
$firstName = trim((string)($data['first_name'] ?? ''));
|
||||
$phonePersonal = trim((string)($data['phone_personal'] ?? ''));
|
||||
$phonePro = trim((string)($data['phone_pro'] ?? ''));
|
||||
$address = trim((string)($data['address'] ?? ''));
|
||||
$postalCode = trim((string)($data['postal_code'] ?? ''));
|
||||
$city = mb_strtoupper(trim((string)($data['city'] ?? '')), 'UTF-8');
|
||||
$email = trim((string)($data['email'] ?? ''));
|
||||
$baseCode = trim((string)($data['base_code'] ?? ''));
|
||||
$baseName = trim((string)($data['base_name'] ?? 'INDIVIDUELS UL FOIX HAUTE-ARIEGE'));
|
||||
$defaultAmount = round(max(0.0, (float)($data['default_amount'] ?? 0.0)), 2);
|
||||
$defaultMethod = $this->sanitizeMethod((string)($data['default_method'] ?? 'Virement'));
|
||||
$isArchived = !empty($data['is_archived']) ? 1 : 0;
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->update('ul_cgt_members')
|
||||
->set('civility', $qb->createNamedParameter($civility, IQueryBuilder::PARAM_STR))
|
||||
->set('last_name', $qb->createNamedParameter($lastName, IQueryBuilder::PARAM_STR))
|
||||
->set('first_name', $qb->createNamedParameter($firstName, IQueryBuilder::PARAM_STR))
|
||||
->set('phone_personal', $qb->createNamedParameter($phonePersonal, IQueryBuilder::PARAM_STR))
|
||||
->set('phone_pro', $qb->createNamedParameter($phonePro, IQueryBuilder::PARAM_STR))
|
||||
->set('address', $qb->createNamedParameter($address, IQueryBuilder::PARAM_STR))
|
||||
->set('postal_code', $qb->createNamedParameter($postalCode, IQueryBuilder::PARAM_STR))
|
||||
->set('city', $qb->createNamedParameter($city, IQueryBuilder::PARAM_STR))
|
||||
->set('email', $qb->createNamedParameter($email, IQueryBuilder::PARAM_STR))
|
||||
->set('base_code', $qb->createNamedParameter($baseCode, IQueryBuilder::PARAM_STR))
|
||||
->set('base_name', $qb->createNamedParameter($baseName, IQueryBuilder::PARAM_STR))
|
||||
->set('default_amount', $qb->createNamedParameter(number_format($defaultAmount, 2, '.', ''), IQueryBuilder::PARAM_STR))
|
||||
->set('default_method', $qb->createNamedParameter($defaultMethod, IQueryBuilder::PARAM_STR))
|
||||
->set('is_archived', $qb->createNamedParameter($isArchived, IQueryBuilder::PARAM_INT))
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
||||
|
||||
if ($cogitielId !== '') {
|
||||
$qb->set('cogitiel_id', $qb->createNamedParameter($cogitielId, IQueryBuilder::PARAM_STR));
|
||||
}
|
||||
|
||||
$qb->executeStatement();
|
||||
|
||||
return $this->getMemberById($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un syndiqué et toutes ses cotisations rattachées.
|
||||
*/
|
||||
public function deleteMember(int $id): void {
|
||||
$this->db->beginTransaction();
|
||||
try {
|
||||
$qbDues = $this->db->getQueryBuilder();
|
||||
$qbDues->delete('ul_cgt_dues')
|
||||
->where($qbDues->expr()->eq('member_id', $qbDues->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||
->executeStatement();
|
||||
|
||||
$qbMember = $this->db->getQueryBuilder();
|
||||
$qbMember->delete('ul_cgt_members')
|
||||
->where($qbMember->expr()->eq('id', $qbMember->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||
->executeStatement();
|
||||
|
||||
$this->db->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Importe le contenu d'un fichier .asp CoGétise avec Upsert sur cogitiel_id.
|
||||
* Ne remplace jamais default_amount, default_method ni les cotisations existantes.
|
||||
*
|
||||
* @return array{inserted: int, updated: int, total: int}
|
||||
*/
|
||||
public function importAspContent(string $rawContent): array {
|
||||
$parsedRows = $this->importParser->parse($rawContent);
|
||||
$inserted = 0;
|
||||
$updated = 0;
|
||||
|
||||
$this->db->beginTransaction();
|
||||
try {
|
||||
foreach ($parsedRows as $row) {
|
||||
$qbFind = $this->db->getQueryBuilder();
|
||||
$qbFind->select('id')
|
||||
->from('ul_cgt_members')
|
||||
->where($qbFind->expr()->eq('cogitiel_id', $qbFind->createNamedParameter($row['cogitiel_id'], IQueryBuilder::PARAM_STR)));
|
||||
$res = $qbFind->executeQuery();
|
||||
$existing = $res->fetchAssociative();
|
||||
$res->closeCursor();
|
||||
|
||||
if ($existing !== false) {
|
||||
// Mise à jour administrative sans écraser default_amount ni default_method
|
||||
$qbUpd = $this->db->getQueryBuilder();
|
||||
$qbUpd->update('ul_cgt_members')
|
||||
->set('civility', $qbUpd->createNamedParameter($row['civility'], IQueryBuilder::PARAM_STR))
|
||||
->set('last_name', $qbUpd->createNamedParameter($row['last_name'], IQueryBuilder::PARAM_STR))
|
||||
->set('first_name', $qbUpd->createNamedParameter($row['first_name'], IQueryBuilder::PARAM_STR))
|
||||
->set('phone_personal', $qbUpd->createNamedParameter($row['phone_personal'], IQueryBuilder::PARAM_STR))
|
||||
->set('phone_pro', $qbUpd->createNamedParameter($row['phone_pro'], IQueryBuilder::PARAM_STR))
|
||||
->set('address', $qbUpd->createNamedParameter($row['address'], IQueryBuilder::PARAM_STR))
|
||||
->set('postal_code', $qbUpd->createNamedParameter($row['postal_code'], IQueryBuilder::PARAM_STR))
|
||||
->set('city', $qbUpd->createNamedParameter($row['city'], IQueryBuilder::PARAM_STR))
|
||||
->set('email', $qbUpd->createNamedParameter($row['email'], IQueryBuilder::PARAM_STR))
|
||||
->set('base_code', $qbUpd->createNamedParameter($row['base_code'], IQueryBuilder::PARAM_STR))
|
||||
->set('base_name', $qbUpd->createNamedParameter($row['base_name'], IQueryBuilder::PARAM_STR))
|
||||
->set('is_archived', $qbUpd->createNamedParameter($row['is_archived'], IQueryBuilder::PARAM_INT))
|
||||
->where($qbUpd->expr()->eq('id', $qbUpd->createNamedParameter((int)$existing['id'], IQueryBuilder::PARAM_INT)))
|
||||
->executeStatement();
|
||||
$updated++;
|
||||
} else {
|
||||
$qbIns = $this->db->getQueryBuilder();
|
||||
$qbIns->insert('ul_cgt_members')
|
||||
->values([
|
||||
'cogitiel_id' => $qbIns->createNamedParameter($row['cogitiel_id'], IQueryBuilder::PARAM_STR),
|
||||
'civility' => $qbIns->createNamedParameter($row['civility'], IQueryBuilder::PARAM_STR),
|
||||
'last_name' => $qbIns->createNamedParameter($row['last_name'], IQueryBuilder::PARAM_STR),
|
||||
'first_name' => $qbIns->createNamedParameter($row['first_name'], IQueryBuilder::PARAM_STR),
|
||||
'phone_personal' => $qbIns->createNamedParameter($row['phone_personal'], IQueryBuilder::PARAM_STR),
|
||||
'phone_pro' => $qbIns->createNamedParameter($row['phone_pro'], IQueryBuilder::PARAM_STR),
|
||||
'address' => $qbIns->createNamedParameter($row['address'], IQueryBuilder::PARAM_STR),
|
||||
'postal_code' => $qbIns->createNamedParameter($row['postal_code'], IQueryBuilder::PARAM_STR),
|
||||
'city' => $qbIns->createNamedParameter($row['city'], IQueryBuilder::PARAM_STR),
|
||||
'email' => $qbIns->createNamedParameter($row['email'], IQueryBuilder::PARAM_STR),
|
||||
'base_code' => $qbIns->createNamedParameter($row['base_code'], IQueryBuilder::PARAM_STR),
|
||||
'base_name' => $qbIns->createNamedParameter($row['base_name'], IQueryBuilder::PARAM_STR),
|
||||
'default_amount' => $qbIns->createNamedParameter('0.00', IQueryBuilder::PARAM_STR),
|
||||
'default_method' => $qbIns->createNamedParameter('Virement', IQueryBuilder::PARAM_STR),
|
||||
'is_archived' => $qbIns->createNamedParameter($row['is_archived'], IQueryBuilder::PARAM_INT),
|
||||
])
|
||||
->executeStatement();
|
||||
$inserted++;
|
||||
}
|
||||
}
|
||||
$this->db->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return [
|
||||
'inserted' => $inserted,
|
||||
'updated' => $updated,
|
||||
'total' => $inserted + $updated,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre (Upsert) une cotisation sur un ou plusieurs mois consécutifs.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function saveDues(
|
||||
int $memberId,
|
||||
int $year,
|
||||
int $monthStart,
|
||||
int $monthEnd,
|
||||
float $amount,
|
||||
string $paymentMethod,
|
||||
?string $note = null,
|
||||
): array {
|
||||
$monthStart = max(1, min(12, $monthStart));
|
||||
$monthEnd = max($monthStart, min(12, $monthEnd));
|
||||
$amount = round(max(0.0, $amount), 2);
|
||||
$method = $this->sanitizeMethod($paymentMethod);
|
||||
$cleanNote = $note !== null ? trim($note) : '';
|
||||
$savedDues = [];
|
||||
|
||||
$this->db->beginTransaction();
|
||||
try {
|
||||
for ($month = $monthStart; $month <= $monthEnd; $month++) {
|
||||
$qbFind = $this->db->getQueryBuilder();
|
||||
$qbFind->select('id')
|
||||
->from('ul_cgt_dues')
|
||||
->where($qbFind->expr()->eq('member_id', $qbFind->createNamedParameter($memberId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qbFind->expr()->eq('year', $qbFind->createNamedParameter($year, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qbFind->expr()->eq('month', $qbFind->createNamedParameter($month, IQueryBuilder::PARAM_INT)));
|
||||
$res = $qbFind->executeQuery();
|
||||
$existing = $res->fetchAssociative();
|
||||
$res->closeCursor();
|
||||
|
||||
if ($existing !== false) {
|
||||
$dueId = (int)$existing['id'];
|
||||
$qbUpd = $this->db->getQueryBuilder();
|
||||
$qbUpd->update('ul_cgt_dues')
|
||||
->set('amount', $qbUpd->createNamedParameter(number_format($amount, 2, '.', ''), IQueryBuilder::PARAM_STR))
|
||||
->set('payment_method', $qbUpd->createNamedParameter($method, IQueryBuilder::PARAM_STR))
|
||||
->set('note', $qbUpd->createNamedParameter($cleanNote, IQueryBuilder::PARAM_STR))
|
||||
->where($qbUpd->expr()->eq('id', $qbUpd->createNamedParameter($dueId, IQueryBuilder::PARAM_INT)))
|
||||
->executeStatement();
|
||||
} else {
|
||||
$qbIns = $this->db->getQueryBuilder();
|
||||
$qbIns->insert('ul_cgt_dues')
|
||||
->values([
|
||||
'member_id' => $qbIns->createNamedParameter($memberId, IQueryBuilder::PARAM_INT),
|
||||
'year' => $qbIns->createNamedParameter($year, IQueryBuilder::PARAM_INT),
|
||||
'month' => $qbIns->createNamedParameter($month, IQueryBuilder::PARAM_INT),
|
||||
'amount' => $qbIns->createNamedParameter(number_format($amount, 2, '.', ''), IQueryBuilder::PARAM_STR),
|
||||
'payment_method' => $qbIns->createNamedParameter($method, IQueryBuilder::PARAM_STR),
|
||||
'note' => $qbIns->createNamedParameter($cleanNote, IQueryBuilder::PARAM_STR),
|
||||
])
|
||||
->executeStatement();
|
||||
$dueId = (int)$this->db->lastInsertId('*PREFIX*ul_cgt_dues');
|
||||
}
|
||||
|
||||
$savedDues[$month] = [
|
||||
'id' => $dueId,
|
||||
'member_id' => $memberId,
|
||||
'year' => $year,
|
||||
'month' => $month,
|
||||
'amount' => $amount,
|
||||
'payment_method' => $method,
|
||||
'note' => $cleanNote,
|
||||
];
|
||||
}
|
||||
|
||||
// Met à jour le montant et le mode de référence du syndiqué si un montant > 0 est saisi
|
||||
if ($amount > 0) {
|
||||
$qbMbr = $this->db->getQueryBuilder();
|
||||
$qbMbr->update('ul_cgt_members')
|
||||
->set('default_amount', $qbMbr->createNamedParameter(number_format($amount, 2, '.', ''), IQueryBuilder::PARAM_STR))
|
||||
->set('default_method', $qbMbr->createNamedParameter($method, IQueryBuilder::PARAM_STR))
|
||||
->where($qbMbr->expr()->eq('id', $qbMbr->createNamedParameter($memberId, IQueryBuilder::PARAM_INT)))
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
$this->db->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $savedDues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime la cotisation d'un mois donné pour un syndiqué.
|
||||
*/
|
||||
public function deleteDue(int $memberId, int $year, int $month): void {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete('ul_cgt_dues')
|
||||
->where($qb->expr()->eq('member_id', $qb->createNamedParameter($memberId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->eq('year', $qb->createNamedParameter($year, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->eq('month', $qb->createNamedParameter($month, IQueryBuilder::PARAM_INT)))
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sauvegarde les modifications d'un modèle de publipostage.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function saveTemplate(string $slug, string $title, string $subject, string $body): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->update('ul_cgt_templates')
|
||||
->set('title', $qb->createNamedParameter(trim($title), IQueryBuilder::PARAM_STR))
|
||||
->set('subject', $qb->createNamedParameter(trim($subject), IQueryBuilder::PARAM_STR))
|
||||
->set('body', $qb->createNamedParameter($body, IQueryBuilder::PARAM_STR))
|
||||
->where($qb->expr()->eq('slug', $qb->createNamedParameter($slug, IQueryBuilder::PARAM_STR)))
|
||||
->executeStatement();
|
||||
|
||||
$qbGet = $this->db->getQueryBuilder();
|
||||
$qbGet->select('*')
|
||||
->from('ul_cgt_templates')
|
||||
->where($qbGet->expr()->eq('slug', $qbGet->createNamedParameter($slug, IQueryBuilder::PARAM_STR)));
|
||||
$res = $qbGet->executeQuery();
|
||||
$row = $res->fetchAssociative();
|
||||
$res->closeCursor();
|
||||
|
||||
if ($row === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'slug' => (string)$row['slug'],
|
||||
'title' => (string)$row['title'],
|
||||
'subject' => (string)$row['subject'],
|
||||
'body' => (string)$row['body'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialise les 3 modèles de publipostage avec leur contenu d'origine.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function resetTemplates(): array {
|
||||
foreach (DefaultTemplates::getAll() as $tpl) {
|
||||
$this->saveTemplate($tpl['slug'], $tpl['title'], $tpl['subject'], $tpl['body']);
|
||||
}
|
||||
return $this->getTemplates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie par e-mail via OCP\Mail\IMailer les courriers personnalisés aux syndiqués sélectionnés.
|
||||
*
|
||||
* @param array<int, int> $memberIds
|
||||
* @return array{sent: int, skipped_no_email: int, errors: array<int, string>}
|
||||
*/
|
||||
public function sendMailingEmails(
|
||||
int $year,
|
||||
array $memberIds,
|
||||
string $subjectTemplate,
|
||||
string $bodyTemplate,
|
||||
): array {
|
||||
$state = $this->getFullState($year);
|
||||
$membersById = [];
|
||||
foreach ($state['members'] as $m) {
|
||||
$membersById[(int)$m['id']] = $m;
|
||||
}
|
||||
$duesMap = (array)$state['dues'];
|
||||
|
||||
$sent = 0;
|
||||
$skippedNoEmail = 0;
|
||||
$errors = [];
|
||||
|
||||
foreach ($memberIds as $rawId) {
|
||||
$memberId = (int)$rawId;
|
||||
if (!isset($membersById[$memberId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$member = $membersById[$memberId];
|
||||
$email = trim((string)($member['email'] ?? ''));
|
||||
if ($email === '' || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
|
||||
$skippedNoEmail++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$memberDues = $duesMap[$memberId] ?? [];
|
||||
$renderedSubject = $this->renderTemplateVariables($subjectTemplate, $member, $memberDues, $year);
|
||||
$renderedBody = $this->renderTemplateVariables($bodyTemplate, $member, $memberDues, $year);
|
||||
$htmlBody = $this->buildEmailHtml($renderedSubject, $renderedBody);
|
||||
|
||||
try {
|
||||
$recipientName = trim($member['first_name'] . ' ' . $member['last_name']);
|
||||
$message = $this->mailer->createMessage();
|
||||
$message->setSubject($renderedSubject);
|
||||
$message->setPlainBody($renderedBody);
|
||||
$message->setHtmlBody($htmlBody);
|
||||
$message->setTo([$email => $recipientName]);
|
||||
$failedRecipients = $this->mailer->send($message);
|
||||
if (!empty($failedRecipients)) {
|
||||
$errors[] = sprintf('%s (%s) : échec SMTP', $recipientName, $email);
|
||||
} else {
|
||||
$sent++;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$errors[] = sprintf('%s (%s) : %s', $member['last_name'], $email, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'sent' => $sent,
|
||||
'skipped_no_email' => $skippedNoEmail,
|
||||
'errors' => $errors,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remplace toutes les variables dynamiques {{...}} pour un syndiqué et une année donnés.
|
||||
*
|
||||
* @param array<string, mixed> $member
|
||||
* @param array<int, array<string, mixed>> $memberDues
|
||||
*/
|
||||
public function renderTemplateVariables(
|
||||
string $text,
|
||||
array $member,
|
||||
array $memberDues,
|
||||
int $year,
|
||||
): string {
|
||||
$currentYear = (int)date('Y');
|
||||
$currentMonth = (int)date('n');
|
||||
$currentDay = (int)date('j');
|
||||
if ($year < $currentYear) {
|
||||
$maxCheckMonth = 12;
|
||||
} elseif ($year > $currentYear) {
|
||||
$maxCheckMonth = 0;
|
||||
} else {
|
||||
$maxCheckMonth = ($currentDay > 10) ? $currentMonth : max(0, $currentMonth - 1);
|
||||
}
|
||||
|
||||
$totalPaye = 0.0;
|
||||
$nbMoisPayes = 0;
|
||||
$lastPaidAmount = 0.0;
|
||||
|
||||
for ($m = 1; $m <= 12; $m++) {
|
||||
$amt = isset($memberDues[$m]) ? (float)$memberDues[$m]['amount'] : 0.0;
|
||||
if ($amt > 0.0) {
|
||||
$totalPaye += $amt;
|
||||
$nbMoisPayes++;
|
||||
$lastPaidAmount = $amt;
|
||||
}
|
||||
}
|
||||
|
||||
$missingMonthsNames = [];
|
||||
for ($m = 1; $m <= $maxCheckMonth; $m++) {
|
||||
$amt = isset($memberDues[$m]) ? (float)$memberDues[$m]['amount'] : 0.0;
|
||||
if ($amt <= 0.0) {
|
||||
$missingMonthsNames[] = self::MONTH_NAMES_FR[$m];
|
||||
}
|
||||
}
|
||||
|
||||
$nbMoisManquants = count($missingMonthsNames);
|
||||
$moisManquantsListe = $nbMoisManquants > 0 ? implode(', ', $missingMonthsNames) : 'Aucun';
|
||||
|
||||
$cotisationHabituelle = (float)($member['default_amount'] ?? 0.0);
|
||||
if ($cotisationHabituelle <= 0.0 && $lastPaidAmount > 0.0) {
|
||||
$cotisationHabituelle = $lastPaidAmount;
|
||||
}
|
||||
|
||||
$creditImpot = round($totalPaye * 0.66, 2);
|
||||
$montantEstimeRetard = round($nbMoisManquants * $cotisationHabituelle, 2);
|
||||
|
||||
$addressParts = [];
|
||||
if (trim((string)$member['address']) !== '') {
|
||||
$addressParts[] = trim((string)$member['address']);
|
||||
}
|
||||
$cpCity = trim(trim((string)$member['postal_code']) . ' ' . trim((string)$member['city']));
|
||||
if ($cpCity !== '') {
|
||||
$addressParts[] = $cpCity;
|
||||
}
|
||||
$adresseComplete = $addressParts !== [] ? implode(' — ', $addressParts) : 'Adresse non renseignée';
|
||||
|
||||
$replacements = [
|
||||
'{{civilite}}' => (string)($member['civility'] ?? ''),
|
||||
'{{nom}}' => (string)($member['last_name'] ?? ''),
|
||||
'{{prenom}}' => (string)($member['first_name'] ?? ''),
|
||||
'{{adresse_complete}}' => $adresseComplete,
|
||||
'{{code_responsable}}' => (string)($member['cogitiel_id'] ?? ''),
|
||||
'{{annee}}' => (string)$year,
|
||||
'{{date_du_jour}}' => date('d/m/Y'),
|
||||
'{{total_paye}}' => $this->formatEuros($totalPaye),
|
||||
'{{credit_impot}}' => $this->formatEuros($creditImpot),
|
||||
'{{nb_mois_payes}}' => (string)$nbMoisPayes,
|
||||
'{{mois_manquants_liste}}' => $moisManquantsListe,
|
||||
'{{nb_mois_manquants}}' => (string)$nbMoisManquants,
|
||||
'{{cotisation_habituelle}}' => $this->formatEuros($cotisationHabituelle),
|
||||
'{{montant_estime_retard}}' => $this->formatEuros($montantEstimeRetard),
|
||||
];
|
||||
|
||||
return strtr($text, $replacements);
|
||||
}
|
||||
|
||||
private function formatEuros(float $amount): string {
|
||||
return number_format($amount, 2, ',', ' ') . ' €';
|
||||
}
|
||||
|
||||
private function buildEmailHtml(string $subject, string $plainBody): string {
|
||||
$escapedSubject = htmlspecialchars($subject, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$escapedBody = nl2br(htmlspecialchars($plainBody, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'));
|
||||
|
||||
return <<<HTML
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{$escapedSubject}</title>
|
||||
</head>
|
||||
<body style="font-family: Arial, Helvetica, sans-serif; color: #1f2937; line-height: 1.6; background-color: #f9fafb; padding: 24px;">
|
||||
<div style="max-width: 680px; margin: 0 auto; background: #ffffff; border: 1px solid #e5e7eb; border-top: 5px solid #d32f2f; border-radius: 6px; padding: 28px;">
|
||||
<div style="border-bottom: 2px solid #f3f4f6; padding-bottom: 14px; margin-bottom: 20px;">
|
||||
<strong style="color: #d32f2f; font-size: 18px;">UNION LOCALE CGT DE FOIX ET DE LA HAUTE-ARIÈGE</strong><br>
|
||||
<span style="color: #6b7280; font-size: 13px;">Suivi des Cotisations Syndicales — Isolés / Multipros</span>
|
||||
</div>
|
||||
<div style="font-size: 14px; color: #111827;">
|
||||
{$escapedBody}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getMemberById(int $id): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('ul_cgt_members')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
||||
$res = $qb->executeQuery();
|
||||
$row = $res->fetchAssociative();
|
||||
$res->closeCursor();
|
||||
|
||||
if ($row === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->normalizeMemberRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function normalizeMemberRow(array $row): array {
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'cogitiel_id' => (string)$row['cogitiel_id'],
|
||||
'civility' => (string)($row['civility'] ?? ''),
|
||||
'last_name' => (string)$row['last_name'],
|
||||
'first_name' => (string)$row['first_name'],
|
||||
'phone_personal' => (string)($row['phone_personal'] ?? ''),
|
||||
'phone_pro' => (string)($row['phone_pro'] ?? ''),
|
||||
'address' => (string)($row['address'] ?? ''),
|
||||
'postal_code' => (string)($row['postal_code'] ?? ''),
|
||||
'city' => (string)($row['city'] ?? ''),
|
||||
'email' => (string)($row['email'] ?? ''),
|
||||
'base_code' => (string)($row['base_code'] ?? ''),
|
||||
'base_name' => (string)($row['base_name'] ?? ''),
|
||||
'default_amount' => round((float)($row['default_amount'] ?? 0.0), 2),
|
||||
'default_method' => (string)($row['default_method'] ?? 'Virement'),
|
||||
'is_archived' => (int)($row['is_archived'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
private function sanitizeMethod(string $method): string {
|
||||
$trimmed = trim($method);
|
||||
if (in_array($trimmed, self::ALLOWED_METHODS, true)) {
|
||||
return $trimmed;
|
||||
}
|
||||
return 'Virement';
|
||||
}
|
||||
}
|
||||
847
ul_tresorerie/templates/main.php
Normal file
847
ul_tresorerie/templates/main.php
Normal file
@ -0,0 +1,847 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/** @var array $_ */
|
||||
script('ul_tresorerie', 'app_v2');
|
||||
style('ul_tresorerie', 'app_v2');
|
||||
|
||||
$currentYear = (string)($_['currentYear'] ?? date('Y'));
|
||||
$canAccess = (bool)($_['canAccess'] ?? false);
|
||||
$isAdmin = (bool)($_['isAdmin'] ?? false);
|
||||
$userUid = (string)($_['userUid'] ?? '');
|
||||
$userDisplayName = (string)($_['userDisplayName'] ?? $userUid);
|
||||
$logoUrl = image_path('ul_tresorerie', 'cgt_logo.svg');
|
||||
$bannerUrl = image_path('ul_tresorerie', 'banniere_ul.png');
|
||||
?>
|
||||
<div
|
||||
id="ul-tresorerie-app"
|
||||
class="ul-app-wrapper"
|
||||
data-initial-year="<?php p($currentYear); ?>"
|
||||
data-can-access="<?php p($canAccess ? '1' : '0'); ?>"
|
||||
data-is-admin="<?php p($isAdmin ? '1' : '0'); ?>"
|
||||
data-logo-url="<?php p($logoUrl); ?>"
|
||||
data-banner-url="<?php p($bannerUrl); ?>"
|
||||
>
|
||||
<?php if (!$canAccess): ?>
|
||||
<div class="ul-access-denied-wrapper">
|
||||
<div class="ul-access-denied-card">
|
||||
<img
|
||||
src="<?php p($bannerUrl); ?>"
|
||||
alt="Union Locale CGT Foix Haute-Ariège"
|
||||
class="ul-access-denied-banner"
|
||||
width="320"
|
||||
height="68"
|
||||
/>
|
||||
<div class="ul-access-denied-badge">🔒 Accès restreint — Trésorerie UL CGT</div>
|
||||
<h2>Vous n'avez pas encore accès à l'application UL Trésorerie</h2>
|
||||
<p>
|
||||
Cette application de suivi des cotisations syndicales est réservée aux trésoriers et membres habilités de
|
||||
l'<strong>Union Locale CGT de Foix et de la Haute-Ariège</strong>.
|
||||
</p>
|
||||
<div class="ul-access-denied-userbox">
|
||||
<span>Compte Nextcloud connecté :</span>
|
||||
<strong><?php p($userDisplayName); ?> (<code><?php p($userUid); ?></code>)</strong>
|
||||
</div>
|
||||
<p class="ul-access-denied-help">
|
||||
Demandez à un administrateur Nextcloud d'autoriser votre compte ou votre groupe depuis l'onglet
|
||||
<strong>« Accès & Permissions »</strong> de l'application.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<!-- Barre latérale gauche rétractable -->
|
||||
<div id="app-navigation" class="ul-no-print">
|
||||
<div class="ul-brand-header">
|
||||
<div class="ul-brand-banner-box">
|
||||
<img
|
||||
src="<?php p($bannerUrl); ?>"
|
||||
alt="Union Locale CGT Foix Haute-Ariège"
|
||||
class="ul-brand-banner-img"
|
||||
width="210"
|
||||
height="46"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="btn-close-sidebar"
|
||||
class="ul-sidebar-close-btn"
|
||||
data-action="toggle-sidebar"
|
||||
title="Fermer le volet latéral gauche"
|
||||
aria-label="Fermer le volet latéral gauche"
|
||||
>«</button>
|
||||
</div>
|
||||
<div class="ul-brand-subtitle-bar">
|
||||
<span>Suivi Cotisations — Isolés / Multipros</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-nav-section-title">Vues principales</div>
|
||||
<ul class="ul-nav-list">
|
||||
<li>
|
||||
<a href="#grid" id="nav-view-grid" class="ul-nav-item active" data-view="grid">
|
||||
<span class="ul-nav-icon">📊</span>
|
||||
<span class="ul-nav-label">Grille des cotisations</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#dashboard" id="nav-view-dashboard" class="ul-nav-item" data-view="dashboard">
|
||||
<span class="ul-nav-icon">📈</span>
|
||||
<span class="ul-nav-label">Tableau de bord</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#mailing" id="nav-view-mailing" class="ul-nav-item" data-view="mailing">
|
||||
<span class="ul-nav-icon">✉️</span>
|
||||
<span class="ul-nav-label">Publipostage & Courriers</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php if ($isAdmin): ?>
|
||||
<li>
|
||||
<a href="#permissions" id="nav-view-permissions" class="ul-nav-item" data-view="permissions">
|
||||
<span class="ul-nav-icon">🔒</span>
|
||||
<span class="ul-nav-label">Accès & Permissions</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
|
||||
<div class="ul-nav-section-title">Filtres & Alertes (<span id="nav-year-indicator"><?php p($currentYear); ?></span>)</div>
|
||||
<ul class="ul-nav-list ul-filter-list">
|
||||
<li>
|
||||
<a href="#filter-all" id="filter-btn-all" class="ul-filter-item active" data-filter="all">
|
||||
<span class="ul-filter-label">👥 Tous les syndiqués</span>
|
||||
<span id="count-all" class="ul-counter">0</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#filter-uptodate" id="filter-btn-uptodate" class="ul-filter-item" data-filter="uptodate">
|
||||
<span class="ul-filter-label">🟢 Cotisations à jour</span>
|
||||
<span id="count-uptodate" class="ul-counter ul-counter-green">0</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#filter-orange" id="filter-btn-orange" class="ul-filter-item" data-filter="orange">
|
||||
<span class="ul-filter-label">🟠 Cotisations pas à jour (<span id="count-orange-inline">0</span>)</span>
|
||||
<span id="count-orange" class="ul-counter ul-counter-orange">0</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#filter-red" id="filter-btn-red" class="ul-filter-item" data-filter="red">
|
||||
<span class="ul-filter-label">🔴 Aucun versement sur l'année (<span id="count-red-inline">0</span>)</span>
|
||||
<span id="count-red" class="ul-counter ul-counter-red">0</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="ul-nav-summary-card">
|
||||
<div class="ul-summary-row">
|
||||
<span>Année active :</span>
|
||||
<strong id="summary-year"><?php p($currentYear); ?></strong>
|
||||
</div>
|
||||
<div class="ul-summary-row">
|
||||
<span>Syndiqués actifs :</span>
|
||||
<strong id="summary-active-count">0</strong>
|
||||
</div>
|
||||
<div class="ul-summary-row ul-summary-total">
|
||||
<span>Total perçu :</span>
|
||||
<strong id="summary-grand-total">0,00 €</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-nav-actions">
|
||||
<button type="button" id="btn-nav-new-member" class="ul-btn ul-btn-secondary ul-btn-block">
|
||||
+ Nouveau syndiqué
|
||||
</button>
|
||||
<button type="button" id="btn-nav-import-asp" class="ul-btn ul-btn-outline ul-btn-block">
|
||||
📥 Importer fichier .asp
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zone centrale #app-content -->
|
||||
<div id="app-content" class="ul-no-print">
|
||||
<!-- Bannière de notification -->
|
||||
<div id="ul-toast" class="ul-toast hidden" role="status" aria-live="polite"></div>
|
||||
|
||||
<!-- VUE 1 : GRILLE ANNUELLE DES COTISATIONS -->
|
||||
<section id="view-grid" class="ul-view-section">
|
||||
<header class="ul-toolbar">
|
||||
<div class="ul-toolbar-left">
|
||||
<button
|
||||
type="button"
|
||||
class="ul-btn-toggle-sidebar"
|
||||
data-action="toggle-sidebar"
|
||||
title="Ouvrir / Fermer le volet latéral gauche"
|
||||
aria-label="Ouvrir ou fermer le volet latéral gauche"
|
||||
>☰</button>
|
||||
|
||||
<div class="ul-year-selector" aria-label="Sélecteur d'année comptable">
|
||||
<button type="button" id="btn-prev-year" class="ul-btn-icon" title="Année précédente"><</button>
|
||||
<span id="label-current-year" class="ul-year-label"><?php p($currentYear); ?></span>
|
||||
<button type="button" id="btn-next-year" class="ul-btn-icon" title="Année suivante">></button>
|
||||
</div>
|
||||
|
||||
<div class="ul-search-box">
|
||||
<input
|
||||
type="search"
|
||||
id="input-search"
|
||||
class="ul-search-input"
|
||||
placeholder="Rechercher nom, prénom, ville, e-mail, n° CoGétise…"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label class="ul-checkbox-label" for="chk-show-archived">
|
||||
<input type="checkbox" id="chk-show-archived" />
|
||||
<span>Afficher les syndiqués archivés</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="ul-toolbar-right">
|
||||
<button type="button" id="btn-new-member" class="ul-btn ul-btn-primary">
|
||||
+ Nouveau syndiqué
|
||||
</button>
|
||||
<button type="button" id="btn-import-asp" class="ul-btn ul-btn-secondary">
|
||||
📥 Importer fichier .asp
|
||||
</button>
|
||||
<input type="file" id="input-import-file" class="hidden" accept=".asp,.csv,.txt" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="ul-legend-bar">
|
||||
<span class="ul-legend-item">
|
||||
<span class="ul-legend-swatch ul-swatch-paid"></span> Case verte : cotisation réglée (clic = modifier)
|
||||
</span>
|
||||
<span class="ul-legend-item">
|
||||
<span class="ul-legend-swatch ul-swatch-empty"></span> Case grise : non cotisé (clic = saisie pré-remplie + Entrée)
|
||||
</span>
|
||||
<span class="ul-legend-item">
|
||||
<strong>V</strong> Virement · <strong>P</strong> Prélèvement · <strong>C</strong> Chèque · <strong>E</strong> Espèces
|
||||
</span>
|
||||
<span id="active-filter-indicator" class="ul-active-filter-pill hidden"></span>
|
||||
</div>
|
||||
|
||||
<div class="ul-grid-container">
|
||||
<table id="dues-table" class="ul-dues-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-sticky-left">Syndiqué (Nom, Prénom & Statut)</th>
|
||||
<th class="col-month">Janv.</th>
|
||||
<th class="col-month">Févr.</th>
|
||||
<th class="col-month">Mars</th>
|
||||
<th class="col-month">Avr.</th>
|
||||
<th class="col-month">Mai</th>
|
||||
<th class="col-month">Juin</th>
|
||||
<th class="col-month">Juil.</th>
|
||||
<th class="col-month">Août</th>
|
||||
<th class="col-month">Sept.</th>
|
||||
<th class="col-month">Oct.</th>
|
||||
<th class="col-month">Nov.</th>
|
||||
<th class="col-month">Déc.</th>
|
||||
<th class="col-sticky-right">Total Annuel</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="dues-tbody">
|
||||
<tr>
|
||||
<td colspan="14" class="ul-empty-cell">Chargement des données de trésorerie…</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot id="dues-tfoot">
|
||||
<tr class="ul-tfoot-row">
|
||||
<th class="col-sticky-left">Total Mensuel (<span id="tfoot-members-count">0</span> affichés)</th>
|
||||
<th id="foot-month-1" class="col-month-total">0,00 €</th>
|
||||
<th id="foot-month-2" class="col-month-total">0,00 €</th>
|
||||
<th id="foot-month-3" class="ul-month-total col-month-total">0,00 €</th>
|
||||
<th id="foot-month-4" class="col-month-total">0,00 €</th>
|
||||
<th id="foot-month-5" class="col-month-total">0,00 €</th>
|
||||
<th id="foot-month-6" class="col-month-total">0,00 €</th>
|
||||
<th id="foot-month-7" class="col-month-total">0,00 €</th>
|
||||
<th id="foot-month-8" class="col-month-total">0,00 €</th>
|
||||
<th id="foot-month-9" class="col-month-total">0,00 €</th>
|
||||
<th id="foot-month-10" class="col-month-total">0,00 €</th>
|
||||
<th id="foot-month-11" class="col-month-total">0,00 €</th>
|
||||
<th id="foot-month-12" class="col-month-total">0,00 €</th>
|
||||
<th id="foot-grand-total" class="col-sticky-right col-grand-total">0,00 €</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- VUE 2 : TABLEAU DE BORD & COMPARAISON PLURIANNUELLE (1 SEUL ÉCRAN) -->
|
||||
<section id="view-dashboard" class="ul-view-section hidden">
|
||||
<header class="ul-toolbar ul-dashboard-toolbar">
|
||||
<div class="ul-toolbar-left">
|
||||
<button
|
||||
type="button"
|
||||
class="ul-btn-toggle-sidebar"
|
||||
data-action="toggle-sidebar"
|
||||
title="Ouvrir / Fermer le volet latéral gauche"
|
||||
aria-label="Ouvrir ou fermer le volet latéral gauche"
|
||||
>☰</button>
|
||||
<img src="<?php p($logoUrl); ?>" alt="CGT" class="ul-toolbar-cgt-logo" width="28" height="36" />
|
||||
<div>
|
||||
<h2 class="ul-section-heading">
|
||||
Tableau de bord & Évolution des Cotisations
|
||||
</h2>
|
||||
<span class="ul-dashboard-subtitle">Vue synthétique annuelle et comparative inter-années — UL CGT Foix Haute-Ariège</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ul-toolbar-right">
|
||||
<div class="ul-dash-control-group">
|
||||
<label class="ul-field-label-inline">Année analysée :</label>
|
||||
<div class="ul-year-selector">
|
||||
<button type="button" id="btn-dash-prev-year" class="ul-btn-icon" title="Année précédente"><</button>
|
||||
<span id="dash-label-year" class="ul-year-label"><?php p($currentYear); ?></span>
|
||||
<button type="button" id="btn-dash-next-year" class="ul-btn-icon" title="Année suivante">></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-dash-control-group">
|
||||
<label for="dash-select-compare-year" class="ul-field-label-inline">Comparer avec :</label>
|
||||
<select id="dash-select-compare-year" class="ul-select ul-select-sm ul-dash-compare-select"></select>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="ul-dashboard-content">
|
||||
<!-- Ligne 1 : 5 Chiffres Clés (KPIs) -->
|
||||
<div class="ul-kpi-grid">
|
||||
<div class="ul-kpi-card ul-kpi-card-primary">
|
||||
<div class="ul-kpi-top">
|
||||
<span class="ul-kpi-label">Total encaissé (<span id="kpi-year-label"><?php p($currentYear); ?></span>)</span>
|
||||
<span id="kpi-total-diff" class="ul-kpi-trend">0 %</span>
|
||||
</div>
|
||||
<div id="kpi-total-amount" class="ul-kpi-value">0,00 €</div>
|
||||
<div id="kpi-total-sub" class="ul-kpi-sub">Année de comparaison : 0,00 €</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-kpi-card">
|
||||
<div class="ul-kpi-top">
|
||||
<span class="ul-kpi-label">Moyenne mensuelle</span>
|
||||
<span class="ul-kpi-icon">📅</span>
|
||||
</div>
|
||||
<div id="kpi-monthly-avg" class="ul-kpi-value">0,00 €</div>
|
||||
<div id="kpi-avg-due" class="ul-kpi-sub">Cotisation moy. : 0,00 € / mois</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-kpi-card">
|
||||
<div class="ul-kpi-top">
|
||||
<span class="ul-kpi-label">Syndiqués à jour</span>
|
||||
<span class="ul-kpi-icon">🟢</span>
|
||||
</div>
|
||||
<div id="kpi-recovery-rate" class="ul-kpi-value">0 %</div>
|
||||
<div id="kpi-recovery-sub" class="ul-kpi-sub">0 à jour sur 0 syndiqués actifs</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-kpi-card">
|
||||
<div class="ul-kpi-top">
|
||||
<span class="ul-kpi-label">Retards estimés</span>
|
||||
<span class="ul-kpi-icon">🟠</span>
|
||||
</div>
|
||||
<div id="kpi-estimated-arrears" class="ul-kpi-value ul-kpi-value-orange">0,00 €</div>
|
||||
<div id="kpi-arrears-sub" class="ul-kpi-sub">0 mois manquant(s) cumulé(s)</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-kpi-card">
|
||||
<div class="ul-kpi-top">
|
||||
<span class="ul-kpi-label">Crédit d'impôt 66 %</span>
|
||||
<span class="ul-kpi-icon">🧾</span>
|
||||
</div>
|
||||
<div id="kpi-tax-credit" class="ul-kpi-value">0,00 €</div>
|
||||
<div id="kpi-dues-count" class="ul-kpi-sub">0 mensualité(s) pointée(s)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ligne 2 : Graphiques principaux -->
|
||||
<div class="ul-dash-charts-grid">
|
||||
<!-- Graphique 1 : Évolution mensuelle & Cumul annuel (Année N vs Année comparée) -->
|
||||
<div class="ul-card ul-dash-chart-card ul-dash-chart-main">
|
||||
<div class="ul-card-header-flex">
|
||||
<div>
|
||||
<h3 class="ul-card-title">
|
||||
Évolution mensuelle & Cumul annuel (<span id="chart-main-year-label"><?php p($currentYear); ?></span> vs <span id="chart-main-comp-label">N-1</span>)
|
||||
</h3>
|
||||
<span class="ul-chart-subtitle">Barres : encaissements mensuels · Courbes : cumul progressif sur l'année</span>
|
||||
</div>
|
||||
<div class="ul-chart-legend">
|
||||
<span class="ul-chart-legend-item">
|
||||
<span class="ul-legend-dot ul-dot-red"></span> <span id="legend-year-curr"><?php p($currentYear); ?></span> (Mensuel & Cumul)
|
||||
</span>
|
||||
<span class="ul-chart-legend-item">
|
||||
<span class="ul-legend-dot ul-dot-slate"></span> <span id="legend-year-comp">N-1</span> (Mensuel & Cumul)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dash-chart-monthly-comparison" class="ul-svg-chart-wrap"></div>
|
||||
</div>
|
||||
|
||||
<!-- Colonne droite : Comparaison multi-années + Modes de paiement & Statuts -->
|
||||
<div class="ul-dash-side-charts">
|
||||
<div class="ul-card ul-dash-chart-card">
|
||||
<div class="ul-card-header-flex">
|
||||
<h3 class="ul-card-title">Comparaison pluriannuelle</h3>
|
||||
<span class="ul-chart-subtitle">Cliquer sur une année pour la charger</span>
|
||||
</div>
|
||||
<div id="dash-chart-multiyear" class="ul-svg-chart-wrap ul-svg-chart-compact"></div>
|
||||
</div>
|
||||
|
||||
<div class="ul-card ul-dash-chart-card">
|
||||
<div class="ul-card-header-flex">
|
||||
<h3 class="ul-card-title">Modes de règlement & État des syndiqués</h3>
|
||||
</div>
|
||||
<div id="dash-chart-methods-status" class="ul-methods-status-wrap"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ligne 3 : Tableau comparatif mensuel synthétique -->
|
||||
<div class="ul-card ul-dash-table-card">
|
||||
<div class="ul-table-responsive">
|
||||
<table class="ul-dash-comp-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Comparatif Mensuel</th>
|
||||
<th>Janv.</th>
|
||||
<th>Févr.</th>
|
||||
<th>Mars</th>
|
||||
<th>Avr.</th>
|
||||
<th>Mai</th>
|
||||
<th>Juin</th>
|
||||
<th>Juil.</th>
|
||||
<th>Août</th>
|
||||
<th>Sept.</th>
|
||||
<th>Oct.</th>
|
||||
<th>Nov.</th>
|
||||
<th>Déc.</th>
|
||||
<th>Total Année</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="dash-comp-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- VUE 3 : OUTIL DE PUBLIPOSTAGE & COURRIERS -->
|
||||
<section id="view-mailing" class="ul-view-section hidden">
|
||||
<header class="ul-toolbar">
|
||||
<div class="ul-toolbar-left">
|
||||
<button
|
||||
type="button"
|
||||
class="ul-btn-toggle-sidebar"
|
||||
data-action="toggle-sidebar"
|
||||
title="Ouvrir / Fermer le volet latéral gauche"
|
||||
aria-label="Ouvrir ou fermer le volet latéral gauche"
|
||||
>☰</button>
|
||||
<h2 class="ul-section-heading">
|
||||
Publipostage & Courriers en série — Année <span id="mailing-year-label"><?php p($currentYear); ?></span>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="ul-toolbar-right">
|
||||
<button type="button" id="btn-print-mailing" class="ul-btn ul-btn-secondary">
|
||||
🖨️ Imprimer / Export PDF groupé (<span id="btn-print-count">0</span>)
|
||||
</button>
|
||||
<button type="button" id="btn-email-mailing" class="ul-btn ul-btn-primary">
|
||||
📧 Envoyer par e-mail (<span id="btn-email-count">0</span>)
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="ul-mailing-workspace">
|
||||
<!-- Colonne gauche : Modèle & Destinataires -->
|
||||
<div class="ul-mailing-sidebar">
|
||||
<div class="ul-card">
|
||||
<label for="select-template" class="ul-field-label">1. Choisir le modèle de courrier :</label>
|
||||
<select id="select-template" class="ul-select">
|
||||
<option value="tax_receipt">1. Reçu fiscal / Crédit d'impôt 66 %</option>
|
||||
<option value="missing_months">2. Rappel des mois manquants (Alerte Orange)</option>
|
||||
<option value="annual_zero_warning">3. Rappel annuel d'absence de versement (Alerte Rouge — 0 €)</option>
|
||||
</select>
|
||||
<p id="mailing-auto-rule-hint" class="ul-hint-text">
|
||||
Sélection automatique : syndiqués actifs ayant versé plus de 0,00 € sur l'année.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="ul-card ul-recipients-card">
|
||||
<div class="ul-recipients-header">
|
||||
<span class="ul-field-label">2. Destinataires sélectionnés (<strong id="mailing-selected-count">0</strong>)</span>
|
||||
<div class="ul-mini-actions">
|
||||
<button type="button" id="btn-mailing-check-all" class="ul-btn-link">Tout cocher</button>
|
||||
<span>·</span>
|
||||
<button type="button" id="btn-mailing-uncheck-all" class="ul-btn-link">Tout décocher</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-recipients-filters">
|
||||
<label class="ul-checkbox-label" for="chk-mailing-no-email-only">
|
||||
<input type="checkbox" id="chk-mailing-no-email-only" />
|
||||
<span>Syndiqués sans adresse e-mail uniquement (courrier papier)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="mailing-recipients-list" class="ul-recipients-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Colonne droite : Éditeur de modèle & Aperçu A4 -->
|
||||
<div class="ul-mailing-main">
|
||||
<div class="ul-card">
|
||||
<div class="ul-card-header-flex">
|
||||
<h3 class="ul-card-title">3. Personnaliser le modèle sélectionné</h3>
|
||||
<div class="ul-card-actions">
|
||||
<button type="button" id="btn-reset-templates" class="ul-btn ul-btn-outline ul-btn-sm">
|
||||
Réinitialiser les modèles
|
||||
</button>
|
||||
<button type="button" id="btn-save-template" class="ul-btn ul-btn-primary ul-btn-sm">
|
||||
💾 Enregistrer le modèle
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-form-row">
|
||||
<div class="ul-form-group">
|
||||
<label for="tpl-title" class="ul-field-label">Intitulé du modèle</label>
|
||||
<input type="text" id="tpl-title" class="ul-input" />
|
||||
</div>
|
||||
<div class="ul-form-group">
|
||||
<label for="tpl-subject" class="ul-field-label">Objet de l'e-mail / courrier</label>
|
||||
<input type="text" id="tpl-subject" class="ul-input" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-variables-box">
|
||||
<span class="ul-variables-title">Variables dynamiques (cliquer pour insérer au curseur) :</span>
|
||||
<div id="tpl-variables-bar" class="ul-variables-pills">
|
||||
<button type="button" class="ul-var-pill" data-var="{{civilite}}">{{civilite}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{nom}}">{{nom}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{prenom}}">{{prenom}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{adresse_complete}}">{{adresse_complete}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{code_responsable}}">{{code_responsable}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{annee}}">{{annee}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{date_du_jour}}">{{date_du_jour}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{total_paye}}">{{total_paye}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{credit_impot}}">{{credit_impot}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{nb_mois_payes}}">{{nb_mois_payes}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{mois_manquants_liste}}">{{mois_manquants_liste}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{nb_mois_manquants}}">{{nb_mois_manquants}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{cotisation_habituelle}}">{{cotisation_habituelle}}</button>
|
||||
<button type="button" class="ul-var-pill" data-var="{{montant_estime_retard}}">{{montant_estime_retard}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-form-group">
|
||||
<label for="tpl-body" class="ul-field-label">Corps du courrier</label>
|
||||
<textarea id="tpl-body" class="ul-textarea" rows="12"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-card">
|
||||
<div class="ul-card-header-flex">
|
||||
<h3 class="ul-card-title">4. Aperçu en direct du courrier généré</h3>
|
||||
<div class="ul-preview-selector-wrap">
|
||||
<label for="select-preview-member" class="ul-field-label-inline">Syndiqué prévisualisé :</label>
|
||||
<select id="select-preview-member" class="ul-select ul-select-sm"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mailing-live-preview" class="ul-letter-preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if ($isAdmin): ?>
|
||||
<!-- VUE 4 : GESTION DES PERMISSIONS D'ACCÈS (ADMINISTRATEURS) -->
|
||||
<section id="view-permissions" class="ul-view-section hidden">
|
||||
<header class="ul-toolbar">
|
||||
<div class="ul-toolbar-left">
|
||||
<button
|
||||
type="button"
|
||||
class="ul-btn-toggle-sidebar"
|
||||
data-action="toggle-sidebar"
|
||||
title="Ouvrir / Fermer le volet latéral gauche"
|
||||
aria-label="Ouvrir ou fermer le volet latéral gauche"
|
||||
>☰</button>
|
||||
<h2 class="ul-section-heading">
|
||||
🔒 Gestion des permissions d'accès à UL Trésorerie
|
||||
</h2>
|
||||
</div>
|
||||
<div class="ul-toolbar-right">
|
||||
<button type="button" id="btn-save-permissions" class="ul-btn ul-btn-primary">
|
||||
💾 Enregistrer les permissions
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="ul-permissions-workspace">
|
||||
<div class="ul-card ul-permissions-mode-card">
|
||||
<h3 class="ul-card-title">1. Politique générale d'accès à l'application</h3>
|
||||
<p class="ul-hint-text">
|
||||
Les administrateurs Nextcloud disposent toujours d'un accès complet à l'application et à cette page de configuration.
|
||||
</p>
|
||||
<div class="ul-perm-mode-options">
|
||||
<label class="ul-perm-mode-option" for="perm-mode-restricted">
|
||||
<input type="radio" name="perm-access-mode" id="perm-mode-restricted" value="restricted" checked />
|
||||
<div>
|
||||
<strong>Accès restreint aux groupes et membres sélectionnés (Recommandé)</strong>
|
||||
<span>Seuls les administrateurs Nextcloud, les membres des groupes cochés ci-dessous et les utilisateurs explicitement autorisés peuvent ouvrir l'application.</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="ul-perm-mode-option" for="perm-mode-all">
|
||||
<input type="radio" name="perm-access-mode" id="perm-mode-all" value="all_users" />
|
||||
<div>
|
||||
<strong>Tous les utilisateurs connectés à ce Nextcloud</strong>
|
||||
<span>Tout compte utilisateur actif sur l'instance Nextcloud peut accéder à l'application de trésorerie.</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="perm-selectors-grid" class="ul-permissions-grid">
|
||||
<!-- Colonne Groupes Nextcloud -->
|
||||
<div class="ul-card ul-perm-list-card">
|
||||
<div class="ul-card-header-flex">
|
||||
<div>
|
||||
<h3 class="ul-card-title">2. Groupes Nextcloud autorisés (<span id="perm-groups-count">0</span>)</h3>
|
||||
<span class="ul-chart-subtitle">Tout membre d'un groupe coché aura accès à l'application</span>
|
||||
</div>
|
||||
<div class="ul-mini-actions">
|
||||
<button type="button" id="btn-perm-groups-all" class="ul-btn-link">Tout cocher</button>
|
||||
<span>·</span>
|
||||
<button type="button" id="btn-perm-groups-none" class="ul-btn-link">Tout décocher</button>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="search"
|
||||
id="input-perm-search-groups"
|
||||
class="ul-input ul-perm-search"
|
||||
placeholder="Filtrer les groupes Nextcloud…"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<div id="perm-groups-list" class="ul-perm-checklist"></div>
|
||||
</div>
|
||||
|
||||
<!-- Colonne Utilisateurs Nextcloud -->
|
||||
<div class="ul-card ul-perm-list-card">
|
||||
<div class="ul-card-header-flex">
|
||||
<div>
|
||||
<h3 class="ul-card-title">3. Utilisateurs autorisés individuellement (<span id="perm-users-count">0</span>)</h3>
|
||||
<span class="ul-chart-subtitle">Autoriser des comptes spécifiques même s'ils ne sont pas dans un groupe</span>
|
||||
</div>
|
||||
<div class="ul-mini-actions">
|
||||
<button type="button" id="btn-perm-users-all" class="ul-btn-link">Tout cocher</button>
|
||||
<span>·</span>
|
||||
<button type="button" id="btn-perm-users-none" class="ul-btn-link">Tout décocher</button>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="search"
|
||||
id="input-perm-search-users"
|
||||
class="ul-input ul-perm-search"
|
||||
placeholder="Rechercher un utilisateur par nom, identifiant ou e-mail…"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<div id="perm-users-list" class="ul-perm-checklist"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- MODALE 1 : SAISIE D'UNE COTISATION AU CLIC -->
|
||||
<div id="due-modal" class="ul-modal-backdrop hidden ul-no-print" role="dialog" aria-modal="true" aria-labelledby="due-modal-title">
|
||||
<div class="ul-modal-dialog ul-modal-sm">
|
||||
<header class="ul-modal-header">
|
||||
<div>
|
||||
<h3 id="due-modal-title" class="ul-modal-title">Saisie de cotisation</h3>
|
||||
<p id="due-modal-subtitle" class="ul-modal-subtitle"></p>
|
||||
</div>
|
||||
<button type="button" id="btn-close-due-modal" class="ul-modal-close" aria-label="Fermer">×</button>
|
||||
</header>
|
||||
|
||||
<form id="due-form" class="ul-modal-body">
|
||||
<input type="hidden" id="due-member-id" />
|
||||
<input type="hidden" id="due-year" />
|
||||
<input type="hidden" id="due-month-start" />
|
||||
|
||||
<div id="due-modal-prefill-hint" class="ul-prefill-banner"></div>
|
||||
|
||||
<div class="ul-form-row">
|
||||
<div class="ul-form-group">
|
||||
<label for="due-input-amount" class="ul-field-label">Montant versé (€) *</label>
|
||||
<input
|
||||
type="number"
|
||||
id="due-input-amount"
|
||||
class="ul-input ul-input-lg"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ul-form-group">
|
||||
<label for="due-input-method" class="ul-field-label">Mode de paiement</label>
|
||||
<select id="due-input-method" class="ul-select ul-input-lg">
|
||||
<option value="Virement">Virement (V)</option>
|
||||
<option value="Prélèvement">Prélèvement (P)</option>
|
||||
<option value="Chèque">Chèque (C)</option>
|
||||
<option value="Espèces">Espèces (E)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-form-group">
|
||||
<label for="due-input-month-end" class="ul-field-label">Appliquer jusqu'au mois de :</label>
|
||||
<select id="due-input-month-end" class="ul-select"></select>
|
||||
<span class="ul-field-help">Permet de saisir plusieurs mois consécutifs en une seule validation (ex : paiement d'un trimestre ou semestre).</span>
|
||||
</div>
|
||||
|
||||
<div class="ul-form-group">
|
||||
<label for="due-input-note" class="ul-field-label">Note facultative</label>
|
||||
<input
|
||||
type="text"
|
||||
id="due-input-note"
|
||||
class="ul-input"
|
||||
maxlength="255"
|
||||
placeholder="Ex : Chèque n°4512, régularisation..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<footer class="ul-modal-footer">
|
||||
<button type="button" id="btn-delete-due" class="ul-btn ul-btn-danger hidden">
|
||||
🗑️ Effacer ce mois
|
||||
</button>
|
||||
<div class="ul-modal-footer-right">
|
||||
<button type="button" id="btn-cancel-due" class="ul-btn ul-btn-outline">
|
||||
Annuler
|
||||
</button>
|
||||
<button type="submit" id="btn-submit-due" class="ul-btn ul-btn-success">
|
||||
✓ Valider (Entrée)
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MODALE 2 : CRÉATION / ÉDITION DE FICHE SYNDIQUÉ -->
|
||||
<div id="member-modal" class="ul-modal-backdrop hidden ul-no-print" role="dialog" aria-modal="true" aria-labelledby="member-modal-title">
|
||||
<div class="ul-modal-dialog ul-modal-lg">
|
||||
<header class="ul-modal-header">
|
||||
<h3 id="member-modal-title" class="ul-modal-title">Fiche du syndiqué</h3>
|
||||
<button type="button" id="btn-close-member-modal" class="ul-modal-close" aria-label="Fermer">×</button>
|
||||
</header>
|
||||
|
||||
<form id="member-form" class="ul-modal-body">
|
||||
<input type="hidden" id="mbr-id" />
|
||||
|
||||
<div class="ul-form-row ul-form-row-3">
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-civility" class="ul-field-label">Civilité</label>
|
||||
<select id="mbr-civility" class="ul-select">
|
||||
<option value="M.">M.</option>
|
||||
<option value="Mme">Mme</option>
|
||||
<option value="">Non précisé</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-last-name" class="ul-field-label">Nom (MAJUSCULES) *</label>
|
||||
<input type="text" id="mbr-last-name" class="ul-input" required placeholder="Ex : MORGET" />
|
||||
</div>
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-first-name" class="ul-field-label">Prénom *</label>
|
||||
<input type="text" id="mbr-first-name" class="ul-input" required placeholder="Ex : FLORIAN" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-form-row ul-form-row-3">
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-cogitiel-id" class="ul-field-label">Code Responsable (CoGétise)</label>
|
||||
<input type="text" id="mbr-cogitiel-id" class="ul-input" placeholder="Ex : 1962796" />
|
||||
</div>
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-default-amount" class="ul-field-label">Montant mensuel de réf. (€)</label>
|
||||
<input type="number" id="mbr-default-amount" class="ul-input" step="0.01" min="0" value="0.00" />
|
||||
</div>
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-default-method" class="ul-field-label">Mode de paiement habituel</label>
|
||||
<select id="mbr-default-method" class="ul-select">
|
||||
<option value="Virement">Virement</option>
|
||||
<option value="Prélèvement">Prélèvement</option>
|
||||
<option value="Chèque">Chèque</option>
|
||||
<option value="Espèces">Espèces</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-form-row ul-form-row-3">
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-email" class="ul-field-label">Adresse e-mail</label>
|
||||
<input type="email" id="mbr-email" class="ul-input" placeholder="exemple@domaine.fr" />
|
||||
</div>
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-phone-personal" class="ul-field-label">Téléphone personnel</label>
|
||||
<input type="text" id="mbr-phone-personal" class="ul-input" placeholder="06 00 00 00 00" />
|
||||
</div>
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-phone-pro" class="ul-field-label">Téléphone professionnel</label>
|
||||
<input type="text" id="mbr-phone-pro" class="ul-input" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-form-row ul-form-row-3">
|
||||
<div class="ul-form-group ul-span-2">
|
||||
<label for="mbr-address" class="ul-field-label">Adresse postale complète</label>
|
||||
<input type="text" id="mbr-address" class="ul-input" placeholder="Ex : 23 AVENUE DE TOULOUSE" />
|
||||
</div>
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-postal-code" class="ul-field-label">Code postal</label>
|
||||
<input type="text" id="mbr-postal-code" class="ul-input" placeholder="Ex : 09100" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-form-row ul-form-row-3">
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-city" class="ul-field-label">Ville</label>
|
||||
<input type="text" id="mbr-city" class="ul-input" placeholder="Ex : PAMIERS" />
|
||||
</div>
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-base-code" class="ul-field-label">Code de la base</label>
|
||||
<input type="text" id="mbr-base-code" class="ul-input" value="90045110520251341" />
|
||||
</div>
|
||||
<div class="ul-form-group">
|
||||
<label for="mbr-base-name" class="ul-field-label">Nom de la base</label>
|
||||
<input type="text" id="mbr-base-name" class="ul-input" value="INDIVIDUELS UL FOIX HAUTE-ARIEGE" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ul-form-row ul-checkbox-group-row">
|
||||
<label class="ul-checkbox-label" for="mbr-is-archived">
|
||||
<input type="checkbox" id="mbr-is-archived" />
|
||||
<span>📦 Syndiqué archivé (masqué par défaut dans la grille)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<footer class="ul-modal-footer">
|
||||
<button type="button" id="btn-delete-member" class="ul-btn ul-btn-danger hidden">
|
||||
🗑️ Supprimer ce syndiqué
|
||||
</button>
|
||||
<div class="ul-modal-footer-right">
|
||||
<button type="button" id="btn-cancel-member" class="ul-btn ul-btn-outline">
|
||||
Annuler
|
||||
</button>
|
||||
<button type="submit" id="btn-submit-member" class="ul-btn ul-btn-primary">
|
||||
💾 Enregistrer la fiche
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ZONE D'IMPRESSION A4 MULTI-PAGES (visible uniquement lors de window.print()) -->
|
||||
<div id="ul-print-container" class="ul-print-only" aria-hidden="true"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
Loading…
Reference in New Issue
Block a user