adherent.py 42.4 KB
Newer Older
1
#!/bin/bash /usr/scripts/python.sh
2
# -*- mode: python; coding: utf-8 -*-
3

4
"""
5 6 7 8
Copyright (C) Valentin Samir
Licence : GPLv3

"""
9 10 11

from __future__ import unicode_literals

12 13
import sys
import time
14
import datetime
15
from dateutil.relativedelta import relativedelta
16
import subprocess
17
import pytz
18

19 20
import config.cotisation

21 22 23
import lc_ldap.objets as objets
import lc_ldap.attributs as attributs
from lc_ldap.attributs import UniquenessError
24
from lc_ldap import crans_utils
25 26 27 28 29

import proprio
from CPS import TailCall, tailcaller, Continue

class Dialog(proprio.Dialog):
30 31 32
    """
    Classe Dialog spécifique aux adhérents pour gest_crans_lc
    """
33 34
    def modif_adherent_blacklist(self, adherent, cont):
        """Raccourci vers edit_blacklist spécifique aux adherent"""
35 36 37 38 39 40 41 42 43
        return self.edit_blacklist(
            obj=adherent,
            title="Éditions des blacklist de %s %s" % (
                adherent['prenom'][0],
                adherent['nom'][0]
            ),
            update_obj='adherent',
            cont=cont
        )
44

45
    def modif_adherent(self, cont, adherent=None, proprio=None, tag=None):
46
        """Menu d'édition d'un adhérent"""
47 48
        if proprio:
            adherent = proprio
49
        if adherent is None:
50 51 52 53 54
            adherent = self.select(
                ["adherent"],
                "Recherche d'un adhérent pour modification",
                cont=cont
            )
55 56 57 58 59 60 61 62 63 64
        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],
65
            'Blackliste':[a.bureau, a.nounou],
66
            'Vente':[a.cableur, a.nounou],
67
            'Ticket':[a.cableur, a.nounou],
68 69 70
            'Supprimer':[a.nounou, a.bureau],
        }
        menu = {
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
            'Administratif' : {
                'text' : "Adhésion, chartes",
                "callback" : self.adherent_administratif
            },
            'Personnel' : {
                'text' : "Nom, prénom, téléphone, et mail de contact",
                "adherent" : "proprio",
                'callback' : self.proprio_personnel
            },
            'Études'       : {
                'text' : "Étude en cours",
                "callback" : self.adherent_etudes
            },
            'Chambre'      : {
                'text' : 'Déménagement',
                "adherent" : "proprio",
                "callback" : self.proprio_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' : {
Maxime Bombar's avatar
Maxime Bombar committed
100
                'text' : 'Ajouter ou supprimer une empreinte GPG',
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
                '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
            },
            'Ticket' : {
                'text' : "Imprime un ticket complet avec toutes les machines de l'utilisateur",
                "adherent" : "proprio",
                "callback" : self.proprio_ticket
            },
            'Supprimer' : {
                'text' : "Supprimer l'adhérent de la base de donnée",
                'callback' : TailCall(
                    self.delete_adherent,
                    del_cont=cont(proprio=None)
                )
            },
132 133 134
        }
        menu_order = ['Administratif', 'Personnel', 'Études', 'Chambre', 'Compte']
        menu_compte_crans = ['Droits']
135
        menu_end = ['GPGFingerprint', 'Remarques', 'Blackliste', 'Vente', 'Ticket', 'Supprimer']
136 137 138

        if "cransAccount" in adherent['objectClass']:
            menu_order.extend(menu_compte_crans)
Pierre-Elliott Bécue's avatar
Pierre-Elliott Bécue committed
139 140
        menu_order.extend(menu_end)

141 142 143 144
        def box(default_item=None):
            choices = []
            for key in menu_order:
                if self.has_right(menu_droits[key], adherent):
Pierre-Elliott Bécue's avatar
Pierre-Elliott Bécue committed
145
                    choices.append((key, menu[key]['text'], menu[key].get('help', "")))
146 147 148 149 150 151 152
            return self.dialog.menu(
                "Que souhaitez vous modifier ?",
                width=0,
                height=0,
                menu_height=0,
                timeout=self.timeout,
                item_help=1,
153
                default_item=unicode(default_item),
154 155 156
                title="Modification de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                scrollbar=True,
                cancel_label="Retour",
157
                backtitle=self._connected_as(),
158 159
                choices=choices
            )
160 161 162 163 164 165

        def todo(tag, menu, adherent, cont_ret):
            if not tag in menu_order:
                raise Continue(cont_ret)
            else:
                if 'callback' in menu[tag]:
166 167 168 169 170 171 172 173 174
                    raise Continue(
                        TailCall(
                            menu[tag]['callback'],
                            cont=cont_ret,
                            **{
                                menu[tag].get('adherent', 'adherent') : adherent
                            }
                        )
                    )
175
                elif 'attribut' in menu[tag]:
176 177 178 179 180 181 182 183
                    raise Continue(
                        TailCall(
                            self.modif_adherent_attributs,
                            adherent=adherent,
                            cont=cont_ret,
                            attr=menu[tag]['attribut'].ldap_name
                        )
                    )
184
                else:
185 186 187
                    raise EnvironmentError(
                        "Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag
                    )
188 189 190 191 192 193 194 195 196

        (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,
197
            codes_todo=[([self.dialog.OK], todo, [tag, menu, adherent, cont_ret])]
198 199
        )

200 201
    def modif_adherent_attributs(self, adherent, attr, cont):
        """Juste un raccourci vers edit_attributs spécifique aux adherents"""
202 203 204 205 206 207 208
        return self.edit_attributs(
            obj=adherent,
            update_obj='adherent',
            attr=attr,
            title="Modification de %s %s" % (adherent['prenom'][0], adherent['nom'][0]),
            cont=cont
        )
209

210 211 212 213 214 215 216 217 218 219
    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 = {
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
            "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
            },
235 236 237 238 239 240 241 242 243 244
        }
        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,
245
                default_item=unicode(default_item),
246 247 248
                title="Gestion administrative de %s %s" % (adherent.get('prenom', [''])[0], adherent["nom"][0]),
                scrollbar=True,
                cancel_label="Retour",
249
                backtitle=self._connected_as(),
250 251 252 253 254 255 256 257
                choices=[
                    (
                        k,
                        menu[k]['text'],
                        menu[k]['help']
                    ) for k in menu_order if self.has_right(menu_droits[k], adherent)
                ]
            )
258 259 260 261 262

        def todo(tag, menu, adherent, self_cont):
            if not tag in menu_order:
                raise Continue(self_cont)
            elif 'callback' in menu[tag]:
263 264 265 266 267 268 269
                raise Continue(
                    TailCall(
                        menu[tag]['callback'],
                        cont=self_cont,
                        adherent=adherent
                    )
                )
270
            else:
271 272 273
                raise EnvironmentError(
                    "Il n'y a pas de champs 'callback' pour le tag %s" % tag
                )
274 275

        (code, tag) = self.handle_dialog(cont, box, default_item)
276 277 278 279 280 281
        self_cont = TailCall(
            self.adherent_administratif,
            adherent=adherent,
            cont=cont,
            default_item=tag
        )
282 283 284 285 286
        return self.handle_dialog_result(
            code=code,
            output=tag,
            cancel_cont=cont,
            error_cont=self_cont,
287
            codes_todo=[([self.dialog.OK], todo, [tag, menu, adherent, self_cont])]
288 289
        )

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
    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:
317
                self.dialog.msgbox(
318
                    text=u"Adhésion effectué avec succès",
319 320 321 322 323
                    title=u"Adhésion terminé",
                    width=0,
                    height=0,
                    timeout=self.timeout
                )
324
            elif not adhesion and connexion:
325
                self.dialog.msgbox(
326
                    text=u"Connexion prolongée avec succès",
327 328 329 330 331
                    title=u"Connexion prolongée",
                    width=0,
                    height=0,
                    timeout=self.timeout
                )
332
            elif adhesion and connexion:
333
                self.dialog.msgbox(
334
                    text=u"Adhésion effectué et connexion prolongée avec succès",
335 336 337 338 339
                    title=u"Connexion & Adhésion",
                    width=0,
                    height=0,
                    timeout=self.timeout
                )
340 341 342 343
        except KeyboardInterrupt:
            pass
        if facture['modePaiement'][0] == "solde":
            try:
344 345 346 347 348 349 350
                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
                )
351 352 353 354
            except KeyboardInterrupt:
                pass
        return adherent

355 356
    def adherent_adhesion(self, cont, adherent, cancel_cont=None, tag_paiment=None,
                          comment_paiement=None, crediter=True, facture=None):
357 358 359 360 361 362 363 364
        """
        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
        """
365 366 367

        # Boite si on ne peux pas réahdérer
        def box_already(end):
368 369 370 371 372 373 374 375 376
            self.dialog.msgbox(
                "Actuellement adhérent jusqu'au %s.\n"
                "Merci 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,
377 378 379 380
            )

        # Boite de confirmation à l'ahésion
        def box_adherer(end=None):
381 382 383 384 385 386 387
            now = crans_utils.localized_datetime()

            if end < now:
                new_end = now + relativedelta(years=1)
            else:
                new_end = end + relativedelta(years=1)

388
            if end != crans_utils.localized_datetime():
389 390 391 392
                adherer = self.confirm(
                    text="Adhésion jusqu'au %s. Réadhérer ?" % new_end,
                    title="Adhésion de %s %s" % (adherent.get("prenom", [''])[0], adherent["nom"][0])
                )
393
            else:
394 395 396 397
                adherer = self.confirm(
                    text="Adhésion pour un an, continuer ?",
                    title="Adhésion de %s %s" % (adherent.get("prenom", [''])[0], adherent["nom"][0])
                )
398 399
            return adherer

400
        # Suppression d'une facture si elle est généré mais non validé
401 402 403 404 405
        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)
406

407
        # Génération de la facture pour adhésion
408
        def paiement(tag_paiement, adherent, finadhesion, comment, facture, cancel_cont, cont):
409
            now = crans_utils.localized_datetime()
410 411 412
            if hasattr(finadhesion, 'value'):
                finadhesion = finadhesion.value
            new_finadhesion = max(finadhesion, now) + relativedelta(years=1)
413
            new_debutadhesion = now
414 415 416 417 418 419
            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
420
            with facture as facture:
421 422
                facture['modePaiement'] = tag_paiement
                facture['info'] = comment
423
                facture['article'].append(config.cotisation.dico_adh)
424 425
                facture["finAdhesion"] = new_finadhesion
                facture["debutAdhesion"] = new_debutadhesion
426
                # On peut retarder le credit pour ajouter des contribution pour la connexion internet à la facture
427
                if crediter:
428 429
                    if self.confirm_item(
                        item=facture,
430 431 432 433 434 435
                        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:
436 437 438 439
                        if not self.confirm(
                            text=u"Le paiement n'a pas été reçue.\n Annuler ?",
                            title="Annulation de l'adhésion",
                            defaultno=True):
440
                            raise Continue(cancel_cont)
441
                else:
442 443 444 445 446 447
                    if to_create:
                        facture.create()
                    else:
                        facture.validate_changes()
                        facture.history_gen()
                        facture.save()
448
                    raise Continue(cont(facture=facture))
449
            raise Continue(cont(adherent=adherent))
450 451


452 453 454 455 456
        now = crans_utils.localized_datetime()
        try:
            finadhesion = adherent.fin_adhesion().value
        except AttributeError:
            finadhesion = now
457
        # Si fin de l'adhésion trop loin dans le futur, rien a faire
458
        if finadhesion and (finadhesion - now).days > config.cotisation.delai_readh_jour:
459
            self.handle_dialog(cancel_cont if cancel_cont else cont, box_already, finadhesion)
460
            raise Continue(cancel_cont if cancel_cont else cont)
461 462 463

        # Sinon, si on accepte l'adhésion
        elif tag_paiment or self.handle_dialog(cont, box_adherer, finadhesion):
464 465 466 467 468 469 470 471 472 473
            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
            )
474 475
            # On choisi un mode de paiement
            if not tag_paiment or not comment_paiement:
476 477 478 479 480 481 482 483 484
                return self.proprio_choose_paiement(
                    proprio=adherent,
                    cont=self_cont,
                    cancel_cont=TailCall(
                        delete_facture,
                        facture,
                        cancel_cont if cancel_cont else cont
                    )
                )
485
            else:
486 487
                lcont = self_cont.copy()
                lcont(comment_paiement=None)
488
                return self.handle_dialog_result(
489
                    code=self.dialog.OK,
490
                    output=[],
491 492
                    cancel_cont=lcont,
                    error_cont=lcont,
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
                    codes_todo=[
                        (
                            [self.dialog.OK],
                            paiement,
                            [
                                tag_paiment,
                                adherent,
                                finadhesion,
                                comment_paiement,
                                facture,
                                lcont,
                                cont
                            ]
                        )
                    ]
508 509
                )
        else:
510
            return self.handle_dialog_result(
511
                code=self.dialog.OK,
512 513 514
                output=[],
                cancel_cont=None,
                error_cont=cancel_cont if cancel_cont else cont,
515 516 517 518 519 520 521
                codes_todo=[
                    (
                        [self.dialog.OK],
                        delete_facture,
                        [facture, cancel_cont if cancel_cont else cont]
                    )
                ]
522 523
            )

524 525
    def adherent_connexion(self, cont, adherent, cancel_cont=None, facture=None, mois=None,
                           default_item=None, tag_paiment=None, comment_paiement=None):
526 527 528 529 530 531 532 533 534
        """
        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
        """
535
        menu = {
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
            "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
                )
            },
564 565 566 567
        }
        menu_order = ["An"]
        for i in range(1, config.cotisation.duree_conn_plafond):
            if config.cotisation.contribution * i < config.cotisation.plafond_contribution:
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
                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
                    )
584
                }
585 586 587 588 589
                menu_order.append("%s mois" % i)

        if facture:
            menu_order.append("NC")

590
        # Une boite pour choisir un nombre de mois pour prolonger la connexion
591
        def box(finconnexion, default_item=None):
592
            t_end = finconnexion
593
            return self.dialog.menu(
594
                "Connexion jusqu'au %s" % t_end if finconnexion != datetime.datetime.fromtimestamp(0, tz=pytz.utc) else "N'a jamais été connecté",
595 596 597 598 599
                width=0,
                height=0,
                menu_height=0,
                timeout=self.timeout,
                item_help=0,
600
                default_item=unicode(default_item),
601 602 603
                title="Connexion de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                scrollbar=True,
                cancel_label="Retour",
604
                backtitle=self._connected_as(),
605 606
                choices=[(k, menu[k]['text']) for k in menu_order]
            )
607

608
        # Génération et crédit de la facture
609 610
        def todo(adherent, mois, finadhesion, finconnexion, cancel_cont,
                 cont, facture=None, tag_paiment=None, comment=None):
611
            # TODO : utiliser la methode lc_ldap adhesion_connexion()
612
            now = crans_utils.localized_datetime()
613
            if isinstance(finconnexion, attributs.generalizedTimeFormat):
614
                finconnexion = finconnexion.value
615 616
            new_debutconnexion = max(now, finconnexion)
            new_finconnexion = new_debutconnexion + relativedelta(months=mois)
617

618
            if new_debutconnexion > finadhesion.value:
619 620 621 622 623 624
                raise ValueError(
                    "Le début de la connexion (%s) est après la fin de l'adhésion (%s)." % (
                        new_debutconnexion,
                        finadhesion.value
                    )
                )
625
            if (new_finconnexion - finadhesion.value).days > 0:
626
                t_end_adh = finadhesion.value
627
                t_end_conn = new_finconnexion
628 629 630 631 632 633
                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 connexion"):
634
                    raise Continue(cancel_cont)
635
            # On édite une facture existante
636 637 638
            if facture:
                with self.conn.search(dn=facture.dn, scope=0, mode='rw')[0] as facture:
                    if mois:
639 640
                        facture["finConnexion"] = new_finconnexion
                        facture["debutConnexion"] = new_debutconnexion
641
                        facture["article"].append(config.cotisation.dico_cotis(mois))
642 643 644 645 646 647 648 649
                    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):
650 651 652
                        # 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:
653 654 655 656
                        if not self.confirm(
                            text=u"Le paiement n'a pas été reçue.\n Annuler ?",
                            title="Annulation de l'adhésion",
                            defaultno=True):
657 658 659 660 661 662
                            raise Continue(cancel_cont)
                        else:
                            facture.delete()

            else:
                if tag_paiment is None or comment is None:
663 664 665
                    raise ValueError(
                        "Il faut définir une méthode de paiement avec un commentaire"
                    )
666
                if not mois:
667 668 669
                    raise ValueError(
                        "Il faut prolonger la connexion d'un nombre de mois strictement positif"
                    )
670
                with self.conn.newFacture(adherent.dn, {}) as facture:
671
                    facture['modePaiement'] = tag_paiment
672
                    facture['article'].append(config.cotisation.dico_cotis(mois))
673
                    facture['info'] = comment
674 675
                    facture["finConnexion"] = new_finconnexion
                    facture["debutConnexion"] = new_debutconnexion
676 677 678 679 680 681 682 683
                    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):
684 685 686
                        # 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:
687 688 689 690
                        if not self.confirm(
                            text=u"Le paiement n'a pas été reçue.\n Annuler ?",
                            title="Annulation de l'adhésion",
                            defaultno=True):
691
                            raise Continue(cancel_cont)
692
            raise Continue(cont)
693 694 695 696

        def todo_mois(tag, self_cont):
            if tag == 'An':
                mois = 12
697 698
            elif tag == 'NC':
                mois = 0
699
            else:
700
                mois = int(tag.split(' ', 1)[0])
701 702
            raise Continue(self_cont(mois=mois, default_item=tag))

703 704 705 706 707 708 709 710 711 712 713
        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
        )
714 715

        finadhesion = adherent.fin_adhesion()
716 717 718
        # Si on édite une facture, on prolonge la date de finadhesion
        if facture and facture["finAdhesion"]:
            finadhesion = max(finadhesion, facture["finAdhesion"][0])
719 720
        finconnexion = adherent.fin_connexion()

721 722
        # Si l'adhésion fini avant la nouvelle fin de connexion, on réadhère si on est dans le delais, sinon warning simple sur la réadhésion
        now = crans_utils.localized_datetime()
723
        if (finadhesion <= now or
724
            finadhesion <= finconnexion):
725 726 727 728
            if finadhesion:
                # Si l'adhésion est déjà fini
                if finadhesion <= now:
                    if finadhesion == datetime.datetime.fromtimestamp(0, tz=pytz.utc):
729 730 731 732 733 734 735 736
                        self.dialog.msgbox(
                            text=u"L'adhérent n'a jamais adhéré à l'association, "
                                  "on va d'abord le faire adhérer (10€)",
                            title="Adhésion nécessaire",
                            width=0,
                            height=0,
                            timeout=self.timeout
                        )
737
                    else:
738 739 740 741 742 743 744 745
                        self.dialog.msgbox(
                            text=u"L'adhésion a expiré le %s, il va falloir "
                                  "réadhérer d'abord (10€)" % finadhesion,
                            title="Réadhésion nécessaire",
                            width=0,
                            height=0,
                            timeout=self.timeout
                        )
746 747 748
                # Sinon si elle fini avant la fin de la connexion courante
                elif finadhesion < finconnexion:
                    t_end_conn = finconnexion
749 750 751 752 753 754 755 756
                    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€)" % (finadhesion, t_end_conn),
                        title="Réadhésion nécessaire",
                        width=0,
                        height=0,
                        timeout=self.timeout
                    )
757
                # É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
758 759 760 761 762 763
            return self.adherent_adhesion(
                cont=self_cont,
                cancel_cont=cont,
                adherent=adherent,
                crediter=False
            )
764 765 766

        # Si on édite une facture, elle vient actuellement forcement de adherent_adhesion
        if facture and cancel_cont is None:
767 768 769 770 771 772 773 774 775 776
            cancel_cont = TailCall(
                self.adherent_adhesion,
                cont=self_cont,
                adherent=adherent,
                cancel_cont=cont,
                tag_paiment=facture['modePaiement'][0] if facture['modePaiement'] else None,
                comment_paiement=None,
                crediter=False,
                facture=facture
            )
777
            self_cont(cancel_cont=cancel_cont)
778 779

        # On choisi le nombre de mois pour prolonger la connexion
780 781 782
        if mois is None:
            (code, tag) = self.handle_dialog(cont, box, finconnexion, default_item)
            return self.handle_dialog_result(
783 784 785 786 787 788
                code=code,
                output=[],
                cancel_cont=cancel_cont if cancel_cont else cont,
                error_cont=self_cont,
                codes_todo=[([self.dialog.OK], todo_mois, [tag, self_cont])]
            )
789
        # Si on connait le moyen de paiement (il peut être a l'intérieure de la facture existante)
790
        elif tag_paiment or facture:
791
            lcont = self_cont.copy()
792 793 794 795 796
            if facture:
                lcont(mois=None)
            else:
                lcont(tag_paiment=None)
            return self.handle_dialog_result(
797
                    code=self.dialog.OK,
798 799 800
                    output=[],
                    cancel_cont=lcont,
                    error_cont=lcont,
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
                    codes_todo=[
                        (
                            [self.dialog.OK],
                            todo,
                            [
                                adherent,
                                mois,
                                finadhesion,
                                finconnexion,
                                lcont,
                                cont,
                                facture,
                                tag_paiment,
                                comment_paiement
                            ]
                        )
                    ]
818
                )
819
        # Sinon, il faut choisir une méthode de paiement
820
        else:
821
            lcont = self_cont.copy()
822
            lcont(mois=None)
823 824 825 826 827
            return self.proprio_choose_paiement(
                proprio=adherent,
                cont=self_cont,
                cancel_cont=lcont
            )
828
        return cont
829

830 831 832 833
    def adherent_charte(self, cont, adherent):
        a = attributs
        attribs = [a.charteMA]
        return self.edit_boolean_attributs(
834 835 836 837 838 839 840 841 842
            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
        )
843 844 845

    def create_adherent(self, cont):
        """Crée un adhérent et potentiellement son compte crans avec lui"""
846 847
        def mycont(proprio=None, **kwargs):
            if proprio:
848
                # Une fois l'adhérent créé, on vois s'il adhére/prend la connexion internet
849
                #adh_cont = TailCall(self.modif_adherent, cont=cont, adherent=adherent)
850 851 852 853 854 855 856 857 858 859
                conn_cont = TailCall(
                    self.adherent_connexion,
                    cont=cont(proprio=proprio),
                    adherent=proprio
                )
                etude_cont = TailCall(
                    self.adherent_etudes,
                    cont=conn_cont,
                    adherent=proprio
                )
860
                etude_cont(cancel_cont=etude_cont)
861 862
                # Comme on crée une facture, pas de retour possible
                conn_cont(cancel_cont=conn_cont)
863 864 865
                raise Continue(etude_cont)
            else:
                raise Continue(cont)
866
        return self.proprio_personnel(cont=TailCall(mycont))
867 868 869 870

    def delete_adherent(self, cont, del_cont, adherent=None):
        """Permet la suppression d'un adhérent de la base ldap"""
        if adherent is None:
871 872 873 874 875
            adherent = self.select(
                ["adherent"],
                "Recherche d'un adhérent pour supression",
                cont=cont
            )
876 877

        def todo(adherent):
878 879 880 881
            if self.confirm_item(
                item=adherent,
                title="Voulez vous vraiement supprimer l'adhérent ?",
                defaultno=True):
882 883
                with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
                    adherent.delete()
884 885 886 887 888
                self.dialog.msgbox(
                    "L'adherent a bien été supprimé",
                    timeout=self.timeout,
                    title="Suppression d'un adherent"
                )
889 890 891 892 893
                raise Continue(del_cont(proprio=None))
            else:
                raise Continue(cont)

        return self.handle_dialog_result(
894
            code=self.dialog.OK,
895
            output="",
896 897
            cancel_cont=cont(adherent=adherent),
            error_cont=cont(adherent=adherent),
898
            codes_todo=[([self.dialog.OK], todo, [adherent])]
899 900 901 902 903 904
        )

    def adherent_droits(self, adherent, cont, choices_values=None):
        """Gestion des droits d'un adhérent"""
        def box():
            return self.dialog.checklist(
905 906 907 908 909 910 911 912 913 914 915 916 917 918
                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
            )
919 920 921
        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:
922
                adherent['droits'] = [unicode(d) for d in droits]
923
                adherent.validate_changes()
924 925 926 927 928 929 930
                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)
931 932 933 934 935 936 937 938 939 940 941 942
        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
            ]
        )
943 944 945 946 947
        return self.handle_dialog_result(
            code=code,
            output=droits,
            cancel_cont=cont,
            error_cont=self_cont,
948
            codes_todo=[([self.dialog.OK], todo, [droits, adherent, self_cont, cont])]
949 950 951
        )


952
    def adherent_etudes(self, adherent, cont, cancel_cont=None, default_item=None, etablissement=None):
953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984
        """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 box_etablissement(default_item):
985 986 987
            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])):
988
                return self.dialog.inputbox(
989 990 991 992 993
                    text="Choisissez l'établissement :",
                    title="Études de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
                    timeout=self.timeout,
                    init=unicode(default_item) if default_item else ""
                )
994 995
            else:
                return self.dialog.menu(
996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
                    "Choisissez l'établissement :",
                    width=0,
                    height=0,
                    menu_height=0,
                    timeout=self.timeout,
                    item_help=0,
                    default_item=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
                )

        self_cont = TailCall(
            self.adherent_etudes,
            adherent=adherent,
            cont=cont,
            cancel_cont=cancel_cont,
            default_item=default_item,
            etablissement=etablissement
        )
1018 1019
        if etablissement is None or etablissement == 'Autre':
            if not default_item and adherent["etudes"]:
1020
                default_item = unicode(adherent["etudes"][0])
1021
            lcont = cancel_cont if cancel_cont else cont
1022 1023 1024 1025 1026
            (code, etablissement) = self.handle_dialog(cancel_cont, box_etablissement, default_item)
            output = etablissement
            self_cont(default_item=etablissement)
        else:
            output = ""
1027 1028 1029 1030 1031 1032 1033 1034
            lcont = TailCall(
                self.adherent_etudes,
                adherent=adherent,
                cont=cont,
                cancel_cont=cancel_cont,
                default_item=section,
                etablissement=etablissement
            )
1035

1036
        def todo(etablissement, adherent, self_cont, cont):
1037 1038 1039
            if etablissement is None or etablissement == 'Autre':
                raise Continue(self_cont(default_item=None, etablissement=etablissement))
            else:
1040
                if not adherent["etudes"] or adherent["etudes"][0] != etablissement:
1041
                    with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
1042
                        adherent["etudes"] = unicode(etablissement)
1043
                        adherent.validate_changes()
1044 1045 1046 1047 1048 1049
                        adherent.history_gen()
                        adherent.save()
                raise Continue(cont(adherent=adherent))
        return self.handle_dialog_result(
            code=code,
            output=output,
1050
            cancel_cont=lcont,
1051
            error_cont=self_cont,
1052
            codes_todo=[([self.dialog.OK], todo, [etablissement, adherent, self_cont, cont])]
1053
        )