Fabs 10 years ago
parent
commit
92332aef28
5 changed files with 58 additions and 40 deletions
  1. 19 14
      coin/billing/admin.py
  2. 25 13
      coin/billing/models.py
  3. 10 9
      coin/billing/tests.py
  4. 1 1
      coin/billing/urls.py
  5. 3 3
      coin/billing/views.py

+ 19 - 14
coin/billing/admin.py

@@ -19,7 +19,7 @@ class InvoiceDetailInline(LimitedAdminInlineMixin, admin.StackedInline):
     model = InvoiceDetail
     model = InvoiceDetail
     extra = 0
     extra = 0
     fields = (('label', 'amount', 'quantity', 'tax'),
     fields = (('label', 'amount', 'quantity', 'tax'),
-        ('offersubscription','period_from', 'period_to'))
+              ('offersubscription', 'period_from', 'period_to'))
 
 
     def get_filters(self, obj):
     def get_filters(self, obj):
         """
         """
@@ -28,7 +28,7 @@ class InvoiceDetailInline(LimitedAdminInlineMixin, admin.StackedInline):
         une liste vide
         une liste vide
         """
         """
         if obj and obj.member:
         if obj and obj.member:
-            return (('offersubscription', {'member':obj.member}),)
+            return (('offersubscription', {'member': obj.member}),)
         else:
         else:
             return (('offersubscription', None),)
             return (('offersubscription', None),)
 
 
@@ -39,6 +39,7 @@ class InvoiceDetailInline(LimitedAdminInlineMixin, admin.StackedInline):
 
 
 
 
 class InvoiceDetailInlineReadOnly(admin.StackedInline):
 class InvoiceDetailInlineReadOnly(admin.StackedInline):
+
     """
     """
     Lorsque la facture est validée, il n'est plus possible de la modifier
     Lorsque la facture est validée, il n'est plus possible de la modifier
     Ce inline est donc identique à InvoiceDetailInline, mais tous
     Ce inline est donc identique à InvoiceDetailInline, mais tous
@@ -57,9 +58,9 @@ class InvoiceDetailInlineReadOnly(admin.StackedInline):
             result = flatten_fieldsets(self.declared_fieldsets)
             result = flatten_fieldsets(self.declared_fieldsets)
         else:
         else:
             result = list(set(
             result = list(set(
-                    [field.name for field in self.opts.local_fields] +
-                    [field.name for field in self.opts.local_many_to_many]
-                ))
+                [field.name for field in self.opts.local_fields] +
+                [field.name for field in self.opts.local_many_to_many]
+            ))
             result.remove('id')
             result.remove('id')
         return result
         return result
 
 
@@ -74,11 +75,11 @@ class InvoiceAdmin(admin.ModelAdmin):
     list_display = ('number', 'date', 'status', 'amount', 'member', 'validated')
     list_display = ('number', 'date', 'status', 'amount', 'member', 'validated')
     list_display_links = ('number', 'date')
     list_display_links = ('number', 'date')
     fields = (('number', 'date', 'status'),
     fields = (('number', 'date', 'status'),
-       ('date_due'),
-       ('member'),
-       ('amount','amount_paid'),
-       ('validated', 'pdf'))
-    readonly_fields = ('amount','amount_paid','validated','pdf')
+              ('date_due'),
+              ('member'),
+              ('amount', 'amount_paid'),
+              ('validated', 'pdf'))
+    readonly_fields = ('amount', 'amount_paid', 'validated', 'pdf')
     form = autocomplete_light.modelform_factory(Invoice)
     form = autocomplete_light.modelform_factory(Invoice)
 
 
     def get_readonly_fields(self, request, obj=None):
     def get_readonly_fields(self, request, obj=None):
@@ -133,22 +134,26 @@ class InvoiceAdmin(admin.ModelAdmin):
         """
         """
         urls = super(InvoiceAdmin, self).get_urls()
         urls = super(InvoiceAdmin, self).get_urls()
         my_urls = [
         my_urls = [
-            url(r'^validate/(?P<id>.+)$', self.admin_site.admin_view(self.validate_view),
+            url(r'^validate/(?P<id>.+)$',
+                self.admin_site.admin_view(self.validate_view),
                 name='invoice_validate'),
                 name='invoice_validate'),
         ]
         ]
         return my_urls + urls
         return my_urls + urls
 
 
     def validate_view(self, request, id):
     def validate_view(self, request, id):
         """
         """
-        Vue appelée lorsque l'admin souhaite valider une facture (et générer le pdf)
+        Vue appelée lorsque l'admin souhaite valider une facture et 
+        générer son pdf
         """
         """
-        #TODO : Add better perm here
+        # TODO : Add better perm here
         if request.user.is_superuser:
         if request.user.is_superuser:
             invoice = get_invoice_from_id_or_number(id)
             invoice = get_invoice_from_id_or_number(id)
             invoice.validate()
             invoice.validate()
             messages.success(request, 'La facture a été validée.')
             messages.success(request, 'La facture a été validée.')
         else:
         else:
-            messages.error(request, 'Vous n\'avez pas l\'autorisation de valider une facture.')
+            messages.error(
+                request, 'Vous n\'avez pas l\'autorisation de valider '
+                         'une facture.')
 
 
         return HttpResponseRedirect(request.META["HTTP_REFERER"])
         return HttpResponseRedirect(request.META["HTTP_REFERER"])
 
 

+ 25 - 13
coin/billing/models.py

@@ -20,13 +20,17 @@ from coin.utils import private_files_storage, start_of_month, end_of_month
 def next_invoice_number():
 def next_invoice_number():
     """Détermine un numéro de facture aléatoire"""
     """Détermine un numéro de facture aléatoire"""
     return '%s%02i-%i-%i' % (datetime.date.today().year,
     return '%s%02i-%i-%i' % (datetime.date.today().year,
-                              datetime.date.today().month,
-                              random.randrange(100, 999),
-                              random.randrange(100, 999))
+                             datetime.date.today().month,
+                             random.randrange(100, 999),
+                             random.randrange(100, 999))
+
 
 
 def invoice_pdf_filename(instance, filename):
 def invoice_pdf_filename(instance, filename):
     """Nom du fichier pdf à stocker pour les factures"""
     """Nom du fichier pdf à stocker pour les factures"""
-    return 'invoices/%d_%s_%s.pdf' % (getattr(instance,'member.id',0), instance.number, uuid.uuid4())
+    return 'invoices/%d_%s_%s.pdf' % (getattr(instance, 'member.id', 0),
+                                      instance.number,
+                                      uuid.uuid4())
+
 
 
 class Invoice(models.Model):
 class Invoice(models.Model):
 
 
@@ -44,7 +48,8 @@ class Invoice(models.Model):
     status = models.CharField(max_length=50, choices=INVOICES_STATUS_CHOICES,
     status = models.CharField(max_length=50, choices=INVOICES_STATUS_CHOICES,
                               default='open',
                               default='open',
                               verbose_name='statut')
                               verbose_name='statut')
-    date = models.DateField(default=datetime.date.today, null=True, verbose_name='date')
+    date = models.DateField(
+        default=datetime.date.today, null=True, verbose_name='date')
     date_due = models.DateField(
     date_due = models.DateField(
         default=end_of_month,
         default=end_of_month,
         null=True,
         null=True,
@@ -53,12 +58,16 @@ class Invoice(models.Model):
                                related_name='invoices',
                                related_name='invoices',
                                verbose_name='membre',
                                verbose_name='membre',
                                on_delete=models.SET_NULL)
                                on_delete=models.SET_NULL)
-    pdf = models.FileField(storage=private_files_storage, upload_to=invoice_pdf_filename,
+    pdf = models.FileField(storage=private_files_storage,
+                           upload_to=invoice_pdf_filename,
                            null=True, blank=True,
                            null=True, blank=True,
                            verbose_name='PDF')
                            verbose_name='PDF')
 
 
     def amount(self):
     def amount(self):
-        """Calcul le montant de la facture en fonction des éléments de détails"""
+        """
+        Calcul le montant de la facture
+        en fonction des éléments de détails
+        """
         total = Decimal('0.0')
         total = Decimal('0.0')
         for detail in self.details.all():
         for detail in self.details.all():
             total += detail.total()
             total += detail.total()
@@ -103,7 +112,9 @@ class Invoice(models.Model):
             self.generate_pdf()
             self.generate_pdf()
 
 
     def is_pdf_exists(self):
     def is_pdf_exists(self):
-        return self.validated and bool(self.pdf) and private_files_storage.exists(self.pdf.name)
+        return (self.validated
+                and bool(self.pdf)
+                and private_files_storage.exists(self.pdf.name))
 
 
     def __unicode__(self):
     def __unicode__(self):
         return '#%s %0.2f€ %s' % (self.number, self.amount(), self.date_due)
         return '#%s %0.2f€ %s' % (self.number, self.amount(), self.date_due)
@@ -146,7 +157,8 @@ class InvoiceDetail(models.Model):
     def total(self):
     def total(self):
         """Calcul le total"""
         """Calcul le total"""
         return (self.amount * (self.tax / Decimal('100.0') +
         return (self.amount * (self.tax / Decimal('100.0') +
-                Decimal('1.0')) * self.quantity).quantize(Decimal('0.01'))
+                               Decimal('1.0')) *
+                self.quantity).quantize(Decimal('0.01'))
 
 
     class Meta:
     class Meta:
         verbose_name = 'détail de facture'
         verbose_name = 'détail de facture'
@@ -177,6 +189,7 @@ class Payment(models.Model):
     class Meta:
     class Meta:
         verbose_name = 'paiement'
         verbose_name = 'paiement'
 
 
+
 @receiver(post_save, sender=Payment)
 @receiver(post_save, sender=Payment)
 def set_invoice_as_paid_if_needed(sender, instance, **kwargs):
 def set_invoice_as_paid_if_needed(sender, instance, **kwargs):
     """
     """
@@ -184,7 +197,6 @@ def set_invoice_as_paid_if_needed(sender, instance, **kwargs):
     complétement payée. Dans ce cas elle passe en réglée
     complétement payée. Dans ce cas elle passe en réglée
     """
     """
     if (instance.invoice.amount_paid >= instance.invoice.amount and
     if (instance.invoice.amount_paid >= instance.invoice.amount and
-       instance.invoice.status == 'open'):
-       instance.invoice.status = 'closed'
-       instance.invoice.save()
-
+            instance.invoice.status == 'open'):
+        instance.invoice.status = 'closed'
+        instance.invoice.save()

+ 10 - 9
coin/billing/tests.py

@@ -30,7 +30,7 @@ class BillingInvoiceCreationTests(TestCase):
             member=self.member,
             member=self.member,
             offer=self.offer)
             offer=self.offer)
         self.subscription.save()
         self.subscription.save()
-    
+
     def tearDown(self):
     def tearDown(self):
         # Supprime l'utilisateur LDAP créé
         # Supprime l'utilisateur LDAP créé
         LdapUser.objects.get(pk=self.username).delete()
         LdapUser.objects.get(pk=self.username).delete()
@@ -54,15 +54,16 @@ class BillingInvoiceCreationTests(TestCase):
         Test que la première facture d'un abonnement est facturée au prorata du
         Test que la première facture d'un abonnement est facturée au prorata du
         nombre de jours restants
         nombre de jours restants
         """
         """
-        #Créé la facture pour le mois de janvier
-        invoice = create_member_invoice_for_a_period(self.member, datetime.date(2014,1,1))
-        #Comme l'abonnement a été souscris le 10/01 et que la période de
-        #facturation est de 3 mois, alors le prorata doit être :
-        #janvier :  22j (31-9)
-        #fevrier :  28j
-        #mars :     31j
+        # Créé la facture pour le mois de janvier
+        invoice = create_member_invoice_for_a_period(
+            self.member, datetime.date(2014, 1, 1))
+        # Comme l'abonnement a été souscris le 10/01 et que la période de
+        # facturation est de 3 mois, alors le prorata doit être :
+        # janvier :  22j (31-9)
+        # fevrier :  28j
+        # mars :     31j
         #22+28+31 / 31+28+31
         #22+28+31 / 31+28+31
-        quantity = Decimal((22.0+28.0+31.0)/(31.0+28.0+31.0))
+        quantity = Decimal((22.0 + 28.0 + 31.0) / (31.0 + 28.0 + 31.0))
         for detail in invoice.details.all():
         for detail in invoice.details.all():
             if detail.amount != 50:
             if detail.amount != 50:
                 self.assertEqual(detail.quantity.quantize(Decimal('0.01')),
                 self.assertEqual(detail.quantity.quantize(Decimal('0.01')),

+ 1 - 1
coin/billing/urls.py

@@ -10,6 +10,6 @@ urlpatterns = patterns(
     url(r'^invoice/(?P<id>.+)/pdf$', views.invoice_pdf, name="invoice_pdf"),
     url(r'^invoice/(?P<id>.+)/pdf$', views.invoice_pdf, name="invoice_pdf"),
     url(r'^invoice/(?P<id>.+)$', views.invoice, name="invoice"),
     url(r'^invoice/(?P<id>.+)$', views.invoice, name="invoice"),
     # url(r'^invoice/(?P<id>.+)/validate$', views.invoice_validate, name="invoice_validate"),
     # url(r'^invoice/(?P<id>.+)/validate$', views.invoice_validate, name="invoice_validate"),
-    
+
     url('invoice/create_all_members_invoices_for_a_period', views.gen_invoices)
     url('invoice/create_all_members_invoices_for_a_period', views.gen_invoices)
 )
 )

+ 3 - 3
coin/billing/views.py

@@ -15,10 +15,12 @@ from coin.html2pdf import render_as_pdf
 from coin.billing.create_subscriptions_invoices import create_all_members_invoices_for_a_period
 from coin.billing.create_subscriptions_invoices import create_all_members_invoices_for_a_period
 from coin.billing.utils import get_invoice_from_id_or_number
 from coin.billing.utils import get_invoice_from_id_or_number
 
 
+
 def gen_invoices(request):
 def gen_invoices(request):
     create_all_members_invoices_for_a_period()
     create_all_members_invoices_for_a_period()
     return HttpResponse('blop')
     return HttpResponse('blop')
 
 
+
 def invoice_pdf(request, id):
 def invoice_pdf(request, id):
     """
     """
     Renvoi une facture générée en format pdf
     Renvoi une facture générée en format pdf
@@ -35,6 +37,7 @@ def invoice_pdf(request, id):
     return sendfile(request, invoice.pdf.path,
     return sendfile(request, invoice.pdf.path,
                     attachment=True, attachment_filename=pdf_filename)
                     attachment=True, attachment_filename=pdf_filename)
 
 
+
 def invoice(request, id):
 def invoice(request, id):
     """
     """
     Affiche une facture et son détail
     Affiche une facture et son détail
@@ -50,6 +53,3 @@ def invoice(request, id):
                               context_instance=RequestContext(request))
                               context_instance=RequestContext(request))
 
 
     return response
     return response
-
-
-