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
     extra = 0
     fields = (('label', 'amount', 'quantity', 'tax'),
-        ('offersubscription','period_from', 'period_to'))
+              ('offersubscription', 'period_from', 'period_to'))
 
     def get_filters(self, obj):
         """
@@ -28,7 +28,7 @@ class InvoiceDetailInline(LimitedAdminInlineMixin, admin.StackedInline):
         une liste vide
         """
         if obj and obj.member:
-            return (('offersubscription', {'member':obj.member}),)
+            return (('offersubscription', {'member': obj.member}),)
         else:
             return (('offersubscription', None),)
 
@@ -39,6 +39,7 @@ class InvoiceDetailInline(LimitedAdminInlineMixin, admin.StackedInline):
 
 
 class InvoiceDetailInlineReadOnly(admin.StackedInline):
+
     """
     Lorsque la facture est validée, il n'est plus possible de la modifier
     Ce inline est donc identique à InvoiceDetailInline, mais tous
@@ -57,9 +58,9 @@ class InvoiceDetailInlineReadOnly(admin.StackedInline):
             result = flatten_fieldsets(self.declared_fieldsets)
         else:
             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')
         return result
 
@@ -74,11 +75,11 @@ class InvoiceAdmin(admin.ModelAdmin):
     list_display = ('number', 'date', 'status', 'amount', 'member', 'validated')
     list_display_links = ('number', 'date')
     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)
 
     def get_readonly_fields(self, request, obj=None):
@@ -133,22 +134,26 @@ class InvoiceAdmin(admin.ModelAdmin):
         """
         urls = super(InvoiceAdmin, self).get_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'),
         ]
         return my_urls + urls
 
     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:
             invoice = get_invoice_from_id_or_number(id)
             invoice.validate()
             messages.success(request, 'La facture a été validée.')
         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"])
 

+ 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():
     """Détermine un numéro de facture aléatoire"""
     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):
     """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):
 
@@ -44,7 +48,8 @@ class Invoice(models.Model):
     status = models.CharField(max_length=50, choices=INVOICES_STATUS_CHOICES,
                               default='open',
                               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(
         default=end_of_month,
         null=True,
@@ -53,12 +58,16 @@ class Invoice(models.Model):
                                related_name='invoices',
                                verbose_name='membre',
                                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,
                            verbose_name='PDF')
 
     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')
         for detail in self.details.all():
             total += detail.total()
@@ -103,7 +112,9 @@ class Invoice(models.Model):
             self.generate_pdf()
 
     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):
         return '#%s %0.2f€ %s' % (self.number, self.amount(), self.date_due)
@@ -146,7 +157,8 @@ class InvoiceDetail(models.Model):
     def total(self):
         """Calcul le total"""
         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:
         verbose_name = 'détail de facture'
@@ -177,6 +189,7 @@ class Payment(models.Model):
     class Meta:
         verbose_name = 'paiement'
 
+
 @receiver(post_save, sender=Payment)
 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
     """
     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,
             offer=self.offer)
         self.subscription.save()
-    
+
     def tearDown(self):
         # Supprime l'utilisateur LDAP créé
         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
         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
-        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():
             if detail.amount != 50:
                 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>.+)$', views.invoice, name="invoice"),
     # url(r'^invoice/(?P<id>.+)/validate$', views.invoice_validate, name="invoice_validate"),
-    
+
     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.utils import get_invoice_from_id_or_number
 
+
 def gen_invoices(request):
     create_all_members_invoices_for_a_period()
     return HttpResponse('blop')
 
+
 def invoice_pdf(request, id):
     """
     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,
                     attachment=True, attachment_filename=pdf_filename)
 
+
 def invoice(request, id):
     """
     Affiche une facture et son détail
@@ -50,6 +53,3 @@ def invoice(request, id):
                               context_instance=RequestContext(request))
 
     return response
-
-
-