1. Ian George
  2. Quiet apps

Source

Quiet apps / quiet / shop / models / order.py

from decimal import *

import django.dispatch

from django.db import models
from django.db.models.signals import post_save, pre_delete
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType

from quiet.shop.utils import debug
from quiet.shop.session import SessionBasket
from quiet.common.emails import internal_mail, external_mail
from quiet.common.countries import CountryField
    
STATUS_CHOICES = (
    ('new', 'New order'),
    ('preparing', 'Being prepared'),
    ('processing', 'Being processed'),
    ('shipped', 'Items have shipped'),
    ('complete', 'Complete'), 
)
PAYMENT_CHOICES = (
    ('PAYPAL', 'PayPal'),
    ('SAGEPAY', 'SagePay'),
    ('PHONE', 'Telephone'),
    ('INSTORE', 'In store'),
    ('GIFT', 'Gift Certificate'),
)
ADDRESS_TYPE_CHOICES = (
    ('billing', 'Billing Address'),
    ('delivery', 'Delivery Address'),
)

class OrderByStatusManager(models.Manager):
    def new(self):
        return self.get_query_set().filter(status__status='new').exclude(status__status__in=('preparing', 'processing', 'shipped', 'complete'))

    def in_progress(self):
        return self.get_query_set().filter(status__status__in=('preparing', 'processing', 'shipped')).exclude(status__status__in=('complete'))

    def closed(self):
        return self.get_query_set().filter(status__status='complete')

class OrderStatusManager(models.Manager):
    def current(self):
        return self.get_query_set.order_by('-date')[0]

class Order(models.Model):
    date = models.DateTimeField(auto_now_add=True)
    order_ref = models.CharField(max_length=30, null=True)
    tax_rate = models.DecimalField(max_digits=8,decimal_places=2)

    subtotal = models.DecimalField(max_digits=8,decimal_places=2, null=True)
    tax = models.DecimalField(max_digits=8,decimal_places=2, null=True, blank=True)
    shipping = models.DecimalField(max_digits=8,decimal_places=2, null=True)
    discount = models.DecimalField(max_digits=8,decimal_places=2, null=True, blank=True)
    total = models.DecimalField(max_digits=8,decimal_places=2, null=True)
    balance = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
    deposit = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
    complete = models.BooleanField()
    exported = models.BooleanField(default=False)

    objects = OrderByStatusManager()

    def __unicode__(self):
        return "%s on %s" % (self.order_ref, self.date)

    def generate_reference(self):
        if self.id and not self.order_ref:
            self.order_ref = 'web-%s' % (self.id + 1000)

    def save(self, *args, **kwargs):
        if not self.id:
            super(Order, self).save(*args, **kwargs)
        self.generate_reference()
        super(Order, self).save(*args, **kwargs) 

    def from_basket(self, basket, save=False):
        self.shipping = basket.shipping_price()
        self.subtotal = basket.net_price(False)
        self.total = basket.total_price()
        self.balance = basket.total_price()

        self.items.all().delete()            
    
        for item in basket.products():
            order_item, created = OrderItem.objects.get_or_create(
                order=self, 
                product_content_type=item.item_content_type,
                product_object_id = item.item_object_id,
                defaults={'qty':1, 'price':0, 'total':0}
                )
            order_item.qty = item.quantity
            order_item.price = item.price
            order_item.tax = (item.tax or 0)
            order_item.total = (item.price * item.quantity) + (item.tax or 0)
            order_item.save()
            debug("payment", "Added %s to order %s" % (order_item, self.id))
        
        for item in basket.basket.shipping_items.all():
            order_item, created = OrderItem.objects.get_or_create(
                order=self,
                product_content_type = item.item_content_type,
                product_object_id = item.item_object_id,
                qty = 1,
                defaults={'qty':1, 'price':0, 'total':0}
                )
            order_item.price = item.price
            order_item.tax = (item.tax or 0)
            order_item.total = item.price + (item.tax or 0)
            order_item.save()
            debug("payment", "Added shipping %s to order %s" % (order_item, self.id))

        for item in basket.basket.discount_items.all():
            order_item, created = OrderItem.objects.get_or_create(
                order=self,
                product_content_type = item.item_content_type,
                product_object_id = item.item_object_id,
                qty = 1,
                defaults={'qty':1, 'price':0, 'total':0}
                )
            order_item.price = item.price
            order_item.tax = (item.tax or 0)
            order_item.total = item.price + (item.tax or 0)
            order_item.save()
            debug("payment", "Added discounts %s to order %s" % (order_item, self.id))
           
        if save:
            self.save()
    
    class Meta:
        app_label='shop'
        ordering = ['-date']

class OrderItem(models.Model):
    order = models.ForeignKey(Order, related_name='items')

    #generic relation for actual item (covers us for odd products, shipping items and discounts, huzzah!)
    product_content_type = models.ForeignKey(ContentType)
    product_object_id = models.PositiveIntegerField()
    product = generic.GenericForeignKey('product_content_type', 'product_object_id')

    qty = models.IntegerField()
    price = models.DecimalField(max_digits=8,decimal_places=2)
    tax = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
    total = models.DecimalField(max_digits=8,decimal_places=2)

    class Meta:
        app_label='shop'

    def __unicode__(self):
        if hasattr(self.product, 'SKU'):
            return "%s - %s" % (self.product.SKU, self.product)
        else:
            return "%s" % (self.product,)

class OrderAddress(models.Model):
    order = models.ForeignKey(Order, related_name='addresses')
    type = models.CharField(max_length=10, choices=ADDRESS_TYPE_CHOICES)
    name = models.CharField(max_length=50)
    address1 = models.CharField(max_length=50)
    address2 = models.CharField(max_length=255, blank=True)
    address3 = models.CharField(max_length=255, blank=True)
    city = models.CharField(max_length=255)
    county = models.CharField(max_length=255, blank=True)
    country = CountryField(max_length=100, null=True, blank=True)
    postcode = models.CharField(max_length=50, blank=True) 
    telephone = models.CharField(max_length=40, blank=True)
    email = models.CharField(max_length=255, blank=True)

    class Meta:
        app_label='shop'
    
    def __unicode__(self):
        return '%s %s' % (self.name, self.postcode)

class OrderStatus(models.Model):
    order = models.ForeignKey(Order, related_name='status')
    status = models.CharField(max_length=10, choices=STATUS_CHOICES)
    date = models.DateTimeField(auto_now_add=True)
    comments = models.TextField(null=True, blank=True)

    class Meta:
        app_label='shop'
        ordering = ['-date']

    def __unicode__(self):
        return self.status

class OrderPayment(models.Model):
    order = models.ForeignKey(Order, related_name='payments')
    reference = models.CharField(max_length=50)
    source = models.CharField(max_length=20, choices=PAYMENT_CHOICES)
    date = models.DateTimeField(auto_now_add=True)
    tax = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
    amount = models.DecimalField(max_digits=8, decimal_places=2)

    class Meta:
        app_label='shop'
        ordering = ['-date']

    def __unicode__(self):
        return '%s - %s' % (self.date, self.amount)

    @staticmethod
    def update_price(sender, **kwargs):
        op = kwargs['instance']
        if op:
            order = op.order
            bal = order.balance
            if order.total > 0:
                order.balance = order.total
                for payment in order.payments.all():
                    order.balance -= payment.amount
                if order.balance != bal:
                    order.save()

post_save.connect(OrderPayment.update_price, sender=OrderPayment)
pre_delete.connect(OrderPayment.update_price, sender=OrderPayment)

def order_complete(**kwargs):
    ctxt = {}
    oid = kwargs.get('order_id', None)
    if oid < 1: 
        oid = None
    try:
        ordr = Order.objects.get(pk=oid)
    except Order.DoesNotExist:
        ordr = None
    
    debug("payment", "Got order %s" % oid)
 
    if ordr.status.count() == 0:
        status = OrderStatus(order=ordr, status='new', comments='Order complete and ready for processing')
        status.save()
    debug("payment", "Added status 'complete' to order %s" % oid)
        
    #give it a genuine balance from the payments recorded
    #ordr.balance = ordr.subtotal + ordr.shipping
    #for payment in ordr.payments.all():
    #    ordr.balance -= payment.amount
    #ordr.save()

    #debug("payment", "Calculated balance for order %s" % oid)

    basket = SessionBasket(kwargs['basket_id'])

    for item in basket.products():
        obj = item.item_object
        if hasattr(obj, 'stock') and hasattr(obj, 'save'):
            if obj.stock > 0:
                obj.stock -= 1
                obj.save()

    debug("payment", "Adjusted stock for order %s" % oid)

    mail_data = { 'subject': 'New order in store', 'order' : ordr }

    invoice_address = ordr.addresses.filter(type='billing')
    delivery_address = ordr.addresses.filter(type='delivery')
    if invoice_address:
        mail_data['invoice_address'] = invoice_address[0]
    if delivery_address:
        mail_data['delivery_address'] = delivery_address[0]
    else:
        mail_data['delivery_address'] = invoice_address[0]

    addresses = ordr.addresses.all()
    if addresses:
        mail_to = addresses[0].email
    else:
        mail_to = 'ian@quiet.sh'

    internal_mail(mail_data, 'orders/emails/internal_new_order.txt')

    debug("payment", "Sent internal mail for order %s" % oid)

    mail_data['subject'] = kwargs['order_email_subject']
    external_mail(mail_to, mail_data, 'orders/emails/external_new_order.txt')

    debug("payment", "Sent external mail for order %s" % oid)

    debug("payment", "Order %s complete" % oid)

def payment_made(**kwargs):
    debug("payment", "Payment made: %s" % kwargs)
    ordr = None
    try:
        ordr = Order.objects.get(pk=int(kwargs['order_id']))
    except:
        raise Exception("No order with id:%s" % kwargs['order_id'])
        
    debug("payment", "Order:%s" % ordr)

    op, created = OrderPayment.objects.get_or_create(order=ordr, reference=kwargs['reference'], source=kwargs['source'], amount=kwargs['amount'])
    op.save()

    if ordr.status.count() == 0:
        status = OrderStatus(order=ordr, status='new', comments='Order complete and ready for processing')
        status.save()

    #if not ordr.balance:
    #    ordr.balance = Decimal(0)
    #    for payment in ordr.payments.all():
    #        if payment.amount:
    #            ordr.balance -= payment.amount
    ordr.complete = True
    ordr.save()

    debug("payment", "Signal complete")

signal_payment_made = django.dispatch.Signal(providing_args=['order_id', 'basket_id', 'reference', 'source', 'amount'])
signal_order_complete = django.dispatch.Signal(providing_args=['order_id', 'basket_id', 'order_email_subject'])


signal_payment_made.connect(payment_made)
signal_order_complete.connect(order_complete)