views.py 26.9 KB
Newer Older
1 2 3 4 5 6 7
# 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
8
# Copyright © 2018  Hugo Levy-Falk
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#
# 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.

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

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

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

chirac's avatar
chirac committed
44
# Import des models, forms et fonctions re2o
45
from reversion import revisions as reversion
chirac's avatar
chirac committed
46 47 48 49
from users.models import User
from re2o.settings import LOGO_PATH
from re2o import settings
from re2o.views import form
50
from re2o.utils import SortTable, re2o_paginator
51
from re2o.acl import (
52 53 54 55
    can_create,
    can_edit,
    can_delete,
    can_view,
56 57 58
    can_view_all,
    can_delete_set,
    can_change,
59
)
60
from preferences.models import AssoOption, GeneralOption
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
61 62 63 64 65 66 67 68 69
from .models import (
    Facture,
    Article,
    Vente,
    Paiement,
    Banque,
    CustomInvoice,
    BaseInvoice
)
70
from .forms import (
71
    FactureForm,
72 73 74 75 76 77
    ArticleForm,
    DelArticleForm,
    PaiementForm,
    DelPaiementForm,
    BanqueForm,
    DelBanqueForm,
78
    SelectArticleForm,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
79 80
    RechargeForm,
    CustomInvoiceForm
81
)
82
from .tex import render_invoice
83
from .payment_methods.forms import payment_method_factory
84
from .utils import find_payment_method
85

86

chirac's avatar
chirac committed
87
@login_required
88 89 90
@can_create(Facture)
@can_edit(User)
def new_facture(request, user, userid):
91 92 93
    """
    View called to create a new invoice.
    Currently, Send the list of available articles for the user along with
94
    a formset of a new invoice (based on the `:forms:FactureForm()` form.
95 96 97 98 99 100
    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.
    """
    invoice = Facture(user=user)
    # The template needs the list of articles (for the JS part)
101 102 103
    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
104
    # Building the invoice form and the article formset
105
    invoice_form = FactureForm(
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
106 107
        request.POST or None,
        instance=invoice,
108 109
        user=request.user,
        creation=True
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
110
    )
111

112 113 114 115
    article_formset = formset_factory(SelectArticleForm)(
        request.POST or None,
        form_kwargs={'user': request.user, 'target_user': user}
    )
116 117 118

    if invoice_form.is_valid() and article_formset.is_valid():
        new_invoice_instance = invoice_form.save(commit=False)
119
        articles = article_formset
120
        # Check if at leat one article has been selected
121
        if any(art.cleaned_data for art in articles):
122
            # Building a purchase for each article sold
123
            purchases = []
124
            total_price = 0
125 126 127 128
            for art_item in articles:
                if art_item.cleaned_data:
                    article = art_item.cleaned_data['article']
                    quantity = art_item.cleaned_data['quantity']
129 130
                    total_price += article.prix*quantity
                    new_purchase = Vente(
131
                        facture=new_invoice_instance,
chirac's avatar
chirac committed
132 133
                        name=article.name,
                        prix=article.prix,
134
                        type_cotisation=article.type_cotisation,
chirac's avatar
chirac committed
135 136 137
                        duration=article.duration,
                        number=quantity
                    )
138 139
                    purchases.append(new_purchase)
            p = find_payment_method(new_invoice_instance.paiement)
140 141 142
            if hasattr(p, 'check_price'):
                price_ok, msg = p.check_price(total_price, user)
                invoice_form.add_error(None, msg)
143
            else:
144 145 146 147 148 149 150
                price_ok = True
            if price_ok:
                new_invoice_instance.save()
                for p in purchases:
                    p.facture = new_invoice_instance
                    p.save()

151 152 153 154 155 156 157 158
                return new_invoice_instance.paiement.end_payment(
                    new_invoice_instance,
                    request
                )
        else:
            messages.error(
                request,
                _("You need to choose at least one article.")
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
159
            )
160 161 162 163 164
    p = Paiement.objects.filter(is_balance=True)
    if len(p) and p[0].can_use_payment(request.user):
        balance = user.solde
    else:
        balance = None
165

166 167
    return form(
        {
168
            'factureform': invoice_form,
169 170 171
            'articlesformset': article_formset,
            'articlelist': article_list,
            'balance': balance,
172
            'action_name': _('Confirm'),
173
        },
174
        'cotisations/facture.html', request
175
    )
176

177

178
# TODO : change facture to invoice
chirac's avatar
chirac committed
179
@login_required
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
180 181
@can_create(CustomInvoice)
def new_custom_invoice(request):
182
    """
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
183
    View used to generate a custom invoice. It's mainly used to
184 185 186
    get invoices that are not taken into account, for the administrative
    point of view.
    """
187 188 189 190 191
    # 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
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
192
    invoice_form = CustomInvoiceForm(request.POST or None)
193

194
    articles_formset = formset_factory(SelectArticleForm)(
195
        request.POST or None,
196
        form_kwargs={'user': request.user}
197 198
    )

199
    if invoice_form.is_valid() and articles_formset.is_valid():
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
        new_invoice_instance = invoice_form.save()
        for art_item in articles_formset:
            if art_item.cleaned_data:
                article = art_item.cleaned_data['article']
                quantity = art_item.cleaned_data['quantity']
                Vente.objects.create(
                    facture=new_invoice_instance,
                    name=article.name,
                    prix=article.prix,
                    type_cotisation=article.type_cotisation,
                    duration=article.duration,
                    number=quantity
                )
        messages.success(
            request,
215
            _("The custom invoice was created.")
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
216 217 218
        )
        return redirect(reverse('cotisations:index-custom-invoice'))

219
    return form({
220
        'factureform': invoice_form,
221
        'action_name': _("Confirm"),
222
        'articlesformset': articles_formset,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
223
        'articlelist': articles
224
    }, 'cotisations/facture.html', request)
225

226

227
# TODO : change facture to invoice
Dalahro's avatar
Dalahro committed
228
@login_required
229
@can_view(Facture)
230
def facture_pdf(request, facture, **_kwargs):
231 232 233 234 235 236
    """
    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.
    """
237
    # TODO : change vente to purchase
238
    purchases_objects = Vente.objects.all().filter(facture=facture)
239 240 241
    # Get the article list and build an list out of it
    # contiaining (article_name, article_price, quantity, total_price)
    purchases_info = []
242
    for purchase in purchases_objects:
243 244 245 246 247 248
        purchases_info.append({
            'name': purchase.name,
            'price': purchase.prix,
            'quantity': purchase.number,
            'total_price': purchase.prix_total
        })
249
    return render_invoice(request, {
250 251 252
        'paid': True,
        'fid': facture.id,
        'DATE': facture.date,
253 254 255 256
        'recipient_name': "{} {}".format(
            facture.user.name,
            facture.user.surname
        ),
Maël Kervella's avatar
Maël Kervella committed
257
        'address': facture.user.room,
258
        'article': purchases_info,
259
        'total': facture.prix_total(),
260 261 262 263 264 265
        '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'),
266
        'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
267
    })
268

Dalahro's avatar
Dalahro committed
269

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

310

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

330

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
@login_required
@can_edit(CustomInvoice)
def edit_custom_invoice(request, invoice, **kwargs):
    # Building the invocie form and the article formset
    invoice_form = CustomInvoiceForm(
        request.POST or None,
        instance=invoice
    )
    purchases_objects = Vente.objects.filter(facture=invoice)
    purchase_form_set = modelformset_factory(
        Vente,
        fields=('name', 'number'),
        extra=0,
        max_num=len(purchases_objects)
    )
    purchase_form = purchase_form_set(
        request.POST or None,
        queryset=purchases_objects
    )
    if invoice_form.is_valid() and purchase_form.is_valid():
        if invoice_form.changed_data:
            invoice_form.save()
        purchase_form.save()
        messages.success(
            request,
356
            _("The invoice was edited.")
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
        )
        return redirect(reverse('cotisations:index-custom-invoice'))

    return form({
        'factureform': invoice_form,
        'venteform': purchase_form
    }, 'cotisations/edit_facture.html', request)


@login_required
@can_view(CustomInvoice)
def custom_invoice_pdf(request, invoice, **_kwargs):
    """
    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.
    """
    # TODO : change vente to purchase
    purchases_objects = Vente.objects.all().filter(facture=invoice)
    # Get the article list and build an list out of it
    # contiaining (article_name, article_price, quantity, total_price)
    purchases_info = []
    for purchase in purchases_objects:
        purchases_info.append({
            'name': purchase.name,
            'price': purchase.prix,
            'quantity': purchase.number,
            'total_price': purchase.prix_total
        })
    return render_invoice(request, {
        'paid': invoice.paid,
        'fid': invoice.id,
        'DATE': invoice.date,
        'recipient_name': invoice.recipient,
        'address': invoice.address,
        'article': purchases_info,
        'total': invoice.prix_total(),
        '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'),
        'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
    })


# TODO : change facture to invoice
@login_required
@can_delete(CustomInvoice)
def del_custom_invoice(request, invoice, **_kwargs):
    """
    View used to delete an existing invocie.
    """
    if request.method == "POST":
        invoice.delete()
        messages.success(
            request,
416
            _("The invoice was deleted.")
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
417 418 419 420 421 422 423 424
        )
        return redirect(reverse('cotisations:index-custom-invoice'))
    return form({
        'objet': invoice,
        'objet_name': _("Invoice")
    }, 'cotisations/delete.html', request)


chirac's avatar
chirac committed
425
@login_required
426
@can_create(Article)
427
def add_article(request):
428 429
    """
    View used to add an article.
430

431 432 433 434 435 436
    .. 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.
    """
437 438
    article = ArticleForm(request.POST or None)
    if article.is_valid():
439
        article.save()
440 441
        messages.success(
            request,
442
            _("The article was created.")
443
        )
444
        return redirect(reverse('cotisations:index-article'))
445 446
    return form({
        'factureform': article,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
447 448
        'action_name': _("Add"),
        'title': _("New article")
449
    }, 'cotisations/facture.html', request)
450

451

chirac's avatar
chirac committed
452
@login_required
453
@can_edit(Article)
454
def edit_article(request, article_instance, **_kwargs):
455 456 457
    """
    View used to edit an article.
    """
458 459
    article = ArticleForm(request.POST or None, instance=article_instance)
    if article.is_valid():
460 461
        if article.changed_data:
            article.save()
462 463
            messages.success(
                request,
464
                _("The article was edited.")
465
            )
466
        return redirect(reverse('cotisations:index-article'))
467 468
    return form({
        'factureform': article,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
469 470
        'action_name': _('Edit'),
        'title': _("Edit article")
471
    }, 'cotisations/facture.html', request)
472

473

chirac's avatar
chirac committed
474
@login_required
475 476
@can_delete_set(Article)
def del_article(request, instances):
477 478 479
    """
    View used to delete one of the articles.
    """
480
    article = DelArticleForm(request.POST or None, instances=instances)
481 482
    if article.is_valid():
        article_del = article.cleaned_data['articles']
483
        article_del.delete()
484 485
        messages.success(
            request,
486
            _("The articles were deleted.")
487
        )
488
        return redirect(reverse('cotisations:index-article'))
489 490
    return form({
        'factureform': article,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
491 492
        'action_name': _("Delete"),
        'title': _("Delete article")
493
    }, 'cotisations/facture.html', request)
494

495

496
# TODO : change paiement to payment
chirac's avatar
chirac committed
497
@login_required
498
@can_create(Paiement)
499
def add_paiement(request):
500 501 502
    """
    View used to add a payment method.
    """
503 504 505 506 507 508 509 510
    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()
511
        payment_method.save(payment)
512 513
        messages.success(
            request,
514
            _("The payment method was created.")
515
        )
516
        return redirect(reverse('cotisations:index-paiement'))
517 518
    return form({
        'factureform': payment,
519
        'payment_method': payment_method,
520 521
        'action_name': _("Add"),
        'title': _("New payment method")
522
    }, 'cotisations/facture.html', request)
523

524

525
# TODO : chnage paiement to Payment
chirac's avatar
chirac committed
526
@login_required
527
@can_edit(Paiement)
528
def edit_paiement(request, paiement_instance, **_kwargs):
529 530 531
    """
    View used to edit a payment method.
    """
532 533 534 535 536 537 538 539
    payment = PaiementForm(
        request.POST or None,
        instance=paiement_instance,
        prefix="payment"
    )
    payment_method = payment_method_factory(
        paiement_instance,
        request.POST or None,
540 541
        prefix='payment_method',
        creation=False
542 543
    )

544 545
    if payment.is_valid() and \
       (payment_method is None or payment_method.is_valid()):
546
        payment.save()
547 548
        if payment_method is not None:
            payment_method.save()
549
        messages.success(
550
            request,_("The payment method was edited.")
551
        )
552
        return redirect(reverse('cotisations:index-paiement'))
553 554
    return form({
        'factureform': payment,
555
        'payment_method': payment_method,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
556 557
        'action_name': _("Edit"),
        'title': _("Edit payment method")
558
    }, 'cotisations/facture.html', request)
559

560

561
# TODO : change paiement to payment
chirac's avatar
chirac committed
562
@login_required
563 564
@can_delete_set(Paiement)
def del_paiement(request, instances):
565 566 567 568 569 570 571
    """
    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:
572
            try:
573
                payment_del.delete()
574
                messages.success(
chirac's avatar
chirac committed
575
                    request,
576
                    _("The payment method %(method_name)s was deleted.") % {
577
                        'method_name': payment_del
578 579
                    }
                )
580
            except ProtectedError:
581 582
                messages.error(
                    request,
583
                    _("The payment method %(method_name)s can't be deleted \
584
                    because there are invoices using it.") % {
585
                        'method_name': payment_del
586
                    }
587
                )
588
        return redirect(reverse('cotisations:index-paiement'))
589 590
    return form({
        'factureform': payment,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
591 592
        'action_name': _("Delete"),
        'title': _("Delete payment method")
593
    }, 'cotisations/facture.html', request)
594

595

596
# TODO : change banque to bank
chirac's avatar
chirac committed
597
@login_required
598
@can_create(Banque)
chirac's avatar
chirac committed
599
def add_banque(request):
600 601 602 603 604 605
    """
    View used to add a bank.
    """
    bank = BanqueForm(request.POST or None)
    if bank.is_valid():
        bank.save()
606 607
        messages.success(
            request,
608
            _("The bank was created.")
609
        )
610
        return redirect(reverse('cotisations:index-banque'))
611 612
    return form({
        'factureform': bank,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
613 614
        'action_name': _("Add"),
        'title': _("New bank")
615
    }, 'cotisations/facture.html', request)
616

617

618
# TODO : change banque to bank
chirac's avatar
chirac committed
619
@login_required
620
@can_edit(Banque)
621
def edit_banque(request, banque_instance, **_kwargs):
622 623 624 625 626 627 628
    """
    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()
629 630
            messages.success(
                request,
631
                _("The bank was edited.")
632
            )
633
        return redirect(reverse('cotisations:index-banque'))
634 635
    return form({
        'factureform': bank,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
636 637
        'action_name': _("Edit"),
        'title': _("Edit bank")
638
    }, 'cotisations/facture.html', request)
chirac's avatar
chirac committed
639

640

641
# TODO : chnage banque to bank
chirac's avatar
chirac committed
642
@login_required
643 644
@can_delete_set(Banque)
def del_banque(request, instances):
645 646 647 648 649 650 651
    """
    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
652
            try:
653
                bank_del.delete()
654 655
                messages.success(
                    request,
656
                    _("The bank %(bank_name)s was deleted.") % {
657
                        'bank_name': bank_del
658 659
                    }
                )
chirac's avatar
chirac committed
660
            except ProtectedError:
661 662 663 664
                messages.error(
                    request,
                    _("The bank %(bank_name)s can't be deleted \
                    because there are invoices using it.") % {
665
                        'bank_name': bank_del
666 667
                    }
                )
668
        return redirect(reverse('cotisations:index-banque'))
669 670
    return form({
        'factureform': bank,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
671 672
        'action_name': _("Delete"),
        'title': _("Delete bank")
673
    }, 'cotisations/facture.html', request)
chirac's avatar
chirac committed
674

675

676
# TODO : change facture to invoice
677
@login_required
678
@can_view_all(Facture)
679
@can_change(Facture, 'control')
680
def control(request):
681 682 683
    """
    View used to control the invoices all at once.
    """
684
    pagination_number = GeneralOption.get_cached_value('pagination_number')
685 686
    invoice_list = (Facture.objects.select_related('user').
                    select_related('paiement'))
687 688
    invoice_list = SortTable.sort(
        invoice_list,
689 690 691 692
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.COTISATIONS_CONTROL
    )
693
    control_invoices_formset = modelformset_factory(
chirac's avatar
chirac committed
694 695 696
        Facture,
        fields=('control', 'valid'),
        extra=0
697 698 699 700 701 702 703 704
    )
    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()
705
        reversion.set_comment("Controle")
706 707 708 709
        messages.success(
            request,
            _("Your changes have been properly taken into account.")
        )
710
        return redirect(reverse('cotisations:control'))
711
    return render(request, 'cotisations/control.html', {
712 713
        'facture_list': invoice_list,
        'controlform': control_invoices_form
714
    })
715

716

chirac's avatar
chirac committed
717
@login_required
718
@can_view_all(Article)
719
def index_article(request):
720 721 722 723
    """
    View used to display the list of all available articles.
    """
    # TODO : Offer other means of sorting
724
    article_list = Article.objects.order_by('name')
725 726
    return render(request, 'cotisations/index_article.html', {
        'article_list': article_list
727
    })
728

729

730
# TODO : change paiement to payment
chirac's avatar
chirac committed
731
@login_required
732
@can_view_all(Paiement)
733
def index_paiement(request):
734 735 736 737
    """
    View used to display the list of all available payment methods.
    """
    payment_list = Paiement.objects.order_by('moyen')
738
    return render(request, 'cotisations/index_paiement.html', {
739
        'paiement_list': payment_list
740
    })
741

742

743
# TODO : change banque to bank
chirac's avatar
chirac committed
744
@login_required
745
@can_view_all(Banque)
746
def index_banque(request):
747 748 749 750
    """
    View used to display the list of all available banks.
    """
    bank_list = Banque.objects.order_by('name')
751
    return render(request, 'cotisations/index_banque.html', {
752
        'banque_list': bank_list
753
    })
754

755

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
@login_required
@can_view_all(CustomInvoice)
def index_custom_invoice(request):
    """View used to display every custom invoice."""
    pagination_number = GeneralOption.get_cached_value('pagination_number')
    custom_invoice_list = CustomInvoice.objects.prefetch_related('vente_set')
    custom_invoice_list = SortTable.sort(
        custom_invoice_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.COTISATIONS_CUSTOM
    )
    custom_invoice_list = re2o_paginator(
        request,
        custom_invoice_list,
        pagination_number,
    )
    return render(request, 'cotisations/index_custom_invoice.html', {
        'custom_invoice_list': custom_invoice_list
    })


chirac's avatar
chirac committed
778
@login_required
779
@can_view_all(Facture)
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
780
@can_view_all(CustomInvoice)
781
def index(request):
782 783 784
    """
    View used to display the list of all exisitng invoices.
    """
785
    pagination_number = GeneralOption.get_cached_value('pagination_number')
786
    invoice_list = Facture.objects.select_related('user')\
787
        .select_related('paiement').prefetch_related('vente_set')
788 789
    invoice_list = SortTable.sort(
        invoice_list,
790 791 792 793
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.COTISATIONS_INDEX
    )
794
    invoice_list = re2o_paginator(request, invoice_list, pagination_number)
795
    return render(request, 'cotisations/index.html', {
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
796
        'facture_list': invoice_list,
797
    })
798 799


800
# TODO : change solde to balance
801
@login_required
802 803
@can_edit(User)
def credit_solde(request, user, **_kwargs):
804
    """
805 806
    View used to edit the balance of a user.
    Can be use either to increase or decrease a user's balance.
807
    """
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
    try:
        balance = find_payment_method(Paiement.objects.get(is_balance=True))
    except Paiement.DoesNotExist:
        credit_allowed = False
    else:
        credit_allowed = (
            balance is not None
            and balance.can_credit_balance(request.user)
        )
    if not credit_allowed:
        messages.error(
            request,
            _("You are not allowed to credit your balance.")
        )
        return redirect(reverse(
            'users:profil',
            kwargs={'userid': user.id}
        ))

827
    refill_form = RechargeForm(request.POST or None, user=user, user_source=request.user)
828
    if refill_form.is_valid():
829
        price = refill_form.cleaned_data['value']
830
        invoice = Facture(user=user)
831
        invoice.paiement = refill_form.cleaned_data['payment']
832 833 834 835 836 837 838 839 840 841 842 843 844 845
        p = find_payment_method(invoice.paiement)
        if hasattr(p, 'check_price'):
            price_ok, msg = p.check_price(price, user)
            refill_form.add_error(None, msg)
        else:
            price_ok = True
        if price_ok:
            invoice.save()
            Vente.objects.create(
                facture=invoice,
                name='solde',
                prix=refill_form.cleaned_data['value'],
                number=1
            )
846

847
            return invoice.paiement.end_payment(invoice, request)
848
    p = get_object_or_404(Paiement, is_balance=True)
849
    return form({
850
        'factureform': refill_form,
851
        'balance': user.solde,
852
        'title': _("Refill your balance"),
853
        'action_name': _("Pay"),
854
        'max_balance': find_payment_method(p).maximum_balance,
855
    }, 'cotisations/facture.html', request)
856