diff --git a/app/routes/file_report.py b/app/routes/file_report.py index c5bd57c..c3aebd1 100644 --- a/app/routes/file_report.py +++ b/app/routes/file_report.py @@ -4,6 +4,7 @@ from flask import Blueprint, render_template, request, send_file, flash, jsonify from app.utils.helpers import login_required from app.utils.regex_utils import RegularExpression from app import db +from sqlalchemy import func import re from app.models.subcontractor_model import Subcontractor @@ -22,7 +23,74 @@ from app.services.abstract_service import AbstractReportService # --- BLUEPRINT DEFINITION --- file_report_bp = Blueprint("file_report", __name__, url_prefix="/file") - + + +# ---------------- LOCATION HELPERS ---------------- +WORK_MODELS = [TrenchExcavation, ManholeExcavation, ManholeDomesticChamber, Laying] + + +def get_distinct_locations(): + """Union of distinct, non-empty Location values across all 4 work tables.""" + locations = set() + for Model in WORK_MODELS: + rows = db.session.query(Model.Location).distinct().all() + for (loc,) in rows: + if loc and loc.strip(): + locations.add(loc.strip()) + return sorted(locations) + + +def get_subcontractors_for_location(location): + """Return Subcontractor objects that have at least one work record + (in any of the 4 category tables) at the given location. If no + location is given, returns every subcontractor. Shared by the AJAX + endpoint below and by the server-rendered dropdown so the list is + correct even before/without JS running (e.g. on page reload or a + validation-error re-render). + + Matching is case- and whitespace-insensitive, since Location is a + free-text field and stored values can drift ("Pune" vs "PUNE " etc.) + even though the dropdown options themselves come from a distinct + query and look identical.""" + location = (location or "").strip() + + if not location: + return Subcontractor.query.order_by(Subcontractor.subcontractor_name).all() + + target = location.upper() + sc_ids = set() + for Model in WORK_MODELS: + rows = ( + db.session.query(Model.subcontractor_id) + .filter(func.upper(func.trim(Model.Location)) == target) + .distinct() + .all() + ) + for (sid,) in rows: + if sid: + sc_ids.add(sid) + + if not sc_ids: + return [] + + return ( + Subcontractor.query.filter(Subcontractor.id.in_(sc_ids)) + .order_by(Subcontractor.subcontractor_name) + .all() + ) + + +@file_report_bp.route("/get_subcontractors_by_location") +@login_required +def get_subcontractors_by_location(): + """AJAX endpoint: return subcontractors that have at least one work + record (in any of the 4 category tables) at the given location.""" + location = request.args.get("location", "").strip() + subs = get_subcontractors_for_location(location) + + return jsonify([{"id": s.id, "name": s.subcontractor_name} for s in subs]) + + # ---------------- ACTION COLUMN ---------------- def add_action_columns(df, model_key): @@ -30,7 +98,7 @@ def add_action_columns(df, model_key): return df df.insert(0, "Select", df["Id"].apply( - lambda x: f'' + lambda x: f'' )) df["Update"] = df["Id"].apply( @@ -44,6 +112,76 @@ def add_action_columns(df, model_key): return df +# ---------------- SELECT-ALL HEADER ---------------- +def add_select_all_header(table_html, model_key): + """Swap pandas' plain 'Select' column header for a select-all checkbox + scoped to this table (via data-model), so checking it only toggles + rows in this table - not the other 3 category tables on the page.""" + return table_html.replace( + "
| cells. jQuery DataTables then tries to
+ initialize on a table with no columns and throws, which (since the
+ init code runs as one synchronous block) silently kills every bit of
+ JS registered after it - including the select-all checkbox handler
+ for the OTHER tables on the page. Returning a plain message instead
+ of an empty |
|---|