Passage aux adhésions glissantes (partie I, sans lc_ldap)
This commit is contained in:
parent
169b7000b8
commit
49cef4c095
12 changed files with 485 additions and 231 deletions
|
@ -17,6 +17,7 @@ import os
|
|||
import random
|
||||
import string
|
||||
import time
|
||||
import datetime
|
||||
import sys
|
||||
import pwd
|
||||
import errno
|
||||
|
@ -25,15 +26,17 @@ import ldap.modlist
|
|||
import ldap_passwd
|
||||
import netaddr
|
||||
|
||||
import time
|
||||
import annuaires_pg as annuaires
|
||||
import config
|
||||
import config.impression
|
||||
import config.cotisation as cotisation
|
||||
import iptools
|
||||
import ip6tools
|
||||
import cPickle
|
||||
import config_mail
|
||||
from chgpass import change_password
|
||||
|
||||
from calendar import monthrange
|
||||
from affich_tools import coul, prompt, cprint
|
||||
from email_tools import send_email
|
||||
from syslog import openlog, closelog, syslog
|
||||
|
@ -151,6 +154,27 @@ def decode(s):
|
|||
else:
|
||||
return s.decode('utf-8', 'ignore') # On ignore les erreurs
|
||||
|
||||
def tz(thetz):
|
||||
abstz = 100*abs(thetz)
|
||||
if thetz == 0:
|
||||
return "Z"
|
||||
else:
|
||||
return "%s%04d" % ("+"*(thetz < 0) + "-"*(thetz > 0), abstz)
|
||||
|
||||
def generalizedTimeFormat(stamp):
|
||||
"""Converts a timestamp (local) in a generalized time format
|
||||
for LDAP
|
||||
|
||||
"""
|
||||
|
||||
return "%s%s" % (time.strftime("%Y%m%d%H%M%S", time.localtime(stamp)), tz(time.altzone/3600))
|
||||
|
||||
def fromGeneralizedTimeFormat(gtf):
|
||||
"""Converts a GTF stamp to unix timestamp
|
||||
|
||||
"""
|
||||
return time.mktime(time.strptime(gtf.split("-", 1)[0].split("+", 1)[0].split('Z', 1)[0], "%Y%m%d%H%M%S"))
|
||||
|
||||
def strip_accents(a, sois_un_porc_avec_les_espaces = True):
|
||||
""" Supression des accents de la chaîne fournie """
|
||||
res = normalize('NFKD', decode(a)).encode('ASCII', 'ignore')
|
||||
|
@ -392,7 +416,8 @@ class CransLdap:
|
|||
'historique', 'blacklist', 'droits', 'uidNumber', 'info',
|
||||
'solde', 'controle', 'contourneGreylist', 'rewriteMailHeaders',
|
||||
'ablacklist', 'homepageAlias', 'charteMA',
|
||||
'adherentPayant', 'gpgFingerprint'], \
|
||||
'adherentPayant', 'gpgFingerprint', 'debutConnexion', 'finConnexion',
|
||||
'debutAdhesion', 'finAdhesion'], \
|
||||
'club': \
|
||||
['cid', 'responsable', 'paiement', 'historique', 'blacklist',
|
||||
'mailAlias', 'info', 'controle', 'ablacklist', 'imprimeurClub'], \
|
||||
|
@ -401,7 +426,7 @@ class CransLdap:
|
|||
'borneWifi': non_auto_search_machines_champs + \
|
||||
['prise', 'puissance', 'canal', 'hotspot', 'positionBorne', 'nvram'],
|
||||
'machineWifi': non_auto_search_machines_champs + ['ipsec'],
|
||||
'facture': ['fid']}
|
||||
'facture': ['fid', 'debutConnexion', 'finConnexion', 'debutAdhesion', 'finAdhesion']}
|
||||
|
||||
# tous les champs de recherche
|
||||
search_champs = {}
|
||||
|
@ -691,7 +716,6 @@ class CransLdap:
|
|||
except:
|
||||
raise ValueError(u"Impossible de créer l'objet %s" % nom_classe)
|
||||
|
||||
|
||||
def search(self, expression, mode=''):
|
||||
"""
|
||||
Recherche dans la base LDAP, expression est une chaîne :
|
||||
|
@ -757,13 +781,13 @@ class CransLdap:
|
|||
# définifif (cf config.py).
|
||||
if config.periode_transitoire:
|
||||
# Pour la période transitoire année précédente ok
|
||||
el = "(|(paiement=%d)(paiement=%d))" % (ann_scol, ann_scol-1)
|
||||
el = "(|(paiement=%d)(paiement=%d)(finAdhesion>=%s))" % (ann_scol, ann_scol-1, generalizedTimeFormat(time.time()))
|
||||
else:
|
||||
el = "(paiement=%s)" % ann_scol
|
||||
el = "(|(paiement=%s)(finAdhesion>=%s))" % (ann_scol, generalizedTimeFormat(time.time()))
|
||||
# Doit-on bloquer en cas de manque de la carte d'etudiant ?
|
||||
# (si période transitoire on ne bloque dans aucun cas)
|
||||
if config.bl_carte_et_definitif:
|
||||
el = "(&(|(carteEtudiant=%d)(objectClass=club))%s)" % (ann_scol, el)
|
||||
el = "(&(|(carteEtudiant=%d)(objectClass=club)(carteEtudiant=TRUE))%s)" % (ann_scol, el)
|
||||
elif champ[1:] == 'blacklist':
|
||||
el = '(blacklist=%s)' % expr
|
||||
else:
|
||||
|
@ -1029,9 +1053,9 @@ class BaseClasseCrans(CransLdap):
|
|||
# Il faut aussi regarder la blackliste du propriétaire
|
||||
p = self.proprietaire()
|
||||
bl_liste += p.blacklist()
|
||||
elif isinstance(self, Adherent) and (config.ann_scol in self.paiement()):
|
||||
elif isinstance(self, Adherent) and (config.ann_scol in self.paiement() or (self.adhesion() > time.time() and self.connexion() > time.time())):
|
||||
# blacklistes virtuelle si on est un adhérent pour carte étudiant et chambre invalides
|
||||
if not config.periode_transitoire and config.bl_carte_et_actif and not (config.ann_scol in self.carteEtudiant()) and not self.sursis_carte():
|
||||
if not config.periode_transitoire and config.bl_carte_et_actif and not bool(self.carteEtudiant()) and not self.sursis_carte():
|
||||
actifs['carte_etudiant']=('-','-')
|
||||
if self.chbre() == '????':
|
||||
actifs['chambre_invalide']=('-','-')
|
||||
|
@ -1144,9 +1168,7 @@ class BaseClasseCrans(CransLdap):
|
|||
[ index de la remarque à modifier, nouvelle remarque ]
|
||||
l'index est celui obtenu dans la liste retournée par info()
|
||||
"""
|
||||
if not self._data.has_key('info'):
|
||||
self._data['info'] = []
|
||||
liste = list(self._data['info'])
|
||||
liste = list(self._data.get('info', []))
|
||||
if new == None: return map(decode, liste)
|
||||
|
||||
if type(new) == list:
|
||||
|
@ -1180,8 +1202,12 @@ class BaseClasseCrans(CransLdap):
|
|||
return []
|
||||
|
||||
if not self.dn:
|
||||
# Enregistrement à placer en tête de base
|
||||
self.dn = self.base_dn
|
||||
# Enregistrement à placer en tête de base sauf si
|
||||
# facture.
|
||||
if isinstance(self, Facture):
|
||||
self.dn = self.proprietaire().dn
|
||||
else:
|
||||
self.dn = self.base_dn
|
||||
|
||||
# Construction de l'historique
|
||||
if not self._init_data:
|
||||
|
@ -1209,7 +1235,8 @@ class BaseClasseCrans(CransLdap):
|
|||
'puissance', 'canal', 'prise', 'responsable',
|
||||
'macAddress', 'ipHostNumber', 'ip6HostNumber',
|
||||
'host', 'positionBorne', 'derniereConnexion',
|
||||
'hotspot', 'dnsIpv6', 'machineAlias']:
|
||||
'hotspot', 'dnsIpv6', 'machineAlias', 'finAdhesion',
|
||||
'finConnexion', 'debutConnexion', 'debutAdhesion']:
|
||||
if champ in self.modifs:
|
||||
if champ not in self._init_data.keys():
|
||||
valeur_initiale = 'N/A'
|
||||
|
@ -1227,7 +1254,9 @@ class BaseClasseCrans(CransLdap):
|
|||
for champ in ['droits', 'controle', 'paiement', 'carteEtudiant',
|
||||
'mailAlias', 'hostAlias', 'exempt', 'nvram',
|
||||
'portTCPin', 'portTCPout', 'portUDPin', 'portUDPout',
|
||||
'homepageAlias', 'imprimeurClub', 'gpgFingerprint']:
|
||||
'homepageAlias', 'imprimeurClub', 'gpgFingerprint',
|
||||
'debutConnexion', 'finConnexion', 'debutAdhesion',
|
||||
'finAdhesion']:
|
||||
if champ in self.modifs:
|
||||
if champ == 'controle':
|
||||
# Ce n'est pas pareil que self._init_data.get('controle', [''])
|
||||
|
@ -1461,6 +1490,46 @@ class BaseProprietaire(BaseClasseCrans):
|
|||
self._init_data = {}
|
||||
self._modifiable = 'w'
|
||||
|
||||
def adhesion(self, update=False, f=None):
|
||||
"""
|
||||
Gestion de l'adhésion d'un adhérent
|
||||
|
||||
La durée d'adhésion ne peut être choisie
|
||||
* Si update vaut True, on ajoute un an
|
||||
* f est une facture passée en référence.
|
||||
|
||||
"""
|
||||
thetime = time.time()
|
||||
|
||||
# On récupère sur les factures l'ensemble de celles comportant une adhésion.
|
||||
adh_factures = self.factures_adh()
|
||||
finAdh = max([0.0] + [fromGeneralizedTimeFormat(facture._data.get('finAdhesion', ["19700101000000Z"])[0]) for facture in adh_factures if facture.controle() != "FALSE"])
|
||||
|
||||
if update == False:
|
||||
return finAdh
|
||||
else:
|
||||
ftime = datetime.datetime.fromtimestamp(max(finAdh, thetime))
|
||||
if not finAdh - thetime < cotisation.delai_readh:
|
||||
raise EnvironmentError, u"On ne peut réadhérer que 15 jours avant l'expiration de l'adhésion précédente."
|
||||
|
||||
# Calcul de la nouvelle date de fin d'adhésion.
|
||||
# le +86400 est une souplesse pour permettre au câblage de se passer sans warning
|
||||
# quand le mec se fait câbler pour un an.
|
||||
newFinAdh = time.mktime(ftime.replace(year=ftime.year + cotisation.duree_adh_an).timetuple()) + 86400
|
||||
|
||||
# Si aucune facture n'est passée en référence, on en crée une nouvelle.
|
||||
if f is None:
|
||||
f = Facture(self)
|
||||
if isinstance(self, Adherent):
|
||||
f.ajoute(cotisation.dico_adh)
|
||||
elif isinstance(self, Club):
|
||||
f.ajoute(cotisation.dico_adh_club)
|
||||
f._set("finAdhesion", [generalizedTimeFormat(newFinAdh)])
|
||||
f._set("debutAdhesion", [generalizedTimeFormat(thetime)])
|
||||
self._set("finAdhesion", self._data.get("finAdhesion", []) + [generalizedTimeFormat(newFinAdh)])
|
||||
self._set("debutAdhesion", self._data.get("debutAdhesion", []) + [generalizedTimeFormat(thetime)])
|
||||
return f
|
||||
|
||||
def droits(self, droits=None, light=False):
|
||||
""" Renvoie les droits courants. Non modifiable (sauf si surchargée dans classe enfant)"""
|
||||
if droits <> None:
|
||||
|
@ -1543,6 +1612,7 @@ class BaseProprietaire(BaseClasseCrans):
|
|||
if not isadm() and isadm(self.compte()):
|
||||
raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.')
|
||||
|
||||
|
||||
else:
|
||||
if type(new) == list:
|
||||
# Modif
|
||||
|
@ -1651,18 +1721,30 @@ class BaseProprietaire(BaseClasseCrans):
|
|||
else:
|
||||
return []
|
||||
|
||||
def factures(self):
|
||||
def factures(self, filtre=None):
|
||||
""" Retourne les factures (instances) appartenant à la classe """
|
||||
# Le champ id n'est pas initialisé lorsque le proprio est en cours
|
||||
# de création
|
||||
if filtre is None:
|
||||
filtre = Facture.filtre_idn
|
||||
else:
|
||||
filtre = "(&%s%s)" % (filtre, Facture.filtre_idn)
|
||||
if self.id():
|
||||
res = []
|
||||
for r in self.conn.search_s('%s=%s,%s' % (self.idn, self.id(), self.base_dn), 1, Facture.filtre_idn):
|
||||
for r in self.conn.search_s('%s=%s,%s' % (self.idn, self.id(), self.base_dn), 1, filtre):
|
||||
res.append(self.make(r, self._modifiable))
|
||||
return res
|
||||
else:
|
||||
return []
|
||||
|
||||
def factures_adh(self):
|
||||
""" Retourne les factures pour adhésion """
|
||||
return self.factures("(debutAdhesion=*)")
|
||||
|
||||
def factures_conn(self):
|
||||
""" Retourne les factures pour connexion """
|
||||
return self.factures("(debutConnexion=*)")
|
||||
|
||||
def solde(self, operation=None, comment=None):
|
||||
""" Retourne ou modifie le solde d'un propriétaire
|
||||
operation doit être un nombre positif ou négatif
|
||||
|
@ -1825,8 +1907,12 @@ class BaseProprietaire(BaseClasseCrans):
|
|||
return False
|
||||
|
||||
def paiement_ok(self):
|
||||
if config.ann_scol in self.paiement() or (config.periode_transitoire and (config.ann_scol-1) in self.paiement()):
|
||||
if config.periode_transitoire or not isinstance(self, Adherent) or not config.bl_carte_et_definitif or config.ann_scol in self.carteEtudiant():
|
||||
if isinstance(self, Adherent):
|
||||
m_paiement = min(self.adhesion(), self.connexion())
|
||||
else:
|
||||
m_paiement = self.adhesion()
|
||||
if config.ann_scol in self.paiement() or (config.periode_transitoire and (config.ann_scol-1) in self.paiement()) or (m_paiement > time.time()) or (config.periode_transitoire and config.debut_periode_transitoire <= m_paiement <= config.fin_periode_transitoire):
|
||||
if config.periode_transitoire or not isinstance(self, Adherent) or not config.bl_carte_et_definitif or bool(self.carteEtudiant()):
|
||||
return True
|
||||
else:
|
||||
return self.sursis_carte()
|
||||
|
@ -2281,6 +2367,47 @@ class Adherent(BaseProprietaire):
|
|||
# renvoie la valeur trouvée dans la base
|
||||
return bool(self._data.get('charteMA', []))
|
||||
|
||||
def connexion(self, mois=None, f=None):
|
||||
"""
|
||||
Gestion de la connexion d'un adhérent
|
||||
|
||||
* valeur est un entier définissant un nombre de mois
|
||||
* f est une facture
|
||||
"""
|
||||
thetime = time.time()
|
||||
|
||||
# On récupère sur les factures l'ensemble de celles comportant une connexion.
|
||||
conn_factures = self.factures_conn()
|
||||
finConn = max([0.0] + [fromGeneralizedTimeFormat(facture._data.get('finConnexion', ["19700101000000Z"])[0]) for facture in conn_factures if facture.controle() != "FALSE"])
|
||||
|
||||
if mois is None:
|
||||
return finConn
|
||||
elif not isinstance(mois, int):
|
||||
raise ValueError, u"Le nombre de mois doit être un entier"
|
||||
else:
|
||||
ftime = max(finConn, thetime)
|
||||
|
||||
# Calcul de la nouvelle date de fin d'adhésion.
|
||||
curyear = datetime.datetime.now().year
|
||||
curmonth = datetime.datetime.now().month
|
||||
nbJours = 0
|
||||
for i in xrange(1, mois+1):
|
||||
nbJours += monthrange((curmonth + i - 1)/12 + curyear, (curmonth + i - 1)%12 + 12 * ((curmonth + i - 1) % 12 == 0))[1]
|
||||
|
||||
# On ajoute 3600 secondes sur suggestion de Raphaël Bonaque (<bonaque@crans.org>), pour tenir compte des malheureux qui
|
||||
# pourraient subir le changement d'heure.
|
||||
newFinConn = ftime + 86400 * nbJours + 3600
|
||||
|
||||
# Si aucune facture n'est passée en référence, on en crée une nouvelle.
|
||||
if f is None:
|
||||
f = Facture(self)
|
||||
f.ajoute(cotisation.dico_cotis(mois))
|
||||
f._set("finConnexion", [generalizedTimeFormat(newFinConn)])
|
||||
f._set("debutConnexion", [generalizedTimeFormat(thetime)])
|
||||
f._set("finConnexion", self._data.get("finConnexion", []) + [generalizedTimeFormat(newFinConn)])
|
||||
f._set("debutConnexion", self._data.get("debutConnexion", []) + [generalizedTimeFormat(thetime)])
|
||||
return f
|
||||
|
||||
def adherentPayant(self, valeur = None):
|
||||
"""
|
||||
L'adhérent paie sa cotisation (a droit au WiFi, à un compte Crans, ... True par défaut
|
||||
|
@ -2354,7 +2481,11 @@ class Adherent(BaseProprietaire):
|
|||
si positif ajoute l'année à la liste
|
||||
si négatif le supprime
|
||||
"""
|
||||
return self._an('carteEtudiant', action)
|
||||
if action == True:
|
||||
self._set('carteEtudiant', ['TRUE'])
|
||||
elif action == False:
|
||||
self._set('carteEtudiant', [])
|
||||
return bool(self._data.get('carteEtudiant', []))
|
||||
|
||||
def checkPassword(self, password):
|
||||
"""Vérifie le mot de passe de l'adhérent"""
|
||||
|
@ -3803,7 +3934,7 @@ class Facture(BaseClasseCrans):
|
|||
|
||||
self._set('modePaiement', [new])
|
||||
|
||||
return decode(self._data.get('modePaiement', [None])[0])
|
||||
return decode(self._data.get('modePaiement', [''])[0])
|
||||
|
||||
def recuPaiement(self, new=None):
|
||||
"""
|
||||
|
@ -3833,6 +3964,19 @@ class Facture(BaseClasseCrans):
|
|||
# renvoie la valeur trouvée dans la base
|
||||
return self._data.get("recuPaiement", [None])[0]
|
||||
|
||||
def controle(self, action=None):
|
||||
if action is None:
|
||||
return self._data.get("controle", [''])[0]
|
||||
else:
|
||||
if action == True:
|
||||
self._set("controle", ["TRUE"])
|
||||
elif action == False:
|
||||
self._set("controle", ["FALSE"])
|
||||
elif action == "":
|
||||
self._set("controle", [])
|
||||
else:
|
||||
raise ValueError("Mauvaise valeur pour l'attribut controle : %r" % (repr(action),))
|
||||
|
||||
def _del_recu_paiement(self):
|
||||
""" Pour test """
|
||||
self._set("recuPaiement", [])
|
||||
|
@ -3940,7 +4084,7 @@ class Facture(BaseClasseCrans):
|
|||
# enregistre la nouvelle liste
|
||||
self._articles(arts)
|
||||
|
||||
def supprime(self, supprime):
|
||||
def supprime(self, supprime=None, pop=None):
|
||||
"""Supprime un/des article(s) à la facture
|
||||
arts est un article ou une liste d'articles
|
||||
"""
|
||||
|
@ -3951,6 +4095,9 @@ class Facture(BaseClasseCrans):
|
|||
# charge la liste des articles
|
||||
arts = self._articles()
|
||||
|
||||
if pop is not None:
|
||||
_ = arts.pop()
|
||||
|
||||
# on supprime les anciens articles
|
||||
if type(supprime)==dict:
|
||||
supprime = [supprime]
|
||||
|
@ -4062,6 +4209,10 @@ class _FakeProprio(CransLdap):
|
|||
for r in res:
|
||||
m.append(self.make(r))
|
||||
return m
|
||||
def adhesion(self):
|
||||
return time.time() + 86400
|
||||
def connexion(self):
|
||||
return time.time() + 86400
|
||||
|
||||
class AssociationCrans(_FakeProprio):
|
||||
""" Classe définissant l'assoce (pour affichage de ses machines) """
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue