DEV Community

loading...
Cover image for Build a Pharmacy Inventory manager with Django.

Build a Pharmacy Inventory manager with Django.

yahaya_hk profile image Yahaya Kehinde ・9 min read

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
Enter fullscreen mode Exit fullscreen mode

Change into the pharmacy_project directory and create a new app

cd pharmacy_project
python manage.py startapp product_app
Enter fullscreen mode Exit fullscreen mode

Add the newly created app into installed apps under 'settings.py'

INSTALLED_APPS = [
'product_app',
]
Enter fullscreen mode Exit fullscreen mode

In the root directory, create a 'templates' folder and add it within 'settings.py'

TEMPLATES = [
 'DIRS': [os.path.join(BASE_DIR, 'templates')],
]
Enter fullscreen mode Exit fullscreen mode

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')
]
Enter fullscreen mode Exit fullscreen mode

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'))
]
Enter fullscreen mode Exit fullscreen mode

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"),
]
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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})

Enter fullscreen mode Exit fullscreen mode

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%}

Enter fullscreen mode Exit fullscreen mode

This is what my app looks now:

Alt Text

Next we Build out the product detail page,

In product_app.urls,

urlpatterns = [
path('home/<int:product_id>/', views.product_detail, name='product_detail'),
]
Enter fullscreen mode Exit fullscreen mode

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})

Enter fullscreen mode Exit fullscreen mode

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%}

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

My product detail page:
Alt Text

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>
Enter fullscreen mode Exit fullscreen mode

Alt Text

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'),
]

Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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})

Enter fullscreen mode Exit fullscreen mode

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 %}

Enter fullscreen mode Exit fullscreen mode

Alt Text

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 %}

Enter fullscreen mode Exit fullscreen mode

Alt Text

Now we need to build out the receipt page. In urls.py,

urlpatterns = [
path('receipt/', views.receipt, name = "receipt"),
]
Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Within views.py

def receipt(request): 
    sales = Sale.objects.all().order_by('-id')
    return render(request, 
    'products/receipt.html', 
    {'sales': sales,
    })
Enter fullscreen mode Exit fullscreen mode

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 %} 
Enter fullscreen mode Exit fullscreen mode

The receipt page:

Alt Text

Then lastly,we have a receipt detail page that gives the order summary for each specific item:

Alt Text

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')
]

Enter fullscreen mode Exit fullscreen mode

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,
      })
Enter fullscreen mode Exit fullscreen mode

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%}
Enter fullscreen mode Exit fullscreen mode

This is a final view of what our sales page looks now.

Alt Text

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"

Discussion (8)

pic
Editor guide
Collapse
jaritekaplan profile image
JariteKaplan • Edited

I think that such services are necessary only for offline pharmacies. In this case, an online pharmacy has obvious advantages over an offline pharmacy. For example, I often came across a situation when pharmacies in my city did not sell the required medicine, but on the Canadian Pharmacy website it was. And the price for this medicine was lower.

Collapse
tonycletus profile image
Tony Cletus

Awesome! Seems like I've gotten myself a Python Django coach. 🙁👀

Collapse
yahaya_hk profile image
Yahaya Kehinde Author

Hahah. Bro! I’m still largely a beginner too though but I’m sure we could learn a few things together 👌

Collapse
jimmycoding profile image
jimmycoding

my first view on this app was fantastic, God bless you!

Collapse
yahaya_hk profile image
Yahaya Kehinde Author

Thank you Jimmy☺️
You too 🙏🏾

Collapse
fredrickughimi profile image
Fredrick Ughimi

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.

Collapse
yahaya_hk profile image
Yahaya Kehinde Author • Edited

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 ☺️

Collapse
tdhirendra12 profile image
Jesse

Excelente man.. I was looking such explanation (