Cheap and dirty active totals

This commit is contained in:
Tom Price
2016-05-24 17:17:52 +01:00
parent e4a955f323
commit 6030288956
3 changed files with 77 additions and 64 deletions

View File

@@ -19,16 +19,24 @@ class InvoiceIndex(generic.ListView):
model = models.Invoice model = models.Invoice
template_name = 'RIGS/invoice_list.html' template_name = 'RIGS/invoice_list.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
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 - payments) <> 0.0 AND void = '0'" \
"ORDER BY invoice_date" "ORDER BY invoice_date"
query = self.model.objects.raw(sql) query = self.model.objects.raw(sql)

View File

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

View File

@@ -5,7 +5,7 @@
{% block content %} {% block content %}
<div class="col-sm-12"> <div class="col-sm-12">
<h2>Invoices</h2> <h2>Invoices (£ {{ total|floatformat:2 }})</h2>
{% 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 %}