# -*- coding: utf-8 -*- # This file is part of k4ever, a point-of-sale system # Contact............ # Website............ http://k4ever.someserver.de/ # Bug tracker........ http://k4ever.someserver.de/report # # Licensed under GNU Affero General Public License v3 or later from django.db import models from django.contrib.auth.models import User import datetime from decimal import Decimal from django.db.models.signals import pre_delete, post_delete # Create your models here. class BuyableType(models.Model): """ Type/Category for a buyable object """ name = models.CharField(max_length=100) def __unicode__(self): return self.name def itemcount(self): return self.buyable_set.values().count() class Buyable(models.Model): """ Represents a buyable item. """ name = models.CharField(max_length=100) price = models.DecimalField(max_digits=8, decimal_places=2) image = models.ImageField(upload_to='img/buyable/', help_text="The Image needs to have a 1:1 aspect ratio.") deposit = models.DecimalField(max_digits=8, decimal_places=2) description = models.TextField() buyableType = models.ManyToManyField(BuyableType) barcode = models.CharField(max_length=100, default='', blank=True) def hasDeposit(self): """ Returns True if the item has deposit. """ return self.deposit > Decimal(0) def __unicode__(self): item = "%s (%.2f EUR" % (self.name, self.price) if self.hasDeposit(): item += "/%.2f Pfand" % self.deposit item += ")" return item def types(self): typelist = "" for item in self.buyableType.values_list(): typelist += item[1] + ", " return typelist.rstrip(u", ") class Order(models.Model): """ Represents an order by the user, consists of several :class:`Purchases `. """ user = models.ForeignKey(User) price = models.DecimalField(max_digits=8, decimal_places=2, default=Decimal(0)) dateTime = models.DateTimeField(auto_now_add=True) def addItems(self, items): for item in items: self.purchase.add(item) self.price += item.price def updatePrice(self, instance, price, saveOrder=True): """ Update price of order, save Order if saveOrder is True. """ self.price += price if self.id and saveOrder: self.save() def __unicode__(self): return "Price %s, User %s" % (self.price, self.user) def itemList(self): l = "" for item in self.purchase_set.all(): l += item.buyable.name + u", " return l.rstrip(u", ") def save(self, *args, **kwargs): profile = self.user.get_profile() if self.id == None: # new item, get it! profile.balance -= self.price profile.save() else: # get old oldobj = Order.objects.get(id=self.id) if oldobj.user == self.user: profile = self.user.get_profile() profile.balance -= (self.price - oldobj.price) profile.save() else: oldProfile = oldobj.user.get_profile() newProfile = self.user.get_profile() oldProfile.balance += oldobj.price oldProfile.save() newprofile.balance -= self.price self.save() super(Order, self).save(*args, **kwargs) @staticmethod def pre_delete_signal(sender, instance, **kwargs): # Normally we would not need this function as for consistency # reasons and the behaviour of Buyables delete at deletion # the orders price should be 0. But for the sake of not getting # inconsistent with the users balance in the strangest situations # we will check that here. This means (at least) one extra query # per order deletion, as we don't get the updated object but the one # where all the buyables have not updated the price updOrder = Order.objects.get(id=instance.id) if updOrder.price != Decimal("0"): profile = updOrder.user.get_profile() profile.balance += updOrder.price profile.save() pre_delete.connect(Order.pre_delete_signal, sender=Order) class Purchase(models.Model): """ Represents a :class:`Buyable` in a :class:`Order`. Could be viewed as 'one bought item' or 'one item on the voucher'.""" order = models.ForeignKey(Order) price = models.DecimalField(max_digits=8, decimal_places=2, default=None) isDeposit = models.BooleanField() buyable = models.ForeignKey(Buyable) def updatePrice(self): if self.isDeposit: self.price = self.buyable.deposit else: self.price = self.buyable.price def __unicode__(self): return "%s%s" % (self.buyable.name, (self.isDeposit and " (deposit)" or "")) def save(self, saveOrder=True, *args, **kwargs): if self.price == None: self.updatePrice() if self.order: if self.id == None: self.order.updatePrice(self, self.price, saveOrder=saveOrder) else: # object exists, update. oldobj = Purchase.objects.get(id=self.id) if oldobj.order == self.order: self.order.updatePrice(self, self.price - oldobj.price, saveOrder=saveOrder) else: oldobj.order.updatePrice(oldobj, -oldobj.price, saveOrder=saveOrder) self.order.updatePrice(self, self.price, saveOrder=saveOrder) super(Purchase, self).save(*args, **kwargs) @staticmethod def pre_delete_signal(sender, instance, **kwargs): # FIXME: For many items this could get very inefficient # Find workarround! print "pre delete from purchase" instance.order.updatePrice(instance, -instance.price, saveOrder=True) pre_delete.connect(Purchase.pre_delete_signal, sender=Purchase)