Compare commits
82 Commits
ebb844fc13
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 08ce6f9151 | |||
| e50ea5c22c | |||
| a037ac5ea7 | |||
| 9e06f7257b | |||
| c2b75076d0 | |||
| 703d2ecda3 | |||
| fc0e813b2b | |||
| e1623d8007 | |||
| 2930fd4e50 | |||
| f26b3d6816 | |||
| 659c93f3a5 | |||
| 422799ece0 | |||
| 5f398fd1f2 | |||
| aadbb77cbc | |||
| 090aa78a59 | |||
| b5ab432fa9 | |||
| c04c7e848b | |||
| 9f3928e584 | |||
| 61178ceb9f | |||
| c510e166d0 | |||
| 4cf67865b8 | |||
| 539d1bdce2 | |||
| 505b89f696 | |||
| 4e97d0cb02 | |||
| b89280d856 | |||
| 792b301314 | |||
| 3c6cc9041e | |||
| 0214e41ee2 | |||
| bb3729f4ed | |||
| b14265418f | |||
| 1119986fc7 | |||
| 4fc444d380 | |||
| 096c44ba79 | |||
| 61de6db5b5 | |||
| ebf6dc59b0 | |||
| 720894eb2c | |||
| 3cf4ddd12d | |||
| 27ea802619 | |||
| 8fc8199a6d | |||
| 0b61a1df8f | |||
| 8c92460cc2 | |||
| 4b2f046a0d | |||
| e97f518d68 | |||
| 0f9f011d99 | |||
| b635fe1626 | |||
| 2c54583a0b | |||
| e38aa7595a | |||
| f7bf4f29eb | |||
| 85321437ed | |||
| 4992c73359 | |||
| 86c9683f0d | |||
| 1cb7e7dd66 | |||
| 05db391f1b | |||
| f2ca1ffe77 | |||
| 155f882f34 | |||
| 44cda34d49 | |||
| 222dd979a5 | |||
| f609c09ca9 | |||
| bc11cd004b | |||
| 2939bd41ac | |||
| 3d3c43f684 | |||
| 0f246e9acc | |||
| 37393c602d | |||
| c1f25eb61d | |||
| 37465a9ece | |||
| 1d769e2f2e | |||
| 137b5ccc71 | |||
| 9f453b95c3 | |||
| 322f32d499 | |||
| 09bc28bec9 | |||
| e14309725f | |||
| 6db1a8de28 | |||
| 81818eb39b | |||
| 977be7dd80 | |||
| 4d6a1e3cf4 | |||
| b638d333b5 | |||
| 498d04b1d1 | |||
| bd27b02a11 | |||
| 18c08d5c84 | |||
| 9f60606f5c | |||
| 6c15f7efe6 | |||
| 5b9c48ad20 |
34
.gitignore
vendored
34
.gitignore
vendored
@@ -23,18 +23,30 @@ dist/
|
||||
# Fichiers temporaires d'Excel (~$fichier.xlsm)
|
||||
~$*.xls*
|
||||
~$*.xlsm
|
||||
# On versionne 1 seul fichier prod "pivot" ; on ignore les exports datés
|
||||
Excel/prod/Ratio_prod_20*.xlsm
|
||||
!Excel/prod/Ratio_prod.xlsm
|
||||
Excel/dev/**
|
||||
!Excel/dev/.gitkeep
|
||||
|
||||
# ----- Dossiers Auth livrables -----
|
||||
# On n’embarque pas les exe ni .env dans le repo (tu livreras ça en zip)
|
||||
Excel/**/Auth/*.exe
|
||||
Excel/**/Auth/*.dll
|
||||
Excel/**/Auth/*.pdb
|
||||
Excel/**/Auth/.env
|
||||
|
||||
# Dossiers générés par Excel — à ne jamais versionner
|
||||
Excel/dev/Desktop/
|
||||
Excel/prod/Desktop/
|
||||
Excel/dev/InventairesCache/
|
||||
Excel/prod/InventairesCache/
|
||||
# Archives prod : on les ignore (elles sont recréées par le .bat)
|
||||
Excel/prod/Ratio_Cuisine_20*.xlsm
|
||||
Excel/prod/Ratio_Restauration_20*.xlsm
|
||||
|
||||
# On versionne uniquement les fichiers prod "pivots"
|
||||
!Excel/prod/Ratio_Cuisine.xlsm
|
||||
!Excel/prod/Ratio_Cuisine_VERSION.txt
|
||||
!Excel/prod/Ratio_Restauration.xlsm
|
||||
!Excel/prod/Ratio_Restauration_VERSION.txt
|
||||
Excel/backup/
|
||||
Softs/
|
||||
Excel/Tableurs modèles/*NEW*.xlsx
|
||||
# On ignore tout le dev (fichiers de travail)
|
||||
Excel/dev/
|
||||
# (Optionnel si tu veux garder le dossier vide dans Git)
|
||||
# !Excel/dev/.gitkeep
|
||||
|
||||
|
||||
# ----- Systèmes / IDE -----
|
||||
.DS_Store
|
||||
|
||||
BIN
Docs/Diaporama_Tableur_Ratio.pptx
Normal file
BIN
Docs/Diaporama_Tableur_Ratio.pptx
Normal file
Binary file not shown.
0
Docs/Logo.bmp
Normal file
0
Docs/Logo.bmp
Normal file
BIN
Docs/Logo.jpg
Normal file
BIN
Docs/Logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
BIN
Docs/Ratio origine Ermitage.xlsm
Normal file
BIN
Docs/Ratio origine Ermitage.xlsm
Normal file
Binary file not shown.
BIN
Excel/Tableurs a problèmes/Ratio_Restauration_dev_old.xlsm
Normal file
BIN
Excel/Tableurs a problèmes/Ratio_Restauration_dev_old.xlsm
Normal file
Binary file not shown.
BIN
Excel/Tableurs a problèmes/Ratios anciens.xlsm
Normal file
BIN
Excel/Tableurs a problèmes/Ratios anciens.xlsm
Normal file
Binary file not shown.
BIN
Excel/Tableurs modèles/Bibliotheque_PQ.xlsm
Normal file
BIN
Excel/Tableurs modèles/Bibliotheque_PQ.xlsm
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Excel/prod/Démo_Ratio_Cuisine.xlsm
Normal file
BIN
Excel/prod/Démo_Ratio_Cuisine.xlsm
Normal file
Binary file not shown.
@@ -1,28 +0,0 @@
|
||||
mois;ref;designation;qte
|
||||
2025-10;1336;Vinaigre yuzu ;1,1
|
||||
2025-10;1039;Boursin ;1,1
|
||||
2025-10;1042;Confiture bonne maman abricot 4 pces;1,1
|
||||
2025-10;543;Smoothie Ananas Mangue;2,3
|
||||
2025-10;1322;Smoothie fraise ;2,3
|
||||
2025-10;1321;smoothie mangue ;2,3
|
||||
2025-10;455;Cafe Grains Gourmet;2,3
|
||||
2025-10;458;Cappucino En Poudre;2,3
|
||||
2025-10;457;Decafeine en poudre ;2,3
|
||||
2025-10;1055;Confiture bm framboise ;5,87
|
||||
2025-10;1167;Lasagne ;5,87
|
||||
2025-10;1166;Mousseline ;5,87
|
||||
2025-10;1119;Orechiete ;5,87
|
||||
2025-10;1168;Salade mexicaine ;5,87
|
||||
2025-10;1151;Sirop d erable ;5,87
|
||||
2025-10;1118;Tagliatelle ;5,87
|
||||
2025-10;1120;Trofie ;5,87
|
||||
2025-10;1587;CORIANDRE BOTTE;5,87
|
||||
2025-10;1593;ROMARIN BOTTE;5,87
|
||||
2025-10;1532;ANANAS SWEET BATEAU CAT1 KG;5,87
|
||||
2025-10;1524;BANANE CAL.MOYEN KG;5,87
|
||||
2025-10;1565;BLANC POIREAUX KG;5,87
|
||||
2025-10;1573;CAROTTE PLATEAU CE2 KG;5,87
|
||||
2025-10;1564;CERFEUIL BOTTE;5,87
|
||||
2025-10;1521;CHAMP ERENGY-ERYNGII;5,87
|
||||
2025-10;1575;CHOU BOCK CHOY SHANGAI (PETIT VERT) KG;5,87
|
||||
2025-10;1545;CHOU CHINOIS CAT 1;5,87
|
||||
|
@@ -1 +0,0 @@
|
||||
mois;ref;designation;qte
|
||||
|
BIN
Excel/prod/Ratio_Cuisine.xlsm
Normal file
BIN
Excel/prod/Ratio_Cuisine.xlsm
Normal file
Binary file not shown.
BIN
Excel/prod/Ratio_Restauration.xlsm
Normal file
BIN
Excel/prod/Ratio_Restauration.xlsm
Normal file
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
2.27
|
||||
BIN
Images/Logo.jpg
Normal file
BIN
Images/Logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 271 KiB |
BIN
Images/cuisine.ico
Normal file
BIN
Images/cuisine.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
BIN
Images/restauration.ico
Normal file
BIN
Images/restauration.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
517
README.md
517
README.md
@@ -1,37 +1,287 @@
|
||||
# Inventaire-Gestion
|
||||
# Ratio Inventaires
|
||||
|
||||
Ce projet permet de gérer les inventaires et les ratios de plusieurs sites à partir d’un fichier Excel central et d’une base MySQL.
|
||||
Projet Excel/VBA permettant de gérer les inventaires, les articles, les fournisseurs, les factures, le chiffre d’affaires et les ratios de consommation pour plusieurs activités.
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
* Mise à jour du pilote ODBC sur machine client
|
||||
* Authentification par site
|
||||
* Lecture automatique du fichier `ratio.xlsm`
|
||||
* Insertion des données dans la base correspondante
|
||||
* Gestion des articles et inventaire par code barres
|
||||
* Affichage dans Streamlit (à venir)
|
||||
|
||||
# 🚀 Installation du pilote MySQL ODBC 8.3
|
||||
|
||||
## ✅ Objectif
|
||||
|
||||
Assurer une compatibilité totale entre les fichiers Excel connectés à MySQL et le pilote ODBC, en uniformisant tous les postes avec la **version 8.3.0 du connecteur MySQL ODBC**.
|
||||
Le projet est organisé pour travailler proprement avec PyCharm, Git/Gitea et des scripts de mise en production automatisés.
|
||||
|
||||
---
|
||||
|
||||
## 📥 Étapes d'installation
|
||||
## Objectif du projet
|
||||
|
||||
1. Télécharger le fichier `mysql-connector-odbc-8.3.0-winx64.msi` depuis le site officiel :
|
||||
Le projet permet de maintenir plusieurs classeurs Excel métier :
|
||||
|
||||
👉 [https://dev.mysql.com/downloads/connector/odbc/](https://dev.mysql.com/downloads/connector/odbc/)
|
||||
- `Ratio_Cuisine.xlsm`
|
||||
- `Ratio_Restauration.xlsm`
|
||||
- `Démo_Ratio_Cuisine.xlsm`
|
||||
|
||||
2. Copier le fichier dans ce dossier sur chaque poste cible :
|
||||
Les fichiers de développement sont conservés dans `Excel/dev`, puis copiés vers `Excel/prod` par des scripts de mise en production.
|
||||
|
||||
Le numéro de version est géré automatiquement par fichier `.txt` interne, puis écrit directement dans le classeur Excel livré.
|
||||
|
||||
---
|
||||
|
||||
## Organisation des dossiers
|
||||
|
||||
```text
|
||||
Ratio_Inventaires/
|
||||
│
|
||||
├── Docs/
|
||||
│
|
||||
├── Excel/
|
||||
│ ├── backup/
|
||||
│ │ └── anciennes versions sauvegardées
|
||||
│ │
|
||||
│ ├── dev/
|
||||
│ │ ├── Ratio_Cuisine_dev.xlsm
|
||||
│ │ ├── Ratio_Cuisine_VERSION.txt
|
||||
│ │ ├── Ratio_Restauration_dev.xlsm
|
||||
│ │ └── Ratio_Restauration_VERSION.txt
|
||||
│ │
|
||||
│ └── prod/
|
||||
│ ├── Démo_Ratio_Cuisine.xlsm
|
||||
│ ├── Ratio_Cuisine.xlsm
|
||||
│ └── Ratio_Restauration.xlsm
|
||||
│
|
||||
└── Scripts/
|
||||
├── Maj_prod_Cuisine.bat
|
||||
├── Maj_prod_Restauration.bat
|
||||
├── maj_version.py
|
||||
└── set_cell_silent.vbs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rôle des dossiers
|
||||
|
||||
### `Excel/dev`
|
||||
|
||||
Contient les fichiers de travail.
|
||||
|
||||
```text
|
||||
Ratio_Cuisine_dev.xlsm
|
||||
Ratio_Restauration_dev.xlsm
|
||||
```
|
||||
|
||||
Ces fichiers sont les fichiers modifiables.
|
||||
|
||||
Les fichiers `*_VERSION.txt` restent uniquement dans `dev`. Ils servent de compteurs internes pour les scripts de versionnage.
|
||||
|
||||
---
|
||||
|
||||
### `Excel/prod`
|
||||
|
||||
Contient les fichiers livrables.
|
||||
|
||||
```text
|
||||
Ratio_Cuisine.xlsm
|
||||
Ratio_Restauration.xlsm
|
||||
Démo_Ratio_Cuisine.xlsm
|
||||
```
|
||||
|
||||
Les fichiers de production ne nécessitent plus de fichier `.txt` à côté. La version est directement écrite dans le classeur Excel.
|
||||
|
||||
---
|
||||
|
||||
### `Excel/backup`
|
||||
|
||||
Contient les anciennes versions de production sauvegardées automatiquement avant remplacement.
|
||||
|
||||
Exemple :
|
||||
|
||||
```text
|
||||
Ratio_Cuisine_Vers1.0.15.xlsm
|
||||
Ratio_Restauration_Vers2.1.3.xlsm
|
||||
```
|
||||
|
||||
Le nombre de sauvegardes conservées est limité dans les scripts `.bat`.
|
||||
|
||||
---
|
||||
|
||||
### `Scripts`
|
||||
|
||||
Contient les scripts d’automatisation :
|
||||
|
||||
```text
|
||||
Maj_prod_Cuisine.bat
|
||||
Maj_prod_Restauration.bat
|
||||
maj_version.py
|
||||
set_cell_silent.vbs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Versionnage
|
||||
|
||||
Le projet utilise deux familles de versions :
|
||||
|
||||
```text
|
||||
Cuisine → 1.x.x
|
||||
Restauration → 2.x.x
|
||||
```
|
||||
|
||||
Les fichiers de version à conserver sont uniquement :
|
||||
|
||||
```text
|
||||
Excel/dev/Ratio_Cuisine_VERSION.txt
|
||||
Excel/dev/Ratio_Restauration_VERSION.txt
|
||||
```
|
||||
|
||||
Les fichiers `.txt` de version ne doivent plus être copiés dans `Excel/prod`.
|
||||
|
||||
---
|
||||
|
||||
## Fonctionnement du script de version
|
||||
|
||||
Le script :
|
||||
|
||||
```text
|
||||
Scripts/maj_version.py
|
||||
```
|
||||
|
||||
fait les opérations suivantes :
|
||||
|
||||
1. lit le fichier `*_VERSION.txt` dans `Excel/dev` ;
|
||||
2. force le numéro majeur selon le classeur ;
|
||||
3. incrémente le patch ;
|
||||
4. réécrit le fichier `.txt` ;
|
||||
5. écrit la version directement dans le classeur Excel de production.
|
||||
|
||||
Exemple de version écrite dans Excel :
|
||||
|
||||
```text
|
||||
Version : 1.0.12
|
||||
Version : 2.1.4
|
||||
```
|
||||
|
||||
L’écriture dans le classeur Excel est faite par :
|
||||
|
||||
```text
|
||||
Scripts/set_cell_silent.vbs
|
||||
```
|
||||
|
||||
Ce script ouvre Excel en arrière-plan, modifie la cellule de version, sauvegarde et ferme le classeur.
|
||||
|
||||
---
|
||||
|
||||
## Cellule de version dans Excel
|
||||
|
||||
La version est écrite automatiquement dans la feuille :
|
||||
|
||||
```text
|
||||
Tableau de bord
|
||||
```
|
||||
|
||||
La cellule utilisée doit être réservée à la version.
|
||||
|
||||
Recommandation actuelle :
|
||||
|
||||
```text
|
||||
E1 = Version : x.x.x
|
||||
```
|
||||
|
||||
Attention : `C1` est utilisé par le contexte du site, notamment `SiteCanonique`, et ne doit donc pas être utilisé pour la version.
|
||||
|
||||
---
|
||||
|
||||
## Mise en production Cuisine
|
||||
|
||||
Depuis le dossier `Scripts`, lancer :
|
||||
|
||||
```bat
|
||||
Maj_prod_Cuisine.bat CUISINE
|
||||
```
|
||||
|
||||
Le script effectue :
|
||||
|
||||
1. sauvegarde de l’ancienne version de production ;
|
||||
2. copie de `Excel/dev/Ratio_Cuisine_dev.xlsm` vers `Excel/prod/Ratio_Cuisine.xlsm` ;
|
||||
3. incrément de `Excel/dev/Ratio_Cuisine_VERSION.txt` ;
|
||||
4. écriture de la version dans le classeur de production ;
|
||||
5. nettoyage des anciennes sauvegardes.
|
||||
|
||||
---
|
||||
|
||||
## Mise en production Restauration
|
||||
|
||||
Depuis le dossier `Scripts`, lancer :
|
||||
|
||||
```bat
|
||||
Maj_prod_Restauration.bat RESTAURATION
|
||||
```
|
||||
|
||||
Le script effectue :
|
||||
|
||||
1. sauvegarde de l’ancienne version de production ;
|
||||
2. copie de `Excel/dev/Ratio_Restauration_dev.xlsm` vers `Excel/prod/Ratio_Restauration.xlsm` ;
|
||||
3. incrément de `Excel/dev/Ratio_Restauration_VERSION.txt` ;
|
||||
4. écriture de la version dans le classeur de production ;
|
||||
5. nettoyage des anciennes sauvegardes.
|
||||
|
||||
---
|
||||
|
||||
## Fichier de démonstration
|
||||
|
||||
Le fichier :
|
||||
|
||||
```text
|
||||
Excel/prod/Démo_Ratio_Cuisine.xlsm
|
||||
```
|
||||
|
||||
est destiné à présenter le fonctionnement sans installation réseau obligatoire.
|
||||
|
||||
Il peut contenir des données locales dans des feuilles de type :
|
||||
|
||||
```text
|
||||
DATA_Articles
|
||||
DATA_Fournisseurs
|
||||
DATA_Factures
|
||||
DATA_Ca
|
||||
DATA_Inventaires
|
||||
```
|
||||
|
||||
La version démo peut fonctionner sans connexion MySQL, sans ODBC et sans fichier `.txt` distribué.
|
||||
|
||||
Recommandations pour la version démo :
|
||||
|
||||
- données locales uniquement ;
|
||||
- pas d’identifiants MySQL ;
|
||||
- pas de requêtes Power Query actives ;
|
||||
- pas de fichier de version `.txt` livré ;
|
||||
- limitation volontaire des ajouts pour éviter un usage réel non maîtrisé.
|
||||
|
||||
---
|
||||
|
||||
## Connexions MySQL et ODBC
|
||||
|
||||
Les classeurs de production connectés peuvent utiliser MySQL via ODBC.
|
||||
|
||||
La version recommandée du pilote est :
|
||||
|
||||
```text
|
||||
MySQL ODBC 8.3 Unicode Driver
|
||||
```
|
||||
|
||||
Sur les postes clients connectés, le pilote doit être installé avant utilisation.
|
||||
|
||||
La version démo locale, elle, ne doit pas dépendre du pilote ODBC.
|
||||
|
||||
---
|
||||
|
||||
## Installation du pilote MySQL ODBC 8.3
|
||||
|
||||
Télécharger le pilote officiel :
|
||||
|
||||
```text
|
||||
mysql-connector-odbc-8.3.0-winx64.msi
|
||||
```
|
||||
|
||||
Puis le placer dans :
|
||||
|
||||
```text
|
||||
C:\Installers\
|
||||
```
|
||||
|
||||
3. Créer un fichier `Installer_ODBC_93.bat` contenant :
|
||||
Exemple de script d’installation silencieuse :
|
||||
|
||||
```bat
|
||||
@echo off
|
||||
@@ -39,165 +289,140 @@ Assurer une compatibilité totale entre les fichiers Excel connectés à MySQL e
|
||||
echo Installation MySQL ODBC 8.3.0
|
||||
echo ===============================
|
||||
|
||||
SET MYPATH=C:\Installers
|
||||
SET INSTALLER=%MYPATH%\mysql-connector-odbc-8.3.0-winx64.msi
|
||||
set MYPATH=C:\Installers
|
||||
set INSTALLER=%MYPATH%\mysql-connector-odbc-8.3.0-winx64.msi
|
||||
|
||||
IF EXIST "%INSTALLER%" (
|
||||
echo >> Démarrage de l'installation silencieuse...
|
||||
if exist "%INSTALLER%" (
|
||||
echo Démarrage de l'installation silencieuse...
|
||||
msiexec /i "%INSTALLER%" /qn
|
||||
echo >> Installation terminée avec succès.
|
||||
pause
|
||||
) ELSE (
|
||||
echo >> Fichier MSI non trouvé :
|
||||
echo >> %INSTALLER%
|
||||
pause
|
||||
echo Installation terminée.
|
||||
) else (
|
||||
echo Fichier MSI non trouvé :
|
||||
echo %INSTALLER%
|
||||
)
|
||||
```
|
||||
|
||||
4. Lancer le script :
|
||||
|
||||
* clic droit sur `Installer_ODBC_83.bat`
|
||||
* puis **"Exécuter en tant qu’administrateur"**
|
||||
À lancer en administrateur.
|
||||
|
||||
---
|
||||
|
||||
## 🔁 (Optionnel) Désinstallation d'une version précédente
|
||||
## Bonnes pratiques Git/Gitea
|
||||
|
||||
Pour supprimer proprement une version antérieure (ex : 8.2), vous pouvez ajouter :
|
||||
À versionner :
|
||||
|
||||
```bat
|
||||
msiexec /x {GUID-DE-LA-VERSION-8.2} /qn
|
||||
```text
|
||||
Excel/dev/Ratio_Cuisine_dev.xlsm
|
||||
Excel/dev/Ratio_Restauration_dev.xlsm
|
||||
Excel/dev/*_VERSION.txt
|
||||
Scripts/*.bat
|
||||
Scripts/*.py
|
||||
Scripts/*.vbs
|
||||
README.md
|
||||
```
|
||||
|
||||
*(à compléter avec l'identifiant de produit si nécessaire)*
|
||||
À ignorer ou éviter de versionner :
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Conseils
|
||||
|
||||
* Intégrez ces fichiers dans votre dépôt (ex : dossier `Installers/`)
|
||||
* Versionnez votre script dans Git (Gitea) pour faciliter le déploiement sur tous les sites
|
||||
* Utilisez RustDesk ou accès direct pour l'installation sur les PC distants
|
||||
|
||||
# 🔧 Gestion et distribution du fichier Excel `Ratio_prod.xlsm`
|
||||
|
||||
Ce document décrit le fonctionnement mis en place pour garantir une version stable et toujours à jour du fichier Excel `Ratio_prod.xlsm`, utilisé dans le cadre du projet **Ratio & Inventaires**.
|
||||
|
||||
---
|
||||
|
||||
## 🗃️ Organisation des fichiers
|
||||
|
||||
### Structure dans le projet (Git)
|
||||
|
||||
/Excel/
|
||||
├── dev/
|
||||
│ └── Ratio\_dev.xlsm ← Fichier de travail
|
||||
├── prod/
|
||||
│ └── Ratio\_prod.xlsm ← Fichier de production (non suivi par Git)
|
||||
|
||||
* `Ratio_dev.xlsm` : version de développement modifiable, suivie par Git.
|
||||
* `Ratio_prod.xlsm` : version validée, protégée (ajoutée au `.gitignore` pour éviter tout push accidentel).
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Synchronisation automatique avec le NAS Synology
|
||||
|
||||
Le fichier `Ratio_prod.xlsm` est copié automatiquement sur le NAS Synology, dans un dossier partagé :
|
||||
|
||||
### ⚙️ Configuration
|
||||
|
||||
* **Synology Drive Server** est activé sur le NAS.
|
||||
* **Synology Drive Client** est installé sur le poste de travail.
|
||||
* Dossier synchronisé : `Partage_Ratio`
|
||||
* Mode de synchronisation recommandé : `Téléchargement uniquement`.
|
||||
|
||||
---
|
||||
|
||||
## 🔌 Copie automatique vers clé USB (via NAS)
|
||||
|
||||
### Prérequis
|
||||
|
||||
* Application **USB Copy** installée et activée sur le NAS.
|
||||
|
||||
### Fonctionnement
|
||||
|
||||
1. Brancher une clé USB sur le port en façade du NAS.
|
||||
2. Le NAS copie automatiquement `Ratio_prod.xlsm` sur la clé USB si une version plus récente est disponible.
|
||||
3. Le fichier est copié dans le dossier racine de la clé, ou dans un dossier `Ratio/`.
|
||||
|
||||
### Avantages
|
||||
|
||||
* Pas besoin d’ordinateur pour copier à la main.
|
||||
* Copie toujours à jour dès que la clé est branchée.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Installation manuelle depuis une clé USB
|
||||
|
||||
Contenu du dossier USB :
|
||||
|
||||
```
|
||||
INSTALL_RATIO/
|
||||
├── Ratio_prod.xlsm
|
||||
└── Installer_Prod.bat
|
||||
```text
|
||||
Excel/backup/
|
||||
~$*.xlsm
|
||||
*.tmp
|
||||
*.bak
|
||||
```
|
||||
|
||||
### Étapes :
|
||||
|
||||
1. Brancher la clé USB sur le poste utilisateur.
|
||||
2. Lancer `Installer_Prod.bat` **en tant qu’administrateur**.
|
||||
3. Le fichier sera copié dans :
|
||||
|
||||
```
|
||||
C:\Program Files\Ratio\Ratio.xlsm
|
||||
```
|
||||
|
||||
et protégé en lecture seule.
|
||||
Selon la stratégie choisie, les fichiers de `Excel/prod` peuvent être versionnés ou non. Dans ce projet, ils servent de fichiers livrables générés par les scripts.
|
||||
|
||||
---
|
||||
|
||||
## ♻️ Restaurer la version de production dans le projet (dev)
|
||||
## Exemple de `.gitignore`
|
||||
|
||||
Si la version de travail (`dev`) a été corrompue ou modifiée par erreur :
|
||||
```gitignore
|
||||
# Fichiers temporaires Excel
|
||||
~$*.xlsm
|
||||
~$*.xlsx
|
||||
*.tmp
|
||||
*.bak
|
||||
|
||||
# Sauvegardes générées
|
||||
Excel/backup/
|
||||
|
||||
# Cache Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Environnements virtuels
|
||||
.venv/
|
||||
venv/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist avant mise en production
|
||||
|
||||
Avant de lancer un script de mise en production :
|
||||
|
||||
1. fermer le classeur Excel concerné ;
|
||||
2. vérifier qu’aucun fichier `~$Ratio_*.xlsm` n’est présent ;
|
||||
3. sauvegarder les modifications dans le fichier `dev` ;
|
||||
4. lancer le script `.bat` correspondant ;
|
||||
5. vérifier que la version est bien écrite dans le classeur `prod` ;
|
||||
6. faire un commit Git/Gitea.
|
||||
|
||||
---
|
||||
|
||||
## Commandes utiles
|
||||
|
||||
Vérifier l’état Git :
|
||||
|
||||
```bash
|
||||
cp Excel/prod/Ratio_prod.xlsm Excel/dev/Ratio_dev.xlsm
|
||||
git status
|
||||
```
|
||||
|
||||
Ou sous Windows :
|
||||
Ajouter les modifications :
|
||||
|
||||
```powershell
|
||||
Copy-Item -Path "Excel\prod\Ratio_prod.xlsm" -Destination "Excel\dev\Ratio_dev.xlsm" -Force
|
||||
```bash
|
||||
git add README_old.md Scripts/ Excel/dev/
|
||||
```
|
||||
|
||||
Créer un commit :
|
||||
|
||||
```bash
|
||||
git commit -m "Normalise versioning and prod deployment scripts"
|
||||
```
|
||||
|
||||
Envoyer vers Gitea :
|
||||
|
||||
```bash
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧾 Notes complémentaires
|
||||
## Dépannage
|
||||
|
||||
* Le fichier `Ratio_prod.xlsm` n’est **pas suivi par Git**, car exclu via `.gitignore`.
|
||||
* Il est **protégé en lecture seule** sur le poste utilisateur.
|
||||
* Une sauvegarde automatique est possible via **Hyper Backup** ou les **snapshots** du NAS.
|
||||
### Erreur : fichier utilisé par un autre processus
|
||||
|
||||
Cause probable : le classeur est encore ouvert dans Excel ou un fichier temporaire existe.
|
||||
|
||||
Solution :
|
||||
|
||||
```bat
|
||||
taskkill /F /IM excel.exe
|
||||
```
|
||||
|
||||
Puis vérifier que le fichier temporaire a disparu :
|
||||
|
||||
```text
|
||||
~$Ratio_Cuisine.xlsm
|
||||
~$Ratio_Restauration.xlsm
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 📋 Checklist Release DEV → PROD
|
||||
### Erreur dans `set_cell_silent.vbs` au moment de fermer Excel
|
||||
|
||||
Pour garantir que DEV reste ouvert et que PROD soit protégé :
|
||||
Cause probable : Excel est resté en arrière-plan.
|
||||
|
||||
1. **Travailler dans DEV** : `Excel/dev/Ratio_dev.xlsm` (projet VBA non verrouillé).
|
||||
2. **Lancer le script batch** (`build_prod.bat`).
|
||||
|
||||
* Copie DEV → PROD
|
||||
* Application des protections (feuilles, structure, horodatage)
|
||||
* Vérification/verrouillage du projet VBA PROD (manuel si nécessaire)
|
||||
3. **Vérifier PROD** :
|
||||
|
||||
* Feuilles protégées
|
||||
* Structure du classeur protégée
|
||||
* Projet VBA verrouillé (mot de passe, déjà posé une fois)
|
||||
4. **Distribuer PROD** : via NAS, USB ou dossier client.
|
||||
|
||||
👉 DEV reste toujours libre, PROD reste verrouillé et sûr.
|
||||
Solution : utiliser la version robuste de `set_cell_silent.vbs`, qui ferme explicitement le classeur et l’application Excel après sauvegarde.
|
||||
|
||||
---
|
||||
|
||||
|
||||
206
README_old.md
Normal file
206
README_old.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# Inventaire-Gestion
|
||||
|
||||
Ce projet permet de gérer les inventaires et les ratios de plusieurs sites à partir d’un fichier Excel central et d’une base MySQL.
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
* Mise à jour du pilote ODBC sur machine client
|
||||
* Authentification par site
|
||||
* Lecture automatique du fichier `ratio.xlsm`
|
||||
* Insertion des données dans la base correspondante
|
||||
* Gestion des articles et inventaire par code barres
|
||||
* Affichage dans Streamlit (à venir)
|
||||
|
||||
# 🚀 Installation du pilote MySQL ODBC 8.3
|
||||
|
||||
## ✅ Objectif
|
||||
|
||||
Assurer une compatibilité totale entre les fichiers Excel connectés à MySQL et le pilote ODBC, en uniformisant tous les postes avec la **version 8.3.0 du connecteur MySQL ODBC**.
|
||||
|
||||
---
|
||||
|
||||
## 📥 Étapes d'installation
|
||||
|
||||
1. Télécharger le fichier `mysql-connector-odbc-8.3.0-winx64.msi` depuis le site officiel :
|
||||
|
||||
👉 [https://dev.mysql.com/downloads/connector/odbc/](https://dev.mysql.com/downloads/connector/odbc/)
|
||||
|
||||
2. Copier le fichier dans ce dossier sur chaque poste cible :
|
||||
|
||||
```
|
||||
C:\Installers\
|
||||
```
|
||||
|
||||
3. Créer un fichier `Installer_ODBC_93.bat` contenant :
|
||||
|
||||
```bat
|
||||
@echo off
|
||||
echo ===============================
|
||||
echo Installation MySQL ODBC 8.3.0
|
||||
echo ===============================
|
||||
|
||||
SET MYPATH=C:\Installers
|
||||
SET INSTALLER=%MYPATH%\mysql-connector-odbc-8.3.0-winx64.msi
|
||||
|
||||
IF EXIST "%INSTALLER%" (
|
||||
echo >> Démarrage de l'installation silencieuse...
|
||||
msiexec /i "%INSTALLER%" /qn
|
||||
echo >> Installation terminée avec succès.
|
||||
pause
|
||||
) ELSE (
|
||||
echo >> Fichier MSI non trouvé :
|
||||
echo >> %INSTALLER%
|
||||
pause
|
||||
)
|
||||
```
|
||||
|
||||
4. Lancer le script :
|
||||
|
||||
* clic droit sur `Installer_ODBC_83.bat`
|
||||
* puis **"Exécuter en tant qu’administrateur"**
|
||||
|
||||
---
|
||||
|
||||
## 🔁 (Optionnel) Désinstallation d'une version précédente
|
||||
|
||||
Pour supprimer proprement une version antérieure (ex : 8.2), vous pouvez ajouter :
|
||||
|
||||
```bat
|
||||
msiexec /x {GUID-DE-LA-VERSION-8.2} /qn
|
||||
```
|
||||
|
||||
*(à compléter avec l'identifiant de produit si nécessaire)*
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Conseils
|
||||
|
||||
* Intégrez ces fichiers dans votre dépôt (ex : dossier `Installers/`)
|
||||
* Versionnez votre script dans Git (Gitea) pour faciliter le déploiement sur tous les sites
|
||||
* Utilisez RustDesk ou accès direct pour l'installation sur les PC distants
|
||||
|
||||
# 🔧 Gestion et distribution du fichier Excel `Ratio_prod.xlsm`
|
||||
|
||||
Ce document décrit le fonctionnement mis en place pour garantir une version stable et toujours à jour du fichier Excel `Ratio_prod.xlsm`, utilisé dans le cadre du projet **Ratio & Inventaires**.
|
||||
|
||||
---
|
||||
|
||||
## 🗃️ Organisation des fichiers
|
||||
|
||||
### Structure dans le projet (Git)
|
||||
|
||||
/Excel/
|
||||
├── dev/
|
||||
│ └── Ratio\_dev.xlsm ← Fichier de travail
|
||||
├── prod/
|
||||
│ └── Ratio\_prod.xlsm ← Fichier de production (non suivi par Git)
|
||||
|
||||
* `Ratio_dev.xlsm` : version de développement modifiable, suivie par Git.
|
||||
* `Ratio_prod.xlsm` : version validée, protégée (ajoutée au `.gitignore` pour éviter tout push accidentel).
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Synchronisation automatique avec le NAS Synology
|
||||
|
||||
Le fichier `Ratio_prod.xlsm` est copié automatiquement sur le NAS Synology, dans un dossier partagé :
|
||||
|
||||
### ⚙️ Configuration
|
||||
|
||||
* **Synology Drive Server** est activé sur le NAS.
|
||||
* **Synology Drive Client** est installé sur le poste de travail.
|
||||
* Dossier synchronisé : `Partage_Ratio`
|
||||
* Mode de synchronisation recommandé : `Téléchargement uniquement`.
|
||||
|
||||
---
|
||||
|
||||
## 🔌 Copie automatique vers clé USB (via NAS)
|
||||
|
||||
### Prérequis
|
||||
|
||||
* Application **USB Copy** installée et activée sur le NAS.
|
||||
|
||||
### Fonctionnement
|
||||
|
||||
1. Brancher une clé USB sur le port en façade du NAS.
|
||||
2. Le NAS copie automatiquement `Ratio_prod.xlsm` sur la clé USB si une version plus récente est disponible.
|
||||
3. Le fichier est copié dans le dossier racine de la clé, ou dans un dossier `Ratio/`.
|
||||
|
||||
### Avantages
|
||||
|
||||
* Pas besoin d’ordinateur pour copier à la main.
|
||||
* Copie toujours à jour dès que la clé est branchée.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Installation manuelle depuis une clé USB
|
||||
|
||||
Contenu du dossier USB :
|
||||
|
||||
```
|
||||
INSTALL_RATIO/
|
||||
├── Ratio_prod.xlsm
|
||||
└── Installer_Prod.bat
|
||||
```
|
||||
|
||||
### Étapes :
|
||||
|
||||
1. Brancher la clé USB sur le poste utilisateur.
|
||||
2. Lancer `Installer_Prod.bat` **en tant qu’administrateur**.
|
||||
3. Le fichier sera copié dans :
|
||||
|
||||
```
|
||||
C:\Program Files\Ratio\Ratio.xlsm
|
||||
```
|
||||
|
||||
et protégé en lecture seule.
|
||||
|
||||
---
|
||||
|
||||
## ♻️ Restaurer la version de production dans le projet (dev)
|
||||
|
||||
Si la version de travail (`dev`) a été corrompue ou modifiée par erreur :
|
||||
|
||||
```bash
|
||||
cp Excel/prod/Ratio_Cuisine.xlsm Excel/dev/Ratio_Cuisine_dev_old.xlsm
|
||||
```
|
||||
|
||||
Ou sous Windows :
|
||||
|
||||
```powershell
|
||||
Copy-Item -Path "Excel\prod\Ratio_prod.xlsm" -Destination "Excel\dev\Ratio_dev.xlsm" -Force
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧾 Notes complémentaires
|
||||
|
||||
* Le fichier `Ratio_prod.xlsm` n’est **pas suivi par Git**, car exclu via `.gitignore`.
|
||||
* Il est **protégé en lecture seule** sur le poste utilisateur.
|
||||
* Une sauvegarde automatique est possible via **Hyper Backup** ou les **snapshots** du NAS.
|
||||
|
||||
---
|
||||
|
||||
# 📋 Checklist Release DEV → PROD
|
||||
|
||||
Pour garantir que DEV reste ouvert et que PROD soit protégé :
|
||||
|
||||
1. **Travailler dans DEV** : `Excel/dev/Ratio_dev.xlsm` (projet VBA non verrouillé).
|
||||
2. **Lancer le script batch** (`build_prod.bat`).
|
||||
|
||||
* Copie DEV → PROD
|
||||
* Application des protections (feuilles, structure, horodatage)
|
||||
* Vérification/verrouillage du projet VBA PROD (manuel si nécessaire)
|
||||
3. **Vérifier PROD** :
|
||||
|
||||
* Feuilles protégées
|
||||
* Structure du classeur protégée
|
||||
* Projet VBA verrouillé (mot de passe, déjà posé une fois)
|
||||
4. **Distribuer PROD** : via NAS, USB ou dossier client.
|
||||
|
||||
👉 DEV reste toujours libre, PROD reste verrouillé et sûr.
|
||||
|
||||
---
|
||||
|
||||
## Auteur
|
||||
|
||||
Michel
|
||||
@@ -1,92 +0,0 @@
|
||||
@echo off
|
||||
chcp 1252 >nul
|
||||
setlocal EnableExtensions
|
||||
|
||||
REM ===================== PARAMS =====================
|
||||
set "ROOT=C:\Users\miche\PycharmProjects\Ratio_Inventaires"
|
||||
set "DEV_XLSM=%ROOT%\Excel\dev\Ratio_dev.xlsm"
|
||||
set "PROD_DIR=%ROOT%\Excel\prod"
|
||||
set "PROD_XLSM=%PROD_DIR%\Ratio_prod.xlsm"
|
||||
set "VERSION_TXT=%ROOT%\Excel\dev\VERSION.txt"
|
||||
set "SHEET_NAME=Tableau de bord"
|
||||
set "CELL_ADDR=U1"
|
||||
|
||||
echo.
|
||||
echo === [1/4] Prep ===============================================
|
||||
|
||||
if not exist "%PROD_DIR%" md "%PROD_DIR%"
|
||||
|
||||
if not exist "%DEV_XLSM%" goto ERR_NO_DEV
|
||||
|
||||
if not exist "%VERSION_TXT%" (
|
||||
>"%VERSION_TXT%" echo 1.0.0
|
||||
echo [INFO] VERSION.txt created with 1.0.0
|
||||
)
|
||||
|
||||
for /f %%i in ('powershell -NoProfile -Command "(Get-Date).ToString('yyyy_MM_dd')"') do set "DATESTAMP=%%i"
|
||||
set "ARCHIVE_FILE=%PROD_DIR%\Ratio_prod_%DATESTAMP%.xlsm"
|
||||
|
||||
echo.
|
||||
echo === [2/4] Lire VERSION.txt et maj cellule =====================
|
||||
|
||||
for /f "usebackq delims=" %%v in ("%VERSION_TXT%") do set "NEW_VER=%%v"
|
||||
set "NEW_VER=%NEW_VER: =%"
|
||||
if "%NEW_VER%"=="" goto ERR_EMPTY_VER
|
||||
echo Version: %NEW_VER%
|
||||
|
||||
REM Mise a jour de P1 dans DEV via VBS (macros & evenements OFF)
|
||||
cscript //nologo "%ROOT%\Scripts\set_cell_silent.vbs" "%DEV_XLSM%" "%SHEET_NAME%" "%CELL_ADDR%" "%NEW_VER%"
|
||||
if errorlevel 1 goto U1_FAIL
|
||||
|
||||
echo [OK] U1 updated on DEV: %NEW_VER%
|
||||
goto AFTER_U1
|
||||
|
||||
:U1_FAIL
|
||||
echo [WARN] U1 update failed on DEV (continuing).
|
||||
|
||||
:AFTER_U1
|
||||
|
||||
echo.
|
||||
echo === [3/4] Backup PROD et purge archives ======================
|
||||
|
||||
if not exist "%PROD_XLSM%" goto NO_BACKUP
|
||||
echo Backup -> "%ARCHIVE_FILE%"
|
||||
copy /Y "%PROD_XLSM%" "%ARCHIVE_FILE%" >nul
|
||||
goto AFTER_BACKUP
|
||||
|
||||
:NO_BACKUP
|
||||
echo Pas de PROD a sauvegarder.
|
||||
|
||||
:AFTER_BACKUP
|
||||
|
||||
REM Garder uniquement les 5 dernieres archives
|
||||
powershell -NoProfile -Command "Get-ChildItem -Path '%PROD_DIR%' -Filter 'Ratio_prod_20*_??_??.xlsm' | Sort-Object LastWriteTime -Descending | Select-Object -Skip 5 | Remove-Item -Force -ErrorAction SilentlyContinue"
|
||||
|
||||
echo.
|
||||
echo === [4/4] Copier DEV -> PROD =================================
|
||||
|
||||
copy /Y "%DEV_XLSM%" "%PROD_XLSM%"
|
||||
if errorlevel 1 goto ERR_COPY
|
||||
|
||||
>"%PROD_DIR%\VERSION.txt" echo %NEW_VER%
|
||||
|
||||
echo.
|
||||
echo [OK] Termine :
|
||||
echo Version : %NEW_VER%
|
||||
echo PROD : "%PROD_XLSM%"
|
||||
echo VERSION : "%PROD_DIR%\VERSION.txt"
|
||||
echo.
|
||||
endlocal & exit /b 0
|
||||
|
||||
REM ===================== ERRORS =====================
|
||||
:ERR_NO_DEV
|
||||
echo [ERROR] DEV file not found: "%DEV_XLSM%"
|
||||
endlocal & exit /b 1
|
||||
|
||||
:ERR_EMPTY_VER
|
||||
echo [ERROR] VERSION.txt is empty. Put a version like 1.8.3 and rerun.
|
||||
endlocal & exit /b 1
|
||||
|
||||
:ERR_COPY
|
||||
echo [ERROR] Copy DEV -> PROD failed.
|
||||
endlocal & exit /b 1
|
||||
115
Scripts/Maj_prod_Cuisine.bat
Normal file
115
Scripts/Maj_prod_Cuisine.bat
Normal file
@@ -0,0 +1,115 @@
|
||||
@echo off
|
||||
setlocal ENABLEDELAYEDEXPANSION
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
echo --- Mise en prod Ratio_Cuisine ---
|
||||
|
||||
set "ROOT=%~dp0.."
|
||||
|
||||
set "VERSION_FILE=%ROOT%\Excel\dev\Ratio_Cuisine_VERSION.txt"
|
||||
set "SRC=%ROOT%\Excel\dev\Ratio_Cuisine_dev.xlsm"
|
||||
set "DST_DIR=%ROOT%\Excel\prod"
|
||||
set "DST=%DST_DIR%\Ratio_Cuisine.xlsm"
|
||||
set "BACKUP_DIR=%ROOT%\Excel\backup"
|
||||
set KEEP_BACKUPS=10
|
||||
|
||||
echo.
|
||||
echo ROOT = %ROOT%
|
||||
echo SRC = %SRC%
|
||||
echo DST = %DST%
|
||||
echo BACKUP_DIR = %BACKUP_DIR%
|
||||
echo VERSION_FILE= %VERSION_FILE%
|
||||
echo.
|
||||
|
||||
set "OLD_VERSION="
|
||||
if exist "%VERSION_FILE%" (
|
||||
set /p OLD_VERSION=<"%VERSION_FILE%"
|
||||
)
|
||||
|
||||
if not exist "%SRC%" (
|
||||
echo ERREUR : fichier source introuvable :
|
||||
echo %SRC%
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%DST_DIR%" (
|
||||
echo ERREUR : dossier de destination introuvable :
|
||||
echo %DST_DIR%
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%BACKUP_DIR%" (
|
||||
echo Creation du dossier backup :
|
||||
echo %BACKUP_DIR%
|
||||
mkdir "%BACKUP_DIR%"
|
||||
)
|
||||
|
||||
if exist "%DST_DIR%\~$Ratio_Cuisine.xlsm" (
|
||||
echo ERREUR : Ratio_Cuisine.xlsm est ouvert dans Excel.
|
||||
echo Ferme le classeur de production avant de relancer la mise en prod.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if exist "%DST%" (
|
||||
if "%OLD_VERSION%"=="" (
|
||||
set "BACKUP=%BACKUP_DIR%\Ratio_Cuisine_sansVersion.xlsm"
|
||||
) else (
|
||||
set "BACKUP=%BACKUP_DIR%\Ratio_Cuisine_Vers%OLD_VERSION%.xlsm"
|
||||
)
|
||||
|
||||
echo Sauvegarde de l'ancienne production...
|
||||
echo De : %DST%
|
||||
echo Vers : !BACKUP!
|
||||
|
||||
copy /Y "%DST%" "!BACKUP!"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo ERREUR : la sauvegarde de l'ancienne production a echoue.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "!BACKUP!" (
|
||||
echo ERREUR : le fichier de sauvegarde n'a pas ete cree.
|
||||
echo Attendu : !BACKUP!
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Sauvegarde OK : !BACKUP!
|
||||
) else (
|
||||
echo Aucun ancien fichier de production trouve, pas de sauvegarde a faire.
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Copie vers la version de production...
|
||||
copy /Y "%SRC%" "%DST%"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo ERREUR lors de la copie vers production.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%DST%" (
|
||||
echo ERREUR : le fichier de production n'a pas ete cree.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Mise a jour de version...
|
||||
python "%~dp0maj_version.py" "%VERSION_FILE%" "%DST%"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo ERREUR lors de la mise a jour de version.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Nettoyage des anciennes sauvegardes Cuisine...
|
||||
for /f "skip=%KEEP_BACKUPS% delims=" %%F in ('dir /b /a-d /o-d "%BACKUP_DIR%\Ratio_Cuisine_Vers*.xlsm" 2^>nul') do (
|
||||
echo Suppression ancienne sauvegarde : %%F
|
||||
del /q "%BACKUP_DIR%\%%F"
|
||||
)
|
||||
|
||||
echo.
|
||||
echo --- Mise en prod terminee ---
|
||||
exit /b 0
|
||||
114
Scripts/Maj_prod_Restauration.bat
Normal file
114
Scripts/Maj_prod_Restauration.bat
Normal file
@@ -0,0 +1,114 @@
|
||||
@echo off
|
||||
setlocal ENABLEDELAYEDEXPANSION
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
echo --- Mise en prod Ratio_Restauration ---
|
||||
|
||||
set "ROOT=%~dp0.."
|
||||
|
||||
set "VERSION_FILE=%ROOT%\Excel\dev\Ratio_Restauration_VERSION.txt"
|
||||
set "SRC=%ROOT%\Excel\dev\Ratio_Restauration_dev.xlsm"
|
||||
set "DST_DIR=%ROOT%\Excel\prod"
|
||||
set "DST=%DST_DIR%\Ratio_Restauration.xlsm"
|
||||
set "BACKUP_DIR=%ROOT%\Excel\backup"
|
||||
set KEEP_BACKUPS=10
|
||||
|
||||
echo.
|
||||
echo ROOT = %ROOT%
|
||||
echo SRC = %SRC%
|
||||
echo DST = %DST%
|
||||
echo BACKUP_DIR = %BACKUP_DIR%
|
||||
echo VERSION_FILE= %VERSION_FILE%
|
||||
echo.
|
||||
|
||||
set "OLD_VERSION="
|
||||
if exist "%VERSION_FILE%" (
|
||||
set /p OLD_VERSION=<"%VERSION_FILE%"
|
||||
)
|
||||
|
||||
if not exist "%SRC%" (
|
||||
echo ERREUR : fichier source introuvable :
|
||||
echo %SRC%
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%DST_DIR%" (
|
||||
echo ERREUR : dossier de destination introuvable :
|
||||
echo %DST_DIR%
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%BACKUP_DIR%" (
|
||||
echo Creation du dossier backup :
|
||||
echo %BACKUP_DIR%
|
||||
mkdir "%BACKUP_DIR%"
|
||||
)
|
||||
|
||||
if exist "%DST_DIR%\~$Ratio_Restauration.xlsm" (
|
||||
echo ERREUR : Ratio_Restauration.xlsm est ouvert dans Excel.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if exist "%DST%" (
|
||||
if "%OLD_VERSION%"=="" (
|
||||
set "BACKUP=%BACKUP_DIR%\Ratio_Restauration_sansVersion.xlsm"
|
||||
) else (
|
||||
set "BACKUP=%BACKUP_DIR%\Ratio_Restauration_Vers%OLD_VERSION%.xlsm"
|
||||
)
|
||||
|
||||
echo Sauvegarde de l'ancienne production...
|
||||
echo De : %DST%
|
||||
echo Vers : !BACKUP!
|
||||
|
||||
copy /Y "%DST%" "!BACKUP!"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo ERREUR : la sauvegarde de l'ancienne production a echoue.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "!BACKUP!" (
|
||||
echo ERREUR : le fichier de sauvegarde n'a pas ete cree.
|
||||
echo Attendu : !BACKUP!
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Sauvegarde OK : !BACKUP!
|
||||
) else (
|
||||
echo Aucun ancien fichier de production trouve, pas de sauvegarde a faire.
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Copie vers la version de production...
|
||||
copy /Y "%SRC%" "%DST%"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo ERREUR lors de la copie vers production.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%DST%" (
|
||||
echo ERREUR : le fichier de production n'a pas ete cree.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Mise a jour de version...
|
||||
python "%~dp0maj_version.py" "%VERSION_FILE%" "%DST%"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo ERREUR lors de la mise a jour de version.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Nettoyage des anciennes sauvegardes Restauration...
|
||||
for /f "skip=%KEEP_BACKUPS% delims=" %%F in ('dir /b /a-d /o-d "%BACKUP_DIR%\Ratio_Restauration_Vers*.xlsm" 2^>nul') do (
|
||||
echo Suppression ancienne sauvegarde : %%F
|
||||
del /q "%BACKUP_DIR%\%%F"
|
||||
)
|
||||
|
||||
echo.
|
||||
echo --- Mise en prod terminee ---
|
||||
exit /b 0
|
||||
@@ -1 +0,0 @@
|
||||
=== [4/4] Copier DEV - =================================
|
||||
2
Scripts/Update_Version/Cuisine.bat
Normal file
2
Scripts/Update_Version/Cuisine.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
powershell -ExecutionPolicy Bypass -File "%~dp0Maj_Ratio_Cuisine.ps1"
|
||||
42
Scripts/Update_Version/Cuisine.ps1
Normal file
42
Scripts/Update_Version/Cuisine.ps1
Normal file
@@ -0,0 +1,42 @@
|
||||
$source = "\\mj91.fr\PyCharm\Ratio_Inventaires\Excel\prod"
|
||||
$dest = "C:\Users\$env:USERNAME\Domo91"
|
||||
|
||||
$nomFichier = "Ratio_Cuisine.xlsm"
|
||||
$nomVersion = "Ratio_Cuisine_VERSION.txt"
|
||||
|
||||
$sourceXlsm = Join-Path $source $nomFichier
|
||||
$sourceVersion = Join-Path $source $nomVersion
|
||||
|
||||
$destXlsm = Join-Path $dest $nomFichier
|
||||
$destVersion = Join-Path $dest $nomVersion
|
||||
|
||||
if (!(Test-Path $dest)) {
|
||||
New-Item -ItemType Directory -Path $dest -Force | Out-Null
|
||||
}
|
||||
|
||||
if (!(Test-Path $sourceVersion)) {
|
||||
Add-Type -AssemblyName PresentationFramework
|
||||
[System.Windows.MessageBox]::Show("Le dossier de mise à jour sur le NAS est inaccessible.", "Mise à jour Ratio Cuisine")
|
||||
exit
|
||||
}
|
||||
|
||||
$versionServeur = (Get-Content $sourceVersion -ErrorAction Stop).Trim()
|
||||
|
||||
if (Test-Path $destVersion) {
|
||||
$versionLocale = (Get-Content $destVersion -ErrorAction SilentlyContinue).Trim()
|
||||
} else {
|
||||
$versionLocale = ""
|
||||
}
|
||||
|
||||
if ($versionServeur -ne $versionLocale) {
|
||||
$excel = Get-Process EXCEL -ErrorAction SilentlyContinue
|
||||
if ($excel) {
|
||||
$excel | Stop-Process -Force
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
Copy-Item -Path $sourceXlsm -Destination $destXlsm -Force
|
||||
Copy-Item -Path $sourceVersion -Destination $destVersion -Force
|
||||
}
|
||||
|
||||
Start-Process $destXlsm
|
||||
2
Scripts/Update_Version/Restauration.bat
Normal file
2
Scripts/Update_Version/Restauration.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
powershell -ExecutionPolicy Bypass -File "%~dp0Maj_Ratio_Restauration.ps1"
|
||||
42
Scripts/Update_Version/Restauration.ps1
Normal file
42
Scripts/Update_Version/Restauration.ps1
Normal file
@@ -0,0 +1,42 @@
|
||||
$source = "\\mj91.fr\PyCharm\Ratio_Inventaires\Excel\prod"
|
||||
$dest = "C:\Users\$env:USERNAME\Domo91"
|
||||
|
||||
$nomFichier = "Ratio_Restauration.xlsm"
|
||||
$nomVersion = "Ratio_Restauration_VERSION.txt"
|
||||
|
||||
$sourceXlsm = Join-Path $source $nomFichier
|
||||
$sourceVersion = Join-Path $source $nomVersion
|
||||
|
||||
$destXlsm = Join-Path $dest $nomFichier
|
||||
$destVersion = Join-Path $dest $nomVersion
|
||||
|
||||
if (!(Test-Path $dest)) {
|
||||
New-Item -ItemType Directory -Path $dest -Force | Out-Null
|
||||
}
|
||||
|
||||
if (!(Test-Path $sourceVersion)) {
|
||||
Add-Type -AssemblyName PresentationFramework
|
||||
[System.Windows.MessageBox]::Show("Le dossier de mise à jour sur le NAS est inaccessible.", "Mise à jour Ratio Restauration")
|
||||
exit
|
||||
}
|
||||
|
||||
$versionServeur = (Get-Content $sourceVersion -ErrorAction Stop).Trim()
|
||||
|
||||
if (Test-Path $destVersion) {
|
||||
$versionLocale = (Get-Content $destVersion -ErrorAction SilentlyContinue).Trim()
|
||||
} else {
|
||||
$versionLocale = ""
|
||||
}
|
||||
|
||||
if ($versionServeur -ne $versionLocale) {
|
||||
$excel = Get-Process EXCEL -ErrorAction SilentlyContinue
|
||||
if ($excel) {
|
||||
$excel | Stop-Process -Force
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
Copy-Item -Path $sourceXlsm -Destination $destXlsm -Force
|
||||
Copy-Item -Path $sourceVersion -Destination $destVersion -Force
|
||||
}
|
||||
|
||||
Start-Process $destXlsm
|
||||
135
Scripts/maj_version.py
Normal file
135
Scripts/maj_version.py
Normal file
@@ -0,0 +1,135 @@
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from datetime import date
|
||||
|
||||
# =========================================================
|
||||
# CONFIG
|
||||
# =========================================================
|
||||
|
||||
VBS_SCRIPT = Path(__file__).parent / "set_cell_silent.vbs"
|
||||
|
||||
# =========================================================
|
||||
# PREFIXE VERSION
|
||||
# =========================================================
|
||||
|
||||
def detecter_major(path: Path) -> int:
|
||||
|
||||
nom = path.name.lower()
|
||||
|
||||
if "restauration" in nom:
|
||||
return 2
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
# =========================================================
|
||||
# LECTURE VERSION
|
||||
# =========================================================
|
||||
|
||||
def lire_version(path: Path, major: int):
|
||||
|
||||
if not path.exists():
|
||||
return [major, 0, 0]
|
||||
|
||||
contenu = path.read_text(encoding="utf-8").strip().splitlines()
|
||||
|
||||
if not contenu:
|
||||
return [major, 0, 0]
|
||||
|
||||
try:
|
||||
version = [int(x) for x in contenu[0].split(".")]
|
||||
|
||||
while len(version) < 3:
|
||||
version.append(0)
|
||||
|
||||
version = version[:3]
|
||||
|
||||
# force le major correct
|
||||
version[0] = major
|
||||
|
||||
return version
|
||||
|
||||
except Exception:
|
||||
return [major, 0, 0]
|
||||
|
||||
|
||||
# =========================================================
|
||||
# INCREMENT
|
||||
# =========================================================
|
||||
|
||||
def increment_patch(version):
|
||||
version[2] += 1
|
||||
return version
|
||||
|
||||
|
||||
# =========================================================
|
||||
# ECRITURE TXT
|
||||
# =========================================================
|
||||
|
||||
def ecrire_version_txt(path: Path, version):
|
||||
|
||||
version_str = ".".join(str(x) for x in version)
|
||||
|
||||
contenu = f"{version_str}\n{date.today().isoformat()}\n"
|
||||
|
||||
path.write_text(contenu, encoding="utf-8")
|
||||
|
||||
return version_str
|
||||
|
||||
|
||||
# =========================================================
|
||||
# ECRITURE EXCEL
|
||||
# =========================================================
|
||||
|
||||
def ecrire_version_excel(classeur: Path, version_str: str):
|
||||
|
||||
if not classeur.exists():
|
||||
print(f"Classeur introuvable : {classeur}")
|
||||
return
|
||||
|
||||
valeur = f"Version : {version_str}"
|
||||
|
||||
cmd = [
|
||||
"cscript",
|
||||
"//nologo",
|
||||
str(VBS_SCRIPT),
|
||||
str(classeur),
|
||||
"Tableau de bord",
|
||||
"C1",
|
||||
valeur
|
||||
]
|
||||
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
|
||||
# =========================================================
|
||||
# MAIN
|
||||
# =========================================================
|
||||
|
||||
def main():
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage :")
|
||||
print("python maj_version.py VERSION.txt classeur.xlsm")
|
||||
sys.exit(1)
|
||||
|
||||
version_file = Path(sys.argv[1])
|
||||
classeur = Path(sys.argv[2])
|
||||
|
||||
major = detecter_major(version_file)
|
||||
|
||||
version = lire_version(version_file, major)
|
||||
|
||||
version = increment_patch(version)
|
||||
|
||||
version_str = ecrire_version_txt(version_file, version)
|
||||
|
||||
ecrire_version_excel(classeur, version_str)
|
||||
|
||||
print(f"Nouvelle version : {version_str}")
|
||||
print(f"Classeur mis à jour : {classeur.name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,68 +1,71 @@
|
||||
' set_cell_silent.vbs
|
||||
' Usage: cscript //nologo set_cell_silent.vbs "C:\chemin\fichier.xlsm" "NomFeuille" "A1" "Valeur"
|
||||
|
||||
Option Explicit
|
||||
Dim f, sheetName, addr, val
|
||||
Dim xl, wb, ws
|
||||
|
||||
Dim xl, wb
|
||||
Dim filePath, sheetName, cellAddress, cellValue
|
||||
|
||||
If WScript.Arguments.Count < 4 Then
|
||||
WScript.Echo "[ERR] Args: set_cell_silent.vbs <fichier.xlsm> <feuille> <cellule> <valeur>"
|
||||
WScript.Echo "[ERR] Usage : set_cell_silent.vbs fichier.xlsm feuille cellule valeur"
|
||||
WScript.Quit 1
|
||||
End If
|
||||
|
||||
f = WScript.Arguments(0)
|
||||
filePath = WScript.Arguments(0)
|
||||
sheetName = WScript.Arguments(1)
|
||||
addr = WScript.Arguments(2)
|
||||
val = WScript.Arguments(3)
|
||||
cellAddress = WScript.Arguments(2)
|
||||
cellValue = WScript.Arguments(3)
|
||||
|
||||
On Error Resume Next
|
||||
|
||||
Set xl = CreateObject("Excel.Application")
|
||||
If Err.Number <> 0 Then
|
||||
WScript.Echo "[ERR] Excel non disponible (" & Err.Description & ")"
|
||||
WScript.Echo "[ERR] Creation Excel : " & Err.Description
|
||||
WScript.Quit 1
|
||||
End If
|
||||
On Error GoTo 0
|
||||
|
||||
xl.Visible = False
|
||||
xl.DisplayAlerts = False
|
||||
|
||||
' Désactiver macros et événements AVANT d’ouvrir
|
||||
On Error Resume Next
|
||||
xl.AutomationSecurity = 3 ' msoAutomationSecurityForceDisable
|
||||
xl.EnableEvents = False
|
||||
xl.ScreenUpdating = False
|
||||
On Error GoTo 0
|
||||
xl.AskToUpdateLinks = False
|
||||
xl.AutomationSecurity = 3
|
||||
|
||||
Err.Clear
|
||||
Set wb = xl.Workbooks.Open(filePath, 0, False)
|
||||
|
||||
On Error Resume Next
|
||||
Set wb = xl.Workbooks.Open(f, False, False)
|
||||
If Err.Number <> 0 Then
|
||||
xl.Quit
|
||||
WScript.Echo "[ERR] Ouverture classeur: " & Err.Description
|
||||
xl.Quit
|
||||
Set xl = Nothing
|
||||
WScript.Quit 1
|
||||
End If
|
||||
On Error GoTo 0
|
||||
|
||||
On Error Resume Next
|
||||
Set ws = wb.Worksheets.Item(sheetName)
|
||||
Err.Clear
|
||||
wb.Worksheets(sheetName).Range(cellAddress).Value = cellValue
|
||||
|
||||
If Err.Number <> 0 Then
|
||||
WScript.Echo "[ERR] Ecriture cellule: " & Err.Description
|
||||
wb.Close False
|
||||
xl.Quit
|
||||
WScript.Echo "[ERR] Feuille introuvable: " & sheetName
|
||||
Set wb = Nothing
|
||||
Set xl = Nothing
|
||||
WScript.Quit 1
|
||||
End If
|
||||
On Error GoTo 0
|
||||
|
||||
On Error Resume Next
|
||||
ws.Range(addr).Value2 = val
|
||||
If Err.Number <> 0 Then
|
||||
wb.Close False
|
||||
xl.Quit
|
||||
WScript.Echo "[ERR] Ecriture cellule " & addr & " : " & Err.Description
|
||||
WScript.Quit 1
|
||||
End If
|
||||
On Error GoTo 0
|
||||
|
||||
Err.Clear
|
||||
wb.Save
|
||||
|
||||
If Err.Number <> 0 Then
|
||||
WScript.Echo "[ERR] Sauvegarde: " & Err.Description
|
||||
wb.Close False
|
||||
xl.Quit
|
||||
Set wb = Nothing
|
||||
Set xl = Nothing
|
||||
WScript.Quit 1
|
||||
End If
|
||||
|
||||
Err.Clear
|
||||
wb.Close False
|
||||
Set wb = Nothing
|
||||
|
||||
xl.Quit
|
||||
Set xl = Nothing
|
||||
|
||||
WScript.Quit 0
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,17 +0,0 @@
|
||||
import os, mysql.connector
|
||||
from dotenv import load_dotenv
|
||||
from pathlib import Path
|
||||
|
||||
# charge le .env **avec chemin absolu**
|
||||
load_dotenv(Path(__file__).resolve().parent.joinpath(".env"))
|
||||
|
||||
conn = mysql.connector.connect(
|
||||
host=os.getenv("DB_HOST"),
|
||||
user=os.getenv("DB_USER"),
|
||||
password=os.getenv("DB_PASSWORD"),
|
||||
database=os.getenv("DB_NAME"),
|
||||
connection_timeout=5,
|
||||
)
|
||||
conn.ping(reconnect=True, attempts=3, delay=1)
|
||||
print("CONNECTED" if conn.is_connected() else "KO")
|
||||
conn.close()
|
||||
@@ -1,427 +0,0 @@
|
||||
# app_users.py — création + modification de champs (wide + onglets + grille)
|
||||
import os
|
||||
import re
|
||||
from datetime import date, datetime
|
||||
|
||||
import bcrypt
|
||||
import mysql.connector
|
||||
from mysql.connector import errorcode
|
||||
import streamlit as st
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# -----------------------
|
||||
# Auth minimale
|
||||
# -----------------------
|
||||
def require_login():
|
||||
st.markdown(
|
||||
"""
|
||||
<style>
|
||||
body {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
load_dotenv()
|
||||
admin_user = os.getenv("ADMIN_USER")
|
||||
admin_hash = os.getenv("ADMIN_PASS_HASH")
|
||||
|
||||
if not admin_hash:
|
||||
st.error("ADMIN_PASS_HASH manquant dans .env")
|
||||
st.stop()
|
||||
|
||||
if "auth_ok" not in st.session_state:
|
||||
st.session_state.auth_ok = False
|
||||
|
||||
if not st.session_state.auth_ok:
|
||||
st.markdown("<h2 style='text-align:center;'>🔐 Accès restreint</h2>", unsafe_allow_html=True)
|
||||
|
||||
# conteneur centré
|
||||
login_left, login_center, login_right = st.columns([1, 2, 1])
|
||||
with login_center:
|
||||
st.markdown(
|
||||
"""
|
||||
<div style='border:1px solid #ddd; border-radius:12px; padding:2rem; background:#fafafa;'>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
u = st.text_input("Utilisateur", key="login_user")
|
||||
p = st.text_input("Mot de passe", type="password", key="login_pass")
|
||||
|
||||
if st.button("Se connecter", use_container_width=True):
|
||||
if u == admin_user and bcrypt.checkpw(p.encode(), admin_hash.encode()):
|
||||
st.session_state.auth_ok = True
|
||||
st.rerun()
|
||||
else:
|
||||
st.error("Identifiants invalides")
|
||||
|
||||
st.markdown("</div>", unsafe_allow_html=True)
|
||||
|
||||
st.stop()
|
||||
|
||||
require_login()
|
||||
|
||||
# -----------------------
|
||||
# Connexion MySQL
|
||||
# -----------------------
|
||||
@st.cache_resource
|
||||
def get_connection():
|
||||
load_dotenv()
|
||||
host = os.getenv("DB_HOST")
|
||||
port = int(os.getenv("MYSQL_PORT", "3306"))
|
||||
user = os.getenv("DB_USER")
|
||||
pwd = os.getenv("DB_PASSWORD")
|
||||
db = os.getenv("DB_NAME")
|
||||
|
||||
missing = [k for k, v in {
|
||||
"MYSQL_USER": user, "MYSQL_PASSWORD": pwd, "MYSQL_HOST": host, "MYSQL_PORT": port, "MYSQL_DATABASE": db
|
||||
}.items() if v in (None, "")]
|
||||
if missing:
|
||||
raise RuntimeError(f"Variables manquantes dans .env : {', '.join(missing)}")
|
||||
|
||||
return mysql.connector.connect(
|
||||
host=host, port=port, user=user, password=pwd, database=db, autocommit=True
|
||||
)
|
||||
|
||||
# -----------------------
|
||||
# Utilitaires
|
||||
# -----------------------
|
||||
EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
|
||||
|
||||
def normalize_phone(phone: str | None) -> str | None:
|
||||
if not phone:
|
||||
return None
|
||||
return re.sub(r"[^\d+]", "", phone)
|
||||
|
||||
def to_sql_date(d: date | str) -> str:
|
||||
if isinstance(d, date):
|
||||
return d.strftime("%Y-%m-%d")
|
||||
for fmt in ("%Y-%m-%d", "%d/%m/%Y"):
|
||||
try:
|
||||
return datetime.strptime(d, fmt).strftime("%Y-%m-%d")
|
||||
except ValueError:
|
||||
continue
|
||||
raise ValueError("Date invalide (attendu: YYYY-MM-DD ou JJ/MM/AAAA)")
|
||||
|
||||
def hash_password(plain: str, rounds: int = 12) -> str:
|
||||
salt = bcrypt.gensalt(rounds=rounds)
|
||||
return bcrypt.hashpw(plain.encode("utf-8"), salt).decode("utf-8")
|
||||
|
||||
def user_exists(cursor, username: str, email: str) -> bool:
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) FROM Utilisateurs WHERE NomUtilisateur=%s OR email=%s",
|
||||
(username, email),
|
||||
)
|
||||
(count,) = cursor.fetchone()
|
||||
return count > 0
|
||||
|
||||
def insert_user(cnx, username, full_name, site, password, expires, phone, email, store_plain=False):
|
||||
if not EMAIL_RE.match(email):
|
||||
raise ValueError("Email invalide.")
|
||||
phone_norm = normalize_phone(phone)
|
||||
exp_sql = to_sql_date(expires)
|
||||
pwd_hash = hash_password(password)
|
||||
|
||||
with cnx.cursor() as cur:
|
||||
if user_exists(cur, username, email):
|
||||
raise RuntimeError("Nom d'utilisateur ou email déjà existant.")
|
||||
if store_plain:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO Utilisateurs
|
||||
(NomUtilisateur, Nom_complet, Site, MotDePasse, MotDePasseHash, DateExpiration, Telephone, email)
|
||||
VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
|
||||
""",
|
||||
(username, full_name, site, password, pwd_hash, exp_sql, phone_norm, email),
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO Utilisateurs
|
||||
(NomUtilisateur, Nom_complet, Site, MotDePasse, MotDePasseHash, DateExpiration, Telephone, email)
|
||||
VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
|
||||
""",
|
||||
(username, full_name, site, None, pwd_hash, exp_sql, phone_norm, email),
|
||||
)
|
||||
return pwd_hash
|
||||
|
||||
def list_users(cnx, limit=200):
|
||||
with cnx.cursor(dictionary=True) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT NomUtilisateur, Nom_complet, Site, DateExpiration, Telephone, email
|
||||
FROM Utilisateurs
|
||||
ORDER BY NomUtilisateur ASC
|
||||
LIMIT %s
|
||||
""",
|
||||
(limit,),
|
||||
)
|
||||
return cur.fetchall()
|
||||
|
||||
def get_user(cnx, username: str):
|
||||
with cnx.cursor(dictionary=True) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT NomUtilisateur, Nom_complet, Site, DateExpiration, Telephone, email
|
||||
FROM Utilisateurs
|
||||
WHERE NomUtilisateur=%s
|
||||
""",
|
||||
(username,),
|
||||
)
|
||||
return cur.fetchone()
|
||||
|
||||
def update_field(cnx, username: str, field: str, value):
|
||||
allowed = {
|
||||
"Nom_complet": "Nom_complet",
|
||||
"Site": "Site",
|
||||
"Telephone": "Telephone",
|
||||
"DateExpiration": "DateExpiration",
|
||||
"email": "email",
|
||||
}
|
||||
if field not in allowed:
|
||||
raise ValueError("Champ non autorisé.")
|
||||
sql_field = allowed[field]
|
||||
|
||||
if field == "email":
|
||||
if not EMAIL_RE.match(str(value)):
|
||||
raise ValueError("Email invalide.")
|
||||
if field == "Telephone":
|
||||
value = normalize_phone(str(value)) if value else None
|
||||
if field == "DateExpiration":
|
||||
value = to_sql_date(value)
|
||||
|
||||
with cnx.cursor() as cur:
|
||||
cur.execute(
|
||||
f"UPDATE Utilisateurs SET {sql_field}=%s WHERE NomUtilisateur=%s",
|
||||
(value, username),
|
||||
)
|
||||
|
||||
def update_password(cnx, username: str, new_password: str):
|
||||
pwd_hash = hash_password(new_password)
|
||||
with cnx.cursor() as cur:
|
||||
cur.execute(
|
||||
"UPDATE Utilisateurs SET MotDePasse=NULL, MotDePasseHash=%s WHERE NomUtilisateur=%s",
|
||||
(pwd_hash, username),
|
||||
)
|
||||
return pwd_hash
|
||||
|
||||
# -----------------------
|
||||
# UI
|
||||
# -----------------------
|
||||
st.set_page_config(page_title="Acces.Utilisateurs", page_icon="👤", layout="wide")
|
||||
|
||||
# CSS doux
|
||||
st.markdown(
|
||||
"""
|
||||
<style>
|
||||
.block-container {padding-top: 1.25rem; padding-bottom: 2rem; max-width: 1400px;}
|
||||
button[kind="primary"], .stButton>button {height: 2.6rem;}
|
||||
.stForm {border: 1px solid #eee; padding: 1rem; border-radius: 12px;}
|
||||
.soft-card {border:1px solid #eee; padding:0.75rem 1rem; border-radius:12px; background:#fafafa;}
|
||||
</style>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
st.title("👤 Acces.Utilisateurs")
|
||||
|
||||
# Connexion DB (badge)
|
||||
try:
|
||||
cnx = get_connection()
|
||||
st.caption("Connexion MySQL via `.env` : **OK** ✅")
|
||||
except Exception as e:
|
||||
st.error(f"Connexion MySQL impossible : {e}")
|
||||
st.stop()
|
||||
|
||||
# Bouton Sortie / Exit
|
||||
hdr_l, hdr_r = st.columns([1, 0.18])
|
||||
with hdr_r:
|
||||
if st.button("🔒 SORTIE / EXIT", use_container_width=True, help="Se déconnecter et verrouiller l'accès"):
|
||||
st.session_state.auth_ok = False
|
||||
st.rerun()
|
||||
|
||||
# -----------------------
|
||||
# Onglets
|
||||
# -----------------------
|
||||
tab_list, tab_create, tab_edit, tab_security = st.tabs(
|
||||
["📋 Liste", "🆕 Créer", "✏️ Modifier", "🔐 Sécurité"]
|
||||
)
|
||||
|
||||
# --- Onglet Liste ---
|
||||
with tab_list:
|
||||
st.subheader("Utilisateurs récents")
|
||||
try:
|
||||
import pandas as pd
|
||||
rows = list_users(cnx, limit=1000)
|
||||
df = pd.DataFrame(rows)
|
||||
|
||||
if not df.empty:
|
||||
df["DateExpiration"] = pd.to_datetime(df["DateExpiration"], errors="coerce").dt.date
|
||||
|
||||
from datetime import date as _date
|
||||
today = _date.today()
|
||||
expired_mask = df["DateExpiration"] <= today
|
||||
|
||||
col_a, col_b = st.columns(2)
|
||||
col_a.metric("Total utilisateurs", len(df))
|
||||
col_b.metric("Comptes périmés", int(expired_mask.sum()))
|
||||
|
||||
def style_expired(row):
|
||||
if row["DateExpiration"] <= today:
|
||||
return ["color: white; background-color: #d32f2f;"] * len(row)
|
||||
return [""] * len(row)
|
||||
styled = df.style.apply(style_expired, axis=1)
|
||||
|
||||
visible_rows = len(df)
|
||||
row_px = 36
|
||||
header_px = 48
|
||||
padding_px = 24
|
||||
height = min(1000, header_px + row_px * visible_rows + padding_px)
|
||||
|
||||
st.dataframe(
|
||||
styled,
|
||||
use_container_width=True,
|
||||
hide_index=True,
|
||||
height=height,
|
||||
)
|
||||
else:
|
||||
st.info("Aucun utilisateur à afficher.")
|
||||
except Exception as e:
|
||||
st.warning(f"Impossible de lister les utilisateurs : {e}")
|
||||
|
||||
# --- Onglet Créer ---
|
||||
with tab_create:
|
||||
with st.form("create_user_form", clear_on_submit=False):
|
||||
st.subheader("Nouveau compte")
|
||||
|
||||
c1, c2, c3 = st.columns([1.2, 1.5, 1])
|
||||
username = c1.text_input("NomUtilisateur", placeholder="ex: cjaquier")
|
||||
full_name = c2.text_input("Nom_complet", placeholder="Clément JAQUIER")
|
||||
site = c3.text_input("Site", placeholder="Roissy", value="Roissy")
|
||||
|
||||
c4, c5, c6 = st.columns([1.4, 1, 1])
|
||||
email = c4.text_input("email", placeholder="prenom.nom@domaine.com")
|
||||
phone = c5.text_input("Téléphone", placeholder="06 12 12 35 32")
|
||||
expires = c6.date_input("DateExpiration", value=date.today())
|
||||
|
||||
c7, c8 = st.columns(2)
|
||||
password = c7.text_input("Mot de passe (saisie)", type="password")
|
||||
confirm = c8.text_input("Confirmer mot de passe", type="password")
|
||||
|
||||
store_plain = st.checkbox("Remplir aussi MotDePasse en clair (déconseillé)", value=False)
|
||||
submitted = st.form_submit_button("Créer l’utilisateur", use_container_width=True)
|
||||
|
||||
if submitted:
|
||||
if not username or not full_name or not site or not email or not password:
|
||||
st.error("Merci de remplir tous les champs obligatoires.")
|
||||
elif password != confirm:
|
||||
st.error("Les mots de passe ne correspondent pas.")
|
||||
else:
|
||||
try:
|
||||
pwd_hash = insert_user(
|
||||
cnx,
|
||||
username=username.strip(),
|
||||
full_name=full_name.strip(),
|
||||
site=site.strip(),
|
||||
password=password,
|
||||
expires=expires,
|
||||
phone=phone.strip() if phone else None,
|
||||
email=email.strip(),
|
||||
store_plain=store_plain,
|
||||
)
|
||||
st.success("Utilisateur créé avec succès ✅")
|
||||
with st.expander("Voir le hash généré (MotDePasseHash)"):
|
||||
st.code(pwd_hash)
|
||||
except mysql.connector.Error as db_err:
|
||||
if db_err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
|
||||
st.error("Identifiants MySQL invalides.")
|
||||
else:
|
||||
st.error(f"Erreur MySQL : {db_err}")
|
||||
except Exception as e:
|
||||
st.error(f"Erreur : {e}")
|
||||
|
||||
# --- Onglet Modifier ---
|
||||
with tab_edit:
|
||||
st.subheader("Modifier un utilisateur existant")
|
||||
|
||||
try:
|
||||
users = list_users(cnx, limit=1000)
|
||||
usernames = [u["NomUtilisateur"] for u in users]
|
||||
except Exception as e:
|
||||
users = []
|
||||
usernames = []
|
||||
st.warning(f"Impossible de charger la liste des utilisateurs : {e}")
|
||||
|
||||
top_left, top_right = st.columns([1.2, 2])
|
||||
|
||||
with top_left:
|
||||
sel_user = st.selectbox("Utilisateur", usernames, placeholder="Choisir un utilisateur")
|
||||
|
||||
field = st.selectbox(
|
||||
"Champ à modifier",
|
||||
["Nom_complet", "Site", "Telephone", "DateExpiration", "email"]
|
||||
)
|
||||
|
||||
if field == "DateExpiration":
|
||||
new_value = st.date_input("Nouvelle valeur (date)", value=date.today(), key="new_date")
|
||||
elif field == "Telephone":
|
||||
new_value = st.text_input("Nouvelle valeur (téléphone)", key="new_tel")
|
||||
else:
|
||||
new_value = st.text_input("Nouvelle valeur", key="new_text")
|
||||
|
||||
update_btn = st.button(
|
||||
"Mettre à jour le champ",
|
||||
type="primary",
|
||||
use_container_width=True,
|
||||
disabled=not sel_user,
|
||||
)
|
||||
|
||||
with top_right:
|
||||
if sel_user:
|
||||
details = get_user(cnx, sel_user)
|
||||
if details:
|
||||
c1, c2, c3, c4, c5 = st.columns(5, gap="small")
|
||||
c1.markdown(f"<div class='soft-card'><b>Nom</b><br>{details['Nom_complet'] or ''}</div>", unsafe_allow_html=True)
|
||||
c2.markdown(f"<div class='soft-card'><b>Site</b><br>{details['Site'] or ''}</div>", unsafe_allow_html=True)
|
||||
c3.markdown(f"<div class='soft-card'><b>Expire</b><br>{details['DateExpiration'] or ''}</div>", unsafe_allow_html=True)
|
||||
c4.markdown(f"<div class='soft-card'><b>Tél</b><br>{details['Telephone'] or ''}</div>", unsafe_allow_html=True)
|
||||
c5.markdown(f"<div class='soft-card'><b>Email</b><br>{details['email'] or ''}</div>", unsafe_allow_html=True)
|
||||
|
||||
if update_btn and sel_user:
|
||||
try:
|
||||
update_field(cnx, sel_user, field, new_value)
|
||||
st.success(f"✅ {field} mis à jour pour {sel_user}")
|
||||
except mysql.connector.Error as db_err:
|
||||
st.error(f"Erreur MySQL : {db_err}")
|
||||
except Exception as e:
|
||||
st.error(f"Erreur : {e}")
|
||||
|
||||
# --- Onglet Sécurité ---
|
||||
with tab_security:
|
||||
st.subheader("Réinitialiser le mot de passe (bcrypt)")
|
||||
try:
|
||||
if 'user_cache_for_pw' not in st.session_state:
|
||||
st.session_state.user_cache_for_pw = [u["NomUtilisateur"] for u in list_users(cnx, limit=1000)]
|
||||
pw_user_list = st.session_state.user_cache_for_pw
|
||||
except Exception:
|
||||
pw_user_list = []
|
||||
|
||||
user_pw = st.selectbox("Utilisateur", pw_user_list, key="pw_user", placeholder="Choisir un utilisateur")
|
||||
colp1, colp2 = st.columns(2)
|
||||
new_pw = colp1.text_input("Nouveau mot de passe", type="password")
|
||||
new_pw2 = colp2.text_input("Confirmer", type="password")
|
||||
|
||||
if st.button("Mettre à jour le mot de passe", disabled=not user_pw, use_container_width=True):
|
||||
if not new_pw:
|
||||
st.error("Mot de passe vide.")
|
||||
elif new_pw != new_pw2:
|
||||
st.error("Les mots de passe ne correspondent pas.")
|
||||
else:
|
||||
try:
|
||||
h = update_password(cnx, user_pw, new_pw)
|
||||
st.success("Mot de passe mis à jour ✅")
|
||||
st.caption("Hash (MotDePasseHash) :")
|
||||
st.code(h)
|
||||
except Exception as e:
|
||||
st.error(f"Erreur : {e}")
|
||||
@@ -1,6 +0,0 @@
|
||||
import bcrypt
|
||||
|
||||
password = input("Entre le mot de passe admin à hacher : ")
|
||||
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
|
||||
print("\nHash généré :")
|
||||
print(hashed.decode())
|
||||
@@ -1,40 +0,0 @@
|
||||
altair==5.5.0
|
||||
attrs==25.4.0
|
||||
blinker==1.9.0
|
||||
cachetools==6.2.1
|
||||
certifi==2025.10.5
|
||||
charset-normalizer==3.4.4
|
||||
click==8.3.0
|
||||
gitdb==4.0.12
|
||||
GitPython==3.1.45
|
||||
idna==3.11
|
||||
Jinja2==3.1.6
|
||||
jsonschema==4.25.1
|
||||
jsonschema-specifications==2025.9.1
|
||||
MarkupSafe==3.0.3
|
||||
mysql-connector-python==9.5.0
|
||||
narwhals==2.9.0
|
||||
numpy==2.3.4
|
||||
packaging==25.0
|
||||
pandas==2.3.3
|
||||
pillow==11.3.0
|
||||
protobuf==6.33.0
|
||||
pyarrow==22.0.0
|
||||
pydeck==0.9.1
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.1.1
|
||||
pytz==2025.2
|
||||
referencing==0.37.0
|
||||
requests==2.32.5
|
||||
rpds-py==0.28.0
|
||||
six==1.17.0
|
||||
smmap==5.0.2
|
||||
streamlit==1.50.0
|
||||
tenacity==9.1.2
|
||||
toml==0.10.2
|
||||
tornado==6.5.2
|
||||
typing_extensions==4.15.0
|
||||
tzdata==2025.2
|
||||
urllib3==2.5.0
|
||||
watchdog==6.0.0
|
||||
bcrypt==5.0.0
|
||||
Reference in New Issue
Block a user