├── Contrat ├── __init__.py ├── fonctionnalités │ ├── __init__.py │ ├── Excel │ │ ├── __init__.py │ │ └── génerationTraitementMoisTerminal.py │ ├── contrat │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-313.pyc │ │ │ └── abrogerContratViaPlanning.cpython-313.pyc │ │ ├── continuiteContrat.py │ │ └── abrogerContratViaPlanning.py │ ├── Facture │ │ ├── Gestion de recouvrement │ │ │ ├── gestionRecouvrement.py │ │ │ ├── dbConfig.py │ │ │ ├── vuesFactures.sql │ │ │ └── Recouvrement.sql │ │ ├── gestionFacture.py │ │ └── modifMontantFacture.py │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ ├── infoClient │ │ ├── AllClients │ │ │ ├── listerTraitementClients.sql │ │ │ └── listerTraitementClients.py │ │ └── Specific │ │ │ ├── selectionClientTraitement.sql │ │ │ └── selectionClientTraitement.py │ ├── triage │ │ ├── triOrdreAlphabetique.py │ │ ├── triTraitementCategorie.py │ │ ├── triTraitementClientSpec.py │ │ └── triSearchContrat.py │ ├── connexionDB.py │ ├── signalement │ │ ├── signalementAD.py │ │ └── gestionEtatSignalement.py │ └── Planning │ │ ├── affichePlanning.py │ │ ├── majPlanningDetail.py │ │ └── ajoutPlanningTraitement.py ├── regroupeTraitementCat │ ├── regroupeTraitement.sql │ └── regroupeTraitement.py ├── CRUDonSignalement.py ├── CRUDonTraitement.py ├── CRUDonFacture.py ├── CRUDonClient.py ├── CRUDonPlanning.py ├── CRUDonHistorique.py └── CRUDonContrat.py ├── .gitignore ├── scriptSQL ├── SuppressionDB.sql └── Planificator.sql ├── README.md └── Account └── accountAvecHash.py /Contrat/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/Excel/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/contrat/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/Facture/Gestion de recouvrement/gestionRecouvrement.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /.idea 3 | /Contrat/fonctionnalités/Excel/__pycache__ 4 | /Contrat/__pycache__ 5 | *.pyc 6 | /Contrat/fonctionnalités/Excel/__pycache__ 7 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josoavj/dbPlanificator/HEAD/Contrat/fonctionnalités/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /Contrat/fonctionnalités/contrat/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josoavj/dbPlanificator/HEAD/Contrat/fonctionnalités/contrat/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /Contrat/regroupeTraitementCat/regroupeTraitement.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | categorieTraitement, 3 | GROUP_CONCAT(typeTraitement) AS traitements 4 | FROM 5 | TypeTraitement 6 | GROUP BY 7 | categorieTraitement; -------------------------------------------------------------------------------- /Contrat/fonctionnalités/contrat/__pycache__/abrogerContratViaPlanning.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josoavj/dbPlanificator/HEAD/Contrat/fonctionnalités/contrat/__pycache__/abrogerContratViaPlanning.cpython-313.pyc -------------------------------------------------------------------------------- /scriptSQL/SuppressionDB.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Script de suppression des tables 3 | */ 4 | 5 | -- Effacer une table dans Planificator 6 | DROP TABLE IF EXISTS Account; 7 | DROP TABLE IF EXISTS Contrat; 8 | DROP TABLE IF EXISTS Traitement; 9 | DROP TABLE IF EXISTS TypeTraitement; 10 | DROP TABLE IF EXISTS Client; 11 | DROP TABLE IF EXISTS Facture; 12 | DROP TABLE IF EXISTS Historique; 13 | DROP TABLE IF EXISTS Planning; 14 | DROP TABLE IF EXISTS PlanningDetails; 15 | DROP TABLE IF EXISTS Signalement; 16 | DROP TABLE IF EXISTS Historique_prix; 17 | DROP TABLE IF EXISTS Remarque; 18 | 19 | 20 | -- Supprimer complètement la base de données 21 | DROP DATABASE IF EXISTS Planificator; 22 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/infoClient/AllClients/listerTraitementClients.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | cl.client_id, 3 | cl.nom AS nom_client, 4 | cl.prenom AS prenom_client, 5 | cl.email AS email_client, 6 | cl.telephone AS telephone_client, 7 | cl.adresse AS adresse_client, 8 | cl.date_ajout AS date_ajout_client, 9 | cl.categorie AS categorie_client, 10 | cl.axe AS axe_client, 11 | COUNT(tr.traitement_id) AS nombre_traitements 12 | FROM 13 | Client cl 14 | LEFT JOIN 15 | Contrat ct ON cl.client_id = ct.client_id 16 | LEFT JOIN 17 | Traitement tr ON ct.contrat_id = tr.traitement_id 18 | GROUP BY 19 | cl.client_id, cl.nom, cl.prenom, cl.email, cl.telephone, cl.adresse, cl.date_ajout, cl.categorie, cl.axe 20 | ORDER BY 21 | cl.nom, cl.prenom; -------------------------------------------------------------------------------- /Contrat/fonctionnalités/infoClient/Specific/selectionClientTraitement.sql: -------------------------------------------------------------------------------- 1 | -- Script SQL pour sélectionner les informations d'un client et le nombre de traitements liés. 2 | 3 | SET @client_id_selectionne = VOTRE_ID_CLIENT; -- Veuillez remplacer VOTRE_ID_CLIENT par l'ID réel du client 4 | 5 | SELECT 6 | cl.client_id, 7 | cl.nom AS nom_client, 8 | cl.prenom AS prenom_client, 9 | cl.email AS email_client, 10 | cl.telephone AS telephone_client, 11 | cl.adresse AS adresse_client, 12 | cl.date_ajout AS date_ajout_client, 13 | cl.categorie AS categorie_client, 14 | cl.axe AS axe_client, 15 | COUNT(t.traitement_id) AS nombre_traitements 16 | FROM 17 | Client cl 18 | LEFT JOIN 19 | Contrat ct ON cl.client_id = ct.client_id 20 | LEFT JOIN 21 | Traitement t ON ct.contrat_id = t.contrat_id 22 | WHERE 23 | cl.client_id = @client_id_selectionne 24 | GROUP BY 25 | cl.client_id, cl.nom, cl.prenom, cl.email, cl.telephone, cl.adresse, cl.date_ajout, cl.categorie, cl.axe; -------------------------------------------------------------------------------- /Contrat/fonctionnalités/triage/triOrdreAlphabetique.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | import asyncio 3 | 4 | async def tri_alphabetique_clients(pool): 5 | """Trie les noms des clients par ordre alphabétique.""" 6 | 7 | async with pool.acquire() as conn: 8 | async with conn.cursor() as cur: 9 | await cur.execute("SELECT nom FROM Client ORDER BY nom ASC") 10 | clients = await cur.fetchall() 11 | 12 | if clients: 13 | print("Liste des clients par ordre alphabétique :") 14 | for nom in clients: 15 | print(nom[0]) 16 | else: 17 | print("Aucun client trouvé.") 18 | 19 | async def main(): 20 | pool = await aiomysql.create_pool(host='127.0.0.1', port=3306, 21 | user='votre_utilisateur', password='votre_mot_de_passe', 22 | db='Planificator', autocommit=True) 23 | 24 | await tri_alphabetique_clients(pool) 25 | 26 | pool.close() 27 | await pool.wait_closed() 28 | 29 | if __name__ == "__main__": 30 | asyncio.run(main()) -------------------------------------------------------------------------------- /Contrat/fonctionnalités/Facture/Gestion de recouvrement/dbConfig.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | 3 | async def create_db_pool(host, port, user, password, database): 4 | """ 5 | Creates and returns an aiomysql connection pool. 6 | """ 7 | try: 8 | pool = await aiomysql.create_pool( 9 | host=host, 10 | port=port, 11 | user=user, 12 | password=password, 13 | db=database, 14 | autocommit=True, # Ensure changes are committed immediately 15 | charset='utf8mb4', # Recommended for broader character support 16 | cursorclass=aiomysql.cursors.DictCursor # Return results as dictionaries 17 | ) 18 | print("Database connection pool created successfully.") 19 | return pool 20 | except aiomysql.Error as e: 21 | print(f"Error connecting to the database: {e}") 22 | return None 23 | 24 | async def close_db_pool(pool): 25 | """ 26 | Closes the database connection pool. 27 | """ 28 | if pool: 29 | pool.close() 30 | await pool.wait_closed() 31 | print("Database connection pool closed.") -------------------------------------------------------------------------------- /Contrat/fonctionnalités/connexionDB.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | 3 | async def DBConnection(): 4 | """ 5 | Demande les informations de connexion à l'utilisateur et crée un pool de connexions. 6 | """ 7 | host = input("Hôte de la base de données (par défaut : localhost) : ") 8 | if not host: 9 | host = "localhost" 10 | 11 | port_str = input("Port de la base de données (par défaut : 3306) : ") 12 | if port_str: 13 | try: 14 | port = int(port_str) 15 | except ValueError: 16 | print("Port invalide. Utilisation du port par défaut 3306.") 17 | port = 3306 18 | else: 19 | port = 3306 20 | 21 | user = input("Nom d'utilisateur : ") 22 | password = input("Mot de passe : ") 23 | database = input("Nom de la base de données : ") 24 | 25 | # Créer le pool de connexions 26 | try: 27 | pool = await aiomysql.create_pool( 28 | host=host, 29 | port=port, 30 | user=user, 31 | password=password, 32 | db=database, 33 | autocommit=True, 34 | maxsize=10 35 | ) 36 | print("Pool de connexions à la base de données créé avec succès.") 37 | return pool 38 | except Exception as e: 39 | print(f"Erreur lors de la création du pool de connexions : {e}") 40 | return None -------------------------------------------------------------------------------- /Contrat/fonctionnalités/triage/triTraitementCategorie.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | import asyncio 3 | 4 | async def trier_traitements_par_categorie(pool): 5 | """Trie les traitements de tous les clients par catégories.""" 6 | 7 | async with pool.acquire() as conn: 8 | async with conn.cursor() as cur: 9 | await cur.execute(""" 10 | SELECT c.nom, tt.categorieTraitement, tt.typeTraitement 11 | FROM Client c 12 | JOIN Contrat co ON c.client_id = co.client_id 13 | JOIN Traitement t ON co.contrat_id = t.contrat_id 14 | JOIN TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement 15 | ORDER BY tt.categorieTraitement, c.nom 16 | """) 17 | traitements = await cur.fetchall() 18 | 19 | if traitements: 20 | print("Traitements des clients par catégories :") 21 | for nom_client, categorie_traitement, type_traitement in traitements: 22 | print(f"Client: {nom_client}, Catégorie: {categorie_traitement}, Type: {type_traitement}") 23 | else: 24 | print("Aucun traitement trouvé.") 25 | 26 | async def main(): 27 | pool = await aiomysql.create_pool(host='127.0.0.1', port=3306, 28 | user='votre_utilisateur', password='votre_mot_de_passe', 29 | db='Planificator', autocommit=True) 30 | 31 | await trier_traitements_par_categorie(pool) 32 | 33 | pool.close() 34 | await pool.wait_closed() 35 | 36 | if __name__ == "__main__": 37 | asyncio.run(main()) -------------------------------------------------------------------------------- /Contrat/fonctionnalités/triage/triTraitementClientSpec.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | import asyncio 3 | 4 | async def trier_traitements_client_par_categorie(pool, client_id): 5 | """Trie les traitements d'un client spécifique par catégories.""" 6 | 7 | async with pool.acquire() as conn: 8 | async with conn.cursor() as cur: 9 | await cur.execute(""" 10 | SELECT tt.categorieTraitement, tt.typeTraitement 11 | FROM Client c 12 | JOIN Contrat co ON c.client_id = co.client_id 13 | JOIN Traitement t ON co.contrat_id = t.contrat_id 14 | JOIN TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement 15 | WHERE c.client_id = %s 16 | ORDER BY tt.categorieTraitement 17 | """, (client_id,)) 18 | traitements = await cur.fetchall() 19 | 20 | if traitements: 21 | print(f"Traitements du client (ID: {client_id}) par catégories :") 22 | for categorie_traitement, type_traitement in traitements: 23 | print(f"Catégorie: {categorie_traitement}, Type: {type_traitement}") 24 | else: 25 | print("Aucun traitement trouvé pour ce client.") 26 | 27 | async def main(): 28 | pool = await aiomysql.create_pool(host='127.0.0.1', port=3306, 29 | user='votre_utilisateur', password='votre_mot_de_passe', 30 | db='Planificator', autocommit=True) 31 | 32 | client_id = int(input("ID du client: ")) 33 | 34 | await trier_traitements_client_par_categorie(pool, client_id) 35 | 36 | pool.close() 37 | await pool.wait_closed() 38 | 39 | if __name__ == "__main__": 40 | asyncio.run(main()) -------------------------------------------------------------------------------- /Contrat/regroupeTraitementCat/regroupeTraitement.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiomysql 3 | 4 | async def regrouper_traitements(): 5 | """Here xD: Récupère et affiche les traitements regroupés par catégorie.""" 6 | host = input("Hôte de la base de données (par défaut : localhost) : ") 7 | if not host: 8 | host = "localhost" 9 | 10 | port_str = input("Port de la base de données (par défaut : 3306) : ") 11 | if port_str: 12 | try: 13 | port = int(port_str) 14 | except ValueError: 15 | print("Port invalide. Utilisation du port par défaut 3306.") 16 | port = 3306 17 | else: 18 | port = 3306 19 | 20 | user = input("Nom d'utilisateur : ") 21 | password = input("Mot de passe : ") 22 | database = input("Nom de la base de données : ") 23 | 24 | try: 25 | # Créer le pool de connexions 26 | pool = await aiomysql.create_pool( 27 | host=host, 28 | port=port, 29 | user=user, 30 | password=password, 31 | db=database, 32 | autocommit=True 33 | ) 34 | 35 | # Requête SQL pour regrouper les traitements par catégorie 36 | query = """ 37 | SELECT 38 | categorieTraitement, 39 | GROUP_CONCAT(typeTraitement) AS traitements 40 | FROM 41 | TypeTraitement 42 | GROUP BY 43 | categorieTraitement; 44 | """ 45 | 46 | await pool.execute(query) 47 | 48 | # Récupération des résultats 49 | resultats = await pool.fetchall() 50 | 51 | # Affichage des résultats 52 | for categorie, traitements in resultats: 53 | print(f"Catégorie: {categorie}, Traitements: {traitements}") 54 | 55 | 56 | finally: 57 | if pool: 58 | pool.close() 59 | await pool.wait_closed() 60 | 61 | 62 | # Exécution de la fonction asynchrone 63 | asyncio.run(regrouper_traitements()) -------------------------------------------------------------------------------- /Contrat/fonctionnalités/signalement/signalementAD.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiomysql 3 | from datetime import datetime 4 | 5 | # Importez les fonctions nécessaires du fichier principal 6 | from gestionEtatSignalement import get_db_credentials, get_client_id, get_client_planning, display_planning_info, get_option_choix, handle_option_1, handle_option_2 7 | 8 | async def enregistrer_signalement(conn, planning_detail_id, motif, type_signalement): 9 | """ 10 | Enregistre un signalement dans la base de données. 11 | 12 | Args: 13 | conn: La connexion aiomysql à la base de données. 14 | planning_detail_id: L'ID du détail de planification concerné par le signalement. 15 | motif: Le motif du signalement. 16 | type_signalement: Le type de signalement ('Avancement' ou 'Décalage'). 17 | 18 | Returns: 19 | True si le signalement est enregistré avec succès, False sinon. 20 | """ 21 | cursor = None 22 | try: 23 | cursor = await conn.cursor() 24 | sql_insert_signalement = "INSERT INTO Signalement (planning_detail_id, motif, type) VALUES (%s, %s, %s)" 25 | await cursor.execute(sql_insert_signalement, (planning_detail_id, motif, type_signalement)) 26 | await conn.commit() 27 | print(f"Signalement de {type_signalement} enregistré.") 28 | return True 29 | except aiomysql.Error as e: 30 | print(f"Erreur lors de l'enregistrement du signalement : {e}") 31 | await conn.rollback() 32 | return False 33 | finally: 34 | if cursor: 35 | await cursor.close() 36 | 37 | async def main(): 38 | conn = None 39 | try: 40 | # Établir la connexion à la base de données en utilisant la fonction du fichier principal 41 | host, port, user, password, database = await get_db_credentials() 42 | conn = await aiomysql.connect(host=host, port=port, user=user, password=password, db=database) 43 | 44 | # Demander l'ID du client 45 | client_id = await get_client_id(conn) 46 | if not client_id: 47 | print("Opération annulée.") 48 | return 49 | 50 | # Récupérer les informations du planning du client 51 | planning_data, planning_details, traitements = await get_client_planning(conn, client_id) 52 | if not planning_data: 53 | print("Opération terminée.") 54 | return 55 | 56 | # Afficher les informations du planning 57 | await display_planning_info(planning_data, planning_details, traitements) 58 | 59 | # Demander à l'utilisateur de choisir comment gérer le planning 60 | choix = await get_option_choix() 61 | 62 | if choix == 1: 63 | await handle_option_1(conn, planning_details) 64 | elif choix == 2: 65 | await handle_option_2(conn, planning_data) 66 | else: 67 | print("Choix invalide. Opération annulée.") 68 | 69 | except aiomysql.Error as e: 70 | print(f"Erreur MySQL : {e}") 71 | finally: 72 | if conn: 73 | conn.close() 74 | 75 | if __name__ == "__main__": 76 | asyncio.run(main()) 77 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/contrat/continuiteContrat.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | import asyncio 3 | from datetime import datetime, timedelta 4 | 5 | async def verifier_statut_contrat(pool, contrat_id): 6 | """Vérifie le statut d'un contrat.""" 7 | 8 | async with pool.acquire as conn: 9 | async with conn.cursor() as cur: 10 | await cur.execute("SELECT statut_contrat FROM Contrat WHERE contrat_id = %s", (contrat_id,)) 11 | statut = await cur.fetchone() 12 | 13 | if statut: 14 | print(f"Statut du contrat {contrat_id}: {statut[0]}") 15 | else: 16 | print(f"Contrat avec l'ID {contrat_id} non trouvé.") 17 | 18 | async def gerer_continuité_contrat(pool, contrat_id): 19 | """Gère la continuité d'un contrat.""" 20 | 21 | async with pool.acquire as conn: 22 | async with conn.cursor() as cur: 23 | await cur.execute("SELECT duree, date_fin, duree_contrat FROM Contrat WHERE contrat_id = %s", (contrat_id,)) 24 | contrat = await cur.fetchone() 25 | 26 | if not contrat: 27 | print(f"Contrat avec l'ID {contrat_id} non trouvé.") 28 | return 29 | 30 | duree, date_fin_contrat, duree_contrat = contrat 31 | 32 | if duree == 'Déterminée': 33 | if date_fin_contrat and date_fin_contrat <= datetime.now().date(): 34 | choix = input("Souhaitez-vous renouveler le contrat ? (oui/non) : ") 35 | if choix.lower() == 'oui': 36 | nouvelle_date_fin = date_fin_contrat + timedelta(months=duree_contrat) 37 | await cur.execute("UPDATE Contrat SET date_fin = %s WHERE contrat_id = %s", (nouvelle_date_fin, contrat_id)) 38 | print("Contrat renouvelé.") 39 | else: 40 | await cur.execute("UPDATE Contrat SET statut_contrat = 'Terminé' WHERE contrat_id = %s", (contrat_id,)) 41 | print("Contrat terminé.") 42 | elif duree == 'Indeterminée': 43 | choix = input("Souhaitez-vous arrêter le contrat ? (oui/non) : ") 44 | if choix.lower() == 'oui': 45 | await cur.execute("UPDATE Contrat SET statut_contrat = 'Terminé' WHERE contrat_id = %s", (contrat_id,)) 46 | print("Contrat terminé.") 47 | 48 | async def main(): 49 | # Connexion à la base de données 50 | host = input("Hôte de la base de données (par défaut : localhost) : ") 51 | if not host: 52 | host = "localhost" 53 | 54 | port_str = input("Port de la base de données (par défaut : 3306) : ") 55 | if port_str: 56 | try: 57 | port = int(port_str) 58 | except ValueError: 59 | print("Port invalide. Utilisation du port par défaut 3306.") 60 | port = 3306 61 | else: 62 | port = 3306 63 | 64 | user = input("Nom d'utilisateur : ") 65 | password = input("Mot de passe : ") 66 | database = input("Nom de la base de données (Planificator): ") 67 | if not database: 68 | database = "Planificator" 69 | 70 | try: 71 | pool = await aiomysql.create_pool( 72 | host=host, 73 | port=port, 74 | user=user, 75 | password=password, 76 | db=database, 77 | autocommit=True 78 | ) 79 | finally: 80 | if pool: 81 | pool.close() 82 | await pool.wait_closed() 83 | 84 | contrat_id = int(input("ID du contrat : ")) 85 | choix = input("Vérifier le statut (1) ou gérer la continuité (2) ? ") 86 | 87 | if choix == '1': 88 | await verifier_statut_contrat(pool, contrat_id) 89 | elif choix == '2': 90 | await gerer_continuité_contrat(pool, contrat_id) 91 | 92 | pool.close() 93 | await pool.wait_closed() 94 | 95 | if __name__ == "__main__": 96 | asyncio.run(main()) -------------------------------------------------------------------------------- /Contrat/fonctionnalités/Facture/Gestion de recouvrement/vuesFactures.sql: -------------------------------------------------------------------------------- 1 | -- Vues pour la facturation : "par traitement" et "totale par client/traitement" 2 | -- Ces vues vous permettront d'extraire les données selon vos deux modèles de facture. 3 | 4 | -- Vue 1: `VueFacturesParTraitement` 5 | -- Donne un aperçu détaillé de chaque facture individuelle, incluant les infos client, contrat et traitement. 6 | CREATE VIEW VueFacturesParTraitement AS 7 | SELECT 8 | f.facture_id, 9 | f.montant, 10 | f.date_emission, 11 | f.date_echeance, 12 | f.etat_facture, 13 | f.remarque AS facture_remarque, 14 | pd.date_planification AS date_traitement_effectue, 15 | pd.statut AS statut_planning, 16 | tt.typeTraitement, 17 | c.nom AS client_nom, 18 | c.prenom AS client_prenom_ou_responsable, 19 | c.email AS client_email, 20 | c.telephone AS client_telephone, 21 | c.adresse AS client_adresse, 22 | c.categorie AS client_categorie, 23 | co.contrat_id, 24 | co.date_contrat, 25 | co.date_debut AS contrat_date_debut, 26 | co.date_fin_contrat AS contrat_date_fin, 27 | co.duree AS contrat_duree 28 | FROM 29 | Factures f 30 | JOIN 31 | PlanningDetails pd ON f.planning_detail_id = pd.planning_detail_id 32 | JOIN 33 | Planning p ON pd.planning_id = p.planning_id 34 | JOIN 35 | Traitement t ON p.traitement_id = t.traitement_id 36 | JOIN 37 | TypesTraitement tt ON t.id_type_traitement = tt.id_type_traitement 38 | JOIN 39 | Contrats co ON t.contrat_id = co.contrat_id 40 | JOIN 41 | Clients c ON co.client_id = c.client_id; 42 | 43 | 44 | -- Vue 2: `VueFacturesTotalesParClientEtTraitement` 45 | -- Agrège le montant total facturé pour un client pour un type de traitement donné sur une période (mois/année). 46 | -- Utile pour générer des factures récapitulatives. 47 | CREATE VIEW VueFacturesTotalesParClientEtTraitement AS 48 | SELECT 49 | c.client_id, 50 | c.nom AS client_nom, 51 | c.prenom AS client_prenom_ou_responsable, 52 | tt.typeTraitement, 53 | YEAR(f.date_emission) AS annee_facture, 54 | MONTH(f.date_emission) AS mois_facture, 55 | SUM(f.montant) AS montant_total_facture, 56 | COUNT(f.facture_id) AS nombre_de_factures_individuelles 57 | FROM 58 | Factures f 59 | JOIN 60 | PlanningDetails pd ON f.planning_detail_id = pd.planning_detail_id 61 | JOIN 62 | Planning p ON pd.planning_id = p.planning_id 63 | JOIN 64 | Traitement t ON p.traitement_id = t.traitement_id 65 | JOIN 66 | TypesTraitement tt ON t.id_type_traitement = tt.id_type_traitement 67 | JOIN 68 | Contrats co ON t.contrat_id = co.contrat_id 69 | JOIN 70 | Clients c ON co.client_id = c.client_id 71 | GROUP BY 72 | c.client_id, 73 | tt.typeTraitement, 74 | YEAR(f.date_emission), 75 | MONTH(f.date_emission) 76 | ORDER BY 77 | c.nom, annee_facture DESC, mois_facture DESC, tt.typeTraitement; 78 | 79 | 80 | -- Vue 3: `VueFacturesTotalesParClient` 81 | -- Agrège le montant total facturé pour un client sur une période donnée (mois/année), tous traitements confondus. 82 | CREATE VIEW VueFacturesTotalesParClient AS 83 | SELECT 84 | c.client_id, 85 | c.nom AS client_nom, 86 | c.prenom AS client_prenom_ou_responsable, 87 | YEAR(f.date_emission) AS annee_facture, 88 | MONTH(f.date_emission) AS mois_facture, 89 | SUM(f.montant) AS montant_total_client_mois 90 | FROM 91 | Factures f 92 | JOIN 93 | PlanningDetails pd ON f.planning_detail_id = pd.planning_detail_id 94 | JOIN 95 | Planning p ON pd.planning_id = p.planning_id 96 | JOIN 97 | Traitement t ON p.traitement_id = t.traitement_id 98 | JOIN 99 | Contrats co ON t.contrat_id = co.contrat_id 100 | JOIN 101 | Clients c ON co.client_id = c.client_id 102 | GROUP BY 103 | c.client_id, 104 | YEAR(f.date_emission), 105 | MONTH(f.date_emission) 106 | ORDER BY 107 | c.nom, annee_facture DESC, mois_facture DESC; 108 | 109 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/infoClient/AllClients/listerTraitementClients.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiomysql 3 | 4 | async def get_db_credentials(): 5 | host = input("Entrez l'adresse du serveur MySQL (par défaut, localhost): ") 6 | if not host: 7 | host = "localhost" 8 | port_str = input("Entrez le port du serveur MySQL (par défaut: 3306): ") 9 | try: 10 | port = int(port_str) if port_str else 3306 11 | except ValueError: 12 | print("Port invalide, utilisation de la valeur par défaut (3306).") 13 | port = 3306 14 | user = input("Entrez le nom d'utilisateur MySQL: ") 15 | password = input("Entrez le mot de passe MySQL: ") 16 | database = input("Entrez le nom de la base de données MySQL (Planificator): ") 17 | if not database: 18 | database = "Planificator" 19 | return host, port, user, password, database 20 | 21 | async def get_all_clients_with_treatment_count(host: str, port: int, user: str, password: str, database: str): 22 | """ 23 | Récupère les informations de tous les clients et le nombre de traitements liés à chacun. 24 | 25 | Args: 26 | host: L'adresse du serveur MySQL. 27 | port: Le port du serveur MySQL. 28 | user: Le nom d'utilisateur MySQL. 29 | password: Le mot de passe MySQL. 30 | database: Le nom de la base de données MySQL. 31 | 32 | Returns: 33 | Une liste de dictionnaires, où chaque dictionnaire contient les informations d'un client 34 | et le nombre de traitements liés. Retourne None en cas d'erreur. 35 | """ 36 | conn = None 37 | cursor = None 38 | try: 39 | conn = await aiomysql.connect(host=host, 40 | port=port, 41 | user=user, 42 | password=password, 43 | db=database, 44 | autocommit=True) 45 | cursor = await conn.cursor(aiomysql.DictCursor) 46 | 47 | sql = """ 48 | SELECT 49 | cl.client_id, 50 | cl.nom AS nom_client, 51 | cl.prenom AS prenom_client, 52 | cl.email AS email_client, 53 | cl.telephone AS telephone_client, 54 | cl.adresse AS adresse_client, 55 | cl.date_ajout AS date_ajout_client, 56 | cl.categorie AS categorie_client, 57 | cl.axe AS axe_client, 58 | COUNT(tr.traitement_id) AS nombre_traitements 59 | FROM 60 | Client cl 61 | LEFT JOIN 62 | Contrat ct ON cl.client_id = ct.client_id 63 | LEFT JOIN 64 | Traitement tr ON ct.contrat_id = tr.contrat_id 65 | GROUP BY 66 | cl.client_id, cl.nom, cl.prenom, cl.email, cl.telephone, cl.adresse, cl.date_ajout, cl.categorie, cl.axe 67 | ORDER BY 68 | cl.nom, cl.prenom; 69 | """ 70 | await cursor.execute(sql) 71 | results = await cursor.fetchall() 72 | return results 73 | 74 | except aiomysql.Error as e: 75 | print(f"Erreur MySQL: {e}") 76 | return None 77 | finally: 78 | if cursor: 79 | await cursor.close() 80 | if conn: 81 | conn.close() 82 | 83 | async def main(): 84 | host, port, user, password, database = await get_db_credentials() 85 | 86 | all_clients_info = await get_all_clients_with_treatment_count(host, port, user, password, database) 87 | 88 | if all_clients_info: 89 | print("\nListe de tous les clients et leur nombre de traitements :") 90 | for client_info in all_clients_info: 91 | print(f"ID: {client_info['client_id']}, Nom: {client_info['nom_client']}, Prénom: {client_info['prenom_client']}, Traitements: {client_info['nombre_traitements']}") 92 | else: 93 | print("\nImpossible de récupérer la liste des clients et leurs traitements.") 94 | 95 | if __name__ == "__main__": 96 | asyncio.run(main()) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Planificator (DB)

2 | 3 |

4 | Script pour la base de données du projet Planificator. 5 |

6 | 7 |

8 | 9 | License 10 | Last Commit 11 | GitHub Stars 12 |

13 | 14 | 15 | # Database for Planificator 16 | 17 | - **DB:** mysql 18 | - **Langage pour les scripts:** Python 19 | - **Extension utilisés:** aiomysql, mysqlconnector, bycript, re 20 | 21 | ## Autres 22 | 23 | - Projet **[Planificator](https://github.com/AinaMaminirina18/Planificator)** 24 | - Contributeur du projet: 25 | - Pour la base de données: [josoavj](https://github.com/josoavj) 26 | - Pour l'interface: [Aina Maminirina](https://github.com/AinaMaminirina18) 27 | - 1.0: [Planificator 1.0](https://github.com/APEXNovaLabs/Planificator.1.0) 28 | - 1.1: [Planificator 1.1](https://github.com/APEXNovaLabs/Planificator-1.1.1) 29 | 30 | ### 📂 Structuration des dossiers 31 | 32 | ``` 33 | Database/ 34 | ├── Acccount/ # Pour les scripts des comptes dans le logiciel (Requête CRUD) 35 | ├── accountAvecHash.py # Script avec hashage du mot de passe pour chaque compte 36 | └── accountSansHash.py # Script sans hashage du mot de passe pour chaque compte 37 | ├── Contrat/ # Pour les scripts sur l'ensemble de la table Planificator (Sans Account) 38 | ├── fonctionnalités/ # Les principaux fonctionnalités dans chaque instance 39 | ├── Planning/ # Fonctionnalités sur planning (Affichage, Ajout du détails pour chaque planning, Mise à jour des détails de planification) 40 | ├── contrat/ # Fonctionnalités sur le contrat (Choix sur la continuité du contrat) 41 | ├── infoClient # Option de sélection des clients et affichage des traitements assignés 42 | ├── remarque/ # Fonctionnalités sur les remarques (Ajout d'une facture à chaque enregistrement de remarque) 43 | ├── Excel/ # Géneration de planning par Excel 44 | ├── Facture/ # Gestion de facture (Maintenance) 45 | ├── signalement/ # Gestion des signalements 46 | └── triage/ # Triage: Triage par ordre alphabetique, Recherche de contrat, Triage des traitements par catégorie, triage des traitements spécifiques à chaque client 47 | ├── regroupeTraitementCat/ # Scripts de regroupement des traitements par catégories (Script Python et SQL) 48 | ├── CRUDonClient.py # Requête CRUD pour la table Client 49 | ├── CRUDonContrat.py # Requête CRUD pour la table Contrat 50 | ├── mainCRUDonPlanificator.py # Programme principale 51 | ├── CRUDonFacture.py # Requête CRUD pour la facture Facture 52 | ├── CRUDonHistorique.py # Requête CRUD pour la table Historique 53 | ├── CRUDonPlanning.py # Requête CRUD pour la table Planning et PlanningDetails 54 | ├── CRUDonSignalement.py # Requête CRUD pour la table Signalement 55 | └── CRUDonTraitement.py # Requête CRUD pour la table Traitement et typeTraitement 56 | ├── scriptSQL/ # Script SQL pour la base de données 57 | ├── testDB.sql # Script pour une série d'insertion de données pour tester la base de données 58 | ├── SuppressionDB.sql # Script de suppression de l'entièreté de la base de données 59 | └── Planificator.sql # Script principal de la base de données 60 | 61 | └── README.md # Documentation 62 | ``` 63 | 64 | ### 📝 Notice 65 | 66 | Veuillez créer un nouveau utilisateur pour la DB si vous voulez la tester. 67 | 68 | ### 📃 Licence 69 | 70 | Ce projet est libre de droits et peut être utilisé pour des projets personnels. Que ce soit pour les scripts python et aussi ceux de la DB 71 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/triage/triSearchContrat.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiomysql 3 | 4 | async def rechercher_client(pool, search_term): 5 | async with pool.acquire() as conn: 6 | async with conn.cursor() as cur: 7 | await cur.execute(""" 8 | SELECT c.*, tt.nom_type_traitement, p.mois_debut, ct.date_ajout 9 | FROM Client c 10 | LEFT JOIN Contrat ct ON c.client_id = ct.client_id 11 | LEFT JOIN Traitement t ON ct.contrat_id = t.contrat_id 12 | LEFT JOIN TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement 13 | LEFT JOIN Planning p ON t.traitement_id = p.traitement_id 14 | WHERE c.nom LIKE %s OR c.prenom LIKE %s 15 | """, (f"%{search_term}%", f"%{search_term}%")) 16 | return await cur.fetchall() 17 | 18 | async def afficher_clients(clients): 19 | if clients: 20 | print("\nClients trouvés :") 21 | for client in clients: 22 | print(f"ID: {client[0]}, Nom: {client[1]}, Prénom: {client[2]}, Email: {client[3]}, " 23 | f"Type de traitement: {client[8] or 'Aucun'}, Mois de début planning: {client[9] or 'Aucun'}, Date d'ajout contrat: {client[10] or 'Aucune'}") 24 | else: 25 | print("\nAucun client trouvé.") 26 | 27 | async def tri_client(clients, sort_by): 28 | if sort_by == 'traitement': 29 | return sorted(clients, key=lambda x: x[8] or '') # Tri par type de traitement 30 | elif sort_by == 'mois': 31 | return sorted(clients, key=lambda x: x[9] or '') # Tri par mois de début planning 32 | elif sort_by == 'date': 33 | return sorted(clients, key=lambda x: x[10] or '') # Tri par date d'ajout contrat 34 | else: 35 | return clients 36 | 37 | async def actualiser_data(pool, search_term=None): # Paramètre search_term optionnel 38 | print("\nDonnées mises à jour.") 39 | if search_term: 40 | return await rechercher_client(pool, search_term) 41 | return None 42 | 43 | async def main(): 44 | # Connexion à la base de données 45 | host = input("Hôte de la base de données (par défaut : localhost) : ") 46 | if not host: 47 | host = "localhost" 48 | 49 | port_str = input("Port de la base de données (par défaut : 3306) : ") 50 | if port_str: 51 | try: 52 | port = int(port_str) 53 | except ValueError: 54 | print("Port invalide. Utilisation du port par défaut 3306.") 55 | port = 3306 56 | else: 57 | port = 3306 58 | 59 | user = input("Nom d'utilisateur : ") 60 | password = input("Mot de passe : ") 61 | database = input("Nom de la base de données : ") 62 | 63 | try: 64 | # Créer le pool de connexions 65 | pool = await aiomysql.create_pool( 66 | host=host, 67 | port=port, 68 | user=user, 69 | password=password, 70 | db=database, 71 | autocommit=True 72 | ) 73 | except Exception as e: 74 | print(f"Erreur de connexion à la base de données: {e}") 75 | return 76 | 77 | try: 78 | while True: 79 | print("\nMenu principal:") 80 | print("1. Rechercher un client") 81 | print("2. Trier les résultats") 82 | print("3. Actualiser les données") 83 | print("4. Quitter") 84 | 85 | choice = input("Choisissez une option : ") 86 | 87 | if choice == '4': 88 | break 89 | 90 | elif choice == '1': 91 | search_term = input("Entrez le terme de recherche (nom ou prénom) : ") 92 | clients = await rechercher_client(pool, search_term) 93 | afficher_clients(clients) 94 | elif choice == '2': 95 | if 'clients' in locals() and clients: # Vérifier si une recherche a été effectuée 96 | sort_by = input("Trier par (traitement/mois) : ").lower() 97 | clients = tri_client(clients, sort_by) 98 | afficher_clients(clients) 99 | else: 100 | print("Veuillez d'abord effectuer une recherche.") 101 | elif choice == '3': 102 | await actualiser_data(pool) 103 | if 'search_term' in locals() and search_term: # Vérifier si une recherche a été effectuée 104 | clients = await rechercher_client(pool, search_term) 105 | afficher_clients(clients) 106 | else: 107 | print("Option invalide. Veuillez réessayer.") 108 | 109 | finally: 110 | if pool: 111 | pool.close() 112 | await pool.wait_closed() 113 | 114 | if __name__ == "__main__": 115 | asyncio.run(main()) -------------------------------------------------------------------------------- /Contrat/fonctionnalités/infoClient/Specific/selectionClientTraitement.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiomysql 3 | 4 | async def get_db_credentials(): 5 | """Demande à l'utilisateur les informations de connexion à la base de données.""" 6 | host = input("Entrez l'adresse du serveur MySQL (Pressez sur entrer si localhost): ") or "localhost" 7 | port_str = input("Entrez le port du serveur MySQL (par défaut: 3306): ") 8 | try: 9 | port = int(port_str) if port_str else 3306 10 | except ValueError: 11 | print("Port invalide, utilisation de la valeur par défaut (3306).") 12 | port = 3306 13 | user = input("Entrez le nom d'utilisateur MySQL: ") 14 | password = input("Entrez le mot de passe MySQL: ") 15 | database = input("Entrez le nom de la base de données MySQL: ") or "Planificator" 16 | return host, port, user, password, database 17 | 18 | async def get_client_id_to_search(): 19 | """Demande à l'utilisateur l'ID du client à rechercher.""" 20 | while True: 21 | client_id_str = input("Entrez l'ID du client que vous souhaitez rechercher: ") 22 | try: 23 | client_id = int(client_id_str) 24 | return client_id 25 | except ValueError: 26 | print("ID de client invalide. Veuillez entrer un nombre entier.") 27 | 28 | async def get_client_info_with_treatment_count(host: str, port: int, user: str, password: str, database: str, client_id: int): 29 | """ 30 | Récupère les informations d'un client et le nombre de traitements liés en utilisant aiomysql. 31 | 32 | Args: 33 | host: L'adresse du serveur MySQL. 34 | port: Le port du serveur MySQL. 35 | user: Le nom d'utilisateur MySQL. 36 | password: Le mot de passe MySQL. 37 | database: Le nom de la base de données MySQL. 38 | client_id: L'ID du client à interroger. 39 | 40 | Returns: 41 | Un dictionnaire contenant les informations du client et le nombre de traitements, 42 | ou None si le client n'est pas trouvé. 43 | """ 44 | conn = None 45 | cursor = None 46 | try: 47 | conn = await aiomysql.connect(host=host, 48 | port=port, 49 | user=user, 50 | password=password, 51 | db=database, 52 | autocommit=True) 53 | cursor = await conn.cursor(aiomysql.DictCursor) 54 | 55 | sql = """ 56 | SELECT 57 | cl.client_id, 58 | cl.nom AS nom_client, 59 | cl.prenom AS prenom_client, 60 | cl.email AS email_client, 61 | cl.telephone AS telephone_client, 62 | cl.adresse AS adresse_client, 63 | cl.date_ajout AS date_ajout_client, 64 | cl.categorie AS categorie_client, 65 | cl.axe AS axe_client, 66 | COUNT(tr.traitement_id) AS nombre_traitements 67 | FROM 68 | Client cl 69 | LEFT JOIN 70 | Contrat ct ON cl.client_id = ct.client_id 71 | LEFT JOIN 72 | Traitement tr ON ct.contrat_id = tr.contrat_id 73 | WHERE 74 | cl.client_id = %s 75 | GROUP BY 76 | cl.client_id, cl.nom, cl.prenom, cl.email, cl.telephone, cl.adresse, cl.date_ajout, cl.categorie, cl.axe 77 | """ 78 | await cursor.execute(sql, (client_id,)) 79 | result = await cursor.fetchone() 80 | return result 81 | 82 | except aiomysql.Error as e: 83 | print(f"Erreur MySQL: {e}") 84 | return None 85 | finally: 86 | if cursor: 87 | await cursor.close() 88 | if conn: 89 | await conn.wait_closed() 90 | async def main(): 91 | host, port, user, password, database = await get_db_credentials() 92 | client_id_a_rechercher = await get_client_id_to_search() 93 | 94 | client_info = await get_client_info_with_treatment_count(host, port, user, password, database, client_id_a_rechercher) 95 | 96 | if client_info: 97 | print(f"\nInformations du client (ID: {client_info['client_id']}):") 98 | print(f" Nom: {client_info['nom_client']}") 99 | print(f" Prénom: {client_info['prenom_client']}") 100 | print(f" Email: {client_info['email_client']}") 101 | print(f" Téléphone: {client_info['telephone_client']}") 102 | print(f" Adresse: {client_info['adresse_client']}") 103 | print(f" Date d'ajout: {client_info['date_ajout_client']}") 104 | print(f" Catégorie: {client_info['categorie_client']}") 105 | print(f" Axe: {client_info['axe_client']}") 106 | print(f" Nombre de traitements liés: {client_info['nombre_traitements']}") 107 | else: 108 | print(f"\nAucun client trouvé avec l'ID: {client_id_a_rechercher}") 109 | 110 | if __name__ == "__main__": 111 | asyncio.run(main()) -------------------------------------------------------------------------------- /Contrat/fonctionnalités/Facture/Gestion de recouvrement/Recouvrement.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Auteur: Josoa (josoavj sur GitHub) 3 | Base de données: `Recouvrement` 4 | */ 5 | 6 | -- -------------------------------------------------------- 7 | -- Suppression 8 | DROP TABLE IF EXISTS Factures; 9 | DROP TABLE IF EXISTS PlanningDetails; 10 | DROP TABLE IF EXISTS Planning; 11 | DROP TABLE IF EXISTS Traitement; 12 | DROP TABLE IF EXISTS Contrat; 13 | DROP TABLE IF EXISTS Clients; 14 | 15 | -- -------------------------------------------------------- 16 | -- Table `Clients` 17 | -- Stocke les informations des clients 18 | CREATE TABLE Clients ( 19 | client_id INT AUTO_INCREMENT PRIMARY KEY, 20 | nom VARCHAR(255) NOT NULL, 21 | prenom VARCHAR(255), 22 | email VARCHAR(255), 23 | telephone VARCHAR(50), 24 | adresse TEXT, 25 | categorie ENUM('Particulier', 'Société', 'Organisation') NOT NULL, 26 | nif VARCHAR(100), -- Numéro d'Identification Fiscale (pour sociétés/organisations) 27 | stat VARCHAR(100), -- Numéro Statistique (pour sociétés/organisations) 28 | axe VARCHAR(255) -- Axe géographique ou commercial 29 | ); 30 | 31 | -- -------------------------------------------------------- 32 | -- Table `Contrats` 33 | -- Stocke les informations sur les contrats, liés aux clients 34 | CREATE TABLE Contrats ( 35 | contrat_id INT AUTO_INCREMENT PRIMARY KEY, 36 | client_id INT NOT NULL, 37 | date_contrat DATE NOT NULL, 38 | date_debut DATE NOT NULL, 39 | date_fin_contrat DATE, -- NULL si durée indéterminée 40 | duree ENUM('Déterminée', 'Indéterminée') NOT NULL, 41 | categorie VARCHAR(255), -- Ex: "Service", "Produit", etc. 42 | duree_contrat_mois INT, -- Durée en mois si déterminée 43 | FOREIGN KEY (client_id) REFERENCES Clients(client_id) 44 | ); 45 | 46 | -- -------------------------------------------------------- 47 | -- Table `TypesTraitement` 48 | -- Stocke les différents types de traitements (ex: "Nettoyage", "Maintenance", "Consulting") 49 | CREATE TABLE TypesTraitement ( 50 | id_type_traitement INT AUTO_INCREMENT PRIMARY KEY, 51 | typeTraitement VARCHAR(255) NOT NULL UNIQUE, 52 | description TEXT 53 | ); 54 | 55 | -- -------------------------------------------------------- 56 | -- Table `Traitement` 57 | -- Représente l'association d'un type de traitement à un contrat 58 | CREATE TABLE Traitement ( 59 | traitement_id INT AUTO_INCREMENT PRIMARY KEY, 60 | contrat_id INT NOT NULL, 61 | id_type_traitement INT NOT NULL, 62 | FOREIGN KEY (contrat_id) REFERENCES Contrats(contrat_id), 63 | FOREIGN KEY (id_type_traitement) REFERENCES TypesTraitement(id_type_traitement), 64 | UNIQUE (contrat_id, id_type_traitement) -- Un contrat n'aura qu'un seul enregistrement pour un type de traitement donné 65 | ); 66 | 67 | -- -------------------------------------------------------- 68 | -- Table `Planning` 69 | -- Définit la planification générale d'un traitement pour un contrat 70 | CREATE TABLE Planning ( 71 | planning_id INT AUTO_INCREMENT PRIMARY KEY, 72 | traitement_id INT NOT NULL, 73 | mois_debut VARCHAR(20), 74 | mois_fin VARCHAR(20), 75 | mois_pause TEXT, 76 | redondance ENUM('Journalier', 'Hebdomadaire', 'Mensuel', 'Trimestriel', 'Annuel') NOT NULL, 77 | FOREIGN KEY (traitement_id) REFERENCES Traitement(traitement_id) 78 | ); 79 | 80 | -- -------------------------------------------------------- 81 | -- Table `PlanningDetails` 82 | -- Représente une instance spécifique et planifiée d'un traitement (date exacte) 83 | -- Ceci est crucial pour la granularité de la facturation par traitement 84 | CREATE TABLE PlanningDetails ( 85 | planning_detail_id INT AUTO_INCREMENT PRIMARY KEY, 86 | planning_id INT NOT NULL, 87 | date_planification DATE NOT NULL, 88 | statut ENUM('Prévu', 'Effectué', 'Annulé', 'Décalé') NOT NULL DEFAULT 'Prévu', 89 | remarque TEXT, 90 | FOREIGN KEY (planning_id) REFERENCES Planning(planning_id), 91 | UNIQUE (planning_id, date_planification) -- Assure qu'un planning n'a qu'un détail par date 92 | ); 93 | 94 | -- -------------------------------------------------------- 95 | -- Table `Factures` 96 | -- Contient les informations de chaque facture, liée à un détail de planning (un traitement spécifique effectué) 97 | CREATE TABLE Factures ( 98 | facture_id INT AUTO_INCREMENT PRIMARY KEY, 99 | planning_detail_id INT NOT NULL, -- La facture est liée à une instance spécifique de traitement planifié 100 | montant DECIMAL(10, 2) NOT NULL, 101 | date_emission DATE NOT NULL, 102 | date_echeance DATE, 103 | etat_facture ENUM('Émise', 'Payée', 'En Retard', 'Annulée') NOT NULL DEFAULT 'Émise', 104 | remarque TEXT, 105 | FOREIGN KEY (planning_detail_id) REFERENCES PlanningDetails(planning_detail_id) 106 | ); 107 | 108 | -- -------------------------------------------------------- 109 | -- Table `Signalements` 110 | -- Pour les signalements d'avancement ou de décalage de traitements 111 | CREATE TABLE Signalements ( 112 | signalement_id INT AUTO_INCREMENT PRIMARY KEY, 113 | planning_detail_id INT NOT NULL, -- Le signalement est lié à un détail de planification 114 | motif TEXT NOT NULL, 115 | date_signalement DATETIME DEFAULT CURRENT_TIMESTAMP, 116 | type_signalement ENUM('Avancement', 'Décalage', 'Problème') NOT NULL, 117 | FOREIGN KEY (planning_detail_id) REFERENCES PlanningDetails(planning_detail_id) 118 | ); -------------------------------------------------------------------------------- /Contrat/CRUDonSignalement.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | 3 | async def create_signalement(pool, planning_detail_id: int, motif: str, type_signalement: str) -> int | None: 4 | """ 5 | Crée un nouveau signalement dans la base de données avec gestion de transaction. 6 | 7 | Args: 8 | pool: Le pool de connexions aiomysql. 9 | planning_detail_id (int): L'ID du détail de planification associé. 10 | motif (str): Le motif du signalement. 11 | type_signalement (str): Le type de signalement. 12 | 13 | Returns: 14 | int | None: L'ID du signalement nouvellement créé, ou None en cas d'échec. 15 | """ 16 | conn = None 17 | try: 18 | conn = await pool.acquire() 19 | await conn.begin() # Début de la transaction 20 | async with conn.cursor() as cur: 21 | await cur.execute( 22 | "INSERT INTO Signalement (planning_detail_id, motif, type) VALUES (%s, %s, %s)", 23 | (planning_detail_id, motif, type_signalement) 24 | ) 25 | await conn.commit() # Validation de la transaction 26 | return cur.lastrowid 27 | except Exception as e: 28 | if conn: 29 | await conn.rollback() # Annulation de la transaction en cas d'erreur 30 | print(f"Erreur lors de la création du signalement : {e}") 31 | return None 32 | finally: 33 | if conn: 34 | pool.release(conn) 35 | 36 | async def read_signalement(pool, signalement_id: int | None = None) -> list[dict] | dict | None: 37 | """ 38 | Lit les informations d'un signalement spécifique par son ID, ou de tous les signalements. 39 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 40 | 41 | Args: 42 | pool: Le pool de connexions aiomysql. 43 | signalement_id (int | None): L'ID du signalement à lire (optionnel). 44 | 45 | Returns: 46 | list[dict] | dict | None: Une liste de dictionnaires pour tous les signalements, 47 | un dictionnaire pour un signalement spécifique, 48 | ou None en cas d'échec. 49 | """ 50 | conn = None 51 | try: 52 | conn = await pool.acquire() 53 | async with conn.cursor(aiomysql.DictCursor) as cur: 54 | if signalement_id is None: 55 | await cur.execute("SELECT * FROM Signalement") 56 | return await cur.fetchall() 57 | else: 58 | await cur.execute(""" 59 | SELECT 60 | s.signalement_id, 61 | s.motif, 62 | s.type, 63 | s.date_creation, 64 | pd.planning_detail_id, 65 | pd.date_planification, 66 | p.traitement_id, 67 | tt.typeTraitement AS nom_type_traitement 68 | FROM Signalement s 69 | JOIN PlanningDetails pd ON s.planning_detail_id = pd.planning_detail_id 70 | JOIN Planning p ON pd.planning_id = p.planning_id 71 | JOIN TypeTraitement tt ON p.id_type_traitement = tt.id_type_traitement 72 | WHERE s.signalement_id = %s 73 | """, (signalement_id,)) 74 | return await cur.fetchone() 75 | except Exception as e: 76 | print(f"Erreur lors de la lecture du signalement : {e}") 77 | return None if signalement_id is not None else [] 78 | finally: 79 | if conn: 80 | pool.release(conn) 81 | 82 | async def update_signalement(pool, signalement_id: int, planning_detail_id: int, motif: str, type_signalement: str) -> int: 83 | """ 84 | Modifie un signalement existant avec gestion de transaction. 85 | 86 | Args: 87 | pool: Le pool de connexions aiomysql. 88 | signalement_id (int): L'ID du signalement à mettre à jour. 89 | planning_detail_id (int): Le nouvel ID du détail de planification. 90 | motif (str): Le nouveau motif. 91 | type_signalement (str): Le nouveau type de signalement. 92 | 93 | Returns: 94 | int: Le nombre de lignes affectées (1 si succès, 0 sinon). 95 | """ 96 | conn = None 97 | try: 98 | conn = await pool.acquire() 99 | await conn.begin() # Début de la transaction 100 | async with conn.cursor() as cur: 101 | await cur.execute( 102 | "UPDATE Signalement SET planning_detail_id = %s, motif = %s, type = %s WHERE signalement_id = %s", 103 | (planning_detail_id, motif, type_signalement, signalement_id) 104 | ) 105 | await conn.commit() # Validation de la transaction 106 | return cur.rowcount 107 | except Exception as e: 108 | if conn: 109 | await conn.rollback() # Annulation de la transaction en cas d'erreur 110 | print(f"Erreur lors de la mise à jour du signalement : {e}") 111 | return 0 112 | finally: 113 | if conn: 114 | pool.release(conn) 115 | 116 | async def delete_signalement(pool, signalement_id: int) -> int: 117 | """ 118 | Supprime un signalement existant avec gestion de transaction. 119 | 120 | Args: 121 | pool: Le pool de connexions aiomysql. 122 | signalement_id (int): L'ID du signalement à supprimer. 123 | 124 | Returns: 125 | int: Le nombre de lignes supprimées (1 si succès, 0 sinon). 126 | """ 127 | conn = None 128 | try: 129 | conn = await pool.acquire() 130 | await conn.begin() # Début de la transaction 131 | async with conn.cursor() as cur: 132 | await cur.execute("DELETE FROM Signalement WHERE signalement_id = %s", (signalement_id,)) 133 | await conn.commit() # Validation de la transaction 134 | return cur.rowcount 135 | except Exception as e: 136 | if conn: 137 | await conn.rollback() # Annulation de la transaction en cas d'erreur 138 | print(f"Erreur lors de la suppression du signalement : {e}") 139 | return 0 140 | finally: 141 | if conn: 142 | pool.release(conn) 143 | -------------------------------------------------------------------------------- /Contrat/CRUDonTraitement.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | 3 | async def creation_traitement(pool, contrat_id: int, id_type_traitement: int) -> int | None: 4 | """ 5 | Crée un nouveau traitement lié à un contrat et un type de traitement avec gestion de transaction. 6 | 7 | Args: 8 | pool: Le pool de connexions aiomysql. 9 | contrat_id (int): L'ID du contrat associé. 10 | id_type_traitement (int): L'ID du type de traitement. 11 | 12 | Returns: 13 | int | None: L'ID du traitement créé, ou None en cas d'échec. 14 | """ 15 | conn = None 16 | try: 17 | conn = await pool.acquire() 18 | await conn.begin() # Début de la transaction 19 | async with conn.cursor() as cur: 20 | await cur.execute( 21 | "INSERT INTO Traitement (contrat_id, id_type_traitement) VALUES (%s, %s)", 22 | (contrat_id, id_type_traitement) 23 | ) 24 | await conn.commit() # Validation de la transaction 25 | return cur.lastrowid 26 | except Exception as e: 27 | if conn: 28 | await conn.rollback() # Annulation de la transaction en cas d'erreur 29 | print(f"Erreur lors de la création du traitement : {e}") 30 | return None 31 | finally: 32 | if conn: 33 | pool.release(conn) 34 | 35 | async def read_traitement(pool, traitement_id: int | None = None) -> list[dict] | dict | None: 36 | """ 37 | Lit les informations d'un traitement spécifique par son ID, ou de tous les traitements. 38 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 39 | 40 | Args: 41 | pool: Le pool de connexions aiomysql. 42 | traitement_id (int | None): L'ID du traitement. 43 | 44 | Returns: 45 | dict | None: Un dictionnaire contenant les informations du traitement, ou None si non trouvé ou en cas d'erreur. 46 | """ 47 | conn = None 48 | try: 49 | conn = await pool.acquire() 50 | async with conn.cursor(aiomysql.DictCursor) as cur: 51 | if traitement_id is None: 52 | await cur.execute(""" 53 | SELECT 54 | t.traitement_id, 55 | t.contrat_id, 56 | t.id_type_traitement, 57 | tt.typeTraitement AS nom_type_traitement 58 | FROM Traitement t 59 | JOIN TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement 60 | """) 61 | return await cur.fetchall() 62 | else: 63 | await cur.execute(""" 64 | SELECT 65 | t.traitement_id, 66 | t.contrat_id, 67 | t.id_type_traitement, 68 | tt.typeTraitement AS nom_type_traitement 69 | FROM Traitement t 70 | JOIN TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement 71 | WHERE t.traitement_id = %s 72 | """, (traitement_id,)) 73 | return await cur.fetchone() 74 | except Exception as e: 75 | print(f"Erreur lors de la lecture du traitement (ID: {traitement_id}) : {e}") 76 | return None if traitement_id is not None else [] 77 | finally: 78 | if conn: 79 | pool.release(conn) 80 | 81 | async def update_traitement(pool, traitement_id: int, contrat_id: int, id_type_traitement: int) -> int: 82 | """ 83 | Modifie un traitement existant avec gestion de transaction. 84 | 85 | Args: 86 | pool: Le pool de connexions aiomysql. 87 | traitement_id (int): L'ID du traitement à modifier. 88 | contrat_id (int): Le nouvel ID du contrat. 89 | id_type_traitement (int): Le nouvel ID du type de traitement. 90 | 91 | Returns: 92 | int: Le nombre de lignes affectées (1 si succès, 0 sinon). 93 | """ 94 | conn = None 95 | try: 96 | conn = await pool.acquire() 97 | await conn.begin() # Début de la transaction 98 | async with conn.cursor() as cur: 99 | await cur.execute( 100 | "UPDATE Traitement SET contrat_id = %s, id_type_traitement = %s WHERE traitement_id = %s", 101 | (contrat_id, id_type_traitement, traitement_id) 102 | ) 103 | await conn.commit() # Validation de la transaction 104 | return cur.rowcount 105 | except Exception as e: 106 | if conn: 107 | await conn.rollback() # Annulation de la transaction en cas d'erreur 108 | print(f"Erreur lors de la modification du traitement (ID: {traitement_id}) : {e}") 109 | return 0 110 | finally: 111 | if conn: 112 | pool.release(conn) 113 | 114 | async def delete_traitement(pool, traitement_id: int) -> int: 115 | """ 116 | Supprime un traitement existant avec gestion de transaction. 117 | 118 | Args: 119 | pool: Le pool de connexions aiomysql. 120 | traitement_id (int): L'ID du traitement à supprimer. 121 | 122 | Returns: 123 | int: Le nombre de lignes supprimées (1 si succès, 0 sinon). 124 | """ 125 | conn = None 126 | try: 127 | conn = await pool.acquire() 128 | await conn.begin() # Début de la transaction 129 | async with conn.cursor() as cur: 130 | await cur.execute("DELETE FROM Traitement WHERE traitement_id = %s", (traitement_id,)) 131 | await conn.commit() # Validation de la transaction 132 | return cur.rowcount 133 | except Exception as e: 134 | if conn: 135 | await conn.rollback() # Annulation de la transaction en cas d'erreur 136 | print(f"Erreur lors de la suppression du traitement (ID: {traitement_id}) : {e}") 137 | return 0 138 | finally: 139 | if conn: 140 | pool.release(conn) 141 | 142 | async def obtenir_types_traitement(pool) -> list[dict]: 143 | """ 144 | Récupère tous les types de traitement disponibles. 145 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 146 | 147 | Returns: 148 | list[dict]: Une liste de dictionnaires, chaque dictionnaire représentant un type de traitement. 149 | """ 150 | conn = None 151 | try: 152 | conn = await pool.acquire() 153 | async with conn.cursor(aiomysql.DictCursor) as cursor: 154 | await cursor.execute("SELECT id_type_traitement, typeTraitement FROM TypeTraitement ORDER BY typeTraitement") 155 | types_data = await cursor.fetchall() 156 | return types_data 157 | except Exception as e: 158 | print(f"Erreur lors de la récupération des types de traitement : {e}") 159 | return [] 160 | finally: 161 | if conn: 162 | pool.release(conn) 163 | -------------------------------------------------------------------------------- /Contrat/CRUDonFacture.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | import aiomysql 3 | 4 | async def create_facture(pool, traitement_id: int, montant: float, date_traitement: date, axe: str, remarque: str | None) -> int | None: 5 | """ 6 | Crée une nouvelle facture dans la base de données avec gestion de transaction. 7 | 8 | Args: 9 | pool: Le pool de connexions aiomysql. 10 | traitement_id (int): L'ID du traitement associé. 11 | montant (float): Le montant de la facture. 12 | date_traitement (date): La date du traitement. 13 | axe (str): L'axe géographique. 14 | remarque (str | None): Une remarque sur la facture (peut être None). 15 | 16 | Returns: 17 | int | None: L'ID de la facture nouvellement créée, ou None en cas d'échec. 18 | """ 19 | conn = None 20 | try: 21 | conn = await pool.acquire() 22 | await conn.begin() # Début de la transaction 23 | async with conn.cursor() as cur: 24 | await cur.execute( 25 | "INSERT INTO Facture (traitement_id, montant, date_traitement, axe, remarque) VALUES (%s, %s, %s, %s, %s)", 26 | (traitement_id, montant, date_traitement, axe, remarque) 27 | ) 28 | await conn.commit() # Validation de la transaction 29 | return cur.lastrowid 30 | except Exception as e: 31 | if conn: 32 | await conn.rollback() # Annulation de la transaction en cas d'erreur 33 | print(f"Erreur lors de la création de la facture: {e}") 34 | return None 35 | finally: 36 | if conn: 37 | pool.release(conn) 38 | 39 | async def read_facture(pool, facture_id: int | None = None) -> list[dict] | dict | None: 40 | """ 41 | Lit les détails d'une facture spécifique par ID ou de toutes les factures. 42 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 43 | 44 | Args: 45 | pool: Le pool de connexions aiomysql. 46 | facture_id (int | None): L'ID de la facture à lire (optionnel). 47 | 48 | Returns: 49 | list[dict] | dict | None: Une liste de dictionnaires pour toutes les factures, 50 | un seul dictionnaire pour une facture spécifique, 51 | ou None en cas d'erreur. 52 | """ 53 | conn = None 54 | try: 55 | conn = await pool.acquire() 56 | async with conn.cursor(aiomysql.DictCursor) as cur: 57 | if facture_id is None: 58 | await cur.execute("SELECT * FROM Facture") 59 | return await cur.fetchall() 60 | else: 61 | await cur.execute("SELECT * FROM Facture WHERE facture_id = %s", (facture_id,)) 62 | return await cur.fetchone() 63 | except Exception as e: 64 | print(f"Erreur lors de la lecture de la facture/des factures: {e}") 65 | return None if facture_id is not None else [] 66 | finally: 67 | if conn: 68 | pool.release(conn) 69 | 70 | async def update_facture(pool, facture_id: int, traitement_id: int, montant: float, date_traitement: date, axe: str, remarque: str | None) -> int: 71 | """ 72 | Modifie une facture existante dans la base de données avec gestion de transaction. 73 | 74 | Args: 75 | pool: Le pool de connexions aiomysql. 76 | facture_id (int): L'ID de la facture à modifier. 77 | traitement_id (int): Le nouvel ID du traitement. 78 | montant (float): Le nouveau montant. 79 | date_traitement (date): La nouvelle date du traitement. 80 | axe (str): Le nouvel axe. 81 | remarque (str | None): La nouvelle remarque. 82 | 83 | Returns: 84 | int: Le nombre de lignes affectées (1 en cas de succès, 0 sinon). 85 | """ 86 | conn = None 87 | try: 88 | conn = await pool.acquire() 89 | await conn.begin() # Début de la transaction 90 | async with conn.cursor() as cur: 91 | await cur.execute( 92 | "UPDATE Facture SET traitement_id = %s, montant = %s, date_traitement = %s, axe = %s, remarque = %s WHERE facture_id = %s", 93 | (traitement_id, montant, date_traitement, axe, remarque, facture_id) 94 | ) 95 | await conn.commit() # Validation de la transaction 96 | return cur.rowcount 97 | except Exception as e: 98 | if conn: 99 | await conn.rollback() # Annulation de la transaction en cas d'erreur 100 | print(f"Erreur lors de la mise à jour de la facture (ID: {facture_id}): {e}") 101 | return 0 102 | finally: 103 | if conn: 104 | pool.release(conn) 105 | 106 | async def delete_facture(pool, facture_id: int) -> int: 107 | """ 108 | Supprime une facture de la base de données avec gestion de transaction. 109 | 110 | Args: 111 | pool: Le pool de connexions aiomysql. 112 | facture_id (int): L'ID de la facture à supprimer. 113 | 114 | Returns: 115 | int: Le nombre de lignes supprimées (1 en cas de succès, 0 sinon). 116 | """ 117 | conn = None 118 | try: 119 | conn = await pool.acquire() 120 | await conn.begin() # Début de la transaction 121 | async with conn.cursor() as cur: 122 | await cur.execute("DELETE FROM Facture WHERE facture_id = %s", (facture_id,)) 123 | await conn.commit() # Validation de la transaction 124 | return cur.rowcount 125 | except Exception as e: 126 | if conn: 127 | await conn.rollback() # Annulation de la transaction en cas d'erreur 128 | print(f"Erreur lors de la suppression de la facture (ID: {facture_id}): {e}") 129 | return 0 130 | finally: 131 | if conn: 132 | pool.release(conn) 133 | 134 | async def obtenir_axe_contrat(pool, contrat_id: int) -> str | None: 135 | """ 136 | Récupère l'axe géographique associé au client d'un contrat donné. 137 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 138 | 139 | Args: 140 | pool: Le pool de connexions aiomysql. 141 | contrat_id (int): L'ID du contrat. 142 | 143 | Returns: 144 | str | None: La valeur de l'axe du client, ou None si non trouvé ou en cas d'erreur. 145 | """ 146 | conn = None 147 | try: 148 | conn = await pool.acquire() 149 | async with conn.cursor(aiomysql.DictCursor) as cursor: # Utilisation de DictCursor 150 | await cursor.execute(""" 151 | SELECT cl.axe 152 | FROM Contrat c 153 | JOIN Client cl ON c.client_id = cl.client_id 154 | WHERE c.contrat_id = %s 155 | """, (contrat_id,)) 156 | resultat = await cursor.fetchone() 157 | if resultat: 158 | return resultat['axe'] # Accès par clé de dictionnaire 159 | else: 160 | return None 161 | except Exception as e: 162 | print(f"Erreur lors de la récupération de l'axe du client pour contrat (ID: {contrat_id}) : {e}") 163 | return None 164 | finally: 165 | if conn: 166 | pool.release(conn) 167 | 168 | 169 | """ 170 | Répartition de la facturation : 171 | - Une facture pour chaque traitement en géneral 172 | - Possibilité d'assigner une seule facture pour deux ou plusieurs traitements 173 | """ -------------------------------------------------------------------------------- /Contrat/CRUDonClient.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | import aiomysql 3 | 4 | 5 | async def obtenir_categories(pool, table_name: str, column_name: str) -> list[str]: 6 | """ 7 | Récupère les valeurs d'une colonne ENUM spécifique. 8 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 9 | 10 | Args: 11 | pool: Le pool de connexions aiomysql. 12 | table_name (str): Le nom de la table contenant la colonne ENUM. 13 | column_name (str): Le nom de la colonne ENUM. 14 | 15 | Returns: 16 | list[str]: Une liste des valeurs ENUM, ou une liste vide en cas d'erreur. 17 | """ 18 | conn = None 19 | try: 20 | conn = await pool.acquire() 21 | async with conn.cursor(aiomysql.DictCursor) as cursor: 22 | query = """ 23 | SELECT COLUMN_TYPE 24 | FROM INFORMATION_SCHEMA.COLUMNS 25 | WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = %s 26 | """ 27 | await cursor.execute(query, (table_name, column_name)) 28 | result = await cursor.fetchone() 29 | 30 | if result and 'COLUMN_TYPE' in result and result['COLUMN_TYPE'].startswith("enum("): 31 | enum_str = result['COLUMN_TYPE'] 32 | enum_values = [val.strip("'") for val in enum_str[5:-1].split(',')] 33 | return enum_values 34 | return [] 35 | except Exception as e: 36 | print(f"Erreur lors de la récupération des catégories pour la table '{table_name}' et la colonne '{column_name}': {e}") 37 | return [] 38 | finally: 39 | if conn: 40 | pool.release(conn) 41 | 42 | async def create_client(pool, nom: str, prenom: str, email: str, telephone: str, adresse: str, nif: str, stat: str, categorie: str, axe: str) -> int | None: 43 | """ 44 | Crée un nouveau client dans la base de données avec gestion de transaction. 45 | 46 | Args: 47 | pool: Le pool de connexions aiomysql. 48 | nom (str): Le nom du client. 49 | prenom (str): Le prénom du client. 50 | email (str): L'email du client. 51 | telephone (str): Le numéro de téléphone du client. 52 | adresse (str): L'adresse du client. 53 | nif (str): Le NIF du client. 54 | stat (str): Le STAT du client. 55 | categorie (str): La catégorie du client ('Particulier', 'Organisation', 'Société'). 56 | axe (str): L'axe géographique du client. 57 | 58 | Returns: 59 | int | None: L'ID du client nouvellement créé, ou None en cas d'échec. 60 | """ 61 | conn = None 62 | try: 63 | conn = await pool.acquire() 64 | await conn.begin() # Début de la transaction 65 | async with conn.cursor() as cur: 66 | await cur.execute( 67 | "INSERT INTO Client (nom, prenom, email, telephone, adresse, nif, stat, date_ajout, categorie, axe) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", 68 | (nom, prenom, email, telephone, adresse, nif, stat, date.today(), categorie, axe) 69 | ) 70 | await conn.commit() # Validation de la transaction 71 | return cur.lastrowid 72 | except Exception as e: 73 | if conn: 74 | await conn.rollback() # Annulation de la transaction en cas d'erreur 75 | print(f"Erreur lors de la création du client: {e}") 76 | return None 77 | finally: 78 | if conn: 79 | pool.release(conn) 80 | 81 | async def read_client(pool, client_id: int | None = None) -> list[dict] | dict | None: 82 | """ 83 | Lit les détails d'un client spécifique par ID ou de tous les clients. 84 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 85 | 86 | Args: 87 | pool: Le pool de connexions aiomysql. 88 | client_id (int | None): L'ID du client à lire (optionnel). 89 | 90 | Returns: 91 | list[dict] | dict | None: Une liste de dictionnaires pour tous les clients, 92 | un seul dictionnaire pour un client spécifique, 93 | ou None en cas d'erreur. 94 | """ 95 | conn = None 96 | try: 97 | conn = await pool.acquire() 98 | async with conn.cursor(aiomysql.DictCursor) as cursor: 99 | if client_id is None: 100 | await cursor.execute("SELECT * FROM Client") 101 | return await cursor.fetchall() 102 | else: 103 | await cursor.execute("SELECT * FROM Client WHERE client_id = %s", (client_id,)) 104 | return await cursor.fetchone() 105 | except Exception as e: 106 | print(f"Erreur lors de la lecture du client/des clients: {e}") 107 | return None if client_id is not None else [] 108 | finally: 109 | if conn: 110 | pool.release(conn) 111 | 112 | async def update_client(pool, client_id: int, nom: str, prenom: str, email: str, telephone: str, adresse: str, nif: str, stat: str, categorie: str, axe: str) -> int: 113 | """ 114 | Modifie un client existant dans la base de données avec gestion de transaction. 115 | 116 | Args: 117 | pool: Le pool de connexions aiomysql. 118 | client_id (int): L'ID du client à mettre à jour. 119 | nom (str): Le nouveau nom. 120 | prenom (str): Le nouveau prénom. 121 | email (str): Le nouvel email. 122 | telephone (str): Le nouveau téléphone. 123 | adresse (str): La nouvelle adresse. 124 | nif (str): Le nouveau NIF. 125 | stat (str): Le nouveau STAT. 126 | categorie (str): La nouvelle catégorie. 127 | axe (str): Le nouvel axe. 128 | 129 | Returns: 130 | int: Le nombre de lignes affectées (1 en cas de succès, 0 sinon). 131 | """ 132 | conn = None 133 | try: 134 | conn = await pool.acquire() 135 | await conn.begin() 136 | async with conn.cursor() as cur: 137 | await cur.execute( 138 | "UPDATE Client SET nom = %s, prenom = %s, email = %s, telephone = %s, adresse = %s, nif = %s, stat = %s, categorie = %s, axe = %s WHERE client_id = %s", 139 | (nom, prenom, email, telephone, adresse, nif, stat, categorie, axe, client_id) 140 | ) 141 | await conn.commit() 142 | return cur.rowcount 143 | except Exception as e: 144 | if conn: 145 | await conn.rollback() 146 | print(f"Erreur lors de la mise à jour du client (ID: {client_id}): {e}") 147 | return 0 148 | finally: 149 | if conn: 150 | pool.release(conn) 151 | 152 | async def delete_client(pool, client_id: int) -> int: 153 | """ 154 | Supprime un client de la base de données avec gestion de transaction. 155 | 156 | Args: 157 | pool: Le pool de connexions aiomysql. 158 | client_id (int): L'ID du client à supprimer. 159 | 160 | Returns: 161 | int: Le nombre de lignes supprimées (1 en cas de succès, 0 sinon). 162 | """ 163 | conn = None 164 | try: 165 | conn = await pool.acquire() 166 | await conn.begin() 167 | async with conn.cursor() as cur: 168 | await cur.execute("DELETE FROM Client WHERE client_id = %s", (client_id,)) 169 | await conn.commit() 170 | return cur.rowcount 171 | except Exception as e: 172 | if conn: 173 | await conn.rollback() 174 | print(f"Erreur lors de la suppression du client (ID: {client_id}): {e}") 175 | return 0 176 | finally: 177 | if conn: 178 | pool.release(conn) 179 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/Planning/affichePlanning.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | import asyncio 3 | import datetime # Assurez-vous que datetime est importé si ce n'est pas déjà fait 4 | 5 | async def obtenirInfoBD(): 6 | """Demande à l'utilisateur les identifiants de connexion à la base de données.""" 7 | print("\nVeuillez entrer les informations de connexion à la base de données MySQL:") 8 | host = input("Entrez l'adresse du serveur MySQL (par défaut, localhost): ").strip() 9 | if not host: 10 | host = "localhost" 11 | 12 | port_str = input("Entrez le port du serveur MySQL (par défaut: 3306): ").strip() 13 | try: 14 | port = int(port_str) if port_str else 3306 15 | except ValueError: 16 | print("Port invalide, utilisation de la valeur par défaut (3306).") 17 | port = 3306 18 | 19 | user = input("Entrez le nom d'utilisateur MySQL: ").strip() 20 | password = input("Entrez le mot de passe MySQL: ").strip() 21 | database = input("Entrez le nom de la base de données MySQL (par défaut, Planificator): ").strip() 22 | if not database: 23 | database = "Planificator" 24 | return host, port, user, password, database 25 | 26 | async def afficher_planning_traitement(pool, client_id: int, traitement_id: int): 27 | """Affiche les détails de planification d'un traitement pour un client, 28 | incluant le type de traitement, la redondance et le montant de la facture.""" 29 | try: 30 | # Acquérir une connexion du pool. Le 'async with' assure qu'elle est relâchée. 31 | async with pool.acquire() as conn: 32 | async with conn.cursor(aiomysql.DictCursor) as cur: # Utilisation de DictCursor pour des résultats plus lisibles 33 | # Récupérer les informations du traitement (catégorie, type, redondance) 34 | await cur.execute(""" 35 | SELECT tt.categorieTraitement, tt.typeTraitement, p.redondance 36 | FROM Traitement t 37 | JOIN TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement 38 | JOIN Planning p ON t.traitement_id = p.traitement_id 39 | WHERE t.traitement_id = %s 40 | """, (traitement_id,)) 41 | traitement_info = await cur.fetchone() 42 | 43 | if not traitement_info: 44 | print(f"**Avertissement :** Traitement avec l'ID {traitement_id} non trouvé.") 45 | return 46 | 47 | # Accéder aux valeurs par leur nom de colonne 48 | categorie_traitement = traitement_info['categorieTraitement'] 49 | type_traitement = traitement_info['typeTraitement'] 50 | redondance = traitement_info['redondance'] 51 | 52 | # Récupérer les détails de planification et le montant de la facture associée 53 | await cur.execute(""" 54 | SELECT pd.date_planification, pd.statut, f.montant 55 | FROM PlanningDetails pd 56 | JOIN Planning p ON pd.planning_id = p.planning_id 57 | JOIN Traitement t ON p.traitement_id = t.traitement_id 58 | JOIN Contrat c ON t.contrat_id = c.contrat_id 59 | LEFT JOIN Facture f ON pd.planning_detail_id = f.planning_detail_id -- LEFT JOIN pour inclure les traitements sans facture (ex: 'À venir') 60 | WHERE c.client_id = %s AND t.traitement_id = %s 61 | ORDER BY pd.date_planification ASC 62 | """, (client_id, traitement_id)) 63 | planning_details = await cur.fetchall() 64 | 65 | if not planning_details: 66 | print(f"**Avertissement :** Aucun détail de planification trouvé pour le client {client_id} et le traitement {traitement_id}.") 67 | return 68 | 69 | # Calculer le nombre de traitements effectués 70 | traitements_effectues = sum(1 for detail in planning_details if detail['statut'] == 'Effectué') 71 | 72 | # Afficher les informations 73 | print("\n" + "="*40) 74 | print(f"--- Détails du Traitement et Planification ---") 75 | print(f"Traitement ID : {traitement_id}") 76 | print(f"Catégorie du traitement: {categorie_traitement}") 77 | print(f"Type de traitement: {type_traitement}") 78 | print(f"Redondance: {redondance} (1=mensuel, 2=bimensuel, 3=trimestriel, 4=quadrimestriel, 6=semestriel, 12=annuel)") # Ajout de la signification de la redondance 79 | print(f"**Traitements effectués :** {traitements_effectues}") 80 | print("\n**Détails de la planification :**") 81 | for detail in planning_details: 82 | # Formatage de la date pour un affichage plus propre 83 | date_formatted = detail['date_planification'].strftime('%Y-%m-%d') 84 | # Afficher le montant si disponible, sinon indiquer "N/A" 85 | montant_str = f", Montant: {detail['montant']} Ar" if detail['montant'] is not None else ", Montant: N/A" 86 | print(f" Date: {date_formatted}, Statut: {detail['statut']}{montant_str}") 87 | print("="*40 + "\n") 88 | 89 | except Exception as e: 90 | print(f"Une erreur est survenue lors de l'accès à la base de données : {e}") 91 | 92 | async def main(): 93 | pool = None # Initialiser le pool à None 94 | try: 95 | # Récupérer les identifiants de la base de données 96 | host, port, user, password, database = await obtenirInfoBD() 97 | 98 | # Créer le pool de connexions avec les identifiants fournis par l'utilisateur 99 | print(f"\nTentative de connexion à la base de données '{database}' sur {host}:{port} avec l'utilisateur '{user}'...") 100 | pool = await aiomysql.create_pool( 101 | host=host, 102 | port=port, 103 | user=user, 104 | password=password, 105 | db=database, 106 | autocommit=True, # S'assurer que les modifications sont auto-validées 107 | minsize=1, # Taille minimale du pool 108 | maxsize=10 # Taille maximale du pool 109 | ) 110 | print("Connexion à la base de données établie avec succès.") 111 | 112 | while True: 113 | client_id_input = input("\nEntrez l'ID du client pour afficher le planning (laissez vide pour quitter): ").strip() 114 | if not client_id_input: 115 | break 116 | try: 117 | client_id = int(client_id_input) 118 | except ValueError: 119 | print("Erreur : L'ID du client doit être un nombre entier. Veuillez réessayer.") 120 | continue 121 | 122 | traitement_id_input = input("Entrez l'ID du traitement pour afficher le planning (laissez vide pour quitter): ").strip() 123 | if not traitement_id_input: 124 | break 125 | try: 126 | traitement_id = int(traitement_id_input) 127 | except ValueError: 128 | print("Erreur : L'ID du traitement doit être un nombre entier. Veuillez réessayer.") 129 | continue 130 | 131 | await afficher_planning_traitement(pool, client_id, traitement_id) 132 | 133 | # Demander à l'utilisateur s'il souhaite continuer 134 | reponse = input("Voulez-vous afficher le planning d'un autre client/traitement ? (oui/non): ").strip().lower() 135 | if reponse != 'oui': 136 | break # Quitter la boucle si la réponse n'est pas 'oui' 137 | 138 | except Exception as e: 139 | print(f"Une erreur inattendue est survenue: {e}") 140 | finally: 141 | # S'assurer que le pool est fermé même en cas d'erreur 142 | if pool: 143 | print("\nFermeture du pool de connexions à la base de données...") 144 | pool.close() 145 | await pool.wait_closed() 146 | print("Pool de connexions fermé.") 147 | 148 | if __name__ == "__main__": 149 | asyncio.run(main()) 150 | -------------------------------------------------------------------------------- /Account/accountAvecHash.py: -------------------------------------------------------------------------------- 1 | import bcrypt 2 | import re 3 | import mysql.connector 4 | from mysql.connector import Error 5 | 6 | # Fonction pour établir une connexion à la base de données 7 | def connect(): 8 | while True: 9 | try: 10 | user = input("Nom d'utilisateur MySQL : ") 11 | password = input("Mot de passe MySQL : ") 12 | host = input("Hôte MySQL (laissez vide pour localhost) : ") or "localhost" 13 | database = input("Nom de la base de données : ") 14 | 15 | config = { 16 | 'user': user, 17 | 'password': password, 18 | 'host': host, 19 | 'database': database, 20 | 'raise_on_warnings': True 21 | } 22 | 23 | conn = mysql.connector.connect(**config) 24 | print("Connexion à la base de données réussie !") 25 | return conn 26 | 27 | except Error as e: 28 | print(f"Erreur de connexion à la base de données : {e}") 29 | retry = input("Voulez-vous réessayer ? (oui/non) : ").lower() 30 | if retry != 'oui': 31 | return None 32 | 33 | # Fonction pour vérifier si le mot de passe correspond aux informations personnelles 34 | def password_is_personal_info(nom, prenom, username, password): 35 | return nom.lower() in password.lower() or prenom.lower() in password.lower() or username.lower() in password.lower() 36 | 37 | # Fonction pour hacher le mot de passe avec bcrypt 38 | def hash_password(password): 39 | # Générer un "salt" aléatoire 40 | salt = bcrypt.gensalt() 41 | # Hacher le mot de passe avec le salt 42 | hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt) 43 | return hashed_password 44 | 45 | # Fonction pour demander un mot de passe valide (modifiée pour utiliser le hachage) 46 | def get_valid_password(nom, prenom, username): 47 | while True: 48 | password = input("Entrez votre nouveau mot de passe : ") 49 | confirm_password = input("Confirmez votre nouveau mot de passe : ") 50 | 51 | if password != confirm_password: 52 | print("Les mots de passe ne correspondent pas. Veuillez réessayer.") 53 | elif len(password) < 8: 54 | print("Le mot de passe doit contenir au moins 8 caractères.") 55 | elif password_is_personal_info(nom, prenom, username, password): 56 | print("Le mot de passe ne doit pas contenir votre nom ou prénom ou même votre nom d'utilisateur. Veuillez réessayer.") 57 | else: 58 | return hash_password(password) # Hacher le mot de passe avant de le retourner 59 | 60 | def is_valid_email(email): 61 | # Expression régulière pour vérifier le format de l'e-mail 62 | email_regex = r"[^@]+@[^@]+\.[^@]+" 63 | return re.match(email_regex, email) is not None 64 | 65 | def creation_compte(conn): 66 | nom = input("Entrez le nom : ") 67 | prenom = input("Entrez le prénom : ") 68 | while True: 69 | email = input("Entrez l'email : ") 70 | if not is_valid_email(email): 71 | print("L'adresse e-mail n'est pas valide. Veuillez réessayer.") 72 | else: 73 | break 74 | username = input("Entrez votre nom d'utilisateur : ") 75 | type_compte = input("Entrez le type de compte (Administrateur/Utilisateur) : ") 76 | password = get_valid_password(nom, prenom, username) # Le mot de passe est déjà haché 77 | 78 | try: 79 | cursor = conn.cursor() 80 | query = """ 81 | INSERT INTO Account (nom, prenom, email, username, password, type_compte) 82 | VALUES (%s, %s, %s, %s, %s, %s) 83 | """ 84 | cursor.execute(query, (nom, prenom, email, username, password, type_compte)) 85 | conn.commit() 86 | print("Compte créé avec succès !") 87 | except Error as e: 88 | print(f"Erreur lors de la création du compte : {e}") 89 | 90 | # Pour voir les comptes existants 91 | def lecture_compte(conn): 92 | try: 93 | cursor = conn.cursor() 94 | cursor.execute("SELECT * FROM Account") 95 | accounts = cursor.fetchall() 96 | 97 | if accounts: # Vérifier si la liste des comptes n'est pas vide 98 | print("\nListe des comptes :") 99 | for account in accounts: 100 | print(f"ID: {account[0]}, Nom: {account[1]}, Prénom: {account[2]}, Email: {account[3]}, Username: {account[4]}, Type: {account[6]}, Date de compte: {account[7]}") 101 | else: 102 | print("\nAucun compte trouvé dans la base de données.") 103 | 104 | except Error as e: 105 | print(f"Erreur lors de la lecture des comptes : {e}") 106 | 107 | # Fonction pour mettre à jour un compte 108 | def update_compte(conn): 109 | account_id = input("Entrez l'ID du compte à mettre à jour : ") 110 | nom = input("Entrez le nouveau nom (laissez vide pour ne pas modifier) : ") 111 | prenom = input("Entrez le nouveau prénom (laissez vide pour ne pas modifier) : ") 112 | while True: 113 | email = input("Entrez le nouvel email (laissez vide pour ne pas modifier) : ") 114 | if email == "": # Si l'utilisateur ne veut pas modifier l'email 115 | break 116 | if not is_valid_email(email): 117 | print("L'adresse e-mail n'est pas valide. Veuillez réessayer.") 118 | else: 119 | break 120 | username = input("Entrer le nouveau nom d'utilisateur (laissez vide pour ne pas modifier) : ") 121 | password = input("Entrez le nouveau mot de passe (laissez vide pour ne pas modifier) : ") 122 | 123 | try: 124 | cursor = conn.cursor() 125 | updates = [] 126 | params = [] 127 | 128 | if nom: 129 | updates.append("nom = %s") 130 | params.append(nom) 131 | if prenom: 132 | updates.append("prenom = %s") 133 | params.append(prenom) 134 | if email: 135 | updates.append("email = %s") 136 | params.append(email) 137 | if username: 138 | updates.append("username = %s") 139 | params.append(username) 140 | if password: 141 | password = get_valid_password(nom if nom else "", prenom if prenom else "", username if username else "") 142 | updates.append("password = %s") 143 | params.append(password) 144 | 145 | if updates: 146 | query = f"UPDATE Account SET {', '.join(updates)} WHERE id_compte = %s" 147 | params.append(account_id) 148 | cursor.execute(query, tuple(params)) 149 | conn.commit() 150 | print("Compte mis à jour avec succès !") 151 | else: 152 | print("Aucune modification apportée.") 153 | except Error as e: 154 | print(f"Erreur lors de la mise à jour du compte : {e}") 155 | 156 | def suppression_compte(conn): 157 | account_id = input("Entrez l'ID du compte à supprimer : ") 158 | admin_password = input("Entrez le mot de passe administrateur pour confirmer : ") 159 | 160 | try: 161 | cursor = conn.cursor() 162 | cursor.execute("SELECT password FROM Account WHERE type_compte = 'Administrateur'") 163 | admin_db_password_hashed = cursor.fetchone()[0] 164 | 165 | # Convertir le mot de passe haché de la base de données en bytes 166 | admin_db_password_hashed_bytes = admin_db_password_hashed.encode('utf-8') 167 | 168 | if bcrypt.checkpw(admin_password.encode('utf-8'), admin_db_password_hashed_bytes): 169 | cursor.execute("DELETE FROM Account WHERE id_compte = %s", (account_id,)) 170 | conn.commit() 171 | print("Compte supprimé avec succès !") 172 | else: 173 | print("Mot de passe administrateur incorrect. Suppression annulée.") 174 | except Error as e: 175 | print(f"Erreur lors de la suppression du compte : {e}") 176 | 177 | # Menu principal 178 | def main(): 179 | conn = connect() 180 | if not conn: 181 | return 182 | 183 | while True: 184 | print("\n--- Menu Principal ---") 185 | print("1. Créer un compte") 186 | print("2. Lister les comptes") 187 | print("3. Mettre à jour un compte") 188 | print("4. Supprimer un compte") 189 | print("5. Quitter") 190 | choice = input("Choisissez une option : ") 191 | 192 | if choice == '1': 193 | creation_compte(conn) 194 | elif choice == '2': 195 | lecture_compte(conn) 196 | elif choice == '3': 197 | update_compte(conn) 198 | elif choice == '4': 199 | suppression_compte(conn) 200 | elif choice == '5': 201 | break 202 | else: 203 | print("Option invalide. Veuillez réessayer.") 204 | 205 | conn.close() 206 | print("Déconnexion de la base de données.") 207 | 208 | # Point d'entrée du script 209 | if __name__ == "__main__": 210 | main() -------------------------------------------------------------------------------- /Contrat/CRUDonPlanning.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | from datetime import date, datetime, timedelta 3 | 4 | 5 | async def get_enum_values(pool, table_name: str, column_name: str) -> list[str]: 6 | """ 7 | Récupère les valeurs d'une colonne ENUM d'une table spécifique. 8 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 9 | 10 | Args: 11 | pool: Le pool de connexions aiomysql. 12 | table_name (str): Le nom de la table. 13 | column_name (str): Le nom de la colonne ENUM. 14 | 15 | Returns: 16 | list[str]: Une liste des valeurs ENUM, ou une liste vide en cas d'erreur. 17 | """ 18 | conn = None 19 | try: 20 | conn = await pool.acquire() 21 | async with conn.cursor(aiomysql.DictCursor) as cursor: 22 | query = """ 23 | SELECT COLUMN_TYPE 24 | FROM INFORMATION_SCHEMA.COLUMNS 25 | WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = %s 26 | """ 27 | await cursor.execute(query, (table_name, column_name)) 28 | result = await cursor.fetchone() 29 | 30 | if result and 'COLUMN_TYPE' in result and result['COLUMN_TYPE'].startswith("enum("): 31 | enum_str = result['COLUMN_TYPE'] 32 | enum_values = [val.strip("'") for val in enum_str[5:-1].split(',')] 33 | return enum_values 34 | return [] 35 | except Exception as e: 36 | print(f"Erreur lors de la récupération des valeurs ENUM pour '{table_name}.{column_name}': {e}") 37 | return [] 38 | finally: 39 | if conn: 40 | pool.release(conn) 41 | 42 | async def create_planning(pool, traitement_id: int, redondance: str, date_debut_planification: date, duree_traitement: int = 12, unite_duree: str = 'mois') -> int | None: 43 | """ 44 | Crée un planning pour un traitement donné avec gestion de transaction. 45 | 46 | Args: 47 | pool: Le pool de connexions aiomysql. 48 | traitement_id (int): L'ID du traitement associé. 49 | redondance (str): Le type de redondance du planning. 50 | date_debut_planification (date): La date de début de la planification. 51 | duree_traitement (int): La durée du traitement. Par défaut, 12. 52 | unite_duree (str): L'unité de la durée ('mois' ou 'années'). 53 | 54 | Returns: 55 | int | None: L'ID du planning nouvellement créé, ou None en cas d'échec. 56 | """ 57 | conn = None 58 | try: 59 | conn = await pool.acquire() 60 | await conn.begin() # Début de la transaction 61 | async with conn.cursor() as cur: 62 | # Calculer la date de fin de la planification 63 | if unite_duree == 'mois': 64 | date_fin_planification = date_debut_planification + timedelta(days=duree_traitement * 30) 65 | elif unite_duree == 'années': 66 | date_fin_planification = date_debut_planification + timedelta(days=duree_traitement * 365) 67 | else: 68 | raise ValueError("Unité de durée non valide. Utilisez 'mois' ou 'années'.") 69 | 70 | await cur.execute(""" 71 | INSERT INTO Planning (traitement_id, redondance, date_debut_planification, date_fin_planification, duree_traitement, unite_duree) 72 | VALUES (%s, %s, %s, %s, %s, %s) 73 | """, (traitement_id, redondance, date_debut_planification, date_fin_planification, duree_traitement, unite_duree)) 74 | await conn.commit() # Validation de la transaction 75 | return cur.lastrowid 76 | except ValueError as ve: 77 | if conn: 78 | await conn.rollback() # Annulation en cas d'erreur de valeur 79 | print(f"Erreur de valeur : {ve}") 80 | return None 81 | except Exception as e: 82 | if conn: 83 | await conn.rollback() # Annulation en cas d'autre erreur 84 | print(f"Erreur lors de la création du planning : {e}") 85 | return None 86 | finally: 87 | if conn: 88 | pool.release(conn) 89 | 90 | async def read_planning(pool, planning_id: int | None = None) -> list[dict] | dict | None: 91 | """ 92 | Lit un planning à partir de son ID ou tous les plannings. 93 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 94 | 95 | Args: 96 | pool: Le pool de connexions aiomysql. 97 | planning_id (int | None): L'ID du planning à lire (optionnel). 98 | 99 | Returns: 100 | list[dict] | dict | None: Une liste de dictionnaires si l'ID est None, un dictionnaire si l'ID est spécifié, ou None en cas d'erreur. 101 | """ 102 | conn = None 103 | try: 104 | conn = await pool.acquire() 105 | async with conn.cursor(aiomysql.DictCursor) as cur: 106 | if planning_id is None: 107 | await cur.execute("SELECT * FROM Planning") 108 | return await cur.fetchall() 109 | else: 110 | await cur.execute("SELECT * FROM Planning WHERE planning_id = %s", (planning_id,)) 111 | return await cur.fetchone() 112 | except Exception as e: 113 | print(f"Erreur lors de la lecture du planning : {e}") 114 | return None if planning_id is not None else [] 115 | finally: 116 | if conn: 117 | pool.release(conn) 118 | 119 | async def update_planning(pool, planning_id: int, traitement_id: int, redondance: str, date_debut_planification: date, duree_traitement: int, unite_duree: str) -> int: 120 | """ 121 | Modifie un planning existant avec gestion de transaction. 122 | 123 | Args: 124 | pool: Le pool de connexions aiomysql. 125 | planning_id (int): L'ID du planning à modifier. 126 | traitement_id (int): Le nouvel ID du traitement. 127 | redondance (str): La nouvelle redondance. 128 | date_debut_planification (date): La nouvelle date de début de planification. 129 | duree_traitement (int): La nouvelle durée du traitement. 130 | unite_duree (str): La nouvelle unité de durée. 131 | 132 | Returns: 133 | int: Le nombre de lignes affectées (1 si succès, 0 sinon). 134 | """ 135 | conn = None 136 | try: 137 | conn = await pool.acquire() 138 | await conn.begin() # Début de la transaction 139 | async with conn.cursor() as cur: 140 | if unite_duree == 'mois': 141 | date_fin_planification = date_debut_planification + timedelta(days=duree_traitement * 30) 142 | elif unite_duree == 'années': 143 | date_fin_planification = date_debut_planification + timedelta(days=duree_traitement * 365) 144 | else: 145 | raise ValueError("Unité de durée non valide. Utilisez 'mois' ou 'années'.") 146 | 147 | await cur.execute(""" 148 | UPDATE Planning SET traitement_id = %s, redondance = %s, date_debut_planification = %s, date_fin_planification = %s, duree_traitement = %s, unite_duree = %s 149 | WHERE planning_id = %s 150 | """, (traitement_id, redondance, date_debut_planification, date_fin_planification, duree_traitement, unite_duree, planning_id)) 151 | await conn.commit() # Validation de la transaction 152 | return cur.rowcount 153 | except ValueError as ve: 154 | if conn: 155 | await conn.rollback() # Annulation en cas d'erreur de valeur 156 | print(f"Erreur de valeur : {ve}") 157 | return 0 158 | except Exception as e: 159 | if conn: 160 | await conn.rollback() # Annulation en cas d'autre erreur 161 | print(f"Erreur lors de la mise à jour du planning : {e}") 162 | return 0 163 | finally: 164 | if conn: 165 | pool.release(conn) 166 | 167 | async def delete_planning(pool, planning_id: int) -> int: 168 | """ 169 | Supprime un planning à partir de son ID avec gestion de transaction. 170 | 171 | Args: 172 | pool: Le pool de connexions aiomysql. 173 | planning_id (int): L'ID du planning à supprimer. 174 | 175 | Returns: 176 | int: Le nombre de lignes supprimées (1 si succès, 0 sinon). 177 | """ 178 | conn = None 179 | try: 180 | conn = await pool.acquire() 181 | await conn.begin() # Début de la transaction 182 | async with conn.cursor() as cur: 183 | await cur.execute("DELETE FROM Planning WHERE planning_id = %s", (planning_id,)) 184 | await conn.commit() # Validation de la transaction 185 | return cur.rowcount 186 | except Exception as e: 187 | if conn: 188 | await conn.rollback() # Annulation de la transaction en cas d'erreur 189 | print(f"Erreur lors de la suppression du planning : {e}") 190 | return 0 191 | finally: 192 | if conn: 193 | pool.release(conn) 194 | 195 | """ 196 | Répartition des redondances pour les plannings : 197 | - Par semaine 198 | - Deux à trois fois par semaine (Toutes les semaines) 199 | - Deux fois par mois (Tous les 15 jours) 200 | - Par mois 201 | - Tous les deux mois 202 | - Tous les 4 mois 203 | - Tous les 6 mois 204 | """ -------------------------------------------------------------------------------- /Contrat/fonctionnalités/Planning/majPlanningDetail.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | import asyncio 3 | from datetime import datetime 4 | 5 | async def get_db_credentials(): 6 | """Demande à l'utilisateur les identifiants de connexion à la base de données.""" 7 | print("\nVeuillez entrer les informations de connexion à la base de données MySQL:") 8 | host = input("Entrez l'adresse du serveur MySQL (par défaut, localhost): ").strip() 9 | if not host: 10 | host = "localhost" 11 | 12 | port_str = input("Entrez le port du serveur MySQL (par défaut: 3306): ").strip() 13 | try: 14 | port = int(port_str) if port_str else 3306 15 | except ValueError: 16 | print("Port invalide, utilisation de la valeur par défaut (3306).") 17 | port = 3306 18 | 19 | user = input("Entrez le nom d'utilisateur MySQL: ").strip() 20 | password = input("Entrez le mot de passe MySQL: ").strip() 21 | database = input("Entrez le nom de la base de données MySQL (par défaut, Planificator): ").strip() 22 | if not database: 23 | database = "Planificator" 24 | return host, port, user, password, database 25 | 26 | async def mettre_a_jour_planning_detail(pool, planning_detail_id: int, statut: str): 27 | """ 28 | Met à jour le statut de PlanningDetails et enregistre l'historique si le statut est "Effectué". 29 | Affiche également les détails du planning avant la mise à jour. 30 | 31 | Args: 32 | pool: Le pool de connexions aiomysql. 33 | planning_detail_id (int): L'ID du détail de planification à mettre à jour. 34 | statut (str): Le nouveau statut ('Effectué' ou 'À venir'). 35 | """ 36 | try: 37 | async with pool.acquire() as conn: 38 | async with conn.cursor(aiomysql.DictCursor) as cur: 39 | # 1. Récupérer et afficher les informations détaillées du planning_detail 40 | # Inclut le type de traitement, la redondance et le montant de la facture. 41 | await cur.execute(""" 42 | SELECT pd.planning_detail_id, 43 | pd.planning_id, 44 | pd.date_planification, 45 | pd.statut, 46 | tt.typeTraitement AS type_traitement, 47 | p.redondance, 48 | f.montant AS montant_facture, 49 | f.facture_id, 50 | f.etat AS etat_facture, 51 | c.client_id, 52 | CONCAT(cl.nom, ' ', cl.prenom) AS client_nom 53 | FROM PlanningDetails pd 54 | JOIN Planning p ON pd.planning_id = p.planning_id 55 | JOIN Traitement t ON p.traitement_id = t.traitement_id 56 | JOIN TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement 57 | JOIN Contrat c ON t.contrat_id = c.contrat_id 58 | JOIN Client cl ON c.client_id = cl.client_id 59 | LEFT JOIN Facture f ON pd.planning_detail_id = f.planning_detail_id 60 | WHERE pd.planning_detail_id = %s 61 | """, (planning_detail_id,)) 62 | detail_info = await cur.fetchone() 63 | 64 | if not detail_info: 65 | print(f"**Erreur:** Le détail de planification avec l'ID {planning_detail_id} n'existe pas. Aucune mise à jour effectuée.") 66 | return 67 | 68 | print("\n--- Informations actuelles du détail de planification ---") 69 | print(f"ID Détail Planning: {detail_info['planning_detail_id']}") 70 | print(f"Client: {detail_info['client_nom']} (ID: {detail_info['client_id']})") 71 | print(f"Date de planification: {detail_info['date_planification'].strftime('%Y-%m-%d')}") 72 | print(f"Statut actuel: {detail_info['statut']}") 73 | print(f"Type de traitement: {detail_info['type_traitement']}") 74 | print(f"Redondance: {detail_info['redondance']} (1=mensuel, 2=bimensuel, 3=trimestriel, 4=quadrimestriel, 6=semestriel, 12=annuel)") 75 | print(f"Montant Facture: {detail_info['montant_facture']} Ar" if detail_info['montant_facture'] is not None else "Montant Facture: N/A") 76 | print(f"État Facture: {detail_info['etat_facture']}" if detail_info['etat_facture'] is not None else "État Facture: N/A") 77 | print("-----------------------------------------------------") 78 | 79 | # 2. Mettre à jour le statut de PlanningDetails 80 | await cur.execute("UPDATE PlanningDetails SET statut = %s WHERE planning_detail_id = %s", (statut, planning_detail_id)) 81 | rows_affected = cur.rowcount 82 | 83 | if rows_affected == 0: 84 | print(f"**Avertissement:** Aucune ligne mise à jour pour planning_detail_id {planning_detail_id}. Le statut était peut-être déjà '{statut}'.") 85 | else: 86 | print(f"Statut du détail de planification {planning_detail_id} mis à jour en '{statut}'.") 87 | 88 | # 3. Enregistrer l'historique si le statut est "Effectué" 89 | if statut == "Effectué": 90 | # Récupérer facture_id et définir des valeurs par défaut pour issue et action 91 | facture_id_for_history = detail_info['facture_id'] 92 | issue_for_history = "Aucun problème signalé." # Valeur par défaut pour la colonne NOT NULL 93 | action_for_history = "Traitement standard effectué." # Valeur par défaut pour la colonne NOT NULL 94 | 95 | contenu_historique = f"Traitement effectué. Ancien statut: {detail_info['statut']}, Nouveau statut: {statut}. État de la facture: {detail_info['etat_facture'] if detail_info['etat_facture'] is not None else 'N/A'}." 96 | 97 | # Assurez-vous que les colonnes 'issue' et 'action' sont fournies car elles sont NOT NULL 98 | await cur.execute(""" 99 | INSERT INTO Historique (facture_id, planning_detail_id, signalement_id, date_historique, contenu, issue, action) 100 | VALUES (%s, %s, %s, %s, %s, %s, %s) 101 | """, (facture_id_for_history, planning_detail_id, None, datetime.now(), contenu_historique, issue_for_history, action_for_history)) 102 | print(f"Historique enregistré pour planning_detail_id {planning_detail_id}.") 103 | 104 | except Exception as e: 105 | print(f"**Erreur lors de la mise à jour du planning_detail {planning_detail_id}:** {e}") 106 | 107 | async def main(): 108 | pool = None # Initialiser le pool à None 109 | try: 110 | # Récupérer les identifiants de la base de données 111 | host, port, user, password, database = await get_db_credentials() 112 | 113 | # Créer le pool de connexions avec les identifiants fournis par l'utilisateur 114 | print(f"\nTentative de connexion à la base de données '{database}' sur {host}:{port} avec l'utilisateur '{user}'...") 115 | pool = await aiomysql.create_pool( 116 | host=host, 117 | port=port, 118 | user=user, 119 | password=password, 120 | db=database, 121 | autocommit=True, # Auto-commit les opérations 122 | minsize=1, # Taille minimale du pool 123 | maxsize=5 # Taille maximale du pool 124 | ) 125 | print("Connexion à la base de données établie avec succès.") 126 | 127 | while True: 128 | print("\n--- Mise à jour du statut d'un détail de planification ---") 129 | planning_detail_id_input = input("Entrez l'ID du détail de planification à mettre à jour (laissez vide pour quitter) : ").strip() 130 | if not planning_detail_id_input: 131 | break # Quitter la boucle si l'utilisateur n'entre rien 132 | 133 | try: 134 | planning_detail_id = int(planning_detail_id_input) 135 | except ValueError: 136 | print("**Erreur :** L'ID du détail de planification doit être un nombre entier. Veuillez réessayer.") 137 | continue # Recommencer la boucle 138 | 139 | statut_input = input("Entrez le nouveau statut ('Effectué' ou 'À venir') : ").strip() 140 | if statut_input.lower() not in ['effectué', 'à venir']: 141 | print("**Erreur :** Statut invalide. Veuillez entrer 'Effectué' ou 'À venir'.") 142 | continue # Recommencer la boucle 143 | 144 | # Capitaliser la première lettre pour correspondre aux valeurs de l'ENUM si nécessaire 145 | statut = statut_input.capitalize() 146 | 147 | await mettre_a_jour_planning_detail(pool, planning_detail_id, statut) 148 | 149 | # Demander à l'utilisateur s'il souhaite continuer 150 | reponse = input("\nVoulez-vous mettre à jour un autre détail de planification ? (oui/non) : ").strip().lower() 151 | if reponse != 'oui': 152 | break # Quitter la boucle si la réponse n'est pas 'oui' 153 | 154 | except Exception as e: 155 | print(f"**Une erreur inattendue est survenue dans le script principal :** {e}") 156 | finally: 157 | # S'assurer que le pool est fermé même en cas d'erreur 158 | if pool: 159 | print("\nFermeture du pool de connexions à la base de données...") 160 | pool.close() 161 | await pool.wait_closed() 162 | print("Pool de connexions fermé.") 163 | 164 | if __name__ == "__main__": 165 | asyncio.run(main()) 166 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/Facture/gestionFacture.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | from datetime import date, datetime 3 | 4 | from rapportExelFacture import generationFactureClient 5 | 6 | async def obtenir_etats_facture(pool): 7 | """Récupère les valeurs ENUM pour la colonne 'etat' de la table Facture.""" 8 | conn = None 9 | try: 10 | conn = await pool.acquire() 11 | async with conn.cursor(aiomysql.DictCursor) as cursor: 12 | query = """ 13 | SELECT COLUMN_TYPE 14 | FROM INFORMATION_SCHEMA.COLUMNS 15 | WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'Facture' AND COLUMN_NAME = 'etat' 16 | """ 17 | await cursor.execute(query) 18 | result = await cursor.fetchone() 19 | if result and 'COLUMN_TYPE' in result and result['COLUMN_TYPE'].startswith("enum("): 20 | enum_str = result['COLUMN_TYPE'] 21 | return [val.strip("'") for val in enum_str[len("enum("):-1].split(',')] 22 | return [] 23 | except Exception as e: 24 | print(f"Erreur lors de la récupération des états de facture : {e}") 25 | return [] 26 | finally: 27 | if conn: 28 | pool.release(conn) 29 | 30 | async def obtenir_axes(pool): 31 | """Récupère les valeurs ENUM pour la colonne 'axe' de la table Facture.""" 32 | conn = None 33 | try: 34 | conn = await pool.acquire() 35 | async with conn.cursor(aiomysql.DictCursor) as cursor: 36 | query = """ 37 | SELECT COLUMN_TYPE 38 | FROM INFORMATION_SCHEMA.COLUMNS 39 | WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'Facture' AND COLUMN_NAME = 'axe' 40 | """ 41 | await cursor.execute(query) 42 | result = await cursor.fetchone() 43 | if result and 'COLUMN_TYPE' in result and result['COLUMN_TYPE'].startswith("enum("): 44 | enum_str = result['COLUMN_TYPE'] 45 | return [val.strip("'") for val in enum_str[len("enum("):-1].split(',')] 46 | return [] 47 | except Exception as e: 48 | print(f"Erreur lors de la récupération des axes : {e}") 49 | return [] 50 | finally: 51 | if conn: 52 | pool.release(conn) 53 | 54 | 55 | # --- Vos fonctions CRUD pour Facture (inchangées) --- 56 | async def create_facture(pool, planning_detail_id, montant, date_traitement, etat='Non payé', axe=None): 57 | """ 58 | Crée une nouvelle facture dans la base de données. 59 | """ 60 | conn = None 61 | try: 62 | conn = await pool.acquire() 63 | async with conn.cursor() as cur: 64 | if axe is None: 65 | raise ValueError("L'axe ne peut pas être NULL pour la création d'une facture.") 66 | 67 | await cur.execute(""" 68 | INSERT INTO Facture (planning_detail_id, montant, date_traitement, etat, axe) 69 | VALUES (%s, %s, %s, %s, %s) 70 | """, (planning_detail_id, montant, date_traitement, etat, axe)) 71 | await conn.commit() 72 | facture_id = cur.lastrowid 73 | 74 | print(f"Facture {facture_id} créée avec succès pour PlanningDetail ID {planning_detail_id}.") 75 | return facture_id 76 | except Exception as e: 77 | print(f"Erreur lors de la création de la facture : {e}") 78 | if conn: 79 | await conn.rollback() 80 | return None 81 | finally: 82 | if conn: 83 | pool.release(conn) 84 | 85 | async def get_facture_details(pool, facture_id): 86 | """ 87 | Récupère les détails complets d'une facture, y compris les informations de traitement et l'historique des prix. 88 | """ 89 | conn = None 90 | try: 91 | conn = await pool.acquire() 92 | async with conn.cursor(aiomysql.DictCursor) as cur: 93 | await cur.execute(""" 94 | SELECT 95 | f.facture_id, 96 | f.planning_detail_id, 97 | f.montant, 98 | f.date_traitement, 99 | f.etat, 100 | f.axe, 101 | pd.date_planification, 102 | p.traitement_id, 103 | tt.typeTraitement AS nom_type_traitement, 104 | cl.client_id, 105 | cl.nom AS nom_client, 106 | cl.prenom AS prenom_client 107 | FROM Facture f 108 | JOIN PlanningDetails pd ON f.planning_detail_id = pd.planning_detail_id 109 | JOIN Planning p ON pd.planning_id = p.planning_id 110 | JOIN Traitement t ON p.traitement_id = t.traitement_id 111 | JOIN TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement 112 | JOIN Contrat co ON t.contrat_id = co.contrat_id 113 | JOIN Client cl ON co.client_id = cl.client_id 114 | WHERE f.facture_id = %s 115 | """, (facture_id,)) 116 | facture_details = await cur.fetchone() 117 | 118 | if not facture_details: 119 | return None 120 | 121 | await cur.execute(""" 122 | SELECT history_id, old_amount, new_amount, change_date, changed_by 123 | FROM Historique_prix 124 | WHERE facture_id = %s 125 | ORDER BY change_date DESC, history_id DESC 126 | """, (facture_id,)) 127 | historique_prix = await cur.fetchall() 128 | 129 | facture_details['historique_prix'] = historique_prix 130 | return facture_details 131 | except Exception as e: 132 | print(f"Erreur lors de la récupération des détails de la facture {facture_id} : {e}") 133 | return None 134 | finally: 135 | if conn: 136 | pool.release(conn) 137 | 138 | async def update_facture_montant_and_status(pool, facture_id, nouveau_montant=None, nouvel_etat=None, changed_by='System'): 139 | """ 140 | Met à jour le montant ou l'état d'une facture. 141 | Si le montant est modifié, un enregistrement est ajouté à Historique_prix. 142 | """ 143 | conn = None 144 | try: 145 | conn = await pool.acquire() 146 | async with conn.cursor(aiomysql.DictCursor) as cur: 147 | await cur.execute("SELECT montant, etat FROM Facture WHERE facture_id = %s", (facture_id,)) 148 | facture_actuelle = await cur.fetchone() 149 | 150 | if not facture_actuelle: 151 | print(f"Facture avec l'ID {facture_id} non trouvée.") 152 | return False 153 | 154 | old_montant = facture_actuelle['montant'] 155 | current_etat = facture_actuelle['etat'] 156 | 157 | updates = [] 158 | params = [] 159 | 160 | if nouveau_montant is not None and nouveau_montant != old_montant: 161 | updates.append("montant = %s") 162 | params.append(nouveau_montant) 163 | 164 | await cur.execute(""" 165 | INSERT INTO Historique_prix (facture_id, old_amount, new_amount, change_date, changed_by) 166 | VALUES (%s, %s, %s, %s, %s) 167 | """, (facture_id, old_montant, nouveau_montant, datetime.now(), changed_by)) 168 | print(f"Historique de prix enregistré pour la facture {facture_id}: {old_montant} -> {nouveau_montant}.") 169 | 170 | if nouvel_etat is not None and nouvel_etat != current_etat: 171 | updates.append("etat = %s") 172 | params.append(nouvel_etat) 173 | 174 | if not updates: 175 | print("Aucune modification à appliquer (montant et état sont identiques ou non spécifiés).") 176 | return False 177 | 178 | query_update = f"UPDATE Facture SET {', '.join(updates)} WHERE facture_id = %s" 179 | params.append(facture_id) 180 | 181 | await cur.execute(query_update, tuple(params)) 182 | await conn.commit() 183 | print(f"Facture {facture_id} mise à jour avec succès.") 184 | return True 185 | except Exception as e: 186 | print(f"Erreur lors de la mise à jour de la facture {facture_id} : {e}") 187 | if conn: 188 | await conn.rollback() 189 | return False 190 | finally: 191 | if conn: 192 | pool.release(conn) 193 | 194 | async def delete_facture(pool, facture_id): 195 | """ 196 | Supprime une facture de la base de données. 197 | Note: Cela déclenchera ON DELETE CASCADE sur Historique_prix et FactureTraitement. 198 | """ 199 | conn = None 200 | try: 201 | conn = await pool.acquire() 202 | async with conn.cursor() as cur: 203 | await cur.execute("DELETE FROM Facture WHERE facture_id = %s", (facture_id,)) 204 | await conn.commit() 205 | print(f"Facture {facture_id} supprimée avec succès.") 206 | return cur.rowcount 207 | except Exception as e: 208 | print(f"Erreur lors de la suppression de la facture {facture_id} : {e}") 209 | return 0 210 | finally: 211 | if conn: 212 | pool.release(conn) 213 | 214 | async def get_all_factures_for_client(pool, client_id): 215 | """ 216 | Récupère toutes les factures émises pour un client spécifique. 217 | """ 218 | conn = None 219 | try: 220 | conn = await pool.acquire() 221 | async with conn.cursor(aiomysql.DictCursor) as cur: 222 | await cur.execute(""" 223 | SELECT 224 | f.facture_id, 225 | f.montant, 226 | f.date_traitement, 227 | f.etat, 228 | f.axe, 229 | cl.nom AS nom_client, 230 | cl.prenom AS prenom_client, 231 | tt.typeTraitement AS nom_type_traitement, 232 | pd.date_planification 233 | FROM Facture f 234 | JOIN PlanningDetails pd ON f.planning_detail_id = pd.planning_detail_id 235 | JOIN Planning p ON pd.planning_id = p.planning_id 236 | JOIN Traitement tr ON p.traitement_id = tr.traitement_id 237 | JOIN TypeTraitement tt ON tr.id_type_traitement = tt.id_type_traitement 238 | JOIN Contrat co ON tr.contrat_id = co.contrat_id 239 | JOIN Client cl ON co.client_id = cl.client_id 240 | WHERE cl.client_id = %s 241 | ORDER BY f.date_traitement DESC 242 | """, (client_id,)) 243 | return await cur.fetchall() 244 | except Exception as e: 245 | print(f"Erreur lors de la récupération des factures pour le client {client_id} : {e}") 246 | return [] 247 | finally: 248 | if conn: 249 | pool.release(conn) 250 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/contrat/abrogerContratViaPlanning.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import asyncio 3 | import aiomysql 4 | from Contrat.fonctionnalités.connexionDB import DBConnection 5 | 6 | 7 | async def get_planning_detail_info(pool, planning_detail_id: int): 8 | """ 9 | Récupère les informations détaillées d'un planning_detail spécifique, 10 | incluant les IDs du planning, traitement et contrat associés, 11 | ainsi que le type de traitement et le nom du client. 12 | Prend le pool de connexions en argument. 13 | """ 14 | conn = None 15 | try: 16 | conn = await pool.acquire() # Obtenir une connexion du pool 17 | async with conn.cursor(aiomysql.DictCursor) as cursor: 18 | query = """ 19 | SELECT pd.planning_detail_id, 20 | pd.planning_id, 21 | pd.date_planification, 22 | pd.statut, -- Valeurs possibles selon le schéma: 'Effectué', 'À venir' 23 | p.traitement_id, 24 | t.contrat_id, 25 | tt.typeTraitement, -- Ajout du type de traitement 26 | c.nom AS client_nom -- Ajout du nom du client 27 | FROM PlanningDetails pd 28 | JOIN Planning p ON pd.planning_id = p.planning_id 29 | JOIN Traitement t ON p.traitement_id = t.traitement_id 30 | JOIN TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement -- Jointure pour TypeTraitement 31 | JOIN Contrat co ON t.contrat_id = co.contrat_id -- Jointure pour Contrat 32 | JOIN Client c ON co.client_id = c.client_id -- Jointure pour Client 33 | WHERE pd.planning_detail_id = %s; 34 | """ 35 | await cursor.execute(query, (planning_detail_id,)) 36 | result = await cursor.fetchone() 37 | return result 38 | except Exception as e: 39 | print(f"Erreur lors de la récupération des informations du planning_detail {planning_detail_id}: {e}") 40 | return None 41 | finally: 42 | if conn: 43 | pool.release(conn) # Relâcher la connexion dans le pool 44 | 45 | 46 | async def mark_treatment_as_performed(pool, planning_detail_id: int): 47 | """ 48 | Marque un traitement (planning_detail) comme 'Effectué'. 49 | Le statut 'Effectué' est une valeur valide pour l'ENUM 'statut' 50 | de la table PlanningDetails. 51 | Prend le pool de connexions en argument. 52 | """ 53 | conn = None 54 | try: 55 | conn = await pool.acquire() 56 | async with conn.cursor() as cursor: 57 | query = """ 58 | UPDATE PlanningDetails 59 | SET statut = 'Effectué' 60 | WHERE planning_detail_id = %s; 61 | """ 62 | await cursor.execute(query, (planning_detail_id,)) 63 | # Commit la transaction pour que les changements soient persistants 64 | await conn.commit() 65 | print( 66 | f"Le traitement (planning_detail_id: {planning_detail_id}) a été marqué comme 'Effectué' avec succès.") 67 | return True 68 | except Exception as e: 69 | print(f"Erreur lors de la mise à jour du statut du traitement {planning_detail_id}: {e}") 70 | # Rollback en cas d'erreur 71 | if conn: 72 | await conn.rollback() 73 | return False 74 | finally: 75 | if conn: 76 | pool.release(conn) 77 | 78 | 79 | async def abrogate_contract(pool, planning_detail_id: int, resignation_date: datetime.date): 80 | """ 81 | Abroge un contrat à partir d'une date de résiliation. 82 | Supprime les traitements futurs et marque le contrat comme 'Terminé'. 83 | Prend le pool de connexions en argument. 84 | """ 85 | conn = None 86 | try: 87 | conn = await pool.acquire() 88 | # 1. Récupérer les informations initiales pour obtenir planning_id et contrat_id 89 | # Passez le pool à get_planning_detail_info 90 | detail_info = await get_planning_detail_info(pool, planning_detail_id) 91 | if not detail_info: 92 | print(f"Impossible de trouver les informations pour planning_detail_id {planning_detail_id}.") 93 | return False 94 | 95 | current_planning_id = detail_info['planning_id'] 96 | current_contrat_id = detail_info['contrat_id'] 97 | 98 | async with conn.cursor() as cursor: 99 | # 2. Supprimer les traitements (PlanningDetails) futurs pour ce planning 100 | delete_query = """ 101 | DELETE 102 | FROM PlanningDetails 103 | WHERE planning_id = %s 104 | AND date_planification > %s; 105 | """ 106 | await cursor.execute(delete_query, (current_planning_id, resignation_date)) 107 | deleted_count = cursor.rowcount 108 | print( 109 | f"{deleted_count} traitements futurs (PlanningDetails) associés au planning {current_planning_id} ont été supprimés après le {resignation_date}.") 110 | 111 | # 3. Mettre à jour le statut du contrat 112 | update_contract_query = """ 113 | UPDATE Contrat 114 | SET statut_contrat = 'Terminé', -- 'Terminé' est une valeur valide pour l'ENUM 'statut_contrat' 115 | date_fin = %s, -- date_fin est de type VARCHAR(50) dans le schéma 116 | duree = 'Déterminée' -- 'Déterminée' est une valeur valide pour l'ENUM 'duree' 117 | WHERE contrat_id = %s; 118 | """ 119 | await cursor.execute(update_contract_query, (resignation_date, current_contrat_id)) 120 | 121 | # Commit la transaction pour que les changements soient persistants 122 | await conn.commit() 123 | print( 124 | f"Le contrat {current_contrat_id} a été marqué comme 'Terminé' avec date de fin {resignation_date} avec succès.") 125 | return True 126 | 127 | except Exception as e: 128 | print(f"Erreur lors de l'abrogation du contrat ou de la suppression des traitements: {e}") 129 | # Rollback en cas d'erreur 130 | if conn: 131 | await conn.rollback() 132 | return False 133 | finally: 134 | if conn: 135 | pool.release(conn) 136 | 137 | 138 | async def main_contract_management(): 139 | pool = None # Initialiser le pool à None 140 | try: 141 | pool = await DBConnection() # Appel de la fonction asynchrone pour obtenir le pool 142 | if not pool: # Si la création du pool a échoué 143 | print("Échec de la connexion à la base de données. Annulation de l'opération.") 144 | return 145 | 146 | print("\n--- Gestion des Contrats et Traitements ---") 147 | 148 | while True: 149 | try: 150 | planning_detail_id_input = input( 151 | "Veuillez entrer l'ID du détail de planification (planning_detail_id) concerné : ") 152 | planning_detail_id = int(planning_detail_id_input) 153 | 154 | # Passer le pool aux fonctions 155 | detail_info = await get_planning_detail_info(pool, planning_detail_id) 156 | if not detail_info: 157 | print( 158 | "L'ID du détail de planification spécifié n'existe pas ou une erreur est survenue. Veuillez réessayer.") 159 | continue 160 | 161 | print(f"\nInformations pour planning_detail_id {planning_detail_id}:") 162 | print(f" Client: {detail_info['client_nom']}") # Affichage du nom du client 163 | print(f" Type de traitement: {detail_info['typeTraitement']}") # Affichage du type de traitement 164 | print(f" Date de planification: {detail_info['date_planification']}") 165 | print(f" Statut actuel: {detail_info['statut']} (valeurs possibles: 'Effectué', 'À venir')") # Clarification des valeurs de l'ENUM 166 | print(f" Associé au planning_id: {detail_info['planning_id']}") 167 | print(f" Associé au contrat_id: {detail_info['contrat_id']}") 168 | 169 | 170 | print("\nOptions disponibles:") 171 | print("1. Abroger le contrat (supprime les traitements futurs et termine le contrat)") 172 | print("2. Marquer ce traitement comme 'Effectué'") 173 | print("3. Annuler") 174 | 175 | choice = input("Votre choix (1, 2 ou 3) : ") 176 | 177 | if choice == '1': 178 | while True: 179 | resignation_date_str = input("Veuillez entrer la date de résiliation (AAAA-MM-JJ) : ") 180 | try: 181 | resignation_date = datetime.datetime.strptime(resignation_date_str, "%Y-%m-%d").date() 182 | break 183 | except ValueError: 184 | print("Format de date invalide. Veuillez utiliser AAAA-MM-JJ.") 185 | 186 | print( 187 | f"\nTentative d'abrogation du contrat lié à planning_detail_id {planning_detail_id} à partir du {resignation_date}...") 188 | success = await abrogate_contract(pool, planning_detail_id, resignation_date) # Passer le pool 189 | if success: 190 | print("Opération d'abrogation terminée avec succès.") 191 | else: 192 | print("L'opération d'abrogation a échoué.") 193 | break 194 | 195 | elif choice == '2': 196 | print(f"\nTentative de marquer planning_detail_id {planning_detail_id} comme 'Effectué'...") 197 | success = await mark_treatment_as_performed(pool, planning_detail_id) # Passer le pool 198 | if success: 199 | print("Le traitement a été marqué comme 'Effectué' avec succès.") 200 | else: 201 | print("L'opération de marquage a échoué.") 202 | break 203 | 204 | elif choice == '3': 205 | print("Opération annulée.") 206 | break 207 | 208 | else: 209 | print("Choix invalide. Veuillez entrer 1, 2 ou 3.") 210 | 211 | except ValueError: 212 | print("Entrée invalide. Veuillez entrer un nombre pour l'ID du détail de planification.") 213 | except Exception as e: 214 | print(f"Une erreur inattendue est survenue: {e}") 215 | finally: 216 | if pool: 217 | pool.close() # Fermer le pool à la fin de l'exécution du script 218 | 219 | 220 | if __name__ == "__main__": 221 | asyncio.run(main_contract_management()) 222 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/Facture/modifMontantFacture.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import asyncio 3 | import aiomysql 4 | from Contrat.fonctionnalités.connexionDB import DBConnection 5 | 6 | 7 | # --- Fonctions utilitaires (réutilisées de vos scripts précédents) --- 8 | 9 | async def obtenirTousClients(pool): 10 | conn = None 11 | try: 12 | conn = await pool.acquire() 13 | async with conn.cursor(aiomysql.DictCursor) as cursor: 14 | query = """ 15 | SELECT client_id, CONCAT(nom, ' ', prenom) AS full_name 16 | FROM Client 17 | ORDER BY full_name; 18 | """ 19 | await cursor.execute(query) 20 | result = await cursor.fetchall() 21 | return result 22 | except Exception as e: 23 | print(f"Erreur lors de la récupération de la liste des clients : {e}") 24 | return [] 25 | finally: 26 | if conn: 27 | pool.release(conn) 28 | 29 | async def obtenirIDCLientsParNom(pool, client_name: str): 30 | conn = None 31 | try: 32 | conn = await pool.acquire() 33 | async with conn.cursor(aiomysql.DictCursor) as cursor: 34 | query = """ 35 | SELECT client_id 36 | FROM Client 37 | WHERE CONCAT(nom, ' ', prenom) = %s 38 | LIMIT 1; 39 | """ 40 | await cursor.execute(query, (client_name,)) 41 | result = await cursor.fetchone() 42 | return result['client_id'] if result else None 43 | except Exception as e: 44 | print(f"Erreur lors de la recherche du client par nom : {e}") 45 | return None 46 | finally: 47 | if conn: 48 | pool.release(conn) 49 | 50 | # --- Nouvelles fonctions spécifiques à la modification de facture --- 51 | 52 | async def obtentionRésuméFactureClient(pool, client_id: int): 53 | """Récupère un résumé des factures pour un client donné.""" 54 | conn = None 55 | try: 56 | conn = await pool.acquire() 57 | async with conn.cursor(aiomysql.DictCursor) as cursor: 58 | query = """ 59 | SELECT f.facture_id, 60 | f.date_traitement, 61 | f.montant, 62 | f.etat AS `Etat paiement`, 63 | tt.typeTraitement AS `Type de Traitement` 64 | FROM Facture f 65 | JOIN PlanningDetails pd ON f.planning_detail_id = pd.planning_detail_id 66 | JOIN Planning p ON pd.planning_id = p.planning_id 67 | JOIN Traitement tr ON p.traitement_id = tr.traitement_id 68 | JOIN TypeTraitement tt ON tr.id_type_traitement = tt.id_type_traitement 69 | JOIN Contrat co ON tr.contrat_id = co.contrat_id 70 | WHERE co.client_id = %s 71 | ORDER BY f.date_traitement DESC; 72 | """ 73 | await cursor.execute(query, (client_id,)) 74 | return await cursor.fetchall() 75 | except Exception as e: 76 | print(f"Erreur lors de la récupération des factures du client : {e}") 77 | return [] 78 | finally: 79 | if conn: 80 | pool.release(conn) 81 | 82 | async def obtentionRésuméMontantActuel(pool, facture_id: int): 83 | """Récupère le montant actuel d'une facture.""" 84 | conn = None 85 | try: 86 | conn = await pool.acquire() 87 | async with conn.cursor(aiomysql.DictCursor) as cursor: 88 | query = "SELECT montant FROM Facture WHERE facture_id = %s;" 89 | await cursor.execute(query, (facture_id,)) 90 | result = await cursor.fetchone() 91 | return result['montant'] if result else None 92 | except Exception as e: 93 | print(f"Erreur lors de la récupération du montant actuel de la facture {facture_id}: {e}") 94 | return None 95 | finally: 96 | if conn: 97 | pool.release(conn) 98 | 99 | async def majMontantEtHistorique(pool, facture_id: int, old_amount: float, new_amount: float, changed_by: str = 'System'): 100 | """ 101 | Met à jour le montant d'une facture et enregistre l'ancien/nouveau montant 102 | dans la table d'historique. 103 | """ 104 | conn = None 105 | try: 106 | conn = await pool.acquire() 107 | # Assurez-vous que les opérations sont atomiques (tout ou rien) 108 | async with conn.cursor() as cursor: 109 | # 1. Mettre à jour le montant dans la table Facture 110 | update_query = "UPDATE Facture SET montant = %s WHERE facture_id = %s;" 111 | await cursor.execute(update_query, (new_amount, facture_id)) 112 | 113 | # 2. Insérer l'entrée d'historique 114 | insert_history_query = """ 115 | INSERT INTO Historique_prix 116 | (facture_id, old_amount, new_amount, change_date, changed_by) 117 | VALUES (%s, %s, %s, %s, %s); 118 | """ 119 | await cursor.execute(insert_history_query, 120 | (facture_id, old_amount, new_amount, datetime.datetime.now(), changed_by)) 121 | 122 | await conn.commit() # Valider la transaction si autocommit n'est pas activé ou pour plus de clarté 123 | print(f"Montant de la facture {facture_id} mis à jour de {old_amount} à {new_amount}.") 124 | print("Historique de la modification enregistré.") 125 | return True 126 | except Exception as e: 127 | if conn: 128 | await conn.rollback() # Annuler la transaction en cas d'erreur 129 | print(f"Erreur lors de la modification de la facture et de l'enregistrement de l'historique : {e}") 130 | return False 131 | finally: 132 | if conn: 133 | pool.release(conn) 134 | 135 | # --- Fonction principale --- 136 | async def mainModificationMontant(): 137 | pool = None 138 | try: 139 | pool = await DBConnection() 140 | if not pool: 141 | print("Échec de la connexion à la base de données. Impossible de modifier la facture.") 142 | return 143 | 144 | print("\n--- Modification du montant de la facture ---") 145 | 146 | # 1. Sélectionner le client 147 | clients = await obtenirTousClients(pool) 148 | if not clients: 149 | print("Aucun client trouvé dans la base de données.") 150 | return 151 | 152 | print("\nClients disponibles :") 153 | client_map = {} 154 | for i, client in enumerate(clients): 155 | print(f"{i + 1}. {client['full_name']}") 156 | client_map[str(i + 1)] = client 157 | 158 | idClientChoisi = None 159 | nomCLientChoisi = None 160 | while idClientChoisi is None: 161 | choix = input( 162 | "\nVeuillez entrer le numéro du client dans la liste, ou son nom complet (Nom Prénom) : ").strip() 163 | if choix.isdigit(): 164 | if choix in client_map: 165 | selected_client = client_map[choix] 166 | idClientChoisi = selected_client['client_id'] 167 | nomCLientChoisi = selected_client['full_name'] 168 | print(f"Client sélectionné : {nomCLientChoisi}") 169 | else: 170 | print("Numéro invalide. Veuillez réessayer.") 171 | else: 172 | nomCLientChoisi = choix 173 | idClientChoisi = await obtenirIDCLientsParNom(pool, nomCLientChoisi) 174 | if idClientChoisi is None: 175 | print(f"Client '{nomCLientChoisi}' non trouvé. Veuillez vérifier le nom et réessayer.") 176 | else: 177 | print(f"Client trouvé : {nomCLientChoisi}") 178 | 179 | # 2. Récupérer et afficher les factures du client 180 | invoices = await obtentionRésuméFactureClient(pool, idClientChoisi) 181 | if not invoices: 182 | print(f"Aucune facture trouvée pour le client '{nomCLientChoisi}'.") 183 | return 184 | 185 | print(f"\nFactures de {nomCLientChoisi} :") 186 | invoice_map = {} 187 | for i, inv in enumerate(invoices): 188 | print(f"{i + 1}. ID Facture: {inv['facture_id']}, Date: {inv['date_traitement'].strftime('%Y-%m-%d')}, " 189 | f"Type: {inv['Type de Traitement']}, Montant Actuel: {inv['montant']:.2f} {inv['Etat paiement']}") 190 | invoice_map[str(i + 1)] = inv['facture_id'] 191 | 192 | # 3. Sélectionner la facture à modifier 193 | selected_facture_id = None 194 | while selected_facture_id is None: 195 | invoice_choice = input("\nVeuillez entrer le numéro de la facture à modifier : ").strip() 196 | if invoice_choice.isdigit() and invoice_choice in invoice_map: 197 | selected_facture_id = invoice_map[invoice_choice] 198 | else: 199 | print("Numéro de facture invalide. Veuillez réessayer.") 200 | 201 | # 4. Récupérer le montant actuel de la facture sélectionnée 202 | old_amount = await obtentionRésuméMontantActuel(pool, selected_facture_id) 203 | if old_amount is None: 204 | print(f"Impossible de récupérer le montant actuel pour la facture ID {selected_facture_id}.") 205 | return 206 | 207 | print(f"\nMontant actuel de la facture ID {selected_facture_id}: {old_amount:.2f}") 208 | 209 | # 5. Demander le nouveau montant 210 | new_amount = None 211 | while new_amount is None: 212 | try: 213 | new_amount_input = input(f"Entrez le nouveau montant pour la facture ID {selected_facture_id} : ").strip() 214 | new_amount = float(new_amount_input) 215 | if new_amount < 0: 216 | print("Le montant ne peut pas être négatif.") 217 | new_amount = None # Reset to re-enter loop 218 | except ValueError: 219 | print("Montant invalide. Veuillez entrer un nombre.") 220 | 221 | if new_amount == old_amount: 222 | print("Le nouveau montant est identique à l'ancien. Aucune modification nécessaire.") 223 | return 224 | 225 | # 6. Confirmation 226 | confirm = input( 227 | f"Confirmez-vous la modification du montant de la facture {selected_facture_id} de {old_amount:.2f} à {new_amount:.2f} ? (oui/non) : ").lower().strip() 228 | 229 | if confirm == 'oui': 230 | success = await majMontantEtHistorique(pool, selected_facture_id, old_amount, new_amount) 231 | if success: 232 | print("Modification effectuée avec succès.") 233 | else: 234 | print("La modification a échoué.") 235 | else: 236 | print("Modification annulée.") 237 | 238 | except Exception as e: 239 | print(f"Une erreur inattendue est survenue : {e}") 240 | finally: 241 | if pool: 242 | await pool.close() 243 | 244 | if __name__ == "__main__": 245 | asyncio.run(mainModificationMontant()) -------------------------------------------------------------------------------- /Contrat/CRUDonHistorique.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | from datetime import date 3 | 4 | 5 | async def create_historique(pool, traitement_id: int, contenu: str, date_traitement: date | None = None) -> int | None: 6 | """ 7 | Crée un historique pour un traitement donné avec gestion de transaction. 8 | 9 | Args: 10 | pool: Le pool de connexions aiomysql. 11 | traitement_id (int): L'ID du traitement associé. 12 | contenu (str): Le contenu de l'historique. 13 | date_traitement (date | None): La date du traitement. Par défaut, utilise la date d'aujourd'hui. 14 | 15 | Returns: 16 | int | None: L'ID de l'historique nouvellement créé, ou None en cas d'échec. 17 | """ 18 | conn = None 19 | try: 20 | conn = await pool.acquire() 21 | await conn.begin() # Début de la transaction 22 | async with conn.cursor() as cur: 23 | if date_traitement is None: 24 | date_traitement = date.today() 25 | await cur.execute( 26 | "INSERT INTO Historique (traitement_id, contenu, date_traitement) VALUES (%s, %s, %s)", 27 | (traitement_id, contenu, date_traitement) 28 | ) 29 | await conn.commit() # Validation de la transaction 30 | return cur.lastrowid 31 | except Exception as e: 32 | if conn: 33 | await conn.rollback() # Annulation de la transaction en cas d'erreur 34 | print(f"Erreur lors de la création de l'historique : {e}") 35 | return None 36 | finally: 37 | if conn: 38 | pool.release(conn) 39 | 40 | 41 | async def read_historique(pool, historique_id: int) -> dict | None: 42 | """ 43 | Lit un historique à partir de son ID. 44 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 45 | 46 | Args: 47 | pool: Le pool de connexions aiomysql. 48 | historique_id (int): L'ID de l'historique à lire. 49 | 50 | Returns: 51 | dict | None: Un dictionnaire contenant les informations de l'historique, ou None si non trouvé ou en cas d'erreur. 52 | """ 53 | conn = None 54 | try: 55 | conn = await pool.acquire() 56 | async with conn.cursor(aiomysql.DictCursor) as cur: 57 | await cur.execute("SELECT * FROM Historique WHERE historique_id = %s", (historique_id,)) 58 | return await cur.fetchone() 59 | except Exception as e: 60 | print(f"Erreur lors de la lecture de l'historique (ID: {historique_id}) : {e}") 61 | return None 62 | finally: 63 | if conn: 64 | pool.release(conn) 65 | 66 | 67 | async def update_historique(pool, historique_id: int, contenu: str, date_traitement: date) -> int: 68 | """ 69 | Modifie un historique existant avec gestion de transaction. 70 | 71 | Args: 72 | pool: Le pool de connexions aiomysql. 73 | historique_id (int): L'ID de l'historique à modifier. 74 | contenu (str): Le nouveau contenu de l'historique. 75 | date_traitement (date): La nouvelle date de traitement. 76 | 77 | Returns: 78 | int: Le nombre de lignes affectées (1 si succès, 0 sinon). 79 | """ 80 | conn = None 81 | try: 82 | conn = await pool.acquire() 83 | await conn.begin() # Début de la transaction 84 | async with conn.cursor() as cur: 85 | await cur.execute( 86 | "UPDATE Historique SET contenu = %s, date_traitement = %s WHERE historique_id = %s", 87 | (contenu, date_traitement, historique_id) 88 | ) 89 | await conn.commit() # Validation de la transaction 90 | return cur.rowcount 91 | except Exception as e: 92 | if conn: 93 | await conn.rollback() # Annulation de la transaction en cas d'erreur 94 | print(f"Erreur lors de la modification de l'historique (ID: {historique_id}) : {e}") 95 | return 0 96 | finally: 97 | if conn: 98 | pool.release(conn) 99 | 100 | 101 | async def delete_historique(pool, historique_id: int) -> int: 102 | """ 103 | Supprime un historique à partir de son ID avec gestion de transaction. 104 | 105 | Args: 106 | pool: Le pool de connexions aiomysql. 107 | historique_id (int): L'ID de l'historique à supprimer. 108 | 109 | Returns: 110 | int: Le nombre de lignes supprimées (1 si succès, 0 sinon). 111 | """ 112 | conn = None 113 | try: 114 | conn = await pool.acquire() 115 | await conn.begin() # Début de la transaction 116 | async with conn.cursor() as cur: 117 | await cur.execute("DELETE FROM Historique WHERE historique_id = %s", (historique_id,)) 118 | await conn.commit() # Validation de la transaction 119 | return cur.rowcount 120 | except Exception as e: 121 | if conn: 122 | await conn.rollback() # Annulation de la transaction en cas d'erreur 123 | print(f"Erreur lors de la suppression de l'historique (ID: {historique_id}) : {e}") 124 | return 0 125 | finally: 126 | if conn: 127 | pool.release(conn) 128 | 129 | 130 | async def get_historique_for_traitement(pool, traitement_id: int) -> list[dict]: 131 | """ 132 | Récupère l'historique d'un traitement spécifique. 133 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 134 | 135 | Args: 136 | pool: Le pool de connexions aiomysql. 137 | traitement_id (int): L'ID du traitement. 138 | 139 | Returns: 140 | list[dict]: Une liste de dictionnaires, chaque dictionnaire représentant un historique. 141 | """ 142 | conn = None 143 | try: 144 | conn = await pool.acquire() 145 | async with conn.cursor(aiomysql.DictCursor) as cur: 146 | await cur.execute("SELECT * FROM Historique WHERE traitement_id = %s ORDER BY date_traitement DESC", 147 | (traitement_id,)) 148 | return await cur.fetchall() 149 | except Exception as e: 150 | print(f"Erreur lors de la récupération de l'historique pour le traitement ID {traitement_id} : {e}") 151 | return [] 152 | finally: 153 | if conn: 154 | pool.release(conn) 155 | 156 | 157 | async def create_historique_for_planning(pool, planning_id: int, contenu_specifique: str | None = None) -> bool: 158 | """ 159 | Crée automatiquement un historique pour le traitement associé à un planning donné. 160 | Le contenu peut être spécifié ou généré par défaut. 161 | Cette fonction appelle 'create_historique' qui gère déjà sa propre transaction. 162 | 163 | Args: 164 | pool: Le pool de connexions aiomysql. 165 | planning_id (int): L'ID du planning. 166 | contenu_specifique (str | None): Le contenu à insérer. Si None, un contenu par défaut est généré. 167 | 168 | Returns: 169 | bool: True si l'historique a été créé avec succès, False sinon. 170 | """ 171 | conn = None 172 | try: 173 | conn = await pool.acquire() 174 | async with conn.cursor(aiomysql.DictCursor) as cur: 175 | await cur.execute("SELECT traitement_id FROM Planning WHERE planning_id = %s", (planning_id,)) 176 | result = await cur.fetchone() 177 | if result: 178 | traitement_id = result['traitement_id'] 179 | 180 | if contenu_specifique is None: 181 | contenu = f"Historique généré automatiquement pour la planification du traitement {traitement_id}." 182 | else: 183 | contenu = contenu_specifique 184 | 185 | success = await create_historique(pool, traitement_id, contenu) 186 | if success: 187 | print(f"Historique créé pour le traitement {traitement_id} lié au planning {planning_id}.") 188 | return True 189 | else: 190 | print(f"Échec de la création de l'historique pour le traitement {traitement_id} lié au planning {planning_id}.") 191 | return False 192 | else: 193 | print(f"Aucun traitement trouvé pour le planning ID {planning_id}.") 194 | return False 195 | except Exception as e: 196 | print(f"Erreur lors de la création automatique de l'historique pour le planning {planning_id} : {e}") 197 | return False 198 | finally: 199 | if conn: 200 | pool.release(conn) 201 | 202 | 203 | async def afficher_historique_client(pool, client_id: int) -> None: 204 | """ 205 | Affiche l'historique de tous les traitements d'un client. 206 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 207 | 208 | Args: 209 | pool: Le pool de connexions aiomysql. 210 | client_id (int): L'ID du client. 211 | """ 212 | conn = None 213 | try: 214 | conn = await pool.acquire() 215 | async with conn.cursor(aiomysql.DictCursor) as cur: 216 | await cur.execute(""" 217 | SELECT h.historique_id, 218 | h.traitement_id, 219 | h.contenu, 220 | h.date_traitement, 221 | t.id_type_traitement, 222 | tt.typeTraitement AS nom_type_traitement, 223 | c.contrat_id, 224 | cl.nom AS nom_client, 225 | cl.prenom AS prenom_client 226 | FROM Historique h 227 | JOIN Traitement t ON h.traitement_id = t.traitement_id 228 | JOIN Contrat c ON t.contrat_id = c.contrat_id 229 | JOIN Client cl ON c.client_id = cl.client_id 230 | JOIN TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement 231 | WHERE c.client_id = %s 232 | ORDER BY h.date_traitement DESC 233 | """, (client_id,)) 234 | historiques = await cur.fetchall() 235 | 236 | if not historiques: 237 | print(f"Aucun historique trouvé pour le client ID {client_id}.") 238 | return 239 | 240 | print( 241 | f"\n--- Historique des traitements pour le client ID {client_id} ({historiques[0]['nom_client']} {historiques[0]['prenom_client']}) ---") 242 | for historique in historiques: 243 | print("-" * 30) 244 | print(f"ID Historique: {historique['historique_id']}") 245 | print(f"ID Traitement: {historique['traitement_id']} (Type: {historique['nom_type_traitement']})") 246 | print(f"Contenu: {historique['contenu']}") 247 | print(f"Date Traitement: {historique['date_traitement']}") 248 | print(f"ID Contrat: {historique['contrat_id']}") 249 | print("-" * 30) 250 | 251 | except Exception as e: 252 | print(f"Erreur lors de l'affichage de l'historique du client ID {client_id} : {e}") 253 | finally: 254 | if conn: 255 | pool.release(conn) 256 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/Planning/ajoutPlanningTraitement.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | import asyncio 3 | from datetime import datetime, timedelta 4 | 5 | async def get_db_credentials(): 6 | """Demande à l'utilisateur les identifiants de connexion à la base de données.""" 7 | print("\nVeuillez entrer les informations de connexion à la base de données MySQL:") 8 | host = input("Entrez l'adresse du serveur MySQL (par défaut, localhost): ").strip() 9 | if not host: 10 | host = "localhost" 11 | 12 | port_str = input("Entrez le port du serveur MySQL (par défaut: 3306): ").strip() 13 | try: 14 | port = int(port_str) if port_str else 3306 15 | except ValueError: 16 | print("Port invalide, utilisation de la valeur par défaut (3306).") 17 | port = 3306 18 | 19 | user = input("Entrez le nom d'utilisateur MySQL: ").strip() 20 | password = input("Entrez le mot de passe MySQL: ").strip() 21 | database = input("Entrez le nom de la base de données MySQL (par défaut, Planificator): ").strip() 22 | if not database: 23 | database = "Planificator" 24 | return host, port, user, password, database 25 | 26 | async def get_public_holidays(pool, year: int) -> set[datetime.date]: 27 | """ 28 | Récupère les dates des jours fériés pour une année donnée depuis la base de données. 29 | Nécessite une table `JoursFeries` avec une colonne `date_ferie` de type DATE. 30 | """ 31 | public_holidays = set() 32 | try: 33 | async with pool.acquire() as conn: 34 | async with conn.cursor(aiomysql.DictCursor) as cur: 35 | # Assuming you have a table named JoursFeries with a column date_ferie 36 | await cur.execute("SELECT date_ferie FROM JoursFeries WHERE YEAR(date_ferie) = %s", (year,)) 37 | results = await cur.fetchall() 38 | for row in results: 39 | if 'date_ferie' in row and isinstance(row['date_ferie'], datetime): 40 | public_holidays.add(row['date_ferie'].date()) 41 | elif 'date_ferie' in row and isinstance(row['date_ferie'], datetime.date): 42 | public_holidays.add(row['date_ferie']) 43 | except Exception as e: 44 | print(f"**Avertissement:** Erreur lors de la récupération des jours fériés: {e}. Les jours fériés ne seront pas pris en compte.") 45 | return public_holidays 46 | 47 | def is_holiday_or_sunday(date_to_check: datetime.date, public_holidays: set[datetime.date]) -> bool: 48 | """ 49 | Vérifie si une date donnée est un dimanche ou un jour férié. 50 | """ 51 | # date.weekday() returns 6 for Sunday 52 | if date_to_check.weekday() == 6: 53 | return True 54 | if date_to_check in public_holidays: 55 | return True 56 | return False 57 | 58 | async def ajouter_planning_traitement(pool, traitement_id: int, redondance_value: int, redondance_unit: str, date_debut_planification: datetime.date): 59 | """ 60 | Ajoute un planning pour un traitement donné en générant des détails de planification. 61 | Prend en compte les jours fériés et les dimanches. 62 | """ 63 | conn = None 64 | try: 65 | async with pool.acquire() as conn: 66 | async with conn.cursor(aiomysql.DictCursor) as cur: 67 | # Vérifier si le traitement existe 68 | await cur.execute("SELECT traitement_id FROM Traitement WHERE traitement_id = %s", (traitement_id,)) 69 | traitement_exists = await cur.fetchone() 70 | 71 | if not traitement_exists: 72 | print(f"**Erreur:** Traitement avec l'ID {traitement_id} non trouvé. Opération annulée.") 73 | return 74 | 75 | # Définir une date de fin (par exemple, 1 an à partir de la date de début) 76 | date_fin_planification = date_debut_planification + timedelta(days=365) 77 | print(f"Planification générée du {date_debut_planification} au {date_fin_planification}.") 78 | 79 | # Insérer le planning principal 80 | await cur.execute(""" 81 | INSERT INTO Planning (traitement_id, redondance, date_debut_planification, date_fin_planification) 82 | VALUES (%s, %s, %s, %s) 83 | """, (traitement_id, f"{redondance_value} {redondance_unit}", date_debut_planification, date_fin_planification)) 84 | 85 | planning_id = cur.lastrowid 86 | if not planning_id: 87 | print("**Erreur:** Impossible d'obtenir le planning_id après insertion. Opération annulée.") 88 | return 89 | 90 | print(f"Planning principal créé avec ID: {planning_id}") 91 | 92 | # Récupérer les jours fériés pour l'année en cours et l'année prochaine si la période s'étend sur deux ans 93 | public_holidays_current_year = await get_public_holidays(pool, date_debut_planification.year) 94 | public_holidays_next_year = await get_public_holidays(pool, date_fin_planification.year) 95 | all_public_holidays = public_holidays_current_year.union(public_holidays_next_year) 96 | 97 | 98 | # Calculer et insérer les détails de la planification 99 | current_date = date_debut_planification 100 | details_added_count = 0 101 | 102 | while current_date <= date_fin_planification: 103 | # Ajuster la date si c'est un dimanche ou un jour férié 104 | while is_holiday_or_sunday(current_date, all_public_holidays): 105 | print(f"La date {current_date} est un dimanche ou un jour férié. Déplacement au jour suivant.") 106 | current_date += timedelta(days=1) 107 | 108 | # Insérer le détail de planification uniquement si la date est dans la période et validée 109 | if current_date <= date_fin_planification: 110 | await cur.execute(""" 111 | INSERT INTO PlanningDetails (planning_id, date_planification, statut, element_planification) 112 | VALUES (%s, %s, 'À venir', 'Traitement') 113 | """, (planning_id, current_date)) 114 | details_added_count += 1 115 | else: 116 | # Si la date ajustée dépasse la date de fin, ne pas l'ajouter 117 | break 118 | 119 | 120 | # Calculer la prochaine date de planification 121 | if redondance_unit == 'jours': 122 | current_date += timedelta(days=redondance_value) 123 | elif redondance_unit == 'semaines': 124 | current_date += timedelta(weeks=redondance_value) 125 | elif redondance_unit == 'mois': 126 | try: 127 | new_month = current_date.month + redondance_value 128 | new_year = current_date.year + (new_month - 1) // 12 129 | new_month = (new_month - 1) % 12 + 1 130 | current_date = current_date.replace(year=new_year, month=new_month) 131 | except ValueError: 132 | current_date = (current_date.replace(day=1) + 133 | timedelta(days=32 * redondance_value)).replace(day=1) - timedelta(days=1) 134 | elif redondance_unit == 'ans': 135 | current_date = current_date.replace(year=current_date.year + redondance_value) 136 | else: 137 | print(f"**Avertissement:** Unité de redondance '{redondance_unit}' non valide. Arrêt de la planification.") 138 | break 139 | 140 | print(f"{details_added_count} détails de planification ajoutés pour le traitement {traitement_id}.") 141 | 142 | except Exception as e: 143 | print(f"**Erreur lors de l'ajout du planning :** {e}") 144 | 145 | async def main(): 146 | pool = None 147 | try: 148 | # Get DB credentials 149 | host, port, user, password, database = await get_db_credentials() 150 | 151 | # Create connection pool 152 | print(f"\nTentative de connexion à la base de données '{database}' sur {host}:{port} avec l'utilisateur '{user}'...") 153 | pool = await aiomysql.create_pool( 154 | host=host, 155 | port=port, 156 | user=user, 157 | password=password, 158 | db=database, 159 | autocommit=True, 160 | minsize=1, 161 | maxsize=5 162 | ) 163 | print("Connexion à la base de données établie avec succès.") 164 | 165 | while True: 166 | print("\n--- Ajout d'un nouveau planning de traitement ---") 167 | traitement_id_input = input("Entrez l'ID du traitement (laissez vide pour quitter) : ").strip() 168 | if not traitement_id_input: 169 | break 170 | 171 | try: 172 | traitement_id = int(traitement_id_input) 173 | except ValueError: 174 | print("**Erreur :** L'ID du traitement doit être un nombre entier.") 175 | continue 176 | 177 | redondance_value_input = input("Entrez la valeur de la redondance (ex: 1 pour chaque mois, 2 pour chaque 2 semaines) : ").strip() 178 | if not redondance_value_input: 179 | break 180 | try: 181 | redondance_value = int(redondance_value_input) 182 | if redondance_value <= 0: 183 | print("**Erreur :** La valeur de redondance doit être un nombre positif.") 184 | continue 185 | except ValueError: 186 | print("**Erreur :** La valeur de redondance doit être un nombre entier.") 187 | continue 188 | 189 | redondance_unit_input = input("Entrez l'unité de la redondance (jours, semaines, mois, ans) : ").strip().lower() 190 | if redondance_unit_input not in ['jours', 'semaines', 'mois', 'ans']: 191 | print("**Erreur :** Unité de redondance non valide. Veuillez choisir parmi 'jours', 'semaines', 'mois', 'ans'.") 192 | continue 193 | 194 | date_debut_planification_str = input("Date de début de la planification (YYYY-MM-DD) : ").strip() 195 | if not date_debut_planification_str: 196 | break 197 | try: 198 | date_debut_planification = datetime.strptime(date_debut_planification_str, '%Y-%m-%d').date() 199 | except ValueError: 200 | print("**Erreur :** Format de date invalide. Utilisez le format YYYY-MM-DD.") 201 | continue 202 | 203 | await ajouter_planning_traitement(pool, traitement_id, redondance_value, redondance_unit_input, date_debut_planification) 204 | 205 | continuer = input("Voulez-vous ajouter un autre planning ? (oui/non) : ").strip().lower() 206 | if continuer != 'oui': 207 | break 208 | 209 | except Exception as e: 210 | print(f"**Une erreur inattendue est survenue dans le script principal :** {e}") 211 | finally: 212 | if pool: 213 | print("\nFermeture du pool de connexions à la base de données...") 214 | pool.close() 215 | await pool.wait_closed() 216 | print("Pool de connexions fermé.") 217 | 218 | if __name__ == "__main__": 219 | asyncio.run(main()) -------------------------------------------------------------------------------- /Contrat/CRUDonContrat.py: -------------------------------------------------------------------------------- 1 | import aiomysql 2 | from datetime import date 3 | 4 | async def create_contrat(pool, client_id: int, date_contrat: date, date_debut: date, date_fin_contrat: str | None, duree: str, categorie: str, duree_contrat: int | None = None) -> int | None: 5 | """ 6 | Crée un nouveau contrat dans la base de données avec gestion de transaction. 7 | 8 | Args: 9 | pool: Le pool de connexions aiomysql. 10 | client_id (int): L'ID du client associé au contrat. 11 | date_contrat (date): La date de signature du contrat. 12 | date_debut (date): La date de début du contrat. 13 | date_fin_contrat (str | None): La date de fin du contrat sous forme de chaîne (peut être None si durée indéterminée). 14 | Doit être au format 'YYYY-MM-DD' ou une description textuelle. 15 | duree (str): La durée du contrat ('Indeterminee' ou 'Déterminée'). 16 | categorie (str): La catégorie du contrat ('Nouveau' ou 'Renouvellement'). 17 | duree_contrat (int | None): La durée du contrat en mois/années (INT dans la DB), ou None. 18 | 19 | Returns: 20 | int | None: L'ID du contrat nouvellement créé, ou None en cas d'échec. 21 | """ 22 | conn = None 23 | try: 24 | conn = await pool.acquire() 25 | await conn.begin() # Début de la transaction 26 | async with conn.cursor() as cur: 27 | # Vérifier si duree est 'Indeterminée' et ajuster date_fin_contrat et duree_contrat 28 | if duree == 'Indeterminée': 29 | date_fin_contrat_db = None 30 | duree_contrat_db = None 31 | else: 32 | date_fin_contrat_db = date_fin_contrat 33 | duree_contrat_db = duree_contrat 34 | 35 | await cur.execute(""" 36 | INSERT INTO Contrat (client_id, date_contrat, date_debut, date_fin, duree, categorie, duree_contrat) 37 | VALUES (%s, %s, %s, %s, %s, %s, %s) 38 | """, (client_id, date_contrat, date_debut, date_fin_contrat_db, duree, categorie, duree_contrat_db)) 39 | await conn.commit() # Validation de la transaction 40 | return cur.lastrowid 41 | except Exception as e: 42 | if conn: 43 | await conn.rollback() # Annulation de la transaction en cas d'erreur 44 | print(f"Erreur lors de la création du contrat pour client {client_id}: {e}") 45 | return None 46 | finally: 47 | if conn: 48 | pool.release(conn) 49 | 50 | async def read_contrat(pool, contrat_id: int | None = None) -> list[dict] | dict | None: 51 | """ 52 | Lit les détails d'un contrat spécifique à partir de son ID ou tous les contrats. 53 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 54 | 55 | Args: 56 | pool: Le pool de connexions aiomysql. 57 | contrat_id (int | None): L'ID du contrat à lire (optionnel). 58 | 59 | Returns: 60 | list[dict] | dict | None: Une liste de dictionnaires pour tous les contrats, 61 | un seul dictionnaire pour un contrat spécifique, 62 | ou None en cas d'erreur. 63 | """ 64 | conn = None 65 | try: 66 | conn = await pool.acquire() 67 | async with conn.cursor(aiomysql.DictCursor) as cur: # Utilisation de DictCursor 68 | if contrat_id is None: 69 | await cur.execute(""" 70 | SELECT 71 | c.contrat_id, 72 | c.client_id, 73 | cl.nom AS nom_client, 74 | cl.prenom AS prenom_client, 75 | cl.email AS email_client, 76 | c.reference_contrat, 77 | c.date_contrat, 78 | c.date_debut, 79 | c.date_fin, 80 | c.statut_contrat, 81 | c.duree, 82 | c.categorie, 83 | c.duree_contrat 84 | FROM Contrat c 85 | JOIN Client cl ON c.client_id = cl.client_id 86 | """) 87 | return await cur.fetchall() 88 | else: 89 | await cur.execute(""" 90 | SELECT 91 | c.contrat_id, 92 | c.client_id, 93 | cl.nom AS nom_client, 94 | cl.prenom AS prenom_client, 95 | cl.email AS email_client, 96 | c.reference_contrat, 97 | c.date_contrat, 98 | c.date_debut, 99 | c.date_fin, 100 | c.statut_contrat, 101 | c.duree, 102 | c.categorie, 103 | c.duree_contrat 104 | FROM Contrat c 105 | JOIN Client cl ON c.client_id = cl.client_id 106 | WHERE c.contrat_id = %s 107 | """, (contrat_id,)) 108 | return await cur.fetchone() 109 | except Exception as e: 110 | print(f"Erreur lors de la lecture du contrat (ID: {contrat_id}) : {e}") 111 | return None if contrat_id is not None else [] 112 | finally: 113 | if conn: 114 | pool.release(conn) 115 | 116 | async def update_contrat(pool, contrat_id: int, client_id: int, date_contrat: date, date_debut: date, date_fin_contrat: str | None, duree: str, categorie: str, duree_contrat: int | None = None) -> int: 117 | """ 118 | Modifie un contrat existant dans la base de données avec gestion de transaction. 119 | 120 | Args: 121 | pool: Le pool de connexions aiomysql. 122 | contrat_id (int): L'ID du contrat à modifier. 123 | client_id (int): Le nouvel ID du client. 124 | date_contrat (date): La nouvelle date de signature. 125 | date_debut (date): La nouvelle date de début. 126 | date_fin_contrat (str | None): La nouvelle date de fin sous forme de chaîne (peut être None). 127 | Doit être au format 'YYYY-MM-DD' ou une description textuelle. 128 | duree (str): La nouvelle durée ('Indeterminee' ou 'Déterminée'). 129 | categorie (str): La nouvelle catégorie ('Nouveau' ou 'Renouvellement'). 130 | duree_contrat (int | None): La nouvelle durée du contrat en mois/années (INT dans la DB), ou None. 131 | 132 | Returns: 133 | int: Le nombre de lignes affectées (1 en cas de succès, 0 sinon ou en cas d'erreur). 134 | """ 135 | conn = None 136 | try: 137 | conn = await pool.acquire() 138 | await conn.begin() # Début de la transaction 139 | async with conn.cursor() as cur: 140 | # Ajuster date_fin_contrat et duree_contrat en fonction de la valeur de 'duree' 141 | if duree == 'Indeterminée': 142 | date_fin_contrat_db = None 143 | duree_contrat_db = None 144 | else: 145 | date_fin_contrat_db = date_fin_contrat 146 | duree_contrat_db = duree_contrat 147 | 148 | await cur.execute(""" 149 | UPDATE Contrat SET client_id = %s, date_contrat = %s, date_debut = %s, date_fin = %s, 150 | duree = %s, categorie = %s, duree_contrat = %s 151 | WHERE contrat_id = %s 152 | """, (client_id, date_contrat, date_debut, date_fin_contrat_db, duree, categorie, duree_contrat_db, contrat_id)) 153 | await conn.commit() # Validation de la transaction 154 | return cur.rowcount # Retourne 1 si mise à jour, 0 si contrat non trouvé 155 | except Exception as e: 156 | if conn: 157 | await conn.rollback() # Annulation de la transaction en cas d'erreur 158 | print(f"Erreur lors de la modification du contrat (ID: {contrat_id}) : {e}") 159 | return 0 160 | finally: 161 | if conn: 162 | pool.release(conn) 163 | 164 | async def delete_contrat(pool, contrat_id: int) -> int: 165 | """ 166 | Supprime un contrat de la base de données avec gestion de transaction. 167 | 168 | Args: 169 | pool: Le pool de connexions aiomysql. 170 | contrat_id (int): L'ID du contrat à supprimer. 171 | 172 | Returns: 173 | int: Le nombre de lignes supprimées (1 en cas de succès, 0 sinon ou en cas d'erreur). 174 | """ 175 | conn = None 176 | try: 177 | conn = await pool.acquire() 178 | await conn.begin() # Début de la transaction 179 | async with conn.cursor() as cur: 180 | await cur.execute("DELETE FROM Contrat WHERE contrat_id = %s", (contrat_id,)) 181 | await conn.commit() # Validation de la transaction 182 | return cur.rowcount # Retourne 1 si supprimé, 0 si contrat non trouvé 183 | except Exception as e: 184 | if conn: 185 | await conn.rollback() # Annulation de la transaction en cas d'erreur 186 | print(f"Erreur lors de la suppression du contrat (ID: {contrat_id}) : {e}") 187 | return 0 188 | finally: 189 | if conn: 190 | pool.release(conn) 191 | 192 | async def obtenir_duree_contrat(pool, contrat_id: int) -> str | None: 193 | """ 194 | Récupère la durée principale du contrat ('Indeterminee', 'Déterminée'). 195 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 196 | 197 | Args: 198 | pool: Le pool de connexions aiomysql. 199 | contrat_id (int): L'ID du contrat. 200 | 201 | Returns: 202 | str | None: La valeur de la colonne 'duree' du contrat, ou None si non trouvé ou en cas d'erreur. 203 | """ 204 | conn = None 205 | try: 206 | conn = await pool.acquire() 207 | async with conn.cursor(aiomysql.DictCursor) as cursor: # Utilisation de DictCursor 208 | await cursor.execute("SELECT duree FROM Contrat WHERE contrat_id = %s", (contrat_id,)) 209 | resultat = await cursor.fetchone() 210 | if resultat: 211 | return resultat['duree'] # Accès par clé de dictionnaire 212 | else: 213 | return None 214 | except Exception as e: 215 | print(f"Erreur lors de la récupération de la durée du contrat (ID: {contrat_id}) : {e}") 216 | return None 217 | finally: 218 | if conn: 219 | pool.release(conn) 220 | 221 | async def obtenir_axe_client_par_contrat(pool, contrat_id: int) -> str | None: 222 | """ 223 | Récupère l'axe géographique associé au client d'un contrat donné. 224 | Cette fonction ne modifie pas la base de données, donc pas de transaction explicite. 225 | 226 | Args: 227 | pool: Le pool de connexions aiomysql. 228 | contrat_id (int): L'ID du contrat. 229 | 230 | Returns: 231 | str | None: La valeur de l'axe du client, ou None si non trouvé ou en cas d'erreur. 232 | """ 233 | conn = None 234 | try: 235 | conn = await pool.acquire() 236 | async with conn.cursor(aiomysql.DictCursor) as cursor: # Utilisation de DictCursor 237 | await cursor.execute(""" 238 | SELECT cl.axe 239 | FROM Contrat c 240 | JOIN Client cl ON c.client_id = cl.client_id 241 | WHERE c.contrat_id = %s 242 | """, (contrat_id,)) 243 | resultat = await cursor.fetchone() 244 | if resultat: 245 | return resultat['axe'] # Accès par clé de dictionnaire 246 | else: 247 | return None 248 | except Exception as e: 249 | print(f"Erreur lors de la récupération de l'axe du client pour contrat (ID: {contrat_id}) : {e}") 250 | return None 251 | finally: 252 | if conn: 253 | pool.release(conn) 254 | -------------------------------------------------------------------------------- /scriptSQL/Planificator.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Auteur: Josoa (josoavj sur GitHub) 3 | Ce script est la source de la base de données du projet Planificator 4 | Veuillez vous réferer à la documentation ou envoyer un mail à l'auteur si vous avez besoin d'aide 5 | */ 6 | 7 | -- Supprimer complètement la base de données 8 | DROP DATABASE IF EXISTS Planificator; 9 | 10 | -- Création de la DB 11 | CREATE DATABASE Planificator; 12 | USE Planificator; 13 | 14 | -- Table Compte 15 | CREATE TABLE Account ( 16 | id_compte INT AUTO_INCREMENT PRIMARY KEY, -- Identifiant unique pour chaque compte 17 | nom VARCHAR(70) NOT NULL, 18 | prenom VARCHAR(70) NOT NULL, 19 | email VARCHAR(100) NOT NULL UNIQUE, 20 | username VARCHAR(50) NOT NULL UNIQUE, 21 | password VARCHAR(255) NOT NULL, 22 | type_compte ENUM('Administrateur', 'Utilisateur') NOT NULL, -- Type de compte 23 | date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Date de création du compte 24 | UNIQUE (nom, prenom) -- Contrainte d'unicité sur le nom et prénom 25 | ); 26 | 27 | /* 28 | Pour la table Account: 29 | - Il est recommandé d'utiliser un mot de passe crypté: veuillez crypter votre mot de passe en fonction du techno ou langage utilisé 30 | - Le mot de passe ne doit pas contenir des informations sensibles (Informations personnelles) 31 | - Un seul compte Administrateur est requis. 32 | - Seul l'administrateur qui possède le droit de supprimer des comptes dans la base de données. 33 | */ 34 | 35 | -- Compte administrateur unique 36 | DELIMITER $$ 37 | 38 | CREATE TRIGGER avant_ajout_compte 39 | BEFORE INSERT ON Account 40 | FOR EACH ROW 41 | BEGIN 42 | IF NEW.type_compte = 'Administrateur' AND (SELECT COUNT(*) FROM Account WHERE type_compte = 'Administrateur') > 0 THEN 43 | SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Un compte Administrateur existe déjà.'; 44 | END IF; 45 | END$$ 46 | 47 | DELIMITER ; 48 | 49 | -- Vérification du mail 50 | DELIMITER $$ 51 | 52 | CREATE TRIGGER ajout_compte 53 | BEFORE INSERT ON Account 54 | FOR EACH ROW 55 | BEGIN 56 | IF NEW.email NOT LIKE '%@%.%' THEN 57 | SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'L\'email doit contenir un "@" et un "."'; 58 | END IF; 59 | END$$ 60 | 61 | CREATE TRIGGER maj_compte 62 | BEFORE UPDATE ON Account 63 | FOR EACH ROW 64 | BEGIN 65 | IF NEW.email NOT LIKE '%@%.%' THEN 66 | SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'L\'email doit contenir un "@" et un "."'; 67 | END IF; 68 | END$$ 69 | 70 | DELIMITER ; 71 | 72 | -- Modification du type de compte 73 | DELIMITER $$ 74 | 75 | CREATE TRIGGER avant_maj_compte 76 | BEFORE UPDATE ON Account 77 | FOR EACH ROW 78 | BEGIN 79 | IF OLD.type_compte != NEW.type_compte THEN 80 | SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Le type de compte ne peut pas être modifié.'; 81 | END IF; 82 | END$$ 83 | 84 | DELIMITER ; 85 | 86 | 87 | -- Pour le mot de passe 88 | 89 | DELIMITER $$ 90 | 91 | CREATE TRIGGER avant_ajout_password 92 | BEFORE INSERT ON Account 93 | FOR EACH ROW 94 | BEGIN 95 | IF CHAR_LENGTH(NEW.password) < 8 THEN 96 | SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Le mot de passe doit contenir au moins 8 caractères.'; 97 | END IF; 98 | END$$ 99 | 100 | CREATE TRIGGER avant_maj_password 101 | BEFORE UPDATE ON Account 102 | FOR EACH ROW 103 | BEGIN 104 | IF CHAR_LENGTH(NEW.password) < 8 THEN 105 | SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Le mot de passe doit contenir au moins 8 caractères.'; 106 | END IF; 107 | END$$ 108 | 109 | DELIMITER ; 110 | 111 | -- Empêcher la suppression du dernier compte administrateur présent dans la base de données 112 | DELIMITER $$ 113 | 114 | CREATE TRIGGER avant_suppression_compte_administrateur 115 | BEFORE DELETE ON Account 116 | FOR EACH ROW 117 | BEGIN 118 | IF OLD.type_compte = 'Administrateur' AND (SELECT COUNT(*) FROM Account WHERE type_compte = 'Administrateur') = 1 THEN 119 | SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'La suppression du dernier compte Administrateur est interdite.'; 120 | END IF; 121 | END$$ 122 | 123 | DELIMITER ; 124 | 125 | /* 126 | Début du script pour l'ensemble des tables utilisées dans Planificator 127 | Structuration: 128 | - Table Client: Pour les informations sur les clients 129 | - Table Contrat: Pour les informations sur les contrats des clients 130 | - Table TypeTraitement: Pour les types de traitements prévus pour les clients 131 | - Table Traitement: Pour les informations sur chaque service choisit par un client dans son contrat 132 | - Table Planning: Pour les informations sur le planning de chaque service dedié à un client 133 | - Table PlanningDetails: Pour les détails de chaque planning 134 | - Table Facture: Pour la facturation de chaque service du client 135 | - Table Remarque: Ajout d'un remarque et confirmation si un traitement est effectué 136 | - Table Signalement: Signalement pour un avancement ou décalage 137 | - Table Historique: Historique des traitements pour chaque client 138 | */ 139 | 140 | -- Table Client 141 | CREATE TABLE Client ( 142 | client_id INT PRIMARY KEY AUTO_INCREMENT, 143 | nom VARCHAR(255) NOT NULL, 144 | prenom VARCHAR(255), 145 | email VARCHAR(255) NOT NULL, 146 | telephone VARCHAR(30) NOT NULL, 147 | adresse VARCHAR(255) NOT NULL, 148 | nif VARCHAR(50), 149 | stat VARCHAR(50), 150 | date_ajout DATE NOT NULL, 151 | categorie ENUM ('Particulier', 'Organisation', 'Société') NOT NULL, 152 | axe ENUM ('Nord (N)', 'Sud (S)', 'Est (E)', 'Ouest (O)', 'Centre (C)') NOT NULL 153 | ); 154 | 155 | # NIF STAT: Valable uniquement pour les sociétés 156 | 157 | /* 158 | Dans le code back-end: 159 | Si ce n'est pas un particulier alors prénom change en Responsable 160 | */ 161 | 162 | CREATE TABLE Contrat ( 163 | contrat_id INT PRIMARY KEY AUTO_INCREMENT, 164 | client_id INT NOT NULL, 165 | reference_contrat VARCHAR(20), 166 | date_contrat DATE NOT NULL, 167 | date_debut DATE NOT NULL, 168 | date_fin VARCHAR(50), 169 | statut_contrat ENUM ('Actif', 'Terminé', 'Résilié') NOT NULL DEFAULT 'Actif', 170 | duree_contrat INT DEFAULT NULL, 171 | duree ENUM ('Indeterminée', 'Déterminée') NOT NULL, 172 | categorie ENUM ('Nouveau', 'Renouvellement') NOT NULL, 173 | FOREIGN KEY (client_id) REFERENCES Client(client_id) ON DELETE CASCADE 174 | ); 175 | 176 | /* 177 | Cette partie est à décommenter si vous voulez calculer la duréee du contrat. 178 | L'opération se fait en fonction de la date de fin et date de début du contrat 179 | Dans le code, lors de l'ajout du contrat: 180 | SELECT *, DATEDIFF(date_fin, date_debut) AS duree FROM Contrat; 181 | */ 182 | 183 | -- Type de traitement utilisant enum 184 | CREATE TABLE TypeTraitement ( 185 | id_type_traitement INT PRIMARY KEY AUTO_INCREMENT, 186 | categorieTraitement ENUM ('AT: Anti termites', 'PC', 'NI: Nettoyage Industriel', 'RO: Ramassage Ordures'), 187 | typeTraitement ENUM('Dératisation (PC)', 'Désinfection (PC)', 'Désinsectisation (PC)', 'Fumigation (PC)', 'Nettoyage industriel (NI)', 'Anti termites (AT)', 'Ramassage ordure') NOT NULL 188 | ); 189 | 190 | 191 | -- Table Traitement 192 | CREATE TABLE Traitement ( 193 | traitement_id INT PRIMARY KEY AUTO_INCREMENT, 194 | contrat_id INT NOT NULL, 195 | id_type_traitement INT NOT NULL, 196 | FOREIGN KEY (contrat_id) REFERENCES Contrat(contrat_id) ON DELETE CASCADE, 197 | FOREIGN KEY (id_type_traitement) REFERENCES TypeTraitement(id_type_traitement) ON DELETE CASCADE 198 | ); 199 | 200 | 201 | -- Table Planning 202 | CREATE TABLE Planning ( 203 | planning_id INT PRIMARY KEY AUTO_INCREMENT, 204 | traitement_id INT NOT NULL, 205 | date_debut_planification DATE, 206 | mois_debut INT, 207 | mois_fin INT, 208 | duree_traitement INT NOT NULL DEFAULT 12, 209 | redondance INT NOT NULL, 210 | date_fin_planification DATE, 211 | FOREIGN KEY (traitement_id) REFERENCES Traitement(traitement_id) ON DELETE CASCADE 212 | ); 213 | 214 | -- Table PlanningDetails 215 | CREATE TABLE PlanningDetails ( 216 | planning_detail_id INT PRIMARY KEY AUTO_INCREMENT, 217 | planning_id INT NOT NULL, 218 | date_planification DATE NOT NULL, 219 | statut ENUM ('Effectué', 'À venir') NOT NULL, 220 | FOREIGN KEY (planning_id) REFERENCES Planning(planning_id) ON DELETE CASCADE 221 | ); 222 | 223 | -- Table Facture (Pour la facturation de chaque service effectué) 224 | CREATE TABLE Facture ( 225 | facture_id INT PRIMARY KEY AUTO_INCREMENT, 226 | planning_detail_id INT NOT NULL, 227 | reference_facture VARCHAR(30), 228 | montant INT NOT NULL, 229 | mode ENUM('Chèque', 'Espèce', 'Mobile Money', 'Virement'), 230 | etablissemnt_payeur VARCHAR(50), -- Pour la chèque uniquement 231 | numero_cheque VARCHAR(50) NULL, 232 | date_paiement DATE NULL, -- Valide pour tous 233 | date_traitement DATE NOT NULL, 234 | etat ENUM('Payé', 'Non payé', 'À venir') DEFAULT 'Non payé', 235 | axe ENUM ('Nord (N)', 'Sud (S)', 'Est (E)', 'Ouest (O)', 'Centre (C)') NOT NULL, 236 | FOREIGN KEY (planning_detail_id) REFERENCES PlanningDetails(planning_detail_id) ON DELETE CASCADE 237 | ); 238 | 239 | -- Historique des prix 240 | CREATE TABLE Historique_prix ( 241 | history_id INT AUTO_INCREMENT PRIMARY KEY, 242 | facture_id INT NOT NULL, 243 | old_amount INT NOT NULL, 244 | new_amount INT NOT NULL, 245 | change_date DATETIME NOT NULL, 246 | changed_by VARCHAR(255) DEFAULT 'System', 247 | FOREIGN KEY (facture_id) REFERENCES Facture(facture_id) ON DELETE CASCADE 248 | ); 249 | 250 | -- Table Remarque (Pour confirmer si une traitement a été effectuée) 251 | CREATE TABLE Remarque ( 252 | remarque_id INT PRIMARY KEY AUTO_INCREMENT, 253 | client_id INT NOT NULL, 254 | planning_detail_id INT NOT NULL, 255 | facture_id INT, 256 | contenu TEXT NOT NULL, 257 | issue TEXT, 258 | action TEXT NOT NULL, 259 | date_remarque TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 260 | FOREIGN KEY (facture_id) REFERENCES Facture(facture_id) ON DELETE SET NULL, 261 | FOREIGN KEY (client_id) REFERENCES Client(client_id) ON DELETE CASCADE, 262 | FOREIGN KEY (planning_detail_id) REFERENCES PlanningDetails(planning_detail_id) ON DELETE CASCADE 263 | ); 264 | /* 265 | contenu pour contenir les remarques effectués 266 | issue pour un cas isolé, en cas de problème ou erreur lors du traitement 267 | action pour contenire les actions entrepris à chaque traitement 268 | */ 269 | -- Table Signalement (Pour un signalement d'avancement ou de décalage) 270 | CREATE TABLE Signalement ( 271 | signalement_id INT PRIMARY KEY AUTO_INCREMENT, 272 | planning_detail_id INT NOT NULL, 273 | motif TEXT NOT NULL, 274 | type ENUM ('Avancement', 'Décalage') NOT NULL, 275 | FOREIGN KEY (planning_detail_id) REFERENCES PlanningDetails(planning_detail_id) ON DELETE CASCADE 276 | ); 277 | 278 | 279 | -- Table Historique (Historique des traitements effectués) 280 | CREATE TABLE Historique ( 281 | historique_id INT PRIMARY KEY AUTO_INCREMENT, 282 | facture_id INT NOT NULL, 283 | planning_detail_id INT, 284 | signalement_id INT, 285 | date_historique TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 286 | contenu TEXT NOT NULL, 287 | issue TEXT, 288 | action TEXT NOT NULL, 289 | FOREIGN KEY (planning_detail_id) REFERENCES PlanningDetails(planning_detail_id) ON DELETE SET NULL, 290 | FOREIGN KEY (signalement_id) REFERENCES Signalement(signalement_id) ON DELETE SET NULL, 291 | FOREIGN KEY (facture_id) REFERENCES Facture(facture_id) ON DELETE CASCADE 292 | ); 293 | 294 | /* 295 | Historique regroupe toutes les informations utiles pour chaque traitement effectué 296 | Modifié et corrigé le 18 Septembre 2025 297 | */ 298 | -------------------------------------------------------------------------------- /Contrat/fonctionnalités/Excel/génerationTraitementMoisTerminal.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import datetime 3 | import asyncio 4 | import aiomysql 5 | from io import BytesIO 6 | from openpyxl import Workbook 7 | from openpyxl.styles import Font, Alignment, PatternFill # Importez PatternFill ici 8 | from openpyxl.utils import get_column_letter 9 | from Contrat.fonctionnalités.connexionDB import DBConnection 10 | 11 | # --- Fonction de récupération des traitements pour un mois donné --- 12 | async def obtenirTraitementParMois(pool, year: int, month: int): 13 | conn = None 14 | try: 15 | conn = await pool.acquire() 16 | async with conn.cursor(aiomysql.DictCursor) as cursor: 17 | query = """ 18 | SELECT pd.date_planification AS `Date du traitement`, 19 | tt.typeTraitement AS `Traitement concerné`, 20 | tt.categorieTraitement AS `Catégorie du traitement`, 21 | CONCAT(c.nom, ' ', c.prenom) AS `Client concerné`, 22 | c.categorie AS `Catégorie du client`, 23 | c.axe AS `Axe du client`, 24 | pd.statut AS `Etat traitement` -- AJOUT DE CETTE COLONNE POUR RÉCUPÉRER LE STATUT 25 | FROM PlanningDetails pd 26 | JOIN 27 | Planning p ON pd.planning_id = p.planning_id 28 | JOIN 29 | Traitement t ON p.traitement_id = t.traitement_id 30 | JOIN 31 | TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement 32 | JOIN 33 | Contrat co ON t.contrat_id = co.contrat_id 34 | JOIN 35 | Client c ON co.client_id = c.client_id 36 | WHERE YEAR(pd.date_planification) = %s 37 | AND MONTH(pd.date_planification) = %s 38 | ORDER BY pd.date_planification; 39 | """ 40 | await cursor.execute(query, (year, month)) 41 | result = await cursor.fetchall() 42 | return result 43 | except Exception as e: 44 | print(f"Erreur lors de la récupération des traitements : {e}") 45 | return [] 46 | finally: 47 | if conn: 48 | pool.release(conn) 49 | 50 | # --- Fonction de récupération de toutes les combinaisons année-mois contenant des traitements --- 51 | async def obtenirToutTraitementDuMois(pool): 52 | conn = None 53 | try: 54 | conn = await pool.acquire() 55 | async with conn.cursor(aiomysql.DictCursor) as cursor: 56 | query = """ 57 | SELECT DISTINCT 58 | YEAR(date_planification) AS annee, 59 | MONTH(date_planification) AS mois 60 | FROM PlanningDetails 61 | ORDER BY annee DESC, mois DESC; 62 | """ 63 | await cursor.execute(query) 64 | result = await cursor.fetchall() 65 | return result 66 | except Exception as e: 67 | print(f"Erreur lors de la récupération des mois avec traitements : {e}") 68 | return [] 69 | finally: 70 | if conn: 71 | pool.release(conn) 72 | 73 | # --- Fonction pour générer le fichier Excel des traitements --- 74 | def generationTraitementExcel(data: list[dict], year: int, month: int): 75 | month_name_fr = datetime.date(year, month, 1).strftime('%B').capitalize() 76 | file_name = f"traitements-{month_name_fr}-{year}.xlsx" 77 | 78 | wb = Workbook() 79 | ws = wb.active 80 | ws.title = f"Traitements {month_name_fr} {year}" 81 | 82 | # Styles 83 | bold_font = Font(bold=True) 84 | header_font = Font(bold=True, size=14) 85 | center_align = Alignment(horizontal='center', vertical='center') 86 | 87 | # Définition des couleurs de remplissage 88 | red_fill = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid") # Rouge clair 89 | green_fill = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid") # Vert clair 90 | 91 | # Titre du rapport 92 | ws.cell(row=1, column=1, value=f"Rapport des Traitements du mois de {month_name_fr} {year}").font = header_font 93 | ws.cell(row=1, column=1).alignment = center_align 94 | num_data_cols = 7 95 | ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=num_data_cols) 96 | 97 | # Nombre total de traitements 98 | total_traitements = len(data) 99 | ws.cell(row=3, column=1, value=f"Nombre total de traitements ce mois-ci : {total_traitements}").font = bold_font 100 | 101 | # Ligne vide pour la séparation 102 | ws.cell(row=4, column=1, value="") 103 | 104 | df = pd.DataFrame(data) 105 | 106 | if df.empty: 107 | ws.cell(row=5, column=1, value="Aucun traitement trouvé pour ce mois.") 108 | else: 109 | headers = df.columns.tolist() 110 | status_col_index = -1 111 | try: 112 | status_col_index = headers.index('Etat traitement') # Trouvez l'index de la colonne de statut 113 | except ValueError: 114 | print("AVERTISSEMENT: La colonne 'Etat traitement' n'a pas été trouvée dans les données. Les couleurs ne seront pas appliquées.") 115 | 116 | for col_idx, header in enumerate(headers, 1): 117 | cell = ws.cell(row=5, column=col_idx, value=header) 118 | cell.font = bold_font 119 | 120 | for r_idx, row_dict in enumerate(data, start=6): 121 | for c_idx, col_name in enumerate(headers, 1): 122 | value = row_dict.get(col_name) 123 | cell = ws.cell(row=r_idx, column=c_idx, value=value) 124 | 125 | # Appliquer la couleur si c'est la colonne 'Etat traitement' et si la valeur est connue 126 | if col_name == 'Etat traitement': 127 | if value == 'Effectué': 128 | cell.fill = red_fill 129 | elif value == 'À venir': 130 | cell.fill = green_fill 131 | # else: 132 | # cell.fill = PatternFill(fill_type=None) # Optionnel: Réinitialiser la couleur pour les autres colonnes 133 | 134 | max_col_for_width = len(df.columns) if not df.empty else num_data_cols 135 | 136 | for i in range(1, max_col_for_width + 1): 137 | column_letter = get_column_letter(i) 138 | length = 0 139 | for row_idx in range(1, ws.max_row + 1): 140 | cell = ws.cell(row=row_idx, column=i) 141 | if cell.value is not None: 142 | length = max(length, len(str(cell.value))) 143 | ws.column_dimensions[column_letter].width = length + 2 144 | 145 | try: 146 | output = BytesIO() 147 | wb.save(output) 148 | with open(file_name, 'wb') as f: 149 | f.write(output.getvalue()) 150 | 151 | print(f"Fichier '{file_name}' généré avec succès.") 152 | except Exception as e: 153 | print(f"Erreur lors de la génération du fichier Excel des traitements : {e}") 154 | 155 | # --- Fonction principale pour exécuter le rapport des traitements --- 156 | async def generationRapportMain(): 157 | pool = None 158 | try: 159 | pool = await DBConnection() 160 | if not pool: 161 | print("Échec de la connexion à la base de données. Annulation de l'opération.") 162 | return 163 | 164 | donnéesMoisExistant = await obtenirToutTraitementDuMois(pool) 165 | 166 | annéeChoisie = None 167 | moisChoisie = None 168 | 169 | if donnéesMoisExistant: 170 | # 1. Obtenir les années distinctes 171 | distinct_years = sorted(list(set(entry['annee'] for entry in donnéesMoisExistant)), reverse=True) 172 | 173 | print("\nAnnées contenant des traitements déjà enregistrés :") 174 | for i, year in enumerate(distinct_years): 175 | print(f" {i + 1}. {year}") 176 | print(" 0. Entrer une autre année manuellement") 177 | 178 | chosen_year_index = -1 179 | while True: 180 | try: 181 | choice = int(input("Choisissez un numéro d'année dans la liste ou '0' pour entrer manuellement : ")) 182 | if 0 < choice <= len(distinct_years): 183 | annéeChoisie = distinct_years[choice - 1] 184 | chosen_year_index = choice - 1 185 | break 186 | elif choice == 0: 187 | while True: 188 | try: 189 | inputAnnée = input("Veuillez entrer l'année pour le rapport (ex: 2023) : ") 190 | annéeChoisie = int(inputAnnée) 191 | if not (2000 <= annéeChoisie <= datetime.datetime.now().year + 5): 192 | print(f"Année invalide. Veuillez entrer une année entre 2000 et {datetime.datetime.now().year + 5}.") 193 | continue 194 | break 195 | except ValueError: 196 | print("Entrée invalide. Veuillez entrer un nombre pour l'année.") 197 | break 198 | else: 199 | print("Choix invalide. Veuillez réessayer.") 200 | except ValueError: 201 | print("Entrée invalide. Veuillez entrer un numéro.") 202 | 203 | # 2. Après avoir choisi l'année, filtrer et afficher les mois disponibles pour cette année 204 | if annéeChoisie: 205 | months_for_selected_year = sorted(list(set(entry['mois'] for entry in donnéesMoisExistant if entry['annee'] == annéeChoisie)), reverse=True) 206 | 207 | if months_for_selected_year: 208 | print(f"\nMois disponibles pour l'année {annéeChoisie} :") 209 | for i, month_num in enumerate(months_for_selected_year): 210 | month_name = datetime.date(annéeChoisie, month_num, 1).strftime('%B').capitalize() 211 | print(f" {i + 1}. {month_name} ({month_num})") 212 | print(" 0. Entrer un autre mois manuellement") 213 | 214 | while True: 215 | try: 216 | choice = int(input(f"Choisissez un numéro de mois pour {annéeChoisie} ou '0' pour entrer manuellement : ")) 217 | if 0 < choice <= len(months_for_selected_year): 218 | moisChoisie = months_for_selected_year[choice - 1] 219 | break 220 | elif choice == 0: 221 | while True: 222 | try: 223 | month_input = input("Veuillez entrer le numéro du mois (1-12) : ") 224 | moisChoisie = int(month_input) 225 | if not (1 <= moisChoisie <= 12): 226 | print("Numéro de mois invalide. Veuillez entrer un nombre entre 1 et 12.") 227 | continue 228 | break 229 | except ValueError: 230 | print("Entrée invalide. Veuillez entrer un nombre pour le mois.") 231 | break 232 | else: 233 | print("Choix invalide. Veuillez réessayer.") 234 | except ValueError: 235 | print("Entrée invalide. Veuillez entrer un numéro.") 236 | else: # Entrée manuelle de l'année 237 | print(f"\nAucun traitement trouvé pour l'année {annéeChoisie}. Veuillez entrer le mois manuellement.") 238 | while True: 239 | try: 240 | month_input = input("Veuillez entrer le numéro du mois (1-12) pour le rapport (ex: 6 pour Juin) : ") 241 | moisChoisie = int(month_input) 242 | if not (1 <= moisChoisie <= 12): 243 | print("Numéro de mois invalide. Veuillez entrer un nombre entre 1 et 12.") 244 | continue 245 | break 246 | except ValueError: 247 | print("Entrée invalide. Veuillez entrer un nombre pour le mois.") 248 | 249 | else: # Aucune données dans la BD 250 | print("\nAucun traitement trouvé dans la base de données. Veuillez entrer le mois et l'année manuellement.") 251 | while True: 252 | try: 253 | inputAnnée = input("Veuillez entrer l'année pour le rapport (ex: 2023) : ") 254 | month_input = input("Veuillez entrer le numéro du mois (1-12) pour le rapport (ex: 6 pour Juin) : ") 255 | 256 | annéeChoisie = int(inputAnnée) 257 | moisChoisie = int(month_input) 258 | 259 | if not (1 <= moisChoisie <= 12): 260 | print("Numéro de mois invalide. Veuillez entrer un nombre entre 1 et 12.") 261 | continue 262 | if not (2000 <= annéeChoisie <= datetime.datetime.now().year + 5): 263 | print(f"Année invalide. Veuillez entrer une année entre 2000 et {datetime.datetime.now().year + 5}.") 264 | continue 265 | break 266 | except ValueError: 267 | print("Entrée invalide. Veuillez entrer un nombre pour l'année et le mois.") 268 | 269 | if annéeChoisie is None or moisChoisie is None: 270 | print("Sélection de l'année ou du mois annulée. Fin du rapport.") 271 | return 272 | 273 | print( 274 | f"\nPréparation du rapport des traitements pour {datetime.date(annéeChoisie, moisChoisie, 1).strftime('%B').capitalize()} {annéeChoisie}...") 275 | traitements_data = await obtenirTraitementParMois(pool, annéeChoisie, moisChoisie) 276 | generationTraitementExcel(traitements_data, annéeChoisie, moisChoisie) 277 | 278 | except Exception as e: 279 | print(f"Une erreur inattendue est survenue dans le script principal : {e}") 280 | finally: 281 | if pool: 282 | await pool.close() 283 | 284 | if __name__ == "__main__": 285 | asyncio.run(generationRapportMain()) -------------------------------------------------------------------------------- /Contrat/fonctionnalités/signalement/gestionEtatSignalement.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiomysql 3 | from datetime import datetime, timedelta 4 | # Import the relativedelta for robust month/year arithmetic 5 | from dateutil.relativedelta import relativedelta 6 | 7 | async def get_db_credentials(): 8 | """ 9 | Demande à l'utilisateur les informations de connexion à la base de données. 10 | """ 11 | host = input("Entrez l'adresse du serveur MySQL (par exemple, localhost): ").strip() 12 | if not host: # Default to localhost if empty 13 | host = "localhost" 14 | 15 | port_str = input("Entrez le port du serveur MySQL (par défaut: 3306): ").strip() 16 | try: 17 | port = int(port_str) if port_str else 3306 18 | except ValueError: 19 | print("Port invalide, utilisation de la valeur par défaut (3306).") 20 | port = 3306 21 | user = input("Entrez le nom d'utilisateur MySQL: ").strip() 22 | password = input("Entrez le mot de passe MySQL: ").strip() 23 | database = input("Entrez le nom de la base de données MySQL (par exemple, Planificator): ").strip() 24 | if not database: # Default to Planificator if empty 25 | database = "Planificator" 26 | return host, port, user, password, database 27 | 28 | async def get_client_id(pool): # Changed conn to pool as it's better practice for functions that acquire/release 29 | """ 30 | Demande à l'utilisateur l'ID du client et vérifie son existence dans la base de données. 31 | 32 | Args: 33 | pool: Le pool de connexions aiomysql. 34 | 35 | Returns: 36 | L'ID du client si trouvé, None sinon. 37 | """ 38 | async with pool.acquire() as conn: 39 | async with conn.cursor() as cur: 40 | while True: 41 | client_id_str = input("Entrez l'ID du client dont vous souhaitez ajuster le planning: ").strip() 42 | try: 43 | client_id = int(client_id_str) 44 | # Vérifier si le client existe 45 | await cur.execute("SELECT client_id FROM Client WHERE client_id = %s", (client_id,)) 46 | if await cur.fetchone(): 47 | return client_id 48 | else: 49 | print("Client non trouvé. Veuillez entrer un ID de client valide.") 50 | except ValueError: 51 | print("ID de client invalide. Veuillez entrer un nombre entier.") 52 | 53 | async def get_client_planning(pool, client_id): # Changed conn to pool 54 | """ 55 | Récupère le planning du client, les détails du planning et les traitements associés. 56 | 57 | Args: 58 | pool: Le pool de connexions aiomysql. 59 | client_id: L'ID du client. 60 | 61 | Returns: 62 | Un tuple contenant les informations du planning, les détails du planning et les traitements. 63 | Retourne None si une erreur se produit. 64 | """ 65 | try: 66 | async with pool.acquire() as conn: 67 | async with conn.cursor(aiomysql.DictCursor) as cur: 68 | 69 | # Récupérer le planning principal du client 70 | # ATTENTION: La colonne 'mois_pause' et 'unite_duree' n'existent pas dans votre DDL Planning. 71 | # Elles ont été retirées des colonnes SELECT. 72 | sql_planning = """ 73 | SELECT 74 | p.planning_id, p.date_debut_planification, p.mois_debut, p.mois_fin, 75 | p.duree_traitement, p.redondance, p.date_fin_planification 76 | FROM Planning p 77 | JOIN Traitement t ON p.traitement_id = t.traitement_id 78 | JOIN Contrat c ON t.contrat_id = c.contrat_id -- Correction: c.contrat_id est la PK 79 | WHERE c.client_id = %s 80 | """ 81 | await cur.execute(sql_planning, (client_id,)) 82 | planning_data = await cur.fetchone() 83 | 84 | if not planning_data: 85 | print("Aucun planning trouvé pour ce client.") 86 | return None, None, None 87 | 88 | planning_id = planning_data['planning_id'] 89 | 90 | # Récupérer les détails du planning 91 | # ATTENTION: La colonne 'mois' n'existe pas dans votre DDL PlanningDetails. 92 | # Elle a été retirée des colonnes SELECT. 93 | sql_planning_details = """ 94 | SELECT planning_detail_id, date_planification, statut 95 | FROM PlanningDetails 96 | WHERE planning_id = %s 97 | ORDER BY date_planification 98 | """ 99 | await cur.execute(sql_planning_details, (planning_id,)) 100 | planning_details = await cur.fetchall() 101 | 102 | # Récupérer les traitements 103 | sql_traitements = """ 104 | SELECT t.traitement_id, tt.typeTraitement 105 | FROM Traitement t 106 | JOIN Contrat c ON t.contrat_id = c.contrat_id -- Correction: c.contrat_id est la PK 107 | JOIN TypeTraitement tt ON t.id_type_traitement = tt.id_type_traitement 108 | WHERE c.client_id = %s 109 | """ 110 | await cur.execute(sql_traitements, (client_id,)) 111 | traitements = await cur.fetchall() 112 | 113 | return planning_data, planning_details, traitements 114 | 115 | except aiomysql.Error as e: 116 | print(f"**Erreur lors de la récupération du planning du client :** {e}") 117 | return None, None, None 118 | 119 | async def display_planning_info(planning_data, planning_details, traitements): 120 | """ 121 | Affiche les informations du planning du client. 122 | 123 | Args: 124 | planning_data: Les données du planning principal. 125 | planning_details: Les détails du planning. 126 | traitements: Les traitements associés au client. 127 | """ 128 | if not planning_data: 129 | print("Aucun planning à afficher.") 130 | return 131 | 132 | print("\n--- Informations sur le planning du client ---") 133 | print(f" **Planning ID:** {planning_data['planning_id']}") 134 | print(f" **Date de début de planification:** {planning_data['date_debut_planification']}") 135 | print(f" **Mois de début:** {planning_data['mois_debut']}") 136 | print(f" **Mois de fin:** {planning_data['mois_fin']}") 137 | # 'Mois de pause' et 'Unite_duree' retirés car non présents dans DDL 138 | print(f" **Durée du traitement (en mois par défaut):** {planning_data['duree_traitement']}") 139 | print(f" **Redondance (en mois par défaut):** {planning_data['redondance']}") 140 | print(f" **Date de fin de planification:** {planning_data['date_fin_planification']}") 141 | 142 | print("\n--- Détails du planning ---") 143 | if not planning_details: 144 | print(" Aucun détail de planning trouvé.") 145 | else: 146 | for detail in planning_details: 147 | # 'Mois' retiré car non présent dans DDL PlanningDetails 148 | print(f" **ID:** {detail['planning_detail_id']}, **Date:** {detail['date_planification']}, **Statut:** {detail['statut']}") 149 | 150 | print("\n--- Traitements associés ---") 151 | if not traitements: 152 | print(" Aucun traitement associé.") 153 | else: 154 | for traitement in traitements: 155 | print(f" **ID:** {traitement['traitement_id']}, **Type:** {traitement['typeTraitement']}") 156 | 157 | async def get_option_choix(): 158 | """ 159 | Demande à l'utilisateur de choisir comment gérer le planning. 160 | 161 | Returns: 162 | Le choix de l'utilisateur (1, 2 ou 3), ou None en cas d'erreur. 163 | """ 164 | while True: 165 | print("\nChoisissez comment gérer le planning :") 166 | print(" 1. Garder l'état actuel du planning (ajuster uniquement le statut d'un détail)") 167 | print(" 2. Changer le planning (modifier les dates des traitements futurs)") 168 | print(" 3. Annuler l'opération") 169 | choix = input("Entrez votre choix (1, 2 ou 3): ").strip() 170 | if choix in ('1', '2', '3'): 171 | return int(choix) 172 | else: 173 | print("Choix invalide. Veuillez entrer 1, 2 ou 3.") 174 | 175 | async def handle_option_1(pool, planning_details): # Changed conn to pool 176 | """ 177 | Gère l'option de garder l'état actuel du planning (ajuster le statut d'un détail). 178 | 179 | Args: 180 | pool: Le pool de connexions aiomysql. 181 | planning_details: Les détails du planning. 182 | """ 183 | if not planning_details: 184 | print("Aucun détail de planning à modifier.") 185 | return 186 | 187 | async with pool.acquire() as conn: 188 | async with conn.cursor() as cur: 189 | while True: 190 | detail_id_str = input("Entrez l'ID du détail du planning que vous souhaitez modifier (ou '0' pour annuler): ").strip() 191 | if detail_id_str == '0': 192 | print("Opération annulée.") 193 | return 194 | 195 | try: 196 | detail_id = int(detail_id_str) 197 | # Vérifier si detail_id existe parmi les planning_details fournis 198 | detail_exists = False 199 | for detail in planning_details: 200 | if detail['planning_detail_id'] == detail_id: 201 | detail_exists = True 202 | break 203 | if not detail_exists: 204 | print(f"ID de détail du planning {detail_id} non trouvé dans la liste pour ce client.") 205 | continue 206 | break 207 | except ValueError: 208 | print("ID de détail du planning invalide. Veuillez entrer un nombre entier ou 0.") 209 | 210 | # Statuts valides selon votre DDL ENUM ('Effectué', 'À venir', 'Annulé', 'Reporté') 211 | valid_statuses = ['Effectué', 'À venir', 'Annulé', 'Reporté'] 212 | while True: 213 | nouveau_statut = input(f"Entrez le nouveau statut ({', '.join(valid_statuses)}): ").strip() 214 | if nouveau_statut in valid_statuses: 215 | break 216 | else: 217 | print(f"Statut invalide. Veuillez choisir parmi {', '.join(valid_statuses)}.") 218 | 219 | try: 220 | sql_update_statut = "UPDATE PlanningDetails SET statut = %s WHERE planning_detail_id = %s" 221 | await cur.execute(sql_update_statut, (nouveau_statut, detail_id)) 222 | # autocommit=True sur le pool s'en charge, mais commit explicite n'est pas nuisible 223 | # await conn.commit() 224 | print(f"Statut du détail du planning ID {detail_id} mis à jour à '{nouveau_statut}'.") 225 | 226 | except aiomysql.Error as e: 227 | print(f"**Erreur lors de la mise à jour du statut :** {e}") 228 | # rollback() n'est pas nécessaire avec autocommit=True, mais peut être utile si autocommit est désactivé. 229 | # await conn.rollback() 230 | 231 | 232 | async def handle_option_2(pool, planning_data): # Changed conn to pool 233 | """ 234 | Gère l'option de changer le planning (modifier les dates des traitements futurs). 235 | La redondance est basée sur 'redondance' de la table Planning (assumée en mois). 236 | 237 | Args: 238 | pool: Le pool de connexions aiomysql. 239 | planning_data: Les données du planning principal. 240 | """ 241 | async with pool.acquire() as conn: 242 | async with conn.cursor(aiomysql.DictCursor) as cur: 243 | ajustement_type = input("S'agit-il d'un 'Avancement' ou d'un 'Décalage' ? ").strip() 244 | if ajustement_type not in ('Avancement', 'Décalage'): 245 | print("Type d'ajustement invalide. Veuillez entrer 'Avancement' ou 'Décalage'.") 246 | return 247 | 248 | date_str = input(f"Entrez la date du prochain traitement que vous souhaitez modifier (AAAA-MM-JJ): ").strip() 249 | try: 250 | date_a_modifier = datetime.strptime(date_str, '%Y-%m-%d').date() 251 | except ValueError: 252 | print("Format de date invalide. Veuillez utiliser le format AAAA-MM-JJ.") 253 | return 254 | 255 | nouvelle_date_str = input(f"Entrez la nouvelle date pour ce traitement (AAAA-MM-JJ): ").strip() 256 | try: 257 | nouvelle_date = datetime.strptime(nouvelle_date_str, '%Y-%m-%d').date() 258 | except ValueError: 259 | print("Format de date invalide. Veuillez utiliser le format AAAA-MM-JJ.") 260 | return 261 | 262 | # Trouver tous les planning details à partir de la date spécifiée 263 | sql_details_to_update = """ 264 | SELECT planning_detail_id, date_planification 265 | FROM PlanningDetails 266 | WHERE planning_id = %s AND date_planification >= %s 267 | ORDER BY date_planification ASC 268 | """ 269 | await cur.execute(sql_details_to_update, (planning_data['planning_id'], date_a_modifier)) 270 | details_to_update = await cur.fetchall() 271 | 272 | if not details_to_update: 273 | print(f"Aucune planification trouvée à partir du {date_a_modifier} pour ce planning.") 274 | return 275 | 276 | # Calculer le décalage pour la première date trouvée (celle que l'utilisateur veut modifier) 277 | first_detail_original_date = None 278 | first_detail_id_to_signal = None 279 | for detail in details_to_update: 280 | if detail['date_planification'] == date_a_modifier: 281 | first_detail_original_date = detail['date_planification'] 282 | first_detail_id_to_signal = detail['planning_detail_id'] 283 | break 284 | 285 | if not first_detail_original_date: 286 | print(f"La date {date_a_modifier} n'est pas une date de planification future valide pour ce planning.") 287 | return 288 | 289 | delta_days = (nouvelle_date - first_detail_original_date).days 290 | print(f"Décalage calculé de {delta_days} jours pour la première date affectée.") 291 | 292 | # Mettre à jour toutes les dates futures à partir de la date spécifiée 293 | # On applique le même delta_days à toutes les dates suivantes. 294 | # On utilise un CURDATE() dans la requête pour ne pas affecter les passées 295 | sql_update_dates = """ 296 | UPDATE PlanningDetails 297 | SET date_planification = DATE_ADD(date_planification, INTERVAL %s DAY) 298 | WHERE planning_id = %s AND date_planification >= %s 299 | """ 300 | await cur.execute(sql_update_dates, (delta_days, planning_data['planning_id'], date_a_modifier)) 301 | print(f"{cur.rowcount} dates de planification futures mises à jour à partir du {date_a_modifier}.") 302 | 303 | # Enregistrer le signalement pour la première planification décalée/avancée 304 | if first_detail_id_to_signal: 305 | motif = input(f"Entrez le motif de l'{ajustement_type.lower()} pour la planification du {date_a_modifier}: ").strip() 306 | sql_insert_signalement = "INSERT INTO Signalement (planning_detail_id, motif, type) VALUES (%s, %s, %s)" 307 | await cur.execute(sql_insert_signalement, (first_detail_id_to_signal, motif, ajustement_type)) 308 | signalement_id = cur.lastrowid 309 | print(f"Signalement d'{ajustement_type.lower()} enregistré avec ID: {signalement_id}.") 310 | 311 | # Enregistrer également dans Historique 312 | # ATTENTION: La FK composite dans Historique (contenu, issue, action) vers Remarque est très inhabituelle. 313 | # Ici, nous insérons des données directes, non liées à Remarque par cette FK composite. 314 | # 'issue' et 'action' sont définies comme vides si l'utilisateur ne les fournit pas. 315 | issue_hist = input("Entrez un problème (issue) lié à ce signalement (laissez vide si aucun): ").strip() 316 | action_hist = input("Entrez une action entreprise pour ce signalement: ").strip() 317 | 318 | sql_insert_historique = """ 319 | INSERT INTO Historique ( 320 | facture_id, planning_detail_id, signalement_id, 321 | date_historique, contenu, issue, action 322 | ) VALUES (%s, %s, %s, %s, %s, %s, %s) 323 | """ 324 | # facture_id est NULL ici car ce n'est pas directement lié à une facture existante. 325 | # contenu est le motif du signalement. 326 | await cur.execute(sql_insert_historique, ( 327 | None, # facture_id est NULL pour un signalement pur 328 | first_detail_id_to_signal, 329 | signalement_id, 330 | datetime.now(), 331 | motif, 332 | issue_hist if issue_hist else "", # Empty string if no issue 333 | action_hist if action_hist else "Non spécifié" # Empty string if no action 334 | )) 335 | print("Entrée d'historique enregistrée pour le signalement.") 336 | 337 | async def main(): 338 | pool = None 339 | try: 340 | host, port, user, password, database = await get_db_credentials() 341 | # Use create_pool instead of connect directly for better resource management 342 | pool = await aiomysql.create_pool( 343 | host=host, 344 | port=port, 345 | user=user, 346 | password=password, 347 | db=database, 348 | autocommit=True, # Ensure transactions are committed 349 | minsize=1, 350 | maxsize=5 351 | ) 352 | print("Connexion à la base de données établie avec succès.") 353 | 354 | client_id = await get_client_id(pool) 355 | if not client_id: 356 | print("Opération annulée.") 357 | return 358 | 359 | planning_data, planning_details, traitements = await get_client_planning(pool, client_id) 360 | if not planning_data: 361 | print("Aucun planning trouvé pour ce client. Opération terminée.") 362 | return 363 | 364 | await display_planning_info(planning_data, planning_details, traitements) 365 | 366 | choix = await get_option_choix() 367 | if choix == 1: 368 | await handle_option_1(pool, planning_details) 369 | elif choix == 2: 370 | await handle_option_2(pool, planning_data) 371 | elif choix == 3: 372 | print("Opération annulée.") 373 | else: 374 | print("Choix invalide. Opération annulée.") # This branch should theoretically be unreachable 375 | 376 | print("--- Opération terminée ---") 377 | 378 | except Exception as e: 379 | print(f"**Une erreur inattendue est survenue dans le script principal :** {e}") 380 | finally: 381 | if pool: 382 | print("\nFermeture du pool de connexions à la base de données...") 383 | pool.close() 384 | await pool.wait_closed() 385 | print("Pool de connexions fermé.") 386 | 387 | if __name__ == "__main__": 388 | asyncio.run(main()) --------------------------------------------------------------------------------