Compare commits

..

3 Commits

Author SHA1 Message Date
Tom Price
8b2aaa0c0e Add some suggestions for asset category choices.
This is a lot more complex than it needs to be due to somebody wanting to be clever in the past and making asset numbers dependant upon the category they are in.
2016-05-23 00:50:40 +01:00
Tom Price
b1952b96f7 Change supplier and abstract model to match the design agreed in the office. 2016-05-23 00:50:40 +01:00
Tom Price
5cdc75ce5c Very basic start to assets
no real work done yet, just a basic structure and a start to models
2016-05-23 00:50:40 +01:00
21 changed files with 238 additions and 364 deletions

View File

@@ -58,7 +58,7 @@ def api_key_required(function):
try:
user_object = models.Profile.objects.get(pk=userid)
except models.Profile.DoesNotExist:
except Profile.DoesNotExist:
return error_resp
if user_object.api_key != key:

View File

@@ -12,6 +12,8 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
@@ -25,10 +27,6 @@ TEMPLATE_DEBUG = True
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
if not DEBUG:
SECURE_SSL_REDIRECT = True # Redirect all http requests to https
INTERNAL_IPS = ['127.0.0.1']
ADMINS = (
@@ -46,7 +44,6 @@ INSTALLED_APPS = (
'django.contrib.messages',
'django.contrib.staticfiles',
'RIGS',
'subhire',
'debug_toolbar',
'registration',
@@ -58,7 +55,6 @@ INSTALLED_APPS = (
MIDDLEWARE_CLASSES = (
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
'django.middleware.security.SecurityMiddleware',
'reversion.middleware.RevisionMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',

View File

@@ -1,41 +1,32 @@
import cStringIO as StringIO
import datetime
import re
from django.contrib import messages
from django.core.urlresolvers import reverse_lazy
from django.db import connection
from django.http import Http404, HttpResponseRedirect
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.views import generic
from django.template import RequestContext
from django.template.loader import get_template
from django.views import generic
from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.contrib import messages
import datetime
from z3c.rml import rml2pdf
from RIGS import models
import re
class InvoiceIndex(generic.ListView):
model = models.Invoice
template_name = 'RIGS/invoice_list_active.html'
def get_context_data(self, **kwargs):
context = super(InvoiceIndex, self).get_context_data(**kwargs)
total = 0
for i in context['object_list']:
total += i.balance
context['total'] = total
context['count'] = len(list(context['object_list']))
return context
template_name = 'RIGS/invoice_list.html'
def get_queryset(self):
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must
sql = "SELECT * FROM " \
"(SELECT " \
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
"(SELECT COUNT(p.amount) FROM \"RIGS_payment\" as p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payment_count\", " \
"(SELECT SUM(ei.cost * ei.quantity) FROM \"RIGS_eventitem\" AS ei WHERE ei.event_id=\"RIGS_invoice\".event_id) AS \"cost\", " \
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" AS p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
"(SELECT SUM(p.amount) FROM \"RIGS_payment\" as p WHERE p.invoice_id=\"RIGS_invoice\".id) AS \"payments\", " \
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
"AS sub " \
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
@@ -49,7 +40,6 @@ class InvoiceIndex(generic.ListView):
class InvoiceDetail(generic.DetailView):
model = models.Invoice
class InvoicePrint(generic.View):
def get(self, request, pk):
invoice = get_object_or_404(models.Invoice, pk=pk)
@@ -64,8 +54,8 @@ class InvoicePrint(generic.View):
'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF',
}
},
'invoice': invoice,
'current_user': request.user,
'invoice':invoice,
'current_user':request.user,
})
rml = template.render(context)
@@ -82,7 +72,6 @@ class InvoicePrint(generic.View):
response.write(pdfData)
return response
class InvoiceVoid(generic.View):
def get(self, *args, **kwargs):
pk = kwargs.get('pk')
@@ -97,7 +86,6 @@ class InvoiceVoid(generic.View):
class InvoiceArchive(generic.ListView):
model = models.Invoice
template_name = 'RIGS/invoice_list_archive.html'
paginate_by = 25
@@ -106,33 +94,14 @@ class InvoiceWaiting(generic.ListView):
paginate_by = 25
template_name = 'RIGS/event_invoice.html'
def get_context_data(self, **kwargs):
context = super(InvoiceWaiting, self).get_context_data(**kwargs)
total = 0
for obj in self.get_objects():
total += obj.sum_total
context['total'] = total
context['count'] = len(self.get_objects())
return context
def get_queryset(self):
return self.get_objects()
def get_objects(self):
# @todo find a way to select items
events = self.model.objects.filter(
(
Q(start_date__lte=datetime.date.today(), end_date__isnull=True) | # Starts before with no end
Q(end_date__lte=datetime.date.today()) # Has end date, finishes before
) & Q(invoice__isnull=True) # Has not already been invoiced
& Q(is_rig=True) # Is a rig (not non-rig)
).order_by('start_date') \
events = self.model.objects.filter(is_rig=True, end_date__lt=datetime.date.today(),
invoice__isnull=True) \
.order_by('start_date') \
.select_related('person',
'organisation',
'venue', 'mic') \
.prefetch_related('items')
'venue', 'mic')
return events
@@ -150,7 +119,7 @@ class InvoiceEvent(generic.View):
class PaymentCreate(generic.CreateView):
model = models.Payment
fields = ['invoice', 'date', 'amount', 'method']
fields = ['invoice','date','amount','method']
def get_initial(self):
initial = super(generic.CreateView, self).get_initial()
@@ -170,4 +139,4 @@ class PaymentDelete(generic.DeleteView):
model = models.Payment
def get_success_url(self):
return self.request.POST.get('next')
return self.request.POST.get('next')

View File

@@ -1,32 +1,31 @@
import datetime
import hashlib
import pytz
import random
import string
from collections import Counter
from decimal import Decimal
import datetime, pytz
import reversion
from django.conf import settings
from django.db import models, connection
from django.contrib.auth.models import AbstractUser
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse_lazy
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.conf import settings
from django.utils.functional import cached_property
from django.utils.encoding import python_2_unicode_compatible
import reversion
import string
import random
from collections import Counter
from django.core.urlresolvers import reverse_lazy
from django.core.exceptions import ValidationError
from decimal import Decimal
# Create your models here.
@python_2_unicode_compatible
class Profile(AbstractUser):
initials = models.CharField(max_length=5, unique=True, null=True, blank=False)
phone = models.CharField(max_length=13, null=True, blank=True)
api_key = models.CharField(max_length=40, blank=True, editable=False, null=True)
api_key = models.CharField(max_length=40,blank=True,editable=False, null=True)
@classmethod
def make_api_key(cls):
size = 20
chars = string.ascii_letters + string.digits
size=20
chars=string.ascii_letters + string.digits
new_api_key = ''.join(random.choice(chars) for x in range(size))
return new_api_key;
@@ -56,7 +55,6 @@ class Profile(AbstractUser):
('view_profile', 'Can view Profile'),
)
class RevisionMixin(object):
@property
def last_edited_at(self):
@@ -81,11 +79,10 @@ class RevisionMixin(object):
versions = reversion.get_for_object(self)
if versions:
version = reversion.get_for_object(self)[0]
return "V{0} | R{1}".format(version.pk, version.revision.pk)
return "V{0} | R{1}".format(version.pk,version.revision.pk)
else:
return None
@reversion.register
@python_2_unicode_compatible
class Person(models.Model, RevisionMixin):
@@ -100,7 +97,7 @@ class Person(models.Model, RevisionMixin):
def __str__(self):
string = self.name
if self.notes is not None:
if len(self.notes) > 0:
if len(self.notes) > 0:
string += "*"
return string
@@ -111,7 +108,7 @@ class Person(models.Model, RevisionMixin):
if e.organisation:
o.append(e.organisation)
# Count up occurances and put them in descending order
#Count up occurances and put them in descending order
c = Counter(o)
stats = c.most_common()
return stats
@@ -144,7 +141,7 @@ class Organisation(models.Model, RevisionMixin):
def __str__(self):
string = self.name
if self.notes is not None:
if len(self.notes) > 0:
if len(self.notes) > 0:
string += "*"
return string
@@ -154,8 +151,8 @@ class Organisation(models.Model, RevisionMixin):
for e in Event.objects.filter(organisation=self).select_related('person'):
if e.person:
p.append(e.person)
# Count up occurances and put them in descending order
#Count up occurances and put them in descending order
c = Counter(p)
stats = c.most_common()
return stats
@@ -241,18 +238,12 @@ class Venue(models.Model, RevisionMixin):
class EventManager(models.Manager):
def current_events(self):
events = self.filter(
(models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q(
status=Event.CANCELLED)) | # Starts after with no end
(models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(
status=Event.CANCELLED)) | # Ends after
(models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~models.Q(
status=Event.CANCELLED)) | # Active dry hire
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (
models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
(models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Starts after with no end
(models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Ends after
(models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~models.Q(status=Event.CANCELLED)) | # Active dry hire
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
models.Q(status=Event.CANCELLED, start_date__gte=datetime.date.today()) # Canceled but not started
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
'organisation',
'venue', 'mic')
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic')
return events
def events_in_bounds(self, start, end):
@@ -260,17 +251,15 @@ class EventManager(models.Manager):
(models.Q(start_date__gte=start.date(), start_date__lte=end.date())) | # Start date in bounds
(models.Q(end_date__gte=start.date(), end_date__lte=end.date())) | # End date in bounds
(models.Q(access_at__gte=start, access_at__lte=end)) | # Access at in bounds
(models.Q(meet_at__gte=start, meet_at__lte=end)) | # Meet at in bounds
(models.Q(meet_at__gte=start, meet_at__lte=end)) | # Meet at in bounds
(models.Q(start_date__lte=start, end_date__gte=end)) | # Start before, end after
(models.Q(access_at__lte=start, start_date__gte=end)) | # Access before, start after
(models.Q(access_at__lte=start, end_date__gte=end)) | # Access before, end after
(models.Q(meet_at__lte=start, start_date__gte=end)) | # Meet before, start after
(models.Q(meet_at__lte=start, end_date__gte=end)) # Meet before, end after
(models.Q(start_date__lte=start, end_date__gte=end)) | # Start before, end after
(models.Q(access_at__lte=start, start_date__gte=end)) | # Access before, start after
(models.Q(access_at__lte=start, end_date__gte=end)) | # Access before, end after
(models.Q(meet_at__lte=start, start_date__gte=end)) | # Meet before, start after
(models.Q(meet_at__lte=start, end_date__gte=end)) # Meet before, end after
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person',
'organisation',
'venue', 'mic')
).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic')
return events
def rig_count(self):
@@ -312,8 +301,7 @@ class Event(models.Model, RevisionMixin):
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
dry_hire = models.BooleanField(default=False)
is_rig = models.BooleanField(default=True)
based_on = models.ForeignKey('Event', on_delete=models.SET_NULL, related_name='future_events', blank=True,
null=True)
based_on = models.ForeignKey('Event', on_delete=models.SET_NULL, related_name='future_events', blank=True, null=True)
# Timing
start_date = models.DateField()
@@ -339,7 +327,6 @@ class Event(models.Model, RevisionMixin):
"""
EX Vat
"""
@property
def sum_total(self):
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
@@ -347,15 +334,14 @@ class Event(models.Model, RevisionMixin):
# sql = "SELECT SUM(quantity * cost) AS sum_total FROM \"RIGS_eventitem\" WHERE event_id=%i" % self.id
# else:
# sql = "SELECT id, SUM(quantity * cost) AS sum_total FROM RIGS_eventitem WHERE event_id=%i" % self.id
# total = self.items.raw(sql)[0]
# if total.sum_total:
#total = self.items.raw(sql)[0]
#if total.sum_total:
# return total.sum_total
# total = 0.0
# for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
#total = 0.0
#for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
# total += item.sum
total = EventItem.objects.filter(event=self).aggregate(
sum_total=models.Sum(models.F('cost') * models.F('quantity'),
output_field=models.DecimalField(max_digits=10, decimal_places=2))
sum_total=models.Sum(models.F('cost')*models.F('quantity'), output_field=models.DecimalField(max_digits=10, decimal_places=2))
)['sum_total']
if total:
return total
@@ -372,7 +358,6 @@ class Event(models.Model, RevisionMixin):
"""
Inc VAT
"""
@property
def total(self):
return self.sum_total + self.vat
@@ -397,7 +382,7 @@ class Event(models.Model, RevisionMixin):
def earliest_time(self):
"""Finds the earliest time defined in the event - this function could return either a tzaware datetime, or a naiive date object"""
# Put all the datetimes in a list
#Put all the datetimes in a list
datetime_list = []
if self.access_at:
@@ -409,22 +394,22 @@ class Event(models.Model, RevisionMixin):
# If there is no start time defined, pretend it's midnight
startTimeFaked = False
if self.has_start_time:
startDateTime = datetime.datetime.combine(self.start_date, self.start_time)
startDateTime = datetime.datetime.combine(self.start_date,self.start_time)
else:
startDateTime = datetime.datetime.combine(self.start_date, datetime.time(00, 00))
startDateTime = datetime.datetime.combine(self.start_date,datetime.time(00,00))
startTimeFaked = True
# timezoneIssues - apply the default timezone to the naiive datetime
#timezoneIssues - apply the default timezone to the naiive datetime
tz = pytz.timezone(settings.TIME_ZONE)
startDateTime = tz.localize(startDateTime)
datetime_list.append(startDateTime) # then add it to the list
datetime_list.append(startDateTime) # then add it to the list
earliest = min(datetime_list).astimezone(tz) # find the earliest datetime in the list
earliest = min(datetime_list).astimezone(tz) #find the earliest datetime in the list
# if we faked it & it's the earliest, better own up
if startTimeFaked and earliest == startDateTime:
if startTimeFaked and earliest==startDateTime:
return self.start_date
return earliest
@property
@@ -436,7 +421,7 @@ class Event(models.Model, RevisionMixin):
endDate = self.start_date
if self.has_end_time:
endDateTime = datetime.datetime.combine(endDate, self.end_time)
endDateTime = datetime.datetime.combine(endDate,self.end_time)
tz = pytz.timezone(settings.TIME_ZONE)
endDateTime = tz.localize(endDateTime)
@@ -445,6 +430,7 @@ class Event(models.Model, RevisionMixin):
else:
return endDate
objects = EventManager()
def get_absolute_url(self):
@@ -460,7 +446,7 @@ class Event(models.Model, RevisionMixin):
startEndSameDay = not self.end_date or self.end_date == self.start_date
hasStartAndEnd = self.has_start_time and self.has_end_time
if startEndSameDay and hasStartAndEnd and self.start_time > self.end_time:
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
raise ValidationError('Unless you\'ve invented time travel, the event can\'t finish before it has started.')
def save(self, *args, **kwargs):
"""Call :meth:`full_clean` before saving."""
@@ -517,6 +503,15 @@ class Invoice(models.Model):
@property
def payment_total(self):
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
#if connection.vendor == 'postgresql':
# sql = "SELECT SUM(amount) AS total FROM \"RIGS_payment\" WHERE invoice_id=%i" % self.id
#else:
# sql = "SELECT id, SUM(amount) AS total FROM RIGS_payment WHERE invoice_id=%i" % self.id
#total = self.payment_set.raw(sql)[0]
#if total.total:
# return total.total
#return 0.0
total = self.payment_set.aggregate(total=models.Sum('amount'))['total']
if total:
return total
@@ -557,4 +552,4 @@ class Payment(models.Model):
method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True)
def __str__(self):
return "%s: %d" % (self.get_method_display(), self.amount)
return "%s: %d" % (self.get_method_display(), self.amount)

View File

@@ -1,91 +1,68 @@
{% extends 'base.html' %}
{% load paginator from filters %}
{% load static %}
{% block title %}Events for Invoice{% endblock %}
{% block js %}
<script src="{% static "js/tooltip.js" %}"></script>
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip();
});
</script>
{% endblock %}
{% block content %}
<div class="col-sm-12">
<h2>Events for Invoice ({{count}} Events, £ {{ total|floatformat:2 }})</h2>
<p>These events have happened, but paperwork has not yet been sent to treasury</p>
<h2>Events for Invoice</h2>
{% if is_paginated %}
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
{% paginator %}
</div>
{% endif %}
<div class="table-responsive col-sm-12">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Start Date</th>
<th>Event Name</th>
<th>Client</th>
<th>Cost</th>
<th>MIC</th>
<th></th>
<table class="table table-responsive table-hover">
<thead>
<tr>
<th class="hiddenx-xs">#</th>
<th>Date</th>
<th>Event</th>
<th>Client</th>
<th>Cost</th>
<th class="hidden-xs">MIC</th>
<th></th>
</tr>
</thead>
<tbody>
{% for object in object_list %}
<tr class="
{% if object.cancelled %}
active text-muted
{% elif not object.is_rig %}
info
{% elif object.confirmed and object.mic %}
{# interpreated as (booked and mic) #}
success
{% elif object.mic %}
warning
{% else %}
danger
{% endif %}
">
<td class="hidden-xs"><a href="{% url 'event_detail' object.pk %}" target="_blank">N{{ object.pk|stringformat:"05d" }}</a></td>
<td>{{ object.end_date }}</td>
<td>{{ object.name }}</td>
<td>
{% if object.organisation %}
{{ object.organisation.name }}
{% else %}
{{ object.person.name }}
{% endif %}
</td>
<td>{{ object.sum_total|floatformat:2 }}</td>
<td class="text-center">
{{ object.mic.initials }}<br/>
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo"/>
</td>
<td class="text-right">
<a href="{% url 'invoice_event' object.pk %}" target="_blank" class="btn btn-default">
<span class="glyphicon glyphicon-gbp"></span>
</a>
</td>
</tr>
</thead>
<tbody>
{% for object in object_list %}
<tr class="
{% if object.cancelled %}
active text-muted
{% elif not object.is_rig %}
info
{% elif object.confirmed and object.mic %}
{# interpreated as (booked and mic) #}
success
{% elif object.mic %}
warning
{% else %}
danger
{% endif %}
">
<td><a href="{% url 'event_detail' object.pk %}">N{{ object.pk|stringformat:"05d" }}</a><br>
<span class="text-muted">{{ object.get_status_display }}</span></td>
<td>{{ object.start_date }}</td>
<td>{{ object.name }}</td>
<td>
{% if object.organisation %}
{{ object.organisation.name }}
<br>
<span class="text-muted">{{ object.organisation.union_account|yesno:'Internal,External' }}</span>
{% else %}
{{ object.person.name }}
<br>
<span class="text-muted">External</span>
{% endif %}
</td>
<td>{{ object.sum_total|floatformat:2 }}</td>
<td class="text-center">
{% if object.mic %}
{{ object.mic.initials }}<br>
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo"/>
{% else %}
<span class="glyphicon glyphicon-exclamation-sign"></span>
{% endif %}
</td>
<td class="text-right">
<a href="{% url 'invoice_event' object.pk %}" class="btn btn-default" data-toggle="tooltip" title="'Invoice' this event - click this when paperwork has been sent to treasury">
<span class="glyphicon glyphicon-gbp"></span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
{% paginator %}

View File

@@ -1,7 +1,7 @@
<div class="table-responsive">
<table class="table">
<thead>
<td>#</td>
<td class="hidden-xs">#</td>
<td>Event Date</td>
<td>Event Details</td>
<td>Event Timings</td>
@@ -23,7 +23,7 @@
danger
{% endif %}
">
<td>{{ event.pk }}</td>
<td class="hidden-xs">{{ event.pk }}</td>
<td>
<div><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></div>
{% if event.end_date and event.end_date != event.start_date %}

View File

@@ -109,11 +109,6 @@
</td>
</tr>
{% endfor %}
<tr>
<td class="text-right"><strong>Balance:</strong></td>
<td>{{ object.balance|floatformat:2 }}</td>
<td></td>
<td></td>
</tbody>
</table>
</div>

View File

@@ -5,56 +5,38 @@
{% block content %}
<div class="col-sm-12">
<h2>{% block heading %}Invoices{% endblock %}</h2>
{% block description %}{% endblock %}
<h2>Invoices</h2>
{% if is_paginated %}
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
{% paginator %}
</div>
{% endif %}
<div class="table-responsive col-sm-12">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Event</th>
<th>Client</th>
<th>Event Date</th>
<th>Invoice Date</th>
<th>Balance</th>
<th></th>
<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 class="{% if object.void %}danger{% elif object.balance == 0 %}success{% endif %}">
<td>{{ object.pk }}</td>
<td><a href="{% url 'event_detail' object.event.pk %}" target="_blank">N{{ object.event.pk|stringformat:"05d" }}</a>: {{ object.event.name }}</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>
</thead>
<tbody>
{% for object in object_list %}
<tr class="{% if object.void %}danger{% elif object.balance == 0 %}success{% endif %}">
<td>{{ object.pk }}</td>
<td><a href="{% url 'event_detail' object.event.pk %}">N{{ object.event.pk|stringformat:"05d" }}</a>: {{ object.event.name }} <br>
<span class="text-muted">{{ object.event.get_status_display }}</span></td>
</td>
<td>{% if object.event.organisation %}
{{ object.event.organisation.name }}
<br>
<span class="text-muted">{{ object.event.organisation.union_account|yesno:'Internal,External' }}</span>
{% else %}
{{ object.event.person.name }}
<br>
<span class="text-muted">External</span>
{% endif %}
</td>
<td>{{ object.event.start_date }}</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>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
{% paginator %}

View File

@@ -1,13 +0,0 @@
{% extends 'RIGS/invoice_list.html' %}
{% block title %}
Outstanding Invoices
{% endblock %}
{% block heading %}
Outstanding Invoices ({{ count }} Events, £ {{ total|floatformat:2 }})
{% endblock %}
{% block description %}
<p>Paperwork for these events has been sent to treasury, but the full balance has not yet appeared on a ledger</p>
{% endblock %}

View File

@@ -1,13 +0,0 @@
{% extends 'RIGS/invoice_list.html' %}
{% block title %}
Invoice Archive
{% endblock %}
{% block heading %}
All Invoices
{% endblock %}
{% block description %}
<p>This page displays all invoices: outstanding, paid, and void</p>
{% endblock %}

View File

@@ -126,7 +126,7 @@
<dd>
{% if user.api_key %}
<pre id="cal-url" data-url="http{{ request.is_secure|yesno:"s,"}}://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}"></pre>
<small><a id="gcal-link" data-url="https://support.google.com/calendar/answer/37100" href="">Click here</a> for instructions on adding to google calendar.<br/>
<small><a id="gcal-link" data-url="http://www.google.com/calendar/render?cid=http{{ request.is_secure|yesno:"s,"}}://{{ request.get_host }}{% url 'ics_calendar' api_pk=user.pk api_key=user.api_key %}" href="">Click here</a> to add to google calendar.<br/>
To sync from google calendar to mobile device, visit <a href="https://www.google.com/calendar/syncselect" target="_blank">this page</a> on your device and tick "RIGS Calendar".</small>
{% else %}
<pre>No API Key Generated</pre>

3
assets/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

71
assets/models.py Normal file
View File

@@ -0,0 +1,71 @@
from django.db import models
import reversion
# Create your models here.
class Suppliers(models.Model):
name = models.CharField(max_length=100)
class AbstractAsset(models.Model):
STATUS_LOST = 1
STATUS_ACTIVE = 2
STATUS_INACTIVE = 3
STATUS_BROKEN = 4
STATUS_SCRAPPED = 5
STATUS_NOT_BUILT = 6
STATUS_SOLD = 7
STATUS_CHOICES = (
(STATUS_LOST, 'Lost'),
(STATUS_ACTIVE, 'Active'),
(STATUS_INACTIVE, 'Inactive'),
(STATUS_BROKEN, 'Broken'),
(STATUS_SCRAPPED, 'Scrapped'),
(STATUS_NOT_BUILT, 'Not Yet Built'),
(STATUS_SOLD, 'Sold'),
)
status = models.IntegerField(choices=STATUS_CHOICES)
notes = models.TextField(blank=True, null=True)
# test_period # decide what to do with this later
class Meta:
abstract = True
class Asset(models.Model):
CATEGORY_GENERAL = 1
CATEGORY_CASE = 2
CATEGORY_COMMS = 3
CATEGORY_DECKING = 4
CATEGORY_OFFICE = 5
CATEGORY_SOUND = 10
CATEGORY_LIGHTING = 20
CATEGORY_VIDEO = 30
CATEGORY_RIGGING = 40
CATEGORY_TRUSS = 41
CATEGORY_LADDERS = 42
CATEGORY_POWER = 50
CATEGORY_DISTRO = 51
CATEGORY_CHOICES = (
(CATEGORY_SOUND, 'Sound'),
(CATEGORY_LIGHTING, 'Lighting'),
('Other', (
(CATEGORY_GENERAL, 'General'),
(CATEGORY_CASE, 'Case'),
(CATEGORY_COMMS, 'Comms'),
(CATEGORY_DECKING, 'Decking'),
(CATEGORY_OFFICE, 'Office'),
)),
)
name = models.CharField(max_length=255)
serial_number = models.CharField(max_length=255)
date_acquired = models.DateField(null=True, blank=True)
date_sold = models.DateField(null=True, blank=True)
purchase_price = models.DecimalField(max_digits=10, decimal_places=2)
replacement_price = models.DecimalField(max_digits=10, decimal_places=2)

View File

@@ -1,6 +0,0 @@
from django.contrib import admin
import models
# Register your models here.
admin.site.register(models.Hire)
admin.site.register(models.Provider)

View File

@@ -1,43 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Hire',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=255)),
('description', models.TextField(null=True, blank=True)),
('start_date', models.DateField()),
('end_date', models.DateField()),
('start_transport', models.IntegerField(blank=True, null=True, choices=[(0, b'TEC Transport'), (1, b'Provider Transports')])),
('mic', models.ForeignKey(related_name='hire_mic', verbose_name=b'MIC', blank=True, to=settings.AUTH_USER_MODEL, null=True)),
],
),
migrations.CreateModel(
name='Provider',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=50)),
('phone', models.CharField(max_length=15, null=True, blank=True)),
('email', models.EmailField(max_length=254, null=True, blank=True)),
('address', models.TextField(null=True, blank=True)),
('notes', models.TextField(null=True, blank=True)),
],
),
migrations.AddField(
model_name='hire',
name='provider',
field=models.ForeignKey(blank=True, to='subhire.Provider', null=True),
),
]

View File

@@ -1,39 +0,0 @@
import reversion
from django.db import models
from django.conf import settings
from django.utils.encoding import python_2_unicode_compatible
# Create your models here.
class Hire(models.Model):
WE_TRANSPORT = 0
THEY_TRANSPORT = 1
TRANSPORT_CHOICES = (
(WE_TRANSPORT, 'TEC Transport'),
(THEY_TRANSPORT, 'Provider Transports'),
)
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
provider = models.ForeignKey('Provider', blank=True, null=True)
start_date = models.DateField()
end_date = models.DateField()
start_transport = models.IntegerField(
choices=TRANSPORT_CHOICES, blank=True, null=True)
mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='hire_mic', blank=True, null=True,
verbose_name="MIC")
class Provider(models.Model):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=15, blank=True, null=True)
email = models.EmailField(blank=True, null=True)
address = models.TextField(blank=True, null=True)
notes = models.TextField(blank=True, null=True)
@property
def latest_hires(self):
return self.hire_set.order_by('-start_date').select_related()

View File

@@ -14,7 +14,7 @@
<link rel="icon" type="image/png" href="{% static "imgs/pyrigs-avatar.png" %}">
<link rel="apple-touch-icon" href="{% static "imgs/pyrigs-avatar.png" %}">
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400italic,700,300,400' rel='stylesheet'
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400italic,700,300,400' rel='stylesheet'
type='text/css'>
@@ -74,12 +74,12 @@
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Invoices<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp"></span> Active</a>
</li>
{% if perms.RIGS.add_invoice %}
<li><a href="{% url 'invoice_waiting' %}"><span
class="glyphicon glyphicon-briefcase"></span> Waiting</a></li>
{% endif %}
<li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp"></span> Outstanding</a>
</li>
<li><a href="{% url 'invoice_archive' %}"><span class="glyphicon glyphicon-book"></span>
Archive</a></li>
</ul>