diff --git a/cotisations/forms.py b/cotisations/forms.py index 7ad9e413..ccf9d5d6 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -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): diff --git a/cotisations/migrations/0031_custom_invoice.py b/cotisations/migrations/0031_custom_invoice.py new file mode 100644 index 00000000..52921739 --- /dev/null +++ b/cotisations/migrations/0031_custom_invoice.py @@ -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'), + ) + ] diff --git a/cotisations/models.py b/cotisations/models.py index 52d71b58..c4515cc7 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -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) diff --git a/cotisations/templates/cotisations/aff_custom_invoice.html b/cotisations/templates/cotisations/aff_custom_invoice.html new file mode 100644 index 00000000..1a477613 --- /dev/null +++ b/cotisations/templates/cotisations/aff_custom_invoice.html @@ -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 %} + +
+ {% trans "Recipient" as tr_recip %} + {% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_user %} + | +{% trans "Designation" %} | +{% trans "Total price" %} | ++ {% trans "Payment method" as tr_payment_method %} + {% include 'buttons/sort.html' with prefix='invoice' col='payement' text=tr_payment_method %} + | ++ {% trans "Date" as tr_date %} + {% include 'buttons/sort.html' with prefix='invoice' col='date' text=tr_date %} + | ++ {% trans "Invoice id" as tr_invoice_id %} + {% include 'buttons/sort.html' with prefix='invoice' col='id' text=tr_invoice_id %} + | +{% trans "Paid" %} | ++ | + |
---|---|---|---|---|---|---|---|---|
{{ invoice.recipient }} | +{{ invoice.name }} | +{{ invoice.prix_total }} | +{{ invoice.payment }} | +{{ invoice.date }} | +{{ invoice.id }} | +{{ invoice.paid }} | +
+
+
+
+
+ |
+ + + {% trans "PDF" %} + + | +