mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-17 13:32:15 +00:00
170 lines
5.1 KiB
Python
170 lines
5.1 KiB
Python
from decimal import Decimal
|
|
|
|
from django.db.models import Q
|
|
from django.db import models
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from reversion import revisions as reversion
|
|
from versioning.versioning import RevisionMixin
|
|
from .utils import filter_by_pk
|
|
|
|
|
|
class VatManager(models.Manager):
|
|
def current_rate(self):
|
|
return self.find_rate(timezone.now())
|
|
|
|
def find_rate(self, date):
|
|
try:
|
|
return self.filter(start_at__lte=date).latest()
|
|
except VatRate.DoesNotExist:
|
|
r = VatRate
|
|
r.rate = 0
|
|
return r
|
|
|
|
|
|
@reversion.register
|
|
class VatRate(models.Model, RevisionMixin):
|
|
start_at = models.DateField()
|
|
rate = models.DecimalField(max_digits=6, decimal_places=6)
|
|
comment = models.CharField(max_length=255)
|
|
|
|
objects = VatManager()
|
|
|
|
reversion_hide = True
|
|
|
|
@property
|
|
def as_percent(self):
|
|
return self.rate * 100
|
|
|
|
class Meta:
|
|
ordering = ['-start_at']
|
|
get_latest_by = 'start_at'
|
|
|
|
def __str__(self):
|
|
return f"{self.comment} {self.start_at} @ {self.as_percent}%"
|
|
|
|
|
|
class InvoiceManager(models.Manager):
|
|
def outstanding_invoices(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 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\", " \
|
|
"\"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'" \
|
|
"ORDER BY invoice_date"
|
|
|
|
query = self.raw(sql)
|
|
return query
|
|
|
|
def search(self, query=None):
|
|
qs = self.get_queryset()
|
|
if query is not None:
|
|
or_lookup = Q(event__name__icontains=query)
|
|
|
|
or_lookup = filter_by_pk(or_lookup, query)
|
|
|
|
# try and parse an int
|
|
try:
|
|
val = int(query)
|
|
or_lookup = or_lookup | Q(event__pk=val)
|
|
except: # noqa
|
|
# not an integer
|
|
pass
|
|
|
|
try:
|
|
if query[0] == "N":
|
|
val = int(query[1:])
|
|
or_lookup = Q(event__pk=val) # If string is Nxxxxx then filter by event number
|
|
elif query[0] == "#":
|
|
val = int(query[1:])
|
|
or_lookup = Q(pk=val) # If string is #xxxxx then filter by invoice number
|
|
except: # noqa
|
|
pass
|
|
|
|
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
|
|
return qs
|
|
|
|
|
|
@reversion.register(follow=['payment_set'])
|
|
class Invoice(models.Model, RevisionMixin):
|
|
event = models.OneToOneField('Event', on_delete=models.CASCADE)
|
|
invoice_date = models.DateField(auto_now_add=True)
|
|
void = models.BooleanField(default=False)
|
|
|
|
reversion_perm = 'RIGS.view_invoice'
|
|
|
|
objects = InvoiceManager()
|
|
|
|
@property
|
|
def sum_total(self):
|
|
return self.event.sum_total
|
|
|
|
@property
|
|
def total(self):
|
|
return self.event.total
|
|
|
|
@property
|
|
def payment_total(self):
|
|
total = self.payment_set.aggregate(total=models.Sum('amount'))['total']
|
|
if total:
|
|
return total
|
|
return Decimal("0.00")
|
|
|
|
@property
|
|
def balance(self):
|
|
return self.sum_total - self.payment_total
|
|
|
|
@property
|
|
def is_closed(self):
|
|
return self.balance == 0 or self.void
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('invoice_detail', kwargs={'pk': self.pk})
|
|
|
|
@property
|
|
def activity_feed_string(self):
|
|
return f"{self.display_id} for Event {self.event.display_id}"
|
|
|
|
def __str__(self):
|
|
return f"{self.display_id}: {self.event} (£{self.balance:.2f})"
|
|
|
|
@property
|
|
def display_id(self):
|
|
return f"#{self.pk:05d}"
|
|
|
|
class Meta:
|
|
ordering = ['-invoice_date']
|
|
|
|
|
|
@reversion.register
|
|
class Payment(models.Model, RevisionMixin):
|
|
CASH = 'C'
|
|
INTERNAL = 'I'
|
|
EXTERNAL = 'E'
|
|
SUCORE = 'SU'
|
|
ADJUSTMENT = 'T'
|
|
METHODS = (
|
|
(CASH, 'Cash'),
|
|
(INTERNAL, 'Internal'),
|
|
(EXTERNAL, 'External'),
|
|
(SUCORE, 'SU Core'),
|
|
(ADJUSTMENT, 'TEC Adjustment'),
|
|
)
|
|
|
|
invoice = models.ForeignKey('Invoice', on_delete=models.CASCADE)
|
|
date = models.DateField()
|
|
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text='Please use ex. VAT')
|
|
method = models.CharField(max_length=2, choices=METHODS, default='', blank=True)
|
|
|
|
reversion_hide = True
|
|
|
|
def __str__(self):
|
|
return f"{self.get_method_display()}: {self.amount}"
|
|
|
|
@property
|
|
def activity_feed_string(self):
|
|
return f"payment of £{self.amount}" |