diff --git a/app/routes/dashboard.py b/app/routes/dashboard.py index 20953f1..32ea9c6 100644 --- a/app/routes/dashboard.py +++ b/app/routes/dashboard.py @@ -19,12 +19,19 @@ from app.models.trench_excavation_model import TrenchExcavation 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.subcontractor_rate_model import SubcontractorRate # client models import 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.models.client_rate_model import ClientRate + +# Services +from app.services.subcontractor_rate_service import SubcontractorRateService +from app.services.client_rate_service import ClientRateService + dashboard_bp = Blueprint("dashboard", __name__, url_prefix="/dashboard") @@ -140,309 +147,1670 @@ def total(records, field): return float(sum(getattr(r, field) or 0 for r in records)) # category= trench_excavation +# @dashboard_bp.route("/api/tr-analysis") +# def trench_analysis(): + +# subcontractor_id = request.args.get("subcontractor", "").strip() +# ra_bill = request.args.get("ra_bill", "").strip() + +# # Convert "1,2,3" -> ["1", "2", "3"] +# ra_bill_list = [] +# if ra_bill: +# ra_bill_list = [x.strip() for x in ra_bill.split(",") if x.strip()] + +# # Subcontractor Query +# sub_query = TrenchExcavation.query +# if subcontractor_id: +# sub_query = sub_query.filter( +# TrenchExcavation.subcontractor_id == int(subcontractor_id) +# ) + +# if ra_bill_list: +# sub_query = sub_query.filter( +# TrenchExcavation.RA_Bill_No.in_(ra_bill_list) +# ) + +# sub_records = sub_query.all() + +# sub_keys = [ +# ( +# (r.MH_NO or "").strip().upper(), +# (r.Location or "").strip().upper() +# ) +# for r in sub_records +# ] + +# client_query = TrenchExcavationClient.query + +# if sub_keys: + +# client_query = client_query.filter( +# tuple_( +# func.upper(func.trim(TrenchExcavationClient.MH_NO)), +# func.upper(func.trim(TrenchExcavationClient.Location)) +# ).in_(sub_keys) + +# ) + +# client_records = client_query.all() + +# chart_data = [ +# { +# "label": "Marshi 0 to 1.5", +# "client": total(client_records, "Marshi_Muddy_Slushy_0_to_1_5_total"), +# "sub": 0 +# }, +# { +# "label": "Marshi 1.5 to 3.0", +# "client": total(client_records, "Marshi_Muddy_Slushy_1_5_to_3_0_total"), +# "sub": 0 +# }, +# { +# "label": "Marshi 3.0 to 4.5", +# "client": total(client_records, "Marshi_Muddy_Slushy_3_0_to_4_5_total"), +# "sub": 0 +# }, + +# { +# "label": "Soft Murum 0 to 1.5", +# "client": total(client_records, "Soft_Murum_0_to_1_5_total"), +# "sub": total(sub_records, "Soft_Murum_0_to_1_5_total") +# }, +# { +# "label": "Soft Murum 1.5 to 3.0", +# "client": total(client_records, "Soft_Murum_1_5_to_3_0_total"), +# "sub": total(sub_records, "Soft_Murum_1_5_to_3_0_total") +# }, +# { +# "label": "Soft Murum 3.0 to 4.5", +# "client": total(client_records, "Soft_Murum_3_0_to_4_5_total"), +# "sub": total(sub_records, "Soft_Murum_3_0_to_4_5_total") +# }, + +# { +# "label": "Hard Murum 0 to 1.5", +# "client": total(client_records, "Hard_Murum_0_to_1_5_total"), +# "sub": total(sub_records, "Hard_Murum_0_to_1_5_total") +# }, +# { +# "label": "Hard Murum 1.5 to 3.0", +# "client": total(client_records, "Hard_Murum_1_5_to_3_0_total"), +# "sub": total(sub_records, "Hard_Murum_1_5_and_above_total") +# }, + +# { +# "label": "Soft Rock 0 to 1.5", +# "client": total(client_records, "Soft_Rock_0_to_1_5_total"), +# "sub": total(sub_records, "Soft_Rock_0_to_1_5_total") +# }, +# { +# "label": "Soft Rock 1.5 to 3.0", +# "client": total(client_records, "Soft_Rock_1_5_to_3_0_total"), +# "sub": total(sub_records, "Soft_Rock_1_5_and_above_total") +# }, + +# { +# "label": "Hard Rock 0 to 1.5", +# "client": total(client_records, "Hard_Rock_0_to_1_5_total"), +# "sub": total(sub_records, "Hard_Rock_0_to_1_5_total") +# }, +# { +# "label": "Hard Rock 1.5 to 3.0", +# "client": total(client_records, "Hard_Rock_1_5_to_3_0_total"), +# "sub": total(sub_records, "Hard_Rock_1_5_to_3_0_total") +# }, +# { +# "label": "Hard Rock 3.0 to 4.5", +# "client": total(client_records, "Hard_Rock_3_0_to_4_5_total"), +# "sub": total(sub_records, "Hard_Rock_3_0_to_4_5_total") +# }, +# { +# "label": "Hard Rock 4.5 to 6.0", +# "client": total(client_records, "Hard_Rock_4_5_to_6_0_total"), +# "sub": total(sub_records, "Hard_Rock_4_5_to_6_0_total") +# }, +# { +# "label": "Hard Rock 6.0 to 7.5", +# "client": total(client_records, "Hard_Rock_6_0_to_7_5_total"), +# "sub": total(sub_records, "Hard_Rock_6_0_to_7_5_total") +# } +# ] + +# return jsonify({ +# "title": "Trench Excavation Comparison", +# "y_title": "Excavation Qty (Cum)", +# "labels": [x["label"] for x in chart_data], +# "client_qty": [x["client"] for x in chart_data], +# "sub_qty": [x["sub"] for x in chart_data] +# }) + +# # category = manhole_excavation +# @dashboard_bp.route("/api/mh-analysis") +# def manhole_analysis(): + +# subcontractor_id = request.args.get("subcontractor", "").strip() +# ra_bill = request.args.get("ra_bill", "").strip() + +# # Convert "1,2,3" -> ["1", "2", "3"] +# ra_bill_list = [] +# if ra_bill: +# ra_bill_list = [x.strip() for x in ra_bill.split(",") if x.strip()] + +# # Subcontractor Query +# sub_query = ManholeExcavation.query +# if subcontractor_id: +# sub_query = sub_query.filter( +# ManholeExcavation.subcontractor_id == int(subcontractor_id) +# ) + +# if ra_bill_list: +# sub_query = sub_query.filter( +# ManholeExcavation.RA_Bill_No.in_(ra_bill_list) +# ) + +# sub_records = sub_query.all() + +# sub_keys = [ +# ( +# (r.MH_NO or "").strip().upper(), +# (r.Location or "").strip().upper() +# ) +# for r in sub_records +# ] + +# client_query = ManholeExcavationClient.query + +# if sub_keys: + +# client_query = client_query.filter( +# tuple_( +# func.upper(func.trim(ManholeExcavationClient.MH_NO)), +# func.upper(func.trim(ManholeExcavationClient.Location)) +# ).in_(sub_keys) + +# ) + +# client_records = client_query.all() + +# chart_data = [ +# { +# "label": "Marshi 0 to 1.5", +# "client": total(client_records, "Marshi_Muddy_Slushy_0_to_1_5_total"), +# "sub": 0 +# }, +# { +# "label": "Marshi 1.5 to 3.0", +# "client": total(client_records, "Marshi_Muddy_Slushy_1_5_to_3_0_total"), +# "sub": 0 +# }, +# { +# "label": "Marshi 3.0 to 4.5", +# "client": total(client_records, "Marshi_Muddy_Slushy_3_0_to_4_5_total"), +# "sub": 0 +# }, + +# { +# "label": "Soft Murum 0 to 1.5", +# "client": total(client_records, "Soft_Murum_0_to_1_5_total"), +# "sub": total(sub_records, "Soft_Murum_0_to_1_5_total") +# }, +# { +# "label": "Soft Murum 1.5 to 3.0", +# "client": total(client_records, "Soft_Murum_1_5_to_3_0_total"), +# "sub": total(sub_records, "Soft_Murum_1_5_to_3_0_total") +# }, +# { +# "label": "Soft Murum 3.0 to 4.5", +# "client": total(client_records, "Soft_Murum_3_0_to_4_5_total"), +# "sub": total(sub_records, "Soft_Murum_3_0_to_4_5_total") +# }, + +# { +# "label": "Hard Murum 0 to 1.5", +# "client": total(client_records, "Hard_Murum_0_to_1_5_total"), +# "sub": total(sub_records, "Hard_Murum_0_to_1_5_total") +# }, +# { +# "label": "Hard Murum 1.5 to 3.0", +# "client": total(client_records, "Hard_Murum_1_5_to_3_0_total"), +# "sub": total(sub_records, "Hard_Murum_1_5_and_above_total") +# }, + +# { +# "label": "Soft Rock 0 to 1.5", +# "client": total(client_records, "Soft_Rock_0_to_1_5_total"), +# "sub": total(sub_records, "Soft_Rock_0_to_1_5_total") +# }, +# { +# "label": "Soft Rock 1.5 to 3.0", +# "client": total(client_records, "Soft_Rock_1_5_to_3_0_total"), +# "sub": total(sub_records, "Soft_Rock_1_5_and_above_total") +# }, + +# { +# "label": "Hard Rock 0 to 1.5", +# "client": total(client_records, "Hard_Rock_0_to_1_5_total"), +# "sub": total(sub_records, "Hard_Rock_0_to_1_5_total") +# }, +# { +# "label": "Hard Rock 1.5 to 3.0", +# "client": total(client_records, "Hard_Rock_1_5_to_3_0_total"), +# "sub": total(sub_records, "Hard_Rock_1_5_to_3_0_total") +# }, +# { +# "label": "Hard Rock 3.0 to 4.5", +# "client": total(client_records, "Hard_Rock_3_0_to_4_5_total"), +# "sub": total(sub_records, "Hard_Rock_3_0_to_4_5_total") +# }, +# { +# "label": "Hard Rock 4.5 to 6.0", +# "client": total(client_records, "Hard_Rock_4_5_to_6_0_total"), +# "sub": total(sub_records, "Hard_Rock_4_5_to_6_0_total") +# }, +# { +# "label": "Hard Rock 6.0 to 7.5", +# "client": total(client_records, "Hard_Rock_6_0_to_7_5_total"), +# "sub": total(sub_records, "Hard_Rock_6_0_to_7_5_total") +# } +# ] + +# return jsonify({ +# "title": "Manhole Excavation Comparison", +# "y_title": "Manhole Qty (Nos)", +# "labels": [x["label"] for x in chart_data], +# "client_qty": [x["client"] for x in chart_data], +# "sub_qty": [x["sub"] for x in chart_data] +# }) + + + +# category = Manhole_Domestic_Chamber +# @dashboard_bp.route("/api/mdc-analysis") +# def Manhole_Domestic_Chamber_analysis(): + +# subcontractor_id = request.args.get("subcontractor", "").strip() +# ra_bill = request.args.get("ra_bill", "").strip() + +# # Convert "1,2,3" -> ["1", "2", "3"] +# ra_bill_list = [] +# if ra_bill: +# ra_bill_list = [x.strip() for x in ra_bill.split(",") if x.strip()] + +# # Subcontractor Query +# sub_query = ManholeDomesticChamber.query +# if subcontractor_id: +# sub_query = sub_query.filter( +# ManholeDomesticChamber.subcontractor_id == int(subcontractor_id) +# ) + +# if ra_bill_list: +# sub_query = sub_query.filter( +# ManholeDomesticChamber.RA_Bill_No.in_(ra_bill_list) +# ) + +# sub_records = sub_query.all() + +# sub_keys = [ +# ( +# (r.MH_NO or "").strip().upper(), +# (r.Location or "").strip().upper() +# ) +# for r in sub_records +# ] + +# client_query = ManholeDomesticChamberClient.query + +# if sub_keys: + +# client_query = client_query.filter( +# tuple_( +# func.upper(func.trim(ManholeDomesticChamberClient.MH_NO)), +# func.upper(func.trim(ManholeDomesticChamberClient.Location)) +# ).in_(sub_keys) + +# ) + +# client_records = client_query.all() + +# chart_data = [ +# { +# "label": "Depth of MH", +# "client": total(client_records, "Depth_of_MH"), +# "sub": total(sub_records, "Depth_of_MH") +# }, +# { +# "label": "Domestic_Chambers Total", +# "client": total(client_records, "Total"), +# "sub": total(sub_records, "Total") +# } +# ] + +# return jsonify({ +# "title": "Domestic Chamber Comparison", +# "y_title": "Quantity (Nos)", +# "labels": [x["label"] for x in chart_data], +# "client_qty": [x["client"] for x in chart_data], +# "sub_qty": [x["sub"] for x in chart_data] +# }) + + + +# # category = Laying +# @dashboard_bp.route("/api/laying-analysis") +# def laying_analysis(): + +# subcontractor_id = request.args.get("subcontractor", "").strip() +# ra_bill = request.args.get("ra_bill", "").strip() + +# # Convert "1,2,3" -> ["1", "2", "3"] +# ra_bill_list = [] +# if ra_bill: +# ra_bill_list = [x.strip() for x in ra_bill.split(",") if x.strip()] + +# # Subcontractor Query +# sub_query = Laying.query +# if subcontractor_id: +# sub_query = sub_query.filter( +# Laying.subcontractor_id == int(subcontractor_id) +# ) + +# if ra_bill_list: +# sub_query = sub_query.filter( +# Laying.RA_Bill_No.in_(ra_bill_list) +# ) + +# sub_records = sub_query.all() + +# sub_keys = [ +# ( +# (r.MH_NO or "").strip().upper(), +# (r.Location or "").strip().upper() +# ) +# for r in sub_records +# ] + +# client_query = LayingClient.query + +# if sub_keys: + +# client_query = client_query.filter( +# tuple_( +# func.upper(func.trim(LayingClient.MH_NO)), +# func.upper(func.trim(LayingClient.Location)) +# ).in_(sub_keys) + +# ) + +# client_records = client_query.all() + +# chart_data = [ +# { +# "label": "150 mm", +# "client": total(client_records, "pipe_150_mm"), +# "sub": total(sub_records, "pipe_150_mm") +# }, +# { +# "label": "200 mm", +# "client": total(client_records, "pipe_200_mm"), +# "sub": total(sub_records, "pipe_200_mm") +# }, +# { +# "label": "250 mm", +# "client": total(client_records, "pipe_250_mm"), +# "sub": total(sub_records, "pipe_250_mm") +# }, + +# { +# "label": "300 mm", +# "client": total(client_records, "pipe_300_mm"), +# "sub": total(sub_records, "pipe_300_mm") +# }, +# { +# "label": "350 mm", +# "client": total(client_records, "pipe_350_mm"), +# "sub": total(sub_records, "pipe_350_mm") +# }, + +# { +# "label": "400 mm", +# "client": total(client_records, "pipe_400_mm"), +# "sub": total(sub_records, "pipe_400_mm") +# }, +# { +# "label": "450 mm", +# "client": total(client_records, "pipe_450_mm"), +# "sub": total(sub_records, "pipe_450_mm") +# }, + +# { +# "label": "500 mm", +# "client": total(client_records, "pipe_500_mm"), +# "sub": total(sub_records, "pipe_500_mm") +# }, +# { +# "label": "600 mm", +# "client": total(client_records, "pipe_600_mm"), +# "sub": total(sub_records, "pipe_600_mm") +# }, +# { +# "label": "700 mm", +# "client": total(client_records, "pipe_700_mm"), +# "sub": total(sub_records, "pipe_700_mm") +# }, +# { +# "label": "900 mm", +# "client": total(client_records, "pipe_900_mm"), +# "sub": total(sub_records, "pipe_900_mm") +# }, +# { +# "label": "1200 mm", +# "client": total(client_records, "pipe_1200_mm"), +# "sub": total(sub_records, "pipe_1200_mm") +# } +# ] + +# return jsonify({ +# "title": "Pipe Laying Comparison", +# "y_title": "Pipe Length (Mtr)", +# "labels": [x["label"] for x in chart_data], +# "client_qty": [x["client"] for x in chart_data], +# "sub_qty": [x["sub"] for x in chart_data] +# }) + + + @dashboard_bp.route("/api/tr-analysis") def trench_analysis(): - subcontractor_id = request.args.get("subcontractor", "").strip() - ra_bill = request.args.get("ra_bill", "").strip() + # ========================================================= + # CONSTANT + # ========================================================= + CATEGORY = "trench_excavation" - # Convert "1,2,3" -> ["1", "2", "3"] - ra_bill_list = [] - if ra_bill: - ra_bill_list = [x.strip() for x in ra_bill.split(",") if x.strip()] + # ========================================================= + # REQUEST PARAMETERS + # ========================================================= + subcontractor_id = request.args.get( + "subcontractor", + "" + ).strip() + + ra_bill = request.args.get( + "ra_bill", + "" + ).strip() + + # ========================================================= + # VALIDATE SUBCONTRACTOR + # ========================================================= + subcontractor_id_int = None - # Subcontractor Query - sub_query = TrenchExcavation.query if subcontractor_id: + + try: + subcontractor_id_int = int(subcontractor_id) + + except ValueError: + return jsonify({ + "success": False, + "message": "Invalid subcontractor." + }), 400 + + # ========================================================= + # RA BILL LIST + # ========================================================= + ra_bill_list = [] + + if ra_bill: + ra_bill_list = [ + bill.strip() + for bill in ra_bill.split(",") + if bill.strip() + ] + + # ========================================================= + # SUBCONTRACTOR QUERY + # ========================================================= + sub_query = TrenchExcavation.query + + if subcontractor_id_int: + sub_query = sub_query.filter( - TrenchExcavation.subcontractor_id == int(subcontractor_id) + TrenchExcavation.subcontractor_id + == subcontractor_id_int ) if ra_bill_list: + sub_query = sub_query.filter( TrenchExcavation.RA_Bill_No.in_(ra_bill_list) ) sub_records = sub_query.all() + # ========================================================= + # SUBCONTRACTOR KEYS + # ========================================================= sub_keys = [ ( - (r.MH_NO or "").strip().upper(), - (r.Location or "").strip().upper() + (record.MH_NO or "").strip().upper(), + (record.Location or "").strip().upper() ) - for r in sub_records + for record in sub_records ] + # ========================================================= + # CLIENT QUERY + # ========================================================= client_query = TrenchExcavationClient.query if sub_keys: client_query = client_query.filter( tuple_( - func.upper(func.trim(TrenchExcavationClient.MH_NO)), - func.upper(func.trim(TrenchExcavationClient.Location)) + func.upper( + func.trim( + TrenchExcavationClient.MH_NO + ) + ), + func.upper( + func.trim( + TrenchExcavationClient.Location + ) + ) ).in_(sub_keys) - ) client_records = client_query.all() - chart_data = [ + # ========================================================= + # GET CLIENT RATES + # + # Matching: + # category = trench_excavation + # item_name = actual model field name + # ========================================================= + client_rates = ( + ClientRate.query + .filter( + func.lower( + func.trim(ClientRate.category) + ) == CATEGORY.lower(), + + func.lower( + func.trim(ClientRate.status) + ) == "active" + ) + .all() + ) + + # ========================================================= + # GET SUBCONTRACTOR RATES + # + # Matching: + # subcontractor_id + # category = trench_excavation + # item_name = actual model field name + # ========================================================= + sub_rates = [] + + if subcontractor_id_int: + + sub_rates = ( + SubcontractorRate.query + .filter( + SubcontractorRate.subcontractor_id + == subcontractor_id_int, + + func.lower( + func.trim( + SubcontractorRate.category + ) + ) == CATEGORY.lower(), + + func.lower( + func.trim( + SubcontractorRate.status + ) + ) == "active" + ) + .all() + ) + + # ========================================================= + # RATE MAP + # + # Example: + # + # Hard_Rock_6_0_to_7_5_total : 600 + # Hard_Rock_4_5_to_6_0_total : 600 + # ========================================================= + client_rate_map = { + (rate.item_name or "").strip().lower(): + float(rate.rate or 0) + for rate in client_rates + } + + sub_rate_map = { + (rate.item_name or "").strip().lower(): + float(rate.rate or 0) + for rate in sub_rates + } + + # ========================================================= + # TOTAL HELPER + # ========================================================= + def total(records, field): + + value = 0 + + for record in records: + + field_value = getattr( + record, + field, + 0 + ) + + if field_value is not None: + value += float(field_value) + + return round(value, 2) + + # ========================================================= + # TRECH MAPPING + # + # label = Dashboard display name + # field = Client/Subcontractor model field + # item_name = Rate Master item_name + # + # item_name intentionally uses the actual model field name. + # ========================================================= + TRENCH_MAPPING = [ + { "label": "Marshi 0 to 1.5", - "client": total(client_records, "Marshi_Muddy_Slushy_0_to_1_5_total"), - "sub": 0 + "field": "Marshi_Muddy_Slushy_0_to_1_5_total", + "item_name": "Marshi_Muddy_Slushy_0_to_1_5_total" }, + { "label": "Marshi 1.5 to 3.0", - "client": total(client_records, "Marshi_Muddy_Slushy_1_5_to_3_0_total"), - "sub": 0 + "field": "Marshi_Muddy_Slushy_1_5_to_3_0_total", + "item_name": "Marshi_Muddy_Slushy_1_5_to_3_0_total" }, + { "label": "Marshi 3.0 to 4.5", - "client": total(client_records, "Marshi_Muddy_Slushy_3_0_to_4_5_total"), - "sub": 0 + "field": "Marshi_Muddy_Slushy_3_0_to_4_5_total", + "item_name": "Marshi_Muddy_Slushy_3_0_to_4_5_total" }, { "label": "Soft Murum 0 to 1.5", - "client": total(client_records, "Soft_Murum_0_to_1_5_total"), - "sub": total(sub_records, "Soft_Murum_0_to_1_5_total") + "field": "Soft_Murum_0_to_1_5_total", + "item_name": "Soft_Murum_0_to_1_5_total" }, + { "label": "Soft Murum 1.5 to 3.0", - "client": total(client_records, "Soft_Murum_1_5_to_3_0_total"), - "sub": total(sub_records, "Soft_Murum_1_5_to_3_0_total") + "field": "Soft_Murum_1_5_to_3_0_total", + "item_name": "Soft_Murum_1_5_to_3_0_total" }, + { "label": "Soft Murum 3.0 to 4.5", - "client": total(client_records, "Soft_Murum_3_0_to_4_5_total"), - "sub": total(sub_records, "Soft_Murum_3_0_to_4_5_total") + "field": "Soft_Murum_3_0_to_4_5_total", + "item_name": "Soft_Murum_3_0_to_4_5_total" }, { "label": "Hard Murum 0 to 1.5", - "client": total(client_records, "Hard_Murum_0_to_1_5_total"), - "sub": total(sub_records, "Hard_Murum_0_to_1_5_total") + "field": "Hard_Murum_0_to_1_5_total", + "item_name": "Hard_Murum_0_to_1_5_total" }, + { "label": "Hard Murum 1.5 to 3.0", - "client": total(client_records, "Hard_Murum_1_5_to_3_0_total"), - "sub": total(sub_records, "Hard_Murum_1_5_and_above_total") + "field": "Hard_Murum_1_5_to_3_0_total", + "item_name": "Hard_Murum_1_5_to_3_0_total" }, { "label": "Soft Rock 0 to 1.5", - "client": total(client_records, "Soft_Rock_0_to_1_5_total"), - "sub": total(sub_records, "Soft_Rock_0_to_1_5_total") + "field": "Soft_Rock_0_to_1_5_total", + "item_name": "Soft_Rock_0_to_1_5_total" }, + { "label": "Soft Rock 1.5 to 3.0", - "client": total(client_records, "Soft_Rock_1_5_to_3_0_total"), - "sub": total(sub_records, "Soft_Rock_1_5_and_above_total") + "field": "Soft_Rock_1_5_to_3_0_total", + "item_name": "Soft_Rock_1_5_to_3_0_total" }, { "label": "Hard Rock 0 to 1.5", - "client": total(client_records, "Hard_Rock_0_to_1_5_total"), - "sub": total(sub_records, "Hard_Rock_0_to_1_5_total") + "field": "Hard_Rock_0_to_1_5_total", + "item_name": "Hard_Rock_0_to_1_5_total" }, + { "label": "Hard Rock 1.5 to 3.0", - "client": total(client_records, "Hard_Rock_1_5_to_3_0_total"), - "sub": total(sub_records, "Hard_Rock_1_5_to_3_0_total") + "field": "Hard_Rock_1_5_to_3_0_total", + "item_name": "Hard_Rock_1_5_to_3_0_total" }, + { "label": "Hard Rock 3.0 to 4.5", - "client": total(client_records, "Hard_Rock_3_0_to_4_5_total"), - "sub": total(sub_records, "Hard_Rock_3_0_to_4_5_total") + "field": "Hard_Rock_3_0_to_4_5_total", + "item_name": "Hard_Rock_3_0_to_4_5_total" }, + { "label": "Hard Rock 4.5 to 6.0", - "client": total(client_records, "Hard_Rock_4_5_to_6_0_total"), - "sub": total(sub_records, "Hard_Rock_4_5_to_6_0_total") + "field": "Hard_Rock_4_5_to_6_0_total", + "item_name": "Hard_Rock_4_5_to_6_0_total" }, + { "label": "Hard Rock 6.0 to 7.5", - "client": total(client_records, "Hard_Rock_6_0_to_7_5_total"), - "sub": total(sub_records, "Hard_Rock_6_0_to_7_5_total") + "field": "Hard_Rock_6_0_to_7_5_total", + "item_name": "Hard_Rock_6_0_to_7_5_total" } ] - + + # ========================================================= + # BUILD CHART DATA + # ========================================================= + chart_data = [] + + for mapping in TRENCH_MAPPING: + + field = mapping["field"] + + client_qty = total( + client_records, + field + ) + + # ----------------------------------------------------- + # SUBCONTRACTOR QUANTITY + # ----------------------------------------------------- + sub_qty = 0 + + if sub_records: + + sub_qty = total( + sub_records, + field + ) + + # ----------------------------------------------------- + # RATE ITEM NAME + # ----------------------------------------------------- + item_name = mapping["item_name"] + + rate_key = item_name.strip().lower() + + # ----------------------------------------------------- + # CLIENT RATE + # ----------------------------------------------------- + client_rate = client_rate_map.get( + rate_key, + 0 + ) + + # ----------------------------------------------------- + # SUBCONTRACTOR RATE + # ----------------------------------------------------- + sub_rate = sub_rate_map.get( + rate_key, + 0 + ) + + # ----------------------------------------------------- + # AMOUNT + # ----------------------------------------------------- + client_amount = ( + client_qty * client_rate + ) + + sub_amount = ( + sub_qty * sub_rate + ) + + # ----------------------------------------------------- + # DIFFERENCES + # ----------------------------------------------------- + qty_difference = ( + client_qty - sub_qty + ) + + rate_difference = ( + client_rate - sub_rate + ) + + amount_difference = ( + client_amount - sub_amount + ) + + # ----------------------------------------------------- + # FINAL ROW + # ----------------------------------------------------- + chart_data.append({ + + "label": mapping["label"], + + # Actual model field + "field": field, + + # Rate Master item_name + "item_name": item_name, + + # Quantity + "client": round( + client_qty, + 2 + ), + + "sub": round( + sub_qty, + 2 + ), + + # Rate + "client_rate": round( + client_rate, + 2 + ), + + "sub_rate": round( + sub_rate, + 2 + ), + + # Amount + "client_amount": round( + client_amount, + 2 + ), + + "sub_amount": round( + sub_amount, + 2 + ), + + # Difference + "qty_difference": round( + qty_difference, + 2 + ), + + "rate_difference": round( + rate_difference, + 2 + ), + + "amount_difference": round( + amount_difference, + 2 + ) + }) + + # ========================================================= + # RESPONSE + # ========================================================= return jsonify({ + + "success": True, + "title": "Trench Excavation Comparison", + "y_title": "Excavation Qty (Cum)", - "labels": [x["label"] for x in chart_data], - "client_qty": [x["client"] for x in chart_data], - "sub_qty": [x["sub"] for x in chart_data] + + # ----------------------------------------------------- + # CHART + # ----------------------------------------------------- + "labels": [ + item["label"] + for item in chart_data + ], + + "client_qty": [ + item["client"] + for item in chart_data + ], + + "sub_qty": [ + item["sub"] + for item in chart_data + ], + + # ----------------------------------------------------- + # RATE + # ----------------------------------------------------- + "client_rate": [ + item["client_rate"] + for item in chart_data + ], + + "sub_rate": [ + item["sub_rate"] + for item in chart_data + ], + + # ----------------------------------------------------- + # AMOUNT + # ----------------------------------------------------- + "client_amount": [ + item["client_amount"] + for item in chart_data + ], + + "sub_amount": [ + item["sub_amount"] + for item in chart_data + ], + + # ----------------------------------------------------- + # DIFFERENCE + # ----------------------------------------------------- + "qty_difference": [ + item["qty_difference"] + for item in chart_data + ], + + "rate_difference": [ + item["rate_difference"] + for item in chart_data + ], + + "amount_difference": [ + item["amount_difference"] + for item in chart_data + ], + + # ----------------------------------------------------- + # COMPLETE ROW DATA + # Useful for table/export + # ----------------------------------------------------- + "rows": chart_data }) -# category = manhole_excavation + @dashboard_bp.route("/api/mh-analysis") def manhole_analysis(): - subcontractor_id = request.args.get("subcontractor", "").strip() - ra_bill = request.args.get("ra_bill", "").strip() + # ========================================================= + # CATEGORY + # ========================================================= + CATEGORY = "manhole_excavation" - # Convert "1,2,3" -> ["1", "2", "3"] - ra_bill_list = [] - if ra_bill: - ra_bill_list = [x.strip() for x in ra_bill.split(",") if x.strip()] + # ========================================================= + # REQUEST PARAMETERS + # ========================================================= + subcontractor_id = request.args.get( + "subcontractor", + "" + ).strip() + + ra_bill = request.args.get( + "ra_bill", + "" + ).strip() + + # ========================================================= + # VALIDATE SUBCONTRACTOR + # ========================================================= + subcontractor_id_int = None - # Subcontractor Query - sub_query = ManholeExcavation.query if subcontractor_id: + + try: + subcontractor_id_int = int( + subcontractor_id + ) + + except ValueError: + + return jsonify({ + "success": False, + "message": "Invalid subcontractor." + }), 400 + + # ========================================================= + # RA BILL LIST + # ========================================================= + ra_bill_list = [] + + if ra_bill: + + ra_bill_list = [ + bill.strip() + for bill in ra_bill.split(",") + if bill.strip() + ] + + # ========================================================= + # SUBCONTRACTOR QUERY + # ========================================================= + sub_query = ManholeExcavation.query + + if subcontractor_id_int: + sub_query = sub_query.filter( - ManholeExcavation.subcontractor_id == int(subcontractor_id) + ManholeExcavation.subcontractor_id + == subcontractor_id_int ) if ra_bill_list: + sub_query = sub_query.filter( - ManholeExcavation.RA_Bill_No.in_(ra_bill_list) + ManholeExcavation.RA_Bill_No.in_( + ra_bill_list + ) ) sub_records = sub_query.all() + # ========================================================= + # SUBCONTRACTOR KEYS + # MH_NO + LOCATION + # ========================================================= sub_keys = [ ( - (r.MH_NO or "").strip().upper(), - (r.Location or "").strip().upper() + (record.MH_NO or "").strip().upper(), + (record.Location or "").strip().upper() ) - for r in sub_records + for record in sub_records ] + # ========================================================= + # CLIENT QUERY + # ========================================================= client_query = ManholeExcavationClient.query if sub_keys: client_query = client_query.filter( - tuple_( - func.upper(func.trim(ManholeExcavationClient.MH_NO)), - func.upper(func.trim(ManholeExcavationClient.Location)) - ).in_(sub_keys) + tuple_( + func.upper( + func.trim( + ManholeExcavationClient.MH_NO + ) + ), + + func.upper( + func.trim( + ManholeExcavationClient.Location + ) + ) + + ).in_(sub_keys) ) client_records = client_query.all() - chart_data = [ + # ========================================================= + # GET CLIENT RATES + # + # Category: + # manhole_excavation + # + # item_name: + # actual model field name + # ========================================================= + client_rates = ( + ClientRate.query + .filter( + + func.lower( + func.trim( + ClientRate.category + ) + ) == CATEGORY.lower(), + + func.lower( + func.trim( + ClientRate.status + ) + ) == "active" + ) + .all() + ) + + # ========================================================= + # GET SUBCONTRACTOR RATES + # + # Selected Subcontractor + # + + # Category + # + + # Active + # ========================================================= + sub_rates = [] + + if subcontractor_id_int: + + sub_rates = ( + SubcontractorRate.query + .filter( + + SubcontractorRate.subcontractor_id + == subcontractor_id_int, + + func.lower( + func.trim( + SubcontractorRate.category + ) + ) == CATEGORY.lower(), + + func.lower( + func.trim( + SubcontractorRate.status + ) + ) == "active" + ) + .all() + ) + + # ========================================================= + # RATE MAP + # + # Example: + # + # hard_rock_0_to_1_5_total : 450 + # hard_rock_1_5_to_3_0_total : 500 + # ========================================================= + + client_rate_map = { + + (rate.item_name or "") + .strip() + .lower(): + + float(rate.rate or 0) + + for rate in client_rates + } + + sub_rate_map = { + + (rate.item_name or "") + .strip() + .lower(): + + float(rate.rate or 0) + + for rate in sub_rates + } + + # ========================================================= + # TOTAL HELPER + # ========================================================= + def total(records, field): + + value = 0 + + for record in records: + + field_value = getattr( + record, + field, + 0 + ) + + if field_value is not None: + + value += float( + field_value + ) + + return round(value, 2) + + # ========================================================= + # MANHOLE MAPPING + # + # label: + # Dashboard display name + # + # field: + # Actual model field + # + # item_name: + # Rate Master item_name + # + # IMPORTANT: + # item_name must be the actual model field name. + # ========================================================= + + MANHOLE_MAPPING = [ + { "label": "Marshi 0 to 1.5", - "client": total(client_records, "Marshi_Muddy_Slushy_0_to_1_5_total"), - "sub": 0 + "field": + "Marshi_Muddy_Slushy_0_to_1_5_total", + "item_name": + "Marshi_Muddy_Slushy_0_to_1_5_total" }, + { "label": "Marshi 1.5 to 3.0", - "client": total(client_records, "Marshi_Muddy_Slushy_1_5_to_3_0_total"), - "sub": 0 + "field": + "Marshi_Muddy_Slushy_1_5_to_3_0_total", + "item_name": + "Marshi_Muddy_Slushy_1_5_to_3_0_total" }, + { "label": "Marshi 3.0 to 4.5", - "client": total(client_records, "Marshi_Muddy_Slushy_3_0_to_4_5_total"), - "sub": 0 + "field": + "Marshi_Muddy_Slushy_3_0_to_4_5_total", + "item_name": + "Marshi_Muddy_Slushy_3_0_to_4_5_total" }, { "label": "Soft Murum 0 to 1.5", - "client": total(client_records, "Soft_Murum_0_to_1_5_total"), - "sub": total(sub_records, "Soft_Murum_0_to_1_5_total") + "field": + "Soft_Murum_0_to_1_5_total", + "item_name": + "Soft_Murum_0_to_1_5_total" }, + { "label": "Soft Murum 1.5 to 3.0", - "client": total(client_records, "Soft_Murum_1_5_to_3_0_total"), - "sub": total(sub_records, "Soft_Murum_1_5_to_3_0_total") + "field": + "Soft_Murum_1_5_to_3_0_total", + "item_name": + "Soft_Murum_1_5_to_3_0_total" }, + { "label": "Soft Murum 3.0 to 4.5", - "client": total(client_records, "Soft_Murum_3_0_to_4_5_total"), - "sub": total(sub_records, "Soft_Murum_3_0_to_4_5_total") + "field": + "Soft_Murum_3_0_to_4_5_total", + "item_name": + "Soft_Murum_3_0_to_4_5_total" }, { "label": "Hard Murum 0 to 1.5", - "client": total(client_records, "Hard_Murum_0_to_1_5_total"), - "sub": total(sub_records, "Hard_Murum_0_to_1_5_total") + "field": + "Hard_Murum_0_to_1_5_total", + "item_name": + "Hard_Murum_0_to_1_5_total" }, + { "label": "Hard Murum 1.5 to 3.0", - "client": total(client_records, "Hard_Murum_1_5_to_3_0_total"), - "sub": total(sub_records, "Hard_Murum_1_5_and_above_total") + "field": + "Hard_Murum_1_5_to_3_0_total", + "item_name": + "Hard_Murum_1_5_to_3_0_total" }, { "label": "Soft Rock 0 to 1.5", - "client": total(client_records, "Soft_Rock_0_to_1_5_total"), - "sub": total(sub_records, "Soft_Rock_0_to_1_5_total") + "field": + "Soft_Rock_0_to_1_5_total", + "item_name": + "Soft_Rock_0_to_1_5_total" }, + { "label": "Soft Rock 1.5 to 3.0", - "client": total(client_records, "Soft_Rock_1_5_to_3_0_total"), - "sub": total(sub_records, "Soft_Rock_1_5_and_above_total") + "field": + "Soft_Rock_1_5_to_3_0_total", + "item_name": + "Soft_Rock_1_5_to_3_0_total" }, { "label": "Hard Rock 0 to 1.5", - "client": total(client_records, "Hard_Rock_0_to_1_5_total"), - "sub": total(sub_records, "Hard_Rock_0_to_1_5_total") + "field": + "Hard_Rock_0_to_1_5_total", + "item_name": + "Hard_Rock_0_to_1_5_total" }, + { "label": "Hard Rock 1.5 to 3.0", - "client": total(client_records, "Hard_Rock_1_5_to_3_0_total"), - "sub": total(sub_records, "Hard_Rock_1_5_to_3_0_total") + "field": + "Hard_Rock_1_5_to_3_0_total", + "item_name": + "Hard_Rock_1_5_to_3_0_total" }, + { "label": "Hard Rock 3.0 to 4.5", - "client": total(client_records, "Hard_Rock_3_0_to_4_5_total"), - "sub": total(sub_records, "Hard_Rock_3_0_to_4_5_total") + "field": + "Hard_Rock_3_0_to_4_5_total", + "item_name": + "Hard_Rock_3_0_to_4_5_total" }, + { "label": "Hard Rock 4.5 to 6.0", - "client": total(client_records, "Hard_Rock_4_5_to_6_0_total"), - "sub": total(sub_records, "Hard_Rock_4_5_to_6_0_total") + "field": + "Hard_Rock_4_5_to_6_0_total", + "item_name": + "Hard_Rock_4_5_to_6_0_total" }, + { "label": "Hard Rock 6.0 to 7.5", - "client": total(client_records, "Hard_Rock_6_0_to_7_5_total"), - "sub": total(sub_records, "Hard_Rock_6_0_to_7_5_total") + "field": + "Hard_Rock_6_0_to_7_5_total", + "item_name": + "Hard_Rock_6_0_to_7_5_total" } ] + # ========================================================= + # BUILD CHART DATA + # ========================================================= + chart_data = [] + + for mapping in MANHOLE_MAPPING: + + field = mapping["field"] + + # ===================================================== + # CLIENT QUANTITY + # ===================================================== + client_qty = total( + client_records, + field + ) + + # ===================================================== + # SUBCONTRACTOR QUANTITY + # ===================================================== + sub_qty = 0 + + if sub_records: + + sub_qty = total( + sub_records, + field + ) + + # ===================================================== + # RATE MASTER ITEM NAME + # ===================================================== + item_name = mapping[ + "item_name" + ] + + rate_key = ( + item_name + .strip() + .lower() + ) + + # ===================================================== + # CLIENT RATE + # ===================================================== + client_rate = client_rate_map.get( + rate_key, + 0 + ) + + # ===================================================== + # SUBCONTRACTOR RATE + # ===================================================== + sub_rate = sub_rate_map.get( + rate_key, + 0 + ) + + # ===================================================== + # AMOUNT + # ===================================================== + client_amount = ( + client_qty + * client_rate + ) + + sub_amount = ( + sub_qty + * sub_rate + ) + + # ===================================================== + # DIFFERENCE + # ===================================================== + qty_difference = ( + client_qty + - sub_qty + ) + + rate_difference = ( + client_rate + - sub_rate + ) + + amount_difference = ( + client_amount + - sub_amount + ) + + # ===================================================== + # FINAL ROW + # ===================================================== + chart_data.append({ + + # Display + "label": + mapping["label"], + + # Actual model field + "field": + field, + + # Rate Master item_name + "item_name": + item_name, + + # Quantity + "client": + round(client_qty, 2), + + "sub": + round(sub_qty, 2), + + # Rate + "client_rate": + round(client_rate, 2), + + "sub_rate": + round(sub_rate, 2), + + # Amount + "client_amount": + round(client_amount, 2), + + "sub_amount": + round(sub_amount, 2), + + # Difference + "qty_difference": + round(qty_difference, 2), + + "rate_difference": + round(rate_difference, 2), + + "amount_difference": + round(amount_difference, 2) + }) + + # ========================================================= + # RESPONSE + # ========================================================= return jsonify({ - "title": "Manhole Excavation Comparison", - "y_title": "Manhole Qty (Nos)", - "labels": [x["label"] for x in chart_data], - "client_qty": [x["client"] for x in chart_data], - "sub_qty": [x["sub"] for x in chart_data] + + "success": True, + + "title": + "Manhole Excavation Comparison", + + "y_title": + "Manhole Qty (Nos)", + + # ===================================================== + # CHART DATA + # ===================================================== + + "labels": [ + item["label"] + for item in chart_data + ], + + "client_qty": [ + item["client"] + for item in chart_data + ], + + "sub_qty": [ + item["sub"] + for item in chart_data + ], + + # ===================================================== + # RATE DATA + # ===================================================== + + "client_rate": [ + item["client_rate"] + for item in chart_data + ], + + "sub_rate": [ + item["sub_rate"] + for item in chart_data + ], + + # ===================================================== + # AMOUNT DATA + # ===================================================== + + "client_amount": [ + item["client_amount"] + for item in chart_data + ], + + "sub_amount": [ + item["sub_amount"] + for item in chart_data + ], + + # ===================================================== + # DIFFERENCE DATA + # ===================================================== + + "qty_difference": [ + item["qty_difference"] + for item in chart_data + ], + + "rate_difference": [ + item["rate_difference"] + for item in chart_data + ], + + "amount_difference": [ + item["amount_difference"] + for item in chart_data + ], + + # ===================================================== + # COMPLETE TABLE ROWS + # ===================================================== + + "rows": chart_data }) - - -# category = Manhole_Domestic_Chamber @dashboard_bp.route("/api/mdc-analysis") def Manhole_Domestic_Chamber_analysis(): - subcontractor_id = request.args.get("subcontractor", "").strip() - ra_bill = request.args.get("ra_bill", "").strip() + # ========================================================= + # CATEGORY + # ========================================================= + CATEGORY = "Manhole_Domestic_Chamber" - # Convert "1,2,3" -> ["1", "2", "3"] - ra_bill_list = [] - if ra_bill: - ra_bill_list = [x.strip() for x in ra_bill.split(",") if x.strip()] + # ========================================================= + # REQUEST PARAMETERS + # ========================================================= + subcontractor_id = request.args.get( + "subcontractor", + "" + ).strip() + + ra_bill = request.args.get( + "ra_bill", + "" + ).strip() + + # ========================================================= + # VALIDATE SUBCONTRACTOR + # ========================================================= + subcontractor_id_int = None - # Subcontractor Query - sub_query = ManholeDomesticChamber.query if subcontractor_id: + + try: + subcontractor_id_int = int( + subcontractor_id + ) + + except ValueError: + + return jsonify({ + "success": False, + "message": "Invalid subcontractor." + }), 400 + + # ========================================================= + # RA BILL LIST + # ========================================================= + ra_bill_list = [] + + if ra_bill: + + ra_bill_list = [ + x.strip() + for x in ra_bill.split(",") + if x.strip() + ] + + # ========================================================= + # SUBCONTRACTOR QUERY + # ========================================================= + sub_query = ManholeDomesticChamber.query + + if subcontractor_id_int: + sub_query = sub_query.filter( - ManholeDomesticChamber.subcontractor_id == int(subcontractor_id) + ManholeDomesticChamber.subcontractor_id + == subcontractor_id_int ) if ra_bill_list: + sub_query = sub_query.filter( - ManholeDomesticChamber.RA_Bill_No.in_(ra_bill_list) + ManholeDomesticChamber.RA_Bill_No.in_( + ra_bill_list + ) ) sub_records = sub_query.all() + # ========================================================= + # MATCHING KEYS + # MH_NO + LOCATION + # ========================================================= sub_keys = [ ( (r.MH_NO or "").strip().upper(), @@ -451,69 +1819,463 @@ def Manhole_Domestic_Chamber_analysis(): for r in sub_records ] + # ========================================================= + # CLIENT QUERY + # ========================================================= client_query = ManholeDomesticChamberClient.query if sub_keys: client_query = client_query.filter( - tuple_( - func.upper(func.trim(ManholeDomesticChamberClient.MH_NO)), - func.upper(func.trim(ManholeDomesticChamberClient.Location)) - ).in_(sub_keys) + tuple_( + func.upper( + func.trim( + ManholeDomesticChamberClient.MH_NO + ) + ), + + func.upper( + func.trim( + ManholeDomesticChamberClient.Location + ) + ) + + ).in_(sub_keys) ) client_records = client_query.all() - chart_data = [ + # ========================================================= + # GET CLIENT RATES + # + # Category: + # Manhole_Domestic_Chamber + # ========================================================= + client_rates = ( + ClientRate.query + .filter( + + func.lower( + func.trim( + ClientRate.category + ) + ) == CATEGORY.lower(), + + func.lower( + func.trim( + ClientRate.status + ) + ) == "active" + ) + .all() + ) + + # ========================================================= + # GET SUBCONTRACTOR RATES + # + # Selected subcontractor + # + + # Category + # + + # Active + # ========================================================= + sub_rates = [] + + if subcontractor_id_int: + + sub_rates = ( + SubcontractorRate.query + .filter( + + SubcontractorRate.subcontractor_id + == subcontractor_id_int, + + func.lower( + func.trim( + SubcontractorRate.category + ) + ) == CATEGORY.lower(), + + func.lower( + func.trim( + SubcontractorRate.status + ) + ) == "active" + ) + .all() + ) + + # ========================================================= + # CLIENT RATE MAP + # ========================================================= + client_rate_map = { + + (rate.item_name or "") + .strip() + .lower(): + + float(rate.rate or 0) + + for rate in client_rates + } + + # ========================================================= + # SUBCONTRACTOR RATE MAP + # ========================================================= + sub_rate_map = { + + (rate.item_name or "") + .strip() + .lower(): + + float(rate.rate or 0) + + for rate in sub_rates + } + + # ========================================================= + # TOTAL HELPER + # ========================================================= + def total(records, field): + + value = 0 + + for record in records: + + field_value = getattr( + record, + field, + 0 + ) + + if field_value is not None: + + value += float( + field_value + ) + + return round(value, 2) + + # ========================================================= + # MDC MAPPING + # + # label = Dashboard display name + # field = Actual model field + # item_name = Rate Master item_name + # ========================================================= + MDC_MAPPING = [ + { "label": "Depth of MH", - "client": total(client_records, "Depth_of_MH"), - "sub": total(sub_records, "Depth_of_MH") + + "field": + "Depth_of_MH", + + "item_name": + "Depth_of_MH" }, + { - "label": "Domestic_Chambers Total", - "client": total(client_records, "Total"), - "sub": total(sub_records, "Total") + "label": "Domestic Chambers Total", + + "field": + "Total", + + "item_name": + "Total" } ] + # ========================================================= + # BUILD DATA + # ========================================================= + chart_data = [] + + for item in MDC_MAPPING: + + field = item["field"] + + item_name = item["item_name"] + + # ===================================================== + # QUANTITY + # ===================================================== + client_qty = total( + client_records, + field + ) + + sub_qty = total( + sub_records, + field + ) + + # ===================================================== + # RATE KEY + # ===================================================== + rate_key = ( + item_name + .strip() + .lower() + ) + + # ===================================================== + # CLIENT RATE + # ===================================================== + client_rate = client_rate_map.get( + rate_key, + 0 + ) + + # ===================================================== + # SUBCONTRACTOR RATE + # ===================================================== + sub_rate = sub_rate_map.get( + rate_key, + 0 + ) + + # ===================================================== + # AMOUNT + # ===================================================== + client_amount = ( + client_qty + * client_rate + ) + + sub_amount = ( + sub_qty + * sub_rate + ) + + # ===================================================== + # DIFFERENCES + # ===================================================== + qty_difference = ( + client_qty + - sub_qty + ) + + rate_difference = ( + client_rate + - sub_rate + ) + + amount_difference = ( + client_amount + - sub_amount + ) + + # ===================================================== + # FINAL ROW + # ===================================================== + chart_data.append({ + + "label": + item["label"], + + "field": + field, + + "item_name": + item_name, + + # Quantity + "client": + round(client_qty, 2), + + "sub": + round(sub_qty, 2), + + # Rate + "client_rate": + round(client_rate, 2), + + "sub_rate": + round(sub_rate, 2), + + # Amount + "client_amount": + round(client_amount, 2), + + "sub_amount": + round(sub_amount, 2), + + # Difference + "qty_difference": + round(qty_difference, 2), + + "rate_difference": + round(rate_difference, 2), + + "amount_difference": + round(amount_difference, 2) + }) + + # ========================================================= + # RESPONSE + # ========================================================= return jsonify({ - "title": "Domestic Chamber Comparison", - "y_title": "Quantity (Nos)", - "labels": [x["label"] for x in chart_data], - "client_qty": [x["client"] for x in chart_data], - "sub_qty": [x["sub"] for x in chart_data] + + "success": True, + + "title": + "Domestic Chamber Comparison", + + "y_title": + "Quantity (Nos)", + + # ===================================================== + # CHART + # ===================================================== + + "labels": [ + x["label"] + for x in chart_data + ], + + "client_qty": [ + x["client"] + for x in chart_data + ], + + "sub_qty": [ + x["sub"] + for x in chart_data + ], + + # ===================================================== + # RATE + # ===================================================== + + "client_rate": [ + x["client_rate"] + for x in chart_data + ], + + "sub_rate": [ + x["sub_rate"] + for x in chart_data + ], + + # ===================================================== + # AMOUNT + # ===================================================== + + "client_amount": [ + x["client_amount"] + for x in chart_data + ], + + "sub_amount": [ + x["sub_amount"] + for x in chart_data + ], + + # ===================================================== + # DIFFERENCE + # ===================================================== + + "qty_difference": [ + x["qty_difference"] + for x in chart_data + ], + + "rate_difference": [ + x["rate_difference"] + for x in chart_data + ], + + "amount_difference": [ + x["amount_difference"] + for x in chart_data + ], + + # Complete table data + "rows": chart_data }) - - - -# category = Laying + + @dashboard_bp.route("/api/laying-analysis") def laying_analysis(): - subcontractor_id = request.args.get("subcontractor", "").strip() - ra_bill = request.args.get("ra_bill", "").strip() + # ========================================================= + # CATEGORY + # ========================================================= + CATEGORY = "Laying" - # Convert "1,2,3" -> ["1", "2", "3"] - ra_bill_list = [] - if ra_bill: - ra_bill_list = [x.strip() for x in ra_bill.split(",") if x.strip()] + # ========================================================= + # REQUEST PARAMETERS + # ========================================================= + subcontractor_id = request.args.get( + "subcontractor", + "" + ).strip() + + ra_bill = request.args.get( + "ra_bill", + "" + ).strip() + + # ========================================================= + # VALIDATE SUBCONTRACTOR + # ========================================================= + subcontractor_id_int = None - # Subcontractor Query - sub_query = Laying.query if subcontractor_id: + + try: + subcontractor_id_int = int( + subcontractor_id + ) + + except ValueError: + + return jsonify({ + "success": False, + "message": "Invalid subcontractor." + }), 400 + + # ========================================================= + # RA BILL LIST + # ========================================================= + ra_bill_list = [] + + if ra_bill: + + ra_bill_list = [ + x.strip() + for x in ra_bill.split(",") + if x.strip() + ] + + # ========================================================= + # SUBCONTRACTOR QUERY + # ========================================================= + sub_query = Laying.query + + if subcontractor_id_int: + sub_query = sub_query.filter( - Laying.subcontractor_id == int(subcontractor_id) + Laying.subcontractor_id + == subcontractor_id_int ) if ra_bill_list: + sub_query = sub_query.filter( - Laying.RA_Bill_No.in_(ra_bill_list) + Laying.RA_Bill_No.in_( + ra_bill_list + ) ) sub_records = sub_query.all() + # ========================================================= + # MATCHING KEYS + # MH_NO + LOCATION + # ========================================================= sub_keys = [ ( (r.MH_NO or "").strip().upper(), @@ -522,93 +2284,440 @@ def laying_analysis(): for r in sub_records ] + # ========================================================= + # CLIENT QUERY + # ========================================================= client_query = LayingClient.query if sub_keys: client_query = client_query.filter( - tuple_( - func.upper(func.trim(LayingClient.MH_NO)), - func.upper(func.trim(LayingClient.Location)) - ).in_(sub_keys) + tuple_( + func.upper( + func.trim( + LayingClient.MH_NO + ) + ), + + func.upper( + func.trim( + LayingClient.Location + ) + ) + + ).in_(sub_keys) ) client_records = client_query.all() - chart_data = [ + # ========================================================= + # GET CLIENT RATES + # + # Category = Laying + # ========================================================= + client_rates = ( + ClientRate.query + .filter( + + func.lower( + func.trim( + ClientRate.category + ) + ) == CATEGORY.lower(), + + func.lower( + func.trim( + ClientRate.status + ) + ) == "active" + ) + .all() + ) + + # ========================================================= + # GET SUBCONTRACTOR RATES + # + # Selected Subcontractor + # + + # Category = Laying + # + + # Active + # ========================================================= + sub_rates = [] + + if subcontractor_id_int: + + sub_rates = ( + SubcontractorRate.query + .filter( + + SubcontractorRate.subcontractor_id + == subcontractor_id_int, + + func.lower( + func.trim( + SubcontractorRate.category + ) + ) == CATEGORY.lower(), + + func.lower( + func.trim( + SubcontractorRate.status + ) + ) == "active" + ) + .all() + ) + + # ========================================================= + # CLIENT RATE MAP + # ========================================================= + client_rate_map = { + + (rate.item_name or "") + .strip() + .lower(): + + float(rate.rate or 0) + + for rate in client_rates + } + + # ========================================================= + # SUBCONTRACTOR RATE MAP + # ========================================================= + sub_rate_map = { + + (rate.item_name or "") + .strip() + .lower(): + + float(rate.rate or 0) + + for rate in sub_rates + } + + # ========================================================= + # TOTAL HELPER + # ========================================================= + def total(records, field): + + value = 0 + + for record in records: + + field_value = getattr( + record, + field, + 0 + ) + + if field_value is not None: + + value += float( + field_value + ) + + return round(value, 2) + + # ========================================================= + # LAYING MAPPING + # + # label: + # Dashboard display name + # + # field: + # Actual model field + # + # item_name: + # Rate Master item_name + # ========================================================= + LAYING_MAPPING = [ + { "label": "150 mm", - "client": total(client_records, "pipe_150_mm"), - "sub": total(sub_records, "pipe_150_mm") + "field": "pipe_150_mm", + "item_name": "pipe_150_mm" }, + { "label": "200 mm", - "client": total(client_records, "pipe_200_mm"), - "sub": total(sub_records, "pipe_200_mm") + "field": "pipe_200_mm", + "item_name": "pipe_200_mm" }, + { "label": "250 mm", - "client": total(client_records, "pipe_250_mm"), - "sub": total(sub_records, "pipe_250_mm") + "field": "pipe_250_mm", + "item_name": "pipe_250_mm" }, { "label": "300 mm", - "client": total(client_records, "pipe_300_mm"), - "sub": total(sub_records, "pipe_300_mm") + "field": "pipe_300_mm", + "item_name": "pipe_300_mm" }, + { "label": "350 mm", - "client": total(client_records, "pipe_350_mm"), - "sub": total(sub_records, "pipe_350_mm") + "field": "pipe_350_mm", + "item_name": "pipe_350_mm" }, { "label": "400 mm", - "client": total(client_records, "pipe_400_mm"), - "sub": total(sub_records, "pipe_400_mm") + "field": "pipe_400_mm", + "item_name": "pipe_400_mm" }, + { "label": "450 mm", - "client": total(client_records, "pipe_450_mm"), - "sub": total(sub_records, "pipe_450_mm") + "field": "pipe_450_mm", + "item_name": "pipe_450_mm" }, { "label": "500 mm", - "client": total(client_records, "pipe_500_mm"), - "sub": total(sub_records, "pipe_500_mm") + "field": "pipe_500_mm", + "item_name": "pipe_500_mm" }, + { "label": "600 mm", - "client": total(client_records, "pipe_600_mm"), - "sub": total(sub_records, "pipe_600_mm") + "field": "pipe_600_mm", + "item_name": "pipe_600_mm" }, + { "label": "700 mm", - "client": total(client_records, "pipe_700_mm"), - "sub": total(sub_records, "pipe_700_mm") + "field": "pipe_700_mm", + "item_name": "pipe_700_mm" }, + { "label": "900 mm", - "client": total(client_records, "pipe_900_mm"), - "sub": total(sub_records, "pipe_900_mm") + "field": "pipe_900_mm", + "item_name": "pipe_900_mm" }, + { "label": "1200 mm", - "client": total(client_records, "pipe_1200_mm"), - "sub": total(sub_records, "pipe_1200_mm") + "field": "pipe_1200_mm", + "item_name": "pipe_1200_mm" } ] + # ========================================================= + # BUILD DATA + # ========================================================= + chart_data = [] + + for item in LAYING_MAPPING: + + field = item["field"] + + item_name = item["item_name"] + + # ===================================================== + # QUANTITY + # ===================================================== + client_qty = total( + client_records, + field + ) + + sub_qty = total( + sub_records, + field + ) + + # ===================================================== + # RATE KEY + # ===================================================== + rate_key = ( + item_name + .strip() + .lower() + ) + + # ===================================================== + # CLIENT RATE + # ===================================================== + client_rate = client_rate_map.get( + rate_key, + 0 + ) + + # ===================================================== + # SUBCONTRACTOR RATE + # ===================================================== + sub_rate = sub_rate_map.get( + rate_key, + 0 + ) + + # ===================================================== + # AMOUNT + # ===================================================== + client_amount = ( + client_qty + * client_rate + ) + + sub_amount = ( + sub_qty + * sub_rate + ) + + # ===================================================== + # DIFFERENCES + # ===================================================== + qty_difference = ( + client_qty + - sub_qty + ) + + rate_difference = ( + client_rate + - sub_rate + ) + + amount_difference = ( + client_amount + - sub_amount + ) + + # ===================================================== + # FINAL ROW + # ===================================================== + chart_data.append({ + + "label": + item["label"], + + # Actual model field + "field": + field, + + # Rate Master item_name + "item_name": + item_name, + + # Quantity + "client": + round(client_qty, 2), + + "sub": + round(sub_qty, 2), + + # Rate + "client_rate": + round(client_rate, 2), + + "sub_rate": + round(sub_rate, 2), + + # Amount + "client_amount": + round(client_amount, 2), + + "sub_amount": + round(sub_amount, 2), + + # Difference + "qty_difference": + round(qty_difference, 2), + + "rate_difference": + round(rate_difference, 2), + + "amount_difference": + round(amount_difference, 2) + }) + + # ========================================================= + # RESPONSE + # ========================================================= return jsonify({ - "title": "Pipe Laying Comparison", - "y_title": "Pipe Length (Mtr)", - "labels": [x["label"] for x in chart_data], - "client_qty": [x["client"] for x in chart_data], - "sub_qty": [x["sub"] for x in chart_data] + + "success": True, + + "title": + "Pipe Laying Comparison", + + "y_title": + "Pipe Length (Mtr)", + + # ===================================================== + # CHART DATA + # ===================================================== + + "labels": [ + x["label"] + for x in chart_data + ], + + "client_qty": [ + x["client"] + for x in chart_data + ], + + "sub_qty": [ + x["sub"] + for x in chart_data + ], + + # ===================================================== + # RATE DATA + # ===================================================== + + "client_rate": [ + x["client_rate"] + for x in chart_data + ], + + "sub_rate": [ + x["sub_rate"] + for x in chart_data + ], + + # ===================================================== + # AMOUNT DATA + # ===================================================== + + "client_amount": [ + x["client_amount"] + for x in chart_data + ], + + "sub_amount": [ + x["sub_amount"] + for x in chart_data + ], + + # ===================================================== + # DIFFERENCE DATA + # ===================================================== + + "qty_difference": [ + x["qty_difference"] + for x in chart_data + ], + + "rate_difference": [ + x["rate_difference"] + for x in chart_data + ], + + "amount_difference": [ + x["amount_difference"] + for x in chart_data + ], + + # Complete table rows + "rows": chart_data }) - + \ No newline at end of file diff --git a/app/routes/engineering_master_routes.py b/app/routes/engineering_master_routes.py index ae41d73..d9b212f 100644 --- a/app/routes/engineering_master_routes.py +++ b/app/routes/engineering_master_routes.py @@ -57,7 +57,7 @@ def client_edit_rate(rate_id): if result["success"]: flash(result["message"], "success") - return redirect(url_for("engineering.client-rate")) + return redirect(url_for("engineering.client_rate_master")) flash(result["message"], "danger") @@ -75,7 +75,7 @@ def client_edit_rate(rate_id): def client_delete_rate(rate_id): ClientRateService.delete_rate(rate_id) flash("Rate deleted successfully.", "success") - return redirect(url_for("engineering.client-rate")) + return redirect(url_for("engineering.client_rate_master")) diff --git a/app/templates/subcontractor_dashboard.html b/app/templates/subcontractor_dashboard.html index d5f9732..bc8c573 100644 --- a/app/templates/subcontractor_dashboard.html +++ b/app/templates/subcontractor_dashboard.html @@ -142,11 +142,21 @@