Controle tresorier 1 et 2 aux archives

This commit is contained in:
Gabriel Detraz 2015-08-27 23:27:26 +02:00
parent 89c8f33f68
commit 3f70d1b825
2 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,477 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) Stéphane Glondu
# Licence : GPLv2
u"""Ce script permet au trésorier de repérer plus facilement les papiers que
les câbleurs n'ont pas encore rendus.
Utilisations possibles :
* %(prog)s OPTIONS {paiement|carte|list}
* %(prog)s OPTIONS mail <liste>
Les options sont:
-d, --debug <adresse> envoyer tous les mails à l'<adresse> indiquée, plutôt
qu'aux vrais destinataires
-h, --help affiche ce message
-t, --tri <champ> trier suivant ce champ. Les champs possibles sont:
id, nom, cableur ou date. Le défaut est ``nom''
-i, --inverse trier par ordre inverse
Les commandes sont :
* paiement contrôle interactif des nouvelles (-)adhésions
* carte contrôle interactif des nouvelles cartes d'étudiant
* list affiche le récapitulatif des papiers manquants
* mail <liste> envoyer les mails de rappel aux câbleurs dont le
login est dans la <liste>, 'bureau' désignant le mail
récapitulatif envoyé à bureau ; si la liste est vide
tous les mails nécessaires seront envoyés
* help affiche ce message
Le modèle du mail envoyé aux câbleurs est lu sur l'entrée standard (typiquement
via une redirection). Les trois premières lignes doivent être :
Encoding: <encodage du fichier>
Subject: <sujet du mail>
<ligne vide>
Le reste est le corps du message. Il doit contenir une occurrence de '%%s' qui
sera remplacée par la liste des papiers manquants.
Le mail récapitulatif envoyé à bureau est immuable."""
import sys, os, re, getopt
sys.path.append('/usr/scripts/gestion')
# Fonctions d'affichage
from affich_tools import coul, tableau, prompt, cprint
# 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 :
cprint(u"Impossible de déterminer l'utilisateur !")
sys.exit(1)
cableur = db.search('uid=%s' % uid)['adherent'][0]
# Vérification des droits
if u'Tresorier' not in cableur.droits():
cprint(u"Il faut etre tresorier pour exécuter ce script !")
sys.exit(1)
# Lors des tests, on m'envoie tous les mails !
from socket import gethostname
debug = False
# Le champ sur lequel on veut trier (id, nom, cableur, date)
trier_par = "id"
tri_inverse = True
if gethostname().split(".")[0] == 'egon':
debug = 'glondu@crans.org'
ann_scol = 2004
def _controle_interactif_adherents(liste, quoi):
"""
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)
if restant == 0:
return 0, 0
cprint(u'\nContrôle %s des adhérents' % explicite, 'cyan')
cprint(u"Pour chaque entrée, il faut taper 'o' ou 'n' (défaut=n).")
cprint(u"Une autre réponse entraîne l'interruption du processus.")
cprint(u"Le format est [nb_restant] Nom, Prénom (aid).")
cprint(u"")
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)
cprint(modifiable.save())
nb += 1
else:
cprint(u'Adhérent %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge')
elif ok != 'n':
cprint(u'Arrêt du contrôle %s des adhérents' % explicite, 'rouge')
break
return nb, len(liste)-nb
def _controle_interactif_clubs(liste):
"""
Contrôle interactif des clubs de la liste (uniquement la charte).
Retourne (nb_OK, nb_pas_OK).
"""
restant = len(liste)
if restant == 0:
return 0, 0
cprint(u'\nContrôle de la charte des clubs', 'cyan')
cprint(u"Pour chaque entrée, il faut taper 'o' ou 'n'.")
cprint(u"Une autre réponse entraîne l'interruption du processus.")
cprint(u"Le format est [nb_restant] Nom (cid).")
cprint(u"")
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')
cprint(modifiable.save())
nb += 1
else:
cprint(u'Club %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge')
elif ok != 'n':
cprint(u'Arrêt du contrôle de la charte des clubs', 'rouge')
break
return nb, len(liste)-nb
def qui(historique, quoi):
"""
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, cableur) ou ('_inconnu_', '_inconnu_').
"""
regexp = re.compile(r'^([^,]*), ([^ :]*)')
cableur = ('_inconnu_', '_inconnu_')
champ = re.compile(quoi)
for ligne in historique:
if champ.search(ligne) or 'inscription' in ligne:
matched = regexp.search(ligne)
if matched: cableur = matched.group(1), matched.group(2)
return cableur
def controle_interactif(quoi):
"""
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
todo_list['adherent'].sort(key = lambda x: (x.nom(), x.prenom()))
if trier_par == 'id':
todo_list['adherent'].sort(key = lambda x: int(x.id()))
elif trier_par == 'nom':
pass
else:
champ = { 'date': 0, 'cableur': 1 }[trier_par]
todo_list['adherent'].sort(key = lambda x: qui(x.historique(), r'paiement')[champ])
# 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'])
cprint(u'\nRécapitulatif des nouveaux contrôles +%s :' % quoi, 'violet')
liste = [[u'adhérents', str(oka), str(noka)]]
if quoi == 'p':
liste.append([u'clubs', str(okc), str(nokc)])
cprint(tableau(liste,
titre = [u'Catégorie', u'OK', u'pas OK'],
largeur = [15, 10, 10]))
def formater_pour_cableur(liste):
"""
Formate la liste d'adhérents ou de clubs avec les dates correspondantes.
liste est une liste de couples (date, objet).
"""
lignes = []
total = 0
liste.sort(lambda x, y: cmp(x[1].nom(), y[1].nom()))
for date, a in liste:
lignes.append([a.id(), a.Nom(), date])
total += 1
if trier_par == 'id':
lignes.sort(key = lambda ligne: int(ligne[0]))
else:
champ = {'nom' : 1, 'cableur' : 1, 'date' : 2}[trier_par]
lignes.sort(key = lambda ligne: ligne[champ])
if tri_inverse: lignes.reverse()
return tableau(lignes,
titre = [u'id', u'Nom', u'Date Heure'],
largeur = [6, 40, 18],
alignement = ['d', 'c', 'c']) + u'\nTotal : %d' % total
def formater_pour_bureau(dico):
"""
Formate la liste d'adhérents ou de clubs avec les câbleurs correspondantes
pour le mail récapitulatif envoyé à bureau.
"""
lignes = []
total = 0
liste = dico.keys()
liste.sort()
for cableur in liste:
for date, a in dico[cableur]:
lignes.append([a.id(), a.Nom(), cableur, date])
total += 1
if trier_par == 'id':
lignes.sort(key = lambda ligne: int(ligne[0]))
else:
champ = {'nom' : 1, 'cableur' : 2, 'date' : 3}[trier_par]
lignes.sort(key = lambda ligne: ligne[champ])
if tri_inverse: lignes.reverse()
return tableau(lignes,
titre = [u'id', u'Nom', u'Câbleur', u'Date'],
largeur = [6, 40, 18, 18],
alignement = ['d', 'c', 'c', 'c']) + u'\nTotal : %d' % total
def chercher_cableurs(liste, quoi):
"""
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 email_tools import send_email, parse_mail_template
# Mise en application du mémorable cours de Fred sur les objets :-)
class ControleMailer:
def __init__(self):
# Recherche des câbleurs possédant des cotisations/chartes
todo_list = db.search('paiement=%d&controle!=*p*' % ann_scol)
self._paiements = chercher_cableurs(todo_list['adherent'], r'(paiement|controle.*\+k)')
self._chartes = chercher_cableurs(todo_list['club'], 'paiement')
# Recherche des câbleurs possédant des cartes d'étudiant
todo_list = db.search('carteEtudiant=%d&controle!=*c*' % ann_scol)
self._cartes = chercher_cableurs(todo_list['adherent'], 'carteEtudiant')
# Récupère tous les câbleurs qui doivent quelque chose
cableurs = {}
for a in self._paiements.keys():
if a != '_inconnu_': cableurs[a] = None
for a in self._chartes.keys():
if a != '_inconnu_': cableurs[a] = None
for a in self._cartes.keys():
if a != '_inconnu_': cableurs[a] = None
self._cableurs = cableurs.keys()
# Vérification de l'alias pour l'expéditeur
if u'tresorier' in cableur.alias():
self._sender = u'%s <tresorier@crans.org>' % cableur.Nom()
else:
self._sender = u'%s <%s@crans.org>' % (cableur.Nom(),
cableur.canonical_alias() or cableur.mail())
# Se connecte au serveur SMTP par défaut
from smtplib import SMTP
self._server = SMTP()
self._server.connect()
def __del__(self):
# Termine la connexion au serveur SMTP
self._server.quit()
def recapitulatif(self):
"""
Renvoie le récapitulatif pour le bureau.
"""
msg = u''
if self._paiements:
msg += u"Fiches d'adhésion et cotisations et/ou caution des adhérents :\n"
msg += formater_pour_bureau(self._paiements) + '\n\n'
if self._chartes:
msg += u"Chartes signées des clubs :\n"
msg += formater_pour_bureau(self._chartes) + '\n\n'
if self._cartes:
msg += u"Carte d'étudiant des adhérents :\n"
msg += formater_pour_bureau(self._cartes) + '\n\n'
return msg.strip()
def mail_bureau(self):
"""
Envoie le mail récapitulatif à bureau (s'il y a qqch à envoyer).
"""
msg = self.recapitulatif()
if msg:
msg += u"\n\n-- \nScript exécuté par %s\n" % cableur.Nom()
send_email(self._sender,
u"Bureau <bureau@crans.org>",
u"Récapitulatif des papiers manquants",
msg,
server = self._server,
debug = debug)
return coul(u'Mail envoyé à bureau avec succès !', 'vert')
else:
return coul(u'Tout est à jour, aucun mail envoyé.', 'vert')
def mail_cableurs(self, subject, body, liste=None):
"""
Envoie un mail aux câbleurs concernés qui figurent dans la liste.
Si liste vaut None, envoie un mail à tous les câbleurs concernés.
Les arguments subject, body permettent de personnaliser (un peu)
le mail qui sera envoyé.
"""
nb = 0
if liste == None:
liste = self._cableurs
for c in liste:
msg = u''
if self._paiements.has_key(c):
msg += u"Fiches d'adhésion, cotisations et/ou caution des adhérents :\n"
msg += formater_pour_cableur(self._paiements[c]) + '\n\n'
if self._chartes.has_key(c):
msg += u"Chartes signées des clubs :\n"
msg += formater_pour_cableur(self._chartes[c]) + '\n\n'
if self._cartes.has_key(c):
msg += u"Carte d'étudiant des adhérents :\n"
msg += formater_pour_cableur(self._cartes[c]) + '\n\n'
if msg:
msg = msg.strip()
send_email(self._sender,
"%s@crans.org" % c,
subject,
body % msg,
server = self._server,
debug = debug)
nb += 1
return coul(u'%d mail(s) envoyé(s) aux câbleurs avec succès !' % nb, 'vert')
def __usage(message=None):
""" Comment ça marche ? """
cprint(__doc__ % { 'prog': sys.argv[0] })
if message:
cprint(message)
sys.exit(1)
if __name__ == '__main__' :
try:
options, arg = getopt.getopt(sys.argv[1:], 'ht:d:i', [ 'help', 'debug=', 'tri=', 'inverse' ])
except getopt.error, msg:
__usage(unicode(msg))
for opt, val in options:
if opt in [ '-h', '--help' ]:
__usage()
elif opt in [ '-d', '--debug' ]:
debug = val
cprint(u'Mode debug, tous les mails seront envoyés à %s.' % debug)
elif opt in [ '-t', '--tri' ]:
if val in [ 'id', 'nom', 'cableur', 'date' ]:
trier_par = val
else:
__usage(u'Champ de tri invalie %s' % val)
elif opt in [ '-i', '--inverse' ]:
tri_inverse = True
else:
__usage("option inconnue « %s »'" % opt)
if len(arg) == 0 or arg[0] == 'help':
__usage()
elif arg[0] == 'paiement':
if len(arg) != 1:
__usage(u'Mauvaise utilisation de paiement')
controle_interactif('p')
elif arg[0] == 'carte':
if len(arg) != 1:
__usage(u'Mauvaise utilisation de carte')
controle_interactif('c')
elif arg[0] == 'list':
if len(arg) != 1:
__usage(u'Mauvaise utilisation de list')
cprint(ControleMailer().recapitulatif(), newline=False)
elif arg[0] == 'mail':
mailer = ControleMailer()
cableurs = arg[1:]
if cableurs:
bureau = False
if 'bureau' in cableurs:
cableurs.remove('bureau')
bureau = True
else:
bureau = True
cableurs = mailer._cableurs
if cableurs:
cprint(u'Des mails vont être envoyés aux câbleurs, lecture du modèle...')
subject, body = parse_mail_template(sys.stdin)
try:
body % u''
except TypeError:
cprint(u"Le format du modèle n'est pas correct, arrêt.")
sys.exit(1)
cprint(u'Modèle OK, on envoie les mails...')
cprint(mailer.mail_cableurs(subject, body, cableurs))
if bureau:
cprint(mailer.mail_bureau())
else:
__usage(u'Commande inconnue : %s' % arg[0])
sys.exit(0)
# pydoc n'aime pas l'unicode :-(

View file

@ -0,0 +1,292 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# $Id: controle_tresorier2.py,v 1.3 2007-11-08 21:18:32 dimino Exp $
#
# tresorier.py
# ------------
#
# Copyright (C) 2007 Jeremie Dimino <jeremie@dimino.org>
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
import os, sys
sys.path.append('/usr/scripts/gestion')
from ldap_crans import crans_ldap, Adherent, Club
from config import ann_scol
import dialog, time, config, re
db = crans_ldap()
# Détermination de l'uid du câbleur
from user_tests import getuser
uid = getuser()
if not uid :
print "Impossible de determiner l'utilisateur !"
sys.exit(1)
cableur = db.search('uid=%s' % uid)['adherent'][0]
# Vérification des droits
if u'Tresorier' not in cableur.droits():
print "Il faut etre tresorier pour executer ce script !"
sys.exit(1)
dlg = dialog.Dialog()
dlg.setBackgroundTitle('Tresorerie')
encoding = sys.stdin.encoding or 'UTF-8'
########################################################################
# Retrait des accents
#
_unaccent_dict = {u'Æ': u'AE', u'æ': u'ae', u'Œ': u'OE', u'œ': u'oe', u'ß': 'ss'}
_re_latin_letter = re.compile(r"^(LATIN [A-Z]+ LETTER [A-Z]+) WITH")
def unaccent(string):
"""Remove accents ``string``."""
result = []
for char in string:
if char in _unaccent_dict:
char = _unaccent_dict[char]
else:
try:
name = unicodedata.name(char)
match = _re_latin_letter.search(name)
if match:
char = unicodedata.lookup(match.group(1))
except:
pass
result.append(char)
return "".join(result)
########################################################################
# Ajoute un cheque a la liste
#
_civilite = [ "M", "MLLE", "MME" ]
_banque = [("CIC", u"CiC"),
("BNP", u"BNP"),
("BP", u"Banque Populaire"),
("CA", u"Crédit Agricole"),
("CE", u"Caisse d'Épargne"),
("CL", u"Crédit Lyonnais"),
("CM", u"Crédit Mutuel"),
("CN", u"Crédit du Nord"),
("HSBC", u"HSBC"),
("LBP", u"La Banque Postale"),
("LCL", u"Le Crédit Lyonnais"),
("LP", u"La Poste"),
("SG", u"Société Générale")]
_banque.sort()
_banque.insert(0, ("Autre", u"Éspèces"))
def add_cheque(adh):
annul, result = dlg.menu(u"Banque", choices = _banque)
if annul: return False
banque = result
if banque == "Autre":
return True
annul, result = dlg.menu(u"Civilité",
choices=[("1", u"Monsieur"),
("2", u"Mademoiselle"),
("3", u"Madame")])
if annul: return False
civilite = _civilite[int(result)-1]
line = '%s %s %s' % (civilite, unaccent(adh.Nom()).upper(), banque)
annul, line = dlg.inputbox(u"Vérification de l'émetteur du chèque", init=line)
f = open("%s/remise_cheques" % os.getenv("HOME"), 'a')
f.write('%s\n' % line)
f.close()
return True
########################################################################
# Menu de sélection des adhérents
#
def main_menu():
while True:
annul, result = dlg.checklist(u"Choisissez les adhérents à inclure dans la liste",
choices=[("1", u'Nouvelles adhésions', 1),
("2", u'Réadhésions', 0),
("3", u'Inscriptions(gratuite)', 1),
("4", u'Clubs', 0)])
if annul:
return
include_new_adhs = "1" in result
include_re_adhs = "2" in result
include_inscriptions = "3" in result
include_clubs = "4" in result
# Construction de la liste des adhérents à contrôler
search_result_p = db.search('paiement=%d&controle!=*p*' % ann_scol)
search_result_c = db.search('paiement=%d&controle!=*c*' % ann_scol)
lst_p = search_result_p['adherent'] + search_result_p['club']
lst_c = search_result_c['adherent'] + search_result_c['club']
lst = {}
for adh in lst_p:
lst[adh.id()] = adh
for adh in lst_c:
lst[adh.id()] = adh
lst = lst.values()
# Date de début de la nouvelle année
start_date = time.mktime((ann_scol, 8, 1, 0, 0, 0, 0, 0, 0))
# Filtre des adhérents
lst = [adh for adh in lst if
((include_new_adhs and isinstance(adh, Adherent) and adh.adherentPayant() and adh.dateInscription() >= start_date) or
(include_re_adhs and isinstance(adh, Adherent) and adh.adherentPayant() and adh.dateInscription() < start_date) or
(include_inscriptions and isinstance(adh, Adherent) and not adh.adherentPayant()) or
(include_clubs and isinstance(adh, Club)))]
adherent_menu(lst)
########################################################################
# Liste des adhérents
#
def adherent_menu(lst):
if lst == []:
dlg.msgbox(u"Il n'y a personne à contrôler!")
return
nom_adh = {}
adhs = []
for adh in lst:
disp = ' '.join([unicode(adh.id()).rjust(4), adh.Nom()]).encode(encoding)
adhs.append((disp, ''))
nom_adh[disp] = adh
adhs.sort()
while True:
annul, result = dlg.menu(u'Liste des adhérents', choices=adhs)
if annul:
return
adh = nom_adh[result]
if admin_menu(adh):
adhs.remove((result, ''))
########################################################################
# Controle d'un adhérent (repompé de gest_crans.py)
#
def on_off(cond):
if cond:
return 1
else:
return 0
def admin_menu(adh):
# Le proprietaire a-t-il une section carte d'étudiant (pas les clubs) ?
has_card = adh.idn != 'cid'
# Initialisation des différentes checkbox
carte = on_off(ann_scol in adh.carteEtudiant())
paiement = on_off(ann_scol in adh.paiement())
#precab = on_off(ann_scol + 1 in adh.paiement())
caution = on_off('k' in adh.controle())
paiement_ok = on_off('p' in adh.controle())
carte_ok = on_off('c' in adh.controle())
# Construction de la boîte de dialogue
texte = []
checklist = []
if has_card:
checklist.append(("1", u"Carte d'étudiant %d/%d fournie" % (ann_scol, ann_scol+1), carte))
checklist.append(("2", u"Cotisation %d/%d réglée et charte signée" % (ann_scol, ann_scol+1), paiement))
# TODO: controle pour le précâblage
#if config.precab:
# checklist.append(("3", u"Adhésion %d/%d réglée et charte signée (précâblage)" % (ann_scol+1, ann_scol+2), precab))
if has_card:
checklist.append(("4", u"Carte d\'étudiant vérifiée", carte_ok))
checklist.append(("5", u"Cotisation/charte/caution vérifées", paiement_ok))
annul, result = dlg.checklist(u"Etat administratif de %s" % adh.Nom(),
choices=checklist)
if annul:
return False
modif = False
# On cherche s'il y a des modifs
for tag, item, state in checklist:
if (tag in result) ^ state:
modif = True
if not modif:
return False
if isinstance(adh, Club):
adh = db.search('cid=%s' % adh.id(), 'w')['club'][0]
else:
adh = db.search('aid=%s' % adh.id(), 'w')['adherent'][0]
# Traitement
if has_card:
if '1' in result:
adh.carteEtudiant(ann_scol)
elif carte_ok:
adh.carteEtudiant(-ann_scol)
if '4' in result:
adh.controle('+c')
else:
adh.controle('-c')
if '2' in result and ann_scol not in adh.paiement():
adh.paiement(ann_scol)
elif '2' not in result and paiement_ok:
adh.paiement(-ann_scol)
if '3' in result:
adh.paiement(ann_scol+1)
elif paiement_ok:
adh.paiement(-ann_scol-1)
if '5' in result:
adh.controle('+p')
else:
adh.controle('-p')
if 'C' in result:
adh.controle('+k')
else:
adh.controle('-k')
adh.save()
return True
main_menu()