from flask import Blueprint, render_template, request, send_file, flash from collections import defaultdict import pandas as pd import io from app.models.subcontractor_model import Subcontractor 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.laying_model import Laying from app.models.tr_ex_client_model import TrenchExcavationClient from app.models.mh_ex_client_model import ManholeExcavationClient 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: return None return str(value).strip().upper() # HEADER FORMATTER def format_header(header): if "-" in header: prefix, rest = header.split("-", 1) prefix = prefix.title() else: prefix, rest = None, header parts = rest.split("_") result = [] i = 0 while i < len(parts): if i + 1 < len(parts) and parts[i].isdigit() and parts[i + 1].isdigit(): result.append(f"{parts[i]}.{parts[i + 1]}") i += 2 else: result.append(parts[i].title()) i += 1 final_text = " ".join(result) return f"{prefix}-{final_text}" if prefix else final_text # LOOKUP CREATOR def make_lookup(rows, 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.setdefault((location, key_val), []).append(r) return lookup # COMPARISON BUILDER def build_comparison(client_rows, contractor_rows, key_field): contractor_lookup = make_lookup(contractor_rows, key_field) output = [] used_index = defaultdict(int) # 🔥 THIS FIXES YOUR ISSUE for c in client_rows: client_location = normalize_key(c.get("Location")) client_key = normalize_key(c.get(key_field)) if not client_location or not client_key: continue subs = contractor_lookup.get((client_location, client_key)) if not subs: continue idx = used_index[(client_location, client_key)] # ❗ If subcontractor rows are exhausted, skip if idx >= len(subs): continue s = subs[idx] # ✅ take NEXT subcontractor row used_index[(client_location, client_key)] += 1 # ---- totals ---- client_total = sum( float(v or 0) for k, v in c.items() 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") or D_RANGE_PATTERN.match(k) or PIPE_MM_PATTERN.match(k) ) row = { "Location": client_location, key_field.replace("_", " "): client_key } for k, v in c.items(): if k not in ["id", "created_at"]: row[f"Client-{k}"] = v row["Client-Total"] = round(client_total, 2) row[" "] = "" for k, v in s.items(): if k not in ["id", "created_at", "subcontractor_id"]: 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 # 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}) 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"}) ws.merge_range( 0, 0, 0, len(df.columns) - 1, "CLIENT vs SUBCONTRACTOR", title_fmt ) ws.merge_range( 1, 0, 1, len(df.columns) - 1, f"Subcontractor 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") 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) else: ws.write(3, col_num, col_name, default_header_fmt) ws.set_column(col_num, col_num, 20) # REPORT ROUTE @generate_report_bp.route("/comparison_report", methods=["GET", "POST"]) @login_required 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 subcontractor", "danger") return render_template("generate_comparison_report.html",subcontractors=subcontractors) subcontractor = Subcontractor.query.get_or_404(subcontractor_id) # -------- 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") 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") 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") # 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 -------- output = io.BytesIO() filename = f"{subcontractor.subcontractor_name}_Comparison_Report.xlsx" with pd.ExcelWriter(output, engine="xlsxwriter") as writer: write_sheet(writer, df_tr, "Tr.Ex", subcontractor.subcontractor_name) write_sheet(writer, df_mh, "Mh.Ex", subcontractor.subcontractor_name) write_sheet(writer, df_dc, "MH & DC", subcontractor.subcontractor_name) write_sheet(writer, df_lay, "Laying", subcontractor.subcontractor_name) output.seek(0) return send_file( output, as_attachment=True, download_name=filename, mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) 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