diff --git a/app/__init__.py b/app/__init__.py index 1e67977..44bb6f8 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,8 +1,6 @@ from flask import Flask -from flask_sqlalchemy import SQLAlchemy from app.config import Config - -db = SQLAlchemy() +from app.services.db_service import db def create_app(): app = Flask(__name__) @@ -10,26 +8,22 @@ def create_app(): db.init_app(app) - # Register Blueprints - # from app.routes.user import user_bp - from app.routes.subcontractor_routes import subcontractor_bp + from app.routes.auth import auth_bp + from app.routes.user import user_bp from app.routes.dashboard import dashboard_bp + from app.routes.subcontractor_routes import subcontractor_bp from app.routes.file_import import file_import_bp from app.routes.file_report import file_report_bp from app.routes.generate_comparison_report import generate_report_bp - from app.routes.file_format import file_format - # app.register_blueprint(user_bp) - app.register_blueprint(subcontractor_bp) + app.register_blueprint(auth_bp) + app.register_blueprint(user_bp) app.register_blueprint(dashboard_bp) + app.register_blueprint(subcontractor_bp) app.register_blueprint(file_import_bp) app.register_blueprint(file_report_bp) app.register_blueprint(generate_report_bp) - app.register_blueprint(file_format) - return app - - diff --git a/app/config.py b/app/config.py index 302f703..c6a0474 100644 --- a/app/config.py +++ b/app/config.py @@ -9,16 +9,3 @@ class Config: UPLOAD_FOLDER = "app/static/uploads/" ALLOWED_EXTENSIONS = {"xlsx", "xls", "csv"} - - - -# class Config: -# SECRET_KEY = os.getenv("SECRET_KEY", "dev_key_12345") - -# UPLOAD_FOLDER = "app/static/uploads/" -# ALLOWED_EXTENSIONS = {"xlsx", "xls", "csv"} - -# DB_HOST = "localhost" -# DB_USER = "root" -# DB_PASSWORD = "root" -# DB_NAME = "comparisondb" diff --git a/app/models/trench_excavation_model.py b/app/models/trench_excavation_model.py index 17e34d7..446bf55 100644 --- a/app/models/trench_excavation_model.py +++ b/app/models/trench_excavation_model.py @@ -75,76 +75,5 @@ class TrenchExcavation(db.Model): def __repr__(self): return f"" - # def serialize(self): - # return { - # "id": self.id, - # "subcontractor_id": self.subcontractor_id, - # "subcontractor_name": self.subcontractor.subcontractor_name if self.subcontractor else None, - - # "Location": self.Location, - # "MH_NO": self.MH_NO, - # "CC_length": self.CC_length, - # "Invert_Level": self.Invert_Level, - # "MH_Top_Level": self.MH_Top_Level, - # "Ground_Level": self.Ground_Level, - # "ID_of_MH_m": self.ID_of_MH_m, - # "Actual_Trench_Length": self.Actual_Trench_Length, - # "Pipe_Dia_mm": self.Pipe_Dia_mm, - - # # Width - # "Width_0_to_2_5": self.Width_0_to_2_5, - # "Width_2_5_to_3_0": self.Width_2_5_to_3_0, - # "Width_3_0_to_4_5": self.Width_3_0_to_4_5, - # "Width_4_5_to_6_0": self.Width_4_5_to_6_0, - - # # Depth - # "Upto_IL_Depth": self.Upto_IL_Depth, - # "Cutting_Depth": self.Cutting_Depth, - # "Avg_Depth": self.Avg_Depth, - - # # Soft Murum - # "Soft_Murum_0_to_1_5": self.Soft_Murum_0_to_1_5, - # "Soft_Murum_1_5_to_3_0": self.Soft_Murum_1_5_to_3_0, - # "Soft_Murum_3_0_to_4_5": self.Soft_Murum_3_0_to_4_5, - - # # Hard Murum - # "Hard_Murum_0_to_1_5": self.Hard_Murum_0_to_1_5, - # "Hard_Murum_1_5_to_3_0": self.Hard_Murum_1_5_to_3_0, - - # # Soft Rock - # "Soft_Rock_0_to_1_5": self.Soft_Rock_0_to_1_5, - # "Soft_Rock_1_5_to_3_0": self.Soft_Rock_1_5_to_3_0, - - # # Hard Rock - # "Hard_Rock_0_to_1_5": self.Hard_Rock_0_to_1_5, - # "Hard_Rock_1_5_to_3_0": self.Hard_Rock_1_5_to_3_0, - # "Hard_Rock_3_0_to_4_5": self.Hard_Rock_3_0_to_4_5, - # "Hard_Rock_4_5_to_6_0": self.Hard_Rock_4_5_to_6_0, - # "Hard_Rock_6_0_to_7_5": self.Hard_Rock_6_0_to_7_5, - - # # Totals - # "Soft_Murum_0_to_1_5_total": self.Soft_Murum_0_to_1_5_total, - # "Soft_Murum_1_5_to_3_0_total": self.Soft_Murum_1_5_to_3_0_total, - # "Soft_Murum_3_0_to_4_5_total": self.Soft_Murum_3_0_to_4_5_total, - - # "Hard_Murum_0_to_1_5_total": self.Hard_Murum_0_to_1_5_total, - # "Hard_Murum_1_5_and_above_total": self.Hard_Murum_1_5_and_above_total, - - # "Soft_Rock_0_to_1_5_total": self.Soft_Rock_0_to_1_5_total, - # "Soft_Rock_1_5_and_above_total": self.Soft_Rock_1_5_and_above_total, - - # "Hard_Rock_0_to_1_5_total": self.Hard_Rock_0_to_1_5_total, - # "Hard_Rock_1_5_to_3_0_total": self.Hard_Rock_1_5_to_3_0_total, - # "Hard_Rock_3_0_to_4_5_total": self.Hard_Rock_3_0_to_4_5_total, - # "Hard_Rock_4_5_to_6_0_total": self.Hard_Rock_4_5_to_6_0_total, - # "Hard_Rock_6_0_to_7_5_total": self.Hard_Rock_6_0_to_7_5_total, - - # "Total": self.Total, - # "Remarks": self.Remarks, - # "RA_Bill_No": self.RA_Bill_No, - - # "created_at": self.created_at.strftime("%d-%m-%Y") if self.created_at else None - # } - def serialize(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} diff --git a/app/models/user_model.py b/app/models/user_model.py index 63be6c1..4cf7c26 100644 --- a/app/models/user_model.py +++ b/app/models/user_model.py @@ -1,21 +1,16 @@ -# from app.services.db_service import db -# from werkzeug.security import generate_password_hash, check_password_hash +from app.services.db_service import db +from werkzeug.security import generate_password_hash, check_password_hash -# class User(db.Model): -# id = db.Column(db.Integer, primary_key=True) -# name = db.Column(db.String(120)) -# email = db.Column(db.String(120), unique=True) -# password_hash = db.Column(db.String(255)) +class User(db.Model): + __tablename__ = "users" -# def set_password(self, password): -# self.password_hash = generate_password_hash(password) + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(120), nullable=False) + email = db.Column(db.String(120), unique=True, nullable=False) + password_hash = db.Column(db.String(255), nullable=False) -# def check_password(self, password): -# return check_password_hash(self.password_hash, password) + def set_password(self, password): + self.password_hash = generate_password_hash(password) - -class User: - def __init__(self, id, name, email): - self.id = id - self.name = name - self.email = email + def check_password(self, password): + return check_password_hash(self.password_hash, password) diff --git a/app/routes/auth.py b/app/routes/auth.py index cd0cb10..4a79e12 100644 --- a/app/routes/auth.py +++ b/app/routes/auth.py @@ -1,31 +1,45 @@ -from flask import Blueprint, request, render_template, redirect, flash, session -from app.services.user_service import UserService +from flask import Blueprint, render_template, request, redirect, url_for, flash, session +from app.services.user_service import UserService -auth_bp = Blueprint("auth", __name__, url_prefix="/auth") +auth_bp = Blueprint("auth", __name__) -# LOGIN PAGE +@auth_bp.route("/", methods=["GET", "POST"]) @auth_bp.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": - user = UserService.validate_login( - request.form["email"], - request.form["password"] - ) + email = request.form.get("email") + password = request.form.get("password") + + user = UserService.validate_login(email, password) if user: session["user_id"] = user.id - return redirect("/dashboard") - flash("Invalid credentials", "danger") - return render_template("login.html") + session["user_name"] = user.name + flash("Login successful", "success") + return redirect(url_for("dashboard.dashboard")) -# REGISTER API ONLY -@auth_bp.route("/register", methods=["POST"]) -def register(): - data = request.json - UserService.register_user(data["name"], data["email"], data["password"]) - return {"message": "User registered successfully"}, 201 + flash("Invalid email or password", "danger") + + return render_template("login.html", title="Login") -# LOGOUT @auth_bp.route("/logout") def logout(): session.clear() - return redirect("/auth/login") + flash("Logged out successfully", "info") + return redirect(url_for("auth.login")) + +@auth_bp.route("/register", methods=["GET", "POST"]) +def register(): + if request.method == "POST": + name = request.form.get("name") + email = request.form.get("email") + password = request.form.get("password") + + user = UserService.register_user(name, email, password) + if not user: + flash("Email already exists", "danger") + return redirect(url_for("auth.register")) + + flash("User registered successfully", "success") + return redirect(url_for("auth.login")) + + return render_template("register.html", title="Register") diff --git a/app/routes/dashboard.py b/app/routes/dashboard.py index fa4b6e9..40d6089 100644 --- a/app/routes/dashboard.py +++ b/app/routes/dashboard.py @@ -1,8 +1,9 @@ from flask import Blueprint, render_template +from app.utils.helpers import login_required -dashboard_bp = Blueprint("dashboard", __name__) +dashboard_bp = Blueprint("dashboard", __name__, url_prefix="/dashboard") @dashboard_bp.route("/") -@dashboard_bp.route("/dashboard") +@login_required def dashboard(): return render_template("dashboard.html", title="Dashboard") diff --git a/app/routes/file_format.py b/app/routes/file_format.py index 5e9c75c..f7541ec 100644 --- a/app/routes/file_format.py +++ b/app/routes/file_format.py @@ -1,14 +1,17 @@ from flask import Blueprint, render_template, send_from_directory, abort, current_app +from app.utils.helpers import login_required import os file_format = Blueprint("file_format", __name__) @file_format.route("/file_format") +@login_required def download_format(): return render_template("ex_format.html", title="Download File Formats") @file_format.route("/file_format/download/") +@login_required def download_excel_format(filename): download_folder = os.path.join( diff --git a/app/routes/file_import.py b/app/routes/file_import.py index a1f2106..9983308 100644 --- a/app/routes/file_import.py +++ b/app/routes/file_import.py @@ -1,11 +1,12 @@ from flask import Blueprint, render_template, request, flash from app.services.file_service import FileService from app.models.subcontractor_model import Subcontractor +from app.utils.helpers import login_required file_import_bp = Blueprint("file_import", __name__, url_prefix="/file") -# this route import Subcontractor files @file_import_bp.route("/import", methods=["GET", "POST"]) +@login_required def import_file(): subcontractors = Subcontractor.query.all() @@ -13,17 +14,22 @@ def import_file(): file = request.files.get("file") subcontractor_id = request.form.get("subcontractor_id") RA_Bill_No = request.form.get("RA_Bill_No") - + service = FileService() success, msg = service.handle_file_upload(file, subcontractor_id, RA_Bill_No) flash(msg, "success" if success else "danger") - return render_template("file_import.html", title="Sub-cont. File Import", subcontractors=subcontractors) + return render_template( + "file_import.html", + title="Sub-cont. File Import", + subcontractors=subcontractors + ) # this route import client files @file_import_bp.route("/import_client", methods=["GET", "POST"]) +@login_required def client_import_file(): subcontractors = Subcontractor.query.all() diff --git a/app/routes/file_report.py b/app/routes/file_report.py index 0c9fb71..83f5b11 100644 --- a/app/routes/file_report.py +++ b/app/routes/file_report.py @@ -6,8 +6,7 @@ from app.models.manhole_domestic_chamber_model import ManholeDomesticChamber from app.models.mh_ex_client_model import ManholeExcavationClient from app.models.tr_ex_client_model import TrenchExcavationClient from app.models.mh_dc_client_model import ManholeDomesticChamberClient - -from app import db +from app.utils.helpers import login_required import pandas as pd import io @@ -110,6 +109,7 @@ class SubcontractorBill: # get report by contractor id and dowanload @file_report_bp.route("/report", methods=["GET", "POST"]) +@login_required def report_file(): subcontractors = Subcontractor.query.all() @@ -172,6 +172,7 @@ class ClientBill: @file_report_bp.route("/client_vs_subcont", methods=["GET", "POST"]) +@login_required def client_vs_all_subcontractor(): diff --git a/app/routes/generate_comparison_report.py b/app/routes/generate_comparison_report.py index 8adfb8e..a2ed92d 100644 --- a/app/routes/generate_comparison_report.py +++ b/app/routes/generate_comparison_report.py @@ -1,7 +1,6 @@ from flask import Blueprint, render_template, request, send_file, flash import pandas as pd import io -import re from app.models.subcontractor_model import Subcontractor from app.models.trench_excavation_model import TrenchExcavation @@ -10,14 +9,20 @@ from app.models.manhole_excavation_model import ManholeExcavation from app.models.mh_ex_client_model import ManholeExcavationClient from app.models.manhole_domestic_chamber_model import ManholeDomesticChamber from app.models.mh_dc_client_model import ManholeDomesticChamberClient +from app.utils.helpers import login_required generate_report_bp = Blueprint("generate_report", __name__, url_prefix="/report") +# NORMALIZER +def normalize_key(value): + if value is None: + return None + return str(value).strip().upper() + + # HEADER FORMATTER def format_header(header): - - # Handle Client- / Subcontractor- if "-" in header: prefix, rest = header.split("-", 1) prefix = prefix.title() @@ -29,7 +34,6 @@ def format_header(header): i = 0 while i < len(parts): - # Detect num_num → num.num if i + 1 < len(parts) and parts[i].isdigit() and parts[i + 1].isdigit(): result.append(f"{parts[i]}.{parts[i + 1]}") i += 2 @@ -41,32 +45,52 @@ def format_header(header): return f"{prefix}-{final_text}" if prefix else final_text -# LOOKUP CREATOR +# LOOKUP CREATOR def make_lookup(rows, key_field): - return { - (r.get("Location"), r.get(key_field)): r - for r in rows - if r.get("Location") and r.get(key_field) - } + lookup = {} + for r in rows: + location = normalize_key(r.get("Location")) + key_val = normalize_key(r.get(key_field)) + + if location and key_val: + lookup[(location, key_val)] = r + + return lookup -# GENERIC COMPARISON BUILDER +# COMPARISON BUILDER def build_comparison(client_rows, contractor_rows, key_field): contractor_lookup = make_lookup(contractor_rows, key_field) output = [] for c in client_rows: - s = contractor_lookup.get((c.get("Location"), c.get(key_field))) + client_location = normalize_key(c.get("Location")) + client_key = normalize_key(c.get(key_field)) + + if not client_location or not client_key: + continue + + s = contractor_lookup.get((client_location, client_key)) if not s: continue - client_total = sum(v or 0 for k, v in c.items() if k.endswith("_total")) - sub_total = sum(v or 0 for k, v in s.items() if k.endswith("_total")) + client_total = sum( + float(v or 0) + for k, v in c.items() + if k.endswith("_total") + ) + + sub_total = sum( + float(v or 0) + for k, v in s.items() + if k.endswith("_total") + ) + diff = client_total - sub_total row = { - "Location": c.get("Location"), - key_field.replace("_", " "): c.get(key_field) + "Location": client_location, + key_field.replace("_", " "): client_key } # CLIENT DATA @@ -94,49 +118,31 @@ def build_comparison(client_rows, contractor_rows, key_field): return df -# EXCEL SHEET WRITER (SEPARATED) +# EXCEL SHEET WRITER def write_sheet(writer, df, sheet_name, subcontractor_name): workbook = writer.book df.to_excel(writer, sheet_name=sheet_name, index=False, startrow=3) ws = writer.sheets[sheet_name] - title_fmt = workbook.add_format({ - "bold": True, - "font_size": 14 - }) + title_fmt = workbook.add_format({"bold": True, "font_size": 14}) + client_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#D9EDF7"}) + sub_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#F7E1D9"}) + total_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#FFF2CC"}) + diff_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#E2EFDA"}) + default_header_fmt = workbook.add_format({"bold": True,"border": 1,"bg_color": "#E7E6E6","align": "center","valign": "vcenter"}) - client_fmt = workbook.add_format({ - "bold": True, - "border": 1, - "bg_color": "#D9EDF7" - }) - sub_fmt = workbook.add_format({ - "bold": True, - "border": 1, - "bg_color": "#F7E1D9" - }) - - total_fmt = workbook.add_format({ - "bold": True, - "border": 1, - "bg_color": "#FFF2CC" - }) - - diff_fmt = workbook.add_format({ - "bold": True, - "border": 1, - "bg_color": "#E2EFDA" - }) - - # MAIN HEADING ws.merge_range( 0, 0, 0, len(df.columns) - 1, - f"CLIENT vs SUBCONTRACTOR – {subcontractor_name}", + "CLIENT vs SUBCONTRACTOR", + title_fmt + ) + ws.merge_range( + 1, 0, 1, len(df.columns) - 1, + f"Subcontractor Name - {subcontractor_name}", title_fmt ) - # COLUMN HEADERS for col_num, col_name in enumerate(df.columns): if col_name.startswith("Client-"): ws.write(3, col_num, col_name, client_fmt) @@ -147,13 +153,14 @@ def write_sheet(writer, df, sheet_name, subcontractor_name): elif col_name == "Diff": ws.write(3, col_num, col_name, diff_fmt) else: - ws.write(3, col_num, col_name) + ws.write(3, col_num, col_name, default_header_fmt) ws.set_column(col_num, col_num, 20) -# old report generate ROUTE +# REPORT ROUTE @generate_report_bp.route("/comparison_report", methods=["GET", "POST"]) +@login_required def comparison_report(): subcontractors = Subcontractor.query.all() @@ -161,47 +168,30 @@ def comparison_report(): subcontractor_id = request.form.get("subcontractor_id") if not subcontractor_id: flash("Please select subcontractor", "danger") - return render_template( - "generate_comparison_report.html", - subcontractors=subcontractors - ) + return render_template("generate_comparison_report.html",subcontractors=subcontractors) subcontractor = Subcontractor.query.get_or_404(subcontractor_id) - # ===================== DATA ===================== + # -------- DATA -------- tr_client = [r.serialize() for r in TrenchExcavationClient.query.all()] tr_sub = [r.serialize() for r in TrenchExcavation.query.filter_by( subcontractor_id=subcontractor_id ).all()] df_tr = build_comparison(tr_client, tr_sub, "MH_NO") - print("-- tr clint --:",tr_client ) - print("-- tr sub --:",tr_sub ) - - - - mh_client = [r.serialize() for r in ManholeExcavationClient.query.all()] mh_sub = [r.serialize() for r in ManholeExcavation.query.filter_by( subcontractor_id=subcontractor_id ).all()] df_mh = build_comparison(mh_client, mh_sub, "MH_NO") - # print("-- mh clint --:",mh_client) - # print("-- mh sub --:",mh_sub ) - - dc_client = [r.serialize() for r in ManholeDomesticChamberClient.query.all()] dc_sub = [r.serialize() for r in ManholeDomesticChamber.query.filter_by( subcontractor_id=subcontractor_id ).all()] df_dc = build_comparison(dc_client, dc_sub, "MH_NO") - # print("-- mh dc clint --:",dc_client ) - # print("-- mh dc sub --:", dc_sub) - - - # ===================== EXCEL ===================== + # -------- EXCEL -------- output = io.BytesIO() filename = f"{subcontractor.subcontractor_name}_Comparison_Report.xlsx" @@ -218,7 +208,6 @@ def comparison_report(): mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) - return render_template( - "generate_comparison_report.html", - subcontractors=subcontractors - ) + return render_template("generate_comparison_report.html",subcontractors=subcontractors) + + diff --git a/app/routes/subcontractor_routes.py b/app/routes/subcontractor_routes.py index 5fd3b71..d1242cb 100644 --- a/app/routes/subcontractor_routes.py +++ b/app/routes/subcontractor_routes.py @@ -1,15 +1,18 @@ from flask import Blueprint, render_template, request, redirect, flash from app import db from app.models.subcontractor_model import Subcontractor +from app.utils.helpers import login_required subcontractor_bp = Blueprint("subcontractor", __name__, url_prefix="/subcontractor") # ---------------- ADD ----------------- @subcontractor_bp.route("/add") +@login_required def add_subcontractor(): return render_template("subcontractor/add.html") @subcontractor_bp.route("/save", methods=["POST"]) +@login_required def save_subcontractor(): subcontractor = Subcontractor( subcontractor_name=request.form.get("subcontractor_name"), @@ -26,18 +29,21 @@ def save_subcontractor(): # ---------------- LIST ----------------- @subcontractor_bp.route("/list") +@login_required def subcontractor_list(): subcontractors = Subcontractor.query.all() return render_template("subcontractor/list.html", subcontractors=subcontractors) # ---------------- EDIT ----------------- @subcontractor_bp.route("/edit/") +@login_required def edit_subcontractor(id): subcontractor = Subcontractor.query.get_or_404(id) return render_template("subcontractor/edit.html", subcontractor=subcontractor) # ---------------- UPDATE ----------------- @subcontractor_bp.route("/update/", methods=["POST"]) +@login_required def update_subcontractor(id): subcontractor = Subcontractor.query.get_or_404(id) @@ -54,6 +60,7 @@ def update_subcontractor(id): # ---------------- DELETE ----------------- @subcontractor_bp.route("/delete/") +@login_required def delete_subcontractor(id): subcontractor = Subcontractor.query.get_or_404(id) diff --git a/app/routes/user.py b/app/routes/user.py index 12757bc..5f16e3c 100644 --- a/app/routes/user.py +++ b/app/routes/user.py @@ -1,9 +1,11 @@ from flask import Blueprint, render_template from app.services.user_service import UserService +from app.utils.helpers import login_required user_bp = Blueprint("user", __name__, url_prefix="/user") @user_bp.route("/list") +@login_required def list_users(): - users = UserService().get_all_users() + users = UserService.get_all_users() return render_template("users.html", users=users, title="Users") diff --git a/app/services/file_service.py b/app/services/file_service.py index 08da634..e98e3f7 100644 --- a/app/services/file_service.py +++ b/app/services/file_service.py @@ -18,14 +18,21 @@ from app.utils.file_utils import ensure_upload_folder class FileService: + # ---------------- COMMON HELPERS ---------------- def allowed_file(self, filename): - return ( - "." in filename - and filename.rsplit(".", 1)[1].lower() in Config.ALLOWED_EXTENSIONS - ) + return ("." in filename and filename.rsplit(".", 1)[1].lower() in Config.ALLOWED_EXTENSIONS) - # -------------------------- SUBCONTRACTORFILE UPLOAD ------------------------- - # SUBCONTRACTOR FILE UPLOAD + def normalize(self, val): + if val is None or pd.isna(val): + return None + + val = str(val).strip() + if val.lower() in ["", "nan", "none", "-", "—"]: + return None + + return val.upper() + + # ---------------- SUBCONTRACTOR FILE UPLOAD ---------------- def handle_file_upload(self, file, subcontractor_id, RA_Bill_No): if not subcontractor_id: @@ -50,9 +57,9 @@ class FileService: file.save(filepath) try: - df_tr_ex = pd.read_excel(filepath, sheet_name="Tr.Ex.", header=12) - df_mh_ex = pd.read_excel(filepath, sheet_name="MH Ex.", header=12) - df_mh_dc = pd.read_excel(filepath, sheet_name="MH & DC", header=11) + df_tr_ex = pd.read_excel(filepath, sheet_name="Tr.Ex.", header=12, dtype={"MH No": str}) + df_mh_ex = pd.read_excel(filepath, sheet_name="MH Ex.", header=12, dtype={"MH No": str}) + df_mh_dc = pd.read_excel(filepath, sheet_name="MH & DC", header=11, dtype={"MH No": str}) self.process_trench_excavation(df_tr_ex, subcontractor_id, RA_Bill_No) self.process_manhole_excavation(df_mh_ex, subcontractor_id, RA_Bill_No) @@ -62,9 +69,9 @@ class FileService: except Exception as e: db.session.rollback() - return False, f"Processing failed(SUBCONTRACTOR): {e}" + return False, f"Import failed: {e}" - # Trench Excavation (Subcontractor) + # ---------------- Trench Excavation (Subcontractor) ---------------- def process_trench_excavation(self, df, subcontractor_id, RA_Bill_No): df.columns = ( @@ -80,42 +87,49 @@ class FileService: if "Location" in df.columns: df["Location"] = df["Location"].ffill() - try: - for _, row in df.iterrows(): - location = row.get("Location") - mh_no = row.get("MH_NO") + errors = [] - if ( - pd.isna(location) - or str(location).strip() == "" - or pd.isna(mh_no) - or str(mh_no).strip() == "" - ): - continue + for idx, row in df.iterrows(): + location = self.normalize(row.get("Location")) + mh_no = self.normalize(row.get("MH_NO")) - record_data = {} - for col in df.columns: - if hasattr(TrenchExcavation, col): - val = row[col] - if pd.isna(val) or str(val).strip() in ["", "-", "—", "nan"]: - val = None - record_data[col] = val + if not location or not mh_no: + continue - record = TrenchExcavation( - subcontractor_id=subcontractor_id, - RA_Bill_No=RA_Bill_No, - **record_data, + exists = TrenchExcavation.query.filter_by( + subcontractor_id=subcontractor_id, + RA_Bill_No=RA_Bill_No, + Location=location, + MH_NO=mh_no, + ).first() + + if exists: + errors.append( + f"Model-Tr.Ex. (Row {idx+1}): Duplicate → Location={location}, MH_NO={mh_no}" ) - db.session.add(record) + continue - db.session.commit() - return True, "Trench Excavation saved successfully." + record_data = {} + for col in df.columns: + if hasattr(TrenchExcavation, col): + val = row[col] + if pd.isna(val) or str(val).strip() in ["", "-", "—", "nan"]: + val = None + record_data[col] = val - except Exception as e: - db.session.rollback() - raise e + record = TrenchExcavation( + subcontractor_id=subcontractor_id, + RA_Bill_No=RA_Bill_No, + **record_data, + ) + db.session.add(record) - # Manhole Excavation (Subcontractor) + if errors: + raise Exception(" | ".join(errors)) + + db.session.commit() + + # ---------------- Manhole Excavation (Subcontractor) ---------------- def process_manhole_excavation(self, df, subcontractor_id, RA_Bill_No): df.columns = ( @@ -131,43 +145,49 @@ class FileService: if "Location" in df.columns: df["Location"] = df["Location"].ffill() - try: - for _, row in df.iterrows(): - location = row.get("Location") - mh_no = row.get("MH_NO") + errors = [] - if ( - pd.isna(location) - or str(location).strip() == "" - or pd.isna(mh_no) - or str(mh_no).strip() == "" - ): - continue + for idx, row in df.iterrows(): + location = self.normalize(row.get("Location")) + mh_no = self.normalize(row.get("MH_NO")) - record_data = {} - for col in df.columns: - if hasattr(ManholeExcavation, col): - val = row[col] - if pd.isna(val) or str(val).strip() in ["", "-", "—", "nan"]: - val = None - record_data[col] = val + if not location or not mh_no: + continue - record = ManholeExcavation( - subcontractor_id=subcontractor_id, - RA_Bill_No=RA_Bill_No, - **record_data, + exists = ManholeExcavation.query.filter_by( + subcontractor_id=subcontractor_id, + RA_Bill_No=RA_Bill_No, + Location=location, + MH_NO=mh_no, + ).first() + + if exists: + errors.append( + f"Model-MH Ex. (Row {idx+1}): Duplicate → Location={location}, MH_NO={mh_no}" ) - db.session.add(record) + continue - db.session.commit() - return True, "Manhole Excavation saved successfully." + record_data = {} + for col in df.columns: + if hasattr(ManholeExcavation, col): + val = row[col] + if pd.isna(val) or str(val).strip() in ["", "-", "—", "nan"]: + val = None + record_data[col] = val - except Exception as e: - db.session.rollback() - raise e + record = ManholeExcavation( + subcontractor_id=subcontractor_id, + RA_Bill_No=RA_Bill_No, + **record_data, + ) + db.session.add(record) - - # Manhole & Domestic Chamber (Subcontractor) + if errors: + raise Exception(" | ".join(errors)) + + db.session.commit() + + # ---------------- Manhole & Domestic Chamber (Subcontractor) ---------------- def process_manhole_domestic_chamber(self, df, subcontractor_id, RA_Bill_No): df.columns = ( @@ -183,43 +203,49 @@ class FileService: if "Location" in df.columns: df["Location"] = df["Location"].ffill() - try: - for _, row in df.iterrows(): - location = row.get("Location") - node_no = row.get("MH_NO") + errors = [] - if ( - pd.isna(location) - or str(location).strip() == "" - or pd.isna(node_no) - or str(node_no).strip() == "" - ): - continue + for idx, row in df.iterrows(): + location = self.normalize(row.get("Location")) + mh_no = self.normalize(row.get("MH_NO")) - record_data = {} - for col in df.columns: - if hasattr(ManholeDomesticChamber, col): - val = row[col] - if pd.isna(val) or str(val).strip() in ["", "-", "—", "nan"]: - val = None - record_data[col] = val + if not location or not mh_no: + continue - record = ManholeDomesticChamber( - subcontractor_id=subcontractor_id, - RA_Bill_No=RA_Bill_No, - **record_data, + exists = ManholeDomesticChamber.query.filter_by( + subcontractor_id=subcontractor_id, + RA_Bill_No=RA_Bill_No, + Location=location, + MH_NO=mh_no, + ).first() + + if exists: + errors.append( + f"Model-MH & DC (Row {idx+1}): Duplicate → Location={location}, MH_NO={mh_no}" ) - db.session.add(record) + continue - db.session.commit() - return True, "Manhole Domestic Chamber saved successfully." + record_data = {} + for col in df.columns: + if hasattr(ManholeDomesticChamber, col): + val = row[col] + if pd.isna(val) or str(val).strip() in ["", "-", "—", "nan"]: + val = None + record_data[col] = val - except Exception as e: - db.session.rollback() - raise e + record = ManholeDomesticChamber( + subcontractor_id=subcontractor_id, + RA_Bill_No=RA_Bill_No, + **record_data, + ) + db.session.add(record) - - # ------------------- CLIENT FILE UPLOAD -------------------------- + if errors: + raise Exception(" | ".join(errors)) + + db.session.commit() + + # ---------------- CLIENT FILE UPLOAD ---------------- def handle_client_file_upload(self, file, RA_Bill_No): if not RA_Bill_No: @@ -245,26 +271,18 @@ class FileService: df_mh_ex = pd.read_excel(filepath, sheet_name="MH Ex.", header=4) df_mh_dc = pd.read_excel(filepath, sheet_name="MH & DC", header=3) - print("------------------") - print("df_tr_ex :",df_tr_ex) - print("------------------") - print("df_mh_ex :",df_mh_ex) - print("------------------") - print("df_mh_dc :",df_mh_dc) - print("------------------") - self.save_client_data(df_tr_ex, TrenchExcavationClient, RA_Bill_No) self.save_client_data(df_mh_ex, ManholeExcavationClient, RA_Bill_No) self.save_client_data(df_mh_dc, ManholeDomesticChamberClient, RA_Bill_No) db.session.commit() - return True, "Client file uploaded and all data saved successfully." + return True, "Client file uploaded successfully." except Exception as e: db.session.rollback() - return False, f"Client data save failed: {e}" + return False, f"Client import failed: {e}" - # Client all model Save Method + # ---------------- CLIENT SAVE METHOD ---------------- def save_client_data(self, df, model, RA_Bill_No): df.columns = [str(c).strip() for c in df.columns] @@ -274,23 +292,13 @@ class FileService: df = df.dropna(how="all") - if "Location" in df.columns: - missing_loc = df[ - df["Location"].isna() - | (df["Location"].astype(str).str.strip() == "") - ] - if not missing_loc.empty: - raise Exception( - f"{model.__name__}: Empty Location at rows {missing_loc.index.tolist()}" - ) - - for _, row in df.iterrows(): + for idx, row in df.iterrows(): record_data = {} for col in df.columns: if hasattr(model, col): val = row[col] - if pd.isna(val) or str(val).strip() in ["", "-", "—", "nan", "NaN"]: + if pd.isna(val) or str(val).strip() in ["", "-", "—", "nan"]: val = None record_data[col] = val diff --git a/app/services/user_service.py b/app/services/user_service.py index 88da95a..59f787b 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -1,47 +1,27 @@ -# from app.models.user_model import User -# from app.services.db_service import db -# import logging - -# class UserService: - -# @staticmethod -# def register_user(name, email, password): -# user = User(name=name, email=email) -# user.set_password(password) -# db.session.add(user) -# db.session.commit() -# logging.info(f"New user registered: {email}") -# return user - -# @staticmethod -# def validate_login(email, password): -# user = User.query.filter_by(email=email).first() -# if user and user.check_password(password): -# logging.info(f"Login success: {email}") -# return user -# logging.warning(f"Login failed for: {email}") -# return None - -# @staticmethod -# def get_all_users(): -# return User.query.all() - - -from app.services.db_service import DBService from app.models.user_model import User +from app.services.db_service import db class UserService: - def get_all_users(self): - db = DBService().connect() - cursor = db.cursor(dictionary=True) + @staticmethod + def register_user(name, email, password): + if User.query.filter_by(email=email).first(): + return None - cursor.execute("SELECT id, name, email FROM users") - rows = cursor.fetchall() + user = User(name=name, email=email) + user.set_password(password) - users = [User(**row) for row in rows] + db.session.add(user) + db.session.commit() + return user - cursor.close() - db.close() + @staticmethod + def validate_login(email, password): + user = User.query.filter_by(email=email).first() + if user and user.check_password(password): + return user + return None - return users + @staticmethod + def get_all_users(): + return User.query.all() diff --git a/app/templates/base.html b/app/templates/base.html index 49d95eb..b4ba845 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -60,7 +60,15 @@ - + {% if session.get("user_id") %} + + + {% endif %} + diff --git a/app/templates/register.html b/app/templates/register.html index d09a31b..80090fd 100644 --- a/app/templates/register.html +++ b/app/templates/register.html @@ -1,18 +1,17 @@ {% extends "base.html" %} {% block content %} -

User Registration

+

Register User

+ + - - + + - - - - - + +
diff --git a/app/utils/helpers.py b/app/utils/helpers.py index 2ab63b9..ba87d21 100644 --- a/app/utils/helpers.py +++ b/app/utils/helpers.py @@ -1,2 +1,14 @@ -def is_logged_in(session): - return session.get("user_id") is not None +# def is_logged_in(session): +# return session.get("user_id") is not None + +from functools import wraps +from flask import session, redirect, url_for, flash + +def login_required(view): + @wraps(view) + def wrapped_view(*args, **kwargs): + if "user_id" not in session: + flash("Please login first", "warning") + return redirect(url_for("auth.login")) + return view(*args, **kwargs) + return wrapped_view