Skip to content

Commit d354710

Browse files
committed
feat: ajout de la réactivation des adresses, téléphones et emails inactifs pour les clients pour les utilisateurs ADMINISTRATEURS et GESTIONNAIRES
fix: correction de la gestion des erreurs réseau lors de la suppression d'adresses, téléphones et emails refactor: simplification des appels de fonction JavaScript pour la réactivation des adresses, téléphones et emails
1 parent ee81c0f commit d354710

6 files changed

Lines changed: 381 additions & 201 deletions

File tree

app_acfc/contextes_bp/clients.py

Lines changed: 174 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@
2929
"""
3030
from flask import (Blueprint, jsonify, request, redirect, url_for, Request,
3131
session)
32-
from sqlalchemy.orm import Session as SessionBdDType, joinedload
32+
from sqlalchemy.orm import Session as SessionBdDType, joinedload, contains_eager
3333
from sqlalchemy import or_, func
3434
from werkzeug import Response as ResponseWerkzeug
3535
from app_acfc.modeles import (
3636
get_db_session, Client, Part, Pro, Telephone, Mail, Commande,
3737
Facture, Adresse, PrepareTemplates, Constants)
38-
from app_acfc.habilitations import validate_habilitation, CLIENTS, GESTIONNAIRE
38+
from app_acfc.habilitations import validate_habilitation, CLIENTS, GESTIONNAIRE, ADMINISTRATEUR
3939
from datetime import datetime
4040
from typing import List
4141

@@ -151,8 +151,7 @@ def create_or_modify_pro(request: Request, client: Client,
151151
# ROUTES - INTERFACE DE RECHERCHE CLIENTS
152152
# ================================================================
153153

154-
@validate_habilitation(CLIENTS)
155-
@validate_habilitation(GESTIONNAIRE)
154+
@validate_habilitation([CLIENTS, GESTIONNAIRE], _and=False)
156155
@clients_bp.route('/rechercher', methods=['GET'])
157156
def clients_list() -> str:
158157
"""
@@ -177,8 +176,7 @@ def clients_list() -> str:
177176
# API REST - DONNÉES CLIENTS GLOBALES
178177
# ================================================================
179178

180-
@validate_habilitation(GESTIONNAIRE)
181-
@validate_habilitation(CLIENTS)
179+
@validate_habilitation([GESTIONNAIRE, CLIENTS], _and=False)
182180
@clients_bp.route('/recherche_avancee', methods=['GET'])
183181
def recherche_avancee() -> ResponseWerkzeug | str:
184182
"""
@@ -197,9 +195,25 @@ def recherche_avancee() -> ResponseWerkzeug | str:
197195
# Récupération des paramètres
198196
search_term = request.args.get('q', '').strip()
199197
search_type = request.args.get('type', 'part').strip()
200-
# TODO: Dans le formulaire, cette possibilité ne sera proposée qu'aux gestionnaires et administrateurs
201198
search_is_inactive = request.args.get('search-inactive', 'false').strip().lower() == 'true'
202-
199+
search_inactive_phone = or_(Telephone.is_inactive == True, Telephone.is_inactive == False) if (
200+
search_is_inactive
201+
and ('1' in session['habilitations'] or '2' in session['habilitations'])
202+
) else Telephone.is_inactive == False
203+
search_inactive_mail = or_(Mail.is_inactive == True, Mail.is_inactive == False) if (
204+
search_is_inactive
205+
and ('1' in session['habilitations'] or '2' in session['habilitations'])
206+
) else Mail.is_inactive == False
207+
search_inactive_address = or_(Adresse.is_inactive == True, Adresse.is_inactive == False) if (
208+
search_is_inactive
209+
and ('1' in session['habilitations'] or '2' in session['habilitations'])
210+
) else Adresse.is_inactive == False
211+
search_active_clients = or_(Client.is_active == True, Client.is_active == False) if (
212+
not search_is_inactive
213+
and ('1' in session['habilitations'] or '2' in session['habilitations'])
214+
) else Client.is_active == True
215+
216+
203217
# Récupération de la session de base de données
204218
db_session: SessionBdDType = get_db_session()
205219

@@ -213,15 +227,7 @@ def recherche_avancee() -> ResponseWerkzeug | str:
213227
db_session.query(Client)
214228
.join(Client.part)
215229
.filter(
216-
Client.is_active == (
217-
(True or False) if (
218-
search_is_inactive \
219-
and (
220-
'1' in session['habilitations'] or \
221-
'2' in session['habilitations']
222-
)
223-
) else True
224-
),
230+
search_active_clients,
225231
or_(
226232
Part.prenom.ilike(f'%{search_term}%'),
227233
Part.nom.ilike(f'%{search_term}%'),
@@ -236,15 +242,7 @@ def recherche_avancee() -> ResponseWerkzeug | str:
236242
db_session.query(Client)
237243
.join(Client.pro)
238244
.filter(
239-
Client.is_active == (
240-
(True or False) if (
241-
search_is_inactive \
242-
and (
243-
'1' in session['habilitations'] or \
244-
'2' in session['habilitations']
245-
)
246-
) else True
247-
),
245+
search_active_clients,
248246
Pro.raison_sociale.ilike(f'%{search_term}%')
249247
)
250248
.all()
@@ -255,17 +253,9 @@ def recherche_avancee() -> ResponseWerkzeug | str:
255253
db_session.query(Client)
256254
.join(Client.mails)
257255
.filter(
258-
Client.is_active == (
259-
(True or False) if (
260-
search_is_inactive \
261-
and (
262-
'1' in session['habilitations'] or \
263-
'2' in session['habilitations']
264-
)
265-
) else True
266-
),
256+
search_active_clients,
267257
Mail.mail.ilike(f'%{search_term}%'),
268-
Mail.is_inactive == ((True or False) if search_is_inactive else False)
258+
search_inactive_mail
269259
)
270260
.distinct()
271261
.all()
@@ -275,18 +265,10 @@ def recherche_avancee() -> ResponseWerkzeug | str:
275265
clients = (
276266
db_session.query(Client)
277267
.join(Client.tels)
278-
.filter(
279-
Client.is_active == (
280-
(True or False) if (
281-
search_is_inactive \
282-
and (
283-
'1' in session['habilitations'] or \
284-
'2' in session['habilitations']
285-
)
286-
) else True
287-
),
268+
.filter(
269+
search_active_clients,
288270
Telephone.telephone.ilike(f'%{search_term}%'),
289-
Telephone.is_inactive == ((True or False) if search_is_inactive else False)
271+
search_inactive_phone
290272
)
291273
.distinct()
292274
.all()
@@ -297,22 +279,13 @@ def recherche_avancee() -> ResponseWerkzeug | str:
297279
db_session.query(Client)
298280
.join(Client.adresses)
299281
.filter(
300-
Client.is_active == (
301-
(True or False) if (
302-
search_is_inactive \
303-
and (
304-
'1' in session['habilitations'] or \
305-
'2' in session['habilitations']
306-
)
307-
) else True
308-
),
309-
Adresse.is_inactive == ((True or False) if search_is_inactive else True),
282+
search_active_clients,
310283
or_(
311284
Adresse.adresse_l1.ilike(f'%{search_term}%'),
312285
Adresse.adresse_l2.ilike(f'%{search_term}%'),
313286
Adresse.code_postal.ilike(f'%{search_term}%'),
314287
Adresse.ville.ilike(f'%{search_term}%'),
315-
Adresse.is_inactive == ((True or False) if search_is_inactive else False)
288+
search_inactive_address
316289
)
317290
)
318291
.distinct()
@@ -752,6 +725,7 @@ def del_phone(id_client: int, id_phone: int) -> ResponseWerkzeug:
752725
phone_obj.is_inactive = True
753726
phone_obj.modified_by = session.get('pseudo', 'N/A')
754727
phone_obj.is_principal = False # Ne peut plus être principal
728+
phone_obj.modified_at = datetime.now()
755729
db_session.commit()
756730
return redirect(url_for(Constants.return_pages('clients', 'detail'),
757731
id_client=id_client, log=True, tab='phone',
@@ -761,7 +735,6 @@ def del_phone(id_client: int, id_phone: int) -> ResponseWerkzeug:
761735
id_client=id_client, log=True, tab='phone',
762736
error_message=Constants.messages('error_500', 'default') + f" : {e}"))
763737

764-
765738
@clients_bp.route('/<int:id_client>/add-email/', methods=['POST'])
766739
@validate_habilitation(CLIENTS)
767740
def add_email(id_client: int) -> ResponseWerkzeug:
@@ -931,6 +904,8 @@ def del_email(id_client: int, id_mail: int) -> ResponseWerkzeug:
931904
# Suppression logique
932905
mail_obj.is_inactive = True
933906
mail_obj.modified_by = session.get('pseudo', 'N/A')
907+
mail_obj.is_principal = False # Ne peut plus être principal
908+
mail_obj.modified_at = datetime.now()
934909
db_session.commit()
935910

936911
return redirect(url_for(Constants.return_pages('clients', 'detail'), tab='mail',
@@ -1122,3 +1097,143 @@ def del_address(id_client: int, id_address: int) -> ResponseWerkzeug:
11221097
id_client=id_client, log=True, tab='add',
11231098
error_message=Constants.messages('error_500', 'default') + f" : {e}"))
11241099

1100+
@clients_bp.route('/<int:id_client>/activate-address-<int:id_address>/', methods=['POST'])
1101+
@validate_habilitation([CLIENTS, ADMINISTRATEUR], _and=True)
1102+
@validate_habilitation([CLIENTS, GESTIONNAIRE], _and=True)
1103+
def activate_address(id_client: int, id_address: int) -> ResponseWerkzeug:
1104+
"""
1105+
Réactivation d'une adresse inactive pour un client.
1106+
1107+
Endpoint REST pour réactiver une adresse précédemment supprimée (logiquement) d'un client.
1108+
1109+
Form Data:
1110+
- id_client (int): ID du client
1111+
- id_address (int): ID de l'adresse à réactiver
1112+
1113+
Returns:
1114+
Redirect: Vers la page de détails du client avec message de succès ou d'erreur
1115+
"""
1116+
db_session = get_db_session()
1117+
1118+
try:
1119+
# Récupération et validation des données
1120+
client = (db_session
1121+
.query(Client).join(Client.adresses)
1122+
.options(
1123+
contains_eager(Client.adresses)
1124+
).filter(
1125+
Client.id == id_client, Adresse.id == id_address
1126+
).first()
1127+
)
1128+
address_obj = client.adresses[0] if client and client.adresses else None
1129+
if not address_obj: return redirect(url_for(Constants.return_pages('clients', 'detail'),
1130+
id_client=id_client, tab='add',
1131+
error_message=Constants.messages('address', 'not_found')))
1132+
1133+
# Réactivation logique
1134+
address_obj.is_inactive = False
1135+
address_obj.modified_by = session.get('pseudo', 'N/A')
1136+
address_obj.modified_at = datetime.now()
1137+
db_session.commit()
1138+
1139+
return redirect(url_for(Constants.return_pages('clients', 'detail'), tab='add',
1140+
id_client=id_client, success_message=Constants.messages('address', 'reactivated')))
1141+
1142+
except Exception as e:
1143+
return redirect(url_for(Constants.return_pages('clients', 'detail'),
1144+
id_client=id_client, log=True, tab='add',
1145+
error_message=Constants.messages('error_500', 'default') + f" : {e}"))
1146+
1147+
@clients_bp.route('/<int:id_client>/activate-phone-<int:id_phone>/', methods=['POST'])
1148+
@validate_habilitation([CLIENTS, ADMINISTRATEUR], _and=True)
1149+
@validate_habilitation([CLIENTS, GESTIONNAIRE], _and=True)
1150+
def activate_phone(id_client: int, id_phone: int) -> ResponseWerkzeug:
1151+
"""
1152+
Réactivation d'un numéro de téléphone inactif pour un client.
1153+
1154+
Endpoint REST pour réactiver un numéro de téléphone précédemment supprimé (logiquement) d'un client.
1155+
1156+
Form Data:
1157+
- id_client (int): ID du client
1158+
- id_phone (int): ID du téléphone à réactiver
1159+
1160+
Returns:
1161+
Redirect: Vers la page de détails du client avec message de succès ou d'erreur
1162+
"""
1163+
db_session = get_db_session()
1164+
1165+
try:
1166+
# Récupération et validation des données
1167+
client = (db_session
1168+
.query(Client).join(Client.tels)
1169+
.options(
1170+
contains_eager(Client.tels)
1171+
).filter(
1172+
Client.id == id_client, Telephone.id == id_phone
1173+
).first()
1174+
)
1175+
phone_obj = client.tels[0] if client and client.tels else None
1176+
if not phone_obj: return redirect(url_for(Constants.return_pages('clients', 'detail'),
1177+
id_client=id_client, tab='phone',
1178+
error_message=Constants.messages('phone', 'not_found')))
1179+
1180+
# Réactivation logique
1181+
phone_obj.is_inactive = False
1182+
phone_obj.modified_by = session.get('pseudo', 'N/A')
1183+
phone_obj.modified_at = datetime.now()
1184+
db_session.commit()
1185+
1186+
return redirect(url_for(Constants.return_pages('clients', 'detail'), tab='phone',
1187+
id_client=id_client, success_message=Constants.messages('phone', 'reactivated')))
1188+
1189+
except Exception as e:
1190+
return redirect(url_for(Constants.return_pages('clients', 'detail'),
1191+
id_client=id_client, log=True, tab='phone',
1192+
error_message=Constants.messages('error_500', 'default') + f" : {e}"))
1193+
1194+
@clients_bp.route('/<int:id_client>/activate-email-<int:id_mail>/', methods=['POST'])
1195+
@validate_habilitation([CLIENTS, ADMINISTRATEUR], _and=True)
1196+
@validate_habilitation([CLIENTS, GESTIONNAIRE], _and=True)
1197+
def activate_email(id_client: int, id_mail: int) -> ResponseWerkzeug:
1198+
"""
1199+
Réactivation d'une adresse email inactive pour un client.
1200+
1201+
Endpoint REST pour réactiver une adresse email précédemment supprimée (logiquement) d'un client.
1202+
1203+
Form Data:
1204+
- id_client (int): ID du client
1205+
- id_mail (int): ID de l'email à réactiver
1206+
1207+
Returns:
1208+
Redirect: Vers la page de détails du client avec message de succès ou d'erreur
1209+
"""
1210+
db_session = get_db_session()
1211+
1212+
try:
1213+
# Récupération et validation des données
1214+
client = (db_session
1215+
.query(Client).join(Client.mails)
1216+
.options(
1217+
contains_eager(Client.mails)
1218+
).filter(
1219+
Client.id == id_client, Mail.id == id_mail
1220+
).first()
1221+
)
1222+
mail_obj = client.mails[0] if client and client.mails else None
1223+
if not mail_obj: return redirect(url_for(Constants.return_pages('clients', 'detail'),
1224+
id_client=id_client, tab='mail',
1225+
error_message=Constants.messages('email', 'not_found')))
1226+
1227+
# Réactivation logique
1228+
mail_obj.is_inactive = False
1229+
mail_obj.modified_by = session.get('pseudo', 'N/A')
1230+
mail_obj.modified_at = datetime.now()
1231+
db_session.commit()
1232+
1233+
return redirect(url_for(Constants.return_pages('clients', 'detail'), tab='mail',
1234+
id_client=id_client, success_message=Constants.messages('email', 'reactivated')))
1235+
1236+
except Exception as e:
1237+
return redirect(url_for(Constants.return_pages('clients', 'detail'),
1238+
id_client=id_client, log=True, tab='mail',
1239+
error_message=Constants.messages('error_500', 'default') + f" : {e}"))

app_acfc/habilitations.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
Version : 1.0
2323
'''
2424
from functools import wraps
25-
from typing import Callable, Any
26-
from flask import session
25+
from typing import Callable, Any, List
26+
from flask import session, g
2727
from app_acfc.modeles import PrepareTemplates
2828

2929
# Définition des niveaux d'habilitation
@@ -35,33 +35,40 @@
3535
DEVELOPPEMENT_IT = '6'
3636
FORCE_DE_VENTE = '7'
3737

38-
# TODO: Ajouter la possibilité d'insérer une liste dans required_habilitation pour vérifier plusieurs habilitations et un symbole logique (AND/OR)
39-
40-
def validate_habilitation(required_habilitation: str) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
38+
def validate_habilitation(required_habilitation: List[str] | str, _and: bool = False) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
4139
"""
4240
Décorateur pour valider si l'utilisateur connecté possède une habilitation spécifique.
4341
4442
Args:
45-
required_habilitation (str): Habilitation requise (ex: '3').
43+
required_habilitation (str): Habilitation requise (ex: '3') : un seul caractère.
44+
Utiliser les constantes définies ci-dessus pour plus de clarté.
45+
_and (bool): Si True, toutes les habilitations dans la liste doivent être présentes
4646
4747
Returns:
4848
Callable[[Callable[..., Any]], Callable[..., Any]]: La fonction décorée ou une réponse d'erreur si l'habilitation est manquante.
4949
"""
5050
def decorator(function: Callable[..., Any]) -> Callable[..., Any]:
5151
@wraps(function)
5252
def wrapper(*args: Any, **kwargs: Any) -> Any:
53+
# Si l'habilitation a déjà été validée dans cette requête, on passe directement
54+
if getattr(g, 'habilitation_validated', False):
55+
return function(*args, **kwargs)
5356
# Vérifie si l'utilisateur est connecté et possède une habilitation
5457
habilitations = session.get('habilitations', '') # Exemple : '123'
5558
validate_habilitation = False
56-
for habilitation in habilitations:
57-
if habilitation == required_habilitation:
58-
validate_habilitation = True
59-
break
59+
if isinstance(required_habilitation, list):
60+
if _and:
61+
validate_habilitation = all(habilitation in habilitations for habilitation in required_habilitation)
62+
else:
63+
validate_habilitation = any(habilitation in habilitations for habilitation in required_habilitation)
64+
else:
65+
validate_habilitation = required_habilitation in habilitations
6066
if not validate_habilitation:
6167
message = f'Accès refusé. Habilitation requise : {required_habilitation}.' \
6268
+ f'\nVotre habilitation actuelle : {session.get('habilitations', 'inconnu')}.' \
6369
+ f'\nUtilisateur : {session.get("pseudo", "Anonyme")}.'
6470
return PrepareTemplates.error_4xx(status_code=403, status_message=message, log=True)
71+
g.habilitation_validated = True # Marque que l'habilitation a été validée
6572
return function(*args, **kwargs)
6673
return wrapper
6774
return decorator

0 commit comments

Comments
 (0)