
from decimal import Decimal

from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json
import mysql.connector
from mysql.connector import Error
import config
import openpyxl
import os
import re
import ast
from datetime import datetime
import pandas as pd
from openpyxl.styles import Font

# this is server
app = Flask(__name__)
app.secret_key = '9f2a1b8c4d6e7f0123456789abcdef01'

str_pattern_reg = "^[A-Za-z ]+$"


class ResponseHandler:
    @staticmethod
    def invalid_name(entity):
        return {'status': 'error', 'message': f'Invalid {entity} name. Only letters are allowed!'}

    @staticmethod
    def already_exists(entity):
        return {'status': 'exists', 'message': f'{entity.capitalize()} already exists!'}

    @staticmethod
    def add_success(entity):
        return {'status': 'success', 'message': f'{entity.capitalize()} added successfully!'}

    @staticmethod
    def add_failure(entity):
        return {'status': 'error', 'message': f'Failed to add {entity}.'}

    @staticmethod
    def is_available(entity):
        return {'status': 'available', 'message': f'{entity.capitalize()} name is available!'}

    @staticmethod
    def delete_success(entity):
        return {'status': 'success', 'message': f'{entity.capitalize()} deleted successfully!'}

    @staticmethod
    def delete_failure(entity):
        return {'status': 'error', 'message': f'Failed to delete {entity}.'}

    @staticmethod
    def update_success(entity):
        return {'status': 'success', 'message': f'{entity.capitalize()} updated successfully!'}

    @staticmethod
    def update_failure(entity):
        return {'status': 'error', 'message': f'Failed to update {entity}.'}

    @staticmethod
    def fetch_failure(entity):
        return f"Failed to fetch {entity}"


# Helper: JSON Response Formatter
def json_response(message_obj, status_code):
    return jsonify(message_obj), status_code


# this is Index page OR Home page..
@app.route('/')
def index():
    return render_template('index.html')


# this is Profile page ..
@app.route('/admin_profile', methods=['GET', 'POST'])
def admin_profile():
    return render_template('admin_profile.html')


# ------------------------- State controller ------------------------------------------
@app.route('/add_state', methods=['GET', 'POST'])
def add_state():
    connection = config.get_db_connection()
    statedata = []

    if connection:
        cursor = connection.cursor()
        if request.method == 'POST':
            state_name = request.form['state_Name'].strip()

            if not re.match(str_pattern_reg, state_name):
                return json_response(ResponseHandler.invalid_name("state"), 400)

            try:
                # cursor.execute("SELECT * FROM states WHERE State_Name = %s", (state_name,))
                # if cursor.fetchone():
                #     return json_response(ResponseHandler.already_exists("state"), 409)

                cursor.callproc("CheckStateExists", (state_name,))
                for data in cursor.stored_results():
                    existing_state = data.fetchone()

                if existing_state:
                    return json_response(ResponseHandler.already_exists("state"), 409)

                # cursor.execute("call SaveState (%s)", (state_name,))
                cursor.callproc("SaveState", (state_name,))
                connection.commit()
                return json_response(ResponseHandler.add_success("state"), 200)

            except mysql.connector.Error as e:
                print(f"Error inserting state: {e}")
                return json_response(ResponseHandler.add_failure("state"), 500)

        try:
            # cursor.execute("SELECT State_ID, State_Name FROM states")
            # statedata = cursor.fetchall()
            cursor.callproc("GetAllStates")
            for res in cursor.stored_results():
                statedata = res.fetchall()
        except mysql.connector.Error as e:
            print(f"Error fetching states: {e}")
            return ResponseHandler.fetch_failure("states"), 500
        finally:
            cursor.close()
            connection.close()

    return render_template('add_state.html', statedata=statedata)


# AJAX route to check state existence
@app.route('/check_state', methods=['POST'])
def check_state():
    connection = config.get_db_connection()

    if connection:
        cursor = connection.cursor()
        state_name = request.json.get('state_Name', '').strip()

        if not re.match(str_pattern_reg, state_name):
            return json_response(ResponseHandler.invalid_name("state"), 400)

        try:
            # cursor.execute("SELECT * FROM states WHERE State_Name = %s", (state_name,))
            # existing_state = cursor.fetchone()

            cursor.callproc("CheckStateExists", (state_name,))
            for data in cursor.stored_results():
                existing_state = data.fetchone()

            if existing_state:
                return json_response(ResponseHandler.already_exists("state"), 409)
            else:
                return json_response(ResponseHandler.is_available("state"), 200)

        except mysql.connector.Error as e:
            print(f"Error checking state: {e}")
            return json_response(ResponseHandler.add_failure("state"), 500)
        finally:
            cursor.close()
            connection.close()


# Delete State
@app.route('/delete_state/<int:id>', methods=['GET'])
def deleteState(id):
    connection = config.get_db_connection()
    cursor = connection.cursor()

    try:
        # cursor.execute("DELETE FROM states WHERE State_ID = %s", (id,))
        cursor.callproc('DeleteState', (id,))
        connection.commit()
        # For API response
        # return json_response(ResponseHandler.delete_success("state"), 200)

    except mysql.connector.Error as e:
        print(f"Error deleting data: {e}")
        return json_response(ResponseHandler.delete_failure("state"), 500)

    finally:
        cursor.close()
        connection.close()

    return redirect(url_for('add_state'))


# Edit State
@app.route('/edit_state/<int:id>', methods=['GET', 'POST'])
def editState(id):
    connection = config.get_db_connection()
    cursor = connection.cursor()
    # str_pattern_reg = r"^[A-Za-z\s]+$"

    if request.method == 'POST':
        state_name = request.form['state_Name'].strip()

        if not re.match(str_pattern_reg, state_name):
            return ResponseHandler.invalid_name("state"), 400

        try:
            # cursor.execute("UPDATE states SET State_Name = %s WHERE State_ID = %s", (state_name, id))
            cursor.callproc("UpdateStateById", (id, state_name))
            connection.commit()
            return redirect(url_for('add_state'))
        except mysql.connector.Error as e:
            print(f"Error updating data: {e}")
            return ResponseHandler.add_failure("state"), 500
        finally:
            cursor.close()
            connection.close()

    try:
        # cursor.execute("SELECT * FROM states WHERE State_ID = %s", (id,))
        # state = cursor.fetchone()
        cursor.callproc("GetStateByID", (id,))
        for result in cursor.stored_results():
            state = result.fetchone()
        if state is None:
            return "State not found", 404
    except mysql.connector.Error as e:
        print(f"Error retrieving data: {e}")
        return ResponseHandler.fetch_failure("state"), 500
    finally:
        cursor.close()
        connection.close()

    return render_template('edit_state.html', state=state)


# -------- end State controller -----------

# ------------------------- District controller ------------------------------------------
@app.route('/add_district', methods=['GET', 'POST'])
def add_district():
    connection = config.get_db_connection()
    districtdata = []
    states = []

    if connection:
        cursor = connection.cursor()

        try:
            # cursor.execute("SELECT State_ID, State_Name FROM states")
            # states = cursor.fetchall()
            cursor.callproc("GetAllStates")
            for res in cursor.stored_results():
                states = res.fetchall()

        except mysql.connector.Error as e:
            print(f"Error fetching states: {e}")
            return ResponseHandler.fetch_failure("states"), 500

        if request.method == 'POST':
            district_name = request.form['district_Name'].strip()
            state_id = request.form['state_Id']

            if not re.match(str_pattern_reg, district_name):
                return json_response(ResponseHandler.invalid_name("district"), 400)

            try:
                # cursor.execute("SELECT * FROM districts WHERE District_Name = %s AND State_Id = %s",
                #                (district_name, state_id))
                cursor.callproc("GetDistrictByNameAndState", (district_name, state_id))
                for data in cursor.stored_results():
                    rs = data.fetchone()
                if rs:
                    return json_response(ResponseHandler.already_exists("district"), 409)

                cursor.callproc('SaveDistrict', (district_name, state_id))
                connection.commit()

                return json_response(ResponseHandler.add_success("district"), 200)

            except mysql.connector.Error as e:
                print(f"Error inserting district: {e}")
                return json_response(ResponseHandler.add_failure("district"), 500)

        try:
            # cursor.execute("SELECT d.District_id, d.District_Name, s.State_Name, s.State_Id FROM districts d JOIN states s ON d.State_Id = s.State_ID")
            # districtdata = cursor.fetchall()
            cursor.callproc("GetAllDistricts")
            for dis in cursor.stored_results():
                districtdata = dis.fetchall()
        except mysql.connector.Error as e:
            print(f"Error fetching districts: {e}")
            return ResponseHandler.fetch_failure("districts"), 500
        finally:
            cursor.close()
            connection.close()

    return render_template('add_district.html', districtdata=districtdata, states=states)


# AJAX route to check district existence
@app.route('/check_district', methods=['POST'])
def check_district():
    connection = config.get_db_connection()

    if connection:
        cursor = connection.cursor()
        district_name = request.json.get('district_Name', '').strip()
        state_id = request.json.get('state_Id', '')

        if not re.match(str_pattern_reg, district_name):
            return json_response(ResponseHandler.invalid_name("district"), 400)

        try:
            # cursor.execute("SELECT * FROM districts WHERE District_Name = %s AND State_Id = %s",
            #                (district_name, state_id))
            # existing_district = cursor.fetchone()
            cursor.callproc("GetDistrictByNameAndState", (district_name, state_id,))
            for result in cursor.stored_results():
                existing_district = result.fetchone()

            if existing_district:
                return json_response(ResponseHandler.already_exists("district"), 409)
            else:
                return json_response(ResponseHandler.is_available("district"), 200)

        except mysql.connector.Error as e:
            print(f"Error checking district: {e}")
            return json_response(ResponseHandler.add_failure("district"), 500)
        finally:
            cursor.close()
            connection.close()


# this is delete District method by id..
@app.route('/delete_district/<int:district_id>', methods=['GET', 'POST'])
def delete_district(district_id):
    connection = config.get_db_connection()

    if connection:
        cursor = connection.cursor()
        try:
            # cursor.execute("DELETE FROM districts WHERE District_id = %s", (district_id,))
            cursor.callproc("DeleteDistrict", (district_id,))
            connection.commit()
        except mysql.connector.Error as e:
            print(f"Error deleting district: {e}")
            return json_response(ResponseHandler.delete_failure("district"), 500)
        finally:
            cursor.close()
            connection.close()

    return redirect('/add_district')


# this is update District page by id ..
@app.route('/edit_district/<int:district_id>', methods=['GET', 'POST'])
def edit_district(district_id):
    connection = config.get_db_connection()
    districtdata = []
    states = []

    if connection:
        cursor = connection.cursor()

        # Retrieve all states for dropdown
        try:
            # cursor.execute("SELECT State_ID, State_Name FROM states")
            # states = cursor.fetchall()
            cursor.callproc("GetAllStates")
            for res in cursor.stored_results():
                states = res.fetchall()

        except mysql.connector.Error as e:
            print(f"Error fetching states: {e}")
            return ResponseHandler.fetch_failure("states"), 500

        # Retrieve district info
        try:
            # cursor.execute("SELECT District_Name, State_Id FROM districts WHERE District_id = %s", (district_id,))
            # districtdata = cursor.fetchone()
            cursor.callproc("GetDistrictDataByID", (district_id,))
            for rs in cursor.stored_results():
                districtdata = rs.fetchone()
        except mysql.connector.Error as e:
            print(f"Error fetching district data: {e}")
            return ResponseHandler.fetch_failure("district"), 500

        # Handle update
        if request.method == 'POST':
            district_name = request.form['district_Name']
            state_id = request.form['state_Id']

            try:
                # cursor.execute( "UPDATE districts SET District_Name = %s, State_Id = %s WHERE District_id = %s",
                #     (district_name, state_id, district_id) )

                cursor.callproc("UpdateDistrict", (district_id, state_id, district_name,))
                connection.commit()
            except mysql.connector.Error as e:
                print(f"Error updating district: {e}")
                return ResponseHandler.update_failure("district"), 500
            return redirect('/add_district')

    return render_template('edit_district.html', districtdata=districtdata, states=states)


# --------- end District controller -------------

# ------------------------- Block controller ------------------------------------------
@app.route('/add_block', methods=['GET', 'POST'])
def add_block():
    connection = config.get_db_connection()
    block_data = []
    states = []

    if connection:
        cursor = connection.cursor()
        try:
            # cursor.execute("SELECT State_ID, State_Name FROM states")
            # states = cursor.fetchall()
            cursor.callproc("GetAllStates")
            for res in cursor.stored_results():
                states = res.fetchall()

        except mysql.connector.Error as e:
            print(f"Error fetching states: {e}")
            return json_response(ResponseHandler.fetch_failure("states"), 500)

        if request.method == 'POST':
            block_name = request.form['block_Name'].strip()
            district_id = request.form['district_Id']

            if not re.match(str_pattern_reg, block_name):
                return json_response(ResponseHandler.invalid_name("block"), 400)

            try:
                # cursor.execute("SELECT * FROM blocks WHERE Block_Name = %s AND District_id = %s",
                #                (block_name, district_id))
                # existing_block = cursor.fetchone()
                cursor.callproc("GetBlockByNameAndDistrict", (block_name, district_id,))
                for rs in cursor.stored_results():
                    existing_block = rs.fetchone()

                if existing_block:
                    return json_response(ResponseHandler.already_exists("block"), 409)

                cursor.callproc('SaveBlock', (block_name, district_id))
                connection.commit()

                return json_response(ResponseHandler.add_success("block"), 200)

            except mysql.connector.Error as e:
                print(f"Error adding block: {e}")
                return json_response(ResponseHandler.add_failure("block"), 500)

        # Fetch all blocks to display
        try:
            # cursor.execute(
            #     """SELECT b.Block_Id, b.Block_Name, d.District_Name
            #        FROM blocks b
            #        JOIN districts d ON b.District_id = d.District_id"""
            # )
            # block_data = cursor.fetchall()
            cursor.callproc("GetAllBlocks")
            for blocks in cursor.stored_results():
                block_data = blocks.fetchall()

        except mysql.connector.Error as e:
            print(f"Error fetching blocks: {e}")
            return json_response(ResponseHandler.fetch_failure("blocks"), 500)
        finally:
            cursor.close()
            connection.close()
    return render_template('add_block.html', block_data=block_data, states=states)


# check block
@app.route('/check_block', methods=['POST'])
def check_block():
    connection = config.get_db_connection()
    cursor = connection.cursor()
    block_name = request.json.get('block_Name', '').strip()
    district_id = request.json.get('district_Id', '')

    if not re.match(str_pattern_reg, block_name):
        return json_response(ResponseHandler.invalid_name("block"), 400)

    # cursor.execute("SELECT * FROM blocks WHERE Block_Name = %s AND District_id = %s", (block_name, district_id))
    # existing_block = cursor.fetchone()
    cursor.callproc("GetBlockByNameAndDistrict", (block_name, district_id))
    for rs in cursor.stored_results():
        existing_block = rs.fetchone()

    if existing_block:
        return json_response(ResponseHandler.already_exists("block"), 409)

    return json_response(ResponseHandler.is_available("block"), 200)


# update block by id
# @app.route('/edit_block/<int:block_id>', methods=['GET', 'POST'])
# def edit_block(block_id):
#     connection = config.get_db_connection()
#     block_data, states, districts = [], [], []
#
#     if connection:
#         cursor = connection.cursor()
#         try:
#             # cursor.execute("SELECT State_ID, State_Name FROM states")
#             # states = cursor.fetchall()
#             cursor.callproc("GetAllStates")
#             for res in cursor.stored_results():
#                 states = res.fetchall()
#
#             # cursor.execute("SELECT Block_Name, District_id FROM blocks WHERE Block_Id = %s", (block_id,))
#             # block_data = cursor.fetchone()
#             cursor.callproc("GetBlockById",(block_id,))
#             for block in cursor.stored_results():
#                 block_data = block.fetchone()
#
#         except mysql.connector.Error as e:
#             print(f"Error fetching block data: {e}")
#             return json_response(ResponseHandler.fetch_failure("block data"), 500)
#
#         if request.method == 'POST':
#             block_name = request.form['block_Name']
#             district_id = request.form['district_Id']
#
#             try:
#                 # cursor.execute("UPDATE blocks SET Block_Name = %s, District_id = %s WHERE Block_Id = %s",
#                 #                (block_name, district_id, block_id))
#
#                 cursor.callproc("UpdateBlock",(block_id,block_name, district_id))
#                 connection.commit()
#             except mysql.connector.Error as e:
#                 print(f"Error updating block: {e}")
#                 return json_response(ResponseHandler.update_failure("block"), 500)
#
#             return redirect('/add_block')
#
#         try:
#             # cursor.execute("SELECT District_id, District_Name FROM districts")
#             # districts = cursor.fetchall()
#
#             cursor.callproc("GetAllDistricts")
#             for dis in cursor.stored_results():
#                 districts = dis.fetchall()
#
#         except mysql.connector.Error as e:
#             print(f"Error fetching districts: {e}")
#             return json_response(ResponseHandler.fetch_failure("districts"), 500)
#
#         return render_template('edit_block.html', block_data=block_data, states=states, districts=districts)

@app.route('/edit_block/<int:block_id>', methods=['GET', 'POST'])
def edit_block(block_id):
    connection = config.get_db_connection()
    block_data = []
    states = []
    districts = []

    if connection:
        cursor = connection.cursor()
        # Retrieve all states
        try:
            # cursor.execute("SELECT State_ID, State_Name FROM states")
            # states = cursor.fetchall()
            cursor.callproc("GetAllStates")
            for rs in cursor.stored_results():
                states = rs.fetchall()
        except mysql.connector.Error as e:
            print(f"Error fetching states: {e}")
            return "Failed to fetch states", 500

        # Retrieve block data
        try:
            # cursor.execute("SELECT Block_Name, District_id FROM blocks WHERE Block_Id = %s", (block_id,))
            # block_data = cursor.fetchone()
            cursor.callproc("GetBlockDataByID", (block_id,))
            for rs in cursor.stored_results():
                block_data = rs.fetchone()
        except mysql.connector.Error as e:
            print(f"Error fetching block data: {e}")
            return "Failed to fetch block data", 500

        # Handle POST request
        if request.method == 'POST':
            block_name = request.form['block_Name']
            district_id = request.form['district_Id']
            try:
                # cursor.execute("UPDATE blocks SET Block_Name = %s, District_id = %s WHERE Block_Id = %s",
                #                (block_name, district_id, block_id))
                cursor.callproc("UpdateBlockById", (block_name, district_id, block_id,))
                connection.commit()
                flash("Block updated successfully!", "success")
                return redirect(url_for('add_block', block_id=block_id))
            except mysql.connector.Error as e:
                print(f"Error updating blocks: {e}")
                return "Failed to update blocks", 500

        # Retrieve districts for the dropdown
        try:
            # cursor.execute("SELECT District_id, District_Name FROM districts")
            # districts = cursor.fetchall()
            cursor.callproc("GetAllDistrictsData")
            for rs in cursor.stored_results():
                districts = rs.fetchall()
        except mysql.connector.Error as e:
            print(f"Error fetching districts: {e}")
            return "Failed to fetch districts", 500

        return render_template('edit_block.html', block_data=block_data, states=states, districts=districts)






# delete block by id
@app.route('/delete_block/<int:block_id>', methods=['GET', 'POST'])
def delete_block(block_id):
    connection = config.get_db_connection()

    if connection:
        cursor = connection.cursor()
        try:
            # cursor.execute("DELETE FROM blocks WHERE Block_Id = %s", (block_id,))
            cursor.callproc("DeleteBlock", (block_id,))
            connection.commit()
        except mysql.connector.Error as e:
            print(f"Error deleting block: {e}")
            return json_response(ResponseHandler.add_failure("block"), 500)
        finally:
            cursor.close()
            connection.close()

    return redirect('/add_block')


# this is get district all data by using state id ..
@app.route('/get_districts/<int:state_id>', methods=['GET'])
def get_districts(state_id):
    connection = config.get_db_connection()
    districts = []

    if connection:
        cursor = connection.cursor()
        try:
            # cursor.execute("SELECT District_id, District_Name FROM districts WHERE State_Id = %s", (state_id,))
            # districts = cursor.fetchall()

            cursor.callproc("GetDistrictsByStateId", (state_id,))
            for dis in cursor.stored_results():
                districts = dis.fetchall()
        except mysql.connector.Error as e:
            print(f"Error fetching districts: {e}")
            return json_response(ResponseHandler.fetch_failure("districts"), 500)
        finally:
            cursor.close()
            connection.close()

    return jsonify({
        "districts": [{"District_id": d[0], "District_Name": d[1]} for d in districts]
    })


# ----------- end Block controller -----------------

# ------------------------- Village controller ------------------------------------------
# Route to add a village
@app.route('/add_village', methods=['GET', 'POST'])
def add_village():
    connection = config.get_db_connection()
    cursor = connection.cursor()
    states = []
    villages = []

    try:
        # Fetch all states
        # cursor.execute("SELECT State_ID, State_Name FROM states")
        # states = cursor.fetchall()

        cursor.callproc("GetAllStates")
        for res in cursor.stored_results():
            states = res.fetchall()

        # Fetch all villages with their block names
        # cursor.execute("""
        #     SELECT v.Village_Id, v.Village_Name, b.Block_Name
        #     FROM villages v
        #     JOIN blocks b ON v.Block_Id = b.Block_Id
        # """)
        # villages = cursor.fetchall()
        cursor.callproc("GetAllVillages")
        for result in cursor.stored_results():
            villages = result.fetchall()

        if request.method == 'POST':
            block_id = request.form.get('block_Id')
            village_name = request.form.get('Village_Name', '').strip()

            if not block_id:
                return json_response(ResponseHandler.add_failure("block"), 400)

            if not re.match(str_pattern_reg, village_name):
                return json_response(ResponseHandler.invalid_name("village"), 400)

            # Check if the village already exists in the block
            # cursor.execute("SELECT * FROM villages WHERE Village_Name = %s AND Block_Id = %s", (village_name, block_id))
            # existing_village = cursor.fetchone()
            cursor.callproc("GetVillageByNameAndBlock", (village_name, block_id,))
            for rs in cursor.stored_results():
                existing_village = rs.fetchone()
            if existing_village:
                return json_response(ResponseHandler.already_exists("village"), 409)

            # Insert new village
            cursor.callproc('SaveVillage', (village_name, block_id))
            connection.commit()

            return json_response(ResponseHandler.add_success("village"), 200)

    except mysql.connector.Error as e:
        print(f"Database Error: {e}")
        return json_response(ResponseHandler.add_failure("village"), 500)
    finally:
        cursor.close()
        connection.close()

    return render_template('add_village.html', states=states, villages=villages)


# get block by district id
@app.route('/get_blocks/<int:district_id>', methods=['GET'])
def get_blocks(district_id):
    connection = config.get_db_connection()
    cursor = connection.cursor()
    blocks = []

    try:
        # cursor.execute("SELECT Block_Id, Block_Name FROM blocks WHERE District_id = %s", (district_id,))
        # blocks = cursor.fetchall()
        cursor.callproc("GetBlocksByDistrict", (district_id,))
        for rs in cursor.stored_results():
            blocks = rs.fetchall()
    except mysql.connector.Error as e:
        print(f"Error fetching blocks: {e}")
        return json_response({"error": "Failed to fetch blocks"}, 500)
    finally:
        cursor.close()
        connection.close()

    return jsonify({"blocks": [{"Block_Id": block[0], "Block_Name": block[1]} for block in blocks]})


# check village
@app.route('/check_village', methods=['POST'])
def check_village():
    connection = config.get_db_connection()
    cursor = connection.cursor()

    block_id = request.form.get('block_Id')
    village_name = request.form.get('Village_Name', '').strip()

    # Validate village name
    if not re.match(str_pattern_reg, village_name):
        return json_response(ResponseHandler.invalid_name("village"), 400)

    if not block_id or not village_name:
        return json_response({'status': 'error', 'message': 'Block and Village Name are required!'}, 400)

    # cursor.execute("SELECT * FROM villages WHERE Village_Name = %s AND Block_Id = %s", (village_name, block_id))
    # existing_village = cursor.fetchone()
    cursor.callproc("GetVillageByNameAndBlocks", (village_name, block_id))
    for rs in cursor.stored_results():
        existing_village = rs.fetchone()

    cursor.close()
    connection.close()

    if existing_village:
        return json_response(ResponseHandler.already_exists("village"), 409)
    else:
        return json_response(ResponseHandler.is_available("village"), 200)


# update village
# @app.route('/edit_village/<int:village_id>', methods=['GET', 'POST'])
# def edit_village(village_id):
#     connection = config.get_db_connection()
#     village_data = None
#     blocks = []
#
#     try:
#         cursor = connection.cursor()
#         # Fetch village details
#         # cursor.execute("SELECT Village_Name, Block_Id FROM villages WHERE Village_Id = %s", (village_id,))
#         # village_data = cursor.fetchone()
#         cursor.callproc("GetVillageById", (village_id,))
#         for result in cursor.stored_results():
#             village_data = result.fetchone()
#
#         # Fetch all blocks for dropdown
#         # cursor.execute("SELECT Block_Id, Block_Name FROM blocks")
#         # blocks = cursor.fetchall()
#
#         cursor.callproc("GetAllBlocks")
#         for result in cursor.stored_results():
#             blocks = result.fetchall()
#
#         if request.method == 'POST':
#             village_name = request.form['Village_Name']
#             block_id = request.form['block_Id']
#
#             if not re.match(str_pattern_reg, village_name):
#                 return json_response(ResponseHandler.invalid_name("village"), 400)
#
#             # cursor.execute("UPDATE villages SET Village_Name = %s, Block_Id = %s WHERE Village_Id = %s",
#             #                (village_name, block_id, village_id))
#
#             cursor.callproc("UpdateVillage", (village_id, village_name, block_id,))
#
#             connection.commit()
#             return json_response(ResponseHandler.update_success("village"), 200)
#
#     except mysql.connector.Error as e:
#         print(f"Error: {e}")
#         return json_response(ResponseHandler.update_failure("village"), 500)
#     finally:
#         if cursor:
#             cursor.close()
#         if connection:
#             connection.close()
#
#     return render_template('edit_village.html', village_data=village_data, blocks=blocks)

@app.route('/edit_village/<int:village_id>', methods=['GET', 'POST'])
def edit_village(village_id):
    connection = config.get_db_connection()
    village_data = None
    blocks = []

    try:
        cursor = connection.cursor()
        # # Fetch village details
        # cursor.execute("SELECT Village_Name, Block_Id FROM villages WHERE Village_Id = %s", (village_id,))
        # village_data = cursor.fetchone()
        cursor.callproc("GetVillageDetailsById", (village_id,))
        for rs in cursor.stored_results():
            village_data = rs.fetchone()
        # Fetch all blocks for dropdown
        # cursor.execute("SELECT Block_Id, Block_Name FROM blocks")
        # blocks = cursor.fetchall()
        cursor.callproc('GetAllBlocks')
        for result in cursor.stored_results():
            blocks = result.fetchall()

        if request.method == 'POST':
            village_name = request.form['Village_Name']
            block_id = request.form['block_Id']

            if not re.match("^[A-Za-z ]+$", village_name):
                flash("Invalid village name! Only letters and spaces allowed.", "error")
                return redirect(url_for('edit_village', village_id=village_id))

            cursor.execute("UPDATE villages SET Village_Name = %s, Block_Id = %s WHERE Village_Id = %s",
                           (village_name, block_id, village_id))
            connection.commit()
            flash("Village updated successfully!", "success")
            return redirect(url_for('edit_village', village_id=village_id))

    except mysql.connector.Error as e:
        print(f"Error: {e}")
        return "Failed to process request", 500
    finally:
        if cursor:
            cursor.close()
        if connection:
            connection.close()

    return render_template('edit_village.html', village_data=village_data, blocks=blocks)


# delete village
@app.route('/delete_village/<int:village_id>', methods=['GET', 'POST'])
def delete_village(village_id):
    connection = config.get_db_connection()
    cursor = connection.cursor()

    try:
        # cursor.execute("DELETE FROM villages WHERE Village_Id = %s", (village_id,))
        cursor.callproc("DeleteVillage", (village_id,))
        connection.commit()
        # return json_response(ResponseHandler.delete_success("village"), 200)
    except mysql.connector.Error as e:
        print(f"Error: {e}")
        return json_response(ResponseHandler.add_failure("village"), 500)
    finally:
        if cursor:
            cursor.close()
        if connection:
            connection.close()

    return redirect(url_for('add_village'))


# ---- end Village controller ---------------------


# -------------------------------- Invoice controller ------------------------------------------
@app.route('/add_invoice', methods=['GET', 'POST'])
def add_invoice():
    connection = config.get_db_connection()
    if not connection:
        return jsonify({"status": "error", "message": "Database connection failed"}), 500

    if request.method == 'POST':
        try:
            cursor = connection.cursor(dictionary=True)

            # Get the village name from the form
            village_name = request.form.get('village')
            print("village name", village_name)

            # Query the database to get the corresponding Village_Id based on the village name
            # cursor.execute("SELECT Village_Id FROM villages WHERE Village_Name = %s", (village_name,))
            # village_result = cursor.fetchone()
            cursor.callproc("GetVillageIdByName", (village_name,))
            for rs in cursor.stored_results():
                village_result = rs.fetchone()

            if not village_result:
                return jsonify({"status": "error", "message": f"Village '{village_name}' not found"}), 400

            village_id = village_result['Village_Id']

            # Fetch form data
            pmc_no = request.form.get('pmc_no')
            work_type = request.form.get('work_type')
            invoice_details = request.form.get('invoice_details')
            invoice_date = request.form.get('invoice_date')
            invoice_no = request.form.get('invoice_no')
            basic_amount = request.form.get('basic_amount')
            debit_amount = request.form.get('debit_amount')
            after_debit_amount = request.form.get('after_debit_amount')
            amount = request.form.get('amount')
            gst_amount = request.form.get('gst_amount')
            tds_amount = request.form.get('tds_amount')
            sd_amount = request.form.get('sd_amount')
            on_commission = request.form.get('on_commission')
            hydro_testing = request.form.get('hydro_testing')
            gst_sd_amount = request.form.get('gst_sd_amount')
            final_amount = request.form.get('final_amount')

            # insert_invoice_query = '''
            #     INSERT INTO invoice (
            #         PMC_No, Village_Id, Work_Type, Invoice_Details, Invoice_Date, Invoice_No,
            #         Basic_Amount, Debit_Amount, After_Debit_Amount, Amount, GST_Amount, TDS_Amount,
            #         SD_Amount, On_Commission, Hydro_Testing, GST_SD_Amount, Final_Amount
            #     ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            # '''
            # invoice_values = (
            #     pmc_no, village_id, work_type, invoice_details, invoice_date, invoice_no,
            #     basic_amount, debit_amount, after_debit_amount, amount, gst_amount, tds_amount,
            #     sd_amount, on_commission, hydro_testing, gst_sd_amount, final_amount
            # )
            # cursor.execute(insert_invoice_query, invoice_values)
            # connection.commit()
            # invoice_id = cursor.lastrowid
            cursor.callproc('InsertInvoice', [
                pmc_no, village_id, work_type, invoice_details, invoice_date, invoice_no,
                basic_amount, debit_amount, after_debit_amount, amount, gst_amount, tds_amount,
                sd_amount, on_commission, hydro_testing, gst_sd_amount, final_amount])

            for result in cursor.stored_results():
                invoice_id = result.fetchone()['invoice_id']
            connection.commit()

            print("This is the invocie id from the invoice table ", invoice_id)

            # Insert into assign_subcontractors table
            # subcontractor_id = request.form.get('subcontractor_id')
            # insert_assign_query = '''
            #     INSERT INTO assign_subcontractors (PMC_no, Contractor_Id, Village_Id)
            #     VALUES (%s, %s, %s)
            # '''
            # cursor.execute(insert_assign_query, (pmc_no, subcontractor_id, village_id))
            # connection.commit()
            subcontractor_id = request.form.get('subcontractor_id')
            cursor.callproc('AssignSubcontractor', [pmc_no, subcontractor_id, village_id])
            connection.commit()

            # Insert Hold Amounts into invoice_subcontractor_hold_join table
            hold_types = request.form.getlist('hold_type[]')
            hold_amounts = request.form.getlist('hold_amount[]')
            hold_count = 0

            for hold_type, hold_amount in zip(hold_types, hold_amounts):
                # cursor.execute("SELECT hold_type_id FROM hold_types WHERE hold_type = %s", (hold_type,))
                # hold_type_result = cursor.fetchone()
                cursor.callproc('GetHoldTypeIdByName', [hold_type])
                for result in cursor.stored_results():
                    hold_type_result = result.fetchone()
                print("hold type from invoice ", hold_type_result)
                if not hold_type_result:
                    return jsonify({"status": "error", "message": f"Invalid Hold Type: {hold_type}"}), 400
                hold_type_id = hold_type_result['hold_type_id']
                # insert_hold_query = '''
                #     INSERT INTO invoice_subcontractor_hold_join (Contractor_Id, Invoice_Id, hold_type_id, hold_amount)
                #     VALUES (%s, %s, %s, %s)
                # '''
                # cursor.execute(insert_hold_query, (subcontractor_id, invoice_id, hold_type_id, hold_amount))
                # hold_count += 1

                # connection.commit()
                cursor.callproc('InsertInvoiceSubcontractorHold', [
                    subcontractor_id, invoice_id, hold_type_id, hold_amount
                ])
                connection.commit()
                hold_count += 1
                print("Hold count from the invoice", hold_count)
                connection.commit()

            return jsonify({"status": "success", "message": "Invoice added successfully"}), 201

        except mysql.connector.Error as e:
            connection.rollback()
            return jsonify({"status": "error", "message": f"Failed to add invoice: {str(e)}"}), 500
        finally:
            cursor.close()
            connection.close()

    # GET request: fetch and display all invoices (all fields) along with the form
    try:
        cursor = connection.cursor(dictionary=True)
        # cursor.execute("SELECT * FROM view_invoice_details")
        # invoices = cursor.fetchall()
        cursor.callproc('GetAllInvoiceDetails')
        for result in cursor.stored_results():
            invoices = result.fetchall()

        villages = []
        cursor.callproc("GetAllVillages")
        for result in cursor.stored_results():
            villages = result.fetchall()

    except mysql.connector.Error as e:
        print(f"Error: {e}")
        invoices = []
    finally:
        cursor.close()
        connection.close()

    return render_template('add_invoice.html', invoices=invoices, villages=villages)


# search subcontraactor to assing invoice
@app.route('/search_subcontractor', methods=['POST'])
def search_subcontractor():
    connection = config.get_db_connection()
    if not connection:
        return json_response(ResponseHandler.fetch_failure("database connection"), 500)

    sub_query = request.form.get("query")
    try:
        cursor = connection.cursor(dictionary=True)
        # cursor.execute(
        #     "SELECT Contractor_Id, Contractor_Name FROM subcontractors WHERE Contractor_Name LIKE %s",
        #     (f"%{sub_query}%",)
        # )
        # results = cursor.fetchall()
        cursor.callproc('SearchContractorsByName', [sub_query])
        for result in cursor.stored_results():
            results = result.fetchall()
        print(results)
        if not results:
            return "<li>No subcontractor found</li>"

        output = "".join(
            f"<li data-id='{row['Contractor_Id']}'>{row['Contractor_Name']}</li>"
            for row in results
        )
        print("Ajax Call for subcontractor", output)
        return output

    except mysql.connector.Error as e:
        return json_response(ResponseHandler.fetch_failure(f"Search failed: {str(e)}"), 500)

    finally:
        cursor.close()
        connection.close()


# get hold types
@app.route('/get_hold_types', methods=['GET'])
def get_hold_types():
    connection = config.get_db_connection()
    try:
        cursor = connection.cursor(dictionary=True)
        # cursor.execute("SELECT hold_type_id, hold_type FROM hold_types")
        # hold_types = cursor.fetchall()

        cursor.callproc("GetAllHoldTypes")
        for hold in cursor.stored_results():
            hold_types = hold.fetchall()

        return jsonify(hold_types)
    except mysql.connector.Error as e:
        return ResponseHandler.fetch_failure({str(e)}), 500
        # return jsonify({"status": "error", "message": f"Failed to fetch hold types: {str(e)}"}), 500
    finally:
        cursor.close()
        connection.close()


# update invoice by id

@app.route('/edit_invoice/<int:invoice_id>', methods=['GET', 'POST'])
def edit_invoice(invoice_id):
    connection = config.get_db_connection()
    if not connection:
        return jsonify({"status": "error", "message": "Database connection failed"}), 500

    cursor = connection.cursor(dictionary=True)

    if request.method == 'POST':
        try:
            # Fetch updated form data
            subcontractor_id = request.form.get('subcontractor_id', '').strip()
            subcontractor_id = int(subcontractor_id) if subcontractor_id else None

            village_name = request.form.get('village')
            # cursor.execute("SELECT Village_Id FROM villages WHERE Village_Name = %s", (village_name,))
            # village_result = cursor.fetchone()
            cursor.callproc("GetVillageIdByName", (village_name,))
            for rs in cursor.stored_results():
                village_result = rs.fetchone()
            if not village_result:
                return jsonify({"status": "error", "message": "Invalid Village Name"}), 400
            village_id = village_result['Village_Id']

            pmc_no = request.form.get('pmc_no')
            work_type = request.form.get('work_type')
            invoice_details = request.form.get('invoice_details')
            invoice_date = request.form.get('invoice_date')
            invoice_no = request.form.get('invoice_no')

            # Convert numeric fields properly
            numeric_fields = {
                "basic_amount": request.form.get('basic_amount'),
                "debit_amount": request.form.get('debit_amount'),
                "after_debit_amount": request.form.get('after_debit_amount'),
                "amount": request.form.get('amount'),
                "gst_amount": request.form.get('gst_amount'),
                "tds_amount": request.form.get('tds_amount'),
                "sd_amount": request.form.get('sd_amount'),
                "on_commission": request.form.get('on_commission'),
                "hydro_testing": request.form.get('hydro_testing'),
                "gst_sd_amount": request.form.get('gst_sd_amount'),
                "final_amount": request.form.get('final_amount'),
            }
            numeric_fields = {k: float(v) if v else 0 for k, v in numeric_fields.items()}

            # # Update invoice
            # update_invoice_query = '''
            #     UPDATE invoice
            #     SET PMC_No=%s, Village_Id=%s, Work_Type=%s, Invoice_Details=%s, Invoice_Date=%s,
            #         Invoice_No=%s, Basic_Amount=%s, Debit_Amount=%s, After_Debit_Amount=%s,
            #         Amount=%s, GST_Amount=%s, TDS_Amount=%s, SD_Amount=%s, On_Commission=%s,
            #         Hydro_Testing=%s, GST_SD_Amount=%s, Final_Amount=%s
            #     WHERE Invoice_Id=%s
            # '''
            # invoice_values = (
            #     pmc_no, village_id, work_type, invoice_details, invoice_date, invoice_no,
            #     *numeric_fields.values(), invoice_id
            # )
            # cursor.execute(update_invoice_query, invoice_values)
            # connection.commit()
            cursor.callproc('UpdateInvoice', [
                pmc_no, village_id, work_type, invoice_details, invoice_date, invoice_no,
                *numeric_fields.values(), invoice_id
            ])
            connection.commit()

            # Handle holds
            hold_types = request.form.getlist('hold_type[]')
            hold_amounts = request.form.getlist('hold_amount[]')

            for hold_type, hold_amount in zip(hold_types, hold_amounts):
                if not hold_type:
                    continue  # skip empty hold types

                # Get or insert hold type
                # cursor.execute("SELECT hold_type_id FROM hold_types WHERE hold_type = %s", (hold_type,))
                # hold_type_result = cursor.fetchone()
                cursor.callproc('GetHoldTypeIdByName', [hold_type])
                for result in cursor.stored_results():
                    hold_type_result = result.fetchone()

                # if not hold_type_result:
                #     cursor.execute("INSERT INTO hold_types (hold_type) VALUES (%s)", (hold_type,))
                #     connection.commit()
                #     hold_type_id = cursor.lastrowid
                # else:
                #     hold_type_id = hold_type_result['hold_type_id']

                if not hold_type_result:
                    # Call stored procedure to insert and return new ID
                    cursor.callproc('InsertHoldType', [hold_type, 0])
                    for result in cursor.stored_results():
                        pass  # advance past any results
                    cursor.execute("SELECT @_InsertHoldType_1")
                    hold_type_id = cursor.fetchone()[0]
                    print("if not hold type result anish:", hold_type_id)
                else:
                    hold_type_id = hold_type_result['hold_type_id']
                    print("if hold type result anish:", hold_type_id)

                hold_amount = float(hold_amount) if hold_amount else 0

                # Check if join exists
                # cursor.execute("""
                #     SELECT join_id FROM invoice_subcontractor_hold_join
                #     WHERE Invoice_Id = %s AND Contractor_Id = %s AND hold_type_id = %s
                # """, (invoice_id, subcontractor_id, hold_type_id))
                # join_result = cursor.fetchone()
                cursor.callproc('GetHoldJoinId', [invoice_id, subcontractor_id, hold_type_id])
                for result in cursor.stored_results():
                    join_result = result.fetchone()

                if join_result:
                    # cursor.execute("""
                    #     UPDATE invoice_subcontractor_hold_join
                    #     SET hold_amount = %s
                    #     WHERE join_id = %s
                    # """, (hold_amount, join_result['join_id']))
                    cursor.callproc('UpdateHoldAmountByJoinId', [hold_amount, join_result['join_id']])
                    connection.commit()

                else:
                    # cursor.execute("""
                    #     INSERT INTO invoice_subcontractor_hold_join (Contractor_Id, Invoice_Id, hold_type_id, hold_amount)
                    #     VALUES (%s, %s, %s, %s)
                    # """, (subcontractor_id, invoice_id, hold_type_id, hold_amount))
                    cursor.callproc('InsertInvoiceSubcontractorHold', [
                        subcontractor_id, invoice_id, hold_type_id, hold_amount
                    ])
                    connection.commit()

            connection.commit()
            return jsonify({"status": "success", "message": "Invoice updated successfully"}), 200

        except mysql.connector.Error as e:
            connection.rollback()
            return jsonify({"status": "error", "message": f"Failed to update invoice: {str(e)}"}), 500
        finally:
            cursor.close()
            connection.close()

    # ------------------ GET Request ------------------

    try:
        # Fetch invoice data
        # cursor.execute(
        #     """SELECT i.*, s.Contractor_Name, v.Village_Name
        #        FROM invoice i
        #        LEFT JOIN assign_subcontractors a ON i.PMC_No = a.PMC_no AND i.Village_Id = a.Village_Id
        #        LEFT JOIN subcontractors s ON a.Contractor_Id = s.Contractor_Id
        #        LEFT JOIN villages v ON i.Village_Id = v.Village_Id
        #        WHERE i.Invoice_Id = %s""", (invoice_id,)
        # )
        # invoice = cursor.fetchone()
        cursor.callproc('GetInvoiceDetailsById', [invoice_id])
        for result in cursor.stored_results():
            invoice = result.fetchone()

        if not invoice:
            return jsonify({"status": "error", "message": "Invoice not found"}), 404

        # Important! Clear unread result issue
        while cursor.nextset():
            pass

        # Fetch hold amounts
        # cursor.execute(
        #     """SELECT h.hold_type, ihj.hold_amount
        #        FROM invoice_subcontractor_hold_join ihj
        #        JOIN hold_types h ON ihj.hold_type_id = h.hold_type_id
        #        WHERE ihj.Invoice_Id = %s""", (invoice_id,)
        # )
        # hold_amounts = cursor.fetchall()
        # invoice["hold_amounts"] = hold_amounts
        cursor.callproc('GetHoldAmountsByInvoiceId', [invoice_id])
        for result in cursor.stored_results():
            hold_amounts = result.fetchall()

        invoice["hold_amounts"] = hold_amounts


    except mysql.connector.Error as e:
        return jsonify({"status": "error", "message": f"Database error: {str(e)}"}), 500
    finally:
        cursor.close()
        connection.close()

    return render_template('edit_invoice.html', invoice=invoice)


# delete invoice by id
@app.route('/delete_invoice/<int:invoice_id>', methods=['GET'])
def delete_invoice(invoice_id):
    connection = config.get_db_connection()
    if not connection:
        return json_response(ResponseHandler.fetch_failure("invoice"), 500)

    try:
        cursor = connection.cursor()
        # cursor.execute("DELETE FROM invoice WHERE Invoice_Id = %s", (invoice_id,))

        cursor.callproc("DeleteInvoice", (invoice_id,))
        connection.commit()

        # Check if the invoice was actually deleted
        if cursor.rowcount == 0:
            return json_response(ResponseHandler.fetch_failure("invoice"), 404)

        return redirect(url_for('add_invoice'))

    except mysql.connector.Error as e:
        print("Error deleting invoice:", e)
        return json_response(ResponseHandler.delete_failure("invoice"), 500)

    finally:
        cursor.close()
        connection.close()


# ---------- end Invoice controller ------------------


# ----------------------------- Payment controller ------------------------------------------
# this is Payment Page to add data
# @app.route('/add_payment', methods=['GET', 'POST'])
# def add_payment():
#     connection = config.get_db_connection()
#     payments = []  # List to hold payment history
#
#     if not connection:
#         return json_response(ResponseHandler.fetch_failure("payment"), 500)
#
#     try:
#         cursor = connection.cursor()
#
#         # Retrieve payment history
#         # cursor.execute(
#         #     "SELECT Payment_Id, PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR FROM payment"
#         # )
#         # payments = cursor.fetchall()
#         cursor.callproc("GetAllPayments")
#         for result in cursor.stored_results():
#             payments = result.fetchall()
#
#     except mysql.connector.Error as e:
#         print(f"Error fetching payment history: {e}")
#         return json_response(ResponseHandler.fetch_failure("payment"), 500)
#     finally:
#         cursor.close()
#
#     if request.method == 'POST':
#         pmc_no = request.form['PMC_No']
#         invoice_no = request.form['invoice_No']
#         amount = request.form['Payment_Amount']
#         tds_amount = request.form['TDS_Payment_Amount']
#         total_amount = request.form['total_amount']
#         utr = request.form['utr']
#
#         try:
#             cursor = connection.cursor()
#             cursor.callproc('SavePayment', (
#                 pmc_no, invoice_no, amount, tds_amount, total_amount, utr
#             ))
#             connection.commit()
#             return redirect(url_for('add_payment'))  # Redirect to add_payment page to reload the form
#         except mysql.connector.Error as e:
#             print(f"Error inserting payment: {e}")
#             return json_response(ResponseHandler.add_failure("payment"), 500)
#         finally:
#             cursor.close()
#             connection.close()
#
#     return render_template('add_payment.html', payments=payments)

@app.route('/add_payment', methods=['GET', 'POST'])
def add_payment():
    connection = config.get_db_connection()
    payments = []

    if connection:
        cursor = connection.cursor()

        try:
            # cursor.execute(
            #     "SELECT Payment_Id, PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR FROM payment"
            # )
            # payments = cursor.fetchall()
            cursor.callproc('GetAllPayments')
            for result in cursor.stored_results():
                payments = result.fetchall()

        except mysql.connector.Error as e:
            print(f"Error fetching payment history: {e}")
            return "Failed to fetch payment history", 500
        finally:
            cursor.close()

    if request.method == 'POST':
        pmc_no = request.form['PMC_No']
        invoice_no = request.form['invoice_No']
        amount = request.form['Payment_Amount']
        tds_amount = request.form['TDS_Payment_Amount']
        total_amount = request.form['total_amount']
        utr = request.form['utr']

        try:
            cursor = connection.cursor()
            # cursor.execute('''INSERT INTO payment (PMC_No, invoice_no, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR)
            #                   VALUES (%s, %s, %s, %s, %s, %s)''',
            #                (pmc_no, invoice_no, amount, tds_amount, total_amount, utr))
            # connection.commit()
            cursor.callproc('InsertPayments', [
                pmc_no, invoice_no, amount, tds_amount, total_amount, utr
            ])
            connection.commit()

            return redirect(url_for('add_payment'))

        except mysql.connector.Error as e:
            print(f"Error inserting payment: {e}")
            return "Failed to add payment", 500
        finally:
            cursor.close()
            connection.close()

    return render_template('add_payment.html', payments=payments)


@app.route('/get_pmc_nos_by_subcontractor/<subcontractorId>')
def get_pmc_nos_by_subcontractor(subcontractorId):
    connection = config.get_db_connection()
    cur = connection.cursor()
    print(subcontractorId)
    # query = """
    #     SELECT DISTINCT i.PMC_No
    #     FROM invoice i
    #     JOIN assign_subcontractors a ON i.PMC_No = a.PMC_no
    #     JOIN subcontractors s ON a.Contractor_Id = s.Contractor_Id
    #     WHERE s.Contractor_Id=%s;
    # """
    # cur.execute(query, (subcontractorId,))
    # results = cur.fetchall()
    cur.callproc('GetDistinctPMCNoByContractorId', [subcontractorId])
    for result in cur.stored_results():
        results = result.fetchall()

    print(results)
    pmc_nos = [row[0] for row in results]
    cur.close()
    return jsonify({'pmc_nos': pmc_nos})


# Edit Payment Route
@app.route('/edit_payment/<int:payment_id>', methods=['GET', 'POST'])
def edit_payment(payment_id):
    connection = config.get_db_connection()
    payment_data = {}  # To hold the payment data for the given ID

    if not connection:
        return json_response(ResponseHandler.fetch_failure("payment"), 500)

    try:
        cursor = connection.cursor()

        # Fetch the existing payment data for the given payment_id
        # cursor.execute(
        #     "SELECT Payment_Id, PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR FROM payment WHERE Payment_Id = %s",
        #     (payment_id,)
        # )
        # payment_data = cursor.fetchone()

        cursor.callproc("GetPaymentById", (payment_id,))
        for result in cursor.stored_results():
            payment_data = result.fetchone()

        # Handle POST request to update the payment
        if request.method == 'POST':
            pmc_no = request.form['PMC_No']
            invoice_no = request.form['invoice_No']
            amount = request.form['Payment_Amount']
            tds_amount = request.form['TDS_Payment_Amount']
            total_amount = request.form['total_amount']
            utr = request.form['utr']

            try:
                # cursor.execute('''UPDATE payment SET PMC_No=%s, Invoice_No=%s, Payment_Amount=%s, TDS_Payment_Amount=%s,
                #                   Total_Amount=%s, UTR=%s WHERE Payment_Id=%s''',
                #                (pmc_no, invoice_no, amount, tds_amount, total_amount, utr, payment_id))

                cursor.callproc("UpdatePayment",
                                (payment_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr,))
                connection.commit()

                return redirect(url_for('add_payment'))  # Redirect to add_payment page to view the updated list
            except mysql.connector.Error as e:
                print(f"Error updating payment: {e}")
                return json_response(ResponseHandler.update_failure("payment"), 500)

    except mysql.connector.Error as e:
        print(f"Error fetching payment data: {e}")
        return json_response(ResponseHandler.fetch_failure("payment"), 500)
    finally:
        cursor.close()
        connection.close()

    return render_template('edit_payment.html', payment_data=payment_data)


# Delete Payment Route
@app.route('/delete_payment/<int:payment_id>', methods=['GET', 'POST'])
def delete_payment(payment_id):
    connection = config.get_db_connection()
    if not connection:
        return json_response(ResponseHandler.fetch_failure("payment"), 500)
    try:
        cursor = connection.cursor()
        # cursor.execute("DELETE FROM payment WHERE Payment_Id = %s", (payment_id,))

        cursor.callproc("DeletePayment", (payment_id,))
        connection.commit()
        # Check if any rows were deleted
        if cursor.rowcount == 0:
            return json_response(ResponseHandler.fetch_failure("payment"), 404)
        return redirect(url_for('add_payment'))  # Redirect back to the add_payment page

    except mysql.connector.Error as e:
        print(f"Error deleting payment: {e}")
        return json_response(ResponseHandler.delete_failure("payment"), 500)
    finally:
        cursor.close()
        connection.close()


# --- end Payment controller -----------

# ------------------------- GST Release controller ------------------------------------------
@app.route('/add_gst_release', methods=['GET', 'POST'])
def add_gst_release():
    connection = config.get_db_connection()
    gst_releases = []  # List to hold GST Release history
    invoices = []  # List to hold invoices for the dropdown

    if not connection:
        return json_response(ResponseHandler.fetch_failure("GST Release"), 500)

    try:
        cursor = connection.cursor()

        # Retrieve GST Release history
        # cursor.execute("SELECT GST_Release_Id, PMC_No, Invoice_No, Basic_Amount, Final_Amount FROM gst_release")
        # gst_releases = cursor.fetchall()

        cursor.callproc("GetAllGSTReleases")
        for result in cursor.stored_results():
            gst_releases = result.fetchall()

        if request.method == 'POST':
            pmc_no = request.form['PMC_No']
            invoice_no = request.form['invoice_No']
            basic_amount = request.form['basic_amount']
            final_amount = request.form['final_amount']

            cursor.callproc('SaveGSTRelease', (
                pmc_no, invoice_no, basic_amount, final_amount
            ))
            connection.commit()

            return redirect(url_for('add_gst_release'))  # Redirect to add_gst_release page

    except mysql.connector.Error as e:
        print(f"Error: {e}")
        return json_response(ResponseHandler.add_failure("GST Release"), 500)

    finally:
        cursor.close()
        connection.close()

    return render_template('add_gst_release.html', invoices=invoices, gst_releases=gst_releases)


# update gst Release by id
@app.route('/edit_gst_release/<int:gst_release_id>', methods=['GET', 'POST'])
def edit_gst_release(gst_release_id):
    connection = config.get_db_connection()
    gst_release_data = {}  # To hold the GST release data for the given ID
    invoices = []  # List to hold invoices for the dropdown

    if not connection:
        return json_response(ResponseHandler.fetch_failure("GST Release"), 500)

    try:
        cursor = connection.cursor()

        # Fetch the existing GST release data for the given gst_release_id
        # cursor.execute(
        #     "SELECT GST_Release_Id, PMC_No, Invoice_No, Basic_Amount, Final_Amount FROM gst_release WHERE GST_Release_Id = %s",
        #     (gst_release_id,)
        # )
        # gst_release_data = cursor.fetchone()

        cursor.callproc("GetGSTReleaseById", (gst_release_id,))
        for result in cursor.stored_results():
            gst_release_data = result.fetchone()

        if request.method == 'POST':
            pmc_id = request.form['PMC_No']
            invoice_no = request.form['invoice_No']
            basic_amount = request.form['basic_amount']
            final_amount = request.form['final_amount']

            try:
                # cursor.execute('''UPDATE gst_release SET PMC_No=%s, Invoice_No=%s, Basic_Amount=%s, Final_Amount=%s
                #                   WHERE GST_Release_Id=%s''',
                #                (pmc_id, invoice_no, basic_amount, final_amount, gst_release_id))

                cursor.callproc("UpdateGSTRelease", (gst_release_id, pmc_id, invoice_no, basic_amount, final_amount))

                connection.commit()

                return redirect(url_for('add_gst_release'))  # Redirect to the page to view the updated list

            except mysql.connector.Error as e:
                print(f"Error updating GST Release: {e}")
                return json_response(ResponseHandler.update_failure("GST Release"), 500)

    except mysql.connector.Error as e:
        print(f"Error fetching GST Release data: {e}")
        return json_response(ResponseHandler.fetch_failure("GST Release"), 500)

    finally:
        cursor.close()
        connection.close()

    return render_template('edit_gst_release.html', gst_release_data=gst_release_data, invoices=invoices)


# delete gst release by id
@app.route('/delete_gst_release/<int:gst_release_id>', methods=['GET', 'POST'])
def delete_gst_release(gst_release_id):
    connection = config.get_db_connection()
    if not connection:
        return json_response(ResponseHandler.fetch_failure("GST Release"), 500)
    try:
        cursor = connection.cursor()
        # cursor.execute("DELETE FROM gst_release WHERE GST_Release_Id = %s", (gst_release_id,))
        cursor.callproc("DeleteGSTRelease", (gst_release_id,))
        connection.commit()
        # Check if any rows were deleted
        if cursor.rowcount == 0:
            return json_response(ResponseHandler.fetch_failure("GST Release"), 404)
        return redirect(url_for('add_gst_release'))  # Redirect to the add_gst_release page
    except mysql.connector.Error as e:
        print(f"Error deleting GST Release: {e}")
        return json_response(ResponseHandler.delete_failure("GST Release"), 500)
    finally:
        cursor.close()
        connection.close()


# --- end GST Release controller  -----

# ------------------------- Subcontractor controller ------------------------------------------
@app.route('/subcontractor', methods=['GET', 'POST'])
def subcontract():
    connection = config.get_db_connection()
    subcontractor = []

    if not connection:
        return json_response(ResponseHandler.fetch_failure("Subcontractor"), 500)

    try:
        cursor = connection.cursor()

        if request.method == 'GET':
            try:
                # cursor.execute('SELECT * FROM subcontractors;')
                # subcontractor = cursor.fetchall()  # Fetch the current subcontractor list
                # connection.commit()
                cursor.callproc('GetAllSubcontractors')
                for result in cursor.stored_results():
                    subcontractor = result.fetchall()

            except Error as e:
                print(f"Error fetching data: {e}")
                return json_response(ResponseHandler.fetch_failure("Subcontractor"), 500)

        if request.method == 'POST':
            contractor_data = {
                'Contractor_Name': request.form['Contractor_Name'],
                'Address': request.form['Address'],
                'Mobile_No': request.form['Mobile_No'],
                'PAN_No': request.form['PAN_No'],
                'Email': request.form['Email'],
                'Gender': request.form['Gender'],
                'GST_Registration_Type': request.form['GST_Registration_Type'],
                'GST_No': request.form['GST_No'],
                'Contractor_password': request.form['Contractor_password'],
            }

            try:
                cursor.callproc('SaveContractor', (
                    contractor_data['Contractor_Name'],
                    contractor_data['Address'],
                    contractor_data['Mobile_No'],
                    contractor_data['PAN_No'],
                    contractor_data['Email'],
                    contractor_data['Gender'],
                    contractor_data['GST_Registration_Type'],
                    contractor_data['GST_No'],
                    contractor_data['Contractor_password']
                ))
                connection.commit()

                # Re-fetch subcontractors after inserting the new one
                # cursor.execute('SELECT * FROM subcontractors')
                # subcontractor = cursor.fetchall()
                cursor.callproc('GetAllSubcontractors')
                for result in cursor.stored_results():
                    subcontractor = result.fetchall()


            except Error as e:
                print(f"Error inserting data: {e}")
                return json_response(ResponseHandler.add_failure("Subcontractor"), 500)

    except Error as e:
        print(f"Error handling subcontractor data: {e}")
        return json_response(ResponseHandler.fetch_failure("Subcontractor"), 500)

    finally:
        cursor.close()
        connection.close()

    return render_template('add_subcontractor.html', subcontractor=subcontractor)


# update subcontractor by id
@app.route('/edit_subcontractor/<int:id>', methods=['GET', 'POST'])
def edit_subcontractor(id):
    connection = config.get_db_connection()

    if not connection:
        return json_response(ResponseHandler.fetch_failure("Subcontractor"), 500)

    try:
        cursor = connection.cursor()
        subcontractor = None

        # Fetch existing subcontractor data by ID
        # cursor.execute('SELECT * FROM subcontractors WHERE Contractor_Id = %s', (id,))
        # subcontractor = cursor.fetchone()

        cursor.callproc("GetSubcontractorById", (id,))
        for contractors in cursor.stored_results():
            subcontractor = contractors.fetchone()

        if not subcontractor:
            return json_response(ResponseHandler.fetch_failure("Subcontractor"), 404)

        if request.method == 'POST':
            updated_data = {
                'Contractor_Name': request.form['Contractor_Name'],
                'Address': request.form['Address'],
                'Mobile_No': request.form['Mobile_No'],
                'PAN_No': request.form['PAN_No'],
                'Email': request.form['Email'],
                'Gender': request.form['Gender'],
                'GST_Registration_Type': request.form['GST_Registration_Type'],
                'GST_No': request.form['GST_No'],
                'Contractor_password': request.form['Contractor_password'],
                'id': id
            }

            try:
                # cursor.execute("""UPDATE subcontractors SET
                #     Contractor_Name=%(Contractor_Name)s,
                #     Address=%(Address)s,
                #     Mobile_No=%(Mobile_No)s,
                #     PAN_No=%(PAN_No)s,
                #     Email=%(Email)s,
                #     Gender=%(Gender)s,
                #     GST_Registration_Type=%(GST_Registration_Type)s,
                #     GST_No=%(GST_No)s,
                #     Contractor_password=%(Contractor_password)s
                #     WHERE Contractor_Id=%(id)s""", updated_data)

                cursor.callproc("UpdateSubcontractor", (
                    id,
                    updated_data['Contractor_Name'],
                    updated_data['Address'],
                    updated_data['Mobile_No'],
                    updated_data['PAN_No'],
                    updated_data['Email'],
                    updated_data['Gender'],
                    updated_data['GST_Registration_Type'],
                    updated_data['GST_No'],
                    updated_data['Contractor_password']
                ))
                connection.commit()
                return redirect(url_for('subcontract'))

            except Error as e:
                print(f"Error updating subcontractor: {e}")
                return json_response(ResponseHandler.update_failure("Subcontractor"), 500)

    except Error as e:
        print(f"Error fetching subcontractor data: {e}")
        return json_response(ResponseHandler.fetch_failure("Subcontractor"), 500)

    finally:
        cursor.close()
        connection.close()

    return render_template('edit_subcontractor.html', subcontractor=subcontractor)


# delete Sub-Contractor methods by id ..
# @app.route('/deleteSubContractor/<int:id>', methods=['GET', 'POST'])
# def deleteSubContractor(id):
#     connection = config.get_db_connection()

#     if not connection:
#         return json_response(ResponseHandler.fetch_failure("Subcontractor"), 500)

#     try:
#         cursor = connection.cursor()

#         # cursor.execute("DELETE FROM subcontractors WHERE Contractor_Id = %s", (id,))
#         cursor.callproc("DeleteSubcontractor", (id,))
#         connection.commit()

#         # Check if any row was deleted (subcontractor found)
#         if cursor.rowcount == 0:
#             return json_response(ResponseHandler.fetch_failure("Subcontractor"), 404)

#     except Error as e:
#         print(f"Error deleting subcontractor: {e}")
#         return json_response(ResponseHandler.delete_failure("Subcontractor"), 500)

#     finally:
#         cursor.close()
#         connection.close()

#     return redirect(url_for('subcontract'))
@app.route('/deleteSubContractor/<int:id>', methods=['GET', 'POST'])
def deleteSubContractor(id):
    connection = config.get_db_connection()
 
    if not connection:
        return json_response(ResponseHandler.fetch_failure("Subcontractor"), 500)
 
    try:
        cursor = connection.cursor()
 
        # Optional: check if subcontractor exists before attempting delete
        cursor.execute("SELECT 1 FROM subcontractors WHERE Contractor_Id = %s", (id,))
        if cursor.fetchone() is None:
            return json_response(ResponseHandler.fetch_failure("Subcontractor not found"), 404)
 
        # Call stored procedure to delete subcontractor and related records
        cursor.callproc("DeleteSubcontractor", (id,))
        connection.commit()
 
        # Retrieve result from procedure (SELECT ROW_COUNT())
        affected_rows = 0
        for result in cursor.stored_results():
            row = result.fetchone()
            affected_rows = row[0] if row else 0
 
        if affected_rows == 0:
            return json_response(ResponseHandler.fetch_failure("Subcontractor not deleted"), 404)
 
    except Error as e:
        print(f"Error deleting subcontractor: {e}")
        return json_response(ResponseHandler.delete_failure("Subcontractor"), 500)
 
    finally:
        cursor.close()
        connection.close()
 
    return redirect(url_for('subcontract'))  # redirect to subcontractor list page


# ------------------------------- Show Report Subcontractor  ---------------------

UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)


# Upload Excel file html page
@app.route('/upload_excel_file', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        file = request.files['file']
        if file and file.filename.endswith('.xlsx'):
            filepath = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
            file.save(filepath)
            return redirect(url_for('show_table', filename=file.filename))
    return render_template('uploadExcelFile.html')


# Show excel data in tables6
# @app.route('/show_table/<filename>')
# def show_table(filename):
#     global data
#     data = []
#
#     filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
#     wb = openpyxl.load_workbook(filepath, data_only=True)
#     sheet = wb.active
#
#     # Extract key file information from the first 4 rows
#     file_info = {
#         "Subcontractor": sheet.cell(row=1, column=2).value,
#         "State": sheet.cell(row=2, column=2).value,
#         "District": sheet.cell(row=3, column=2).value,
#         "Block": sheet.cell(row=4, column=2).value,
#     }
#
#     errors = []
#     subcontractor_data = None
#     state_data = None
#     district_data = None
#     block_data = None
#
#     # Database connection
#     connection = config.get_db_connection()
#     if connection:
#         try:
#             cursor = connection.cursor(dictionary=True)
#
#             # Validate State
#             # cursor.execute("SELECT State_ID, State_Name FROM states WHERE State_Name = %s", (file_info['State'],))
#             # state_data = cursor.fetchone()
#             cursor.callproc('GetStateByName', [file_info['State']])
#             for result in cursor.stored_results():
#                 state_data = result.fetchone()
#
#             if not state_data:
#                 errors.append(f"State '{file_info['State']}' is not valid. Please add it.")
#
#             # Validate District
#             if state_data:
#                 # cursor.execute(
#                 #     "SELECT District_ID, District_Name FROM districts WHERE District_Name = %s AND State_ID = %s",
#                 #     (file_info['District'], state_data['State_ID'])
#                 # )
#                 # district_data = cursor.fetchone()
#                 cursor.callproc('GetDistrictByNameAndStates', [file_info['District'], state_data['State_ID']])
#                 for result in cursor.stored_results():
#                     district_data = result.fetchone()
#
#                 if not district_data:
#                     errors.append(
#                         f"District '{file_info['District']}' is not valid under state '{file_info['State']}'.")
#
#             # Validate Block
#             if district_data:
#                 # cursor.execute(
#                 #     "SELECT Block_Id, Block_Name FROM blocks WHERE Block_Name = %s AND District_ID = %s",
#                 #     (file_info['Block'], district_data['District_ID'])
#                 # )
#                 # block_data = cursor.fetchone()
#                 cursor.callproc('GetBlockByNameAndDistricts', [file_info['Block'], district_data['District_ID']])
#                 for result in cursor.stored_results():
#                     block_data = result.fetchone()
#
#                 if not block_data:
#                     errors.append(
#                         f"Block '{file_info['Block']}' is not valid under district '{file_info['District']}'.")
#
#             # old code
#             # # Validate Subcontractor
#             # cursor.execute("SELECT Contractor_Id, Contractor_Name FROM SubContractors WHERE Contractor_Name = %s",
#             #                (file_info['Subcontractor'],))
#             # subcontractor_data = cursor.fetchone()
#             cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']])
#             for result in cursor.stored_results():
#                 subcontractor_data = result.fetchone()
#
#             if not subcontractor_data:
#                 # cursor.execute("INSERT INTO subcontractors (Contractor_Name) VALUES (%s)",
#                 #                (file_info['Subcontractor'],))
#                 # connection.commit()
#                 cursor.callproc('InsertSubcontractor', [file_info['Subcontractor']])
#                 connection.commit()
#
#                 # cursor.execute("SELECT Contractor_Id, Contractor_Name FROM SubContractors WHERE Contractor_Name = %s",
#                 #                (file_info['Subcontractor'],))
#                 # subcontractor_data = cursor.fetchone()
#                 cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']])
#                 for result in cursor.stored_results():
#                     subcontractor_data = result.fetchone()
#
#             # new code
#             # cursor.callproc('ValidateAndInsertSubcontractor', (file_info['Subcontractor'], 0, ''))
#             #
#             # for con in cursor.stored_results():
#             #     subcontractor_data = con.fetchone()
#             #     print("subcon:",subcontractor_data)
#             #
#             # print("subcontractor_data",subcontractor_data)
#
#             # Get hold types data from database (for faster lookup)
#             # cursor.execute("SELECT hold_type_id, hold_type FROM hold_types")
#             # hold_types_data = cursor.fetchall()
#
#             cursor.callproc("GetAllHoldTypes")
#             for ht in cursor.stored_results():
#                 hold_types_data = ht.fetchall()
#
#             hold_types_lookup = {row['hold_type'].lower(): row['hold_type_id'] for row in hold_types_data if
#                                  row['hold_type']}
#
#             cursor.close()
#         except mysql.connector.Error as e:
#             print(f"Database error: {e}")
#             return "Database operation failed", 500
#         finally:
#             connection.close()
#
#     # Extract dynamic variable names from row 5 and detect "hold" columns
#     variables = {}
#     hold_columns = []
#     hold_counter = 0
#
#     for j in range(1, sheet.max_column + 1):
#         col_value = sheet.cell(row=5, column=j).value
#         if col_value:
#             variables[col_value] = j  # Store column name with its position
#
#             # Check if the column header contains the word 'hold'
#             if 'hold' in str(col_value).lower():
#                 hold_counter += 1
#                 # Lookup hold type id from database
#                 hold_type_key = str(col_value).lower().strip()
#                 hold_type_id = hold_types_lookup.get(hold_type_key, None)
#                 hold_columns.append({
#                     'column_name': col_value,
#                     'column_number': j,
#                     'hold_type_id': hold_type_id
#                 })
#
#     # Extract data dynamically based on row numbers
#     for i in range(6, sheet.max_row + 1):
#         row_data = {}
#         if sheet.cell(row=i, column=1).value:
#             row_data["Row Number"] = i  # Store row number
#             for var_name, col_num in variables.items():
#                 row_data[var_name] = sheet.cell(row=i, column=col_num).value
#             # Check if at least 4 non-empty cells exist in the row
#             if sum(1 for value in row_data.values() if value) >= 4:
#                 data.append(row_data)
#
#     # For debugging or console output, you can print the hold columns info
#     for hold in hold_columns:
#         if hold['hold_type_id']:
#             print(
#                 f" if Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")
#         else:
#             errors.append(
#                 f"Hold Type not added ! Column name '{hold['column_name']}'.")
#             print(
#                 f" else Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")
#
#     return render_template(
#         'show_excel_file.html',
#         file_info=file_info,
#         variables=variables,
#         data=data,
#         subcontractor_data=subcontractor_data,
#         state_data=state_data,
#         district_data=district_data,
#         block_data=block_data,
#         errors=errors,
#         hold_columns=hold_columns,
#         hold_counter=hold_counter
#     )

@app.route('/show_table/<filename>')
def show_table(filename):
    global data
    data = []

    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    wb = openpyxl.load_workbook(filepath, data_only=True)
    sheet = wb.active

    file_info = {
        "Subcontractor": sheet.cell(row=1, column=2).value,
        "State": sheet.cell(row=2, column=2).value,
        "District": sheet.cell(row=3, column=2).value,
        "Block": sheet.cell(row=4, column=2).value,
    }

    errors = []
    subcontractor_data = None
    state_data = None
    district_data = None
    block_data = None

    connection = config.get_db_connection()
    if connection:
        try:
            cursor = connection.cursor(dictionary=True)

            print(f"Calling GetStateByName with: {file_info['State']}")
            cursor.callproc('GetStateByName', [file_info['State']])
            for result in cursor.stored_results():
                state_data = result.fetchone()

            if not state_data:
                errors.append(f"State '{file_info['State']}' is not valid. Please add it.")

            if state_data:
                print(f"Calling GetDistrictByNameAndStates with: {file_info['District']}, {state_data['State_ID']}")
                cursor.callproc('GetDistrictByNameAndStates', [file_info['District'], state_data['State_ID']])
                for result in cursor.stored_results():
                    district_data = result.fetchone()

                if not district_data:
                    errors.append(f"District '{file_info['District']}' is not valid under state '{file_info['State']}'.")

            if district_data:
                print(f"Calling GetBlockByNameAndDistricts with: {file_info['Block']}, {district_data['District_ID']}")
                cursor.callproc('GetBlockByNameAndDistricts', [file_info['Block'], district_data['District_ID']])
                for result in cursor.stored_results():
                    block_data = result.fetchone()

                if not block_data:
                    errors.append(f"Block '{file_info['Block']}' is not valid under district '{file_info['District']}'.")

            print(f"Calling GetSubcontractorByName with: {file_info['Subcontractor']}")
            cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']])
            for result in cursor.stored_results():
                subcontractor_data = result.fetchone()

            if not subcontractor_data:
                print(f"Inserting subcontractor: {file_info['Subcontractor']}")
                cursor.callproc('InsertSubcontractor', [file_info['Subcontractor']])
                connection.commit()
                print(f"Calling GetSubcontractorByName again with: {file_info['Subcontractor']}")
                cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']])
                for result in cursor.stored_results():
                    subcontractor_data = result.fetchone()

            print("Calling GetAllHoldTypes")
            cursor.callproc("GetAllHoldTypes")
            hold_types_data = []
            for ht in cursor.stored_results():
                hold_types_data = ht.fetchall()

            hold_types_lookup = {row['hold_type'].lower(): row['hold_type_id'] for row in hold_types_data if row['hold_type']}

            cursor.close()
        except mysql.connector.Error as e:
            print(f"Database error: {e}")
            return f"Database operation failed: {e}", 500
        finally:
            connection.close()

    variables = {}
    hold_columns = []
    hold_counter = 0

    for j in range(1, sheet.max_column + 1):
        col_value = sheet.cell(row=5, column=j).value
        if col_value:
            variables[col_value] = j
            if 'hold' in str(col_value).lower():
                hold_counter += 1
                hold_type_key = str(col_value).lower().strip()
                hold_type_id = hold_types_lookup.get(hold_type_key, None)
                hold_columns.append({
                    'column_name': col_value,
                    'column_number': j,
                    'hold_type_id': hold_type_id
                })

    for i in range(6, sheet.max_row + 1):
        row_data = {}
        if sheet.cell(row=i, column=1).value:
            row_data["Row Number"] = i
            for var_name, col_num in variables.items():
                row_data[var_name] = sheet.cell(row=i, column=col_num).value
            if sum(1 for value in row_data.values() if value) >= 4:
                data.append(row_data)

    for hold in hold_columns:
        if hold['hold_type_id']:
            print(f" if Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")
        else:
            errors.append(f"Hold Type not added ! Column name '{hold['column_name']}'.")
            print(f" else Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")

    return render_template(
        'show_excel_file.html',
        file_info=file_info,
        variables=variables,
        data=data,
        subcontractor_data=subcontractor_data,
        state_data=state_data,
        district_data=district_data,
        block_data=block_data,
        errors=errors,
        hold_columns=hold_columns,
        hold_counter=hold_counter
    )

# Show excel data in tables6
# @app.route('/show_table/<filename>')
# def show_table(filename):
#     global data
#     data = []
#
#     filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
#     wb = openpyxl.load_workbook(filepath, data_only=True)
#     sheet = wb.active
#
#     # Extract key file information from the first 4 rows
#     file_info = {
#         "Subcontractor": sheet.cell(row=1, column=2).value,
#         "State": sheet.cell(row=2, column=2).value,
#         "District": sheet.cell(row=3, column=2).value,
#         "Block": sheet.cell(row=4, column=2).value,
#     }
#
#     errors = []
#     subcontractor_data = None
#     state_data = None
#     district_data = None
#     block_data = None
#
#     # Database connection
#     connection = config.get_db_connection()
#     if connection:
#         try:
#             cursor = connection.cursor(dictionary=True)
#
#             # Validate State
#             cursor.execute("SELECT State_ID, State_Name FROM states WHERE State_Name = %s", (file_info['State'],))
#             state_data = cursor.fetchone()
#             if not state_data:
#                 errors.append(f"State '{file_info['State']}' is not valid. Please add it.")
#
#             # Validate District
#             if state_data:
#                 cursor.execute(
#                     "SELECT District_ID, District_Name FROM districts WHERE District_Name = %s AND State_ID = %s",
#                     (file_info['District'], state_data['State_ID'])
#                 )
#                 district_data = cursor.fetchone()
#                 if not district_data:
#                     errors.append(
#                         f"District '{file_info['District']}' is not valid under state '{file_info['State']}'.")
#
#             # Validate Block
#             if district_data:
#                 cursor.execute(
#                     "SELECT Block_Id, Block_Name FROM blocks WHERE Block_Name = %s AND District_ID = %s",
#                     (file_info['Block'], district_data['District_ID'])
#                 )
#                 block_data = cursor.fetchone()
#                 if not block_data:
#                     errors.append(
#                         f"Block '{file_info['Block']}' is not valid under district '{file_info['District']}'.")
#
#
#             # old code
#             # # Validate Subcontractor
#             cursor.execute("SELECT Contractor_Id, Contractor_Name FROM subcontractors WHERE Contractor_Name = %s",
#                            (file_info['Subcontractor'],))
#             subcontractor_data = cursor.fetchone()
#
#             if not subcontractor_data:
#                 cursor.execute("INSERT INTO subcontractors (Contractor_Name) VALUES (%s)",
#                                (file_info['Subcontractor'],))
#                 connection.commit()
#                 cursor.execute("SELECT Contractor_Id, Contractor_Name FROM subcontractors WHERE Contractor_Name = %s",
#                                (file_info['Subcontractor'],))
#                 subcontractor_data = cursor.fetchone()
#
#             # new code
#             # cursor.callproc('ValidateAndInsertSubcontractor', (file_info['Subcontractor'], 0, ''))
#             #
#             # for con in cursor.stored_results():
#             #     subcontractor_data = con.fetchone()
#             #     print("subcon:",subcontractor_data)
#             #
#             # print("subcontractor_data",subcontractor_data)
#
#             # Get hold types data from database (for faster lookup)
#             # cursor.execute("SELECT hold_type_id, hold_type FROM hold_types")
#             # hold_types_data = cursor.fetchall()
#
#             cursor.callproc("GetAllHoldTypes")
#             for ht in cursor.stored_results():
#                 hold_types_data = ht.fetchall()
#
#
#             hold_types_lookup = {row['hold_type'].lower(): row['hold_type_id'] for row in hold_types_data if row['hold_type']}
#
#
#             cursor.close()
#         except mysql.connector.Error as e:
#             print(f"Database error: {e}")
#
#            # return "Database operation failed", 500
#             return f"{e}",500
#         finally:
#             connection.close()
#
#     # Extract dynamic variable names from row 5 and detect "hold" columns
#     variables = {}
#     hold_columns = []
#     hold_counter = 0
#
#     for j in range(1, sheet.max_column + 1):
#         col_value = sheet.cell(row=5, column=j).value
#         if col_value:
#             variables[col_value] = j  # Store column name with its position
#
#             # Check if the column header contains the word 'hold'
#             if 'hold' in str(col_value).lower():
#                 hold_counter += 1
#                 # Lookup hold type id from database
#                 hold_type_key = str(col_value).lower().strip()
#                 hold_type_id = hold_types_lookup.get(hold_type_key, None)
#                 hold_columns.append({
#                     'column_name': col_value,
#                     'column_number': j,
#                     'hold_type_id': hold_type_id
#                 })
#
#     # Extract data dynamically based on row numbers
#     for i in range(6, sheet.max_row + 1):
#         row_data = {}
#         if sheet.cell(row=i, column=1).value:
#             row_data["Row Number"] = i  # Store row number
#             for var_name, col_num in variables.items():
#                 row_data[var_name] = sheet.cell(row=i, column=col_num).value
#             # Check if at least 4 non-empty cells exist in the row
#             if sum(1 for value in row_data.values() if value) >= 4:
#                 data.append(row_data)
#
#     # For debugging or console output, you can print the hold columns info
#     for hold in hold_columns:
#         if hold['hold_type_id']:
#             print(
#                 f" if Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")
#         else:
#             errors.append(
#                 f"Hold Type not added ! Column name '{hold['column_name']}'.")
#             print(
#                 f" else Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")
#
#     return render_template(
#         'show_excel_file.html',
#         file_info=file_info,
#         variables=variables,
#         data=data,
#         subcontractor_data=subcontractor_data,
#         state_data=state_data,
#         district_data=district_data,
#         block_data=block_data,
#         errors=errors,
#         hold_columns=hold_columns,
#         hold_counter=hold_counter
#     )

# save Excel data
@app.route('/save_data', methods=['POST'])
def save_data():
    # Extract form data
    subcontractor_id = request.form.get("subcontractor_data")
    state_id = request.form.get("state_data")
    district_id = request.form.get("district_data")
    block_id = request.form.get("block_data")

    variables = request.form.getlist('variables[]')
    hold_columns = request.form.get("hold_columns")
    hold_counter = request.form.get("hold_counter")

    # print("Info: ", subcontractor_id, state_id, district_id, block_id)

    if not data:
        return jsonify({"error": "No data provided to save"}), 400

    if data:
        # print("Total number of entries in data:", len(data))

        connection = config.get_db_connection()
        cursor = connection.cursor()

        try:
            for entry in data:
                save_data = {
                    "PMC_No": entry.get("PMC_No"),
                    "Invoice_Details": entry.get("Invoice_Details", ''),
                    "Work_Type": 'none',
                    "Invoice_Date": entry.get("Invoice_Date").strftime('%Y-%m-%d') if entry.get(
                        "Invoice_Date") else None,
                    "Invoice_No": entry.get("Invoice_No", ''),
                    "Basic_Amount": entry.get("Basic_Amount", 0.00),
                    "Debit_Amount": entry.get("Debit_Amount", 0.00),
                    "After_Debit_Amount": entry.get("After_Debit_Amount", 0.00),
                    "Amount": entry.get("Amount", 0.00),
                    "GST_Amount": entry.get("GST_Amount", 0.00),
                    "TDS_Amount": entry.get("TDS_Amount", 0.00),
                    "SD_Amount": entry.get("SD_Amount", 0.00),
                    "On_Commission": entry.get("On_Commission", 0.00),
                    "Hydro_Testing": entry.get("Hydro_Testing", 0.00),
                    "Hold_Amount": 0,
                    "GST_SD_Amount": entry.get("GST_SD_Amount", 0.00),
                    "Final_Amount": entry.get("Final_Amount", 0.00),
                    "Payment_Amount": entry.get("Payment_Amount", 0.00),
                    "Total_Amount": entry.get("Total_Amount", 0.00),
                    "TDS_Payment_Amount": entry.get("TDS_Payment_Amount", 0.00),
                    "UTR": entry.get("UTR", ''),
                }

                village_name, work_type = None, None
                village_id = 0

                PMC_No = save_data.get('PMC_No')
                Invoice_Details = save_data.get('Invoice_Details')
                Invoice_Date = save_data.get('Invoice_Date')
                Invoice_No = save_data.get('Invoice_No')
                Basic_Amount = save_data.get('Basic_Amount')
                Debit_Amount = save_data.get('Debit_Amount')
                After_Debit_Amount = save_data.get('After_Debit_Amount')
                Amount = save_data.get('Amount')
                GST_Amount = save_data.get('GST_Amount')
                TDS_Amount = save_data.get('TDS_Amount')
                SD_Amount = save_data.get('SD_Amount')
                On_Commission = save_data.get('On_Commission')
                Hydro_Testing = save_data.get('Hydro_Testing')
                GST_SD_Amount = save_data.get('GST_SD_Amount')
                Final_Amount = save_data.get('Final_Amount')

                Payment_Amount = save_data.get('Payment_Amount')
                Total_Amount = save_data.get('Total_Amount')
                TDS_Payment_Amount = save_data.get('TDS_Payment_Amount')
                UTR = save_data.get('UTR')

                if Invoice_Details:
                    words = Invoice_Details.lower().split()
                    if 'village' in words:
                        village_pos = words.index('village')
                        village_name = " ".join(words[:village_pos])
                    if 'work' in words:
                        work_pos = words.index('work')
                        if village_name:
                            work_type = " ".join(words[village_pos + 1:work_pos + 1])
                        else:
                            work_type = " ".join(words[:work_pos + 1])

                    if Invoice_Details and 'village' in Invoice_Details.lower() and 'work' in Invoice_Details.lower():
                        print("village_name ::", village_name, "|| work_type ::", work_type)
                        if block_id and village_name:
                            village_id = None
                            # cursor.execute("SELECT Village_Id FROM villages WHERE Block_Id = %s AND Village_Name = %s",(block_id, village_name))
                            # result = cursor.fetchone()
                            cursor.callproc("GetVillageId", (block_id, village_name))
                            for result in cursor.stored_results():
                                result = result.fetchone()
                            village_id = result[0] if result else None

                            if not village_id:
                                # cursor.execute("INSERT INTO villages (Village_Name, Block_Id) VALUES (%s, %s)", (village_name, block_id))

                                cursor.callproc("SaveVillage", (village_name, block_id))
                                # cursor.execute("SELECT Village_Id FROM villages WHERE Block_Id = %s AND Village_Name = %s",(block_id, village_name))
                                # result = cursor.fetchone()
                                cursor.callproc("GetVillageId", (block_id, village_name))
                                for result in cursor.stored_results():
                                    result = result.fetchone()
                                village_id = result[0] if result else None

                        print("village_id :", village_id)
                        print("block_id :", block_id)
                        print("invoice :", PMC_No, village_id, work_type, Invoice_Details, Invoice_Date, Invoice_No,
                              Basic_Amount, Debit_Amount, After_Debit_Amount, Amount, GST_Amount, TDS_Amount,
                              SD_Amount, On_Commission, Hydro_Testing, GST_SD_Amount, Final_Amount)
                        #
                        # cursor.execute("SET @p_invoice_id = 0")
                        # cursor.callproc("SaveInvoice", (
                        #     PMC_No, village_id, work_type, Invoice_Details, Invoice_Date, Invoice_No,
                        #     Basic_Amount, Debit_Amount, After_Debit_Amount, Amount, GST_Amount, TDS_Amount,
                        #     SD_Amount, On_Commission, Hydro_Testing, GST_SD_Amount, Final_Amount,
                        #     subcontractor_id, "@p_invoice_id"
                        # ))
                        # cursor.execute("SELECT @p_invoice_id")
                        # invoice_id = cursor.fetchone()[0]

                        args = (
                            PMC_No, village_id, work_type, Invoice_Details, Invoice_Date, Invoice_No,
                            Basic_Amount, Debit_Amount, After_Debit_Amount, Amount, GST_Amount, TDS_Amount,
                            SD_Amount, On_Commission, Hydro_Testing, GST_SD_Amount, Final_Amount,
                            subcontractor_id, 0
                        )
                        # for result in cursor.stored_results():
                        #      invoice_id = result.fetchone()['invoice_id']
                        results = cursor.callproc('SaveInvoice', args)
                        # cursor.callproc("SaveInvoice",args)
                        # for re in cursor.stored_results():
                        invoice_id = results[-1]

                        print("invoice id from the excel ", invoice_id)

                        if isinstance(hold_columns, str):
                            hold_columns = ast.literal_eval(hold_columns)

                            # Check if hold_columns is actually a list of dictionaries
                        if isinstance(hold_columns, list) and all(isinstance(hold, dict) for hold in hold_columns):
                            for hold in hold_columns:
                                print(f"Processing hold: {hold}")
                                hold_column_name = hold.get('column_name')  # Get column name
                                hold_type_id = hold.get('hold_type_id')  # Get hold_type_id

                                if hold_column_name:
                                    hold_amount = entry.get(
                                        hold_column_name)  # Get the value for that specific hold column
                                    if hold_amount is not None:
                                        print(f"Processing hold type: {hold_column_name}, Hold Amount: {hold_amount}")

                                        # Insert into the invoice_subcontractor_hold_join table
                                        hold_join_data = {
                                            "Contractor_Id": subcontractor_id,
                                            "Invoice_Id": invoice_id,
                                            "hold_type_id": hold_type_id,
                                            "hold_amount": hold_amount
                                        }

                                        # insert_hold_query = """INSERT INTO invoice_subcontractor_hold_join (Contractor_Id, Invoice_Id, hold_type_id, hold_amount)
                                        #                     VALUES (%(Contractor_Id)s, %(Invoice_Id)s, %(hold_type_id)s, %(hold_amount)s);
                                        #                 """
                                        # cursor.execute(insert_hold_query, hold_join_data)
                                        # print(f"Inserted hold join data: {hold_join_data}")
                                        cursor.callproc('InsertHoldJoinData', [
                                            hold_join_data['Contractor_Id'], hold_join_data['Invoice_Id'],
                                            hold_join_data['hold_type_id'], hold_join_data['hold_amount']
                                        ])
                                        connection.commit()
                                        print(f"Inserted hold join data: {hold_join_data}")


                                else:
                                    print(f"Invalid hold entry: {hold}")
                        else:
                            print("Hold columns data is not a valid list of dictionaries.")
#---------------------------------------------Credit Note---------------------------------------------------------------------------
                    elif any(keyword in Invoice_Details.lower() for keyword in ['credit note']):
                        print("Credit note found:", PMC_No, Invoice_No, Basic_Amount, Debit_Amount, Final_Amount,
                              After_Debit_Amount, GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR, Invoice_No)
                        cursor.execute(
                             """INSERT INTO credit_note (PMC_No, Invoice_Details, Basic_Amount, Debit_Amount, After_Debit_Amount, GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR, Contractor_Id, invoice_no) VALUES (%s,%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,%s)""",
                             ( PMC_No, Invoice_Details, Basic_Amount, Debit_Amount, After_Debit_Amount, GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR, subcontractor_id, Invoice_No))
#-----------------------------------------------Hold Amount----------------------------------------------------------------------
                    elif Invoice_Details and any(keyword in Invoice_Details.lower() for keyword in
                                                 ['excess hold', 'ht', 'hold release amount']):

                        cursor.execute("""
                                                INSERT INTO hold_release (
                                                    PMC_No, Invoice_No, Invoice_Details, Basic_Amount, Total_Amount, UTR,Contractor_Id
                                                ) VALUES (%s, %s, %s, %s, %s, %s,%s)
                                            """, (
                        PMC_No, Invoice_No, Invoice_Details, Basic_Amount, Final_Amount, UTR, subcontractor_id))
                        print("Hold amount release from excel file:", PMC_No, Invoice_Details)
  #------------------------------------------------------------------------------------------------------------------
                    elif Invoice_Details and any(
                            keyword in Invoice_Details.lower() for keyword in ['gst', 'release', 'note']):
                        print("Gst rels :", PMC_No, Invoice_No, Basic_Amount, Final_Amount)
                        cursor.callproc("SaveGSTRelease", (PMC_No, Invoice_No, Basic_Amount, Final_Amount))
                        # cursor.execute(
                        #     """INSERT INTO gst_release (PMC_No, Invoice_No, Basic_Amount, Final_Amount) VALUES (%s,%s, %s, %s)""",
                        #     (PMC_No, Invoice_No, Basic_Amount, Final_Amount))

                        # insert_payment = """INSERT INTO payment (PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR) VALUES (%s, %s, %s, %s, %s, %s)"""
                        # cursor.execute(insert_payment,
                        #                (PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR))

                if PMC_No and Total_Amount and UTR:
                    print("Payment :", PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR)
                    # insert_payment = """INSERT INTO payment (PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR) VALUES (%s, %s, %s, %s, %s, %s)"""
                    # cursor.execute(insert_payment,
                    #                (PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR))

                    cursor.callproc("SavePayment",
                                    (PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR))

            connection.commit()
            return jsonify({"success": "Data saved successfully!"}), 200
            # return render_template('uploadExcelFile.html')
        except Exception as e:
            connection.rollback()
            return jsonify({"error": f"An unexpected error occurred: {e}"}), 500
        finally:
            cursor.close()
            connection.close()

    return render_template('index.html')


# ---------------------- Report --------------------------------
# call repor page
@app.route('/report')
def report_page():
    return render_template('report.html')


# Search list multiples input and search reports
@app.route('/search_contractor', methods=['POST'])
def search_contractor():
    connection = config.get_db_connection()
    cursor = connection.cursor(dictionary=True)

    subcontractor_name = request.form.get('subcontractor_name')
    pmc_no = request.form.get('pmc_no')
    state = request.form.get('state')
    district = request.form.get('district')
    block = request.form.get('block')
    village = request.form.get('village')
    year_from = request.form.get('year_from')
    year_to = request.form.get('year_to')

    conditions = []
    params = []

    if subcontractor_name:
        conditions.append("LOWER(s.Contractor_Name) LIKE LOWER(%s)")
        params.append(f"%{subcontractor_name}%")
    if pmc_no:
        conditions.append("i.PMC_No = %s")
        params.append(pmc_no)
    if state:
        conditions.append("LOWER(st.State_Name) LIKE LOWER(%s)")
        params.append(f"%{state}%")
    if district:
        conditions.append("LOWER(d.District_Name) LIKE LOWER(%s)")
        params.append(f"%{district}%")
    if block:
        conditions.append("LOWER(b.Block_Name) LIKE LOWER(%s)")
        params.append(f"%{block}%")
    if village:
        conditions.append("LOWER(v.Village_Name) LIKE LOWER(%s)")
        params.append(f"%{village}%")
    if year_from and year_to:
        conditions.append("i.Invoice_Date BETWEEN %s AND %s")
        params.append(year_from)
        params.append(year_to)

    if not conditions:
        return jsonify({"error": "At least one field is required for search."}), 400

    # query = f"""
    #        SELECT DISTINCT s.Contractor_Id, s.Contractor_Name, i.PMC_No, st.State_Name,
    #                        d.District_Name, b.Block_Name, v.Village_Name
    #        FROM subcontractors s
    #        INNER JOIN assign_subcontractors asg ON s.Contractor_Id = asg.Contractor_Id
    #        INNER JOIN villages v ON asg.Village_Id = v.Village_Id
    #        INNER JOIN invoice i ON i.Village_Id = asg.Village_Id AND i.PMC_No = asg.PMC_No
    #        LEFT JOIN blocks b ON v.Block_Id = b.Block_Id
    #        LEFT JOIN districts d ON b.District_id = d.District_id
    #        LEFT JOIN states st ON d.State_Id = st.State_Id
    #        WHERE {' AND '.join(conditions)}
    #        ORDER BY s.Contractor_Name ASC, i.PMC_No ASC
    #    """
    # cursor.execute(query, tuple(params))
    # data = cursor.fetchall()
    cursor.callproc("search_contractor_info", [
        subcontractor_name or None,
        pmc_no or None,
        state or None,
        district or None,
        block or None,
        village or None,
        year_from or None,
        year_to or None
    ])

    for result in cursor.stored_results():
        data = result.fetchall()

    return jsonify(data)



# @app.route('/contractor_report/<int:contractor_id>')
# def contractor_report(contractor_id):
#     connection = config.get_db_connection()
#     cursor = connection.cursor(dictionary=True, buffered=True)
#
#     try:
#         # Fetch contractor details
#         cursor.execute("""
#             SELECT DISTINCT s.Contractor_Name, st.State_Name, d.District_Name, b.Block_Name,
#                    s.Mobile_No, s.GST_Registration_Type, s.GST_No,s.PAN_No,s.Email,s.Address
#             FROM subcontractors s
#             LEFT JOIN assign_subcontractors asg ON s.Contractor_Id = asg.Contractor_Id
#             LEFT JOIN villages v ON asg.Village_Id = v.Village_Id
#             LEFT JOIN blocks b ON v.Block_Id = b.Block_Id
#             LEFT JOIN districts d ON b.District_id = d.District_id
#             LEFT JOIN states st ON d.State_Id = st.State_Id
#             WHERE s.Contractor_Id = %s
#         """, (contractor_id,))
#         contInfo = cursor.fetchone()
#
#         # cursor.callproc('GetContractorInfoById', [contractor_id])
#         # contInfo = next(cursor.stored_results()).fetchone()
#
#         # Fetch distinct hold types present in invoices for the contractor
#         cursor.execute("""
#             SELECT DISTINCT ht.hold_type_id, ht.hold_type
#             FROM invoice_subcontractor_hold_join h
#             JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#             JOIN invoice i ON h.Invoice_Id = i.Invoice_Id
#             WHERE h.Contractor_Id = %s
#         """, (contractor_id,))
#         hold_types = cursor.fetchall()
#         # cursor.callproc('GetHoldTypesByContId', [contractor_id])
#         # hold_types = next(cursor.stored_results()).fetchall()
#
#         # Fetch all invoices for the contractor, with optional hold information
#         query = """
#             SELECT DISTINCT i.PMC_No, v.Village_Name, i.Work_Type, i.Invoice_Details,
#                    i.Invoice_Date, i.Invoice_No, i.Basic_Amount, i.Debit_Amount,
#                    i.After_Debit_Amount, i.Amount, i.GST_Amount, i.TDS_Amount, i.SD_Amount,
#                    i.On_Commission, i.Hydro_Testing, i.GST_SD_Amount,
#                    i.Final_Amount, h.hold_amount, ht.hold_type
#             FROM invoice i
#             LEFT JOIN villages v ON i.Village_Id = v.Village_Id
#             LEFT JOIN assign_subcontractors asg ON v.Village_Id = asg.Village_Id
#             LEFT JOIN subcontractors s ON asg.Contractor_Id = s.Contractor_Id
#             LEFT JOIN invoice_subcontractor_hold_join h ON i.Invoice_Id = h.Invoice_Id AND h.Contractor_Id = s.Contractor_Id
#             LEFT JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#             WHERE s.Contractor_Id = %s
#             ORDER BY i.PMC_No ASC;
#         """
#         cursor.execute(query, (contractor_id,))
#         invoices = cursor.fetchall()
#
#         # cursor.callproc('GetInvoicesWithHoldByContId', [contractor_id])
#         # invoices = next(cursor.stored_results()).fetchall()
#
#         gst_query = """select pmc_no,invoice_no,basic_amount,final_amount from gst_release where pmc_no in
#                         (select distinct pmc_no from assign_subcontractors where Contractor_Id= %s ) ORDER BY pmc_no ASC """
#         cursor.execute(gst_query, (contractor_id,))
#         gst_rel = cursor.fetchall()
#
#         # cursor.callproc('GetGstReleaseBycontId', [contractor_id])
#         # gst_rel = next(cursor.stored_results()).fetchall()
#
#         pay_query = """select  pmc_no,invoice_no,Payment_Amount,TDS_Payment_Amount,Total_amount,utr from payment where pmc_no in
#                         (select distinct pmc_no from assign_subcontractors where Contractor_Id=%s ) ORDER BY pmc_no ASC """
#         cursor.execute(pay_query, (contractor_id,))
#         payments = cursor.fetchall()
#         # cursor.callproc('GetPaymentsByContId', [contractor_id])
#         # payments = next(cursor.stored_results()).fetchall()
#
#         total = {
#             "sum_invo_basic_amt": float(sum(row['Basic_Amount'] or 0 for row in invoices)),
#             "sum_invo_debit_amt": float(sum(row['Debit_Amount'] or 0 for row in invoices)),
#             "sum_invo_after_debit_amt": float(sum(row['After_Debit_Amount'] or 0 for row in invoices)),
#             "sum_invo_amt": float(sum(row['Amount'] or 0 for row in invoices)),
#             "sum_invo_gst_amt": float(sum(row['GST_Amount'] or 0 for row in invoices)),
#             "sum_invo_tds_amt": float(sum(row['TDS_Amount'] or 0 for row in invoices)),
#             "sum_invo_ds_amt": float(sum(row['SD_Amount'] or 0 for row in invoices)),
#             "sum_invo_on_commission": float(sum(row['On_Commission'] or 0 for row in invoices)),
#             "sum_invo_hydro_test": float(sum(row['Hydro_Testing'] or 0 for row in invoices)),
#             "sum_invo_gst_sd_amt": float(sum(row['GST_SD_Amount'] or 0 for row in invoices)),
#             "sum_invo_final_amt": float(sum(row['Final_Amount'] or 0 for row in invoices)),
#             "sum_invo_hold_amt": float(sum(row['hold_amount'] or 0 for row in invoices)),
#
#             "sum_gst_basic_amt": float(sum(row['basic_amount'] or 0 for row in gst_rel)),
#             "sum_gst_final_amt": float(sum(row['final_amount'] or 0 for row in gst_rel)),
#
#             "sum_pay_payment_amt": float(sum(row['Payment_Amount'] or 0 for row in payments)),
#             "sum_pay_tds_payment_amt": float(sum(row['TDS_Payment_Amount'] or 0 for row in payments)),
#             "sum_pay_total_amt": float(sum(row['Total_amount'] or 0 for row in payments))
#         }
#         current_date = datetime.now().strftime('%Y-%m-%d')
#
#     except Exception as e:
#         print(f"Error fetching contractor report: {e}")
#         return "An error occurred while fetching contractor report", 500
#
#     finally:
#         cursor.close()
#         connection.close()
#
#     return render_template('subcontractor_report.html', contInfo=contInfo, contractor_id=contractor_id,
#                            invoices=invoices,
#                            hold_types=hold_types, gst_rel=gst_rel, payments=payments, total=total,
#                            current_date=current_date)

# @app.route('/contractor_report/<int:contractor_id>')
# def contractor_report(contractor_id):
#     connection = config.get_db_connection()
#     cursor = connection.cursor(dictionary=True, buffered=True)

#     try:
#         # Fetch contractor details
#         cursor.execute("""
#             SELECT DISTINCT s.Contractor_Name, st.State_Name, d.District_Name, b.Block_Name,
#                    s.Mobile_No, s.GST_Registration_Type, s.GST_No,s.PAN_No,s.Email,s.Address
#             FROM subcontractors s
#             LEFT JOIN assign_subcontractors asg ON s.Contractor_Id = asg.Contractor_Id
#             LEFT JOIN villages v ON asg.Village_Id = v.Village_Id
#             LEFT JOIN blocks b ON v.Block_Id = b.Block_Id
#             LEFT JOIN districts d ON b.District_id = d.District_id
#             LEFT JOIN states st ON d.State_Id = st.State_Id
#             WHERE s.Contractor_Id = %s
#         """, (contractor_id,))
#         contInfo = cursor.fetchone()

#         # # Fetch distinct hold types present in invoices for the contractor
#         cursor.execute("""
#             SELECT DISTINCT ht.hold_type_id, ht.hold_type
#             FROM invoice_subcontractor_hold_join h
#             JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#             JOIN invoice i ON h.Invoice_Id = i.Invoice_Id
#             WHERE h.Contractor_Id = %s
#         """, (contractor_id,))
#         hold_types = cursor.fetchall()
#         # cursor.callproc('GetDistinctHoldTypesInInvoicesByContractor', [contractor_id])

#         # for result in cursor.stored_results():
#         #     hold_types = result.fetchall()

#         # # Fetch all invoices for the contractor, with optional hold information

#         query = """
#                     SELECT DISTINCT i.PMC_No, v.Village_Name, i.Work_Type, i.Invoice_Details,
#                            i.Invoice_Date, i.Invoice_No, i.Basic_Amount, i.Debit_Amount,
#                            i.After_Debit_Amount, i.Amount, i.GST_Amount, i.TDS_Amount, i.SD_Amount,
#                            i.On_Commission, i.Hydro_Testing, i.GST_SD_Amount,
#                            i.Final_Amount, h.hold_amount, ht.hold_type
#                     FROM assign_subcontractors asg
#                     INNER JOIN villages v ON asg.Village_Id = v.Village_Id
#                     INNER JOIN invoice i ON i.Village_Id = v.Village_Id AND i.PMC_No = asg.PMC_No
#                     LEFT JOIN subcontractors s ON asg.Contractor_Id = s.Contractor_Id
#                     LEFT JOIN invoice_subcontractor_hold_join h ON i.Invoice_Id = h.Invoice_Id AND h.Contractor_Id = asg.Contractor_Id
#                     LEFT JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#                     WHERE asg.Contractor_Id = %s
#                     ORDER BY i.PMC_No ASC;
#                 """
#         cursor.execute(query, (contractor_id,))
#         invoices = cursor.fetchall()

#         # cursor.callproc("GetInvoicesWithHoldInfoByContractor", [contractor_id])
#         # for result in cursor.stored_results():
#         #     invoices = result.fetchall()

#         gst_query = """select pmc_no,invoice_no,basic_amount,final_amount from gst_release where pmc_no in
#                         (select distinct pmc_no from assign_subcontractors where Contractor_Id= %s ) ORDER BY pmc_no ASC """
#         cursor.execute(gst_query, (contractor_id,))
#         gst_rel = cursor.fetchall()
#         # cursor.callproc('GetGSTReleasesByContractor', [contractor_id])

#         # for result in cursor.stored_results():
#         #     gst_rel = result.fetchall()

#         pay_query = """select  pmc_no,invoice_no,Payment_Amount,TDS_Payment_Amount,Total_amount,utr from payment where pmc_no in
#                         (select distinct pmc_no from assign_subcontractors where Contractor_Id=%s ) ORDER BY pmc_no ASC """
#         cursor.execute(pay_query, (contractor_id,))
#         payments = cursor.fetchall()
#         # cursor.callproc('GetPaymentsByContractor', [contractor_id])

#         # for result in cursor.stored_results():
#         #     payments = result.fetchall()

#         total = {
#             "sum_invo_basic_amt": float(sum(row['Basic_Amount'] or 0 for row in invoices)),
#             "sum_invo_debit_amt": float(sum(row['Debit_Amount'] or 0 for row in invoices)),
#             "sum_invo_after_debit_amt": float(sum(row['After_Debit_Amount'] or 0 for row in invoices)),
#             "sum_invo_amt": float(sum(row['Amount'] or 0 for row in invoices)),
#             "sum_invo_gst_amt": float(sum(row['GST_Amount'] or 0 for row in invoices)),
#             "sum_invo_tds_amt": float(sum(row['TDS_Amount'] or 0 for row in invoices)),
#             "sum_invo_ds_amt": float(sum(row['SD_Amount'] or 0 for row in invoices)),
#             "sum_invo_on_commission": float(sum(row['On_Commission'] or 0 for row in invoices)),
#             "sum_invo_hydro_test": float(sum(row['Hydro_Testing'] or 0 for row in invoices)),
#             "sum_invo_gst_sd_amt": float(sum(row['GST_SD_Amount'] or 0 for row in invoices)),
#             "sum_invo_final_amt": float(sum(row['Final_Amount'] or 0 for row in invoices)),
#             "sum_invo_hold_amt": float(sum(row['hold_amount'] or 0 for row in invoices)),

#             "sum_gst_basic_amt": float(sum(row['basic_amount'] or 0 for row in gst_rel)),
#             "sum_gst_final_amt": float(sum(row['final_amount'] or 0 for row in gst_rel)),

#             "sum_pay_payment_amt": float(sum(row['Payment_Amount'] or 0 for row in payments)),
#             "sum_pay_tds_payment_amt": float(sum(row['TDS_Payment_Amount'] or 0 for row in payments)),
#             "sum_pay_total_amt": float(sum(row['Total_amount'] or 0 for row in payments))
#         }
#         current_date = datetime.now().strftime('%Y-%m-%d')

#     except Exception as e:
#         print(f"Error fetching contractor report: {e}")
#         return "An error occurred while fetching contractor report", 500

#     finally:
#         cursor.close()
#         connection.close()

#     return render_template('subcontractor_report.html', contInfo=contInfo, contractor_id=contractor_id,
#                            invoices=invoices,
#                            hold_types=hold_types, gst_rel=gst_rel, payments=payments, total=total,
#                            current_date=current_date)

from flask import render_template
from datetime import datetime
import config

@app.route('/contractor_report/<int:contractor_id>')
def contractor_report(contractor_id):
    connection = config.get_db_connection()
    cursor = connection.cursor(dictionary=True, buffered=True)

    try:
        # Contractor details
        cursor.execute("""
            SELECT DISTINCT s.Contractor_Name, st.State_Name, d.District_Name, b.Block_Name,
                   s.Mobile_No, s.GST_Registration_Type, s.GST_No, s.PAN_No, s.Email, s.Address
            FROM subcontractors s
            LEFT JOIN assign_subcontractors asg ON s.Contractor_Id = asg.Contractor_Id
            LEFT JOIN villages v ON asg.Village_Id = v.Village_Id
            LEFT JOIN blocks b ON v.Block_Id = b.Block_Id
            LEFT JOIN districts d ON b.District_id = d.District_id
            LEFT JOIN states st ON d.State_Id = st.State_Id
            WHERE s.Contractor_Id = %s
        """, (contractor_id,))
        contInfo = cursor.fetchone()

        # Hold types
        cursor.execute("""
            SELECT DISTINCT ht.hold_type_id, ht.hold_type
            FROM invoice_subcontractor_hold_join h
            JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
            JOIN invoice i ON h.Invoice_Id = i.Invoice_Id
            WHERE h.Contractor_Id = %s
        """, (contractor_id,))
        hold_types = cursor.fetchall()

        # Invoices
        cursor.execute("""
            SELECT DISTINCT i.PMC_No, v.Village_Name, i.Work_Type, i.Invoice_Details,
                   i.Invoice_Date, i.Invoice_No, i.Basic_Amount, i.Debit_Amount,
                   i.After_Debit_Amount, i.Amount, i.GST_Amount, i.TDS_Amount, i.SD_Amount,
                   i.On_Commission, i.Hydro_Testing, i.GST_SD_Amount,
                   i.Final_Amount, h.hold_amount, ht.hold_type
            FROM assign_subcontractors asg
            INNER JOIN villages v ON asg.Village_Id = v.Village_Id
            INNER JOIN invoice i ON i.Village_Id = v.Village_Id AND i.PMC_No = asg.PMC_No
            LEFT JOIN invoice_subcontractor_hold_join h ON i.Invoice_Id = h.Invoice_Id AND h.Contractor_Id = asg.Contractor_Id
            LEFT JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
            WHERE asg.Contractor_Id = %s
            ORDER BY i.PMC_No ASC
        """, (contractor_id,))
        invoices = cursor.fetchall()

        # GST Release
        cursor.execute("""
            SELECT gr.pmc_no, gr.invoice_no, gr.basic_amount, gr.final_amount
            FROM gst_release gr
            INNER JOIN (
                SELECT DISTINCT i.PMC_No, i.Invoice_No
                FROM invoice i
                JOIN assign_subcontractors a ON i.PMC_No = a.PMC_No AND i.Village_Id = a.Village_Id
                WHERE a.Contractor_Id = %s
            ) x ON gr.pmc_no = x.PMC_No AND gr.invoice_no = x.Invoice_No
            ORDER BY gr.pmc_no ASC
        """, (contractor_id,))
        gst_rel = cursor.fetchall()
        #Hold
        # Hold Release
        cursor.execute("SELECT * FROM hold_release WHERE Contractor_Id=%s", (contractor_id,))
        hold_release = cursor.fetchall()
        print(hold_release)

        #Credit Note

        cursor.execute("select * from credit_note where Contractor_Id=%s",(contractor_id,))
        credit_note=cursor.fetchall()
        print(credit_note)
        # Payments (include valid matches and payments with pmc_no but invoice_no is NULL)
        cursor.execute("""
            SELECT p.pmc_no, p.invoice_no, p.Payment_Amount, p.TDS_Payment_Amount, p.Total_amount, p.utr
            FROM payment p
            WHERE 
                EXISTS (
                    SELECT 1
                    FROM invoice i
                    JOIN assign_subcontractors a ON i.PMC_No = a.PMC_No AND i.Village_Id = a.Village_Id
                    WHERE a.Contractor_Id = %s
                    AND i.PMC_No = p.pmc_no AND i.Invoice_No = p.invoice_no
                )
                OR (
                    p.invoice_no IS NULL
                    AND EXISTS (
                        SELECT 1
                        FROM assign_subcontractors a
                        WHERE a.Contractor_Id = %s
                        AND a.PMC_No = p.pmc_no
                    )
                )
            ORDER BY p.pmc_no ASC
        """, (contractor_id, contractor_id))
        payments = cursor.fetchall()

        # Totals
        total = {
            "sum_invo_basic_amt": float(sum(row['Basic_Amount'] or 0 for row in invoices)),
            "sum_invo_debit_amt": float(sum(row['Debit_Amount'] or 0 for row in invoices)),
            "sum_invo_after_debit_amt": float(sum(row['After_Debit_Amount'] or 0 for row in invoices)),
            "sum_invo_amt": float(sum(row['Amount'] or 0 for row in invoices)),
            "sum_invo_gst_amt": float(sum(row['GST_Amount'] or 0 for row in invoices)),
            "sum_invo_tds_amt": float(sum(row['TDS_Amount'] or 0 for row in invoices)),
            "sum_invo_ds_amt": float(sum(row['SD_Amount'] or 0 for row in invoices)),
            "sum_invo_on_commission": float(sum(row['On_Commission'] or 0 for row in invoices)),
            "sum_invo_hydro_test": float(sum(row['Hydro_Testing'] or 0 for row in invoices)),
            "sum_invo_gst_sd_amt": float(sum(row['GST_SD_Amount'] or 0 for row in invoices)),
            "sum_invo_final_amt": float(sum(row['Final_Amount'] or 0 for row in invoices)),
            "sum_invo_hold_amt": float(sum(row['hold_amount'] or 0 for row in invoices)),

            "sum_gst_basic_amt": float(sum(row['basic_amount'] or 0 for row in gst_rel)),
            "sum_gst_final_amt": float(sum(row['final_amount'] or 0 for row in gst_rel)),

            "sum_pay_payment_amt": float(sum(row['Payment_Amount'] or 0 for row in payments)),
            "sum_pay_tds_payment_amt": float(sum(row['TDS_Payment_Amount'] or 0 for row in payments)),
            "sum_pay_total_amt": float(sum(row['Total_amount'] or 0 for row in payments))
        }

        current_date = datetime.now().strftime('%Y-%m-%d')

    except Exception as e:
        print(f"Error fetching contractor report: {e}")
        return "An error occurred while fetching contractor report", 500

    finally:
        cursor.close()
        connection.close()

    return render_template('subcontractor_report.html',
                           contInfo=contInfo,
                           contractor_id=contractor_id,
                           invoices=invoices,
                           hold_types=hold_types,
                           gst_rel=gst_rel,
                           payments=payments,
                           credit_note=credit_note,
                           hold_release=hold_release,
                           total=total,
                           current_date=current_date)






# -----------------------------download contractor report---------------------------------
# @app.route('/download_report/<int:contractor_id>')
# def download_report(contractor_id):
#     connection = config.get_db_connection()
#     cursor = connection.cursor(dictionary=True)
#
#     output_folder = "static/download"
#     output_file = os.path.join(output_folder, f"Contractor_Report_{contractor_id}.xlsx")
#
#     if not os.path.exists(output_folder):
#         os.makedirs(output_folder)
#
#     try:
#         # Fetch Contractor Details
#         # cursor.execute("""
#         #     SELECT DISTINCT s.Contractor_Name, st.State_Name, d.District_Name, b.Block_Name,
#         #                     s.Mobile_No, s.GST_Registration_Type, s.GST_No, s.PAN_No, s.Email, s.Address
#         #     FROM subcontractors s
#         #     LEFT JOIN assign_subcontractors asg ON s.Contractor_Id = asg.Contractor_Id
#         #     LEFT JOIN villages v ON asg.Village_Id = v.Village_Id
#         #     LEFT JOIN blocks b ON v.Block_Id = b.Block_Id
#         #     LEFT JOIN districts d ON b.District_id = d.District_id
#         #     LEFT JOIN states st ON d.State_Id = st.State_Id
#         #     WHERE s.Contractor_Id = %s
#         # """, (contractor_id,))
#         # contInfo = cursor.fetchone()
#         cursor.callproc('GetContractorInfoById', [contractor_id])
#         contInfo = next(cursor.stored_results()).fetchone()
#
#         if not contInfo:
#             return "No contractor found", 404
#
#         # Fetch distinct hold types present for the contractor
#         cursor.execute("""
#             SELECT DISTINCT ht.hold_type_id, ht.hold_type
#             FROM invoice_subcontractor_hold_join h
#             JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#             WHERE h.Contractor_Id = %s
#         """, (contractor_id,))
#         hold_types = cursor.fetchall()
#         hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
#
#         # Fetch Invoices & GST Releases
#         cursor.execute("""
#             SELECT DISTINCT i.Invoice_Id, i.PMC_No, v.Village_Name, i.Work_Type, i.Invoice_Details,
#                    i.Invoice_Date, i.Invoice_No, i.Basic_Amount, i.Debit_Amount,
#                    i.After_Debit_Amount, i.GST_Amount, i.Amount, i.TDS_Amount, i.SD_Amount,
#                    i.On_Commission, i.Hydro_Testing, i.GST_SD_Amount, i.Final_Amount,
#                    g.pmc_no AS gst_pmc_no, g.invoice_no AS gst_invoice_no,
#                    g.basic_amount AS gst_basic_amount, g.final_amount AS gst_final_amount
#             FROM invoice i
#             LEFT JOIN assign_subcontractors asg ON i.PMC_No = asg.PMC_No
#             LEFT JOIN villages v ON i.Village_Id = v.Village_Id
#             LEFT JOIN gst_release g ON i.PMC_No = g.pmc_no AND i.Invoice_No = g.invoice_no
#             WHERE asg.Contractor_Id = %s
#         """, (contractor_id,))
#         invoices = cursor.fetchall()
#
#         # Fetch Hold Amounts separately
#         cursor.execute("""
#             SELECT h.Invoice_Id, ht.hold_type_id, h.hold_amount
#             FROM invoice_subcontractor_hold_join h
#             JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#             WHERE h.Contractor_Id = %s
#         """, (contractor_id,))
#         hold_amounts = cursor.fetchall()
#
#         # Create a mapping of invoice_id to hold amounts by type
#         hold_data = {}
#         for h in hold_amounts:
#             hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
#
#         # Extract unique PMC numbers for payments
#         pmc_numbers = tuple(set(inv['PMC_No'] for inv in invoices if inv['PMC_No'] is not None))
#
#         # Fetch all Payments for the PMC numbers (including those with null invoice_no)
#         payments_map = {}
#         extra_payments_map = {}  # Now using a map to organize extra payments by PMC
#         if pmc_numbers:
#             # First get payments with invoice_no
#             query = f"""
#                 SELECT pmc_no, invoice_no, Payment_Amount, TDS_Payment_Amount, Total_amount, UTR
#                 FROM payment
#                 WHERE pmc_no IN ({','.join(['%s'] * len(pmc_numbers))})
#                 AND invoice_no IS NOT NULL
#                 ORDER BY pmc_no, invoice_no
#             """
#             cursor.execute(query, pmc_numbers)
#             payments = cursor.fetchall()
#
#             # Organize payments by PMC No & Invoice No
#             for pay in payments:
#                 key = (pay['pmc_no'], pay['invoice_no'])
#                 if key not in payments_map:
#                     payments_map[key] = []
#                 payments_map[key].append(pay)
#
#             # Then get extra payments (null invoice_no) and organize by PMC
#             query = f"""
#                 SELECT pmc_no, invoice_no, Payment_Amount, TDS_Payment_Amount, Total_amount, UTR
#                 FROM payment
#                 WHERE pmc_no IN ({','.join(['%s'] * len(pmc_numbers))})
#                 AND invoice_no IS NULL
#                 ORDER BY pmc_no
#             """
#             cursor.execute(query, pmc_numbers)
#             extra_payments = cursor.fetchall()
#
#             for pay in extra_payments:
#                 if pay['pmc_no'] not in extra_payments_map:
#                     extra_payments_map[pay['pmc_no']] = []
#                 extra_payments_map[pay['pmc_no']].append(pay)
#
#         # Create Excel workbook
#         workbook = openpyxl.Workbook()
#         sheet = workbook.active
#         sheet.title = "Contractor Report"
#
#         # Write Contractor Details
#         sheet.append(["", "", "Laxmi Civil Engineering Services PVT. LTD.", "", ""])
#         sheet.append(
#             ["Contractor Name", contInfo["Contractor_Name"], " ", "GST No", contInfo["GST_No"], " ", "GST Type",
#              contInfo["GST_Registration_Type"]])
#         sheet.append(
#             ["State", contInfo["State_Name"], " ", "PAN No", contInfo["PAN_No"], " ", "Address", contInfo["Address"]])
#         sheet.append(["District", contInfo["District_Name"], " ", "Mobile No", contInfo["Mobile_No"]])
#         sheet.append(["Block", contInfo["Block_Name"], " ", "Email", contInfo["Email"]])
#         sheet.append([])
#
#         # Table Headers - include all hold types as separate columns
#         base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
#                         "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
#                         "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]
#
#         hold_headers = [ht['hold_type'] for ht in hold_types]
#
#         payment_headers = ["Final Amount", "Payment Amount", "TDS Payment", "Total Paid", "UTR"]
#
#         sheet.append(base_headers + hold_headers + payment_headers)
#
#         seen_invoices = set()
#         seen_gst_notes = set()
#         processed_payments = set()  # Track which payments we've processed
#
#         # Process invoices grouped by PMC No
#         pmc_groups = {}
#         for inv in invoices:
#             pmc_no = inv["PMC_No"]
#             if pmc_no not in pmc_groups:
#                 pmc_groups[pmc_no] = []
#             pmc_groups[pmc_no].append(inv)
#
#         # Process each PMC group separately
#         for pmc_no, pmc_invoices in pmc_groups.items():
#             # Process all invoices for this PMC first
#             for inv in pmc_invoices:
#                 invoice_no = inv["Invoice_No"]
#                 payments = payments_map.get((pmc_no, invoice_no), [])
#
#                 # Process invoice row with first payment (if exists)
#                 if (pmc_no, invoice_no) not in seen_invoices:
#                     seen_invoices.add((pmc_no, invoice_no))
#                     first_payment = payments[0] if len(payments) > 0 else None
#
#                     # Base invoice data
#                     row = [
#                         pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
#                         inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
#                         inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
#                         inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
#                     ]
#
#                     # Add hold amounts for each hold type
#                     invoice_holds = hold_data.get(inv["Invoice_Id"], {})
#                     for ht_id in hold_type_map.keys():
#                         row.append(invoice_holds.get(ht_id, ""))
#
#                     # Add payment information
#                     row += [
#                         inv["Final_Amount"],
#                         first_payment["Payment_Amount"] if first_payment else "",
#                         first_payment["TDS_Payment_Amount"] if first_payment else "",
#                         first_payment["Total_amount"] if first_payment else "",
#                         first_payment["UTR"] if first_payment else ""
#                     ]
#
#                     sheet.append(row)
#
#                     if first_payment:
#                         payment_id = f"{pmc_no}-{invoice_no}-{first_payment['Payment_Amount']}-{first_payment.get('UTR', '')}"
#                         processed_payments.add(payment_id)
#
#                 # Process GST release if exists (only if we have a matching GST record)
#                 if inv["gst_pmc_no"] and (inv["gst_pmc_no"], inv["gst_invoice_no"]) not in seen_gst_notes:
#                     seen_gst_notes.add((inv["gst_pmc_no"], inv["gst_invoice_no"]))
#
#                     # Find the payment that matches this GST release
#                     gst_payment = None
#                     for payment in payments[1:]:  # Skip first payment (already used for invoice)
#                         if payment['invoice_no'] == inv["gst_invoice_no"]:
#                             gst_payment = payment
#                             break
#
#                     # GST release row
#                     row = [
#                         pmc_no, "", "", "GST Release Note", "", inv["gst_invoice_no"],
#                         inv["gst_basic_amount"], "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                     ]
#
#                     # Empty holds for GST release
#                     row += ["" for _ in hold_headers]
#
#                     # Add payment information
#                     row += [
#                         inv["gst_final_amount"],
#                         gst_payment["Payment_Amount"] if gst_payment else "",
#                         gst_payment["TDS_Payment_Amount"] if gst_payment else "",
#                         gst_payment["Total_amount"] if gst_payment else "",
#                         gst_payment["UTR"] if gst_payment else ""
#                     ]
#
#                     sheet.append(row)
#
#                     if gst_payment:
#                         payment_id = f"{pmc_no}-{inv['gst_invoice_no']}-{gst_payment['Payment_Amount']}-{gst_payment.get('UTR', '')}"
#                         processed_payments.add(payment_id)
#
#                 # Process remaining payments as extra payments
#                 for payment in payments[1:]:
#                     payment_id = f"{pmc_no}-{payment['invoice_no']}-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                     if payment_id not in processed_payments:
#                         row = [
#                             pmc_no, "", "", "", "", payment['invoice_no'],
#                             "", "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                         ]
#
#                         # Empty holds for extra payments
#                         row += ["" for _ in hold_headers]
#
#                         # Add payment information
#                         row += [
#                             "",
#                             payment["Payment_Amount"],
#                             payment["TDS_Payment_Amount"],
#                             payment["Total_amount"],
#                             payment["UTR"]
#                         ]
#
#                         sheet.append(row)
#                         processed_payments.add(payment_id)
#
#             # Process extra payments for this PMC (null invoice_no) immediately after the PMC's invoices
#             if pmc_no in extra_payments_map:
#                 for payment in extra_payments_map[pmc_no]:
#                     payment_id = f"{pmc_no}-null-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                     if payment_id not in processed_payments:
#                         row = [
#                             pmc_no, "", "", "", "", "",
#                             "", "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                         ]
#
#                         # Empty holds for null invoice payments
#                         row += ["" for _ in hold_headers]
#
#                         # Add payment information
#                         row += [
#                             "",
#                             payment["Payment_Amount"],
#                             payment["TDS_Payment_Amount"],
#                             payment["Total_amount"],
#                             payment["UTR"]
#                         ]
#
#                         sheet.append(row)
#                         processed_payments.add(payment_id)
#
#         workbook.save(output_file)
#         workbook.close()
#     finally:
#         cursor.close()
#         connection.close()
#     from openpyxl.styles import Font
#
#     # Initialize totals
#     total_basic_amount = 0
#     total_tds_amount = 0
#     total_sd_amount = 0
#     total_on_commission = 0
#     total_hold_amount = 0
#     total_final_amount = 0
#     total_total_amount = 0  # Sum of all Total Amounts from payment data
#
#     # Iterate through rows to calculate totals
#     for row in sheet.iter_rows(min_row=2, max_row=sheet.max_row, values_only=True):
#         try:
#             total_basic_amount += float(row[6] or 0)  # Basic_Amount
#             total_tds_amount += float(row[11] or 0)  # TDS_Amount
#             total_sd_amount += float(row[12] or 0)  # SD_Amount
#             total_on_commission += float(row[13] or 0)  # On_Commission
#             total_final_amount += float(row[-5] or 0)  # Final_Amount
#             total_total_amount += float(row[-2] or 0)  # Total_Amount (from payments)
#
#             # Sum of hold amounts (dynamically calculated based on hold_headers)
#             hold_start_col = len(base_headers)  # First column where hold types start
#             hold_end_col = hold_start_col + len(hold_headers)  # Last column where hold types end
#             total_hold_amount += sum(float(row[i] or 0) for i in range(hold_start_col, hold_end_col))
#
#         except ValueError:
#             continue  # Skip if non-numeric values cause errors
#
#     # Append the totals row at the bottom of the sheet
#     totals_row = [
#         "TOTAL", "", "", "", "", "",  # Empty values for non-numeric columns
#         total_basic_amount, "", "", "", "", total_tds_amount, total_sd_amount,
#         total_on_commission, "", "",  # Empty GST SD Amount
#     ]
#
#     # Add empty placeholders for hold types
#     totals_row += [total_hold_amount] + [""] * (len(hold_headers) - 1)
#
#     # Add totals for Final Amount and Total Amount
#     totals_row += [
#         total_final_amount, "", "", total_total_amount, ""  # UTR column remains empty
#     ]
#
#     # Append totals row to sheet
#     sheet.append([])
#     sheet.append(totals_row)
#
#     # Make the totals row bold
#     for cell in sheet[sheet.max_row]:
#         cell.font = Font(bold=True)
#
#     sheet.append([])
#     sheet.append([])
#     sheet.append([])
#     sheet.append([])
#     sheet.append(["This is Generated by LCEPL Application.."])
#
#     # Save workbook after modifications
#     workbook.save(output_file)
#     workbook.close()
#
#     return send_from_directory(output_folder, f"Contractor_Report_{contractor_id}.xlsx", as_attachment=True)
from openpyxl import Workbook

# # Download report by contractor id
# # Download report by contractor id
from flask import send_from_directory

import config


# -----------------------PMC Report---------------------------
# @app.route('/download_report/<int:contractor_id>')
# def download_report(contractor_id):
#     connection = config.get_db_connection()
#     cursor = connection.cursor(dictionary=True)

#     output_folder = "static/download"
#     output_file = os.path.join(output_folder, f"Contractor_Report_{contractor_id}.xlsx")

#     if not os.path.exists(output_folder):
#         os.makedirs(output_folder)

#     try:
#         # Fetch Contractor Details
#         cursor.execute("""
#             SELECT DISTINCT s.Contractor_Name, st.State_Name, d.District_Name, b.Block_Name,
#                             s.Mobile_No, s.GST_Registration_Type, s.GST_No, s.PAN_No, s.Email, s.Address
#             FROM subcontractors s
#             LEFT JOIN assign_subcontractors asg ON s.Contractor_Id = asg.Contractor_Id
#             LEFT JOIN villages v ON asg.Village_Id = v.Village_Id
#             LEFT JOIN blocks b ON v.Block_Id = b.Block_Id
#             LEFT JOIN districts d ON b.District_id = d.District_id
#             LEFT JOIN states st ON d.State_Id = st.State_Id
#             WHERE s.Contractor_Id = %s
#         """, (contractor_id,))
#         contInfo = cursor.fetchone()

#         if not contInfo:
#             return "No contractor found", 404

#         # # Fetch distinct hold types present for the contractor
#         # cursor.execute("""
#         #     SELECT DISTINCT ht.hold_type_id, ht.hold_type
#         #     FROM invoice_subcontractor_hold_join h
#         #     JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#         #     WHERE h.Contractor_Id = %s
#         # """, (contractor_id,))
#         # hold_types = cursor.fetchall()
#         cursor.callproc('GetDistinctHoldTypesByContractor', [contractor_id])

#         for result in cursor.stored_results():
#             hold_types = result.fetchall()

#         hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}

#         # # Fetch Invoices & GST Releases
#         # cursor.execute("""
#         #     SELECT DISTINCT i.Invoice_Id, i.PMC_No, v.Village_Name, i.Work_Type, i.Invoice_Details,
#         #            i.Invoice_Date, i.Invoice_No, i.Basic_Amount, i.Debit_Amount,
#         #            i.After_Debit_Amount, i.GST_Amount, i.Amount, i.TDS_Amount, i.SD_Amount,
#         #            i.On_Commission, i.Hydro_Testing, i.GST_SD_Amount, i.Final_Amount,
#         #            g.pmc_no AS gst_pmc_no, g.invoice_no AS gst_invoice_no,
#         #            g.basic_amount AS gst_basic_amount, g.final_amount AS gst_final_amount
#         #     FROM invoice i
#         #     LEFT JOIN assign_subcontractors asg ON i.PMC_No = asg.PMC_No
#         #     LEFT JOIN villages v ON i.Village_Id = v.Village_Id
#         #     LEFT JOIN gst_release g ON i.PMC_No = g.pmc_no AND i.Invoice_No = g.invoice_no
#         #     WHERE asg.Contractor_Id = %s
#         # """, (contractor_id,))
#         # invoices = cursor.fetchall()
#         cursor.callproc('GetInvoicesAndGSTReleasesByContractor', [contractor_id])

#         for result in cursor.stored_results():
#             invoices = result.fetchall()

#         # # Fetch Hold Amounts separately
#         # cursor.execute("""
#         #     SELECT h.Invoice_Id, ht.hold_type_id, h.hold_amount
#         #     FROM invoice_subcontractor_hold_join h
#         #     JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#         #     WHERE h.Contractor_Id = %s
#         # """, (contractor_id,))
#         # hold_amounts = cursor.fetchall()
#         cursor.callproc('GetHoldAmountsByContractors', [contractor_id])

#         for result in cursor.stored_results():
#             hold_amounts = result.fetchall()

#         # Create a mapping of invoice_id to hold amounts by type
#         hold_data = {}
#         for h in hold_amounts:
#             hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']

#         # Extract unique PMC numbers for payments
#         pmc_numbers = tuple(set(inv['PMC_No'] for inv in invoices if inv['PMC_No'] is not None))

#         # Fetch all Payments for the PMC numbers (including those with null invoice_no)
#         payments_map = {}
#         extra_payments_map = {}  # Now using a map to organize extra payments by PMC
#         if pmc_numbers:
#             # First get payments with invoice_no
#             query = f"""
#                 SELECT pmc_no, invoice_no, Payment_Amount, TDS_Payment_Amount, Total_amount, UTR
#                 FROM payment
#                 WHERE pmc_no IN ({','.join(['%s'] * len(pmc_numbers))})
#                 AND invoice_no IS NOT NULL
#                 ORDER BY pmc_no, invoice_no
#             """
#             cursor.execute(query, pmc_numbers)
#             payments = cursor.fetchall()

#             # Organize payments by PMC No & Invoice No
#             for pay in payments:
#                 key = (pay['pmc_no'], pay['invoice_no'])
#                 if key not in payments_map:
#                     payments_map[key] = []
#                 payments_map[key].append(pay)

#             # Then get extra payments (null invoice_no) and organize by PMC
#             query = f"""
#                 SELECT pmc_no, invoice_no, Payment_Amount, TDS_Payment_Amount, Total_amount, UTR
#                 FROM payment
#                 WHERE pmc_no IN ({','.join(['%s'] * len(pmc_numbers))})
#                 AND invoice_no IS NULL
#                 ORDER BY pmc_no
#             """
#             cursor.execute(query, pmc_numbers)
#             extra_payments = cursor.fetchall()

#             for pay in extra_payments:
#                 if pay['pmc_no'] not in extra_payments_map:
#                     extra_payments_map[pay['pmc_no']] = []
#                 extra_payments_map[pay['pmc_no']].append(pay)

#         # Create Excel workbook
#         workbook = openpyxl.Workbook()
#         sheet = workbook.active
#         sheet.title = "Contractor Report"

#         # Write Contractor Details
#         sheet.append(["Contractor Name", contInfo["Contractor_Name"]])
#         sheet.append(["State", contInfo["State_Name"]])
#         sheet.append(["District", contInfo["District_Name"]])
#         sheet.append(["Block", contInfo["Block_Name"]])
#         sheet.append(["Mobile No", contInfo["Mobile_No"]])
#         sheet.append(["GST Type", contInfo["GST_Registration_Type"]])
#         sheet.append(["GST No", contInfo["GST_No"]])
#         sheet.append(["PAN No", contInfo["PAN_No"]])
#         sheet.append(["Email", contInfo["Email"]])
#         sheet.append(["Address", contInfo["Address"]])
#         sheet.append([])

#         # Table Headers - include all hold types as separate columns
#         base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
#                         "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
#                         "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]

#         hold_headers = [ht['hold_type'] for ht in hold_types]

#         payment_headers = ["Final Amount", "Payment Amount", "TDS Payment", "Total Paid", "UTR"]

#         sheet.append(base_headers + hold_headers + payment_headers)

#         seen_invoices = set()
#         seen_gst_notes = set()
#         processed_payments = set()  # Track which payments we've processed

#         # Process invoices grouped by PMC No
#         pmc_groups = {}
#         for inv in invoices:
#             pmc_no = inv["PMC_No"]
#             if pmc_no not in pmc_groups:
#                 pmc_groups[pmc_no] = []
#             pmc_groups[pmc_no].append(inv)

#         # Process each PMC group separately
#         for pmc_no, pmc_invoices in pmc_groups.items():
#             # Process all invoices for this PMC first
#             for inv in pmc_invoices:
#                 invoice_no = inv["Invoice_No"]
#                 payments = payments_map.get((pmc_no, invoice_no), [])

#                 # Process invoice row with first payment (if exists)
#                 if (pmc_no, invoice_no) not in seen_invoices:
#                     seen_invoices.add((pmc_no, invoice_no))
#                     first_payment = payments[0] if len(payments) > 0 else None

#                     # Base invoice data
#                     row = [
#                         pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
#                         inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
#                         inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
#                         inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
#                     ]

#                     # Add hold amounts for each hold type
#                     invoice_holds = hold_data.get(inv["Invoice_Id"], {})
#                     for ht_id in hold_type_map.keys():
#                         row.append(invoice_holds.get(ht_id, ""))

#                     # Add payment information
#                     row += [
#                         inv["Final_Amount"],
#                         first_payment["Payment_Amount"] if first_payment else "",
#                         first_payment["TDS_Payment_Amount"] if first_payment else "",
#                         first_payment["Total_amount"] if first_payment else "",
#                         first_payment["UTR"] if first_payment else ""
#                     ]

#                     sheet.append(row)

#                     if first_payment:
#                         payment_id = f"{pmc_no}-{invoice_no}-{first_payment['Payment_Amount']}-{first_payment.get('UTR', '')}"
#                         processed_payments.add(payment_id)

#                 # Process GST release if exists (only if we have a matching GST record)
#                 if inv["gst_pmc_no"] and (inv["gst_pmc_no"], inv["gst_invoice_no"]) not in seen_gst_notes:
#                     seen_gst_notes.add((inv["gst_pmc_no"], inv["gst_invoice_no"]))

#                     # Find the payment that matches this GST release
#                     gst_payment = None
#                     for payment in payments[1:]:  # Skip first payment (already used for invoice)
#                         if payment['invoice_no'] == inv["gst_invoice_no"]:
#                             gst_payment = payment
#                             break

#                     # GST release row
#                     row = [
#                         pmc_no, "", "", "GST Release Note", "", inv["gst_invoice_no"],
#                         inv["gst_basic_amount"], "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                     ]

#                     # Empty holds for GST release
#                     row += ["" for _ in hold_headers]

#                     # Add payment information
#                     row += [
#                         inv["gst_final_amount"],
#                         gst_payment["Payment_Amount"] if gst_payment else "",
#                         gst_payment["TDS_Payment_Amount"] if gst_payment else "",
#                         gst_payment["Total_amount"] if gst_payment else "",
#                         gst_payment["UTR"] if gst_payment else ""
#                     ]

#                     sheet.append(row)

#                     if gst_payment:
#                         payment_id = f"{pmc_no}-{inv['gst_invoice_no']}-{gst_payment['Payment_Amount']}-{gst_payment.get('UTR', '')}"
#                         processed_payments.add(payment_id)

#                 # Process remaining payments as extra payments
#                 for payment in payments[1:]:
#                     payment_id = f"{pmc_no}-{payment['invoice_no']}-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                     if payment_id not in processed_payments:
#                         row = [
#                             pmc_no, "", "", "", "", payment['invoice_no'],
#                             "", "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                         ]

#                         # Empty holds for extra payments
#                         row += ["" for _ in hold_headers]

#                         # Add payment information
#                         row += [
#                             "",
#                             payment["Payment_Amount"],
#                             payment["TDS_Payment_Amount"],
#                             payment["Total_amount"],
#                             payment["UTR"]
#                         ]

#                         sheet.append(row)
#                         processed_payments.add(payment_id)

#             # Process extra payments for this PMC (null invoice_no) immediately after the PMC's invoices
#             if pmc_no in extra_payments_map:
#                 for payment in extra_payments_map[pmc_no]:
#                     payment_id = f"{pmc_no}-null-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                     if payment_id not in processed_payments:
#                         row = [
#                             pmc_no, "", "", "", "", "",
#                             "", "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                         ]

#                         # Empty holds for null invoice payments
#                         row += ["" for _ in hold_headers]

#                         # Add payment information
#                         row += [
#                             "",
#                             payment["Payment_Amount"],
#                             payment["TDS_Payment_Amount"],
#                             payment["Total_amount"],
#                             payment["UTR"]
#                         ]


#                         sheet.append(row)
#                         processed_payments.add(payment_id)

#         workbook.save(output_file)
#         workbook.close()
#     finally:
#         cursor.close()
#         connection.close()
#     from openpyxl.styles import Font

#     # Initialize totals
#     total_basic_amount = 0
#     total_tds_amount = 0
#     total_sd_amount = 0
#     total_on_commission = 0
#     total_hold_amount = 0
#     total_final_amount = 0
#     total_total_amount = 0  # Sum of all Total Amounts from payment data

#     # Iterate through rows to calculate totals
#     for row in sheet.iter_rows(min_row=2, max_row=sheet.max_row, values_only=True):
#         try:
#             total_basic_amount += float(row[6] or 0)  # Basic_Amount
#             total_tds_amount += float(row[11] or 0)  # TDS_Amount
#             total_sd_amount += float(row[12] or 0)  # SD_Amount
#             total_on_commission += float(row[13] or 0)  # On_Commission
#             total_final_amount += float(row[-5] or 0)  # Final_Amount
#             total_total_amount += float(row[-2] or 0)  # Total_Amount (from payments)

#             # Sum of hold amounts (dynamically calculated based on hold_headers)
#             hold_start_col = len(base_headers)  # First column where hold types start
#             hold_end_col = hold_start_col + len(hold_headers)  # Last column where hold types end
#             total_hold_amount += sum(float(row[i] or 0) for i in range(hold_start_col, hold_end_col))

#         except ValueError:
#             continue  # Skip if non-numeric values cause errors

#     # Append the totals row at the bottom of the sheet
#     totals_row = [
#         "TOTAL", "", "", "", "", "",  # Empty values for non-numeric columns
#         total_basic_amount, "", "", "", "", total_tds_amount, total_sd_amount,
#         total_on_commission, "", "",  # Empty GST SD Amount
#     ]

#     # Add empty placeholders for hold types
#     totals_row += [total_hold_amount] + [""] * (len(hold_headers) - 1)

#     # Add totals for Final Amount and Total Amount
#     totals_row += [
#         total_final_amount, "", "", total_total_amount, ""  # UTR column remains empty
#     ]

#     # Append totals row to sheet
#     sheet.append([])
#     sheet.append(totals_row)

#     # Make the totals row bold
#     for cell in sheet[sheet.max_row]:
#         cell.font = Font(bold=True)

#     # Save workbook after modifications
#     workbook.save(output_file)
#     workbook.close()

#     return send_from_directory(output_folder, f"Contractor_Report_{contractor_id}.xlsx", as_attachment=True)

# @app.route('/download_report/<int:contractor_id>')
# def download_report(contractor_id):
#     connection = config.get_db_connection()
#     cursor = connection.cursor(dictionary=True)
#
#     output_folder = "static/download"
#     output_file = os.path.join(output_folder, f"Contractor_Report_{contractor_id}.xlsx")
#
#     if not os.path.exists(output_folder):
#         os.makedirs(output_folder)
#
#     try:
#         # Fetch Contractor Details
#         # cursor.execute("""
#         #     SELECT DISTINCT s.Contractor_Name, st.State_Name, d.District_Name, b.Block_Name,
#         #                     s.Mobile_No, s.GST_Registration_Type, s.GST_No, s.PAN_No, s.Email, s.Address
#         #     FROM subcontractors s
#         #     LEFT JOIN assign_subcontractors asg ON s.Contractor_Id = asg.Contractor_Id
#         #     LEFT JOIN villages v ON asg.Village_Id = v.Village_Id
#         #     LEFT JOIN blocks b ON v.Block_Id = b.Block_Id
#         #     LEFT JOIN districts d ON b.District_id = d.District_id
#         #     LEFT JOIN states st ON d.State_Id = st.State_Id
#         #     WHERE s.Contractor_Id = %s
#         # """, (contractor_id,))
#         # contInfo = cursor.fetchone()
#         cursor.callproc('GetContractorInfoId', [contractor_id])
#         for result in cursor.stored_results():
#             contInfo = result.fetchone()
#
#         if not contInfo:
#             return "No contractor found", 404
#
#         # # Fetch distinct hold types present for the contractor
#         # cursor.execute("""
#         #     SELECT DISTINCT ht.hold_type_id, ht.hold_type
#         #     FROM invoice_subcontractor_hold_join h
#         #     JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#         #     WHERE h.Contractor_Id = %s
#         # """, (contractor_id,))
#         # hold_types = cursor.fetchall()
#         cursor.callproc('GetDistinctHoldTypesByContractor', [contractor_id])
#
#         for result in cursor.stored_results():
#             hold_types = result.fetchall()
#
#         hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
#
#         # # Fetch Invoices & GST Releases
#         # cursor.execute("""
#         #     SELECT DISTINCT i.Invoice_Id, i.PMC_No, v.Village_Name, i.Work_Type, i.Invoice_Details,
#         #            i.Invoice_Date, i.Invoice_No, i.Basic_Amount, i.Debit_Amount,
#         #            i.After_Debit_Amount, i.GST_Amount, i.Amount, i.TDS_Amount, i.SD_Amount,
#         #            i.On_Commission, i.Hydro_Testing, i.GST_SD_Amount, i.Final_Amount,
#         #            g.pmc_no AS gst_pmc_no, g.invoice_no AS gst_invoice_no,
#         #            g.basic_amount AS gst_basic_amount, g.final_amount AS gst_final_amount
#         #     FROM invoice i
#         #     LEFT JOIN assign_subcontractors asg ON i.PMC_No = asg.PMC_No
#         #     LEFT JOIN villages v ON i.Village_Id = v.Village_Id
#         #     LEFT JOIN gst_release g ON i.PMC_No = g.pmc_no AND i.Invoice_No = g.invoice_no
#         #     WHERE asg.Contractor_Id = %s
#         # """, (contractor_id,))
#         # invoices = cursor.fetchall()
#         cursor.callproc('GetInvoicesAndGSTReleasesByContractor', [contractor_id])
#
#         for result in cursor.stored_results():
#             invoices = result.fetchall()
#
#         # # Fetch Hold Amounts separately
#         # cursor.execute("""
#         #     SELECT h.Invoice_Id, ht.hold_type_id, h.hold_amount
#         #     FROM invoice_subcontractor_hold_join h
#         #     JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#         #     WHERE h.Contractor_Id = %s
#         # """, (contractor_id,))
#         # hold_amounts = cursor.fetchall()
#         cursor.callproc('GetHoldAmountsByContractors', [contractor_id])
#
#         for result in cursor.stored_results():
#             hold_amounts = result.fetchall()
#
#         # Create a mapping of invoice_id to hold amounts by type
#         hold_data = {}
#         for h in hold_amounts:
#             hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
#
#         # Extract unique PMC numbers for payments
#         pmc_numbers = tuple(set(inv['PMC_No'] for inv in invoices if inv['PMC_No'] is not None))
#
#         # Fetch all Payments for the PMC numbers (including those with null invoice_no)
#         payments_map = {}
#         extra_payments_map = {}  # Now using a map to organize extra payments by PMC
#         if pmc_numbers:
#             # # First get payments with invoice_no
#             # query = f"""
#             #     SELECT pmc_no, invoice_no, Payment_Amount, TDS_Payment_Amount, Total_amount, UTR
#             #     FROM payment
#             #     WHERE pmc_no IN ({','.join(['%s'] * len(pmc_numbers))})
#             #     AND invoice_no IS NOT NULL
#             #     ORDER BY pmc_no, invoice_no
#             # """
#             # cursor.execute(query, pmc_numbers)
#             # payments = cursor.fetchall()
#
#             pmc_list_str = ','.join(str(pmc) for pmc in pmc_numbers)
#             # e.g. 'PMC001,PMC002,PMC003'
#             cursor.callproc('GetPaymentsByPmcNosWithInvoice', [pmc_list_str])
#
#             for result in cursor.stored_results():
#                 payments = result.fetchall()
#
#             print("payments by procedure:", payments)
#             # Organize payments by PMC No & Invoice No
#             for pay in payments:
#                 key = (pay['pmc_no'], pay['invoice_no'])
#                 if key not in payments_map:
#                     payments_map[key] = []
#                 payments_map[key].append(pay)
#
#             # Then get extra payments (null invoice_no) and organize by PMC
#             # query = f"""
#             #     SELECT pmc_no, invoice_no, Payment_Amount, TDS_Payment_Amount, Total_amount, UTR
#             #     FROM payment
#             #     WHERE pmc_no IN ({','.join(['%s'] * len(pmc_numbers))})
#             #     AND invoice_no IS NULL
#             #     ORDER BY pmc_no
#             # """
#             # cursor.execute(query, pmc_numbers)
#             # extra_payments = cursor.fetchall()
#             pmc_list_str = ','.join(str(pmc) for pmc in pmc_numbers)
#             cursor.callproc('GetExtraPaymentsByPmcNos', [pmc_list_str])
#
#             for result in cursor.stored_results():
#                 extra_payments = result.fetchall()
#             print(
#                 "-----------------------------------------------------------------------------------------Extra Payment---------------------------------------------------------------")
#             print("Extra payment from produre", extra_payments)
#
#             for pay in extra_payments:
#                 if pay['pmc_no'] not in extra_payments_map:
#                     extra_payments_map[pay['pmc_no']] = []
#                 extra_payments_map[pay['pmc_no']].append(pay)
#             print(
#                 "-----------------------------------------------------------------------------------------Extra Payment MAP --------------------------------------------------------------")
#             print("Extra payment MAp from produre", extra_payments_map)
#         # Create Excel workbook
#         workbook = openpyxl.Workbook()
#         sheet = workbook.active
#         sheet.title = "Contractor Report"
#
#         # Write Contractor Details
#         sheet.append(["Contractor Name", contInfo["Contractor_Name"]])
#         sheet.append(["State", contInfo["State_Name"]])
#         sheet.append(["District", contInfo["District_Name"]])
#         sheet.append(["Block", contInfo["Block_Name"]])
#         sheet.append(["Mobile No", contInfo["Mobile_No"]])
#         sheet.append(["GST Type", contInfo["GST_Registration_Type"]])
#         sheet.append(["GST No", contInfo["GST_No"]])
#         sheet.append(["PAN No", contInfo["PAN_No"]])
#         sheet.append(["Email", contInfo["Email"]])
#         sheet.append(["Address", contInfo["Address"]])
#         sheet.append([])
#
#         # Table Headers - include all hold types as separate columns
#         base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
#                         "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
#                         "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]
#
#         hold_headers = [ht['hold_type'] for ht in hold_types]
#
#         payment_headers = ["Final Amount", "Payment Amount", "TDS Payment", "Total Paid", "UTR"]
#
#         sheet.append(base_headers + hold_headers + payment_headers)
#
#         seen_invoices = set()
#         seen_gst_notes = set()
#         processed_payments = set()  # Track which payments we've processed
#
#         # Process invoices grouped by PMC No
#         pmc_groups = {}
#         for inv in invoices:
#             pmc_no = inv["PMC_No"]
#             if pmc_no not in pmc_groups:
#                 pmc_groups[pmc_no] = []
#             pmc_groups[pmc_no].append(inv)
#
#         # Process each PMC group separately
#         for pmc_no, pmc_invoices in pmc_groups.items():
#             # Process all invoices for this PMC first
#             for inv in pmc_invoices:
#                 invoice_no = inv["Invoice_No"]
#                 payments = payments_map.get((pmc_no, invoice_no), [])
#
#                 # Process invoice row with first payment (if exists)
#                 if (pmc_no, invoice_no) not in seen_invoices:
#                     seen_invoices.add((pmc_no, invoice_no))
#                     first_payment = payments[0] if len(payments) > 0 else None
#
#                     # Base invoice data
#                     row = [
#                         pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
#                         inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
#                         inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
#                         inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
#                     ]
#
#                     # Add hold amounts for each hold type
#                     invoice_holds = hold_data.get(inv["Invoice_Id"], {})
#                     for ht_id in hold_type_map.keys():
#                         row.append(invoice_holds.get(ht_id, ""))
#
#                     # Add payment information
#                     row += [
#                         inv["Final_Amount"],
#                         first_payment["Payment_Amount"] if first_payment else "",
#                         first_payment["TDS_Payment_Amount"] if first_payment else "",
#                         first_payment["Total_amount"] if first_payment else "",
#                         first_payment["UTR"] if first_payment else ""
#                     ]
#
#                     sheet.append(row)
#
#                     if first_payment:
#                         payment_id = f"{pmc_no}-{invoice_no}-{first_payment['Payment_Amount']}-{first_payment.get('UTR', '')}"
#                         processed_payments.add(payment_id)
#
#                 # Process GST release if exists (only if we have a matching GST record)
#                 if inv["gst_pmc_no"] and (inv["gst_pmc_no"], inv["gst_invoice_no"]) not in seen_gst_notes:
#                     seen_gst_notes.add((inv["gst_pmc_no"], inv["gst_invoice_no"]))
#
#                     # Find the payment that matches this GST release
#                     gst_payment = None
#                     for payment in payments[1:]:  # Skip first payment (already used for invoice)
#                         if payment['invoice_no'] == inv["gst_invoice_no"]:
#                             gst_payment = payment
#                             break
#
#                     # GST release row
#                     row = [
#                         pmc_no, "", "", "GST Release Note", "", inv["gst_invoice_no"],
#                         inv["gst_basic_amount"], "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                     ]
#
#                     # Empty holds for GST release
#                     row += ["" for _ in hold_headers]
#
#                     # Add payment information
#                     row += [
#                         inv["gst_final_amount"],
#                         gst_payment["Payment_Amount"] if gst_payment else "",
#                         gst_payment["TDS_Payment_Amount"] if gst_payment else "",
#                         gst_payment["Total_amount"] if gst_payment else "",
#                         gst_payment["UTR"] if gst_payment else ""
#                     ]
#
#                     sheet.append(row)
#
#                     if gst_payment:
#                         payment_id = f"{pmc_no}-{inv['gst_invoice_no']}-{gst_payment['Payment_Amount']}-{gst_payment.get('UTR', '')}"
#                         processed_payments.add(payment_id)
#
#                 # Process remaining payments as extra payments
#                 for payment in payments[1:]:
#                     payment_id = f"{pmc_no}-{payment['invoice_no']}-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                     if payment_id not in processed_payments:
#                         row = [
#                             pmc_no, "", "", "", "", payment['invoice_no'],
#                             "", "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                         ]
#
#                         # Empty holds for extra payments
#                         row += ["" for _ in hold_headers]
#
#                         # Add payment information
#                         row += [
#                             "",
#                             payment["Payment_Amount"],
#                             payment["TDS_Payment_Amount"],
#                             payment["Total_amount"],
#                             payment["UTR"]
#                         ]
#
#                         sheet.append(row)
#                         processed_payments.add(payment_id)
#
#             # Process extra payments for this PMC (null invoice_no) immediately after the PMC's invoices
#             if pmc_no in extra_payments_map:
#                 for payment in extra_payments_map[pmc_no]:
#                     payment_id = f"{pmc_no}-null-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                     if payment_id not in processed_payments:
#                         row = [
#                             pmc_no, "", "", "", "", "",
#                             "", "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                         ]
#
#                         # Empty holds for null invoice payments
#                         row += ["" for _ in hold_headers]
#
#                         # Add payment information
#                         row += [
#                             "",
#                             payment["Payment_Amount"],
#                             payment["TDS_Payment_Amount"],
#                             payment["Total_amount"],
#                             payment["UTR"]
#                         ]
#
#                         sheet.append(row)
#                         processed_payments.add(payment_id)
#
#         workbook.save(output_file)
#         workbook.close()
#     finally:
#         cursor.close()
#         connection.close()
#     from openpyxl.styles import Font
#
#     # Initialize totals
#     total_basic_amount = 0
#     total_tds_amount = 0
#     total_sd_amount = 0
#     total_on_commission = 0
#     total_hold_amount = 0
#     total_final_amount = 0
#     total_total_amount = 0  # Sum of all Total Amounts from payment data
#
#     # Iterate through rows to calculate totals
#     for row in sheet.iter_rows(min_row=2, max_row=sheet.max_row, values_only=True):
#         try:
#             total_basic_amount += float(row[6] or 0)  # Basic_Amount
#             total_tds_amount += float(row[11] or 0)  # TDS_Amount
#             total_sd_amount += float(row[12] or 0)  # SD_Amount
#             total_on_commission += float(row[13] or 0)  # On_Commission
#             total_final_amount += float(row[-5] or 0)  # Final_Amount
#             total_total_amount += float(row[-2] or 0)  # Total_Amount (from payments)
#
#             # Sum of hold amounts (dynamically calculated based on hold_headers)
#             hold_start_col = len(base_headers)  # First column where hold types start
#             hold_end_col = hold_start_col + len(hold_headers)  # Last column where hold types end
#             total_hold_amount += sum(float(row[i] or 0) for i in range(hold_start_col, hold_end_col))
#
#         except ValueError:
#             continue  # Skip if non-numeric values cause errors
#
#     # Append the totals row at the bottom of the sheet
#     totals_row = [
#         "TOTAL", "", "", "", "", "",  # Empty values for non-numeric columns
#         total_basic_amount, "", "", "", "", total_tds_amount, total_sd_amount,
#         total_on_commission, "", "",  # Empty GST SD Amount
#     ]
#     if hold_headers:
#         totals_row += [total_hold_amount] + [""] * (len(hold_headers) - 1)
#
#     # Add empty placeholders for hold types
#     # totals_row += [total_hold_amount] + [""] * (len(hold_headers) - 1)
#
#     # Add totals for Final Amount and Total Amount
#     totals_row += [
#         total_final_amount, "", "", total_total_amount, ""  # UTR column remains empty
#     ]
#
#     # Append totals row to sheet
#     sheet.append([])
#     sheet.append(totals_row)
#     total_hold_amount=Decimal('0.00')
#     for d in invoices:
#         total_hold_amount = total_hold_amount + d.get('SD_Amount', Decimal('0.00')) + d.get('On_Commission',
#                                                                                                   Decimal(
#                                                                                                       '0.00')) + d.get(
#             'Hydro_Testing', Decimal('0.00'))
#     #new coded for summary chart added
#     for data in hold_amounts:
#         total_hold_amount = total_hold_amount + data.get('hold_amount', Decimal('0.00'))
#     print("Total Hold Amount after adding the hold amount ", total_hold_amount)
#
#
#     from datetime import datetime
#
#     # Add payment information
#
#     # Get today's date with weekday
#     today_date = datetime.today().strftime('%A, %Y-%m-%d')
#
#     # Add headers (optional)
#     sheet.append(["Contractor Name", contInfo["Contractor_Name"]])
#     sheet.append(["Date", today_date])
#     sheet.append(["Description", "Amount"])
#
#     # Add your values
#     sheet.append(["Advance/Surplus", str( total_final_amount - total_total_amount)])
#     sheet.append(["Total Hold Amount", str(total_hold_amount)])
#     sheet.append(["Amount With TDS", str(total_tds_amount)])
#     #new added code end here for summary chart
#
#     # Make the totals row bold
#     for cell in sheet[sheet.max_row]:
#         cell.font = Font(bold=True)
#
#     # Save workbook after modifications
#     workbook.save(output_file)
#     workbook.close()
#
#     return send_from_directory(output_folder, f"Contractor_Report_{contractor_id}.xlsx", as_attachment=True)

from flask import send_from_directory
import os
import openpyxl
from openpyxl.styles import Font, PatternFill
from decimal import Decimal
from datetime import datetime

from flask import send_from_directory
import os
import openpyxl
from openpyxl.styles import Font, PatternFill
from decimal import Decimal
from datetime import datetime

# @app.route('/download_report/<int:contractor_id>')
# def download_report(contractor_id):
#     connection = config.get_db_connection()
#     cursor = connection.cursor(dictionary=True)

#     output_folder = "static/download"
#     output_file = os.path.join(output_folder, f"Contractor_Report_{contractor_id}.xlsx")

#     if not os.path.exists(output_folder):
#         os.makedirs(output_folder)

#     try:
#         # Fetch Contractor Info
#         cursor.callproc('GetContractorInfoId', [contractor_id])
#         for result in cursor.stored_results():
#             contInfo = result.fetchone()
#         if not contInfo:
#             return "No contractor found", 404

#         # Fetch Hold Types
#         cursor.callproc('GetDistinctHoldTypesByContractor', [contractor_id])
#         for result in cursor.stored_results():
#             hold_types = result.fetchall()
#         hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}

#         # Fetch Invoices
#         cursor.callproc('GetInvoicesAndGSTReleasesByContractor', [contractor_id])
#         for result in cursor.stored_results():
#             invoices = result.fetchall()

#         # Fetch Hold Amounts
#         cursor.callproc('GetHoldAmountsByContractors', [contractor_id])
#         for result in cursor.stored_results():
#             hold_amounts = result.fetchall()
#         hold_data = {}
#         for h in hold_amounts:
#             hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']

#         # Extract PMC numbers
#         pmc_numbers = tuple(set(inv['PMC_No'] for inv in invoices if inv['PMC_No'] is not None))

#         payments_map = {}
#         extra_payments_map = {}
#         if pmc_numbers:
#             pmc_list_str = ','.join(str(pmc) for pmc in pmc_numbers)
#             cursor.callproc('GetPaymentsByPmcNosWithInvoice', [pmc_list_str])
#             for result in cursor.stored_results():
#                 payments = result.fetchall()
#             for pay in payments:
#                 key = (pay['pmc_no'], pay['invoice_no'])
#                 payments_map.setdefault(key, []).append(pay)

#             cursor.callproc('GetExtraPaymentsByPmcNos', [pmc_list_str])
#             for result in cursor.stored_results():
#                 extra_payments = result.fetchall()
#             for pay in extra_payments:
#                 extra_payments_map.setdefault(pay['pmc_no'], []).append(pay)

#         # Create Excel Workbook
#         workbook = openpyxl.Workbook()
#         sheet = workbook.active
#         sheet.title = "Contractor Report"

#         # Write Contractor Info
#         sheet.append(["Contractor Name", contInfo["Contractor_Name"]])
#         sheet.append(["State", contInfo["State_Name"]])
#         sheet.append(["District", contInfo["District_Name"]])
#         sheet.append(["Block", contInfo["Block_Name"]])
#         sheet.append(["Mobile No", contInfo["Mobile_No"]])
#         sheet.append(["GST Type", contInfo["GST_Registration_Type"]])
#         sheet.append(["GST No", contInfo["GST_No"]])
#         sheet.append(["PAN No", contInfo["PAN_No"]])
#         sheet.append(["Email", contInfo["Email"]])
#         sheet.append(["Address", contInfo["Address"]])
#         sheet.append([])

#         # Table Headers
#         base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
#                         "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
#                         "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]
#         hold_headers = [ht['hold_type'] for ht in hold_types]
#         payment_headers = ["Final Amount", "Payment Amount", "TDS Payment", "Total Paid", "UTR"]
#         all_headers = base_headers + hold_headers + payment_headers

#         sheet.append(all_headers)

#         # Style the headers
#         header_fill = PatternFill(start_color="ADD8E6", end_color="ADD8E6", fill_type="solid")
#         header_font = Font(bold=True)
#         for cell in sheet[sheet.max_row]:
#             cell.font = header_font
#             cell.fill = header_fill

#         seen_invoices = set()
#         seen_gst_notes = set()
#         processed_payments = set()
#         pmc_groups = {}
#         for inv in invoices:
#             pmc_groups.setdefault(inv["PMC_No"], []).append(inv)

#         for pmc_no, pmc_invoices in pmc_groups.items():
#             for inv in pmc_invoices:
#                 invoice_no = inv["Invoice_No"]
#                 payments = payments_map.get((pmc_no, invoice_no), [])

#                 if (pmc_no, invoice_no) not in seen_invoices:
#                     seen_invoices.add((pmc_no, invoice_no))
#                     first_payment = payments[0] if payments else None
#                     row = [
#                         pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
#                         inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
#                         inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
#                         inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
#                     ]
#                     invoice_holds = hold_data.get(inv["Invoice_Id"], {})
#                     for ht_id in hold_type_map:
#                         row.append(invoice_holds.get(ht_id, ""))

#                     row += [
#                         inv["Final_Amount"],
#                         first_payment["Payment_Amount"] if first_payment else "",
#                         first_payment["TDS_Payment_Amount"] if first_payment else "",
#                         first_payment["Total_amount"] if first_payment else "",
#                         first_payment["UTR"] if first_payment else ""
#                     ]
#                     sheet.append(row)
#                     if first_payment:
#                         payment_id = f"{pmc_no}-{invoice_no}-{first_payment['Payment_Amount']}-{first_payment.get('UTR', '')}"
#                         processed_payments.add(payment_id)

#                 # GST Notes
#                 if inv["gst_pmc_no"] and (inv["gst_pmc_no"], inv["gst_invoice_no"]) not in seen_gst_notes:
#                     seen_gst_notes.add((inv["gst_pmc_no"], inv["gst_invoice_no"]))
#                     gst_payment = None
#                     for payment in payments[1:]:
#                         if payment['invoice_no'] == inv["gst_invoice_no"]:
#                             gst_payment = payment
#                             break
#                     row = [pmc_no, "", "", "GST Release Note", "", inv["gst_invoice_no"],
#                            inv["gst_basic_amount"], "", "", "", "", "", "", "", "", ""]
#                     row += ["" for _ in hold_headers]
#                     row += [
#                         inv["gst_final_amount"],
#                         gst_payment["Payment_Amount"] if gst_payment else "",
#                         gst_payment["TDS_Payment_Amount"] if gst_payment else "",
#                         gst_payment["Total_amount"] if gst_payment else "",
#                         gst_payment["UTR"] if gst_payment else ""
#                     ]
#                     sheet.append(row)
#                     if gst_payment:
#                         payment_id = f"{pmc_no}-{inv['gst_invoice_no']}-{gst_payment['Payment_Amount']}-{gst_payment.get('UTR', '')}"
#                         processed_payments.add(payment_id)

#                 for payment in payments[1:]:
#                     payment_id = f"{pmc_no}-{payment['invoice_no']}-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                     if payment_id not in processed_payments:
#                         row = [pmc_no, "", "", "", "", payment['invoice_no'], "", "", "", "", "", "", "", "", "", ""]
#                         row += ["" for _ in hold_headers]
#                         row += [
#                             "",
#                             payment["Payment_Amount"],
#                             payment["TDS_Payment_Amount"],
#                             payment["Total_amount"],
#                             payment["UTR"]
#                         ]
#                         sheet.append(row)
#                         processed_payments.add(payment_id)

#             # Extra payments (null invoice)
#             for payment in extra_payments_map.get(pmc_no, []):
#                 payment_id = f"{pmc_no}-null-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                 if payment_id not in processed_payments:
#                     row = [pmc_no, "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]
#                     row += ["" for _ in hold_headers]
#                     row += [
#                         "",
#                         payment["Payment_Amount"],
#                         payment["TDS_Payment_Amount"],
#                         payment["Total_amount"],
#                         payment["UTR"]
#                     ]
#                     sheet.append(row)
#                     processed_payments.add(payment_id)

#         # Totals Calculation
#         total_basic_amount = total_tds_amount = total_sd_amount = total_on_commission = 0
#         total_hold_amount = total_final_amount = total_total_amount = 0

#         for row in sheet.iter_rows(min_row=13, max_row=sheet.max_row, values_only=True):
#             try:
#                 total_basic_amount += float(row[6] or 0)
#                 total_tds_amount += float(row[11] or 0)
#                 total_sd_amount += float(row[12] or 0)
#                 total_on_commission += float(row[13] or 0)
#                 total_final_amount += float(row[-5] or 0)
#                 total_total_amount += float(row[-2] or 0)
#                 hold_start_col = len(base_headers)
#                 hold_end_col = hold_start_col + len(hold_headers)
#                 total_hold_amount += sum(float(row[i] or 0) for i in range(hold_start_col, hold_end_col))
#             except ValueError:
#                 continue

#         # Totals Row
#         sheet.append([])
#         totals_row = [
#             "TOTAL", "", "", "", "", "", total_basic_amount, "", "", "", "", total_tds_amount,
#             total_sd_amount, total_on_commission, "", ""
#         ]
#         if total_hold_amount:
#             totals_row += [total_hold_amount] + [""] * (len(hold_headers) - 1)

#         totals_row += [total_final_amount, "", "", total_total_amount, ""]
#         sheet.append(totals_row)

#         for cell in sheet[sheet.max_row]:
#             cell.font = Font(bold=True)

#         # Summary Section
#         today_date = datetime.today().strftime('%A, %Y-%m-%d')
#         sheet.append([])
#         sheet.append(["Contractor Name", contInfo["Contractor_Name"]])
#         sheet.append(["Date", today_date])
#         sheet.append(["Description", "Amount"])
#         sheet.append(["Advance/Surplus", str(total_final_amount - total_total_amount)])
#         sheet.append(["Total Hold Amount", str(Decimal(total_hold_amount))])
#         sheet.append(["Amount With TDS", str(total_tds_amount)])

#         # Auto adjust column widths
#         for col in sheet.columns:
#             max_length = 0
#             col_letter = openpyxl.utils.get_column_letter(col[0].column)
#             for cell in col:
#                 try:
#                     if cell.value:
#                         max_length = max(max_length, len(str(cell.value)))
#                 except:
#                     pass
#             sheet.column_dimensions[col_letter].width = max_length + 2

#         workbook.save(output_file)
#         workbook.close()

#     finally:
#         cursor.close()
#         connection.close()

#     return send_from_directory(output_folder, f"Contractor_Report_{contractor_id}.xlsx", as_attachment=True)
from flask import send_from_directory
from decimal import Decimal
from datetime import datetime
import os
import openpyxl
from openpyxl.styles import Font, PatternFill
import config


# @app.route('/download_report/<int:contractor_id>')
# def download_report(contractor_id):
#     connection = config.get_db_connection()
#     cursor = connection.cursor(dictionary=True, buffered=True)
#
#     output_folder = "static/download"
#     output_file = os.path.join(output_folder, f"Contractor_Report_{contractor_id}.xlsx")
#     os.makedirs(output_folder, exist_ok=True)
#
#     try:
#         # Contractor Info
#         cursor.execute("""
#             SELECT DISTINCT s.Contractor_Name, st.State_Name, d.District_Name, b.Block_Name,
#                    s.Mobile_No, s.GST_Registration_Type, s.GST_No, s.PAN_No, s.Email, s.Address
#             FROM subcontractors s
#             LEFT JOIN assign_subcontractors asg ON s.Contractor_Id = asg.Contractor_Id
#             LEFT JOIN villages v ON asg.Village_Id = v.Village_Id
#             LEFT JOIN blocks b ON v.Block_Id = b.Block_Id
#             LEFT JOIN districts d ON b.District_id = d.District_id
#             LEFT JOIN states st ON d.State_Id = st.State_Id
#             WHERE s.Contractor_Id = %s
#         """, (contractor_id,))
#         contInfo = cursor.fetchone()
#         if not contInfo:
#             return "No contractor found", 404
#
#         # Hold Types
#         cursor.execute("""
#             SELECT DISTINCT ht.hold_type_id, ht.hold_type
#             FROM invoice_subcontractor_hold_join h
#             JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#             JOIN invoice i ON h.Invoice_Id = i.Invoice_Id
#             WHERE h.Contractor_Id = %s
#         """, (contractor_id,))
#         hold_types = cursor.fetchall()
#         hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
#
#         # Invoices
#         cursor.execute("""
#             SELECT DISTINCT i.PMC_No, v.Village_Name, i.Work_Type, i.Invoice_Details,
#                    i.Invoice_Date, i.Invoice_No, i.Basic_Amount, i.Debit_Amount,
#                    i.After_Debit_Amount, i.GST_Amount, i.Amount, i.TDS_Amount, i.SD_Amount,
#                    i.On_Commission, i.Hydro_Testing, i.GST_SD_Amount,
#                    i.Final_Amount, i.Invoice_Id
#             FROM assign_subcontractors asg
#             INNER JOIN villages v ON asg.Village_Id = v.Village_Id
#             INNER JOIN invoice i ON i.Village_Id = v.Village_Id AND i.PMC_No = asg.PMC_No
#             WHERE asg.Contractor_Id = %s
#             ORDER BY i.PMC_No ASC, i.Invoice_No ASC
#         """, (contractor_id,))
#         invoices = cursor.fetchall()
#
#         # Hold Amounts
#         cursor.execute("""
#             SELECT h.Invoice_Id, h.hold_type_id, h.hold_amount
#             FROM invoice_subcontractor_hold_join h
#             JOIN invoice i ON h.Invoice_Id = i.Invoice_Id
#             WHERE h.Contractor_Id = %s
#         """, (contractor_id,))
#         hold_amounts = cursor.fetchall()
#         hold_data = {}
#         for h in hold_amounts:
#             hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
#
#         # GST Release query - Modified to get all GST releases for the contractor
#         cursor.execute("""
#             SELECT gr.pmc_no, gr.invoice_no, gr.basic_amount, gr.final_amount
#             FROM gst_release gr
#             WHERE gr.pmc_no IN (
#                 SELECT DISTINCT a.PMC_No
#                 FROM assign_subcontractors a
#                 WHERE a.Contractor_Id = %s
#             )
#             ORDER BY gr.pmc_no ASC
#         """, (contractor_id,))
#         gst_rel = cursor.fetchall()
#
#         gst_rel_map = {}
#         for gr in gst_rel:
#             invoice_nos = []
#             if gr['invoice_no']:
#                 if ',' in gr['invoice_no']:
#                     invoice_nos = [x.strip() for x in gr['invoice_no'].replace(' ', '').split(',')]
#                 elif '&' in gr['invoice_no']:
#                     invoice_nos = [x.strip() for x in gr['invoice_no'].split('&')]
#                 else:
#                     invoice_nos = [gr['invoice_no'].strip()]
#
#             # Map each individual invoice to the GST release
#             for inv_no in invoice_nos:
#                 key = (gr['pmc_no'], inv_no)
#                 if key not in gst_rel_map:
#                     gst_rel_map[key] = {
#                         'basic_amount': gr['basic_amount'],
#                         'final_amount': gr['final_amount'],
#                         'invoice_no': gr['invoice_no'],
#                         'all_invoice_nos': invoice_nos
#                     }
#
#             # Also map the combined invoice number if it's a combined release
#             if len(invoice_nos) > 1:
#                 combined_key = (gr['pmc_no'], gr['invoice_no'])
#                 gst_rel_map[combined_key] = {
#                     'basic_amount': gr['basic_amount'],
#                     'final_amount': gr['final_amount'],
#                     'invoice_no': gr['invoice_no'],
#                     'all_invoice_nos': invoice_nos
#                 }
#
#         # GST Release Payments - Updated to include Payment_Amount and TDS_Payment_Amount
#         cursor.execute("""
#             SELECT p.pmc_no, p.invoice_no, p.Payment_Amount, p.TDS_Payment_Amount, p.Total_amount, p.utr
#             FROM payment p
#             JOIN gst_release gr ON p.pmc_no = gr.pmc_no AND (
#                 p.invoice_no = gr.invoice_no
#                 OR (gr.invoice_no LIKE '%,%' AND FIND_IN_SET(p.invoice_no, REPLACE(gr.invoice_no, ' ', '')) > 0)
#                 OR (gr.invoice_no LIKE '%&%' AND (
#                     p.invoice_no = SUBSTRING_INDEX(gr.invoice_no, '&', 1)
#                     OR p.invoice_no = SUBSTRING_INDEX(gr.invoice_no, '&', -1)
#                     OR (gr.invoice_no LIKE '%&%&%' AND p.invoice_no = SUBSTRING_INDEX(SUBSTRING_INDEX(gr.invoice_no, '&', 2), '&', -1))
#                 ))
#             )
#             WHERE gr.pmc_no IN (
#                 SELECT DISTINCT i.PMC_No
#                 FROM invoice i
#                 JOIN assign_subcontractors a ON i.PMC_No = a.PMC_No AND i.Village_Id = a.Village_Id
#                 WHERE a.Contractor_Id = %s
#             )
#             AND p.Total_amount != 0
#         """, (contractor_id,))
#         gst_payments = cursor.fetchall()
#
#         gst_payment_map = {}
#         for pay in gst_payments:
#             key = (pay['pmc_no'], pay['invoice_no'])
#             gst_payment_map[key] = {
#                 'Payment_Amount': pay['Payment_Amount'],
#                 'TDS_Payment_Amount': pay['TDS_Payment_Amount'],
#                 'Total_amount': pay['Total_amount'],
#                 'utr': pay['utr']
#             }
#             # Also map to the combined invoice number if it exists
#             for gr_key, gr_val in gst_rel_map.items():
#                 if pay['pmc_no'] == gr_key[0] and pay['invoice_no'] in gr_val['all_invoice_nos']:
#                     combined_key = (pay['pmc_no'], gr_val['invoice_no'])
#                     gst_payment_map[combined_key] = {
#                         'Payment_Amount': pay['Payment_Amount'],
#                         'TDS_Payment_Amount': pay['TDS_Payment_Amount'],
#                         'Total_amount': pay['Total_amount'],
#                         'utr': pay['utr']
#                     }
#
#         # Invoice Payments - Updated to include Payment_Amount and TDS_Payment_Amount
#         cursor.execute("""
#             SELECT p.pmc_no, p.invoice_no, p.Payment_Amount, p.TDS_Payment_Amount, p.Total_amount, p.utr
#             FROM payment p
#             INNER JOIN (
#                 SELECT DISTINCT i.PMC_No, i.Invoice_No
#                 FROM invoice i
#                 JOIN assign_subcontractors a ON i.PMC_No = a.PMC_No AND i.Village_Id = a.Village_Id
#                 WHERE a.Contractor_Id = %s
#             ) x ON p.pmc_no = x.PMC_No AND p.invoice_no = x.Invoice_No
#             ORDER BY p.pmc_no ASC
#         """, (contractor_id,))
#         payments = cursor.fetchall()
#         payments_map = {}
#         for pay in payments:
#             key = (pay['pmc_no'], pay['invoice_no'])
#             payments_map.setdefault(key, []).append({
#                 'Payment_Amount': pay['Payment_Amount'],
#                 'TDS_Payment_Amount': pay['TDS_Payment_Amount'],
#                 'Total_amount': pay['Total_amount'],
#                 'utr': pay['utr']
#             })
#
#         # Extra Payments (no invoice_no) - Updated to include Payment_Amount and TDS_Payment_Amount
#         cursor.execute("""
#             SELECT pmc_no, Payment_Amount, TDS_Payment_Amount, Total_amount, utr
#             FROM payment
#             WHERE (invoice_no IS NULL OR invoice_no = '')
#               AND Total_amount != 0
#               AND pmc_no IS NOT NULL
#         """)
#         extra_payments_raw = cursor.fetchall()
#         extra_payments_map = {}
#         for pay in extra_payments_raw:
#             extra_payments_map.setdefault(pay['pmc_no'], []).append({
#                 'Payment_Amount': pay['Payment_Amount'],
#                 'TDS_Payment_Amount': pay['TDS_Payment_Amount'],
#                 'Total_amount': pay['Total_amount'],
#                 'utr': pay['utr']
#             })
#
#             # Credit Note
#             # Credit Note Fetch
#             cursor.execute("""
#                                     SELECT PMC_No, Invoice_Details, Basic_Amount, Debit_Amount, After_Debit_Amount,
#                                            GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR, Invoice_No
#                                     FROM credit_note
#                                     WHERE Contractor_Id = %s
#                                 """, (contractor_id,))
#
#             credit_notes = cursor.fetchall()
#
#             # Build map by (PMC_No, Invoice_No)
#             credit_note_map = {}
#             for cn in credit_notes:
#                 key = (cn["PMC_No"], cn["Invoice_No"])  # Use correct casing!
#                 credit_note_map.setdefault(key, []).append(cn)
#
#             # Track already appended credit notes
#             appended_credit_keys = set()
#
#         # Excel Workbook
#         workbook = openpyxl.Workbook()
#         sheet = workbook.active
#         sheet.title = "Contractor Report"
#
#         # Contractor Info section
#         for field, value in contInfo.items():
#             sheet.append([field.replace("_", " "), value])
#         sheet.append([])
#
#         base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
#                         "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
#                         "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]
#         hold_headers = [ht['hold_type'] for ht in hold_types]
#         # Updated payment headers to include Payment Amount and TDS Payment
#         payment_headers = ["Final Amount", "Payment Amount", "TDS Payment", "Total Paid", "UTR"]
#         all_headers = base_headers + hold_headers + payment_headers
#         sheet.append(all_headers)
#         for cell in sheet[sheet.max_row]:
#             cell.font = Font(bold=True)
#             cell.fill = PatternFill(start_color="ADD8E6", end_color="ADD8E6", fill_type="solid")
#
#         # Track which GST releases we've already processed
#         processed_gst_releases = set()
#
#         # Data Rows
#         for inv in invoices:
#             pmc_no = inv["PMC_No"]
#             invoice_no = inv["Invoice_No"]
#
#             row = [
#                 pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
#                 inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
#                 inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
#                 inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
#             ]
#
#             invoice_holds = hold_data.get(inv["Invoice_Id"], {})
#             for ht_id in hold_type_map:
#                 row.append(invoice_holds.get(ht_id, ""))
#
#             payment = payments_map.get((pmc_no, invoice_no), [None])[0]
#             row += [
#                 inv["Final_Amount"],
#                 payment["Payment_Amount"] if payment else "",
#                 payment["TDS_Payment_Amount"] if payment else "",
#                 payment["Total_amount"] if payment else "",
#                 payment["utr"] if payment and payment["utr"] else ""
#             ]
#             sheet.append(row)
#
#             # GST Release row - only show after the last invoice in the sequence
#             if (pmc_no, invoice_no) in gst_rel_map:
#                 gr = gst_rel_map[(pmc_no, invoice_no)]
#
#                 # Check if this is the last invoice in the sequence
#                 if invoice_no == gr['all_invoice_nos'][-1] and gr['invoice_no'] not in processed_gst_releases:
#                     processed_gst_releases.add(gr['invoice_no'])
#
#                     # Find matching payment if it exists
#                     gst_payment = gst_payment_map.get((pmc_no, gr['invoice_no']), None)
#                     if not gst_payment:
#                         # Try matching with individual invoice numbers
#                         for inv_no in gr['all_invoice_nos']:
#                             if (pmc_no, inv_no) in gst_payment_map:
#                                 gst_payment = gst_payment_map[(pmc_no, inv_no)]
#                                 break
#
#                     gst_row = [
#                         pmc_no, "", "", "GST Release Note", "", gr["invoice_no"],
#                         gr["basic_amount"], "", "", "", "", "", "", "", "", ""
#                     ]
#                     gst_row += ["" for _ in hold_headers]
#                     gst_row += [
#                         gr["final_amount"],
#                         gst_payment["Payment_Amount"] if gst_payment else "",
#                         gst_payment["TDS_Payment_Amount"] if gst_payment else "",
#                         gst_payment["Total_amount"] if gst_payment else "",
#                         gst_payment["utr"] if gst_payment and gst_payment.get("utr") else ""
#                     ]
#                     sheet.append(gst_row)
#
#             # Extra Payments (once per pmc_no)
#             if pmc_no in extra_payments_map:
#                 for extra in extra_payments_map[pmc_no]:
#                     extra_row = [
#                         pmc_no, "", "", " ", "", "", "", "", "", "", "", "", "", "", "", ""
#                     ]
#                     extra_row += ["" for _ in hold_headers]
#                     extra_row += [
#                         "",
#                         extra['Payment_Amount'],
#                         extra['TDS_Payment_Amount'],
#                         extra['Total_amount'],
#                         extra['utr'] if extra['utr'] else ""
#                     ]
#                     sheet.append(extra_row)
#                 del extra_payments_map[pmc_no]
#
#         # Now add any GST releases that weren't associated with any invoices
#         for gr in gst_rel:
#             if gr['invoice_no'] not in processed_gst_releases:
#                 pmc_no = gr['pmc_no']
#                 invoice_no = gr['invoice_no']
#
#                 # Find matching payment if it exists
#                 gst_payment = gst_payment_map.get((pmc_no, invoice_no), None)
#
#                 gst_row = [
#                     pmc_no, "", "", "GST Release Note", "", invoice_no,
#                     gr["basic_amount"], "", "", "", "", "", "", "", "", ""
#                 ]
#                 gst_row += ["" for _ in hold_headers]
#                 gst_row += [
#                     gr["final_amount"],
#                     gst_payment["Payment_Amount"] if gst_payment else "",
#                     gst_payment["TDS_Payment_Amount"] if gst_payment else "",
#                     gst_payment["Total_amount"] if gst_payment else "",
#                     gst_payment["utr"] if gst_payment and gst_payment.get("utr") else ""
#                 ]
#                 sheet.append(gst_row)
#                 processed_gst_releases.add(invoice_no)
#                 # Credit Note row(s)
#
#                 # Track already appended credit notes
#                 appended_credit_keys = set()
#
#                 # While writing invoices
#                 key = (pmc_no, invoice_no)
#                 if key in credit_note_map and key not in appended_credit_keys:
#                     for cn in credit_note_map[key]:
#                         credit_row = [
#                             pmc_no, "", "", cn.get("Invoice_Details", "Credit Note"), "", cn.get("Invoice_No", ""),
#                             cn.get("Basic_Amount", ""), cn.get("Debit_Amount", ""),
#                             cn.get("After_Debit_Amount", ""), cn.get("GST_Amount", ""), cn.get("Amount", ""), "", "",
#                             "",
#                             "", ""
#                         ]
#                         credit_row += ["" for _ in hold_headers]
#                         credit_row += [
#                             cn.get("Final_Amount", ""),
#                             cn.get("Total_Amount", ""),
#                             cn.get("UTR", "")
#                         ]
#                         sheet.append(credit_row)
#                     appended_credit_keys.add(key)
#
#         # Totals row (NO UTR totals, UTR blank)
#         total_basic_amount = total_tds_amount = total_sd_amount = total_on_commission = 0
#         total_final_amount = total_total_amount = total_hold_amount = 0
#         for row in sheet.iter_rows(min_row=13, max_row=sheet.max_row, values_only=True):
#             try:
#                 desc = row[3]
#                 if desc == " ":
#                     total_total_amount += float(row[-2] or 0)
#                     continue
#
#                 total_basic_amount += float(row[6] or 0)
#                 total_tds_amount += float(row[11] or 0)
#                 total_sd_amount += float(row[12] or 0)
#                 total_on_commission += float(row[13] or 0)
#                 total_final_amount += float(row[-5] or 0)  # Changed to -5 for Final Amount
#                 total_total_amount += float(row[-2] or 0)
#                 # sum holds
#                 total_hold_amount += sum(
#                     float(row[i] or 0) for i in range(len(base_headers), len(base_headers) + len(hold_headers)))
#             except Exception:
#                 pass
#
#         totals_row = [
#             "Total", "", "", "", "", "", total_basic_amount, "", "", "", "", total_tds_amount,
#             total_sd_amount, total_on_commission, "", ""
#         ]
#         totals_row += [total_hold_amount for _ in hold_headers]
#         totals_row += [total_final_amount, "", "", total_total_amount,
#                        ""]  # UTR blank, Payment Amount and TDS Payment blank
#         sheet.append([])
#         sheet.append(totals_row)
#         for cell in sheet[sheet.max_row]:
#             cell.font = Font(bold=True)
#
#         # Summary Section
#         today_date = datetime.today().strftime('%A, %Y-%m-%d')
#         sheet.append([])
#         sheet.append(["Contractor Name", contInfo["Contractor_Name"]])
#         sheet.append(["Date", today_date])
#         sheet.append(["Description", "Amount"])
#         sheet.append(["Advance/Surplus", str(total_final_amount - total_total_amount)])
#         sheet.append(["Total Hold Amount", str(Decimal(total_hold_amount))])
#         sheet.append(["Amount With TDS", str(total_tds_amount)])
#
#         # Auto adjust column widths
#         for col in sheet.columns:
#             max_length = 0
#             col_letter = openpyxl.utils.get_column_letter(col[0].column)
#             for cell in col:
#                 try:
#                     if cell.value:
#                         max_length = max(max_length, len(str(cell.value)))
#                 except:
#                     pass
#             sheet.column_dimensions[col_letter].width = max_length + 2
#
#         workbook.save(output_file)
#         workbook.close()
#
#     finally:
#         cursor.close()
#         connection.close()
#
#     return send_from_directory(output_folder, f"Contractor_Report_{contractor_id}.xlsx", as_attachment=True)
@app.route('/download_report/<int:contractor_id>')
def download_report(contractor_id):
    connection = config.get_db_connection()
    cursor = connection.cursor(dictionary=True, buffered=True)

    output_folder = "static/download"
    output_file = os.path.join(output_folder, f"Contractor_Report_{contractor_id}.xlsx")
    os.makedirs(output_folder, exist_ok=True)

    try:
        # Contractor Info
        cursor.execute("""
                       SELECT DISTINCT s.Contractor_Name,
                                       st.State_Name,
                                       d.District_Name,
                                       b.Block_Name,
                                       s.Mobile_No,
                                       s.GST_Registration_Type,
                                       s.GST_No,
                                       s.PAN_No,
                                       s.Email,
                                       s.Address
                       FROM subcontractors s
                                LEFT JOIN assign_subcontractors asg ON s.Contractor_Id = asg.Contractor_Id
                                LEFT JOIN villages v ON asg.Village_Id = v.Village_Id
                                LEFT JOIN blocks b ON v.Block_Id = b.Block_Id
                                LEFT JOIN districts d ON b.District_id = d.District_id
                                LEFT JOIN states st ON d.State_Id = st.State_Id
                       WHERE s.Contractor_Id = %s
                       """, (contractor_id,))
        contInfo = cursor.fetchone()
        if not contInfo:
            return "No contractor found", 404

        # Hold Types
        cursor.execute("""
                       SELECT DISTINCT ht.hold_type_id, ht.hold_type
                       FROM invoice_subcontractor_hold_join h
                                JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
                                JOIN invoice i ON h.Invoice_Id = i.Invoice_Id
                       WHERE h.Contractor_Id = %s
                       """, (contractor_id,))
        hold_types = cursor.fetchall()
        hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}

        # Invoices
        cursor.execute("""
                       SELECT DISTINCT i.PMC_No,
                                       v.Village_Name,
                                       i.Work_Type,
                                       i.Invoice_Details,
                                       i.Invoice_Date,
                                       i.Invoice_No,
                                       i.Basic_Amount,
                                       i.Debit_Amount,
                                       i.After_Debit_Amount,
                                       i.GST_Amount,
                                       i.Amount,
                                       i.TDS_Amount,
                                       i.SD_Amount,
                                       i.On_Commission,
                                       i.Hydro_Testing,
                                       i.GST_SD_Amount,
                                       i.Final_Amount,
                                       i.Invoice_Id
                       FROM assign_subcontractors asg
                                INNER JOIN villages v ON asg.Village_Id = v.Village_Id
                                INNER JOIN invoice i ON i.Village_Id = v.Village_Id AND i.PMC_No = asg.PMC_No
                       WHERE asg.Contractor_Id = %s
                       ORDER BY i.PMC_No ASC, i.Invoice_No ASC
                       """, (contractor_id,))
        invoices = cursor.fetchall()

        # Hold Amounts
        cursor.execute("""
                       SELECT h.Invoice_Id, h.hold_type_id, h.hold_amount
                       FROM invoice_subcontractor_hold_join h
                                JOIN invoice i ON h.Invoice_Id = i.Invoice_Id
                       WHERE h.Contractor_Id = %s
                       """, (contractor_id,))
        hold_amounts = cursor.fetchall()
        hold_data = {}
        for h in hold_amounts:
            hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']

        # GST Release query - Modified to get all GST releases for the contractor
        cursor.execute("""
                       SELECT gr.pmc_no, gr.invoice_no, gr.basic_amount, gr.final_amount
                       FROM gst_release gr
                       WHERE gr.pmc_no IN (SELECT DISTINCT a.PMC_No
                                           FROM assign_subcontractors a
                                           WHERE a.Contractor_Id = %s)
                       ORDER BY gr.pmc_no ASC
                       """, (contractor_id,))
        gst_rel = cursor.fetchall()

        gst_rel_map = {}
        for gr in gst_rel:
            invoice_nos = []
            if gr['invoice_no']:
                if ',' in gr['invoice_no']:
                    invoice_nos = [x.strip() for x in gr['invoice_no'].replace(' ', '').split(',')]
                elif '&' in gr['invoice_no']:
                    invoice_nos = [x.strip() for x in gr['invoice_no'].split('&')]
                else:
                    invoice_nos = [gr['invoice_no'].strip()]

            # Map each individual invoice to the GST release
            for inv_no in invoice_nos:
                key = (gr['pmc_no'], inv_no)
                if key not in gst_rel_map:
                    gst_rel_map[key] = {
                        'basic_amount': gr['basic_amount'],
                        'final_amount': gr['final_amount'],
                        'invoice_no': gr['invoice_no'],
                        'all_invoice_nos': invoice_nos
                    }

            # Also map the combined invoice number if it's a combined release
            if len(invoice_nos) > 1:
                combined_key = (gr['pmc_no'], gr['invoice_no'])
                gst_rel_map[combined_key] = {
                    'basic_amount': gr['basic_amount'],
                    'final_amount': gr['final_amount'],
                    'invoice_no': gr['invoice_no'],
                    'all_invoice_nos': invoice_nos
                }

        # GST Release Payments - Updated to include Payment_Amount and TDS_Payment_Amount
        cursor.execute("""
                       SELECT p.pmc_no, p.invoice_no, p.Payment_Amount, p.TDS_Payment_Amount, p.Total_amount, p.utr
                       FROM payment p
                                JOIN gst_release gr ON p.pmc_no = gr.pmc_no AND (
                           p.invoice_no = gr.invoice_no
                               OR
                           (gr.invoice_no LIKE '%,%' AND FIND_IN_SET(p.invoice_no, REPLACE(gr.invoice_no, ' ', '')) > 0)
                               OR (gr.invoice_no LIKE '%&%' AND (
                               p.invoice_no = SUBSTRING_INDEX(gr.invoice_no, '&', 1)
                                   OR p.invoice_no = SUBSTRING_INDEX(gr.invoice_no, '&', -1)
                                   OR (gr.invoice_no LIKE '%&%&%' AND
                                       p.invoice_no = SUBSTRING_INDEX(SUBSTRING_INDEX(gr.invoice_no, '&', 2), '&', -1))
                               ))
                           )
                       WHERE gr.pmc_no IN (SELECT DISTINCT i.PMC_No
                                           FROM invoice i
                                                    JOIN assign_subcontractors a
                                                         ON i.PMC_No = a.PMC_No AND i.Village_Id = a.Village_Id
                                           WHERE a.Contractor_Id = %s)
                         AND p.Total_amount != 0
                       """, (contractor_id,))
        gst_payments = cursor.fetchall()

        gst_payment_map = {}
        for pay in gst_payments:
            key = (pay['pmc_no'], pay['invoice_no'])
            gst_payment_map[key] = {
                'Payment_Amount': pay['Payment_Amount'],
                'TDS_Payment_Amount': pay['TDS_Payment_Amount'],
                'Total_amount': pay['Total_amount'],
                'utr': pay['utr']
            }
            # Also map to the combined invoice number if it exists
            for gr_key, gr_val in gst_rel_map.items():
                if pay['pmc_no'] == gr_key[0] and pay['invoice_no'] in gr_val['all_invoice_nos']:
                    combined_key = (pay['pmc_no'], gr_val['invoice_no'])
                    gst_payment_map[combined_key] = {
                        'Payment_Amount': pay['Payment_Amount'],
                        'TDS_Payment_Amount': pay['TDS_Payment_Amount'],
                        'Total_amount': pay['Total_amount'],
                        'utr': pay['utr']
                    }

        # Invoice Payments - Updated to include Payment_Amount and TDS_Payment_Amount
        cursor.execute("""
                       SELECT p.pmc_no, p.invoice_no, p.Payment_Amount, p.TDS_Payment_Amount, p.Total_amount, p.utr
                       FROM payment p
                                INNER JOIN (SELECT DISTINCT i.PMC_No, i.Invoice_No
                                            FROM invoice i
                                                     JOIN assign_subcontractors a
                                                          ON i.PMC_No = a.PMC_No AND i.Village_Id = a.Village_Id
                                            WHERE a.Contractor_Id = %s) x
                                           ON p.pmc_no = x.PMC_No AND p.invoice_no = x.Invoice_No
                       ORDER BY p.pmc_no ASC
                       """, (contractor_id,))
        payments = cursor.fetchall()
        payments_map = {}
        for pay in payments:
            key = (pay['pmc_no'], pay['invoice_no'])
            payments_map.setdefault(key, []).append({
                'Payment_Amount': pay['Payment_Amount'],
                'TDS_Payment_Amount': pay['TDS_Payment_Amount'],
                'Total_amount': pay['Total_amount'],
                'utr': pay['utr']
            })
            # Fetch hold_release data
            cursor.execute("""
                           SELECT *
                           FROM hold_release
                           WHERE Contractor_Id = %s
                           """, (contractor_id,))
            hold_releases = cursor.fetchall()
            hold_release_map = {}
            for hr in hold_releases:
                invoice_nos = [i.strip() for i in str(hr['Invoice_No']).split('&')]
                for inv_no in invoice_nos:
                    key = (hr['PMC_No'], inv_no)
                    hold_release_map.setdefault(key, []).append(hr)
            # Credit Note
            # Credit Note Fetch
        cursor.execute("""
                           SELECT PMC_No,
                                  Invoice_Details,
                                  Basic_Amount,
                                  Debit_Amount,
                                  After_Debit_Amount,
                                  GST_Amount,
                                  Amount,
                                  Final_Amount,
                                  Payment_Amount,
                                  Total_Amount,
                                  UTR,
                                  Invoice_No
                           FROM credit_note
                           WHERE Contractor_Id = %s
                           """, (contractor_id,))

        credit_notes = cursor.fetchall()

            # Build map by (PMC_No, Invoice_No)
        credit_note_map = {}
        for cn in credit_notes:
            key = (cn["PMC_No"], cn["Invoice_No"])  # Use correct casing!
            credit_note_map.setdefault(key, []).append(cn)
            print("Credit_note_map:",credit_note_map)
            # Track already appended credit notes
        appended_credit_keys = set()
        #print("appended_credit_keys",appended_credit_keys)


        # Extra Payments (no invoice_no) - Updated to include Payment_Amount and TDS_Payment_Amount
        cursor.execute("""
                       SELECT pmc_no, Payment_Amount, TDS_Payment_Amount, Total_amount, utr
                       FROM payment
                       WHERE (invoice_no IS NULL OR invoice_no = '')
                         AND Total_amount != 0
              AND pmc_no IS NOT NULL
                       """)
        extra_payments_raw = cursor.fetchall()
        extra_payments_map = {}
        for pay in extra_payments_raw:
            extra_payments_map.setdefault(pay['pmc_no'], []).append({
                'Payment_Amount': pay['Payment_Amount'],
                'TDS_Payment_Amount': pay['TDS_Payment_Amount'],
                'Total_amount': pay['Total_amount'],
                'utr': pay['utr']
            })

        # Excel Workbook
        workbook = openpyxl.Workbook()
        sheet = workbook.active
        sheet.title = "Contractor Report"

        # Contractor Info section
        for field, value in contInfo.items():
            sheet.append([field.replace("_", " "), value])
        sheet.append([])

        base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
                        "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
                        "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]
        hold_headers = [ht['hold_type'] for ht in hold_types]
        # Updated payment headers to include Payment Amount and TDS Payment
        payment_headers = ["Final Amount", "Payment Amount", "TDS Payment", "Total Paid", "UTR"]
        all_headers = base_headers + hold_headers + payment_headers
        sheet.append(all_headers)
        for cell in sheet[sheet.max_row]:
            cell.font = Font(bold=True)
            cell.fill = PatternFill(start_color="ADD8E6", end_color="ADD8E6", fill_type="solid")

        # Track which GST releases we've already processed
        processed_gst_releases = set()

        # Data Rows
        for inv in invoices:
            pmc_no = inv["PMC_No"]
            invoice_no = inv["Invoice_No"]

            row = [
                pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
                inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
                inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
                inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
            ]

            invoice_holds = hold_data.get(inv["Invoice_Id"], {})
            for ht_id in hold_type_map:
                row.append(invoice_holds.get(ht_id, ""))

            payment = payments_map.get((pmc_no, invoice_no), [None])[0]
            row += [
                inv["Final_Amount"],
                payment["Payment_Amount"] if payment else "",
                payment["TDS_Payment_Amount"] if payment else "",
                payment["Total_amount"] if payment else "",
                payment["utr"] if payment and payment["utr"] else ""
            ]
            sheet.append(row)

            # GST Release row - only show after the last invoice in the sequence
            if (pmc_no, invoice_no) in gst_rel_map:
                gr = gst_rel_map[(pmc_no, invoice_no)]

                # Check if this is the last invoice in the sequence
                if invoice_no == gr['all_invoice_nos'][-1] and gr['invoice_no'] not in processed_gst_releases:
                    processed_gst_releases.add(gr['invoice_no'])

                    # Find matching payment if it exists
                    gst_payment = gst_payment_map.get((pmc_no, gr['invoice_no']), None)
                    if not gst_payment:
                        # Try matching with individual invoice numbers
                        for inv_no in gr['all_invoice_nos']:
                            if (pmc_no, inv_no) in gst_payment_map:
                                gst_payment = gst_payment_map[(pmc_no, inv_no)]
                                break

                    gst_row = [
                        pmc_no, "", "", "GST Release Note", "", gr["invoice_no"],
                        gr["basic_amount"], "", "", "", "", "", "", "", "", ""
                    ]
                    gst_row += ["" for _ in hold_headers]
                    gst_row += [
                        gr["final_amount"],
                        gst_payment["Payment_Amount"] if gst_payment else "",
                        gst_payment["TDS_Payment_Amount"] if gst_payment else "",
                        gst_payment["Total_amount"] if gst_payment else "",
                        gst_payment["utr"] if gst_payment and gst_payment.get("utr") else ""
                    ]
                    sheet.append(gst_row)
                    # Hold Release row(s)
                    if (pmc_no, invoice_no) in hold_release_map:
                        for hr in hold_release_map[(pmc_no, invoice_no)]:
                            hr_row = [
                                pmc_no, "", "", hr["Invoice_Details"], "", hr["Invoice_No"],
                                hr.get("Basic_Amount", ""), "", "", "", "", "", "", "", "", "","",""
                            ]
                            hr_row += ["" for _ in hold_headers]
                            hr_row += [
                                hr.get("Final_Amount", ""),
                                hr.get("Total_Amount", ""),
                                hr.get("UTR", "")
                            ]
                            sheet.append(hr_row)
            # Extra Payments (once per pmc_no)
            if pmc_no in extra_payments_map:
                for extra in extra_payments_map[pmc_no]:
                    extra_row = [
                        pmc_no, "", "", " ", "", "", "", "", "", "", "", "", "", "", "", ""
                    ]
                    extra_row += ["" for _ in hold_headers]
                    extra_row += [
                        "",
                        extra['Payment_Amount'],
                        extra['TDS_Payment_Amount'],
                        extra['Total_amount'],
                        extra['utr'] if extra['utr'] else ""
                    ]
                    sheet.append(extra_row)
                del extra_payments_map[pmc_no]

        # Now add any GST releases that weren't associated with any invoices
        for gr in gst_rel:
            if gr['invoice_no'] not in processed_gst_releases:
                pmc_no = gr['pmc_no']
                invoice_no = gr['invoice_no']

                # Find matching payment if it exists
                gst_payment = gst_payment_map.get((pmc_no, invoice_no), None)

                gst_row = [
                    pmc_no, "", "", "GST Release Note", "", invoice_no,
                    gr["basic_amount"], "", "", "", "", "", "", "", "", ""
                ]
                gst_row += ["" for _ in hold_headers]
                gst_row += [
                    gr["final_amount"],
                    gst_payment["Payment_Amount"] if gst_payment else "",
                    gst_payment["TDS_Payment_Amount"] if gst_payment else "",
                    gst_payment["Total_amount"] if gst_payment else "",
                    gst_payment["utr"] if gst_payment and gst_payment.get("utr") else ""
                ]
                sheet.append(gst_row)
                processed_gst_releases.add(invoice_no)
        # Credit Note row(s)

        # # Track already appended credit notes
        appended_credit_keys = set()

        sheet.append(row)

        # ➕ Add this block right here:
        key = (pmc_no, invoice_no)
        if key in credit_note_map and key not in appended_credit_keys:
            for cn in credit_note_map[key]:
                credit_row = [
                    pmc_no, "", "", cn.get("Invoice_Details", "Credit Note"), "", cn.get("Invoice_No", ""),
                    cn.get("Basic_Amount", ""), cn.get("Debit_Amount", ""),
                    cn.get("After_Debit_Amount", ""), cn.get("GST_Amount", ""), cn.get("Amount", ""), "", "", "",
                    "", ""
                ]
                credit_row += ["" for _ in hold_headers]
                credit_row += [
                    cn.get("Final_Amount", ""), "", "", cn.get("Total_Amount", ""),
                    cn.get("UTR", "")
                ]
                sheet.append(credit_row)
            appended_credit_keys.add(key)




        # Totals row (NO UTR totals, UTR blank)
        total_basic_amount = total_tds_amount = total_sd_amount = total_on_commission = 0
        total_final_amount = total_total_amount = total_hold_amount = 0
        for row in sheet.iter_rows(min_row=13, max_row=sheet.max_row, values_only=True):
            try:
                desc = row[3]
                if desc == " ":
                    total_total_amount += float(row[-2] or 0)
                    continue

                total_basic_amount += float(row[6] or 0)
                total_tds_amount += float(row[11] or 0)
                total_sd_amount += float(row[12] or 0)
                total_on_commission += float(row[13] or 0)
                total_final_amount += float(row[-5] or 0)  # Changed to -5 for Final Amount
                total_total_amount += float(row[-2] or 0)
                # sum holds
                total_hold_amount += sum(
                    float(row[i] or 0) for i in range(len(base_headers), len(base_headers) + len(hold_headers)))
            except Exception:
                pass

        totals_row = [
            "Total", "", "", "", "", "", total_basic_amount, "", "", "", "", total_tds_amount,
            total_sd_amount, total_on_commission, "", ""
        ]
        totals_row += [total_hold_amount for _ in hold_headers]
        totals_row += [total_final_amount, "", "", total_total_amount,
                       ""]  # UTR blank, Payment Amount and TDS Payment blank
        sheet.append([])
        sheet.append(totals_row)
        for cell in sheet[sheet.max_row]:
            cell.font = Font(bold=True)

        # Summary Section
        today_date = datetime.today().strftime('%A, %Y-%m-%d')
        sheet.append([])
        sheet.append(["Contractor Name", contInfo["Contractor_Name"]])
        sheet.append(["Date", today_date])
        sheet.append(["Description", "Amount"])
        sheet.append(["Advance/Surplus", str(total_final_amount - total_total_amount)])
        sheet.append(["Total Hold Amount", str(Decimal(total_hold_amount))])
        sheet.append(["Amount With TDS", str(total_tds_amount)])

        # Auto adjust column widths
        for col in sheet.columns:
            max_length = 0
            col_letter = openpyxl.utils.get_column_letter(col[0].column)
            for cell in col:
                try:
                    if cell.value:
                        max_length = max(max_length, len(str(cell.value)))
                except:
                    pass
            sheet.column_dimensions[col_letter].width = max_length + 2

        workbook.save(output_file)
        workbook.close()

    finally:
        cursor.close()
        connection.close()

    return send_from_directory(output_folder, f"Contractor_Report_{contractor_id}.xlsx", as_attachment=True)
# @app.route('/download_report/<int:contractor_id>')
# def download_report(contractor_id):
#     connection = config.get_db_connection()
#     cursor = connection.cursor(dictionary=True, buffered=True)
#
#     output_folder = "static/download"
#     output_file = os.path.join(output_folder, f"Contractor_Report_{contractor_id}.xlsx")
#     os.makedirs(output_folder, exist_ok=True)
#
#     try:
#         # Contractor Info
#         cursor.execute("""
#             SELECT DISTINCT s.Contractor_Name, st.State_Name, d.District_Name, b.Block_Name,
#                    s.Mobile_No, s.GST_Registration_Type, s.GST_No, s.PAN_No, s.Email, s.Address
#             FROM subcontractors s
#             LEFT JOIN assign_subcontractors asg ON s.Contractor_Id = asg.Contractor_Id
#             LEFT JOIN villages v ON asg.Village_Id = v.Village_Id
#             LEFT JOIN blocks b ON v.Block_Id = b.Block_Id
#             LEFT JOIN districts d ON b.District_id = d.District_id
#             LEFT JOIN states st ON d.State_Id = st.State_Id
#             WHERE s.Contractor_Id = %s
#         """, (contractor_id,))
#         contInfo = cursor.fetchone()
#         if not contInfo:
#             return "No contractor found", 404
#
#         # Hold Types
#         cursor.execute("""
#             SELECT DISTINCT ht.hold_type_id, ht.hold_type
#             FROM invoice_subcontractor_hold_join h
#             JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#             JOIN invoice i ON h.Invoice_Id = i.Invoice_Id
#             WHERE h.Contractor_Id = %s
#         """, (contractor_id,))
#         hold_types = cursor.fetchall()
#         hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
#
#         # Invoices
#         cursor.execute("""
#             SELECT DISTINCT i.PMC_No, v.Village_Name, i.Work_Type, i.Invoice_Details,
#                    i.Invoice_Date, i.Invoice_No, i.Basic_Amount, i.Debit_Amount,
#                    i.After_Debit_Amount, i.GST_Amount, i.Amount, i.TDS_Amount, i.SD_Amount,
#                    i.On_Commission, i.Hydro_Testing, i.GST_SD_Amount,
#                    i.Final_Amount, i.Invoice_Id
#             FROM assign_subcontractors asg
#             INNER JOIN villages v ON asg.Village_Id = v.Village_Id
#             INNER JOIN invoice i ON i.Village_Id = v.Village_Id AND i.PMC_No = asg.PMC_No
#             WHERE asg.Contractor_Id = %s
#             ORDER BY i.PMC_No ASC
#         """, (contractor_id,))
#         invoices = cursor.fetchall()
#
#         # Hold Amounts
#         cursor.execute("""
#             SELECT h.Invoice_Id, h.hold_type_id, h.hold_amount
#             FROM invoice_subcontractor_hold_join h
#             JOIN invoice i ON h.Invoice_Id = i.Invoice_Id
#             WHERE h.Contractor_Id = %s
#         """, (contractor_id,))
#         hold_amounts = cursor.fetchall()
#         hold_data = {}
#         for h in hold_amounts:
#             hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
#
#         # GST Release
#         cursor.execute("""
#             SELECT gr.pmc_no, gr.invoice_no, gr.basic_amount, gr.final_amount
#             FROM gst_release gr
#             INNER JOIN (
#                 SELECT DISTINCT i.PMC_No, i.Invoice_No
#                 FROM invoice i
#                 JOIN assign_subcontractors a ON i.PMC_No = a.PMC_No AND i.Village_Id = a.Village_Id
#                 WHERE a.Contractor_Id = %s
#             ) x ON gr.pmc_no = x.PMC_No AND gr.invoice_no = x.Invoice_No
#             ORDER BY gr.pmc_no ASC
#         """, (contractor_id,))
#         gst_rel = cursor.fetchall()
#         gst_rel_map = {(gr['pmc_no'], gr['invoice_no']): gr for gr in gst_rel}
#         #
#
#
#
#
#
#         #Credit Note
#         # Credit Note Fetch
#         cursor.execute("""
#                     SELECT PMC_No, Invoice_Details, Basic_Amount, Debit_Amount, After_Debit_Amount,
#                            GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR, Invoice_No
#                     FROM credit_note
#                     WHERE Contractor_Id = %s
#                 """, (contractor_id,))
#
#         credit_notes = cursor.fetchall()
#
#         # Build map by (PMC_No, Invoice_No)
#         credit_note_map = {}
#         for cn in credit_notes:
#             key = (cn["PMC_No"], cn["Invoice_No"])  # Use correct casing!
#             credit_note_map.setdefault(key, []).append(cn)
#
#         # Track already appended credit notes
#         appended_credit_keys = set()
#
#
#
#
#
#
#         # Invoice Payments
#         cursor.execute("""
#             SELECT p.pmc_no, p.invoice_no, p.Total_amount, p.utr
#             FROM payment p
#             INNER JOIN (
#                 SELECT DISTINCT i.PMC_No, i.Invoice_No
#                 FROM invoice i
#                 JOIN assign_subcontractors a ON i.PMC_No = a.PMC_No AND i.Village_Id = a.Village_Id
#                 WHERE a.Contractor_Id = %s
#             ) x ON p.pmc_no = x.PMC_No AND p.invoice_no = x.Invoice_No
#             ORDER BY p.pmc_no ASC
#         """, (contractor_id,))
#         payments = cursor.fetchall()
#         payments_map = {}
#         for pay in payments:
#             key = (pay['pmc_no'], pay['invoice_no'])
#             payments_map.setdefault(key, []).append(pay)
#
#         #Credit Note Fetch-----------------------------------------------------------------------------
#
#
#         # GST Release Payments
#         cursor.execute("""
#             SELECT p.pmc_no, p.invoice_no, p.Total_amount, p.utr
#             FROM payment p
#             INNER JOIN gst_release gr ON p.pmc_no = gr.pmc_no AND p.invoice_no = gr.invoice_no
#             WHERE gr.invoice_no IS NOT NULL AND gr.pmc_no IS NOT NULL
#               AND p.Total_amount != 0
#         """)
#         gst_payments = cursor.fetchall()
#         gst_payment_map = {}
#         for pay in gst_payments:
#             key = (pay['pmc_no'], pay['invoice_no'])
#             gst_payment_map[key] = pay
#
#         # Extra Payments (no invoice_no)
#         cursor.execute("""
#             SELECT pmc_no, Total_amount, utr
#             FROM payment
#             WHERE (invoice_no IS NULL OR invoice_no = '')
#               AND Total_amount != 0
#               AND pmc_no IS NOT NULL
#         """)
#         extra_payments_raw = cursor.fetchall()
#         extra_payments_map = {}
#         for pay in extra_payments_raw:
#             extra_payments_map.setdefault(pay['pmc_no'], []).append(pay)
#
#         # Excel Workbook
#         workbook = openpyxl.Workbook()
#         sheet = workbook.active
#         sheet.title = "Contractor Report"
#
#         # Contractor Info section
#         for field, value in contInfo.items():
#             sheet.append([field.replace("_", " "), value])
#         sheet.append([])
#
#         base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
#                         "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
#                         "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]
#         hold_headers = [ht['hold_type'] for ht in hold_types]
#         payment_headers = ["Final Amount", "Total Paid", "UTR"]
#         all_headers = base_headers + hold_headers + payment_headers
#         sheet.append(all_headers)
#         for cell in sheet[sheet.max_row]:
#             cell.font = Font(bold=True)
#             cell.fill = PatternFill(start_color="ADD8E6", end_color="ADD8E6", fill_type="solid")
#
#         # Data Rows
#         for inv in invoices:
#             pmc_no = inv["PMC_No"]
#             invoice_no = inv["Invoice_No"]
#
#             row = [
#                 pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
#                 inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
#                 inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
#                 inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
#             ]
#
#             invoice_holds = hold_data.get(inv["Invoice_Id"], {})
#             for ht_id in hold_type_map:
#                 row.append(invoice_holds.get(ht_id, ""))
#
#             payment = payments_map.get((pmc_no, invoice_no), [None])[0]
#             row += [
#                 inv["Final_Amount"],
#                 payment["Total_amount"] if payment else "",
#                 payment["utr"] if payment and payment["utr"] else ""  # UTR only display, no total
#             ]
#             sheet.append(row)
#
#             # GST Release row
#             if (pmc_no, invoice_no) in gst_rel_map:
#                 gr = gst_rel_map[(pmc_no, invoice_no)]
#                 gst_payment = gst_payment_map.get((pmc_no, invoice_no))
#
#                 gst_row = [
#                     pmc_no, "", "", "GST Release Note", "", gr["invoice_no"],
#                     gr["basic_amount"], "", "", "", "", "", "", "", "", ""
#                 ]
#                 gst_row += ["" for _ in hold_headers]
#                 gst_row += [
#                     gr["final_amount"],
#                     gst_payment["Total_amount"] if gst_payment else "",
#                     gst_payment["utr"] if gst_payment and gst_payment["utr"] else ""
#                 ]
#                 sheet.append(gst_row)
#             # Credit Note row(s)
#
#             # Track already appended credit notes
#             appended_credit_keys = set()
#
#             # While writing invoices
#             key = (pmc_no, invoice_no)
#             if key in credit_note_map and key not in appended_credit_keys:
#                 for cn in credit_note_map[key]:
#                     credit_row = [
#                         pmc_no, "", "", cn.get("Invoice_Details", "Credit Note"), "", cn.get("Invoice_No",""),
#                         cn.get("Basic_Amount", ""), cn.get("Debit_Amount", ""),
#                         cn.get("After_Debit_Amount", ""), cn.get("GST_Amount", ""), cn.get("Amount", ""), "", "", "",
#                         "", ""
#                     ]
#                     credit_row += ["" for _ in hold_headers]
#                     credit_row += [
#                         cn.get("Final_Amount", ""),
#                         cn.get("Total_Amount", ""),
#                         cn.get("UTR", "")
#                     ]
#                     sheet.append(credit_row)
#                 appended_credit_keys.add(key)
#
#
#             # Extra Payments (once per pmc_no)
#             if pmc_no in extra_payments_map:
#                 for extra in extra_payments_map[pmc_no]:
#                     extra_row = [
#                         pmc_no, "", "", " ", "", "", "", "", "", "", "", "", "", "", "", ""
#                     ]
#                     extra_row += ["" for _ in hold_headers]
#                     extra_row += ["", extra['Total_amount'], extra['utr'] if extra['utr'] else ""]
#                     sheet.append(extra_row)
#                 del extra_payments_map[pmc_no]
#
#         # Totals row (NO UTR totals, UTR blank)
#         total_basic_amount = total_tds_amount = total_sd_amount = total_on_commission = 0
#         total_final_amount = total_total_amount = total_hold_amount = 0
#         for row in sheet.iter_rows(min_row=13, max_row=sheet.max_row, values_only=True):
#             try:
#                 desc = row[3]
#                 if desc == " ":
#                     total_total_amount += float(row[-2] or 0)
#                     continue
#
#                 total_basic_amount += float(row[6] or 0)
#                 total_tds_amount += float(row[11] or 0)
#                 total_sd_amount += float(row[12] or 0)
#                 total_on_commission += float(row[13] or 0)
#                 total_final_amount += float(row[-3] or 0)
#                 total_total_amount += float(row[-2] or 0)
#                 # sum holds
#                 total_hold_amount += sum(float(row[i] or 0) for i in range(len(base_headers), len(base_headers) + len(hold_headers)))
#
#                 # DO NOT sum UTR column (row[-1]) — skip it
#             except Exception:
#                 pass
#
#         totals_row = [
#             "Total", "", "", "", "", "", total_basic_amount, "", "", "", "", total_tds_amount,
#             total_sd_amount, total_on_commission, "", ""
#         ]
#         totals_row += [total_hold_amount for _ in hold_headers]
#         totals_row += [total_final_amount, total_total_amount, ""]  # UTR blank
#         sheet.append([])
#         sheet.append(totals_row)
#         for cell in sheet[sheet.max_row]:
#             cell.font = Font(bold=True)
#
#     # Summary Section
#         today_date = datetime.today().strftime('%A, %Y-%m-%d')
#         sheet.append([])
#         sheet.append(["Contractor Name", contInfo["Contractor_Name"]])
#         sheet.append(["Date", today_date])
#         sheet.append(["Description", "Amount"])
#         sheet.append(["Advance/Surplus", str(total_final_amount - total_total_amount)])
#         sheet.append(["Total Hold Amount", str(Decimal(total_hold_amount))])
#         sheet.append(["Amount With TDS", str(total_tds_amount)])
#
#         # Auto adjust column widths
#         for col in sheet.columns:
#             max_length = 0
#             col_letter = openpyxl.utils.get_column_letter(col[0].column)
#             for cell in col:
#                 try:
#                     if cell.value:
#                         max_length = max(max_length, len(str(cell.value)))
#                 except:
#                     pass
#             sheet.column_dimensions[col_letter].width = max_length + 2
#
#         workbook.save(output_file)
#         workbook.close()
#
#     finally:
#         cursor.close()
#         connection.close()
#
#     return send_from_directory(output_folder, f"Contractor_Report_{contractor_id}.xlsx", as_attachment=True)
# @app.route('/download_report/<int:contractor_id>')
# def download_report(contractor_id):
#     connection = config.get_db_connection()
#     cursor = connection.cursor(dictionary=True)
#
#     output_folder = "static/download"
#     output_file = os.path.join(output_folder, f"Contractor_Report_{contractor_id}.xlsx")
#
#     if not os.path.exists(output_folder):
#         os.makedirs(output_folder)
#
#     try:
#         # Fetch Contractor Info
#         cursor.callproc('GetContractorInfoId', [contractor_id])
#         for result in cursor.stored_results():
#             contInfo = result.fetchone()
#         if not contInfo:
#             return "No contractor found", 404
#
#         # Fetch Hold Types
#         cursor.callproc('GetDistinctHoldTypesByContractor', [contractor_id])
#         for result in cursor.stored_results():
#             hold_types = result.fetchall()
#         hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
#
#         # Fetch Invoices
#         cursor.callproc('GetInvoicesAndGSTReleasesByContractor', [contractor_id])
#         for result in cursor.stored_results():
#             invoices = result.fetchall()
#
#         # Fetch Hold Amounts
#         cursor.callproc('GetHoldAmountsByContractors', [contractor_id])
#         for result in cursor.stored_results():
#             hold_amounts = result.fetchall()
#         hold_data = {}
#         for h in hold_amounts:
#             hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
#
#         # Extract PMC numbers
#         pmc_numbers = tuple(set(inv['PMC_No'] for inv in invoices if inv['PMC_No'] is not None))
#
#         payments_map = {}
#         extra_payments_map = {}
#         if pmc_numbers:
#             pmc_list_str = ','.join(str(pmc) for pmc in pmc_numbers)
#             cursor.callproc('GetPaymentsByPmcNosWithInvoice', [pmc_list_str])
#             for result in cursor.stored_results():
#                 payments = result.fetchall()
#             for pay in payments:
#                 key = (pay['pmc_no'], pay['invoice_no'])
#                 payments_map.setdefault(key, []).append(pay)
#
#             cursor.callproc('GetExtraPaymentsByPmcNos', [pmc_list_str])
#             for result in cursor.stored_results():
#                 extra_payments = result.fetchall()
#             for pay in extra_payments:
#                 extra_payments_map.setdefault(pay['pmc_no'], []).append(pay)
#
#         # Create Excel Workbook
#         workbook = openpyxl.Workbook()
#         sheet = workbook.active
#         sheet.title = "Contractor Report"
#
#         # Write Contractor Info
#         sheet.append(["Contractor Name", contInfo["Contractor_Name"]])
#         sheet.append(["State", contInfo["State_Name"]])
#         sheet.append(["District", contInfo["District_Name"]])
#         sheet.append(["Block", contInfo["Block_Name"]])
#         sheet.append(["Mobile No", contInfo["Mobile_No"]])
#         sheet.append(["GST Type", contInfo["GST_Registration_Type"]])
#         sheet.append(["GST No", contInfo["GST_No"]])
#         sheet.append(["PAN No", contInfo["PAN_No"]])
#         sheet.append(["Email", contInfo["Email"]])
#         sheet.append(["Address", contInfo["Address"]])
#         sheet.append([])
#
#         # Table Headers
#         base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
#                         "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
#                         "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]
#         hold_headers = [ht['hold_type'] for ht in hold_types]
#         payment_headers = ["Final Amount", "Payment Amount", "TDS Payment", "Total Paid", "UTR"]
#         all_headers = base_headers + hold_headers + payment_headers
#
#         sheet.append(all_headers)
#
#         # Style the headers
#         header_fill = PatternFill(start_color="ADD8E6", end_color="ADD8E6", fill_type="solid")
#         header_font = Font(bold=True)
#         for cell in sheet[sheet.max_row]:
#             cell.font = header_font
#             cell.fill = header_fill
#
#         seen_invoices = set()
#         seen_gst_notes = set()
#         processed_payments = set()
#         pmc_groups = {}
#         for inv in invoices:
#             pmc_groups.setdefault(inv["PMC_No"], []).append(inv)
#
#         for pmc_no, pmc_invoices in pmc_groups.items():
#             for inv in pmc_invoices:
#                 invoice_no = inv["Invoice_No"]
#                 payments = payments_map.get((pmc_no, invoice_no), [])
#
#                 if (pmc_no, invoice_no) not in seen_invoices:
#                     seen_invoices.add((pmc_no, invoice_no))
#                     first_payment = payments[0] if payments else None
#                     row = [
#                         pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
#                         inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
#                         inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
#                         inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
#                     ]
#                     invoice_holds = hold_data.get(inv["Invoice_Id"], {})
#                     for ht_id in hold_type_map:
#                         row.append(invoice_holds.get(ht_id, ""))
#
#                     row += [
#                         inv["Final_Amount"],
#                         first_payment["Payment_Amount"] if first_payment else "",
#                         first_payment["TDS_Payment_Amount"] if first_payment else "",
#                         first_payment["Total_amount"] if first_payment else "",
#                         first_payment["UTR"] if first_payment else ""
#                     ]
#                     sheet.append(row)
#                     if first_payment:
#                         payment_id = f"{pmc_no}-{invoice_no}-{first_payment['Payment_Amount']}-{first_payment.get('UTR', '')}"
#                         processed_payments.add(payment_id)
#
#                 # GST Notes
#                 if inv["gst_pmc_no"] and (inv["gst_pmc_no"], inv["gst_invoice_no"]) not in seen_gst_notes:
#                     seen_gst_notes.add((inv["gst_pmc_no"], inv["gst_invoice_no"]))
#                     gst_payment = None
#                     for payment in payments[1:]:
#                         if payment['invoice_no'] == inv["gst_invoice_no"]:
#                             gst_payment = payment
#                             break
#                     row = [pmc_no, "", "", "GST Release Note", "", inv["gst_invoice_no"],
#                            inv["gst_basic_amount"], "", "", "", "", "", "", "", "", ""]
#                     row += ["" for _ in hold_headers]
#                     row += [
#                         inv["gst_final_amount"],
#                         gst_payment["Payment_Amount"] if gst_payment else "",
#                         gst_payment["TDS_Payment_Amount"] if gst_payment else "",
#                         gst_payment["Total_amount"] if gst_payment else "",
#                         gst_payment["UTR"] if gst_payment else ""
#                     ]
#                     sheet.append(row)
#                     if gst_payment:
#                         payment_id = f"{pmc_no}-{inv['gst_invoice_no']}-{gst_payment['Payment_Amount']}-{gst_payment.get('UTR', '')}"
#                         processed_payments.add(payment_id)
#
#                 for payment in payments[1:]:
#                     payment_id = f"{pmc_no}-{payment['invoice_no']}-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                     if payment_id not in processed_payments:
#                         row = [pmc_no, "", "", "", "", payment['invoice_no'], "", "", "", "", "", "", "", "", "", ""]
#                         row += ["" for _ in hold_headers]
#                         row += [
#                             "",
#                             payment["Payment_Amount"],
#                             payment["TDS_Payment_Amount"],
#                             payment["Total_amount"],
#                             payment["UTR"]
#                         ]
#                         sheet.append(row)
#                         processed_payments.add(payment_id)
#
#             # Extra payments (null invoice)
#             for payment in extra_payments_map.get(pmc_no, []):
#                 payment_id = f"{pmc_no}-null-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                 if payment_id not in processed_payments:
#                     row = [pmc_no, "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]
#                     row += ["" for _ in hold_headers]
#                     row += [
#                         "",
#                         payment["Payment_Amount"],
#                         payment["TDS_Payment_Amount"],
#                         payment["Total_amount"],
#                         payment["UTR"]
#                     ]
#                     sheet.append(row)
#                     processed_payments.add(payment_id)
#
#         # Totals Calculation
#         total_basic_amount = total_tds_amount = total_sd_amount = total_on_commission = 0
#         total_hold_amount = total_final_amount = total_total_amount = 0
#
#         for row in sheet.iter_rows(min_row=13, max_row=sheet.max_row, values_only=True):
#             try:
#                 total_basic_amount += float(row[6] or 0)
#                 total_tds_amount += float(row[11] or 0)
#                 total_sd_amount += float(row[12] or 0)
#                 total_on_commission += float(row[13] or 0)
#                 total_final_amount += float(row[-5] or 0)
#                 total_total_amount += float(row[-2] or 0)
#                 hold_start_col = len(base_headers)
#                 hold_end_col = hold_start_col + len(hold_headers)
#                 total_hold_amount += sum(float(row[i] or 0) for i in range(hold_start_col, hold_end_col))
#             except ValueError:
#                 continue
#
#         # Totals Row
#         sheet.append([])
#         totals_row = [
#             "TOTAL", "", "", "", "", "", total_basic_amount, "", "", "", "", total_tds_amount,
#             total_sd_amount, total_on_commission, "", ""
#         ]
#         totals_row += [total_hold_amount] + [""] * (len(hold_headers) - 1)
#         totals_row += [total_final_amount, "", "", total_total_amount, ""]
#         sheet.append(totals_row)
#
#         for cell in sheet[sheet.max_row]:
#             cell.font = Font(bold=True)
#
#         # Summary Section
#         today_date = datetime.today().strftime('%A, %Y-%m-%d')
#         sheet.append([])
#         sheet.append(["Contractor Name", contInfo["Contractor_Name"]])
#         sheet.append(["Date", today_date])
#         sheet.append(["Description", "Amount"])
#         sheet.append(["Advance/Surplus", str(total_final_amount - total_total_amount)])
#         sheet.append(["Total Hold Amount", str(Decimal(total_hold_amount))])
#         sheet.append(["Amount With TDS", str(total_tds_amount)])
#
#         # Auto adjust column widths
#         for col in sheet.columns:
#             max_length = 0
#             col_letter = openpyxl.utils.get_column_letter(col[0].column)
#             for cell in col:
#                 try:
#                     if cell.value:
#                         max_length = max(max_length, len(str(cell.value)))
#                 except:
#                     pass
#             sheet.column_dimensions[col_letter].width = max_length + 2
#
#         workbook.save(output_file)
#         workbook.close()
#
#     finally:
#         cursor.close()
#         connection.close()
#
#     return send_from_directory(output_folder, f"Contractor_Report_{contractor_id}.xlsx", as_attachment=True)


# show report by pmc no


@app.route('/pmc_report/<pmc_no>')
def pmc_report(pmc_no):
    connection = config.get_db_connection()
    cursor = connection.cursor(dictionary=True, buffered=True)

    try:
        # 1. Fetch PMC info using stored procedure
       # cursor.execute("""
        #     SELECT DISTINCT a.PMC_No, a.Village_Id, v.Village_Name, b.Block_Name,
        #            d.District_Name, s.State_Name, sc.Contractor_Id, sc.Contractor_Name,
        #            sc.Address, sc.Mobile_No, sc.PAN_No, sc.Email, sc.Gender,
        #            sc.GST_Registration_Type, sc.GST_No
        #     FROM assign_subcontractors a
        #     INNER JOIN villages v ON a.Village_Id = v.Village_Id
        #     INNER JOIN blocks b ON v.Block_Id = b.Block_Id
        #     INNER JOIN districts d ON b.District_id = d.District_id
        #     INNER JOIN states s ON d.State_Id = s.State_Id
        #     INNER JOIN subcontractors sc ON a.Contractor_Id = sc.Contractor_Id
        #     WHERE a.pmc_no = %s
        # """, (pmc_no,))
        # pmc_info = cursor.fetchone()

        cursor.callproc("GetContractorInfoByPmcNo", (pmc_no,))
        pmc_info = next(cursor.stored_results()).fetchone()

        if not pmc_info:
            return "No PMC found with this number", 404

        # 2. Fetch hold types using stored procedure
         # cursor.execute("""
        #     SELECT DISTINCT ht.hold_type_id, ht.hold_type
        #     FROM invoice_subcontractor_hold_join h
        #     JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
        #     JOIN invoice i ON h.Invoice_Id = i.Invoice_Id
        #     JOIN assign_subcontractors a ON i.PMC_No = a.PMC_No
        #     WHERE a.PMC_No = %s AND a.Contractor_Id = %s
        # """, (pmc_no, pmc_info["Contractor_Id"]))
        # hold_types = cursor.fetchall()
        cursor.callproc("Get_pmc_hold_types", (pmc_no, pmc_info["Contractor_Id"]))
        hold_types = next(cursor.stored_results()).fetchall()
        hold_type_ids = [ht['hold_type_id'] for ht in hold_types]

        # 3. Initialize invoice data
        invoices = []
        hold_amount_total = 0

        # 4. Build invoice query
        if hold_type_ids:
            placeholders = ','.join(['%s'] * len(hold_type_ids))
            query = f"""
                SELECT DISTINCT i.PMC_No, v.Village_Name, i.Work_Type, i.Invoice_Details,
                       i.Invoice_Date, i.Invoice_No, i.Basic_Amount, i.Debit_Amount,
                       i.After_Debit_Amount, i.Amount, i.GST_Amount, i.TDS_Amount, i.SD_Amount,
                       i.On_Commission, i.Hydro_Testing, i.GST_SD_Amount,
                       i.Final_Amount, h.hold_amount, ht.hold_type
                FROM invoice i
                LEFT JOIN villages v ON i.Village_Id = v.Village_Id
                LEFT JOIN assign_subcontractors a ON i.PMC_No = a.PMC_No
                LEFT JOIN invoice_subcontractor_hold_join h ON i.Invoice_Id = h.Invoice_Id
                LEFT JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
                WHERE a.PMC_No = %s AND a.Contractor_Id = %s
                AND (ht.hold_type_id IS NULL OR ht.hold_type_id IN ({placeholders}))
                ORDER BY i.Invoice_Date, i.Invoice_No
            """
            params = [pmc_no, pmc_info["Contractor_Id"]] + hold_type_ids
        else:
            query = """
                SELECT DISTINCT i.PMC_No, v.Village_Name, i.Work_Type, i.Invoice_Details,
                       i.Invoice_Date, i.Invoice_No, i.Basic_Amount, i.Debit_Amount,
                       i.After_Debit_Amount, i.Amount, i.GST_Amount, i.TDS_Amount, i.SD_Amount,
                       i.On_Commission, i.Hydro_Testing, i.GST_SD_Amount, i.Final_Amount
                FROM invoice i
                LEFT JOIN villages v ON i.Village_Id = v.Village_Id
                LEFT JOIN assign_subcontractors a ON i.PMC_No = a.PMC_No
                WHERE a.PMC_No = %s AND a.Contractor_Id = %s
                ORDER BY i.Invoice_Date, i.Invoice_No
            """
            params = [pmc_no, pmc_info["Contractor_Id"]]

        cursor.execute(query, params)
        invoices = cursor.fetchall()

        if hold_type_ids:
            hold_amount_total = sum(row.get('hold_amount', 0) or 0 for row in invoices)

        # 5. Totals from invoices
        total_invo_final = sum(row.get('Final_Amount', 0) or 0 for row in invoices)

        # 6. GST release
        cursor.execute("""
            SELECT pmc_no, invoice_no, basic_amount, final_amount
            FROM gst_release
            WHERE pmc_no = %s
            ORDER BY invoice_no ASC
        """, (pmc_no,))
        gst_rel = cursor.fetchall()
        # gst_rel = cursor.fetchall()
        # cursor.callproc('GetGSTReleaseByPMC', [pmc_no])
        #
        # # Fetch results
        # for result in cursor.stored_results():
        #     gst_rel = result.fetchall()

        total_gst_basic = sum(row.get('basic_amount', 0) or 0 for row in gst_rel)
        total_gst_final = sum(row.get('final_amount', 0) or 0 for row in gst_rel)

        # Hold Release Amount
        cursor.execute("""select * from hold_release where pmc_no=%s""", (pmc_no,))
        hold_release = cursor.fetchall()
        print("All Hold Release ", hold_release)

        # Credit Note

        cursor.execute("select * from credit_note where pmc_no=%s", (pmc_no,))
        credit_note = cursor.fetchall()
        print(credit_note)

        # 7. Payments
        cursor.execute("""
            SELECT pmc_no, invoice_no, Payment_Amount, TDS_Payment_Amount, Total_amount, utr
            FROM payment
            WHERE pmc_no = %s
            ORDER BY invoice_no ASC
        """, (pmc_no,))
        payments = cursor.fetchall()
        # cursor.callproc('GetPaymentByPMC', [pmc_no])
        #
        # for result in cursor.stored_results():
        #     payments = result.fetchall()

        total_pay_amount = sum(row.get('Payment_Amount', 0) or 0 for row in payments)
        total_pay_total = sum(row.get('Total_amount', 0) or 0 for row in payments)

        # 8. Final totals dictionary
        totals = {
            "sum_invo_basic_amt": sum(row.get('Basic_Amount', 0) or 0 for row in invoices),
            "sum_invo_debit_amt": sum(row.get('Debit_Amount', 0) or 0 for row in invoices),
            "sum_invo_after_debit_amt": sum(row.get('After_Debit_Amount', 0) or 0 for row in invoices),
            "sum_invo_amt": sum(row.get('Amount', 0) or 0 for row in invoices),
            "sum_invo_gst_amt": sum(row.get('GST_Amount', 0) or 0 for row in invoices),
            "sum_invo_tds_amt": sum(row.get('TDS_Amount', 0) or 0 for row in invoices),
            "sum_invo_ds_amt": sum(row.get('SD_Amount', 0) or 0 for row in invoices),
            "sum_invo_on_commission": sum(row.get('On_Commission', 0) or 0 for row in invoices),
            "sum_invo_hydro_test": sum(row.get('Hydro_Testing', 0) or 0 for row in invoices),
            "sum_invo_gst_sd_amt": sum(row.get('GST_SD_Amount', 0) or 0 for row in invoices),
            "sum_invo_final_amt": total_invo_final,
            "sum_invo_hold_amt": hold_amount_total,
            "sum_gst_basic_amt": total_gst_basic,
            "sum_gst_final_amt": total_gst_final,
            "sum_pay_payment_amt": total_pay_amount,
            "sum_pay_tds_payment_amt": sum(row.get('TDS_Payment_Amount', 0) or 0 for row in payments),
            "sum_pay_total_amt": total_pay_total
        }

    except Exception as e:
        print(f"Error fetching PMC report: {e}")
        return "An error occurred while fetching PMC report", 500

    finally:
        cursor.close()
        connection.close()

    return render_template(
        'pmc_report.html',
        info=pmc_info,
        invoices=invoices,
        hold_types=hold_types,
        gst_rel=gst_rel,
        payments=payments,
        credit_note=credit_note,
        hold_release=hold_release,
        total=totals
    )


# # Download report by PMC No
# @app.route('/download_pmc_report/<pmc_no>')
# def download_pmc_report(pmc_no):
#     connection = config.get_db_connection()
#     output_folder = "static/download"
#     output_file = os.path.join(output_folder, f"PMC_Report_{pmc_no}.xlsx")

#     if not os.path.exists(output_folder):
#         os.makedirs(output_folder)

#     cursor = connection.cursor(dictionary=True)

#     try:
#         # # Fetch Contractor Details using PMC No
#         # cursor.execute("""
#         #     SELECT DISTINCT s.Contractor_Id, s.Contractor_Name, st.State_Name, d.District_Name, b.Block_Name,
#         #                     s.Mobile_No, s.GST_Registration_Type, s.GST_No, s.PAN_No, s.Email, s.Address
#         #     FROM subcontractors s
#         #     LEFT JOIN assign_subcontractors asg ON s.Contractor_Id = asg.Contractor_Id
#         #     LEFT JOIN villages v ON asg.Village_Id = v.Village_Id
#         #     LEFT JOIN blocks b ON v.Block_Id = b.Block_Id
#         #     LEFT JOIN districts d ON b.District_id = d.District_id
#         #     LEFT JOIN states st ON d.State_Id = st.State_Id
#         #     WHERE asg.PMC_No = %s
#         # """, (pmc_no,))
#         # contractor_info = cursor.fetchone()
#         cursor.callproc('GetContractorDetailsByPMC', [pmc_no])

#         # Now fetch the result:
#         for result in cursor.stored_results():
#             contractor_info = result.fetchone()

#         if not contractor_info:
#             return "No contractor found for this PMC No", 404

#         # # Fetch distinct hold types present for the contractor
#         # cursor.execute("""
#         #     SELECT DISTINCT ht.hold_type_id, ht.hold_type
#         #     FROM invoice_subcontractor_hold_join h
#         #     JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#         #     WHERE h.Contractor_Id = %s
#         # """, (contractor_info["Contractor_Id"],))
#         # hold_types = cursor.fetchall()
#         cursor.callproc('GetHoldTypesByContractor', [contractor_info["Contractor_Id"]])

#         for result in cursor.stored_results():
#             hold_types = result.fetchall()

#         hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}

#         # # # Fetch Invoices & GST Releases
#         # cursor.execute("""
#         #     SELECT DISTINCT i.Invoice_Id, i.PMC_No, v.Village_Name, i.Work_Type, i.Invoice_Details,
#         #            i.Invoice_Date, i.Invoice_No, i.Basic_Amount, i.Debit_Amount,
#         #            i.After_Debit_Amount, i.GST_Amount, i.Amount, i.TDS_Amount, i.SD_Amount,
#         #            i.On_Commission, i.Hydro_Testing, i.GST_SD_Amount, i.Final_Amount,
#         #            g.pmc_no AS gst_pmc_no, g.invoice_no AS gst_invoice_no,
#         #            g.basic_amount AS gst_basic_amount, g.final_amount AS gst_final_amount
#         #     FROM invoice i
#         #     LEFT JOIN assign_subcontractors asg ON i.PMC_No = asg.PMC_No
#         #     LEFT JOIN villages v ON i.Village_Id = v.Village_Id
#         #     LEFT JOIN gst_release g ON i.PMC_No = g.pmc_no AND i.Invoice_No = g.invoice_no
#         #     WHERE asg.PMC_No = %s
#         #     ORDER BY i.Invoice_Date, i.Invoice_No
#         # """, (pmc_no,))
#         # invoices = cursor.fetchall()

#         cursor.callproc('GetInvoicesAndGstReleaseByPmcNo', [pmc_no])

#         for result in cursor.stored_results():
#             invoices = result.fetchall()
#         print("pmc_report invoice data:",invoices)

#         # cursor.callproc('GetInvoicesAndGSTReleasesByPMC', [pmc_no])

#         # for result in cursor.stored_results():
#         #     invoices = result.fetchall()

#         # # Fetch Hold Amounts separately
#         # cursor.execute("""
#         #     SELECT h.Invoice_Id, ht.hold_type_id, h.hold_amount
#         #     FROM invoice_subcontractor_hold_join h
#         #     JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
#         #     WHERE h.Contractor_Id = %s
#         # """, (contractor_info["Contractor_Id"],))
#         # hold_amounts = cursor.fetchall()
#         cursor.callproc('GetHoldAmountsByContractor', [contractor_info["Contractor_Id"]])

#         for result in cursor.stored_results():
#             hold_amounts = result.fetchall()

#         # Create a mapping of invoice_id to hold amounts by type
#         hold_data = {}
#         for h in hold_amounts:
#             hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']

#         # # Fetch all Payments for the PMC number
#         # cursor.execute("""
#         #     SELECT pmc_no, invoice_no, Payment_Amount, TDS_Payment_Amount, Total_amount, UTR
#         #     FROM payment
#         #     WHERE pmc_no = %s
#         #     ORDER BY invoice_no
#         # """, (pmc_no,))
#         # all_payments = cursor.fetchall()
#         cursor.callproc('GetAllPaymentsByPMC', [pmc_no])

#         for result in cursor.stored_results():
#             all_payments = result.fetchall()

#         # Organize payments by Invoice No (both regular and GST release notes)
#         payments_map = {}
#         extra_payments = []
#         for pay in all_payments:
#             if pay['invoice_no']:
#                 key = pay['invoice_no']
#                 if key not in payments_map:
#                     payments_map[key] = []
#                 payments_map[key].append(pay)
#             else:
#                 extra_payments.append(pay)

#         # Create Excel workbook
#         workbook = openpyxl.Workbook()
#         sheet = workbook.active
#         sheet.title = "PMC Report"

#         # Write Contractor Details
#         sheet.append(["", "", "Laxmi Civil Engineering Services PVT. LTD.", "", ""])
#         sheet.append(
#             ["Contractor Name", contractor_info["Contractor_Name"], " ", "GST No", contractor_info["GST_No"], " ",
#              "GST Type", contractor_info["GST_Registration_Type"]])
#         sheet.append(["State", contractor_info["State_Name"], " ", "PAN No", contractor_info["PAN_No"], " ", "Address",
#                       contractor_info["Address"]])
#         sheet.append(["District", contractor_info["District_Name"], " ", "Mobile No", contractor_info["Mobile_No"]])
#         sheet.append(["Block", contractor_info["Block_Name"], " ", "Email", contractor_info["Email"]])
#         sheet.append([])

#         # Table Headers - include all hold types as separate columns
#         base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
#                         "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
#                         "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]

#         hold_headers = [ht['hold_type'] for ht in hold_types]

#         payment_headers = ["Final Amount", "Payment Amount", "TDS Payment", "Total Paid", "UTR"]

#         sheet.append(base_headers + hold_headers + payment_headers)

#         seen_invoices = set()
#         seen_gst_notes = set()
#         processed_payments = set()

#         # Process invoices
#         for inv in invoices:
#             invoice_no = inv["Invoice_No"]
#             payments = payments_map.get(invoice_no, [])

#             # Process invoice row with first payment (if exists)
#             if invoice_no not in seen_invoices:
#                 seen_invoices.add(invoice_no)
#                 first_payment = payments[0] if len(payments) > 0 else None

#                 # Base invoice data
#                 row = [
#                     pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
#                     inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
#                     inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
#                     inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
#                 ]

#                 # Add hold amounts for each hold type
#                 invoice_holds = hold_data.get(inv["Invoice_Id"], {})
#                 for ht_id in hold_type_map.keys():
#                     row.append(invoice_holds.get(ht_id, ""))

#                 # Add payment information
#                 row += [
#                     inv["Final_Amount"],
#                     first_payment["Payment_Amount"] if first_payment else "",
#                     first_payment["TDS_Payment_Amount"] if first_payment else "",
#                     first_payment["Total_amount"] if first_payment else "",
#                     first_payment["UTR"] if first_payment else ""
#                 ]

#                 sheet.append(row)

#                 if first_payment:
#                     payment_id = f"{invoice_no}-{first_payment['Payment_Amount']}-{first_payment.get('UTR', '')}"
#                     processed_payments.add(payment_id)

#             # Process GST release if exists (only if we have a matching GST record)
#             if inv["gst_pmc_no"] and inv["gst_invoice_no"] and inv["gst_invoice_no"] not in seen_gst_notes:
#                 seen_gst_notes.add(inv["gst_invoice_no"])

#                 # Find the payment that matches this GST release
#                 gst_payment = None
#                 for payment in payments[1:]:  # Skip first payment (already used for invoice)
#                     if payment['invoice_no'] == inv["gst_invoice_no"]:
#                         gst_payment = payment
#                         break

#                 # If no payment found in the invoice's payments, check all payments
#                 if not gst_payment:
#                     gst_payments = payments_map.get(inv["gst_invoice_no"], [])
#                     if gst_payments:
#                         gst_payment = gst_payments[0]

#                 # GST release row
#                 row = [
#                     pmc_no, "", "", "GST Release Note", "", inv["gst_invoice_no"],
#                     inv["gst_basic_amount"], "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                 ]

#                 # Empty holds for GST release
#                 row += ["" for _ in hold_headers]

#                 # Add payment information
#                 row += [
#                     inv["gst_final_amount"],
#                     gst_payment["Payment_Amount"] if gst_payment else "",
#                     gst_payment["TDS_Payment_Amount"] if gst_payment else "",
#                     gst_payment["Total_amount"] if gst_payment else "",
#                     gst_payment["UTR"] if gst_payment else ""
#                 ]

#                 sheet.append(row)

#                 if gst_payment:
#                     payment_id = f"{inv['gst_invoice_no']}-{gst_payment['Payment_Amount']}-{gst_payment.get('UTR', '')}"
#                     processed_payments.add(payment_id)

#             # Process remaining payments as extra payments
#             for payment in payments[1:]:
#                 payment_id = f"{payment['invoice_no']}-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                 if payment_id not in processed_payments:
#                     row = [
#                         pmc_no, "", "", "", "", payment['invoice_no'],
#                         "", "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                     ]

#                     # Empty holds for extra payments
#                     row += ["" for _ in hold_headers]

#                     # Add payment information
#                     row += [
#                         "",
#                         payment["Payment_Amount"],
#                         payment["TDS_Payment_Amount"],
#                         payment["Total_amount"],
#                         payment["UTR"]
#                     ]

#                     sheet.append(row)
#                     processed_payments.add(payment_id)

#         # Process extra payments (null invoice_no)
#         for payment in extra_payments:
#             payment_id = f"null-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#             if payment_id not in processed_payments:
#                 row = [
#                     pmc_no, "", "", "", "", "",
#                     "", "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                 ]

#                 # Empty holds for null invoice payments
#                 row += ["" for _ in hold_headers]

#                 # Add payment information
#                 row += [
#                     "",
#                     payment["Payment_Amount"],
#                     payment["TDS_Payment_Amount"],
#                     payment["Total_amount"],
#                     payment["UTR"]
#                 ]

#                 sheet.append(row)
#                 processed_payments.add(payment_id)

#         # Calculate totals
#         total_basic_amount = 0
#         total_tds_amount = 0
#         total_sd_amount = 0
#         total_on_commission = 0
#         total_hold_amount = 0
#         total_final_amount = 0
#         total_payment_amount = 0
#         total_tds_payment_amount = 0
#         total_total_paid = 0

#         for row in sheet.iter_rows(min_row=2, max_row=sheet.max_row, values_only=True):
#             try:
#                 total_basic_amount += float(row[6] or 0)  # Basic_Amount
#                 total_tds_amount += float(row[11] or 0)  # TDS_Amount
#                 total_sd_amount += float(row[12] or 0)  # SD_Amount
#                 total_on_commission += float(row[13] or 0)  # On_Commission
#                 total_final_amount += float(row[-5] or 0)  # Final_Amount
#                 total_payment_amount += float(row[-4] or 0)  # Payment_Amount
#                 total_tds_payment_amount += float(row[-3] or 0)  # TDS_Payment
#                 total_total_paid += float(row[-2] or 0)  # Total_Paid

#                 # Sum of hold amounts
#                 hold_start_col = len(base_headers)
#                 hold_end_col = hold_start_col + len(hold_headers)
#                 total_hold_amount += sum(float(row[i] or 0) for i in range(hold_start_col, hold_end_col))
#             except (ValueError, IndexError, TypeError):
#                 continue

#         # Append totals row
#         totals_row = [
#             "TOTAL", "", "", "", "", "",
#             total_basic_amount, "", "", "", "", total_tds_amount, total_sd_amount,
#             total_on_commission, "", "",  # Empty GST SD Amount
#         ]

#         # Add hold totals
#         totals_row += [total_hold_amount] + [""] * (len(hold_headers) - 1)

#         # Add payment totals
#         totals_row += [
#             total_final_amount,
#             total_payment_amount,
#             total_tds_payment_amount,
#             total_total_paid,
#             ""  # UTR column remains empty
#         ]

#         sheet.append([])
#         sheet.append(totals_row)

#         # Make totals row bold
#         for cell in sheet[sheet.max_row]:
#             cell.font = Font(bold=True)

#         # Save Excel file
#         workbook.save(output_file)
#         workbook.close()

#     finally:
#         cursor.close()
#         connection.close()

#     return send_from_directory(output_folder, f"PMC_Report_{pmc_no}.xlsx", as_attachment=True)



# @app.route('/download_pmc_report/<pmc_no>')
# def download_pmc_report(pmc_no):
#     connection = config.get_db_connection()
#     output_folder = "static/download"
#     output_file = os.path.join(output_folder, f"PMC_Report_{pmc_no}.xlsx")
#
#     if not os.path.exists(output_folder):
#         os.makedirs(output_folder)
#
#     cursor = connection.cursor(dictionary=True)
#
#     try:
#         cursor.callproc('GetContractorDetailsByPMC', [pmc_no])
#
#         for result in cursor.stored_results():
#             contractor_info = result.fetchone()
#
#         if not contractor_info:
#             return "No contractor found for this PMC No", 404
#
#         cursor.callproc('GetHoldTypesByContractor', [contractor_info["Contractor_Id"]])
#
#         for result in cursor.stored_results():
#             hold_types = result.fetchall()
#
#         hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
#
#         cursor.callproc('GetInvoicesAndGstReleaseByPmcNo', [pmc_no])
#
#         for result in cursor.stored_results():
#             invoices = result.fetchall()
#         total_tds=Decimal('0.00')
#         final_amount=Decimal('0.00')
#         # total_hold_amount=Decimal('0.00')
#         for data in invoices:
#             total_tds=total_tds+data.get('TDS_Amount',Decimal('0.00'))
#             final_amount=final_amount+data.get('Final_Amount',Decimal('0.00'))
#
#         cursor.callproc('GetHoldAmountsByContractor', [contractor_info["Contractor_Id"]])
#
#         for result in cursor.stored_results():
#             hold_amounts = result.fetchall()
#
#         hold_data = {}
#         for h in hold_amounts:
#             hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
#
#         cursor.callproc('GetAllPaymentsByPMC', [pmc_no])
#
#         for result in cursor.stored_results():
#             all_payments = result.fetchall()
#         total_amount=Decimal('0.00')
#         for d in all_payments:
#             total_amount=total_amount+ d.get('Total_Amount',Decimal('0.00'))
#         total_amount_paid= final_amount- total_amount;
#         payments_map = {}
#         extra_payments = []
#         for pay in all_payments:
#             if pay['invoice_no']:
#                 key = pay['invoice_no']
#                 if key not in payments_map:
#                     payments_map[key] = []
#                 payments_map[key].append(pay)
#             else:
#                 extra_payments.append(pay)
#
#         workbook = openpyxl.Workbook()
#         sheet = workbook.active
#         sheet.title = "PMC Report"
#
#         # Write Contractor Details
#         sheet.append(["", "", "Laxmi Civil Engineering Services PVT. LTD.", "", ""])
#         sheet.append(
#             ["Contractor Name", contractor_info["Contractor_Name"], " ", "GST No", contractor_info["GST_No"], " ",
#              "GST Type", contractor_info["GST_Registration_Type"]])
#         sheet.append(["State", contractor_info["State_Name"], " ", "PAN No", contractor_info["PAN_No"], " ", "Address",
#                       contractor_info["Address"]])
#         sheet.append(["District", contractor_info["District_Name"], " ", "Mobile No", contractor_info["Mobile_No"]])
#         sheet.append(["Block", contractor_info["Block_Name"], " ", "Email", contractor_info["Email"]])
#         sheet.append([])
#
#         # Table Headers - include all hold types as separate columns
#         base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
#                         "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
#                         "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]
#
#         hold_headers = [ht['hold_type'] for ht in hold_types]
#
#         payment_headers = ["Final Amount", "Payment Amount", "TDS Payment", "Total Paid", "UTR"]
#
#         sheet.append(base_headers + hold_headers + payment_headers)
#
#         seen_invoices = set()
#         seen_gst_notes = set()
#         processed_payments = set()
#
#         # Process invoices
#         for inv in invoices:
#             invoice_no = inv["Invoice_No"]
#             payments = payments_map.get(invoice_no, [])
#
#             # Process invoice row with first payment (if exists)
#             if invoice_no not in seen_invoices:
#                 seen_invoices.add(invoice_no)
#                 first_payment = payments[0] if len(payments) > 0 else None
#
#                 # Base invoice data
#                 row = [
#                     pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
#                     inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
#                     inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
#                     inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
#                 ]
#
#                 # Add hold amounts for each hold type
#                 invoice_holds = hold_data.get(inv["Invoice_Id"], {})
#                 for ht_id in hold_type_map.keys():
#                     row.append(invoice_holds.get(ht_id, ""))
#
#                 # Add payment information
#                 row += [
#                     inv["Final_Amount"],
#                     first_payment["Payment_Amount"] if first_payment else "",
#                     first_payment["TDS_Payment_Amount"] if first_payment else "",
#                     first_payment["Total_amount"] if first_payment else "",
#                     first_payment["UTR"] if first_payment else ""
#                 ]
#
#                 sheet.append(row)
#
#                 if first_payment:
#                     payment_id = f"{invoice_no}-{first_payment['Payment_Amount']}-{first_payment.get('UTR', '')}"
#                     processed_payments.add(payment_id)
#
#             # Process GST release if exists (only if we have a matching GST record)
#             if inv["gst_pmc_no"] and inv["gst_invoice_no"] and inv["gst_invoice_no"] not in seen_gst_notes:
#                 seen_gst_notes.add(inv["gst_invoice_no"])
#
#                 # Find the payment that matches this GST release
#                 gst_payment = None
#                 for payment in payments[1:]:  # Skip first payment (already used for invoice)
#                     if payment['invoice_no'] == inv["gst_invoice_no"]:
#                         gst_payment = payment
#                         break
#
#                 # If no payment found in the invoice's payments, check all payments
#                 if not gst_payment:
#                     gst_payments = payments_map.get(inv["gst_invoice_no"], [])
#                     if gst_payments:
#                         gst_payment = gst_payments[0]
#
#                 # GST release row (this will be in the same row, after the invoice information)
#                 gst_row = [
#                     pmc_no, "", "", "GST Release Note", "", inv["gst_invoice_no"],
#                     inv["gst_basic_amount"], "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                 ]
#
#                 # Empty holds for GST release
#                 gst_row += ["" for _ in hold_headers]
#
#                 # Add GST payment information (same columns as invoice payment information)
#                 gst_row += [
#                     inv["gst_final_amount"],
#                     gst_payment["Payment_Amount"] if gst_payment else "",
#                     gst_payment["TDS_Payment_Amount"] if gst_payment else "",
#                     gst_payment["Total_amount"] if gst_payment else "",
#                     gst_payment["UTR"] if gst_payment else ""
#                 ]
#
#                 sheet.append(gst_row)
#
#                 if gst_payment:
#                     payment_id = f"{inv['gst_invoice_no']}-{gst_payment['Payment_Amount']}-{gst_payment.get('UTR', '')}"
#                     processed_payments.add(payment_id)
#
#             # Process remaining payments as extra payments (if any)
#             for payment in payments[1:]:
#                 payment_id = f"{payment['invoice_no']}-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                 if payment_id not in processed_payments:
#                     row = [
#                         pmc_no, "", "", "", "", payment['invoice_no'],
#                         "", "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                     ]
#
#                     # Empty holds for extra payments
#                     row += ["" for _ in hold_headers]
#
#                     # Add payment information
#                     row += [
#                         "",
#                         payment["Payment_Amount"],
#                         payment["TDS_Payment_Amount"],
#                         payment["Total_amount"],
#                         payment["UTR"]
#                     ]
#
#                     sheet.append(row)
#                     processed_payments.add(payment_id)
#
#         # Process extra payments (null invoice_no)
#         for payment in extra_payments:
#             payment_id = f"null-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#             if payment_id not in processed_payments:
#                 row = [
#                     pmc_no, "", "", "", "", "",
#                     "", "", "", "", "", "", "", "", "", ""  # Empty GST SD Amount
#                 ]
#
#                 # Empty holds for null invoice payments
#                 row += ["" for _ in hold_headers]
#
#                 # Add payment information
#                 row += [
#                     "",
#                     payment["Payment_Amount"],
#                     payment["TDS_Payment_Amount"],
#                     payment["Total_amount"],
#                     payment["UTR"]
#                 ]
#
#                 sheet.append(row)
#                 processed_payments.add(payment_id)
#
#         # Calculate totals
#         total_basic_amount = 0
#         total_tds_amount = 0
#         total_sd_amount = 0
#         total_on_commission = 0
#         total_hold_amount = 0
#         total_final_amount = 0
#         total_payment_amount = 0
#         total_tds_payment_amount = 0
#         total_total_paid = 0
#
#         for row in sheet.iter_rows(min_row=2, max_row=sheet.max_row, values_only=True):
#             try:
#                 total_basic_amount += float(row[6] or 0)  # Basic_Amount
#                 total_tds_amount += float(row[11] or 0)  # TDS_Amount
#                 total_sd_amount += float(row[12] or 0)  # SD_Amount
#                 total_on_commission += float(row[13] or 0)  # On_Commission
#                 total_final_amount += float(row[-5] or 0)  # Final_Amount
#                 total_payment_amount += float(row[-4] or 0)  # Payment_Amount
#                 total_tds_payment_amount += float(row[-3] or 0)  # TDS_Payment
#                 total_total_paid += float(row[-2] or 0)  # Total_Paid
#
#                 # Sum of hold amounts
#                 hold_start_col = len(base_headers)
#                 hold_end_col = hold_start_col + len(hold_headers)
#                 total_hold_amount += sum(float(row[i] or 0) for i in range(hold_start_col, hold_end_col))
#             except (ValueError, IndexError, TypeError):
#                 continue
#
#         # Append totals row
#         totals_row = [
#             "TOTAL", "", "", "", "", "",
#             total_basic_amount, "", "", "", "", total_tds_amount, total_sd_amount,
#             total_on_commission, "", "",  # Empty GST SD Amount
#         ]
#         if  hold_headers:
#             totals_row += [total_hold_amount] + [""] * (len(hold_headers) - 1)
#
#         # Add payment totals
#         totals_row += [
#             total_final_amount,
#             total_payment_amount,
#             total_tds_payment_amount,
#             total_total_paid,
#             ""  # UTR column remains empty
#         ]
#
#         sheet.append([])
#         sheet.append(totals_row)
#         #new code added for small chart---summary
#         total_hold_amount=Decimal('0.00')
#         for d in invoices:
#             total_hold_amount = total_hold_amount + d.get('SD_Amount', Decimal('0.00')) + d.get('On_Commission',
#                                                                                                       Decimal(
#                                                                                                           '0.00')) + d.get(
#                 'Hydro_Testing', Decimal('0.00'))
#         for data in hold_amounts:
#             total_hold_amount = total_hold_amount + data.get('hold_amount', Decimal('0.00'))
#         print("Total Hold Amount after adding the hold amount ", total_hold_amount)
#
#         # Add payment information
#         # Get today's date
#         today_date = datetime.today().strftime('%A,%Y-%m-%d')
#         # Add headers (optional)
#         sheet.append(["Contractor Name", contractor_info["Contractor_Name"]])
#         sheet.append(["Date", today_date])
#         sheet.append(["Description", "Amount"])
#         # Add your values
#         sheet.append(["Advance/Surplus", str(total_final_amount-total_payment_amount)])
#         sheet.append(["Total Hold Amount", str(total_hold_amount)])
#         sheet.append(["Amount With TDS", str(total_tds_payment_amount)])
# # new coded ended here for summary chart
#         # Make totals row bold
#         for cell in sheet[sheet.max_row]:
#             cell.font = Font(bold=True)
#
#         # Save Excel file
#         workbook.save(output_file)
#         workbook.close()
#
#     finally:
#         cursor.close()
#         connection.close()
#
#     return send_from_directory(output_folder, f"PMC_Report_{pmc_no}.xlsx", as_attachment=True)

# @app.route('/download_pmc_report/<pmc_no>')
# def download_pmc_report(pmc_no):
#     connection = config.get_db_connection()
#     output_folder = "static/download"
#     output_file = os.path.join(output_folder, f"PMC_Report_{pmc_no}.xlsx")
#
#     if not os.path.exists(output_folder):
#         os.makedirs(output_folder)
#
#     cursor = connection.cursor(dictionary=True)
#
#     try:
#         cursor.callproc('GetContractorDetailsByPMC', [pmc_no])
#         contractor_info = next(cursor.stored_results()).fetchone()
#
#         if not contractor_info:
#             return "No contractor found for this PMC No", 404
#
#         cursor.callproc('GetHoldTypesByContractor', [contractor_info["Contractor_Id"]])
#         hold_types = next(cursor.stored_results()).fetchall()
#         hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
#
#         cursor.callproc('GetInvoicesAndGstReleaseByPmcNo', [pmc_no])
#         invoices = next(cursor.stored_results()).fetchall()
#
#         cursor.callproc('GetHoldAmountsByContractor', [contractor_info["Contractor_Id"]])
#         hold_amounts = next(cursor.stored_results()).fetchall()
#         hold_data = {}
#         for h in hold_amounts:
#             hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
#
#         cursor.callproc('GetAllPaymentsByPMC', [pmc_no])
#         all_payments = next(cursor.stored_results()).fetchall()
#
#         payments_map = {}
#         extra_payments = []
#         for pay in all_payments:
#             if pay['invoice_no']:
#                 payments_map.setdefault(pay['invoice_no'], []).append(pay)
#             else:
#                 extra_payments.append(pay)
#
#         workbook = openpyxl.Workbook()
#         sheet = workbook.active
#         sheet.title = "PMC Report"
#
#         # Write contractor header
#         sheet.append(["", "", "Laxmi Civil Engineering Services PVT. LTD."])
#         sheet.append(["Contractor Name", contractor_info["Contractor_Name"], "", "GST No", contractor_info["GST_No"], "", "GST Type", contractor_info["GST_Registration_Type"]])
#         sheet.append(["State", contractor_info["State_Name"], "", "PAN No", contractor_info["PAN_No"], "", "Address", contractor_info["Address"]])
#         sheet.append(["District", contractor_info["District_Name"], "", "Mobile No", contractor_info["Mobile_No"]])
#         sheet.append(["Block", contractor_info["Block_Name"], "", "Email", contractor_info["Email"]])
#         sheet.append([])
#
#         base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
#                         "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
#                         "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]
#
#         hold_headers = [ht['hold_type'] for ht in hold_types]
#         payment_headers = ["Final Amount", "Payment Amount", "TDS Payment", "Total Paid", "UTR"]
#         sheet.append(base_headers + hold_headers + payment_headers)
#         # Style the headers
#         header_fill=PatternFill(start_color="ADD8E6",end_color="ADD8E6",fill_type="solid")
#         header_font=Font(bold=True)
#         for cell in sheet[sheet.max_row]:
#             cell.font=header_font
#             cell.fill=header_fill
#
#         seen_invoices = set()
#         seen_gst_notes = set()
#         processed_payments = set()
#
#         for inv in invoices:
#             invoice_no = inv["Invoice_No"]
#             payments = payments_map.get(invoice_no, [])
#
#             if invoice_no not in seen_invoices:
#                 seen_invoices.add(invoice_no)
#                 first_payment = payments[0] if payments else None
#
#                 row = [
#                     pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
#                     inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
#                     inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
#                     inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
#                 ]
#
#                 invoice_holds = hold_data.get(inv["Invoice_Id"], {})
#                 for ht_id in hold_type_map.keys():
#                     row.append(invoice_holds.get(ht_id, ""))
#
#                 row += [
#                     inv["Final_Amount"],
#                     first_payment["Payment_Amount"] if first_payment else "",
#                     first_payment["TDS_Payment_Amount"] if first_payment else "",
#                     first_payment["Total_amount"] if first_payment else "",
#                     first_payment["UTR"] if first_payment else ""
#                 ]
#
#                 sheet.append(row)
#
#                 if first_payment:
#                     processed_payments.add(f"{invoice_no}-{first_payment['Payment_Amount']}-{first_payment.get('UTR', '')}")
#
#             if inv["gst_pmc_no"] and inv["gst_invoice_no"] and inv["gst_invoice_no"] not in seen_gst_notes:
#                 seen_gst_notes.add(inv["gst_invoice_no"])
#                 gst_payment = None
#                 for payment in payments[1:]:
#                     if payment['invoice_no'] == inv["gst_invoice_no"]:
#                         gst_payment = payment
#                         break
#                 if not gst_payment:
#                     gst_payment = payments_map.get(inv["gst_invoice_no"], [None])[0]
#
#                 gst_row = [
#                     pmc_no, "", "", "GST Release Note", "", inv["gst_invoice_no"],
#                     inv["gst_basic_amount"], "", "", "", "", "", "", "", "", ""
#                 ]
#                 gst_row += ["" for _ in hold_headers]
#                 gst_row += [
#                     inv["gst_final_amount"],
#                     gst_payment["Payment_Amount"] if gst_payment else "",
#                     gst_payment["TDS_Payment_Amount"] if gst_payment else "",
#                     gst_payment["Total_amount"] if gst_payment else "",
#                     gst_payment["UTR"] if gst_payment else ""
#                 ]
#                 sheet.append(gst_row)
#                 if gst_payment:
#                     processed_payments.add(f"{inv['gst_invoice_no']}-{gst_payment['Payment_Amount']}-{gst_payment.get('UTR', '')}")
#
#             for payment in payments[1:]:
#                 payment_id = f"{payment['invoice_no']}-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
#                 if payment_id not in processed_payments:
#                     row = [pmc_no, "", "", "", "", payment['invoice_no']] + [""] * 10
#                     row += ["" for _ in hold_headers]
#                     row += [
#                         "", payment["Payment_Amount"], payment["TDS_Payment_Amount"],
#                         payment["Total_amount"], payment["UTR"]
#                     ]
#                     sheet.append(row)
#                     processed_payments.add(payment_id)
#
#         for payment in extra_payments:
#             row = [pmc_no, "", "", "", "", ""] + [""] * 10
#             row += ["" for _ in hold_headers]
#             row += [
#                 "", payment["Payment_Amount"], payment["TDS_Payment_Amount"],
#                 payment["Total_amount"], payment["UTR"]
#             ]
#             sheet.append(row)
#
#         # Totals
#         total_basic_amount = Decimal('0.00')
#         total_tds_amount = Decimal('0.00')
#         total_sd_amount = Decimal('0.00')
#         total_on_commission = Decimal('0.00')
#         total_final_amount = Decimal('0.00')
#         total_payment_amount = Decimal('0.00')
#         total_tds_payment_amount = Decimal('0.00')
#         total_total_paid = Decimal('0.00')
#         total_hold_amount_dynamic = Decimal('0.00')
#
#         for row in sheet.iter_rows(min_row=8, max_row=sheet.max_row, values_only=True):
#             try:
#                 total_basic_amount += Decimal(str(row[6] or 0))
#                 total_tds_amount += Decimal(str(row[11] or 0))
#                 total_sd_amount += Decimal(str(row[12] or 0))
#                 total_on_commission += Decimal(str(row[13] or 0))
#                 total_final_amount += Decimal(str(row[-5] or 0))
#                 total_payment_amount += Decimal(str(row[-4] or 0))
#                 total_tds_payment_amount += Decimal(str(row[-3] or 0))
#                 total_total_paid += Decimal(str(row[-2] or 0))
#
#                 for i in range(len(base_headers), len(base_headers) + len(hold_headers)):
#                     total_hold_amount_dynamic += Decimal(str(row[i] or 0))
#             except:
#                 continue
#
#         totals_row = [
#             "TOTAL", "", "", "", "", "",
#             total_basic_amount, "", "", "", "", total_tds_amount, total_sd_amount,
#             total_on_commission, "", ""
#         ]
#         totals_row += [total_hold_amount_dynamic] + [""] * (len(hold_headers) - 1)
#         totals_row += [
#             total_final_amount,
#             total_payment_amount,
#             total_tds_payment_amount,
#             total_total_paid,
#             ""
#         ]
#
#         sheet.append([])
#         sheet.append(totals_row)
#
#         # Summary
#         summary_hold = Decimal('0.00')
#         for d in invoices:
#             summary_hold += Decimal(str(d.get('SD_Amount', 0.00))) + Decimal(str(d.get('On_Commission', 0.00))) + Decimal(str(d.get('Hydro_Testing', 0.00)))
#         for h in hold_amounts:
#             summary_hold += Decimal(str(h.get('hold_amount', 0.00)))
#
#         sheet.append([])
#         today = datetime.today().strftime('%A, %Y-%m-%d')
#         sheet.append(["Contractor Name", contractor_info["Contractor_Name"]])
#         sheet.append(["Date", today])
#         sheet.append(["Description", "Amount"])
#         sheet.append(["Advance/Surplus", str(total_final_amount - total_payment_amount)])
#         sheet.append(["Total Hold Amount", str(summary_hold)])
#         sheet.append(["Amount With TDS", str(total_payment_amount + total_tds_payment_amount)])
#
#         for cell in sheet[sheet.max_row]:
#             cell.font = Font(bold=True)
#
#         workbook.save(output_file)
#         workbook.close()
#
#     finally:
#         cursor.close()
#         connection.close()
#
#     return send_from_directory(output_folder, f"PMC_Report_{pmc_no}.xlsx", as_attachment=True)


@app.route('/download_pmc_report/<pmc_no>')
def download_pmc_report(pmc_no):
    connection = config.get_db_connection()
    output_folder = "static/download"
    output_file = os.path.join(output_folder, f"PMC_Report_{pmc_no}.xlsx")

    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    cursor = connection.cursor(dictionary=True)

    try:
        cursor.callproc('GetContractorDetailsByPMC', [pmc_no])
        contractor_info = next(cursor.stored_results()).fetchone()

        if not contractor_info:
            return "No contractor found for this PMC No", 404

        cursor.callproc('GetHoldTypesByContractor', [contractor_info["Contractor_Id"]])
        hold_types = next(cursor.stored_results()).fetchall()
        hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}

        cursor.callproc('GetInvoicesAndGstReleaseByPmcNo', [pmc_no])
        invoices = next(cursor.stored_results()).fetchall()

        # Credit Note
        # Credit Note Fetch
        cursor.execute("""
                            SELECT PMC_No, Invoice_Details, Basic_Amount, Debit_Amount, After_Debit_Amount,
                                   GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR, Invoice_No
                            FROM credit_note
                            WHERE Contractor_Id = %s
                        """, (pmc_no,))

        credit_notes = cursor.fetchall()


        # Build map by (PMC_No, Invoice_No)
        credit_note_map = {}
        for cn in credit_notes:
            key = (cn["PMC_No"], cn["Invoice_No"])  # Use correct casing!
            credit_note_map.setdefault(key, []).append(cn)

        # Track already appended credit notes
        appended_credit_keys = set()

        cursor.callproc('GetHoldAmountsByContractor', [contractor_info["Contractor_Id"]])
        hold_amounts = next(cursor.stored_results()).fetchall()
        hold_data = {}
        for h in hold_amounts:
            hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']

        cursor.callproc('GetAllPaymentsByPMC', [pmc_no])
        all_payments = next(cursor.stored_results()).fetchall()


        payments_map = {}
        extra_payments = []
        for pay in all_payments:
            if pay['invoice_no']:
                payments_map.setdefault(pay['invoice_no'], []).append(pay)
            else:
                extra_payments.append(pay)



        workbook = openpyxl.Workbook()
        sheet = workbook.active
        sheet.title = "PMC Report"

        # Write contractor header
        sheet.append(["", "", "Laxmi Civil Engineering Services PVT. LTD."])
        sheet.append(["Contractor Name", contractor_info["Contractor_Name"], "", "GST No", contractor_info["GST_No"], "", "GST Type", contractor_info["GST_Registration_Type"]])
        sheet.append(["State", contractor_info["State_Name"], "", "PAN No", contractor_info["PAN_No"], "", "Address", contractor_info["Address"]])
        sheet.append(["District", contractor_info["District_Name"], "", "Mobile No", contractor_info["Mobile_No"]])
        sheet.append(["Block", contractor_info["Block_Name"], "", "Email", contractor_info["Email"]])
        sheet.append([])

        base_headers = ["PMC No", "Village", "Work Type", "Invoice Details", "Invoice Date", "Invoice No",
                        "Basic Amount", "Debit", "After Debit Amount", "GST (18%)", "Amount", "TDS (1%)",
                        "SD (5%)", "On Commission", "Hydro Testing", "GST SD Amount"]

        hold_headers = [ht['hold_type'] for ht in hold_types]
        payment_headers = ["Final Amount", "Payment Amount", "TDS Payment", "Total Paid", "UTR"]
        sheet.append(base_headers + hold_headers + payment_headers)
        # Style the headers
        header_fill=PatternFill(start_color="ADD8E6",end_color="ADD8E6",fill_type="solid")
        header_font=Font(bold=True)
        for cell in sheet[sheet.max_row]:
            cell.font=header_font
            cell.fill=header_fill

        seen_invoices = set()
        seen_gst_notes = set()
        processed_payments = set()

        for inv in invoices:
            invoice_no = inv["Invoice_No"]
            payments = payments_map.get(invoice_no, [])

            if invoice_no not in seen_invoices:
                seen_invoices.add(invoice_no)
                first_payment = payments[0] if payments else None

                row = [
                    pmc_no, inv["Village_Name"], inv["Work_Type"], inv["Invoice_Details"],
                    inv["Invoice_Date"], invoice_no, inv["Basic_Amount"], inv["Debit_Amount"],
                    inv["After_Debit_Amount"], inv["GST_Amount"], inv["Amount"], inv["TDS_Amount"],
                    inv["SD_Amount"], inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
                ]

                invoice_holds = hold_data.get(inv["Invoice_Id"], {})
                for ht_id in hold_type_map.keys():
                    row.append(invoice_holds.get(ht_id, ""))

                row += [
                    inv["Final_Amount"],
                    first_payment["Payment_Amount"] if first_payment else "",
                    first_payment["TDS_Payment_Amount"] if first_payment else "",
                    first_payment["Total_amount"] if first_payment else "",
                    first_payment["UTR"] if first_payment else ""
                ]

                sheet.append(row)

                if first_payment:
                    processed_payments.add(f"{invoice_no}-{first_payment['Payment_Amount']}-{first_payment.get('UTR', '')}")

            if inv["gst_pmc_no"] and inv["gst_invoice_no"] and inv["gst_invoice_no"] not in seen_gst_notes:
                seen_gst_notes.add(inv["gst_invoice_no"])
                gst_payment = None
                for payment in payments[1:]:
                    if payment['invoice_no'] == inv["gst_invoice_no"]:
                        gst_payment = payment
                        break
                if not gst_payment:
                    gst_payment = payments_map.get(inv["gst_invoice_no"], [None])[0]

                gst_row = [
                    pmc_no, "", "", "GST Release Note", "", inv["gst_invoice_no"],
                    inv["gst_basic_amount"], "", "", "", "", "", "", "", "", ""
                ]
                gst_row += ["" for _ in hold_headers]
                gst_row += [
                    inv["gst_final_amount"],
                    gst_payment["Payment_Amount"] if gst_payment else "",
                    gst_payment["TDS_Payment_Amount"] if gst_payment else "",
                    gst_payment["Total_amount"] if gst_payment else "",
                    gst_payment["UTR"] if gst_payment else ""
                ]
                sheet.append(gst_row)
                if gst_payment:
                    processed_payments.add(f"{inv['gst_invoice_no']}-{gst_payment['Payment_Amount']}-{gst_payment.get('UTR', '')}")

            for payment in payments[1:]:
                payment_id = f"{payment['invoice_no']}-{payment['Payment_Amount']}-{payment.get('UTR', '')}"
                if payment_id not in processed_payments:
                    row = [pmc_no, "", "", "", "", payment['invoice_no']] + [""] * 10
                    row += ["" for _ in hold_headers]
                    row += [
                        "", payment["Payment_Amount"], payment["TDS_Payment_Amount"],
                        payment["Total_amount"], payment["UTR"]
                    ]
                    sheet.append(row)
                    processed_payments.add(payment_id)

        for payment in extra_payments:
            row = [pmc_no, "", "", "", "", ""] + [""] * 10
            row += ["" for _ in hold_headers]
            row += [
                "", payment["Payment_Amount"], payment["TDS_Payment_Amount"],
                payment["Total_amount"], payment["UTR"]
            ]
            sheet.append(row)

        # Credit Note row(s)

        # Track already appended credit notes
        appended_credit_keys = set()

        # While writing invoices
        key = (pmc_no, invoice_no)
        if key in credit_note_map and key not in appended_credit_keys:
            for cn in credit_note_map[key]:
                credit_row = [
                    pmc_no, "", "", cn.get("Invoice_Details", "Credit Note"), "", cn.get("Invoice_No", ""),
                    cn.get("Basic_Amount", ""), cn.get("Debit_Amount", ""),
                    cn.get("After_Debit_Amount", ""), cn.get("GST_Amount", ""), cn.get("Amount", ""), "", "", "",
                    "", ""
                ]
                credit_row += ["" for _ in hold_headers]
                credit_row += [
                    cn.get("Final_Amount", ""),
                    cn.get("Total_Amount", ""),
                    cn.get("UTR", "")
                ]

                sheet.append(credit_row)

            appended_credit_keys.add(key)

        # Totals
        total_basic_amount = Decimal('0.00')
        total_tds_amount = Decimal('0.00')
        total_sd_amount = Decimal('0.00')
        total_on_commission = Decimal('0.00')
        total_final_amount = Decimal('0.00')
        total_payment_amount = Decimal('0.00')
        total_tds_payment_amount = Decimal('0.00')
        total_total_paid = Decimal('0.00')
        total_hold_amount_dynamic = Decimal('0.00')

        for row in sheet.iter_rows(min_row=8, max_row=sheet.max_row, values_only=True):
            try:
                total_basic_amount += Decimal(str(row[6] or 0))
                total_tds_amount += Decimal(str(row[11] or 0))
                total_sd_amount += Decimal(str(row[12] or 0))
                total_on_commission += Decimal(str(row[13] or 0))
                total_final_amount += Decimal(str(row[-5] or 0))
                total_payment_amount += Decimal(str(row[-4] or 0))
                total_tds_payment_amount += Decimal(str(row[-3] or 0))
                total_total_paid += Decimal(str(row[-2] or 0))

                for i in range(len(base_headers), len(base_headers) + len(hold_headers)):
                    total_hold_amount_dynamic += Decimal(str(row[i] or 0))
            except:
                continue

        totals_row = [
            "TOTAL", "", "", "", "", "",
            total_basic_amount, "", "", "", "", total_tds_amount, total_sd_amount,
            total_on_commission, "", ""
        ]
        if total_hold_amount_dynamic:
            totals_row += [total_hold_amount_dynamic] + [""] * (len(hold_headers) - 1)
        totals_row += [
            total_final_amount,
            total_payment_amount,
            total_tds_payment_amount,
            total_total_paid,
            ""
        ]

        sheet.append([])
        sheet.append(totals_row)

        # Summary
        # summary_hold = Decimal('0.00')
        # for d in invoices:
        #     summary_hold += Decimal(str(d.get('SD_Amount', 0))) + Decimal(str(d.get('On_Commission', 0))) + Decimal(str(d.get('Hydro_Testing', 0)))
        # for h in hold_amounts:
        #     summary_hold += Decimal(str(h.get('hold_amount', 0)))

        sheet.append([])
        today = datetime.today().strftime('%A, %Y-%m-%d')
        sheet.append(["Contractor Name", contractor_info["Contractor_Name"]])
        sheet.append(["Date", today])
        sheet.append(["Description", "Amount"])
        sheet.append(["Advance/Surplus", str(total_final_amount - total_total_paid)])
        sheet.append(["Total Hold Amount", str(total_hold_amount_dynamic)])
        sheet.append(["Amount With TDS", str(total_tds_amount)])

        for cell in sheet[sheet.max_row]:
            cell.font = Font(bold=True)

        workbook.save(output_file)
        workbook.close()

    finally:
        cursor.close()
        connection.close()

    return send_from_directory(output_folder, f"PMC_Report_{pmc_no}.xlsx", as_attachment=True)

# --------- Hold Types Controller --------------------------------------------
#  Route to Add a New Hold Type
@app.route('/add_hold_type', methods=['POST', 'GET'])
def add_hold_type():
    connection = config.get_db_connection()
    cursor = connection.cursor(dictionary=True)

    try:
        # Fetch all hold types using the stored procedure
        cursor.callproc("GetAllHoldTypes")
        hold_types = []
        for hold in cursor.stored_results():
            hold_types = hold.fetchall()

        if request.method == 'POST':
            hold_type = request.form.get('hold_type', '').strip()

            # Validation: Must start with a letter
            if not hold_type or not hold_type[0].isalpha():
                return jsonify({"status": "error", "message": "Hold Type must start with a letter."}), 400

            # Validation: Check if it already exists (case-insensitive)
            # cursor.execute("SELECT COUNT(*) AS count FROM hold_types WHERE LOWER(hold_type) = LOWER(%s)", (hold_type,))
            # if cursor.fetchone()['count'] > 0:
            #     return jsonify({"status": "error", "message": "This Hold Type already exists."}), 400
            # Call the procedure to check if the hold_type exists

            cursor.callproc('CheckHoldTypeExists', [hold_type])

            try:
                # Insert new hold type into the database
                # cursor.execute("INSERT INTO hold_types (hold_type) VALUES (%s)", (hold_type,))
                # connection.commit()
                cursor.callproc('SaveHoldType', [hold_type])
                connection.commit()

                return jsonify({"status": "success", "message": "Hold Type added successfully!"}), 201
            except mysql.connector.Error as e:
                connection.rollback()
                return jsonify({"status": "error", "message": f"Database error: {str(e)}"}), 500

    except mysql.connector.Error as e:
        return jsonify({"status": "error", "message": f"Database error: {str(e)}"}), 500

    finally:
        cursor.close()
        connection.close()

    return render_template('add_hold_type.html', Hold_Types_data=hold_types)


# Route to Update Hold Type
# @app.route('/update_hold_type/<int:id>', methods=['POST', 'GET'])
# def update_hold_type(id):
#     # GET request: Show the form with the current hold type
#     if request.method == 'GET':
#         connection = config.get_db_connection()
#         cursor = connection.cursor()
#         # cursor.execute("SELECT * FROM hold_types WHERE hold_type_id = %s", (id,))
#         # hold_type = cursor.fetchone()
#
#         cursor.callproc("GetHoldTypesById", (id,))
#         for hold in cursor.stored_results():
#             hold_type = hold.fetchone()
#
#         cursor.close()
#         connection.close()
#
#         if not hold_type:
#             return jsonify({'status': 'error', 'message': 'Hold Type not found.'}), 404
#
#         return render_template('edit_hold_type.html', hold_type=hold_type)
#
#     # POST request: Update the hold type
#     if request.method == 'POST':
#         new_hold_type = request.form.get('hold_type').strip()
#
#         # Validation: Must start with a letter
#         if not new_hold_type or not new_hold_type[0].isalpha():
#             return jsonify(ResponseHandler.invalid_name('Hold Type')), 400
#
#         connection = config.get_db_connection()
#         cursor = connection.cursor()
#
#         try:
#             # Check if the hold type exists before updating
#             # cursor.execute("SELECT * FROM hold_types WHERE hold_type_id = %s", (id,))
#             # hold_type = cursor.fetchone()
#             cursor.callproc("GetHoldTypesById", (id,))
#             for hold in cursor.stored_results():
#                 hold_type = hold.fetchone()
#
#             if not hold_type:
#                 return jsonify({'status': 'error', 'message': 'Hold Type not found.'}), 404
#
#             # Update the hold type
#             # cursor.execute("UPDATE hold_types SET hold_type = %s WHERE hold_type_id = %s", (new_hold_type, id))
#             cursor.callproc("UpdateHoldTypeById", (id,new_hold_type))
#             connection.commit()
#             return jsonify(ResponseHandler.update_success('Hold Type'))
#
#         except mysql.connector.Error as e:
#             connection.rollback()
#             return jsonify(ResponseHandler.update_failure('Hold Type')), 500
#         finally:
#             cursor.close()
#             connection.close()


@app.route('/update_hold_type/<int:id>', methods=['GET', 'POST'])
def update_hold_type(id):
    connection = config.get_db_connection()
    cursor = connection.cursor()

    if request.method == 'GET':
        cursor.callproc("GetHoldTypesById", (id,))
        for hold in cursor.stored_results():
            hold_type = hold.fetchone()
        cursor.close()
        connection.close()

        if not hold_type:
            flash('Hold Type not found.', 'error')
            return redirect(url_for('add_hold_type'))

        return render_template('edit_hold_type.html', hold_type=hold_type)

    elif request.method == 'POST':
        new_hold_type = request.form.get('hold_type', '').strip()

        if not new_hold_type or not new_hold_type[0].isalpha():
            flash('Invalid hold type name. Must start with a letter.', 'error')
            return redirect(url_for('add_hold_type'))

        try:
            cursor.callproc("GetHoldTypesById", (id,))
            for h in cursor.stored_results():
                hold_type = h.fetchone()

            if not hold_type:
                flash('Hold Type not found.', 'error')
                return redirect(url_for('add_hold_type'))

            cursor.callproc("UpdateHoldTypeById", (id, new_hold_type))
            connection.commit()
            flash('Hold Type updated successfully!', 'success')

        except mysql.connector.Error as e:
            connection.rollback()
            flash('Failed to update Hold Type.', 'error')

        finally:
            cursor.close()
            connection.close()
        
        return redirect(url_for('add_hold_type'))



#  Route to Delete Hold Type
@app.route('/delete_hold_type/<int:id>', methods=['POST'])
def delete_hold_type(id):
    connection = config.get_db_connection()
    cursor = connection.cursor()

    try:
        # cursor.execute("SELECT * FROM hold_types WHERE hold_type_id = %s", (id,))
        # hold_type = cursor.fetchone()
        cursor.callproc("GetHoldTypesById", (id,))
        for hold in cursor.stored_results():
            hold_type = hold.fetchone()

        if not hold_type:
            return jsonify({'status': 'error', 'message': 'Hold Type not found.'}), 404

        # Proceed with deletion
        # cursor.execute("DELETE FROM hold_types WHERE hold_type_id = %s", (id,))
        cursor.callproc("DeleteHoldType", (id,))
        connection.commit()
        return jsonify(ResponseHandler.delete_success('Hold Type'))

    except mysql.connector.Error as e:
        return jsonify(ResponseHandler.delete_failure('Hold Type')), 500
    finally:
        cursor.close()
        connection.close()


# -- end hold types controlller --------------------


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

