diff --git a/admin/controle_tresorier.py b/admin/controle_tresorier.py new file mode 100755 index 00000000..9b95cf3b --- /dev/null +++ b/admin/controle_tresorier.py @@ -0,0 +1,331 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-15 -*- + +# Copyright (C) Stéphane Glondu +# Licence : GPLv2 + +u"""Ce script permet au trésorier de contrôler plus facilement le travail +des câbleurs. + +Usage: %(prog)s {paiement|carte|mail} + +Les options sont : + * paiement : contrôle interactif des nouvelles (ré-)adhésions + * carte : contrôle interactif des nouvelles cartes d'étudiant + * mails : envoyer les mails de rappel""" + +import sys, os, re +sys.path.append('/usr/scripts/gestion') + +# Fonctions d'affichage +from affich_tools import coul, tableau, prompt + +# Importation de la base de données +from ldap_crans import crans_ldap, ann_scol +db = crans_ldap() + +# Détermination de l'uid du câbleur +from user_tests import getuser +uid = getuser() +if not uid : + print u"Impossible de déterminer l'utilisateur !" + sys.exit(1) +cableur = db.search('uid=%s' % uid)['adherent'][0] + +# Vérification des droits +if u'Contrôleur' not in cableur.droits(): + print u"Il faut être contrôleur pour exécuter ce script !" + sys.exit(1) + +# Lors des tests sur egon, on m'envoie tous les mails ! +from socket import gethostname +testing = gethostname().split(".")[0] == 'egon' +if testing: + print coul(u'Mode testing !', 'violet') + ann_scol = 2004 + + +def _controle_interactif_adherents(liste, quoi): + u""" + Contrôle interactif des adhérents de la liste (quoi = p ou c). + Retourne (nb_OK, nb_pas_OK). + """ + if quoi not in 'pc': raise ValueError + explicite = {'p': u'du paiement', 'c': u"de la carte d'étudiant"}[quoi] + restant = len(liste) + + print coul(u'\nContrôle %s des adhérents' % explicite, 'cyan') + print u"Pour chaque entrée, il faut taper 'o' ou 'n' (défaut=n)." + print u"Une autre réponse entraîne l'interruption du processus." + print u"Le format est [nb_restant] Nom, Prénom (aid)." + print + + nb = 0 + for a in liste: + ok = prompt(u'[%3d] %s, %s (%s) ?' + % (restant, a.nom(), a.prenom(), a.id()), 'n', '').lower() + restant -= 1 + if ok == 'o': + modifiable = db.search('aid=%s' % a.id(), 'w')['adherent'][0] + if modifiable._modifiable: + modifiable.controle('+%s' % quoi) + print modifiable.save() + nb += 1 + else: + print coul(u'Adhérent %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge') + elif ok != 'n': + print coul(u'Arrêt du contrôle %s des adhérents' % explicite, 'rouge') + break + + return nb, len(liste)-nb + + +def _controle_interactif_clubs(liste): + u""" + Contrôle interactif des clubs de la liste (uniquement la charte). + Retourne (nb_OK, nb_pas_OK). + """ + restant = len(liste) + + print coul(u'\nContrôle de la charte des clubs', 'cyan') + print u"Pour chaque entrée, il faut taper 'o' ou 'n'." + print u"Une autre réponse entraîne l'interruption du processus." + print u"Le format est [nb_restant] Nom (cid)." + print + + nb = 0 + for c in liste: + ok = prompt(u'[%2d] %s (%s) ?' + % (restant, c.Nom(), c.id()), 'n', '').lower() + restant -= 1 + if ok == 'o': + modifiable = db.search('cid=%s' % c.id(), 'w')['club'][0] + if modifiable._modifiable: + modifiable.controle('+p') + print modifiable.save() + nb += 1 + else: + print coul(u'Club %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge') + elif ok != 'n': + print coul(u'Arrêt du contrôle de la charte des clubs', 'rouge') + break + + return nb, len(liste)-nb + + +def controle_interactif(quoi): + u""" + Procédure interactive de contrôle des paiements/chartes (quoi=p) et cartes (quoi=c). + """ + if quoi not in 'pc': raise ValueError + todo_list = db.search('%s=%d&controle!=*%s*' + % ({'p': 'paiement', 'c': 'carteEtudiant'}[quoi], ann_scol, quoi)) + + # Tri de la liste des adhérents selon nom, prénom + # Ça peut se faire plus facilement en Python 2.4 avec l'argument key + todo_list['adherent'].sort(lambda x, y: cmp((x.nom(), x.prenom()), (y.nom(), y.prenom()))) + + # Traitement des adhérents + oka, noka = _controle_interactif_adherents(todo_list['adherent'], quoi) + + if quoi == 'p': + # Tri de la liste des clubs + todo_list['club'].sort(lambda x, y: cmp(x.nom(), y.nom())) + + # Traitement des clubs (uniquement la charte) + okc, nokc =_controle_interactif_clubs(todo_list['club']) + + print coul(u'\nRécapitulatif des nouveaux contrôles +%s :' % quoi, 'violet') + liste = [(u'Catégorie', u'OK', u'pas OK'), + (u'adhérents', str(oka), str(noka))] + if quoi == 'p': + liste.append((u'clubs', str(okc), str(nokc))) + print tableau([15, 10, 10], liste) + + +def formater_pour_cableur(liste): + u""" + Formate la liste d'adhérents ou de clubs avec les dates correspondantes. + liste est une liste de couples (date, objet). + """ + lignes = [(u'id', u'Nom', u'Date Heure')] + + for date, a in liste: + lignes.append((a.id(), a.Nom(), date)) + + return tableau([6, 40, 18], lignes) + + +def formater_pour_bureau(dico): + u""" + Formate la liste d'adhérents ou de clubs avec les câbleurs correspondantes + pour le mail récapitulatif envoyé à bureau. + """ + lignes = [(u'id', u'Nom', u'Câbleur')] + + total = 0 + for cableur in dico.keys(): + for date, a in dico[cableur]: + lignes.append((a.id(), a.Nom(), cableur)) + total += 1 + + return tableau([6, 40, 17], lignes) + '\nTotal : %d' % total + + +def qui(historique, quoi): + u""" + Recherche le câbleur qui a effectué la dernière modification quoi + dans l'historique, ou qui a inscrit l'adhérent. + Retourne le couple (date_heure, nom_cableur). + """ + regexp = re.compile(r'^([^,]*), ([^ :]*)') + cableur = ('_inconnu_', '_inconnu_') + + for ligne in historique: + if quoi in ligne or 'inscription' in ligne: + matched = regexp.match(ligne) + if matched: cableur = matched.group(1), matched.group(2) + + return cableur + + +def chercher_cableurs(liste, quoi): + u""" + Renvoie un dictionnaire cableur->adherents à partir de liste, quoi + désigne le champ qui sera recherché dans l'historique. + """ + resultat = {} + + for a in liste: + date, cableur = qui(a.historique(), quoi) + if not resultat.has_key(cableur): resultat[cableur] = [] + resultat[cableur].append((date, a)) + + return resultat + + +from smtplib import SMTP +from email.MIMEText import MIMEText + + +def envoyer_mails_rappel(): + u""" + Envoyer les mails de rappel aux câbleurs avec bureau en cc, + ainsi que le récapitulatif à bureau. + """ + # On recherche tous les câbleurs concernés + todo_list = db.search('paiement=%d&controle!=*p*' % ann_scol) + paiements = chercher_cableurs(todo_list['adherent'], 'paiement') + chartes = chercher_cableurs(todo_list['club'], 'paiement') + cartes = chercher_cableurs(db.search('carteEtudiant=%d&controle!=*c*' % ann_scol)['adherent'], 'carteEtudiant') + + # Méthode de Vince + cableurs = {} + for a in paiements.keys(): + if a != '_inconnu_': cableurs[a] = None + for a in chartes.keys(): + if a != '_inconnu_': cableurs[a] = None + for a in cartes.keys(): + if a != '_inconnu_': cableurs[a] = None + + # Squelette à modifier après + msg_skeleton = u"""\ +Salut, + +Tu me dois des papiers. J'ai mis l'aid ou le cid afin que tu puisses +vérifier facilement dans la base. +%s +Peux-tu confirmer et donner ces papiers le plus rapidement possible à +Florian, Augustin, les laisser au 2B ou directement me les donner ? + +-- +Stéphane +""" + + # Vérification de l'alias pour l'envoi des mails + if u'tresorier' in cableur.alias(): + moi = (u'%s ' % cableur.Nom()).encode('utf8') + else: + moi = (u'%s <%s@crans.org>' % (cableur.Nom(), cableur.canonical_alias())).encode('utf8') + + # On se connecte au serveur + s = SMTP() + s.connect() + + # On envoie les mails + nb = 0 + for c in cableurs.keys(): + msg = u'' + + if c in paiements.keys(): + msg += u"\nFiches d'adhésion et cotisations des adhérents :\n" + msg += formater_pour_cableur(paiements[c]) + '\n' + + if c in chartes.keys(): + msg += u"\nChartes signées des clubs :\n" + msg += formater_pour_cableur(chartes[c]) + '\n' + + if c in cartes.keys(): + msg += u"\nCarte d'étudiant des adhérents :\n" + msg += formater_pour_cableur(cartes[c]) + '\n' + + mail = MIMEText((msg_skeleton % msg).encode('utf8'), _charset='UTF-8') + mail['Subject'] = u'Papiers manquants'.encode('utf8') + mail['From'] = moi + adresse_cableur = '%s@crans.org' % c + mail['To'] = adresse_cableur + mail['Cc'] = 'Bureau ' + if testing: + recipients = ['glondu@crans.org'] + else: + recipients = [adresse_cableur, 'bureau@crans.org'] + s.sendmail(moi, recipients, mail.as_string()) + nb += 1 + + # On envoie le mail récapitulatif sur bureau + if paiements or chartes or cartes: + msg = u'' + + msg += u"\nFiches d'adhésion et cotisations des adhérents :\n" + msg += formater_pour_bureau(paiements) + '\n' + + msg += u"\nChartes signées des clubs :\n" + msg += formater_pour_bureau(chartes) + '\n' + + msg += u"\nCarte d'étudiant des adhérents :\n" + msg += formater_pour_bureau(cartes) + '\n' + + msg += u"\n-- \nScript exécuté par %s\n" % cableur.Nom() + mail = MIMEText(msg.encode('utf8'), _charset='utf8') + mail['Subject'] = u'Récapitulatif des papiers manquants'.encode('utf8') + mail['From'] = moi + mail['To'] = 'Bureau ' + if testing: + recipients = ['glondu@crans.org'] + else: + recipients = ['bureau@crans.org'] + s.sendmail(moi, recipients, mail.as_string()) + nb += 1 + + s.close() + print coul(u"%d mail(s) envoyé(s) avec succès !" % nb, "vert") + + +def __usage(): + u""" Comment ça marche ? """ + print __doc__ % {'prog': sys.argv[0]} + sys.exit(1) + +if __name__ == '__main__' : + if len(sys.argv) == 2: + if sys.argv[1] == 'paiement': + controle_interactif('p') + elif sys.argv[1] == 'carte': + controle_interactif('c') + elif sys.argv[1] == 'mail': + envoyer_mails_rappel() + else: + __usage() + else: + __usage() + sys.exit(0)