In this article, I am going to explain how to build an inventory manager for a pharmacy.
The aim of the app was to be able to do the following:
1) The user can create a database table of each item, the item category, unit price and total quantity in stock.
2) User can click on an item, buy it, and generate a receipt.
3) Within the receipt page, the total cost of the item is calculated based on the quantity purchased and the unit price.
4) The balance to be received is also calculated by subtracting the total cost from the amount paid.
5) Based on products sold, a database table of all sales made is created the keeps track of the following:
-The total amount paid summed up for all the items sold.
-The total balance received by all customers also summed up.
-Net balance after subtracting total balance received from total amout paid.
6) The app keeps a log of the quantity remaining in stock for each item as it is being sold.
7) The app is also able to replenish depleting stock in the Products quantity table.
Please note that this tutorial assumes a baseline knowledge of python and Django and that you have django already installed. We will be using Bootstrap 4 and crispy forms for the styling.
Create a new project
django-admin startproject pharmacy_project
Change into the pharmacy_project directory and create a new app
cd pharmacy_project
python manage.py startapp product_app
Add the newly created app into installed apps under 'settings.py'
INSTALLED_APPS = [
'product_app',
]
In the root directory, create a 'templates' folder and add it within 'settings.py'
TEMPLATES = [
'DIRS': [os.path.join(BASE_DIR, 'templates')],
]
Within the 'product_app' create a 'static' folder and add a path and a static root within settings.py
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIR = [
os.path.join(BASE_DIR, 'product_app/static')
]
In product_app, create a 'urls.py' file and within the 'pharmacy_project' urls.py file, include the path to the product_app 's urls.py file
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('', include ('product_app.urls'))
]
In product_app.urls, add the path to our views
from django.urls import path
from product_app import views
urlpatterns = [
path('home/', views.home, name = "home"),
]
In templates folder, create a 'products' folder and within it a 'base.html' from which all other templates will inherit from. The navbar also contains a conditional statement to ensure that users can only view the details of the app when logged in.
base.html
{% load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<title>Pharmacy App</title>
<link rel = "stylesheet" href = {% static 'css/main.css'%}>
<!-- Raleway font -->
<link
href="https://fonts.googleapis.com/css?family=Raleway"
rel="stylesheet"
/>
<!-- Montserrat font -->
<link href='https://fonts.googleapis.com/css?family=Montserrat' rel='stylesheet'>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark ">
<a class="nav-link" href=""><h1 class="navbar-brand" style = "font-size: 25px">PHARM PLUS</h1></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="nav navbar-nav ml-auto">
{% if user.is_authenticated%}
<li class="nav-item">
<a class="nav-link" href="{% url 'home' %}">HOME</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'receipt'%}">RECEIPTS</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'all_sales'%}">ALL SALES</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'logout'%}">LOGOUT</a>
</li>
{% endif %}
</ul>
</nav>
{% block content%}
{% endblock%}
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
</body>
</html>
Now we need to create our models. For the project we are going to have three models, a Product model, a Category and a Sale model.
from django.db import models
class Category(models.Model):
name = models.CharField(max_length = 50, null = True, blank = True)
def __str__(self):
return self.name
class Product(models.Model):
category_name = models.ForeignKey(Category, on_delete = models.CASCADE,null = True, blank = True )
item_name = models.CharField(max_length = 50, null = True, blank = True)
total_quantity = models.IntegerField(default = 0, null = True, blank = True)
issued_quantity = models.IntegerField(default = 0, null = True, blank = True)
received_quantity = models.IntegerField(default = 0, null = True, blank = True)
unit_price = models.IntegerField(default = 0, null = True, blank = True)
def __str__(self):
return self.item_name
class Sale(models.Model):
item = models.ForeignKey(Product, on_delete = models.CASCADE)
quantity = models.IntegerField(default = 0, null = True, blank = True)
amount_received = models.IntegerField(default = 0, null = True, blank = True)
issued_to = models.CharField(max_length = 50, null = True, blank = True)
unit_price = models.IntegerField(default = 0, null = True, blank = True)
def __str__(self):
return self.item.item_name
Within 'views.py'
from django.shortcuts import render
from product_app.models import Product
def home(request):
products = Product.objects.all().order_by('-id')
return render(request,
'products/index.html',
{'products': products})
In 'index.html',
{% extends 'products/base.html'%} {% block content%}
<br></br>
<div class = "container">
<h1 class= "text-center">ITEMS IN STOCK</h1>
<br>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Category</th>
<th scope="col">Item Name</th>
<th scope="col">Quantity</th>
<th scope="col">Unit Price(₦)</th>
<th scope = "col">Detail</th>
</tr>
</thead>
{% for product in products%}
<tbody>
<tr>
<td>{{product.category_name}}</td>
<td>{{product.item_name}}</td>
<td>{{product.total_quantity}}</td>
<td>₦{{product.unit_price}}</td>
<td><a href="#"
><input
type="submit"
value="BUY ITEM"
class="btn btn-secondary btn"
type="button"
/></a></td>
</tr>
</tbody>
{% endfor %}
</table>
</div>
{% endblock%}
This is what my app looks now:
Next we Build out the product detail page,
In product_app.urls,
urlpatterns = [
path('home/<int:product_id>/', views.product_detail, name='product_detail'),
]
Next, Create a product_detail view:
def product_detail(request, product_id):
product = Product.objects.get(id = product_id)
return render(request,
'products/product_detail.html',
{'product': product})
product_detail.html
{% extends 'products/base.html'%} {% block content%}
<br></br>
<div class = "container">
<a href="{% url 'issue_item' product.id %}" ><input type="submit" value="Order Item" class="btn btn-secondary" btn type="button"/></a>
<a href="{% url 'add_to_stock' product.id %}" ><input type= "submit" value="Add Item" class="btn btn-secondary btn" type="button"/></a>
</div>
<br></br>
<div class = "container">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Category</th>
<th scope="col">Item Name</th>
<th scope="col">Quantity</th>
<th scope="col">Unit Price(₦)</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{product.category_name}}</td>
<td>{{product.item_name}}</td>
<td>{{product.total_quantity}}</td>
<td>₦{{product.unit_price}}</td>
</tr>
</tbody>
</table>
</div>
{% endblock%}
Lastly add the 'href' attribute to the button within the product page that links to the product detail page:
<a href="{% url 'product_detail' product.id %}">
<input
type="submit"
value="BUY ITEM"
class="btn btn-secondary btn"
type="button"
/>
</a>
Now within the product detail page, we want to add two buttons, one for ordering the item and another for replenishing stock. Both lead to different forms.
Within product_detail.html,
<div class = "container">
<a href="{% url 'issue_item' product.id %}" ><input type="submit" value="Order Item" class="btn btn-secondary" btn type="button"/></a>
<a href="{% url 'add_to_stock' product.id %}" ><input type= "submit" value="Add Item" class="btn btn-secondary btn" type="button"/></a>
</div>
Next we create the url patterns,
urlpatterns = [
path('issue_item/<str:pk>/', views.issue_item, name='issue_item'),
path('add_to_stock/<str:pk>/', views.add_to_stock, name='add_to_stock'),
]
Now we want to define an add_to_stock form and a sales form. Create a 'forms.py' file within 'product_app'. Next, create an AddForm which uses the 'received_quantity' field earlier defined in our Product model. Finally create a SaleForm that uses the Sale model and inherits the 'quantity, amount_received and issued_to' fields.
from django.forms import ModelForm
from .models import *
class AddForm(ModelForm):
class Meta:
model = Product
fields = ['received_quantity']
class SaleForm(ModelForm):
class Meta:
model = Sale
fields = ["quantity", "amount_received", "issued_to"]
Lastly we need to create our views
in views.py
from product_app.forms import AddForm, SaleForm
def issue_item(request, pk):
issued_item = Product.objects.get(id = pk)
sales_form = SaleForm(request.POST)
if request.method == 'POST':
if sales_form.is_valid():
new_sale = sales_form.save(commit=False)
new_sale.item = issued_item
new_sale.unit_price = issued_item.unit_price
new_sale.save()
#To keep track of the stock remaining after sales
issued_quantity = int(request.POST['quantity'])
issued_item.total_quantity -= issued_quantity
issued_item.save()
return redirect('receipt')
return render (request, 'products/issue_item.html',
{
'sales_form': sales_form,
})
def add_to_stock(request, pk):
issued_item = Product.objects.get(id = pk)
form = AddForm(request.POST)
if request.method == 'POST':
if form.is_valid():
#To add to the remaining stock quantity is reducing
added_quantity =
int(request.POST['received_quantity'])
issued_item.total_quantity += added_quantity
issued_item.save()
return redirect('home')
return render (request, 'products/add_to_stock.html', {'form': form})
issue.item.html
{% extends 'products/base.html'%}
{% load crispy_forms_tags%}
{% load static %}
{% block content%}
<br/>
<div class="container">
<form method="POST" action = "">
<h1 class = "text-center
">SALES FORM</h1><br>
{% csrf_token %}
{{sales_form | crispy}}
<input type = "submit" class="btn btn-secondary btn"type="button">
</form>
</div>
{% endblock %}
add_to_stock.html
{% extends 'products/base.html'%}
{% load crispy_forms_tags%}
{% load static %}
{% block content%}
<br/>
<div class="container">
<form method="POST" action = "">
<h1 class = "text-center
">SALES FORM</h1><br>
{% csrf_token %}
{{form | crispy}}
<input type = "submit" class="btn btn-secondary btn"type="button">
</form>
</div>
{% endblock %}
Now we need to build out the receipt page. In urls.py,
urlpatterns = [
path('receipt/', views.receipt, name = "receipt"),
]
To carry out the logic for calculating the total cost of each item based on the unit price and the quantity, we need to create a function 'get_total' within our sales model to handle this. Also another function 'get_change' calculates the balance received by each customer by subtracting the total cost from the amount received. Finally, call both functions within our receipt template.
class Sale(models.Model):
item = models.ForeignKey(Product, on_delete = models.CASCADE)
quantity = models.IntegerField(default = 0, null = True, blank = True)
amount_received = models.IntegerField(default = 0, null = True, blank = True)
issued_to = models.CharField(max_length = 50, null = True, blank = True)
unit_price = models.IntegerField(default = 0, null = True, blank = True)
def get_total(self):
total = self.quantity * self.item.unit_price
return int(total)
def get_change(self):
change = self.get_total() - self.amount_received
return abs(int(change))
def __str__(self):
return self.item.item_name
Within views.py
def receipt(request):
sales = Sale.objects.all().order_by('-id')
return render(request,
'products/receipt.html',
{'sales': sales,
})
receipt.html
{% extends 'products/base.html'%} {% block content%}
<br /><br>
<div class="container">
<h1 class="card-header text-center">ALL RECEIPTS</h1><br>
{% for sale in sales %}
<div class="card">
<div class="card-body">
<h3 class="card-title">Customer : {{sale.issued_to | title}}</h3>
<h6 class="card-text">Item : {{sale.item}}</h6>
<h6 class="card-text">Amount Paid: ₦{{ sale.amount_received }}</h6>
<h6 class="card-text">Quantity: {{sale.quantity }}</h6>
<h2>Total Price : ₦{{ sale.get_total }}</h2>
<div style="flex: 1">
<h6 class="card-text">Change Collected: ₦{{ sale.get_change }}</h6>
<a href="{% url 'receipt_detail' sale.id %}">
<input
type="submit"
value="Final Receipt"
class="btn btn-danger"
type="button"
/>
</a>
</div>
</div>
</div>
<br />
{% endfor %}
</div>
{% endblock %}
The receipt page:
Then lastly,we have a receipt detail page that gives the order summary for each specific item:
We have made great progress so far with our app and now the last part is to create a sales table to keep track and sum up all the total sales made, total amount received and the total change given throughout a particular period in review.
In urls.py,
urlpatterns = [
path('all_sales/', views.all_sales, name = 'all_sales')
]
in views.py
def all_sales(request):
sales = Sale.objects.all()
total = sum([items.amount_received for items in sales])
change = sum([items.get_change() for items in sales])
net = total - change
return render(request, 'products/all_sales.html',
{
'sales': sales,
'total': total,
'change': change,
'net': net,
})
We sum up all the total amount received and total change given within our 'all_sale' method and pass the value into our context.
all_sales.html
{% extends 'products/base.html'%} {% block content%}
<br></br>
<div class = "container">
<h1 class = "text-center">TOTAL SALES MADE</h1><br>
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Total Amount Received</th>
<th scope="col">Total Change Given</th>
<th scope="col">Net Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td>₦{{total}}</td>
<td>₦{{change}}</td>
<td>₦{{net}}</td>
</tr>
</tbody>
</table>
</div>
<br></br>
<div class = "container">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Customer name</th>
<th scope="col">Item Bought</th>
<th scope="col">Quantity</th>
<th scope="col">Unit Price(₦)</th>
<th scope="col">Total Cost</th>
<th scope="col">Amount Paid</th>
<th scope="col">Change Collected</th>
</tr>
</thead>
{% for sale in sales %}
<tbody>
<tr>
<td>{{sale.issued_to | title}}</td>
<td>{{sale.item | title}}</td>
<td>{{sale.quantity}}</td>
<td>₦{{sale.unit_price}}</td>
<td>₦{{sale.get_total}}</td>
<td>₦{{sale.amount_received}}</td>
<td>₦{{sale.get_change}}</td>
</tr>
</tbody>
{% endfor %}
</table>
</div>
{% endblock%}
This is a final view of what our sales page looks now.
You can get the complete code on this url:
https://github.com/yahayakenny/Pharmacy-inventory-manager
Live site: https://pharm-plus.herokuapp.com/
Test the app:
username: "pharmacy"
password: "1234"
Top comments (10)
A really great job! It helps a lot.
I take this opportunity to ask you for advice on putting Django projects into production. You use Keroku but they tell me the cost over time is quite high. Other solutions?
Hello Yahaya!
Brilliant tutorial!
I was actually looking out to see product quantity deduction when sale is made and add to product quantity when add to product (purchase) is made.
Overall kudos.
Hi Fredrick. Yes that’s a key functionality of the app. I’m glad you noticed. It’s being handled within the issue_item and add_to_stock views. Thank you so much for taking your time to go through it and for the encouraging comment ☺️
Awesome! Seems like I've gotten myself a Python Django coach. 🙁👀
Hahah. Bro! I’m still largely a beginner too though but I’m sure we could learn a few things together 👌
Excelente man.. I was looking such explanation (
my first view on this app was fantastic, God bless you!
Thank you Jimmy☺️
You too 🙏🏾
Awesome knoledge sharing! Pleasure to read!
it does not run this code
Some comments may only be visible to logged-in visitors. Sign in to view all comments.