El7a2ni Pharmacy is a virtual online Pharmacy platform that enables patients and pharmacists to communicate and streamline the usually tedious process of buying medicines
This project was motivated by the very dire need for a more accessible way of buying medicines and contacting pharmacists unlike what is usually present in the real world. Most traditional pharmacies are usually limited by how many patients they can serve at the same time and how many of the staff is able to work on any given day. Most tasks have the ability to be automated and streamlined using software solutions that will subsequently increase the capacity of pharmacies to serve more patients and the ability of patients to communicate with more pharmacists effeciently.
Currently the system is working as expected, all functionalities including the more sophisticated ones like video calling, live chatting, booking visits and downloading prescriptions are running with no known issues. Currently if any user is using a Chromium based web browser; they will need to run a custom command through termninal on their machine:
The code style is enforced using eslint and prettier, both are plugins installed in VS-Code to manage the overall code cleanliness and follow usual conventions in web development.
Prettier link: Prettier.
Eslint link: Eslint.
React | |
Node.js | |
JWT | |
Express | |
MongoDB | |
Mongoose | |
Material-UI | |
Stripe | |
Git | |
Github Actions | |
MongoDB Atlas | |
Postman | |
VSCode | |
JavaScript |
⚠️ : Note: Click to expand.
As a Guest user, you can:
- register as a patient using username, name, email, password, date of birth, gender, mobile number, emergency contact (full name , mobile number, relation to the patient)
- submit a request to register as a pharmacist using username, name, email, password, date of birth, hourly rate, affiliation (hospital), educational background
As a Administrator, you can:
- view a list of all available medicines with all the relevant details
- search for a medicine based on name or medical usage.
- view a sales report based on a chosen month.
- login using your username and password
- logout
- add another adminstrator with a set username and password.
- remove a pharmacist/patient from the system.
- view all of the information uploaded by a pharmacist to apply to join the platform.
- accept or reject the request of a pharmacist to join the platform.
- change my password
- reset a forgotten password through OTP sent to email
- view a pharmacist's information.
- view a patients's basic information.
As a Pharmacist, you can:
- view a list of all available medicines with all the relevant details
- search for a medicine based on name or medical usage.
- add or edit a medicine with all its relevant details.
- view a sales report based on a chosen month and can further filter it based on medicine/date.
- view his/her wallet balance.
- recevice a notification when a medicine is out of stock via email.
- chat with a Doctor in the clinic.
- chat with a Patient in the pharmacy
- upload and submit required documents upon registration such as ID, pharmacy degree anf Working licenses
- login using your username and password
- logout
- change my password
- reset a forgotten password through OTP sent to email
As a Patient, you can:
- view a list of all available medicines with all the relevant details
- search for a medicine based on name or medical usage.
- add medicines to his/her cart if he is eligible to take it.
- view the medicines in the cart.
- remove or change the quantity of an item in the cart.
- checkout his/her order.
- add a new delivery address or choose from his previous delivery addresses.
- view his/her wallet balance.
- pay for his order using his/her wallet balance/credit card/Cash on delivery.
- view his/her current and past order details and its status.
- cancel his/her order.
- view alternative medicines for the medicines currently out of stock.
- chat with a Pharmacist in the pharmacy.
- login using your username and password
- logout
- change my password
- reset a forgotten password through OTP sent to email
Project structure
Get Cart of Patient
const getCart = async (req, res) => {
const { username } = req.body;
try {
const patient = await Patient.findOne({ username: username });
if (patient != null) {
return res
.status(200)
.json({ success: true, message: "Cart returened", data: patient.cart });
} else {
return res
.status(404)
.json({ success: false, message: "Patient not found", data: null });
}
} catch (error) {
const reply = {
success: false,
data: null,
message: error.message,
};
return res.status(500).json(reply);
}
};
Remove Medicine From Cart
const removeMedicine = async (req, res) => {
const { userName, medicineId } = req.body;
console.log(userName, medicineId);
try {
const patient = await Patient.findOne({ username: userName });
if (patient != null) {
const cart = patient.cart.medicines;
const newCart = cart.filter(
(medicine) => medicine.medicine_id != medicineId
);
patient.cart.medicines = newCart;
await patient.save();
return res
.status(200)
.json({
success: true,
message: "Medicine removed",
data: patient.cart,
});
} else {
return res
.status(404)
.json({ success: false, message: "Patient not found", data: null });
}
} catch (error) {
return res
.status(500)
.json({ success: false, message: error.message, data: null });
}
};
Get Patient
const getPatient = async (req, res) => {
const { username } = req.body;
try {
const patient = await Patient.findOne({ username: username });
if (patient != null) {
return res
.status(200)
.json({ success: true, message: "Patient returened", data: patient });
} else {
return res
.status(404)
.json({ success: false, message: "Patient not found", data: null });
}
} catch (error) {
const reply = {
success: false,
data: null,
message: error.message,
};
return res.status(500).json(reply);
}
};
Clear cart of a patient
const clearCart = async (req, res) => {
const { username } = req.body;
try {
const patient = await Patient.findOne({ username: username });
if (patient == null) {
return res
.status(404)
.json({ success: false, message: "Patient not found", data: null });
} else {
patient.cart = {};
await patient.save();
return res
.status(200)
.json({ success: true, message: "Cart Cleared", data: patient.cart });
}
} catch (error) {
return res
.status(500)
.json({ success: false, message: error.message, data: null });
}
};
⚠️ : Note: Click to expand.
Patient Routes (/patient)
router.post('/getPatient' , patientController.getPatient);
fetches a specific patient
router.post('/cart',patientController.getCart)
Fetch the user's cart
router.post('/removeMedicineFromCart' , patientController.removeMedicine)
Removes a medicine from the user's cart
router.post('/addAddress' , patientController.addAddressToPatient);
adds an address to the list of addresses of the user
router.post('/addOrder' , patientController.addOrderToPatient);
adds an order to the list of orders for the patient
router.post('/deleteOrder' , patientController.deleteOrder);
deletes an order for the user
router.post('/clearCart',patientController.clearCart);
Removes all medicines in the user's cart
router.post('/saveCart',patientController.saveCart);
takes all the chosen medicinces by the user and add them to the cart
router.post('/getPrescription/sendPrescriptionMedicinesToPharmacy',patientController.getMedicinesFromClinc);
returns all medicines that are in the prescription of this patient.
router.post('/addAlternative',patientController.addToCart);
adds a chosen alternative medicine in the cart
pharmacist Routes (/pharmacist/medicines)
router.post('/',upload.single('image'),AddMedicine);
Create new medicine
router.get('/',getMedicines);
Gets all medicines
router.get('/:id',medicinedetailsbyid);
gets all medicine details (by id of that medicine)
router.patch('/:id', updateMedicine);
updates the details of a specific medicine
router.post('/archive',archiveMedicine);
archive/unarchive a specific medicine
router.post('/viewAlternative',getAlternativeMedicines)
gets all alternatives for a specific medicine based on the main ingredient
router.post('/getWalletBalance',getWalletBalancepharmacist);
retrieves the wallet balance of a specific pharmacist
patient extra Routes (/medicine)
router.get('/' , medicineController.getAllMedicine)
Fetches all medicines
router.get('/searchByName/:name?' , medicineController.getMedicineByName)
fillter on the medicines by a specific name
router.get('/searchByMedial_use/:medical_use?' , medicineController.getMedicineByMedicalUse)
fillter on the medicines by a specific medical usage
router.post('/addToCart/:userName/:medicineId/:quantity' , medicineController.AddToCart);
Adds the selected medicine to the patient cart
router.post('/getArrayMedicinesByID',medicineController.getArrayOfMedicine);
returns the medicines in the cart of the patient
Doctor Routes (/doctor)
router.post("/createPatient", submitPrescriptionToPharmacy);
The doctor creates an account for the patient and puts the medicine in cart if the patient does not have an account otherwise puts the medicine in the cart directly
router.get("/chat/getPatients/", getPatients);
fetches a list of all patients that the pharmacist can chat with.
router.get("/chat/getPharmacist/", getPharmacist);
fetches a list of all pharmacist that a doctor or a patient can chat with.
Admin Routes (/admin)
router.post("/getAdmin/:username")
Gets an admin with a specified username
router.get("/viewAdmins")
Views all admins
router.post("/createAdmin")
Creates an admin and adds it to the DB
router.delete("/removeAdmin/:username")
Removes the admin with the specified username from the DB
router.get("/viewPharmacistInfo/", viewPharmacistInfo);
Allows an admin to view the pharmacist information
router.delete("/removePharmacist/:username", removePharmacist);
Allows an admin to remove pharmacist with a specified username from the system
router.get("/viewPatientInfo/", viewPatientInfo);
Allows an admin to view all registered patients on the system
router.delete("/removePatient/:username")
Allows an admin to remove a patient with the specified username from the system
router.get("/viewPharmacistRequests", viewPharmacistRequests);
Allows admin to view all pending pharmacist applications.
router.get('/orders' ,viewallorders)
Allows an admin to view all orders in the system
router.get('/ordersbymonth' ,viewallordersbymonth)
Allows an admin to view orders in a specific month
router.get("/medicines", getMedicines);
Allows an admin to view all medicines in the system
router.get("/findMedicine", findMedicine);
Allows an admin to get a specific medicine based on the medicine name
router.post("/acceptPharmacist", acceptPharmacist);
allows an admin to accept a pharmacist application.
router.post("/rejectPharmacist", rejectPharmacist);
Allows an admin to reject a pharmacist application.
Account Router (/account)
router.post("/registerPatient")
Registers a new patinent to the platorm
router.post("/registerDoctor")
Registers a new doctor to the platform
router.post("/login")
Allows for the login functionality
router.get("/logout")
allows for the logout functionality
router.post("/forgotPassword")
router.post("/resetPassword")
allows an account to reset their password in case they forgot it
router.post("/verifyOTP")
Allows for OTP verification
We used Postman to test our different API endpoints.
Element | Input |
---|---|
Test API: | http://localhost:8001/patient/cart |
Test Body JSON: | { "username":"omarS"} |
{
"success": true,
"message": "Cart returened",
"data": {
"medicines": [
{
"medicine_id": "6526d02ddc980ba82a4a62bb",
"quantity": 2,
"_id": "657e34c15228f58a6f07d83d"
}
],
"netPrice": 40,
"totalPrice": 40
}
}
Element | Input |
---|---|
Test API: | http://localhost:8001/patient/removeMedicineFromCart |
Test Body JSON: | {"userName":"omarS","medicineId":"6526d02ddc980ba82a4a62bb"} |
{
"success": true,
"message": "Medicine removed",
"data": {
"medicines": [],
"netPrice": 40,
"totalPrice": 40
}
}
Element | Input |
---|---|
Test API: | http://localhost:8001/patient/getPatient |
Test Body JSON: | { "username":"omarS" } |
{
"success": true,
"message": "Patient returened",
"data": {
"emergencyContact": {
"fullName": "khaled",
"mobileNumber": "012321321321",
"relationToPatient": "son"
},
"cart": {
"medicines": [],
"netPrice": 40,
"totalPrice": 40
},
"_id": "657dcc49774ada6dbb291d57",
"username": "omarS",
"name": "omar shaaban",
"email": "omarS@gmail.com",
"password": "$2b$10$kh6VAn5iOKgLlg.nVCAPAOagTUqg7TYYRq0BHtujFHxPzNT3qyQO2",
"dateOfBirth": "2023-11-28T00:00:00.000Z",
"gender": "M",
"mobileNumber": "01232322323",
"deliveryAddresses": [
{
"streetName": "nour",
"propertyNum": 12,
"floorNum": 15,
"apartNum": 15,
"extraLandMarks": "LandM",
"_id": "657dcf48774ada6dbb291e86"
}
],
"orders": [
{
"cart": {
"medicines": [
{
"medicine_id": "6526d02ddc980ba82a4a62bb",
"quantity": 2,
"_id": "657dd05f673a216be3b3b31d"
}
]
},
"deliveryAddress": {
"streetName": "nour",
"propertyNum": 12,
"floorNum": 15,
"apartNum": 15,
"extraLandMarks": "LandM"
},
"status": "cancelled",
"date": "2023-12-16T16:29:24.151Z",
"paymentMethod": "wallet",
"discount": 0,
"netPrice": 40,
"_id": "657dd064673a216be3b3b372"
},
{
"cart": {
"medicines": [
{
"medicine_id": "6526d02ddc980ba82a4a62bb",
"quantity": 3,
"_id": "657ddf4ef9b20f3d2a13e7f4"
}
]
},
"deliveryAddress": {
"streetName": "nour",
"propertyNum": 12,
"floorNum": 15,
"apartNum": 15,
"extraLandMarks": "LandM"
},
"status": "cancelled",
"date": "2023-12-16T17:48:32.453Z",
"paymentMethod": "cash",
"discount": 0,
"netPrice": 60,
"_id": "657de2f0f9b20f3d2a13ea59"
},
{
"cart": {
"medicines": [
{
"medicine_id": "6526d02ddc980ba82a4a62bb",
"quantity": 2,
"_id": "657df1034788ad4e11ef76ac"
}
]
},
"deliveryAddress": {
"streetName": "nour",
"propertyNum": 12,
"floorNum": 15,
"apartNum": 15,
"extraLandMarks": "LandM"
},
"status": "cancelled",
"date": "2023-12-16T18:48:41.179Z",
"paymentMethod": "cash",
"discount": 0,
"netPrice": 40,
"_id": "657df1094788ad4e11ef7755"
},
{
"cart": {
"medicines": [
{
"quantity": 2,
"_id": "657e2ed517813e9969dfaf75"
},
{
"quantity": 1,
"_id": "657e2ed517813e9969dfaf76"
}
]
},
"deliveryAddress": {
"streetName": "Main Street",
"propertyNum": 10,
"floorNum": 3,
"apartNum": 2,
"extraLandMarks": "Near the post office"
},
"status": "pending",
"date": "2023-12-16T00:00:00.000Z",
"paymentMethod": "card",
"discount": 10,
"netPrice": 50,
"_id": "657e2ed517813e9969dfaf74"
},
{
"cart": {
"medicines": [
{
"quantity": 2,
"_id": "657e2f2517813e9969dfaf85"
},
{
"quantity": 1,
"_id": "657e2f2517813e9969dfaf86"
}
]
},
"deliveryAddress": {
"streetName": "Main Street",
"propertyNum": 10,
"floorNum": 3,
"apartNum": 2,
"extraLandMarks": "Near the post office"
},
"status": "pending",
"date": "2023-12-16T00:00:00.000Z",
"paymentMethod": "card",
"discount": 10,
"netPrice": 50,
"_id": "657e2f2517813e9969dfaf84"
}
],
"__v": 19,
"walletBalance": 200
}
}
Element | Input |
---|---|
Test API: | http://localhost:8001/patient/clearCart |
Test Body JSON: | {"username":"omarS"} |
{
"success": true,
"message": "Cart Cleared",
"data": {}
}
Contributions are always welcome!
Please adhere to this project's code of conduct.
- Fork the repository
- Clone the repository
- Install dependencies
- Create a new branch
- Make your changes
- Commit and push your changes
- Create a pull request
- Wait for your pull request to be reviewed and merged
This project follows the Contributor Covenant Code of Conduct. Please read the full text so that you can understand what actions will and will not be tolerated.
⚠️ : Note: Click to expand.
Documentations:
Youtube Videos and Playlists:
MIT License
Copyright (c) 2023 Skill-IssueSquad
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.