Billing or orders management is a typical use case in most applications, there are many Saas solutions for this but still there can be situations where you need an in-house solution, and it can be tricky and time-consuming to make a design thats efficient and needs least queries. This is a basic model design you may add or modify this as per requirement.
The design includes 4 models
- Items : Stores the items that can be order. Not required but provides consistency.
- Order: Stores order information and functions ( Total, Subtotal, etc).
- Order Items: Stores order items information.
import datetime
class Items(models.Model):
name = models.CharField(max_length=200, null=False)
price = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)
active = models.BooleanField(default=True)
# Records
created = models.DateTimeField(auto_now_add=True)
time = models.DateTimeField(auto_now=True)
def increment_order_id():
""" Creates Order ID of format ABCD . Change as per neeed."""
prefix = 'ABCD'
# if you are gonna change it but its not 4 character long
# make sure to change the numbers below too
last_order = Order.objects.all().order_by('id').last()
if not last_order:
return str(prefix) + str(datetime.date.today().year) + str(
datetime.date.today().month).zfill(2) + str(
datetime.date.today().day).zfill(2) + '0000'
order_id = last_order.order_id
order_id_int = int(order_id[12:16])
new_order_id_int = order_id_int + 1
new_order_id = str(prefix) + str(str(datetime.date.today().year)) + str(
datetime.date.today().month).zfill(2) + str(
datetime.date.today().day).zfill(2) + str(new_order_id_int).zfill(4)
return new_order_id
class Order(models.Model):
""" Order Model """
NOTPAID = 0
PAID = 1
PARTPAID = 2
PAYMENT_STATUS = (
(NOTPAID, 'Not Paid'),
(PARTPAID, 'Partial Paid'),
(PAID, 'Paid'),
)
order_id = models.CharField(max_length=20, default=increment_order_id, null=True, blank=True, editable=False)
payment_status = models.IntegerField(default=NOTPAID, choices=PAYMENT_STATUS, null=False)
lock = models.BooleanField(default=False, null=False)
discount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
created = models.DateTimeField(auto_now_add=True)
time = models.DateTimeField(auto_now=True)
class Meta:
ordering = ('-created', '-id')
def __str__(self):
return '%s' % (self.order_id)
def subtotal(self):
total = Decimal('0.00').quantize(Decimal('0.01'))
for item in self.orderitems.all().filter(status=True):
total = total + Decimal(item.subtotal()).quantize(Decimal('0.01'))
return str(total)
def total(self):
total = Decimal('0.00').quantize(Decimal('0.01'))
for item in self.orderitems.all().filter(status=True):
total = total + Decimal(item.total()).quantize(Decimal('0.01'))
return str(total - self.discount)
class OrderItems(models.Model):
order = models.ForeignKey(Order, on_delete=models.PROTECT,
related_name='orderitems', null=True)
desc = models.CharField(max_length=500, null=True)
# Item
item = models.ForeignKey(Items, on_delete=models.PROTECT, null=True)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
quantity = models.IntegerField(default=1, null=False)
tax = models.DecimalField(max_digits=10, decimal_places=4, default=0.00)
discount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
# Records
created = models.DateTimeField(auto_now_add=True)
time = models.DateTimeField(auto_now=True)
def __str__(self):
return '%s' % (self.id)
def description(self):
# If items is linked, return item's description
if self.item:
return str(self.item.name)
else:
return 'Order Item'
def subtotal(self):
total = ((self.price * Decimal(self.quantity)).quantize(
Decimal('0.01')) - self.discount)
return str(total)
def taxtotal(self):
subtotal = Decimal(self.subtotal()).quantize(Decimal('0.01'))
total = subtotal * self.tax
return str(Decimal(total).quantize(Decimal('0.01')))
def total(self):
total = Decimal(self.subtotal()).quantize(Decimal('0.01')) + Decimal(
self.taxtotal()).quantize(Decimal('0.01'))
return str(total)