diff --git a/.env b/.env index c4c74c7..42d67f5 100644 --- a/.env +++ b/.env @@ -3,7 +3,7 @@ # ----------------------------- FLASK_ENV=development FLASK_DEBUG=True -FLASK_HOST='0.0.0.0' +FLASK_HOST=0.0.0.0 FLASK_PORT=5001 # ----------------------------- @@ -20,7 +20,7 @@ DB_HOST=127.0.0.1 DB_PORT=3306 DB_NAME=comparisondb DB_USER=root -DB_PASSWORD=root +DB_PASSWORD=admin # DATABASE_URL=mysql+pymysql://root:root@localhost/comparisondb diff --git a/app/__init__.py b/app/__init__.py index c236761..e5f62bb 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -32,7 +32,7 @@ def register_blueprints(app): from app.routes.generate_comparison_report import generate_report_bp from app.routes.file_format import file_format_bp - app.register_blueprint(auth_bp) + app.register_blueprint(auth_bp) app.register_blueprint(user_bp) app.register_blueprint(dashboard_bp) app.register_blueprint(subcontractor_bp) diff --git a/app/models/laying_client_model.py b/app/models/laying_client_model.py index 4152165..e2b35c8 100644 --- a/app/models/laying_client_model.py +++ b/app/models/laying_client_model.py @@ -5,16 +5,19 @@ class LayingClient(db.Model): __tablename__ = "laying_client" id = db.Column(db.Integer, primary_key=True) - # Foreign Key to Subcontractor tables - # subcontractor_id = db.Column(db.Integer, db.ForeignKey("subcontractors.id"), nullable=False) - # Relationship for easy access (subcontractor.subcontractor_name) - # subcontractor = db.relationship("Subcontractor", backref="laying_records") # Basic Fields Location = db.Column(db.String(500)) MH_NO = db.Column(db.String(100)) CC_length = db.Column(db.Float) + # Bedding Qty. + Outer_dia_of_MH_m = db.Column(db.Float) + Bedding_Length = db.Column(db.Float) + Width = db.Column(db.Float) + Depth = db.Column(db.Float) + Qty = db.Column(db.Float) + # PIPE LAYING Qty. Pipe_Dia_mm = db.Column(db.Float) ID_of_MH_m = db.Column(db.Float) Laying_Length = db.Column(db.Float) @@ -32,7 +35,6 @@ class LayingClient(db.Model): pipe_900_mm = db.Column(db.Float) pipe_1200_mm = db.Column(db.Float) - Total = db.Column(db.Float) Remarks = db.Column(db.String(500)) RA_Bill_No=db.Column(db.String(500)) @@ -45,3 +47,12 @@ class LayingClient(db.Model): def serialize(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + + def sum_laying_fields(): + return [ + "pipe_150_mm", "pipe_200_mm", "pipe_250_mm", + "pipe_300_mm", "pipe_350_mm", "pipe_400_mm", + "pipe_450_mm", "pipe_500_mm", "pipe_600_mm", + "pipe_700_mm", "pipe_900_mm", "pipe_1200_mm" + ] \ No newline at end of file diff --git a/app/models/laying_model.py b/app/models/laying_model.py index b8510ff..3d49343 100644 --- a/app/models/laying_model.py +++ b/app/models/laying_model.py @@ -10,11 +10,10 @@ class Laying(db.Model): # Relationship for easy access (subcontractor.subcontractor_name) subcontractor = db.relationship("Subcontractor", backref="laying_records") - # Basic Fields + # Pipe Laying Fields Location = db.Column(db.String(500)) MH_NO = db.Column(db.String(100)) CC_length = db.Column(db.Float) - Pipe_Dia_mm = db.Column(db.Float) ID_of_MH_m = db.Column(db.Float) Laying_Length = db.Column(db.Float) @@ -32,7 +31,6 @@ class Laying(db.Model): pipe_900_mm = db.Column(db.Float) pipe_1200_mm = db.Column(db.Float) - Total = db.Column(db.Float) Remarks = db.Column(db.String(500)) RA_Bill_No=db.Column(db.String(500)) @@ -45,3 +43,12 @@ class Laying(db.Model): def serialize(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + + def sum_laying_fields(): + return [ + "pipe_150_mm", "pipe_200_mm", "pipe_250_mm", + "pipe_300_mm", "pipe_350_mm", "pipe_400_mm", + "pipe_450_mm", "pipe_500_mm", "pipe_600_mm", + "pipe_700_mm", "pipe_900_mm", "pipe_1200_mm" + ] diff --git a/app/models/manhole_domestic_chamber_model.py b/app/models/manhole_domestic_chamber_model.py index 0902f0d..274198e 100644 --- a/app/models/manhole_domestic_chamber_model.py +++ b/app/models/manhole_domestic_chamber_model.py @@ -49,4 +49,15 @@ class ManholeDomesticChamber(db.Model): def serialize(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + + def sum_mh_dc_fields(): + return [ + "d_0_to_0_75", "d_0_76_to_1_05", "d_1_06_to_1_65", + "d_1_66_to_2_15", "d_2_16_to_2_65", "d_2_66_to_3_15", + "d_3_16_to_3_65", "d_3_66_to_4_15", "d_4_16_to_4_65", + "d_4_66_to_5_15", "d_5_16_to_5_65", "d_5_66_to_6_15", + "d_6_16_to_6_65", "d_6_66_to_7_15", "d_7_16_to_7_65", + "d_7_66_to_8_15", "d_8_16_to_8_65", "d_8_66_to_9_15", + "d_9_16_to_9_65"] diff --git a/app/models/mh_dc_client_model.py b/app/models/mh_dc_client_model.py index 6ea30bc..ae0d508 100644 --- a/app/models/mh_dc_client_model.py +++ b/app/models/mh_dc_client_model.py @@ -44,3 +44,14 @@ class ManholeDomesticChamberClient(db.Model): def serialize(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + + def sum_mh_dc_fields(): + return [ + "d_0_to_0_75", "d_0_76_to_1_05", "d_1_06_to_1_65", + "d_1_66_to_2_15", "d_2_16_to_2_65", "d_2_66_to_3_15", + "d_3_16_to_3_65", "d_3_66_to_4_15", "d_4_16_to_4_65", + "d_4_66_to_5_15", "d_5_16_to_5_65", "d_5_66_to_6_15", + "d_6_16_to_6_65", "d_6_66_to_7_15", "d_7_16_to_7_65", + "d_7_66_to_8_15", "d_8_16_to_8_65", "d_8_66_to_9_15", + "d_9_16_to_9_65" ] \ No newline at end of file diff --git a/app/models/trench_excavation_model.py b/app/models/trench_excavation_model.py index 446bf55..eacfd0a 100644 --- a/app/models/trench_excavation_model.py +++ b/app/models/trench_excavation_model.py @@ -77,3 +77,34 @@ class TrenchExcavation(db.Model): def serialize(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + def excavation_category_sums(self): + + def safe(val): + return val or 0 + + return { + "Soft_Murum_Total": ( + safe(self.Soft_Murum_0_to_1_5) + + safe(self.Soft_Murum_1_5_to_3_0) + + safe(self.Soft_Murum_3_0_to_4_5) + ), + + "Hard_Murum_Total": ( + safe(self.Hard_Murum_0_to_1_5) + + safe(self.Hard_Murum_1_5_to_3_0) + ), + + "Soft_Rock_Total": ( + safe(self.Soft_Rock_0_to_1_5) + + safe(self.Soft_Rock_1_5_to_3_0) + ), + + "Hard_Rock_Total": ( + safe(self.Hard_Rock_0_to_1_5) + + safe(self.Hard_Rock_1_5_to_3_0) + + safe(self.Hard_Rock_3_0_to_4_5) + + safe(self.Hard_Rock_4_5_to_6_0) + + safe(self.Hard_Rock_6_0_to_7_5) + ), + } diff --git a/app/routes/dashboard.py b/app/routes/dashboard.py index 59c16f7..f6d79c3 100644 --- a/app/routes/dashboard.py +++ b/app/routes/dashboard.py @@ -1,82 +1,136 @@ -import matplotlib -matplotlib.use("Agg") +# import matplotlib +# matplotlib.use("Agg") -from flask import Blueprint, render_template, session, redirect, url_for -import matplotlib.pyplot as plt -import io -import base64 +# from flask import Blueprint, render_template, session, redirect, url_for +# import matplotlib.pyplot as plt +# import io +# import base64 +# from app.utils.plot_utils import plot_to_base64 +# from app.services.dashboard_service import DashboardService + +# dashboard_bp = Blueprint("dashboard", __name__, url_prefix="/dashboard") + +# # dashboard_bp = Blueprint("dashboard", __name__) + +# # charts +# # def plot_to_base64(): +# # img = io.BytesIO() +# # plt.savefig(img, format="png", bbox_inches="tight") +# # plt.close() +# # img.seek(0) +# # return base64.b64encode(img.getvalue()).decode() + +# # bar chart +# def bar_chart(): +# categories = ["Trench", "Manhole", "Pipe Laying", "Restoration"] +# values = [120, 80, 150, 60] + +# plt.figure() +# plt.bar(categories, values) +# plt.title("Work Category Report") +# plt.xlabel("test Category") +# plt.ylabel("test Quantity") + + +# return plot_to_base64(plt) + +# # Pie chart +# def pie_chart(): +# labels = ["Completed", "In Progress", "Pending"] +# sizes = [55, 20, 25] + +# plt.figure() +# plt.pie(sizes, labels=labels, autopct="%1.1f%%", startangle=140) +# plt.title("Project Status") + +# return plot_to_base64(plt) + +# # Histogram chart +# def histogram_chart(): +# daily_work = [5, 10, 15, 20, 20, 25, 30, 35, 40, 45, 50] + +# plt.figure() +# plt.hist(daily_work, bins=5) +# plt.title("Daily Work Distribution") +# plt.xlabel("Work Units") +# plt.ylabel("Frequency") + +# return plot_to_base64(plt) + +# # Dashboaed page +# @dashboard_bp.route("/") +# def dashboard(): +# if not session.get("user_id"): +# return redirect(url_for("auth.login")) + +# return render_template( +# "dashboard.html", +# title="Dashboard", +# bar_chart=bar_chart(), +# pie_chart=pie_chart(), +# histogram=histogram_chart() +# ) + +# # subcontractor dashboard +# @dashboard_bp.route("/subcontractor_dashboard", methods=["GET", "POST"]) +# def subcontractor_dashboard(): +# if not session.get("user_id"): +# return redirect(url_for("auth.login")) + +# tr_dash = DashboardService().bar_chart_of_tr_ex + + +# return render_template( +# "subcontractor_dashboard.html", +# title="Dashboard", +# bar_chart=tr_dash +# ) + +from flask import Blueprint, render_template, session, redirect, url_for, jsonify +from sqlalchemy import func +from app import db +from app.models.trench_excavation_model import TrenchExcavation +from app.models.manhole_excavation_model import ManholeExcavation +from app.models.laying_model import Laying dashboard_bp = Blueprint("dashboard", __name__, url_prefix="/dashboard") -# charts -def plot_to_base64(): - img = io.BytesIO() - plt.savefig(img, format="png", bbox_inches="tight") - plt.close() - img.seek(0) - return base64.b64encode(img.getvalue()).decode() +@dashboard_bp.route("/api/live-stats") +def live_stats(): + try: + # 1. Overall Volume + t_count = TrenchExcavation.query.count() + m_count = ManholeExcavation.query.count() + l_count = Laying.query.count() -# bar chart -def bar_chart(): - categories = ["Trench", "Manhole", "Pipe Laying", "Restoration"] - values = [120, 80, 150, 60] + # 2. Location Distribution (Business reach) + loc_results = db.session.query( + TrenchExcavation.Location, + func.count(TrenchExcavation.id) + ).group_by(TrenchExcavation.Location).all() - plt.figure() - plt.bar(categories, values) - plt.title("Work Category Report") - plt.xlabel("Category") - plt.ylabel("Count") + # 3. Work Timeline (Business productivity trend) + # Assuming your models have a 'created_at' field + timeline_results = db.session.query( + func.date(TrenchExcavation.created_at), + func.count(TrenchExcavation.id) + ).group_by(func.date(TrenchExcavation.created_at)).order_by(func.date(TrenchExcavation.created_at)).all() + return jsonify({ + "summary": { + "trench": t_count, + "manhole": m_count, + "laying": l_count, + "total": t_count + m_count + l_count + }, + "locations": {row[0]: row[1] for row in loc_results if row[0]}, + "timeline": {str(row[0]): row[1] for row in timeline_results} + }) + except Exception as e: + return jsonify({"error": str(e)}), 500 - return plot_to_base64() - -# Pie chart -def pie_chart(): - labels = ["Completed", "In Progress", "Pending"] - sizes = [55, 20, 25] - - plt.figure() - plt.pie(sizes, labels=labels, autopct="%1.1f%%", startangle=140) - plt.title("Project Status") - - return plot_to_base64() - -# Histogram chart -def histogram_chart(): - daily_work = [5, 10, 15, 20, 20, 25, 30, 35, 40, 45, 50] - - plt.figure() - plt.hist(daily_work, bins=5) - plt.title("Daily Work Distribution") - plt.xlabel("Work Units") - plt.ylabel("Frequency") - - return plot_to_base64() - -# Dashboaed page @dashboard_bp.route("/") def dashboard(): if not session.get("user_id"): return redirect(url_for("auth.login")) - - return render_template( - "dashboard.html", - title="Dashboard", - bar_chart=bar_chart(), - pie_chart=pie_chart(), - histogram=histogram_chart() - ) - -# subcontractor dashboard -@dashboard_bp.route("/subcontractor_dashboard") -def subcontractor_dashboard(): - if not session.get("user_id"): - return redirect(url_for("auth.login")) - - return render_template( - "subcontractor_dashboard.html", - title="Dashboard", - bar_chart=bar_chart(), - pie_chart=pie_chart(), - histogram=histogram_chart() - ) + return render_template("dashboard.html", title="Business Intelligence Dashboard") \ No newline at end of file diff --git a/app/routes/file_import.py b/app/routes/file_import.py index c30b2bd..1dda07d 100644 --- a/app/routes/file_import.py +++ b/app/routes/file_import.py @@ -43,4 +43,4 @@ def client_import_file(): flash(msg, "success" if success else "danger") - return render_template("file_import_client.html", title="Client File Import", subcontractors=subcontractors) + return render_template("file_import_client.html", title="Client File Import", subcontractors=subcontractors) \ No newline at end of file diff --git a/app/routes/file_report.py b/app/routes/file_report.py index a4714b4..0daaade 100644 --- a/app/routes/file_report.py +++ b/app/routes/file_report.py @@ -72,7 +72,8 @@ class SubcontractorBill: for df in [self.df_tr, self.df_mh, self.df_dc, self.df_laying]: if not df.empty: df.drop(columns=drop_cols, errors="ignore", inplace=True) - + + # --- subcontractor report only --- @file_report_bp.route("/Subcontractor_report", methods=["GET", "POST"]) @login_required @@ -91,7 +92,7 @@ def report_file(): if not subcontractor_id: flash("Please select a subcontractor.", "danger") - return render_template("report.html", subcontractors=subcontractors) + return render_template("subcontractor_report.html", subcontractors=subcontractors) subcontractor = Subcontractor.query.get(subcontractor_id) bill_gen = SubcontractorBill() @@ -102,13 +103,13 @@ def report_file(): else: if not ra_bill_no: flash("Please enter an RA Bill Number.", "danger") - return render_template("report.html", subcontractors=subcontractors) + return render_template("subcontractor_report.html", subcontractors=subcontractors) bill_gen.Fetch(RA_Bill_No=ra_bill_no, subcontractor_id=subcontractor_id) file_name = f"{subcontractor.subcontractor_name}_RA_{ra_bill_no}_Report.xlsx" if bill_gen.df_tr.empty and bill_gen.df_mh.empty and bill_gen.df_dc.empty: flash("No data found for this selection.", "warning") - return render_template("report.html", subcontractors=subcontractors) + return render_template("subcontractor_report.html", subcontractors=subcontractors) # If download is clicked, return file immediately if action == "download": @@ -144,87 +145,70 @@ def report_file(): @file_report_bp.route("/client_report", methods=["GET", "POST"]) @login_required def client_vs_all_subcontractor(): - # Initialize dictionary keys to match HTML variables - tables = {"tr": None, "mh": None, "dc": None, "laying": None} + tables = {"tr": None, "mh": None, "dc": None} ra_val = "" - + if request.method == "POST": RA_Bill_No = request.form.get("RA_Bill_No") - action = request.form.get("action") # Identify if 'download' or 'preview' ra_val = RA_Bill_No - + if not RA_Bill_No: flash("Please enter RA Bill No.", "danger") return render_template("client_report.html", tables=tables, ra_val=ra_val) - + clientBill = ClientBill() clientBill.Fetch(RA_Bill_No=RA_Bill_No) contractorBill = SubcontractorBill() contractorBill.Fetch(RA_Bill_No=RA_Bill_No) - - # Safety Check: Verify if data exists - if clientBill.df_tr.empty and clientBill.df_mh.empty and clientBill.df_laying.empty: + + # --- SAFETY CHECK: Verify data exists before merging --- + if clientBill.df_tr.empty and clientBill.df_mh.empty: flash(f"No Client records found for RA Bill {RA_Bill_No}", "warning") return render_template("client_report.html", tables=tables, ra_val=ra_val) - - # Define columns based on your model field names - qty_cols = [ - "pipe_150_mm", "pipe_200_mm", "pipe_250_mm", "pipe_300_mm", - "pipe_350_mm", "pipe_400_mm", "pipe_450_mm", "pipe_500_mm", - "pipe_600_mm", "pipe_700_mm", "pipe_900_mm", "pipe_1200_mm" - ] - mh_dc_qty_cols = ["qty", "total"] # Update these based on your DC model fields - mh_lay_qty_cols = qty_cols + ["Laying_Length", "CC_length"] + + qty_cols = [...] # (Keep your existing list) + mh_dc_qty_cols = [...] # (Keep your existing list) + mh_lay_qty_cols =[...] def aggregate_df(df, group_cols, sum_cols): if df.empty: + # Create an empty DF with the correct columns to avoid Merge/Key Errors return pd.DataFrame(columns=group_cols + sum_cols) existing_cols = [c for c in sum_cols if c in df.columns] + # Ensure group_cols exist in the DF for col in group_cols: if col not in df.columns: - df[col] = "N/A" + df[col] = "N/A" # Fill missing join keys return df.groupby(group_cols, as_index=False)[existing_cols].sum() - - # Aggregate data from Subcontractor tables + + # Aggregate data df_sub_tr_grp = aggregate_df(contractorBill.df_tr, ["Location", "MH_NO"], qty_cols) df_sub_mh_grp = aggregate_df(contractorBill.df_mh, ["Location", "MH_NO"], qty_cols) df_sub_dc_grp = aggregate_df(contractorBill.df_dc, ["Location", "MH_NO"], mh_dc_qty_cols) - # FIXED: Aggregating from .df_laying - df_sub_lay_grp = aggregate_df(contractorBill.df_laying, ["Location", "MH_NO"], mh_lay_qty_cols) - - # Standardize "Location" column to prevent merge keys being missing - for df_client in [clientBill.df_tr, clientBill.df_mh, clientBill.df_dc, clientBill.df_laying]: + df_sub_lay_grp = aggregate_df(contractorBill.df_dc, ["Location", "MH_NO"], mh_lay_qty_cols) + + # --- FINAL MERGE LOGIC --- + # We check if "Location" exists in the client data. If not, we add it to prevent the KeyError. + for df_client in [clientBill.df_tr, clientBill.df_mh, clientBill.df_dc, clientBill.df_laying ]: if not df_client.empty and "Location" not in df_client.columns: df_client["Location"] = "Unknown" - + try: - # Final Merge for Comparison df_tr_cmp = clientBill.df_tr.merge(df_sub_tr_grp, on=["Location", "MH_NO"], how="left", suffixes=("_Client", "_Sub")) df_mh_cmp = clientBill.df_mh.merge(df_sub_mh_grp, on=["Location", "MH_NO"], how="left", suffixes=("_Client", "_Sub")) df_dc_cmp = clientBill.df_dc.merge(df_sub_dc_grp, on=["Location", "MH_NO"], how="left", suffixes=("_Client", "_Sub")) df_lay_cmp = clientBill.df_laying.merge(df_sub_lay_grp, on=["Location", "MH_NO"], how="left", suffixes=("_Client", "_Sub")) - - # --- EXCEL DOWNLOAD LOGIC --- - if action == "download": - output = io.BytesIO() - with pd.ExcelWriter(output, engine="xlsxwriter") as writer: - df_tr_cmp.to_excel(writer, index=False, sheet_name="Tr.Ex ") - df_mh_cmp.to_excel(writer, index=False, sheet_name="Mh.Ex ") - df_dc_cmp.to_excel(writer, index=False, sheet_name="DC ") - df_lay_cmp.to_excel(writer, index=False, sheet_name="Laying and Bedding") - output.seek(0) - file_name = f"Client_Comparison_RA_{RA_Bill_No}.xlsx" - return send_file(output, download_name=file_name, as_attachment=True) - - # --- PREVIEW HTML LOGIC --- - table_classes = 'table table-striped table-hover table-sm border shadow-sm' - tables["tr"] = df_tr_cmp.to_html(classes=table_classes, index=False) - tables["mh"] = df_mh_cmp.to_html(classes=table_classes, index=False) - tables["dc"] = df_dc_cmp.to_html(classes=table_classes, index=False) - tables["laying"] = df_lay_cmp.to_html(classes=table_classes, index=False) - except KeyError as e: - flash(f"Merge Error: Missing column {str(e)}", "danger") + flash(f"Merge Error: Missing column {str(e)}. Check if 'Location' is defined in your database models.", "danger") return render_template("client_report.html", tables=tables, ra_val=ra_val) - - return render_template("client_report.html", tables=tables, ra_val=ra_val) \ No newline at end of file + + + # Convert to HTML for preview + tables["tr"] = df_tr_cmp.to_html(classes='table table-striped table-hover table-sm', index=False) + tables["mh"] = df_mh_cmp.to_html(classes='table table-striped table-hover table-sm', index=False) + tables["dc"] = df_dc_cmp.to_html(classes='table table-striped table-hover table-sm', index=False) + tables["laying"] = df_lay_cmp.to_html(classes='table table-striped table-hover table-sm', index=False) + + + return render_template("client_report.html", tables=tables, ra_val=ra_val) + \ No newline at end of file diff --git a/app/routes/generate_comparison_report.py b/app/routes/generate_comparison_report.py index 3992134..ea344b2 100644 --- a/app/routes/generate_comparison_report.py +++ b/app/routes/generate_comparison_report.py @@ -14,10 +14,18 @@ from app.models.mh_dc_client_model import ManholeDomesticChamberClient from app.models.laying_client_model import LayingClient from app.utils.helpers import login_required +import re + generate_report_bp = Blueprint("generate_report", __name__, url_prefix="/report") +# sum field of pipe laying (pipe_150_mm) +PIPE_MM_PATTERN = re.compile(r"^pipe_\d+_mm$") +# sum fields of MH dc (d_0_to_0_75) +D_RANGE_PATTERN = re.compile( r"^d_\d+(?:_\d+)?_to_\d+(?:_\d+)?$") + + # NORMALIZER def normalize_key(value): if value is None: @@ -81,13 +89,13 @@ def build_comparison(client_rows, contractor_rows, key_field): client_total = sum( float(v or 0) for k, v in c.items() - if k.endswith("_total") + if k.endswith("_total") or D_RANGE_PATTERN.match(k) or PIPE_MM_PATTERN.match(k) ) sub_total = sum( float(v or 0) for k, v in s.items() - if k.endswith("_total") + if k.endswith("_total") or D_RANGE_PATTERN.match(k) or PIPE_MM_PATTERN.match(k) ) diff = client_total - sub_total @@ -129,10 +137,10 @@ def write_sheet(writer, df, sheet_name, subcontractor_name): ws = writer.sheets[sheet_name] 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"}) + client_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#B6DAED"}) + sub_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#F3A081"}) + total_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#F7D261"}) + diff_fmt = workbook.add_format({"bold": True, "border": 1, "bg_color": "#82DD49"}) default_header_fmt = workbook.add_format({"bold": True,"border": 1,"bg_color": "#E7E6E6","align": "center","valign": "vcenter"}) @@ -147,12 +155,13 @@ def write_sheet(writer, df, sheet_name, subcontractor_name): title_fmt ) + for col_num, col_name in enumerate(df.columns): if col_name.startswith("Client-"): ws.write(3, col_num, col_name, client_fmt) elif col_name.startswith("Subcontractor-"): ws.write(3, col_num, col_name, sub_fmt) - elif col_name.endswith("Total"): + elif col_name.endswith("_total") or col_name.endswith("_total") : ws.write(3, col_num, col_name, total_fmt) elif col_name == "Diff": ws.write(3, col_num, col_name, diff_fmt) @@ -194,12 +203,14 @@ def comparison_report(): subcontractor_id=subcontractor_id ).all()] df_dc = build_comparison(dc_client, dc_sub, "MH_NO") + # df_dc = build_comparison_mh_dc(dc_client, dc_sub, "MH_NO") lay_client = [r.serialize() for r in LayingClient.query.all()] lay_sub = [r.serialize() for r in Laying.query.filter_by( subcontractor_id=subcontractor_id ).all()] df_lay = build_comparison(lay_client, lay_sub, "MH_NO") + # df_lay = build_comparison_laying(lay_client, lay_sub, "MH_NO") # -------- EXCEL -------- @@ -223,3 +234,104 @@ def comparison_report(): return render_template("generate_comparison_report.html",subcontractors=subcontractors) +# def build_comparison_mh_dc(client_rows, contractor_rows, key_field): +# contractor_lookup = make_lookup(contractor_rows, key_field) +# mh_dc_fields = ManholeDomesticChamberClient.sum_mh_dc_fields() + +# output = [] + +# for c in client_rows: +# loc = normalize_key(c.get("Location")) +# key = normalize_key(c.get(key_field)) +# if not loc or not key: +# continue + +# s = contractor_lookup.get((loc, key)) +# if not s: +# continue + +# client_total = sum(float(c.get(f, 0) or 0) for f in mh_dc_fields) +# sub_total = sum(float(s.get(f, 0) or 0) for f in mh_dc_fields) + +# row = { +# "Location": loc, +# key_field.replace("_", " "): key +# } + +# # CLIENT – ALL FIELDS +# for k, v in c.items(): +# if k in ["id", "created_at"]: +# continue +# row[f"Client-{k}"] = v + +# row["Client-Total"] = round(client_total, 2) +# row[" "] = "" + +# # SUBCONTRACTOR – ALL FIELDS +# for k, v in s.items(): +# if k in ["id", "created_at", "subcontractor_id"]: +# continue +# row[f"Subcontractor-{k}"] = v + +# row["Subcontractor-Total"] = round(sub_total, 2) +# row["Diff"] = round(client_total - sub_total, 2) + +# output.append(row) + +# df = pd.DataFrame(output) +# df.columns = [format_header(col) for col in df.columns] +# return df + + +# def build_comparison_laying(client_rows, contractor_rows, key_field): +# contractor_lookup = make_lookup(contractor_rows, key_field) +# laying_fields = Laying.sum_laying_fields() + +# output = [] + +# for c in client_rows: +# loc = normalize_key(c.get("Location")) +# key = normalize_key(c.get(key_field)) +# if not loc or not key: +# continue + +# s = contractor_lookup.get((loc, key)) +# if not s: +# continue + +# client_total = sum(float(c.get(f, 0) or 0) for f in laying_fields) +# sub_total = sum(float(s.get(f, 0) or 0) for f in laying_fields) + +# print("--------------",key,"----------") +# print("sum -client_total ",client_total) +# print("sum -sub_total ",sub_total) +# print("Diff ---- ",client_total - sub_total) +# print("------------------------") +# row = { +# "Location": loc, +# key_field.replace("_", " "): key +# } + +# # CLIENT – ALL FIELDS +# for k, v in c.items(): +# if k in ["id", "created_at"]: +# continue +# row[f"Client-{k}"] = v + +# row["Client-Total"] = round(client_total, 2) +# row[" "] = "" + +# # SUBCONTRACTOR – ALL FIELDS +# for k, v in s.items(): +# if k in ["id", "created_at", "subcontractor_id"]: +# continue +# row[f"Subcontractor-{k}"] = v + +# row["Subcontractor-Total"] = round(sub_total, 2) +# row["Diff"] = round(client_total - sub_total, 2) + +# output.append(row) + +# df = pd.DataFrame(output) +# df.columns = [format_header(col) for col in df.columns] +# return df diff --git a/app/services/dashboard_service.py b/app/services/dashboard_service.py new file mode 100644 index 0000000..af082ad --- /dev/null +++ b/app/services/dashboard_service.py @@ -0,0 +1,49 @@ +from app.config import Config +from app import db +import matplotlib +matplotlib.use("Agg") +from app.routes.dashboard import plot_to_base64 +import matplotlib.pyplot as plt +import io +import base64 + +from app.utils.plot_utils import plot_to_base64 + +# Subcontractor models import +from app.models.trench_excavation_model import TrenchExcavation + +# Client models import +# from app.models.tr_ex_client_model import TrenchExcavationClient + + +class DashboardService: + + + # bar chart + def bar_chart_of_tr_ex(): + categories = ["Soft Murum", "Hard Murum", "Soft Rock", "Hard Rock"] + values = [120, 80, 150, 60] + + tr = TrenchExcavation() + + record = TrenchExcavation.query.first() + print(" RA_Bill_No::",record["RA_Bill_No"]) + + totals = tr.excavation_category_sums() + + # print(totals["Soft_Murum_Total"]) + # print(totals["Hard_Rock_Total"]) + + + plt.figure() + plt.bar(categories, values) + plt.title("Trench Excavation Work Category Report") + plt.xlabel("Excavation category") + plt.ylabel("Quantity") + + return plot_to_base64(plt) + + + def subcontractor_dash(): + return True + \ No newline at end of file diff --git a/app/static/downloads/format/client_format.xlsx b/app/static/downloads/format/client_format.xlsx index ac6eab6..8dc754e 100644 Binary files a/app/static/downloads/format/client_format.xlsx and b/app/static/downloads/format/client_format.xlsx differ diff --git a/app/static/downloads/format/subcontractor_format.xlsx b/app/static/downloads/format/subcontractor_format.xlsx index b82cc4d..6991030 100644 Binary files a/app/static/downloads/format/subcontractor_format.xlsx and b/app/static/downloads/format/subcontractor_format.xlsx differ diff --git a/app/templates/client_report.html b/app/templates/client_report.html index 94f8bff..ab95e43 100644 --- a/app/templates/client_report.html +++ b/app/templates/client_report.html @@ -4,17 +4,6 @@