From 2ce45b9297fd0e476d44d90dc201b8d9a22b11c4 Mon Sep 17 00:00:00 2001 From: tomtom5152 Date: Thu, 29 Jan 2015 23:17:50 +0000 Subject: [PATCH] Basic working invoice system. Need to add a way to create invoices. --- RIGS/admin.py | 4 +- RIGS/finanace.py | 0 RIGS/finance.py | 60 ++++++++++ RIGS/migrations/0017_auto_20150129_2041.py | 17 +++ RIGS/models.py | 35 +++++- RIGS/templates/RIGS/invoice_detail.html | 108 ++++++++++++++++++ RIGS/templates/RIGS/invoice_list.html | 35 ++++++ .../RIGS/payment_confirm_delete.html | 37 ++++++ RIGS/templates/RIGS/payment_form.html | 51 +++++++++ RIGS/urls.py | 25 +++- 10 files changed, 366 insertions(+), 6 deletions(-) delete mode 100644 RIGS/finanace.py create mode 100644 RIGS/finance.py create mode 100644 RIGS/migrations/0017_auto_20150129_2041.py create mode 100644 RIGS/templates/RIGS/invoice_detail.html create mode 100644 RIGS/templates/RIGS/invoice_list.html create mode 100644 RIGS/templates/RIGS/payment_confirm_delete.html create mode 100644 RIGS/templates/RIGS/payment_form.html diff --git a/RIGS/admin.py b/RIGS/admin.py index bdec5cdd..37f35a8b 100644 --- a/RIGS/admin.py +++ b/RIGS/admin.py @@ -9,4 +9,6 @@ admin.site.register(models.Organisation, reversion.VersionAdmin) admin.site.register(models.VatRate, reversion.VersionAdmin) admin.site.register(models.Venue, reversion.VersionAdmin) admin.site.register(models.Event, reversion.VersionAdmin) -admin.site.register(models.EventItem, reversion.VersionAdmin) \ No newline at end of file +admin.site.register(models.EventItem, reversion.VersionAdmin) +admin.site.register(models.Invoice) +admin.site.register(models.Payment) \ No newline at end of file diff --git a/RIGS/finanace.py b/RIGS/finanace.py deleted file mode 100644 index e69de29b..00000000 diff --git a/RIGS/finance.py b/RIGS/finance.py new file mode 100644 index 00000000..72439d95 --- /dev/null +++ b/RIGS/finance.py @@ -0,0 +1,60 @@ +from django.core.urlresolvers import reverse_lazy +from django.http import Http404, HttpResponseRedirect +from django.views import generic +from django.shortcuts import get_object_or_404 +from django.contrib import messages + +from RIGS import models + + +class InvoiceIndex(generic.ListView): + model = models.Invoice + template_name = 'RIGS/invoice_list.html' + + def get_queryset(self): + active = self.model.objects.filter(void=False).select_related('payment_set') + set = [] + for invoice in active: + if invoice.balance != 0: + set.append(invoice) + return set + + +class InvoiceDetail(generic.DetailView): + model = models.Invoice + + +class InvoiceVoid(generic.View): + def get(self, request, *args, **kwargs): + pk = kwargs.get('pk') + object = get_object_or_404(models.Invoice, pk=pk) + object.void = not object.void + object.save() + + if object.void: + return HttpResponseRedirect(reverse_lazy('invoice_list')) + return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': object.pk})) + + +class PaymentCreate(generic.CreateView): + model = models.Payment + + def get_initial(self): + initial = super(generic.CreateView, self).get_initial() + invoicepk = self.request.GET.get('invoice', self.request.POST.get('invoice', None)) + if invoicepk == None: + raise Http404() + invoice = get_object_or_404(models.Invoice, pk=invoicepk) + initial.update({'invoice': invoice}) + return initial + + def get_success_url(self): + messages.info(self.request, "location.reload()") + return reverse_lazy('closemodal') + + +class PaymentDelete(generic.DeleteView): + model = models.Payment + + def get_success_url(self): + return self.request.POST.get('next') \ No newline at end of file diff --git a/RIGS/migrations/0017_auto_20150129_2041.py b/RIGS/migrations/0017_auto_20150129_2041.py new file mode 100644 index 00000000..e90491a6 --- /dev/null +++ b/RIGS/migrations/0017_auto_20150129_2041.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + dependencies = [ + ('RIGS', '0016_auto_20150127_1905'), + ] + + operations = [ + migrations.AlterModelOptions( + name='invoice', + options={'permissions': (('view_invoice', 'Can view Invoices'),)}, + ), + ] diff --git a/RIGS/models.py b/RIGS/models.py index aba43043..f264066d 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -242,6 +242,9 @@ class Event(models.Model, RevisionMixin): collector = models.CharField(max_length=255, blank=True, null=True) # Calculated values + """ + EX Vat + """ @property def sum_total(self): total = 0 @@ -257,6 +260,9 @@ class Event(models.Model, RevisionMixin): def vat(self): return self.sum_total * self.vat_rate.rate + """ + Inc VAT + """ @property def total(self): return self.sum_total + self.vat @@ -313,19 +319,44 @@ class Invoice(models.Model): invoice_date = models.DateField(auto_now_add=True) void = models.BooleanField() + @property + def sum_total(self): + return self.event.sum_total + + @property + def total(self): + return self.event.total + + @property + def payment_total(self): + total = 0 + for payment in self.payment_set.all(): + total += payment.amount + return total + + @property + def balance(self): + return self.sum_total - self.payment_total + + def __str__(self): + return "%i: %s (%.2f)" % (self.pk, self.event, self.balance) + + class Meta: + permissions = ( + ('view_invoice', 'Can view Invoices'), + ) + class Payment(models.Model): CASH = 'C' INTERNAL = 'I' EXTERNAL = 'E' SUCORE = 'SU' - MEMBERS = 'M' METHODS = ( (CASH, 'Cash'), (INTERNAL, 'Internal'), (EXTERNAL, 'External'), (SUCORE, 'SU Core'), - (MEMBERS, 'Members'), ) invoice = models.ForeignKey('Invoice') diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html new file mode 100644 index 00000000..9ce5a667 --- /dev/null +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -0,0 +1,108 @@ +{% extends 'base.html' %} + +{% block title %}Invoice {{ object.pk }}{% endblock %} + +{% block content %} +
+

Invoice {{ object.pk }}

+ +
+
+
+
Invoice Details
+
+ {% if object.event.organisation %} + {{ object.event.organisation.name }}
+ {{ object.event.organisation.address|linebreaksbr }} + {% else %} + {{ object.event.person.name }}
+ {{ object.event.person.address|linebreaksbr }} + {% endif %} +
+
+
+
+
+
Event Details
+
+
+
Event Number
+
N{{ object.event.pk|stringformat:"05d" }}
+ +
Event
+
{{ objet.event.pk }}
+ +
Event Venue
+
{{ object.event.venue }}
+ +
Event MIC
+
{{ object.event.mic.name }}
+ +
Status
+
{{ object.event.get_status_display }}
+ + {% if object.event.dry_hire %} +
 
+
Checked In By
+
{{ object.checked_in_by.name }}
+ {% endif %} + +
PO
+
{{ object.event.purchase_order }}
+
+
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + {% for payment in object.payment_set.all %} + + + + + + + {% endfor %} + +
DateAmountMethod
{{ payment.date }}{{ payment.amount|floatformat:2 }}{{ payment.get_method_display }} + +
+
+
+
+ +
+
+
+ {% with object.event as object %} + {% include 'RIGS/item_table.html' %} + {% endwith %} +
+
+
+
+ +
+{% endblock %} \ No newline at end of file diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html new file mode 100644 index 00000000..ef246970 --- /dev/null +++ b/RIGS/templates/RIGS/invoice_list.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} + +{% block title %}Active Invoices{% endblock %} + +{% block content %} +
+

Active Invoices

+ + + + + + + + + + + + {% for object in object_list %} + + + + + + + + {% endfor %} + +
#EventInvoice DateBalance
{{ object.pk }}{{ object.event }}{{ object.invoice_date }}{{ object.balance|floatformat:2 }} + + + +
+
+{% endblock %} \ No newline at end of file diff --git a/RIGS/templates/RIGS/payment_confirm_delete.html b/RIGS/templates/RIGS/payment_confirm_delete.html new file mode 100644 index 00000000..daa5d004 --- /dev/null +++ b/RIGS/templates/RIGS/payment_confirm_delete.html @@ -0,0 +1,37 @@ +{% extends 'base.html' %} + +{% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %} + +{% block content %} +
+ +
+{% endblock %} \ No newline at end of file diff --git a/RIGS/templates/RIGS/payment_form.html b/RIGS/templates/RIGS/payment_form.html new file mode 100644 index 00000000..41bbf15b --- /dev/null +++ b/RIGS/templates/RIGS/payment_form.html @@ -0,0 +1,51 @@ +{% extends 'base_ajax.html' %} +{% load widget_tweaks %} + +{% block title %}Add Payment{% endblock %} + +{% block content %} +
+
+
{% csrf_token %} + + +
+ {% include 'form_errors.html' %} +
+ + +
+ {% render_field form.date type="date" class+="form-control" %} +
+
+ +
+ + +
+
+
£
+ {% render_field form.amount class+="form-control" %} +
+
+
+
+ + +
+ {% render_field form.method class+="form-control" %} +
+
+
+
+ +
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/RIGS/urls.py b/RIGS/urls.py index 2987c2de..f06e1cef 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import patterns, include, url from django.contrib.auth.decorators import login_required -from RIGS import views, rigboard +from RIGS import views, rigboard, finance from django.views.generic import RedirectView from PyRIGS.decorators import permission_required_with_403 @@ -74,7 +74,25 @@ urlpatterns = patterns('', url(r'^event/(?P\d+)/duplicate/$', permission_required_with_403('RIGS.add_event')(rigboard.EventDuplicate.as_view()), name='event_duplicate'), - url(r'^event/archive/$', login_required()(rigboard.EventArchive.as_view()), name='event_archive'), + url(r'^event/archive/$', login_required()(rigboard.EventArchive.as_view()), + name='event_archive'), + + # Finance + url(r'^invoice/$', + permission_required_with_403('RIGS.view_invoice')(finance.InvoiceIndex.as_view()), + name='invoice_list'), + url(r'^invoice/(?P\d+)/$', + permission_required_with_403('RIGS.view_invoice')(finance.InvoiceDetail.as_view()), + name='invoice_detail'), + url(r'^invoice/(?P\d+)/void/$', + permission_required_with_403('RIGS.change_invoice')(finance.InvoiceVoid.as_view()), + name='invoice_void'), + url(r'^payment/create/$', + permission_required_with_403('RIGS.add_payment')(finance.PaymentCreate.as_view()), + name='payment_create'), + url(r'^payment/(?P\d+)/delete/$', + permission_required_with_403('RIGS.add_payment')(finance.PaymentDelete.as_view()), + name='payment_delete'), # API url(r'^api/(?P\w+)/$', (views.SecureAPIRequest.as_view()), name="api_secure"), @@ -85,6 +103,7 @@ urlpatterns = patterns('', url(r'^bookings/$', RedirectView.as_view(pattern_name='rigboard')), url(r'^bookings/past/$', RedirectView.as_view(pattern_name='event_archive')), # Calendar may have gone away, redirect to the archive for now - url(r'^rigboard/calendar/$', RedirectView.as_view(pattern_name='event_archive', permanent=False)), + url(r'^rigboard/calendar/$', + RedirectView.as_view(pattern_name='event_archive', permanent=False)), )