Ce serveur Gitlab sera éteint le 30 juin 2020, pensez à migrer vos projets vers les serveurs gitlab-research.centralesupelec.fr et gitlab-student.centralesupelec.fr !

views.py 28.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017  Gabriel Détraz
# Copyright © 2017  Goulven Kermarec
# Copyright © 2017  Augustin Lemesle
#
# This program 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 program 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.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

23 24 25
# App de gestion des users pour re2o
# Goulven Kermarec, Gabriel Détraz
# Gplv2
26 27 28 29
"""cotisations.views
The different views used in the Cotisations module
"""

30
from __future__ import unicode_literals
chirac's avatar
chirac committed
31
import os
32 33

from django.urls import reverse
34
from django.shortcuts import render, redirect
35
from django.contrib.auth.decorators import login_required
36
from django.contrib import messages
chirac's avatar
chirac committed
37
from django.db.models import ProtectedError
38
from django.db.models import Q
Dalahro's avatar
Dalahro committed
39
from django.forms import modelformset_factory, formset_factory
chirac's avatar
chirac committed
40
from django.utils import timezone
41
from django.utils.translation import ugettext as _
42

chirac's avatar
chirac committed
43
# Import des models, forms et fonctions re2o
44
from reversion import revisions as reversion
chirac's avatar
chirac committed
45 46 47 48
from users.models import User
from re2o.settings import LOGO_PATH
from re2o import settings
from re2o.views import form
49
from re2o.utils import SortTable, re2o_paginator
50
from re2o.acl import (
51 52 53 54
    can_create,
    can_edit,
    can_delete,
    can_view,
55 56 57
    can_view_all,
    can_delete_set,
    can_change,
58
)
chirac's avatar
chirac committed
59 60
from preferences.models import OptionalUser, AssoOption, GeneralOption
from .models import Facture, Article, Vente, Paiement, Banque
61 62 63 64 65 66 67 68 69 70 71 72
from .forms import (
    NewFactureForm,
    EditFactureForm,
    ArticleForm,
    DelArticleForm,
    PaiementForm,
    DelPaiementForm,
    BanqueForm,
    DelBanqueForm,
    NewFactureFormPdf,
    SelectUserArticleForm,
    SelectClubArticleForm,
73 74
    CreditSoldeForm,
    RechargeForm
75
)
76
from .tex import render_invoice
77
from .payment_methods.forms import payment_method_factory
78

79

chirac's avatar
chirac committed
80
@login_required
81 82 83
@can_create(Facture)
@can_edit(User)
def new_facture(request, user, userid):
84 85 86 87 88 89 90 91 92 93 94 95 96 97
    """
    View called to create a new invoice.
    Currently, Send the list of available articles for the user along with
    a formset of a new invoice (based on the `:forms:NewFactureForm()` form.
    A bit of JS is used in the template to add articles in a fancier way.
    If everything is correct, save each one of the articles, save the
    purchase object associated and finally the newly created invoice.

    TODO : The whole verification process should be moved to the model. This
    function should only act as a dumb interface between the model and the
    user.
    """
    invoice = Facture(user=user)
    # The template needs the list of articles (for the JS part)
98 99 100
    article_list = Article.objects.filter(
        Q(type_user='All') | Q(type_user=request.user.class_name)
    )
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
101 102 103 104 105 106
    # Building the invoice form and the article formset
    invoice_form = NewFactureForm(
        request.POST or None,
        instance=invoice,
        user=request.user
    )
107

108
    if request.user.is_class_club:
109
        article_formset = formset_factory(SelectClubArticleForm)(
110
            request.POST or None,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
111
            form_kwargs={'user': request.user}
112
        )
113
    else:
114
        article_formset = formset_factory(SelectUserArticleForm)(
115
            request.POST or None,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
116
            form_kwargs={'user': request.user}
117
        )
118 119 120

    if invoice_form.is_valid() and article_formset.is_valid():
        new_invoice_instance = invoice_form.save(commit=False)
121
        articles = article_formset
122
        # Check if at leat one article has been selected
123
        if any(art.cleaned_data for art in articles):
124 125 126
            new_invoice_instance.save()

            # Building a purchase for each article sold
127 128 129 130
            for art_item in articles:
                if art_item.cleaned_data:
                    article = art_item.cleaned_data['article']
                    quantity = art_item.cleaned_data['quantity']
131 132
                    new_purchase = Vente.objects.create(
                        facture=new_invoice_instance,
chirac's avatar
chirac committed
133 134
                        name=article.name,
                        prix=article.prix,
135
                        type_cotisation=article.type_cotisation,
chirac's avatar
chirac committed
136 137 138
                        duration=article.duration,
                        number=quantity
                    )
139 140
                    new_purchase.save()

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
141 142 143 144
            return new_invoice_instance.paiement.end_payment(
                new_invoice_instance,
                request
            )
145

146
        messages.error(
chirac's avatar
chirac committed
147
            request,
148 149 150 151
            _("You need to choose at least one article.")
        )
    return form(
        {
152
            'factureform': invoice_form,
153 154 155 156 157
            'venteform': article_formset,
            'articlelist': article_list
        },
        'cotisations/new_facture.html', request
    )
158

159

160
# TODO : change facture to invoice
chirac's avatar
chirac committed
161
@login_required
162
@can_change(Facture, 'pdf')
163
def new_facture_pdf(request):
164
    """
165
    View used to generate a custom PDF invoice. It's mainly used to
166 167 168
    get invoices that are not taken into account, for the administrative
    point of view.
    """
169 170 171 172 173
    # The template needs the list of articles (for the JS part)
    articles = Article.objects.filter(
        Q(type_user='All') | Q(type_user=request.user.class_name)
    )
    # Building the invocie form and the article formset
174
    invoice_form = NewFactureFormPdf(request.POST or None)
175
    if request.user.is_class_club:
176 177 178
        articles_formset = formset_factory(SelectClubArticleForm)(
            request.POST or None
        )
179
    else:
180 181 182
        articles_formset = formset_factory(SelectUserArticleForm)(
            request.POST or None
        )
183 184 185 186 187 188 189 190 191 192 193 194 195 196
    if invoice_form.is_valid() and articles_formset.is_valid():
        # Get the article list and build an list out of it
        # contiaining (article_name, article_price, quantity, total_price)
        articles_info = []
        for articles_form in articles_formset:
            if articles_form.cleaned_data:
                article = articles_form.cleaned_data['article']
                quantity = articles_form.cleaned_data['quantity']
                articles_info.append({
                    'name': article.name,
                    'price': article.prix,
                    'quantity': quantity,
                    'total_price': article.prix * quantity
                })
197 198 199
        paid = invoice_form.cleaned_data['paid']
        recipient = invoice_form.cleaned_data['dest']
        address = invoice_form.cleaned_data['chambre']
200 201
        total_price = sum(a['total_price'] for a in articles_info)

202
        return render_invoice(request, {
203
            'DATE': timezone.now(),
204 205
            'recipient_name': recipient,
            'address': address,
206
            'article': articles_info,
207
            'total': total_price,
208
            'paid': paid,
209 210 211 212 213 214
            'asso_name': AssoOption.get_cached_value('name'),
            'line1': AssoOption.get_cached_value('adresse1'),
            'line2': AssoOption.get_cached_value('adresse2'),
            'siret': AssoOption.get_cached_value('siret'),
            'email': AssoOption.get_cached_value('contact'),
            'phone': AssoOption.get_cached_value('telephone'),
215
            'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
216
        })
217
    return form({
218
        'factureform': invoice_form,
219 220 221
        'action_name': _("Create"),
        'articlesformset': articles_formset,
        'articles': articles
222
    }, 'cotisations/facture.html', request)
223

224

225
# TODO : change facture to invoice
Dalahro's avatar
Dalahro committed
226
@login_required
227
@can_view(Facture)
228
def facture_pdf(request, facture, **_kwargs):
229 230 231 232 233 234
    """
    View used to generate a PDF file from  an existing invoice in database
    Creates a line for each Purchase (thus article sold) and generate the
    invoice with the total price, the payment method, the address and the
    legal information for the user.
    """
235
    # TODO : change vente to purchase
236
    purchases_objects = Vente.objects.all().filter(facture=facture)
237 238 239
    # Get the article list and build an list out of it
    # contiaining (article_name, article_price, quantity, total_price)
    purchases_info = []
240
    for purchase in purchases_objects:
241 242 243 244 245 246
        purchases_info.append({
            'name': purchase.name,
            'price': purchase.prix,
            'quantity': purchase.number,
            'total_price': purchase.prix_total
        })
247
    return render_invoice(request, {
248 249 250
        'paid': True,
        'fid': facture.id,
        'DATE': facture.date,
251 252 253 254
        'recipient_name': "{} {}".format(
            facture.user.name,
            facture.user.surname
        ),
Maël Kervella's avatar
Maël Kervella committed
255
        'address': facture.user.room,
256
        'article': purchases_info,
257
        'total': facture.prix_total(),
258 259 260 261 262 263
        'asso_name': AssoOption.get_cached_value('name'),
        'line1': AssoOption.get_cached_value('adresse1'),
        'line2': AssoOption.get_cached_value('adresse2'),
        'siret': AssoOption.get_cached_value('siret'),
        'email': AssoOption.get_cached_value('contact'),
        'phone': AssoOption.get_cached_value('telephone'),
264
        'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
265
    })
266

Dalahro's avatar
Dalahro committed
267

268
# TODO : change facture to invoice
Dalahro's avatar
Dalahro committed
269
@login_required
270
@can_edit(Facture)
271
def edit_facture(request, facture, **_kwargs):
272 273 274 275 276 277
    """
    View used to edit an existing invoice.
    Articles can be added or remove to the invoice and quantity
    can be set as desired. This is also the view used to invalidate
    an invoice.
    """
278 279 280 281 282
    invoice_form = EditFactureForm(
        request.POST or None,
        instance=facture,
        user=request.user
    )
283 284
    purchases_objects = Vente.objects.filter(facture=facture)
    purchase_form_set = modelformset_factory(
chirac's avatar
chirac committed
285 286 287
        Vente,
        fields=('name', 'number'),
        extra=0,
288
        max_num=len(purchases_objects)
289
    )
290 291 292 293
    purchase_form = purchase_form_set(
        request.POST or None,
        queryset=purchases_objects
    )
294 295 296 297
    if invoice_form.is_valid() and purchase_form.is_valid():
        if invoice_form.changed_data:
            invoice_form.save()
        purchase_form.save()
298 299 300 301
        messages.success(
            request,
            _("The invoice has been successfully edited.")
        )
302
        return redirect(reverse('cotisations:index'))
303
    return form({
304 305
        'factureform': invoice_form,
        'venteform': purchase_form
306
    }, 'cotisations/edit_facture.html', request)
307

308

309
# TODO : change facture to invoice
310
@login_required
311
@can_delete(Facture)
312
def del_facture(request, facture, **_kwargs):
313 314 315
    """
    View used to delete an existing invocie.
    """
316
    if request.method == "POST":
317
        facture.delete()
318 319
        messages.success(
            request,
320
            _("The invoice has been successfully deleted.")
321
        )
322
        return redirect(reverse('cotisations:index'))
323 324
    return form({
        'objet': facture,
325
        'objet_name': _("Invoice")
326
    }, 'cotisations/delete.html', request)
327

328

329
# TODO : change solde to balance
chibrac's avatar
chibrac committed
330
@login_required
331 332
@can_create(Facture)
@can_edit(User)
333
def credit_solde(request, user, **_kwargs):
334 335 336 337
    """
    View used to edit the balance of a user.
    Can be use either to increase or decrease a user's balance.
    """
338
    # TODO : change facture to invoice
339 340 341 342 343 344 345
    invoice = CreditSoldeForm(request.POST or None)
    if invoice.is_valid():
        invoice_instance = invoice.save(commit=False)
        invoice_instance.user = user
        invoice_instance.save()
        new_purchase = Vente.objects.create(
            facture=invoice_instance,
chirac's avatar
chirac committed
346
            name="solde",
347
            prix=invoice.cleaned_data['montant'],
chirac's avatar
chirac committed
348
            number=1
349
        )
350
        new_purchase.save()
351 352
        messages.success(
            request,
353
            _("Balance successfully updated.")
354
        )
355
        return redirect(reverse('cotisations:index'))
356
    return form({
Maël Kervella's avatar
Maël Kervella committed
357
        'factureform': invoice,
358
        'action_name': _("Edit")
359
    }, 'cotisations/facture.html', request)
chibrac's avatar
chibrac committed
360 361


chirac's avatar
chirac committed
362
@login_required
363
@can_create(Article)
364
def add_article(request):
365 366
    """
    View used to add an article.
367

368 369 370 371 372 373
    .. note:: If a purchase has already been sold, the price are calculated
        once and for all. That means even if the price of an article is edited
        later, it won't change the invoice. That is really important to keep
        this behaviour in order not to modify all the past and already
        accepted invoices.
    """
374 375
    article = ArticleForm(request.POST or None)
    if article.is_valid():
376
        article.save()
377 378 379 380
        messages.success(
            request,
            _("The article has been successfully created.")
        )
381
        return redirect(reverse('cotisations:index-article'))
382 383 384
    return form({
        'factureform': article,
        'action_name': _("Add")
385
    }, 'cotisations/facture.html', request)
386

387

chirac's avatar
chirac committed
388
@login_required
389
@can_edit(Article)
390
def edit_article(request, article_instance, **_kwargs):
391 392 393
    """
    View used to edit an article.
    """
394 395
    article = ArticleForm(request.POST or None, instance=article_instance)
    if article.is_valid():
396 397
        if article.changed_data:
            article.save()
398 399 400 401
            messages.success(
                request,
                _("The article has been successfully edited.")
            )
402
        return redirect(reverse('cotisations:index-article'))
403 404 405
    return form({
        'factureform': article,
        'action_name': _('Edit')
406
    }, 'cotisations/facture.html', request)
407

408

chirac's avatar
chirac committed
409
@login_required
410 411
@can_delete_set(Article)
def del_article(request, instances):
412 413 414
    """
    View used to delete one of the articles.
    """
415
    article = DelArticleForm(request.POST or None, instances=instances)
416 417
    if article.is_valid():
        article_del = article.cleaned_data['articles']
418
        article_del.delete()
419 420 421 422
        messages.success(
            request,
            _("The article(s) have been successfully deleted.")
        )
423
        return redirect(reverse('cotisations:index-article'))
424 425 426
    return form({
        'factureform': article,
        'action_name': _("Delete")
427
    }, 'cotisations/facture.html', request)
428

429

430
# TODO : change paiement to payment
chirac's avatar
chirac committed
431
@login_required
432
@can_create(Paiement)
433
def add_paiement(request):
434 435 436
    """
    View used to add a payment method.
    """
437 438 439 440 441 442 443 444 445
    payment = PaiementForm(request.POST or None, prefix='payment')
    payment_method = payment_method_factory(
        payment.instance,
        request.POST or None,
        prefix='payment_method'
    )
    if payment.is_valid() and payment_method.is_valid():
        payment = payment.save()
        payment_method.save(payment=payment)
446 447 448 449
        messages.success(
            request,
            _("The payment method has been successfully created.")
        )
450
        return redirect(reverse('cotisations:index-paiement'))
451 452
    return form({
        'factureform': payment,
453
        'payment_method': payment_method,
454
        'action_name': _("Add")
455
    }, 'cotisations/facture.html', request)
456

457

458
# TODO : chnage paiement to Payment
chirac's avatar
chirac committed
459
@login_required
460
@can_edit(Paiement)
461
def edit_paiement(request, paiement_instance, **_kwargs):
462 463 464
    """
    View used to edit a payment method.
    """
465 466 467 468 469 470 471 472
    payment = PaiementForm(
        request.POST or None,
        instance=paiement_instance,
        prefix="payment"
    )
    payment_method = payment_method_factory(
        paiement_instance,
        request.POST or None,
473 474
        prefix='payment_method',
        creation=False
475 476 477 478 479 480 481 482 483
    )

    if payment.is_valid() and payment_method.is_valid():
        payment.save()
        payment_method.save()
        messages.success(
            request,
            _("The payement method has been successfully edited.")
        )
484
        return redirect(reverse('cotisations:index-paiement'))
485 486
    return form({
        'factureform': payment,
487
        'payment_method': payment_method,
488
        'action_name': _("Edit")
489
    }, 'cotisations/facture.html', request)
490

491

492
# TODO : change paiement to payment
chirac's avatar
chirac committed
493
@login_required
494 495
@can_delete_set(Paiement)
def del_paiement(request, instances):
496 497 498 499 500 501 502
    """
    View used to delete a set of payment methods.
    """
    payment = DelPaiementForm(request.POST or None, instances=instances)
    if payment.is_valid():
        payment_dels = payment.cleaned_data['paiements']
        for payment_del in payment_dels:
503
            try:
504
                payment_del.delete()
505
                messages.success(
chirac's avatar
chirac committed
506
                    request,
507
                    _("The payment method %(method_name)s has been \
508
                    successfully deleted.") % {
509
                        'method_name': payment_del
510 511
                    }
                )
512
            except ProtectedError:
513 514
                messages.error(
                    request,
515
                    _("The payment method %(method_name)s can't be deleted \
516
                    because there are invoices using it.") % {
517
                        'method_name': payment_del
518
                    }
519
                )
520
        return redirect(reverse('cotisations:index-paiement'))
521 522 523
    return form({
        'factureform': payment,
        'action_name': _("Delete")
524
    }, 'cotisations/facture.html', request)
525

526

527
# TODO : change banque to bank
chirac's avatar
chirac committed
528
@login_required
529
@can_create(Banque)
chirac's avatar
chirac committed
530
def add_banque(request):
531 532 533 534 535 536
    """
    View used to add a bank.
    """
    bank = BanqueForm(request.POST or None)
    if bank.is_valid():
        bank.save()
537 538 539 540
        messages.success(
            request,
            _("The bank has been successfully created.")
        )
541
        return redirect(reverse('cotisations:index-banque'))
542 543 544
    return form({
        'factureform': bank,
        'action_name': _("Add")
545
    }, 'cotisations/facture.html', request)
546

547

548
# TODO : change banque to bank
chirac's avatar
chirac committed
549
@login_required
550
@can_edit(Banque)
551
def edit_banque(request, banque_instance, **_kwargs):
552 553 554 555 556 557 558
    """
    View used to edit a bank.
    """
    bank = BanqueForm(request.POST or None, instance=banque_instance)
    if bank.is_valid():
        if bank.changed_data:
            bank.save()
559 560 561 562
            messages.success(
                request,
                _("The bank has been successfully edited")
            )
563
        return redirect(reverse('cotisations:index-banque'))
564 565 566
    return form({
        'factureform': bank,
        'action_name': _("Edit")
567
    }, 'cotisations/facture.html', request)
chirac's avatar
chirac committed
568

569

570
# TODO : chnage banque to bank
chirac's avatar
chirac committed
571
@login_required
572 573
@can_delete_set(Banque)
def del_banque(request, instances):
574 575 576 577 578 579 580
    """
    View used to delete a set of banks.
    """
    bank = DelBanqueForm(request.POST or None, instances=instances)
    if bank.is_valid():
        bank_dels = bank.cleaned_data['banques']
        for bank_del in bank_dels:
chirac's avatar
chirac committed
581
            try:
582
                bank_del.delete()
583 584 585 586
                messages.success(
                    request,
                    _("The bank %(bank_name)s has been successfully \
                    deleted.") % {
587
                        'bank_name': bank_del
588 589
                    }
                )
chirac's avatar
chirac committed
590
            except ProtectedError:
591 592 593 594
                messages.error(
                    request,
                    _("The bank %(bank_name)s can't be deleted \
                    because there are invoices using it.") % {
595
                        'bank_name': bank_del
596 597
                    }
                )
598
        return redirect(reverse('cotisations:index-banque'))
599 600 601
    return form({
        'factureform': bank,
        'action_name': _("Delete")
602
    }, 'cotisations/facture.html', request)
chirac's avatar
chirac committed
603

604

605
# TODO : change facture to invoice
606
@login_required
607
@can_view_all(Facture)
608
@can_change(Facture, 'control')
609
def control(request):
610 611 612
    """
    View used to control the invoices all at once.
    """
613
    pagination_number = GeneralOption.get_cached_value('pagination_number')
614 615
    invoice_list = (Facture.objects.select_related('user').
                    select_related('paiement'))
616 617
    invoice_list = SortTable.sort(
        invoice_list,
618 619 620 621
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.COTISATIONS_CONTROL
    )
622
    control_invoices_formset = modelformset_factory(
chirac's avatar
chirac committed
623 624 625
        Facture,
        fields=('control', 'valid'),
        extra=0
626 627 628 629 630 631 632 633
    )
    invoice_list = re2o_paginator(request, invoice_list, pagination_number)
    control_invoices_form = control_invoices_formset(
        request.POST or None,
        queryset=invoice_list.object_list
    )
    if control_invoices_form.is_valid():
        control_invoices_form.save()
634
        reversion.set_comment("Controle")
635 636 637 638
        messages.success(
            request,
            _("Your changes have been properly taken into account.")
        )
639
        return redirect(reverse('cotisations:control'))
640
    return render(request, 'cotisations/control.html', {
641 642
        'facture_list': invoice_list,
        'controlform': control_invoices_form
643
    })
644

645

chirac's avatar
chirac committed
646
@login_required
647
@can_view_all(Article)
648
def index_article(request):
649 650 651 652
    """
    View used to display the list of all available articles.
    """
    # TODO : Offer other means of sorting
653
    article_list = Article.objects.order_by('name')
654 655
    return render(request, 'cotisations/index_article.html', {
        'article_list': article_list
656
    })
657

658

659
# TODO : change paiement to payment
chirac's avatar
chirac committed
660
@login_required
661
@can_view_all(Paiement)
662
def index_paiement(request):
663 664 665 666
    """
    View used to display the list of all available payment methods.
    """
    payment_list = Paiement.objects.order_by('moyen')
667
    return render(request, 'cotisations/index_paiement.html', {
668
        'paiement_list': payment_list
669
    })
670

671

672
# TODO : change banque to bank
chirac's avatar
chirac committed
673
@login_required
674
@can_view_all(Banque)
675
def index_banque(request):
676 677 678 679
    """
    View used to display the list of all available banks.
    """
    bank_list = Banque.objects.order_by('name')
680
    return render(request, 'cotisations/index_banque.html', {
681
        'banque_list': bank_list
682
    })
683

684

chirac's avatar
chirac committed
685
@login_required
686
@can_view_all(Facture)
687
def index(request):
688 689 690
    """
    View used to display the list of all exisitng invoices.
    """
691
    pagination_number = GeneralOption.get_cached_value('pagination_number')
692
    invoice_list = Facture.objects.select_related('user')\
693
        .select_related('paiement').prefetch_related('vente_set')
694 695
    invoice_list = SortTable.sort(
        invoice_list,
696 697 698 699
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.COTISATIONS_INDEX
    )
700
    invoice_list = re2o_paginator(request, invoice_list, pagination_number)
701
    return render(request, 'cotisations/index.html', {
702
        'facture_list': invoice_list
703
    })
704 705


706
# TODO : merge this function with new_facture() which is nearly the same
707
# TODO : change facture to invoice
708 709
@login_required
def new_facture_solde(request, userid):
710 711 712 713 714 715 716 717 718 719 720 721
    """
    View called to create a new invoice when using the balance to pay.
    Currently, send the list of available articles for the user along with
    a formset of a new invoice (based on the `:forms:NewFactureForm()` form.
    A bit of JS is used in the template to add articles in a fancier way.
    If everything is correct, save each one of the articles, save the
    purchase object associated and finally the newly created invoice.

    TODO : The whole verification process should be moved to the model. This
    function should only act as a dumb interface between the model and the
    user.
    """
722
    user = request.user
723 724
    invoice = Facture(user=user)
    payment, _created = Paiement.objects.get_or_create(moyen='Solde')
Maël Kervella's avatar
Maël Kervella committed
725
    invoice.paiement = payment
726
    # The template needs the list of articles (for the JS part)
727 728 729 730
    article_list = Article.objects.filter(
        Q(type_user='All') | Q(type_user=request.user.class_name)
    )
    if request.user.is_class_club:
731 732 733
        article_formset = formset_factory(SelectClubArticleForm)(
            request.POST or None
        )
734
    else:
735 736 737
        article_formset = formset_factory(SelectUserArticleForm)(
            request.POST or None
        )
738

739 740
    if article_formset.is_valid():
        articles = article_formset
741
        # Check if at leat one article has been selected
742
        if any(art.cleaned_data for art in articles):
743 744 745 746 747 748 749
            user_balance = OptionalUser.get_cached_value('user_solde')
            negative_balance = OptionalUser.get_cached_value('solde_negatif')
            # If the paiement using balance has been activated,
            # checking that the total price won't get the user under
            # the authorized minimum (negative_balance)
            if user_balance:
                total_price = 0
750 751
                for art_item in articles:
                    if art_item.cleaned_data:
752
                        total_price += art_item.cleaned_data['article']\
753
                            .prix*art_item.cleaned_data['quantity']
754
                if float(user.solde) - float(total_price) < negative_balance:
755 756 757 758
                    messages.error(
                        request,
                        _("The balance is too low for this operation.")
                    )
759 760 761
                    return redirect(reverse(
                        'users:profil',
                        kwargs={'userid': userid}
762
                    ))
763 764 765 766
            # Saving the invoice
            invoice.save()

            # Building a purchase for each article sold
767 768 769 770
            for art_item in articles:
                if art_item.cleaned_data:
                    article = art_item.cleaned_data['article']
                    quantity = art_item.cleaned_data['quantity']
771 772
                    new_purchase = Vente.objects.create(
                        facture=invoice,
773 774 775 776 777 778
                        name=article.name,
                        prix=article.prix,
                        type_cotisation=article.type_cotisation,
                        duration=article.duration,
                        number=quantity
                    )
779 780 781 782
                    new_purchase.save()

            # In case a cotisation was bought, inform the user, the
            # cotisation time has been extended too
783 784 785 786
            if any(art_item.cleaned_data['article'].type_cotisation
                   for art_item in articles if art_item.cleaned_data):
                messages.success(
                    request,
787 788
                    _("The cotisation of %(member_name)s has been successfully \
                    extended to %(end_date)s.") % {
789 790
                        'member_name': user.pseudo,
                        'end_date': user.end_adhesion()
791 792
                    }
                )
793
            # Else, only tell the invoice was created
794
            else:
795 796 797 798
                messages.success(
                    request,
                    _("The invoice has been successuflly created.")
                )
799 800 801
            return redirect(reverse(
                'users:profil',
                kwargs={'userid': userid}
802
            ))
803 804
        messages.error(
            request,
805 806
            _("You need to choose at least one article.")
        )
807 808 809 810 811 812 813 814
        return redirect(reverse(
            'users:profil',
            kwargs={'userid': userid}
        ))

    return form({
        'venteform': article_formset,
        'articlelist': article_list
815
    }, 'cotisations/new_facture_solde.html', request)
816 817


818
# TODO : change recharge to refill
819 820
@login_required
def recharge(request):
821 822 823
    """
    View used to refill the balance by using online payment.
    """
824
    if AssoOption.get_cached_value('payment') == 'NONE':
825 826
        messages.error(
            request,
827
            _("Online payment is disabled.")
828 829 830 831 832
        )
        return redirect(reverse(
            'users:profil',
            kwargs={'userid': request.user.id}
        ))
833 834 835
    refill_form = RechargeForm(request.POST or None, user=request.user)
    if refill_form.is_valid():
        invoice = Facture(user=request.user)
836 837 838
        payment, _created = Paiement.objects.get_or_create(
            moyen='Rechargement en ligne'
        )
Maël Kervella's avatar
Maël Kervella committed
839 840 841
        invoice.paiement = payment
        invoice.valid = False
        invoice.save()
842 843
        purchase = Vente.objects.create(
            facture=invoice,
844
            name='solde',
845 846
            prix=refill_form.cleaned_data['value'],
            number=1
847
        )
848
        purchase.save()
849
        # content = online_payment.PAYMENT_SYSTEM[
850
        # AssoOption.get_cached_value('payment')
851
        # ](invoice, request)
852
        return render(request, 'cotisations/payment.html', content)
853
    return form({
854 855
        'rechargeform': refill_form,
        'solde': request.user.solde
856
    }, 'cotisations/recharge.html', request)