mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-17 05:22:16 +00:00
Basic working invoice system. Need to add a way to create invoices.
This commit is contained in:
@@ -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)
|
||||
admin.site.register(models.EventItem, reversion.VersionAdmin)
|
||||
admin.site.register(models.Invoice)
|
||||
admin.site.register(models.Payment)
|
||||
60
RIGS/finance.py
Normal file
60
RIGS/finance.py
Normal 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')
|
||||
17
RIGS/migrations/0017_auto_20150129_2041.py
Normal file
17
RIGS/migrations/0017_auto_20150129_2041.py
Normal 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'),)},
|
||||
),
|
||||
]
|
||||
@@ -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')
|
||||
|
||||
108
RIGS/templates/RIGS/invoice_detail.html
Normal file
108
RIGS/templates/RIGS/invoice_detail.html
Normal 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> </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 %}
|
||||
35
RIGS/templates/RIGS/invoice_list.html
Normal file
35
RIGS/templates/RIGS/invoice_list.html
Normal 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 %}
|
||||
37
RIGS/templates/RIGS/payment_confirm_delete.html
Normal file
37
RIGS/templates/RIGS/payment_confirm_delete.html
Normal 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 %}
|
||||
51
RIGS/templates/RIGS/payment_form.html
Normal file
51
RIGS/templates/RIGS/payment_form.html
Normal 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 %}
|
||||
25
RIGS/urls.py
25
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<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)),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user