#!/bin/bash /usr/scripts/python.sh # -*- coding: utf-8 -*- u""" Copyright (C) Valentin Samir Licence : GPLv3 """ import os import sys import copy import ldap if '/usr/scripts' not in sys.path: sys.path.append('/usr/scripts') from gestion.chgpass import check_password import gestion.config as config import gestion.config.factures import lc_ldap.objets as objets import lc_ldap.attributs as attributs import lc_ldap.crans_utils as lc_utils import machine import blacklist from CPS import TailCall, tailcaller, Continue class Dialog(machine.Dialog, blacklist.Dialog): def modif_proprio_attributs(self, proprio, attr, cont): """Juste un raccourci vers edit_attributs spécifique aux proprios""" return self.edit_attributs(obj=proprio, update_obj='proprio', attr=attr, title="Modification de %s %s" % (proprio.get('prenom', [''])[0], proprio['nom'][0]), cont=cont) @tailcaller def proprio_compte_create(self, proprio, cont, warning=True, guess_login=True, guess_pass=0, return_obj=False, update_obj='proprio'): """Permet de créer un compte crans à un proprio (club ou adhérent)""" def box_warning(warning, proprio, cont): # Affiche-t-on le warning sur la consutation de l'adresse crans if warning: if self.dialog.yesno( text="\Zr\Z1AVERTISSEMENT :\Zn \nL'adhérent devra impérativement consulter l'adresse mail associée\n\n\n\ZnContinuer ?", title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), defaultno=True, width=70, colors=True, timeout=self.timeout) != self.dialog.DIALOG_OK: raise Continue(cont) def get_login(guess_login, guess_pass, proprio, self_cont, cont): # Essaye-t-on de deviner le login à utiliser if not guess_login: (code, login) = self.dialog.inputbox( text="Le login doit faire au maximum %s caractères\nIl ne doit pas être un pseudo ou prénom mais doit être relié au nom de famille\nSeuls les caractères alphabétiques et le trait d'union sont autorisés" % config.maxlen_login, title="Choix du login pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), init=str(proprio['nom'][0]).lower(), width=60, height=10, timeout=self.timeout) if code != self.dialog.DIALOG_OK: raise Continue(cont) else: # Si oui, de quelle manière if guess_pass == 0: login = str(proprio['nom'][0]) elif guess_pass == 1 and proprio.get('prenom', [''])[0]: login = "%s%s" % (str(proprio['prenom'][0])[0], proprio['nom'][0]) # Si toutes les manières ont échoués, la prochaine fois, ça on n'essaye pas de deviner else: raise Continue(self_cont(warning=False, guess_login=False, guess_pass=2)) return login def create_compte(proprio, login, guess_login, self_cont, cont): try: proprio.compte(login=unicode(login, 'utf-8')) except ValueError: # Il y a eu une erreur, si on essaye de deviner, on essaye la manière suivante if guess_login: raise Continue(self_cont(warning=False, guess_login=True, guess_pass=guess_pass+1)) # Sinon on propage l'erreur pour l'afficher à l'utilisateur else: raise self.dialog.msgbox( text="Le compte ne sera créé que lors de l'enregistrement des données\n\nL'adresse mail de l'adhérent est : %s\nL'adhérent possède également l'alias :\n%s\n" % (proprio['mail'][0], proprio['canonicalAlias'][0]), title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), width=75, height=12, timeout=self.timeout, ) return proprio @tailcaller def set_password(proprio, update_obj, cont): if self.dialog.yesno("Attribuer un mot de passe maintenant ?", title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), timeout=self.timeout ) == self.dialog.DIALOG_OK: #return self.proprio_compte_password(proprio=proprio, return_obj=return_obj, cont=cont(**{update_obj:proprio})) proprio = self.proprio_compte_password(proprio=proprio, return_obj=True, cont=TailCall(set_password, proprio, update_obj, cont)) if return_obj: return proprio else: raise Continue(cont(**{update_obj:proprio})) elif return_obj: return proprio else: raise Continue(cont(**{update_obj:proprio})) def todo(proprio, warning, guess_login, guess_pass, return_obj, self_cont, cont): box_warning(warning, proprio, cont) login = get_login(guess_login, guess_pass, proprio, self_cont, cont) if return_obj: proprio = create_compte(proprio, login, guess_login, self_cont, cont) return set_password(proprio, update_obj, cont) else: with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: proprio = create_compte(proprio, login, guess_login, self_cont, cont) if not self.confirm_item(item=proprio, title="Création du compte crans pour l'adhérent ?"): raise Continue(cont) else: proprio.validate_changes() proprio.history_gen() proprio.save() self.dialog.msgbox( text="Compte créé avec succès.", title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), timeout=self.timeout ) return set_password(proprio, update_obj, cont) self_cont = TailCall(self.proprio_compte_create, proprio=proprio, cont=cont, warning=warning, guess_login=guess_login, guess_pass=guess_pass) return self.handle_dialog_result( code=self.dialog.DIALOG_OK, output="", cancel_cont=cont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [proprio, warning, guess_login, guess_pass, return_obj, self_cont, cont])] ) @tailcaller def proprio_compte_password(self, proprio, cont, return_obj=False): """Permet de changer le mot de passe d'un compte crans""" def test_password(password, self_cont): (good, msg) = check_password(password, dialog=True) if not good: self.dialog.msgbox( msg, title="Erreur dans le mot de passe de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), colors=True, width=70, timeout=self.timeout) raise Continue(self_cont) else: return True def todo(passwords, proprio, return_obj, self_cont, cont): password = self.get_password(cont=cont, title="Choix du mot de passe pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), backtitle="Le mot de passe doit être assez difficile") if test_password(password, self_cont): if return_obj: proprio['userPassword']=unicode(lc_utils.hash_password(password)) return proprio else: with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: proprio['userPassword']=unicode(lc_utils.hash_password(password)) proprio.validate_changes() proprio.history_gen() proprio.save() self.dialog.msgbox( "Mot de passe changé avec succès", title="Choix du mot de passe pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), width=70, timeout=self.timeout ) raise Continue(cont(proprio=proprio)) #(code, passwords) = self.handle_dialog(cont, box) (code, passwords) = (self.dialog.DIALOG_OK, "") self_cont = TailCall(self.proprio_compte_password, proprio=proprio, cont=cont) return self.handle_dialog_result( code=code, output=passwords, cancel_cont=cont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [passwords, proprio, return_obj, self_cont, cont])] ) @tailcaller def proprio_compte_delete(self, proprio, cont, force=False): """Permet la suppression du compte crans d'un proprio""" def todo(proprio, self_cont, cont): if force or self.confirm_item(item=proprio, title="Voulez vous vraiement supprimer le compte de %s %s ?" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), defaultno=True): (code, mail) = self.dialog.inputbox( text="Il faut choisir une nouvelle adresse de contact.\n(On regarde s'il y a une adresse optionnel)", title="Choix d'une adresse de contact pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), init=str(proprio.get("mailExt", [""])[0]), width=50, timeout=self.timeout) if not code == self.dialog.DIALOG_OK: raise Continue(cont) elif not mail: raise ValueError("Il faut entrer une adresse mail") with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: proprio.delete_compte(unicode(mail, 'utf-8')) proprio.validate_changes() proprio.history_gen() proprio.save() self.dialog.msgbox("Le compte a bien été supprimée", timeout=self.timeout, title="Suppression du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0])) raise Continue(cont(proprio=proprio)) else: raise Continue(cont) self_cont = TailCall(self.proprio_compte_delete, proprio=proprio, cont=cont, force=force) return self.handle_dialog_result( code=self.dialog.DIALOG_OK, output="", cancel_cont=cont(proprio=proprio), error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [proprio, self_cont, cont])] ) def proprio_compte_etat(self, proprio, disable, cont): """Permet de d'éastiver ou activer un compte crans avec l'attribut shadowExpire""" with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: if disable: proprio["shadowExpire"]=0 else: proprio["shadowExpire"]=[] proprio.validate_changes() proprio.history_gen() proprio.save() raise Continue(cont(proprio=proprio)) def proprio_compte_shell(self, proprio, cont, choices_values=None): """Permet de modifier le shell d'un compte crans""" a = attributs # Seul les nounous peuvent changer les shells restrictifs shells_droits = { 'default' : [a.soi, a.nounou, a.cableur], 'rbash' : [a.nounou], 'rssh' : [a.nounou], 'badPassSh' : [a.nounou], 'disconnect_shell':[a.nounou], } shell = os.path.basename(str(proprio['loginShell'][0])).lower() shells = config.shells_gest_crans shells_order = config.shells_gest_crans_order def box(): # liste des shell éditables par l'utilisateur editable_sheels = [s for s in shells_order if self.has_right(shells_droits.get(s, shells_droits['default']), proprio)] # Si l'utilisateur de gest_crans peut éditer le shell courant # il peut le changer pour un autre shell qu'il peut éditer if shell in editable_sheels: choices=[(s, shells[s]['desc'], 1 if s == shell else 0) for s in editable_sheels] return self.dialog.radiolist( text="", height=0, width=0, list_height=0, choices=choices_values if choices_values else choices, title="Shell de %s %s" % (proprio.get('prenom', [""])[0], proprio['nom'][0]), timeout=self.timeout ) # Sinon, on affiche un message d'erreur et on annule else: self.dialog.msgbox("Vous ne pouvez pas changer le shell de cet utilisateur", title="Édition du shell impossible", timeout=self.timeout, width=0, height=0) return (self.dialog.DIALOG_CANCEL, None) def todo(output, shell, shells, proprio, self_cont, cont): loginShell = shells[output]['path'] if shell and shell != output: with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: proprio['loginShell']=unicode(loginShell) proprio.validate_changes() proprio.history_gen() proprio.save() self.dialog.msgbox("Shell modifié avec succès.\nLa modification peut prendre une quainzaine de minute avant d'être effective.", title="Shell de %s %s" % (proprio.get('prenom', [""])[0], proprio['nom'][0]), width=50, timeout=self.timeout, ) raise Continue(cont(proprio=proprio)) (code, output) = self.handle_dialog(cont, box) self_cont = TailCall(self.proprio_compte_shell, proprio=proprio, cont=cont, choices_values=[(s, shells[s]['desc'], 1 if s == output else 0) for s in shells_order]) return self.handle_dialog_result( code=code, output=output, cancel_cont=cont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], todo, [output, shell, shells, proprio, self_cont, cont])] ) def proprio_compte(self, proprio, cont, default_item=None): """Menu de gestion du compte crans d'un proprio""" has_compte = 'cransAccount' in proprio['objectClass'] disabled_compte = has_compte and 0 in proprio['shadowExpire'] a = attributs menu_droits = { "Password": [a.cableur, a.nounou], 'MailAlias': [a.cableur, a.nounou], "Activer" : [a.nounou], "Désactiver" : [a.nounou], "Créer" : [a.cableur, a.nounou], "Supprimer" : [a.cableur, a.nounou], "Shell" : [a.nounou, a.soi, a.cableur], } menu = { "Password" : {"text":"Changer le mot de passe du compte", "help":"", "callback":self.proprio_compte_password}, 'MailAlias' : {'text': 'Créer ou supprimer des alias mail', "help":"", 'attribut':attributs.mailAlias}, "Shell" : {"text" : "Changer le shell de cet utilisateur", "help":'', "callback":self.proprio_compte_shell}, "Activer" : {"text" : "Activer le compte pour la connexion mail/serveur", "help":"Permet d'autoriser les connexions smtp, imap, ssh, etc… avec le compte", "callback":TailCall(self.proprio_compte_etat, disable=False)}, "Désactiver" : {"text" : "Désactiver le compte pour la connexion mail/serveur", "help":"Permet d'interdire les connexions smtp, imap, ssh, etc… avec le compte", "callback":TailCall(self.proprio_compte_etat, disable=True)}, "Créer" : {"text": "Créer un compte", "help":'', "callback":self.proprio_compte_create}, "Supprimer" : {"text": "Supprimer le compte", "help":"Le home sera archivé dans le cimetière", "callback":self.proprio_compte_delete}, } menu_order = [] tag_translate = { "Créer":"Password", "Password":"Password", "Supprimer":"Créer", "Activer":"Désactiver", "Désactiver":"Activer", "Shell":"Shell", 'MailAlias':'MailAlias', '':'', } if has_compte: if disabled_compte: menu_order.append("Activer") else: menu_order.append("Désactiver") menu_order.extend(['MailAlias', "Shell", "Password", "Supprimer"]) else: menu_order.append("Créer") def box(default_item=None): return self.dialog.menu( "Quel action effectuer sur le compte ?", width=0, height=0, menu_height=0, timeout=self.timeout, item_help=1, default_item=str(default_item), title="Gestion du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), scrollbar=True, cancel_label="Retour", backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], proprio)]) def todo(tag, menu, proprio, 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, proprio=proprio)) elif 'attribut' in menu[tag]: raise Continue(TailCall(self.modif_proprio_attributs, proprio=proprio, cont=self_cont, 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, default_item) self_cont = TailCall(self.proprio_compte, proprio=proprio, cont=cont, default_item=tag_translate.get(tag, 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, proprio, self_cont])] ) @tailcaller def proprio_vente_set(self, article, cont): """Permet de définir la quantité de l'article à vendre""" def box(): if article['pu'] == '*': return self.dialog.inputbox(title="Montant pour %s ?" % article['designation'], text="", init=str(article.get('nombre','')), timeout=self.timeout, width=70) else: return self.dialog.inputbox(title="Nombre de %s ?" % article['designation'], text="", timeout=self.timeout, init=str(article.get('nombre','1')), width=70) def todo(article, output, cont): article['nombre']=output # Il faut entrer quelque chose if not output: raise ValueError("Merci d'entrer une valeur") # Vérification des centimes if article['pu'] == '*' and '.' in output: if len(output.split('.', 1)[1])>2: raise ValueError("Les centimes, c'est seulement deux chiffres après la virgule") typ = float if article['pu'] == '*' else int # Vérification du type de l'entré try: output=typ(output) except ValueError: raise ValueError("Merci d'entrez seulement des nombres") # On définis le nombre d'entrée. Pour pu=* il y aura du trairement à faire # avant de générer la facture : mettre pu à nombre et nombre à 1 # on le met comme ça pour pouvoir naviger aisément entre les écrans dialog article['nombre'] = output return article (code, output) = self.handle_dialog(cont, box) self_cont = TailCall(self.proprio_vente_set, article=article, 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, [article, output, cont])] ) @tailcaller def proprio_vente(self, proprio, cont, tags=[], tag_paiment=None, to_set=[], have_set=[]): """Menu de vente du crans. Permet également de recharger le solde crans""" box_paiement = { "liquide" : "Espèces", "cheque" : "Chèque", "solde" : "Solde Crans (actuel : %s€)", } def box_choose_item(tags): choices = [] for code, article in gestion.config.factures.items.items(): choices.append((code, u"%s%s" % (article['designation'], (u' (%s€)' % article['pu']) if article['pu'] != '*' else ""), 1 if code in tags else 0)) return self.dialog.checklist( text="", title="Vente de truc à %s %s" % (proprio.get("prenom", [''])[0], proprio["nom"][0]), choices=choices, timeout=self.timeout) def box_choose_paiment(tag, articles): box_paiement_order = ["liquide", "cheque"] if "cransAccount" in proprio['objectClass']: if not "SOLDE" in [art['code'] for art in articles]: box_paiement_order.append("solde") box_paiement["solde"] = box_paiement["solde"] % proprio["solde"][0] choices = [] for key in box_paiement_order: choices.append((key, box_paiement[key], 1 if key == tag else 0)) return self.dialog.radiolist( text="", title="Choix d'un mode de paiement pour %s %s" % (proprio.get("prenom", [''])[0], proprio["nom"][0]), choices=choices, timeout=self.timeout) def choose_item(proprio, tags, articles, self_cont): to_set=[] for tag in tags: articles[tag]['code']=tag to_set.append(articles[tag]) raise Continue(self_cont(to_set=to_set, have_set=[])) def number_of_items(to_set, have_set, self_cont): # Où faut-il aller si l'utilisateur appuis sur annuler if not have_set: lcont = self_cont(to_set=[]) else: lcont = self_cont(to_set=[have_set[-1]] + to_set, have_set=have_set[:-1]) art = self.proprio_vente_set(to_set[0], cont=lcont) if not to_set[1:]: total = 0 line=1 text=u"Résumé :\n" for article in have_set + [art]: if article['pu'] == '*': total += article['nombre'] text+=u" * %s pour %s€\n" % (article['designation'], article['nombre']) else: total += article['nombre']*article['pu'] text+=u" * %dx %s à %s€\n" % (article['nombre'], article['designation'], article['pu']) line+=1 text+=u"Total à payer : %s€" % total self.dialog.msgbox(text=text, title="Résumé de la facture à payer", width=70, height=5+line, timeout=self.timeout) return self_cont(to_set=to_set[1:], have_set=have_set + [art]) def choose_paiment(have_set, tag, proprio, lcont, self_cont, cont): if not tag: raise ValueError("Il faut choisir un moyen de paiement") code, comment = self.dialog.inputbox(text="Détail pour les espèce, nom de la note ou banque du chèque", title="Commentaire", width=70, timeout=self.timeout) if code != self.dialog.DIALOG_OK: raise Continue(self_cont) if not comment: raise ValueError("Commentaire nécessaire") articles = copy.deepcopy(have_set) for article in articles: if article['pu'] == '*': article['pu'] = article['nombre'] article['nombre'] = 1 with self.conn.newFacture(proprio.dn, {}) as facture: facture['modePaiement']=unicode(tag, 'utf-8') facture['article']=articles facture['info']=unicode(comment, 'utf-8') if self.confirm_item(item=facture, text=u"Le paiement de %s€ a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), tag), 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 facture.crediter() arts = ["%s %s" % (art['nombre'], art['designation']) for art in facture['article'] if art['code'] != 'SOLDE'] if arts: self.dialog.msgbox( text=u"Vous pouvez remettre à l'adherent les articles (si se sont des articles) suivant :\n * %s" % '\n * '.join(arts), title=u"Vente terminée", width=0, height=0, timeout=self.timeout) if tag == "solde": 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) if [a for a in facture['article'] if art['code'] == 'SOLDE']: self.dialog.msgbox(text=u"Le solde de l'adhérent à bien été crédité", title="Solde crédité", width=0, height=0, timeout=self.timeout) else: self.dialog.msgbox(text=u"Le paiement n'ayant pas été reçue\nla vente est annulée", title="Annulation de la vente", width=0, height=0, timeout=self.timeout) raise Continue(cont) self_cont=TailCall(self.proprio_vente, proprio=proprio, cont=cont, tags=tags, tag_paiment=tag_paiment, to_set=to_set, have_set=have_set) # S'il y a des article dont il faut définir la quantité if to_set: return self.handle_dialog_result( code=self.dialog.DIALOG_OK, output=None, cancel_cont=None, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], number_of_items, [to_set, have_set, self_cont])] ) # Sinon, si tous les quantités de tous les articles sont définis elif have_set: lcont = self_cont.copy() lcont(to_set=[have_set[-1]] + to_set, have_set=have_set[:-1]) (code, tag) = self.handle_dialog(lcont, box_choose_paiment, tag_paiment, have_set) self_cont=self_cont(tag_paiment=tag) lcont(tag_paiment=tag) return self.handle_dialog_result( code=code, output=tag, cancel_cont=lcont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], choose_paiment, [have_set, tag, proprio, lcont, self_cont, cont])] ) # Sinon, on propose des articles à chosir else: (code, tags) = self.handle_dialog(cont, box_choose_item, tags) self_cont=self_cont(tags=tags, have_set=[], to_set=[], tag_paiment=None) return self.handle_dialog_result( code=code, output=tags, cancel_cont=cont, error_cont=self_cont, codes_todo=[([self.dialog.DIALOG_OK], choose_item, [proprio, tags, copy.deepcopy(gestion.config.factures.items), self_cont])] )