Initial commit

This commit is contained in:
2026-03-23 12:00:53 +05:30
commit 29bc547fce
590 changed files with 20626 additions and 0 deletions

9
.env Normal file
View File

@@ -0,0 +1,9 @@
Secret_Key = 9f2a1b8c4d6e7f0123456789abcdef01
MYSQL_HOST=127.0.0.1
MYSQL_USER=root
MYSQL_PASSWORD=admin
MYSQL_DB=test
DEFAULT_USERNAME=admin
DEFAULT_PASSWORD=admin123

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
venv/
*.pyc
__pycache__/
.uploads
static/download/

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
main.py

10
.idea/ManagementApplicationt.iml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13 (ManagementApplicationt)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ManagementApplicationt.iml" filepath="$PROJECT_DIR$/.idea/ManagementApplicationt.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
# Use official Python image
FROM python:3.9
# Set working directory inside container
WORKDIR /app
# Copy all files to container
COPY . .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Expose Flask's default port
EXPOSE 5000
# Run Flask app
CMD ["python", "main.py"]

1
README.md Normal file
View File

@@ -0,0 +1 @@
# MA07-05-2025

7867
activity.log Normal file

File diff suppressed because it is too large Load Diff

114
app.log Normal file
View File

@@ -0,0 +1,114 @@
2025-02-15 11:07:16,100 - INFO - Logging is set up.
2025-02-15 11:07:16,100 - INFO - Logging is set up.
2025-02-15 11:07:16,131 - WARNING - * Debugger is active!
2025-02-15 11:07:16,131 - WARNING - * Debugger is active!
2025-02-15 11:07:16,137 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:07:16,137 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:13:26,290 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET / HTTP/1.1" 200 -
2025-02-15 11:13:26,290 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET / HTTP/1.1" 200 -
2025-02-15 11:13:26,315 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/css/index.css HTTP/1.1" 304 -
2025-02-15 11:13:26,315 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/css/index.css HTTP/1.1" 304 -
2025-02-15 11:13:26,504 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:26,504 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:26,626 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/js/validateFileInput.js HTTP/1.1" 304 -
2025-02-15 11:13:26,626 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/js/validateFileInput.js HTTP/1.1" 304 -
2025-02-15 11:13:26,633 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/js/searchContractor.js HTTP/1.1" 304 -
2025-02-15 11:13:26,633 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /static/js/searchContractor.js HTTP/1.1" 304 -
2025-02-15 11:13:26,950 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /favicon.ico HTTP/1.1" 404 -
2025-02-15 11:13:26,950 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "GET /favicon.ico HTTP/1.1" 404 -
2025-02-15 11:13:28,623 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET / HTTP/1.1" 200 -
2025-02-15 11:13:28,623 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET / HTTP/1.1" 200 -
2025-02-15 11:13:28,933 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/css/index.css HTTP/1.1" 304 -
2025-02-15 11:13:28,933 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/css/index.css HTTP/1.1" 304 -
2025-02-15 11:13:28,952 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/js/validateFileInput.js HTTP/1.1" 304 -
2025-02-15 11:13:28,952 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/js/validateFileInput.js HTTP/1.1" 304 -
2025-02-15 11:13:28,954 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/js/searchContractor.js HTTP/1.1" 304 -
2025-02-15 11:13:28,955 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:28,954 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/js/searchContractor.js HTTP/1.1" 304 -
2025-02-15 11:13:28,955 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:31,608 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:13:31,608 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:13:31,639 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:31,639 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:13:31,649 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:13:31,649 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:13:31,967 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:13:31,967 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:15:01,349 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:01] "POST /check_state HTTP/1.1" 409 -
2025-02-15 11:15:01,349 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:01] "POST /check_state HTTP/1.1" 409 -
2025-02-15 11:15:21,783 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:21] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:21,783 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:21] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,127 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,127 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,151 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,151 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,391 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,391 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,440 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:22,440 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:22] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:15:24,266 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:24] "POST /add_state HTTP/1.1" 200 -
2025-02-15 11:15:24,266 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:24] "POST /add_state HTTP/1.1" 200 -
2025-02-15 11:15:25,418 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:15:25,418 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:15:25,716 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:15:25,716 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:15:25,749 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:15:25,749 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:15:25,752 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:15:25,752 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:46,338 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:16:46,338 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:16:48,798 - INFO - Logging is set up.
2025-02-15 11:16:48,798 - INFO - Logging is set up.
2025-02-15 11:16:48,843 - WARNING - * Debugger is active!
2025-02-15 11:16:48,843 - WARNING - * Debugger is active!
2025-02-15 11:16:48,847 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:16:48,847 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:16:52,045 - DEBUG - Fetched state data successfully.
2025-02-15 11:16:52,045 - DEBUG - Fetched state data successfully.
2025-02-15 11:16:52,054 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:16:52,054 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:16:52,076 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:16:52,076 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:16:52,078 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:52,078 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:52,377 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:52,377 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:54,758 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:54] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:54,758 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:54] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:54,992 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:54] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:54,992 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:54] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:55,016 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:55] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:55,016 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:55] "POST /check_state HTTP/1.1" 200 -
2025-02-15 11:16:55,669 - INFO - State 'sss' added successfully.
2025-02-15 11:16:55,669 - INFO - 'State 'sss added successfully.
2025-02-15 11:16:55,670 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:55] "POST /add_state HTTP/1.1" 200 -
2025-02-15 11:16:55,670 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:55] "POST /add_state HTTP/1.1" 200 -
2025-02-15 11:16:57,235 - DEBUG - Fetched state data successfully.
2025-02-15 11:16:57,235 - DEBUG - Fetched state data successfully.
2025-02-15 11:16:57,239 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:16:57,239 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /add_state HTTP/1.1" 200 -
2025-02-15 11:16:57,263 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:16:57,263 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/css/style.css HTTP/1.1" 304 -
2025-02-15 11:16:57,483 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:57,483 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/images/icons/pen_blue_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:57,567 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:16:57,567 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "GET /static/images/icons/bin_red_icon.png HTTP/1.1" 304 -
2025-02-15 11:20:55,547 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:20:55,547 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:20:56,800 - INFO - Logging is set up.
2025-02-15 11:20:56,800 - INFO - Logging is set up.
2025-02-15 11:20:56,835 - WARNING - * Debugger is active!
2025-02-15 11:20:56,835 - WARNING - * Debugger is active!
2025-02-15 11:20:56,837 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:20:56,837 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:21:04,060 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:21:04,060 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:21:05,429 - INFO - Logging is set up.
2025-02-15 11:21:05,429 - INFO - Logging is set up.
2025-02-15 11:21:05,461 - WARNING - * Debugger is active!
2025-02-15 11:21:05,461 - WARNING - * Debugger is active!
2025-02-15 11:21:05,463 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:21:05,463 - INFO - * Debugger PIN: 558-213-972
2025-02-15 11:21:17,911 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
2025-02-15 11:21:17,911 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading

21
config.py Normal file
View File

@@ -0,0 +1,21 @@
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")
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():
return mysql.connector.connect(
host=MYSQL_HOST,
user=MYSQL_USER,
password=MYSQL_PASSWORD,
database=MYSQL_DB
)

View File

@@ -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'))

View File

@@ -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/<int:state_id>')
@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/<int:block_id>', 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/<int:block_id>')
@login_required
def delete_block(block_id):
block = Block()
block.DeleteBlock(request, block_id)
return redirect(url_for('block.add_block'))

View File

@@ -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/<int:district_id>')
@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/<int:district_id>', 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
)

View File

@@ -0,0 +1,389 @@
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 # your database connection module
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)
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/<filename>')
def show_table(filename):
global data
data = []
filepath = os.path.join(get_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()
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 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 --------------------------------

View File

@@ -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/<int:gst_release_id>', 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/<int:gst_release_id>', 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

View File

@@ -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/<int:id>', 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/<int:id>')
@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
)

View File

@@ -0,0 +1,200 @@
# # 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 "<li>No subcontractor found</li>"
# output = "".join(
# f"<li data-id='{row['Contractor_Id']}'>{row['Contractor_Name']}</li>"
# 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/<int:invoice_id>', 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/<int:invoice_id>', 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
# 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__)
# ------------------------------- Helpers -------------------------------
def handle_exception(func):
"""Decorator to handle exceptions and return JSON error responses."""
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
return jsonify({"status": "error", "message": str(e)}), 500
wrapper.__name__ = func.__name__
return wrapper
def log_action(action: str, detail: str):
LogHelper.log_action(action, f"User {current_user.id} {detail}")
# ------------------------------- Add Invoice -------------------------------
@invoice_bp.route('/add_invoice', methods=['GET', 'POST'])
@login_required
@handle_exception
def add_invoice():
if request.method == 'POST':
data = request.form
village_name = data.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']
invoice_id = insert_invoice(data, village_id)
assign_subcontractor(data, village_id)
insert_hold_types(data, invoice_id)
log_action("Add invoice", f"added invoice '{data.get('pmc_no')}'")
return jsonify({"status": "success", "message": "Invoice added successfully"}), 201
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
@handle_exception
def search_subcontractor():
query = request.form.get("query", "").strip()
results = search_contractors(query)
if not results:
return "<li>No subcontractor found</li>"
return "".join(f"<li data-id='{r['Contractor_Id']}'>{r['Contractor_Name']}</li>" for r in results)
# ------------------------------- Get Hold Types -------------------------------
@invoice_bp.route('/get_hold_types', methods=['GET'])
@login_required
@handle_exception
def get_hold_types():
hold_types = get_all_hold_types()
log_action("Get hold type", f"retrieved hold types '{hold_types}'")
return jsonify(hold_types)
# ------------------------------- Edit Invoice -------------------------------
@invoice_bp.route('/edit_invoice/<int:invoice_id>', methods=['GET', 'POST'])
@login_required
@handle_exception
def edit_invoice(invoice_id):
if request.method == 'POST':
data = request.form
update_invoice(data, invoice_id)
update_inpayment(data)
log_action("Edit invoice", f"edited 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/<int:invoice_id>', methods=['GET'])
@login_required
@handle_exception
def delete_invoice_route(invoice_id):
delete_invoice_data(invoice_id, current_user.id)
log_action("Delete invoice", f"deleted invoice '{invoice_id}'")
return jsonify({"status": "success", "message": f"Invoice {invoice_id} deleted successfully."})

View File

@@ -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
)

View File

@@ -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/<subcontractorId>')
@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/<int:payment_id>', 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/<int:payment_id>', 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

View File

@@ -0,0 +1,36 @@
from flask import Blueprint, render_template, send_from_directory
from model.PmcReport import PmcReport
pmc_report_bp = Blueprint("pmc_report", __name__)
@pmc_report_bp.route("/pmc_report/<pmc_no>")
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/<pmc_no>")
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)

View File

@@ -0,0 +1,202 @@
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
from datetime import datetime
import os
import openpyxl
from openpyxl.styles import Font
from model.ContractorInfo import ContractorInfo
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")
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")
LogHelper.log_action(
"Search Contractor",
f"User {current_user.id} Search contractor '{subcontractor_name}'"
)
data = ReportHelper.search_contractor(
subcontractor_name,
pmc_no,
state,
district,
block,
village,
year_from,
year_to
)
return jsonify(data)
# ---------------- Contractor Report ----------------
@report_bp.route('/contractor_report/<int:contractor_id>')
@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
)
class FilePathData:
downloadReportFolder = "static/download"
@report_bp.route('/download_report/<int:contractor_id>')
@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)
workbook.save(output_file)
return send_file(output_file, as_attachment=True)
except Exception as e:
return str(e)

View File

@@ -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/<int:id>')
@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/<int:id>', 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)

View File

@@ -0,0 +1,100 @@
# controllers/subcontractor_controller.py
from flask import Blueprint, render_template, request, redirect, url_for, jsonify
from flask_login import login_required
from model.Subcontractor import Subcontractor
from model.Log import LogHelper
subcontractor_bp = Blueprint('subcontractor', __name__)
# Simple replacements for missing HtmlHelper and ResponseHandler
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}"}
@subcontractor_bp.route('/subcontractor', methods=['GET', 'POST'])
@login_required
def subcontract():
subcontractor = []
if request.method == 'GET':
subcontractor, error = Subcontractor.get_all_subcontractors()
if error:
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'],
}
error = Subcontractor.save_subcontractor(contractor_data)
if error:
return HtmlHelper.json_response(ResponseHandler.add_failure("Subcontractor"), 500)
subcontractor, _ = Subcontractor.get_all_subcontractors()
return render_template('add_subcontractor.html', subcontractor=subcontractor)
@subcontractor_bp.route('/edit_subcontractor/<int:id>', methods=['GET', 'POST'])
@login_required
def edit_subcontractor(id):
subcontractor, error = Subcontractor.get_subcontractor_by_id(id)
if error:
return HtmlHelper.json_response(ResponseHandler.fetch_failure("Subcontractor"), 500)
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'],
}
error = Subcontractor.update_subcontractor(id, updated_data)
if error:
return HtmlHelper.json_response(ResponseHandler.update_failure("Subcontractor"), 500)
return redirect(url_for('subcontractor.subcontract'))
return render_template('edit_subcontractor.html', subcontractor=subcontractor)
@subcontractor_bp.route('/deleteSubContractor/<int:id>', methods=['GET', 'POST'])
@login_required
def deleteSubContractor(id):
error, affected_rows = Subcontractor.delete_subcontractor(id)
if error:
return HtmlHelper.json_response(ResponseHandler.delete_failure("Subcontractor"), 500)
if affected_rows == 0:
return HtmlHelper.json_response(ResponseHandler.fetch_failure("Subcontractor not deleted"), 404)
return redirect(url_for('subcontractor.subcontract'))

View File

@@ -0,0 +1,305 @@
# 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/<int:state_id>')
# @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/<int:district_id>')
# @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/<int:village_id>')
# @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/<int:village_id>', 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(id=village_id)
# blocks = village.GetAllBlocks()
# 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
# )
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/<int:state_id>')
@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()
return jsonify([{"id": d[0], "name": d[1]} for d in districts])
# ------------------------- Fetch Blocks -------------------------
@village_bp.route('/get_blocks/<int:district_id>')
@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()
return jsonify([{"id": b[0], "name": b[1]} for b in blocks])
# ------------------------- 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/<int:village_id>')
@login_required
def delete_village(village_id):
village = Village()
village.DeleteVillage(request=request, village_id=village_id)
flash(village.resultMessage, "success" if village.isSuccess else "error")
return redirect(url_for('village.add_village'))
# ------------------------- Edit Village -------------------------
@village_bp.route('/edit_village/<int:village_id>', 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(id=village_id) or []
blocks = village.GetAllBlocks() or []
return render_template(
'edit_village.html',
village_data=village_data,
blocks=blocks
)
else:
# ✅ FIXED HERE (removed request)
village_data = village.GetVillageByID(id=village_id)
if not village.isSuccess:
flash(village.resultMessage, "error")
return redirect(url_for('village.add_village'))
blocks = village.GetAllBlocks() or []
return render_template(
'edit_village.html',
village_data=village_data or [],
blocks=blocks
)

35
docker-compose.yml Normal file
View File

@@ -0,0 +1,35 @@
version: "3.8"
services:
flask-app:
build: .
ports:
- "5000:5000"
depends_on:
- mysql
environment:
- MYSQL_HOST=mysql
- MYSQL_USER=root
- MYSQL_PASSWORD=root
- MYSQL_DB=test
networks:
- mynetwork
mysql:
image: mysql:8.0
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
networks:
- mynetwork
volumes:
mysql-data:
networks:
mynetwork:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

0
logs/activity.log Normal file
View File

74
logs/audit.log Normal file
View File

@@ -0,0 +1,74 @@
2025-08-22 13:29:24,485 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-22 13:41:42,046 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.180
2025-08-22 14:07:01,924 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-22 15:05:59,287 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.180
2025-08-22 15:06:05,201 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-23 15:58:28,248 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-23 17:33:06,648 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-23 17:39:08,442 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-23 18:14:51,722 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
2025-08-25 11:57:12,202 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.238
2025-08-25 12:00:17,780 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 14:09:29,385 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 14:12:35,084 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 14:23:53,539 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:23:54,024 | User: v.sinha | Action: Added State | Details: User MP Adding State | IP: 192.168.0.181
2025-08-25 14:23:57,113 | User: v.sinha | Action: Deleted State | Details: User 11 Deleting State | IP: 192.168.0.181
2025-08-25 14:31:55,715 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 14:36:21,158 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:36:21,496 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
2025-08-25 14:47:08,719 | User: v.sinha | Action: Checked State | Details: User Maharashtra Checking State | IP: 192.168.0.181
2025-08-25 14:47:14,759 | User: v.sinha | Action: Deleted State | Details: User 12 Deleting State | IP: 192.168.0.181
2025-08-25 14:47:16,915 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:47:17,708 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
2025-08-25 14:49:09,480 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:49:13,014 | User: v.sinha | Action: Deleted State | Details: User 13 Deleting State | IP: 192.168.0.181
2025-08-25 14:49:14,584 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:49:15,055 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
2025-08-25 14:51:55,187 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:51:58,463 | User: v.sinha | Action: Deleted State | Details: User 14 Deleting State | IP: 192.168.0.181
2025-08-25 14:52:00,606 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:52:00,953 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
2025-08-25 14:54:26,674 | User: v.sinha | Action: Deleted State | Details: User 15 Deleting State | IP: 192.168.0.181
2025-08-25 14:54:28,892 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 14:54:29,553 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
2025-08-25 15:18:37,773 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
2025-08-25 15:18:43,347 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 15:20:41,331 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
2025-08-25 15:20:47,525 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 15:20:55,687 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
2025-08-25 15:20:58,544 | User: v.sinha | Action: Deleted State | Details: User 16 Deleting State | IP: 192.168.0.181
2025-08-25 16:33:49,898 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'MP' | IP: 192.168.0.181
2025-08-25 16:33:50,394 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'MP' | IP: 192.168.0.181
2025-08-25 16:43:46,446 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 16:43:49,710 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
2025-08-25 16:43:58,093 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 16:44:11,935 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'shamli' | IP: 192.168.0.181
2025-08-25 16:44:12,466 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'shamli' | IP: 192.168.0.181
2025-08-25 16:44:17,731 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '18' | IP: 192.168.0.181
2025-08-25 16:57:27,983 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
2025-08-25 16:57:33,498 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 16:57:41,438 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'shamli' | IP: 192.168.0.181
2025-08-25 16:57:42,250 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'shamli' | IP: 192.168.0.181
2025-08-25 16:57:45,339 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '19' | IP: 192.168.0.181
2025-08-25 16:57:48,794 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '17' | IP: 192.168.0.181
2025-08-25 17:04:11,021 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
2025-08-25 17:04:16,165 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
2025-08-25 17:04:21,702 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'shamli' | IP: 192.168.0.181
2025-08-25 17:04:22,159 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'shamli' | IP: 192.168.0.181
2025-08-25 17:04:26,850 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'M' | IP: 192.168.0.181
2025-08-25 17:04:27,076 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'MP' | IP: 192.168.0.181
2025-08-25 17:04:28,070 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'MP' | IP: 192.168.0.181
2025-08-25 17:04:32,165 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '21' | IP: 192.168.0.181
2025-08-25 17:04:35,058 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '20' | IP: 192.168.0.181
2025-08-25 17:06:05,113 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'Shamli' | IP: 192.168.0.181
2025-08-25 17:06:05,114 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'Shamli' | IP: 192.168.0.181
2025-08-25 17:06:08,040 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'p' | IP: 192.168.0.181
2025-08-25 17:06:08,360 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pu' | IP: 192.168.0.181
2025-08-25 17:06:08,554 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pun' | IP: 192.168.0.181
2025-08-25 17:06:08,756 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
2025-08-25 17:06:10,190 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
2025-08-25 17:06:11,204 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
2025-08-25 17:06:11,206 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
2025-08-25 17:06:13,085 | User: v.sinha | Action: Add District | Details: User v.sinha Added District 'pune' | IP: 192.168.0.181
2025-08-25 17:06:19,524 | User: v.sinha | Action: Delete District | Details: User v.sinha Deleted District '5' | IP: 192.168.0.181

68
main.py Normal file
View File

@@ -0,0 +1,68 @@
# main.py
from flask import Flask, render_template
from flask_login import LoginManager
from model.Auth import User
# 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 controllers.hold_types_controller import hold_bp
from dotenv import load_dotenv
import os
# ---------------- 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 = 'auth.login'
@login_manager.user_loader
def load_user(user_id):
return User(user_id)
# ---------------- Home Route ----------------
@app.route('/')
def index():
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)

63
model/Auth.py Normal file
View File

@@ -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

165
model/Block.py Normal file
View File

@@ -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

136
model/ContractorInfo.py Normal file
View File

@@ -0,0 +1,136 @@
# import mysql.connector
# from mysql.connector import Error
# import config
# import openpyxl
# import os
# import re
# import ast
# from datetime import datetime
# class ContractorInfo:
# ID = ""
# contInfo = None
# def __init__(self, id):
# self.ID = id
# print(id)
# self.fetchData()
# def fetchData(self):
# try:
# connection = config.get_db_connection()
# cursor = connection.cursor(dictionary=True, buffered=True)
# print("here", flush=True)
# cursor.callproc('GetContractorInfoById', [self.ID])
# for result in cursor.stored_results():
# self.contInfo = result.fetchone()
# print(self.contInfo,flush=True)
# finally:
# cursor.close()
# connection.close()
# def fetchalldata(self):
# try:
# connection = config.get_db_connection()
# cursor = connection.cursor(dictionary=True, buffered=True)
# print("here", flush=True)
# # ---------------- Hold Types ----------------
# 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 = 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()
# 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
# finally:
# cursor.close()
# connection.close()
from mysql.connector import Error
import config
from datetime import datetime
class ContractorInfo:
def __init__(self, contractor_id):
self.ID = contractor_id
self.contInfo = None
self.fetchData()
def fetchData(self):
"""Fetch basic contractor info by ID."""
try:
connection = config.get_db_connection()
with connection.cursor(dictionary=True, buffered=True) as cursor:
cursor.callproc('GetContractorInfoById', [self.ID])
# Get the first result set
for result in cursor.stored_results():
self.contInfo = result.fetchone()
except Error as e:
print(f"Error fetching contractor info: {e}")
finally:
if connection.is_connected():
connection.close()
def fetchalldata(self):
"""Fetch hold types and invoices for contractor."""
data = {}
try:
connection = config.get_db_connection()
with connection.cursor(dictionary=True, buffered=True) as cursor:
# Fetch Hold Types
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}
data['hold_types'] = hold_type_map
# Fetch Invoices
cursor.callproc('GetInvoicesByContractor', [self.ID])
invoices = []
for result in cursor.stored_results():
invoices = result.fetchall()
# Remove duplicate invoices
seen_ids = set()
unique_invoices = []
for inv in invoices:
if inv['Invoice_Id'] not in seen_ids:
seen_ids.add(inv['Invoice_Id'])
unique_invoices.append(inv)
data['invoices'] = unique_invoices
except Error as e:
print(f"Error fetching contractor data: {e}")
finally:
if connection.is_connected():
connection.close()
return data

100
model/District.py Normal file
View File

@@ -0,0 +1,100 @@
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
class District:
isSuccess = False
resultMessage = ""
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
def EditDistrict(self, request, district_id):
district = ItemCRUD(itemType=ItemCRUDType.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,storedprocupdate="UpdateDistrict" )
self.isSuccess = district.isSuccess
self.resultMessage = district.resultMessage
return
def AddDistrict(self, request):
district = ItemCRUD(ItemCRUDType.District)
district_name = request.form['district_Name'].strip()
state_id = request.form['state_Id']
district.AddItem(request=request, parentid=state_id, childname=district_name, storedprocfetch="GetDistrictByNameAndState", storedprocadd="SaveDistrict" )
self.isSuccess = district.isSuccess
self.resultMessage = district.resultMessage
return
def GetAllDistricts(self, request):
district = ItemCRUD(itemType=ItemCRUDType.District)
districtsdata = district.GetAllData(request=request, storedproc="GetAllDistricts")
self.isSuccess = district.isSuccess
self.resultMessage = district.resultMessage
return districtsdata
def CheckDistrict(self, request):
district = ItemCRUD(itemType=ItemCRUDType.District)
district_name = request.json.get('district_Name', '').strip()
state_id = request.json.get('state_Id', '')
result = district.CheckItem(request=request, parentid=state_id, childname=district_name, storedprocfetch="GetDistrictByNameAndState")
self.isSuccess = district.isSuccess
self.resultMessage = district.resultMessage
return result
# 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
#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)

55
model/GST.py Normal file
View File

@@ -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()

90
model/HoldTypes.py Normal file
View File

@@ -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

235
model/Invoice.py Normal file
View File

@@ -0,0 +1,235 @@
import config
import mysql.connector
# ------------------- Helper Functions -------------------
def clear_results(cursor):
"""Consume all stored results to prevent cursor issues."""
for r in cursor.stored_results():
r.fetchall()
def fetch_one(cursor):
"""Fetch first row from stored results."""
for result in cursor.stored_results():
return result.fetchone()
return None
def fetch_all(cursor):
"""Fetch all rows from stored results."""
data = []
for result in cursor.stored_results():
data.extend(result.fetchall())
return data
def get_numeric_values(data):
"""Return numeric fields for invoices safely."""
return [
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),
]
def execute_db_operation(operation_func):
"""General DB operation wrapper with commit/rollback."""
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
result = operation_func(cursor)
connection.commit()
return result
except Exception:
connection.rollback()
raise
finally:
cursor.close()
connection.close()
# ------------------- Village Functions -------------------
def get_village_id(village_name):
def operation(cursor):
cursor.callproc("GetVillageIdByName", (village_name,))
return fetch_one(cursor)
return execute_db_operation(operation)
def get_all_villages():
def operation(cursor):
cursor.callproc("GetAllVillages")
return fetch_all(cursor)
return execute_db_operation(operation)
# ------------------- Invoice Functions -------------------
def insert_invoice(data, village_id):
def operation(cursor):
# 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'),
*get_numeric_values(data)
])
invoice_row = fetch_one(cursor)
if not invoice_row:
raise Exception("Invoice ID not returned")
invoice_id = invoice_row.get('invoice_id')
# 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'),
*get_numeric_values(data),
data.get('subcontractor_id')
])
clear_results(cursor)
return invoice_id
return execute_db_operation(operation)
def get_all_invoice_details():
def operation(cursor):
cursor.callproc('GetAllInvoiceDetails')
return fetch_all(cursor)
return execute_db_operation(operation)
def get_invoice_by_id(invoice_id):
def operation(cursor):
cursor.callproc('GetInvoiceDetailsById', [invoice_id])
invoice = fetch_one(cursor)
cursor.callproc('GetHoldAmountsByInvoiceId', [invoice_id])
hold_amounts = fetch_all(cursor)
if invoice:
invoice["hold_amounts"] = hold_amounts
return invoice
return execute_db_operation(operation)
def update_invoice(data, invoice_id):
def operation(cursor):
cursor.callproc("GetVillageIdByName", (data.get('village'),))
village = fetch_one(cursor)
if not village:
raise Exception("Village not found")
village_id = village['Village_Id']
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'),
*get_numeric_values(data),
invoice_id
])
clear_results(cursor)
execute_db_operation(operation)
def update_inpayment(data):
def operation(cursor):
cursor.callproc('UpdateInpayment', [
data.get('work_type'),
data.get('invoice_details'),
data.get('invoice_date'),
*get_numeric_values(data),
data.get('pmc_no'),
data.get('invoice_no')
])
clear_results(cursor)
execute_db_operation(operation)
def delete_invoice_data(invoice_id, user_id):
def operation(cursor):
# Fetch PMC and Invoice_No from DB
cursor.callproc('GetInvoicePMCById', (invoice_id,))
record = {}
for result in cursor.stored_results():
record = result.fetchone() or {}
if not record:
raise Exception("Invoice not found")
# Use exact DB keys
pmc_no = record['PMC_No']
invoice_no = record['Invoice_No']
# Delete invoice
cursor.callproc("DeleteInvoice", (invoice_id,))
clear_results(cursor)
# Delete inpayment
cursor.callproc('DeleteInpaymentByPMCInvoice', (pmc_no, invoice_no))
clear_results(cursor)
execute_db_operation(operation)
# ------------------- Subcontractor Functions -------------------
def assign_subcontractor(data, village_id):
def operation(cursor):
cursor.callproc('AssignSubcontractor', [
data.get('pmc_no'),
data.get('subcontractor_id'),
village_id
])
clear_results(cursor)
execute_db_operation(operation)
# ------------------- Hold Types Functions -------------------
def insert_hold_types(data, invoice_id):
def operation(cursor):
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 = fetch_one(cursor)
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']
cursor.callproc('InsertInvoiceSubcontractorHold', [
data.get('subcontractor_id'),
invoice_id,
hold_type_id,
float(hold_amount or 0)
])
clear_results(cursor)
execute_db_operation(operation)
def get_all_hold_types():
def operation(cursor):
cursor.callproc("GetAllHoldTypes")
return fetch_all(cursor)
return execute_db_operation(operation)
# ------------------- Contractor Functions -------------------
def search_contractors(sub_query):
def operation(cursor):
cursor.callproc('SearchContractorsByName', [sub_query])
return fetch_all(cursor)
return execute_db_operation(operation)

287
model/ItemCRUD.py Normal file
View File

@@ -0,0 +1,287 @@
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"
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 = ResponseHandler.delete_success(self.itemCRUDMapping.name)['message']
except mysql.connector.Error as e:
print(f"Error deleting {self.itemCRUDMapping.name}: {e}")
self.isSuccess = False
self.resultMessage = ResponseHandler.delete_failure(self.itemCRUDMapping.name)['message']
finally:
cursor.close()
connection.close()
# ----------------------------------------------------------
# ADD
# ----------------------------------------------------------
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 '{childname}'"
)
# Validation
if not re.match(RegEx.patternAlphabetOnly, childname):
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400
)
return
try:
# Call check procedure
if parentid is None:
cursor.callproc(storedprocfetch, (childname,))
else:
cursor.callproc(storedprocfetch, (childname, parentid))
# ✅ FIX: initialize variable
existing_item = None
for rs in cursor.stored_results():
existing_item = rs.fetchone()
# Check duplicate
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, childname, storedprocupdate):
connection = config.get_db_connection()
cursor = connection.cursor()
LogHelper.log_action(
f"Edit {self.itemCRUDMapping.name}",
f"User {current_user.id} edited '{childid}'"
)
if not re.match(RegEx.patternAlphabetOnly, childname):
self.isSuccess = False
self.resultMessage = ResponseHandler.update_failure(self.itemCRUDMapping.name)['message']
return
try:
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))
# ✅ FIX
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()

87
model/Log.py Normal file
View File

@@ -0,0 +1,87 @@
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
import os
class LogHelper:
@staticmethod
def log_action(action, details=""):
"""Log user actions with timestamp, user, action, and details."""
logData = LogData()
logData.WriteLog(action, details="")
class LogData:
filepath = ""
timestamp = None
def __init__(self):
self.filepath = os.path.join(current_app.root_path, 'activity.log')
self.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if hasattr(current_user, "cn") and current_user.cn:
self.user = current_user.cn
elif hasattr(current_user, "username") and current_user.username:
self.user = current_user.username
elif hasattr(current_user, "sAMAccountName") and current_user.sAMAccountName:
self.user = current_user.sAMAccountName
else:
self.user = "Unknown"
def WriteLog(self, action, details=""):
"""Log user actions with timestamp, user, action, and details."""
with open(self.filepath, "a", encoding="utf-8") as f:
f.write(
f"Timestamp: {self.timestamp} | "
f"User: {self.user} | "
f"Action: {action} | "
f"Details: {details}\n"
)
def GetActivitiesLog(self):
logs = []
if os.path.exists(self.filepath):
with open(self.filepath, 'r') as f:
for line in f:
parts = line.strip().split(" | ")
if len(parts) == 4:
logs.append({
"timestamp": parts[0].replace("Timestamp:", "").strip(),
"user": parts[1].replace("User:", "").strip(),
"action": parts[2].replace("Action:", "").strip(),
"details": parts[3].replace("Details:", "").strip()
})
return logs
def GetFilteredActivitiesLog(self, startDate, endDate, userName):
filtered_logs = self.GetActivitiesLog()
# Date filter
if startDate or endDate:
try:
start_dt = datetime.strptime(startDate, "%Y-%m-%d") if startDate else datetime.min
end_dt = datetime.strptime(endDate, "%Y-%m-%d") if endDate else datetime.max
filtered_logs = [
log for log in filtered_logs
if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt
]
except Exception as e:
print("Date filter error:", e)
#Why catching all exceptions? Need to handle specific exceptions
# Username filter
if userName:
filtered_logs = [log for log in filtered_logs if userName.lower() in log["user"].lower()]
return filtered_logs

293
model/PmcReport.py Normal file
View File

@@ -0,0 +1,293 @@
import os
import openpyxl
from openpyxl.styles import Font, PatternFill
from decimal import Decimal
from datetime import datetime
import config
from flask_login import current_user
from model.Log import LogHelper
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()
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()
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()
# ---------------- CREDIT NOTE ----------------
cursor.callproc('GetCreditNoteByPMC', [pmc_no])
credit_note = []
for result in cursor.stored_results():
credit_note = result.fetchall()
# ---------------- 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()
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 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, f"PMC_Report_{pmc_no}.xlsx"
finally:
cursor.close()
connection.close()

116
model/Report.py Normal file
View File

@@ -0,0 +1,116 @@
import config
from datetime import datetime
class ReportHelper:
@staticmethod
def search_contractor(subcontractor_name, pmc_no, state, district, block, village, year_from, year_to):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
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
])
data = []
for result in cursor.stored_results():
data = result.fetchall()
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
cursor.callproc('GetContractorInfo', [contractor_id])
for result in cursor.stored_results():
contInfo = result.fetchone()
# Hold Types
cursor.callproc('GetContractorHoldTypes', [contractor_id])
for result in cursor.stored_results():
hold_types = result.fetchall()
# Invoices
cursor.callproc('GetContractorInvoices', [contractor_id])
for result in cursor.stored_results():
invoices = result.fetchall()
# GST Release
cursor.callproc('GetGSTRelease', [contractor_id])
for result in cursor.stored_results():
gst_rel = result.fetchall()
# Hold Release
cursor.callproc('GetHoldRelease', [contractor_id])
for result in cursor.stored_results():
hold_release = result.fetchall()
# Credit Note
cursor.callproc('GetCreditNote', [contractor_id])
for result in cursor.stored_results():
credit_note = result.fetchall()
# Payments
cursor.callproc('GetPayments', [contractor_id])
for result in cursor.stored_results():
payments = result.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')
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
}

0
model/ReportGenerator.py Normal file
View File

246
model/State.py Normal file
View File

@@ -0,0 +1,246 @@
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
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('state.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

131
model/Subcontractor.py Normal file
View File

@@ -0,0 +1,131 @@
# model/Subcontractor.py
import config
from model.Log import LogHelper
from mysql.connector import Error
class Subcontractor:
@staticmethod
def get_connection():
return config.get_db_connection()
@staticmethod
def get_all_subcontractors():
connection = Subcontractor.get_connection()
subcontractors = []
if not connection:
return None, "Database connection failed"
try:
cursor = connection.cursor()
cursor.callproc('GetAllSubcontractors')
for result in cursor.stored_results():
subcontractors = result.fetchall()
except Error as e:
print(f"Error fetching subcontractors: {e}")
return None, str(e)
finally:
cursor.close()
connection.close()
return subcontractors, None
@staticmethod
def get_subcontractor_by_id(id):
connection = Subcontractor.get_connection()
subcontractor = None
if not connection:
return None, "Database connection failed"
try:
cursor = connection.cursor()
cursor.callproc("GetSubcontractorById", (id,))
for result in cursor.stored_results():
subcontractor = result.fetchone()
except Error as e:
print(f"Error fetching subcontractor: {e}")
return None, str(e)
finally:
cursor.close()
connection.close()
return subcontractor, None
@staticmethod
def save_subcontractor(data):
connection = Subcontractor.get_connection()
if not connection:
return "Database connection failed"
try:
cursor = connection.cursor()
cursor.callproc('SaveContractor', (
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()
# Active log
LogHelper.log_action("Add Subcontractor", f"Added subcontractor '{data['Contractor_Name']}'")
except Error as e:
print(f"Error inserting subcontractor: {e}")
return str(e)
finally:
cursor.close()
connection.close()
return None
@staticmethod
def update_subcontractor(id, data):
connection = Subcontractor.get_connection()
if not connection:
return "Database connection failed"
try:
cursor = connection.cursor()
cursor.callproc('UpdateSubcontractor', (
id,
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()
# Active log
LogHelper.log_action("Edit Subcontractor", f"Edited subcontractor '{id}'")
except Error as e:
print(f"Error updating subcontractor: {e}")
return str(e)
finally:
cursor.close()
connection.close()
return None
@staticmethod
def delete_subcontractor(id):
connection = Subcontractor.get_connection()
if not connection:
return "Database connection failed", 0
affected_rows = 0
try:
cursor = connection.cursor()
cursor.callproc('DeleteSubcontractor', (id,))
connection.commit()
for result in cursor.stored_results():
row = result.fetchone()
affected_rows = row[0] if row else 0
# Active log
LogHelper.log_action("Delete Subcontractor", f"Deleted subcontractor '{id}'")
except Error as e:
print(f"Error deleting subcontractor: {e}")
return str(e), 0
finally:
cursor.close()
connection.close()
return None, affected_rows

64
model/Utilities.py Normal file
View File

@@ -0,0 +1,64 @@
from flask import flash, jsonify, json
from enum import Enum
class ItemCRUDType(Enum):
Village = 1
Block = 2
District = 3
State = 4
HoldType = 5
class RegEx:
patternAlphabetOnly = "^[A-Za-z ]+$"
class ResponseHandler:
@staticmethod
def invalid_name(entity):
return {'status': 'error', 'message': f'Invalid {entity} name. Only letters are allowed!'}
@staticmethod
def already_exists(entity):
return {'status': 'exists', 'message': f'{entity.capitalize()} already exists!'}
@staticmethod
def add_success(entity):
return {'status': 'success', 'message': f'{entity.capitalize()} added successfully!'}
@staticmethod
def add_failure(entity):
return {'status': 'error', 'message': f'Failed to add {entity}.'}
@staticmethod
def is_available(entity):
return {'status': 'available', 'message': f'{entity.capitalize()} name is available!'}
@staticmethod
def delete_success(entity):
return {'status': 'success', 'message': f'{entity.capitalize()} deleted successfully!'}
@staticmethod
def delete_failure(entity):
return {'status': 'error', 'message': f'Failed to delete {entity}.'}
@staticmethod
def update_success(entity):
return {'status': 'success', 'message': f'{entity.capitalize()} updated successfully!'}
@staticmethod
def update_failure(entity):
return {'status': 'error', 'message': f'Failed to update {entity}.'}
@staticmethod
def fetch_failure(entity):
return {'status': 'error', 'message': f'Failed to fetch {entity}.'}
class HtmlHelper:
# Helper: JSON Response Formatter
@staticmethod
def json_response(message_obj, status_code):
return jsonify(message_obj), status_code
#May need to refactor further

173
model/Village.py Normal file
View File

@@ -0,0 +1,173 @@
# return blocks
from model.Utilities import ResponseHandler, HtmlHelper, ItemCRUDType
import config
import mysql.connector
from model.ItemCRUD import ItemCRUD
class Village:
isSuccess = False
resultMessage = ""
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
self.village = ItemCRUD(itemType=ItemCRUDType.Village)
# 🔹 Helper: sync status
def _set_status(self, village):
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
# 🔹 Helper: get request data
def _get_form_data(self, request):
block_id = request.form.get('block_Id')
village_name = request.form.get('Village_Name', '').strip()
return block_id, village_name
def AddVillage(self, request):
block_id, village_name = self._get_form_data(request)
if not village_name:
self.isSuccess = False
self.resultMessage = "Village name cannot be empty"
return
try:
self.village.AddItem(
request=request,
parentid=block_id,
childname=village_name,
storedprocfetch="GetVillageByNameAndBlock",
storedprocadd="SaveVillage"
)
self._set_status(self.village)
except Exception as e:
self.isSuccess = False
self.resultMessage = str(e)
def GetAllVillages(self, request):
try:
villagesdata = self.village.GetAllData(
request=request,
storedproc="GetAllVillages"
)
self._set_status(self.village)
return villagesdata
except Exception as e:
self.isSuccess = False
self.resultMessage = str(e)
return []
def CheckVillage(self, request):
block_id, village_name = self._get_form_data(request)
if not village_name:
self.isSuccess = False
self.resultMessage = "Village name cannot be empty"
return None
try:
result = self.village.CheckItem(
request=request,
parentid=block_id,
childname=village_name,
storedprocfetch="GetVillageByNameAndBlocks"
)
self._set_status(self.village)
return result
except Exception as e:
self.isSuccess = False
self.resultMessage = str(e)
return None
def DeleteVillage(self, request, village_id):
try:
self.village.DeleteItem(
request=request,
itemID=village_id,
storedprocDelete="DeleteVillage"
)
self._set_status(self.village)
except Exception as e:
self.isSuccess = False
self.resultMessage = str(e)
def EditVillage(self, request, village_id):
block_id, village_name = self._get_form_data(request)
if not village_name:
self.isSuccess = False
self.resultMessage = "Village name cannot be empty"
return
try:
self.village.EditItem(
request=request,
childid=village_id,
parentid=block_id,
childname=village_name,
storedprocupdate="UpdateVillage"
)
self._set_status(self.village)
except Exception as e:
self.isSuccess = False
self.resultMessage = str(e)
def GetVillageByID(self, id):
try:
villagedetailsdata = self.village.GetDataByID(
id=id,
storedproc="GetVillageDetailsById"
)
if villagedetailsdata:
self.isSuccess = True
else:
self.isSuccess = False
self.resultMessage = "Village not found"
return villagedetailsdata
except Exception as e:
self.isSuccess = False
self.resultMessage = str(e)
return None
def GetAllBlocks(self):
blocks = []
self.isSuccess = False
self.resultMessage = ""
connection = config.get_db_connection()
if not connection:
return []
try:
with connection.cursor() as cursor:
cursor.callproc('GetAllBlocks')
for result in cursor.stored_results():
blocks.extend(result.fetchall())
self.isSuccess = True
return blocks
except mysql.connector.Error as e:
print(f"Error fetching blocks: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.fetch_failure("block"), 500
)
return []
finally:
connection.close()

150
model/gst_release.py Normal file
View File

@@ -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()

158
model/payment.py Normal file
View File

@@ -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()

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
Flask
mysql-connector-python
openpyxl
pandas

89
static/css/base.css Normal file
View File

@@ -0,0 +1,89 @@
body {
margin: 0;
font-family: Arial, sans-serif;
background-color: #f5f5f5;
display: flex;
}
/* Sidebar */
.sidebar {
width: 250px;
height: 100vh;
background-color: #ffffff;
position: fixed;
left: -250px;
transition: left 0.3s ease;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
padding-top: 20px;
}
.sidebar.open {
left: 0;
}
/* Sidebar Navigation */
.nav-menu {
list-style: none;
padding: 0;
margin: 0;
}
.nav-item {
width: 100%;
}
.nav-link {
display: flex;
align-items: center;
text-decoration: none;
color: #333;
padding: 15px 20px;
font-size: 16px;
transition: background-color 0.3s, color 0.3s;
}
.nav-link i {
margin-right: 10px;
font-size: 18px;
}
.nav-link:hover, .nav-link.active {
background-color: #e6f7ff;
color: #007bff;
}
/* Menu Button */
.menu-icon {
position: absolute;
top: 15px;
left: 15px;
font-size: 24px;
cursor: pointer;
background: none;
border: none;
}
/* Content Area */
.content {
margin-left: 0;
padding: 20px;
width: 100%;
transition: margin-left 0.3s ease;
}
.content.shift {
margin-left: 250px;
}
@media (max-width: 768px) {
.sidebar {
width: 200px;
left: -200px;
}
.sidebar.open {
left: 0;
}
.content.shift {
margin-left: 200px;
}
}

226
static/css/index.css Normal file
View File

@@ -0,0 +1,226 @@
body {
margin: 0;
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
background-color: #f5f5f5;
}
/* Sidebar */
.sidebar {
width: 250px;
height: 100%;
background-color: #ffffff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
position: fixed;
}
.logo {
width: 80px;
margin-bottom: 20px;
}
.nav-menu {
width: 100%;
list-style: none;
padding: 0;
margin: 0;
}
.nav-item {
width: 100%;
}
.nav-link {
display: flex;
align-items: center;
text-decoration: none;
color: #333;
padding: 15px 20px;
font-size: 16px;
transition: background-color 0.3s, color 0.3s;
}
.nav-link:hover,
.nav-link.active {
background-color: #e6f7ff;
color: #007bff;
}
.nav-link i {
margin-right: 10px;
font-size: 18px;
}
.user-section {
margin-top: auto;
width: 100%;
padding: 20px;
display: flex;
align-items: center;
border-top: 1px solid #eee;
}
.user-section img {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 10px;
}
.user-info {
display: flex;
flex-direction: column;
}
.user-info span {
font-size: 14px;
color: #333;
}
/* Main content area */
.content {
margin-left: 250px;
padding: 20px;
width: calc(100% - 250px);
}
/* Menu Cards */
.menu {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.card {
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 20px;
text-align: center;
flex: 1 1 calc(30% - 20px);
max-width: calc(30% - 20px);
}
.card h2 {
margin: 0 0 10px;
font-size: 20px;
}
.btn {
display: inline-block;
padding: 10px 20px;
color: #fff;
background-color: #007bff;
border-radius: 4px;
text-decoration: none;
font-size: 14px;
transition: background-color 0.3s;
}
.btn:hover {
background-color: #0056b3;
}
/* Company Info */
.company-info {
text-align: center;
padding: 20px;
background-color: Whitesmoke;
color: blue;
margin-left: 250px;
}
.company-name {
font-size: 30px;
font-weight: bold;
margin: 0;
}
.app-name {
font-weight: bold;
app-name-shadow:0 2px 4px rgba(0, 0, 0, 0.1);
font-size: 24px;
margin: 5px 0 0;
}
/* Responsive Design */
@media screen and (max-width: 768px) {
.sidebar {
width: 100%;
height: auto;
position: static;
}
.content {
margin-left: 0;
width: 100%;
}
.menu {
flex-direction: column;
align-items: center;
}
.card {
flex: 1 1 100%;
max-width: 100%;
min-width: 50%;
}
}
@media screen and (max-width: 480px) {
.nav-link {
font-size: 14px;
padding: 10px 15px;
}
.btn {
font-size: 12px;
padding: 8px 15px;
}
}
@media screen and (max-width: 768px) {
.sidebar {
width: 100%;
height: auto;
position: static;
}
.content {
margin-left: 0;
width: 100%;
}
.menu {
flex-direction: column;
align-items: center;
}
.card {
flex: 1 1 100%;
max-width: 100%;
min-width: 50%;
}
.company-info {
margin-left: 0;
}
}
@media screen and (max-width: 480px) {
.nav-link {
font-size: 14px;
padding: 10px 15px;
}
.btn {
font-size: 12px;
padding: 8px 15px;
}
.user-section {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.user-section img {
margin-right: 0;
}
.card {
min-width: 90%;
}
}

395
static/css/invoice.css Normal file
View File

@@ -0,0 +1,395 @@
/* General Styles */
h2 {
text-align: center;
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
/* Form Styling */
form {
width:50%;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 15px;
}
/* Responsive Form Layout */
.row1,
.row2,
.row3 {
display: grid;
gap: 15px;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
label {
font-weight: bold;
display: block;
margin-bottom: 6px;
}
input,
textarea,
select {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 16px;
box-sizing: border-box;
}
/* Button Styling */
.button {
padding: 12px;
background-color: #007bff;
color: white;
border: none;
border-radius: 6px;
font-size: 18px;
cursor: pointer;
transition: background-color 0.3s ease-in-out;
}
.button:hover {
background-color: #0056b3;
}
/* Dynamic Hold Amount Fields */
.hold-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.hold-amount-field {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
flex-wrap: wrap;
}
.hold-amount-field select,
.hold-amount-field input {
flex: 1;
min-width: 100px;
}
.hold-amount-field button {
background-color: red;
color: white;
font-size: 14px;
padding: 8px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.hold-amount-field button:hover {
background-color: #c0392b;
}
/* Success Alert Box */
.success-alert {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background-color: #4CAF50;
color: #fff;
padding: 15px 25px;
border-radius: 5px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
opacity: 0;
visibility: hidden;
transition: opacity 0.5s ease, visibility 0.5s ease;
z-index: 1000;
}
.success-alert.show {
opacity: 1;
visibility: visible;
}
/* Table Styles */
.invoice-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
overflow-x: auto;
}
.invoice-table th,
.invoice-table td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
font-size: 14px;
word-break: break-word;
}
.invoice-table th {
background-color: #007bff;
color: #fff;
}
.invoice-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.invoice-table tr:hover {
background-color: #f1f1f1;
}
/* icon */
.icon {
width: 20px;
height: 20px;
cursor: pointer;
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
}
.icon:hover {
transform: scale(1.5);
opacity: 0.8;
}
.edit-icon:hover {
filter: drop-shadow(0 0 5px #007bff); /* Blue glow for edit */
}
.delete-icon:hover {
filter: drop-shadow(0 0 5px #ff0000); /* Red glow for delete */
}
/* Center the buttons and apply consistent styling */
.button-container {
display: flex;
justify-content: center; /* Center buttons horizontally */
gap: 10px; /* Space between buttons */
margin-top: 20px; /* Add some top margin */
}
.action-button {
background-color: #007BFF; /* Blue background */
color: white; /* White text */
padding: 15px 30px; /* Larger padding for bigger buttons */
border: none; /* Remove border */
border-radius: 5px; /* Rounded corners */
cursor: pointer; /* Pointer cursor on hover */
font-size: 18px; /* Larger font size */
text-align: center; /* Center text */
text-decoration: none; /* Remove underline */
transition: background-color 0.3s ease; /* Smooth hover transition */
}
.action-button:hover {
background-color: #0056b3; /* Darker blue on hover */
}
#addStateForm, #stateTable {
display: none; /* Initially hide both the form and the table */
}
/* Success Popup */
.success-popup {
display: none;
color: green;
font-size: 1.2em;
margin-top: 10px;
}
/* Sorting buttons */
.sortable .sort-buttons {
margin-left: 5px;
}
.sortable {
cursor: pointer;
user-select: none;
}
.sort-buttons a {
text-decoration: none;
color: black;
font-weight: bold;
margin-left: 5px;
margin-right: 5px;
}
.sort-buttons a:hover {
text-decoration: underline;
}
.back-button {
display: inline-block;
margin-top: 20px;
padding: 8px 15px;
background-color: #28a745;
color: white;
border-radius: 5px;
text-decoration: none;
font-size: 16px;
}
.back-button:hover {
background-color: #218838;
}
span .sort-desc:hover{
cursor: pointer;
}
span .sort-asc:hover{
cursor: pointer;
}
/* Responsive Design */
@media (max-width: 1024px) {
form {
padding: 15px;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.invoice-table th,
.invoice-table td {
font-size: 12px;
padding: 8px;
}
}
@media (max-width: 768px) {
.row1,
.row2,
.row3 {
grid-template-columns: 1fr;
}
.hold-amount-field {
flex-direction: column;
gap: 5px;
align-items: flex-start;
}
.hold-amount-field select,
.hold-amount-field input {
width: 100%;
}
.invoice-table {
display: block;
overflow-x: auto;
white-space: nowrap;
}
}
@media (max-width: 480px) {
body {
max-width: 100%;
padding: 10px;
}
form {
padding: 10px;
}
.invoice-table th,
.invoice-table td {
font-size: 12px;
padding: 5px;
}
button {
font-size: 16px;
padding: 10px;
}
}
/* Additional Media Queries */
/* For tablets and medium devices */
@media (max-width: 992px) {
form {
width: 90%;
}
.button-container {
flex-direction: column;
align-items: center;
}
.action-button {
width: 100%;
max-width: 300px;
}
}
/* For smaller tablets and large phones */
@media (max-width: 600px) {
.button-container {
flex-direction: column;
align-items: stretch;
}
.action-button {
width: 100%;
}
.icon {
width: 16px;
height: 16px;
}
.success-alert {
width: 90%;
font-size: 14px;
padding: 10px 15px;
}
}
/* For very small phones */
@media (max-width: 360px) {
h2 {
font-size: 20px;
}
.back-button {
width: 100%;
text-align: center;
font-size: 14px;
padding: 8px 10px;
}
.sort-buttons a {
font-size: 12px;
}
.invoice-table th,
.invoice-table td {
font-size: 10px;
padding: 4px;
}
}

313
static/css/invoice1.css Normal file
View File

@@ -0,0 +1,313 @@
/* General Styling */
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f9f9f9;
color: #333;
}
/* Header Styling */
h1, h2, h3 {
color: #0056b3;
text-align: center;
}
/* Table Styling */
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table th, table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
table th {
background-color: #007bff;
color: white;
font-weight: bold;
cursor: pointer;
position: relative;
}
table tr:nth-child(even) {
background-color: #f2f2f2;
}
table tr:hover {
background-color: #ddd;
}
/* Sort Dropdown */
.sort-options {
display: none;
position: absolute;
background: white;
border: 1px solid black;
padding: 5px;
z-index: 100;
width: 120px;
text-align: center;
}
.sort-options button {
display: block;
width: 100%;
border: none;
background: none;
padding: 5px;
cursor: pointer;
}
.sort-options button:hover {
background-color: #f0f0f0;
}
/* Form Styling */
form {
max-width: 700px;
margin: 0 auto;
padding: 20px;
background: #fff;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
form label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
form input[type="text"],
form input[type="number"],
form input[type="date"],
form input[type="email"],
form textarea,
select {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
}
form textarea {
resize: vertical;
}
/* Button Styling */
.button, form button {
width: 100%;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
text-align: center;
}
.button:hover, form button:hover {
background-color: #0056b3;
}
/* Back Button */
.back-button {
background-color: red;
color: white;
padding: 10px 20px;
text-decoration: none;
font-weight: bold;
border-radius: 5px;
display: inline-block;
margin-top: 20px;
text-align: center;
}
.back-button:hover {
background-color: darkred;
}
/* Success Alert */
.success-alert {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #d4edda;
color: #155724;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
text-align: center;
font-size: 18px;
font-weight: bold;
}
.success-popup i {
color: green;
font-size: 30px;
margin-right: 10px;
}
/* Error Message */
.error-message {
color: red;
font-size: 14px;
margin-top: 5px;
}
/* Table Icons */
td img {
width: 20px; /* Adjust as needed */
height: 20px; /* Adjust as needed */
transition: transform 0.3s ease; /* Smooth transition for hover effect */
}
td img:hover {
transform: scale(1.2); /* Slight zoom effect on hover */
}
/* Select Dropdown */
select {
width: 100%;
padding: 12px 15px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 14px;
background-color: #fff;
box-sizing: border-box;
appearance: none;
}
/* Custom Dropdown Arrow */
select::-ms-expand {
display: none;
}
select:focus {
outline: none;
border-color: #4CAF50;
}
select option {
padding: 12px 15px;
font-size: 14px;
}
option[disabled] {
color: #aaa;
font-style: italic;
}
/* Save Button */
.save-button {
background-color: green;
color: white;
padding: 15px;
text-decoration: none;
font-weight: bold;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
margin: 20px auto;
text-align: center;
}
.save-button:hover {
background-color: darkgreen;
}
/* Responsive Design */
@media (max-width: 768px) {
form {
max-width: 100%;
padding: 15px;
}
table th, table td {
padding: 6px;
font-size: 14px;
}
.button, form button {
font-size: 14px;
padding: 8px;
}
}
/* Additional Media Queries */
/* For tablets and medium-sized screens */
@media (max-width: 992px) {
form {
padding: 18px;
}
table th, table td {
font-size: 14px;
padding: 6px;
}
.save-button, .back-button, .button, form button {
font-size: 15px;
}
}
/* For smaller tablets and large phones */
@media (max-width: 600px) {
body {
margin: 10px;
}
h1, h2, h3 {
font-size: 20px;
}
table th, table td {
font-size: 13px;
padding: 5px;
}
.success-alert {
width: 90%;
font-size: 16px;
padding: 15px;
}
.button, form button, .save-button, .back-button {
padding: 10px;
font-size: 14px;
}
}
/* For very small phones */
@media (max-width: 360px) {
h1, h2, h3 {
font-size: 18px;
}
.save-button, .back-button {
width: 100%;
font-size: 13px;
padding: 10px;
}
table th, table td {
font-size: 12px;
padding: 4px;
}
form input, form select, form textarea {
font-size: 14px;
}
}

181
static/css/report.css Normal file
View File

@@ -0,0 +1,181 @@
h2 {
text-align: center;
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
/* Form Styling */
.info {
width: 60%;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 15px;
}
/* Responsive Form Layout */
.row1,
.row2,
.row3 {
display: grid;
gap: 15px;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
label {
font-weight: bold;
display: block;
margin-bottom: 6px;
}
input,
textarea,
select {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 16px;
box-sizing: border-box;
}
/* Table styling */
.table-wrapper {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table th, table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
table th {
background-color: #007bff;
color: white;
font-weight: bold;
}
table tr:nth-child(even) {
background-color: #f2f2f2;
}
table tr:hover {
background-color: #ddd;
}
.download-btn {
display: inline-block;
padding: 10px 15px;
background-color: #28a745;
color: white;
text-decoration: none;
border-radius: 5px;
margin-top: 15px;
}
.total-table {
width: 35%;
}
/* Responsive Design */
@media (max-width: 1024px) {
.info {
padding: 15px;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.invoice-table th,
.invoice-table td {
font-size: 12px;
padding: 8px;
}
}
@media (max-width: 768px) {
.row1,
.row2,
.row3 {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
body {
max-width: 100%;
padding: 10px;
}
.info {
padding: 10px;
}
table th,
table td {
font-size: 10px;
padding: 6px;
}
}
/* Ensure table responsiveness */
@media (max-width: 600px) {
.table-wrapper {
overflow-x: auto;
}
table {
min-width: 600px;
}
}
/* Extra Small Devices (max-width: 360px) */
@media (max-width: 360px) {
h2 {
font-size: 20px;
margin-bottom: 15px;
}
.info {
width: 100%;
padding: 8px;
}
input,
textarea,
select {
font-size: 14px;
padding: 8px;
}
.download-btn {
font-size: 14px;
padding: 8px 12px;
}
table th,
table td {
font-size: 9px;
padding: 5px;
}
.total-table {
width: 100%;
}
}

212
static/css/show_excel.css Normal file
View File

@@ -0,0 +1,212 @@
/* General Styles */
body {
font-family: Arial, sans-serif;
margin: 20px;
padding: 20px;
background-color: #f4f4f9;
color: #333;
}
/* Headings */
h1, h2 {
color: #2c3e50;
}
/* File Information Section */
ul {
list-style-type: none;
padding: 0;
}
ul li {
background: #ecf0f1;
margin: 5px 0;
padding: 10px;
border-radius: 5px;
}
/* Error Messages */
.errors {
background-color: #ffdddd;
border: 1px solid #ff4d4d;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
}
.errors h2 {
color: #ff4d4d;
}
.error {
color: #d8000c;
font-weight: bold;
}
/* Table Styles */
.table-container {
max-width: 100%;
overflow-x: auto; /* Allows horizontal scrolling */
margin-bottom: 20px;
border: 1px solid #ddd; /* Optional for better visibility */
}
table {
width: 100%;
border-collapse: collapse;
background-color: white;
table-layout: auto; /* Let the columns adjust based on content */
}
th, td {
padding: 10px;
border: 1px solid #ddd;
text-align: left;
word-wrap: break-word; /* Prevents text from overflowing */
}
th {
background-color: #3498db;
color: white;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
/* Set input width to 100px */
input[type="text"] {
width: 200px; /* Set a fixed width of 100px */
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box; /* Ensures padding is included in width calculation */
white-space: nowrap; /* Prevent line breaks */
overflow: hidden; /* Prevents overflow */
text-overflow: ellipsis; /* Shows ellipsis if the content is too long */
}
/* Buttons */
button {
display: inline-block;
background-color: #27ae60;
color: white;
padding: 10px 15px;
text-decoration: none;
border: none;
cursor: pointer;
border-radius: 5px;
transition: background 0.3s ease;
font-size: 16px;
width: 100%; /* Ensures button is centered */
}
.back-button {
display: inline-block;
background-color: #27ae60;
color: white;
padding: 10px 15px;
text-decoration: none;
border: none;
cursor: pointer;
border-radius: 5px;
transition: background 0.3s ease;
font-size: 16px;
width: 10%; /* Ensures button is centered */
}
/* Hover Effects */
button:hover, .back-button:hover {
background-color: #219150;
}
button:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
/* Back Button */
.back-button {
background-color: #e74c3c;
}
.back-button:hover {
background-color: #c0392b;
}
/* Center Save Data Button */
form {
display: flex;
flex-direction: column;
align-items: center;
}
.save-button {
margin-top: 20px;
width: 50%;
}
/* Responsive Design */
@media (max-width: 768px) {
.table-container {
display: block;
overflow-x: auto;
white-space: nowrap;
}
table {
width: 100%;
table-layout: auto; /* Let the table adjust to fit its content */
}
input[type="text"] {
width: 100px; /* Fixed width of 100px */
padding: 6px; /* Smaller padding on mobile */
}
button, .back-button {
width: 100%;
text-align: center;
}
}
@media (max-width: 480px) {
body {
margin: 10px;
padding: 10px;
}
h1, h2 {
font-size: 20px;
text-align: center;
}
input[type="text"] {
width: 80px;
font-size: 14px;
}
th, td {
font-size: 12px;
padding: 6px;
}
.save-button {
width: 100%;
}
.back-button {
width: 100%;
font-size: 14px;
}
button {
font-size: 14px;
padding: 8px 12px;
}
.errors {
font-size: 14px;
padding: 10px;
}
}

448
static/css/style.css Normal file
View File

@@ -0,0 +1,448 @@
/* General styling */
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f9f9f9;
color: #333;
}
/* Header styling */
h1 {
color: back;
text-align: center;
}
h2 {
color: #0056b3;
text-align: center;
}
h3 {
color: #0056b3;
}
/* Table styling */
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table th, table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
table th {
background-color: #007bff;
color: white;
font-weight: bold;
}
table tr:nth-child(even) {
background-color: #f2f2f2;
}
table tr:hover {
background-color: #ddd;
}
/* Form styling */
form {
max-width: 600px;
margin: 0 auto;
padding: 20px;
background: #fff;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
form label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
form input[type="text"], form input[type="number"], form input[type="date"],form input[type="email"], form textarea {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
}
form textarea {
resize: vertical;
}
form button {
width: 100%;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
form button:hover {
background-color: #0056b3;
}
/* Style the <h1> elements to display inline */
h1 {
display: inline-block;
margin-right: 10px; /* Spacing between buttons */
}
/* Style the <a> tags to look like buttons */
.button {
text-decoration: none; /* Remove underline */
padding: 10px 20px; /* Add padding for size */
background-color: #4CAF50; /* Green background color */
color: white; /* White text */
font-size: 16px; /* Font size */
border-radius: 5px; /* Rounded corners */
border: none; /* Remove default border */
cursor: pointer; /* Cursor style */
transition: background-color 0.3s ease; /* Smooth transition for hover */
}
/* Change background color on hover */
.button:hover {
background-color: #45a049;
}
/* Style for the Back Button */
.back-button {
background-color: red;
color: white;
padding: 10px 20px;
text-decoration: none;
font-weight: bold;
border-radius: 5px;
display: inline-block;
margin-top: 20px;
text-align: center;
}
.back-button:hover {
background-color: darkred;
}
/* Styling for the select dropdown */
select {
width: 100%; /* Use full width for better responsiveness */
padding: 12px 15px; /* Increased padding for better spacing */
margin: 10px 0; /* Spacing above and below */
border: 1px solid #ccc; /* Border color */
border-radius: 5px; /* Rounded corners */
font-size: 14px; /* Font size for text */
background-color: #fff; /* White background */
box-sizing: border-box; /* Ensures padding and border are included in width */
appearance: none; /* Remove default dropdown arrow for custom styling */
}
/* Custom dropdown arrow */
select::-ms-expand {
display: none; /* Remove the default arrow for IE/Edge */
}
select:focus {
outline: none; /* Remove outline when focused */
border-color: #4CAF50; /* Change border color when focused */
}
/* Option styling for the select */
select option {
padding: 12px 15px; /* Padding for each option */
font-size: 14px; /* Font size */
}
/* Style the placeholder (default) option */
option[disabled] {
color: #aaa; /* Light grey color for disabled option */
font-style: italic; /* Italicize the disabled option */
}
/* Label styling */
label {
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
display: block;
}
.save-button {
background-color: Green;
color: white;
padding: 15px;
text-decoration: none;
font-weight: bold;
border-radius: 5px;
display:flex;
justify-content: center;
align-item: center;
margin: 20px;
text-align: center;
}
.save-button:hover {
background-color: darkGreen;
}
.success-popup {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #d4edda;
color: #155724;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
text-align: center;
font-size: 18px;
font-weight: bold;
}
.success-popup i {
color: green;
font-size: 30px;
margin-right: 10px;
}
.error-message {
color: red;
font-size: 14px;
margin-top: 5px;
}
.icon {
width: 20px;
height: 20px;
cursor: pointer;
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
}
.icon:hover {
transform: scale(1.5);
opacity: 0.8;
}
.edit-icon:hover {
filter: drop-shadow(0 0 5px #007bff); /* Blue glow for edit */
}
.delete-icon:hover {
filter: drop-shadow(0 0 5px #ff0000); /* Red glow for delete */
}
/* Search Bar Container */
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
/* Search Bar Input */
#searchBar {
padding: 8px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 5px;
width: 200px;
transition: 0.3s;
}
/* Search Bar Focus Effect */
#searchBar:focus {
border-color: #007bff;
outline: none;
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
}
/* Center the buttons and apply consistent styling */
.button-container {
display: flex;
justify-content: center; /* Center buttons horizontally */
gap: 10px; /* Space between buttons */
margin-top: 20px; /* Add some top margin */
}
.action-button {
background-color: #007BFF; /* Blue background */
color: white; /* White text */
padding: 15px 30px; /* Larger padding for bigger buttons */
border: none; /* Remove border */
border-radius: 5px; /* Rounded corners */
cursor: pointer; /* Pointer cursor on hover */
font-size: 18px; /* Larger font size */
text-align: center; /* Center text */
text-decoration: none; /* Remove underline */
transition: background-color 0.3s ease; /* Smooth hover transition */
}
.action-button:hover {
background-color: #0056b3; /* Darker blue on hover */
}
#addStateForm, #stateTable {
display: none; /* Initially hide both the form and the table */
}
/* Success Popup */
.success-popup {
display: none;
color: green;
font-size: 1.2em;
margin-top: 10px;
}
/* Sorting buttons */
.sortable .sort-buttons {
margin-left: 5px;
}
.sortable {
cursor: pointer;
user-select: none;
}
.sort-buttons a {
text-decoration: none;
color: black;
font-weight: bold;
margin-left: 5px;
margin-right: 5px;
}
.sort-buttons a:hover {
text-decoration: underline;
}
.back-button {
display: inline-block;
margin-top: 20px;
padding: 8px 15px;
background-color: #28a745;
color: white;
border-radius: 5px;
text-decoration: none;
font-size: 16px;
}
.back-button:hover {
background-color: #218838;
}
span .sort-desc:hover{
cursor: pointer;
}
span .sort-asc:hover{
cursor: pointer;
}
.sortable select {
background-color: #007BFF; /* Blue background */
color: white; /* White text */
border: none;
border-radius: 4px;
padding: 5px 10px;
font-size: 14px;
cursor: pointer;
appearance: none; /* Remove default browser styling */
background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-caret-down-fill' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658c-.566-.63-.106-1.658.753-1.658h9.592c.86 0 1.32 1.027.753 1.658L8.753 11.14a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 12px;
}
.sortable select:focus {
outline: none;
background-color: #0056b3; /* Darker blue on focus */
}
.download-btn {
display: inline-block;
padding: 10px 15px;
background-color: #28a745;
color: white;
text-decoration: none;
border-radius: 5px;
margin-top: 15px;
}
@media (max-width: 480px) {
body {
margin: 10px;
padding: 10px;
}
h1, h2, h3 {
font-size: 18px;
text-align: center;
}
table th, table td {
font-size: 12px;
padding: 6px;
}
form {
padding: 10px;
max-width: 100%;
}
form input[type="text"],
form input[type="number"],
form input[type="date"],
form input[type="email"],
form textarea,
select {
padding: 8px;
font-size: 14px;
}
form button,
.button,
.back-button,
.save-button,
.action-button {
font-size: 14px;
padding: 10px;
width: 100%;
}
.button-container {
flex-direction: column;
gap: 10px;
}
#searchBar {
width: 100%;
font-size: 14px;
}
.sortable select {
font-size: 14px;
padding: 8px 10px;
background-position: right 8px center;
}
}

View File

@@ -0,0 +1,199 @@
/* Global box-sizing for predictable sizing */
*, *::before, *::after {
box-sizing: border-box;
}
h2 {
text-align: center;
/* Fluid font size between 20px and 24px */
font-size: clamp(20px, 3vw, 24px);
color: #333;
margin-bottom: 20px;
}
/* Form Styling */
.info {
width: 60%;
max-width: 900px; /* optional max-width */
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 15px;
margin: 0 auto; /* center horizontally */
}
/* Responsive Form Layout */
.row1,
.row2,
.row3 {
display: grid;
gap: 15px;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
label {
font-weight: bold;
display: block;
margin-bottom: 6px;
}
input,
textarea,
select {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 6px;
/* Fluid font size between 14px and 16px */
font-size: clamp(14px, 1.5vw, 16px);
}
/* Table styling */
.table-wrapper {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table th,
table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
/* Fluid font size between 10px and 14px */
font-size: clamp(10px, 1vw, 14px);
}
table th {
background-color: #007bff;
color: white;
font-weight: bold;
}
table tr:nth-child(even) {
background-color: #f2f2f2;
}
table tr:hover {
background-color: #ddd;
}
.download-btn {
display: inline-block;
padding: 10px 15px;
background-color: #28a745;
color: white;
text-decoration: none;
border-radius: 5px;
margin-top: 15px;
/* Fluid font size between 12px and 16px */
font-size: clamp(12px, 1.5vw, 16px);
}
.total-table {
width: 35%;
}
/* Responsive Design */
@media (max-width: 1024px) {
.info {
padding: 15px;
width: 80%;
}
.row2,
.row3 {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.invoice-table th,
.invoice-table td {
font-size: 12px;
padding: 8px;
}
}
@media (max-width: 768px) {
.info {
width: 90%;
}
.row1,
.row2,
.row3 {
grid-template-columns: 1fr;
}
}
@media (max-width: 600px) {
.table-wrapper {
overflow-x: auto;
}
table {
min-width: 600px;
}
}
@media (max-width: 480px) {
body {
max-width: 100%;
padding: 10px;
}
.info {
padding: 10px;
width: 100%;
}
table th,
table td {
font-size: 10px;
padding: 6px;
}
}
@media (max-width: 360px) {
h2 {
font-size: 20px;
margin-bottom: 15px;
}
.info {
width: 100%;
padding: 8px;
gap: 10px;
}
input,
textarea,
select {
font-size: 14px;
padding: 8px;
}
table th,
table td {
font-size: 8px;
padding: 4px;
}
.total-table {
width: 100%;
}
.download-btn {
padding: 8px 12px;
font-size: 14px;
}
}

View File

@@ -0,0 +1,111 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f9f9f9;
}
.container {
max-width: 500px;
width: 90%;
background: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
text-align: center;
}
h1 {
margin-bottom: 20px;
color: #333;
}
form {
margin-bottom: 20px;
}
input[type="file"] {
display: block;
margin: 0 auto 15px auto;
font-size: 18px;
padding: 10px;
cursor: pointer;
border: 2px solid #007bff;
border-radius: 5px;
background-color: #f4f4f4;
}
input[type="file"]:hover {
border-color: #0056b3;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 15px 25px;
font-size: 18px;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
/* Style for the Back Button */
.back-button {
background-color: red;
color: white;
padding: 10px 20px;
text-decoration: none;
font-weight: bold;
border-radius: 5px;
display: inline-block;
margin-top: 20px;
text-align: center;
}
.back-button:hover {
background-color: darkred;
}
@media screen and (max-width: 600px) {
.container {
padding: 15px;
}
h1 {
font-size: 20px;
}
input[type="file"] {
font-size: 16px;
padding: 8px;
}
button, .back-button {
font-size: 14px;
padding: 10px 20px;
}
}
@media screen and (max-width: 360px) {
.container {
width: 95%;
padding: 10px;
}
h1 {
font-size: 18px;
margin-bottom: 15px;
}
input[type="file"] {
font-size: 14px;
padding: 6px;
}
button, .back-button {
font-size: 12px;
padding: 8px 16px;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

116
static/js/block.js Normal file
View File

@@ -0,0 +1,116 @@
window.onload = function () {
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();
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) {
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);
}
}
});
});
$("#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('<option value="" disabled selected>Select District</option>');
data.forEach(function (district) {
districtDropdown.append(
'<option value="' + district.id + '">' + district.name + '</option>'
);
});
districtDropdown.prop('disabled', false);
},
error: function () {
alert('Error fetching districts. Please try again.');
}
});
} else {
$('#district_Id').prop('disabled', true);
}
});
});

62
static/js/district.js Normal file
View File

@@ -0,0 +1,62 @@
window.onload = function () {
document.getElementById('district_Name').focus();
};
$(document).ready(function () {
$("#district_Name").on("input", function () {
let districtName = $(this).val();
// Remove numbers and special characters automatically
let cleanedName = districtName.replace(/[^A-Za-z ]/g, "");
$(this).val(cleanedName);
});
$("#district_Name, #state_Id").on("input change", function () {
let districtName = $("#district_Name").val().trim();
let stateId = $("#state_Id").val();
if (districtName === "" || stateId === "") {
$("#districtMessage").text("").css("color", "");
$("#submitButton").prop("disabled", true);
return;
}
$.ajax({
url: "/check_district",
type: "POST",
contentType: "application/json",
data: JSON.stringify({ district_Name: districtName, state_Id: stateId }),
success: function (response) {
if (response.status === "available") {
$("#districtMessage").text(response.message).css("color", "green");
$("#submitButton").prop("disabled", false);
}
},
error: function (xhr) {
if (xhr.status === 409) {
$("#districtMessage").text("District already exists!").css("color", "red");
$("#submitButton").prop("disabled", true);
} else if (xhr.status === 400) {
$("#districtMessage").text("Invalid district name! Only letters are allowed.").css("color", "red");
$("#submitButton").prop("disabled", true);
}
}
});
});
$("#districtForm").on("submit", function (event) {
event.preventDefault();
$.ajax({
url: "/add_district",
type: "POST",
data: $(this).serialize(),
success: function (response) {
alert(response.message);
location.reload();
},
error: function (xhr) {
alert(xhr.responseJSON.message);
}
});
});
});

View File

@@ -0,0 +1,18 @@
$("#updateHoldTypeForm").on("submit", function(event) {
event.preventDefault();
let holdTypeId = $("#hold_type_id").val();
let newHoldType = $("#edit_hold_type").val().trim();
let reg = /^[A-Za-z]/;
if (!reg.test(newHoldType)) {
alert("Hold Type must start with a letter.");
return;
}
$.post(`/update_hold_type/${holdTypeId}`, { hold_type: newHoldType }, function(response) {
alert(response.message);
window.location.href = "/";
}).fail(function(xhr) {
alert(xhr.responseJSON.message);
});
});

95
static/js/holdAmount.js Normal file
View File

@@ -0,0 +1,95 @@
$(document).ready(function () {
// Create a module to manage hold amounts
window.holdAmountModule = {
holdCount: 0,
holdTypes: [],
init: function() {
this.loadHoldTypes();
this.setupEventListeners();
},
loadHoldTypes: function() {
$.ajax({
url: '/get_hold_types',
method: 'GET',
dataType: 'json',
success: (data) => {
this.holdTypes = data;
$("#add_hold_amount").prop('disabled', false);
},
error: (err) => {
console.error('Failed to load hold types', err);
}
});
},
setupEventListeners: function() {
$("#add_hold_amount").click(() => this.addHoldAmountField());
$(document).on("click", ".remove-hold", (e) => this.removeHoldAmountField(e));
$(document).on("change", ".hold-type-dropdown", () => this.refreshDropdowns());
$(document).on("input", "input[name='hold_amount[]']", () => this.triggerHoldAmountChanged());
},
addHoldAmountField: function() {
this.holdCount++;
$("#hold_amount_container").append(`
<div class="hold-amount-field" id="hold_${this.holdCount}">
<select name="hold_type[]" class="hold-type-dropdown" required>
${this.generateOptions()}
</select>
<input type="number" step="0.01" name="hold_amount[]"
class="hold-amount-input" placeholder="Hold Amount" required>
<button type="button" class="remove-hold" data-id="hold_${this.holdCount}">X</button>
</div>
`);
this.refreshDropdowns();
this.triggerHoldAmountChanged();
},
removeHoldAmountField: function(e) {
const id = $(e.target).attr("data-id");
$(`#${id}`).remove();
this.refreshDropdowns();
this.triggerHoldAmountChanged();
},
generateOptions: function(selectedForThisDropdown = '') {
const selectedValues = $("select[name='hold_type[]']").map(function() {
return $(this).val();
}).get();
let options = '<option value="">Select Hold Type</option>';
this.holdTypes.forEach((type) => {
if (!selectedValues.includes(type.hold_type) || type.hold_type === selectedForThisDropdown) {
options += `<option value="${type.hold_type}">${type.hold_type}</option>`;
}
});
return options;
},
refreshDropdowns: function() {
$("select[name='hold_type[]']").each(function() {
const currentVal = $(this).val();
$(this).html(window.holdAmountModule.generateOptions(currentVal));
$(this).val(currentVal);
});
},
getTotalHoldAmount: function() {
let total = 0;
$("input[name='hold_amount[]']").each(function() {
total += parseFloat($(this).val()) || 0;
});
return total;
},
triggerHoldAmountChanged: function() {
const event = new Event('holdAmountChanged');
document.dispatchEvent(event);
}
};
// Initialize the module
window.holdAmountModule.init();
});

39
static/js/hold_types.js Normal file
View File

@@ -0,0 +1,39 @@
$(document).ready(function () {
$("#hold_type").on("input", function () {
let holdType = $(this).val().replace(/^\s+/, "");
$(this).val(holdType);
let reg = /^[A-Za-z]/;
if (!reg.test(holdType)) {
$("#holdTypeMessage").text("Hold Type must start with a letter.").css("color", "red");
$("#addButton").prop("disabled", true);
return;
} else {
$("#holdTypeMessage").text("").css("color", "");
$("#addButton").prop("disabled", false);
}
});
$("#holdTypeForm").on("submit", function (event) {
event.preventDefault();
$.post("/add_hold_type", $(this).serialize(), function (response) {
alert(response.message);
location.reload();
}).fail(function (xhr) {
alert(xhr.responseJSON.message);
});
});
$(".delete-button").on("click", function () {
let id = $(this).data("id");
if (confirm("Are you sure?")) {
$.post(`/delete_hold_type/${id}`, function (response) {
alert(response.message);
location.reload();
}).fail(function (xhr) {
alert(xhr.responseJSON.message);
});
}
});
});

62
static/js/invoice.js Normal file
View File

@@ -0,0 +1,62 @@
// Subcontractor autocomplete functionality
$(document).ready(function () {
$("#subcontractor").keyup(function () {
let query = $(this).val();
if (query !== "") {
$.ajax({
url: "/search_subcontractor",
method: "POST",
data: { query: query },
success: function (data) {
$("#subcontractor_list").fadeIn().html(data);
}
});
} else {
$("#subcontractor_list").fadeOut();
}
});
$(document).on("click", "li", function () {
$("#subcontractor").val($(this).text());
$("#subcontractor_id").val($(this).attr("data-id"));
$("#subcontractor_list").fadeOut();
});
});
// Success Alert: show alert and reload after 3 seconds
function showSuccessAlert() {
const alertBox = document.getElementById("invoiceSuccessAlert");
alertBox.classList.add("show");
setTimeout(() => {
alertBox.classList.remove("show");
// Reload page or redirect after alert hides
window.location.href = '/add_invoice';
}, 3000);
}
// Submit form via AJAX
$("#invoiceForm").on("submit", function (e) {
e.preventDefault();
let formData = $(this).serialize();
$.ajax({
url: '/add_invoice',
method: 'POST',
data: formData,
success: function (response) {
if(response.status === "success") {
showSuccessAlert();
} else {
alert(response.message);
}
},
error: function (xhr, status, error) {
alert("Submission failed: " + error);
}
});
});
window.onload = function () {
document.getElementById('subcontractor').focus();
};

View File

@@ -0,0 +1,39 @@
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('saveForm');
form.addEventListener('submit', function (e) {
e.preventDefault(); // Prevent normal form submission
const formData = new FormData(form);
fetch('/save_data', {
method: 'POST',
body: formData,
}).then(response => response.json()).then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: 'Success!',
text: data.success,
showConfirmButton: true,
confirmButtonText: 'OK'
}).then(() => {
const redirectUrl = "{{ url_for('upload_excel_file') }}"; // Redirect after success pop
});
} else if (data.error) {
Swal.fire({
icon: 'error',
title: 'Error!',
text: data.error,
});
}
}).catch(error => {
Swal.fire({
icon: 'error',
title: 'Error!',
text: 'An unexpected error occurred.',
});
console.error('Error:', error);
});
});
});

View File

@@ -0,0 +1,21 @@
$("#saveForm").on("submit", function (event) {
event.preventDefault();
$.ajax({
url: "/save_data",
type: "POST",
data: $(this).serialize(),
success: function (response) {
if (response.success) {
alert("Success: " + response.success); // Show success alert
window.location.href = "/upload_excel_file"; // Redirect to the upload page
}
},
error: function (xhr) {
if (xhr.responseJSON && xhr.responseJSON.error) {
alert("Error: " + xhr.responseJSON.error);
} else {
alert("An unexpected error occurred. Please try again.");
}
}
});
});

View File

@@ -0,0 +1,43 @@
$(document).ready(function () {
function fetchResults() {
let formData = $('#search-form').serialize();
$.ajax({
type: 'POST',
url: '/search_contractor',
data: formData,
success: function (data) {
let tableBody = $('#result-table tbody');
tableBody.empty();
if (data.length === 0) {
tableBody.append('<tr><td colspan="6">No data found</td></tr>');
} else {
data.forEach(function (row) {
tableBody.append(`
<tr>
<td><a href="/contractor_report/${row.Contractor_Id}" target="_blank">${row.Contractor_Name}</a></td>
<td><a href="/pmc_report/${row.PMC_No}" target="_blank">${row.PMC_No}</a></td>
<td>${row.State_Name}</td>
<td>${row.District_Name}</td>
<td>${row.Block_Name}</td>
<td>${row.Village_Name}</td>
</tr>
`);
});
}
},
error: function (xhr) {
alert(xhr.responseJSON.error);
}
});
}
$('#search-form input').on('keyup change', function () {
fetchResults();
});
});
window.onload = function () {
document.getElementById('subcontractor_name').focus();
};

View File

@@ -0,0 +1,108 @@
// Search on table using search inpute options
function searchTable() {
let input = document.getElementById("searchBar").value.toLowerCase();
let rows = document.querySelectorAll("table tbody tr");
rows.forEach(row => {
let blockName = row.cells[1].textContent.toLowerCase();
let districtName = row.cells[2].textContent.toLowerCase();
let villageName = row.cells[3].textContent.toLowerCase();
if (blockName.includes(input) || districtName.includes(input)|| villageName.includes(input)) {
row.style.display = "";
} else {
row.style.display = "none";
}
});
}
// Common Sorting Script for Tables
function sortTable(n, dir) {
var table, rows, switching, i, x, y, shouldSwitch;
table = document.getElementById("sortableTable"); // Ensure your table has this ID
switching = true;
while (switching) {
switching = false;
rows = table.rows;
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
if (dir == "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
} else if (dir == "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
}
}
}
// Attach sorting functionality to all sortable tables
document.addEventListener("DOMContentLoaded", function() {
// Find all elements with the class "sortable-header"
var sortableHeaders = document.querySelectorAll(".sortable-header");
sortableHeaders.forEach(function(header) {
// Attach click event for ascending sort
if (header.querySelector(".sort-asc")) {
header.querySelector(".sort-asc").addEventListener("click", function() {
var columnIndex = Array.from(header.parentNode.children).indexOf(header);
sortTable(columnIndex, "asc");
});
}
// Attach click event for descending sort
if (header.querySelector(".sort-desc")) {
header.querySelector(".sort-desc").addEventListener("click", function() {
var columnIndex = Array.from(header.parentNode.children).indexOf(header);
sortTable(columnIndex, "desc");
});
}
});
});
// ADD & Dispaly screen show
document.addEventListener("DOMContentLoaded", function () {
const addButton = document.getElementById("addButton");
const displayButton = document.getElementById("displayButton");
const addForm = document.getElementById("addForm");
const addTable = document.getElementById("addTable");
// Show "Add State" form by default
addForm.style.display = "block";
addButton.classList.add("active-button"); // Optional: Add styling for active button
addButton.addEventListener("click", function () {
addForm.style.display = "block";
addTable.style.display = "none";
addButton.classList.add("active-button");
displayButton.classList.remove("active-button");
});
displayButton.addEventListener("click", function () {
addForm.style.display = "none";
addTable.style.display = "block";
displayButton.classList.add("active-button");
addButton.classList.remove("active-button");
});
});

View File

@@ -0,0 +1,8 @@
function showSuccessAlert(event) {
event.preventDefault(); // Prevent form submission
document.getElementById("successPopup").style.display = "block";
setTimeout(function() {
document.getElementById("successPopup").style.display = "none";
event.target.submit(); // Submit the form after showing the message
}, 2000);
}

66
static/js/sorting.js Normal file
View File

@@ -0,0 +1,66 @@
$(document).ready(function () {
function fetchResults(sortBy = '', sortOrder = '') {
let formData = $('#search-form').serialize();
formData += &sort_by=${sortBy}&sort_order=${sortOrder};
$.ajax({
type: 'POST',
url: '/search_contractor',
data: formData,
success: function (data) {
let tableBody = $('#result-table tbody');
tableBody.empty();
if (data.length === 0) {
tableBody.append('<tr><td colspan="6">No data found</td></tr>');
} else {
data.forEach(function (row) {
tableBody.append(`
<tr>
<td><a href="/contractor_report/${row.Contractor_Id}" target="_blank">${row.Contractor_Name}</a></td>
<td><a href="/pmc_report/${row.PMC_No}" target="_blank">${row.PMC_No}</a></td>
<td>${row.State_Name}</td>
<td>${row.District_Name}</td>
<td>${row.Block_Name}</td>
<td>${row.Village_Name}</td>
</tr>
`);
});
}
},
error: function (xhr) {
alert(xhr.responseJSON.error);
}
});
}
$('#search-form input').on('keyup change', function () {
fetchResults();
});
function showSortOptions(thElement, column) {
let sortMenu = $('#sort-options');
let offset = $(thElement).position();
let thHeight = $(thElement).outerHeight();
sortMenu.html(`
<button onclick="fetchResults('${column}', 'ASC')">Ascending</button>
<button onclick="fetchResults('${column}', 'DESC')">Descending</button>
`);
sortMenu.css({
display: 'block',
top: offset.top + thHeight + 'px',
left: offset.left + 'px'
});
}
$(document).click(function(event) {
if (!$(event.target).closest('.sort-options, th').length) {
$('#sort-options').hide();
}
});
window.fetchResults = fetchResults;
window.showSortOptions = showSortOptions;
});

61
static/js/state.js Normal file
View File

@@ -0,0 +1,61 @@
window.onload = function () {
document.getElementById('state_Name').focus();
};
$(document).ready(function () {
$("#state_Name").on("input", function () {
let stateName = $(this).val();
// Remove numbers and special characters automatically
let cleanedName = stateName.replace(/[^A-Za-z ]/g, "");
$(this).val(cleanedName);
});
$("#state_Name").on("input", function () {
let stateName = $("#state_Name").val().trim();
if (stateName === "") {
$("#stateMessage").text("").css("color", "");
$("#submitButton").prop("disabled", true);
return;
}
$.ajax({
url: "/check_state",
type: "POST",
contentType: "application/json",
data: JSON.stringify({ state_Name: stateName }),
success: function (response) {
if (response.status === "available") {
$("#stateMessage").text(response.message).css("color", "green");
$("#submitButton").prop("disabled", false);
}
},
error: function (xhr) {
if (xhr.status === 409) {
$("#stateMessage").text("State already exists!").css("color", "red");
$("#submitButton").prop("disabled", true);
} else if (xhr.status === 400) {
$("#stateMessage").text("Invalid state name! Only letters are allowed.").css("color", "red");
$("#submitButton").prop("disabled", true);
}
}
});
});
$("#stateForm").on("submit", function (event) {
event.preventDefault();
$.ajax({
url: "/add_state",
type: "POST",
data: $(this).serialize(),
success: function (response) {
alert(response.message);
location.reload();
},
error: function (xhr) {
alert(xhr.responseJSON.message);
}
});
});
});

View File

@@ -0,0 +1,49 @@
function validateInput() {
let isValid = true;
// Get form elements
let contractorName = document.getElementById("Contractor_Name").value;
let mobileNo = document.getElementById("Mobile_No").value;
let panNo = document.getElementById("PAN_No").value;
let email = document.getElementById("Email").value;
let passwordField = document.getElementById("Contractor_password");
let submitBtn = document.getElementById("submitBtn");
// Validation patterns
let mobileRegex = /^[0-9]{10}$/;
let panRegex = /^[A-Z0-9]{10}$/;
let emailRegex = /^[a-z]+@[a-z]+\.[a-z]{2,6}$/;
// Validate Mobile No
if (!mobileNo.match(mobileRegex)) {
document.getElementById("mobileError").innerText = "Mobile No must be exactly 10 digits.";
isValid = false;
} else {
document.getElementById("mobileError").innerText = "";
}
// Validate PAN No
if (!panNo.match(panRegex)) {
document.getElementById("panError").innerText = "PAN No must be uppercase letters or digits (10 chars).";
isValid = false;
} else {
document.getElementById("panError").innerText = "";
}
// Validate Email
if (!email.match(emailRegex)) {
document.getElementById("emailError").innerText = "Email must be lowercase, contain '@' and '.'";
isValid = false;
} else {
document.getElementById("emailError").innerText = "";
}
// Enable or disable the submit button
submitBtn.disabled = !isValid;
}
window.onload = function () {
document.getElementById('Contractor_Name').focus();
};

View File

@@ -0,0 +1,12 @@
function validateFileInput() {
const fileInput = document.querySelector('input[type="file"]');
const filePath = fileInput.value;
const allowedExtensions = /(\.xlsx|\.xls)$/i;
if (!allowedExtensions.exec(filePath)) {
alert("Please upload a valid Excel file (.xlsx or .xls only).");
fileInput.value = '';
return false;
}
return true;
}

198
static/js/village.js Normal file
View File

@@ -0,0 +1,198 @@
window.onload = function () {
document.getElementById('Village_Name').focus();
};
$(document).ready(function () {
// STATE → DISTRICT
$('#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('<option value="" disabled selected>Select District</option>');
data.forEach(function (district) {
districtDropdown.append(
'<option value="' + district.id + '">' + district.name + '</option>'
);
});
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('<option value="" disabled selected>Select Block</option>');
data.forEach(function (block) {
blockDropdown.append(
'<option value="' + block.id + '">' + block.name + '</option>'
);
});
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.');
}
});
});
});

102
templates/activity_log.html Normal file
View File

@@ -0,0 +1,102 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Activity Logs</title>
<style>
body {
font-family: "Segoe UI", Tahoma, sans-serif;
background-color: #f8f9fa;
margin: 20px;
}
h2 {
text-align: center;
margin-bottom: 20px;
}
form {
display: flex;
gap: 10px;
justify-content: center;
margin-bottom: 20px;
}
input, button {
padding: 8px;
border-radius: 6px;
border: 1px solid #ccc;
}
button {
background-color: #007bff;
color: white;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
table {
width: 90%;
margin: auto;
border-collapse: collapse;
background: white;
box-shadow: 0 0 8px rgba(0,0,0,0.1);
}
th, td {
padding: 12px;
border: 1px solid #ddd;
text-align: center;
}
th {
background-color: #007bff;
color: white;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
</style>
</head>
<body>
<h2>Activity Logs</h2>
<form method="get" action="{{ url_for('activity_log') }}" class="filter-form">
<label for="username">Username:</label>
<input type="text" id="username" name="username" placeholder="Enter username" value="{{ username or '' }}">
<label for="start_date">Start Date:</label>
<input type="date" id="start_date" name="start_date" value="{{ start_date or '' }}">
<label for="end_date">End Date:</label>
<input type="date" id="end_date" name="end_date" value="{{ end_date or '' }}">
<button type="submit" class="btn btn-primary">Filter</button>
<!-- <button type="button" style="background-color: #6c757d;" onclick="resetFilter()">Reset</button> -->
</form>
<table>
<tr>
<th>Timestamp</th>
<th>User</th>
<th>Action</th>
<th>Details</th>
</tr>
{% for log in logs %}
<tr>
<td>{{ log.timestamp }}</td>
<td>{{ log.user }}</td>
<td>{{ log.action }}</td>
<td>{{ log.details }}</td>
</tr>
{% endfor %}
{% if logs|length == 0 %}
<tr>
<td colspan="4">No logs found</td>
</tr>
{% endif %}
</table>
<script>
function resetFilter() {
window.location.href = "{{ url_for('activity_log') }}";
}
</script>
</body>
</html>

99
templates/add_block.html Normal file
View File

@@ -0,0 +1,99 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Block</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/block.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<!-- Button Container to Center Buttons -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<div class="container">
<div class="form-block">
<h2>Create a New Block</h2>
<form id="blockForm" method="POST">
<!-- Select State Dropdown -->
<label for="state_Id">State:</label>
<select id="state_Id" name="state_Id" required>
<option value="" disabled selected>Select State</option>
{% for state in states %}
<option value="{{ state[0] }}">{{ state[1] }}</option>
{% endfor %}
</select>
<!-- Select District Dropdown -->
<label for="district_Id">District:</label>
<select id="district_Id" name="district_Id" required disabled>
<option value="" disabled selected>Select District</option>
</select>
<!-- Block Name Input -->
<label for="block_Name">Enter Block Name:</label>
<input type="text" id="block_Name" name="block_Name" placeholder="Block Name" required>
<span id="blockMessage"></span>
<button type="submit" id="submitButton" disabled>Add Block</button>
</form>
</div>
</div>
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Display Blocks</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<!-- Display Blocks -->
<table id="sortableTable" border="1">
<tr>
<th>Block Sr no</th>
<th class="sortable-header">Block Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th class="sortable-header">District Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th>Update</th>
<th>Delete</th>
</tr>
{% for block in block_data %}
<tr>
<td>{{ block[0] }}</td>
<td>{{ block[1] }}</td>
<td>{{ block[3] }}</td>
<td>
<a href="{{ url_for('block.edit_block', block_id=block[0]) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
<a href="{{ url_for('block.delete_block', block_id=block[0]) }}"
onclick="return confirm('Are you sure you want to delete this block?');">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
class="icon">
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,85 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Add District</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/district.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<!-- Button Container to Center Buttons -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<h2>Add District</h2>
<form id="districtForm" method="POST">
<label for="state_Id">State :</label>
<select name="state_Id" id="state_Id" required>
<option value="" disabled selected>Select State</option>
{% for state in states %}
<option value="{{ state[0] }}">{{ state[1] }}</option>
{% endfor %}
</select>
<label>Enter District :</label>
<input type="text" id="district_Name" name="district_Name" placeholder="District Name" required>
<span id="districtMessage"></span>
<button type="submit" id="submitButton" disabled>Add District</button>
</form>
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Display Districts</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<tr>
<th>District ID</th>
<th class="sortable-header">
District Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span></th>
<th class="sortable-header">
State Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span></th>
<th>Edit District</th>
<th>Delete District</th>
</tr>
{% for district in districtdata %}
<tr>
<td>{{ district[0] }}</td>
<td>{{ district[1] }}</td>
<td>{{ district[2] }}</td>
<td>
<a href="{{ url_for('district.edit_district', district_id=district[0]) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
<a href="{{ url_for('district.delete_district', district_id=district[0]) }}"
onclick="return confirm('Are you sure you want to delete this district?')">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
class="icon">
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,175 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add GST Release</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
<script src="{{ url_for('static', filename='js/invoice.js') }}"></script>
</head>
<body>
<!-- Button Container to Center Buttons -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<h2>Add GST Release</h2>
<form action="/add_gst_release" method="POST" onsubmit="showSuccessAlert(event)">
<div class="row1">
<div>
<label for="subcontractor">Subcontractor Name:</label>
<input type="text" id="subcontractor" name="subcontractor" required autocomplete="off"/>
<input type="hidden" id="subcontractor_id" name="subcontractor_id"/>
<div id="subcontractor_list" class="autocomplete-items"></div>
</div>
</div>
<label for="PMC_No">PMC No:</label><br>
<select id="PMC_No" name="PMC_No" required>
<option value="">Select PMC No</option>
{% for option in pmc_options %}
<option value="{{ option.PMC_No }}">{{ option.PMC_No }} - {{ option.Subcontractor_Name }}</option>
{% endfor %}
</select><br><br>
<label for="invoice_No">Invoice No:</label><br>
<input type="text" id="invoice_No" name="invoice_No" required><br><br>
<label for="basic_amount">Basic Amount:</label><br>
<input type="number" step="0.01" id="basic_amount" name="basic_amount" placeholder="₹ - 00.00" required><br><br>
<label for="final_amount">Final Amount:</label><br>
<input type="number" step="0.01" id="final_amount" name="final_amount" placeholder="₹ - 00.00" required><br><br>
<label for="total_amount">Total Amount:</label><br>
<input type="number" step="0.01" id="total_amount" name="total_amount" placeholder="₹ - 00.00" required><br><br>
<label for="utr">UTR:</label><br>
<input type="text" id="utr" name="utr" required><br><br>
<button type="submit">Submit GST Release</button>
</form>
</div>
<!-- Success Popup -->
<div id="successPopup" class="success-popup">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
<i>&#10004;</i> {{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>GST Release History</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<thead>
<tr>
<th class="sortable-header">GST_Release_Id</th>
<th class="sortable-header">PMC_No</th>
<th>Invoice_No</th>
<th>Basic_Amount</th>
<th>Final_Amount</th>
<th>Total_Amount</th>
<th>UTR</th>
<th>Update</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for gst_rel in gst_releases %}
<tr>
<td>{{ gst_rel[0] }}</td>
<td>{{ gst_rel[1] }}</td>
<td>{{ gst_rel[2] }}</td>
<td>{{ gst_rel[3] }}</td>
<td>{{ gst_rel[4] }}</td>
<td>{{ gst_rel[5] }}</td>
<td>{{ gst_rel[6] }}</td>
<td>
<a href="/edit_gst_release/{{ gst_rel[0] }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
<a href="/delete_gst_release/{{ gst_rel[0] }}"
onclick="return confirm('Are you sure you want to delete this GST Release?')">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
class="icon">
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Handle subcontractor autocomplete
document.getElementById("subcontractor").addEventListener("input", function () {
const query = this.value;
const list = document.getElementById("subcontractor_list");
if (query.length < 2) {
list.innerHTML = '';
return;
}
fetch(`/search_subcontractor?query=${encodeURIComponent(query)}`)
.then(response => response.json())
.then(data => {
list.innerHTML = '';
data.results.forEach(item => {
const div = document.createElement("div");
div.setAttribute("data-id", item.id);
div.textContent = item.name;
list.appendChild(div);
});
});
});
// Handle subcontractor selection
document.getElementById("subcontractor_list").addEventListener("click", function (e) {
const selectedId = e.target.getAttribute("data-id");
const selectedName = e.target.textContent;
if (selectedId) {
document.getElementById("subcontractor_id").value = selectedId;
document.getElementById("subcontractor").value = selectedName;
document.getElementById("subcontractor_list").innerHTML = "";
// Update PMC dropdown for selected subcontractor
fetch(`/get_pmc_nos_by_subcontractor/${encodeURIComponent(selectedId)}`)
.then(response => response.json())
.then(data => {
const pmcDropdown = document.getElementById("PMC_No");
pmcDropdown.innerHTML = '<option value="">Select PMC No</option>';
data.pmc_nos.forEach(pmc => {
const option = document.createElement("option");
option.value = pmc;
option.textContent = pmc;
pmcDropdown.appendChild(option);
});
});
}
});
});
</script>
<script src="{{ url_for('static', filename='js/showSuccessAlert.js') }}"></script>
</body>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Manage Hold Types</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- <script src="{{ url_for('static', filename='js/hold_types.js') }}"></script> -->
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm">
<h2>Add Hold Types</h2>
<form id="holdTypeForm" method="POST" action="{{ url_for('hold_types.add_hold_type') }}">
<label>Enter Hold Amount Type:</label>
<input type="text" id="hold_type" name="hold_type" placeholder="Enter Type" required>
<span id="holdTypeMessage"></span>
<button type="submit" value="Add Hold Type" id="successPopup">Add Hold Type</button>
</form>
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Hold Type List</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<tr>
<th>ID</th>
<th class="sortable-header">
Hold Type
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th>Update</th>
<th>Delete</th>
</tr>
{% for htd in Hold_Types_data %}
<tr>
<td>{{ htd['hold_type_id'] }}</td>
<td>{{ htd['hold_type'] }}</td>
<td><a href="{{ url_for('hold_types.edit_hold_type', id=htd['hold_type_id']) }}">Edit</a></td>
<td><button class="delete-button" data-id="{{ htd['hold_type_id'] }}">Delete</button></td>
</tr>
{% endfor %}
</table>
<a href="/">Back to Dashboard</a>
</div>
</body>
{% endblock %}

370
templates/add_invoice.html Normal file
View File

@@ -0,0 +1,370 @@
{% extends 'base.html' %}
{% block content %}
<head xmlns="http://www.w3.org/1999/html">
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Add Invoice</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/invoice.css') }}">
<script src="{{ url_for('static', filename='js/invoice.js') }}"></script>
<script src="{{ url_for('static', filename='js/holdAmount.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
{% if success == 'true' %}
<div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
✅ Invoice added successfully!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-messages">
{% for category, message in messages %}
<div class="alert {{ category }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<h2>Add Invoice</h2>
<form id="invoiceForm" action="{{ url_for('invoice.add_invoice') }}" method="POST">
<div class="row1">
<div>
<label for="subcontractor">Subcontractor Name:</label>
<input type="text" id="subcontractor" name="subcontractor" required autocomplete="off"/>
<input type="hidden" id="subcontractor_id" name="subcontractor_id"/>
<div id="subcontractor_list"></div>
</div>
</div>
<div class="row2">
<div>
<label for="village">Village Name:</label>
<select id="village" name="village" required>
<option value="">-- Select Village --</option>
{% for village in villages %}
<option value="{{ village.Village_Name }}">{{ village.Village_Name }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="pmc_no">PMC No:</label>
<input type="text" id="pmc_no" name="pmc_no" required/>
<div id="pmc_no_list" class="autocomplete-list"></div>
</div>
</div>
<div class="row2">
<div>
<label for="work_type">Work Type:</label>
<input type="text" id="work_type" name="work_type" required/>
</div>
<div>
<label for="invoice_details">Invoice Details:</label>
<textarea id="invoice_details" name="invoice_details" required></textarea>
</div>
</div>
<div class="row2">
<div>
<label for="invoice_no">Invoice No:</label>
<input type="text" id="invoice_no" name="invoice_no" required/>
</div>
<div>
<label for="invoice_date">Invoice Date:</label>
<input type="date" id="invoice_date" name="invoice_date" required/>
</div>
</div>
<div class="row3">
<div>
<label for="basic_amount">Basic Amount:</label>
<input type="number" step="0.01" id="basic_amount" name="basic_amount" placeholder="₹ - 00.00" required/>
</div>
<div>
<label for="debit_amount">Debit Amount:</label>
<input type="number" step="0.01" id="debit_amount" name="debit_amount" placeholder="₹ - 00.00" required/>
</div>
<div>
<label for="after_debit_amount">After Debit Amount:</label>
<input type="number" step="0.01" id="after_debit_amount" name="after_debit_amount" placeholder="₹ - 00.00" readonly required/>
</div>
</div>
<div class="row3">
<div class="percentage-field">
<label for="gst_percentage">GST %:</label>
<input type="number" step="0.01" id="gst_percentage" name="gst_percentage" placeholder="%"/>
<label for="gst_amount">GST Amount:</label>
<input type="number" step="0.01" id="gst_amount" name="gst_amount" placeholder="₹ - 00.00" readonly required/>
</div>
<div>
<label for="amount">Amount:</label>
<input type="number" step="0.01" id="amount" name="amount" placeholder="₹ - 00.00" readonly required/>
</div>
<div class="percentage-field">
<label for="tds_percentage">TDS %:</label>
<input type="number" step="0.01" id="tds_percentage" name="tds_percentage" placeholder="%"/>
<label for="tds_amount">TDS Amount:</label>
<input type="number" step="0.01" id="tds_amount" name="tds_amount" placeholder="₹ - 00.00" readonly required/>
</div>
</div>
<div class="row3">
<div class="percentage-field">
<label for="sd_percentage">SD %:</label>
<input type="number" step="0.01" id="sd_percentage" name="sd_percentage" placeholder="%"/>
<label for="sd_amount">SD Amount:</label>
<input type="number" step="0.01" id="sd_amount" name="sd_amount" placeholder="₹ - 00.00" readonly required>
</div>
<div class="percentage-field">
<label for="commission_percentage">On Commission %:</label>
<input type="number" step="0.01" id="commission_percentage" name="commission_percentage" placeholder="%"/>
<label for="on_commission">On Commission:</label>
<input type="number" step="0.01" id="on_commission" name="on_commission" placeholder="₹ - 00.00" readonly required>
</div>
<div class="percentage-field">
<label for="hydro_percentage">Hydro Testing %:</label>
<input type="number" step="0.01" id="hydro_percentage" name="hydro_percentage" placeholder="%"/>
<label for="hydro_testing">Hydro Testing:</label>
<input type="number" step="0.01" id="hydro_testing" name="hydro_testing" placeholder="₹ - 00.00" readonly required>
</div>
</div>
<div class="hold-row">
<button type="button" id="add_hold_amount" class="button">+ Add Hold Amount</button>
</div>
<!-- Dynamically added hold amount fields -->
<div id="hold_amount_container"></div>
<div class="row2">
<div>
<label for="gst_sd_amount">GST SD Amount:</label>
<input type="number" step="0.01" id="gst_sd_amount" name="gst_sd_amount" placeholder="₹ - 00.00" required/>
</div>
<div>
<label for="final_amount">Final Amount:</label>
<input type="number" step="0.01" id="final_amount" name="final_amount" placeholder="₹ - 00.00" required/>
</div>
</div>
<button type="submit" class="button">Submit</button>
</form>
</div>
<div id="addTable" style="display: none;">
<!-- Invoice Table Section -->
<div class="search-container">
<h2>Invoice List</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
{% if invoices %}
<table class="invoice-table">
<thead>
<tr>
<th>Invoice Id</th>
<th>SubContractor Name</th>
<th>PMC No</th>
<th>Village</th>
<th>Work Type</th>
<th>Invoice Details</th>
<th>Invoice Date</th>
<th>Invoice No</th>
<th>Basic Amount</th>
<th>Debit Amount</th>
<th>After Debit Amount</th>
<th>Amount</th>
<th>GST Amount</th>
<th>TDS Amount</th>
<th>SD Amount</th>
<th>On Commission</th>
<th>Hydro Testing</th>
<th>GST SD Amount</th>
<th>Final Amount</th>
<th>Update</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for invoice in invoices %}
<tr>
<td>{{ invoice.Invoice_Id }}</td>
<td>{{ invoice.Contractor_Name }}</td>
<td>{{ invoice.PMC_No }}</td>
<td>{{ invoice.Village_Name or 'N/A' }}</td>
<td>{{ invoice.Work_Type }}</td>
<td>{{ invoice.Invoice_Details }}</td>
<td>{{ invoice.Invoice_Date }}</td>
<td>{{ invoice.Invoice_No }}</td>
<td>{{ invoice.Basic_Amount }}</td>
<td>{{ invoice.Debit_Amount }}</td>
<td>{{ invoice.After_Debit_Amount }}</td>
<td>{{ invoice.Amount }}</td>
<td>{{ invoice.GST_Amount }}</td>
<td>{{ invoice.TDS_Amount }}</td>
<td>{{ invoice.SD_Amount }}</td>
<td>{{ invoice.On_Commission }}</td>
<td>{{ invoice.Hydro_Testing }}</td>
<td>{{ invoice.GST_SD_Amount }}</td>
<td>{{ invoice.Final_Amount }}</td>
<td>
<!-- Edit -->
<a href="{{ url_for('invoice.edit_invoice', invoice_id=invoice.Invoice_Id) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit" class="icon">
</a>
</td>
<td>
<!-- Delete -->
<a href="{{ url_for('invoice.delete_invoice_route', invoice_id=invoice.Invoice_Id) }}" onclick="return confirm('Are you sure?')">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete" class="icon">
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No invoices found.</p>
{% endif %}
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get all the input fields
const basicAmount = document.getElementById('basic_amount');
const debitAmount = document.getElementById('debit_amount');
const afterDebitAmount = document.getElementById('after_debit_amount');
const amount = document.getElementById('amount');
// Percentage fields
const gstPercentage = document.getElementById('gst_percentage');
const gstAmount = document.getElementById('gst_amount');
const tdsPercentage = document.getElementById('tds_percentage');
const tdsAmount = document.getElementById('tds_amount');
const sdPercentage = document.getElementById('sd_percentage');
const sdAmountInput = document.getElementById('sd_amount');
const commissionPercentage = document.getElementById('commission_percentage');
const onCommission = document.getElementById('on_commission');
const hydroPercentage = document.getElementById('hydro_percentage');
const hydroTesting = document.getElementById('hydro_testing');
const gstSdAmount = document.getElementById('gst_sd_amount');
const finalAmount = document.getElementById('final_amount');
// Calculate after debit amount when basic or debit amount changes
function calculateAfterDebitAmount() {
const basic = parseFloat(basicAmount.value) || 0;
const debit = parseFloat(debitAmount.value) || 0;
const afterDebit = basic - debit;
afterDebitAmount.value = afterDebit.toFixed(2);
calculateGST();
}
// Calculate GST and Amount
function calculateGST() {
const baseAmount = parseFloat(afterDebitAmount.value) || 0;
if (gstPercentage.value) {
const gstPerc = parseFloat(gstPercentage.value) || 0;
const gstAmt = (baseAmount * gstPerc) / 100;
gstAmount.value = gstAmt.toFixed(2);
gstSdAmount.value = gstAmt.toFixed(2);
// Calculate Amount (After Debit + GST)
amount.value = (baseAmount + gstAmt).toFixed(2);
} else {
amount.value = baseAmount.toFixed(2);
}
calculateOtherDeductions();
}
// Calculate other deductions (TDS, SD, Commission, Hydro)
function calculateOtherDeductions() {
const baseAmount = parseFloat(afterDebitAmount.value) || 0;
// Calculate TDS
if (tdsPercentage.value) {
const tdsPerc = parseFloat(tdsPercentage.value) || 0;
const tdsAmt = (baseAmount * tdsPerc) / 100;
tdsAmount.value = tdsAmt.toFixed(2);
}
// Calculate SD
if (sdPercentage.value) {
const sdPerc = parseFloat(sdPercentage.value) || 0;
const sdAmt = (baseAmount * sdPerc) / 100;
sdAmountInput.value = sdAmt.toFixed(2);
}
// Calculate Commission
if (commissionPercentage.value) {
const commPerc = parseFloat(commissionPercentage.value) || 0;
const commAmt = (baseAmount * commPerc) / 100;
onCommission.value = commAmt.toFixed(2);
}
// Calculate Hydro Testing
if (hydroPercentage.value) {
const hydroPerc = parseFloat(hydroPercentage.value) || 0;
const hydroAmt = (baseAmount * hydroPerc) / 100;
hydroTesting.value = hydroAmt.toFixed(2);
}
calculateFinalAmount();
}
// Calculate final amount
function calculateFinalAmount() {
const amt = parseFloat(amount.value) || 0;
const tds = parseFloat(tdsAmount.value) || 0;
const sd = parseFloat(sdAmountInput.value) || 0;
const commission = parseFloat(onCommission.value) || 0;
const hydro = parseFloat(hydroTesting.value) || 0;
const gstSd = parseFloat(gstSdAmount.value) || 0;
// Get hold amounts
let totalHold = 0;
document.querySelectorAll('input[name="hold_amount[]"]').forEach(input => {
totalHold += parseFloat(input.value) || 0;
});
// Final Amount = Amount - TDS - SD - Commission - Hydro - GST SD - Hold Amounts
const final = amt - tds - sd - commission - hydro - gstSd - totalHold;
finalAmount.value = final.toFixed(2);
}
// Add event listeners
basicAmount.addEventListener('input', calculateAfterDebitAmount);
debitAmount.addEventListener('input', calculateAfterDebitAmount);
// Percentage fields
gstPercentage.addEventListener('input', calculateGST);
tdsPercentage.addEventListener('input', calculateOtherDeductions);
sdPercentage.addEventListener('input', calculateOtherDeductions);
commissionPercentage.addEventListener('input', calculateOtherDeductions);
hydroPercentage.addEventListener('input', calculateOtherDeductions);
// Listen for changes in hold amounts
document.addEventListener('holdAmountChanged', calculateFinalAmount);
});
// Optional JS for auto-hiding flash
setTimeout(() => {
document.querySelectorAll('.alert').forEach(el => el.style.display = 'none');
}, 5000);
</script>
</div>
</body>
{% endblock %}

193
templates/add_payment.html Normal file
View File

@@ -0,0 +1,193 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Payment</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
<script src="{{ url_for('static', filename='js/invoice.js') }}"></script>
</head>
<body>
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<h2>Add Payment</h2>
<form action="/add_payment" method="POST" onsubmit="showSuccessAlert(event)">
<div class="row1">
<div>
<label for="subcontractor">Subcontractor Name:</label>
<input type="text" id="subcontractor" name="subcontractor" required autocomplete="off"/>
<input type="hidden" id="subcontractor_id" name="subcontractor_id"/>
<div id="subcontractor_list" class="autocomplete-items"></div>
</div>
</div>
<label for="PMC_No">PMC No:</label><br>
<select id="PMC_No" name="PMC_No" required>
<option value="">Select PMC No</option>
</select><br><br>
<label for="invoice_No">Invoice No:</label><br>
<input type="number" step="0.01" id="invoice_No" name="invoice_No" ><br><br>
<label for="Payment_Amount">Amount:</label><br>
<input type="number" step="0.01" id="Payment_Amount" name="Payment_Amount" required oninput="calculateTDSAndTotal()"><br><br>
<label for="TDS_Percentage">TDS Percentage (%):</label><br>
<input type="number" step="0.01" id="TDS_Percentage" name="TDS_Percentage" oninput="calculateTDSAndTotal()"><br><br>
<label for="TDS_Payment_Amount">TDS Amount:</label><br>
<input type="number" step="0.01" id="TDS_Payment_Amount" name="TDS_Payment_Amount" required readonly><br><br>
<label for="total_amount">Total Amount:</label><br>
<input type="number" step="0.01" id="total_amount" name="total_amount" required readonly><br><br>
<label for="utr">UTR:</label><br>
<input type="text" id="utr" name="utr"><br><br>
<button type="submit">Submit Payment</button>
</form>
</div>
<div id="successPopup" class="success-popup">
<i>&#10004;</i> Payment added successfully!
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Payment History</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<thead>
<tr>
<th class="sortable-header">Payment ID</th>
<th class="sortable-header">PMC No</th>
<th>Invoice No</th>
<th>Payment Amount</th>
<th>TDS Amount</th>
<th>Total Amount</th>
<th>UTR</th>
<th>Update</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for payment in payments %}
<tr>
<td>{{ payment[0] }}</td>
<td>{{ payment[1] }}</td>
<td>{{ payment[2] }}</td>
<td>{{ payment[3] }}</td>
<td>{{ payment[4] }}</td>
<td>{{ payment[5] }}</td>
<td>{{ payment[6] }}</td>
<td><a href="/edit_payment/{{ payment[0] }}"><img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit" class="icon"></a></td>
<td><a href="/delete_payment/{{ payment[0] }}" onclick="return confirm('Are you sure you want to delete this payment?')"><img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete" class="icon"></a></td>
</tr>
<!-- <tr>
<td>{{ payment['Payment_Id'] }}</td>
<td>{{ payment['PMC_No'] }}</td>
<td>{{ payment['invoice_no'] }}</td>
<td>{{ payment['Payment_Amount'] }}</td>
<td>{{ payment['TDS_Payment_Amount'] }}</td>
<td>{{ payment['Total_Amount'] }}</td>
<td>{{ payment['UTR'] }}</td>
<td><a href="/edit_payment/{{ payment['Payment_Id'] }}"><img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit" class="icon"></a></td>
<td><a href="/delete_payment/{{ payment['Payment_Id'] }}" onclick="return confirm('Are you sure you want to delete this payment?')"><img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete" class="icon"></a></td>
</tr> -->
{% endfor %}
</tbody>
</table>
</div>
<script>
document.getElementById("subcontractor").addEventListener("input", function () {
const query = this.value;
const list = document.getElementById("subcontractor_list");
if (query.length < 2) {
list.innerHTML = '';
return;
}
fetch(`/search_subcontractor?query=${encodeURIComponent(query)}`)
.then(response => response.json())
.then(data => {
list.innerHTML = '';
data.results.forEach(item => {
const div = document.createElement("div");
div.setAttribute("data-id", item.id);
div.textContent = item.name;
list.appendChild(div);
});
});
});
document.getElementById("subcontractor_list").addEventListener("click", function (e) {
const selectedId = e.target.getAttribute("data-id");
const selectedName = e.target.textContent;
if (selectedId) {
document.getElementById("subcontractor_id").value = selectedId;
document.getElementById("subcontractor").value = selectedName;
document.getElementById("subcontractor_list").innerHTML = ""; // hide the list
console.log("Contractor id is", selectedId);
// Fetch PMC numbers
fetch(`/get_pmc_nos_by_subcontractor/${encodeURIComponent(selectedId)}`)
.then(response => response.json())
.then(data => {
console.log("Fetched PMC Nos:", data.pmc_nos);
const pmcDropdown = document.getElementById("PMC_No");
pmcDropdown.innerHTML = "";
const defaultOption = document.createElement("option");
defaultOption.value = "";
defaultOption.textContent = "Select PMC No";
pmcDropdown.appendChild(defaultOption);
data.pmc_nos.forEach(pmc => {
const option = document.createElement("option");
option.value = pmc;
option.textContent = pmc;
pmcDropdown.appendChild(option);
});
if (data.pmc_nos.length === 0) {
alert("No PMC Nos found for this subcontractor.");
}
})
.catch(error => {
console.error("Error fetching PMC Nos:", error);
alert("Failed to fetch PMC numbers.");
});
}
});
</script>
<script>
function calculateTDSAndTotal() {
const amount = parseFloat(document.getElementById("Payment_Amount").value) || 0;
const tdsPercent = parseFloat(document.getElementById("TDS_Percentage").value) || 0;
const tdsAmount = (amount * tdsPercent / 100).toFixed(2);
const totalAmount = (amount - tdsAmount).toFixed(2);
document.getElementById("TDS_Payment_Amount").value = tdsAmount;
document.getElementById("total_amount").value = totalAmount;
}
</script>
<script src="{{ url_for('static', filename='js/showSuccessAlert.js') }}"></script>
</body>
{% endblock %}

View File

@@ -0,0 +1,147 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Manage Purchases</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<div class="button-container">
<button id="addButton" class="action-button">Add Purchase</button>
<button id="displayButton" class="action-button">Display Purchases</button>
</div>
<!-- ADD PURCHASE FORM -->
<!-- ADD PURCHASE FORM -->
<div id="addForm" style="display: none;">
<h2>Add Purchase Entry</h2>
<form action="/add_purchase_order" method="POST">
<label for="purchase_date">Purchase Date:</label><br>
<input type="date" id="purchase_date" name="purchase_date" required><br><br>
<label for="supplier_name">Supplier Name:</label><br>
<input type="text" id="supplier_name" name="supplier_name" required><br><br>
<label for="Purchase Order No">Purchase Order No:</label><br>
<input type="text" name="purchase_order_no" required><br><br>
<label for="unit">Item Name:</label><br>
<input type="text" id="item_name" name="item_name" required><br><br>
<label for="quantity">Quantity:</label><br>
<input type="number" id="quantity" name="quantity" step="1" required><br><br>
<label for="unit">Unit:</label><br>
<input type="text" id="unit" name="unit" required><br><br>
<label for="rate">Rate:</label><br>
<input type="number" step="0.01" id="rate" name="rate" required><br><br>
<label for="amount">Amount:</label><br>
<input type="number" step="0.01" id="amount" name="amount" readonly><br><br>
<!-- GST input -->
<label for="gst_percent">GST %:</label><br>
<input type="number" step="0.01" id="gst_percent"><br><br>
<label>GST Amount:</label><br>
<input type="number" step="0.01" id="GST_Amount" name="GST_Amount" readonly><br><br>
<!-- TDS input -->
<label for="tds_percent">TDS %:</label><br>
<input type="number" step="0.01" id="tds_percent"><br><br>
<label>TDS:</label><br>
<input type="number" step="0.01" id="TDS" name="TDS" readonly><br><br>
<label>Final Amount:</label><br>
<input type="number" step="0.01" id="final_amount" name="final_amount" readonly><br><br>
<input type="submit" value="Submit">
</form>
</div>
<!-- DISPLAY TABLE -->
<div id="displayTable" style="display: none;">
<h2>Purchase List</h2>
<table border="1">
<tr>
<th>ID</th>
<th>Purchase Date</th>
<th>Supplier Name</th>
<th>Purchase Order No</th>
<th>Item Name</th>
<th>Quantity</th>
<th>Unit</th>
<th>Rate</th>
<th>Amount</th>
<th>GST Amount</th>
<th>TDS</th>
<th>Final Amount</th>
<th>Edit</th>
<th>Delete</th>
</tr>
{% for purchase in purchases %}
<tr>
<td>{{ purchase['purchase_id'] }}</td>
<td>{{ purchase['purchase_date'] }}</td>
<td>{{ purchase['supplier_name'] }}</td>
<td>{{ purchase['purchase_order_no'] }}</td>
<td>{{ purchase['item_name'] }}</td>
<td>{{ purchase['quantity'] }}</td>
<td>{{ purchase['unit'] }}</td>
<td>{{ purchase['rate'] }}</td>
<td>{{ purchase['amount'] }}</td>
<td>{{ purchase['GST_Amount'] }}</td>
<td>{{ purchase['TDS'] }}</td>
<td>{{ purchase['final_amount'] }}</td>
<td><a href="{{ url_for('update_purchase', id=purchase['purchase_id']) }}">Edit</a></td>
<td>
<form method="POST" action="{{ url_for('delete_purchase', id=purchase['purchase_id']) }}" style="display:inline;">
<button type="submit" onclick="return confirm('Are you sure?')">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</table>
</div>
<script>
$(document).ready(function() {
$('#addButton').click(function() {
$('#addForm').toggle();
});
$('#displayButton').click(function() {
$('#displayTable').toggle();
});
});
</script>
<script>
function calculateAmounts() {
const qty = parseFloat(document.getElementById('quantity').value) || 0;
const rate = parseFloat(document.getElementById('rate').value) || 0;
const gstPercent = parseFloat(document.getElementById('gst_percent').value) || 0;
const tdsPercent = parseFloat(document.getElementById('tds_percent').value) || 0;
const amount = qty * rate;
document.getElementById('amount').value = amount.toFixed(2);
const gstAmount = (gstPercent / 100) * amount;
document.getElementById('GST_Amount').value = gstAmount.toFixed(2);
const tdsAmount = (tdsPercent / 100) * amount;
document.getElementById('TDS').value = tdsAmount.toFixed(2);
const finalAmount = amount + gstAmount - tdsAmount;
document.getElementById('final_amount').value = finalAmount.toFixed(2);
}
// Attach events
document.getElementById('quantity').addEventListener('input', calculateAmounts);
document.getElementById('rate').addEventListener('input', calculateAmounts);
document.getElementById('gst_percent').addEventListener('input', calculateAmounts);
document.getElementById('tds_percent').addEventListener('input', calculateAmounts);
</script>
</body>
{% endblock %}

74
templates/add_state.html Normal file
View File

@@ -0,0 +1,74 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Add State</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/state.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<!-- Button Container to Center Buttons -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<h2>Add State</h2>
<form id="stateForm" method="POST">
<label>Enter State :</label>
<input type="text" id="state_Name" name="state_Name" placeholder="State Name" required>
<span id="stateMessage"></span>
<button type="submit" id="submitButton" disabled>Add State</button>
</form>
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Display States</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<script>
console.log(statedata)
</script>
<table id="sortableTable" border="1">
<tr>
<th>State ID</th>
<th class="sortable-header">
State Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th>Update State</th>
<th>Delete State</th>
</tr>
{% for state in statedata %}
<tr>
<td>{{ state[0] }}</td>
<td>{{ state[1]}}</td>
<td>
<a href="{{ url_for('state.editState', id=state[0]) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
<a href="{{ url_for('state.deleteState', id=state[0]) }}"
onclick="return confirm('Are you sure you want to delete this state?')">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
class="icon">
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,127 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<title>SubContractor</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/subcontractor.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<!-- Button Container to Center Buttons -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<div id="addForm" style="display: none;">
<h2>Add Sub-Contractor</h2>
<form name="subcontractorForm" method="POST">
<label>Enter Contractor Name :</label>
<input type="text" id="Contractor_Name" name="Contractor_Name" required onkeyup="validateInput()"><br>
<label>Address :</label>
<!-- <input type="text" name="Address" required>-->
<textarea id="Address" name="Address" required></textarea>
<br>
<label>Mobile No :</label>
<input type="text" id="Mobile_No" name="Mobile_No" required onkeyup="validateInput()" maxlength="10" placeholder="Ex - 9091012011">
<span class="error" id="mobileError" style="color: red;"></span><br>
<label>PAN No :</label>
<input type="text" id="PAN_No" name="PAN_No" required onkeyup="validateInput()" maxlength="10" placeholder="Ex - ABCDE1234F">
<span class="error" id="panError" style="color: red;"></span><br>
<label>Email :</label>
<input type="email" id="Email" name="Email" required onkeyup="validateInput()" placeholder="Ex - user@example.com">
<span class="error" id="emailError" style="color: red;"></span><br>
<label>Gender :</label>
<select name="Gender" required>
<option value="Male">Male</option>
<option value="Female">Female</option>
<option value="Other">Other</option>
</select><br>
<label>GST Registration Type :</label>
<input type="text" name="GST_Registration_Type" ><br>
<label>GST No :</label>
<input type="text" id="GST_No" name="GST_No" placeholder="Ex - 27AAACL5602N1ZE" ><br>
<label>Generated Password :</label>
<input type="text" id="Contractor_password" name="Contractor_password" >
<span class="error" id="passwordError" style="color: red;"></span><br>
<button type="submit" id="submitBtn" disabled>Submit</button>
</form>
</div>
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Display SubContractor</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<tr>
<th>Contractor ID</th>
<th class="sortable-header">Contractor Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th>Address</th>
<th>Mobile No</th>
<th>PAN No</th>
<th>Email</th>
<th>Gender</th>
<th>GST Registration Type</th>
<th class="sortable-header">GST No
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th>Update Contractor</th>
<th>Delete Contractor</th>
</tr>
{% for subc in subcontractor %}
<tr>
<td>{{ subc[0] }}</td>
<td>{{ subc[1] }}</td>
<td>{{ subc[2] }}</td>
<td>{{ subc[3] }}</td>
<td>{{ subc[4] }}</td>
<td>{{ subc[5] }}</td>
<td>{{ subc[6] }}</td>
<td>{{ subc[7] }}</td>
<td>{{ subc[8] }}</td>
<td>
<a href="{{ url_for('subcontractor.edit_subcontractor', id=subc[0]) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
{# <a href="{{ url_for('subcontractor.deleteSubContractor', id=subc[0]) }}"#}
{# onclick="return confirm('Are you sure you want to delete?')">#}
{# <img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"#}
{# class="icon">#}
{# </a>#}
</td>
</tr>
{% endfor %}
</table>
</div>
</body>
{% endblock %}

119
templates/add_village.html Normal file
View File

@@ -0,0 +1,119 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Village Management</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/village.js') }}"></script>
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
</head>
<body>
<!-- ===================== BUTTONS ===================== -->
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<!-- ===================== ADD FORM ===================== -->
<div id="addForm" style="display: none;">
<div class="container">
<div class="form-block">
<h2>Add a New Village</h2>
<form id="villageForm" method="POST">
<label for="state_Id">State:</label>
<select id="state_Id" name="state_Id" required>
<option value="" disabled selected>Select State</option>
{% for state in states %}
<option value="{{ state[0] }}">{{ state[1] }}</option>
{% endfor %}
</select>
<label for="district_Id">District:</label>
<select id="district_Id" name="district_Id" required disabled>
<option value="" disabled selected>Select District</option>
</select>
<label for="block_Id">Block:</label>
<select id="block_Id" name="block_Id" required disabled>
<option value="" disabled selected>Select Block</option>
</select>
<label for="Village_Name">Village Name:</label>
<input type="text" id="Village_Name" name="Village_Name" placeholder="Enter Village Name" required>
<span id="villageMessage"></span>
<button type="submit" id="submitVillage" disabled>Add Village</button>
</form>
</div>
</div>
</div>
<!-- ===================== DISPLAY TABLE ===================== -->
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>Display Villages</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<tr>
<th>Village Sr No</th>
<th class="sortable-header">
Village Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th class="sortable-header">
Block Name
<span class="sort-buttons">
<span class="sort-asc">⬆️</span>
<span class="sort-desc">⬇️</span>
</span>
</th>
<th>Update</th>
<th>Delete</th>
</tr>
{% for village in villages %}
<tr>
<td>{{ village[0] }}</td>
<td>{{ village[1] }}</td>
<td>{{ village[2] }}</td>
<td>
<a href="{{ url_for('village.edit_village', village_id=village[0]) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" class="icon">
</a>
</td>
<td>
<a href="{{ url_for('village.delete_village', village_id=village[0]) }}"
onclick="return confirm('Are you sure you want to delete this village?');">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" class="icon">
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
<!-- ===================== BROWSER ALERT FLASH ===================== -->
<script>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
alert({{ message|tojson|safe }});
{% endfor %}
{% endif %}
{% endwith %}
</script>
</body>
{% endblock %}

View File

@@ -0,0 +1,137 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Manage Work Order</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
</head>
<body>
<div class="button-container">
<button id="addButton" class="action-button">Add</button>
<button id="displayButton" class="action-button">Display</button>
</div>
<form action="/submit_work_order" method="POST">
<div id="addForm" style="display: none;">
<h2>Create Work Order</h2>
<!-- Subcontractor Dropdown -->
<label for="subcontractor_id">Select Vender Name:</label><br>
<select name="vendor_name" id="vendor_name" required>
<option value="">-- Select Subcontractor --</option>
{% for sc in subcontractor %}
<option value="{{ sc.Contractor_Name }}">{{ sc.Contractor_Name }}</option>
{% endfor %}
</select><br><br>
<label for="work_order_number">Work Order Number:</label><br>
<input type="text" id="work_order_number" name="work_order_number" required><br><br>
<label for="work_order_amount">Work Order Amount</label><br>
<input type="number" step="0.01" id="work_order_amount" name="work_order_amount" required><br><br>
<label for="gst_amount">GST Amount:</label><br>
<input type="number" step="0.01" id="gst_amount" name="gst_amount" required><br><br>
<label for="tds_amount">TDS Amount:</label><br>
<input type="number" step="0.01" id="tds_amount" name="tds_amount" required><br><br>
<label for="security_deposit">Security Deposit:</label><br>
<input type="number" step="0.01" id="security_deposit" name="security_deposit" required><br><br>
<label for="sd_against_gst">SD Against GST:</label><br>
<input type="number" step="0.01" id="sd_against_gst" name="sd_against_gst" required><br><br>
<label for="final_total">Final Total:</label><br>
<input type="number" step="0.01" id="final_total" name="final_total" readonly><br><br>
<input type="submit" value="Submit">
</div>
</form>
{# display data #}
<div id="addTable" style="display: none;">
{# <div class="search-container">#}
{# <h2>Hold Type List</h2>#}
{# <input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">#}
{# </div>#}
<table id="sortableTable" border="1">
<tr>
<th>ID</th>
<th class="sortable-header">
Vender Name
{# <span class="sort-buttons">#}
{# <span class="sort-asc">⬆️</span>#}
{# <span class="sort-desc">⬇️</span>#}
{# </span>#}
</th>
<th>Work Order Type</th>
<th>Work Order Amount</th>
<th>BOQ Amount</th>
<th>Work Done Percentage</th>
<th>Update</th>
<th>Delete</th>
</tr>
{% for htd in wo %}
<tr>
<td>{{ htd.work_order_id }}</td>
<td>{{ htd['vendor_name'] }}</td>
<td>{{ htd['work_order_type'] }}</td>
<td>{{ htd['work_order_amount'] }}</td>
<td>{{ htd['boq_amount'] }}</td>
<td>{{ htd['work_done_percentage'] }}</td>
<td>{{ htd['work_order_number'] }}</td>
<td>{{ htd['gst_amount'] }}</td>
<td>{{ htd['tds_amount'] }}</td>
<td>{{ htd[''] }}</td>
<td><a href="{{ url_for('update_work_order', id=htd['work_order_id']) }}">Edit</a></td>
<td>
<form action="{{ url_for('delete_work_order', id=htd['work_order_id']) }}" method="POST" style="display:inline;">
<button type="submit" onclick="return confirm('Are you sure you want to delete this work order?');" class="delete-button">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</table>
<a href="/">Back to Dashboard</a>
</div>
<script>
$(document).ready(function () {
$('#displayButton').click(function () {
$('#addTable').toggle();
});
$('#addButton').click(function () {
$('#addForm').toggle();
// Bind input event handlers ONLY after form is shown
setTimeout(function () {
["work_order_amount", "gst_amount", "tds_amount", "security_deposit", "sd_against_gst"].forEach(function (id) {
document.getElementById(id).addEventListener("input", calculateFinalTotal);
});
}, 100); // Delay ensures elements are available in DOM
});
function calculateFinalTotal() {
const workOrderAmount = parseFloat(document.getElementById("work_order_amount").value) || 0;
const gstAmount = parseFloat(document.getElementById("gst_amount").value) || 0;
const tdsAmount = parseFloat(document.getElementById("tds_amount").value) || 0;
const securityDeposit = parseFloat(document.getElementById("security_deposit").value) || 0;
const sdAgainstGst = parseFloat(document.getElementById("sd_against_gst").value) || 0;
const finalTotal = workOrderAmount + gstAmount - (tdsAmount + securityDeposit + sdAgainstGst);
document.getElementById("final_total").value = finalTotal.toFixed(2);
}
});
</script>
</body>
{% endblock %}

View File

@@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Profile</title>
</head>
<body>
<div class="container">
<h2>Admin Profile</h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<p class="{{ category }}">{{ message }}</p>
{% endfor %}
{% endif %}
{% endwith %}
<form action="{{ url_for('admin_profile') }}" method="POST" enctype="multipart/form-data">
<div class="profile-picture">
<!-- <img id="profile-pic-preview" src="{{ url_for('static', filename='images/' + (profile['profile_picture'] or 'default.png')) }}" alt="Profile Picture" />-->
<input type="file" name="profile_picture" id="profile-pic-input" onchange="previewImage()">
</div>
<label>Username:</label>
<input type="text" name="username" value="{{ profile['user_name'] }}" required>
<label>Phone:</label>
<input type="tel" name="phone" value="{{ profile['mobile'] }}" required>
<label>Email:</label>
<input type="email" name="email" value="{{ profile['email'] }}" required>
<label>New Password:</label>
<input type="password" name="new_password">
<label>Current Password (required for password change):</label>
<input type="password" name="current_password">
<button type="submit">Save Changes</button>
</form>
</div>
<script>
function previewImage() {
const file = document.getElementById('profile-pic-input').files[0];
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('profile-pic-preview').src = e.target.result;
};
if (file) {
reader.readAsDataURL(file);
}
}
</script>
</body>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show More