diff --git a/ENV SETUP/setenv.sh b/ENV SETUP/setenv.sh new file mode 100644 index 00000000..08670ac4 --- /dev/null +++ b/ENV SETUP/setenv.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Set Environment Variables Script + +#Make it executable: Run chmod +x setenv.sh to make the script executable. +#Execute the script: Run ./setenv.sh to set the environment variables. +#Verification: After execution, verify the variables by typing echo $SECRET_KEY or any other variable name. + +# Define environment variable values +SECRET_KEY='Secret123' +DATABASE_URL='mysql+pymysql://capstonepro:blowfish-orange-840@mysql.ecn.purdue.edu/capstonepro' +MAIL_USERNAME='PurdueCapstone@gmail.com' +MAIL_PASSWORD='vbla evma tqpz umof' + +# Write to .bashrc or .bash_profile +echo "export SECRET_KEY='$SECRET_KEY'" >> ~/.bashrc +echo "export DATABASE_URL='$DATABASE_URL'" >> ~/.bashrc +echo "export MAIL_USERNAME='$MAIL_USERNAME'" >> ~/.bashrc +echo "export MAIL_PASSWORD='$MAIL_PASSWORD'" >> ~/.bashrc + +echo "Environment variables have been set." + +# Reload .bashrc or .bash_profile +source ~/.bashrc diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..41ea726a --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn -w 4 app:app \ No newline at end of file diff --git a/__pycache__/PRFSub_lib.cpython-311.pyc b/__pycache__/PRFSub_lib.cpython-311.pyc index 03d17381..014054c5 100644 Binary files a/__pycache__/PRFSub_lib.cpython-311.pyc and b/__pycache__/PRFSub_lib.cpython-311.pyc differ diff --git a/__pycache__/app.cpython-311.pyc b/__pycache__/app.cpython-311.pyc index a9b2066c..124abd67 100644 Binary files a/__pycache__/app.cpython-311.pyc and b/__pycache__/app.cpython-311.pyc differ diff --git a/app.py b/app.py index de4b5198..20656065 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,4 @@ -from flask import Flask, render_template, request, redirect, url_for,flash,session +from flask import Flask, render_template, request, redirect, url_for,flash,session, current_app from flask_sqlalchemy import SQLAlchemy from sqlalchemy import text from flask_mail import Mail, Message @@ -7,7 +7,7 @@ from werkzeug.utils import secure_filename import os from datetime import datetime, timedelta -import pytz + #Local Imports from PRFSub_lib import digestFileContents, store_parsed_data, restructure_data, extract_file_details @@ -44,11 +44,11 @@ class User(db.Model): id = db.Column(db.Integer, primary_key=True, autoincrement=True) email = db.Column(db.String(150), nullable=False, unique=True) password = db.Column(db.String(150), nullable=False) - team_number = db.Column(db.Integer, nullable=False) + team_number = db.Column(db.String(150), nullable=False) class Team(db.Model): id = db.Column(db.Integer, primary_key=True) - team_number = db.Column(db.Integer, nullable=False) + team_number = db.Column(db.String(150), nullable=False) team_name = db.Column(db.String(255), nullable=False) class Item(db.Model): @@ -63,16 +63,30 @@ class Item(db.Model): class BOM(db.Model): id = db.Column(db.Integer, primary_key=True) - team_number = db.Column(db.Integer, index=True, nullable=False) + team_number = db.Column(db.String(150), nullable=False) vendor = db.Column(db.String(150), nullable=True) part_number = db.Column(db.String(50), nullable=True) item_status = db.Column(db.String(50), nullable=True) date = db.Column(db.DateTime, nullable=True) comments = db.Column(db.String(255), nullable=True) - + class team_procurement_detail(db.Model): id = db.Column(db.Integer, primary_key=True) - team_number = db.Column(db.Integer, index=True, nullable=False) + team_number = db.Column(db.String(150), nullable=False) + item_description = db.Column(db.String(255), nullable=False) + part_number = db.Column(db.String(50), nullable=True) + quantity = db.Column(db.Integer, nullable=True) + unit_price = db.Column(db.Float, nullable=True) + ext_price = db.Column(db.Float, nullable=True) + url_link = db.Column(db.String(255), nullable=True) + delivery_date = db.Column(db.DateTime, nullable=True) + file_last_modified = db.Column(db.DateTime, nullable=True) # Last modification date of the file + total_file_price = db.Column(db.Float, nullable=True) # Total price from file details + created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) # Timestamp of record creation + +class approved_bom(db.Model): + id = db.Column(db.Integer, primary_key=True) + team_number = db.Column(db.String(150), nullable=False) item_description = db.Column(db.String(255), nullable=False) part_number = db.Column(db.String(50), nullable=True) quantity = db.Column(db.Integer, nullable=True) @@ -83,7 +97,6 @@ class team_procurement_detail(db.Model): file_last_modified = db.Column(db.DateTime, nullable=True) # Last modification date of the file total_file_price = db.Column(db.Float, nullable=True) # Total price from file details created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) # Timestamp of record creation - #FILE UPLOAD INFORMATION UPLOAD_FOLDER = os.getcwd() + r'/uploads' @@ -99,11 +112,21 @@ def decorated_function(*args, **kwargs): return f(*args, **kwargs) return decorated_function -#Web Decoration +#Homepage @app.route('/home') @login_required def home(): - return render_template('HomePage.html') + # Fetch the current user's team number + user_id = session.get('user_id') + current_user = db.session.get(User, user_id) + print(current_user) + if current_user: + team_number = current_user.team_number + if current_user.team_number == "PURDUE": + ButtonDisable = False + else: + ButtonDisable = True + return render_template('HomePage.html', team_number=team_number,ButtonDisable=ButtonDisable) @app.before_request def before_request(): @@ -228,7 +251,6 @@ def login(): return render_template('LoginPage.html') - @app.route('/registration', methods=['GET', 'POST']) def registration(): if request.method == 'POST': @@ -321,7 +343,7 @@ def upload_file(): print(file_details) restructured_list = restructure_data(DataInstance[1], file_details) print(restructured_list) - store_parsed_data(DataInstance[1], team_number, team_procurement_detail,restructured_list,db) + store_parsed_data(DataInstance[1], team_number, team_procurement_detail, restructured_list, db) flash('File successfully uploaded') else: flash('Error: User not found.') @@ -331,57 +353,47 @@ def upload_file(): flash('Allowed file types are .xlsx') return redirect(url_for('prfsub')) -#BOM Endpoints - - #BOMlist -@app.route('/BOMlist', methods=['GET', 'POST']) -@login_required -def bomlist(): - #get team # from current session - user_id = session.get('user_id') - current_user = db.session.get(User, user_id) - team_num = current_user.team_number - # Get records from TeamProcurementDetail - team_details = team_procurement_detail.query.all() - # Update BOM records based on TeamProcurementDetail - for team_detail in team_details: - bom_record = BOM(team_number=team_num) - # bom_record.vendor = team_detail.vendor - bom_record.date = team_detail.delivery_date - bom_record.part_number = team_detail.part_number - print(bom_record.part_number) - - # Commit the changes - db.session.commit() - return render_template('BOMlist.html', bom_record=bom_record) - - #StudentBOM +#Maintenence Endpoints + #StudentBOM @app.route('/StudentBOM', methods=['GET']) @login_required def studentbom(): - query = text("SELECT created_at, item_description, part_number, quantity, unit_price FROM team_procurement_detail") - result = db.session.execute(query) - stubom_data = result.fetchall() - return render_template('StudentBOM.html', stubom_data=stubom_data) - -''' -#PRF Status Endpoints -@app.route('/prf_status') + curr_user = session.get('user_id') + teamquery = text("SELECT team_number FROM user WHERE id = :id LIMIT 1") + teamres = db.session.execute(teamquery, {"id": curr_user}) + team_number = teamres.fetchone() + + if team_number: + teamnum = str(team_number[0]) + + # Query to retrieve filtered data based on the team number + query = text("SELECT created_at, item_description, part_number, quantity, unit_price, team_number FROM approved_bom WHERE team_number = :teamnum") + result = db.session.execute(query, {"teamnum": teamnum}) + stubom_data = result.fetchall() + + return render_template('StudentBOM.html', stubom_data=stubom_data, teamnum=teamnum) + + # Handle the case where team_number is not available + return render_template('error.html', message="Team number not found.") + +#admin BOM +@app.route('/BOMlist', methods=['GET']) @login_required -def prf_status(): - # Query the database to retrieve the form data for the current team - form_data = db.query.filter_by(team_number=team_number).first() - # Check if the form data exists - if form_data: - # Render the template with the form data - return render_template('PrfStatus.html', form_data=form_data) +def bomlist(): + user_id = session.get('user_id') + current_user = User.query.get(user_id) + # Query to retrieve filtered data based on the team number + if current_user and current_user.team_number == "PURDUE": + query = text("SELECT created_at, item_description, part_number, quantity, unit_price, team_number FROM approved_bom") + result = db.session.execute(query) + bomlist_data = result.fetchall() + return render_template('BOMlist.html', bomlist_data=bomlist_data) else: - # Case where form data doesn't exist - return "Form data not found for this team." -''' -#Maintenence Endpoints + # Redirect to unauthorized page or handle the case where team_number is not "PURDUE" + return redirect(url_for('home')) +#show user @app.route('/show_users') @login_required def show_users(): @@ -397,13 +409,18 @@ def root(): @app.route('/prf_status', methods=['GET']) -@login_required +@login_required def prfstatus(): - query = text("SELECT id, created_at, team_number FROM team_procurement_detail") - result = db.session.execute(query) - prf_data = result.fetchall() - return render_template('PrfStatus.html', prf_data=prf_data) - + user_id = session.get('user_id') + current_user = User.query.get(user_id) + if current_user and current_user.team_number == "PURDUE": + query = text("SELECT id, created_at, team_number,item_description, unit_price, quantity, total_file_price FROM team_procurement_detail") + result = db.session.execute(query) + prf_data = result.fetchall() + return render_template('PrfStatus.html', prf_data=prf_data) + else: + # Redirect to unauthorized page or handle the case where team_number is not "PURDUE" + return render_template('HomePage.html') @app.route('/', methods=['GET']) @@ -414,10 +431,17 @@ def catch_all(unknown_route): @app.route('/adminview', methods=['GET']) @login_required def get_user_data(): - query = text("SELECT email, team_number FROM user") - result = db.session.execute(query) - user_data = result.fetchall() - return render_template('adminview.html', user_data=user_data) + user_id = session.get('user_id') + current_user = User.query.get(user_id) + + if current_user and current_user.team_number == "PURDUE": + query = text("SELECT email, team_number FROM user") + result = db.session.execute(query) + user_data = result.fetchall() + return render_template('adminview.html', user_data=user_data) + else: + # Redirect to unauthorized page or handle the case where team_number is not "PURDUE" + return redirect(url_for('home')) @app.route('/file_info') @login_required @@ -439,5 +463,46 @@ def file_info(): 'last_modified': formatted_last_modified } +@app.route('/update_status', methods=['POST']) +@login_required +def update_status(): + selected_ids = request.form.getlist('selected_ids') # Get list of selected row IDs + + for id in selected_ids: + prf_row = team_procurement_detail.query.filter_by(id=id).first() # Get the row by ID + if prf_row: + status = request.form.get(f'status_{id}') # Get the selected status value for the row + + if status == 'status3': # If status is 'Approved' + # Move the data to the approved_bom table + approved_data = approved_bom( + team_number=prf_row.team_number, + item_description=prf_row.item_description, + part_number=prf_row.part_number, + quantity=prf_row.quantity, + unit_price=prf_row.unit_price, + ext_price=prf_row.ext_price, + url_link=prf_row.url_link, + total_file_price=prf_row.total_file_price + ) + db.session.add(approved_data) + db.session.delete(prf_row) # Delete the row from team_procurement_detail + elif status == 'status2': # If status is 'Status 2' + # Retrieve the user with the matching team_number + user = User.query.filter_by(team_number=prf_row.team_number).first() + if user: + send_notification_email(user.email, prf_row.item_description) + db.session.delete(prf_row) + + + db.session.commit() + return redirect(url_for('prfstatus')) # Redirect to the PRF status page after processing + +def send_notification_email(email, item_description): + msg = Message("Item Not Approved Notification", sender="PurdueCapstone@gmail.com", recipients=[email]) + msg.body = f"Dear User,\n\nThe item '{item_description}' has not been approved. Please contact your mentor or professor for further information." + with current_app.app_context(): + mail.send(msg) + if __name__ == '__main__': app.run(debug=True) \ No newline at end of file diff --git a/prfapproval.py b/prfapproval.py deleted file mode 100644 index 62536f61..00000000 --- a/prfapproval.py +++ /dev/null @@ -1,30 +0,0 @@ -from flask import Flask, render_template -from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import inspect,text - -# Connecting to DB -app = Flask(__name__) -app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://capstonepro:blowfish-orange-840@mysql.ecn.purdue.edu/capstonepro' -app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # To suppress a warning message - -db = SQLAlchemy(app) - -# Get Data -@app.route('/getAdminData', methods=['GET']) -def getAdminData(): - """ query = 'SELECT * FROM user' - result = db.session.execute(query) - - df = pd.DataFrame() - for data in result: - df2 = pd.DataFrame(list(data)).T - df = pd.concat([df, df2]) - - return df.to_html('templates/sql-data.html') """ - query = 'SELECT * FROM user' - result = db.engine.execute(query) - - # Process the result into a list of dictionaries - users = [dict(row) for row in result] - - return render_template('sql-data.html', users=users) \ No newline at end of file diff --git a/prfupload.py b/prfupload.py deleted file mode 100644 index 692096ea..00000000 --- a/prfupload.py +++ /dev/null @@ -1,36 +0,0 @@ -import pandas as pd -from sqlalchemy import create_engine - -# MySQL database credentials -DB_USERNAME = "capstonepro" -DB_PASSWORD = "blowfish-orange-840" -DB_HOST = "mysql.ecn.purdue.edu" -DB_NAME = "capstonepro" - -#HTML Url for file -HTML_URL = "file:///Users/shashwat/Desktop/PurdueApp/PurdueApp/templates/prfsub.html" - -# Parse the Excel file into a DataFrame -try: - dfs = pd.read_html(HTML_URL, header=0) - if not dfs: - raise Exception("No tables found in the HTML page.") -except Exception as e: - print("Error:", str(e)) - exit(1) - -# Storage into table -data = dfs[0] - -# Create a connection to your MySQL database -engine = create_engine(f"mysql+mysqlconnector://{DB_USERNAME}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}") - -# Store the data in the MySQL database -try: - data.to_sql('your_table_name', con=engine, if_exists='replace', index=False) - print("Data has been successfully stored in the MySQL database.") -except Exception as e: - print("Error:", str(e)) - -# Close the database connection -engine.dispose() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..6143d705 Binary files /dev/null and b/requirements.txt differ diff --git a/setup_db.py b/setup_db.py index a174ec74..7becad97 100644 --- a/setup_db.py +++ b/setup_db.py @@ -11,15 +11,6 @@ db = SQLAlchemy(app) # Define the Items table -class BOM(db.Model): - id = db.Column(db.Integer, primary_key=True) - team_number = db.Column(db.Integer, index=True, nullable=False) - vendor = db.Column(db.String(150), nullable=True) - part_number = db.Column(db.String(50), nullable=True) - item_status = db.Column(db.String(50), nullable=True) - date = db.Column(db.DateTime, nullable=True) - comments = db.Column(db.String(255), nullable=True) - # This function will create the table in the database diff --git a/templates/BOMlist.html b/templates/BOMlist.html index 3c9f30d3..610bac1c 100644 --- a/templates/BOMlist.html +++ b/templates/BOMlist.html @@ -82,68 +82,55 @@

Bill of Materials

- - - - - -
- + - - - - - + + + + + - - - - - - - - - - - - - + {% for bomlist in bomlist_data %} + + + + + + + + + {% endfor %}
VendorPart #Status DateCommentsItemPart NumberQuantityUnit PriceTeam
bing bong {{ bom_record.part_number }} - - {{ bom_record.date }} - - -
{{ bomlist[0] }}{{ bomlist[1] }}{{ bomlist[2] }}{{ bomlist[3] }}{{ bomlist[4] }}{{ bomlist[5] }}
+ \ No newline at end of file diff --git a/templates/LoginPage.html b/templates/LoginPage.html index 42212f72..b48ad79a 100644 --- a/templates/LoginPage.html +++ b/templates/LoginPage.html @@ -62,7 +62,7 @@
-

Capstone Logon

+

Capstone Login

diff --git a/templates/PrfStatus.html b/templates/PrfStatus.html index 9fd7d04a..7726867f 100644 --- a/templates/PrfStatus.html +++ b/templates/PrfStatus.html @@ -81,16 +81,28 @@

Parts Requisition Form Status

- +
- +
- - + + +
+ + + + {% for prf in prf_data %} @@ -98,22 +110,52 @@

Parts Requisition Form Status

+ + + + + {% endfor %}
Date ID TEAMDescriptionUnit PriceQuantityTotal Price Status
{{ prf[1] }} {{ prf[0] }} {{ prf[2] }}{{ prf[3] }}{{ prf[4] }}{{ prf[5] }}{{ prf[6] }} - + + + +
- + +
+ + \ No newline at end of file diff --git a/templates/RegistrationPage.html b/templates/RegistrationPage.html index 6e61f929..faa0c350 100644 --- a/templates/RegistrationPage.html +++ b/templates/RegistrationPage.html @@ -88,7 +88,7 @@ input[type="password"]:focus, input[type="text"]:focus { outline: none; - background-color: rgba(255, 255, 255, 0.3); + background-color: rgba(255, 255, 255, 0.873); } Purdue Capstone Registration @@ -112,9 +112,11 @@

Register Below

+ Team Number: - + +
diff --git a/templates/adminview.html b/templates/adminview.html index 9691f04a..8115098d 100644 --- a/templates/adminview.html +++ b/templates/adminview.html @@ -78,8 +78,8 @@

Admin View