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..067c214 100644 --- a/app/routes/dashboard.py +++ b/app/routes/dashboard.py @@ -5,16 +5,20 @@ 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() +# 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(): @@ -28,7 +32,7 @@ def bar_chart(): plt.ylabel("Count") - return plot_to_base64() + return plot_to_base64(plt) # Pie chart def pie_chart(): @@ -39,7 +43,7 @@ def pie_chart(): plt.pie(sizes, labels=labels, autopct="%1.1f%%", startangle=140) plt.title("Project Status") - return plot_to_base64() + return plot_to_base64(plt) # Histogram chart def histogram_chart(): @@ -51,7 +55,7 @@ def histogram_chart(): plt.xlabel("Work Units") plt.ylabel("Frequency") - return plot_to_base64() + return plot_to_base64(plt) # Dashboaed page @dashboard_bp.route("/") @@ -68,15 +72,16 @@ def dashboard(): ) # subcontractor dashboard -@dashboard_bp.route("/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=bar_chart(), - pie_chart=pie_chart(), - histogram=histogram_chart() - ) + bar_chart=tr_dash + ) \ 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 5fa0c0f..c574c33 100644 --- a/app/routes/file_report.py +++ b/app/routes/file_report.py @@ -72,7 +72,12 @@ 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) - + + print(":::-- TR. EX --:::",self.df_tr,"\n") + print(":::--MH EX--:::",self.df_mh,"\n") + print(":::--MH DC--:::",self.df_dc,"\n") + print(":::--Laying--:::",self.df_laying,"\n") + # --- subcontractor report only --- @file_report_bp.route("/Subcontractor_report", methods=["GET", "POST"]) @login_required @@ -153,7 +158,7 @@ def client_vs_all_subcontractor(): if not RA_Bill_No: flash("Please enter RA Bill No.", "danger") - return render_template("generate_comparison_client_vs_subcont.html", tables=tables, ra_val=ra_val) + return render_template("client_report.html", tables=tables, ra_val=ra_val) clientBill = ClientBill() clientBill.Fetch(RA_Bill_No=RA_Bill_No) @@ -163,7 +168,7 @@ def client_vs_all_subcontractor(): # --- 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("generate_comparison_client_vs_subcont.html", tables=tables, ra_val=ra_val) + return render_template("client_report.html", tables=tables, ra_val=ra_val) qty_cols = [...] # (Keep your existing list) mh_dc_qty_cols = [...] # (Keep your existing list) 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..315e584 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/static/downloads/format/~$client_format.xlsx b/app/static/downloads/format/~$client_format.xlsx new file mode 100644 index 0000000..771b882 Binary files /dev/null and b/app/static/downloads/format/~$client_format.xlsx differ diff --git a/app/utils/plot_utils.py b/app/utils/plot_utils.py new file mode 100644 index 0000000..5bed985 --- /dev/null +++ b/app/utils/plot_utils.py @@ -0,0 +1,10 @@ +import base64 +from io import BytesIO +import matplotlib +matplotlib.use("Agg") + +def plot_to_base64(plt): + img = BytesIO() + plt.savefig(img, format="png", bbox_inches="tight") + img.seek(0) + return base64.b64encode(img.read()).decode("utf-8")