mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-09-12 01:03:09 +00:00
Custom invoices.
This commit is contained in:
parent
0527206eb4
commit
d6091d117c
10 changed files with 526 additions and 139 deletions
|
@ -46,7 +46,7 @@ from django.shortcuts import get_object_or_404
|
|||
|
||||
from re2o.field_permissions import FieldPermissionFormMixin
|
||||
from re2o.mixins import FormRevMixin
|
||||
from .models import Article, Paiement, Facture, Banque
|
||||
from .models import Article, Paiement, Facture, Banque, CustomInvoice
|
||||
from .payment_methods import balance
|
||||
|
||||
|
||||
|
@ -131,24 +131,13 @@ class SelectClubArticleForm(Form):
|
|||
self.fields['article'].queryset = Article.find_allowed_articles(user)
|
||||
|
||||
|
||||
# TODO : change Facture to Invoice
|
||||
class NewFactureFormPdf(Form):
|
||||
class CustomInvoiceForm(FormRevMixin, ModelForm):
|
||||
"""
|
||||
Form used to create a custom PDF invoice.
|
||||
Form used to create a custom invoice.
|
||||
"""
|
||||
paid = forms.BooleanField(label=_l("Paid"), required=False)
|
||||
# TODO : change dest field to recipient
|
||||
dest = forms.CharField(
|
||||
required=True,
|
||||
max_length=255,
|
||||
label=_l("Recipient")
|
||||
)
|
||||
# TODO : change chambre field to address
|
||||
chambre = forms.CharField(
|
||||
required=False,
|
||||
max_length=10,
|
||||
label=_l("Address")
|
||||
)
|
||||
class Meta:
|
||||
model = CustomInvoice
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class ArticleForm(FormRevMixin, ModelForm):
|
||||
|
|
72
cotisations/migrations/0031_custom_invoice.py
Normal file
72
cotisations/migrations/0031_custom_invoice.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-07-21 20:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import re2o.field_permissions
|
||||
import re2o.mixins
|
||||
|
||||
|
||||
def reattribute_ids(apps, schema_editor):
|
||||
Facture = apps.get_model('cotisations', 'Facture')
|
||||
BaseInvoice = apps.get_model('cotisations', 'BaseInvoice')
|
||||
|
||||
for f in Facture.objects.all():
|
||||
base = BaseInvoice.objects.create(id=f.pk, date=f.date)
|
||||
f.baseinvoice_ptr = base
|
||||
f.save()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cotisations', '0030_custom_payment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BaseInvoice',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateTimeField(auto_now_add=True, verbose_name='Date')),
|
||||
],
|
||||
bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, re2o.field_permissions.FieldPermissionModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomInvoice',
|
||||
fields=[
|
||||
('baseinvoice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cotisations.BaseInvoice')),
|
||||
('recipient', models.CharField(max_length=255, verbose_name='Recipient')),
|
||||
('payment', models.CharField(max_length=255, verbose_name='Payment type')),
|
||||
('address', models.CharField(max_length=255, verbose_name='Address')),
|
||||
('paid', models.BooleanField(verbose_name='Paid')),
|
||||
],
|
||||
bases=('cotisations.baseinvoice',),
|
||||
options={'permissions': (('view_custom_invoice', 'Can view a custom invoice'),)},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='facture',
|
||||
name='baseinvoice_ptr',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='cotisations.BaseInvoice', null=True),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.RunPython(reattribute_ids),
|
||||
migrations.AlterField(
|
||||
model_name='vente',
|
||||
name='facture',
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, verbose_name='Invoice', to='cotisations.BaseInvoice')
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='facture',
|
||||
name='id',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='facture',
|
||||
name='date',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='facture',
|
||||
name='baseinvoice_ptr',
|
||||
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cotisations.BaseInvoice'),
|
||||
)
|
||||
]
|
|
@ -55,80 +55,11 @@ from cotisations.utils import find_payment_method
|
|||
from cotisations.validators import check_no_balance
|
||||
|
||||
|
||||
# TODO : change facture to invoice
|
||||
class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||
"""
|
||||
The model for an invoice. It reprensents the fact that a user paid for
|
||||
something (it can be multiple article paid at once).
|
||||
|
||||
An invoice is linked to :
|
||||
* one or more purchases (one for each article sold that time)
|
||||
* a user (the one who bought those articles)
|
||||
* a payment method (the one used by the user)
|
||||
* (if applicable) a bank
|
||||
* (if applicable) a cheque number.
|
||||
Every invoice is dated throught the 'date' value.
|
||||
An invoice has a 'controlled' value (default : False) which means that
|
||||
someone with high enough rights has controlled that invoice and taken it
|
||||
into account. It also has a 'valid' value (default : True) which means
|
||||
that someone with high enough rights has decided that this invoice was not
|
||||
valid (thus it's like the user never paid for his articles). It may be
|
||||
necessary in case of non-payment.
|
||||
"""
|
||||
|
||||
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
|
||||
# TODO : change paiement to payment
|
||||
paiement = models.ForeignKey('Paiement', on_delete=models.PROTECT)
|
||||
# TODO : change banque to bank
|
||||
banque = models.ForeignKey(
|
||||
'Banque',
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
# TODO : maybe change to cheque nummber because not evident
|
||||
cheque = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
verbose_name=_l("Cheque number")
|
||||
)
|
||||
class BaseInvoice(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||
date = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
verbose_name=_l("Date")
|
||||
)
|
||||
# TODO : change name to validity for clarity
|
||||
valid = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_l("Validated")
|
||||
)
|
||||
# TODO : changed name to controlled for clarity
|
||||
control = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_l("Controlled")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = False
|
||||
permissions = (
|
||||
# TODO : change facture to invoice
|
||||
('change_facture_control',
|
||||
_l("Can change the \"controlled\" state")),
|
||||
# TODO : seems more likely to be call create_facture_pdf
|
||||
# or create_invoice_pdf
|
||||
('change_facture_pdf',
|
||||
_l("Can create a custom PDF invoice")),
|
||||
('view_facture',
|
||||
_l("Can see an invoice's details")),
|
||||
('change_all_facture',
|
||||
_l("Can edit all the previous invoices")),
|
||||
)
|
||||
verbose_name = _l("Invoice")
|
||||
verbose_name_plural = _l("Invoices")
|
||||
|
||||
def linked_objects(self):
|
||||
"""Return linked objects : machine and domain.
|
||||
Usefull in history display"""
|
||||
return self.vente_set.all()
|
||||
|
||||
# TODO : change prix to price
|
||||
def prix(self):
|
||||
|
@ -167,6 +98,78 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
|||
).values_list('name', flat=True))
|
||||
return name
|
||||
|
||||
|
||||
# TODO : change facture to invoice
|
||||
class Facture(BaseInvoice):
|
||||
"""
|
||||
The model for an invoice. It reprensents the fact that a user paid for
|
||||
something (it can be multiple article paid at once).
|
||||
|
||||
An invoice is linked to :
|
||||
* one or more purchases (one for each article sold that time)
|
||||
* a user (the one who bought those articles)
|
||||
* a payment method (the one used by the user)
|
||||
* (if applicable) a bank
|
||||
* (if applicable) a cheque number.
|
||||
Every invoice is dated throught the 'date' value.
|
||||
An invoice has a 'controlled' value (default : False) which means that
|
||||
someone with high enough rights has controlled that invoice and taken it
|
||||
into account. It also has a 'valid' value (default : True) which means
|
||||
that someone with high enough rights has decided that this invoice was not
|
||||
valid (thus it's like the user never paid for his articles). It may be
|
||||
necessary in case of non-payment.
|
||||
"""
|
||||
|
||||
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
|
||||
# TODO : change paiement to payment
|
||||
paiement = models.ForeignKey('Paiement', on_delete=models.PROTECT)
|
||||
# TODO : change banque to bank
|
||||
banque = models.ForeignKey(
|
||||
'Banque',
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
# TODO : maybe change to cheque nummber because not evident
|
||||
cheque = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
verbose_name=_l("Cheque number")
|
||||
)
|
||||
# TODO : change name to validity for clarity
|
||||
valid = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_l("Validated")
|
||||
)
|
||||
# TODO : changed name to controlled for clarity
|
||||
control = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_l("Controlled")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = False
|
||||
permissions = (
|
||||
# TODO : change facture to invoice
|
||||
('change_facture_control',
|
||||
_l("Can change the \"controlled\" state")),
|
||||
# TODO : seems more likely to be call create_facture_pdf
|
||||
# or create_invoice_pdf
|
||||
('change_facture_pdf',
|
||||
_l("Can create a custom PDF invoice")),
|
||||
('view_facture',
|
||||
_l("Can see an invoice's details")),
|
||||
('change_all_facture',
|
||||
_l("Can edit all the previous invoices")),
|
||||
)
|
||||
verbose_name = _l("Invoice")
|
||||
verbose_name_plural = _l("Invoices")
|
||||
|
||||
def linked_objects(self):
|
||||
"""Return linked objects : machine and domain.
|
||||
Usefull in history display"""
|
||||
return self.vente_set.all()
|
||||
|
||||
def can_edit(self, user_request, *args, **kwargs):
|
||||
if not user_request.has_perm('cotisations.change_facture'):
|
||||
return False, _("You don't have the right to edit an invoice.")
|
||||
|
@ -265,6 +268,28 @@ def facture_post_delete(**kwargs):
|
|||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||
|
||||
|
||||
class CustomInvoice(BaseInvoice):
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_custom_invoice', _l("Can view a custom invoice")),
|
||||
)
|
||||
recipient = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_l("Recipient")
|
||||
)
|
||||
payment = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_l("Payment type")
|
||||
)
|
||||
address = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_l("Address")
|
||||
)
|
||||
paid = models.BooleanField(
|
||||
verbose_name="Paid"
|
||||
)
|
||||
|
||||
|
||||
# TODO : change Vente to Purchase
|
||||
class Vente(RevMixin, AclMixin, models.Model):
|
||||
"""
|
||||
|
@ -288,7 +313,7 @@ class Vente(RevMixin, AclMixin, models.Model):
|
|||
|
||||
# TODO : change facture to invoice
|
||||
facture = models.ForeignKey(
|
||||
'Facture',
|
||||
'BaseInvoice',
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_l("Invoice")
|
||||
)
|
||||
|
@ -355,6 +380,10 @@ class Vente(RevMixin, AclMixin, models.Model):
|
|||
cotisation_type defined (which means the article sold represents
|
||||
a cotisation)
|
||||
"""
|
||||
try:
|
||||
invoice = self.facture.facture
|
||||
except Facture.DoesNotExist:
|
||||
return
|
||||
if not hasattr(self, 'cotisation') and self.type_cotisation:
|
||||
cotisation = Cotisation(vente=self)
|
||||
cotisation.type_cotisation = self.type_cotisation
|
||||
|
@ -362,7 +391,7 @@ class Vente(RevMixin, AclMixin, models.Model):
|
|||
end_cotisation = Cotisation.objects.filter(
|
||||
vente__in=Vente.objects.filter(
|
||||
facture__in=Facture.objects.filter(
|
||||
user=self.facture.user
|
||||
user=invoice.user
|
||||
).exclude(valid=False))
|
||||
).filter(
|
||||
Q(type_cotisation='All') |
|
||||
|
@ -371,9 +400,9 @@ class Vente(RevMixin, AclMixin, models.Model):
|
|||
date_start__lt=date_start
|
||||
).aggregate(Max('date_end'))['date_end__max']
|
||||
elif self.type_cotisation == "Adhesion":
|
||||
end_cotisation = self.facture.user.end_adhesion()
|
||||
end_cotisation = invoice.user.end_adhesion()
|
||||
else:
|
||||
end_cotisation = self.facture.user.end_connexion()
|
||||
end_cotisation = invoice.user.end_connexion()
|
||||
date_start = date_start or timezone.now()
|
||||
end_cotisation = end_cotisation or date_start
|
||||
date_max = max(end_cotisation, date_start)
|
||||
|
@ -445,6 +474,10 @@ def vente_post_save(**kwargs):
|
|||
LDAP user when a purchase has been saved.
|
||||
"""
|
||||
purchase = kwargs['instance']
|
||||
try:
|
||||
purchase.facture.facture
|
||||
except Facture.DoesNotExist:
|
||||
return
|
||||
if hasattr(purchase, 'cotisation'):
|
||||
purchase.cotisation.vente = purchase
|
||||
purchase.cotisation.save()
|
||||
|
@ -462,8 +495,12 @@ def vente_post_delete(**kwargs):
|
|||
Synchronise the LDAP user after a purchase has been deleted.
|
||||
"""
|
||||
purchase = kwargs['instance']
|
||||
try:
|
||||
invoice = purchase.facture.facture
|
||||
except Facture.DoesNotExist:
|
||||
return
|
||||
if purchase.type_cotisation:
|
||||
user = purchase.facture.user
|
||||
user = invoice.user
|
||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||
|
||||
|
||||
|
|
108
cotisations/templates/cotisations/aff_custom_invoice.html
Normal file
108
cotisations/templates/cotisations/aff_custom_invoice.html
Normal file
|
@ -0,0 +1,108 @@
|
|||
{% comment %}
|
||||
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
|
||||
Copyright © 2018 Hugo Levy-Falk
|
||||
|
||||
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.
|
||||
{% endcomment %}
|
||||
{% load i18n %}
|
||||
{% load acl %}
|
||||
|
||||
<div class="table-responsive">
|
||||
{% if custom_invoice_list.paginator %}
|
||||
{% include 'pagination.html' with list=custom_invoice_list %}
|
||||
{% endif %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{% trans "Recipient" as tr_recip %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_user %}
|
||||
</th>
|
||||
<th>{% trans "Designation" %}</th>
|
||||
<th>{% trans "Total price" %}</th>
|
||||
<th>
|
||||
{% trans "Payment method" as tr_payment_method %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='payement' text=tr_payment_method %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Date" as tr_date %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='date' text=tr_date %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Invoice id" as tr_invoice_id %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='id' text=tr_invoice_id %}
|
||||
</th>
|
||||
<th>{% trans "Paid" %}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for invoice in custom_invoice_list %}
|
||||
<tr>
|
||||
<td>{{ invoice.recipient }}</td>
|
||||
<td>{{ invoice.name }}</td>
|
||||
<td>{{ invoice.prix_total }}</td>
|
||||
<td>{{ invoice.payment }}</td>
|
||||
<td>{{ invoice.date }}</td>
|
||||
<td>{{ invoice.id }}</td>
|
||||
<td>{{ invoice.paid }}</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" id="editinvoice" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
{% trans "Edit" %}<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="editinvoice">
|
||||
{% can_edit invoice %}
|
||||
<li>
|
||||
<a href="{% url 'cotisations:edit-custom-invoice' invoice.id %}">
|
||||
<i class="fa fa-dollar-sign"></i> {% trans "Edit" %}
|
||||
</a>
|
||||
</li>
|
||||
{% acl_end %}
|
||||
{% can_delete invoice %}
|
||||
<li>
|
||||
<a href="{% url 'cotisations:del-custom-invoice' invoice.id %}">
|
||||
<i class="fa fa-trash"></i> {% trans "Delete" %}
|
||||
</a>
|
||||
</li>
|
||||
{% acl_end %}
|
||||
<li>
|
||||
<a href="{% url 'cotisations:history' 'custominvoice' invoice.id %}">
|
||||
<i class="fa fa-history"></i> {% trans "Historique" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:custom-invoice-pdf' invoice.id %}">
|
||||
<i class="fa fa-file-pdf"></i> {% trans "PDF" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% if custom_invoice_list.paginator %}
|
||||
{% include 'pagination.html' with list=custom_invoice_list %}
|
||||
{% endif %}
|
||||
</div>
|
36
cotisations/templates/cotisations/index_custom_invoice.html
Normal file
36
cotisations/templates/cotisations/index_custom_invoice.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
{% extends "cotisations/sidebar.html" %}
|
||||
{% comment %}
|
||||
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.
|
||||
{% endcomment %}
|
||||
{% load acl %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Custom invoices" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans "Custom invoices list" %}</h2>
|
||||
{% can_create CustomInvoice %}
|
||||
{% include "buttons/add.html" with href='cotisations:new-custom-invoice'%}
|
||||
{% acl_end %}
|
||||
{% include 'cotisations/aff_custom_invoice.html' with custom_invoice_list=custom_invoice_list %}
|
||||
{% endblock %}
|
|
@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% block sidebar %}
|
||||
{% can_change Facture pdf %}
|
||||
<a class="list-group-item list-group-item-success" href="{% url "cotisations:new-facture-pdf" %}">
|
||||
<a class="list-group-item list-group-item-success" href="{% url "cotisations:new-custom-invoice" %}">
|
||||
<i class="fa fa-plus"></i> {% trans "Create an invoice" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-warning" href="{% url "cotisations:control" %}">
|
||||
|
@ -40,6 +40,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<i class="fa fa-list-ul"></i> {% trans "Invoices" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all CustomInvoice %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-custom-invoice" %}">
|
||||
<i class="fa fa-list-ul"></i> {% trans "Custom invoices" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all Article %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-article" %}">
|
||||
<i class="fa fa-list-ul"></i> {% trans "Available articles" %}
|
||||
|
|
|
@ -52,9 +52,29 @@ urlpatterns = [
|
|||
name='facture-pdf'
|
||||
),
|
||||
url(
|
||||
r'^new_facture_pdf/$',
|
||||
views.new_facture_pdf,
|
||||
name='new-facture-pdf'
|
||||
r'^index_custom_invoice/$',
|
||||
views.index_custom_invoice,
|
||||
name='index-custom-invoice'
|
||||
),
|
||||
url(
|
||||
r'^new_custom_invoice/$',
|
||||
views.new_custom_invoice,
|
||||
name='new-custom-invoice'
|
||||
),
|
||||
url(
|
||||
r'^edit_custom_invoice/(?P<custominvoiceid>[0-9]+)$',
|
||||
views.edit_custom_invoice,
|
||||
name='edit-custom-invoice'
|
||||
),
|
||||
url(
|
||||
r'^custom_invoice_pdf/(?P<custominvoiceid>[0-9]+)$',
|
||||
views.custom_invoice_pdf,
|
||||
name='custom-invoice-pdf',
|
||||
),
|
||||
url(
|
||||
r'^del_custom_invoice/(?P<custominvoiceid>[0-9]+)$',
|
||||
views.del_custom_invoice,
|
||||
name='del-custom-invoice'
|
||||
),
|
||||
url(
|
||||
r'^credit_solde/(?P<userid>[0-9]+)$',
|
||||
|
|
|
@ -58,7 +58,15 @@ from re2o.acl import (
|
|||
can_change,
|
||||
)
|
||||
from preferences.models import AssoOption, GeneralOption
|
||||
from .models import Facture, Article, Vente, Paiement, Banque
|
||||
from .models import (
|
||||
Facture,
|
||||
Article,
|
||||
Vente,
|
||||
Paiement,
|
||||
Banque,
|
||||
CustomInvoice,
|
||||
BaseInvoice
|
||||
)
|
||||
from .forms import (
|
||||
FactureForm,
|
||||
ArticleForm,
|
||||
|
@ -67,10 +75,10 @@ from .forms import (
|
|||
DelPaiementForm,
|
||||
BanqueForm,
|
||||
DelBanqueForm,
|
||||
NewFactureFormPdf,
|
||||
SelectUserArticleForm,
|
||||
SelectClubArticleForm,
|
||||
RechargeForm
|
||||
RechargeForm,
|
||||
CustomInvoiceForm
|
||||
)
|
||||
from .tex import render_invoice
|
||||
from .payment_methods.forms import payment_method_factory
|
||||
|
@ -178,10 +186,10 @@ def new_facture(request, user, userid):
|
|||
|
||||
# TODO : change facture to invoice
|
||||
@login_required
|
||||
@can_change(Facture, 'pdf')
|
||||
def new_facture_pdf(request):
|
||||
@can_create(CustomInvoice)
|
||||
def new_custom_invoice(request):
|
||||
"""
|
||||
View used to generate a custom PDF invoice. It's mainly used to
|
||||
View used to generate a custom invoice. It's mainly used to
|
||||
get invoices that are not taken into account, for the administrative
|
||||
point of view.
|
||||
"""
|
||||
|
@ -190,7 +198,7 @@ def new_facture_pdf(request):
|
|||
Q(type_user='All') | Q(type_user=request.user.class_name)
|
||||
)
|
||||
# Building the invocie form and the article formset
|
||||
invoice_form = NewFactureFormPdf(request.POST or None)
|
||||
invoice_form = CustomInvoiceForm(request.POST or None)
|
||||
if request.user.is_class_club:
|
||||
articles_formset = formset_factory(SelectClubArticleForm)(
|
||||
request.POST or None,
|
||||
|
@ -202,44 +210,31 @@ def new_facture_pdf(request):
|
|||
form_kwargs={'user': request.user}
|
||||
)
|
||||
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
|
||||
})
|
||||
paid = invoice_form.cleaned_data['paid']
|
||||
recipient = invoice_form.cleaned_data['dest']
|
||||
address = invoice_form.cleaned_data['chambre']
|
||||
total_price = sum(a['total_price'] for a in articles_info)
|
||||
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,
|
||||
_('The custom invoice was successfully created.')
|
||||
)
|
||||
return redirect(reverse('cotisations:index-custom-invoice'))
|
||||
|
||||
|
||||
return render_invoice(request, {
|
||||
'DATE': timezone.now(),
|
||||
'recipient_name': recipient,
|
||||
'address': address,
|
||||
'article': articles_info,
|
||||
'total': total_price,
|
||||
'paid': paid,
|
||||
'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)
|
||||
})
|
||||
return form({
|
||||
'factureform': invoice_form,
|
||||
'action_name': _("Create"),
|
||||
'articlesformset': articles_formset,
|
||||
'articles': articles
|
||||
'articlelist': articles
|
||||
}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
|
@ -292,7 +287,7 @@ def facture_pdf(request, facture, **_kwargs):
|
|||
def edit_facture(request, facture, **_kwargs):
|
||||
"""
|
||||
View used to edit an existing invoice.
|
||||
Articles can be added or remove to the invoice and quantity
|
||||
Articles can be added or removed to the invoice and quantity
|
||||
can be set as desired. This is also the view used to invalidate
|
||||
an invoice.
|
||||
"""
|
||||
|
@ -347,6 +342,100 @@ def del_facture(request, facture, **_kwargs):
|
|||
}, 'cotisations/delete.html', request)
|
||||
|
||||
|
||||
@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,
|
||||
_("The invoice has been successfully edited.")
|
||||
)
|
||||
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,
|
||||
_("The invoice has been successfully deleted.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-custom-invoice'))
|
||||
return form({
|
||||
'objet': invoice,
|
||||
'objet_name': _("Invoice")
|
||||
}, 'cotisations/delete.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_create(Article)
|
||||
def add_article(request):
|
||||
|
@ -681,8 +770,31 @@ def index_banque(request):
|
|||
})
|
||||
|
||||
|
||||
@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
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@can_view_all(Facture)
|
||||
@can_view_all(CustomInvoice)
|
||||
def index(request):
|
||||
"""
|
||||
View used to display the list of all exisitng invoices.
|
||||
|
@ -698,7 +810,7 @@ def index(request):
|
|||
)
|
||||
invoice_list = re2o_paginator(request, invoice_list, pagination_number)
|
||||
return render(request, 'cotisations/index.html', {
|
||||
'facture_list': invoice_list
|
||||
'facture_list': invoice_list,
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -250,6 +250,14 @@ class SortTable:
|
|||
'cotis_id': ['id'],
|
||||
'default': ['-date']
|
||||
}
|
||||
COTISATIONS_CUSTOM = {
|
||||
'invoice_date': ['date'],
|
||||
'invoice_id': ['id'],
|
||||
'invoice_recipient': ['recipient'],
|
||||
'invoice_address': ['address'],
|
||||
'invoice_payment': ['payment'],
|
||||
'default': ['-date']
|
||||
}
|
||||
COTISATIONS_CONTROL = {
|
||||
'control_name': ['user__adherent__name'],
|
||||
'control_surname': ['user__surname'],
|
||||
|
|
|
@ -21,6 +21,6 @@ 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.
|
||||
{% endcomment %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url href id %}" title="{{ desc|default:"Ajouter" }}">
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% if id %}{% url href id %}{% else %}{% url href %}{% endif %}" title="{{ desc|default:"Ajouter" }}">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
|
|
Loading…
Reference in a new issue