#!/bin/bash /usr/scripts/python.sh # -*- coding: utf-8 -*- u""" Copyright (C) Valentin Samir Licence : GPLv3 """ import sys import time import datetime import dateutil.relativedelta if '/usr/scripts' not in sys.path: sys.path.append('/usr/scripts') import config.cotisation import lc_ldap.objets as objets import lc_ldap.attributs as attributs from lc_ldap.attributs import UniquenessError from lc_ldap import crans_utils import proprio from CPS import TailCall, tailcaller, Continue class Dialog(proprio.Dialog): """ Classe Dialog spécifique aux adhérents pour gest_crans_lc """ def modif_adherent_blacklist(self, adherent, cont): """Raccourci vers edit_blacklist spécifique aux adherent""" return self.edit_blacklist(obj=adherent, title="Éditions des blacklist de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), update_obj='adherent', cont=cont) def modif_adherent(self, cont, adherent=None, tag=None): """Menu d'édition d'un adhérent""" if adherent is None: adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont) a = attributs menu_droits = { 'Administratif' : [a.cableur, a.nounou], 'Personnel':[a.cableur, a.nounou, a.soi], 'Études':[a.nounou, a.soi, a.cableur], 'Chambre':[a.cableur, a.nounou], 'Compte':[a.cableur, a.nounou], 'GPGFingerprint' : [a.nounou, a.soi], 'Remarques' : [a.cableur, a.nounou], 'Droits':[a.nounou, a.bureau], 'Blackliste':[a.bureau, a.nounou], 'Vente':[a.cableur, a.nounou], 'Supprimer':[a.nounou, a.bureau], } menu = { 'Administratif' : {'text' : "Adhésion, chartes", "callback":self.adherent_administratif}, 'Personnel' : {'text' : "Nom, prénom, téléphone, et mail de contact", 'callback':self.adherent_personnel}, 'Études' : {'text' : "Étude en cours", "callback":self.adherent_etudes}, 'Chambre' : {'text' : 'Déménagement', "callback":self.adherent_chambre}, 'Compte' : {'text' : "Gestion du compte crans", "adherent":"proprio", "callback":TailCall(self.proprio_compte, update_obj='adherent'), 'help':"Création/Suppression/Activation/Désactivation du compte, gestion des alias mails crans du compte"}, 'GPGFingerprint' : {'text':'Ajouter ou supprimer une empeinte GPG', 'attribut':attributs.gpgFingerprint}, 'Remarques' : {'text':'Ajouter ou supprimer une remarque à cet adhérent', 'attribut':attributs.info}, 'Droits' : {'text':"Modifier les droits alloués à cet adhérent", "callback":self.adherent_droits}, 'Blackliste' : {'text': 'Modifier les blacklist de cet adhérent', 'callback':self.modif_adherent_blacklist}, 'Vente' : {'text':"Chargement solde crans, vente de cable ou adaptateur ethernet ou autre", "adherent":"proprio", "callback":self.proprio_vente}, 'Supprimer' : {'text':"Supprimer l'adhérent de la base de donnée", 'callback':TailCall(self.delete_adherent, del_cont=cont(proprio=None))}, } menu_order = ['Administratif', 'Personnel', 'Études', 'Chambre', 'Compte'] menu_compte_crans = ['Droits'] menu_end = ['GPGFingerprint', 'Remarques', 'Blackliste', 'Vente', 'Supprimer'] if "cransAccount" in adherent['objectClass']: menu_order.extend(menu_compte_crans) menu_order.extend(menu_end) def box(default_item=None): choices = [] for key in menu_order: if self.has_right(menu_droits[key], adherent): choices.append((key, menu[key]['text'], menu[key].get('help', ""))) return self.dialog.menu( "Que souhaitez vous modifier ?", width=0, height=0, menu_height=0, timeout=self.timeout, item_help=1, default_item=str(default_item), title="Modification de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), scrollbar=True, cancel_label="Retour", backtitle=self._connected_as(), choices=choices) def todo(tag, menu, adherent, cont_ret): if not tag in menu_order: raise Continue(cont_ret) else: if 'callback' in menu[tag]: raise Continue(TailCall(menu[tag]['callback'], cont=cont_ret, **{menu[tag].get('adherent', 'adherent'):adherent})) elif 'attribut' in menu[tag]: raise Continue(TailCall(self.modif_adherent_attributs, adherent=adherent, cont=cont_ret, attr=menu[tag]['attribut'].ldap_name)) else: raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) (code, tag) = self.handle_dialog(cont, box, tag) cont_ret = TailCall(self.modif_adherent, cont=cont, adherent=adherent, tag=tag) return self.handle_dialog_result( code=code, output=tag, cancel_cont=cont(proprio=adherent), error_cont=cont_ret, codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, cont_ret])] ) def modif_adherent_attributs(self, adherent, attr, cont): """Juste un raccourci vers edit_attributs spécifique aux adherents""" return self.edit_attributs(obj=adherent, update_obj='adherent', attr=attr, title="Modification de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), cont=cont) def adherent_administratif(self, cont, adherent, default_item=None): """Menu de gestion du compte crans d'un proprio""" a = attributs menu_droits = { "Adhésion": [a.cableur, a.nounou], 'Connexion': [a.cableur, a.nounou], "Charte MA" : [a.nounou, a.bureau], } menu = { "Adhésion" : {"text":"Pour toute réadhésion *sans* connexion.", "help":"", "callback":self.adherent_adhesion}, 'Connexion' : {'text': "Mise à jour de l'accès Internet (effectue la réadhésion si besoin)", "help":"", 'callback':self.adherent_connexion}, "Charte MA" : {"text" : "Signature de la charte des membres actifs", "help":'', "callback":self.adherent_charte}, } menu_order = ["Adhésion", 'Connexion'] menu_order.append("Charte MA") def box(default_item=None): return self.dialog.menu( "Quelle action administrative effectuer", width=0, height=0, timeout=self.timeout, item_help=1, default_item=str(default_item), title="Gestion administrative de %s %s" % (adherent.get('prenom', [''])[0], adherent["nom"][0]), scrollbar=True, cancel_label="Retour", backtitle=self._connected_as(), choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], adherent)]) def todo(tag, menu, adherent, self_cont): if not tag in menu_order: raise Continue(self_cont) elif 'callback' in menu[tag]: raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, adherent=adherent)) else: raise EnvironmentError("Il n'y a pas de champs 'callback' pour le tag %s" % tag) (code, tag) = self.handle_dialog(cont, box, default_item) self_cont = TailCall(self.adherent_administratif, adherent=adherent, cont=cont, default_item=tag) return self.handle_dialog_result( code=code, output=tag, cancel_cont=cont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, self_cont])] ) def adherent_adhesion_connexion_crediter(self, facture, adherent): adhesion = False connexion = False if [a for a in facture["article"] if a["code"] == "ADH"]: adhesion = True if [a for a in facture["article"] if a["code"].startswith("CAI")]: connexion = True # Appeler créditer va créditer ou débiter le solde, sauver le proprio et créer la facture if facture.mode == 'rw': facture.crediter() else: with self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] as facture: facture.crediter() with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: if facture["finAdhesion"]: adherent["finAdhesion"].append(facture["finAdhesion"][0]) if facture["debutAdhesion"]: adherent["debutAdhesion"].append(facture["debutAdhesion"][0]) if facture["debutConnexion"]: adherent["debutConnexion"].append(facture["debutConnexion"][0]) if facture["finConnexion"]: adherent["finConnexion"].append(facture["finConnexion"][0]) adherent.validate_changes() adherent.history_gen() adherent.save() try: if adhesion and not connexion: self.dialog.msgbox(text=u"Adhésion effectué avec success", title=u"Adhésion terminé", width=0, height=0, timeout=self.timeout) elif not adhesion and connexion: self.dialog.msgbox(text=u"Connexion prolongée avec success", title=u"Connexion prolongée", width=0, height=0, timeout=self.timeout) elif adhesion and connexion: self.dialog.msgbox(text=u"Adhésion effectué et connexion prolongée avec success", title=u"Connexion & Adhésion", width=0, height=0, timeout=self.timeout) except KeyboardInterrupt: pass if facture['modePaiement'][0] == "solde": try: self.dialog.msgbox(text=u"Le solde de l'adhérent à bien été débité", title="Solde débité", width=0, height=0, timeout=self.timeout) except KeyboardInterrupt: pass return adherent def adherent_adhesion(self, cont, adherent, cancel_cont=None, tag_paiment=None, comment_paiement=None, crediter=True, facture=None): """ Gestion de l'adhésion à l'association d'un adhérent Si cancel_cont est à None, cont est utilisé en cas d'annulation tag_paiment : un mode de paiement comment_paiement : un commentaire pour la facture crediter : Doit-on ou non créditer tout de suite la facture facture : Doit-t-on éditer une facture existante """ # Boite si on ne peux pas réahdérer def box_already(end): self.dialog.msgbox("Actuellement adhérent jusqu'au %s.\nMerci de revenir lorsqu'il restera moins de %s jours avant la fin." % (end, config.cotisation.delai_readh_jour), width=0, height=0, timeout=self.timeout, ) # Boite de confirmation à l'ahésion def box_adherer(end=None): if end: adherer = self.confirm(text="Adhésion jusqu'au %s. Réadhérer ?" % end, title="Adhésion de %s %s" % (adherent.get("prenom", [''])[0], adherent["nom"][0])) else: adherer = self.confirm(text="Adhésion pour un an, continuer ?", title="Adhésion de %s %s" % (adherent.get("prenom", [''])[0], adherent["nom"][0])) return adherer # Suppression d'une facture si elle est généré mais non validé def delete_facture(facture, cont): if facture: with self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] as facture: facture.delete() raise Continue(cont) # Génération de la facture pour adhésion def paiement(tag_paiement, adherent, finadhesion, comment, facture, cancel_cont, cont): now = crans_utils.localized_datetime() new_finadhesion = max(finadhesion, now).replace(year=max(finadhesion, now).year + 1) new_debutadhesion = now if facture: facture = self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] to_create = False else: facture = self.conn.newFacture(adherent.dn, {}) to_create = True with facture as facture: facture['modePaiement'] = unicode(tag_paiement, 'utf-8') facture['info'] = unicode(comment, 'utf-8') facture['article'].append(config.cotisation.dico_adh) facture["finAdhesion"] = new_finadhesion facture["debutAdhesion"] = new_debutadhesion # On peut retarder le credit pour ajouter des contribution pour la connexion internet à la facture if crediter: if self.confirm_item(item=facture, text=u"Le paiement de %sEUR a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), tag_paiement), title=u"Validation du paiement", timeout=self.timeout): # Appeler créditer va créditer ou débiter le solde, sauver le proprio et créer la facture adherent = self.adherent_adhesion_connexion_crediter(facture, adherent) else: if not self.confirm(text=u"Le paiement n'a pas été reçue.\n Annuler ?", title="Annulation de l'adhésion", defaultno=True): raise Continue(cancel_cont) else: if to_create: facture.create() else: facture.validate_changes() facture.history_gen() facture.save() raise Continue(cont(facture=facture)) raise Continue(cont(adherent=adherent)) now = crans_utils.localized_datetime() try: finadhesion = adherent.fin_adhesion().value except AttributeError: finadhesion = now # Si fin de l'adhésion trop loin dans le futur, rien a faire if finadhesion and (finadhesion - now).days > config.cotisation.delai_readh_jour: self.handle_dialog(cancel_cont if cancel_cont else cont, box_already, finadhesion) raise Continue(cancel_cont if cancel_cont else cont) # Sinon, si on accepte l'adhésion elif tag_paiment or self.handle_dialog(cont, box_adherer, finadhesion): self_cont = TailCall(self.adherent_adhesion, cont=cont, adherent=adherent, cancel_cont=cancel_cont, tag_paiment=tag_paiment, comment_paiement=comment_paiement, crediter=crediter, facture=facture) # On choisi un mode de paiement if not tag_paiment or not comment_paiement: return self.proprio_choose_paiement(proprio=adherent, cont=self_cont, cancel_cont=TailCall(delete_facture, facture, cancel_cont if cancel_cont else cont)) else: lcont = self_cont.copy() lcont(comment_paiement=None) return self.handle_dialog_result( code=self.dialog.DIALOG_OK, output=[], cancel_cont=lcont, error_cont=lcont, codes_todo=[([self.dialog.DIALOG_OK], paiement, [tag_paiment, adherent, finadhesion, comment_paiement, facture, lcont, cont])] ) else: return self.handle_dialog_result( code=self.dialog.DIALOG_OK, output=[], cancel_cont=None, error_cont=cancel_cont if cancel_cont else cont, codes_todo=[([self.dialog.DIALOG_OK], delete_facture, [facture, cancel_cont if cancel_cont else cont])] ) def adherent_connexion(self, cont, adherent, cancel_cont=None, facture=None, mois=None, default_item=None, tag_paiment=None, comment_paiement=None): """ Prolonger la connexion d'un adhérent Si cancel_cont est à None, cont sera utilisé facture : doit-on éditer une facture existante mois : de combien de mois prolonger la connexion default_item : le nombre de mois selectionné par defaut tag_paiment : le mode de paiement a utiliser si on crée une facture comment_paiement : un commentaire à mettre si on crée une facture """ menu = { "An": {'text':"Prolonger d'un an (pour %s€)" % config.cotisation.plafond_contribution, 'callback':TailCall(self.adherent_connexion, cont, adherent, cancel_cont, facture, 12, default_item, tag_paiment, comment_paiement)}, "NC": {'text':"Pas de connexion", 'callback':TailCall(self.adherent_connexion, cont, adherent, cancel_cont, facture, 12, default_item, tag_paiment, comment_paiement)} } menu_order = ["An"] for i in range(1, config.cotisation.duree_conn_plafond): if config.cotisation.contribution * i < config.cotisation.plafond_contribution: menu["%s mois" % i] = {'text':"Prolonger de %s mois (pour %s€)" % (i, config.cotisation.contribution * i), 'callback':TailCall(self.adherent_connexion, cont, adherent, cancel_cont, facture, i, default_item, tag_paiment, comment_paiement)} menu_order.append("%s mois" % i) if facture: menu_order.append("NC") # Une boite pour choisir un nombre de mois pour prolonger la connexion def box(finconnexion, default_item=None): t_end = finconnexion return self.dialog.menu( "Connexion jusqu'au %s" % t_end if finconnexion else "N'a jamais été connecté", width=0, height=0, menu_height=0, timeout=self.timeout, item_help=0, default_item=str(default_item), title="Connexion de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), scrollbar=True, cancel_label="Retour", backtitle=self._connected_as(), choices=[(k, menu[k]['text']) for k in menu_order]) # Génération et crédit de la facture def todo(adherent, mois, finadhesion, finconnexion, cancel_cont, cont, facture=None, tag_paiment=None, comment=None): now = crans_utils.localized_datetime() new_debutconnexion = max(now, finconnexion) con_month = new_debutconnexion.month con_year = new_debutconnexion.year new_finconnexion = max(finconnexion, now).replace(year=con_year + ((con_month + mois) // 13), month= (con_month + mois - 1) % 12 + 1) if (new_finconnexion - finadhesion.value).days > 0: t_end_adh = finadhesion.value t_end_conn = finconnexion if (new_finconnexion - finadhesion.value).days > 30: raise ValueError("Impossible de prolonger la connexion jusqu'au %s plus d'un mois après la fin de l'adhésion au %s" % (t_end_conn, t_end_adh)) else: if not self.confirm("La fin de la connexion de l'adhérent (%s) tombera après la fin de son adhésion (%s).\n" \ "S'il veut en profiter, il lui faudra éventuellement réadhérer. Continuer ?" % (t_end_conn, t_end_adh), title="Prolongement de connexon"): raise Continue(cancel_cont) # On édite une facture existante if facture: with self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] as facture: if mois: facture["finConnexion"] = new_finconnexion facture["debutConnexion"] = new_debutconnexion facture["article"].append(config.cotisation.dico_cotis(mois)) if self.confirm_item(item=facture, text=u"Le paiement de %sEUR a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), facture['modePaiement'][0]), title=u"Validation du paiement", timeout=self.timeout): # Appeler créditer va créditer ou débiter le solde, sauver le proprio et crée adherent = self.adherent_adhesion_connexion_crediter(facture, adherent) else: if not self.confirm(text=u"Le paiement n'a pas été reçue.\n Annuler ?", title="Annulation de l'adhésion", defaultno=True): raise Continue(cancel_cont) else: facture.delete() else: if tag_paiment is None or comment is None: raise ValueError("Il faut définir une méthode de paiement avec un commentaire") if not mois: raise ValueError("Il faut prolonger la connexion d'un nombre de mois strictement positif") with self.conn.newFacture(adherent.dn, {}) as facture: facture['modePaiement'] = unicode(tag_paiment, 'utf-8') facture['article'].append(config.cotisation.dico_cotis(mois)) facture['info'] = unicode(comment, 'utf-8') facture["finConnexion"] = new_finconnexion facture["debutConnexion"] = new_debutconnexion if self.confirm_item(item=facture, text=u"Le paiement de %sEUR a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), tag_paiment), title=u"Validation du paiement", timeout=self.timeout): # Appeler créditer va créditer ou débiter le solde, sauver le proprio et crée adherent = self.adherent_adhesion_connexion_crediter(facture, adherent) else: if not self.confirm(text=u"Le paiement n'a pas été reçue.\n Annuler ?", title="Annulation de l'adhésion", defaultno=True): raise Continue(cancel_cont) raise Continue(cont) def todo_mois(tag, self_cont): if tag == 'An': mois = 12 elif tag == 'NC': mois = 0 else: mois = int(tag.split(' ', 1)[0]) raise Continue(self_cont(mois=mois, default_item=tag)) self_cont = TailCall(self.adherent_connexion, cont=cont, adherent=adherent, cancel_cont=cancel_cont, facture=facture, mois=mois, default_item=default_item, tag_paiment=tag_paiment, comment_paiement=comment_paiement) finadhesion = adherent.fin_adhesion() # Si on édite une facture, on prolonge la date de finadhesion if facture and facture["finAdhesion"]: finadhesion = max(finadhesion, facture["finAdhesion"][0]) finconnexion = adherent.fin_connexion() # Si l'adhésion fini avant la connexion if finadhesion <= crans_utils.localized_datetime() or finadhesion <= finconnexion: if finadhesion: t_end_adh = finadhesion # Si l'adhésion est déjà fini if finadhesion <= crans_utils.localized_datetime(): self.dialog.msgbox(text=u"L'adhésion a expiré le %s, il va falloir réadhérer d'abord (10€)" % t_end_adh, title="Réadhésion nécessaire", width=0, height=0, timeout=self.timeout) # Sinon si elle fini avant la fin de la connexion courante elif finadhesion < finconnexion: t_end_conn = finconnexion self.dialog.msgbox(text=u"L'adhésion de termine le %s, avant la fin de la connexion le %s, il va falloir réadhérer d'abord (10€)" % (t_end_adh, t_end_conn), title="Réadhésion nécessaire", width=0, height=0, timeout=self.timeout) # Échouera si on essaie de prolonger la connexion au dela de l'adhésion et que l'adhésion est encore valable plus de quinze jours return self.adherent_adhesion(cont=self_cont, cancel_cont=cont, adherent=adherent, crediter=False) # Si on édite une facture, elle vient actuellement forcement de adherent_adhesion if facture and cancel_cont is None: cancel_cont = TailCall(self.adherent_adhesion, cont=self_cont, adherent=adherent, cancel_cont=cont, tag_paiment=str(facture['modePaiement'][0]) if facture['modePaiement'] else None, comment_paiement=None, crediter=False, facture=facture) self_cont(cancel_cont=cancel_cont) # On choisi le nombre de mois pour prolonger la connexion if mois is None: (code, tag) = self.handle_dialog(cont, box, finconnexion, default_item) return self.handle_dialog_result( code=code, output=[], cancel_cont=cancel_cont if cancel_cont else cont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], todo_mois, [tag, self_cont])] ) # Si on connait le moyen de paiement (il peut être a l'intérieure de la facture existante) elif tag_paiment or facture: lcont = self_cont.copy() if facture: lcont(mois=None) else: lcont(tag_paiment=None) return self.handle_dialog_result( code=self.dialog.DIALOG_OK, output=[], cancel_cont=lcont, error_cont=lcont, codes_todo=[([self.dialog.DIALOG_OK], todo, [adherent, mois, finadhesion, finconnexion, lcont, cont, facture, tag_paiment, comment_paiement])] ) # Sinon, il faut choisir une méthode de paiement else: lcont = self_cont.copy() lcont(mois=None) return self.proprio_choose_paiement(proprio=adherent, cont=self_cont, cancel_cont=lcont) return cont def adherent_charte(self, cont, adherent): a = attributs attribs = [a.charteMA] return self.edit_boolean_attributs( obj=adherent, attribs=attribs, title="Signature de la charte membre actif de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), update_obj='adherent', cont=cont) def adherent_personnel(self, cont, adherent=None, fields_attrs={}, make_compte_crans=None, force_create=False): """ Permet d'éditer les nom, prénom et téléphone d'un adhérent, ou de créer un adhérent. Il faut encore trouver un moyen de récupérer des valeurs pour les attributs mail et chbre """ a = attributs # Quel sont les attributs ldap dont on veut afficher et # la taille du champs d'édition correspondant to_display = [(a.nom, 30), (a.prenom, 30), (a.tel, 30), (a.mail, 30)] non_empty = [a.nom, a.prenom, a.tel] input_type = {'default':0} # Quel séparateur on utilise pour les champs multivalué separateur = ' ' def box(make_compte_crans): if force_create and adherent is None and fields_attrs and make_compte_crans is not None: return (self.dialog.DIALOG_OK, [fields_attrs[a] for (a, l) in to_display], make_compte_crans) if adherent: attrs = dict((k, [str(a) for a in at]) for (k, at) in adherent.items()) if 'cransAccount' in adherent['objectClass']: input_type[attributs.mail] = 2 to_display.append((attributs.mailExt, 30)) else: attrs = {} if make_compte_crans is None: if self.dialog.yesno("Crééra-t-on un compte crans à l'utilisateur ?", timeout=self.timeout, title="Création d'un adhérent", width=50) == self.dialog.DIALOG_OK: input_type[attributs.mail] = 2 make_compte_crans = True to_display.append((attributs.mailExt, 30)) else: make_compte_crans = False fields = [( "%s %s:" % (a.legend, '(optionnel) ' if a.optional else ''), fields_attrs.get(a, separateur.join(attrs.get(a.ldap_name, [a.default] if a.default else []))), l+1, l, input_type.get(a, input_type['default']) ) for a,l in to_display] (code, tags) = self.dialog.form( text="", timeout=self.timeout, height=0, width=0, form_height=0, fields=fields, title="Création d'un adhérent" if adherent is None else "Édition des informations de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), backtitle="Gestion des adhérents du Crans") return (code, tags, make_compte_crans) def modif_adherent(adherent, attrs): with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: for (key, values) in attrs.items(): adherent[key] = values adherent.validate_changes() adherent.history_gen() adherent.save() return adherent def create_adherent(attrs, make_compte_crans, force_create, self_cont, cont): if not force_create: items = self.conn.search("(&(prenom=%s)(nom=%s))" % (attrs['prenom'], attrs['nom'])) if items: newadherent = self.select_one(items, title="Choisir un adhérant existant", text="Des adhérent avec les même noms et prénoms existent déjà, en utiliser un ?\n(Annuler pour continuer la création)", cont=self_cont(make_compte_crans=make_compte_crans, force_create=True)) raise Continue(cont(adherent=newadherent)) with self.conn.newAdherent({}) as adherent: delay = {} for (key, values) in attrs.items(): try: adherent[key] = values # En cas d'erreur, on a peut être besoin du compte crans except ValueError: delay[key] = values print delay # on récupère la chambre adherent = self.adherent_chambre_campus(success_cont=None, cont=self_cont(make_compte_crans=make_compte_crans), adherent=adherent, create=True) # Si c'est EXT, on demande une adresse complète if 'EXT' in adherent['chbre']: adherent = self.adherent_chambre_ext(keep_machine=True, keep_compte=True, success_cont=None, cont=self_cont(make_compte_crans=make_compte_crans), adherent=adherent, create=True) # Si compte crans à créer, on le crée. # On le met en dernier pour éviter de faire entrez plusieurs fois son mdp à l'adhérent # en cas d'erreur de la part du cableur if make_compte_crans: adherent = self.proprio_compte_create(proprio=adherent, cont=self_cont(make_compte_crans=None, force_create=False, adherent=None), update_obj='adherent', return_obj=True) # On réeaffecte les attributs de tout à l'heure for (key, values) in delay.items(): adherent[key] = values # On confirme la création if self.confirm_item(adherent, title="Créer l'adhérent suivant ?"): adherent.validate_changes() adherent.create() else: adherent = None return adherent def todo(to_display, non_empty, tags, adherent, separateur, make_compte_crans, force_create, self_cont, cont): attrs = {} # On traite les valeurs reçues for ((a, l), values) in zip(to_display, tags): if not values and a in non_empty: raise ValueError(u"%s ne devrait pas être vide" % a.legend) values = unicode(values, 'utf-8') # Si le champs n'est pas single value, on utilise separateur pour découper # et on ne garde que les valeurs non vides if not a.singlevalue: values = [v for v in values.split(separateur) if v] attrs[a.ldap_name] = values if adherent: adherent = modif_adherent(adherent, attrs) else: adherent = create_adherent(attrs, make_compte_crans, force_create, self_cont, cont) raise Continue(cont(adherent=adherent)) (code, tags, make_compte_crans) = self.handle_dialog(cont, box, make_compte_crans) # On prépare les fiels à afficher à l'utilisateur si une erreure à lieu # pendant le traitement des donnée (on n'éfface pas ce qui a déjà été entré # c'est au cableur de corriger ou d'annuler fields_attrs = dict((a, values) for ((a, l), values) in zip(to_display, tags)) retry_cont = TailCall(self.adherent_personnel, adherent=adherent, cont=cont, fields_attrs=fields_attrs) return self.handle_dialog_result( code=code, output=tags, cancel_cont=cont, error_cont=retry_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [to_display, non_empty, tags, adherent, separateur, make_compte_crans, force_create, retry_cont, cont])] ) @tailcaller def adherent_chambre_campus(self, success_cont, cont, adherent, create=False): """Permet de faire déménager d'adhérent sur le campus""" def box(): return self.dialog.inputbox( "chambre ?", title="%s de %s %s" % ("Création" if create else "Déménagement", adherent['prenom'][0], adherent["nom"][0]), cancel_label="Retour", width=50, timeout=self.timeout, ) def expulse_squatteur(adherent, chbre): with self.conn.search(u"chbre=%s" % chbre, mode='rw')[0] as squatteur: if self.confirm_item( item=squatteur, title="Chambre occupée", defaultno=True, text=u"L'adhérent ci-dessous occupé déjà la chambre %s :\n" % output, text_bottom=u"\nPasser la chambre de cet adhérent en chambre inconnue ?" ): squatteur['chbre'] = u'????' squatteur.validate_changes() squatteur.history_gen() squatteur.save() return True else: return False def set_chambre(adherent, chbre): try: adherent['postalAddress'] = [] adherent['chbre'] = unicode(output, 'utf-8') except UniquenessError: if expulse_squatteur(adherent, chbre): # La chambre est maintenant normalement libre adherent['chbre'] = unicode(output, 'utf-8') else: raise Continue(self_cont) return adherent def todo(chbre, adherent, self_cont, success_cont): if not output: raise Continue(self_cont) if output == "????": raise ValueError("Chambre ???? invalide") if create: return set_chambre(adherent, chbre) else: with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: adherent = set_chambre(adherent, chbre) adherent.validate_changes() adherent.history_gen() adherent.save() self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % output) raise Continue(success_cont(adherent=adherent)) (code, output) = self.handle_dialog(cont, box) self_cont = TailCall(self.adherent_chambre_campus, adherent=adherent, success_cont=success_cont, cont=cont, create=create) return self.handle_dialog_result( code=code, output=output, cancel_cont=cont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [output, adherent, self_cont, success_cont])] ) @tailcaller def adherent_chambre_ext(self, keep_machine, keep_compte, success_cont, cont, adherent, create=False): """Permet de faire déménager l'adhérent hors campus""" if keep_machine and not keep_compte: raise EnvironmentError("On ne devrait pas supprimer un compte crans et y laisser des machines") elif keep_machine and keep_compte: def box(values={}): form = [("Adresse", 40), ("Compl. adr.", 40), ("Code postal", 7), ("Ville", 16)] fields = [("%s :" % k, values.get(k, ""), l, 50) for (k, l) in form] return self.dialog.form( text="", timeout=self.timeout, height=0, width=0, form_height=0, fields=fields, title="Paramètres machine", backtitle="Gestion des machines du Crans") def todo(output, adherent, success_cont, cont): if not create: if self.dialog.yesno("changer l'adresse de l'adhérent pour %s ?" % ", ".join([o for o in output if o]), title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), defaultno=True, timeout=self.timeout) == self.dialog.DIALOG_OK: with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: adherent['postalAddress'] = [unicode(pa, 'utf-8') for pa in output] adherent['chbre'] = u'EXT' adherent.validate_changes() adherent.history_gen() adherent.save() self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines conservées") raise Continue(success_cont(adherent=adherent)) else: raise Continue(cont) else: adherent['postalAddress'] = [unicode(pa, 'utf-8') for pa in output] adherent['chbre'] = u'EXT' return adherent elif not keep_machine and keep_compte: if create: raise EnvironmentError("On ne crée pas un adhérent en lui supprimant des machines") def box(values={}): return (self.dialog.DIALOG_OK, "") def todo(output, adherent, success_cont, cont): if self.confirm_item( item=adherent, text=u"Supprimer toutes les machines de l'adhérent ci-dessous ?\n\n", text_bottom=u"\nLa chambre de l'adhérent passera de plus en EXT.", title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), defaultno=True): with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: for machine in adherent.machines(): with machine: machine.delete() adherent['chbre'] = u'EXT' adherent.validate_changes() adherent.history_gen() adherent.save() self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines supprimées") raise Continue(success_cont(adherent=adherent)) else: raise Continue(cont) elif not keep_machine and not keep_compte: if create: raise EnvironmentError("On ne crée pas un adhérent en lui supprimant des machines et un compte") def box(values={}): return (self.dialog.DIALOG_OK, "") def todo(output, adherent, success_cont, cont): if adherent.get('solde', [0])[0] > 0: self.dialog.msgbox("Solde de l'adhérent %s€ strictement positif, impossible de supprimer le compte\nRepasser le solde à 0€ pour supprimer le compte." % adherent.get('solde', [0])[0], title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), width=50, timeout=self.timeout) raise Continue(cont) elif self.confirm_item( item=adherent, text=u"Supprimer toutes les machines et du compte crans de l'adhérent ci-dessous ?\n\n", text_bottom=u"\nLa chambre de l'adhérent passera de plus en EXT.", title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), defaultno=True): with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: for machine in adherent.machines(): with machine: machine.delete() adherent['chbre'] = u'EXT' adherent.validate_changes() adherent.history_gen() adherent.save() # On supprime le compte crans (on essaye) def post_deletion(proprio, cont): if not "cransAccount" in proprio['objectClass']: self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines et compte crans supprimées") else: self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines supprimées et compte crans concervé") raise Continue(cont(adherent=proprio)) self.proprio_compte_delete(proprio=adherent, cont=TailCall(post_deletion, proprio=adherent, cont=success_cont(adherent=adherent)), force=True) else: raise Continue(cont) else: raise EnvironmentError("Impossible, on a fait tous les cas, python est buggué") (code, output) = self.handle_dialog(cont, box) self_cont = TailCall(self.adherent_chambre_ext, adherent=adherent, keep_machine=keep_machine, keep_compte=keep_compte, success_cont=success_cont, cont=cont) return self.handle_dialog_result( code=code, output=output, cancel_cont=cont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [output, adherent, success_cont, cont])] ) def adherent_chambre(self, adherent, cont, default_item=None): """Permet de faire déménager d'adhérent""" a = attributs menu_droits = { '0' : [a.cableur, a.nounou], '1' : [a.cableur, a.nounou], '2' : [a.cableur, a.nounou], '3' : [a.cableur, a.nounou], } menu = { "0": {'text':"Déménagement sur le campus", 'callback':self.adherent_chambre_campus, 'help': "Déménagement vers une chambre sur le campus, on ne demande que le bâtiment et la chambre"}, "1": {'text':"Déménagement à l'extérieur en conservant les machines", "callback": TailCall(self.adherent_chambre_ext, keep_machine=True, keep_compte=True), "help": "Pour concerver ses machines, il faut donner un adresse postale complète"}, "2": {'text':"Départ du campus en conservant son compte", "callback":TailCall(self.adherent_chambre_ext, keep_machine=False, keep_compte=True), "help":"Supprime les machines mais concerve le compte crans, l'adresse passe en EXT sans plus d'information"}, "3": {'text':"Départ du campus en supprimant son compte", "callback":TailCall(self.adherent_chambre_ext, keep_machine=False, keep_compte=False), "help":"Supprime les machines et le compte crans, l'adhérent reste dans la base de donnée, il est possible de mettre une redirection du mail crans vers une autre adresse dans bcfg2"}, } menu_order = [str(i) for i in range(4)] def box(default_item=None): return self.dialog.menu( "Quel est le type du déménagement ?", width=0, height=0, menu_height=0, timeout=self.timeout, item_help=1, default_item=str(default_item), title="Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), scrollbar=True, cancel_label="Retour", backtitle=self._connected_as(), choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], adherent)]) def todo(tag, menu, adherent, self_cont, cont): if not tag in menu_order: raise Continue(self_cont) else: raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, success_cont=cont, adherent=adherent)) (code, tag) = self.handle_dialog(cont, box, default_item) self_cont = TailCall(self.adherent_chambre, adherent=adherent, cont=cont, default_item=tag) return self.handle_dialog_result( code=code, output=tag, cancel_cont=cont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, self_cont, cont])] ) def create_adherent(self, cont): """Crée un adhérent et potentiellement son compte crans avec lui""" def mycont(adherent=None, **kwargs): if adherent: # Une fois l'adhérent créé, on vois s'il adhére/prend la connexion internet #adh_cont = TailCall(self.modif_adherent, cont=cont, adherent=adherent) conn_cont = TailCall(self.adherent_connexion, cont=cont(proprio=adherent), adherent=adherent) etude_cont = TailCall(self.adherent_etudes, cont=conn_cont, adherent=adherent) etude_cont(cancel_cont=etude_cont) # Comme on crée une facture, pas de retour possible conn_cont(cancel_cont=conn_cont) raise Continue(etude_cont) else: raise Continue(cont) return self.adherent_personnel(cont=TailCall(mycont)) def delete_adherent(self, cont, del_cont, adherent=None): """Permet la suppression d'un adhérent de la base ldap""" if adherent is None: adherent = self.select(["adherent"], "Recherche d'un adhérent pour supression", cont=cont) def todo(adherent): if self.confirm_item(item=adherent, title="Voulez vous vraiement supprimer l'adhérent ?", defaultno=True): with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: adherent.delete() self.dialog.msgbox("L'adherent a bien été supprimée", timeout=self.timeout, title="Suppression d'un adherent") raise Continue(del_cont(proprio=None)) else: raise Continue(cont) return self.handle_dialog_result( code=self.dialog.DIALOG_OK, output="", cancel_cont=cont(adherent=adherent), error_cont=cont(adherent=adherent), codes_todo=[([self.dialog.DIALOG_OK], todo, [adherent])] ) def adherent_droits(self, adherent, cont, choices_values=None): """Gestion des droits d'un adhérent""" def box(): return self.dialog.checklist( text="", height=0, width=0, list_height=0, choices=choices_values if choices_values else [(droit, "", 1 if droit in adherent["droits"] else 0) for droit in attributs.TOUS_DROITS], title="Droits de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), timeout=self.timeout ) def todo(droits, adherent, self_cont, cont): # Les vérifications de sécurité sont faites dans lc_ldap with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: adherent['droits'] = [unicode(d) for d in droits] adherent.validate_changes() adherent.history_gen() adherent.save() if adherent["uid"] and adherent["uid"][0] == self.conn.current_login: self.check_ldap() raise Continue(cont(adherent=adherent)) (code, droits) = self.handle_dialog(cont, box) self_cont = TailCall(self.adherent_droits, adherent=adherent, cont=cont, choices_values=[(d, "", 1 if d in droits else 0) for d in attributs.TOUS_DROITS]) return self.handle_dialog_result( code=code, output=droits, cancel_cont=cont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [droits, adherent, self_cont, cont])] ) def adherent_etudes(self, adherent, cont, cancel_cont=None, default_item=None, etablissement=None, annee=None, section=None): """Gestion des études de l'adhérent. etudes est un triplet (établissement, année, section)""" choices_etablissement = [ ('Autre', ''), ('ENS', 'École Normale Supérieure'), ('IUT Cachan', ''), ('Maximilien Sorre', ''), ('Gustave Eiffel', ''), ('EFREI', ''), ('ESTP', ''), ('P1', 'Université Panthéon Sorbonne'), ('P2', 'Université Panthéon Assas'), ('P3', 'Université de la Sorbonne Nouvelle'), ('P4', 'Université Paris Sorbonne'), ('P5', 'Université René Descartes'), ('P6', 'Université Pierre et Marie Curie'), ('P7', 'Université Paris Diderot'), ('P8', 'Université Vincennes Saint Denis'), ('P9', 'Université Paris Dauphine'), ('P10', 'Université de Nanterre'), ('P11', 'Université de Paris Sud (Orsay)'), ('P12', 'Université Val de Marne (Créteil)'), ('P13', 'Université Paris Nord'), ('IUFM', ''), ('Personnel ENS', "appartement ENS ou personnel CROUS"), ] LMD = ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7', 'P8', 'P9', 'P10', 'P11', 'P12', 'P13'] LM = ['EFREI'] LYCEE = ['Maximilien Sorre', 'Gustave Eiffel'] ANNEE = ['IUT Cachan', 'ESTP'] def choices(max_annee): c = [] if max_annee >= 1: c.append(('1', '1ère année')) for i in range(2, max_annee+1): c.append((str(i), "%sème année" % i)) c.append(('Autre', '')) return c choices_LMD = [ ('L1', 'Licence 1ère année'), ('L2', 'Licence 2ième année'), ('L3', 'Licence 3ième année'), ('M1', 'Master 1ère année'), ('M2', 'Master 2ième année'), ('D1', '1ère année de thèse'), ('D2', '2ième année de thèse'), ('D3', '3ième année de thèse'), ('Autre', ''), ] choices_LM = choices_LMD[:5] + choices_LMD[-1:] choices_ENS = [ ('1', 'Licence'), ('2', 'Master 1'), ('3', 'Agrégation'), ('4', 'Master 2'), ('5', '1ère année de thèse'), ('6', '2ième année de thèse'), ('7', '3ième année de thèse'), ('Autre', ''), ] section_ENS = [ ('A0', 'Informatique'), ('A1', 'Mathématiques'), ('A2', 'Physique fondamentale'), ("A'2", 'Physique appliquée'), ("A''2", 'Chimie'), ('A3', 'Biochimie'), ('B1', 'Mécanique'), ('B2', 'Génie civil'), ('B3', 'Génie mécanique'), ('B4', 'Génie électrique'), ('C', 'Art et création industrielle'), ('D2', 'Economie gestion'), ('D3', 'Sciences sociales'), ('E', 'Anglais'), ('Autre', ''), ] choices_LYCEE = [ ('Seconde',''), ('Première',''), ('Terminale',''), ('1/2', '1ère année de prépa'), ('3/2', '2nd année de prépa'), ('5/2', '3ième année de prépa'), ('Autre',''), ] def box_etablissement(default_item): if etablissement == 'Autre' or \ (etablissement is not None and etablissement not in [i[0] for i in choices_etablissement]) or \ (default_item is not None and default_item not in [i[0] for i in choices_etablissement]): return self.dialog.inputbox( text="Choisissez l'établissement :", title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), timeout=self.timeout, init=str(default_item) if default_item else "") else: return self.dialog.menu( "Choisissez l'établissement :", width=0, height=0, menu_height=0, timeout=self.timeout, item_help=0, default_item=str(default_item) if default_item else 'ENS', title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), scrollbar=True, cancel_label="Retour", backtitle=self._connected_as(), choices=choices_etablissement) def box_annee(default_item): if etablissement in LMD: box_choice = choices_LMD elif etablissement in LM: box_choice = choices_LM elif etablissement in LYCEE: box_choice = choices_LYCEE elif etablissement == 'ENS': box_choice = choices_ENS else: box_choice = choices(7) if not box_choice or annee == 'Autre' or \ (annee is not None and annee not in [i[0] for i in box_choice]) or \ (default_item is not None and default_item not in [i[0] for i in box_choice]): return self.dialog.inputbox( text="Choisissez l'année administrative :", title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), timeout=self.timeout, init=str(default_item) if default_item else "") else: return self.dialog.menu( "Choisissez l'année administrative :", width=0, height=0, menu_height=0, timeout=self.timeout, item_help=0, default_item=str(default_item), title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), scrollbar=True, cancel_label="Retour", backtitle=self._connected_as(), choices=box_choice) def box_section(default_item): if etablissement != 'ENS' or section == 'Autre' or \ (section and section not in [i[0] for i in section_ENS]) or \ (default_item is not None and default_item not in [i[0] for i in section_ENS]): return self.dialog.inputbox( text="Choisissez la section :", title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), timeout=self.timeout, init=str(default_item) if default_item else "") else: return self.dialog.menu( "Choisissez la section :", width=0, height=0, menu_height=0, timeout=self.timeout, item_help=0, default_item=str(default_item), title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]), scrollbar=True, cancel_label="Retour", backtitle=self._connected_as(), choices=section_ENS) self_cont = TailCall(self.adherent_etudes, adherent=adherent, cont=cont, cancel_cont=cancel_cont, default_item=default_item, etablissement=etablissement, annee=annee, section=section) if etablissement is None or etablissement == 'Autre': if not default_item and adherent["etudes"]: default_item = str(adherent["etudes"][0]) lcont = cancel_cont if cancel_cont else cont (code, etablissement) = self.handle_dialog(cancel_cont, box_etablissement, default_item) output = etablissement self_cont(default_item=etablissement) elif annee is None or annee == 'Autre': if not default_item and adherent["etudes"]: default_item = str(adherent["etudes"][1]) print default_item lcont = TailCall(self.adherent_etudes, adherent=adherent, cont=cont, cancel_cont=cancel_cont, default_item=etablissement, etablissement=None, annee=None, section=None) (code , annee) = self.handle_dialog(lcont, box_annee, default_item) output = annee self_cont(default_item=annee) elif section is None or section == 'Autre': if not default_item and adherent["etudes"]: default_item = str(adherent["etudes"][2]) lcont = TailCall(self.adherent_etudes, adherent=adherent, cont=cont, cancel_cont=cancel_cont, default_item=annee, etablissement=etablissement, annee=None, section=None) (code, section) = self.handle_dialog(lcont, box_section, default_item) output = section self_cont(default_item=section) else: output = "" lcont = TailCall(self.adherent_etudes, adherent=adherent, cont=cont, cancel_cont=cancel_cont, default_item=section, etablissement=etablissement, annee=annee, section=None) def todo(etablissement, annee, section, adherent, self_cont, cont): if etablissement is None or etablissement == 'Autre': raise Continue(self_cont(default_item=None, etablissement=etablissement)) elif annee is None or annee == 'Autre': raise Continue(self_cont(default_item=None, etablissement=etablissement, annee=annee)) elif section is None or section == 'Autre': raise Continue(self_cont(default_item=None, etablissement=etablissement, annee=annee, section=section)) else: if not adherent["etudes"] or adherent["etudes"][0] != etablissement or adherent["etudes"][1] != annee or adherent["etudes"][2] != section: with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent: adherent["etudes"] = [unicode(etablissement), unicode(annee), unicode(section)] adherent.validate_changes() adherent.history_gen() adherent.save() raise Continue(cont(adherent=adherent)) return self.handle_dialog_result( code=code, output=output, cancel_cont=lcont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [etablissement, annee, section, adherent, self_cont, cont])] )