from flask import Blueprint, request, send_from_directory, redirect, url_for, current_app, flash from openpyxl import Workbook from openpyxl.styles import Font, Alignment, PatternFill, Border, Side import os import re from datetime import datetime from app.models import Task reports = Blueprint('reports', __name__) def clean_text(text): if not isinstance(text, str): return "" return text.strip().replace(",", "").replace("(", "").replace(")", "") \ .replace(".", "").replace("&", "").replace("\n", "").lower() def safe_float(value): try: return round(float(value), 2) except (ValueError, TypeError): return 0.0 @reports.route('/report_excel', methods=['GET']) def generate_report(): main_task_rexp = r'[\\/*?:"<>|]' block = request.args.get('block', '') main_task = request.args.get('main_task', '') block_clean = clean_text(block) main_task_clean = clean_text(main_task) if not block_clean: return "Please select a Block.", 400 if not main_task_clean: return "Please select a Main Task.", 400 # ---------------- FETCH DATA ---------------- all_tasks = Task.query.filter(Task.block_name == block).all() # MAIN TASK # main_task_record = next( # (task for task in all_tasks # if clean_text(task.task_name) == main_task_clean), # None # ) main_task_records = [ task for task in all_tasks # if clean_text(task.task_name) == main_task_clean if main_task_clean in clean_text(task.task_name) ] # SUBTASKS (ONLY TRUE CHILDREN) subtasks_query = [ task for task in all_tasks if task.parent_task_name and main_task_clean in clean_text(task.parent_task_name) # and clean_text(task.parent_task_name) == main_task_clean ] # ---------------- VALIDATION ---------------- if not subtasks_query and not main_task_records: flash("No Task Data Found", "error") return redirect(url_for('main.generate_report_page')) # ---------------- BUILD REPORT ---------------- report_data = [] # CASE 1: SUBTASKS EXIST if subtasks_query: for task in subtasks_query: boq_amount = safe_float(task.boq_amount) previous_billing_amount = safe_float(task.previous_billing_amount) remaining_amount = boq_amount - previous_billing_amount tender_amount = safe_float(task.qty) * safe_float(task.rate) report_data.append([ task.id, (task.village_name or "").strip(), (task.task_name or "").strip(), (task.unit or "").strip(), safe_float(task.qty), safe_float(task.rate), tender_amount, safe_float(task.previous_billed_qty), previous_billing_amount, remaining_amount ]) # CASE 2: ONLY MAIN TASK EXISTS # elif main_task_record: # task = main_task_record # boq_amount = safe_float(task.boq_amount) # previous_billing_amount = safe_float(task.previous_billing_amount) # remaining_amount = boq_amount - previous_billing_amount # tender_amount = safe_float(task.qty) * safe_float(task.rate) # report_data.append([ # task.id, # (task.village_name or "").strip(), # (task.task_name or "").strip(), # safe_float(task.qty), # safe_float(task.rate), # tender_amount, # safe_float(task.previous_billed_qty), # previous_billing_amount, # remaining_amount # ]) elif main_task_records: for task in main_task_records: boq_amount = safe_float(task.boq_amount) previous_billing_amount = safe_float(task.previous_billing_amount) remaining_amount = boq_amount - previous_billing_amount tender_amount = safe_float(task.qty) * safe_float(task.rate) report_data.append([ task.id, (task.village_name or "").strip(), (task.task_name or "").strip(), (task.unit or "").strip(), safe_float(task.qty), safe_float(task.rate), tender_amount, safe_float(task.previous_billed_qty), previous_billing_amount, remaining_amount ]) # ---------------- FILE NAME ---------------- sanitized_main_task = re.sub(main_task_rexp, "", main_task) if len(sanitized_main_task) > 30: sanitized_main_task = sanitized_main_task[:30] timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_name = f"{sanitized_main_task}_{timestamp}.xlsx" file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], file_name) # ---------------- EXCEL ---------------- wb = Workbook() ws = wb.active ws.title = "Report" thin_border = Border( left=Side(style="thin"), right=Side(style="thin"), top=Side(style="thin"), bottom=Side(style="thin") ) header_fill = PatternFill(start_color="FFC000", end_color="FFC000", fill_type="solid") # --------------------------------------------------- # REPORT TITLE # --------------------------------------------------- ws.merge_cells("A1:J1") title_cell = ws["A1"] title_cell.value = "MAIN TASK REPORT" title_cell.font = Font(bold=True, size=14) title_cell.alignment = Alignment(horizontal="center") title_cell.fill = header_fill title_cell.border = thin_border # --------------------------------------------------- # DETAIL ROWS # --------------------------------------------------- ws["A2"] = "District" ws["B2"] = request.args.get("district", "") ws["A3"] = "Block" ws["B3"] = block ws["A4"] = "Main Task" ws["B4"] = main_task # Style details rows for r in range(2,5): ws[f"A{r}"].font = Font(bold=True) ws[f"A{r}"].fill = header_fill ws[f"A{r}"].border = thin_border ws[f"B{r}"].border = thin_border # --------------------------------------------------- # TABLE HEADER STARTS ROW 6 # --------------------------------------------------- start_row = 6 headers = [ "Task ID", "Village", "Task Name", "Unit", "Tender Qty", "Tender Rate", "Tender Amount", "Previous Bill Qty", "Previous Bill Amount", "Remaining Amount" ] for col_num, value in enumerate(headers,1): cell = ws.cell(row=start_row, column=col_num) cell.value = value cell.font = Font(bold=True) cell.alignment = Alignment(horizontal="center") cell.fill = header_fill cell.border = thin_border # --------------------------------------------------- # DATA ROWS START AFTER HEADER # --------------------------------------------------- data_row = start_row + 1 for row_data in report_data: for col_num, value in enumerate(row_data,1): cell = ws.cell( row=data_row, column=col_num, value=value ) cell.border = thin_border data_row += 1 # Freeze header while scrolling ws.freeze_panes = "A7" # Column Width for i in range(1, len(headers)+1): ws.column_dimensions[ ws.cell(row=start_row,column=i).column_letter ].width = 20 wb.save(file_path) return redirect(url_for('reports.download_report', filename=file_name)) @reports.route('/download/') def download_report(filename): return send_from_directory( current_app.config['UPLOAD_FOLDER'], filename, as_attachment=True )