From 01f6831e9c8d97c4bbf445cd301dbab46c9859a7 Mon Sep 17 00:00:00 2001 From: LaxmiB Date: Sat, 8 Aug 2026 17:44:47 +0530 Subject: [PATCH] Formated Client & Subctr Report --- app/routes/file_report.py | 192 ++++++++++++++++++++++++++++++++++---- 1 file changed, 176 insertions(+), 16 deletions(-) diff --git a/app/routes/file_report.py b/app/routes/file_report.py index b39ed2d..bbd5179 100644 --- a/app/routes/file_report.py +++ b/app/routes/file_report.py @@ -1,5 +1,6 @@ import pandas as pd import io +from datetime import datetime from flask import Blueprint, render_template, request, send_file, flash, jsonify,redirect, url_for from app.utils.helpers import login_required from app.utils.regex_utils import RegularExpression @@ -187,6 +188,119 @@ def render_table_or_empty(df, model_key, table_class, raw_fields=None): return html +# ---------------- EXCEL SHEET FORMATTING (Tr.Ex / Mh.Ex / MH & DC / Pipe Laying) ---------------- + +SHEET_TITLES = { + "Tr.Ex": "TRENCH EXCAVATION", + "Mh.Ex": "MANHOLE EXCAVATION", + "MH & DC": "MANHOLE DOMESTIC CHAMBER", + "Pipe Laying": "PIPE LAYING", +} + + +def format_detail_sheet(workbook, worksheet, df, sheet_name, contractor_name="", ra_bill_no="", report_date=""): + """ + Writes an info line (Category / Contractor / RA Bill No / Date) at the + top, followed by a bold/bordered table with sensible column widths + and a frozen header row. + + Assumes df.to_excel(..., startrow=1) already wrote the header at row 1 + and the data starting at row 2 - this function overwrites those rows + with formatting and fills in row 0 with the info line. + """ + if df.empty: + return + + header_format = workbook.add_format({ + "bold": True, + "bg_color": "#D9EAD3", + "border": 1, + "align": "center", + "valign": "vcenter", + "text_wrap": True + }) + + text_format = workbook.add_format({ + "border": 1, + "valign": "vcenter" + }) + + number_format = workbook.add_format({ + "border": 1, + "valign": "vcenter", + "num_format": "#,##0.00" + }) + + n_rows, n_cols = df.shape + header_row = 1 # matches startrow=1 used in df.to_excel() + + # ----------------------------------------------------- + # INFO LINE (row 0) - separate cells, not one merged block, + # so each field (RA Bill No, Date, etc.) can be clicked/copied + # on its own. Always starts at column 0, so position is + # consistent regardless of how many table columns exist. + # ----------------------------------------------------- + last_col = max(n_cols - 1, 0) + report_title = SHEET_TITLES.get(sheet_name, sheet_name.upper()) + + label_format = workbook.add_format({"bold": True}) + value_format = workbook.add_format({}) + + info_fields = [("Category:", report_title)] + + # Client reports don't have a single contractor (data can span + # multiple subcontractors), so this field is only shown when a + # contractor name is actually passed in (Subcontractor report). + if contractor_name: + info_fields.append(("Contractor:", contractor_name)) + + info_fields.append(("RA Bill No:", ra_bill_no or "All")) + info_fields.append(("Date:", report_date)) + + col = 0 + for label, value in info_fields: + worksheet.write(0, col, label, label_format) + worksheet.write(0, col + 1, value, value_format) + col += 3 # leave one blank column as a visual gap before next field + + # Fill any remaining columns on row 0 with a blank bordered cell so + # the row reads cleanly if the sheet is wider than the info fields. + if last_col > col: + worksheet.write_blank(0, col, None) + + # ----------------------------------------------------- + # TABLE HEADER (row 1) - re-write with formatting + # ----------------------------------------------------- + for col_idx, col_name in enumerate(df.columns): + worksheet.write(header_row, col_idx, col_name, header_format) + + # ----------------------------------------------------- + # TABLE BODY (rows 2+) - borders / number formatting + # ----------------------------------------------------- + for row_idx in range(n_rows): + for col_idx, col_name in enumerate(df.columns): + value = df.iat[row_idx, col_idx] + + if pd.isna(value): + worksheet.write(header_row + 1 + row_idx, col_idx, "", text_format) + elif isinstance(value, (int, float)): + worksheet.write_number(header_row + 1 + row_idx, col_idx, float(value), number_format) + else: + worksheet.write(header_row + 1 + row_idx, col_idx, str(value), text_format) + + # Auto width columns based on header + content length + for col_idx, col_name in enumerate(df.columns): + max_len = len(str(col_name)) + if n_rows: + col_values = df.iloc[:, col_idx].astype(str) + content_max = col_values.map(len).max() + max_len = max(max_len, content_max) + worksheet.set_column(col_idx, col_idx, min(max_len + 4, 30)) + + worksheet.freeze_panes(header_row + 1, 1) + worksheet.set_row(header_row, 30) + + # ---------------- FETCH ---------------- class SubcontractorBill: def __init__(self): @@ -565,14 +679,23 @@ def report_file(): # DOWNLOAD EXCEL # =================================================== if action in ["excel", "excel_all"]: + + # Look up the contractor's real name once, up front, so it can + # be shown in the title block of every sheet. + sc_obj = next( + (s for s in subcontractors if str(s.id) == str(subcontractor_id)), + None + ) + contractor_display_name = sc_obj.subcontractor_name if sc_obj else "" + report_date = datetime.now().strftime("%d-%b-%Y") + output = io.BytesIO() - + with pd.ExcelWriter(output,engine="xlsxwriter") as writer: workbook = writer.book abstract = AbstractReportService(subcontractor_id=subcontractor_id,ra_bill_no=ra_bill_no) abstract.generate(workbook) - - + sheet_map = [ (bill.df_tr, "Tr.Ex"), (bill.df_mh, "Mh.Ex"), @@ -581,17 +704,24 @@ def report_file(): ] for df, sheet_name in sheet_map: if not df.empty: - df.to_excel(writer, sheet_name=sheet_name, index=False) + # Use a copy for the export only - bill.df_tr etc. still + # need the real "Id" column later for the Edit/Delete + # buttons on the web preview table. + export_df = df.drop(columns=["Id"], errors="ignore").copy() + export_df.insert(0, "Sr No", range(1, len(export_df) + 1)) + + export_df.to_excel(writer, sheet_name=sheet_name, index=False, startrow=1) + worksheet = writer.sheets[sheet_name] + format_detail_sheet( + workbook, worksheet, export_df, sheet_name, + contractor_name=contractor_display_name, + ra_bill_no=ra_bill_no, + report_date=report_date + ) writer.close() output.seek(0) - - sc_obj = next( - (s for s in subcontractors if str(s.id) == str(subcontractor_id)), - None - ) - sc_name = sc_obj.subcontractor_name if sc_obj else "Subcontractor" - sc_name = re.sub(r'[^A-Za-z0-9_-]+', '_', sc_name).strip('_') + sc_name = re.sub(r'[^A-Za-z0-9_-]+', '_', contractor_display_name or "Subcontractor").strip('_') name_parts = [sc_name] @@ -820,22 +950,52 @@ def client_report(): # -------- DOWNLOAD -------- if action == "download": + report_date = datetime.now().strftime("%d-%b-%Y") + output = io.BytesIO() with pd.ExcelWriter(output, engine="xlsxwriter") as writer: workbook = writer.book abstract_service.generate(workbook) - bill_gen.df_tr.to_excel(writer, index=False, sheet_name="Tr.Ex") - bill_gen.df_mh.to_excel(writer, index=False, sheet_name="Mh.Ex") - bill_gen.df_dc.to_excel(writer, index=False, sheet_name="MH & DC") - bill_gen.df_laying.to_excel(writer, index=False, sheet_name="Pipe Laying") + sheet_map = [ + (bill_gen.df_tr, "Tr.Ex"), + (bill_gen.df_mh, "Mh.Ex"), + (bill_gen.df_dc, "MH & DC"), + (bill_gen.df_laying, "Pipe Laying"), + ] + for df, sheet_name in sheet_map: + if not df.empty: + # Use a copy for the export only, same as the + # Subcontractor report, so the formatted sheet gets + # a clean "Sr No" column instead of the raw Id. + export_df = df.drop(columns=["Id"], errors="ignore").copy() + export_df.insert(0, "Sr No", range(1, len(export_df) + 1)) + + export_df.to_excel(writer, sheet_name=sheet_name, index=False, startrow=1) + worksheet = writer.sheets[sheet_name] + format_detail_sheet( + workbook, worksheet, export_df, sheet_name, + contractor_name="", + ra_bill_no=RA_Bill_No, + report_date=report_date + ) output.seek(0) + filename_parts = [f"Client_RA_{re.sub(r'[^A-Za-z0-9_-]+', '_', RA_Bill_No)}"] + + if location: + filename_parts.append(re.sub(r'[^A-Za-z0-9_-]+', '_', location).strip('_')) + + if category and category != "all": + filename_parts.append(category.upper()) + + filename = "_".join(filename_parts) + "_Report.xlsx" + return send_file( output, - download_name=f"Client_RA_{RA_Bill_No}_Report.xlsx", + download_name=filename, as_attachment=True )