symposion_app/registrasion/controllers/invoice.py

183 lines
5.8 KiB
Python
Raw Normal View History

2016-01-22 05:01:30 +00:00
from decimal import Decimal
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError
from django.db import transaction
2016-01-22 06:02:07 +00:00
from django.db.models import Sum
2016-01-22 05:01:30 +00:00
from registrasion import models as rego
from cart import CartController
2016-01-22 06:02:07 +00:00
2016-01-22 05:01:30 +00:00
class InvoiceController(object):
def __init__(self, invoice):
self.invoice = invoice
self.update_validity() # Make sure this invoice is up-to-date
2016-01-22 05:01:30 +00:00
@classmethod
def for_cart(cls, cart):
''' Returns an invoice object for a given cart at its current revision.
If such an invoice does not exist, the cart is validated, and if valid,
an invoice is generated.'''
try:
invoice = rego.Invoice.objects.get(
cart=cart,
cart_revision=cart.revision,
void=False,
)
2016-01-22 05:01:30 +00:00
except ObjectDoesNotExist:
cart_controller = CartController(cart)
2016-01-22 06:02:07 +00:00
cart_controller.validate_cart() # Raises ValidationError on fail.
# Void past invoices for this cart
rego.Invoice.objects.filter(cart=cart).update(void=True)
2016-01-22 05:01:30 +00:00
invoice = cls._generate(cart)
return InvoiceController(invoice)
@classmethod
def resolve_discount_value(cls, item):
try:
condition = rego.DiscountForProduct.objects.get(
discount=item.discount,
product=item.product
)
except ObjectDoesNotExist:
condition = rego.DiscountForCategory.objects.get(
discount=item.discount,
category=item.product.category
)
if condition.percentage is not None:
value = item.product.price * (condition.percentage / 100)
else:
value = condition.price
return value
@classmethod
def _generate(cls, cart):
''' Generates an invoice for the given cart. '''
invoice = rego.Invoice.objects.create(
user=cart.user,
cart=cart,
cart_revision=cart.revision,
value=Decimal()
)
invoice.save()
# TODO: calculate line items.
product_items = rego.ProductItem.objects.filter(cart=cart)
product_items = product_items.order_by(
"product__category__order", "product__order"
)
2016-01-22 05:01:30 +00:00
discount_items = rego.DiscountItem.objects.filter(cart=cart)
invoice_value = Decimal()
for item in product_items:
product = item.product
2016-01-22 05:01:30 +00:00
line_item = rego.LineItem.objects.create(
invoice=invoice,
description="%s - %s" % (product.category.name, product.name),
2016-01-22 05:01:30 +00:00
quantity=item.quantity,
price=product.price,
2016-01-22 05:01:30 +00:00
)
line_item.save()
invoice_value += line_item.quantity * line_item.price
for item in discount_items:
line_item = rego.LineItem.objects.create(
invoice=invoice,
description=item.discount.description,
quantity=item.quantity,
price=cls.resolve_discount_value(item) * -1,
)
line_item.save()
invoice_value += line_item.quantity * line_item.price
# TODO: calculate line items from discounts
invoice.value = invoice_value
invoice.save()
return invoice
def update_validity(self):
''' Updates the validity of this invoice if the cart it is attached to
has updated. '''
2016-01-22 05:01:30 +00:00
if self.invoice.cart is not None:
if self.invoice.cart.revision != self.invoice.cart_revision:
self.void()
2016-01-22 05:01:30 +00:00
def void(self):
''' Voids the invoice if it is valid to do so. '''
if self.invoice.paid:
raise ValidationError("Paid invoices cannot be voided, "
"only refunded.")
2016-01-22 05:01:30 +00:00
self.invoice.void = True
self.invoice.save()
2016-01-22 05:01:30 +00:00
@transaction.atomic
2016-01-22 05:01:30 +00:00
def pay(self, reference, amount):
''' Pays the invoice by the given amount. If the payment
equals the total on the invoice, finalise the invoice.
(NB should be transactional.)
'''
if self.invoice.cart:
2016-01-22 05:01:30 +00:00
cart = CartController(self.invoice.cart)
2016-01-22 06:02:07 +00:00
cart.validate_cart() # Raises ValidationError if invalid
2016-01-22 05:01:30 +00:00
if self.invoice.void:
raise ValidationError("Void invoices cannot be paid")
if self.invoice.paid:
raise ValidationError("Paid invoices cannot be paid again")
2016-01-22 05:01:30 +00:00
''' Adds a payment '''
payment = rego.Payment.objects.create(
invoice=self.invoice,
reference=reference,
amount=amount,
)
payment.save()
2016-03-24 03:20:29 +00:00
payments = rego.Payment.objects.filter(invoice=self.invoice)
2016-01-22 05:01:30 +00:00
agg = payments.aggregate(Sum("amount"))
total = agg["amount__sum"]
2016-01-22 06:02:07 +00:00
if total == self.invoice.value:
2016-01-22 05:01:30 +00:00
self.invoice.paid = True
if self.invoice.cart:
cart = self.invoice.cart
cart.active = False
cart.save()
self.invoice.save()
@transaction.atomic
def refund(self, reference, amount):
''' Refunds the invoice by the given amount. The invoice is
marked as unpaid, and the underlying cart is marked as released.
'''
if self.invoice.void:
raise ValidationError("Void invoices cannot be refunded")
''' Adds a payment '''
payment = rego.Payment.objects.create(
invoice=self.invoice,
reference=reference,
amount=0 - amount,
)
payment.save()
self.invoice.paid = False
self.invoice.void = True
if self.invoice.cart:
2016-01-22 05:01:30 +00:00
cart = self.invoice.cart
cart.released = True
2016-01-22 05:01:30 +00:00
cart.save()
self.invoice.save()