Basic working invoice system. Need to add a way to create invoices.

This commit is contained in:
tomtom5152
2015-01-29 23:17:50 +00:00
parent 5f88e1e759
commit 2ce45b9297
10 changed files with 366 additions and 6 deletions

View File

@@ -10,3 +10,5 @@ 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)
admin.site.register(models.Invoice)
admin.site.register(models.Payment)

View File

60
RIGS/finance.py Normal file
View File

@@ -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')

View File

@@ -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'),)},
),
]

View File

@@ -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')

View File

@@ -0,0 +1,108 @@
{% extends 'base.html' %}
{% block title %}Invoice {{ object.pk }}{% endblock %}
{% block content %}
<div class="col-sm-12">
<h2>Invoice {{ object.pk }}</h2>
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">Invoice Details</div>
<div class="panel-body">
{% if object.event.organisation %}
{{ object.event.organisation.name }}<br/>
{{ object.event.organisation.address|linebreaksbr }}
{% else %}
{{ object.event.person.name }}<br/>
{{ object.event.person.address|linebreaksbr }}
{% endif %}
</div>
</div>
</div>
<div class="col-sm-6">
<div class="panel panel-{% if object.void %}danger{% else %}info{% endif %}">
<div class="panel-heading">Event Details</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>Event Number</dt>
<dd>N{{ object.event.pk|stringformat:"05d" }}</dd>
<dt>Event</dt>
<dd>{{ objet.event.pk }}</dd>
<dt>Event Venue</dt>
<dd>{{ object.event.venue }}</dd>
<dt>Event MIC</dt>
<dd>{{ object.event.mic.name }}</dd>
<dt>Status</dt>
<dd>{{ object.event.get_status_display }}</dd>
{% if object.event.dry_hire %}
<dd>&nbsp;</dd>
<dt>Checked In By</dt>
<dd>{{ object.checked_in_by.name }}</dd>
{% endif %}
<dt>PO</dt>
<dd>{{ object.event.purchase_order }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-body">
<div class="pull-right">
<a href="{% url 'payment_create' %}?invoice={{ object.pk }}"
class="btn btn-default modal-href"
data-target="#{{ form.person.id_for_label }}">
<span class="glyphicon glyphicon-plus"></span> Add
</a>
</div>
<table class="table table-hover">
<thead>
<tr>
<td>Date</td>
<td>Amount</td>
<td>Method</td>
<td></td>
</tr>
</thead>
<tbody>
{% for payment in object.payment_set.all %}
<tr>
<td>{{ payment.date }}</td>
<td>{{ payment.amount|floatformat:2 }}</td>
<td>{{ payment.get_method_display }}</td>
<td>
<a href="{% url 'payment_delete' payment.pk %}"
class="btn btn-small btn-danger"><span class="glyphicon glyphicon-remove"</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-body">
{% with object.event as object %}
{% include 'RIGS/item_table.html' %}
{% endwith %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,35 @@
{% extends 'base.html' %}
{% block title %}Active Invoices{% endblock %}
{% block content %}
<div class="col-sm-12">
<h2>Active Invoices</h2>
<table class="table table-responsive table-hover">
<thead>
<tr>
<th>#</th>
<th>Event</th>
<th>Invoice Date</th>
<th>Balance</th>
<th></th>
</tr>
</thead>
<tbody>
{% for object in object_list %}
<tr>
<td>{{ object.pk }}</td>
<td>{{ object.event }}</td>
<td>{{ object.invoice_date }}</td>
<td>{{ object.balance|floatformat:2 }}</td>
<td class="text-right">
<a href="{% url 'invoice_detail' object.pk %}" class="btn btn-default">
<span class="glyphicon glyphicon-pencil"></span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@@ -0,0 +1,37 @@
{% extends 'base.html' %}
{% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %}
{% block content %}
<div class="col-sm-offset-2 col-sm-8">
<div class="alert alert-danger" role="alert">
<h2>Delete payment on invoice {{ object.invoice.pk }}</h2>
<p>Are you sure you wish to delete a payment for £{{ object.amount|floatformat:2 }}
({{ object.get_method_display }})
from {{ object.date }} on invoice {{ object.invoice.pk }}.</p>
<p class="text-center"><strong>This action cannot be undone!</strong></p>
<div class="row">
<div class="col-sm-12">
<form action="{{ action_link }}" method="post">{% csrf_token %}
<input type="hidden" name="next" value="{% url 'invoice_detail' object.invoice.pk %}"/>
<a href="{% url 'invoice_detail' object.invoice.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.invoice.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.invoice.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.invoice.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.invoice.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.invoice.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.invoice.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.invoice.pk %}" class="btn btn-default col-sm-1">No</a>
<input type="submit" value="Yes" class="btn btn-danger col-sm-1"/>
<a href="{% url 'invoice_detail' object.invoice.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.invoice.pk %}" class="btn btn-default col-sm-1">No</a>
<a href="{% url 'invoice_detail' object.invoice.pk %}" class="btn btn-default col-sm-1">No</a>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,51 @@
{% extends 'base_ajax.html' %}
{% load widget_tweaks %}
{% block title %}Add Payment{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-offset-3 col-sm-6">
<form action="{{ form.action|default:request.path }}" method="post" class="form-horizontal">{% csrf_token %}
<input type="hidden" name="{{ form.invoice.name }}" value="{{ form.invoice.value }}"/>
<div class="row">
{% include 'form_errors.html' %}
<div class="form-group">
<label class="col-sm-2 control-label"
for="{{ form.date.id_for_label }}">{{ form.date.label }}</label>
<div class="col-sm-10">
{% render_field form.date type="date" class+="form-control" %}
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label"
for="{{ form.amount.id_for_label }}">{{ form.amount.label }}</label>
<div class="col-sm-10">
<div class="input-group">
<div class="input-group-addon">£</div>
{% render_field form.amount class+="form-control" %}
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label"
for="{{ form.method.id_for_label }}">{{ form.method.label }}</label>
<div class="col-sm-10">
{% render_field form.method class+="form-control" %}
</div>
</div>
<div class="text-right col-sm-3 col-sm-offset-9">
<div class="form-group">
<input type="submit" class="form-control btn btn-primary"/>
</div>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -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<pk>\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<pk>\d+)/$',
permission_required_with_403('RIGS.view_invoice')(finance.InvoiceDetail.as_view()),
name='invoice_detail'),
url(r'^invoice/(?P<pk>\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<pk>\d+)/delete/$',
permission_required_with_403('RIGS.add_payment')(finance.PaymentDelete.as_view()),
name='payment_delete'),
# API
url(r'^api/(?P<model>\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)),
)