Browse Source

Properly manage case where a payment is added to an invoice specifically

Alexandre Aubin 7 years ago
parent
commit
a0f8841099
2 changed files with 68 additions and 16 deletions
  1. 36 14
      coin/billing/admin.py
  2. 32 2
      coin/billing/models.py

+ 36 - 14
coin/billing/admin.py

@@ -64,10 +64,31 @@ class InvoiceDetailInlineReadOnly(admin.StackedInline):
         return result
 
 
-class PaymentInline(admin.StackedInline):
+class PaymentInlineAdd(admin.StackedInline):
     model = Payment
     extra = 0
     fields = (('date', 'payment_mean', 'amount'),)
+    can_delete = False
+
+    verbose_name_plural = "Add a payment"
+
+    def has_change_permission(self, request):
+        return False
+
+
+class PaymentInlineReadOnly(admin.StackedInline):
+    model = Payment
+    extra = 0
+    fields = PaymentInlineAdd.fields
+    can_delete = False
+    verbose_name = None
+    verbose_name_plural = "Linked payments"
+
+    def has_add_permission(self, request):
+        return False
+
+    def get_readonly_fields(self, request, obj=None):
+        return flatten_fieldsets(self.declared_fieldsets)
 
 
 class InvoiceAdmin(admin.ModelAdmin):
@@ -112,7 +133,10 @@ class InvoiceAdmin(admin.ModelAdmin):
             else:
                 inlines = [InvoiceDetailInline]
 
-            inlines += [PaymentInline]
+            inlines += [PaymentInlineReadOnly]
+
+            if obj.status == 'open' and obj.validated:
+                inlines += [PaymentInlineAdd]
 
         for inline_class in inlines:
             inline = inline_class(self.model, self.admin_site)
@@ -170,22 +194,20 @@ class PaymentAdmin(admin.ModelAdmin):
                     'invoice', 'amount_already_allocated', 'label')
     list_display_links = ()
     fields = (('member'),
-              ('amount', 'payment_mean', 'date', 'label'),
-              ('invoice'),
-              ('amount_already_allocated'))
+              ('amount', 'payment_mean', 'date', 'label'))
     readonly_fields = ('amount_already_allocated', 'label')
     form = autocomplete_light.modelform_factory(Payment, fields='__all__')
 
     def get_readonly_fields(self, request, obj=None):
-        if obj:
-            if self.declared_fieldsets:
-                return flatten_fieldsets(self.declared_fieldsets)
-            else:
-                return list(set(
-                    [field.name for field in self.opts.local_fields] +
-                    [field.name for field in self.opts.local_many_to_many]
-                ))
-        return self.readonly_fields
+        if not obj:
+            return self.readonly_fields
+
+        # If payment already started to be allocated or already have a member
+        if obj.amount_already_allocated != 0 or obj.member != None:
+            # All fields are readonly
+            return flatten_fieldsets(self.declared_fieldsets)
+        else:
+            return self.readonly_fields
 
 
 admin.site.register(Invoice, InvoiceAdmin)

+ 32 - 2
coin/billing/models.py

@@ -13,6 +13,7 @@ from django.utils import timezone
 from django.utils.encoding import python_2_unicode_compatible
 from django.dispatch import receiver
 from django.db.models.signals import post_save
+from django.core.exceptions import ValidationError
 
 
 from coin.offers.models import OfferSubscription
@@ -390,8 +391,29 @@ class Payment(models.Model):
                              verbose_name='libellé')
 
     def save(self, *args, **kwargs):
+
+        # Only if no amount already allocated...
+        if self.amount_already_allocated == 0:
+
+            # If there's a linked invoice and no member defined
+            if self.invoice and not self.member:
+                # Automatically set member to invoice's member
+                self.member = self.invoice.member
+
         super(Payment, self).save(*args, **kwargs)
 
+
+    def clean(self):
+
+        # Only if no amount already allocated...
+        if self.amount_already_allocated == 0:
+
+            # If there's a linked invoice and this payment would pay more than
+            # the remaining amount needed to pay the invoice...
+            if self.invoice and self.amount > self.invoice.amount_remaining_to_pay():
+                raise ValidationError("This payment would pay more than the invoice's remaining to pay")
+
+
     def amount_not_allocated(self):
         return self.amount - self.amount_already_allocated
 
@@ -506,7 +528,16 @@ def reconcile_invoices_and_payments(member):
 
         # Only consider the oldest active payment and the oldest active invoice
         p = active_payments[0]
-        i = active_invoices[0]
+
+        # If this payment is to be allocated for a specific invoice...
+        if p.invoice:
+            # Assert that the invoice is still 'active'
+            assert p.invoice in active_invoices
+            i = p.invoice
+            accounting_log.info("Payment is to be allocated specifically to " \
+                                "invoice %s" % str(i.number))
+        else:
+            i = active_invoices[0]
 
         # TODO : should add an assert that the ammount not allocated / remaining to
         # pay is lower before and after calling the allocate_to_invoice
@@ -553,7 +584,6 @@ def payment_changed(sender, instance, created, **kwargs):
     and (instance.member is not None):
         update_accounting_for_member(instance.member)
 
-
 @receiver(post_save, sender=Invoice)
 def invoice_changed(sender, instance, created, **kwargs):