From 06fe162198cada17f62bb10babd709b88e614509 Mon Sep 17 00:00:00 2001 From: pjpatil12 Date: Fri, 7 Aug 2026 13:51:58 +0530 Subject: [PATCH 1/3] edit .gitignore file --- .gitignore | 5 ++++- logs/app.log | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 2eba46a..f5b87c4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,8 +11,11 @@ app/static/uploads/ # Ignore env files venv -# Ignore Log files ss +# Ignore Log files logs/ +logs/app.log +logs/debug.log +logs/error.log *.log diff --git a/logs/app.log b/logs/app.log index 16c8000..e69de29 100644 --- a/logs/app.log +++ b/logs/app.log @@ -1,3 +0,0 @@ -2026-08-06 12:44:45 | INFO | User=System | IP=- | - | - | ====================================================================== -2026-08-06 12:44:45 | INFO | User=System | IP=- | - | - | Application Started Successfully -2026-08-06 12:44:45 | INFO | User=System | IP=- | - | - | ====================================================================== From 4baf8378c7b03f4a5f604eee5f48ad1e89637535 Mon Sep 17 00:00:00 2001 From: pjpatil12 Date: Sat, 8 Aug 2026 10:34:07 +0530 Subject: [PATCH 2/3] Added on Client rate model --- app/models/client_rate_model.py | 20 + app/models/rate_model.py | 0 app/routes/engineering_master_routes.py | 66 +++- app/services/client_rate_service.py | 97 +++++ app/templates/engineering/client_rate.html | 346 ++++++++++++++++++ .../engineering/contractor_rate.html | 50 +-- app/templates/engineering/index.html | 4 +- 7 files changed, 545 insertions(+), 38 deletions(-) create mode 100644 app/models/client_rate_model.py delete mode 100644 app/models/rate_model.py create mode 100644 app/services/client_rate_service.py diff --git a/app/models/client_rate_model.py b/app/models/client_rate_model.py new file mode 100644 index 0000000..4ce334a --- /dev/null +++ b/app/models/client_rate_model.py @@ -0,0 +1,20 @@ +from app import db +from datetime import datetime + +class ClientRate(db.Model): + __tablename__ = "client_rates" + + id = db.Column(db.Integer, primary_key=True) + category = db.Column(db.String(50), nullable=False) + item_code = db.Column(db.String(50), nullable=False) + item_name = db.Column(db.String(200), nullable=False) + unit = db.Column(db.String(20)) + rate = db.Column(db.Numeric(12,2), nullable=False) + effective_from = db.Column(db.Date, nullable=False) + effective_to = db.Column(db.Date) + status = db.Column(db.String(20), default="Active") + created_at = db.Column(db.DateTime, default=datetime.now) + + def __repr__(self): + return f"< Client Rate {self.item_name}>" + \ No newline at end of file diff --git a/app/models/rate_model.py b/app/models/rate_model.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/routes/engineering_master_routes.py b/app/routes/engineering_master_routes.py index 4a2fea5..ae41d73 100644 --- a/app/routes/engineering_master_routes.py +++ b/app/routes/engineering_master_routes.py @@ -6,10 +6,11 @@ from flask import ( url_for, flash, jsonify ) +from app.constants.messages import SuccessMessage, ErrorMessage from app.models.subcontractor_model import Subcontractor from app.services.subcontractor_rate_service import SubcontractorRateService -from app.constants.messages import SuccessMessage, ErrorMessage +from app.services.client_rate_service import ClientRateService engi_bp = Blueprint( "engineering", @@ -27,17 +28,60 @@ def engineering_master(): title="Engineering Masters" ) -# Client rate model -@engi_bp.route("/client-rate") -def client_rates(): +#---------------------- Client rate model ------------------------------- +@engi_bp.route("/client-rate", methods=["GET", "POST"]) +def client_rate_master(): + + if request.method == "POST": + result = ClientRateService.save_or_update(request.form) + if result["success"]: + flash(result["message"], "success") + return redirect(url_for("engineering.client_rate_master")) + else: + flash(result["message"], "danger") + + rates = ClientRateService.get_all_rates() return render_template( "engineering/client_rate.html", - title="Client Rate Master" + title="Client Rate Master", + rates=rates ) +@engi_bp.route("/client-rate/edit/", methods=["GET", "POST"]) +def client_edit_rate(rate_id): + + if request.method == "POST": + + result = ClientRateService.save_or_update(request.form) + + if result["success"]: + flash(result["message"], "success") + return redirect(url_for("engineering.client-rate")) + + flash(result["message"], "danger") + + rate = ClientRateService.get_rate(rate_id) + + rates = ClientRateService.get_all_rates() + + return render_template( + "engineering/client_rate.html", + rate=rate, + rates=rates + ) + +@engi_bp.route("/client-rate/delete/") +def client_delete_rate(rate_id): + ClientRateService.delete_rate(rate_id) + flash("Rate deleted successfully.", "success") + return redirect(url_for("engineering.client-rate")) + + + +# -------------------- Sub-Contractor ------------------------------------- @engi_bp.route("/subcontractor-rate", methods=["GET", "POST"]) -def add_subcontractor_rates(): +def subcontractor_rate_master(): subcontractors = Subcontractor.query.filter_by(status="Active").all() @@ -45,7 +89,7 @@ def add_subcontractor_rates(): result = SubcontractorRateService.save_or_update(request.form) if result["success"]: flash(result["message"], "success") - return redirect(url_for("engineering.add_subcontractor_rates")) + return redirect(url_for("engineering.subcontractor_rate_master")) else: flash(result["message"], "danger") @@ -59,7 +103,7 @@ def add_subcontractor_rates(): ) @engi_bp.route("/subcontractor-rate/edit/", methods=["GET", "POST"]) -def edit_rate(rate_id): +def subcontractor_edit_rate(rate_id): subcontractors = Subcontractor.query.filter_by(status="Active").all() @@ -69,7 +113,7 @@ def edit_rate(rate_id): if result["success"]: flash(result["message"], "success") - return redirect(url_for("engineering.add_subcontractor_rates")) + return redirect(url_for("engineering.subcontractor_rate_master")) flash(result["message"], "danger") @@ -85,10 +129,10 @@ def edit_rate(rate_id): ) @engi_bp.route("/subcontractor-rate/delete/") -def delete_rate(rate_id): +def subcontractor_delete_rate(rate_id): SubcontractorRateService.delete_rate(rate_id) flash("Rate deleted successfully.", "success") - return redirect(url_for("engineering.add_subcontractor_rates")) + return redirect(url_for("engineering.subcontractor_rate_master")) @engi_bp.route("/check-rate") diff --git a/app/services/client_rate_service.py b/app/services/client_rate_service.py new file mode 100644 index 0000000..f1c386d --- /dev/null +++ b/app/services/client_rate_service.py @@ -0,0 +1,97 @@ +from app.services.db_service import db +from app.models.client_rate_model import ClientRate +from sqlalchemy import func + + +class ClientRateService: + + @staticmethod + def save_or_update(form): + + rate_id = form.get("id") + category = form.get("category") + item_name = form.get("item_name").strip() + + # ----------------------------- + # Duplicate Validation + # ----------------------------- + duplicate = ( + ClientRate.query + .filter( + ClientRate.category == category, + func.lower(ClientRate.item_name) == item_name.lower() + ) + .first() + ) + + # Ignore current record while editing + if duplicate and (not rate_id or duplicate.id != int(rate_id)): + return { + "success": False, + "message": "Category and Rate already exists for this Client." + } + + # ----------------------------- + # Insert / Update + # ----------------------------- + if rate_id: + rate = ClientRate.query.get_or_404(rate_id) + else: + rate = ClientRate() + + rate.category = category + rate.item_code = form.get("item_code") + rate.item_name = item_name + rate.unit = form.get("unit") + rate.rate = form.get("rate") + rate.effective_from = form.get("effective_from") + rate.effective_to = form.get("effective_to") or None + rate.status = form.get("status") + + if not rate_id: + db.session.add(rate) + + db.session.commit() + + return { + "success": True, + "message": "Saved Successfully." + } + + @staticmethod + def get_all_rates(): + + return ( + ClientRate.query + .order_by(ClientRate.created_at.desc()) + .all() + ) + + + @staticmethod + def get_rate(rate_id): + + return ClientRate.query.get_or_404(rate_id) + + def delete_rate(rate_id): + rate = ClientRate.query.get_or_404(rate_id) + db.session.delete(rate) + db.session.commit() + + + @staticmethod + def check_duplicate(subcontractor_id, category, item_name, rate_id=None): + + query = ClientRate.query.filter( + ClientRate.category == category, + func.lower(ClientRate.item_name) == item_name.strip().lower() + ) + + if rate_id: + query = query.filter(ClientRate.id != int(rate_id)) + + return query.first() is not None + + + + \ No newline at end of file diff --git a/app/templates/engineering/client_rate.html b/app/templates/engineering/client_rate.html index a41a2b1..c2d09d9 100644 --- a/app/templates/engineering/client_rate.html +++ b/app/templates/engineering/client_rate.html @@ -14,7 +14,353 @@ +
+ +
+ + + + +
+ + +
+ + +
+ + +
+ + + +
+
+ +
+ + +
+ + +
+ + +
+ + + + +
+ + + +
+ + +
+ + +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ +
+ + Reset + + + +
+
+
+ +
+
+
+ Rate List +
+
+ +
+ + + + + + + + + + + + + + + + {% for row in rates %} + + + + + + + + + + + + {% endfor %} + + +
#CategoryItem CodeItem NameUnitRateStatusAction
{{ loop.index }}{{ row.category }}{{ row.item_code }}{{ row.item_name }}{{ row.unit }}{{ row.rate }} + {% if row.status=="Active" %} + + Active + + {% else %} + + Inactive + + {% endif %} + + + + + + + + +
+
+
+ + + {% endblock %} \ No newline at end of file diff --git a/app/templates/engineering/contractor_rate.html b/app/templates/engineering/contractor_rate.html index 5f892a9..1337c51 100644 --- a/app/templates/engineering/contractor_rate.html +++ b/app/templates/engineering/contractor_rate.html @@ -4,14 +4,12 @@
- +
-

Subcontractor Rate Master

-
@@ -67,23 +65,6 @@
- -
- - - -
@@ -95,7 +76,7 @@
-
+
+
+
+ + +
+ + +
+

- + Reset @@ -222,11 +222,11 @@ - + - diff --git a/app/templates/engineering/index.html b/app/templates/engineering/index.html index 1a4d4f8..23eac31 100644 --- a/app/templates/engineering/index.html +++ b/app/templates/engineering/index.html @@ -32,7 +32,7 @@ Manage subcontractor-wise rates.

- @@ -62,7 +62,7 @@ Manage client standard rates.

-
From 272705e437a9276403ba991f520fda3c529ef9b7 Mon Sep 17 00:00:00 2001 From: Prajaktas8876 Date: Sat, 8 Aug 2026 11:25:22 +0530 Subject: [PATCH 3/3] Changes done in client report page --- app/routes/file_report.py | 213 ++++++++++--- app/services/abstract_service.py | 219 +++++++++++++ app/templates/client_report.html | 401 ++++++++++++++++++++---- app/templates/subcontractor_report.html | 2 +- 4 files changed, 728 insertions(+), 107 deletions(-) diff --git a/app/routes/file_report.py b/app/routes/file_report.py index f533868..b39ed2d 100644 --- a/app/routes/file_report.py +++ b/app/routes/file_report.py @@ -18,7 +18,7 @@ from app.models.tr_ex_client_model import TrenchExcavationClient from app.models.mh_dc_client_model import ManholeDomesticChamberClient from app.models.laying_client_model import LayingClient -from app.services.abstract_service import AbstractReportService +from app.services.abstract_service import AbstractReportService, ClientAbstractReportService # --- BLUEPRINT DEFINITION --- @@ -114,9 +114,6 @@ def add_action_columns(df, model_key): # ---------------- 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( "Select", f' avoids ever handing DataTables - something it can't initialize.""" + something it can't initialize. + + raw_fields is only needed for Subcontractor tables (Bulk Edit); + leave it as None for Client tables and this just skips the + data-field tagging step.""" if df.empty: return '
No records found.
' html = df.to_html(classes=table_class, index=False, escape=False) html = add_select_all_header(html, model_key) - html = add_data_field_attrs(html, raw_fields or []) + if raw_fields: + html = add_data_field_attrs(html, raw_fields) return html - - - # ---------------- FETCH ---------------- class SubcontractorBill: def __init__(self): @@ -302,7 +304,11 @@ def delete_records(): "tr": TrenchExcavation, "mh": ManholeExcavation, "dc": ManholeDomesticChamber, - "laying": Laying + "laying": Laying, + "tr_client": TrenchExcavationClient, + "mh_client": ManholeExcavationClient, + "dc_client": ManholeDomesticChamberClient, + "laying_client": LayingClient } ModelClass = model_map.get(model) @@ -330,7 +336,8 @@ def delete_records(): @file_report_bp.route("/bulk_update", methods=["POST"]) @login_required def bulk_update(): - """Bulk-edit save endpoint. Expects JSON shaped like: + """Bulk-edit save endpoint. Subcontractor tables only - Client + report does not have Bulk Edit. Expects JSON shaped like: { "tr": { "5": {"MH_NO": "12A", "Location": "Pune"}, ... }, "mh": {...}, ... } Field names are validated against each model's real table columns @@ -389,7 +396,11 @@ def edit_record(model, record_id): "tr": TrenchExcavation, "mh": ManholeExcavation, "dc": ManholeDomesticChamber, - "laying": Laying + "laying": Laying, + "tr_client": TrenchExcavationClient, + "mh_client": ManholeExcavationClient, + "dc_client": ManholeDomesticChamberClient, + "laying_client": LayingClient } ModelClass = model_map.get(model) @@ -414,7 +425,10 @@ def edit_record(model, record_id): try: db.session.commit() flash("Record updated successfully.", "success") - # ✅ fixed: correct blueprint name + + if model.endswith("_client"): + return redirect(url_for("file_report.client_report")) + return redirect(url_for("file_report.report_file")) except Exception as e: @@ -628,7 +642,6 @@ def report_file(): } # this are html classes - # table_class = ( "table " "table-bordered" "table-hover " "table-striped " "table-sm " "align-middle " "datatable " "mb-0") table_class = ( "table " "table-bordered " @@ -671,23 +684,66 @@ class ClientBill: self.df_mh = pd.DataFrame() self.df_dc = pd.DataFrame() self.df_laying = pd.DataFrame() - - def Fetch(self, RA_Bill_No): - trench = TrenchExcavationClient.query.filter_by(RA_Bill_No=RA_Bill_No).all() - mh = ManholeExcavationClient.query.filter_by(RA_Bill_No=RA_Bill_No).all() - dc = ManholeDomesticChamberClient.query.filter_by(RA_Bill_No=RA_Bill_No).all() - lay = LayingClient.query.filter_by(RA_Bill_No=RA_Bill_No).all() - + + def Fetch(self, RA_Bill_No=None, location=None, mh_no=None): + filters = {} + if RA_Bill_No: + filters["RA_Bill_No"] = RA_Bill_No + + trench = TrenchExcavationClient.query.filter_by(**filters).all() + mh = ManholeExcavationClient.query.filter_by(**filters).all() + dc = ManholeDomesticChamberClient.query.filter_by(**filters).all() + lay = LayingClient.query.filter_by(**filters).all() + + # LOCATION FILTER + if location: + search = location.strip().lower() + trench = [ + t for t in trench + if search in (t.Location or "").strip().lower() + ] + mh = [ + t for t in mh + if search in (t.Location or "").strip().lower() + ] + dc = [ + t for t in dc + if search in (t.Location or "").strip().lower() + ] + lay = [ + t for t in lay + if search in (t.Location or "").strip().lower() + ] + + # MH NO FILTER + if mh_no: + search_mh = mh_no.strip().lower() + trench = [ + t for t in trench + if search_mh in (t.MH_NO or "").strip().lower() + ] + mh = [ + t for t in mh + if search_mh in (t.MH_NO or "").strip().lower() + ] + dc = [ + t for t in dc + if search_mh in (t.MH_NO or "").strip().lower() + ] + lay = [ + t for t in lay + if search_mh in (t.MH_NO or "").strip().lower() + ] + self.df_tr = pd.DataFrame([c.serialize() for c in trench]) self.df_mh = pd.DataFrame([c.serialize() for c in mh]) self.df_dc = pd.DataFrame([c.serialize() for c in dc]) self.df_laying = pd.DataFrame([c.serialize() for c in lay]) - - drop_cols = ["id", "created_at", "_sa_instance_state"] + drop_cols = ["created_at", "_sa_instance_state"] 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) - + format_column_names(df) # --- CLIENT REPORT (PREVIEW + DOWNLOAD) --- @@ -695,23 +751,41 @@ class ClientBill: @login_required def client_report(): - tables = {"tr": None, "mh": None, "dc": None, "laying": None} + tables = None ra_val = "" + location_val = "" + mh_no_val = "" + category_val = "" + abstract_html = "" + has_data = {"tr": False, "mh": False, "dc": False, "laying": False} if request.method == "POST": - # ⚠ MUST match HTML name - RA_Bill_No = request.form.get("RA_Bill_No") - action = request.form.get("action") + RA_Bill_No = request.form.get("RA_Bill_No", "").strip() + location = request.form.get("location", "").strip() + mh_no = request.form.get("mh_no", "").strip() + category = request.form.get("category", "") + action = request.form.get("action", "preview") + ra_val = RA_Bill_No + location_val = location + mh_no_val = mh_no + category_val = category if not RA_Bill_No: flash("Please enter RA Bill No.", "danger") - return render_template("client_report.html", tables=tables, ra_val=ra_val) + return render_template( + "client_report.html", + tables=tables, ra_val=ra_val, + location_val=location_val, mh_no_val=mh_no_val, + category_val=category_val, + abstract_html=abstract_html, + has_data=has_data + ) # -------- FETCH CLIENT DATA -------- bill_gen = ClientBill() - bill_gen.Fetch(RA_Bill_No) + bill_gen.Fetch(RA_Bill_No, location, mh_no) # If no data if ( @@ -721,7 +795,27 @@ def client_report(): bill_gen.df_laying.empty ): flash(f"No Client records found for RA Bill {RA_Bill_No}", "warning") - return render_template("client_report.html", tables=tables, ra_val=ra_val) + return render_template( + "client_report.html", + tables=tables, ra_val=ra_val, + location_val=location_val, mh_no_val=mh_no_val, + category_val=category_val, + abstract_html=abstract_html, + has_data=has_data + ) + + abstract_service = ClientAbstractReportService(ra_bill_no=RA_Bill_No) + abstract_html = abstract_service.generate_html() + + # ---------------- CATEGORY FILTER ---------------- + if category == "tr": + bill_gen.df_mh = bill_gen.df_dc = bill_gen.df_laying = pd.DataFrame() + elif category == "mh": + bill_gen.df_tr = bill_gen.df_dc = bill_gen.df_laying = pd.DataFrame() + elif category == "dc": + bill_gen.df_tr = bill_gen.df_mh = bill_gen.df_laying = pd.DataFrame() + elif category == "laying": + bill_gen.df_tr = bill_gen.df_mh = bill_gen.df_dc = pd.DataFrame() # -------- DOWNLOAD -------- if action == "download": @@ -729,10 +823,13 @@ def client_report(): output = io.BytesIO() with pd.ExcelWriter(output, engine="xlsxwriter") as writer: - bill_gen.df_tr.to_excel(writer, index=False, sheet_name="Trench") - bill_gen.df_mh.to_excel(writer, index=False, sheet_name="MH") + 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="Laying") + bill_gen.df_laying.to_excel(writer, index=False, sheet_name="Pipe Laying") output.seek(0) @@ -742,15 +839,47 @@ def client_report(): as_attachment=True ) - # -------- PREVIEW -------- - table_class = "table table-bordered table-striped table-hover table-sm" + # =================================================== + # ADD ACTIONS (same helpers as Subcontractor report) + # =================================================== + bill_gen.df_tr = add_action_columns(bill_gen.df_tr, "tr_client") + bill_gen.df_mh = add_action_columns(bill_gen.df_mh, "mh_client") + bill_gen.df_dc = add_action_columns(bill_gen.df_dc, "dc_client") + bill_gen.df_laying = add_action_columns(bill_gen.df_laying, "laying_client") - tables["tr"] = bill_gen.df_tr.to_html(classes=table_class, index=False) - tables["mh"] = bill_gen.df_mh.to_html(classes=table_class, index=False) - tables["dc"] = bill_gen.df_dc.to_html(classes=table_class, index=False) - tables["laying"] = bill_gen.df_laying.to_html(classes=table_class, index=False) + has_data = { + "tr": not bill_gen.df_tr.empty, + "mh": not bill_gen.df_mh.empty, + "dc": not bill_gen.df_dc.empty, + "laying": not bill_gen.df_laying.empty, + } - return render_template("client_report.html", tables=tables, ra_val=ra_val) + table_class = ( + "table " + "table-bordered " + "table-hover " + "table-striped " + "table-sm " + "align-middle " + "datatable " + "text-nowrap " + "mb-0" + ) + tables = { + "tr": render_table_or_empty(bill_gen.df_tr, "tr_client", table_class), + "mh": render_table_or_empty(bill_gen.df_mh, "mh_client", table_class), + "dc": render_table_or_empty(bill_gen.df_dc, "dc_client", table_class), + "laying": render_table_or_empty(bill_gen.df_laying, "laying_client", table_class) + } + + return render_template( + "client_report.html", + tables=tables, ra_val=ra_val, + location_val=location_val, mh_no_val=mh_no_val, + category_val=category_val, + abstract_html=abstract_html, + has_data=has_data + ) def format_column_names(df): diff --git a/app/services/abstract_service.py b/app/services/abstract_service.py index 62e55d8..c861997 100644 --- a/app/services/abstract_service.py +++ b/app/services/abstract_service.py @@ -1,3 +1,4 @@ +import re from sqlalchemy import func from app import db @@ -7,6 +8,13 @@ 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.regex_utils import RegularExpression + class AbstractReportService: @@ -356,4 +364,215 @@ class AbstractReportService:
""" + return html + + +# ================================================================ +# CLIENT ABSTRACT REPORT +def _format_range_text(raw): + """'6_0_to_7_5' -> '6.0-7.5' '0_to_1_5' -> '0-1.5'""" + raw = re.sub(r'(\d+)_(\d+)', lambda m: f"{m.group(1)}.{m.group(2)}", raw) + return raw.replace("_to_", "-") + + +def _label_for_total_column(col_name): + """'Soft_Murum_0_to_1_5_total' -> 'Soft Murum 0-1.5 mm'""" + value = col_name[:-6] if col_name.endswith("_total") else col_name + + m = re.search(r'(\d[\d_]*_to_[\d_]+)$', value) + if m: + prefix = value[:m.start()].rstrip("_").replace("_", " ") + range_text = _format_range_text(m.group(1)) + return f"{prefix} {range_text} mm".strip() + + return value.replace("_", " ").title() + + +def _label_for_d_range_column(col_name): + """'d_6_0_to_6_5' -> '6.0-6.5 mm'""" + raw = col_name[2:] # strip leading "d_" + return f"{_format_range_text(raw)} mm" + + +def _label_for_pipe_column(col_name): + """'pipe_150_mm' -> '150 mm Dia'""" + m = re.match(r"pipe_(\d+)_mm", col_name) + if m: + return f"{m.group(1)} mm Dia" + return col_name.replace("_", " ").title() + + +class ClientAbstractReportService: + + def __init__(self, ra_bill_no=None): + self.ra_bill_no = ra_bill_no + + def filters(self): + f = {} + if self.ra_bill_no: + f["RA_Bill_No"] = self.ra_bill_no + return f + + def _summary(self, model, matcher, uom, label_fn): + f = self.filters() + summary = [] + + for column in model.__table__.columns: + if matcher(column.name): + qty = ( + db.session.query(func.sum(getattr(model, column.name))) + .filter_by(**f) + .scalar() + ) + summary.append({ + "Description": label_fn(column.name), + "UOM": uom, + "Qty": float(qty or 0) + }) + + return summary + + # ------------------------------------------------------------ + def trench_summary(self): + return self._summary( + TrenchExcavationClient, + RegularExpression.STR_TOTAL_PATTERN.match, + "Cum", + _label_for_total_column + ) + + def manhole_summary(self): + return self._summary( + ManholeExcavationClient, + RegularExpression.STR_TOTAL_PATTERN.match, + "Cum", + _label_for_total_column + ) + + def domestic_summary(self): + return self._summary( + ManholeDomesticChamberClient, + RegularExpression.D_RANGE_PATTERN.match, + "Nos", + _label_for_d_range_column + ) + + def laying_summary(self): + return self._summary( + LayingClient, + RegularExpression.PIPE_MM_PATTERN.match, + "RM", + _label_for_pipe_column + ) + + # ------------------------------------------------------------ + # EXCEL SHEET + # ------------------------------------------------------------ + def generate(self, workbook): + + worksheet = workbook.add_worksheet("Abstract") + + title = workbook.add_format({ + "bold": True, "font_size": 16, "align": "center", + "valign": "vcenter", "border": 1 + }) + heading = workbook.add_format({ + "bold": True, "bg_color": "#D9EAD3", "border": 1, "align": "center" + }) + cell = workbook.add_format({"border": 1}) + number = workbook.add_format({"border": 1, "num_format": "#,##0.00"}) + + worksheet.merge_range("A1:D1", "ABSTRACT OF QUANTITY (CLIENT)", title) + + worksheet.write("A3", "RA Bill No", heading) + worksheet.write("B3", self.ra_bill_no or "", cell) + + worksheet.write_row("A5", ["Sr", "Description", "UOM", "Qty"], heading) + + row = 5 + sr = 1 + + sections = [ + ("TRENCH EXCAVATION", self.trench_summary()), + ("MANHOLE EXCAVATION", self.manhole_summary()), + ("DOMESTIC CHAMBER", self.domestic_summary()), + ("PIPE LAYING", self.laying_summary()), + ] + + for title_text, rows in sections: + worksheet.write(row, 1, title_text, heading) + row += 1 + + for item in rows: + worksheet.write(row, 0, sr, cell) + worksheet.write(row, 1, item["Description"], cell) + worksheet.write(row, 2, item["UOM"], cell) + worksheet.write(row, 3, item["Qty"], number) + sr += 1 + row += 1 + + worksheet.set_column("A:A", 8) + worksheet.set_column("B:B", 55) + worksheet.set_column("C:C", 10) + worksheet.set_column("D:D", 18) + + # ------------------------------------------------------------ + # HTML (for web preview) + # ------------------------------------------------------------ + def generate_html(self): + + html = """ +
+ + + + + + + + + + + + + + + + + + """.format(self.ra_bill_no or "") + + sr = 1 + sections = [ + ("TRENCH EXCAVATION", self.trench_summary()), + ("MANHOLE EXCAVATION", self.manhole_summary()), + ("DOMESTIC CHAMBER", self.domestic_summary()), + ("PIPE LAYING", self.laying_summary()), + ] + + for title_text, rows in sections: + html += f""" + + + + """ + for item in rows: + html += f""" + + + + + + + """ + sr += 1 + + html += """ + +
+ ABSTRACT OF QUANTITY +
RA Bill NO{}
SrDescriptionUOMQty
{title_text}
{sr}{item['Description']}{item['UOM']}{item['Qty']:.2f}
+
+ """ + return html \ No newline at end of file diff --git a/app/templates/client_report.html b/app/templates/client_report.html index 03f5bc3..73811c0 100644 --- a/app/templates/client_report.html +++ b/app/templates/client_report.html @@ -1,74 +1,347 @@ {% extends "base.html" %} - {% block content %} -
-

Client RA Bills Reports

-
-
- - +
-
-
- + +
+
+
+
+

+ + Client Report +

+ + View, Filter, Edit and Delete Client Records +
-
- -
-
- -
- - {% if tables.tr or tables.mh or tables.dc or tables.laying %} -
-

Table Preview

- - - -
-
-
- {{ tables.tr|safe }} -
-
-
-
- {{ tables.mh|safe }} -
-
-
-
- {{ tables.dc|safe }} -
-
-
-
- {{ tables.laying|safe }} -
-
- {% endif %} -
+ + +
+
+
+ + Report Filters +
+
+ +
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + + + + + + + + +
+
+ +
+
+ + {% if tables %} + {% set show_all = (not category_val) or category_val == 'all' %} + +
+
+ +
+ +
+
+ +
+ {{ abstract_html|safe }} +
+ + {% if show_all or category_val == 'tr' %} + +
+ {% if has_data.tr %} +
+ +
+ {% endif %} +
+ {{ tables.tr|safe }} +
+
+ {% endif %} + + {% if show_all or category_val == 'mh' %} + +
+ {% if has_data.mh %} +
+ +
+ {% endif %} +
+ {{ tables.mh|safe }} +
+
+ {% endif %} + + {% if show_all or category_val == 'dc' %} + +
+ {% if has_data.dc %} +
+ +
+ {% endif %} +
+ {{ tables.dc|safe }} +
+
+ {% endif %} + + {% if show_all or category_val == 'laying' %} + +
+ {% if has_data.laying %} +
+ +
+ {% endif %} +
+ {{ tables.laying|safe }} +
+
+ {% endif %} +
+
+
+ {% endif %} +
+ + + {% endblock %} \ No newline at end of file diff --git a/app/templates/subcontractor_report.html b/app/templates/subcontractor_report.html index 209c457..ebc4016 100644 --- a/app/templates/subcontractor_report.html +++ b/app/templates/subcontractor_report.html @@ -349,7 +349,7 @@ // Reset document.getElementById("resetBtn").addEventListener("click", function () { sessionStorage.removeItem(TAB_STORAGE_KEY); - location.reload(); + window.location.href = window.location.pathname; }); // LOCATION -> SUBCONTRACTOR CASCADE