diff --git a/.env b/.env new file mode 100644 index 0000000..5bfeba1 --- /dev/null +++ b/.env @@ -0,0 +1,9 @@ +Secret_Key = 9f2a1b8c4d6e7f0123456789abcdef01 + +MYSQL_HOST=127.0.0.1 +MYSQL_USER=root +MYSQL_PASSWORD=root +MYSQL_DB=test + +DEFAULT_USERNAME=admin +DEFAULT_PASSWORD=admin123 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 14f61be..0000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -venv/ -*.pyc -__pycache__/ -.env -.uploads -static/download/ - - diff --git a/AppCode/Auth.py b/AppCode/Auth.py deleted file mode 100644 index cf92480..0000000 --- a/AppCode/Auth.py +++ /dev/null @@ -1,54 +0,0 @@ -from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json -from flask_login import LoginManager, UserMixin - -from logging.handlers import RotatingFileHandler -from ldap3 import Server, Connection, ALL, SUBTREE - -from ldap3 import Server, Connection, ALL -from ldap3.core.exceptions import LDAPBindError - - - -class DefaultCredentials: - username = 'admin' - password = 'admin123' - -class LoginLDAP: - def __init__(self, request): - self.username = request.form['username'].strip() - self.password = request.form['password'] - self.isDefaultCredentials = False - self.isValidLogin = False - self.errorMessage = "" - ldap_user_dn = f"uid={self.username},ou=users,dc=lcepl,dc=org" - ldap_server = 'ldap://localhost:389' - #Need to re-factor further - - - # Static fallback user - if self.username == DefaultCredentials.username and self.password == DefaultCredentials.password: - self.isDefaultCredentials = True - self.isValidLogin = True - return - - try: - # LDAP authentication - conn = Connection( - Server(self.ldap_server, get_info=ALL), - user=self.ldap_user_dn, - password=self.password, - auto_bind=True - ) - - self.isValidLogin = True - return - - except LDAPBindError: - self.errorMessage = "Invalid credentials." - except Exception as e: - self.errorMessage = str(e) - - -class User(UserMixin): - def __init__(self, id): - self.id = id diff --git a/AppCode/Block.py b/AppCode/Block.py deleted file mode 100644 index 84e7a58..0000000 --- a/AppCode/Block.py +++ /dev/null @@ -1,108 +0,0 @@ -from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json -from flask import current_app - -from datetime import datetime -from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user - -from AppCode.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType -from AppCode.Log import LogData, LogHelper - -import os -import config -import re - -import mysql.connector -from mysql.connector import Error - -from AppCode.ItemCRUD import ItemCRUD, itemCRUDMapping - - -class Block: - - isSuccess = False - resultMessage = "" - - def __init__(self): - self.isSuccess = False - self.resultMessage = "" - - # ---------------------------------------------------------- - # Add Block - # ---------------------------------------------------------- - def AddBlock(self, request): - - block = ItemCRUD(itemType=ItemCRUDType.Block) - - district_id = request.form.get('district_Id') - block_name = request.form.get('block_Name', '').strip() - - block.AddItem(request=request, parentid=district_id, childname=block_name, storedprocfetch="GetVillageByNameAndBlock", storedprocadd="SaveVillage" ) - self.isSuccess = block.isSuccess - self.resultMessage = block.resultMessage - return - - # ---------------------------------------------------------- - # Get All Blocks - # ---------------------------------------------------------- - def GetAllBlocks(self): - - block = ItemCRUD(itemType=ItemCRUDType.Block) - blocksdata = block.GetAllData(request=request, storedproc="GetAllBlock") - self.isSuccess = block.isSuccess - self.resultMessage = block.resultMessage - return blocksdata - - - - # ---------------------------------------------------------- - # Check Block Exists - # ---------------------------------------------------------- - - def CheckBlock(self, request): - block = ItemCRUD(itemType=ItemCRUDType.Block) - block_name = request.json.get('block_Name', '').strip() - district_id = request.json.get('district_Id') - result = block.CheckItem(request=request, parentid=district_id, childname=block_name, storedprocfetch="GetBlockByNameAndDistrict") - self.isSuccess = block.isSuccess - self.resultMessage = block.resultMessage - return result - - - # ---------------------------------------------------------- - # Get Block By ID - # ---------------------------------------------------------- - def GetBlockByID(self, id): - - block = ItemCRUD(itemType=ItemCRUDType.Village) - blockdata = block.GetAllData("GetBlockDataByID") - self.isSuccess = block.isSuccess - self.resultMessage = block.resultMessage - return blockdata - - - # ---------------------------------------------------------- - # Update Block - # ---------------------------------------------------------- - def EditBlock(self, request, block_id): - - block = ItemCRUD(itemType=ItemCRUDType.Block) - - district_id = request.form.get('district_Id') - block_name = request.form.get('block_Name', '').strip() - - block.EditItem(request=request, childid=block_id, parentid=district_id, childname=block_name, storedprocadd="UpdateBlockById" ) - self.isSuccess = block.isSuccess - self.resultMessage = block.resultMessage - return - - - # ---------------------------------------------------------- - # Delete Block - # ---------------------------------------------------------- - def DeleteBlock(self, id): - block = ItemCRUD(itemType=ItemCRUDType.Block) - - block.DeleteItem(request=request, itemID=id, storedprocDelete="DeleteBlock" ) - self.isSuccess = block.isSuccess - self.resultMessage = block.resultMessage - return \ No newline at end of file diff --git a/AppCode/ItemCRUD.py b/AppCode/ItemCRUD.py deleted file mode 100644 index 91b3db9..0000000 --- a/AppCode/ItemCRUD.py +++ /dev/null @@ -1,248 +0,0 @@ - -from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user - -from AppCode.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType -from AppCode.Log import LogData, LogHelper - -import os -import config -import re - -import mysql.connector -from mysql.connector import Error - - -class itemCRUDMapping: - name = "" - - def __init__(self, itemType): - if itemType is ItemCRUDType.Village: - self.name = "Village" - elif itemType is ItemCRUDType.Block: - self.name = "Block" - elif itemType is ItemCRUDType.State: - self.name = "State" - - - -class ItemCRUD: - isSuccess = False - resultMessage = "" - itemCRUDType = ItemCRUDType.Village - itemCRUDMapping = ItemCRUDType(itemCRUDType) - #itemCRUDMapping itemCRUDMapping - - - def __init__(self, itemType): - self.isSuccess = False - self.resultMessage = "" - self.itemCRUDType = itemType - self.itemCRUDMapping = ItemCRUDType(self.itemCRUDType) - - - def DeleteItem(self, request, itemID, storedprocDelete): - self.isSuccess = False - self.resultMessage = "" - connection = config.get_db_connection() - cursor = connection.cursor() - LogHelper.log_action("Delete Village", f"User {current_user.id} deleted village '{itemID}'") - - try: - cursor.callproc(storedprocDelete, (itemID,)) - connection.commit() - self.resultMessage = ResponseHandler.delete_success(self.itemCRUDMapping.name) # Simple message, route will handle redirect - self.isSuccess = True - except mysql.connector.Error as e: - print(f"Error deleting village: {e}") - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.delete_failure(self.itemCRUDMapping.name), 500) - finally: - cursor.close() - connection.close() - - #return self.resultMessage - - - - def AddItem(self, request, parentid, childname, storedprocfetch, storedprocadd): - - connection = config.get_db_connection() - if not connection: - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.db_connection_failure(), 500) - return - - cursor = connection.cursor() - - - LogHelper.log_action(f"Add '{self.itemCRUDMapping.name}'", - f"User {current_user.id} adding '{self.itemCRUDMapping.name}' '{childname}' to block '{parentid}'") - - if not parentid: - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("block"), - 400) # Assuming this is a valid response - return - - if not re.match(RegEx.patternAlphabetOnly, childname): - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400) - return - - try: - # Check if the village already exists in the block - cursor.callproc(storedprocfetch, (childname, parentid,)) - for rs in cursor.stored_results(): - existing_item = rs.fetchone() - - if existing_item: - print("Existing ", self.itemCRUDMapping.name) - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists(self.itemCRUDMapping.name), 409) - return - - # Insert new village - cursor.callproc(storedprocadd, (childname, parentid)) - connection.commit() - self.isSuccess = True - self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_success(self.itemCRUDMapping.name), 200) - return - - except mysql.connector.Error as e: - print(f"Database Error: {e}") - print("DatabaseError") - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("village"), 500) - return - finally: - cursor.close() - connection.close() - - - - def EditItem(self, request, childid, parentid, childname, storedprocupdate): - """Handles the POST logic for updating a district.""" - self.isSuccess = False - self.resultMessage = "" - connection = config.get_db_connection() - cursor = connection.cursor() - - #district_name = request.form['district_Name'].strip() - #state_id = request.form['state_Id'] - LogHelper.log_action("Edit District", f"User {current_user.id} Edited District '{childid}'") - - #Need to add validation to see if item exits - - # Added validation consistent with your other Edit methods - if not re.match(RegEx.patternAlphabetOnly, childname): - self.isSuccess = False - self.resultMessage = ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400 - return self.resultMessage - - try: - cursor.callproc(storedprocupdate, (childid, parentid, childname,)) - connection.commit() - self.isSuccess = True - self.resultMessage = "Successfully Edited" - except mysql.connector.Error as e: - print(f"Error updating district: {e}") - self.isSuccess = False - self.resultMessage = ResponseHandler.update_failure(self.itemCRUDMapping.name), 500 - finally: - cursor.close() - connection.close() - - return self.resultMessage - - - - def GetAllData(self, request, storedproc): - - data = [] - connection = config.get_db_connection() - - self.isSuccess = False - self.resultMessage = "" - - if not connection: - return [] - - cursor = connection.cursor() - - try: - cursor.callproc(storedproc) - for result in cursor.stored_results(): - data = result.fetchall() - self.isSuccess = True - - except mysql.connector.Error as e: - print(f"Error fetching villages: {e}") - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500) - return [] - - finally: - cursor.close() - connection.close() - - return data - - - - def GetDataByID(self, id, storedproc): - data = None - connection = config.get_db_connection() - cursor = connection.cursor() - - try: - cursor.callproc(storedproc, (id,)) - for rs in cursor.stored_results(): - data = rs.fetchone() - - except mysql.connector.Error as e: - print(f"Error fetching block data: {e}") - return None - finally: - cursor.close() - connection.close() - - return data - - - - def CheckItem(self, request, parentid, childname, storedprocfetch): - self.isSuccess = False - self.resultMessage = "" - connection = config.get_db_connection() - cursor = connection.cursor() - - LogHelper.log_action("Check Block", f"User {current_user.id} Checked block '{childname}'") - - if not re.match(RegEx.patternAlphabetOnly, childname): - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400) - return HtmlHelper.json_response(ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400) - - try: - cursor.callproc(storedprocfetch, (childname, parentid)) - for rs in cursor.stored_results(): - existing_item = rs.fetchone() - - if existing_item: - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists(self.itemCRUDMapping.name), 409) - return HtmlHelper.json_response(ResponseHandler.already_exists(self.itemCRUDMapping.name), 409) - - self.isSuccess = True - self.resultMessage = HtmlHelper.json_response(ResponseHandler.is_available(self.itemCRUDMapping.name), 200) - return HtmlHelper.json_response(ResponseHandler.is_available(self.itemCRUDMapping.name), 200) - - except mysql.connector.Error as e: - print(f"Error checking block: {e}") - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500) - return HtmlHelper.json_response(ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500) - - finally: - cursor.close() - connection.close() diff --git a/AppCode/ReportGenerator.py b/AppCode/ReportGenerator.py deleted file mode 100644 index e69de29..0000000 diff --git a/AppCode/State.py b/AppCode/State.py deleted file mode 100644 index 8646119..0000000 --- a/AppCode/State.py +++ /dev/null @@ -1,246 +0,0 @@ -from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json -from flask import current_app - -from datetime import datetime -from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user - -from AppCode.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType -from AppCode.Log import LogData, LogHelper - -import os -import config -import re - -import mysql.connector -from mysql.connector import Error - -class State: - - isSuccess = False - resultMessage = "" - - def __init__(self): - self.isSuccess = False - self.resultMessage = "" - - def AddState(self, request): - """Log user actions with timestamp, user, action, and details.""" - statedata = [] - - connection = config.get_db_connection() - - if connection: - cursor = connection.cursor() - state_name = request.form['state_Name'].strip() - LogHelper.log_action("Add State", f"User {current_user.id} added state '{state_name}'") - - if not re.match(RegEx.patternAlphabetOnly, state_name): - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("state"), 400) - return - - try: - - cursor.callproc("CheckStateExists", (state_name,)) - for data in cursor.stored_results(): - existing_state = data.fetchone() - - if existing_state: - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists("state"), 409) - return - - # cursor.execute("call SaveState (%s)", (state_name,)) - cursor.callproc("SaveState", (state_name,)) - connection.commit() - self.isSuccess = True - self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_success("state"), 200) - return - - except mysql.connector.Error as e: - print(f"Error inserting state: {e}") - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("state"), 500) - return - - #Need to make this seperate - - - - def GetAllStates(self, request): - """Log user actions with timestamp, user, action, and details.""" - statedata = [] - connection = config.get_db_connection() - - self.isSuccess = False - self.resultMessage = "" - - if not connection: - return [] - - cursor = connection.cursor() - - try: - cursor.callproc("GetAllStates") - for res in cursor.stored_results(): - statedata = res.fetchall() - self.isSuccess = True - - - except mysql.connector.Error as e: - print(f"Error fetching states: {e}") - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("state"), 500) - return [] - - finally: - cursor.close() - connection.close() - - return statedata - - - - def CheckState(self, request): - self.isSuccess = False - self.resultMessage = "" - - connection = config.get_db_connection() - #connection closing needs to be verified - if connection: - cursor = connection.cursor() - state_name = request.json.get('state_Name', '').strip() - LogHelper.log_action("Check State", f"User {current_user.id} Checked state '{state_name}'") - if not re.match(RegEx.patternAlphabetOnly, state_name): - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("state"), 400) - return HtmlHelper.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: - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists("state"), 409) - return HtmlHelper.json_response(ResponseHandler.already_exists("state"), 409) - else: - self.isSuccess = True - self.resultMessage = HtmlHelper.json_response(ResponseHandler.is_available("state"), 200) - return HtmlHelper.json_response(ResponseHandler.is_available("state"), 200) - - except mysql.connector.Error as e: - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("state"), 500) - - print(f"Error checking state: {e}") - return HtmlHelper.json_response(ResponseHandler.add_failure("state"), 500) - finally: - cursor.close() - connection.close() - - - - def DeleteState(self, request, id): - self.isSuccess = False - self.resultMessage = "" - - connection = config.get_db_connection() - cursor = connection.cursor() - LogHelper.log_action("Delete State", f"User {current_user.id} Deleted state '{id}'") - try: - cursor.callproc('DeleteState', (id,)) - connection.commit() - - self.resultMessage = "Successfully Deleted" - self.isSuccess = True - except mysql.connector.Error as e: - print(f"Error deleting data: {e}") - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.delete_failure("state"), 500) - return HtmlHelper.json_response(ResponseHandler.delete_failure("state"), 500) - - finally: - cursor.close() - connection.close() - return self.resultMessage - - - def EditState(self, request, id): - self.isSuccess = False - self.resultMessage = "" - - connection = config.get_db_connection() - cursor = connection.cursor() - # str_pattern_reg = r"^[A-Za-z\s]+$" - - state_name = request.form['state_Name'].strip() - LogHelper.log_action("Edit State", f"User {current_user.id} Edited state '{state_name}'") - if not re.match(RegEx.patternAlphabetOnly, state_name): - self.isSuccess = False - self.resultMessage = ResponseHandler.invalid_name("state"), 400 - 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() - self.isSuccess = True - self.resultMessage = "Successfully Edited" - return redirect(url_for('add_state')) - except mysql.connector.Error as e: - print(f"Error updating data: {e}") - self.isSuccess = True - self.resultMessage = ResponseHandler.add_failure("state"), 500 - - return ResponseHandler.add_failure("state"), 500 - finally: - cursor.close() - connection.close() - - - - - - def GetStateByID(self, request, id): - """Log user actions with timestamp, user, action, and details.""" - statedata = [] - - self.isSuccess = False - self.resultMessage = "" - - connection = config.get_db_connection() - if not connection: - return [] - cursor = connection.cursor() - - try: - cursor.callproc("GetStateByID", (id,)) - for res in cursor.stored_results(): - statedata = res.fetchone() - - if statedata: - self.isSuccess = True - self.resultMessage = "Success in Fetching" - else: - self.isSuccess = False - self.resultMessage = "State Not Found" - - - except mysql.connector.Error as e: - print(f"Error fetching states: {e}") - self.isSuccess = False - self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("state"), 500) - return [] - - finally: - cursor.close() - connection.close() - - return statedata - - diff --git a/AppRoutes/StateRoute.py b/AppRoutes/StateRoute.py deleted file mode 100644 index e69de29..0000000 diff --git a/README.md b/README.md index 3f469cc..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,8 +0,0 @@ -# Payment reconciliation - - - - -http://103.186.132.129:3000/pjpatil12/Version-1-old - -file path:C:\Work\lcepl_Projects\Payment reconciliation \ No newline at end of file diff --git a/Reconciliation-Product.code-workspace b/Reconciliation-Product.code-workspace deleted file mode 100644 index 876a149..0000000 --- a/Reconciliation-Product.code-workspace +++ /dev/null @@ -1,8 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": {} -} \ No newline at end of file diff --git a/Version-1.code-workspace b/Version-1.code-workspace deleted file mode 100644 index 876a149..0000000 --- a/Version-1.code-workspace +++ /dev/null @@ -1,8 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": {} -} \ No newline at end of file diff --git a/__pycache__/config.cpython-313.pyc b/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000..af5230c Binary files /dev/null and b/__pycache__/config.cpython-313.pyc differ diff --git a/__pycache__/config.cpython-314.pyc b/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000..f7ac19f Binary files /dev/null and b/__pycache__/config.cpython-314.pyc differ diff --git a/activity.log b/activity.log index e7d0566..c5d6a02 100644 --- a/activity.log +++ b/activity.log @@ -5091,5 +5091,2899 @@ Timestamp: 2025-11-18 00:48:55 | User: Unknown | Action: Check Block | Details: Timestamp: 2025-11-18 00:48:57 | User: Unknown | Action: Check Block | Details: Timestamp: 2025-11-18 00:48:57 | User: Unknown | Action: Add 'Village' | Details: Timestamp: 2025-11-18 09:58:53 | User: Unknown | Action: Login | Details: -Timestamp: 2025-11-24 12:57:24 | User: Unknown | Action: Login | Details: -Timestamp: 2025-11-25 14:12:56 | User: Unknown | Action: Login | Details: +Timestamp: 2026-02-27 10:58:49 | User: Unknown | Action: Login | Details: +Timestamp: 2026-02-27 10:59:11 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 10:59:13 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-02-27 11:00:00 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-02-27 11:00:07 | User: Unknown | Action: Delete State | Details: +Timestamp: 2026-02-27 11:07:20 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:07:20 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:07:23 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:07:23 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:07:26 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:07:26 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:07:27 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:07:27 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:07:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:07:38 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:07:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-02-27 11:08:15 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:08:15 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:08:16 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:08:17 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:08:19 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:29:08 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:29:08 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:29:08 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:29:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:29:15 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:43:15 | User: Unknown | Action: Login | Details: +Timestamp: 2026-02-27 11:43:22 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:43:22 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:43:24 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:43:24 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:43:25 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:43:25 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:43:27 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 11:58:37 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 11:58:37 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 11:58:38 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 11:58:38 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 11:58:39 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 11:58:39 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 11:58:40 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 11:58:41 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 11:58:41 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 11:58:41 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 11:58:41 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-02-27 11:58:42 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-02-27 11:58:54 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 11:58:55 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 11:58:55 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 11:58:55 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 11:59:02 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 11:59:02 | User: Unknown | Action: Add 'District' | Details: +Timestamp: 2026-02-27 12:08:09 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 12:09:51 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-02-27 12:23:18 | User: Unknown | Action: Login | Details: +Timestamp: 2026-02-27 12:32:10 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-02-27 12:32:17 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-02-27 12:32:20 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-02-27 12:32:27 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 12:32:27 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 12:32:27 | User: Unknown | Action: Add 'District' | Details: +Timestamp: 2026-02-27 12:32:33 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-02-27 12:32:48 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-02-27 12:32:53 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 12:32:53 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 12:32:54 | User: Unknown | Action: Add 'Block' | Details: +Timestamp: 2026-02-27 12:34:57 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-02-27 12:35:00 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 12:35:00 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 12:35:00 | User: Unknown | Action: Add 'Block' | Details: +Timestamp: 2026-02-27 12:35:14 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-02-27 12:35:20 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 12:35:20 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 12:35:21 | User: Unknown | Action: Add 'Village' | Details: +Timestamp: 2026-02-27 12:35:43 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 12:35:48 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 12:35:56 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 12:41:37 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 12:41:43 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 12:41:48 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 12:48:48 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 12:48:51 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 13:01:04 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 13:01:04 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 13:01:05 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 13:01:05 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 13:01:06 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 13:01:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 13:12:15 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 13:13:18 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 13:15:31 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 13:15:34 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 13:15:40 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 13:15:50 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 13:17:53 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 13:19:27 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 13:20:05 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 13:20:36 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-02-27 13:22:10 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-02-27 13:22:14 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:14 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:18 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:19 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:19 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:26 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:26 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:27 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:27 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:28 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:28 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:29 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 13:22:30 | User: Unknown | Action: Add 'Block' | Details: +Timestamp: 2026-02-27 14:29:35 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-02-27 14:42:58 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-02-27 15:10:25 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-02-27 15:10:28 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 15:10:28 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 15:10:29 | User: Unknown | Action: Add 'Block' | Details: +Timestamp: 2026-02-27 15:10:36 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-02-27 15:15:37 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-02-27 15:15:40 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-02-27 15:15:43 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 15:15:43 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-02-27 15:15:44 | User: Unknown | Action: Add 'Block' | Details: +Timestamp: 2026-02-27 15:38:20 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 15:38:24 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 15:38:44 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 15:43:06 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 15:45:19 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 15:45:41 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-02-27 18:48:06 | User: Unknown | Action: Login | Details: +Timestamp: 2026-02-27 18:48:15 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 18:48:15 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 18:48:15 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 18:48:16 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 18:48:17 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 18:48:17 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 18:48:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 18:48:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:23:54 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:23:55 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:23:55 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:23:55 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:23:57 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:40:57 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:40:57 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:40:57 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:40:58 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:41:00 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:53:42 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:53:42 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:53:43 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 19:53:45 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:06:27 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:06:27 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:06:28 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:06:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:12:49 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:12:49 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:12:49 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:12:50 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:12:53 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:12:53 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:12:54 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:12:55 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:13:02 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:17:34 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:17:34 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:17:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:17:43 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:17:43 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:17:44 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:17:47 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:17:50 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:19:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:19:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:19:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:19:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:19:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:34:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:34:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:34:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:34:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:34:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:37:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:37:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:37:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:37:19 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:37:20 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:39:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:39:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:39:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:39:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:39:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:42:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:42:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:42:34 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:42:39 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:42:41 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:45:51 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:45:52 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:45:54 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:45:55 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:48:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:48:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:48:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:48:15 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:48:16 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:52:19 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:52:19 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:52:19 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:52:23 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:52:25 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:54:34 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:54:35 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:54:35 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:54:38 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 20:54:40 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:07:16 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:07:16 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:07:16 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:07:17 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:07:19 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:11:52 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:11:52 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:11:53 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:11:53 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:11:55 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:15:16 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:15:16 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:15:17 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:15:18 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:15:21 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:20:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:20:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:20:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:20:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:20:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:22:56 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:22:56 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:22:56 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:22:58 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 21:22:59 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:19:47 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:19:48 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:19:49 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:19:50 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:19:52 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:25:56 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:25:56 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:25:56 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:25:58 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:25:59 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:32:08 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:32:08 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:32:09 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:32:11 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:35:10 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:35:12 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:35:12 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:35:13 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:35:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:38:55 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:38:55 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:38:55 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:39:00 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:39:01 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:42:22 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:42:22 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:42:22 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:42:25 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:42:26 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:48:04 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:48:04 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:48:05 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:48:05 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:48:07 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:50:57 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:50:57 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:50:57 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:50:58 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:50:59 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:55:56 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:55:57 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:55:57 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:55:57 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:55:59 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:59:11 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:59:11 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:59:11 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:59:12 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 22:59:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:08:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:08:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:08:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:08:37 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:08:38 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:20:21 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:20:21 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:20:22 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:20:25 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:20:27 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:27:07 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:27:07 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:27:08 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:27:11 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:27:13 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:33:04 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:33:04 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:33:04 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:33:05 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:33:07 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:35:28 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:35:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:35:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:35:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:35:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:38:03 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:38:04 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:38:04 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:38:05 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:38:06 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:43:21 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:43:21 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:43:22 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:43:23 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:43:26 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:47:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:47:37 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:47:40 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:47:41 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:47:41 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:47:41 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:47:44 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:50:51 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:50:51 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:50:52 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:50:52 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:50:54 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:54:28 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:54:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:54:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:54:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:54:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-27 23:54:34 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:01:34 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:01:34 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:01:35 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:01:38 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:01:38 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:01:40 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:11:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:11:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:11:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:11:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:11:35 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:14:45 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:14:46 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:14:48 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:14:49 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:14:50 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:32:49 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:32:49 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:32:49 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:32:50 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:32:52 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:43:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:43:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:43:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:43:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:43:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:48:13 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:48:13 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:48:13 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:48:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:48:16 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:54:43 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:54:43 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:54:43 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:54:44 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:54:45 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 00:54:45 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:07:19 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:07:19 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:07:20 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:07:21 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:07:23 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:09:28 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:09:28 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:09:28 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:09:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:09:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:35:50 | User: Unknown | Action: Login | Details: +Timestamp: 2026-02-28 01:36:03 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:36:03 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:36:03 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:36:04 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-02-28 01:36:06 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 11:08:01 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-02 11:08:16 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-02 11:08:20 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:08:20 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:08:20 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:08:20 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:18:39 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-02 11:18:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:21:24 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-02 11:21:28 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:27 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:29:30 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 11:43:27 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 11:43:27 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 11:43:28 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 11:43:37 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 11:43:53 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 11:44:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 11:46:40 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:20:58 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:21:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:43:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:43:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:43:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:43:32 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:43:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:48:16 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-02 12:48:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:48:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:48:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:48:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:48:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:48:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:48:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:48:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:48:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 12:48:28 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:48:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:48:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:49:10 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:49:12 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:59:35 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:59:35 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:59:35 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 12:59:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:01:53 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:01:53 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:01:53 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:01:54 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:04:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:04:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:04:29 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:04:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:05:51 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:05:52 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:05:52 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:05:53 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:07:26 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:07:27 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:13:16 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:13:16 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:13:17 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:27:00 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:27:00 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:27:02 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:31:23 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:31:24 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:31:24 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 13:31:25 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:26:19 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:26:20 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:26:20 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:26:21 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:34:40 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:34:41 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:34:41 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:34:42 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:35:48 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:35:49 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:35:50 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:40:57 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-02 14:41:29 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:09 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:10 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 14:42:27 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:42:28 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:42:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 14:59:59 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:07 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-02 15:00:39 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-02 15:02:23 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 15:02:24 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 15:02:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 15:21:32 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 15:22:29 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 15:44:37 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 15:44:49 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-02 15:44:53 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-02 15:44:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-02 15:44:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-02 15:44:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-02 15:44:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-02 15:44:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-02 15:44:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-02 15:44:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-02 15:44:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-02 15:44:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-02 15:45:09 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:12 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:12 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:12 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:12 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:13 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:14 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:14 | User: Unknown | Action: Add 'District' | Details: +Timestamp: 2026-03-02 15:45:20 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-03-02 15:45:24 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:24 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:25 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:25 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:27 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:27 | User: Unknown | Action: Add 'Block' | Details: +Timestamp: 2026-03-02 15:45:34 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-03-02 15:45:43 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:43 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-02 15:45:44 | User: Unknown | Action: Add 'Village' | Details: +Timestamp: 2026-03-02 15:46:52 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 15:48:08 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-02 15:50:33 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 15:50:34 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 15:50:34 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 15:50:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 16:08:43 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-02 16:12:06 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-02 16:13:18 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-02 16:13:51 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-02 16:46:07 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-02 17:03:42 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 17:05:08 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-02 17:06:21 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-02 17:10:00 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 17:11:55 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-02 17:12:36 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-02 17:17:14 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-02 17:17:36 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-02 17:18:21 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-02 17:19:21 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-02 17:23:41 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-02 17:24:36 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-02 17:28:35 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-02 17:29:32 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-02 17:30:25 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-02 17:33:02 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-02 17:33:38 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 17:34:31 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-02 17:35:30 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-02 17:36:13 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-02 17:36:22 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 17:38:17 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 17:39:18 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-02 17:39:49 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-02 17:40:19 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 17:41:16 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-02 17:43:01 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-02 17:43:10 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:43:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:44:02 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:44:02 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:44:02 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:44:02 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:44:04 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:44:24 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 17:44:35 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:44:35 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:44:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:44:37 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:44:55 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-02 17:44:57 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-02 17:45:01 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:45:01 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:45:02 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:46:21 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-02 17:47:18 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-02 17:47:35 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:47:35 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:47:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:47:36 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:47:39 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:47:39 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-02 17:47:41 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 00:20:07 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-04 00:20:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 00:20:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 00:20:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 00:20:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 00:20:18 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 00:31:29 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-04 00:32:35 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 00:35:07 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-04 00:35:15 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 00:35:20 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 00:37:20 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-04 00:38:02 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 00:40:42 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 00:41:20 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 00:41:33 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 00:42:37 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-04 00:44:04 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 00:48:47 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 00:49:35 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-04 00:50:11 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-04 00:50:50 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 00:54:54 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:04:14 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:04:37 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:11:57 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:13:30 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:13:37 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:13:44 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:13:51 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:14:09 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:14:39 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:14:59 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:15:06 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:16:17 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:16:22 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:16:28 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:17:56 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 01:24:46 | User: Unknown | Action: Edit gst_release | Details: +Timestamp: 2026-03-04 01:35:30 | User: Unknown | Action: Add gst_release | Details: +Timestamp: 2026-03-04 01:36:20 | User: Unknown | Action: Add gst_release | Details: +Timestamp: 2026-03-04 01:37:32 | User: Unknown | Action: Add gst_release | Details: +Timestamp: 2026-03-04 01:39:16 | User: Unknown | Action: Add gst_release | Details: +Timestamp: 2026-03-04 01:41:38 | User: Unknown | Action: Add gst_release | Details: +Timestamp: 2026-03-04 01:42:46 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 01:42:46 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 01:42:46 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 01:42:48 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 01:45:17 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 01:45:18 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 01:45:18 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 01:45:19 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:05:19 | User: Unknown | Action: Add gst_release | Details: +Timestamp: 2026-03-04 02:06:56 | User: Unknown | Action: Add gst_release | Details: +Timestamp: 2026-03-04 02:07:00 | User: Unknown | Action: Add gst_release | Details: +Timestamp: 2026-03-04 02:07:16 | User: Unknown | Action: delete gst_release | Details: +Timestamp: 2026-03-04 02:07:24 | User: Unknown | Action: delete gst_release | Details: +Timestamp: 2026-03-04 02:10:23 | User: Unknown | Action: Add gst_release | Details: +Timestamp: 2026-03-04 02:11:05 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:11:05 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:11:06 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:11:06 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:11:06 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:11:06 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:11:08 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:11:08 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:11:13 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:11:14 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:11:15 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:14:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:14:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:14:30 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:14:31 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:21:46 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:21:46 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 02:21:47 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-04 18:39:27 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-04 18:39:34 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 18:39:50 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-04 18:43:24 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 18:43:48 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-03-04 18:43:54 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-04 18:43:54 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-04 18:43:55 | User: Unknown | Action: Add 'Village' | Details: +Timestamp: 2026-03-04 18:44:02 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 18:44:47 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-04 18:46:35 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 18:46:44 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 18:47:23 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-04 18:48:27 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-04 18:48:43 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 18:51:59 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 18:52:13 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-03-04 18:52:20 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-04 18:52:20 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-04 18:52:21 | User: Unknown | Action: Add 'Village' | Details: +Timestamp: 2026-03-04 18:52:31 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 18:53:10 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-04 18:53:37 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 18:53:49 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 19:01:04 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 19:14:36 | User: Unknown | Action: Get District | Details: +Timestamp: 2026-03-04 19:14:41 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-04 19:14:41 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-04 19:14:42 | User: Unknown | Action: Add 'Village' | Details: +Timestamp: 2026-03-04 19:15:08 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-04 19:15:56 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-04 19:16:27 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 19:23:58 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-04 19:24:31 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 19:25:30 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 19:28:00 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 19:28:29 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 19:30:39 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 19:30:51 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 19:31:48 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 19:32:16 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-04 19:33:06 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-04 19:51:46 | User: Unknown | Action: Add gst_release | Details: +Timestamp: 2026-03-04 21:44:52 | User: Unknown | Action: Edit gst_release | Details: +Timestamp: 2026-03-04 21:44:57 | User: Unknown | Action: Edit gst_release | Details: +Timestamp: 2026-03-04 21:46:50 | User: Unknown | Action: Edit gst_release | Details: +Timestamp: 2026-03-04 21:46:54 | User: Unknown | Action: Edit gst_release | Details: +Timestamp: 2026-03-04 21:47:04 | User: Unknown | Action: Edit gst_release | Details: +Timestamp: 2026-03-04 21:49:14 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-04 21:49:26 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-13 15:13:40 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-13 15:16:07 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:14 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:16:23 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:16:23 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:16:23 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:16:25 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:16:26 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:17:23 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:36 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:17:50 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:17:50 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:17:52 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:17:55 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:17:56 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:28:09 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-13 15:28:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:28:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:28:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-13 15:28:20 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:28:20 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:28:21 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:28:23 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-13 15:28:24 | User: Unknown | Action: Search contractor | Details: +Timestamp: 2026-03-14 16:31:44 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-14 16:51:26 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-14 17:08:11 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-14 17:24:44 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:45 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:45 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:45 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:45 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:46 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:47 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:47 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:47 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:50 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:52 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:52 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:53 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:53 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:53 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:53 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:53 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:54 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:58 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:59 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:59 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:24:59 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:25:00 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:25:01 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:25:01 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:25:10 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:25:11 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:25:11 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:25:14 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-03-14 17:25:23 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-03-14 17:25:37 | User: Unknown | Action: Delete State | Details: +Timestamp: 2026-03-14 17:25:57 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-03-14 17:34:51 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-03-14 17:34:58 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-03-14 17:35:04 | User: Unknown | Action: Delete State | Details: +Timestamp: 2026-03-14 17:35:07 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:35:08 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-03-14 17:35:22 | User: Unknown | Action: Delete State | Details: +Timestamp: 2026-03-14 17:35:34 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 17:35:35 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-03-14 18:05:02 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 18:05:09 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-03-14 18:05:14 | User: Unknown | Action: Delete State | Details: +Timestamp: 2026-03-14 18:05:18 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-14 18:05:19 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-03-14 18:05:33 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:05:33 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:05:35 | User: Unknown | Action: Add 'District' | Details: +Timestamp: 2026-03-14 18:05:47 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-14 18:05:53 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-14 18:06:26 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:06:26 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:06:27 | User: Unknown | Action: Add 'District' | Details: +Timestamp: 2026-03-14 18:07:49 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:07:49 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:07:50 | User: Unknown | Action: Add 'Block' | Details: +Timestamp: 2026-03-14 18:21:58 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:21:58 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:22:01 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:22:02 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:22:02 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:22:02 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:22:02 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:22:02 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:22:03 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:22:03 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:22:03 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:22:04 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-14 18:22:05 | User: Unknown | Action: Add 'District' | Details: +Timestamp: 2026-03-14 18:22:15 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-14 18:22:22 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-14 18:22:27 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-16 10:10:24 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-16 10:16:08 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 10:16:08 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 10:16:09 | User: Unknown | Action: Add 'Block' | Details: +Timestamp: 2026-03-16 10:16:23 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-16 10:16:33 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-16 10:16:38 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-16 10:35:02 | User: Unknown | Action: Add 'Village' | Details: +Timestamp: 2026-03-16 10:40:17 | User: Unknown | Action: Add 'Village' | Details: +Timestamp: 2026-03-16 10:47:05 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 10:47:05 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 10:47:06 | User: Unknown | Action: Add 'Village' | Details: +Timestamp: 2026-03-16 11:00:45 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-16 11:00:50 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-16 11:01:47 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-16 12:09:07 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 12:09:23 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 12:15:18 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 12:15:18 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 12:15:20 | User: Unknown | Action: Add 'Village' | Details: +Timestamp: 2026-03-16 12:15:28 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 12:16:19 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-16 12:19:15 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 12:19:35 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 12:26:54 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 12:27:24 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 12:27:31 | User: Unknown | Action: Delete Invoice | Details: +Timestamp: 2026-03-16 12:27:42 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 12:29:22 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-16 12:36:35 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-16 12:37:09 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-16 12:37:15 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 13:56:41 | User: Unknown | Action: Add Subcontractor | Details: +Timestamp: 2026-03-16 13:57:02 | User: Unknown | Action: Edit Subcontractor | Details: +Timestamp: 2026-03-16 13:57:41 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 13:58:15 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 14:59:33 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-16 15:00:01 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 15:00:01 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 15:00:14 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 15:00:14 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 15:00:15 | User: Unknown | Action: Add 'Block' | Details: +Timestamp: 2026-03-16 15:00:30 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 15:00:30 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-16 15:00:31 | User: Unknown | Action: Add 'Village' | Details: +Timestamp: 2026-03-16 15:01:01 | User: Unknown | Action: Add Subcontractor | Details: +Timestamp: 2026-03-16 15:01:12 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 15:02:06 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-16 15:02:08 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 15:03:23 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-16 15:27:46 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-16 15:27:52 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-16 15:35:55 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-16 15:36:49 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 15:36:54 | User: Unknown | Action: Delete Invoice | Details: +Timestamp: 2026-03-16 15:36:59 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 15:42:35 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 15:43:01 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-16 15:43:03 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-16 15:43:35 | User: Unknown | Action: Add GST Release | Details: +Timestamp: 2026-03-16 15:46:56 | User: Unknown | Action: Edit GST Release | Details: +Timestamp: 2026-03-16 15:47:17 | User: Unknown | Action: Delete GST Release | Details: +Timestamp: 2026-03-16 16:32:59 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 16:33:14 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 16:33:54 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 16:33:59 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:33:59 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:33:59 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:33:59 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:33:59 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:33:59 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:33:59 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:33:59 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:33:59 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:33:59 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:33:59 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:34:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:39:40 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 16:43:24 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 16:43:29 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:43:29 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:43:29 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:43:29 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:43:29 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:43:29 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:43:29 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:43:29 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:43:29 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:43:29 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:43:29 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:43:35 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 16:57:54 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 17:22:48 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 17:22:52 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 17:23:05 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 17:23:08 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 17:26:45 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 17:38:02 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 17:38:04 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 17:39:42 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 17:47:44 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-16 18:14:18 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 18:19:25 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 18:25:48 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 18:26:45 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-16 18:29:04 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-17 10:58:06 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-17 10:58:15 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:16 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:08:24 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 11:09:13 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-17 11:25:26 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:31 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:46 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:47 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:48 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:48 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:50 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:50 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:52 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:52 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:54 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:55 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:25:56 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:01 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:01 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:05 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:06 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:06 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:07 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:07 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:07 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:13 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:13 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:16 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:16 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:18 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:24 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:24 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:26 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:26 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:29 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:29 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:30 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:30 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:26:30 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:27:38 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:27:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:27:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:27:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:50:12 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:50:12 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:50:14 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:52:21 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:52:22 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 11:52:23 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:13:36 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:13:36 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:13:38 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:31:06 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:31:06 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:31:09 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:55:27 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:55:27 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:55:28 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:55:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:55:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:55:47 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:59:34 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:59:34 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 12:59:36 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 13:06:38 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 13:06:39 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 13:06:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 13:14:03 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 13:14:03 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 13:14:04 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 13:14:05 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 13:14:12 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-17 14:32:31 | User: Unknown | Action: Delete Hold Type | Details: +Timestamp: 2026-03-17 15:04:02 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-17 15:04:10 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:04:10 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:04:12 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:07:10 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:07:11 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:07:12 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:07:12 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:07:14 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:07:36 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-17 15:07:39 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:13 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-17 15:11:15 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:11:17 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:11:28 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:31 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:32 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:32 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:32 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:32 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:32 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-17 15:11:36 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:11:36 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:11:38 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:14:10 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:14:10 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:14:11 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:24:20 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:24:20 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:24:20 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:27:23 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:23 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:24 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:24 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:25 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:25 | User: Unknown | Action: Add 'District' | Details: +Timestamp: 2026-03-17 15:27:35 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:36 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:36 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:36 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:37 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:37 | User: Unknown | Action: Add 'Block' | Details: +Timestamp: 2026-03-17 15:27:50 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:50 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:51 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:51 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:51 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-17 15:27:51 | User: Unknown | Action: Add 'Village' | Details: +Timestamp: 2026-03-17 15:28:36 | User: Unknown | Action: Add Subcontractor | Details: +Timestamp: 2026-03-17 15:28:40 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-17 15:29:18 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-17 15:29:21 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-17 15:29:27 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-17 15:29:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:29:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:29:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:33:42 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-17 15:33:50 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-17 15:33:51 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-17 15:33:53 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-17 15:33:55 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-17 15:34:03 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-17 15:34:09 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-17 15:34:12 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-17 15:34:40 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-17 15:34:43 | User: Unknown | Action: Edit District | Details: +Timestamp: 2026-03-17 15:34:54 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-17 15:35:01 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-17 15:35:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:35:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:35:42 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:40:06 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:40:06 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-17 15:40:07 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-18 11:06:03 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-18 11:06:03 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-18 11:06:08 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-18 17:47:20 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-18 17:47:20 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-18 17:47:20 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-18 17:47:21 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-18 17:47:21 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-18 17:47:23 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-03-18 17:47:30 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-18 17:47:31 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-18 17:47:31 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-18 17:47:31 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-18 17:47:31 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-18 17:47:32 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-18 17:47:32 | User: Unknown | Action: Add Item | Details: +Timestamp: 2026-03-18 17:47:40 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-18 17:47:40 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-18 17:47:40 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-18 17:47:40 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-18 17:47:40 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-18 17:47:41 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-18 17:47:42 | User: Unknown | Action: Add Block | Details: +Timestamp: 2026-03-18 17:47:50 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-18 17:47:50 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-18 17:47:50 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-18 17:47:50 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-18 17:47:50 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-18 17:47:51 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-18 17:47:51 | User: Unknown | Action: Add Village | Details: +Timestamp: 2026-03-18 18:10:38 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:10:40 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:10:41 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:10:41 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:10:45 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:10:45 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:10:45 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:10:45 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:10:49 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:10:50 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:11:07 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:11:07 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:11:07 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:13:01 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-18 18:14:59 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-20 15:28:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-20 15:28:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-20 15:28:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-20 15:28:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-20 15:28:50 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-03-20 15:29:03 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-03-20 15:29:10 | User: Unknown | Action: Delete State | Details: +Timestamp: 2026-03-20 15:54:10 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-20 15:54:34 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:38 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 15:54:49 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 15:54:49 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 15:54:49 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 15:54:51 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 16:02:57 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-20 16:02:59 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-20 16:03:26 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-20 16:03:26 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-20 16:03:27 | User: Unknown | Action: Add Village | Details: +Timestamp: 2026-03-20 16:03:56 | User: Unknown | Action: Add Subcontractor | Details: +Timestamp: 2026-03-20 16:04:05 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-20 16:05:09 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-20 16:13:43 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-20 16:29:34 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-20 16:29:37 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-20 16:30:45 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-20 16:31:08 | User: Unknown | Action: Add Subcontractor | Details: +Timestamp: 2026-03-20 16:31:15 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-20 16:31:34 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-20 16:31:37 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-20 16:31:37 | User: Unknown | Action: Add Village | Details: +Timestamp: 2026-03-20 16:31:44 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-20 16:32:26 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-20 16:32:28 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-20 16:33:12 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-20 16:39:53 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-20 16:43:25 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-20 16:44:02 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-20 16:48:00 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-20 16:53:47 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-20 16:58:22 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-20 16:58:35 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-20 17:00:42 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-20 17:00:53 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-20 17:02:08 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-20 17:02:14 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-20 17:13:50 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:13:53 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:30:27 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-20 17:33:14 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-20 17:33:34 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:33:37 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:40:01 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-20 17:42:08 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-20 17:51:11 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:51:15 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:08 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:12 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 17:53:13 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-20 18:02:56 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 18:02:57 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 18:02:59 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 18:25:56 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 18:25:56 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 18:25:58 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 22:09:59 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-20 22:10:06 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 22:10:07 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 22:10:09 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 22:10:18 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-20 22:14:46 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 22:14:46 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 22:14:47 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 22:14:49 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 22:15:02 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-20 22:15:46 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 22:15:46 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 22:15:48 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-20 22:15:55 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-20 22:16:15 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-20 22:16:46 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-21 12:18:15 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-21 12:26:48 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-21 12:37:59 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-21 15:04:29 | User: Unknown | Action: Logout | Details: +Timestamp: 2026-03-21 15:27:44 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-21 15:27:54 | User: Unknown | Action: Logout | Details: +Timestamp: 2026-03-21 15:28:01 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-21 16:38:40 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:38:40 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:38:40 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:38:41 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-03-21 16:38:49 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-03-21 16:38:54 | User: Unknown | Action: Delete State | Details: +Timestamp: 2026-03-21 16:39:13 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:39:13 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:39:25 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-21 16:39:25 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-21 16:39:25 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-21 16:39:26 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-21 16:39:26 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-21 16:39:28 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-21 16:39:28 | User: Unknown | Action: Add Item | Details: +Timestamp: 2026-03-21 16:39:40 | User: Unknown | Action: Edit Item | Details: +Timestamp: 2026-03-21 16:39:47 | User: Unknown | Action: Delete Item | Details: +Timestamp: 2026-03-21 16:40:12 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-21 16:40:12 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-21 16:40:14 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-21 16:40:14 | User: Unknown | Action: Add Block | Details: +Timestamp: 2026-03-21 16:40:25 | User: Unknown | Action: Edit Block | Details: +Timestamp: 2026-03-21 16:40:39 | User: Unknown | Action: Delete Block | Details: +Timestamp: 2026-03-21 16:40:56 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-21 16:40:56 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-21 16:40:57 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-21 16:40:58 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-21 16:40:58 | User: Unknown | Action: Add Village | Details: +Timestamp: 2026-03-21 16:41:10 | User: Unknown | Action: Edit Village | Details: +Timestamp: 2026-03-21 16:41:42 | User: Unknown | Action: Edit Village | Details: +Timestamp: 2026-03-21 16:52:28 | User: Unknown | Action: Edit Village | Details: +Timestamp: 2026-03-21 16:52:37 | User: Unknown | Action: Edit Village | Details: +Timestamp: 2026-03-21 16:52:44 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-21 16:56:09 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-21 16:56:17 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-21 16:56:46 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:46 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:46 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:46 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:47 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:48 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:49 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:50 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:50 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-21 16:56:50 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-03-21 16:56:59 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-03-21 16:57:08 | User: Unknown | Action: Delete State | Details: +Timestamp: 2026-03-21 16:57:22 | User: Unknown | Action: Check Item | Details: +Timestamp: 2026-03-21 16:57:24 | User: Unknown | Action: Add Item | Details: +Timestamp: 2026-03-21 16:57:35 | User: Unknown | Action: Edit Item | Details: +Timestamp: 2026-03-21 16:57:42 | User: Unknown | Action: Delete Item | Details: +Timestamp: 2026-03-21 16:58:02 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-21 16:58:03 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-21 16:58:03 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-21 16:58:05 | User: Unknown | Action: Check Block | Details: +Timestamp: 2026-03-21 16:58:05 | User: Unknown | Action: Add Block | Details: +Timestamp: 2026-03-21 16:58:20 | User: Unknown | Action: Edit Block | Details: +Timestamp: 2026-03-21 16:58:30 | User: Unknown | Action: Delete Block | Details: +Timestamp: 2026-03-21 16:58:57 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-21 16:58:57 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-21 16:58:58 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-21 16:58:58 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-21 16:59:00 | User: Unknown | Action: Check Village | Details: +Timestamp: 2026-03-21 16:59:00 | User: Unknown | Action: Add Village | Details: +Timestamp: 2026-03-21 16:59:10 | User: Unknown | Action: Edit Village | Details: +Timestamp: 2026-03-21 16:59:15 | User: Unknown | Action: Delete Village | Details: +Timestamp: 2026-03-21 17:00:22 | User: Unknown | Action: Add Subcontractor | Details: +Timestamp: 2026-03-21 17:00:31 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-21 17:01:22 | User: Unknown | Action: Add invoice | Details: +Timestamp: 2026-03-21 17:01:24 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-21 17:02:51 | User: Unknown | Action: Edit invoice | Details: +Timestamp: 2026-03-21 17:03:41 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-21 17:04:01 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-21 17:04:43 | User: Unknown | Action: Add GST Release | Details: +Timestamp: 2026-03-21 17:05:11 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-21 17:05:18 | User: Unknown | Action: Add Hold Type | Details: +Timestamp: 2026-03-21 17:09:22 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:25 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 17:09:36 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:09:36 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:09:37 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:09:39 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:19:07 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-21 17:23:07 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:23:08 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:23:10 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:24:38 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-21 17:24:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:24:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:34:13 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:34:13 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:34:15 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:39:16 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:39:17 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:39:17 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:39:19 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:42:00 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 17:42:02 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:21:01 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-21 18:23:21 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-21 18:26:56 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-21 18:26:58 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:27:20 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-21 18:27:21 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:27:21 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:27:21 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:27:21 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:27:21 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:27:21 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:27:21 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:27:21 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:27:21 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:27:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:27:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:27:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:27:46 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:30:17 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-21 18:30:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:30:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:30:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:30:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:30:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:30:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:30:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:30:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:30:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:30:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:30:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:30:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:30:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:30:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:30:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:30:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:31:07 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:11 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-21 18:31:17 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:31:17 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:31:17 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:31:17 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-21 18:31:20 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 12:25:56 | User: Unknown | Action: Add Subcontractor | Details: +Timestamp: 2026-03-22 12:27:17 | User: Unknown | Action: Edit Subcontractor | Details: +Timestamp: 2026-03-22 12:27:31 | User: Unknown | Action: Add Subcontractor | Details: +Timestamp: 2026-03-22 12:27:39 | User: Unknown | Action: Edit Subcontractor | Details: +Timestamp: 2026-03-22 12:32:18 | User: Unknown | Action: Edit Subcontractor | Details: +Timestamp: 2026-03-22 12:33:19 | User: Unknown | Action: Add Subcontractor | Details: +Timestamp: 2026-03-22 12:33:40 | User: Unknown | Action: Edit Subcontractor | Details: +Timestamp: 2026-03-22 12:40:57 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-22 12:40:58 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-03-22 12:42:22 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-22 12:42:23 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-03-22 12:42:28 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-03-22 12:42:37 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-03-22 12:42:42 | User: Unknown | Action: Delete State | Details: +Timestamp: 2026-03-22 12:45:49 | User: Unknown | Action: Add Payment | Details: +Timestamp: 2026-03-22 12:46:08 | User: Unknown | Action: Edit Payment | Details: +Timestamp: 2026-03-22 12:46:14 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-22 13:00:54 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-22 13:16:59 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-22 13:17:00 | User: Unknown | Action: Add State | Details: +Timestamp: 2026-03-22 13:17:08 | User: Unknown | Action: Edit State | Details: +Timestamp: 2026-03-22 13:17:12 | User: Unknown | Action: Delete State | Details: +Timestamp: 2026-03-22 13:17:16 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-22 13:17:16 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-22 13:17:17 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-22 13:17:17 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-22 13:17:17 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-22 13:17:17 | User: Unknown | Action: Check State | Details: +Timestamp: 2026-03-22 13:17:34 | User: Unknown | Action: Add Subcontractor | Details: +Timestamp: 2026-03-22 13:17:43 | User: Unknown | Action: Edit Subcontractor | Details: +Timestamp: 2026-03-22 13:18:07 | User: Unknown | Action: Edit Subcontractor | Details: +Timestamp: 2026-03-22 14:41:43 | User: Unknown | Action: Login | Details: +Timestamp: 2026-03-22 14:41:52 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:41:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:32 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:32 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:33 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:33 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:33 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:33 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:34 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:34 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:34 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:34 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:34 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:34 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:35 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:35 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:35 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:35 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:35 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:36 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:36 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:37 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:37 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:38 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:38 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:38 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:39 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:39 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:39 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:42 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:42 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:42 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:42 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:46 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:46 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:55 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:55 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:57 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:43:57 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:44:18 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-22 14:44:26 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:44:26 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:44:26 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:44:27 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:44:27 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:44:27 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:44:27 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:44:41 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:44:51 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:12 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-22 14:46:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:18 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:19 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 14:46:29 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:29 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:31 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:31 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:31 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:31 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:34 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:34 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:35 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:35 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:35 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:35 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:35 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:35 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:37 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:38 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:39 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:39 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:39 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:46:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 14:47:00 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-22 15:43:40 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:41 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:42 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:42 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:42 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:42 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:42 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:43 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:44 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:45 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:43:47 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:44:25 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-22 15:46:11 | User: Unknown | Action: Delete Payment | Details: +Timestamp: 2026-03-22 15:46:49 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:46:49 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:46:50 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:47:09 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:47:09 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:47:09 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:52:16 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:52:18 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:55:27 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:55:27 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:55:27 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:55:29 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:55:31 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:55:32 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:55:33 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:49 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:50 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:50 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:51 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:51 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:51 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:51 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:52 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:52 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:52 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:52 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:52 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:52 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:54 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:57:54 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:58:07 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:58:08 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:58:08 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:58:10 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:58:10 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:58:10 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:58:10 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:58:12 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 15:58:12 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 16:11:47 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 16:11:48 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 16:25:22 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-22 16:26:30 | User: Unknown | Action: Get hold type | Details: +Timestamp: 2026-03-22 16:27:56 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 16:27:57 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 17:12:59 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 17:13:00 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 17:32:37 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 17:32:39 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 17:49:52 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 17:49:56 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 17:52:53 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 17:52:55 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 18:04:04 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 18:04:09 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 18:04:15 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-22 18:07:07 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-22 18:11:40 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-22 18:15:29 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 18:15:31 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 18:15:37 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 18:15:38 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 18:15:41 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-22 18:18:47 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 18:18:48 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 18:30:11 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 18:30:14 | User: Unknown | Action: Search Contractor | Details: +Timestamp: 2026-03-22 18:34:50 | User: Unknown | Action: Download PMC Report | Details: +Timestamp: 2026-03-22 18:52:52 | User: Unknown | Action: Upload Excel File | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: +Timestamp: 2026-03-22 18:52:55 | User: Unknown | Action: Data saved | Details: diff --git a/config.py b/config.py index 5d13397..b147757 100644 --- a/config.py +++ b/config.py @@ -1,12 +1,15 @@ - import mysql.connector import os +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() # Get MySQL credentials from environment variables -MYSQL_HOST = os.getenv("MYSQL_HOST", "127.0.0.1") -MYSQL_USER = os.getenv("MYSQL_USER", "root") -MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "root") -MYSQL_DB = os.getenv("MYSQL_DB", "test") +MYSQL_HOST = os.getenv("MYSQL_HOST") +MYSQL_USER = os.getenv("MYSQL_USER") +MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD") +MYSQL_DB = os.getenv("MYSQL_DB") # Connect to MySQL def get_db_connection(): diff --git a/controllers/__pycache__/auth_controller.cpython-313.pyc b/controllers/__pycache__/auth_controller.cpython-313.pyc new file mode 100644 index 0000000..f2c185d Binary files /dev/null and b/controllers/__pycache__/auth_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/auth_controller.cpython-314.pyc b/controllers/__pycache__/auth_controller.cpython-314.pyc new file mode 100644 index 0000000..7cb6550 Binary files /dev/null and b/controllers/__pycache__/auth_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/block_controller.cpython-313.pyc b/controllers/__pycache__/block_controller.cpython-313.pyc new file mode 100644 index 0000000..ebd7336 Binary files /dev/null and b/controllers/__pycache__/block_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/block_controller.cpython-314.pyc b/controllers/__pycache__/block_controller.cpython-314.pyc new file mode 100644 index 0000000..6b94898 Binary files /dev/null and b/controllers/__pycache__/block_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/district_controller.cpython-313.pyc b/controllers/__pycache__/district_controller.cpython-313.pyc new file mode 100644 index 0000000..f0d3aff Binary files /dev/null and b/controllers/__pycache__/district_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/district_controller.cpython-314.pyc b/controllers/__pycache__/district_controller.cpython-314.pyc new file mode 100644 index 0000000..a85b069 Binary files /dev/null and b/controllers/__pycache__/district_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/excel_upload_controller.cpython-313.pyc b/controllers/__pycache__/excel_upload_controller.cpython-313.pyc new file mode 100644 index 0000000..615b5fd Binary files /dev/null and b/controllers/__pycache__/excel_upload_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/excel_upload_controller.cpython-314.pyc b/controllers/__pycache__/excel_upload_controller.cpython-314.pyc new file mode 100644 index 0000000..a30548e Binary files /dev/null and b/controllers/__pycache__/excel_upload_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/gst_release_controller.cpython-313.pyc b/controllers/__pycache__/gst_release_controller.cpython-313.pyc new file mode 100644 index 0000000..1386a8f Binary files /dev/null and b/controllers/__pycache__/gst_release_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/gst_release_controller.cpython-314.pyc b/controllers/__pycache__/gst_release_controller.cpython-314.pyc new file mode 100644 index 0000000..ff435e4 Binary files /dev/null and b/controllers/__pycache__/gst_release_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/hold_types_controller.cpython-313.pyc b/controllers/__pycache__/hold_types_controller.cpython-313.pyc new file mode 100644 index 0000000..8b47145 Binary files /dev/null and b/controllers/__pycache__/hold_types_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/hold_types_controller.cpython-314.pyc b/controllers/__pycache__/hold_types_controller.cpython-314.pyc new file mode 100644 index 0000000..f9eb4d1 Binary files /dev/null and b/controllers/__pycache__/hold_types_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/invoice_controller.cpython-313.pyc b/controllers/__pycache__/invoice_controller.cpython-313.pyc new file mode 100644 index 0000000..804728f Binary files /dev/null and b/controllers/__pycache__/invoice_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/invoice_controller.cpython-314.pyc b/controllers/__pycache__/invoice_controller.cpython-314.pyc new file mode 100644 index 0000000..6bfc202 Binary files /dev/null and b/controllers/__pycache__/invoice_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/log_controller.cpython-313.pyc b/controllers/__pycache__/log_controller.cpython-313.pyc new file mode 100644 index 0000000..684200c Binary files /dev/null and b/controllers/__pycache__/log_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/log_controller.cpython-314.pyc b/controllers/__pycache__/log_controller.cpython-314.pyc new file mode 100644 index 0000000..0e24c26 Binary files /dev/null and b/controllers/__pycache__/log_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/payment_controller.cpython-313.pyc b/controllers/__pycache__/payment_controller.cpython-313.pyc new file mode 100644 index 0000000..43f45df Binary files /dev/null and b/controllers/__pycache__/payment_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/payment_controller.cpython-314.pyc b/controllers/__pycache__/payment_controller.cpython-314.pyc new file mode 100644 index 0000000..c942139 Binary files /dev/null and b/controllers/__pycache__/payment_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/pmc_report_controller.cpython-313.pyc b/controllers/__pycache__/pmc_report_controller.cpython-313.pyc new file mode 100644 index 0000000..48cb7be Binary files /dev/null and b/controllers/__pycache__/pmc_report_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/pmc_report_controller.cpython-314.pyc b/controllers/__pycache__/pmc_report_controller.cpython-314.pyc new file mode 100644 index 0000000..76b3ae2 Binary files /dev/null and b/controllers/__pycache__/pmc_report_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/report_controller.cpython-313.pyc b/controllers/__pycache__/report_controller.cpython-313.pyc new file mode 100644 index 0000000..15b9a92 Binary files /dev/null and b/controllers/__pycache__/report_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/report_controller.cpython-314.pyc b/controllers/__pycache__/report_controller.cpython-314.pyc new file mode 100644 index 0000000..bc846ee Binary files /dev/null and b/controllers/__pycache__/report_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/state_controller.cpython-313.pyc b/controllers/__pycache__/state_controller.cpython-313.pyc new file mode 100644 index 0000000..7fa1d64 Binary files /dev/null and b/controllers/__pycache__/state_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/state_controller.cpython-314.pyc b/controllers/__pycache__/state_controller.cpython-314.pyc new file mode 100644 index 0000000..ba9b5ce Binary files /dev/null and b/controllers/__pycache__/state_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/subcontractor_controller.cpython-313.pyc b/controllers/__pycache__/subcontractor_controller.cpython-313.pyc new file mode 100644 index 0000000..b220d1e Binary files /dev/null and b/controllers/__pycache__/subcontractor_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/subcontractor_controller.cpython-314.pyc b/controllers/__pycache__/subcontractor_controller.cpython-314.pyc new file mode 100644 index 0000000..8f6332d Binary files /dev/null and b/controllers/__pycache__/subcontractor_controller.cpython-314.pyc differ diff --git a/controllers/__pycache__/village_controller.cpython-313.pyc b/controllers/__pycache__/village_controller.cpython-313.pyc new file mode 100644 index 0000000..4f0154b Binary files /dev/null and b/controllers/__pycache__/village_controller.cpython-313.pyc differ diff --git a/controllers/__pycache__/village_controller.cpython-314.pyc b/controllers/__pycache__/village_controller.cpython-314.pyc new file mode 100644 index 0000000..888646e Binary files /dev/null and b/controllers/__pycache__/village_controller.cpython-314.pyc differ diff --git a/controllers/auth_controller.py b/controllers/auth_controller.py new file mode 100644 index 0000000..0e63627 --- /dev/null +++ b/controllers/auth_controller.py @@ -0,0 +1,46 @@ +from flask import Blueprint, render_template, request, redirect, url_for, flash, session +from flask_login import login_user, logout_user, login_required, current_user + +from model.Auth import LoginLDAP, User +from model.Log import LogHelper + +auth_bp = Blueprint('auth', __name__) + + +@auth_bp.route('/login', methods=['GET', 'POST']) +def login(): + + if request.method == 'POST': + + loginData = LoginLDAP(request) + + if loginData.isValidLogin: + + if loginData.isDefaultCredentials: + LogHelper.log_action('Login', f"User {loginData.username} logged in (static user)") + else: + LogHelper.log_action('Login', f"User {loginData.username} logged in (LDAP)") + + session['username'] = loginData.username + + login_user(User(loginData.username)) + + return redirect(url_for('index')) + + else: + flash(loginData.errorMessage, 'danger') + + return render_template("login.html") + + +@auth_bp.route('/logout') +@login_required +def logout(): + + LogHelper.log_action('Logout', f"User {current_user.id} logged out") + + logout_user() + + flash('You have been logged out.', 'info') + + return redirect(url_for('auth.login')) \ No newline at end of file diff --git a/controllers/block_controller.py b/controllers/block_controller.py new file mode 100644 index 0000000..7e04a13 --- /dev/null +++ b/controllers/block_controller.py @@ -0,0 +1,119 @@ +from flask import Blueprint, render_template, request, redirect, url_for, jsonify +from flask_login import login_required + +import config +import mysql.connector + +from model.Block import Block +from model.Utilities import HtmlHelper + +block_bp = Blueprint('block', __name__) + + +@block_bp.route('/add_block', methods=['GET', 'POST']) +@login_required +def add_block(): + + block = Block() + + if request.method == 'POST': + block.AddBlock(request) + return block.resultMessage + + connection = config.get_db_connection() + cursor = connection.cursor() + + cursor.callproc("GetAllStates") + for rs in cursor.stored_results(): + states = rs.fetchall() + + block_data = block.GetAllBlocks(request) + + cursor.close() + connection.close() + + return render_template( + 'add_block.html', + states=states, + block_data=block_data + ) + + +# ✅ NEW ROUTE (FIX FOR DISTRICT FETCH) +@block_bp.route('/get_districts/') +@login_required +def get_districts(state_id): + + connection = config.get_db_connection() + cursor = connection.cursor() + + cursor.callproc("GetDistrictsByStateId", (state_id,)) + + districts = [] + for rs in cursor.stored_results(): + districts = rs.fetchall() + + cursor.close() + connection.close() + + district_list = [] + + for district in districts: + district_list.append({ + "id": district[0], + "name": district[1] + }) + + return jsonify(district_list) + + +@block_bp.route('/check_block', methods=['POST']) +@login_required +def check_block(): + + block = Block() + return block.CheckBlock(request) + + +@block_bp.route('/edit_block/', methods=['GET', 'POST']) +@login_required +def edit_block(block_id): + + block = Block() + + if request.method == 'POST': + block.EditBlock(request, block_id) + return block.resultMessage + + connection = config.get_db_connection() + cursor = connection.cursor() + + cursor.callproc("GetAllStates") + for rs in cursor.stored_results(): + states = rs.fetchall() + + cursor.callproc("GetAllDistrictsData") + for rs in cursor.stored_results(): + districts = rs.fetchall() + + block_data = block.GetBlockByID(block_id) + + cursor.close() + connection.close() + + return render_template( + 'edit_block.html', + block_data=block_data, + states=states, + districts=districts + ) + + +@block_bp.route('/delete_block/') +@login_required +def delete_block(block_id): + + block = Block() + block.DeleteBlock(request, block_id) + + return redirect(url_for('block.add_block')) \ No newline at end of file diff --git a/controllers/district_controller.py b/controllers/district_controller.py new file mode 100644 index 0000000..d326d68 --- /dev/null +++ b/controllers/district_controller.py @@ -0,0 +1,84 @@ +from flask import Blueprint, render_template, request, redirect, url_for, flash +from flask_login import login_required + +from model.District import District +from model.State import State + +district_bp = Blueprint('district', __name__) + + +@district_bp.route('/add_district', methods=['GET', 'POST']) +@login_required +def add_district(): + + district = District() + + if request.method == 'POST': + district.AddDistrict(request=request) + return district.resultMessage + + state = State() + states = state.GetAllStates(request=request) + + districtdata = district.GetAllDistricts(request=request) + + return render_template( + 'add_district.html', + districtdata=districtdata, + states=states + ) + + +@district_bp.route('/check_district', methods=['POST']) +@login_required +def check_district(): + + district = District() + + return district.CheckDistrict(request=request) + + +@district_bp.route('/delete_district/') +@login_required +def delete_district(district_id): + + district = District() + + district.DeleteDistrict(request=request, district_id=district_id) + + if not district.isSuccess: + return district.resultMessage + else: + return redirect(url_for('district.add_district')) + + +@district_bp.route('/edit_district/', methods=['GET', 'POST']) +@login_required +def edit_district(district_id): + + district = District() + state = State() + + if request.method == 'POST': + + district.EditDistrict(request=request, district_id=district_id) + + if district.isSuccess: + flash("District updated successfully!", "success") + return redirect(url_for('district.add_district')) + else: + flash(district.resultMessage, "error") + + districtdata = district.GetDistrictByID(request=request, district_id=district_id) + + if not districtdata: + flash("District not found", "error") + return redirect(url_for('district.add_district')) + + states = state.GetAllStates(request=request) + + return render_template( + 'edit_district.html', + districtdata=districtdata, + states=states + ) \ No newline at end of file diff --git a/controllers/excel_upload_controller.py b/controllers/excel_upload_controller.py new file mode 100644 index 0000000..0f543d5 --- /dev/null +++ b/controllers/excel_upload_controller.py @@ -0,0 +1,388 @@ +import os +import ast +import re +from flask_login import login_required +import openpyxl +from flask import Blueprint, request, render_template, redirect, url_for, jsonify, current_app +from flask_login import current_user +from model.Log import LogHelper +import config +from model.FolderAndFile import FolderAndFile + +excel_bp = Blueprint('excel', __name__) + +# Default folder in case config not set +# DEFAULT_UPLOAD_FOLDER = 'uploads' + + +# def get_upload_folder(): +# """Returns the upload folder from Flask config or default, ensures it exists.""" +# folder = current_app.config.get('UPLOAD_FOLDER', DEFAULT_UPLOAD_FOLDER) +# if not os.path.exists(folder): +# os.makedirs(folder) +# return folder + + +# ---------------- Upload Excel File ---------------- +@excel_bp.route('/upload_excel_file', methods=['GET', 'POST']) +@login_required +def upload(): + if request.method == 'POST': + file = request.files.get('file') + if file and file.filename.endswith('.xlsx'): + # upload_folder = get_upload_folder() + # filepath = os.path.join(upload_folder, file.filename) + + filepath =FolderAndFile.get_upload_path(file.filename) + + file.save(filepath) + + LogHelper.log_action( + "Upload Excel File", + f"User {current_user.id} Upload Excel File '{file.filename}'" + ) + return redirect(url_for('excel.show_table', filename=file.filename)) + return render_template('uploadExcelFile.html') + + +# ---------------- Show Excel Table ---------------- +@excel_bp.route('/show_table/') +def show_table(filename): + global data + data = [] + + # filepath = os.path.join(get_upload_folder(), filename) + filepath = FolderAndFile.get_upload_path(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) + + 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: + 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: + 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']}'.") + + cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']]) + for result in cursor.stored_results(): + subcontractor_data = result.fetchone() + + if not subcontractor_data: + cursor.callproc('InsertSubcontractor', [file_info['Subcontractor']]) + connection.commit() + cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']]) + for result in cursor.stored_results(): + subcontractor_data = result.fetchone() + + 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 Exception 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) + 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 + ) + + + # save Excel data +@excel_bp.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") + if not data: + return jsonify({"error": "No data provided to save"}), 400 + if 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 + LogHelper.log_action("Data saved", f"User {current_user.id} Data saved'{ village_name}'") + 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.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.callproc("SaveVillage", (village_name, block_id)) + 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) + + 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 + ) + + print("All invoice Details ",args) + results = cursor.callproc('SaveInvoice', args) + invoice_id = results[-1] + print("invoice id from the excel ", invoice_id) + if isinstance(hold_columns, str): + hold_columns = ast.literal_eval(hold_columns) + 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}") + hold_join_data = { + "Contractor_Id": subcontractor_id, + "Invoice_Id": invoice_id, + "hold_type_id": hold_type_id, + "hold_amount": hold_amount + } + 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','logging report']): + 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.callproc( + 'AddCreditNoteFromExcel', + [ + 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---------------------------------------------------------------------- + # Step 1: Normalize Invoice_Details: lowercase, trim, remove extra spaces + normalized_details = re.sub(r'\s+', ' ', Invoice_Details.strip()).lower() + # Step 2: Define lowercase keywords + keywords = [ + 'excess hold', + 'ht', + 'hold release amount', + 'dpr excess hold amount', + 'excess hold amount', + 'Multi to Single layer bill', + 'hold amount', + 'logging report' + ] + # Step 3: Matching condition + if any(kw in normalized_details for kw in keywords): + print("✅ Match found. Inserting hold release for:", Invoice_Details) + cursor.callproc( + 'AddHoldReleaseFromExcel', + [PMC_No, Invoice_No, Invoice_Details, Basic_Amount, Final_Amount, UTR, subcontractor_id] +) + connection.commit() + print("✅ Hold release inserted for:", 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,Total_Amount,UTR, subcontractor_id) + cursor.callproc( + 'AddGSTReleaseFromExcel', + [PMC_No, Invoice_No, Basic_Amount, Final_Amount, Total_Amount, UTR, subcontractor_id] + ) + + if PMC_No and Total_Amount and UTR: + print("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 )) + if not village_id: + village_id = None + cursor.callproc('InsertOrUpdateInPayment', ( + 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, + 0, + GST_SD_Amount, + Final_Amount, + Payment_Amount, + TDS_Payment_Amount, + Total_Amount, + UTR, + subcontractor_id + )) + connection.commit() + return jsonify({"success": "Data saved successfully!"}), 200 + 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 -------------------------------- \ No newline at end of file diff --git a/controllers/gst_release_controller.py b/controllers/gst_release_controller.py new file mode 100644 index 0000000..a5fea92 --- /dev/null +++ b/controllers/gst_release_controller.py @@ -0,0 +1,70 @@ +from flask import Blueprint, render_template, request, redirect, url_for, jsonify +from flask_login import login_required, current_user +from model.gst_release import GSTReleasemodel +from model.Log import LogHelper + +gst_release_bp = Blueprint('gst_release_bp', __name__) + +# ------------------- Add GST Release ------------------- +@gst_release_bp.route('/add_gst_release', methods=['GET', 'POST']) +@login_required +def add_gst_release(): + gst_releases_dict = GSTReleasemodel.fetch_all_gst_releases() + gst_releases = [ + [ + g['GST_Release_Id'], g['PMC_No'], g['invoice_no'], + g['Basic_Amount'], g['Final_Amount'], g['Total_Amount'], g['UTR'] + ] for g in gst_releases_dict + ] if gst_releases_dict else [] + + 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'] + total_amount = request.form['total_amount'] + utr = request.form['utr'] + contractor_id = request.form['subcontractor_id'] + + LogHelper.log_action("Add GST Release", f"User {current_user.id} added GST release '{pmc_no}'") + GSTReleasemodel.insert_gst_release(pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr, contractor_id) + + return redirect(url_for('gst_release_bp.add_gst_release')) + + return render_template('add_gst_release.html', gst_releases=gst_releases, invoices=[]) + + +# ------------------- Edit GST Release ------------------- +@gst_release_bp.route('/edit_gst_release/', methods=['GET', 'POST']) +@login_required +def edit_gst_release(gst_release_id): + gst_release_data = GSTReleasemodel.fetch_gst_release_by_id(gst_release_id) + if not gst_release_data: + return "GST Release not found", 404 + + 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'] + total_amount = request.form['total_amount'] + utr = request.form['utr'] + + LogHelper.log_action("Edit GST Release", f"User {current_user.id} edited GST release '{pmc_no}'") + GSTReleasemodel.update_gst_release(gst_release_id, pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr) + + return redirect(url_for('gst_release_bp.add_gst_release')) + + return render_template('edit_gst_release.html', gst_release_data=gst_release_data, invoices=[]) + + +# ------------------- Delete GST Release ------------------- +@gst_release_bp.route('/delete_gst_release/', methods=['GET', 'POST']) +@login_required +def delete_gst_release(gst_release_id): + success, utr = GSTReleasemodel.delete_gst_release(gst_release_id) + if not success: + return jsonify({"message": "GST Release not found or failed to delete", "status": "error"}), 404 + + LogHelper.log_action("Delete GST Release", f"User {current_user.id} deleted GST release '{gst_release_id}'") + return jsonify({"message": f"GST Release {gst_release_id} deleted successfully.", "status": "success"}), 200 \ No newline at end of file diff --git a/controllers/hold_types_controller.py b/controllers/hold_types_controller.py new file mode 100644 index 0000000..0b163ab --- /dev/null +++ b/controllers/hold_types_controller.py @@ -0,0 +1,77 @@ +from flask import Blueprint, render_template, request, jsonify, redirect, url_for +from flask_login import login_required +from model.HoldTypes import HoldTypes +from model.GST import GST + +hold_bp = Blueprint("hold_types", __name__) + + +# ---------------- ADD HOLD TYPE ---------------- +@hold_bp.route('/add_hold_type', methods=['GET','POST']) +@login_required +def add_hold_type(): + + hold = HoldTypes() + + if request.method == 'POST': + hold.AddHoldType(request) # ✅ + return hold.resultMessage + + hold_types = hold.GetAllHoldTypes() # ✅ + + return render_template( + "add_hold_type.html", + Hold_Types_data=hold_types + ) + + +# ---------------- CHECK HOLD TYPE (OPTIONAL LIKE BLOCK) ---------------- +@hold_bp.route('/check_hold_type', methods=['POST']) +@login_required +def check_hold_type(): + + hold = HoldTypes() + return hold.CheckHoldType(request) # if exists + + +# ---------------- EDIT HOLD TYPE ---------------- +@hold_bp.route('/edit_hold_type/', methods=['GET','POST']) +@login_required +def edit_hold_type(id): + + hold = HoldTypes() + + if request.method == 'POST': + hold.EditHoldType(request, id) # ✅ + return hold.resultMessage + + hold_data = hold.GetHoldTypeByID(id) # ✅ + + return render_template( + "edit_hold_type.html", + hold_type=hold_data + ) + + +# ---------------- DELETE HOLD TYPE ---------------- +@hold_bp.route('/delete_hold_type/') +@login_required +def delete_hold_type(id): + + hold = HoldTypes() + hold.DeleteHoldType(request, id) # ✅ + + return redirect(url_for("hold_types.add_hold_type")) + + +# ---------------- GST ---------------- +@hold_bp.route('/unreleased_gst') +@login_required +def unreleased_gst(): + + data = GST.get_unreleased_gst() + + return render_template( + "unreleased_gst.html", + data=data + ) \ No newline at end of file diff --git a/controllers/invoice_controller.py b/controllers/invoice_controller.py new file mode 100644 index 0000000..d8af37a --- /dev/null +++ b/controllers/invoice_controller.py @@ -0,0 +1,98 @@ +# controllers/invoice_controller.py + +from flask import Blueprint, request, jsonify, render_template +from flask_login import login_required, current_user +from model.Invoice import * +from model.Log import LogHelper + +invoice_bp = Blueprint('invoice', __name__) + +# -------------------------------- Add Invoice --------------------------------- +@invoice_bp.route('/add_invoice', methods=['GET', 'POST']) +@login_required +def add_invoice(): + if request.method == 'POST': + try: + village_name = request.form.get('village') + village_result = get_village_id(village_name) + + if not village_result: + return jsonify({"status": "error", "message": f"Village '{village_name}' not found"}), 400 + + village_id = village_result['Village_Id'] + data = request.form + + invoice_id = insert_invoice(data, village_id) + assign_subcontractor(data, village_id) + insert_hold_types(data, invoice_id) + + LogHelper.log_action("Add invoice", f"User {current_user.id} Added invoice '{data.get('pmc_no')}'") + + return jsonify({"status": "success", "message": "Invoice added successfully"}), 201 + + except Exception as e: + return jsonify({"status": "error", "message": str(e)}), 500 + + invoices = get_all_invoice_details() + villages = get_all_villages() + return render_template('add_invoice.html', invoices=invoices, villages=villages) + + +# ------------------- Search Subcontractor ------------------- +@invoice_bp.route('/search_subcontractor', methods=['POST']) +@login_required +def search_subcontractor(): + sub_query = request.form.get("query") + results = search_contractors(sub_query) + + if not results: + return "
  • No subcontractor found
  • " + + output = "".join( + f"
  • {row['Contractor_Name']}
  • " + for row in results + ) + return output + + +# ------------------- Get Hold Types ------------------- +@invoice_bp.route('/get_hold_types', methods=['GET']) +@login_required +def get_hold_types(): + hold_types = get_all_hold_types() + LogHelper.log_action("Get hold type", f"User {current_user.id} Get hold type '{hold_types}'") + return jsonify(hold_types) + + +# ------------------- Edit Invoice ------------------- +@invoice_bp.route('/edit_invoice/', methods=['GET', 'POST']) +@login_required +def edit_invoice(invoice_id): + if request.method == 'POST': + data = request.form + update_invoice(data, invoice_id) + update_inpayment(data) + + LogHelper.log_action("Edit invoice", f"User {current_user.id} Edit invoice '{invoice_id}'") + return jsonify({"status": "success", "message": "Invoice updated successfully"}), 200 + + invoice = get_invoice_by_id(invoice_id) + return render_template('edit_invoice.html', invoice=invoice) + + +# ------------------- Delete Invoice ------------------- +@invoice_bp.route('/delete_invoice/', methods=['GET']) +@login_required +def delete_invoice_route(invoice_id): + try: + delete_invoice_data(invoice_id, current_user.id) + LogHelper.log_action("Delete Invoice", f"User {current_user.id} deleted Invoice '{invoice_id}'") + return jsonify({ + "message": f"Invoice {invoice_id} deleted successfully.", + "status": "success" + }) + except Exception as e: + return jsonify({ + "message": str(e), + "status": "error" + }), 500 \ No newline at end of file diff --git a/controllers/log_controller.py b/controllers/log_controller.py new file mode 100644 index 0000000..145c91b --- /dev/null +++ b/controllers/log_controller.py @@ -0,0 +1,31 @@ +from flask import Blueprint, render_template, request +from flask_login import login_required + +from model.Log import LogData + +log_bp = Blueprint('log', __name__) + + +@log_bp.route('/activity_log', methods=['GET', 'POST']) +@login_required +def activity_log(): + + start_date = request.values.get("start_date") + end_date = request.values.get("end_date") + user_name = request.values.get("username") + + logData = LogData() + + filtered_logs = logData.GetFilteredActivitiesLog( + start_date, + end_date, + user_name + ) + + return render_template( + "activity_log.html", + logs=filtered_logs, + start_date=start_date, + end_date=end_date, + username=user_name + ) \ No newline at end of file diff --git a/controllers/payment_controller.py b/controllers/payment_controller.py new file mode 100644 index 0000000..a0dfc40 --- /dev/null +++ b/controllers/payment_controller.py @@ -0,0 +1,103 @@ +from flask import Blueprint, render_template, request, redirect, url_for, jsonify +from flask_login import login_required, current_user +from model.payment import Paymentmodel +from model.Log import LogHelper + +payment_bp = Blueprint('payment_bp', __name__) + +# ------------------- Add Payment ------------------- +@payment_bp.route('/add_payment', methods=['GET', 'POST']) +@login_required +def add_payment(): + payments_dicts = Paymentmodel.fetch_all_payments() + # Convert to array for template + payments = [ + [ + p['Payment_Id'], p['PMC_No'], p['Invoice_No'], + p['Payment_Amount'], p['TDS_Payment_Amount'], p['Total_Amount'], p['UTR'] + ] for p in payments_dicts + ] if payments_dicts else [] + + if request.method == 'POST': + subcontractor_id = request.form.get('subcontractor_id') + 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'] + + LogHelper.log_action("Add Payment", f"User {current_user.id} Add Payment '{pmc_no}'") + Paymentmodel.insert_payment(pmc_no, invoice_no, amount, tds_amount, total_amount, utr) + Paymentmodel.update_inpayment(subcontractor_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr) + + return redirect(url_for('payment_bp.add_payment')) + + return render_template('add_payment.html', payments=payments) + + +# ------------------- Get PMC Nos ------------------- +@payment_bp.route('/get_pmc_nos_by_subcontractor/') +@login_required +def get_pmc_nos_by_subcontractor(subcontractorId): + connection = Paymentmodel.get_connection() + cur = connection.cursor() + cur.callproc('GetDistinctPMCNoByContractorId', [subcontractorId]) + results = [] + for result in cur.stored_results(): + results = result.fetchall() + cur.close() + pmc_nos = [row[0] for row in results] + return jsonify({'pmc_nos': pmc_nos}) + + +# ------------------- Edit Payment ------------------- +@payment_bp.route('/edit_payment/', methods=['GET', 'POST']) +@login_required +def edit_payment(payment_id): + payment_data = Paymentmodel.fetch_payment_by_id(payment_id) + + if not payment_data: + return "Payment not found", 404 + + 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'] + + LogHelper.log_action("Edit Payment", f"User {current_user.id} Edit Payment '{pmc_no}'") + Paymentmodel.call_update_payment_proc(payment_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr) + + # Update inpayment + connection = Paymentmodel.get_connection() + cursor = connection.cursor() + cursor.callproc( + 'UpdateInpaymentByPMCInvoiceUTR', + [amount, tds_amount, total_amount, pmc_no, invoice_no, utr] +) + connection.commit() + cursor.close() + connection.close() + + return redirect(url_for('payment_bp.add_payment')) + + return render_template('edit_payment.html', payment_data=payment_data) + + +# ------------------- Delete Payment ------------------- +@payment_bp.route('/delete_payment/', methods=['GET', 'POST']) +@login_required +def delete_payment(payment_id): + success, pmc_no, invoice_no = Paymentmodel.delete_payment(payment_id) + if not success: + return jsonify({"message": "Payment not found or failed to delete", "status": "error"}), 404 + + LogHelper.log_action("Delete Payment", f"User {current_user.id} deleted Payment '{payment_id}'") + + return jsonify({ + "message": f"Payment ID {payment_id} deleted successfully.", + "status": "success" + }), 200 \ No newline at end of file diff --git a/controllers/pmc_report_controller.py b/controllers/pmc_report_controller.py new file mode 100644 index 0000000..7c92cb7 --- /dev/null +++ b/controllers/pmc_report_controller.py @@ -0,0 +1,37 @@ +from flask import Blueprint, render_template, send_from_directory +from flask_login import login_required, current_user +from model.PmcReport import PmcReport + +pmc_report_bp = Blueprint("pmc_report", __name__) + +@pmc_report_bp.route("/pmc_report/") +@login_required +def pmc_report(pmc_no): + data = PmcReport.get_pmc_report(pmc_no) + if not data: + return "No PMC found with this number", 404 + + return render_template( + "pmc_report.html", + info=data["info"], + invoices=data["invoices"], + hold_types=data["hold_types"], + gst_rel=data["gst_rel"], + payments=data["payments"], + credit_note=data["credit_note"], + hold_release=data["hold_release"], + total=data["total"] + ) + +@pmc_report_bp.route("/download_pmc_report/") +@login_required +def download_pmc_report(pmc_no): + + result = PmcReport.download_pmc_report(pmc_no) + + if not result: + return "No contractor found for this PMC No", 404 + + output_folder, file_name = result + + return send_from_directory(output_folder, file_name, as_attachment=True) \ No newline at end of file diff --git a/controllers/report_controller.py b/controllers/report_controller.py new file mode 100644 index 0000000..5810a8a --- /dev/null +++ b/controllers/report_controller.py @@ -0,0 +1,193 @@ +from flask import Blueprint, render_template, request, jsonify, send_file +from flask_login import login_required, current_user +from model.Report import ReportHelper +from model.Log import LogHelper +import config +import os +import openpyxl +from openpyxl.styles import Font +from model.ContractorInfo import ContractorInfo +from model.FolderAndFile import FolderAndFile + + +report_bp = Blueprint("report", __name__) + + +# ---------------- Report Page ---------------- +@report_bp.route("/report") +@login_required +def report_page(): + return render_template("/report.html") + + +# ---------------- Search Contractor ---------------- +@report_bp.route("/search_contractor", methods=["POST"]) +@login_required +def search_contractor(): + + subcontractor_name = request.form.get("subcontractor_name") + + LogHelper.log_action( + "Search Contractor", + f"User {current_user.id} searched contractor '{subcontractor_name}'" + ) + + data = ReportHelper.search_contractor(request) + + return jsonify(data) + +# ---------------- Contractor Report ---------------- +@report_bp.route('/contractor_report/') +@login_required +def contractor_report(contractor_id): + + data = ReportHelper.get_contractor_report(contractor_id) + + return render_template( + 'subcontractor_report.html', + contractor_id=contractor_id, + **data + ) + +@report_bp.route('/download_report/') +@login_required +def download_report(contractor_id): + + return ReportHelper().download_report(contractor_id=contractor_id) + + + + +# @report_bp.route('/download_report/') +# @login_required +# def download_report(contractor_id): +# try: +# connection = config.get_db_connection() +# cursor = connection.cursor(dictionary=True) + +# # -------- Contractor Info -------- +# contractor = ContractorInfo(contractor_id) +# contInfo = contractor.contInfo + +# if not contInfo: +# return "No contractor found", 404 + +# # -------- Invoice Data -------- +# cursor.callproc('FetchInvoicesByContractor', [contractor_id]) + +# invoices = [] +# for result in cursor.stored_results(): +# invoices.extend(result.fetchall()) + +# if not invoices: +# return "No invoice data found" + +# # -------- Create Workbook -------- +# workbook = openpyxl.Workbook() +# sheet = workbook.active +# sheet.title = "Contractor Report" + +# # ================= CONTRACTOR DETAILS ================= +# sheet.append(["SUB CONTRACTOR DETAILS"]) +# sheet.cell(row=sheet.max_row, column=1).font = Font(bold=True) +# sheet.append([]) + +# sheet.append(["Name", contInfo.get("Contractor_Name") or ""]) +# sheet.append(["Mobile No", contInfo.get("Mobile_No") or ""]) +# sheet.append(["Email", contInfo.get("Email") or ""]) +# sheet.append(["Village", contInfo.get("Village_Name") or ""]) +# sheet.append(["Block", contInfo.get("Block_Name") or ""]) +# sheet.append(["District", contInfo.get("District_Name") or ""]) +# sheet.append(["State", contInfo.get("State_Name") or ""]) +# sheet.append(["Address", contInfo.get("Address") or ""]) +# sheet.append(["GST No", contInfo.get("GST_No") or ""]) +# sheet.append(["PAN No", contInfo.get("PAN_No") or ""]) +# sheet.append([]) +# sheet.append([]) + +# # ================= TABLE HEADERS ================= +# headers = [ +# "PMC No", "Village", "Invoice No", "Invoice Date", "Work Type","Invoice_Details", +# "Basic Amount", "Debit Amount", "After Debit Amount", +# "Amount", "GST Amount", "TDS Amount", "SD Amount", +# "On Commission", "Hydro Testing", "Hold Amount", +# "GST SD Amount", "Final Amount", +# "Payment Amount", "TDS Payment", +# "Total Amount", "UTR" +# ] +# sheet.append(headers) +# for col in range(1, len(headers) + 1): +# sheet.cell(row=sheet.max_row, column=col).font = Font(bold=True) + +# # ================= DATA ================= +# total_final = 0 +# total_payment = 0 +# total_amount = 0 + +# for inv in invoices: +# row = [ +# inv.get("PMC_No"), +# inv.get("Village_Name"), +# inv.get("invoice_no"), +# inv.get("Invoice_Date"), +# inv.get("Work_Type"), +# inv.get("Invoice_Details"), +# inv.get("Basic_Amount"), +# inv.get("Debit_Amount"), +# inv.get("After_Debit_Amount"), +# inv.get("Amount"), +# inv.get("GST_Amount"), +# inv.get("TDS_Amount"), +# inv.get("SD_Amount"), +# inv.get("On_Commission"), +# inv.get("Hydro_Testing"), +# inv.get("Hold_Amount"), +# inv.get("GST_SD_Amount"), +# inv.get("Final_Amount"), +# inv.get("Payment_Amount"), +# inv.get("TDS_Payment_Amount"), +# inv.get("Total_Amount"), +# inv.get("UTR") +# ] + +# total_final += float(inv.get("Final_Amount") or 0) +# total_payment += float(inv.get("Payment_Amount") or 0) +# total_amount += float(inv.get("Total_Amount") or 0) + +# sheet.append(row) + +# # ================= TOTAL ROW ================= +# sheet.append([]) +# sheet.append([ +# "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", +# "TOTAL", +# total_final, +# total_payment, +# "", +# total_amount, +# "" +# ]) + +# # ================= AUTO WIDTH ================= +# for column in sheet.columns: +# max_length = 0 +# column_letter = column[0].column_letter +# for cell in column: +# if cell.value: +# max_length = max(max_length, len(str(cell.value))) +# sheet.column_dimensions[column_letter].width = max_length + 2 + +# # ================= SAVE FILE ================= +# # output_folder = "downloads" +# # os.makedirs(output_folder, exist_ok=True) +# # filename = f"Contractor_Report_{contInfo.get('Contractor_Name')}.xlsx" +# # output_file = os.path.join(output_folder, filename) + +# filename = f"Contractor_Report_{contInfo.get('Contractor_Name')}.xlsx" +# output_file = FolderAndFile.get_download_path(filename) +# workbook.save(output_file) + +# return send_file(output_file, as_attachment=True) + +# except Exception as e: +# return str(e) \ No newline at end of file diff --git a/controllers/state_controller.py b/controllers/state_controller.py new file mode 100644 index 0000000..2ccada9 --- /dev/null +++ b/controllers/state_controller.py @@ -0,0 +1,69 @@ +from flask import Blueprint, render_template, request, redirect, url_for +from flask_login import login_required +from model.State import State + +state_bp = Blueprint('state', __name__) + + +@state_bp.route('/add_state', methods=['GET', 'POST']) +@login_required +def add_state(): + + state = State() + + if request.method == 'POST': + state.AddState(request=request) + return state.resultMessage + + statedata = state.GetAllStates(request=request) + + return render_template('add_state.html', statedata=statedata) + + +@state_bp.route('/check_state', methods=['POST']) +@login_required +def check_state(): + + state = State() + + return state.CheckState(request=request) + + +@state_bp.route('/delete_state/') +@login_required +def deleteState(id): + + state = State() + + msg = state.DeleteState(request=request, id=id) + + if not state.isSuccess: + return state.resultMessage + else: + return redirect(url_for('state.add_state')) + + +@state_bp.route('/edit_state/', methods=['GET', 'POST']) +@login_required +def editState(id): + + state = State() + + if request.method == 'POST': + + state.EditState(request=request, id=id) + + if state.isSuccess: + return redirect(url_for('state.add_state')) + else: + return state.resultMessage + + statedata = state.GetStateByID(request=request, id=id) + + if not state.isSuccess: + return state.resultMessage + + if statedata is None: + statedata = [] + + return render_template('edit_state.html', state=statedata) \ No newline at end of file diff --git a/controllers/subcontractor_controller.py b/controllers/subcontractor_controller.py new file mode 100644 index 0000000..884ed6a --- /dev/null +++ b/controllers/subcontractor_controller.py @@ -0,0 +1,117 @@ +from flask import Blueprint, render_template, request, redirect, url_for, jsonify +from flask_login import login_required +from model.Subcontractor import Subcontractor + +subcontractor_bp = Blueprint('subcontractor', __name__) + +# ---------------------------------------------------------- +# Helpers (unchanged) +# ---------------------------------------------------------- +class HtmlHelper: + @staticmethod + def json_response(data, status=200): + return jsonify(data), status + +class ResponseHandler: + @staticmethod + def fetch_failure(entity): + return {"status": "error", "message": f"Failed to fetch {entity}"} + + @staticmethod + def add_failure(entity): + return {"status": "error", "message": f"Failed to add {entity}"} + + @staticmethod + def update_failure(entity): + return {"status": "error", "message": f"Failed to update {entity}"} + + @staticmethod + def delete_failure(entity): + return {"status": "error", "message": f"Failed to delete {entity}"} + + +# ---------------------------------------------------------- +# LIST + ADD +# ---------------------------------------------------------- +@subcontractor_bp.route('/subcontractor', methods=['GET', 'POST']) +@login_required +def subcontract(): + + sub = Subcontractor() + + # ---------------- GET ---------------- + if request.method == 'GET': + subcontractor = sub.GetAllSubcontractors(request) + + if not sub.isSuccess: + return HtmlHelper.json_response( + ResponseHandler.fetch_failure("Subcontractor"), 500 + ) + + return render_template('add_subcontractor.html', subcontractor=subcontractor) + + # ---------------- POST (ADD) ---------------- + if request.method == 'POST': + + sub.AddSubcontractor(request) + + if not sub.isSuccess: + return HtmlHelper.json_response( + ResponseHandler.add_failure("Subcontractor"), 500 + ) + + # Reload list after insert + subcontractor = sub.GetAllSubcontractors(request) + + return render_template('add_subcontractor.html', subcontractor=subcontractor) + + +# ---------------------------------------------------------- +# EDIT +# ---------------------------------------------------------- +@subcontractor_bp.route('/edit_subcontractor/', methods=['GET', 'POST']) +@login_required +def edit_subcontractor(id): + + sub = Subcontractor() + + # Fetch data + subcontractor = sub.GetSubcontractorByID(id) + + if not subcontractor: + return HtmlHelper.json_response( + ResponseHandler.fetch_failure("Subcontractor"), 404 + ) + + # ---------------- POST (UPDATE) ---------------- + if request.method == 'POST': + + sub.EditSubcontractor(request, id) + + if not sub.isSuccess: + return HtmlHelper.json_response( + ResponseHandler.update_failure("Subcontractor"), 500 + ) + + return redirect(url_for('subcontractor.subcontract')) + + return render_template('edit_subcontractor.html', subcontractor=subcontractor) + + +# ---------------------------------------------------------- +# DELETE +# ---------------------------------------------------------- +@subcontractor_bp.route('/deleteSubContractor/', methods=['GET', 'POST']) +@login_required +def deleteSubContractor(id): + + sub = Subcontractor() + + sub.DeleteSubcontractor(request, id) + + if not sub.isSuccess: + return HtmlHelper.json_response( + ResponseHandler.delete_failure("Subcontractor"), 500 + ) + + return redirect(url_for('subcontractor.subcontract')) \ No newline at end of file diff --git a/controllers/village_controller.py b/controllers/village_controller.py new file mode 100644 index 0000000..02df862 --- /dev/null +++ b/controllers/village_controller.py @@ -0,0 +1,167 @@ +from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify +from flask_login import login_required + +import config + +from model.Village import Village +from model.State import State + +# Create Blueprint +village_bp = Blueprint('village', __name__) + + +# ------------------------- Add Village ------------------------- +@village_bp.route('/add_village', methods=['GET', 'POST']) +@login_required +def add_village(): + + village = Village() + + if request.method == 'POST': + village.AddVillage(request=request) + return village.resultMessage + + state = State() + states = state.GetAllStates(request=request) + + villages = village.GetAllVillages(request=request) + + return render_template( + 'add_village.html', + states=states, + villages=villages + ) + + +# ------------------------- Fetch Districts ------------------------- +@village_bp.route('/get_districts/') +@login_required +def get_districts(state_id): + + connection = config.get_db_connection() + cursor = connection.cursor() + + cursor.callproc("GetDistrictByStateID", [state_id]) + + districts = [] + + for rs in cursor.stored_results(): + districts = rs.fetchall() + + cursor.close() + connection.close() + + district_list = [] + + for d in districts: + district_list.append({ + "id": d[0], + "name": d[1] + }) + + return jsonify(district_list) + + +# ------------------------- Fetch Blocks ------------------------- +@village_bp.route('/get_blocks/') +@login_required +def get_blocks(district_id): + + connection = config.get_db_connection() + cursor = connection.cursor() + + cursor.callproc("GetBlocksByDistrictID", [district_id]) + + blocks = [] + + for rs in cursor.stored_results(): + blocks = rs.fetchall() + + cursor.close() + connection.close() + + block_list = [] + + for b in blocks: + block_list.append({ + "id": b[0], + "name": b[1] + }) + + return jsonify(block_list) + + +# ------------------------- Check Village ------------------------- +@village_bp.route('/check_village', methods=['POST']) +@login_required +def check_village(): + + village = Village() + return village.CheckVillage(request=request) + + +# ------------------------- Delete Village ------------------------- +@village_bp.route('/delete_village/') +@login_required +def delete_village(village_id): + + village = Village() + + village.DeleteVillage(request=request, village_id=village_id) + + if not village.isSuccess: + flash(village.resultMessage, "error") + else: + flash(village.resultMessage, "success") + + return redirect(url_for('village.add_village')) + + +# ------------------------- Edit Village ------------------------- +@village_bp.route('/edit_village/', methods=['GET', 'POST']) +@login_required +def edit_village(village_id): + + village = Village() + + if request.method == 'POST': + + village.EditVillage(request=request, village_id=village_id) + + if village.isSuccess: + flash(village.resultMessage, "success") + return redirect(url_for('village.add_village')) + + else: + flash(village.resultMessage, "error") + + village_data = village.GetVillageByID(request=request, id=village_id) + blocks = village.GetAllBlocks(request=request) + + return render_template( + 'edit_village.html', + village_data=village_data, + blocks=blocks + ) + + else: + + village_data = village.GetVillageByID(request=request, id=village_id) + + if not village.isSuccess: + flash(village.resultMessage, "error") + return redirect(url_for('village.add_village')) + + blocks = village.GetAllBlocks(request=request) + + if village_data is None: + village_data = [] + + if blocks is None: + blocks = [] + + return render_template( + 'edit_village.html', + village_data=village_data, + blocks=blocks + ) \ No newline at end of file diff --git a/main.py b/main.py index 870a7b9..96a2977 100644 --- a/main.py +++ b/main.py @@ -1,5019 +1,68 @@ -from decimal import Decimal +# main.py +from flask import Flask, render_template +from flask_login import LoginManager +from model.Auth import User -from AppCode import ContractorInfo, Auth, Utilities, Log -from AppCode.Utilities import RegEx, ResponseHandler, HtmlHelper -from AppCode.Auth import LoginLDAP, User -from AppCode.Log import LogData, LogHelper -from AppCode.State import State -from AppCode.District import District -from AppCode.Block import Block -from AppCode.Village import Village -# need to optimize above import lines +# Import Blueprints / Controllers +from controllers.auth_controller import auth_bp +from controllers.log_controller import log_bp +from controllers.state_controller import state_bp +from controllers.district_controller import district_bp +from controllers.block_controller import block_bp +from controllers.village_controller import village_bp +from controllers.invoice_controller import invoice_bp +from controllers.subcontractor_controller import subcontractor_bp +from controllers.payment_controller import payment_bp +from controllers.gst_release_controller import gst_release_bp +from controllers.excel_upload_controller import excel_bp +from controllers.report_controller import report_bp +from controllers.pmc_report_controller import pmc_report_bp -from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json -from flask import current_app, session, send_file - -from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user - -import mysql.connector -from mysql.connector import Error - -import config +from controllers.hold_types_controller import hold_bp +from dotenv import load_dotenv import os -import re -import ast -from datetime import datetime - - -import openpyxl -from openpyxl import load_workbook -from openpyxl.styles import Font, PatternFill, Alignment -from openpyxl.utils import get_column_letter - - -import logging - -import pandas as pd - - -#import AppRoutes.StateRoute - - -# this is server +# ---------------- Initialize App ---------------- app = Flask(__name__) + +load_dotenv() +Secret_Key = os.getenv("SECRET_KEY") +app.secret_key = Secret_Key + +# ---------------- Login Manager ---------------- login_manager = LoginManager() login_manager.init_app(app) -login_manager.login_view = 'login' - +login_manager.login_view = 'auth.login' @login_manager.user_loader def load_user(user_id): return User(user_id) -#need to check and understand above function -app.secret_key = '9f2a1b8c4d6e7f0123456789abcdef01' -#Shouldnt be hardcoded - - - - -# this is Index page OR Home page.. +# ---------------- Home Route ---------------- @app.route('/') -@login_required def index(): - return render_template('index.html') - - - - - -# ---------------- LOGIN ROUTE ---------------- - -@app.route('/login', methods=['GET', 'POST']) -def login(): - if request.method == 'POST': - loginData = LoginLDAP(request) - # If bind successful → set session and log - - if loginData.isValidLogin: - if loginData.isDefaultCredentials: - LogHelper.log_action('Login', f"User {loginData.username} logged in (static user)") - else: - LogHelper.log_action('Login', f"User {loginData.username} logged in (LDAP)") - - session['username'] = loginData.username - login_user(User(loginData.username)) - return redirect(url_for('index', login='success')) - else: - flash(loginData.errorMessage, 'danger') - - return render_template('login.html') - - - - -@app.route('/logout') -@login_required -def logout(): - LogHelper.log_action('Logout', f"User {current_user.id} logged out") # log the event - logout_user() - flash('You have been logged out.', 'info') - return redirect(url_for('login')) - - - - -@app.route('/activity_log', methods=['GET', 'POST']) -@login_required -def activity_log(): - - # Filters (GET or POST) - start_date = request.values.get("start_date") - end_date = request.values.get("end_date") - user_name = request.values.get("username") - - logData = LogData() - filtered_logs = logData.GetFilteredActivitiesLog(start_date,end_date,user_name) - - return render_template( - "activity_log.html", - logs=filtered_logs, - start_date=start_date, - end_date=end_date, - username=user_name - ) - - - -# ------------------------- State controller ------------------------------------------ -@app.route('/add_state', methods=['GET', 'POST']) -@login_required -def add_state(): - state = State() - if request.method == 'POST': - state.AddState(request=request) - return state.resultMessage - - statedata = state.GetAllStates(request=request) - return render_template('add_state.html', statedata=statedata) - - - -# AJAX route to check state existence -@app.route('/check_state', methods=['POST']) -@login_required -def check_state(): - state = State() - return state.CheckState(request=request) - - -# Delete State -@app.route('/delete_state/', methods=['GET']) -@login_required -def deleteState(id): - state = State() - msg = state.DeleteState(request=request, id=id) - - if not state.isSuccess: - return state.resultMessage - else: - return redirect(url_for('add_state')) - - -# Edit State -@app.route('/edit_state/', methods=['GET', 'POST']) -@login_required -def editState(id): - connection = config.get_db_connection() - cursor = connection.cursor() - - state = State() - statedata = [] - - if request.method == 'POST': - state.EditState(request=request, id=id) - if state.isSuccess: - return redirect(url_for('add_state')) - else: - return state.resultMessage - else: - statedata = state.GetStateByID(request=request, id=id) - - if not state.isSuccess: - return state.resultMessage - - - if statedata is None: - statedata = [] - - return render_template('edit_state.html', state=statedata) - - -# -------- end State controller ----------- - -# ------------------------- District controller ------------------------------------------ -@app.route('/add_district', methods=['GET', 'POST']) -@login_required -def add_district(): - district = District() - - if request.method == 'POST': - district.AddDistrict(request=request) - return district.resultMessage - state = State() - states = state.GetAllStates(request=request) - - districtdata = district.GetAllDistricts(request=request) - - return render_template('add_district.html', districtdata=districtdata, states=states) - - -# AJAX route to check district existence -@app.route('/check_district', methods=['POST']) -@login_required -def check_district(): - district = District() - return district.CheckDistrict(request=request) - - -# Delete District -@app.route('/delete_district/', methods=['GET']) -@login_required -def delete_district(district_id): - district = District() - district.DeleteDistrict(request=request, id=district_id) - - if not district.isSuccess: - return district.resultMessage - else: - return redirect(url_for('add_district')) - - -# Edit District -@app.route('/edit_district/', methods=['GET', 'POST']) -@login_required -def edit_district(district_id): - district = District() - - if request.method == 'POST': - district.EditDistrict(request=request, id=district_id) - if district.isSuccess: - return redirect(url_for('add_district')) - else: - - flash(district.resultMessage, "error") - - districtdata = district.GetDistrictByID(request=request, id=district_id) - state = State() - states = state.GetAllStates(request=request) - return render_template('edit_district.html', districtdata=districtdata, states=states) - - # GET Request - else: - districtdata = district.GetDistrictByID(request=request, id=district_id) - - if not district.isSuccess: - flash(district.resultMessage, "error") - return redirect(url_for('add_district')) - - - state = State() - states = state.GetAllStates(request=request) - - if districtdata is None: - districtdata = [] - if states is None: - states = [] - - return render_template('edit_district.html', districtdata=districtdata, states=states) - - -# --------- end District controller ------------- - -# ------------------------- Block controller ------------------------------------------ -@app.route('/add_block', methods=['GET', 'POST']) -@login_required -def add_block(): - block = Block() - district = District() - - # form submission - if request.method == 'POST': - block.AddBlock(request) - return block.resultMessage - - # Fetch all states - connection = config.get_db_connection() - cursor = connection.cursor() - cursor.callproc("GetAllStates") - for rs in cursor.stored_results(): - states = rs.fetchall() - - # Fetch all blocks - block_data = block.GetAllBlocks() - - return render_template('add_block.html', states=states, block_data=block_data) - - -@app.route('/check_block', methods=['POST']) -@login_required -def check_block(): - block = Block() - return block.CheckBlock(request) - - -@app.route('/edit_block/', methods=['GET', 'POST']) -@login_required -def edit_block(block_id): - block = Block() - - if request.method == 'POST': - return block.EditBlock(request, block_id) - - # Load all states - connection = config.get_db_connection() - cursor = connection.cursor() - cursor.callproc("GetAllStates") - for rs in cursor.stored_results(): - states = rs.fetchall() - - # Load all districts - cursor.callproc("GetAllDistrictsData") - for rs in cursor.stored_results(): - districts = rs.fetchall() - - block_data = block.GetBlockByID(block_id) - - return render_template('edit_block.html', block_data=block_data, states=states, districts=districts) - - -@app.route('/delete_block/') -@login_required -def delete_block(block_id): - block = Block() - block.DeleteBlock(block_id) - return redirect(url_for('add_block')) - - -# get block by district id -@app.route('/get_blocks/', methods=['GET']) -@login_required -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() - # log_action("Get blocks", f"User {current_user.id} Get Blocks '{district_id}'") - except mysql.connector.Error as e: - print(f"Error fetching blocks: {e}") - return HtmlHelper.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]}) - - - - -# this is get district all data by using state id .. -@app.route('/get_districts/', methods=['GET']) -@login_required -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() - LogHelper.log_action("Get District", f"User {current_user.id} Get District '{state_id}'") - except mysql.connector.Error as e: - print(f"Error fetching districts: {e}") - return HtmlHelper.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']) -@login_required -def add_village(): - village = Village() - - if request.method == 'POST': - village.AddVillage(request=request) - return village.resultMessage - - - state = State() # Use the State class to get states - states = state.GetAllStates(request=request) - - villages = village.GetAllVillages(request=request) - - return render_template('add_village.html', states=states, villages=villages) - -@app.route('/check_village', methods=['POST']) -@login_required -def check_village(): - village = Village() - return village.CheckVillage(request=request) - - -# Delete Village -@app.route('/delete_village/', methods=['GET']) -@login_required -def delete_village(village_id): - village = Village() - village.DeleteVillage(request=request, village_id=village_id) - - if not village.isSuccess: - flash(village.resultMessage, "error") - return redirect(url_for('add_village')) - else: - - return redirect(url_for('add_village')) - - -# Edit Village -@app.route('/edit_village/', methods=['GET', 'POST']) -@login_required -def edit_village(village_id): - village = Village() - - if request.method == 'POST': - village.EditVillage(request=request, village_id=village_id) - if village.isSuccess: - flash(village.resultMessage, "success") - return redirect(url_for('add_village')) - else: - - flash(village.resultMessage, "error") - - village_data = village.GetVillageByID(request=request, id=village_id) - blocks = village.GetAllBlocks(request=request) - return render_template('edit_village.html', village_data=village_data, blocks=blocks) - - - else: - village_data = village.GetVillageByID(request=request, id=village_id) - - if not village.isSuccess: - flash(village.resultMessage, "error") - return redirect(url_for('add_village')) - - blocks = village.GetAllBlocks(request=request) - - if village_data is None: - village_data = [] # Ensure it's iterable in template - if blocks is None: - blocks = [] - - return render_template('edit_village.html', village_data=village_data, blocks=blocks) - - - -# ---- end Village controller --------------------- - - -# -------------------------------- Invoice controller ------------------------------------------ -@app.route('/add_invoice', methods=['GET', 'POST']) -@login_required -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') - basic_amount=float(basic_amount) if basic_amount else 0.0 - debit_amount = request.form.get('debit_amount') - debit_amount=float(debit_amount) if debit_amount else 0.0 - after_debit_amount = request.form.get('after_debit_amount') - after_debit_amount=float(after_debit_amount) if after_debit_amount else 0.0 - amount = request.form.get('amount') - amount=float(amount) if amount else 0.0 - gst_amount = request.form.get('gst_amount') - gst_amount=float(gst_amount) if gst_amount else 0.0 - tds_amount = request.form.get('tds_amount') - tds_amount=float(tds_amount) if tds_amount else 0.0 - sd_amount = request.form.get('sd_amount') - sd_amount=float(sd_amount) if sd_amount else 0.0 - on_commission = request.form.get('on_commission') - on_commission=float(on_commission) if on_commission else 0.0 - hydro_testing = request.form.get('hydro_testing') - hydro_testing=float(hydro_testing) if hydro_testing else 0.0 - gst_sd_amount = request.form.get('gst_sd_amount') - gst_sd_amount=float(gst_sd_amount) if gst_sd_amount else 0.0 - final_amount = request.form.get('final_amount') - final_amount=float(final_amount) if final_amount else 0.0 - - # 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]) - LogHelper.log_action("Add invoice", f"User {current_user.id} Added invoice '{ pmc_no}'") - 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']) -@login_required -def search_subcontractor(): - connection = config.get_db_connection() - if not connection: - return HtmlHelper.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 "
  • No subcontractor found
  • " - - output = "".join( - f"
  • {row['Contractor_Name']}
  • " - for row in results - ) - print("Ajax Call for subcontractor", output) - - return output - - except mysql.connector.Error as e: - return HtmlHelper.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']) -@login_required -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() - LogHelper.log_action("Get hold type", f"User {current_user.id} Get hold type'{ hold_types}'") - 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/', methods=['GET', 'POST']) -@login_required -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') - LogHelper.log_action("Edit invoice", f"User {current_user.id} Edit invoice'{ invoice_id}'") - # 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/', methods=['GET']) -@login_required -def delete_invoice(invoice_id): - connection = config.get_db_connection() - if not connection: - return HtmlHelper.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,)) - LogHelper.log_action("Delete invoice", f"User {current_user.id} Delete invoice'{ invoice_id}'") - connection.commit() - - # Check if the invoice was actually deleted - if cursor.rowcount == 0: - return HtmlHelper.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 HtmlHelper.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 HtmlHelper.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 HtmlHelper.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 HtmlHelper.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']) -@login_required -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'] - LogHelper.log_action("Add Payment", f"User {current_user.id} Add Payment'{ pmc_no}'") - 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/') -@login_required -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/', methods=['GET', 'POST']) -@login_required -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 HtmlHelper.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'] - LogHelper.log_action("Edit Payment", f"User {current_user.id} Edit Payment'{ pmc_no}'") - 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 HtmlHelper.json_response(ResponseHandler.update_failure("payment"), 500) - - except mysql.connector.Error as e: - print(f"Error fetching payment data: {e}") - return HtmlHelper.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/', methods=['GET', 'POST']) -@login_required -def delete_payment(payment_id): - connection = config.get_db_connection() - if not connection: - return HtmlHelper.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,)) - LogHelper.log_action("Delete Payment", f"User {current_user.id} Delete Payment'{ payment_id}'") - connection.commit() - # Check if any rows were deleted - if cursor.rowcount == 0: - return HtmlHelper.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 HtmlHelper.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']) -@login_required -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 HtmlHelper.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,Total_Amount,UTR 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'] - total_amount = request.form['total_amount'] - utr = request.form['utr'] - contractor_id = request.form['subcontractor_id'] - LogHelper.log_action("Add gst_release", f"User {current_user.id} Add gst_release'{ pmc_no}'") - # cursor.callproc('SaveGSTRelease', ( - # pmc_no, invoice_no, basic_amount, final_amount,total_amount, utr - # )) - # connection.commit() - cursor.execute(""" - INSERT INTO gst_release (PMC_No, - invoice_no, - Basic_Amount, - Final_Amount, - Total_Amount, - UTR, - Contractor_Id) - VALUES (%s, %s, %s, %s, %s, %s, %s) - """, ( - pmc_no, - invoice_no, - basic_amount, - final_amount, - total_amount, - utr, - contractor_id - )) - 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 HtmlHelper.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/', methods=['GET', 'POST']) -@login_required -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 HtmlHelper.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,Total_Amount,UTR 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_no = request.form['PMC_No'] - invoice_no = request.form['invoice_No'] - basic_amount = request.form['basic_amount'] - final_amount = request.form['final_amount'] - total_amount = request.form['total_amount'] - utr = request.form['utr'] - LogHelper.log_action("Edit gst_release", f"User {current_user.id} Edit gst_release'{ pmc_no}'") - try: - cursor.execute(""" - UPDATE gst_release - SET PMC_No = %s, - invoice_no = %s, - Basic_Amount = %s, - Final_Amount = %s, - Total_Amount = %s, - UTR = %s - WHERE GST_Release_Id = %s - """, ( - pmc_no, - invoice_no, - basic_amount, - final_amount, - total_amount, - utr, - 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 HtmlHelper.json_response(ResponseHandler.update_failure("GST Release"), 500) - - except mysql.connector.Error as e: - print(f"Error fetching GST Release data: {e}") - return HtmlHelper.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/', methods=['GET', 'POST']) -@login_required -def delete_gst_release(gst_release_id): - connection = config.get_db_connection() - if not connection: - return HtmlHelper.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,)) - LogHelper.log_action("delete gst_release", f"User {current_user.id} delete gst_release'{ gst_release_id}'") - connection.commit() - # Check if any rows were deleted - if cursor.rowcount == 0: - return HtmlHelper.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 HtmlHelper.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']) -@login_required -def subcontract(): - connection = config.get_db_connection() - subcontractor = [] - - if not connection: - return HtmlHelper.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 HtmlHelper.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 HtmlHelper.json_response(ResponseHandler.add_failure("Subcontractor"), 500) - - except Error as e: - print(f"Error handling subcontractor data: {e}") - return HtmlHelper.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/', methods=['GET', 'POST']) -@login_required -def edit_subcontractor(id): - connection = config.get_db_connection() - - if not connection: - return HtmlHelper.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 HtmlHelper.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 - } - LogHelper.log_action("Edit Subcontractor", f"User {current_user.id}Edit Subcontractor'{ 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 HtmlHelper.json_response(ResponseHandler.update_failure("Subcontractor"), 500) - - except Error as e: - print(f"Error fetching subcontractor data: {e}") - return HtmlHelper.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/', methods=['GET', 'POST']) -# def deleteSubContractor(id): -# connection = config.get_db_connection() - -# if not connection: -# return HtmlHelper.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 HtmlHelper.json_response(ResponseHandler.fetch_failure("Subcontractor"), 404) - -# except Error as e: -# print(f"Error deleting subcontractor: {e}") -# return HtmlHelper.json_response(ResponseHandler.delete_failure("Subcontractor"), 500) - -# finally: -# cursor.close() -# connection.close() - -# return redirect(url_for('subcontract')) -@app.route('/deleteSubContractor/', methods=['GET', 'POST']) -@login_required -def deleteSubContractor(id): - connection = config.get_db_connection() - - if not connection: - return HtmlHelper.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 HtmlHelper.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 - LogHelper.log_action("Delete Subcontractor", f"User {current_user.id}Delete Subcontractor'{ id}'") - if affected_rows == 0: - return HtmlHelper.json_response(ResponseHandler.fetch_failure("Subcontractor not deleted"), 404) - - except Error as e: - print(f"Error deleting subcontractor: {e}") - return HtmlHelper.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) - LogHelper.log_action("Upload Excel File", f"User {current_user.id}Upload Excel File'{file}'") - return redirect(url_for('show_table', filename=file.filename)) - return render_template('uploadExcelFile.html') - - -# Show excel data in tables6 -# @app.route('/show_table/') -# 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/') -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/') -# 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 - LogHelper.log_action("Data saved", f"User {current_user.id} Data saved'{ village_name}'") - 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'] - print("All invoice Details ",args) - 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','logging report']): - 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---------------------------------------------------------------------- - - # Step 1: Normalize Invoice_Details: lowercase, trim, remove extra spaces - normalized_details = re.sub(r'\s+', ' ', Invoice_Details.strip()).lower() - - # Step 2: Define lowercase keywords - keywords = [ - 'excess hold', - 'ht', - 'hold release amount', - 'dpr excess hold amount', - 'excess hold amount', - 'Multi to Single layer bill', - 'hold amount', - 'logging report' - ] - - # Step 3: Matching condition - if any(kw in normalized_details for kw in keywords): - print("✅ Match found. Inserting hold release for:", Invoice_Details) - 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 - )) - connection.commit() # ✅ Ensure changes are saved to DB - print("✅ Hold release inserted for:", 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,Total_Amount,UTR, subcontractor_id) - #cursor.callproc("SaveGSTRelease", (PMC_No, Invoice_No, Basic_Amount, Final_Amount,Total_Amount,UTR)) - cursor.execute( - """INSERT INTO gst_release (PMC_No, Invoice_No, Basic_Amount, Final_Amount,Total_Amount,UTR, Contractor_Id) VALUES (%s,%s, %s, %s, %s, %s, %s)""", - (PMC_No, Invoice_No, Basic_Amount, Final_Amount, Total_Amount, UTR, subcontractor_id)) - - # 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 report 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 = [] - - LogHelper.log_action("Search contractor", f"User {current_user.id} Search contractor'{ subcontractor_name}'") - 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/') -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 report by contractor id -# # Download report by contractor id - - -class FilePathData: - downloadReportFolder = "static/download" - - - -@app.route('/download_report/') -def download_report(contractor_id): - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True, buffered=True) - - output_folder = FilePathData.downloadReportFolder - os.makedirs(output_folder, exist_ok=True) - output_file = os.path.join(output_folder, f"Contractor_Report_{contractor_id}.xlsx") - - try: - # ---------------- Contractor Info ---------------- - contractor = ContractorInfo(contractor_id) - contInfo = contractor.contInfo - - if not contractor.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 - 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 i.*, v.Village_Name - FROM assign_subcontractors asg - INNER JOIN invoice i ON i.PMC_No = asg.PMC_No AND i.Village_Id = asg.Village_Id - LEFT JOIN villages v ON i.Village_Id = v.Village_Id - WHERE asg.Contractor_Id = %s - ORDER BY i.PMC_No, i.Invoice_No - """, (contractor_id,)) - invoices = cursor.fetchall() - - # Remove duplicate invoices - invoice_ids_seen = set() - unique_invoices = [] - for inv in invoices: - if inv["Invoice_Id"] not in invoice_ids_seen: - invoice_ids_seen.add(inv["Invoice_Id"]) - unique_invoices.append(inv) - invoices = unique_invoices - - # ---------------- Hold Amounts ---------------- - cursor.execute(""" - SELECT h.Invoice_Id, h.hold_type_id, h.hold_amount - FROM invoice_subcontractor_hold_join h - 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'] - - # ---------------- Payments ---------------- - cursor.execute(""" - SELECT DISTINCT p.pmc_no, p.invoice_no, p.Payment_Amount, p.TDS_Payment_Amount, p.Total_amount, p.utr - FROM payment p - INNER JOIN invoice i ON i.PMC_No = p.pmc_no AND i.invoice_no = p.invoice_no - INNER JOIN assign_subcontractors asg ON i.PMC_No = asg.PMC_No AND i.Village_Id = asg.Village_Id - WHERE asg.Contractor_Id = %s - """, (contractor_id,)) - payments = cursor.fetchall() - payments_map = {} - for pay in payments: - key = (str(pay['pmc_no']), str(pay['invoice_no'])) - payments_map.setdefault(key, []).append(pay) - - # ---------------- Extra Payments (no invoice_no) ---------------- - 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(str(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 Notes ---------------- - cursor.execute(""" - SELECT PMC_No, Invoice_No, Invoice_Details, Basic_Amount, Debit_Amount, After_Debit_Amount, - GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR - FROM credit_note - WHERE Contractor_Id = %s - """, (contractor_id,)) - credit_notes = cursor.fetchall() - credit_note_map = {} - for cn in credit_notes: - key = (str(cn['PMC_No']), str(cn['Invoice_No'])) - credit_note_map.setdefault(key, []).append(cn) - - # ---------------- GST Releases ---------------- - cursor.execute(""" - SELECT PMC_No, Invoice_No, Basic_Amount, Final_Amount, Total_Amount, UTR - FROM gst_release - WHERE Contractor_Id = %s - ORDER BY PMC_No, Invoice_No - """, (contractor_id,)) - gst_releases = cursor.fetchall() - gst_release_map = {} - for gr in gst_releases: - key = (str(gr['PMC_No']), str(gr['Invoice_No'])) - gst_release_map.setdefault(key, []).append(gr) - - # ---------------- Excel Workbook ---------------- - workbook = openpyxl.Workbook() - sheet = workbook.active - sheet.title = "Contractor Report" - - # Contractor Info - for field, value in contInfo.items(): - sheet.append([field.replace("_", " "), value]) - sheet.append([]) - - # 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) - 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 ---------------- - processed_gst_releases = set() - appended_credit_keys = set() - previous_pmc_no = None - - for inv in invoices: - pmc_no = str(inv["PMC_No"]) - invoice_no = str(inv["invoice_no"]) - key = (pmc_no, invoice_no) - - # Yellow separator if PMC_No changes - if previous_pmc_no and pmc_no != previous_pmc_no: - sheet.append([""] * len(all_headers)) - yellow_fill = PatternFill(start_color="FFFF99", end_color="FFFF99", fill_type="solid") - for cell in sheet[sheet.max_row]: - cell.fill = yellow_fill - previous_pmc_no = pmc_no - - # Invoice row - 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"] - ] - # Hold values - invoice_holds = hold_data.get(inv["Invoice_Id"], {}) - for ht_id in hold_type_map: - row.append(invoice_holds.get(ht_id, "")) - - # Payment values - payment = payments_map.get(key, [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.get("utr") else "" - ] - sheet.append(row) - - # ---------------- Extra Payments for this PMC ---------------- - if pmc_no in extra_payments_map: - for ep in extra_payments_map[pmc_no]: - extra_row = [pmc_no] + [""] * (len(base_headers) - 1) - extra_row += [""] * len(hold_headers) - extra_row += [ - "", ep["Payment_Amount"], ep["TDS_Payment_Amount"], ep["Total_amount"], ep.get("utr", "") - ] - sheet.append(extra_row) - del extra_payments_map[pmc_no] - - # GST Releases - if key in gst_release_map and key not in processed_gst_releases: - for gr in gst_release_map[key]: - gst_row = [ - pmc_no, "", "", "GST Release Note", "", gr["Invoice_No"], - gr["Basic_Amount"], "", "", "", "", "", "", "", "", "" - ] - gst_row += ["" for _ in hold_headers] - gst_row += [gr["Final_Amount"], "", "", gr["Total_Amount"], gr.get("UTR", "")] - sheet.append(gst_row) - processed_gst_releases.add(key) - - # Credit Notes - if key in credit_note_map and key not in appended_credit_keys: - for cn in credit_note_map[key]: - cn_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", ""), "", "", "", "", "" - ] - cn_row += ["" for _ in hold_headers] - cn_row += [cn.get("Final_Amount", ""), "", "", cn.get("Total_Amount", ""), cn.get("UTR", "")] - sheet.append(cn_row) - appended_credit_keys.add(key) - - # ---------------- Totals ---------------- - total_basic_amount = total_tds_amount = total_sd_amount = total_on_commission = 0 - total_final_amount = total_total_amount = total_hold_amount = 0 - - start_row = 2 # skip headers - for r in sheet.iter_rows(min_row=start_row, max_row=sheet.max_row, values_only=True): - try: - total_basic_amount += float(r[6] or 0) - total_tds_amount += float(r[11] or 0) - total_sd_amount += float(r[12] or 0) - total_on_commission += float(r[13] or 0) - total_final_amount += float(r[-5] or 0) - total_total_amount += float(r[-2] or 0) - total_hold_amount += sum(float(r[i] or 0) for i in range(len(base_headers), len(base_headers) + len(hold_headers))) - except: - continue - - 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, ""] - sheet.append([]) - sheet.append(totals_row) - for cell in sheet[sheet.max_row]: - cell.font = Font(bold=True) - - # ---------------- Column Width ---------------- - for col in sheet.columns: - max_length = 0 - col_letter = openpyxl.utils.get_column_letter(col[0].column) - for cell in col: - if cell.value: - max_length = max(max_length, len(str(cell.value))) - 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('/pmc_report/') -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/') -# 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/') -# 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/') -# 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/') -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) - - # Fetch GST Release data - cursor.execute(""" - SELECT PMC_No, Invoice_No, Basic_Amount, Final_Amount, Total_Amount, UTR - FROM gst_release - WHERE PMC_No = %s - """, (pmc_no,)) - gst_releases = cursor.fetchall() - gst_release_map = {} - for gr in gst_releases: - invoice_nos = [] - - if gr['Invoice_No']: - cleaned = gr['Invoice_No'].replace(' ', '') - if '&' in cleaned: - invoice_nos = cleaned.split('&') - elif ',' in cleaned: - invoice_nos = cleaned.split(',') - else: - invoice_nos = [cleaned] - - for inv_no in invoice_nos: - gst_release_map.setdefault(inv_no, []).append(gr) - LogHelper.log_action("Download PMC Report", f"User {current_user.id} Download PMC Report'{ pmc_no}'") - 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) - # Add GST Release Note(s) for this invoice if any - if invoice_no in gst_release_map: - for gr in gst_release_map[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"], "", "", gr["Total_Amount"], - gr["UTR"] if gr["UTR"] else "" - ] - - # ✅ Ensure proper alignment - while len(gst_row) < len(base_headers) + len(hold_headers) + len(payment_headers): - gst_row.append("") - - 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, - # "" - # ] - # Prepare empty totals_row with length of base_headers - totals_row = [""] * len(base_headers) - - # Fill in specific columns - totals_row[0] = "TOTAL" # Column 0: Label - totals_row[6] = total_basic_amount # Column 6: Basic Amount - totals_row[11] = total_tds_amount # Column 11: TDS - totals_row[12] = total_sd_amount # Column 12: SD - totals_row[13] = total_on_commission # Column 13: On Commission - - # Add hold header totals - hold_values = ["" for _ in hold_headers] - if total_hold_amount_dynamic: - hold_values[0] = total_hold_amount_dynamic # Only in first column - totals_row += hold_values - - # Add payment section - totals_row += [ - total_final_amount, - total_payment_amount, - total_tds_payment_amount, - total_total_paid, - "" # UTR - ] - - 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/', 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/', 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/', 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() - LogHelper.log_action("Delete hold type", f"User {current_user.id} Delete hold type'{ hold_type}'") - 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() - -@app.route('/unreleased_gst') -def unreleased_gst(): - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True) - - try: - # Step 1: Fetch invoices - cursor.execute(""" - SELECT - i.PMC_No, i.Invoice_No, i.GST_SD_Amount, i.Invoice_Details, - i.Basic_Amount, i.Final_Amount - FROM `invoice` i - """) - invoices = cursor.fetchall() - - # Step 2: Fetch GST releases - cursor.execute(""" - SELECT Invoice_No, Basic_Amount ,Final_Amount - FROM gst_release - """) - gst_releases = cursor.fetchall() - - # Step 3: Lookup sets - gst_invoice_nos = {g['Invoice_No'] for g in gst_releases if g['Invoice_No']} - gst_basic_amounts = {float(g['Basic_Amount']) for g in gst_releases if g['Basic_Amount'] is not None} - - # Step 4: Filter - unreleased = [] - for inv in invoices: - match_by_invoice = inv['Invoice_No'] in gst_invoice_nos - match_by_gst_amount = float(inv.get('GST_SD_Amount') or 0.0) in gst_basic_amounts - if not (match_by_invoice or match_by_gst_amount): - unreleased.append(inv) - - return render_template("unreleased_gst.html", data=unreleased) - - finally: - cursor.close() - connection.close() - -# -- end hold types controlller -------------------- - - -# -- end hold types controlller -------------------- - -# Route to display the HTML form -@app.route('/add_work_order', methods=['GET']) -def add_work_order(): - # Add database connection - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True) - - cursor.execute("SELECT Contractor_id, Contractor_Name FROM subcontractors") # Adjust table/column names as needed - subcontractor = cursor.fetchall() - - cursor.close() - connection.close() - - return render_template('add_work_order.html', subcontractor=subcontractor) # This is your HTML form page - - -# Route to handle form submission (from action="/submit_work_order") -@app.route('/submit_work_order', methods=['POST', 'GET']) -def submit_work_order(): - vendor_name = request.form['vendor_name'] - work_order_type = request.form['work_order_type'] - work_order_amount = request.form['work_order_amount'] - boq_amount = request.form['boq_amount'] - work_done_percentage = request.form['work_done_percentage'] - work_order_number = request.form['work_order_number'] - gst_amount = request.form['gst_amount'] - tds_amount = request.form['tds_amount'] - security_deposite = request.form['security_deposite'] - sd_against_gst = request.form['sd_against_gst'] - final_total = request.form['final_total'] - tds_of_gst = request.form['tds_of_gst'] - LogHelper.log_action("Submit Work Order", f"User {current_user.id} Submit Work Order'{ work_order_type}'") - # print("Good Morning How are U") - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True) - # print("Good morning and how are you") - insert_query = """ - INSERT INTO work_order - (vendor_name, work_order_type, work_order_amount, boq_amount, work_done_percentage,work_order_number,gst_amount,tds_amount - ,security_deposit,sd_against_gst,final_total,tds_of_gst) - VALUES (%s, %s, %s, %s, %s,%s,%s,%s,%s,%s,%s,%s) - """ - cursor.execute(insert_query, - (vendor_name, work_order_type, work_order_amount, boq_amount, work_done_percentage, work_order_number - , gst_amount, tds_amount, security_deposite, sd_against_gst, final_total, tds_of_gst)) - connection.commit() - - # ✅ Fetch all data after insert - select_query = "SELECT * FROM work_order" - cursor.execute(select_query) - wo = cursor.fetchall() - # print("The Work order data is ",wo) - print("The data from work order ", wo) # should now print the data properly - cursor.execute("SELECT Contractor_id, Contractor_Name FROM subcontractors") # Adjust table/column names as needed - subcontractor = cursor.fetchall() - cursor.close() - connection.close() - - return render_template('add_work_order.html', work_order_type=work_order_type, wo=wo, subcontractor=subcontractor) - - -@app.route('/delete_work_order/', methods=['POST']) -def delete_work_order(id): - connection = config.get_db_connection() - cursor = connection.cursor() - cursor.execute("DELETE FROM work_order WHERE work_order_id = %s", (id,)) - connection.commit() - cursor.close() - connection.close() - LogHelper.log_action("delete Work Order", f"User {current_user.id} delete Work Order'{ id}'") - return jsonify({'success': True}) - - -@app.route('/update_work_order/', methods=['GET', 'POST']) -def update_work_order(id): - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True) - - if request.method == 'POST': - work_order_type = request.form['work_order_type'] - work_order_amount = request.form['work_order_amount'] - boq_amount = request.form['boq_amount'] - work_done_percentage = request.form['work_done_percentage'] - work_order_number = request.form['work_order_number'] - gst_amount = request.form['gst_amount'] - tds_amount = request.form['tds_amount'] - security_deposite = request.form['security_deposite'] - sd_against_gst = request.form['sd_against_gst'] - final_amount = request.form['final_amount'] - tds_of_gst = request.form['tds_of_gst'] - update_query = """ - UPDATE work_order - SET work_order_type = %s, - work_order_amount = %s, - boq_amount = %s, - work_done_percentage = %s, - work_order_number= %s, - gst_amount = %s, - tds_amount= %s, - security_deposite= %s, - sd_against_gst=%s, - final_amount= %s, - tds_of_gst=%s - WHERE work_order_id = %s - """ - cursor.execute(update_query, ( - work_order_type, work_order_amount, boq_amount, work_done_percentage, work_order_number, gst_amount, - tds_amount, security_deposite, sd_against_gst, final_amount, tds_of_gst, id)) - connection.commit() - cursor.close() - connection.close() - - # If GET request: fetch the existing record - cursor.execute("SELECT * FROM work_order WHERE work_order_id = %s", (id,)) - work_order = cursor.fetchone() - cursor.close() - connection.close() - return render_template('update_work_order.html', work_order=work_order) - - -# Optional: Route to show a success message -@app.route('/success') -def success(): - return "Work Order Submitted Successfully!" - - - -logging.basicConfig(level=logging.DEBUG) - - -@app.route('/add_purchase_order', methods=['GET', 'POST']) -def add_purchase_order(): - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True) - - if request.method == 'POST': - # Fetch form fields - purchase_date = request.form.get('purchase_date') - supplier_name = request.form.get('supplier_name') - purchase_order_no = request.form.get('purchase_order_no') - item_name = request.form.get('item_name') - quantity = request.form.get('quantity') - unit = request.form.get('unit') - rate = request.form.get('rate') - amount = request.form.get('amount') - GST_Amount = request.form.get('GST_Amount') - TDS = request.form.get('TDS') - final_amount = request.form.get('final_amount') - LogHelper.log_action("Add purchase order", f"User {current_user.id} Added puirchase Order'{ purchase_order_no}'") - # Insert into database - insert_query = """ - INSERT INTO purchase_order ( - purchase_date, supplier_name, purchase_order_no, item_name, quantity, unit, rate, amount, - GST_Amount, TDS, final_amount - ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """ - cursor.execute(insert_query, ( - purchase_date, supplier_name, purchase_order_no, item_name, quantity, unit, rate, amount, - GST_Amount, TDS, final_amount - )) - connection.commit() - - # ✅ Always fetch updated data - cursor.execute("SELECT * FROM purchase_order") - purchases = cursor.fetchall() - - cursor.close() - connection.close() - - return render_template('add_purchase_order.html', purchases=purchases) - - -# Show all purchases -@app.route('/purchase_orders') -def show_purchase_orders(): - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True) - cursor.execute("SELECT * FROM purchase_order") - purchases = cursor.fetchall() - cursor.close() - connection.close() - return render_template('add_purchase_order.html', purchases=purchases) - - -# Delete purchase order -@app.route('/delete_purchase/', methods=['POST']) -def delete_purchase(id): - connection = config.get_db_connection() - cursor = connection.cursor() - cursor.execute("DELETE FROM purchase_order WHERE purchase_id = %s", (id,)) - connection.commit() - cursor.close() - connection.close() - LogHelper.log_action("Delete purchase order", f"User {current_user.id} Deleted puirchase Order'{ id}'") - return render_template(('add_purchase_order.html')) - - -# Edit purchase order (form + update logic) -@app.route('/update_purchase/', methods=['GET', 'POST']) -def update_purchase(id): - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True) - - if request.method == 'POST': - # ✅ Form submitted - update all fields - data = request.form - cursor.execute(""" - UPDATE purchase_order - SET purchase_date = %s, - supplier_name = %s, - purchase_order_no = %s, - item_name = %s, - quantity = %s, - unit = %s, - rate = %s, - amount = %s, - GST_Amount = %s, - TDS = %s, - final_amount = %s - WHERE purchase_id = %s - """, ( - data['purchase_date'], data['supplier_name'], data['purchase_order_no'], data['item_name'], - data['quantity'], - data['unit'], data['rate'], data['amount'], data['GST_Amount'], data['TDS'], data['final_amount'], - id - )) - connection.commit() - cursor.close() - connection.close() - LogHelper.log_action("Delete purchase order", f"User {current_user.id} Deleted puirchase Order'{ id}'") - return redirect(url_for('show_purchase_orders')) - - # Show edit form - cursor.execute("SELECT * FROM purchase_order WHERE purchase_id = %s", (id,)) - purchase = cursor.fetchone() - cursor.close() - connection.close() - return render_template('edit_purchase.html', purchase=purchase) - - -# SHOW all GRNs + ADD form -@app.route('/grn', methods=['GET']) -def grn_page(): - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True) - - # Fetch purchase orders for dropdown - cursor.execute("SELECT purchase_id, supplier_name FROM purchase_order") - purchase_orders = cursor.fetchall() - - # Fetch all GRNs to display - cursor.execute("SELECT * FROM goods_receive_note") - grns = cursor.fetchall() - print(grns) - cursor.close() - connection.close() - - # Render the template with both datasets - return render_template('grn_form.html', purchase_orders=purchase_orders, grns=grns) - - -# ADD new GRN -@app.route('/add_grn', methods=['POST', 'GET']) -def add_grn(): - data = request.form - connection = config.get_db_connection() - cursor = connection.cursor() - query = """ - INSERT INTO goods_receive_note - (grn_date, purchase_id, supplier_name, item_description, received_quantity, unit, rate, amount, remarks) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) \ - """ - cursor.execute(query, ( - data.get('grn_date'), - data.get('purchase_id'), - data.get('supplier_name'), - data.get('item_description'), - data.get('received_quantity'), - data.get('unit'), - data.get('rate'), - data.get('amount'), - data.get('remarks') - )) - connection.commit() - - cursor.execute("SELECT * FROM goods_receive_note") - grns = cursor.fetchall() - print(grns) - query = "select * from purchase_order" - cursor.execute(query) - purchase_orders = cursor.fetchall() - cursor.close() - connection.close() - return render_template('grn_form.html', purchase_orders=purchase_orders, grns=grns) - - -# UPDATE GRN -@app.route('/update_grn/', methods=['GET', 'POST']) -def update_grn(grn_id): - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True) - - if request.method == 'POST': - data = request.form - query = """ - UPDATE goods_receive_note - SET grn_date=%s, purchase_id=%s, supplier_name=%s, item_description=%s, - received_quantity=%s, unit=%s, rate=%s, amount=%s, remarks=%s - WHERE grn_id=%s - """ - cursor.execute(query, ( - data['grn_date'], data['purchase_id'], data['supplier_name'], - data['item_description'], data['received_quantity'], data['unit'], - data['rate'], data['amount'], data['remarks'], grn_id - )) - connection.commit() - cursor.close() - connection.close() - return redirect(url_for('grns')) - - cursor.execute("SELECT * FROM goods_receive_note WHERE grn_id = %s", (grn_id,)) - grn = cursor.fetchone() - cursor.close() - connection.close() - return render_template("edit_grn.html", grn=grn) - - -# DELETE GRN -@app.route('/delete_grn/', methods=['POST']) -def delete_grn(grn_id): - connection = config.get_db_connection() - cursor = connection.cursor() - cursor.execute("DELETE FROM goods_receive_note WHERE grn_id = %s", (grn_id,)) - connection.commit() - cursor.close() - connection.close() - return render_template("grn_form.html") - - -@app.route('/work_order_report', methods=['GET']) -def work_order_report(): - return render_template('work_order_report.html') - - - - -# ✅ Vendor Name Search (for Select2) -@app.route('/get_vendor_names') -def get_vendor_names(): - query = request.args.get('q', '') - connection = config.get_db_connection() - cursor = connection.cursor() - cursor.execute("SELECT DISTINCT vendor_name FROM work_order WHERE vendor_name LIKE %s", (f"%{query}%",)) - vendors = [row[0] for row in cursor.fetchall()] - cursor.close() - connection.close() - return jsonify(vendors) - - -# ✅ Work Order Number Search (with or without vendor) -@app.route('/get_work_order_numbers') -def get_work_order_numbers(): - vendor = request.args.get('vendor_name', '') - query = request.args.get('q', '') - connection = config.get_db_connection() - cursor = connection.cursor() - - if vendor: - cursor.execute( - "SELECT DISTINCT work_order_number FROM work_order WHERE vendor_name = %s AND work_order_number LIKE %s", - (vendor, f"%{query}%")) - else: - cursor.execute("SELECT DISTINCT work_order_number FROM work_order WHERE work_order_number LIKE %s", - (f"%{query}%",)) - - orders = [row[0] for row in cursor.fetchall()] - cursor.close() - connection.close() - return jsonify(orders) - - -# ✅ Get Work Order Data (Filtered) -@app.route('/get_work_order_data') -def get_work_order_data(): - vendor = request.args.get('vendor_name') - order_number = request.args.get('work_order_number') - - query = "SELECT * FROM work_order WHERE 1=1" - params = [] - - if vendor: - query += " AND vendor_name = %s" - params.append(vendor) - if order_number: - query += " AND work_order_number = %s" - params.append(order_number) - - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True) - cursor.execute(query, tuple(params)) - data = cursor.fetchall() - cursor.close() - connection.close() - return jsonify(data) - - - - -@app.route('/download_work_order_report') -def download_work_order_report(): - vendor_name = request.args.get('vendor_name') - work_order_number = request.args.get('work_order_number') - - if work_order_number == "null": - work_order_number = None - - query = "SELECT * FROM work_order WHERE 1=1" - params = [] - - if vendor_name: - query += " AND vendor_name = %s" - params.append(vendor_name) - if work_order_number: - query += " AND work_order_number = %s" - params.append(work_order_number) - - conn = config.get_db_connection() - cursor = conn.cursor(dictionary=True) - cursor.execute(query, tuple(params)) - data = cursor.fetchall() - cursor.close() - conn.close() - - if not data: - return "No data found for the selected filters", 404 - - # Convert to DataFrame - df = pd.DataFrame(data) - - output_path = 'static/downloads/work_order_report.xlsx' - os.makedirs(os.path.dirname(output_path), exist_ok=True) - df.to_excel(output_path, index=False, startrow=2) # Leave space for heading - - # Load workbook for styling - wb = load_workbook(output_path) - ws = wb.active - - # Add a merged title - title = "Work Order Report" - ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(df.columns)) - title_cell = ws.cell(row=1, column=1) - title_cell.value = title - title_cell.font = Font(size=14, bold=True, color="1F4E78") - title_cell.alignment = Alignment(horizontal="center", vertical="center") - - # Style header row (row 3 because data starts from row 3) - header_font = Font(bold=True) - for col_num, column_name in enumerate(df.columns, 1): - cell = ws.cell(row=3, column=col_num) - cell.font = header_font - cell.alignment = Alignment(horizontal="center", vertical="center") - # Optional: adjust column width - max_length = max(len(str(column_name)), 12) - ws.column_dimensions[get_column_letter(col_num)].width = max_length + 2 - - wb.save(output_path) - return send_file(output_path, as_attachment=True) - - -@app.route('/purchase_order_report', methods=['GET']) -def purchase_order_report(): - return render_template('purchase_order_report.html') - - -@app.route('/get_supplier_names') -def get_supplier_names(): - query = request.args.get('q', '') # Get the search term from Select2 - connection = config.get_db_connection() - cursor = connection.cursor() - - # Fetch distinct supplier names that match the search query - cursor.execute( - "SELECT supplier_name FROM purchase_order WHERE supplier_name LIKE %s", - (f"%{query}%",) - ) - suppliers = [row[0] for row in cursor.fetchall()] - - cursor.close() - connection.close() - return jsonify(suppliers) - - -@app.route('/get_purchase_order_numbers') -def get_purchase_order_numbers(): - supplier = request.args.get('supplier_name', '') - query = request.args.get('q', '') - - connection = config.get_db_connection() - cursor = connection.cursor() - - if supplier: - cursor.execute(""" - SELECT purchase_order_no - FROM purchase_order - WHERE supplier_name = %s AND purchase_order_no LIKE %s - """, (supplier, f"%{query}%")) - else: - cursor.execute(""" - SELECT purchase_order_no - FROM purchase_order - WHERE purchase_order_no LIKE %s - """, (f"%{query}%",)) - - orders = [row[0] for row in cursor.fetchall()] - cursor.close() - connection.close() - return jsonify(orders) - - -@app.route('/get_purchase_order_data') -def get_purchase_order_data(): - supplier = request.args.get('supplier_name') - order_no = request.args.get('purchase_order_no') - - query = "SELECT * FROM purchase_order WHERE 1=1" - params = [] - - if supplier: - query += " AND supplier_name = %s" - params.append(supplier) - if order_no: - query += " AND purchase_order_no = %s" - params.append(order_no) - - connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True) - cursor.execute(query, tuple(params)) - data = cursor.fetchall() - cursor.close() - connection.close() - return jsonify(data) - - -@app.route('/download_purchase_order_report') -def download_purchase_order_report(): - supplier_name = request.args.get('supplier_name') - purchase_order_no = request.args.get('purchase_order_no') - - if purchase_order_no == "null": - purchase_order_no = None - LogHelper.log_action("Download purchase order", f"User {current_user.id} Download puirchase Order'{ purchase_order_no}'") - query = "SELECT * FROM purchase_order WHERE 1=1" - params = [] - - if supplier_name: - query += " AND supplier_name = %s" - params.append(supplier_name) - if purchase_order_no: - query += " AND purchase_order_no = %s" - params.append(purchase_order_no) - - conn = config.get_db_connection() - cursor = conn.cursor(dictionary=True) - cursor.execute(query, tuple(params)) - data = cursor.fetchall() - cursor.close() - conn.close() - - if not data: - return "No data found for the selected filters", 404 - - # Convert to DataFrame - df = pd.DataFrame(data) - - output_path = 'static/downloads/purchase_order_report.xlsx' - os.makedirs(os.path.dirname(output_path), exist_ok=True) - - df.to_excel(output_path, index=False, startrow=2) # Reserve space for heading - - # Load workbook for styling - wb = load_workbook(output_path) - ws = wb.active - - # Add a merged title - title = "Purchase Order Report" - ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(df.columns)) - title_cell = ws.cell(row=1, column=1) - title_cell.value = title - title_cell.font = Font(size=14, bold=True, color="1F4E78") - title_cell.alignment = Alignment(horizontal="center", vertical="center") - - # Style header row (row 3 because data starts from row 3) - header_font = Font(bold=True) - for col_num, column_name in enumerate(df.columns, 1): - cell = ws.cell(row=3, column=col_num) - cell.font = header_font - cell.alignment = Alignment(horizontal="center", vertical="center") - max_length = max(len(str(column_name)), 12) - ws.column_dimensions[get_column_letter(col_num)].width = max_length + 2 - - wb.save(output_path) - return send_file(output_path, as_attachment=True) - - + return render_template("index.html") + +# ---------------- Register Blueprints ---------------- + +app.register_blueprint(auth_bp) +app.register_blueprint(log_bp) +app.register_blueprint(state_bp) +app.register_blueprint(district_bp) +app.register_blueprint(block_bp) +app.register_blueprint(village_bp) +app.register_blueprint(invoice_bp) +app.register_blueprint(subcontractor_bp) +app.register_blueprint(payment_bp) +app.register_blueprint(gst_release_bp) +app.register_blueprint(excel_bp) +app.register_blueprint(report_bp) +app.register_blueprint(pmc_report_bp) +app.register_blueprint(hold_bp) + +# ---------------- Run App ---------------- if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True) + + diff --git a/model/Auth.py b/model/Auth.py new file mode 100644 index 0000000..e1b9cd4 --- /dev/null +++ b/model/Auth.py @@ -0,0 +1,63 @@ +import os +from dotenv import load_dotenv +from flask_login import UserMixin +from ldap3 import Server, Connection, ALL +from ldap3.core.exceptions import LDAPBindError + +# Load .env +load_dotenv() + + +class DefaultCredentials: + username = os.getenv("DEFAULT_USERNAME") + password = os.getenv("DEFAULT_PASSWORD") + + +class LoginLDAP: + + def __init__(self, request): + + self.username = request.form.get("username", "").strip() + self.password = request.form.get("password", "") + + self.isDefaultCredentials = False + self.isValidLogin = False + self.errorMessage = "" + + ldap_server = "ldap://localhost:389" + ldap_user_dn = f"uid={self.username},ou=users,dc=lcepl,dc=org" + + # fallback admin login + if ( + self.username == DefaultCredentials.username + and self.password == DefaultCredentials.password + ): + self.isDefaultCredentials = True + self.isValidLogin = True + return + + try: + + server = Server(ldap_server, get_info=ALL) + + conn = Connection( + server, + user=ldap_user_dn, + password=self.password, + auto_bind=True + ) + + if conn.bound: + self.isValidLogin = True + + except LDAPBindError: + self.errorMessage = "Invalid LDAP credentials" + + except Exception as e: + self.errorMessage = str(e) + + +class User(UserMixin): + + def __init__(self, username): + self.id = username \ No newline at end of file diff --git a/model/Block.py b/model/Block.py new file mode 100644 index 0000000..c963d28 --- /dev/null +++ b/model/Block.py @@ -0,0 +1,165 @@ +from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json +from flask import current_app + +from datetime import datetime +from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user + +from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType +from model.Log import LogData, LogHelper + +import os +import config +import re + +import mysql.connector +from mysql.connector import Error + +from model.ItemCRUD import ItemCRUD, itemCRUDMapping + + +class Block: + + isSuccess = False + resultMessage = "" + + def __init__(self): + self.isSuccess = False + self.resultMessage = "" + + # ---------------------------------------------------------- + # Add Block + # ---------------------------------------------------------- + def AddBlock(self, request): + + block = ItemCRUD(itemType=ItemCRUDType.Block) + + district_id = request.form.get('district_Id') + block_name = request.form.get('block_Name', '').strip() + + block.AddItem(request=request, parentid=district_id, childname=block_name, storedprocfetch="GetBlockByNameAndDistricts", storedprocadd="SaveBlock" ) + self.isSuccess = block.isSuccess + self.resultMessage = block.resultMessage + return + + # ---------------------------------------------------------- + # Get All Blocks + # ---------------------------------------------------------- + # def GetAllBlocks(self): + + # block = ItemCRUD(itemType=ItemCRUDType.Block) + # blocksdata = block.GetAllData(request=request, storedproc="GetAllBlock") + # self.isSuccess = block.isSuccess + # self.resultMessage = block.resultMessage + # return blocksdata + + def GetAllBlocks(self, request): + + block = ItemCRUD(itemType=ItemCRUDType.Block) + blocksdata = block.GetAllData(request=request, storedproc="GetAllBlock") + + self.isSuccess = block.isSuccess + self.resultMessage = block.resultMessage + return blocksdata + + # ---------------------------------------------------------- + # Check Block Exists + # ---------------------------------------------------------- + + # def CheckBlock(self, request): + # block = ItemCRUD(itemType=ItemCRUDType.Block) + # block_name = request.json.get('block_Name', '').strip() + # district_id = request.json.get('district_Id') + # result = block.CheckItem(request=request, parentid=district_id, childname=block_name, storedprocfetch="GetBlockByNameAndDistrict") + # self.isSuccess = block.isSuccess + # self.resultMessage = block.resultMessage + # return result + def CheckBlock(self, request): + block = ItemCRUD(itemType=ItemCRUDType.Block) + data = request.get_json(silent=True) or request.form + block_name = (data.get('block_Name') or '').strip() + district_id = data.get('district_Id') + + result = block.CheckItem( + request=request, + parentid=district_id, + childname=block_name, + storedprocfetch="GetBlockByNameAndDistrict" + ) + self.isSuccess = block.isSuccess + self.resultMessage = block.resultMessage + return result + + # ---------------------------------------------------------- + # Get Block By ID + # ---------------------------------------------------------- + # def GetBlockByID(self, id): + + # block = ItemCRUD(itemType=ItemCRUDType.Village) + # blockdata = block.GetAllData(id=id,storedproc="GetBlockDataByID") + # self.isSuccess = block.isSuccess + # self.resultMessage = block.resultMessage + # print("akash"+blockdata) + # return blockdata + + # def GetBlockByID(self,request,id): + # block = ItemCRUD(itemType=ItemCRUDType.Block) + # blockdata = block.GetDataByID(request=request,id=id,storedproc="GetBlockDataByID") + # self.isSuccess = block.isSuccess + # self.resultMessage = block.resultMessage + # return blockdata + def GetBlockByID(self, id): + + block = ItemCRUD(itemType=ItemCRUDType.Block) + + blockdata = block.GetDataByID( + id=id, + storedproc="GetBlockDataByID" + ) + + self.isSuccess = block.isSuccess + self.resultMessage = block.resultMessage + + return blockdata + # ---------------------------------------------------------- + # Update Block + # ---------------------------------------------------------- + # def EditBlock(self, request, block_id): + + # block = ItemCRUD(itemType=ItemCRUDType.Block) + + # district_id = request.form.get('district_Id') + # block_name = request.form.get('block_Name', '').strip() + + # block.EditItem(request=request, childid=block_id, parentid=district_id, childname=block_name, storedprocadd="UpdateBlockById" ) + # self.isSuccess = block.isSuccess + # self.resultMessage = block.resultMessage + # return + def EditBlock(self, request, block_id): + + block = ItemCRUD(itemType=ItemCRUDType.Block) + + district_id = request.form.get('district_Id') + block_name = request.form.get('block_Name', '').strip() + + block.EditItem( + request=request, + childid=block_id, + parentid=district_id, + childname=block_name, + storedprocupdate="UpdateBlockById" + ) + + self.isSuccess = block.isSuccess + self.resultMessage = block.resultMessage + return render_template('add_block.html') + + # ---------------------------------------------------------- + # Delete Block + # --------------------------------------------------------- + def DeleteBlock(self,request, id): + block = ItemCRUD(itemType=ItemCRUDType.Block) + + block.DeleteItem(request=request,itemID=id, storedprocDelete="DeleteBlock") + self.isSuccess = block.isSuccess + self.resultMessage = block.resultMessage + return \ No newline at end of file diff --git a/AppCode/ContractorInfo.py b/model/ContractorInfo.py similarity index 57% rename from AppCode/ContractorInfo.py rename to model/ContractorInfo.py index 88fa7f5..a4eb36b 100644 --- a/AppCode/ContractorInfo.py +++ b/model/ContractorInfo.py @@ -19,12 +19,10 @@ class ContractorInfo: def fetchData(self): try: connection = config.get_db_connection() - cursor = connection.cursor(dictionary=True, buffered=True) - print("here", flush=True) - - cursor.callproc('GetContractorInfoById', [self.contInfo]) - #self.contInfo = next(cursor.stored_results()).fetchone() - self.contInfo = cursor.fetchone() + cursor = connection.cursor(dictionary=True, buffered=True) + cursor.callproc('GetContractorInfoById', [self.ID]) + for result in cursor.stored_results(): + self.contInfo = result.fetchone() print(self.contInfo,flush=True) finally: @@ -40,25 +38,23 @@ class ContractorInfo: # ---------------- 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 - WHERE h.Contractor_Id = %s - """, (self.contractor_id,)) - hold_types = cursor.fetchall() + cursor = connection.cursor(dictionary=True) + + cursor.callproc('GetHoldTypesByContractor', [self.ID]) + + hold_types = [] + 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} # ---------------- Invoices ---------------- - cursor.execute(""" - SELECT i.*, v.Village_Name - FROM assign_subcontractors asg - INNER JOIN invoice i ON i.PMC_No = asg.PMC_No AND i.Village_Id = asg.Village_Id - LEFT JOIN villages v ON i.Village_Id = v.Village_Id - WHERE asg.Contractor_Id = %s - ORDER BY i.PMC_No, i.Invoice_No - """, (self.contractor_id,)) - invoices = cursor.fetchall() + cursor = connection.cursor(dictionary=True) + + cursor.callproc('GetInvoicesByContractor', [self.ID]) + + invoices = [] + for result in cursor.stored_results(): + invoices = result.fetchall() # Remove duplicate invoices invoice_ids_seen = set() diff --git a/AppCode/District.py b/model/District.py similarity index 63% rename from AppCode/District.py rename to model/District.py index 4402b3e..14ecea5 100644 --- a/AppCode/District.py +++ b/model/District.py @@ -4,8 +4,8 @@ from flask import current_app from datetime import datetime from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user -from AppCode.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType -from AppCode.Log import LogData, LogHelper +from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType +from model.Log import LogData, LogHelper import os import config @@ -14,7 +14,7 @@ import re import mysql.connector from mysql.connector import Error -from AppCode.ItemCRUD import ItemCRUD +from model.ItemCRUD import ItemCRUD class District: isSuccess = False @@ -31,13 +31,13 @@ class District: district_name = request.form['district_Name'].strip() state_id = request.form['state_Id'] - district.EditItem(request=request, childid=district_id, parentid=state_id, childname=district_name, storedprocadd="UpdateBlockById" ) + district.EditItem(request=request, childid=district_id, parentid=state_id, childname=district_name,storedprocupdate="UpdateDistrict" ) self.isSuccess = district.isSuccess self.resultMessage = district.resultMessage return - def AddDistrict(self, request): + def AddDistrict(self, request): district = ItemCRUD(ItemCRUDType.District) @@ -69,11 +69,32 @@ class District: return result - def GetDistrictByID(self, request, id): - district = ItemCRUD(itemType=ItemCRUDType.Village) - districtdata = district.GetAllData("GetDistrictDataByID") - self.isSuccess = district.isSuccess - self.resultMessage = district.resultMessage + # def GetDistrictByID(self, request,district_id): + # district = ItemCRUD(itemType=ItemCRUDType.District) + # districtdata = district.GetAllData(id=district_id,storedproc="GetDistrictDataByID") + # self.isSuccess = district.isSuccess + # self.resultMessage = district.resultMessage + # return districtdata + def GetDistrictByID(self, request, district_id): + district = ItemCRUD(itemType=ItemCRUDType.District) + + districtdata = district.GetDataByID( + id=district_id, + storedproc="GetDistrictDataByID" + ) + + if districtdata: + self.isSuccess = True + else: + self.isSuccess = False + self.resultMessage = "District not found" + return districtdata - \ No newline at end of file + +#Delete District + def DeleteDistrict(self, request, district_id): + district = ItemCRUD(itemType=ItemCRUDType.District) + district.DeleteItem(request=request,itemID=district_id,storedprocDelete="DeleteDistrict") + self.isSuccess = district.isSuccess + self.resultMessage = str(district.resultMessage) \ No newline at end of file diff --git a/model/FolderAndFile.py b/model/FolderAndFile.py new file mode 100644 index 0000000..65dedef --- /dev/null +++ b/model/FolderAndFile.py @@ -0,0 +1,39 @@ +import os +from flask import current_app + + +class FolderAndFile: + + # ----------------------------- + # BASE FOLDER METHODS + # ----------------------------- + @staticmethod + def get_download_folder(): + folder = os.path.join(current_app.root_path, "static", "downloads") + + if not os.path.exists(folder): + os.makedirs(folder) + + os.makedirs(folder, exist_ok=True) + return folder + + @staticmethod + def get_upload_folder(): + folder = os.path.join(current_app.root_path, "static", "uploads") + + if not os.path.exists(folder): + os.makedirs(folder) + + os.makedirs(folder, exist_ok=True) + return folder + + # ----------------------------- + # FILE PATH METHODS + # ----------------------------- + @staticmethod + def get_download_path(filename): + return os.path.join(FolderAndFile.get_download_folder(), filename) + + @staticmethod + def get_upload_path(filename): + return os.path.join(FolderAndFile.get_upload_folder(), filename) \ No newline at end of file diff --git a/model/GST.py b/model/GST.py new file mode 100644 index 0000000..a908dd9 --- /dev/null +++ b/model/GST.py @@ -0,0 +1,55 @@ +import config + +class GST: + + @staticmethod + def get_unreleased_gst(): + + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + try: + # ----------- Invoices ----------- + cursor.callproc('GetAllInvoicesBasic') + invoices = [] + for result in cursor.stored_results(): + invoices = result.fetchall() + + + # ----------- GST Releases ----------- + cursor.callproc('GetAllGSTReleasesBasic') + gst_releases = [] + for result in cursor.stored_results(): + gst_releases = result.fetchall() + + gst_invoice_nos = { + g['Invoice_No'] + for g in gst_releases + if g['Invoice_No'] + } + + gst_basic_amounts = { + float(g['Basic_Amount']) + for g in gst_releases + if g['Basic_Amount'] is not None + } + + unreleased = [] + + for inv in invoices: + + match_by_invoice = inv['Invoice_No'] in gst_invoice_nos + + match_by_gst_amount = float( + inv.get('GST_SD_Amount') or 0 + ) in gst_basic_amounts + + if not (match_by_invoice or match_by_gst_amount): + unreleased.append(inv) + + return unreleased + + finally: + cursor.close() + connection.close() + \ No newline at end of file diff --git a/model/HoldTypes.py b/model/HoldTypes.py new file mode 100644 index 0000000..b8a9a46 --- /dev/null +++ b/model/HoldTypes.py @@ -0,0 +1,90 @@ +from flask import request +from model.ItemCRUD import ItemCRUD +from model.Utilities import ItemCRUDType + +class HoldTypes: + """CRUD operations for Hold Types using ItemCRUD""" + + def __init__(self): + self.isSuccess = False + self.resultMessage = "" + + # ------------------- Add Hold Type ------------------- + def AddHoldType(self, request): + hold = ItemCRUD(itemType=ItemCRUDType.HoldType) + hold_name = request.form.get('hold_type', '').strip() + + hold.AddItem( + request=request, + parentid=None, + childname=hold_name, + storedprocfetch="CheckHoldTypeExists", + storedprocadd="SaveHoldType" + ) + + self.isSuccess = hold.isSuccess + self.resultMessage = hold.resultMessage + + # ------------------- Get All Hold Types ------------------- + def GetAllHoldTypes(self, request=None): + hold = ItemCRUD(itemType=ItemCRUDType.HoldType) + rows = hold.GetAllData(request=request, storedproc="GetAllHoldTypes") + + self.isSuccess = hold.isSuccess + self.resultMessage = hold.resultMessage + + # ✅ Convert tuple → dictionary + data = [] + for row in rows: + data.append({ + "hold_type_id": row[0], + "hold_type": row[1] + }) + + return data + + # ------------------- Get Hold Type By ID ------------------- + def GetHoldTypeByID(self, hold_type_id): + hold = ItemCRUD(itemType=ItemCRUDType.HoldType) + row = hold.GetDataByID(hold_type_id, storedproc="GetHoldTypesById") + + self.isSuccess = hold.isSuccess + self.resultMessage = hold.resultMessage + + # ✅ Convert tuple → dictionary + if row: + return { + "hold_type_id": row[0], + "hold_type": row[1] + } + + return None + + # ------------------- Update Hold Type ------------------- + def EditHoldType(self, request, hold_type_id): + hold = ItemCRUD(itemType=ItemCRUDType.HoldType) + hold_name = request.form.get('hold_type', '').strip() + + hold.EditItem( + request=request, + childid=hold_type_id, + parentid=None, + childname=hold_name, + storedprocupdate="UpdateHoldTypeById" + ) + + self.isSuccess = hold.isSuccess + self.resultMessage = hold.resultMessage + + # ------------------- Delete Hold Type ------------------- + def DeleteHoldType(self, request, hold_type_id): + hold = ItemCRUD(itemType=ItemCRUDType.HoldType) + + hold.DeleteItem( + request=request, + itemID=hold_type_id, + storedprocDelete="DeleteHoldType" + ) + + self.isSuccess = hold.isSuccess + self.resultMessage = hold.resultMessage \ No newline at end of file diff --git a/model/Invoice.py b/model/Invoice.py new file mode 100644 index 0000000..ac148ac --- /dev/null +++ b/model/Invoice.py @@ -0,0 +1,379 @@ +import config +import mysql.connector + +# ------------------- Helper ------------------- +def clear_results(cursor): + for r in cursor.stored_results(): + r.fetchall() + + +# ------------------- Get Village Id ------------------- +def get_village_id(village_name): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + cursor.callproc("GetVillageIdByName", (village_name,)) + village_result = None + + for rs in cursor.stored_results(): + village_result = rs.fetchone() + + cursor.close() + connection.close() + return village_result + + +# ------------------- Insert Invoice ------------------- +def insert_invoice(data, village_id): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + try: + # 1. Insert Invoice + cursor.callproc('InsertInvoice', [ + data.get('pmc_no'), + village_id, + data.get('work_type'), + data.get('invoice_details'), + data.get('invoice_date'), + data.get('invoice_no'), + float(data.get('basic_amount') or 0), + float(data.get('debit_amount') or 0), + float(data.get('after_debit_amount') or 0), + float(data.get('amount') or 0), + float(data.get('gst_amount') or 0), + float(data.get('tds_amount') or 0), + float(data.get('sd_amount') or 0), + float(data.get('on_commission') or 0), + float(data.get('hydro_testing') or 0), + float(data.get('gst_sd_amount') or 0), + float(data.get('final_amount') or 0) + ]) + + invoice_id = None + for result in cursor.stored_results(): + row = result.fetchone() + if row: + invoice_id = row.get('invoice_id') + + if not invoice_id: + raise Exception("Invoice ID not returned") + + # 2. Insert Inpayment + cursor.callproc('InsertInpayment', [ + data.get('pmc_no'), + village_id, + data.get('work_type'), + data.get('invoice_details'), + data.get('invoice_date'), + data.get('invoice_no'), + float(data.get('basic_amount') or 0), + float(data.get('debit_amount') or 0), + float(data.get('after_debit_amount') or 0), + float(data.get('amount') or 0), + float(data.get('gst_amount') or 0), + float(data.get('tds_amount') or 0), + float(data.get('sd_amount') or 0), + float(data.get('on_commission') or 0), + float(data.get('hydro_testing') or 0), + float(data.get('gst_sd_amount') or 0), + float(data.get('final_amount') or 0), + data.get('subcontractor_id') + ]) + clear_results(cursor) + + connection.commit() + return invoice_id + + except Exception as e: + connection.rollback() + raise e + + finally: + cursor.close() + connection.close() + + +# ------------------- Assign Subcontractor ------------------- +def assign_subcontractor(data, village_id): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + try: + cursor.callproc('AssignSubcontractor', [ + data.get('pmc_no'), + data.get('subcontractor_id'), + village_id + ]) + clear_results(cursor) + + connection.commit() + + except Exception as e: + connection.rollback() + raise e + + finally: + cursor.close() + connection.close() + + +# ------------------- Insert Hold Types ------------------- +def insert_hold_types(data, invoice_id): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + try: + hold_types = data.getlist('hold_type[]') + hold_amounts = data.getlist('hold_amount[]') + + for hold_type, hold_amount in zip(hold_types, hold_amounts): + if not hold_type: + continue + + cursor.callproc('GetHoldTypeIdByName', [hold_type]) + hold_type_result = None + + for result in cursor.stored_results(): + hold_type_result = result.fetchone() + + if not hold_type_result: + cursor.callproc('InsertHoldType', [hold_type, 0]) + cursor.execute("SELECT @_InsertHoldType_1") + hold_type_id = cursor.fetchone()[0] + else: + hold_type_id = hold_type_result['hold_type_id'] + + hold_amount = float(hold_amount or 0) + + cursor.callproc('InsertInvoiceSubcontractorHold', [ + data.get('subcontractor_id'), + invoice_id, + hold_type_id, + hold_amount + ]) + clear_results(cursor) + + connection.commit() + + except Exception as e: + connection.rollback() + raise e + + finally: + cursor.close() + connection.close() + + +# ------------------- Get All Invoices ------------------- +def get_all_invoice_details(): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + cursor.callproc('GetAllInvoiceDetails') + invoices = [] + + for result in cursor.stored_results(): + invoices = result.fetchall() + + cursor.close() + connection.close() + return invoices + + +# ------------------- Get All Villages ------------------- +def get_all_villages(): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + cursor.callproc("GetAllVillages") + villages = [] + + for result in cursor.stored_results(): + villages = result.fetchall() + + cursor.close() + connection.close() + return villages + + +# ------------------- Search Contractors ------------------- +def search_contractors(sub_query): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + cursor.callproc('SearchContractorsByName', [sub_query]) + results = [] + + for result in cursor.stored_results(): + results = result.fetchall() + + cursor.close() + connection.close() + return results + + +# ------------------- Get All Hold Types ------------------- +def get_all_hold_types(): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + cursor.callproc("GetAllHoldTypes") + hold_types = [] + + for result in cursor.stored_results(): + hold_types = result.fetchall() + + cursor.close() + connection.close() + return hold_types + + +# ------------------- Get Invoice By Id ------------------- +def get_invoice_by_id(invoice_id): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + cursor.callproc('GetInvoiceDetailsById', [invoice_id]) + invoice = None + + for result in cursor.stored_results(): + invoice = result.fetchone() + + cursor.callproc('GetHoldAmountsByInvoiceId', [invoice_id]) + hold_amounts = [] + + for result in cursor.stored_results(): + hold_amounts = result.fetchall() + + if invoice: + invoice["hold_amounts"] = hold_amounts + + cursor.close() + connection.close() + return invoice + + +# ------------------- Update Invoice ------------------- +def update_invoice(data, invoice_id): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + try: + cursor.callproc("GetVillageIdByName", (data.get('village'),)) + village = None + + for rs in cursor.stored_results(): + village = rs.fetchone() + + village_id = village['Village_Id'] + + numeric = [ + float(data.get('basic_amount') or 0), + float(data.get('debit_amount') or 0), + float(data.get('after_debit_amount') or 0), + float(data.get('amount') or 0), + float(data.get('gst_amount') or 0), + float(data.get('tds_amount') or 0), + float(data.get('sd_amount') or 0), + float(data.get('on_commission') or 0), + float(data.get('hydro_testing') or 0), + float(data.get('gst_sd_amount') or 0), + float(data.get('final_amount') or 0), + ] + + cursor.callproc('UpdateInvoice', [ + data.get('pmc_no'), + village_id, + data.get('work_type'), + data.get('invoice_details'), + data.get('invoice_date'), + data.get('invoice_no'), + *numeric, + invoice_id + ]) + clear_results(cursor) + + connection.commit() + + except Exception as e: + connection.rollback() + raise e + + finally: + cursor.close() + connection.close() + + +# ------------------- Update Inpayment ------------------- +def update_inpayment(data): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + try: + numeric = [ + float(data.get('basic_amount') or 0), + float(data.get('debit_amount') or 0), + float(data.get('after_debit_amount') or 0), + float(data.get('amount') or 0), + float(data.get('gst_amount') or 0), + float(data.get('tds_amount') or 0), + float(data.get('sd_amount') or 0), + float(data.get('on_commission') or 0), + float(data.get('hydro_testing') or 0), + float(data.get('gst_sd_amount') or 0), + float(data.get('final_amount') or 0), + ] + + cursor.callproc('UpdateInpayment', [ + data.get('work_type'), + data.get('invoice_details'), + data.get('invoice_date'), + *numeric, + data.get('pmc_no'), + data.get('invoice_no') + ]) + clear_results(cursor) + + connection.commit() + + except Exception as e: + connection.rollback() + raise e + + finally: + cursor.close() + connection.close() + + +# ------------------- Delete Invoice ------------------- +def delete_invoice_data(invoice_id, user_id): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + try: + cursor.callproc('GetInvoicePMCById', [invoice_id]) + + record = {} + for result in cursor.stored_results(): + record = result.fetchone() or {} + if not record: + raise Exception("Invoice not found") + + cursor.callproc("DeleteInvoice", (invoice_id,)) + clear_results(cursor) + + cursor.callproc( + 'DeleteInpaymentByPMCInvoice', + [record['PMC_No'], record['invoice_no']] + ) + + connection.commit() + + except Exception as e: + connection.rollback() + raise e + + finally: + cursor.close() + connection.close() \ No newline at end of file diff --git a/model/ItemCRUD.py b/model/ItemCRUD.py new file mode 100644 index 0000000..19f8dfb --- /dev/null +++ b/model/ItemCRUD.py @@ -0,0 +1,359 @@ +from flask_login import current_user +from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType +from model.Log import LogHelper + +import config +import re +import mysql.connector + + +# ---------------------------------------------------------- +# Mapping Class +# ---------------------------------------------------------- +class itemCRUDMapping: + + def __init__(self, itemType): + if itemType is ItemCRUDType.Village: + self.name = "Village" + elif itemType is ItemCRUDType.Block: + self.name = "Block" + elif itemType is ItemCRUDType.State: + self.name = "State" + elif itemType is ItemCRUDType.HoldType: + self.name = "Hold Type" + elif itemType is ItemCRUDType.Subcontractor: + self.name = "Subcontractor" + else: + self.name = "Item" + + +# ---------------------------------------------------------- +# Generic CRUD Class +# ---------------------------------------------------------- +class ItemCRUD: + + def __init__(self, itemType): + self.isSuccess = False + self.resultMessage = "" + self.itemCRUDType = itemType + self.itemCRUDMapping = itemCRUDMapping(itemType) + + # ---------------------------------------------------------- + # DELETE + # ---------------------------------------------------------- + def DeleteItem(self, request, itemID, storedprocDelete): + + connection = config.get_db_connection() + cursor = connection.cursor() + + LogHelper.log_action( + f"Delete {self.itemCRUDMapping.name}", + f"User {current_user.id} deleted {self.itemCRUDMapping.name} '{itemID}'" + ) + + try: + cursor.callproc(storedprocDelete, (itemID,)) + connection.commit() + + self.isSuccess = True + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.delete_success(self.itemCRUDMapping.name), 200 + ) + + except mysql.connector.Error as e: + print(f"Error deleting {self.itemCRUDMapping.name}: {e}") + self.isSuccess = False + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.delete_failure(self.itemCRUDMapping.name), 500 + ) + + finally: + cursor.close() + connection.close() + + # ---------------------------------------------------------- + # ADD + # ---------------------------------------------------------- + def AddItem(self, request, parentid=None, childname=None, storedprocfetch=None, storedprocadd=None, data=None): + + connection = config.get_db_connection() + if not connection: + self.isSuccess = False + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.db_connection_failure(), 500 + ) + return + + cursor = connection.cursor() + + LogHelper.log_action( + f"Add {self.itemCRUDMapping.name}", + f"User {current_user.id} adding '{childname if childname else (data.get('Contractor_Name') if data else '')}'" + ) + + try: + # ====================================================== + # SUBCONTRACTOR (MULTI-FIELD) + # ====================================================== + if data: + + # Duplicate check + cursor.callproc(storedprocfetch, (data['Contractor_Name'],)) + + existing_item = None + for rs in cursor.stored_results(): + existing_item = rs.fetchone() + + if existing_item: + self.isSuccess = False + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.already_exists(self.itemCRUDMapping.name), 409 + ) + return + + # Insert + cursor.callproc(storedprocadd, ( + data['Contractor_Name'], + data['Address'], + data['Mobile_No'], + data['PAN_No'], + data['Email'], + data['Gender'], + data['GST_Registration_Type'], + data['GST_No'], + data['Contractor_password'] + )) + + connection.commit() + + self.isSuccess = True + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.add_success(self.itemCRUDMapping.name), 200 + ) + return + + # ====================================================== + # NORMAL (Village / Block / State) + # ====================================================== + if not re.match(RegEx.patternAlphabetOnly, childname): + self.isSuccess = False + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400 + ) + return + + # Duplicate check + if parentid is None: + cursor.callproc(storedprocfetch, (childname,)) + else: + cursor.callproc(storedprocfetch, (childname, parentid)) + + existing_item = None + for rs in cursor.stored_results(): + existing_item = rs.fetchone() + + if existing_item: + self.isSuccess = False + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.already_exists(self.itemCRUDMapping.name), 409 + ) + return + + # Insert + if parentid is None: + cursor.callproc(storedprocadd, (childname,)) + else: + cursor.callproc(storedprocadd, (childname, parentid)) + + connection.commit() + + self.isSuccess = True + self.resultMessage = HtmlHelper.json_response( + + ResponseHandler.add_success(self.itemCRUDMapping.name), 200 + ) + + except mysql.connector.Error as e: + print(f"Database Error: {e}") + self.isSuccess = False + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.add_failure(self.itemCRUDMapping.name), 500 + ) + + finally: + cursor.close() + connection.close() + + # ---------------------------------------------------------- + # EDIT + # ---------------------------------------------------------- + def EditItem(self, request, childid, parentid=None, childname=None, storedprocupdate=None, data=None): + + connection = config.get_db_connection() + cursor = connection.cursor() + + LogHelper.log_action( + f"Edit {self.itemCRUDMapping.name}", + f"User {current_user.id} edited '{childid}'" + ) + + try: + # ====================================================== + # SUBCONTRACTOR (MULTI-FIELD) + # ====================================================== + if data: + cursor.callproc(storedprocupdate, ( + childid, + data['Contractor_Name'], + data['Address'], + data['Mobile_No'], + data['PAN_No'], + data['Email'], + data['Gender'], + data['GST_Registration_Type'], + data['GST_No'], + data['Contractor_password'] + )) + + connection.commit() + + self.isSuccess = True + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.update_success(self.itemCRUDMapping.name), 200 + ) + return + + # ====================================================== + # NORMAL + # ====================================================== + if not re.match(RegEx.patternAlphabetOnly, childname): + self.isSuccess = False + self.resultMessage = ResponseHandler.update_failure(self.itemCRUDMapping.name)['message'] + return + + if parentid is None: + cursor.callproc(storedprocupdate, (childid, childname)) + else: + cursor.callproc(storedprocupdate, (childid, parentid, childname)) + + connection.commit() + + self.isSuccess = True + self.resultMessage = ResponseHandler.update_success(self.itemCRUDMapping.name)['message'] + + except mysql.connector.Error as e: + print(f"Error updating {self.itemCRUDMapping.name}: {e}") + self.isSuccess = False + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.update_failure(self.itemCRUDMapping.name), 500 + ) + + finally: + cursor.close() + connection.close() + + # ---------------------------------------------------------- + # GET ALL + # ---------------------------------------------------------- + def GetAllData(self, request, storedproc): + + data = [] + connection = config.get_db_connection() + + if not connection: + return [] + + cursor = connection.cursor() + + try: + cursor.callproc(storedproc) + + for result in cursor.stored_results(): + data = result.fetchall() + + self.isSuccess = True + + except mysql.connector.Error as e: + print(f"Error fetching {self.itemCRUDMapping.name}: {e}") + self.isSuccess = False + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500 + ) + return [] + + finally: + cursor.close() + connection.close() + + return data + + # ---------------------------------------------------------- + # GET BY ID + # ---------------------------------------------------------- + def GetDataByID(self, id, storedproc): + + data = None + connection = config.get_db_connection() + cursor = connection.cursor() + + try: + cursor.callproc(storedproc, (id,)) + + for rs in cursor.stored_results(): + data = rs.fetchone() + + except mysql.connector.Error as e: + print(f"Error fetching {self.itemCRUDMapping.name}: {e}") + + finally: + cursor.close() + connection.close() + + return data + + # ---------------------------------------------------------- + # CHECK ITEM + # ---------------------------------------------------------- + def CheckItem(self, request, parentid, childname, storedprocfetch): + + connection = config.get_db_connection() + cursor = connection.cursor() + + LogHelper.log_action( + f"Check {self.itemCRUDMapping.name}", + f"User {current_user.id} checked '{childname}'" + ) + + if not re.match(RegEx.patternAlphabetOnly, childname): + return HtmlHelper.json_response( + ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400 + ) + + try: + if parentid is None: + cursor.callproc(storedprocfetch, (childname,)) + else: + cursor.callproc(storedprocfetch, (childname, parentid)) + + existing_item = None + for rs in cursor.stored_results(): + existing_item = rs.fetchone() + + if existing_item: + return HtmlHelper.json_response( + ResponseHandler.already_exists(self.itemCRUDMapping.name), 409 + ) + + return HtmlHelper.json_response( + ResponseHandler.is_available(self.itemCRUDMapping.name), 200 + ) + + except mysql.connector.Error as e: + print(f"Error checking {self.itemCRUDMapping.name}: {e}") + return HtmlHelper.json_response( + ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500 + ) + + finally: + cursor.close() + connection.close() \ No newline at end of file diff --git a/AppCode/Log.py b/model/Log.py similarity index 100% rename from AppCode/Log.py rename to model/Log.py diff --git a/model/PmcReport.py b/model/PmcReport.py new file mode 100644 index 0000000..c8ec69e --- /dev/null +++ b/model/PmcReport.py @@ -0,0 +1,456 @@ +import openpyxl +from openpyxl.styles import Font, PatternFill +import config +from flask_login import current_user +from model.Log import LogHelper + +from model.Report import ReportHelper +from model.FolderAndFile import FolderAndFile + +class PmcReport: + + @staticmethod + def get_pmc_report(pmc_no): + + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True, buffered=True) + + try: + + # cursor.callproc("GetContractorInfoByPmcNo", (pmc_no,)) + # pmc_info = next(cursor.stored_results()).fetchone() + pmc_info = ReportHelper.execute_sp(cursor, 'GetContractorInfoByPmcNo', [pmc_no], True) + + if not pmc_info: + return None + + cursor.callproc("Get_pmc_hold_types", (pmc_no, pmc_info["Contractor_Id"])) + hold_types = next(cursor.stored_results()).fetchall() + + # Extract hold_type_ids + hold_type_ids = [ht['hold_type_id'] for ht in hold_types] + + invoices = [] + hold_amount_total = 0 + if hold_type_ids: + hold_type_ids_str = ",".join(map(str, hold_type_ids)) + cursor.callproc( + 'GetInvoices_WithHold', + [pmc_no, pmc_info["Contractor_Id"], hold_type_ids_str] + ) + else: + cursor.callproc( + 'GetInvoices_NoHold', + [pmc_no, pmc_info["Contractor_Id"]] + ) + for result in cursor.stored_results(): + invoices = result.fetchall() + + if hold_type_ids: + hold_amount_total = sum(row.get('hold_amount', 0) or 0 for row in invoices) + + total_invo_final = sum(row.get('Final_Amount', 0) or 0 for row in invoices) + + + # GST RELEASE + # cursor.callproc('GetGSTReleaseByPMC', [pmc_no]) + # gst_rel = [] + # for result in cursor.stored_results(): + # gst_rel = result.fetchall() + + gst_rel = ReportHelper.execute_sp(cursor, 'GetGSTReleaseByPMC', [pmc_no]) + + 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 ---------------- + # cursor.callproc('GetHoldReleaseByPMC', [pmc_no]) + # hold_release = [] + # for result in cursor.stored_results(): + # hold_release = result.fetchall() + + hold_release = ReportHelper.execute_sp(cursor, 'GetHoldReleaseByPMC', [pmc_no]) + + # ---------------- CREDIT NOTE ---------------- + # cursor.callproc('GetCreditNoteByPMC', [pmc_no]) + # credit_note = [] + # for result in cursor.stored_results(): + # credit_note = result.fetchall() + + credit_note = ReportHelper.execute_sp(cursor, 'GetCreditNoteByPMC', [pmc_no]) + + payments = ReportHelper.execute_sp(cursor, 'GetPaymentsByPMC', [pmc_no]) + + + # ---------------- PAYMENTS ---------------- + # cursor.callproc('GetPaymentsByPMC', [pmc_no]) + # payments = [] + # 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) + + 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 + } + + return { + "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 + } + + finally: + cursor.close() + connection.close() + + + @staticmethod + def download_pmc_report(pmc_no): + + connection = config.get_db_connection() + if not connection: + return None + + cursor = connection.cursor(dictionary=True) + + try: + # filename + filename = f"PMC_Report_{pmc_no}.xlsx" + + output_folder = FolderAndFile.get_download_folder() + output_file = FolderAndFile.get_download_path(filename) + + # ================= DATA FETCH ================= + + contractor_info = ReportHelper.execute_sp(cursor, 'GetContractorDetailsByPMC', [pmc_no], "one") + + if not contractor_info: + return None + + hold_types = ReportHelper.execute_sp(cursor, 'GetHoldTypesByContractor', [contractor_info["Contractor_Id"]]) + + hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types} + + invoices = ReportHelper.execute_sp(cursor, 'GetInvoicesAndGstReleaseByPmcNo', [pmc_no]) + + credit_notes = ReportHelper.execute_sp(cursor, 'GetCreditNoteByContractor', [contractor_info["Contractor_Id"]]) + + hold_amounts = ReportHelper.execute_sp(cursor, 'GetHoldAmountsByContractor', [contractor_info["Contractor_Id"]]) + + all_payments = ReportHelper.execute_sp(cursor, 'GetAllPaymentsByPMC', [pmc_no]) + + gst_releases = ReportHelper.execute_sp(cursor, 'GetGSTReleaseDetailsByPMC', [pmc_no]) + + # ================= DATA MAPPING ================= + + hold_data = {} + for h in hold_amounts: + hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount'] + + payments_map = {} + for pay in all_payments: + if pay['invoice_no']: + payments_map.setdefault(pay['invoice_no'], []).append(pay) + + # ================= LOG ================= + LogHelper.log_action( + "Download PMC Report", + f"User {current_user.id} Download PMC Report '{pmc_no}'" + ) + + # ================= EXCEL ================= + workbook = openpyxl.Workbook() + sheet = workbook.active + sheet.title = "PMC Report" + + # HEADER INFO + sheet.append(["", "", "Laxmi Civil Engineering Services PVT. LTD."]) + sheet.append(["Contractor Name", contractor_info["Contractor_Name"]]) + sheet.append(["State", contractor_info["State_Name"]]) + sheet.append(["District", contractor_info["District_Name"]]) + sheet.append(["Block", contractor_info["Block_Name"]]) + sheet.append([]) + + base_headers = [ + "PMC No","Village","Work Type","Invoice Details","Invoice Date","Invoice No", + "Basic Amount","Debit","After Debit Amount","GST","Amount","TDS", + "SD","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" + ] + + headers = base_headers + hold_headers + payment_headers + sheet.append(headers) + + # STYLE + for cell in sheet[sheet.max_row]: + cell.font = Font(bold=True) + + # DATA + seen_invoices = set() + + for inv in invoices: + + invoice_no = inv["Invoice_No"] + payments = payments_map.get(invoice_no, []) + + if invoice_no in seen_invoices: + continue + + 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"] + ] + + # HOLD DATA + invoice_holds = hold_data.get(inv["Invoice_Id"], {}) + for ht_id in hold_type_map.keys(): + row.append(invoice_holds.get(ht_id, "")) + + # PAYMENT DATA + 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) + + # AUTO WIDTH + for col in sheet.columns: + max_len = max((len(str(cell.value)) for cell in col if cell.value), default=0) + sheet.column_dimensions[col[0].column_letter].width = max_len + 2 + + # SAVE + workbook.save(output_file) + workbook.close() + + return output_folder, filename + + except Exception as e: + print(f"Error generating PMC report: {e}") + return None + + finally: + cursor.close() + connection.close() + + # @staticmethod + # def download_pmc_report(pmc_no): + + # connection = config.get_db_connection() + # cursor = connection.cursor(dictionary=True) + + # # output_folder = "static/download" + # # output_file = os.path.join(output_folder, f"PMC_Report_{pmc_no}.xlsx") + # output_folder = FolderAndFile.get_download_folder + # filename = f"PMC_Report_{pmc_no}.xlsx" + # output_file = FolderAndFile.get_download_path(filename) + + # try: + + # cursor.callproc('GetContractorDetailsByPMC', [pmc_no]) + # contractor_info = next(cursor.stored_results()).fetchone() + + # if not contractor_info: + # return None + + # 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('GetCreditNoteByContractor',[contractor_info["Contractor_Id"]]) + + # credit_notes = [] + # for result in cursor.stored_results(): + # credit_notes = result.fetchall() + + # credit_note_map = {} + # for cn in credit_notes: + # key = (cn["PMC_No"], cn["Invoice_No"]) + # credit_note_map.setdefault(key, []).append(cn) + + # 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) + + # # ---------------- GST RELEASE DETAILS ---------------- + # cursor.callproc('GetGSTReleaseDetailsByPMC', [pmc_no]) + + # gst_releases = [] + # for result in cursor.stored_results(): + # gst_releases = result.fetchall() + + # gst_release_map = {} + + # for gr in gst_releases: + + # invoice_nos = [] + + # if gr['Invoice_No']: + + # cleaned = gr['Invoice_No'].replace(' ', '') + + # if '&' in cleaned: + # invoice_nos = cleaned.split('&') + + # elif ',' in cleaned: + # invoice_nos = cleaned.split(',') + + # else: + # invoice_nos = [cleaned] + + # for inv_no in invoice_nos: + # gst_release_map.setdefault(inv_no, []).append(gr) + + # LogHelper.log_action( + # "Download PMC Report", + # f"User {current_user.id} Download PMC Report '{pmc_no}'" + # ) + + # workbook = openpyxl.Workbook() + # sheet = workbook.active + # sheet.title = "PMC Report" + + # 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) + + # 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() + # 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) + + # workbook.save(output_file) + # workbook.close() + + # return output_folder, filename + + # finally: + + # cursor.close() + # connection.close() \ No newline at end of file diff --git a/model/Report.py b/model/Report.py new file mode 100644 index 0000000..41d5ad2 --- /dev/null +++ b/model/Report.py @@ -0,0 +1,275 @@ +import config +from datetime import datetime +from flask import send_file +import openpyxl +from openpyxl.styles import Font + +from model.FolderAndFile import FolderAndFile + +class ReportHelper: + isSuccess = False + resultMessage = "" + data=[] + + def __init__(self): + self.isSuccess = False + self.resultMessage = "" + self.data = [] + + @staticmethod + def execute_sp(cursor, proc_name, params=[], fetch_one=False): + cursor.callproc(proc_name, params) + return ( + ReportHelper.fetch_one_result(cursor) + if fetch_one else + ReportHelper.fetch_all_results(cursor) + ) + + @staticmethod + def fetch_all_results(cursor): + data = [] + for result in cursor.stored_results(): + data = result.fetchall() + return data + + + @staticmethod + def fetch_one_result(cursor): + data = None + for result in cursor.stored_results(): + data = result.fetchone() + return data + + + @staticmethod + def search_contractor(request): + 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") + + connection = config.get_db_connection() + if not connection: + return [] + + cursor = connection.cursor(dictionary=True) + + try: + data = ReportHelper.execute_sp( + cursor, + "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 + ] + ) + + except Exception as e: + print(f"Error in search_contractor: {e}") + data = [] + + finally: + cursor.close() + connection.close() + + return data + + + @staticmethod + def get_contractor_report(contractor_id): + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True, buffered=True) + + try: + # Contractor Info (only one fetch) + contInfo = ReportHelper.execute_sp(cursor, 'GetContractorInfo', [contractor_id], True) + # Hold Types + hold_types = ReportHelper.execute_sp(cursor, 'GetContractorHoldTypes', [contractor_id]) + # Invoices + invoices = ReportHelper.execute_sp(cursor, 'GetContractorInvoices', [contractor_id]) + # GST Release + gst_rel = ReportHelper.execute_sp(cursor, 'GetGSTRelease', [contractor_id]) + # Hold Release + hold_release = ReportHelper.execute_sp(cursor, 'GetHoldRelease', [contractor_id]) + # Credit Note + credit_note = ReportHelper.execute_sp(cursor, 'GetCreditNote', [contractor_id]) + # Payments + payments = ReportHelper.execute_sp(cursor, 'GetPayments', [contractor_id]) + + # 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') + + finally: + cursor.close() + connection.close() + + return { + "contInfo": contInfo, + "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 + } + + @staticmethod + def download_report(contractor_id): + try: + connection = config.get_db_connection() + cursor = connection.cursor(dictionary=True) + + # -------- Contractor Info -------- + contInfo = ReportHelper.execute_sp(cursor, 'GetContractorInfo', [contractor_id], True) + + if not contInfo: + return "No contractor found", 404 + + # -------- Invoice Data -------- + cursor.callproc('FetchInvoicesByContractor', [contractor_id]) + + invoices = [] + for result in cursor.stored_results(): + invoices.extend(result.fetchall()) + + if not invoices: + return "No invoice data found" + + # -------- Create Workbook -------- + workbook = openpyxl.Workbook() + sheet = workbook.active + sheet.title = "Contractor Report" + + # ================= CONTRACTOR DETAILS ================= + sheet.append(["SUB CONTRACTOR DETAILS"]) + sheet.cell(row=sheet.max_row, column=1).font = Font(bold=True) + sheet.append([]) + + sheet.append(["Name", contInfo.get("Contractor_Name") or ""]) + sheet.append(["Mobile No", contInfo.get("Mobile_No") or ""]) + sheet.append(["Email", contInfo.get("Email") or ""]) + sheet.append(["Village", contInfo.get("Village_Name") or ""]) + sheet.append(["Block", contInfo.get("Block_Name") or ""]) + sheet.append(["District", contInfo.get("District_Name") or ""]) + sheet.append(["State", contInfo.get("State_Name") or ""]) + sheet.append(["Address", contInfo.get("Address") or ""]) + sheet.append(["GST No", contInfo.get("GST_No") or ""]) + sheet.append(["PAN No", contInfo.get("PAN_No") or ""]) + sheet.append([]) + sheet.append([]) + + # ================= TABLE HEADERS ================= + headers = [ + "PMC No", "Village", "Invoice No", "Invoice Date", "Work Type","Invoice_Details", + "Basic Amount", "Debit Amount", "After Debit Amount", + "Amount", "GST Amount", "TDS Amount", "SD Amount", + "On Commission", "Hydro Testing", "Hold Amount", + "GST SD Amount", "Final Amount", + "Payment Amount", "TDS Payment", + "Total Amount", "UTR" + ] + sheet.append(headers) + for col in range(1, len(headers) + 1): + sheet.cell(row=sheet.max_row, column=col).font = Font(bold=True) + + # ================= DATA ================= + total_final = 0 + total_payment = 0 + total_amount = 0 + + for inv in invoices: + row = [ + inv.get("PMC_No"), + inv.get("Village_Name"), + inv.get("invoice_no"), + inv.get("Invoice_Date"), + inv.get("Work_Type"), + inv.get("Invoice_Details"), + inv.get("Basic_Amount"), + inv.get("Debit_Amount"), + inv.get("After_Debit_Amount"), + inv.get("Amount"), + inv.get("GST_Amount"), + inv.get("TDS_Amount"), + inv.get("SD_Amount"), + inv.get("On_Commission"), + inv.get("Hydro_Testing"), + inv.get("Hold_Amount"), + inv.get("GST_SD_Amount"), + inv.get("Final_Amount"), + inv.get("Payment_Amount"), + inv.get("TDS_Payment_Amount"), + inv.get("Total_Amount"), + inv.get("UTR") + ] + + total_final += float(inv.get("Final_Amount") or 0) + total_payment += float(inv.get("Payment_Amount") or 0) + total_amount += float(inv.get("Total_Amount") or 0) + + sheet.append(row) + + # ================= TOTAL ROW ================= + sheet.append([]) + sheet.append([ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "TOTAL", + total_final, + total_payment, + "", + total_amount, + "" + ]) + + # ================= AUTO WIDTH ================= + for column in sheet.columns: + max_length = 0 + column_letter = column[0].column_letter + for cell in column: + if cell.value: + max_length = max(max_length, len(str(cell.value))) + sheet.column_dimensions[column_letter].width = max_length + 2 + + # ================= SAVE FILE ================= + filename = f"Contractor_Report_{contInfo.get('Contractor_Name')}.xlsx" + output_file = FolderAndFile.get_download_path(filename) + workbook.save(output_file) + + return send_file(output_file, as_attachment=True) + + except Exception as e: + return str(e) \ No newline at end of file diff --git a/model/State.py b/model/State.py new file mode 100644 index 0000000..9245a99 --- /dev/null +++ b/model/State.py @@ -0,0 +1,168 @@ +from flask import request, redirect, url_for +from flask_login import current_user + +from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType +from model.Log import LogHelper +from model.ItemCRUD import ItemCRUD + +import config +import re +import mysql.connector + + +class State: + + def __init__(self): + self.isSuccess = False + self.resultMessage = "" + + # ---------------------------------------------------------- + # ADD STATE (USING ITEM CRUD) + # ---------------------------------------------------------- + def AddState(self, request): + + state_name = request.form['state_Name'].strip() + + crud = ItemCRUD(ItemCRUDType.State) + + crud.AddItem( + request=request, + childname=state_name, + storedprocfetch="CheckStateExists", + storedprocadd="SaveState" + ) + + self.isSuccess = crud.isSuccess + self.resultMessage = crud.resultMessage + + return self.resultMessage + + + # ---------------------------------------------------------- + # GET ALL STATES (NO CHANGE - THIS IS CORRECT) + # ---------------------------------------------------------- + def GetAllStates(self, request): + + connection = config.get_db_connection() + data = [] + + if not connection: + return [] + + cursor = connection.cursor() + + try: + cursor.callproc("GetAllStates") + for res in cursor.stored_results(): + data = res.fetchall() + + self.isSuccess = True + + except mysql.connector.Error as e: + print(f"Error fetching states: {e}") + self.isSuccess = False + self.resultMessage = HtmlHelper.json_response( + ResponseHandler.fetch_failure("state"), 500 + ) + return [] + + finally: + cursor.close() + connection.close() + + return data + + + # ---------------------------------------------------------- + # CHECK STATE (USING ITEM CRUD) + # ---------------------------------------------------------- + def CheckState(self, request): + + state_name = request.json.get('state_Name', '').strip() + + crud = ItemCRUD(ItemCRUDType.State) + + return crud.CheckItem( + request=request, + parentid=None, + childname=state_name, + storedprocfetch="CheckStateExists" + ) + + + # ---------------------------------------------------------- + # DELETE STATE (USING ITEM CRUD) + # ---------------------------------------------------------- + def DeleteState(self, request, id): + + crud = ItemCRUD(ItemCRUDType.State) + + crud.DeleteItem( + request=request, + itemID=id, + storedprocDelete="DeleteState" + ) + + self.isSuccess = crud.isSuccess + self.resultMessage = crud.resultMessage + + return self.resultMessage + + + # ---------------------------------------------------------- + # EDIT STATE (USING ITEM CRUD) + # ---------------------------------------------------------- + def EditState(self, request, id): + + state_name = request.form['state_Name'].strip() + + crud = ItemCRUD(ItemCRUDType.State) + + crud.EditItem( + request=request, + childid=id, + parentid=None, + childname=state_name, + storedprocupdate="UpdateStateById" + ) + + self.isSuccess = crud.isSuccess + self.resultMessage = crud.resultMessage + + return redirect(url_for('state.add_state')) + + + # ---------------------------------------------------------- + # GET STATE BY ID (KEEP SAME) + # ---------------------------------------------------------- + def GetStateByID(self, request, id): + + connection = config.get_db_connection() + data = None + + if not connection: + return None + + cursor = connection.cursor() + + try: + cursor.callproc("GetStateByID", (id,)) + for res in cursor.stored_results(): + data = res.fetchone() + + if data: + self.isSuccess = True + self.resultMessage = "Success" + else: + self.isSuccess = False + self.resultMessage = "Not Found" + + except mysql.connector.Error as e: + print(f"Error fetching state: {e}") + self.isSuccess = False + + finally: + cursor.close() + connection.close() + + return data \ No newline at end of file diff --git a/model/Subcontractor.py b/model/Subcontractor.py new file mode 100644 index 0000000..0ec659d --- /dev/null +++ b/model/Subcontractor.py @@ -0,0 +1,140 @@ +from model.Utilities import ItemCRUDType +from model.ItemCRUD import ItemCRUD + + +class Subcontractor: + def __init__(self): + self.isSuccess = False + self.resultMessage = "" + + # ---------------------------------------------------------- + # ADD + # ---------------------------------------------------------- + def AddSubcontractor(self, request): + + subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor) + + data = { + "Contractor_Name": request.form.get('Contractor_Name', '').strip(), + "Address": request.form.get('Address', '').strip(), + "Mobile_No": request.form.get('Mobile_No', '').strip(), + "PAN_No": request.form.get('PAN_No', '').strip(), + "Email": request.form.get('Email', '').strip(), + "Gender": request.form.get('Gender', '').strip(), + "GST_Registration_Type": request.form.get('GST_Registration_Type', '').strip(), + "GST_No": request.form.get('GST_No', '').strip(), + "Contractor_password": request.form.get('Contractor_password', '').strip() + } + + subcontractor.AddItem( + request=request, + data=data, + storedprocfetch="GetSubcontractorByName", + storedprocadd="SaveContractor" + ) + + self.isSuccess = subcontractor.isSuccess + self.resultMessage = subcontractor.resultMessage + return + + # ---------------------------------------------------------- + # GET ALL + # ---------------------------------------------------------- + def GetAllSubcontractors(self, request): + + subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor) + + data = subcontractor.GetAllData( + request=request, + storedproc="GetAllSubcontractors" + ) + + self.isSuccess = subcontractor.isSuccess + self.resultMessage = subcontractor.resultMessage + return data + + # ---------------------------------------------------------- + # GET BY ID + # ---------------------------------------------------------- + def GetSubcontractorByID(self, id): + + subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor) + + data = subcontractor.GetDataByID( + id=id, + storedproc="GetSubcontractorById" + ) + + if data: + self.isSuccess = True + else: + self.isSuccess = False + self.resultMessage = "Subcontractor not found" + + return data + + # ---------------------------------------------------------- + # CHECK (Duplicate) + # ---------------------------------------------------------- + def CheckSubcontractor(self, request): + + subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor) + + name = request.form.get('Contractor_Name', '').strip() + + result = subcontractor.CheckItem( + request=request, + childname=name, + storedprocfetch="GetSubcontractorByName" + ) + + self.isSuccess = subcontractor.isSuccess + self.resultMessage = subcontractor.resultMessage + return result + + # ---------------------------------------------------------- + # EDIT + # ---------------------------------------------------------- + def EditSubcontractor(self, request, subcontractor_id): + + subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor) + + data = { + "Contractor_Name": request.form.get('Contractor_Name', '').strip(), + "Address": request.form.get('Address', '').strip(), + "Mobile_No": request.form.get('Mobile_No', '').strip(), + "PAN_No": request.form.get('PAN_No', '').strip(), + "Email": request.form.get('Email', '').strip(), + "Gender": request.form.get('Gender', '').strip(), + "GST_Registration_Type": request.form.get('GST_Registration_Type', '').strip(), + "GST_No": request.form.get('GST_No', '').strip(), + "Contractor_password": request.form.get('Contractor_password', '').strip() + } + + subcontractor.EditItem( + request=request, + childid=subcontractor_id, + data=data, + storedprocupdate="UpdateSubcontractor" + ) + + self.isSuccess = subcontractor.isSuccess + self.resultMessage = subcontractor.resultMessage + return + + # ---------------------------------------------------------- + # DELETE + # ---------------------------------------------------------- + def DeleteSubcontractor(self, request, subcontractor_id): + + subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor) + + subcontractor.DeleteItem( + request=request, + itemID=subcontractor_id, + storedprocDelete="DeleteSubcontractor" + ) + + self.isSuccess = subcontractor.isSuccess + self.resultMessage = subcontractor.resultMessage + return \ No newline at end of file diff --git a/AppCode/Utilities.py b/model/Utilities.py similarity index 97% rename from AppCode/Utilities.py rename to model/Utilities.py index f6228b7..83868ba 100644 --- a/AppCode/Utilities.py +++ b/model/Utilities.py @@ -6,7 +6,9 @@ class ItemCRUDType(Enum): Block = 2 District = 3 State = 4 - + HoldType = 5 + Subcontractor = 6 + class RegEx: patternAlphabetOnly = "^[A-Za-z ]+$" diff --git a/AppCode/Village.py b/model/Village.py similarity index 77% rename from AppCode/Village.py rename to model/Village.py index 79b98db..3b7b0cb 100644 --- a/AppCode/Village.py +++ b/model/Village.py @@ -1,15 +1,15 @@ from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user -from AppCode.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType -from AppCode.Log import LogData, LogHelper +from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType +from model.Log import LogData, LogHelper import config import mysql.connector from mysql.connector import Error -from AppCode.ItemCRUD import ItemCRUD, itemCRUDMapping +from model.ItemCRUD import ItemCRUD class Village: @@ -60,25 +60,36 @@ class Village: return def EditVillage(self, request, village_id): - + corsor=None village = ItemCRUD(itemType=ItemCRUDType.Village) block_id = request.form.get('block_Id') village_name = request.form.get('Village_Name', '').strip() - village.EditItem(request=request, childid=village_id, parentid=block_id, childname=village_name, storedprocadd="UpdateVillage" ) + village.EditItem(request=request,childid=village_id,parentid=block_id,childname=village_name,storedprocupdate="UpdateVillage" ) + self.isSuccess = village.isSuccess self.resultMessage = village.resultMessage return + # def GetVillageByID(self, request, id): + + # village = ItemCRUD(itemType=ItemCRUDType.Village) + # villagedetailsdata = village.GetAllData(request=request, storedproc="GetVillageDetailsById") + # self.isSuccess = village.isSuccess + # self.resultMessage = village.resultMessage + # return villagedetailsdata + def GetVillageByID(self, request, id): - village = ItemCRUD(itemType=ItemCRUDType.Village) - villagedetailsdata = village.GetAllData(request=request, storedproc="GetVillageDetailsById") - self.isSuccess = village.isSuccess - self.resultMessage = village.resultMessage - return villagedetailsdata + villagedetailsdata = village.GetDataByID(id=id,storedproc="GetVillageDetailsById") + if villagedetailsdata: + self.isSuccess = True + else: + self.isSuccess = False + self.resultMessage = "Village not found" + return villagedetailsdata def GetAllBlocks(self, request): diff --git a/model/__pycache__/Auth.cpython-313.pyc b/model/__pycache__/Auth.cpython-313.pyc new file mode 100644 index 0000000..152a9c7 Binary files /dev/null and b/model/__pycache__/Auth.cpython-313.pyc differ diff --git a/model/__pycache__/Auth.cpython-314.pyc b/model/__pycache__/Auth.cpython-314.pyc new file mode 100644 index 0000000..d9bfd24 Binary files /dev/null and b/model/__pycache__/Auth.cpython-314.pyc differ diff --git a/model/__pycache__/Block.cpython-313.pyc b/model/__pycache__/Block.cpython-313.pyc new file mode 100644 index 0000000..3ac0d04 Binary files /dev/null and b/model/__pycache__/Block.cpython-313.pyc differ diff --git a/model/__pycache__/Block.cpython-314.pyc b/model/__pycache__/Block.cpython-314.pyc new file mode 100644 index 0000000..77bf401 Binary files /dev/null and b/model/__pycache__/Block.cpython-314.pyc differ diff --git a/model/__pycache__/ContractorInfo.cpython-313.pyc b/model/__pycache__/ContractorInfo.cpython-313.pyc new file mode 100644 index 0000000..4c36879 Binary files /dev/null and b/model/__pycache__/ContractorInfo.cpython-313.pyc differ diff --git a/model/__pycache__/ContractorInfo.cpython-314.pyc b/model/__pycache__/ContractorInfo.cpython-314.pyc new file mode 100644 index 0000000..e69327b Binary files /dev/null and b/model/__pycache__/ContractorInfo.cpython-314.pyc differ diff --git a/model/__pycache__/District.cpython-313.pyc b/model/__pycache__/District.cpython-313.pyc new file mode 100644 index 0000000..b0d9f5a Binary files /dev/null and b/model/__pycache__/District.cpython-313.pyc differ diff --git a/model/__pycache__/District.cpython-314.pyc b/model/__pycache__/District.cpython-314.pyc new file mode 100644 index 0000000..d77e609 Binary files /dev/null and b/model/__pycache__/District.cpython-314.pyc differ diff --git a/model/__pycache__/FolderAndFile.cpython-313.pyc b/model/__pycache__/FolderAndFile.cpython-313.pyc new file mode 100644 index 0000000..67400e6 Binary files /dev/null and b/model/__pycache__/FolderAndFile.cpython-313.pyc differ diff --git a/model/__pycache__/FolderorFile.cpython-313.pyc b/model/__pycache__/FolderorFile.cpython-313.pyc new file mode 100644 index 0000000..214f09e Binary files /dev/null and b/model/__pycache__/FolderorFile.cpython-313.pyc differ diff --git a/model/__pycache__/GST.cpython-313.pyc b/model/__pycache__/GST.cpython-313.pyc new file mode 100644 index 0000000..2a83b64 Binary files /dev/null and b/model/__pycache__/GST.cpython-313.pyc differ diff --git a/model/__pycache__/GST.cpython-314.pyc b/model/__pycache__/GST.cpython-314.pyc new file mode 100644 index 0000000..5f258d6 Binary files /dev/null and b/model/__pycache__/GST.cpython-314.pyc differ diff --git a/model/__pycache__/HoldTypes.cpython-313.pyc b/model/__pycache__/HoldTypes.cpython-313.pyc new file mode 100644 index 0000000..3cc1ffa Binary files /dev/null and b/model/__pycache__/HoldTypes.cpython-313.pyc differ diff --git a/model/__pycache__/HoldTypes.cpython-314.pyc b/model/__pycache__/HoldTypes.cpython-314.pyc new file mode 100644 index 0000000..3a51958 Binary files /dev/null and b/model/__pycache__/HoldTypes.cpython-314.pyc differ diff --git a/model/__pycache__/Invoice.cpython-313.pyc b/model/__pycache__/Invoice.cpython-313.pyc new file mode 100644 index 0000000..9c74712 Binary files /dev/null and b/model/__pycache__/Invoice.cpython-313.pyc differ diff --git a/model/__pycache__/Invoice.cpython-314.pyc b/model/__pycache__/Invoice.cpython-314.pyc new file mode 100644 index 0000000..68c9058 Binary files /dev/null and b/model/__pycache__/Invoice.cpython-314.pyc differ diff --git a/model/__pycache__/ItemCRUD.cpython-313.pyc b/model/__pycache__/ItemCRUD.cpython-313.pyc new file mode 100644 index 0000000..4cc7be4 Binary files /dev/null and b/model/__pycache__/ItemCRUD.cpython-313.pyc differ diff --git a/model/__pycache__/ItemCRUD.cpython-314.pyc b/model/__pycache__/ItemCRUD.cpython-314.pyc new file mode 100644 index 0000000..df8d535 Binary files /dev/null and b/model/__pycache__/ItemCRUD.cpython-314.pyc differ diff --git a/model/__pycache__/Log.cpython-313.pyc b/model/__pycache__/Log.cpython-313.pyc new file mode 100644 index 0000000..2ace267 Binary files /dev/null and b/model/__pycache__/Log.cpython-313.pyc differ diff --git a/model/__pycache__/Log.cpython-314.pyc b/model/__pycache__/Log.cpython-314.pyc new file mode 100644 index 0000000..4c5491b Binary files /dev/null and b/model/__pycache__/Log.cpython-314.pyc differ diff --git a/model/__pycache__/PmcReport.cpython-313.pyc b/model/__pycache__/PmcReport.cpython-313.pyc new file mode 100644 index 0000000..332434c Binary files /dev/null and b/model/__pycache__/PmcReport.cpython-313.pyc differ diff --git a/model/__pycache__/PmcReport.cpython-314.pyc b/model/__pycache__/PmcReport.cpython-314.pyc new file mode 100644 index 0000000..40f23be Binary files /dev/null and b/model/__pycache__/PmcReport.cpython-314.pyc differ diff --git a/model/__pycache__/Report.cpython-313.pyc b/model/__pycache__/Report.cpython-313.pyc new file mode 100644 index 0000000..c504df7 Binary files /dev/null and b/model/__pycache__/Report.cpython-313.pyc differ diff --git a/model/__pycache__/Report.cpython-314.pyc b/model/__pycache__/Report.cpython-314.pyc new file mode 100644 index 0000000..51dc322 Binary files /dev/null and b/model/__pycache__/Report.cpython-314.pyc differ diff --git a/model/__pycache__/State.cpython-313.pyc b/model/__pycache__/State.cpython-313.pyc new file mode 100644 index 0000000..f6fe37d Binary files /dev/null and b/model/__pycache__/State.cpython-313.pyc differ diff --git a/model/__pycache__/State.cpython-314.pyc b/model/__pycache__/State.cpython-314.pyc new file mode 100644 index 0000000..7e0b85e Binary files /dev/null and b/model/__pycache__/State.cpython-314.pyc differ diff --git a/model/__pycache__/Subcontractor.cpython-313.pyc b/model/__pycache__/Subcontractor.cpython-313.pyc new file mode 100644 index 0000000..5d43735 Binary files /dev/null and b/model/__pycache__/Subcontractor.cpython-313.pyc differ diff --git a/model/__pycache__/Subcontractor.cpython-314.pyc b/model/__pycache__/Subcontractor.cpython-314.pyc new file mode 100644 index 0000000..838bcdf Binary files /dev/null and b/model/__pycache__/Subcontractor.cpython-314.pyc differ diff --git a/model/__pycache__/Utilities.cpython-313.pyc b/model/__pycache__/Utilities.cpython-313.pyc new file mode 100644 index 0000000..68460ee Binary files /dev/null and b/model/__pycache__/Utilities.cpython-313.pyc differ diff --git a/model/__pycache__/Utilities.cpython-314.pyc b/model/__pycache__/Utilities.cpython-314.pyc new file mode 100644 index 0000000..d4ac339 Binary files /dev/null and b/model/__pycache__/Utilities.cpython-314.pyc differ diff --git a/model/__pycache__/Village.cpython-313.pyc b/model/__pycache__/Village.cpython-313.pyc new file mode 100644 index 0000000..39c6b36 Binary files /dev/null and b/model/__pycache__/Village.cpython-313.pyc differ diff --git a/model/__pycache__/Village.cpython-314.pyc b/model/__pycache__/Village.cpython-314.pyc new file mode 100644 index 0000000..dd3c537 Binary files /dev/null and b/model/__pycache__/Village.cpython-314.pyc differ diff --git a/model/__pycache__/gst_release.cpython-313.pyc b/model/__pycache__/gst_release.cpython-313.pyc new file mode 100644 index 0000000..c0de6de Binary files /dev/null and b/model/__pycache__/gst_release.cpython-313.pyc differ diff --git a/model/__pycache__/gst_release.cpython-314.pyc b/model/__pycache__/gst_release.cpython-314.pyc new file mode 100644 index 0000000..69aa90d Binary files /dev/null and b/model/__pycache__/gst_release.cpython-314.pyc differ diff --git a/model/__pycache__/payment.cpython-313.pyc b/model/__pycache__/payment.cpython-313.pyc new file mode 100644 index 0000000..b59f310 Binary files /dev/null and b/model/__pycache__/payment.cpython-313.pyc differ diff --git a/model/__pycache__/payment.cpython-314.pyc b/model/__pycache__/payment.cpython-314.pyc new file mode 100644 index 0000000..9792ad9 Binary files /dev/null and b/model/__pycache__/payment.cpython-314.pyc differ diff --git a/model/gst_release.py b/model/gst_release.py new file mode 100644 index 0000000..732f9d7 --- /dev/null +++ b/model/gst_release.py @@ -0,0 +1,150 @@ +import config +import mysql.connector + +class GSTReleasemodel: + + @staticmethod + def get_connection(): + connection = config.get_db_connection() + if not connection: + return None + return connection + + @staticmethod + def fetch_all_gst_releases(): + connection = GSTReleasemodel.get_connection() + gst_releases = [] + if connection: + cursor = connection.cursor(dictionary=True) + + try: + cursor.callproc('GetAllGSTReleases') + + gst_releases = [] + for result in cursor.stored_results(): # change to procedure + gst_releases = result.fetchall() + + except mysql.connector.Error as e: + print(f"Error fetching GST releases: {e}") + finally: + cursor.close() + connection.close() + return gst_releases + + @staticmethod + def insert_gst_release(pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr, contractor_id): + connection = GSTReleasemodel.get_connection() + if not connection: + return False + try: + cursor = connection.cursor() + + # Insert into gst_release + cursor.callproc( + 'InsertGSTReleaseOnly', + [pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr, contractor_id] + ) + + # Insert into inpayment + cursor.callproc( + 'InsertInpaymentOnly', + [pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr, contractor_id] + ) + + connection.commit() + return True + except mysql.connector.Error as e: + print(f"Error inserting GST release: {e}") + return False + finally: + cursor.close() + connection.close() + + @staticmethod + def fetch_gst_release_by_id(gst_release_id): + connection = GSTReleasemodel.get_connection() + if not connection: + return None + data = {} + try: + cursor = connection.cursor(dictionary=True) + + cursor.callproc('GetGSTReleaseById', [gst_release_id]) + + for result in cursor.stored_results(): + data = result.fetchone() + if data: + # Convert to array for template + data = [ + data.get('GST_Release_Id'), + data.get('PMC_No'), + data.get('Invoice_No'), + data.get('Basic_Amount'), + data.get('Final_Amount'), + data.get('Total_Amount'), + data.get('UTR') + ] + except mysql.connector.Error as e: + print(f"Error fetching GST release by id: {e}") + finally: + cursor.close() + connection.close() + return data + + @staticmethod + def update_gst_release(gst_release_id, pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr): + connection = GSTReleasemodel.get_connection() + if not connection: + return False + try: + cursor = connection.cursor() + # Update gst_release + cursor.callproc( + 'UpdateGSTRelease', + [pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr, gst_release_id] + ) + # Update inpayment + cursor.callproc( + 'UpdateInpaymentByUTR', + [basic_amount, final_amount, total_amount, utr] + ) + connection.commit() + return True + except mysql.connector.Error as e: + print(f"Error updating GST release: {e}") + return False + finally: + cursor.close() + connection.close() + + @staticmethod + def delete_gst_release(gst_release_id): + connection = GSTReleasemodel.get_connection() + if not connection: + return False, None + try: + cursor = connection.cursor(dictionary=True) + cursor.callproc('GetGSTReleaseUTRById', [gst_release_id]) + record = None + for result in cursor.stored_results(): + record = result.fetchone() + + if not record: + return False, None + + utr = record['UTR'] + + # Step 1: Delete gst_release + cursor.callproc('DeleteGSTReleaseById', [gst_release_id]) + + # Step 2: Reset inpayment using UTR + cursor.callproc('ResetInpaymentByUTR', [utr]) + + connection.commit() + return True, utr + except mysql.connector.Error as e: + print(f"Error deleting GST release: {e}") + return False, None + finally: + cursor.close() + connection.close() \ No newline at end of file diff --git a/model/payment.py b/model/payment.py new file mode 100644 index 0000000..90bd738 --- /dev/null +++ b/model/payment.py @@ -0,0 +1,158 @@ +import config +import mysql.connector + +class Paymentmodel: + + @staticmethod + def get_connection(): + connection = config.get_db_connection() + if not connection: + return None + return connection + + @staticmethod + def fetch_all_payments(): + connection = Paymentmodel.get_connection() + payments = [] + if connection: + cursor = connection.cursor(dictionary=True) + try: + 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}") + finally: + cursor.close() + connection.close() + return payments + + @staticmethod + def insert_payment(pmc_no, invoice_no, amount, tds_amount, total_amount, utr): + connection = Paymentmodel.get_connection() + if not connection: + return False + try: + cursor = connection.cursor() + cursor.callproc('InsertPayments', [pmc_no, invoice_no, amount, tds_amount, total_amount, utr]) + connection.commit() + return True + except mysql.connector.Error as e: + print(f"Error inserting payment: {e}") + return False + finally: + cursor.close() + connection.close() + + @staticmethod + def update_inpayment(subcontractor_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr): + connection = Paymentmodel.get_connection() + if not connection: + return False + try: + cursor = connection.cursor() + cursor.callproc('UpdateInpaymentRecord', [ + subcontractor_id, + pmc_no, + invoice_no, + amount, + tds_amount, + total_amount, + utr + ]) + connection.commit() + return True + except mysql.connector.Error as e: + print(f"Error updating inpayment: {e}") + return False + finally: + cursor.close() + connection.close() + + @staticmethod + def fetch_payment_by_id(payment_id): + connection = Paymentmodel.get_connection() + if not connection: + return None + payment_data = {} + try: + cursor = connection.cursor(dictionary=True) + cursor.callproc("GetPaymentById", (payment_id,)) + for result in cursor.stored_results(): + payment_data = result.fetchone() + # Convert to array for template + if payment_data: + payment_data = [ + payment_data.get('Payment_Id'), + payment_data.get('PMC_No'), + payment_data.get('Invoice_No'), + payment_data.get('Payment_Amount'), + payment_data.get('TDS_Payment_Amount'), + payment_data.get('Total_Amount'), + payment_data.get('UTR') + ] + except mysql.connector.Error as e: + print(f"Error fetching payment data: {e}") + finally: + cursor.close() + connection.close() + return payment_data + + @staticmethod + def call_update_payment_proc(payment_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr): + connection = Paymentmodel.get_connection() + if not connection: + return False + try: + cursor = connection.cursor() + cursor.callproc("UpdatePayment", (payment_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr)) + connection.commit() + return True + except mysql.connector.Error as e: + print(f"Error updating payment: {e}") + return False + finally: + cursor.close() + connection.close() + + @staticmethod + def delete_payment(payment_id): + """ + Deletes a payment and resets the related inpayment fields in one go. + Returns (success, pmc_no, invoice_no) + """ + connection = Paymentmodel.get_connection() + if not connection: + return False, None, None + + try: + cursor = connection.cursor(dictionary=True) + + cursor.callproc('GetPaymentPMCInvoiceById', [payment_id]) + + record = {} + for result in cursor.stored_results(): + record = result.fetchone() or {} + if not record: + return False, None, None + + pmc_no = record['PMC_No'] + invoice_no = record['Invoice_No'] + + # Step 2: Delete the payment using the stored procedure + cursor.callproc("DeletePayment", (payment_id,)) + connection.commit() + + # Step 3: Reset inpayment fields using the stored procedure + cursor.callproc("ResetInpayment", [pmc_no, invoice_no]) + connection.commit() + + return True, pmc_no, invoice_no + + except mysql.connector.Error as e: + print(f"Error deleting payment: {e}") + return False, None, None + + finally: + cursor.close() + connection.close() \ No newline at end of file diff --git a/static/js/block.js b/static/js/block.js index 801f38d..2448888 100644 --- a/static/js/block.js +++ b/static/js/block.js @@ -1,87 +1,116 @@ window.onload = function () { - document.getElementById('block_Name').focus(); - }; - + document.getElementById('block_Name').focus(); +}; $(document).ready(function () { - $("#block_Name").on("input", function () { - let blockName = $(this).val(); - let cleanedName = blockName.replace(/[^A-Za-z ]/g, ""); - $(this).val(cleanedName); - }); - $("#block_Name, #district_Id").on("input change", function () { - let blockName = $("#block_Name").val().trim(); - let districtId = $("#district_Id").val(); + $("#block_Name").on("input", function () { + let blockName = $(this).val(); + let cleanedName = blockName.replace(/[^A-Za-z ]/g, ""); + $(this).val(cleanedName); + }); - if (blockName === "" || districtId === "") { - $("#blockMessage").text("").css("color", ""); - $("#submitButton").prop("disabled", true); - return; - } + $("#block_Name, #district_Id").on("input change", function () { - $.ajax({ - url: "/check_block", - type: "POST", - contentType: "application/json", - data: JSON.stringify({ block_Name: blockName, district_Id: districtId }), - success: function (response) { - if (response.status === "available") { - $("#blockMessage").text(response.message).css("color", "green"); - $("#submitButton").prop("disabled", false); - } - }, - error: function (xhr) { - if (xhr.status === 409) { - $("#blockMessage").text("Block already exists!").css("color", "red"); - $("#submitButton").prop("disabled", true); - } else if (xhr.status === 400) { - $("#blockMessage").text("Invalid block name! Only letters are allowed.").css("color", "red"); - $("#submitButton").prop("disabled", true); - } + let blockName = $("#block_Name").val().trim(); + let districtId = $("#district_Id").val(); + + if (blockName === "" || districtId === "") { + $("#blockMessage").text("").css("color", ""); + $("#submitButton").prop("disabled", true); + return; + } + + $.ajax({ + url: "/check_block", + type: "POST", + contentType: "application/json", + data: JSON.stringify({ + block_Name: blockName, + district_Id: districtId + }), + success: function (response) { + + if (response.status === "available") { + $("#blockMessage").text(response.message).css("color", "green"); + $("#submitButton").prop("disabled", false); } - }); - }); + }, + error: function (xhr) { - $("#blockForm").on("submit", function (event) { - event.preventDefault(); - $.ajax({ - url: "/add_block", - type: "POST", - data: $(this).serialize(), - success: function (response) { - alert(response.message); - location.reload(); - }, - error: function (xhr) { - alert(xhr.responseJSON.message); + if (xhr.status === 409) { + $("#blockMessage").text("Block already exists!").css("color", "red"); + $("#submitButton").prop("disabled", true); + } + else if (xhr.status === 400) { + $("#blockMessage").text("Invalid block name! Only letters allowed.").css("color", "red"); + $("#submitButton").prop("disabled", true); } - }); - }); - - $('#state_Id').change(function() { - var stateId = $(this).val(); - if (stateId) { - $.ajax({ - url: '/get_districts/' + stateId, - type: 'GET', - success: function(data) { - var districtDropdown = $('#district_Id'); - districtDropdown.empty(); - districtDropdown.append(''); - - data.districts.forEach(function(district) { - districtDropdown.append(''); - }); - - districtDropdown.prop('disabled', false); - }, - error: function() { - alert('Error fetching districts. Please try again.'); - } - }); - } else { - $('#district_Id').prop('disabled', true); } }); - }); \ No newline at end of file + }); + + + $("#blockForm").on("submit", function (event) { + + event.preventDefault(); + + $.ajax({ + url: "/add_block", + type: "POST", + data: $(this).serialize(), + + success: function (response) { + alert(response.message); + location.reload(); + }, + + error: function (xhr) { + alert(xhr.responseJSON.message); + } + }); + }); + + + + // ✅ FETCH DISTRICTS WHEN STATE CHANGES + $('#state_Id').change(function () { + + var stateId = $(this).val(); + + if (stateId) { + + $.ajax({ + url: '/get_districts/' + stateId, + type: 'GET', + + success: function (data) { + + var districtDropdown = $('#district_Id'); + + districtDropdown.empty(); + districtDropdown.append(''); + + data.forEach(function (district) { + + districtDropdown.append( + '' + ); + + }); + + districtDropdown.prop('disabled', false); + }, + + error: function () { + alert('Error fetching districts. Please try again.'); + } + }); + + } else { + $('#district_Id').prop('disabled', true); + } + + }); + +}); \ No newline at end of file diff --git a/static/js/village.js b/static/js/village.js index 7bcefcf..e07e455 100644 --- a/static/js/village.js +++ b/static/js/village.js @@ -1,102 +1,198 @@ - window.onload = function () { - document.getElementById('Village_Name').focus(); - }; + document.getElementById('Village_Name').focus(); +}; $(document).ready(function () { - $('#state_Id').change(function () { - var stateId = $(this).val(); - if (stateId) { - $.ajax({ - url: '/get_districts/' + stateId, - type: 'GET', - success: function (data) { - var districtDropdown = $('#district_Id'); - districtDropdown.empty().append(''); - data.districts.forEach(function (district) { - districtDropdown.append(''); - }); - districtDropdown.prop('disabled', false); - } - }); - } - }); - $('#district_Id').change(function () { - var districtId = $(this).val(); - if (districtId) { - $.ajax({ - url: '/get_blocks/' + districtId, - type: 'GET', - success: function (data) { - var blockDropdown = $('#block_Id'); - blockDropdown.empty().append(''); - data.blocks.forEach(function (block) { - blockDropdown.append(''); - }); - blockDropdown.prop('disabled', false); - } - }); - } - }); + // STATE → DISTRICT + $('#state_Id').change(function () { - $('#Village_Name').on('input', function () { - var villageName = $(this).val(); - var validPattern = /^[A-Za-z ]*$/; + var stateId = $(this).val(); - if (!validPattern.test(villageName)) { - $('#villageMessage').text('Only letters and spaces are allowed!').css('color', 'red'); - $('#submitVillage').prop('disabled', true); - } else { - $('#villageMessage').text(''); - $('#submitVillage').prop('disabled', false); - } - }); - - $('#Village_Name, #block_Id').on('change keyup', function () { - var blockId = $('#block_Id').val(); - var villageName = $('#Village_Name').val().trim(); - - if (blockId && villageName) { - $.ajax({ - url: '/check_village', - type: 'POST', - data: { block_Id: blockId, Village_Name: villageName }, - success: function (response) { - if (response.status === 'exists') { - $('#villageMessage').text(response.message).css('color', 'red'); - $('#submitVillage').prop('disabled', true); - } else { - $('#villageMessage').text(response.message).css('color', 'green'); - $('#submitVillage').prop('disabled', false); - } - }, - error: function () { - $('#villageMessage').text('Error checking village name').css('color', 'red'); - $('#submitVillage').prop('disabled', true); - } - }); - } - }); - - $('#villageForm').submit(function (event) { - event.preventDefault(); // Prevent default form submission + if (stateId) { $.ajax({ - url: '/add_village', - type: 'POST', - data: $(this).serialize(), - success: function (response) { - if (response.status === 'success') { - alert('Village added successfully!'); - location.reload(); // Refresh the page to show the updated list - } else { - alert('Error adding village. Please try again.'); - } - }, - error: function () { - alert('An error occurred. Please try again.'); + url: '/get_districts/' + stateId, + type: 'GET', + + success: function (data) { + + var districtDropdown = $('#district_Id'); + + districtDropdown.empty(); + districtDropdown.append(''); + + data.forEach(function (district) { + + districtDropdown.append( + '' + ); + + }); + + districtDropdown.prop('disabled', false); + } + }); + + } + + }); + + + // DISTRICT → BLOCK + $('#district_Id').change(function () { + + var districtId = $(this).val(); + + if (districtId) { + + $.ajax({ + url: '/get_blocks/' + districtId, + type: 'GET', + + success: function (data) { + + var blockDropdown = $('#block_Id'); + + blockDropdown.empty(); + blockDropdown.append(''); + + data.forEach(function (block) { + + blockDropdown.append( + '' + ); + + }); + + blockDropdown.prop('disabled', false); + + } + + }); + + } + + }); + + + // VILLAGE NAME VALIDATION + $('#Village_Name').on('input', function () { + + var villageName = $(this).val(); + var validPattern = /^[A-Za-z ]*$/; + + if (!validPattern.test(villageName)) { + + $('#villageMessage') + .text('Only letters and spaces are allowed!') + .css('color', 'red'); + + $('#submitVillage').prop('disabled', true); + + } else { + + $('#villageMessage').text(''); + $('#submitVillage').prop('disabled', false); + + } + + }); + + + // CHECK DUPLICATE VILLAGE + $('#Village_Name, #block_Id').on('change keyup', function () { + + var blockId = $('#block_Id').val(); + var villageName = $('#Village_Name').val().trim(); + + if (blockId && villageName) { + + $.ajax({ + + url: '/check_village', + type: 'POST', + + data: { + block_Id: blockId, + Village_Name: villageName + }, + + success: function (response) { + + if (response.status === 'exists') { + + $('#villageMessage') + .text(response.message) + .css('color', 'red'); + + $('#submitVillage').prop('disabled', true); + + } else { + + $('#villageMessage') + .text(response.message) + .css('color', 'green'); + + $('#submitVillage').prop('disabled', false); + + } + + }, + + error: function () { + + $('#villageMessage') + .text('Error checking village name') + .css('color', 'red'); + + $('#submitVillage').prop('disabled', true); + + } + + }); + + } + + }); + + + // ADD VILLAGE + $('#villageForm').submit(function (event) { + + event.preventDefault(); + + $.ajax({ + + url: '/add_village', + type: 'POST', + data: $(this).serialize(), + + success: function (response) { + + if (response.status === 'success') { + + alert('Village added successfully!'); + location.reload(); + + } else { + + alert('Error adding village. Please try again.'); + + } + + }, + + error: function () { + + alert('An error occurred. Please try again.'); + + } + }); - }); \ No newline at end of file + + }); + +}); \ No newline at end of file diff --git a/templates/add_block.html b/templates/add_block.html index e3ba5e4..4bcf1f7 100644 --- a/templates/add_block.html +++ b/templates/add_block.html @@ -1,5 +1,6 @@ {% extends 'base.html' %} {% block content %} + @@ -8,90 +9,91 @@ + - -
    - - -
    + +
    + + +
    -