├── 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 |
10 |
11 |
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())
--------------------------------------------------------------------------------