Skip to content

Commit 5357724

Browse files
authored
Merge pull request #350 from MBSA-INFINITY/invoice-billing-system
BillSwift: Inventory Billing Management System (Flask Project)
2 parents 573bb1e + 5966dc8 commit 5357724

File tree

1,736 files changed

+145626
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,736 files changed

+145626
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.pyc
2+
/__pycache__
3+
.env
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<!--Please do not remove this part-->
2+
![Star Badge](https://img.shields.io/static/v1?label=%F0%9F%8C%9F&message=If%20Useful&style=style=flat&color=BC4E99)
3+
![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)
4+
5+
# BillSwift: Inventory Billing Management System
6+
7+
[Project Deployment Link](https://billswift.vercel.app/login)
8+
9+
## 🛠️ Description
10+
BillSwift is a comprehensive billing solution that empowers merchants to effortlessly create, generate, and manage bills, track inventory and products, and optimize their billing processes. With BillSwift, you can bid farewell to tedious paperwork and embrace a more efficient way of running your business.
11+
### 🌟 Key Features:
12+
- Intuitive Bill Creation: Easily create professional invoices allowing you to add your branding and personal touch.
13+
- Inventory Management: Keep track of your products and manage stock levels.
14+
- Bill Tracking: Monitor the status of your bills, know which ones are paid and pending, and never miss a payment again.
15+
16+
## ⚙️ Languages or Frameworks Used
17+
- Flask
18+
- Firebase (for Authentication)
19+
- MongoDB (for data storage)
20+
21+
## 🌟 How to run
22+
- ### Install all the requirements
23+
Run `pip install -r requirements.txt` to install all the requirements.
24+
- ### Firebase Setup for Project
25+
26+
- Create a [firebase](https://firebase.google.com/) project, set up a web project and get all the `Project Configurations` from `Project Settings`.
27+
28+
- Navigate to the **Authentication** section in your firebase project and enable the `Email and Password`
29+
authentication.
30+
31+
- The `Project Configurations` will look as follows :-
32+
```bash
33+
"apiKey": YOUR_API_KEY ,
34+
"authDomain": YOUR_AUTH_DOMAIN,
35+
"databaseURL": YOUR_DATABASEURL,
36+
"projectId": YOUR_PROJECT_ID,
37+
"storageBucket": YOUR_STORAGE_BUCKET,
38+
"messagingSenderId": YOUR_MESSAGING_SENDER_ID,
39+
"appId": YOUR_APP_ID,
40+
"measurementId": YOUR_MEASUREMENT_ID
41+
```
42+
- ### MongoDB Setup for Project
43+
44+
- Download monogdb from the [official website](https://www.mongodb.com/try/download/community) and setup in your local system for testing.
45+
- Once it is setup locally, try creating documents and collections in mongodb to familiarize yourself with it.
46+
- You can also download the `MongoDB Compass`, which is the GUI version of Mongo Shell.
47+
- Once all the local testing is done, you can create a free cloud version of MongoDB in [MongoDB Atlas](https://www.mongodb.com/cloud/atlas/register) and get the following credentials from the dashboard of atlas:
48+
```bash
49+
MONGO_URI=YOUR_MONGO_URI
50+
MONGO_USERNAME=YOUR_MONGO_USERNAME
51+
MONGO_PASSWORD=YOUR_MONGO_PASSWORD
52+
```
53+
- ### Setup Environment for the project
54+
- Now create a `.env` file in your project dreictory and include the following parameters as it is :-
55+
```bash
56+
export ENVIRONMENT=local/production
57+
export APP_SECRET=YOUR_APP_SECRET
58+
export MONGO_URI=YOUR_MONGO_URI
59+
export MONGO_USERNAME=YOUR_MONGO_USERNAME
60+
export MONGO_PASSWORD=YOUR_MONGO_PASSWORD
61+
export DB_NAME=YOUR_MONGODB_DATABASE_NAME
62+
export FIREBASE_APIKEY=YOUR_API_KEY
63+
export FIREBASE_AUTHDOMAIN=YOUR_AUTH_DOMAIN
64+
export FIREBASE_DATABASEURL=YOUR_DATABASEURL
65+
export FIREBASE_PROJECT_ID=YOUR_PROJECT_ID
66+
export FIREBASE_STORAGE_BUCKET=YOUR_STORAGE_BUCKET
67+
export FIREBASE_MESSAGING_SENDER_ID=YOUR_MESSAGING_SENDER_ID
68+
export FIREBASE_APP_ID=YOUR_APP_ID
69+
export FIREBASE_MEASUREMENT_ID=YOUR_MEASUREMENT_ID
70+
```
71+
- ### Now Just, Run the project
72+
- To the run the project, go to the `bash` terminal of VSCode or any other code editor and run `./start_server.sh`.
73+
- The server would start running on `http://127.0.0.1:{port_number}`.(generally http://127.0.0.1:5000)
74+
75+
- ### Login/Signup as a user
76+
Since, you are a new user, singup in the application and then login. Then, Start Exploring the project!
77+
> Note: **You will recieve a email verification mail from firebase upon singup and then only you can proceed**
78+
79+
80+
## 📺 Demo
81+
- Login/Singup Screen.
82+
83+
![image](https://github.com/MBSA-INFINITY/Python-project-Scripts/assets/85332648/1a9738b0-106e-4b49-84e4-29713e260fed)
84+
![image](https://github.com/MBSA-INFINITY/Python-project-Scripts/assets/85332648/cccb7c3a-9436-4db3-b535-6a7678c2273d)
85+
86+
- Main screen of the application (Bill generation)
87+
![image](https://github.com/MBSA-INFINITY/Python-project-Scripts/assets/85332648/88acfd54-8f9a-4f2a-a6f2-d4f5464733c1)
88+
89+
- Product Screen/ Adding products
90+
91+
![image](https://github.com/MBSA-INFINITY/Python-project-Scripts/assets/85332648/ebcdd3fd-89fb-427b-b458-2adc6fd3a39a)
92+
![image](https://github.com/MBSA-INFINITY/Python-project-Scripts/assets/85332648/d9aaf039-04dd-42b9-b326-c8080cd879cb)
93+
94+
> Note: **This is where you can manage the inventory of a product by editing it.**
95+
96+
- All Bills Page
97+
98+
![image](https://github.com/MBSA-INFINITY/Python-project-Scripts/assets/85332648/53a60ec6-aa3d-44d8-960e-cc885ac31b60)
99+
100+
- Bill generation in PDF Format.
101+
102+
![image](https://github.com/MBSA-INFINITY/Python-project-Scripts/assets/85332648/7a498083-75d6-40c4-a928-065933841269)
103+
104+
105+
106+
107+
108+
109+
110+
## 🤖 Author
111+
Github - [MBSA-INFINITY](https://github.com/MBSA-INFINITY)
112+
LinkedIn - [MBSAIADITYA](https://www.linkedin.com/in/mbsaiaditya/)
113+
Portfolio - [MBSA](https://mbsaiaditya.in/)
114+
Instagram - [MBSAIADITYA](https://instagram.com/mbsaiaditya)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
from flask import Flask, render_template, request, redirect, abort, flash, session ,url_for
2+
from werkzeug.exceptions import HTTPException
3+
from firebase import Firebase
4+
import json
5+
from datetime import datetime
6+
from db import products_collection, invoices_collection, users_collection
7+
from helper import *
8+
import os
9+
10+
app = Flask(__name__)
11+
app.secret_key = os.environ['APP_SECRET']
12+
13+
config = {
14+
"apiKey": os.environ.get("FIREBASE_APIKEY"),
15+
"authDomain": os.environ.get("FIREBASE_AUTHDOMAIN"),
16+
"databaseURL": os.environ.get("FIREBASE_DATABASEURL"),
17+
"projectId": os.environ.get("FIREBASE_PROJECT_ID"),
18+
"storageBucket": os.environ.get("FIREBASE_STORAGE_BUCKET"),
19+
"messagingSenderId": os.environ.get("FIREBASE_MESSAGING_SENDER_ID"),
20+
"appId": os.environ.get("FIREBASE_APP_ID"),
21+
"measurementId": os.environ.get("FIREBASE_MEASUREMENT_ID")
22+
}
23+
24+
firebase = Firebase(config)
25+
db = firebase.database()
26+
auth = firebase.auth()
27+
28+
exempted_endpoints = ['signup','login','static']
29+
30+
@app.route("/signup", methods = ['GET','POST'])
31+
def signup():
32+
if request.method=='POST':
33+
name = request.form.get("name")
34+
username = request.form.get("email")
35+
password = request.form.get("password")
36+
repassword = request.form.get("repassword")
37+
user_details = {"name": name,"email": username}
38+
if password == repassword:
39+
if len(password)>=6:
40+
try:
41+
_user_ = auth.create_user_with_email_and_password(username ,password)
42+
auth.send_email_verification(_user_['idToken'])
43+
user_details['merchant_id'] = _user_['localId']
44+
users_collection.insert_one(user_details)
45+
return render_template("success.html")
46+
except Exception as e:
47+
return redirect(url_for('login'))
48+
else:
49+
flash('Password is less than 6 characters!')
50+
return redirect("/signup")
51+
else:
52+
flash('Both Passwords do not match!')
53+
return redirect("/signup")
54+
return render_template("signup.html")
55+
56+
@app.route("/login",methods = ['GET','POST'] )
57+
def login():
58+
if request.method == 'POST':
59+
data = dict(request.form)
60+
email = data.get("email")
61+
password = data.get("password")
62+
user_details = users_collection.find_one({"email": email},{"_id": 0})
63+
if user_details:
64+
user = auth.sign_in_with_email_and_password(email ,password)
65+
access_token = user['idToken']
66+
acc_info = auth.get_account_info(access_token)
67+
print(acc_info)
68+
if not acc_info.get("users")[0].get("emailVerified"):
69+
abort(500,{"message": f"{email} is not verified!"})
70+
user_details = users_collection.find_one({"email": email, "merchant_id": user['localId']},{"_id": 0})
71+
if user_details:
72+
session['user'] = user['localId']
73+
return redirect("/")
74+
else:
75+
abort(500,{"message": "Username or Password is Invalid!!"})
76+
77+
else:
78+
flash("User doesn't exist!")
79+
return redirect("/login")
80+
if 'user' in session:
81+
return redirect("/")
82+
return render_template("login.html")
83+
84+
85+
@app.route("/",methods = ['GET','POST'])
86+
def start():
87+
products = list(products_collection.find({"merchant_id": session.get('user')},{"_id":0,"created_on": 0}))
88+
return render_template("index.html",_products_=json.dumps(products))
89+
90+
@app.route("/checkout",methods = ['POST'])
91+
def checkout():
92+
bill_data = dict(request.form)
93+
dt_string = datetime.now()
94+
bill_data['created_on'] = dt_string
95+
bill_data['merchant_id'] = session.get('user')
96+
bill_data['selected_products'] = json.loads(bill_data['selected_products'])
97+
unavailable_items = []
98+
for product in bill_data['selected_products']:
99+
item_id = product['item_id']
100+
qty = int(product['qty'])
101+
product_details = products_collection.find_one({"merchant_id": session.get('user'),"item_id": item_id})
102+
current_stock = product_details.get("item_stock")
103+
item_name = product_details.get("name")
104+
if int(current_stock)<=int(qty):
105+
unavailable_items.append(item_name)
106+
if unavailable_items == []:
107+
for product in bill_data['selected_products']:
108+
item_id = product['item_id']
109+
qty = int(product['qty'])
110+
products_collection.update_one({"merchant_id":session.get('user'),"item_id":item_id},{"$inc":{"item_stock": -qty}})
111+
# return bill_data
112+
bill_id = random_id(10)
113+
if bill_data.get("payment_status") == "paid":
114+
bill_data['amount_to_be_collected'] = "0"
115+
bill_data['bill_id'] = bill_id
116+
invoices_collection.insert_one(bill_data)
117+
return redirect(f"/bill/{bill_id}")
118+
else:
119+
for item in unavailable_items:
120+
flash(f"{item} is out of stock!")
121+
return redirect("/")
122+
123+
@app.route("/bill/<string:bill_key>", methods=['GET','POST'])
124+
def bill(bill_key):
125+
if request.method == 'POST':
126+
incoming_updates = dict(request.form)
127+
if incoming_updates['payment_status'] == 'paid':
128+
incoming_updates['amount_to_be_collected'] = "0"
129+
incoming_updates['paid_amount'] = incoming_updates['checkout_amount'].replace(",",'')
130+
elif incoming_updates['payment_status'] == 'unpaid':
131+
incoming_updates['amount_to_be_collected'] = incoming_updates['checkout_amount'].replace(",",'')
132+
incoming_updates['paid_amount'] = "0"
133+
invoices_collection.update_one({"merchant_id":session.get('user'),"bill_id": bill_key},{"$set":incoming_updates})
134+
return redirect(f"/bill/{bill_key}")
135+
try:
136+
bill_details = invoices_collection.find_one({"merchant_id": session.get('user'),"bill_id": bill_key},{"_id":0,"created_on": 0})
137+
except:
138+
bill_details = None
139+
if bill_details is not None:
140+
return render_template("bill.html",bill_key=bill_key, bill_details=json.dumps(bill_details),bill_details_json=bill_details)
141+
else:
142+
abort(404,
143+
{
144+
"message": f"Bill with Bill ID '{bill_key}' doesn't exist"
145+
})
146+
147+
@app.route("/products", methods = ['GET','POST'])
148+
def products():
149+
if request.method == 'POST':
150+
item_data = dict(request.form)
151+
item_data['item_stock'] = int(item_data['item_stock'])
152+
dt_string = datetime.now()
153+
item_data['created_on'] = dt_string
154+
item_data['merchant_id'] = session.get('user')
155+
item_data['item_id'] = random_id(10)
156+
products_collection.insert_one(item_data)
157+
return redirect("/products/0")
158+
return redirect('/products/0')
159+
160+
@app.route("/products/<int:page_index>", methods = ['GET','POST'])
161+
def products_idx(page_index):
162+
if request.method == "POST":
163+
query = request.form.get("search")
164+
page_size = 10
165+
pipeline = [{"$match":{"merchant_id": session.get('user')}},{"$project":{"_id":0}}]
166+
res= []
167+
products = list(products_collection.aggregate(pipeline))
168+
for product in products:
169+
if query in product.get("name").lower():
170+
res.append(product)
171+
return render_template("products.html",products=res,total_pages=0,page_size=page_size, page_index=0,merchant_id=session.get('user'), handle_catch=handle_catch)
172+
page_size = 10
173+
pipeline = [{"$match":{"merchant_id": session.get('user')}},{"$project":{"_id":0}},{'$skip': int(page_index)*page_size}, {'$limit': page_size}]
174+
products = products_collection.aggregate(pipeline)
175+
temp = len(list(products_collection.find({"merchant_id":session.get("user")},{"_id":0})))
176+
if temp%page_size == 0:
177+
total_pages = temp//page_size
178+
else:
179+
total_pages = temp//page_size + 1
180+
181+
return render_template("products.html",products=products,total_pages=total_pages,page_size=page_size, page_index=page_index,merchant_id=session.get('user'), handle_catch=handle_catch)
182+
183+
@app.route("/products/<string:merchant_id>/<string:item_id>/<string:page_index>", methods = ['POST'])
184+
def products_update(merchant_id, item_id,page_index):
185+
item_data = dict(request.form)
186+
item_data['item_stock'] = int(item_data['item_stock'] )
187+
products_collection.update_one({"item_id": item_id,"merchant_id": merchant_id},{"$set":item_data})
188+
return redirect(f"/products/{page_index}")
189+
190+
@app.route("/products_delete/<string:merchant_id>/<string:item_id>/<string:page_index>", methods = ['POST'])
191+
def products_delete(merchant_id, item_id,page_index):
192+
products_collection.delete_one({"item_id": item_id,"merchant_id": merchant_id})
193+
return redirect(f"/products/{page_index}")
194+
195+
@app.route("/bills", methods = ['GET','POST'])
196+
def bills_temp():
197+
return redirect("/bills/0")
198+
199+
@app.route("/bills/<int:page_index>", methods = ['GET','POST'])
200+
def bills(page_index):
201+
if request.method == "POST":
202+
payment_status_filter = str(request.form.get("payment_status"))
203+
page_size = 10
204+
if payment_status_filter!="":
205+
pipeline = [{"$match":{"merchant_id": session.get('user'),"payment_status":payment_status_filter}},{"$project":{"_id":0}}]
206+
else:
207+
return redirect("/bills/0")
208+
all_bills = list(invoices_collection.aggregate(pipeline))
209+
return render_template("bills.html",all_bills=all_bills,total_pages=0,page_size=page_size, page_index=0,merchant_id=session.get('user'), handle_catch=handle_catch)
210+
page_size = 10
211+
pipeline = [{"$match":{"merchant_id": session.get('user')}},{"$project":{"_id":0}},{'$skip': int(page_index)*page_size}, {'$limit': page_size}]
212+
all_bills = invoices_collection.aggregate(pipeline)
213+
temp = len(list(invoices_collection.find({"merchant_id":session.get("user")},{"_id":0})))
214+
if temp%page_size == 0:
215+
total_pages = temp//page_size
216+
else:
217+
total_pages = temp//page_size +1
218+
219+
# all_bills = invoices_collection.find({"merchant_id":session.get('user')},{"_id":0})
220+
return render_template("bills.html", all_bills=all_bills,total_pages=total_pages,page_index=page_index, page_size=page_size,handle_catch=handle_catch)
221+
222+
@app.route("/logout", methods = ['GET','POST'])
223+
def logout():
224+
session.pop('user')
225+
return redirect("/login")
226+
227+
@app.before_request
228+
def before_request_func():
229+
if request.endpoint in exempted_endpoints:
230+
return
231+
if 'user' not in session:
232+
return redirect(url_for('login'))
233+
234+
235+
if __name__ == '__main__':
236+
app.run(debug=True, port=5000)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import os
2+
import pymongo
3+
4+
ENVIRONMENT = os.environ["ENVIRONMENT"]
5+
if ENVIRONMENT == "local":
6+
connection_string = "mongodb://localhost:27017"
7+
DB_NAME = "billing_system"
8+
else:
9+
MONGO_CLUSTER = os.environ["MONGO_URI"]
10+
MONGO_USERNAME = os.environ["MONGO_USERNAME"]
11+
MONGO_PASSWORD = os.environ["MONGO_PASSWORD"]
12+
DB_NAME = os.environ["DB_NAME"]
13+
connection_string = f"mongodb+srv://{MONGO_USERNAME}:{MONGO_PASSWORD}@{MONGO_CLUSTER}/?retryWrites=true&ssl=true&ssl_cert_reqs=CERT_NONE&w=majority"
14+
15+
16+
db_client = pymongo.MongoClient(connection_string)
17+
db_client = db_client.get_database(DB_NAME)
18+
19+
products_collection = db_client['products']
20+
invoices_collection = db_client['invoices']
21+
users_collection = db_client['users']
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import string, random
2+
3+
def random_id(N):
4+
res = ''.join(random.choices(string.ascii_lowercase +
5+
string.ascii_uppercase, k=N))
6+
return res
7+
8+
def handle_catch(caller, on_exception):
9+
try:
10+
return caller()
11+
except:
12+
return on_exception

0 commit comments

Comments
 (0)