From 18874cc84e4bb836401f9af2236666396cca5ecf Mon Sep 17 00:00:00 2001 From: pjpatil12 Date: Fri, 7 Aug 2026 10:29:15 +0530 Subject: [PATCH 1/2] changes of rate model of sub-cont and there functions --- app/routes/engineering_master_routes.py | 93 ++++- app/services/subcontractor_rate_service.py | 109 +++-- app/templates/base.html | 28 +- app/templates/engineering/add_rate.html | 236 ----------- app/templates/engineering/client_rate.html | 20 + .../engineering/contractor_rate.html | 384 ++++++++++++++++++ app/templates/engineering/list.html | 0 7 files changed, 563 insertions(+), 307 deletions(-) delete mode 100644 app/templates/engineering/add_rate.html create mode 100644 app/templates/engineering/client_rate.html create mode 100644 app/templates/engineering/contractor_rate.html delete mode 100644 app/templates/engineering/list.html diff --git a/app/routes/engineering_master_routes.py b/app/routes/engineering_master_routes.py index 71cb99f..4a2fea5 100644 --- a/app/routes/engineering_master_routes.py +++ b/app/routes/engineering_master_routes.py @@ -4,7 +4,7 @@ from flask import ( request, redirect, url_for, - flash + flash, jsonify ) from app.models.subcontractor_model import Subcontractor @@ -26,26 +26,8 @@ def engineering_master(): "engineering/index.html", title="Engineering Masters" ) - - -@engi_bp.route("/subcontractor-rate") -def add_subcontractor_rates(): - subcontractors = Subcontractor.query.filter_by(status="Active").all() - - if request.method == "POST": - try: - SubcontractorRateService.save_rate(request.form) - flash(SuccessMessage.SAVE, "success") - return redirect(url_for("engineering.add_subcontractor_rates")) - except Exception as e: - flash(ErrorMessage.INTERNAL_SERVER_ERROR, "danger") - - return render_template( - "engineering/add_rate.html", - title="Subcontractor Rate Master", - subcontractors=subcontractors - ) - + +# Client rate model @engi_bp.route("/client-rate") def client_rates(): @@ -54,3 +36,72 @@ def client_rates(): title="Client Rate Master" ) +@engi_bp.route("/subcontractor-rate", methods=["GET", "POST"]) +def add_subcontractor_rates(): + + subcontractors = Subcontractor.query.filter_by(status="Active").all() + + if request.method == "POST": + result = SubcontractorRateService.save_or_update(request.form) + if result["success"]: + flash(result["message"], "success") + return redirect(url_for("engineering.add_subcontractor_rates")) + else: + flash(result["message"], "danger") + + rates = SubcontractorRateService.get_all_rates() + + return render_template( + "engineering/contractor_rate.html", + title="Subcontractor Rate Master", + subcontractors=subcontractors, + rates=rates + ) + +@engi_bp.route("/subcontractor-rate/edit/", methods=["GET", "POST"]) +def edit_rate(rate_id): + + subcontractors = Subcontractor.query.filter_by(status="Active").all() + + if request.method == "POST": + + result = SubcontractorRateService.save_or_update(request.form) + + if result["success"]: + flash(result["message"], "success") + return redirect(url_for("engineering.add_subcontractor_rates")) + + flash(result["message"], "danger") + + rate = SubcontractorRateService.get_rate(rate_id) + + rates = SubcontractorRateService.get_all_rates() + + return render_template( + "engineering/contractor_rate.html", + subcontractors=subcontractors, + rate=rate, + rates=rates + ) + +@engi_bp.route("/subcontractor-rate/delete/") +def delete_rate(rate_id): + SubcontractorRateService.delete_rate(rate_id) + flash("Rate deleted successfully.", "success") + return redirect(url_for("engineering.add_subcontractor_rates")) + + +@engi_bp.route("/check-rate") +def check_rate(): + + exists = SubcontractorRateService.check_duplicate( + subcontractor_id=request.args.get("subcontractor_id"), + category=request.args.get("category"), + item_name=request.args.get("item_name"), + rate_id=request.args.get("rate_id") + ) + + return jsonify({ + "exists": exists + }) + diff --git a/app/services/subcontractor_rate_service.py b/app/services/subcontractor_rate_service.py index 951c378..a21107c 100644 --- a/app/services/subcontractor_rate_service.py +++ b/app/services/subcontractor_rate_service.py @@ -1,28 +1,66 @@ from app.services.db_service import db from app.models.subcontractor_rate_model import SubcontractorRate +from sqlalchemy import func class SubcontractorRateService: @staticmethod - def save_rate(form): + def save_or_update(form): - rate = SubcontractorRate( - subcontractor_id=form.get("subcontractor_id"), - category=form.get("category"), - item_code=form.get("item_code"), - item_name=form.get("item_name"), - unit=form.get("unit"), - rate=form.get("rate"), - effective_from=form.get("effective_from"), - effective_to=form.get("effective_to") or None, - status=form.get("status") + rate_id = form.get("id") + + subcontractor_id = form.get("subcontractor_id") + category = form.get("category") + item_name = form.get("item_name").strip() + + # ----------------------------- + # Duplicate Validation + # ----------------------------- + duplicate = ( + SubcontractorRate.query + .filter( + SubcontractorRate.subcontractor_id == subcontractor_id, + SubcontractorRate.category == category, + func.lower(SubcontractorRate.item_name) == item_name.lower() + ) + .first() ) - db.session.add(rate) + # 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 Subcontractor." + } + + # ----------------------------- + # Insert / Update + # ----------------------------- + if rate_id: + rate = SubcontractorRate.query.get_or_404(rate_id) + else: + rate = SubcontractorRate() + + rate.subcontractor_id = subcontractor_id + 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 rate - + + return { + "success": True, + "message": "Saved Successfully." + } @staticmethod def get_all_rates(): @@ -32,34 +70,33 @@ class SubcontractorRateService: .order_by(SubcontractorRate.created_at.desc()) .all() ) + + @staticmethod def get_rate(rate_id): return SubcontractorRate.query.get_or_404(rate_id) - @staticmethod - def update_rate(rate_id, form): - - rate = SubcontractorRate.query.get_or_404(rate_id) - - rate.subcontractor_id = form.get("subcontractor_id") - rate.category = form.get("category") - rate.item_code = form.get("item_code") - rate.item_name = form.get("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") - - db.session.commit() - return rate - - @staticmethod def delete_rate(rate_id): - rate = SubcontractorRate.query.get_or_404(rate_id) - db.session.delete(rate) + db.session.commit() + - db.session.commit() \ No newline at end of file + @staticmethod + def check_duplicate(subcontractor_id, category, item_name, rate_id=None): + + query = SubcontractorRate.query.filter( + SubcontractorRate.subcontractor_id == subcontractor_id, + SubcontractorRate.category == category, + func.lower(SubcontractorRate.item_name) == item_name.strip().lower() + ) + + if rate_id: + query = query.filter(SubcontractorRate.id != int(rate_id)) + + return query.first() is not None + + + + \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index cca715e..05c9340 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -128,6 +128,13 @@ + + + - - - - + @@ -185,10 +184,10 @@ - +
  • - - Dashboard + + Masters
  • @@ -199,12 +198,13 @@ - +
  • diff --git a/app/templates/engineering/add_rate.html b/app/templates/engineering/add_rate.html deleted file mode 100644 index 2b8f2a7..0000000 --- a/app/templates/engineering/add_rate.html +++ /dev/null @@ -1,236 +0,0 @@ -{% extends "base.html" %} -{% block content %} -
    - -
    - -
    -

    - - Subcontractor Rate Master -

    -
    - -
    - -
    - -
    - - -
    - - - -
    - - -
    - - - - - -
    - - -
    - - - - - -
    - -
    - -
    - - -
    - - - - - -
    - - -
    - - - - - -
    - - -
    - - - - - -
    - - -
    - - - - - -
    - -
    - -
    - - -
    - - - - - -
    - - -
    - - - - - -
    - -
    - -
    - -
    - - - - - -
    - -
    - -
    - -
    - -
    - - -{% endblock %} \ No newline at end of file diff --git a/app/templates/engineering/client_rate.html b/app/templates/engineering/client_rate.html new file mode 100644 index 0000000..a41a2b1 --- /dev/null +++ b/app/templates/engineering/client_rate.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} +{% block content %} + +
    + +
    + +
    + +

    + + Client Rate Master +

    + +
    + +
    + + +{% endblock %} \ No newline at end of file diff --git a/app/templates/engineering/contractor_rate.html b/app/templates/engineering/contractor_rate.html new file mode 100644 index 0000000..5f892a9 --- /dev/null +++ b/app/templates/engineering/contractor_rate.html @@ -0,0 +1,384 @@ +{% extends "base.html" %} +{% block content %} + +
    + +
    + +
    + +

    + + Subcontractor Rate Master +

    + +
    + +
    + +
    + + + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + + +
    +
    + +
    + + +
    + + +
    + + +
    + + + + +
    + + + +
    + + +
    + + +
    + + +
    +
    + +
    +
    + + +
    + +
    + + +
    +
    + +
    + +
    + + Reset + + + +
    +
    +
    +
    + + +
    +
    +
    + Rate List +
    +
    + +
    + + + + + + + + + + + + + + + + + {% for row in rates %} + + + + + + + + + + + + + {% endfor %} + + +
    #SubcontractorCategoryItem CodeItem NameUnitRateStatusAction
    {{ loop.index }}{{ row.subcontractor.subcontractor_name }}{{ 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/list.html b/app/templates/engineering/list.html deleted file mode 100644 index e69de29..0000000 From 375e899857f8d41e5092d1fd23da4dfa2fb14582 Mon Sep 17 00:00:00 2001 From: pjpatil12 Date: Fri, 7 Aug 2026 12:12:07 +0530 Subject: [PATCH 2/2] log changes --- README.md | 5 +- app/routes/file_report.py | 117 ++++++++++++++++++++--- app/templates/base.html | 90 ++++++++++-------- app/templates/client_report.html | 19 ++++ app/templates/subcontractor_report.html | 87 +++++++++++------ logs/app.log | 121 ++++++++++++++++++++++++ 6 files changed, 355 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 6eb3acc..4c87c6b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ The Comparison Project is designed to: ## Tech Stack -**Backend Framework**: Flask +**Frontend Framework**: HTML, CSS, Js, Bootstrap +**Backend Framework**: Python Flask **Database**: SQL Database (MySQL/PostgreSQL/SQLite configured via environment variables) **ORM**: SQLAlchemy **File Processing**: Pandas, OpenPyXL, XlsxWriter @@ -580,4 +581,4 @@ Open browser: `http://127.0.0.1:5000/` For issues, feature requests, or contributions, please contact the development team. -**Last Updated:** January 2026 \ No newline at end of file +**Last Updated:** April 2026 \ No newline at end of file diff --git a/app/routes/file_report.py b/app/routes/file_report.py index 1e5de6b..e48f87d 100644 --- a/app/routes/file_report.py +++ b/app/routes/file_report.py @@ -29,17 +29,24 @@ def add_action_columns(df, model_key): if df.empty: return df - df.insert(0, "Select", df["Id"].apply( + # Edit + Delete side by side in one "Action" column, both as icon buttons. + df.insert(0, "Action", df["Id"].apply( + lambda x: ( + f'
    ' + f'' + f'' + f'' + f'
    ' + ) + )) + + df.insert(1, "Select", df["Id"].apply( lambda x: f'' )) - - df["Update"] = df["Id"].apply( - lambda x: f' Edit' - ) - - df["Delete"] = df["Id"].apply( - lambda x: f'' - ) + + df["Id"] = range(1, len(df) + 1) + df = df.rename(columns={"Id": "Sr No"}) return df @@ -235,6 +242,30 @@ def report_file(): bill.Fetch(subcontractor_id=subcontractor_id) else: bill.Fetch(ra_bill_no,subcontractor_id,location) + + + # --------------------------------------------------------- + if ( + bill.df_tr.empty and + bill.df_mh.empty and + bill.df_dc.empty and + bill.df_laying.empty + ): + flash( + f"No records found for RA Bill No '{ra_bill_no}'. " + "Please check the RA Bill No and try again." + if ra_bill_no else + "No records found for the selected filters.", + "warning" + ) + return render_template( + "subcontractor_report.html", + subcontractors=subcontractors, + selected_sc_id=selected_sc_id, + selected_ra_bill=ra_bill_no, + selected_location=location, + selected_category=category + ) # ----------------------------------------- # Generate Abstract Report for Web @@ -254,6 +285,32 @@ def report_file(): bill.df_tr = bill.df_mh = bill.df_laying = pd.DataFrame() elif category == "laying": bill.df_tr = bill.df_mh = bill.df_dc = pd.DataFrame() + + + if ( + category in ("tr", "mh", "dc", "laying") + and bill.df_tr.empty and bill.df_mh.empty + and bill.df_dc.empty and bill.df_laying.empty + ): + category_labels = { + "tr": "Trench Excavation", + "mh": "Manhole Excavation", + "dc": "Domestic Chamber", + "laying": "Pipe Laying", + } + flash( + f"No {category_labels[category]} records found for the " + "selected filters.", + "warning" + ) + return render_template( + "subcontractor_report.html", + subcontractors=subcontractors, + selected_sc_id=selected_sc_id, + selected_ra_bill=ra_bill_no, + selected_location=location, + selected_category=category + ) # =================================================== @@ -267,16 +324,46 @@ def report_file(): abstract = AbstractReportService(subcontractor_id=subcontractor_id,ra_bill_no=ra_bill_no) abstract.generate(workbook) - bill.df_tr.to_excel(writer,sheet_name="Tr.Ex",index=False) - bill.df_mh.to_excel(writer,sheet_name="Mh.Ex",index=False) - bill.df_dc.to_excel(writer,sheet_name="MH & DC",index=False) - bill.df_laying.to_excel(writer,sheet_name="Pipe Laying",index=False) + + sheet_map = [ + (bill.df_tr, "Tr.Ex"), + (bill.df_mh, "Mh.Ex"), + (bill.df_dc, "MH & DC"), + (bill.df_laying, "Pipe Laying"), + ] + for df, sheet_name in sheet_map: + if not df.empty: + df.to_excel(writer, sheet_name=sheet_name, index=False) 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('_') + + name_parts = [sc_name] + + if ra_bill_no: + name_parts.append(f"RA{re.sub(r'[^A-Za-z0-9_-]+', '_', ra_bill_no)}") + + if location: + name_parts.append(re.sub(r'[^A-Za-z0-9_-]+', '_', location).strip('_')) + + if category and category != "all": + name_parts.append(category.upper()) + + if action == "excel_all": + name_parts.append("All") + + filename = "_".join(name_parts) + "_Report.xlsx" + return send_file( output, - download_name= "subcontractor_Report.xlsx", + download_name=filename, as_attachment=True ) diff --git a/app/templates/base.html b/app/templates/base.html index 05c9340..20b1fcc 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -124,7 +124,11 @@ Client vs Subcontractor - + @@ -151,82 +155,75 @@ + + + + + - -
    + Logged in user - +
  • - - Masters + + Dashboard
  • - -
  • Activity Log
  • -
  • Manage Account
  • - -->
  • - - - Logout + + Logout
  • - -
  • - - - Secured by LCEPL - -
  • - - + {% endif %} @@ -379,6 +376,7 @@ showLoader(); const btn = this.querySelector("button[type='submit']"); + const originalBtnHtml = btn ? btn.innerHTML : null; if(btn){ btn.disabled = true; @@ -388,10 +386,24 @@ `; } + + setTimeout(function () { + hideLoader(); + if (btn) { + btn.disabled = false; + btn.innerHTML = originalBtnHtml; + } + }, 3000); + }); }); }); + + window.addEventListener("pageshow", function () { + hideLoader(); + }); + diff --git a/app/templates/client_report.html b/app/templates/client_report.html index c2da3b3..dfa2696 100644 --- a/app/templates/client_report.html +++ b/app/templates/client_report.html @@ -31,6 +31,24 @@ type="button">Tr.Ex + + @@ -41,6 +59,7 @@