Skip to content

AbdelrahmanRewaished/El7a2ny-Pharmacy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

El7a2ni Pharmacy

A web application which allows Pharmacists to manage their medicine stock, Patients to order medications, and Admins to manage the users and system as a whole.

It is part of the larger El7a2ni Healthcare Platform, which also includes the El7a2ni Clinic web application.

Table of Contents

Badges

MongoDB Express.js ReactJS NodeJS Jira TypeScript MUI Git GitHub JWT Jest

Motivation

Bringing convenience, speed, and ease of use to the forefront of healthcare is our primary objective.

We aim to simplify the way patients order medications and have it delivered to their doorstep through an extremely intuitive and fast process, acknowledging the importance of being precise in situations of urgency where medication is needed as fast as possible with no time to try to figure out how to use the system, all while not compromising on the quality of the system in any regard. We also provide the necessary tools for Pharmacists and Admins to operate the system effectively and efficiently with these same principles in mind.

We eliminate any and all visual clutter that most Pharmacy systems right now suffer from, and simplify the processes as much as possible, making it possible to learn and use the system instantly.

Build Status

  • The project is currently in development.
  • A CI/CD pipeline needs to be implemented.
  • Testing needs to be implemented for the backend using Jest.
  • Authorization middleware needs to be fully added to the API endpoints.
  • Additional authorization middleware/checks need to be implemented to make sure patients can only cancel orders that belong to them, for example.
  • Backend routes need to be reorganized.
  • Backend service layer is not fully implemented.
  • Database querying needs to be optimized and security considerations must be made with regards to the data being returned.
  • API calls in the frontend should be separated from the actual pages.
  • Form validation and more loading indicators need be implemented in a more robust, consistent and reusable way in the frontend, perhaps using React Query.
  • Vitest testing needs to be set up for the API.
  • Pagination is needed for Medicine List, Patient List, Admin List, Pharmacist List, and Notifications.

Code Style

The code style is enforced using prettier. You can find the code formatting rules in the .prettierrc file.

Project Management

Jira was used as our main issue tracking and project management tool for creating this software.

Jira Jira Jira

Screenshots

Landing page

Landing page Landing page Landing page Landing page Landing page

Patient login

Patient login

Patient dashboard

Patient dashboard

Medicine catalog (for patients)

Medicine catalog

Checkout and payment

Checkout Checkout Checkout

Pharmacist login

Pharmacist login

Pharmacist dashboard

Pharmacist dashboard

Pharmacist chat

Pharmacist chat Pharmacist chat

Pharmacist application email

Pharmacist application email

Admin checking patient details

Patient list

Admin reviewing pharmacist registration request

Pharmacist registration request

Tech/Framework used

This software uses the MERN technology stack. Here is a more comprehensive list of used technologies in this software:

Features

The system has five user types: Guest, Patient, Pharmacist, Doctor and Admin.

Patient features
  • Order over the counter or prescription medicines and pay with credit card, e-wallet or cash on delivery.
  • Track past and current orders, cancel ongoing orders, and receive money-back to e-wallet for orders made with e-wallet or credit card.
  • Create an e-wallet on the system with a PIN code and recharge it with credit card.
  • Change my password or reset forgotten password.
  • Chat with pharmacists.
Pharmacist features
  • Add a new medicine to the system.
  • Update existing medicines' details, prices and upload medicine images.
  • Change my password or reset forgotten password.
  • Chat with patients or doctors.
  • View total sales report
Admin features
  • Add an admin to the system.
  • View and Delete all admins
  • Accept or reject a pharmacist registration request.
  • View a patient or pharmacist's details and remove them from the system.
  • View medicines on the system.
  • View total sales report
Doctor features
  • Add or remove medicines to/from a patient's prescription from the pharmacy platform by logging in to an integrated portal for it.
Guest features
  • Sign up as a patient.
  • Submit a registration request as a pharmacist and go through a registration process.

Code Examples

Get items in cart
export const getCartItems = async (req: AuthorizedRequest, res: Response) => {

  try {
    const userId = req.user?.id;

    const patient = await Patient.findById(userId).select("subscribedPackage cart").populate({
      path: "cart.medicineId",
      select: "_id name price pictureUrl"
    });

    if (!patient) {
      return res.status(StatusCodes.NOT_FOUND).json({ message: "Patient not found" });
    }

    if (!patient.cart) {
      return res.status(StatusCodes.NOT_FOUND).json({ message: "Cart not found" });
    }

    let discount = 0;

    if (patient.subscribedPackage && patient.subscribedPackage.status === "subscribed") {
      const healthPackage = await HealthPackage.findById(patient.subscribedPackage.packageId);

      if (healthPackage) {
        discount = healthPackage.discounts.gainedPharmacyMedicinesDiscount;
      }
    }

    const cartItemsWithAppliedDiscount = patient.cart.map((item: any) => {
      const discountedPrice = parseFloat((item.medicineId.price * (1 - discount)).toFixed(2));

      return {
        medicineId: {
          _id: item.medicineId._id,
          name: item.medicineId.name,
          price: discountedPrice,
          pictureUrl: item.medicineId.pictureUrl
        },
        quantity: item.quantity
      };
    });

    return res.json(cartItemsWithAppliedDiscount);
  } catch (err) {
    return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: (err as Error).message });
  }
};
Create an order
export const createOrder = async (req: Request, res: Response) => {
  try {
    const { patientId, patientName, patientAddress, patientMobileNumber, medicines, paidAmount, paymentMethod } =
      req.body;

    const exceedingAvailableQuantityMedicines = [];

    for (const medicine of medicines) {
      const dbMedicine = await Medicine.findById(medicine.medicineId);

      if (!dbMedicine) {
        return res.status(StatusCodes.BAD_REQUEST).json({
          message: `Medicine with id ${medicine.medicineId} not found`
        });
      }

      if (medicine.quantity > dbMedicine.availableQuantity) {
        exceedingAvailableQuantityMedicines.push(dbMedicine.name);
      }
    }

    if (exceedingAvailableQuantityMedicines.length > 0) {
      return res.status(StatusCodes.BAD_REQUEST).json({
        message: `The following medicines are out of stock or do not have enough available quantity: ${exceedingAvailableQuantityMedicines.join(
          ", "
        )}. Please go back to your cart and adjust the quantities or remove these items`
      });
    }

    const newOrder = new Order({
      patientId,
      patientName,
      patientAddress,
      patientMobileNumber,
      medicines,
      paidAmount,
      paymentMethod
    });

    const savedOrder = await newOrder.save();

    return res.status(StatusCodes.CREATED).json(savedOrder);
  } catch (err) {
    console.log(err);
    return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: (err as Error).message });
  }
};
Patient change password
export const changePatientPassword = async (req: AuthorizedRequest, res: Response) => {
  try {
    const { currentPassword, newPassword, confirmPassword } = req.body;
    const patientId = req.user?.id!;
    const patient = await findPatientById(patientId);

    if (!patient) {
      return res.status(StatusCodes.NOT_FOUND).json({ message: "Patient not found" });
    }

    const isPasswordCorrect = await patient.verifyPassword?.(currentPassword);
    if (!isPasswordCorrect) {
      return res.status(StatusCodes.BAD_REQUEST).json({ message: "Old password is incorrect" });
    }

    await updatePatientPasswordById(patientId, newPassword);
    return res.status(StatusCodes.OK).json({ message: "Password updated successfully!" });
  } catch (error) {
    console.error(error);
    res.status(StatusCodes.BAD_REQUEST).json({ message: "An error occurred while updating the password" });
  }
};
Update medicine quantities (after an order is created)
export const bulkUpdateMedicineQuantities = async (req: AuthorizedRequest, res: Response) => {
  try {
    const updates = req.body;

    const bulkOps = updates.map((update: any) => ({
      updateOne: {
        filter: { _id: update.medicineId },
        update: { $inc: { availableQuantity: -update.boughtQuantity } }
      }
    }));

    await Medicine.bulkWrite(bulkOps);

    res.status(200).send();
  } catch (err) {
    res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: (err as Error).message });
  }
};
Search pharmacists
export const searchPharmacists = async (req: Request, res: Response) => {
  try {
    const username = req.query.username as string;
    const email = req.query.email as string;

    const pharmacists: IPharmacistModel[] =
      (!username || username.length === 0) && (!email || email.length === 0)
        ? await Pharmacist.find()
        : !username || username.length === 0
        ? await Pharmacist.find({ email: { $regex: email, $options: "i" } })
        : await Pharmacist.find({
            username: { $regex: username, $options: "i" }
          });

    if (pharmacists.length === 0) {
      return res.status(StatusCodes.NOT_FOUND).json({ message: "No pharmacists found" });
    }
    res.status(StatusCodes.OK).json(pharmacists);
  } catch (err) {
    res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: (err as Error).message });
  }
};
Authenticate user
export const authenticateUser = (req: AuthorizedRequest, res: Response, next: NextFunction) => {
  const authHeader = req.headers.authorization;
  if (!authHeader) {
    console.log("Authorization header is missing");
    return res.status(StatusCodes.UNAUTHORIZED).json({ message: "Authorization header is missing" });
  }

  const accessToken = authHeader.split(" ")[1];
  if (!accessToken) {
    console.log("Access token is missing");
    return res.status(StatusCodes.UNAUTHORIZED).json({ message: "Access token is missing" });
  }

  try {
    const decodedUserData = verifyAndDecodeAccessToken(accessToken);
    req.user = decodedUserData;
    next();
  } catch (error: any) {
    if (error.name === "TokenExpiredError") {
      console.log("Access token has expired");
      return res.status(StatusCodes.UNAUTHORIZED).json({
        message: "Access token has expired",
        accessTokenExpired: true
      });
    }
    console.log("Access token is invalid");
    res.status(StatusCodes.UNAUTHORIZED).json({ message: "Access token is invalid" });
  }
};
Send email
import config from "../config";
import { google } from "googleapis";
import nodemailer from "nodemailer";

const { user, clientId, clientSecret, redirectUri, refreshToken } = config.server.emailServiceCredentials;
const oAuth2Client = new google.auth.OAuth2(clientId, clientSecret, redirectUri);
oAuth2Client.setCredentials({ refresh_token: refreshToken });

type MailOptions = {
  to: string;
  subject: string;
  text: string;
  html?: string;
  from?: string;
};

export const sendEmail = async (mailOptions: MailOptions) => {
  const accessToken = await oAuth2Client.getAccessToken();
  const transport = nodemailer.createTransport({
    service: "gmail",
    auth: {
      type: "OAuth2",
      user,
      clientId,
      clientSecret,
      refreshToken,
      accessToken
    }
  } as nodemailer.TransportOptions);
  const mailRequiredOptions = {
    from: mailOptions.from || `El7a2ny <${user}>`,
    to: mailOptions.to,
    subject: mailOptions.subject,
    text: mailOptions.text,
    html: mailOptions.html || `<h2>${mailOptions.text}</h2>`
  };
  const result = await transport.sendMail(mailOptions);
  return result;
};

Installation

To install the project with npm, run the following commands in order.

> git clone https://github.com/advanced-computer-lab-2023/Code-of-Duty2-Pharmacy.git
> cd Code-of-Duty2-Pharmacy/
> cd server
> npm i
> cd ../client
> npm i
> cd ..

API Reference

Please refer to the api-reference.md file under the docs directory for a comprehensive understanding of the API’s capabilities and usage guidelines.

Tests

Testing was done for this software using Postman.

Postman

How to Use

Please follow the following steps to run and use this software.

Set Environment Variables

To use this software, you must first add your environment variables. To do so, you must create two .env files, one directly inside the server/ folder and the other directly inside the client/ folder.

In each of those directories, you will find a .env.example file. You must copy its content and paste in your newly created .env files respectively. You must then fill in the values needed for each environment variable, with guidance on how to do so provided in each .env.example file.

Note: Some (and only some) environment variables can be ignored for the sake of ease of setup at the cost of the operation of one or two features, such as the Stripe API environment variables which will just prevent the credit card payment feature from working, however the rest of the system will work just fine.

Run the Backend

> cd server
> npm run dev

Run the Frontend

> cd client
> npm run dev

Access the client/server on the specified ports

The client and the server will be running on the ports specified in your .env files respectively.

Authors

From the Pharmacy Team

From the Clinic Team

Contribute

Contributions are always welcome!

See CONTRIBUTING.md for ways to get started. Please adhere to the Code of Conduct for this software outlined in said document.

Credits

Documentation

Tutorials

Websites and Tools

Courses

Articles

Books

Project Repositories

License

This project is licensed under the terms of the Apache 2.0 license. For more details, see the LICENSE file in the root of this repository.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages