mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-18 05:52:15 +00:00
Compare commits
30 Commits
telegram
...
feature/su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee948381b1 | ||
|
|
e1578eb0d4 | ||
|
|
a7247c273e | ||
|
|
f265da2f1d | ||
|
|
1163b117e4 | ||
|
|
f92f418bc5 | ||
|
|
9108cb3c4e | ||
|
|
08d17adc8a | ||
|
|
68b35c2d24 | ||
|
|
5cc69cbb41 | ||
|
|
a48afb9157 | ||
|
|
3ccbdff737 | ||
|
|
0990f0bfbb | ||
|
|
f43635ee89 | ||
|
|
705f1bda2b | ||
|
|
0ff0d06eaf | ||
|
|
a769486c9c | ||
|
|
83302c4439 | ||
|
|
ba020b43f1 | ||
|
|
eaf5c9687e | ||
|
|
a725ef5caf | ||
|
|
aa79f3628e | ||
|
|
000351d884 | ||
|
|
db58c113aa | ||
|
|
7cb8503164 | ||
|
|
cc2450ff87 | ||
|
|
6b77393414 | ||
|
|
7ccc8faf20 | ||
|
|
6030288956 | ||
|
|
e4a955f323 |
@@ -58,7 +58,7 @@ def api_key_required(function):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
user_object = models.Profile.objects.get(pk=userid)
|
user_object = models.Profile.objects.get(pk=userid)
|
||||||
except Profile.DoesNotExist:
|
except models.Profile.DoesNotExist:
|
||||||
return error_resp
|
return error_resp
|
||||||
|
|
||||||
if user_object.api_key != key:
|
if user_object.api_key != key:
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
|
|||||||
import os
|
import os
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
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
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||||
|
|
||||||
@@ -27,6 +25,10 @@ TEMPLATE_DEBUG = True
|
|||||||
|
|
||||||
ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com']
|
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']
|
INTERNAL_IPS = ['127.0.0.1']
|
||||||
|
|
||||||
ADMINS = (
|
ADMINS = (
|
||||||
@@ -44,6 +46,7 @@ INSTALLED_APPS = (
|
|||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'RIGS',
|
'RIGS',
|
||||||
|
'subhire',
|
||||||
|
|
||||||
'debug_toolbar',
|
'debug_toolbar',
|
||||||
'registration',
|
'registration',
|
||||||
@@ -55,6 +58,7 @@ INSTALLED_APPS = (
|
|||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
MIDDLEWARE_CLASSES = (
|
||||||
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
|
'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware',
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'reversion.middleware.RevisionMiddleware',
|
'reversion.middleware.RevisionMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
|||||||
@@ -1,32 +1,41 @@
|
|||||||
import cStringIO as StringIO
|
import cStringIO as StringIO
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
from django.db import connection
|
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.views import generic
|
|
||||||
from django.template import RequestContext
|
|
||||||
from django.template.loader import get_template
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.contrib import messages
|
from django.template import RequestContext
|
||||||
import datetime
|
from django.template.loader import get_template
|
||||||
|
from django.views import generic
|
||||||
|
from django.db.models import Q
|
||||||
from z3c.rml import rml2pdf
|
from z3c.rml import rml2pdf
|
||||||
|
|
||||||
from RIGS import models
|
from RIGS import models
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
class InvoiceIndex(generic.ListView):
|
class InvoiceIndex(generic.ListView):
|
||||||
model = models.Invoice
|
model = models.Invoice
|
||||||
template_name = 'RIGS/invoice_list.html'
|
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
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must
|
# Manual query is the only way I have found to do this efficiently. Not ideal but needs must
|
||||||
sql = "SELECT * FROM " \
|
sql = "SELECT * FROM " \
|
||||||
"(SELECT " \
|
"(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(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\") " \
|
"\"RIGS_invoice\".\"id\", \"RIGS_invoice\".\"event_id\", \"RIGS_invoice\".\"invoice_date\", \"RIGS_invoice\".\"void\" FROM \"RIGS_invoice\") " \
|
||||||
"AS sub " \
|
"AS sub " \
|
||||||
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
|
"WHERE (((cost > 0.0) AND (payment_count=0)) OR (cost - payments) <> 0.0) AND void = '0'" \
|
||||||
@@ -40,6 +49,7 @@ class InvoiceIndex(generic.ListView):
|
|||||||
class InvoiceDetail(generic.DetailView):
|
class InvoiceDetail(generic.DetailView):
|
||||||
model = models.Invoice
|
model = models.Invoice
|
||||||
|
|
||||||
|
|
||||||
class InvoicePrint(generic.View):
|
class InvoicePrint(generic.View):
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
invoice = get_object_or_404(models.Invoice, pk=pk)
|
invoice = get_object_or_404(models.Invoice, pk=pk)
|
||||||
@@ -72,6 +82,7 @@ class InvoicePrint(generic.View):
|
|||||||
response.write(pdfData)
|
response.write(pdfData)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class InvoiceVoid(generic.View):
|
class InvoiceVoid(generic.View):
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
pk = kwargs.get('pk')
|
pk = kwargs.get('pk')
|
||||||
@@ -86,6 +97,7 @@ class InvoiceVoid(generic.View):
|
|||||||
|
|
||||||
class InvoiceArchive(generic.ListView):
|
class InvoiceArchive(generic.ListView):
|
||||||
model = models.Invoice
|
model = models.Invoice
|
||||||
|
template_name = 'RIGS/invoice_list_archive.html'
|
||||||
paginate_by = 25
|
paginate_by = 25
|
||||||
|
|
||||||
|
|
||||||
@@ -94,14 +106,33 @@ class InvoiceWaiting(generic.ListView):
|
|||||||
paginate_by = 25
|
paginate_by = 25
|
||||||
template_name = 'RIGS/event_invoice.html'
|
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):
|
def get_queryset(self):
|
||||||
|
return self.get_objects()
|
||||||
|
|
||||||
|
def get_objects(self):
|
||||||
# @todo find a way to select items
|
# @todo find a way to select items
|
||||||
events = self.model.objects.filter(is_rig=True, end_date__lt=datetime.date.today(),
|
events = self.model.objects.filter(
|
||||||
invoice__isnull=True) \
|
(
|
||||||
.order_by('start_date') \
|
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') \
|
||||||
.select_related('person',
|
.select_related('person',
|
||||||
'organisation',
|
'organisation',
|
||||||
'venue', 'mic')
|
'venue', 'mic') \
|
||||||
|
.prefetch_related('items')
|
||||||
|
|
||||||
return events
|
return events
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import datetime, pytz
|
import pytz
|
||||||
|
|
||||||
from django.db import models, connection
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
|
||||||
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
|
import random
|
||||||
|
import string
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from django.core.urlresolvers import reverse_lazy
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
import reversion
|
||||||
|
from django.conf import settings
|
||||||
|
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.utils.functional import cached_property
|
||||||
|
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Profile(AbstractUser):
|
class Profile(AbstractUser):
|
||||||
@@ -55,6 +56,7 @@ class Profile(AbstractUser):
|
|||||||
('view_profile', 'Can view Profile'),
|
('view_profile', 'Can view Profile'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RevisionMixin(object):
|
class RevisionMixin(object):
|
||||||
@property
|
@property
|
||||||
def last_edited_at(self):
|
def last_edited_at(self):
|
||||||
@@ -83,6 +85,7 @@ class RevisionMixin(object):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@reversion.register
|
@reversion.register
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Person(models.Model, RevisionMixin):
|
class Person(models.Model, RevisionMixin):
|
||||||
@@ -238,12 +241,18 @@ class Venue(models.Model, RevisionMixin):
|
|||||||
class EventManager(models.Manager):
|
class EventManager(models.Manager):
|
||||||
def current_events(self):
|
def current_events(self):
|
||||||
events = self.filter(
|
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(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q(
|
||||||
(models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Ends after
|
status=Event.CANCELLED)) | # Starts after with no end
|
||||||
(models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~models.Q(status=Event.CANCELLED)) | # Active dry hire
|
(models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(
|
||||||
(models.Q(dry_hire=True, checked_in_by__isnull=True) & (models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT
|
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
|
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
|
return events
|
||||||
|
|
||||||
def events_in_bounds(self, start, end):
|
def events_in_bounds(self, start, end):
|
||||||
@@ -259,7 +268,9 @@ class EventManager(models.Manager):
|
|||||||
(models.Q(meet_at__lte=start, start_date__gte=end)) | # Meet before, start 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(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
|
return events
|
||||||
|
|
||||||
def rig_count(self):
|
def rig_count(self):
|
||||||
@@ -301,7 +312,8 @@ class Event(models.Model, RevisionMixin):
|
|||||||
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
|
status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL)
|
||||||
dry_hire = models.BooleanField(default=False)
|
dry_hire = models.BooleanField(default=False)
|
||||||
is_rig = models.BooleanField(default=True)
|
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
|
# Timing
|
||||||
start_date = models.DateField()
|
start_date = models.DateField()
|
||||||
@@ -327,6 +339,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
"""
|
"""
|
||||||
EX Vat
|
EX Vat
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sum_total(self):
|
def sum_total(self):
|
||||||
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
|
# Manual querying is required for efficiency whilst maintaining floating point arithmetic
|
||||||
@@ -341,7 +354,8 @@ class Event(models.Model, RevisionMixin):
|
|||||||
# for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
|
# for item in self.items.filter(cost__gt=0).extra(select="SUM(cost * quantity) AS sum"):
|
||||||
# total += item.sum
|
# total += item.sum
|
||||||
total = EventItem.objects.filter(event=self).aggregate(
|
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']
|
)['sum_total']
|
||||||
if total:
|
if total:
|
||||||
return total
|
return total
|
||||||
@@ -358,6 +372,7 @@ class Event(models.Model, RevisionMixin):
|
|||||||
"""
|
"""
|
||||||
Inc VAT
|
Inc VAT
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total(self):
|
def total(self):
|
||||||
return self.sum_total + self.vat
|
return self.sum_total + self.vat
|
||||||
@@ -430,7 +445,6 @@ class Event(models.Model, RevisionMixin):
|
|||||||
else:
|
else:
|
||||||
return endDate
|
return endDate
|
||||||
|
|
||||||
|
|
||||||
objects = EventManager()
|
objects = EventManager()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
@@ -503,15 +517,6 @@ class Invoice(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def payment_total(self):
|
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']
|
total = self.payment_set.aggregate(total=models.Sum('amount'))['total']
|
||||||
if total:
|
if total:
|
||||||
return total
|
return total
|
||||||
|
|||||||
@@ -1,25 +1,37 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load paginator from filters %}
|
{% load paginator from filters %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}Events for Invoice{% endblock %}
|
{% 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 %}
|
{% block content %}
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<h2>Events for Invoice</h2>
|
<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>
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||||
{% paginator %}
|
{% paginator %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<table class="table table-responsive table-hover">
|
<div class="table-responsive col-sm-12">
|
||||||
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="hiddenx-xs">#</th>
|
<th>#</th>
|
||||||
<th>Date</th>
|
<th>Start Date</th>
|
||||||
<th>Event</th>
|
<th>Event Name</th>
|
||||||
<th>Client</th>
|
<th>Client</th>
|
||||||
<th>Cost</th>
|
<th>Cost</th>
|
||||||
<th class="hidden-xs">MIC</th>
|
<th>MIC</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -39,23 +51,33 @@
|
|||||||
danger
|
danger
|
||||||
{% endif %}
|
{% endif %}
|
||||||
">
|
">
|
||||||
<td class="hidden-xs"><a href="{% url 'event_detail' object.pk %}" target="_blank">N{{ object.pk|stringformat:"05d" }}</a></td>
|
<td><a href="{% url 'event_detail' object.pk %}">N{{ object.pk|stringformat:"05d" }}</a><br>
|
||||||
<td>{{ object.end_date }}</td>
|
<span class="text-muted">{{ object.get_status_display }}</span></td>
|
||||||
|
<td>{{ object.start_date }}</td>
|
||||||
<td>{{ object.name }}</td>
|
<td>{{ object.name }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if object.organisation %}
|
{% if object.organisation %}
|
||||||
{{ object.organisation.name }}
|
{{ object.organisation.name }}
|
||||||
|
<br>
|
||||||
|
<span class="text-muted">{{ object.organisation.union_account|yesno:'Internal,External' }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ object.person.name }}
|
{{ object.person.name }}
|
||||||
|
<br>
|
||||||
|
<span class="text-muted">External</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td>{{ object.sum_total|floatformat:2 }}</td>
|
<td>{{ object.sum_total|floatformat:2 }}</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{{ object.mic.initials }}<br/>
|
{% if object.mic %}
|
||||||
|
{{ object.mic.initials }}<br>
|
||||||
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo"/>
|
<img src="{{ object.mic.profile_picture }}" class="event-mic-photo"/>
|
||||||
|
{% else %}
|
||||||
|
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<a href="{% url 'invoice_event' object.pk %}" target="_blank" class="btn btn-default">
|
<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>
|
<span class="glyphicon glyphicon-gbp"></span>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -63,6 +85,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||||
{% paginator %}
|
{% paginator %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<td class="hidden-xs">#</td>
|
<td>#</td>
|
||||||
<td>Event Date</td>
|
<td>Event Date</td>
|
||||||
<td>Event Details</td>
|
<td>Event Details</td>
|
||||||
<td>Event Timings</td>
|
<td>Event Timings</td>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
danger
|
danger
|
||||||
{% endif %}
|
{% endif %}
|
||||||
">
|
">
|
||||||
<td class="hidden-xs">{{ event.pk }}</td>
|
<td>{{ event.pk }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></div>
|
<div><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></div>
|
||||||
{% if event.end_date and event.end_date != event.start_date %}
|
{% if event.end_date and event.end_date != event.start_date %}
|
||||||
|
|||||||
@@ -109,6 +109,11 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-right"><strong>Balance:</strong></td>
|
||||||
|
<td>{{ object.balance|floatformat:2 }}</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,17 +5,21 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<h2>Invoices</h2>
|
<h2>{% block heading %}Invoices{% endblock %}</h2>
|
||||||
|
{% block description %}{% endblock %}
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||||
{% paginator %}
|
{% paginator %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<table class="table table-responsive table-hover">
|
<div class="table-responsive col-sm-12">
|
||||||
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>#</th>
|
<th>#</th>
|
||||||
<th>Event</th>
|
<th>Event</th>
|
||||||
|
<th>Client</th>
|
||||||
|
<th>Event Date</th>
|
||||||
<th>Invoice Date</th>
|
<th>Invoice Date</th>
|
||||||
<th>Balance</th>
|
<th>Balance</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
@@ -25,7 +29,20 @@
|
|||||||
{% for object in object_list %}
|
{% for object in object_list %}
|
||||||
<tr class="{% if object.void %}danger{% elif object.balance == 0 %}success{% endif %}">
|
<tr class="{% if object.void %}danger{% elif object.balance == 0 %}success{% endif %}">
|
||||||
<td>{{ object.pk }}</td>
|
<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><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.invoice_date }}</td>
|
||||||
<td>{{ object.balance|floatformat:2 }}</td>
|
<td>{{ object.balance|floatformat:2 }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
@@ -37,6 +54,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
<div class="col-md-6 col-md-offset-6 col-sm-12 text-right">
|
||||||
{% paginator %}
|
{% paginator %}
|
||||||
|
|||||||
13
RIGS/templates/RIGS/invoice_list_active.html
Normal file
13
RIGS/templates/RIGS/invoice_list_active.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{% 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 %}
|
||||||
13
RIGS/templates/RIGS/invoice_list_archive.html
Normal file
13
RIGS/templates/RIGS/invoice_list_archive.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{% 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 %}
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
<dd>
|
<dd>
|
||||||
{% if user.api_key %}
|
{% 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>
|
<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="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/>
|
<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/>
|
||||||
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>
|
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 %}
|
{% else %}
|
||||||
<pre>No API Key Generated</pre>
|
<pre>No API Key Generated</pre>
|
||||||
|
|||||||
0
subhire/__init__.py
Normal file
0
subhire/__init__.py
Normal file
6
subhire/admin.py
Normal file
6
subhire/admin.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
import models
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
admin.site.register(models.Hire)
|
||||||
|
admin.site.register(models.Provider)
|
||||||
43
subhire/migrations/0001_initial.py
Normal file
43
subhire/migrations/0001_initial.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# -*- 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
subhire/migrations/__init__.py
Normal file
0
subhire/migrations/__init__.py
Normal file
39
subhire/models.py
Normal file
39
subhire/models.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
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()
|
||||||
3
subhire/tests.py
Normal file
3
subhire/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
subhire/views.py
Normal file
3
subhire/views.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<link rel="icon" type="image/png" href="{% static "imgs/pyrigs-avatar.png" %}">
|
<link rel="icon" type="image/png" href="{% static "imgs/pyrigs-avatar.png" %}">
|
||||||
<link rel="apple-touch-icon" href="{% static "imgs/pyrigs-avatar.png" %}">
|
<link rel="apple-touch-icon" href="{% static "imgs/pyrigs-avatar.png" %}">
|
||||||
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400italic,700,300,400' rel='stylesheet'
|
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400italic,700,300,400' rel='stylesheet'
|
||||||
type='text/css'>
|
type='text/css'>
|
||||||
|
|
||||||
|
|
||||||
@@ -74,12 +74,12 @@
|
|||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Invoices<b class="caret"></b></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Invoices<b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a href="{% url 'invoice_list' %}"><span class="glyphicon glyphicon-gbp"></span> Active</a>
|
|
||||||
</li>
|
|
||||||
{% if perms.RIGS.add_invoice %}
|
{% if perms.RIGS.add_invoice %}
|
||||||
<li><a href="{% url 'invoice_waiting' %}"><span
|
<li><a href="{% url 'invoice_waiting' %}"><span
|
||||||
class="glyphicon glyphicon-briefcase"></span> Waiting</a></li>
|
class="glyphicon glyphicon-briefcase"></span> Waiting</a></li>
|
||||||
{% endif %}
|
{% 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>
|
<li><a href="{% url 'invoice_archive' %}"><span class="glyphicon glyphicon-book"></span>
|
||||||
Archive</a></li>
|
Archive</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user