diff --git a/app/__init__.py b/app/__init__.py index b589d00..caa5ea1 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -11,18 +11,19 @@ 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.dashboard import dashboard_bp from app.routes.file_import import file_import_bp - # from app.routes.user import user_bp from app.routes.file_report import file_report_bp + from app.routes.generate_comparison_report import generate_report_bp - + # app.register_blueprint(user_bp) app.register_blueprint(subcontractor_bp) app.register_blueprint(dashboard_bp) app.register_blueprint(file_import_bp) - # app.register_blueprint(user_bp) app.register_blueprint(file_report_bp) + app.register_blueprint(generate_report_bp) diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc index e29c10b..8433b70 100644 Binary files a/app/__pycache__/__init__.cpython-313.pyc and b/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/app/models/__pycache__/manhole_domestic_chamber_model.cpython-313.pyc b/app/models/__pycache__/manhole_domestic_chamber_model.cpython-313.pyc index cb81deb..0482c1c 100644 Binary files a/app/models/__pycache__/manhole_domestic_chamber_model.cpython-313.pyc and b/app/models/__pycache__/manhole_domestic_chamber_model.cpython-313.pyc differ diff --git a/app/models/__pycache__/mh_ex_client_model.cpython-313.pyc b/app/models/__pycache__/mh_ex_client_model.cpython-313.pyc new file mode 100644 index 0000000..960c1b8 Binary files /dev/null and b/app/models/__pycache__/mh_ex_client_model.cpython-313.pyc differ diff --git a/app/models/__pycache__/tr_ex_client_model.cpython-313.pyc b/app/models/__pycache__/tr_ex_client_model.cpython-313.pyc new file mode 100644 index 0000000..741f451 Binary files /dev/null and b/app/models/__pycache__/tr_ex_client_model.cpython-313.pyc differ diff --git a/app/models/__pycache__/trench_excavation_model.cpython-313.pyc b/app/models/__pycache__/trench_excavation_model.cpython-313.pyc index 9eb233a..35e6b01 100644 Binary files a/app/models/__pycache__/trench_excavation_model.cpython-313.pyc and b/app/models/__pycache__/trench_excavation_model.cpython-313.pyc differ diff --git a/app/models/mh_dc_client_model.py b/app/models/mh_dc_client_model.py new file mode 100644 index 0000000..c9aa264 --- /dev/null +++ b/app/models/mh_dc_client_model.py @@ -0,0 +1,45 @@ +from app import db +from datetime import datetime + +class ManholeDomesticChamberClient(db.Model): + __tablename__ = "mh_dc_client" + + id = db.Column(db.Integer, primary_key=True) + # Foreign Key to Subcontractor table + subcontractor_id = db.Column(db.Integer, db.ForeignKey("subcontractors.id"), nullable=False) + # Relationship for easy access (subcontractor.subcontractor_name) + subcontractor = db.relationship("Subcontractor", backref="mh_dc_records") + + # Basic Fields + Location = db.Column(db.String(255)) + Node_No = db.Column(db.String(100)) + Depth_of_MH = db.Column(db.Float) + + # Excavation categories + d_0_to_0_75 = db.Column(db.Float) + d_1_06_to_1_65 = db.Column(db.Float) + d_1_06_to_1_65 = db.Column(db.Float) + d_1_66_to_2_15 = db.Column(db.Float) + d_2_16_to_2_65 = db.Column(db.Float) + d_2_66_to_3_15 = db.Column(db.Float) + d_3_16_to_3_65= db.Column(db.Float) + d_3_66_to_4_15 = db.Column(db.Float) + d_4_16_to_4_65 = db.Column(db.Float) + d_4_66_to_5_15 = db.Column(db.Float) + d_5_16_to_5_65 = db.Column(db.Float) + + d_5_66_to_6_15 = db.Column(db.Float) + d_6_16_to_6_65 = db.Column(db.Float) + d_6_66_to_7_15 = db.Column(db.Float) + d_7_16_to_7_65 = db.Column(db.Float) + d_7_66_to_8_15 = db.Column(db.Float) + d_8_16_to_8_65 = db.Column(db.Float) + d_8_66_to_9_15 = db.Column(db.Float) + d_9_16_to_9_65 = db.Column(db.Float) + + Domestic_Chambers = db.Column(db.Float) + + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def __repr__(self): + return f"" diff --git a/app/models/mh_ex_client_model.py b/app/models/mh_ex_client_model.py new file mode 100644 index 0000000..ce67abd --- /dev/null +++ b/app/models/mh_ex_client_model.py @@ -0,0 +1,66 @@ +from app import db +from datetime import datetime + +class ManholeExcavationClient(db.Model): + __tablename__ = "mh_ex_client" + + id = db.Column(db.Integer, primary_key=True) + # Foreign Key to Subcontractor table + subcontractor_id = db.Column(db.Integer, db.ForeignKey("subcontractors.id"), nullable=False) + # Relationship for easy access (subcontractor.subcontractor_name) + subcontractor = db.relationship("Subcontractor", backref="mh_ex_records") + + # Basic Fields + Location = db.Column(db.String(255)) + MH_NO = db.Column(db.String(100)) + GL_m = db.Column(db.Float) + + + + Upto_IL_Depth = db.Column(db.Float) + Cutting_Depth = db.Column(db.Float) + ID_of_MH_m = db.Column(db.Float) + Ex_Dia_of_Manhole = db.Column(db.Float) + Area_of_Manhole = db.Column(db.Float) + + # Excavation categories + Soft_Murum_0_to_1_5 = db.Column(db.Float) + Soft_Murum_1_5_to_3_0 = db.Column(db.Float) + Soft_Murum_3_0_to_4_5 = db.Column(db.Float) + + Hard_Murum_0_to_1_5 = db.Column(db.Float) + Hard_Murum_1_5_to_3_0 = db.Column(db.Float) + + Soft_Rock_0_to_1_5 = db.Column(db.Float) + Soft_Rock_1_5_to_3_0 = db.Column(db.Float) + + Hard_Rock_0_to_1_5 = db.Column(db.Float) + Hard_Rock_1_5_to_3_0 = db.Column(db.Float) + Hard_Rock_3_0_to_4_5 = db.Column(db.Float) + Hard_Rock_4_5_to_6_0 = db.Column(db.Float) + Hard_Rock_6_0_to_7_5 = db.Column(db.Float) + + # Totals + Soft_Murum_0_to_1_5_total = db.Column(db.Float) + Soft_Murum_1_5_to_3_0_total = db.Column(db.Float) + Soft_Murum_3_0_to_4_5_total = db.Column(db.Float) + + Hard_Murum_0_to_1_5_total = db.Column(db.Float) + Hard_Murum_1_5_and_above_total = db.Column(db.Float) + + Soft_Rock_0_to_1_5_total = db.Column(db.Float) + Soft_Rock_1_5_and_above_total = db.Column(db.Float) + + Hard_Rock_0_to_1_5_total = db.Column(db.Float) + Hard_Rock_1_5_and_above_total = db.Column(db.Float) + Hard_Rock_3_0_to_4_5_total = db.Column(db.Float) + Hard_Rock_4_5_to_6_0_total = db.Column(db.Float) + Hard_Rock_6_0_to_7_5_total = db.Column(db.Float) + + Remarks = db.Column(db.String(500)) + Total = db.Column(db.Float) + + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def __repr__(self): + return f"" diff --git a/app/models/tr_ex_client_model.py b/app/models/tr_ex_client_model.py new file mode 100644 index 0000000..6438a0b --- /dev/null +++ b/app/models/tr_ex_client_model.py @@ -0,0 +1,84 @@ +from app import db +from datetime import datetime + +class TrenchExcavationClient(db.Model): + __tablename__ = "tr_ex_client" + + id = db.Column(db.Integer, primary_key=True) + # Foreign Key to Subcontractor table + subcontractor_id = db.Column(db.Integer, db.ForeignKey("subcontractors.id"), nullable=False) + # Relationship for easy access (subcontractor.subcontractor_name) + subcontractor = db.relationship("Subcontractor", backref="tr_ex_records") + + # Basic Fields + Location = db.Column(db.String(255)) + MH_NO = db.Column(db.String(100)) + CC_length = db.Column(db.Float) + Actual_Trench_Length = db.Column(db.Float) + Ground_Level = db.Column(db.Float) + Invert_Level = db.Column(db.Float) + Excavated_level = db.Column(db.Float) + Cutting_Depth = db.Column(db.Float) + Avg_Depth = db.Column(db.Float) + Pipe_Dia_mm = db.Column(db.Float) + + # width + Width_0_to_1_5 = db.Column(db.Float) + Width_1_5_to_3_0 = db.Column(db.Float) + Width_3_0_to_4_5 = db.Column(db.Float) + Width_4_5_to_6_0 = db.Column(db.Float) + + + # Excavation categories + Marshi_Muddy_Slushy_0_to_1_5 = db.Column(db.Float) + Marshi_Muddy_Slushy_1_5_to_3_0 = db.Column(db.Float) + Marshi_Muddy_Slushy_3_0_to_4_5 = db.Column(db.Float) + + Soft_Murum_0_to_1_5 = db.Column(db.Float) + Soft_Murum_1_5_to_3_0 = db.Column(db.Float) + Soft_Murum_3_0_to_4_5 = db.Column(db.Float) + + Hard_Murum_0_to_1_5 = db.Column(db.Float) + Hard_Murum_1_5_to_3_0 = db.Column(db.Float) + Hard_Murum_3_0_to_4_5 = db.Column(db.Float) + + Soft_Rock_0_to_1_5 = db.Column(db.Float) + Soft_Rock_1_5_to_3_0 = db.Column(db.Float) + Soft_Rock_3_0_to_4_5 = db.Column(db.Float) + + Hard_Rock_0_to_1_5 = db.Column(db.Float) + Hard_Rock_1_5_to_3_0 = db.Column(db.Float) + Hard_Rock_3_0_to_4_5 = db.Column(db.Float) + Hard_Rock_4_5_to_6_0 = db.Column(db.Float) + Hard_Rock_6_0_to_7_5 = db.Column(db.Float) + + # Totals + Marshi_Muddy_Slushy_0_to_1_5_total = db.Column(db.Float) + Marshi_Muddy_Slushy_1_5_to_3_0_total = db.Column(db.Float) + Marshi_Muddy_Slushy_3_0_to_4_5_total = db.Column(db.Float) + + Soft_Murum_0_to_1_5_total = db.Column(db.Float) + Soft_Murum_1_5_to_3_0_total = db.Column(db.Float) + Soft_Murum_3_0_to_4_5_total = db.Column(db.Float) + + Hard_Murum_0_to_1_5_total = db.Column(db.Float) + Hard_Murum_1_5_to_3_0_total = db.Column(db.Float) + Hard_Murum_3_0_to_4_5_total = db.Column(db.Float) + + Soft_Rock_0_to_1_5_total = db.Column(db.Float) + Soft_Rock_1_5_to_3_0_total = db.Column(db.Float) + Soft_Rock_3_0_to_4_5_total = db.Column(db.Float) + + Hard_Rock_0_to_1_5_total = db.Column(db.Float) + Hard_Rock_1_5_to_3_0_total = db.Column(db.Float) + Hard_Rock_3_0_to_4_5_total = db.Column(db.Float) + Hard_Rock_4_5_to_6_0_total = db.Column(db.Float) + Hard_Rock_6_0_to_7_5_total = db.Column(db.Float) + + Remarks = db.Column(db.String(500)) + Total = db.Column(db.Float) + + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def __repr__(self): + return f"" diff --git a/app/models/trench_excavation_model.py b/app/models/trench_excavation_model.py index 6df15ff..c8de958 100644 --- a/app/models/trench_excavation_model.py +++ b/app/models/trench_excavation_model.py @@ -21,6 +21,7 @@ class TrenchExcavation(db.Model): Actual_Trench_Length = db.Column(db.Float) Pipe_Dia_mm = db.Column(db.Float) + # width Width_0_to_2_5 = db.Column(db.Float) Width_2_5_to_3_0 = db.Column(db.Float) Width_3_0_to_4_5 = db.Column(db.Float) diff --git a/app/routes/__pycache__/file_import.cpython-313.pyc b/app/routes/__pycache__/file_import.cpython-313.pyc index 080be18..433feb3 100644 Binary files a/app/routes/__pycache__/file_import.cpython-313.pyc and b/app/routes/__pycache__/file_import.cpython-313.pyc differ diff --git a/app/routes/__pycache__/file_report.cpython-313.pyc b/app/routes/__pycache__/file_report.cpython-313.pyc index 9ff4879..f7ad4da 100644 Binary files a/app/routes/__pycache__/file_report.cpython-313.pyc and b/app/routes/__pycache__/file_report.cpython-313.pyc differ diff --git a/app/routes/__pycache__/generate_comparison_report.cpython-313.pyc b/app/routes/__pycache__/generate_comparison_report.cpython-313.pyc new file mode 100644 index 0000000..0d06938 Binary files /dev/null and b/app/routes/__pycache__/generate_comparison_report.cpython-313.pyc differ diff --git a/app/routes/file_import.py b/app/routes/file_import.py index 88aae68..e85809e 100644 --- a/app/routes/file_import.py +++ b/app/routes/file_import.py @@ -18,4 +18,21 @@ def import_file(): flash(msg, "success" if success else "danger") - return render_template("file_import.html", title="File Import", subcontractors=subcontractors) + return render_template("file_import.html", title="Sub-cont. File Import", subcontractors=subcontractors) + + +@file_import_bp.route("/import_client", methods=["GET", "POST"]) +def client_import_file(): + subcontractors = Subcontractor.query.all() + + if request.method == "POST": + file = request.files.get("file") + subcontractor_id = request.form.get("subcontractor_id") + file_type = request.form.get("file_type") + + service = FileService() + success, msg = service.handle_file_upload(file, subcontractor_id, file_type) + + flash(msg, "success" if success else "danger") + + return render_template("file_import_client.html", title="Client File Import", subcontractors=subcontractors) diff --git a/app/routes/generate_comparison_report.py b/app/routes/generate_comparison_report.py new file mode 100644 index 0000000..d363e2d --- /dev/null +++ b/app/routes/generate_comparison_report.py @@ -0,0 +1,218 @@ +from flask import Blueprint, render_template, request, send_file, flash +from app.models.subcontractor_model import Subcontractor + +from app.models.trench_excavation_model import TrenchExcavation +from app.models.tr_ex_client_model import TrenchExcavationClient +from app import db + +import pandas as pd +import io + +generate_report_bp = Blueprint("generate_report", __name__, url_prefix="/report") + + +COMPARISON_COLUMNS = [ + ("Soft Murum 0–1.5", + "Soft_Murum_0_to_1_5_total", + "Soft_Murum_0_to_1_5_total"), + + ("Soft Murum 1.5–3.0", + "Soft_Murum_1_5_to_3_0_total", + "Soft_Murum_1_5_to_3_0_total"), + + ("Soft Murum 3.0–4.5", + "Soft_Murum_3_0_to_4_5_total", + "Soft_Murum_3_0_to_4_5_total"), + + ("Hard Murum 0–1.5", + "Hard_Murum_0_to_1_5_total", + "Hard_Murum_0_to_1_5_total"), + + ("Hard Murum Above 1.5", + "Hard_Murum_1_5_and_above_total", + "Hard_Murum_1_5_to_3_0_total"), + + ("Soft Rock 0–1.5", + "Soft_Rock_0_to_1_5_total", + "Soft_Rock_0_to_1_5_total"), + + ("Soft Rock Above 1.5", + "Soft_Rock_1_5_and_above_total", + "Soft_Rock_1_5_to_3_0_total"), + + ("Hard Rock 0–1.5", + "Hard_Rock_0_to_1_5_total", + "Hard_Rock_0_to_1_5_total"), + + ("Hard Rock Above 1.5", + "Hard_Rock_1_5_and_above_total", + "Hard_Rock_1_5_to_3_0_total"), + + ("Hard Rock 3.0–4.5", + "Hard_Rock_3_0_to_4_5_total", + "Hard_Rock_3_0_to_4_5_total"), + + ("Hard Rock 4.5–6.0", + "Hard_Rock_4_5_to_6_0_total", + "Hard_Rock_4_5_to_6_0_total"), + + ("Hard Rock 6.0–7.5", + "Hard_Rock_6_0_to_7_5_total", + "Hard_Rock_6_0_to_7_5_total"), +] +@generate_report_bp.route("/comparison_report", methods=["GET", "POST"]) +def comparison_report(): + subcontractors = Subcontractor.query.all() + + if request.method == "POST": + subcontractor_id = request.form.get("subcontractor_id") + + if not subcontractor_id: + flash("Please select a subcontractor.", "danger") + return render_template("generate_comparison_report.html", subcontractors=subcontractors) + + subcontractor = Subcontractor.query.get_or_404(subcontractor_id) + + # Fetch data + contractor_rows = TrenchExcavation.query.filter_by(subcontractor_id=subcontractor_id).all() + client_rows = TrenchExcavationClient.query.filter_by(subcontractor_id=subcontractor_id).all() + + # Convert to DataFrame + df_contractor = pd.DataFrame([r.__dict__ for r in contractor_rows]) + df_client = pd.DataFrame([r.__dict__ for r in client_rows]) + + # Drop unwanted columns + drop_cols = ["id", "subcontractor_id", "created_at", "_sa_instance_state", "Remarks"] + df_contractor.drop(columns=drop_cols, errors="ignore", inplace=True) + df_client.drop(columns=drop_cols, errors="ignore", inplace=True) + + # Convert to numeric + df_contractor = df_contractor.apply(pd.to_numeric, errors="coerce").fillna(0) + df_client = df_client.apply(pd.to_numeric, errors="coerce").fillna(0) + + # CREATE SUMMARY + + summary = [] + + for label, cont_col, client_col in COMPARISON_COLUMNS: + cont_sum = round(df_contractor.get(cont_col, pd.Series()).sum(), 2) + client_sum = round(df_client.get(client_col, pd.Series()).sum(), 2) + diff = round(client_sum - cont_sum, 2) + + summary.append({ + "Excavation Type": label, + "Subcontractor Total": cont_sum, + "Client Total": client_sum, + "Difference (Client - Subcontractor)": diff + }) + + df_summary = pd.DataFrame(summary) + + + # EXPORT EXCEL + output = io.BytesIO() + file_name = f"{subcontractor.subcontractor_name}_Comparison_Report.xlsx" + + with pd.ExcelWriter(output, engine="xlsxwriter") as writer: + df_contractor.to_excel(writer, index=False, sheet_name="Contractor_Data") + df_client.to_excel(writer, index=False, sheet_name="Client_Data") + df_summary.to_excel(writer, index=False, sheet_name="Comparison_Summary") + + output.seek(0) + + return send_file( + output, + as_attachment=True, + download_name=file_name, + mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ) + + return render_template("generate_comparison_report.html", subcontractors=subcontractors) + + + + +# @generate_report_bp.route("/comparison_report", methods=["GET", "POST"]) +# def report_subcont_client(): +# subcontractors = Subcontractor.query.all() + +# if request.method == "POST": +# subcontractor_id = request.form.get("subcontractor_id") + +# if not subcontractor_id: +# flash("Please select a subcontractor.", "danger") +# return render_template("report.html", subcontractors=subcontractors) + +# # Fetch subcontractor for file name +# subcontractor = Subcontractor.query.get(subcontractor_id) + +# trench_excavation1 = TrenchExcavation.query.filter_by(subcontractor_id=subcontractor_id).all() +# trench_excavation2 = TrenchExcavationClient.query.filter_by(subcontractor_id=subcontractor_id).all() + + + +# # Convert to DataFrame +# df_tr_ex1 = pd.DataFrame([m.__dict__ for m in trench_excavation1]) +# df_tr_ex2 = pd.DataFrame([t.__dict__ for t in trench_excavation2]) + + +# # Drop unnecessary SQLAlchemy fields +# drop_cols = ["id", "subcontractor_id", "created_at", "_sa_instance_state","Remarks"] + +# df_tr_ex1.drop(columns=drop_cols, errors="ignore", inplace=True) +# df_tr_ex2.drop(columns=drop_cols, errors="ignore", inplace=True) + + +# trench_columns = [ +# "Location", "MH_NO", "CC_length", "Invert_Level", "MH_Top_Level", +# "Ground_Level", "ID_of_MH_m", "Actual_Trench_Length", "Pipe_Dia_mm", + +# "Width_0_to_2_5", "Width_2_5_to_3_0", "Width_3_0_to_4_5", "Width_4_5_to_6_0", + +# "Upto_IL_Depth", "Cutting_Depth", "Avg_Depth", + +# "Soft_Murum_0_to_1_5", "Soft_Murum_1_5_to_3_0", "Soft_Murum_3_0_to_4_5", +# "Hard_Murum_0_to_1_5", "Hard_Murum_1_5_to_3_0", + +# "Soft_Rock_0_to_1_5", "Soft_Rock_1_5_to_3_0", + +# "Hard_Rock_0_to_1_5", "Hard_Rock_1_5_to_3_0", +# "Hard_Rock_3_0_to_4_5", "Hard_Rock_4_5_to_6_0", "Hard_Rock_6_0_to_7_5", + +# "Soft_Murum_0_to_1_5_total", "Soft_Murum_1_5_to_3_0_total", +# "Soft_Murum_3_0_to_4_5_total", +# "Hard_Murum_0_to_1_5_total", "Hard_Murum_1_5_and_above_total", +# "Soft_Rock_0_to_1_5_total", "Soft_Rock_1_5_and_above_total", +# "Hard_Rock_0_to_1_5_total", "Hard_Rock_1_5_and_above_total", +# "Hard_Rock_3_0_to_4_5_total", "Hard_Rock_4_5_to_6_0_total", +# "Hard_Rock_6_0_to_7_5_total", + +# "Remarks", "Total" +# ] + + +# # Reorder columns serial wise +# df_tr_ex1 = df_tr_ex1.reindex(columns=trench_columns, fill_value="") +# df_tr_ex2 = df_tr_ex2.reindex(columns=trench_columns, fill_value="") + + +# # WRITE EXCEL FILE +# output = io.BytesIO() +# file_name = f"{subcontractor.subcontractor_name}_Report.xlsx" + +# with pd.ExcelWriter(output, engine="xlsxwriter") as writer: +# df_tr_ex1.to_excel(writer, index=False, sheet_name="Tr.Ex-contractor.") +# df_tr_ex2.to_excel(writer, index=False, sheet_name="Tr.Ex-client.") + + +# output.seek(0) + +# return send_file( +# output, +# download_name=file_name, +# as_attachment=True, +# mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" +# ) + +# return render_template("generate_comparison_report.html", subcontractors=subcontractors) + diff --git a/app/services/__pycache__/file_service.cpython-313.pyc b/app/services/__pycache__/file_service.cpython-313.pyc index 1bd5a8c..9e0afbb 100644 Binary files a/app/services/__pycache__/file_service.cpython-313.pyc and b/app/services/__pycache__/file_service.cpython-313.pyc differ diff --git a/app/services/file_service.py b/app/services/file_service.py index b7a5eab..0a2e911 100644 --- a/app/services/file_service.py +++ b/app/services/file_service.py @@ -6,6 +6,8 @@ from app import db from app.models.trench_excavation_model import TrenchExcavation from app.models.manhole_excavation_model import ManholeExcavation from app.models.manhole_domestic_chamber_model import ManholeDomesticChamber +from app.models.tr_ex_client_model import TrenchExcavationClient +from app.models.mh_ex_client_model import ManholeExcavationClient from app.utils.file_utils import ensure_upload_folder @@ -42,17 +44,25 @@ class FileService: print(df.head()) print("=============================\n") - # Trench Excavation save + # Trench Excavation save (subcontractor) if file_type == "trench_excavation": return self.process_trench_excavation(df, subcontractor_id) - # Manhole Excavation save + # Manhole Excavation save (subcontractor) if file_type == "manhole_excavation": return self.process_manhole_excavation(df, subcontractor_id) - # Manhole and Domestic Chamber Construction save + # Manhole and Domestic Chamber Construction save (subcontractor) if file_type == "manhole_domestic_chamber": return self.process_manhole_domestic_chamber(df, subcontractor_id) + + # Trench Excavation save (client) + if file_type =="tr_ex_client": + return self.client_trench_excavation(df, subcontractor_id) + + # Manhole Excavation save (client) + # if file_type =="mh_ex_client": + # return self.client_manhole_excavation(df, subcontractor_id) return True, "File uploaded successfully." @@ -61,7 +71,7 @@ class FileService: return False, f"Processing failed: {e}" - + # ---------------------- Sub contractor -------------------------- # Trench Excavation save method (TrenchExcavation model) def process_trench_excavation(self, df, subcontractor_id): df.columns = [str(c).strip() for c in df.columns] @@ -105,7 +115,7 @@ class FileService: db.session.rollback() return False, f"Trench Excavation Save Failed: {e}" - + # Manhole Excavation save method (ManholeExcavation model) def process_manhole_excavation(self, df, subcontractor_id): # Clean column names (strip whitespace) @@ -201,6 +211,94 @@ class FileService: + # ---------------------- client ---------------------------------- + # Trench Excavation save method (TrenchExcavationClient model) + def client_trench_excavation(self, df, subcontractor_id): + df.columns = [str(c).strip() for c in df.columns] + # If the sheet has merged cells -> forward fill Location + if "Location" in df.columns: + df["Location"] = df["Location"].ffill() + df = df.dropna(how="all") # REMOVE empty rows + # Identify missing location rows before insert + missing_loc = df[df["Location"].isna() | (df["Location"].astype(str).str.strip() == "")] + if not missing_loc.empty: + return False, f"Error: Some rows have empty Location. Rows: {missing_loc.index.tolist()}" + saved_count = 0 + + try: + for index, row in df.iterrows(): + record_data = {} + # Insert only fields that exist in model + for col in df.columns: + if hasattr(TrenchExcavationClient, col): + value = row[col] + # Normalize empty values + if pd.isna(value) or str(value).strip() in ["", "-", "—", "nan", "NaN"]: + value = None + + record_data[col] = value + + record = TrenchExcavationClient( + subcontractor_id=subcontractor_id, + **record_data + ) + + db.session.add(record) + saved_count += 1 + + db.session.commit() + return True, f"Clinnt Tr Ex data saved successfully. Total rows: {saved_count}" + + except Exception as e: + db.session.rollback() + return False, f"Clinnt Tr Ex Save Failed: {e}" + + + # Manhole Excavation save method (ManholeExcavationClient model) + # def client_manhole_excavation(self, df, subcontractor_id): + # # Clean column names (strip whitespace) + # df.columns = [str(c).strip() for c in df.columns] + # # If the sheet has merged cells -> forward fill Location + # if "Location" in df.columns: + # df["Location"] = df["Location"].ffill() + + # # REMOVE empty rows + # df = df.dropna(how="all") + # # Identify missing location rows before insert + # missing_loc = df[df["Location"].isna() | (df["Location"].astype(str).str.strip() == "")] + # if not missing_loc.empty: + # return False, f"Error: Some rows have empty Location. Rows: {missing_loc.index.tolist()}" + + # saved_count = 0 + + # try: + # for index, row in df.iterrows(): + # record_data = {} + # # Insert only fields that exist in model + # for col in df.columns: + # if hasattr(ManholeExcavationClient, col): + # value = row[col] + + # # Normalize empty values + # if pd.isna(value) or str(value).strip() in ["", "-", "—", "nan", "NaN"]: + # value = None + + # record_data[col] = value + + # record = ManholeExcavationClient( + # subcontractor_id=subcontractor_id, + # **record_data + # ) + + # db.session.add(record) + # saved_count += 1 + + # db.session.commit() + # return True, f" Client Mh Ex. data saved successfully. Total rows: {saved_count}" + + # except Exception as e: + # db.session.rollback() + # return False, f"Client Mh Ex. Save Failed: {e}" diff --git a/app/templates/base.html b/app/templates/base.html index f77028f..89eaf69 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -24,17 +24,38 @@ @@ -61,5 +82,4 @@ - - + \ No newline at end of file diff --git a/app/templates/file_import.html b/app/templates/file_import.html index 68af913..e6bfbda 100644 --- a/app/templates/file_import.html +++ b/app/templates/file_import.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block content %} -

File Import

+

Sub-Contractor File Import

diff --git a/app/templates/file_import_client.html b/app/templates/file_import_client.html new file mode 100644 index 0000000..189e856 --- /dev/null +++ b/app/templates/file_import_client.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block content %} +

Client File Import

+ +
+ +
+ + + + + + + + + + + + + + +
+ +
+{% endblock %} \ No newline at end of file diff --git a/app/templates/generate_comparison_report.html b/app/templates/generate_comparison_report.html new file mode 100644 index 0000000..073f963 --- /dev/null +++ b/app/templates/generate_comparison_report.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block content %} +
+ +

Subcontractor vs Client Comparison

+ + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} + +
+ +
+ + + + + + +
+ +
+
+{% endblock %} \ No newline at end of file