changes of reports and pmc reports by pankaj-dev

This commit is contained in:
2026-03-23 10:37:48 +05:30
parent 9bc8c1dd90
commit c8d5a9c37d
110 changed files with 20849 additions and 0 deletions

9
v-2/.env Normal file
View File

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

7
v-2/.gitignore vendored Normal file
View File

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

18
v-2/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
v-2/README.md Normal file
View File

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

7989
v-2/activity.log Normal file

File diff suppressed because it is too large Load Diff

114
v-2/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
v-2/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,388 @@
import os
import ast
import re
from flask_login import login_required
import openpyxl
from flask import Blueprint, request, render_template, redirect, url_for, jsonify, current_app
from flask_login import current_user
from model.Log import LogHelper
import config
from model.FolderAndFile import FolderAndFile
excel_bp = Blueprint('excel', __name__)
# Default folder in case config not set
# DEFAULT_UPLOAD_FOLDER = 'uploads'
# def get_upload_folder():
# """Returns the upload folder from Flask config or default, ensures it exists."""
# folder = current_app.config.get('UPLOAD_FOLDER', DEFAULT_UPLOAD_FOLDER)
# if not os.path.exists(folder):
# os.makedirs(folder)
# return folder
# ---------------- Upload Excel File ----------------
@excel_bp.route('/upload_excel_file', methods=['GET', 'POST'])
@login_required
def upload():
if request.method == 'POST':
file = request.files.get('file')
if file and file.filename.endswith('.xlsx'):
# upload_folder = get_upload_folder()
# filepath = os.path.join(upload_folder, file.filename)
filepath =FolderAndFile.get_upload_path(file.filename)
file.save(filepath)
LogHelper.log_action(
"Upload Excel File",
f"User {current_user.id} Upload Excel File '{file.filename}'"
)
return redirect(url_for('excel.show_table', filename=file.filename))
return render_template('uploadExcelFile.html')
# ---------------- Show Excel Table ----------------
@excel_bp.route('/show_table/<filename>')
def show_table(filename):
global data
data = []
# filepath = os.path.join(get_upload_folder(), filename)
filepath = FolderAndFile.get_upload_path(filename)
wb = openpyxl.load_workbook(filepath, data_only=True)
sheet = wb.active
file_info = {
"Subcontractor": sheet.cell(row=1, column=2).value,
"State": sheet.cell(row=2, column=2).value,
"District": sheet.cell(row=3, column=2).value,
"Block": sheet.cell(row=4, column=2).value,
}
errors = []
subcontractor_data = None
state_data = None
district_data = None
block_data = None
connection = config.get_db_connection()
if connection:
try:
cursor = connection.cursor(dictionary=True)
cursor.callproc('GetStateByName', [file_info['State']])
for result in cursor.stored_results():
state_data = result.fetchone()
if not state_data:
errors.append(f"State '{file_info['State']}' is not valid. Please add it.")
if state_data:
cursor.callproc('GetDistrictByNameAndStates', [file_info['District'], state_data['State_ID']])
for result in cursor.stored_results():
district_data = result.fetchone()
if not district_data:
errors.append(f"District '{file_info['District']}' is not valid under state '{file_info['State']}'.")
if district_data:
cursor.callproc('GetBlockByNameAndDistricts', [file_info['Block'], district_data['District_ID']])
for result in cursor.stored_results():
block_data = result.fetchone()
if not block_data:
errors.append(f"Block '{file_info['Block']}' is not valid under district '{file_info['District']}'.")
cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']])
for result in cursor.stored_results():
subcontractor_data = result.fetchone()
if not subcontractor_data:
cursor.callproc('InsertSubcontractor', [file_info['Subcontractor']])
connection.commit()
cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']])
for result in cursor.stored_results():
subcontractor_data = result.fetchone()
cursor.callproc("GetAllHoldTypes")
hold_types_data = []
for ht in cursor.stored_results():
hold_types_data = ht.fetchall()
hold_types_lookup = {row['hold_type'].lower(): row['hold_type_id'] for row in hold_types_data if row['hold_type']}
cursor.close()
except Exception as e:
print(f"Database error: {e}")
return f"Database operation failed: {e}", 500
finally:
connection.close()
variables = {}
hold_columns = []
hold_counter = 0
for j in range(1, sheet.max_column + 1):
col_value = sheet.cell(row=5, column=j).value
if col_value:
variables[col_value] = j
if 'hold' in str(col_value).lower():
hold_counter += 1
hold_type_key = str(col_value).lower().strip()
hold_type_id = hold_types_lookup.get(hold_type_key)
hold_columns.append({
'column_name': col_value,
'column_number': j,
'hold_type_id': hold_type_id
})
for i in range(6, sheet.max_row + 1):
row_data = {}
if sheet.cell(row=i, column=1).value:
row_data["Row Number"] = i
for var_name, col_num in variables.items():
row_data[var_name] = sheet.cell(row=i, column=col_num).value
if sum(1 for value in row_data.values() if value) >= 4:
data.append(row_data)
for hold in hold_columns:
if hold['hold_type_id']:
print(f" if Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")
else:
errors.append(f"Hold Type not added ! Column name '{hold['column_name']}'.")
print(f" else Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")
return render_template(
'show_excel_file.html',
file_info=file_info,
variables=variables,
data=data,
subcontractor_data=subcontractor_data,
state_data=state_data,
district_data=district_data,
block_data=block_data,
errors=errors,
hold_columns=hold_columns,
hold_counter=hold_counter
)
# save Excel data
@excel_bp.route('/save_data', methods=['POST'])
def save_data():
# Extract form data
subcontractor_id = request.form.get("subcontractor_data")
state_id = request.form.get("state_data")
district_id = request.form.get("district_data")
block_id = request.form.get("block_data")
variables = request.form.getlist('variables[]')
hold_columns = request.form.get("hold_columns")
hold_counter = request.form.get("hold_counter")
if not data:
return jsonify({"error": "No data provided to save"}), 400
if data:
connection = config.get_db_connection()
cursor = connection.cursor()
try:
for entry in data:
save_data = {
"PMC_No": entry.get("PMC_No"),
"Invoice_Details": entry.get("Invoice_Details", ''),
"Work_Type": 'none',
"Invoice_Date": entry.get("Invoice_Date").strftime('%Y-%m-%d') if entry.get(
"Invoice_Date") else None,
"Invoice_No": entry.get("Invoice_No", ''),
"Basic_Amount": entry.get("Basic_Amount", 0.00),
"Debit_Amount": entry.get("Debit_Amount", 0.00),
"After_Debit_Amount": entry.get("After_Debit_Amount", 0.00),
"Amount": entry.get("Amount", 0.00),
"GST_Amount": entry.get("GST_Amount", 0.00),
"TDS_Amount": entry.get("TDS_Amount", 0.00),
"SD_Amount": entry.get("SD_Amount", 0.00),
"On_Commission": entry.get("On_Commission", 0.00),
"Hydro_Testing": entry.get("Hydro_Testing", 0.00),
"Hold_Amount": 0,
"GST_SD_Amount": entry.get("GST_SD_Amount", 0.00),
"Final_Amount": entry.get("Final_Amount", 0.00),
"Payment_Amount": entry.get("Payment_Amount", 0.00),
"Total_Amount": entry.get("Total_Amount", 0.00),
"TDS_Payment_Amount": entry.get("TDS_Payment_Amount", 0.00),
"UTR": entry.get("UTR", ''),
}
village_name, work_type = None, None
village_id = 0
LogHelper.log_action("Data saved", f"User {current_user.id} Data saved'{ village_name}'")
PMC_No = save_data.get('PMC_No')
Invoice_Details = save_data.get('Invoice_Details')
Invoice_Date = save_data.get('Invoice_Date')
Invoice_No = save_data.get('Invoice_No')
Basic_Amount = save_data.get('Basic_Amount')
Debit_Amount = save_data.get('Debit_Amount')
After_Debit_Amount = save_data.get('After_Debit_Amount')
Amount = save_data.get('Amount')
GST_Amount = save_data.get('GST_Amount')
TDS_Amount = save_data.get('TDS_Amount')
SD_Amount = save_data.get('SD_Amount')
On_Commission = save_data.get('On_Commission')
Hydro_Testing = save_data.get('Hydro_Testing')
GST_SD_Amount = save_data.get('GST_SD_Amount')
Final_Amount = save_data.get('Final_Amount')
Payment_Amount = save_data.get('Payment_Amount')
Total_Amount = save_data.get('Total_Amount')
TDS_Payment_Amount = save_data.get('TDS_Payment_Amount')
UTR = save_data.get('UTR')
if Invoice_Details:
words = Invoice_Details.lower().split()
if 'village' in words:
village_pos = words.index('village')
village_name = " ".join(words[:village_pos])
if 'work' in words:
work_pos = words.index('work')
if village_name:
work_type = " ".join(words[village_pos + 1:work_pos + 1])
else:
work_type = " ".join(words[:work_pos + 1])
if Invoice_Details and 'village' in Invoice_Details.lower() and 'work' in Invoice_Details.lower():
print("village_name ::", village_name, "|| work_type ::", work_type)
if block_id and village_name:
village_id = None
cursor.callproc("GetVillageId", (block_id, village_name))
for result in cursor.stored_results():
result = result.fetchone()
village_id = result[0] if result else None
if not village_id:
cursor.callproc("SaveVillage", (village_name, block_id))
cursor.callproc("GetVillageId", (block_id, village_name))
for result in cursor.stored_results():
result = result.fetchone()
village_id = result[0] if result else None
print("village_id :", village_id)
print("block_id :", block_id)
print("invoice :", PMC_No, village_id, work_type, Invoice_Details, Invoice_Date, Invoice_No,
Basic_Amount, Debit_Amount, After_Debit_Amount, Amount, GST_Amount, TDS_Amount,
SD_Amount, On_Commission, Hydro_Testing, GST_SD_Amount, Final_Amount)
args = (
PMC_No, village_id, work_type, Invoice_Details, Invoice_Date, Invoice_No,
Basic_Amount, Debit_Amount, After_Debit_Amount, Amount, GST_Amount, TDS_Amount,
SD_Amount, On_Commission, Hydro_Testing, GST_SD_Amount, Final_Amount,
subcontractor_id, 0
)
print("All invoice Details ",args)
results = cursor.callproc('SaveInvoice', args)
invoice_id = results[-1]
print("invoice id from the excel ", invoice_id)
if isinstance(hold_columns, str):
hold_columns = ast.literal_eval(hold_columns)
if isinstance(hold_columns, list) and all(isinstance(hold, dict) for hold in hold_columns):
for hold in hold_columns:
print(f"Processing hold: {hold}")
hold_column_name = hold.get('column_name') # Get column name
hold_type_id = hold.get('hold_type_id') # Get hold_type_id
if hold_column_name:
hold_amount = entry.get(
hold_column_name) # Get the value for that specific hold column
if hold_amount is not None:
print(f"Processing hold type: {hold_column_name}, Hold Amount: {hold_amount}")
hold_join_data = {
"Contractor_Id": subcontractor_id,
"Invoice_Id": invoice_id,
"hold_type_id": hold_type_id,
"hold_amount": hold_amount
}
cursor.callproc('InsertHoldJoinData', [
hold_join_data['Contractor_Id'], hold_join_data['Invoice_Id'],
hold_join_data['hold_type_id'], hold_join_data['hold_amount']
])
connection.commit()
print(f"Inserted hold join data: {hold_join_data}")
else:
print(f"Invalid hold entry: {hold}")
else:
print("Hold columns data is not a valid list of dictionaries.")
#---------------------------------------------Credit Note---------------------------------------------------------------------------
elif any(keyword in Invoice_Details.lower() for keyword in ['credit note','logging report']):
print("Credit note found:", PMC_No, Invoice_No, Basic_Amount, Debit_Amount, Final_Amount,
After_Debit_Amount, GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR, Invoice_No)
cursor.callproc(
'AddCreditNoteFromExcel',
[
PMC_No, Invoice_Details, Basic_Amount, Debit_Amount, After_Debit_Amount,
GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR,
subcontractor_id, Invoice_No
]
)
#-----------------------------------------------Hold Amount----------------------------------------------------------------------
# Step 1: Normalize Invoice_Details: lowercase, trim, remove extra spaces
normalized_details = re.sub(r'\s+', ' ', Invoice_Details.strip()).lower()
# Step 2: Define lowercase keywords
keywords = [
'excess hold',
'ht',
'hold release amount',
'dpr excess hold amount',
'excess hold amount',
'Multi to Single layer bill',
'hold amount',
'logging report'
]
# Step 3: Matching condition
if any(kw in normalized_details for kw in keywords):
print("✅ Match found. Inserting hold release for:", Invoice_Details)
cursor.callproc(
'AddHoldReleaseFromExcel',
[PMC_No, Invoice_No, Invoice_Details, Basic_Amount, Final_Amount, UTR, subcontractor_id]
)
connection.commit()
print("✅ Hold release inserted for:", PMC_No, Invoice_Details)
#------------------------------------------------------------------------------------------------------------------
elif Invoice_Details and any(
keyword in Invoice_Details.lower() for keyword in ['gst', 'release', 'note']):
print("Gst rels :", PMC_No, Invoice_No, Basic_Amount, Final_Amount,Total_Amount,UTR, subcontractor_id)
cursor.callproc(
'AddGSTReleaseFromExcel',
[PMC_No, Invoice_No, Basic_Amount, Final_Amount, Total_Amount, UTR, subcontractor_id]
)
if PMC_No and Total_Amount and UTR:
print("Payment :", PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR )
cursor.callproc("SavePayment",(PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR ))
if not village_id:
village_id = None
cursor.callproc('InsertOrUpdateInPayment', (
PMC_No,
village_id,
work_type,
Invoice_Details,
Invoice_Date,
Invoice_No,
Basic_Amount,
Debit_Amount,
After_Debit_Amount,
Amount,
GST_Amount,
TDS_Amount,
SD_Amount,
On_Commission,
Hydro_Testing,
0,
GST_SD_Amount,
Final_Amount,
Payment_Amount,
TDS_Payment_Amount,
Total_Amount,
UTR,
subcontractor_id
))
connection.commit()
return jsonify({"success": "Data saved successfully!"}), 200
except Exception as e:
connection.rollback()
return jsonify({"error": f"An unexpected error occurred: {e}"}), 500
finally:
cursor.close()
connection.close()
return render_template('index.html')
# ---------------------- Report --------------------------------

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,98 @@
# controllers/invoice_controller.py
from flask import Blueprint, request, jsonify, render_template
from flask_login import login_required, current_user
from model.Invoice import *
from model.Log import LogHelper
invoice_bp = Blueprint('invoice', __name__)
# -------------------------------- Add Invoice ---------------------------------
@invoice_bp.route('/add_invoice', methods=['GET', 'POST'])
@login_required
def add_invoice():
if request.method == 'POST':
try:
village_name = request.form.get('village')
village_result = get_village_id(village_name)
if not village_result:
return jsonify({"status": "error", "message": f"Village '{village_name}' not found"}), 400
village_id = village_result['Village_Id']
data = request.form
invoice_id = insert_invoice(data, village_id)
assign_subcontractor(data, village_id)
insert_hold_types(data, invoice_id)
LogHelper.log_action("Add invoice", f"User {current_user.id} Added invoice '{data.get('pmc_no')}'")
return jsonify({"status": "success", "message": "Invoice added successfully"}), 201
except Exception as e:
return jsonify({"status": "error", "message": str(e)}), 500
invoices = get_all_invoice_details()
villages = get_all_villages()
return render_template('add_invoice.html', invoices=invoices, villages=villages)
# ------------------- Search Subcontractor -------------------
@invoice_bp.route('/search_subcontractor', methods=['POST'])
@login_required
def search_subcontractor():
sub_query = request.form.get("query")
results = search_contractors(sub_query)
if not results:
return "<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

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,37 @@
from flask import Blueprint, render_template, send_from_directory
from flask_login import login_required, current_user
from model.PmcReport import PmcReport
pmc_report_bp = Blueprint("pmc_report", __name__)
@pmc_report_bp.route("/pmc_report/<pmc_no>")
@login_required
def pmc_report(pmc_no):
data = PmcReport.get_pmc_report(pmc_no)
if not data:
return "No PMC found with this number", 404
return render_template(
"pmc_report.html",
info=data["info"],
invoices=data["invoices"],
hold_types=data["hold_types"],
gst_rel=data["gst_rel"],
payments=data["payments"],
credit_note=data["credit_note"],
hold_release=data["hold_release"],
total=data["total"]
)
@pmc_report_bp.route("/download_pmc_report/<pmc_no>")
@login_required
def download_pmc_report(pmc_no):
result = PmcReport.download_pmc_report(pmc_no)
if not result:
return "No contractor found for this PMC No", 404
output_folder, file_name = result
return send_from_directory(output_folder, file_name, as_attachment=True)

View File

@@ -0,0 +1,193 @@
from flask import Blueprint, render_template, request, jsonify, send_file
from flask_login import login_required, current_user
from model.Report import ReportHelper
from model.Log import LogHelper
import config
import os
import openpyxl
from openpyxl.styles import Font
from model.ContractorInfo import ContractorInfo
from model.FolderAndFile import FolderAndFile
report_bp = Blueprint("report", __name__)
# ---------------- Report Page ----------------
@report_bp.route("/report")
@login_required
def report_page():
return render_template("/report.html")
# ---------------- Search Contractor ----------------
@report_bp.route("/search_contractor", methods=["POST"])
@login_required
def search_contractor():
subcontractor_name = request.form.get("subcontractor_name")
LogHelper.log_action(
"Search Contractor",
f"User {current_user.id} searched contractor '{subcontractor_name}'"
)
data = ReportHelper.search_contractor(request)
return jsonify(data)
# ---------------- Contractor Report ----------------
@report_bp.route('/contractor_report/<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
)
@report_bp.route('/download_report/<int:contractor_id>')
@login_required
def download_report(contractor_id):
return ReportHelper().download_report(contractor_id=contractor_id)
# @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)
# filename = f"Contractor_Report_{contInfo.get('Contractor_Name')}.xlsx"
# output_file = FolderAndFile.get_download_path(filename)
# workbook.save(output_file)
# return send_file(output_file, as_attachment=True)
# except Exception as e:
# return str(e)

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,117 @@
from flask import Blueprint, render_template, request, redirect, url_for, jsonify
from flask_login import login_required
from model.Subcontractor import Subcontractor
subcontractor_bp = Blueprint('subcontractor', __name__)
# ----------------------------------------------------------
# Helpers (unchanged)
# ----------------------------------------------------------
class HtmlHelper:
@staticmethod
def json_response(data, status=200):
return jsonify(data), status
class ResponseHandler:
@staticmethod
def fetch_failure(entity):
return {"status": "error", "message": f"Failed to fetch {entity}"}
@staticmethod
def add_failure(entity):
return {"status": "error", "message": f"Failed to add {entity}"}
@staticmethod
def update_failure(entity):
return {"status": "error", "message": f"Failed to update {entity}"}
@staticmethod
def delete_failure(entity):
return {"status": "error", "message": f"Failed to delete {entity}"}
# ----------------------------------------------------------
# LIST + ADD
# ----------------------------------------------------------
@subcontractor_bp.route('/subcontractor', methods=['GET', 'POST'])
@login_required
def subcontract():
sub = Subcontractor()
# ---------------- GET ----------------
if request.method == 'GET':
subcontractor = sub.GetAllSubcontractors(request)
if not sub.isSuccess:
return HtmlHelper.json_response(
ResponseHandler.fetch_failure("Subcontractor"), 500
)
return render_template('add_subcontractor.html', subcontractor=subcontractor)
# ---------------- POST (ADD) ----------------
if request.method == 'POST':
sub.AddSubcontractor(request)
if not sub.isSuccess:
return HtmlHelper.json_response(
ResponseHandler.add_failure("Subcontractor"), 500
)
# Reload list after insert
subcontractor = sub.GetAllSubcontractors(request)
return render_template('add_subcontractor.html', subcontractor=subcontractor)
# ----------------------------------------------------------
# EDIT
# ----------------------------------------------------------
@subcontractor_bp.route('/edit_subcontractor/<int:id>', methods=['GET', 'POST'])
@login_required
def edit_subcontractor(id):
sub = Subcontractor()
# Fetch data
subcontractor = sub.GetSubcontractorByID(id)
if not subcontractor:
return HtmlHelper.json_response(
ResponseHandler.fetch_failure("Subcontractor"), 404
)
# ---------------- POST (UPDATE) ----------------
if request.method == 'POST':
sub.EditSubcontractor(request, id)
if not sub.isSuccess:
return HtmlHelper.json_response(
ResponseHandler.update_failure("Subcontractor"), 500
)
return redirect(url_for('subcontractor.subcontract'))
return render_template('edit_subcontractor.html', subcontractor=subcontractor)
# ----------------------------------------------------------
# DELETE
# ----------------------------------------------------------
@subcontractor_bp.route('/deleteSubContractor/<int:id>', methods=['GET', 'POST'])
@login_required
def deleteSubContractor(id):
sub = Subcontractor()
sub.DeleteSubcontractor(request, id)
if not sub.isSuccess:
return HtmlHelper.json_response(
ResponseHandler.delete_failure("Subcontractor"), 500
)
return redirect(url_for('subcontractor.subcontract'))

View File

@@ -0,0 +1,167 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
from flask_login import login_required
import config
from model.Village import Village
from model.State import State
# Create Blueprint
village_bp = Blueprint('village', __name__)
# ------------------------- Add Village -------------------------
@village_bp.route('/add_village', methods=['GET', 'POST'])
@login_required
def add_village():
village = Village()
if request.method == 'POST':
village.AddVillage(request=request)
return village.resultMessage
state = State()
states = state.GetAllStates(request=request)
villages = village.GetAllVillages(request=request)
return render_template(
'add_village.html',
states=states,
villages=villages
)
# ------------------------- Fetch Districts -------------------------
@village_bp.route('/get_districts/<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(request=request, id=village_id)
blocks = village.GetAllBlocks(request=request)
return render_template(
'edit_village.html',
village_data=village_data,
blocks=blocks
)
else:
village_data = village.GetVillageByID(request=request, id=village_id)
if not village.isSuccess:
flash(village.resultMessage, "error")
return redirect(url_for('village.add_village'))
blocks = village.GetAllBlocks(request=request)
if village_data is None:
village_data = []
if blocks is None:
blocks = []
return render_template(
'edit_village.html',
village_data=village_data,
blocks=blocks
)

35
v-2/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:

0
v-2/logs/activity.log Normal file
View File

74
v-2/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
v-2/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
v-2/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
v-2/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

View File

@@ -0,0 +1,72 @@
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)
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()

100
v-2/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)

View File

@@ -0,0 +1,39 @@
import os
from flask import current_app
class FolderAndFile:
# -----------------------------
# BASE FOLDER METHODS
# -----------------------------
@staticmethod
def get_download_folder():
folder = os.path.join(current_app.root_path, "static", "downloads")
if not os.path.exists(folder):
os.makedirs(folder)
os.makedirs(folder, exist_ok=True)
return folder
@staticmethod
def get_upload_folder():
folder = os.path.join(current_app.root_path, "static", "uploads")
if not os.path.exists(folder):
os.makedirs(folder)
os.makedirs(folder, exist_ok=True)
return folder
# -----------------------------
# FILE PATH METHODS
# -----------------------------
@staticmethod
def get_download_path(filename):
return os.path.join(FolderAndFile.get_download_folder(), filename)
@staticmethod
def get_upload_path(filename):
return os.path.join(FolderAndFile.get_upload_folder(), filename)

55
v-2/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
v-2/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

379
v-2/model/Invoice.py Normal file
View File

@@ -0,0 +1,379 @@
import config
import mysql.connector
# ------------------- Helper -------------------
def clear_results(cursor):
for r in cursor.stored_results():
r.fetchall()
# ------------------- Get Village Id -------------------
def get_village_id(village_name):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc("GetVillageIdByName", (village_name,))
village_result = None
for rs in cursor.stored_results():
village_result = rs.fetchone()
cursor.close()
connection.close()
return village_result
# ------------------- Insert Invoice -------------------
def insert_invoice(data, village_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
# 1. Insert Invoice
cursor.callproc('InsertInvoice', [
data.get('pmc_no'),
village_id,
data.get('work_type'),
data.get('invoice_details'),
data.get('invoice_date'),
data.get('invoice_no'),
float(data.get('basic_amount') or 0),
float(data.get('debit_amount') or 0),
float(data.get('after_debit_amount') or 0),
float(data.get('amount') or 0),
float(data.get('gst_amount') or 0),
float(data.get('tds_amount') or 0),
float(data.get('sd_amount') or 0),
float(data.get('on_commission') or 0),
float(data.get('hydro_testing') or 0),
float(data.get('gst_sd_amount') or 0),
float(data.get('final_amount') or 0)
])
invoice_id = None
for result in cursor.stored_results():
row = result.fetchone()
if row:
invoice_id = row.get('invoice_id')
if not invoice_id:
raise Exception("Invoice ID not returned")
# 2. Insert Inpayment
cursor.callproc('InsertInpayment', [
data.get('pmc_no'),
village_id,
data.get('work_type'),
data.get('invoice_details'),
data.get('invoice_date'),
data.get('invoice_no'),
float(data.get('basic_amount') or 0),
float(data.get('debit_amount') or 0),
float(data.get('after_debit_amount') or 0),
float(data.get('amount') or 0),
float(data.get('gst_amount') or 0),
float(data.get('tds_amount') or 0),
float(data.get('sd_amount') or 0),
float(data.get('on_commission') or 0),
float(data.get('hydro_testing') or 0),
float(data.get('gst_sd_amount') or 0),
float(data.get('final_amount') or 0),
data.get('subcontractor_id')
])
clear_results(cursor)
connection.commit()
return invoice_id
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()
# ------------------- Assign Subcontractor -------------------
def assign_subcontractor(data, village_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
cursor.callproc('AssignSubcontractor', [
data.get('pmc_no'),
data.get('subcontractor_id'),
village_id
])
clear_results(cursor)
connection.commit()
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()
# ------------------- Insert Hold Types -------------------
def insert_hold_types(data, invoice_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
hold_types = data.getlist('hold_type[]')
hold_amounts = data.getlist('hold_amount[]')
for hold_type, hold_amount in zip(hold_types, hold_amounts):
if not hold_type:
continue
cursor.callproc('GetHoldTypeIdByName', [hold_type])
hold_type_result = None
for result in cursor.stored_results():
hold_type_result = result.fetchone()
if not hold_type_result:
cursor.callproc('InsertHoldType', [hold_type, 0])
cursor.execute("SELECT @_InsertHoldType_1")
hold_type_id = cursor.fetchone()[0]
else:
hold_type_id = hold_type_result['hold_type_id']
hold_amount = float(hold_amount or 0)
cursor.callproc('InsertInvoiceSubcontractorHold', [
data.get('subcontractor_id'),
invoice_id,
hold_type_id,
hold_amount
])
clear_results(cursor)
connection.commit()
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()
# ------------------- Get All Invoices -------------------
def get_all_invoice_details():
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc('GetAllInvoiceDetails')
invoices = []
for result in cursor.stored_results():
invoices = result.fetchall()
cursor.close()
connection.close()
return invoices
# ------------------- Get All Villages -------------------
def get_all_villages():
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc("GetAllVillages")
villages = []
for result in cursor.stored_results():
villages = result.fetchall()
cursor.close()
connection.close()
return villages
# ------------------- Search Contractors -------------------
def search_contractors(sub_query):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc('SearchContractorsByName', [sub_query])
results = []
for result in cursor.stored_results():
results = result.fetchall()
cursor.close()
connection.close()
return results
# ------------------- Get All Hold Types -------------------
def get_all_hold_types():
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc("GetAllHoldTypes")
hold_types = []
for result in cursor.stored_results():
hold_types = result.fetchall()
cursor.close()
connection.close()
return hold_types
# ------------------- Get Invoice By Id -------------------
def get_invoice_by_id(invoice_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
cursor.callproc('GetInvoiceDetailsById', [invoice_id])
invoice = None
for result in cursor.stored_results():
invoice = result.fetchone()
cursor.callproc('GetHoldAmountsByInvoiceId', [invoice_id])
hold_amounts = []
for result in cursor.stored_results():
hold_amounts = result.fetchall()
if invoice:
invoice["hold_amounts"] = hold_amounts
cursor.close()
connection.close()
return invoice
# ------------------- Update Invoice -------------------
def update_invoice(data, invoice_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
cursor.callproc("GetVillageIdByName", (data.get('village'),))
village = None
for rs in cursor.stored_results():
village = rs.fetchone()
village_id = village['Village_Id']
numeric = [
float(data.get('basic_amount') or 0),
float(data.get('debit_amount') or 0),
float(data.get('after_debit_amount') or 0),
float(data.get('amount') or 0),
float(data.get('gst_amount') or 0),
float(data.get('tds_amount') or 0),
float(data.get('sd_amount') or 0),
float(data.get('on_commission') or 0),
float(data.get('hydro_testing') or 0),
float(data.get('gst_sd_amount') or 0),
float(data.get('final_amount') or 0),
]
cursor.callproc('UpdateInvoice', [
data.get('pmc_no'),
village_id,
data.get('work_type'),
data.get('invoice_details'),
data.get('invoice_date'),
data.get('invoice_no'),
*numeric,
invoice_id
])
clear_results(cursor)
connection.commit()
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()
# ------------------- Update Inpayment -------------------
def update_inpayment(data):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
numeric = [
float(data.get('basic_amount') or 0),
float(data.get('debit_amount') or 0),
float(data.get('after_debit_amount') or 0),
float(data.get('amount') or 0),
float(data.get('gst_amount') or 0),
float(data.get('tds_amount') or 0),
float(data.get('sd_amount') or 0),
float(data.get('on_commission') or 0),
float(data.get('hydro_testing') or 0),
float(data.get('gst_sd_amount') or 0),
float(data.get('final_amount') or 0),
]
cursor.callproc('UpdateInpayment', [
data.get('work_type'),
data.get('invoice_details'),
data.get('invoice_date'),
*numeric,
data.get('pmc_no'),
data.get('invoice_no')
])
clear_results(cursor)
connection.commit()
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()
# ------------------- Delete Invoice -------------------
def delete_invoice_data(invoice_id, user_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
try:
cursor.callproc('GetInvoicePMCById', [invoice_id])
record = {}
for result in cursor.stored_results():
record = result.fetchone() or {}
if not record:
raise Exception("Invoice not found")
cursor.callproc("DeleteInvoice", (invoice_id,))
clear_results(cursor)
cursor.callproc(
'DeleteInpaymentByPMCInvoice',
[record['PMC_No'], record['invoice_no']]
)
connection.commit()
except Exception as e:
connection.rollback()
raise e
finally:
cursor.close()
connection.close()

359
v-2/model/ItemCRUD.py Normal file
View File

@@ -0,0 +1,359 @@
from flask_login import current_user
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
from model.Log import LogHelper
import config
import re
import mysql.connector
# ----------------------------------------------------------
# Mapping Class
# ----------------------------------------------------------
class itemCRUDMapping:
def __init__(self, itemType):
if itemType is ItemCRUDType.Village:
self.name = "Village"
elif itemType is ItemCRUDType.Block:
self.name = "Block"
elif itemType is ItemCRUDType.State:
self.name = "State"
elif itemType is ItemCRUDType.HoldType:
self.name = "Hold Type"
elif itemType is ItemCRUDType.Subcontractor:
self.name = "Subcontractor"
else:
self.name = "Item"
# ----------------------------------------------------------
# Generic CRUD Class
# ----------------------------------------------------------
class ItemCRUD:
def __init__(self, itemType):
self.isSuccess = False
self.resultMessage = ""
self.itemCRUDType = itemType
self.itemCRUDMapping = itemCRUDMapping(itemType)
# ----------------------------------------------------------
# DELETE
# ----------------------------------------------------------
def DeleteItem(self, request, itemID, storedprocDelete):
connection = config.get_db_connection()
cursor = connection.cursor()
LogHelper.log_action(
f"Delete {self.itemCRUDMapping.name}",
f"User {current_user.id} deleted {self.itemCRUDMapping.name} '{itemID}'"
)
try:
cursor.callproc(storedprocDelete, (itemID,))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.delete_success(self.itemCRUDMapping.name), 200
)
except mysql.connector.Error as e:
print(f"Error deleting {self.itemCRUDMapping.name}: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.delete_failure(self.itemCRUDMapping.name), 500
)
finally:
cursor.close()
connection.close()
# ----------------------------------------------------------
# ADD
# ----------------------------------------------------------
def AddItem(self, request, parentid=None, childname=None, storedprocfetch=None, storedprocadd=None, data=None):
connection = config.get_db_connection()
if not connection:
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.db_connection_failure(), 500
)
return
cursor = connection.cursor()
LogHelper.log_action(
f"Add {self.itemCRUDMapping.name}",
f"User {current_user.id} adding '{childname if childname else (data.get('Contractor_Name') if data else '')}'"
)
try:
# ======================================================
# SUBCONTRACTOR (MULTI-FIELD)
# ======================================================
if data:
# Duplicate check
cursor.callproc(storedprocfetch, (data['Contractor_Name'],))
existing_item = None
for rs in cursor.stored_results():
existing_item = rs.fetchone()
if existing_item:
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.already_exists(self.itemCRUDMapping.name), 409
)
return
# Insert
cursor.callproc(storedprocadd, (
data['Contractor_Name'],
data['Address'],
data['Mobile_No'],
data['PAN_No'],
data['Email'],
data['Gender'],
data['GST_Registration_Type'],
data['GST_No'],
data['Contractor_password']
))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.add_success(self.itemCRUDMapping.name), 200
)
return
# ======================================================
# NORMAL (Village / Block / State)
# ======================================================
if not re.match(RegEx.patternAlphabetOnly, childname):
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400
)
return
# Duplicate check
if parentid is None:
cursor.callproc(storedprocfetch, (childname,))
else:
cursor.callproc(storedprocfetch, (childname, parentid))
existing_item = None
for rs in cursor.stored_results():
existing_item = rs.fetchone()
if existing_item:
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.already_exists(self.itemCRUDMapping.name), 409
)
return
# Insert
if parentid is None:
cursor.callproc(storedprocadd, (childname,))
else:
cursor.callproc(storedprocadd, (childname, parentid))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.add_success(self.itemCRUDMapping.name), 200
)
except mysql.connector.Error as e:
print(f"Database Error: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.add_failure(self.itemCRUDMapping.name), 500
)
finally:
cursor.close()
connection.close()
# ----------------------------------------------------------
# EDIT
# ----------------------------------------------------------
def EditItem(self, request, childid, parentid=None, childname=None, storedprocupdate=None, data=None):
connection = config.get_db_connection()
cursor = connection.cursor()
LogHelper.log_action(
f"Edit {self.itemCRUDMapping.name}",
f"User {current_user.id} edited '{childid}'"
)
try:
# ======================================================
# SUBCONTRACTOR (MULTI-FIELD)
# ======================================================
if data:
cursor.callproc(storedprocupdate, (
childid,
data['Contractor_Name'],
data['Address'],
data['Mobile_No'],
data['PAN_No'],
data['Email'],
data['Gender'],
data['GST_Registration_Type'],
data['GST_No'],
data['Contractor_password']
))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.update_success(self.itemCRUDMapping.name), 200
)
return
# ======================================================
# NORMAL
# ======================================================
if not re.match(RegEx.patternAlphabetOnly, childname):
self.isSuccess = False
self.resultMessage = ResponseHandler.update_failure(self.itemCRUDMapping.name)['message']
return
if parentid is None:
cursor.callproc(storedprocupdate, (childid, childname))
else:
cursor.callproc(storedprocupdate, (childid, parentid, childname))
connection.commit()
self.isSuccess = True
self.resultMessage = ResponseHandler.update_success(self.itemCRUDMapping.name)['message']
except mysql.connector.Error as e:
print(f"Error updating {self.itemCRUDMapping.name}: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.update_failure(self.itemCRUDMapping.name), 500
)
finally:
cursor.close()
connection.close()
# ----------------------------------------------------------
# GET ALL
# ----------------------------------------------------------
def GetAllData(self, request, storedproc):
data = []
connection = config.get_db_connection()
if not connection:
return []
cursor = connection.cursor()
try:
cursor.callproc(storedproc)
for result in cursor.stored_results():
data = result.fetchall()
self.isSuccess = True
except mysql.connector.Error as e:
print(f"Error fetching {self.itemCRUDMapping.name}: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500
)
return []
finally:
cursor.close()
connection.close()
return data
# ----------------------------------------------------------
# GET BY ID
# ----------------------------------------------------------
def GetDataByID(self, id, storedproc):
data = None
connection = config.get_db_connection()
cursor = connection.cursor()
try:
cursor.callproc(storedproc, (id,))
for rs in cursor.stored_results():
data = rs.fetchone()
except mysql.connector.Error as e:
print(f"Error fetching {self.itemCRUDMapping.name}: {e}")
finally:
cursor.close()
connection.close()
return data
# ----------------------------------------------------------
# CHECK ITEM
# ----------------------------------------------------------
def CheckItem(self, request, parentid, childname, storedprocfetch):
connection = config.get_db_connection()
cursor = connection.cursor()
LogHelper.log_action(
f"Check {self.itemCRUDMapping.name}",
f"User {current_user.id} checked '{childname}'"
)
if not re.match(RegEx.patternAlphabetOnly, childname):
return HtmlHelper.json_response(
ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400
)
try:
if parentid is None:
cursor.callproc(storedprocfetch, (childname,))
else:
cursor.callproc(storedprocfetch, (childname, parentid))
existing_item = None
for rs in cursor.stored_results():
existing_item = rs.fetchone()
if existing_item:
return HtmlHelper.json_response(
ResponseHandler.already_exists(self.itemCRUDMapping.name), 409
)
return HtmlHelper.json_response(
ResponseHandler.is_available(self.itemCRUDMapping.name), 200
)
except mysql.connector.Error as e:
print(f"Error checking {self.itemCRUDMapping.name}: {e}")
return HtmlHelper.json_response(
ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500
)
finally:
cursor.close()
connection.close()

87
v-2/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

456
v-2/model/PmcReport.py Normal file
View File

@@ -0,0 +1,456 @@
import openpyxl
from openpyxl.styles import Font, PatternFill
import config
from flask_login import current_user
from model.Log import LogHelper
from model.Report import ReportHelper
from model.FolderAndFile import FolderAndFile
class PmcReport:
@staticmethod
def get_pmc_report(pmc_no):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True, buffered=True)
try:
# cursor.callproc("GetContractorInfoByPmcNo", (pmc_no,))
# pmc_info = next(cursor.stored_results()).fetchone()
pmc_info = ReportHelper.execute_sp(cursor, 'GetContractorInfoByPmcNo', [pmc_no], True)
if not pmc_info:
return None
cursor.callproc("Get_pmc_hold_types", (pmc_no, pmc_info["Contractor_Id"]))
hold_types = next(cursor.stored_results()).fetchall()
# Extract hold_type_ids
hold_type_ids = [ht['hold_type_id'] for ht in hold_types]
invoices = []
hold_amount_total = 0
if hold_type_ids:
hold_type_ids_str = ",".join(map(str, hold_type_ids))
cursor.callproc(
'GetInvoices_WithHold',
[pmc_no, pmc_info["Contractor_Id"], hold_type_ids_str]
)
else:
cursor.callproc(
'GetInvoices_NoHold',
[pmc_no, pmc_info["Contractor_Id"]]
)
for result in cursor.stored_results():
invoices = result.fetchall()
if hold_type_ids:
hold_amount_total = sum(row.get('hold_amount', 0) or 0 for row in invoices)
total_invo_final = sum(row.get('Final_Amount', 0) or 0 for row in invoices)
# GST RELEASE
# cursor.callproc('GetGSTReleaseByPMC', [pmc_no])
# gst_rel = []
# for result in cursor.stored_results():
# gst_rel = result.fetchall()
gst_rel = ReportHelper.execute_sp(cursor, 'GetGSTReleaseByPMC', [pmc_no])
total_gst_basic = sum(row.get('basic_amount', 0) or 0 for row in gst_rel)
total_gst_final = sum(row.get('final_amount', 0) or 0 for row in gst_rel)
# ---------------- HOLD RELEASE ----------------
# cursor.callproc('GetHoldReleaseByPMC', [pmc_no])
# hold_release = []
# for result in cursor.stored_results():
# hold_release = result.fetchall()
hold_release = ReportHelper.execute_sp(cursor, 'GetHoldReleaseByPMC', [pmc_no])
# ---------------- CREDIT NOTE ----------------
# cursor.callproc('GetCreditNoteByPMC', [pmc_no])
# credit_note = []
# for result in cursor.stored_results():
# credit_note = result.fetchall()
credit_note = ReportHelper.execute_sp(cursor, 'GetCreditNoteByPMC', [pmc_no])
payments = ReportHelper.execute_sp(cursor, 'GetPaymentsByPMC', [pmc_no])
# ---------------- PAYMENTS ----------------
# cursor.callproc('GetPaymentsByPMC', [pmc_no])
# payments = []
# for result in cursor.stored_results():
# payments = result.fetchall()
total_pay_amount = sum(row.get('Payment_Amount', 0) or 0 for row in payments)
total_pay_total = sum(row.get('Total_amount', 0) or 0 for row in payments)
totals = {
"sum_invo_basic_amt": sum(row.get('Basic_Amount', 0) or 0 for row in invoices),
"sum_invo_debit_amt": sum(row.get('Debit_Amount', 0) or 0 for row in invoices),
"sum_invo_after_debit_amt": sum(row.get('After_Debit_Amount', 0) or 0 for row in invoices),
"sum_invo_amt": sum(row.get('Amount', 0) or 0 for row in invoices),
"sum_invo_gst_amt": sum(row.get('GST_Amount', 0) or 0 for row in invoices),
"sum_invo_tds_amt": sum(row.get('TDS_Amount', 0) or 0 for row in invoices),
"sum_invo_ds_amt": sum(row.get('SD_Amount', 0) or 0 for row in invoices),
"sum_invo_on_commission": sum(row.get('On_Commission', 0) or 0 for row in invoices),
"sum_invo_hydro_test": sum(row.get('Hydro_Testing', 0) or 0 for row in invoices),
"sum_invo_gst_sd_amt": sum(row.get('GST_SD_Amount', 0) or 0 for row in invoices),
"sum_invo_final_amt": total_invo_final,
"sum_invo_hold_amt": hold_amount_total,
"sum_gst_basic_amt": total_gst_basic,
"sum_gst_final_amt": total_gst_final,
"sum_pay_payment_amt": total_pay_amount,
"sum_pay_tds_payment_amt": sum(row.get('TDS_Payment_Amount', 0) or 0 for row in payments),
"sum_pay_total_amt": total_pay_total
}
return {
"info": pmc_info,
"invoices": invoices,
"hold_types": hold_types,
"gst_rel": gst_rel,
"payments": payments,
"credit_note": credit_note,
"hold_release": hold_release,
"total": totals
}
finally:
cursor.close()
connection.close()
@staticmethod
def download_pmc_report(pmc_no):
connection = config.get_db_connection()
if not connection:
return None
cursor = connection.cursor(dictionary=True)
try:
# filename
filename = f"PMC_Report_{pmc_no}.xlsx"
output_folder = FolderAndFile.get_download_folder()
output_file = FolderAndFile.get_download_path(filename)
# ================= DATA FETCH =================
contractor_info = ReportHelper.execute_sp(cursor, 'GetContractorDetailsByPMC', [pmc_no], "one")
if not contractor_info:
return None
hold_types = ReportHelper.execute_sp(cursor, 'GetHoldTypesByContractor', [contractor_info["Contractor_Id"]])
hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
invoices = ReportHelper.execute_sp(cursor, 'GetInvoicesAndGstReleaseByPmcNo', [pmc_no])
credit_notes = ReportHelper.execute_sp(cursor, 'GetCreditNoteByContractor', [contractor_info["Contractor_Id"]])
hold_amounts = ReportHelper.execute_sp(cursor, 'GetHoldAmountsByContractor', [contractor_info["Contractor_Id"]])
all_payments = ReportHelper.execute_sp(cursor, 'GetAllPaymentsByPMC', [pmc_no])
gst_releases = ReportHelper.execute_sp(cursor, 'GetGSTReleaseDetailsByPMC', [pmc_no])
# ================= DATA MAPPING =================
hold_data = {}
for h in hold_amounts:
hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
payments_map = {}
for pay in all_payments:
if pay['invoice_no']:
payments_map.setdefault(pay['invoice_no'], []).append(pay)
# ================= LOG =================
LogHelper.log_action(
"Download PMC Report",
f"User {current_user.id} Download PMC Report '{pmc_no}'"
)
# ================= EXCEL =================
workbook = openpyxl.Workbook()
sheet = workbook.active
sheet.title = "PMC Report"
# HEADER INFO
sheet.append(["", "", "Laxmi Civil Engineering Services PVT. LTD."])
sheet.append(["Contractor Name", contractor_info["Contractor_Name"]])
sheet.append(["State", contractor_info["State_Name"]])
sheet.append(["District", contractor_info["District_Name"]])
sheet.append(["Block", contractor_info["Block_Name"]])
sheet.append([])
base_headers = [
"PMC No","Village","Work Type","Invoice Details","Invoice Date","Invoice No",
"Basic Amount","Debit","After Debit Amount","GST","Amount","TDS",
"SD","On Commission","Hydro Testing","GST SD Amount"
]
hold_headers = [ht['hold_type'] for ht in hold_types]
payment_headers = [
"Final Amount","Payment Amount","TDS Payment","Total Paid","UTR"
]
headers = base_headers + hold_headers + payment_headers
sheet.append(headers)
# STYLE
for cell in sheet[sheet.max_row]:
cell.font = Font(bold=True)
# DATA
seen_invoices = set()
for inv in invoices:
invoice_no = inv["Invoice_No"]
payments = payments_map.get(invoice_no, [])
if invoice_no in seen_invoices:
continue
seen_invoices.add(invoice_no)
first_payment = payments[0] if payments else None
row = [
pmc_no,
inv["Village_Name"],
inv["Work_Type"],
inv["Invoice_Details"],
inv["Invoice_Date"],
invoice_no,
inv["Basic_Amount"],
inv["Debit_Amount"],
inv["After_Debit_Amount"],
inv["GST_Amount"],
inv["Amount"],
inv["TDS_Amount"],
inv["SD_Amount"],
inv["On_Commission"],
inv["Hydro_Testing"],
inv["GST_SD_Amount"]
]
# HOLD DATA
invoice_holds = hold_data.get(inv["Invoice_Id"], {})
for ht_id in hold_type_map.keys():
row.append(invoice_holds.get(ht_id, ""))
# PAYMENT DATA
row += [
inv["Final_Amount"],
first_payment["Payment_Amount"] if first_payment else "",
first_payment["TDS_Payment_Amount"] if first_payment else "",
first_payment["Total_amount"] if first_payment else "",
first_payment["UTR"] if first_payment else ""
]
sheet.append(row)
# AUTO WIDTH
for col in sheet.columns:
max_len = max((len(str(cell.value)) for cell in col if cell.value), default=0)
sheet.column_dimensions[col[0].column_letter].width = max_len + 2
# SAVE
workbook.save(output_file)
workbook.close()
return output_folder, filename
except Exception as e:
print(f"Error generating PMC report: {e}")
return None
finally:
cursor.close()
connection.close()
# @staticmethod
# def download_pmc_report(pmc_no):
# connection = config.get_db_connection()
# cursor = connection.cursor(dictionary=True)
# # output_folder = "static/download"
# # output_file = os.path.join(output_folder, f"PMC_Report_{pmc_no}.xlsx")
# output_folder = FolderAndFile.get_download_folder
# filename = f"PMC_Report_{pmc_no}.xlsx"
# output_file = FolderAndFile.get_download_path(filename)
# try:
# cursor.callproc('GetContractorDetailsByPMC', [pmc_no])
# contractor_info = next(cursor.stored_results()).fetchone()
# if not contractor_info:
# return None
# cursor.callproc('GetHoldTypesByContractor', [contractor_info["Contractor_Id"]])
# hold_types = next(cursor.stored_results()).fetchall()
# hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
# cursor.callproc('GetInvoicesAndGstReleaseByPmcNo', [pmc_no])
# invoices = next(cursor.stored_results()).fetchall()
# cursor.callproc('GetCreditNoteByContractor',[contractor_info["Contractor_Id"]])
# credit_notes = []
# for result in cursor.stored_results():
# credit_notes = result.fetchall()
# credit_note_map = {}
# for cn in credit_notes:
# key = (cn["PMC_No"], cn["Invoice_No"])
# credit_note_map.setdefault(key, []).append(cn)
# cursor.callproc('GetHoldAmountsByContractor', [contractor_info["Contractor_Id"]])
# hold_amounts = next(cursor.stored_results()).fetchall()
# hold_data = {}
# for h in hold_amounts:
# hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
# cursor.callproc('GetAllPaymentsByPMC', [pmc_no])
# all_payments = next(cursor.stored_results()).fetchall()
# payments_map = {}
# extra_payments = []
# for pay in all_payments:
# if pay['invoice_no']:
# payments_map.setdefault(pay['invoice_no'], []).append(pay)
# else:
# extra_payments.append(pay)
# # ---------------- GST RELEASE DETAILS ----------------
# cursor.callproc('GetGSTReleaseDetailsByPMC', [pmc_no])
# gst_releases = []
# for result in cursor.stored_results():
# gst_releases = result.fetchall()
# gst_release_map = {}
# for gr in gst_releases:
# invoice_nos = []
# if gr['Invoice_No']:
# cleaned = gr['Invoice_No'].replace(' ', '')
# if '&' in cleaned:
# invoice_nos = cleaned.split('&')
# elif ',' in cleaned:
# invoice_nos = cleaned.split(',')
# else:
# invoice_nos = [cleaned]
# for inv_no in invoice_nos:
# gst_release_map.setdefault(inv_no, []).append(gr)
# LogHelper.log_action(
# "Download PMC Report",
# f"User {current_user.id} Download PMC Report '{pmc_no}'"
# )
# workbook = openpyxl.Workbook()
# sheet = workbook.active
# sheet.title = "PMC Report"
# sheet.append(["", "", "Laxmi Civil Engineering Services PVT. LTD."])
# sheet.append(["Contractor Name", contractor_info["Contractor_Name"], "", "GST No", contractor_info["GST_No"], "", "GST Type", contractor_info["GST_Registration_Type"]])
# sheet.append(["State", contractor_info["State_Name"], "", "PAN No", contractor_info["PAN_No"], "", "Address", contractor_info["Address"]])
# sheet.append(["District", contractor_info["District_Name"], "", "Mobile No", contractor_info["Mobile_No"]])
# sheet.append(["Block", contractor_info["Block_Name"], "", "Email", contractor_info["Email"]])
# sheet.append([])
# base_headers = [
# "PMC No","Village","Work Type","Invoice Details","Invoice Date","Invoice No",
# "Basic Amount","Debit","After Debit Amount","GST (18%)","Amount","TDS (1%)",
# "SD (5%)","On Commission","Hydro Testing","GST SD Amount"
# ]
# hold_headers = [ht['hold_type'] for ht in hold_types]
# payment_headers = [
# "Final Amount","Payment Amount","TDS Payment","Total Paid","UTR"
# ]
# sheet.append(base_headers + hold_headers + payment_headers)
# header_fill = PatternFill(start_color="ADD8E6",end_color="ADD8E6",fill_type="solid")
# header_font = Font(bold=True)
# for cell in sheet[sheet.max_row]:
# cell.font = header_font
# cell.fill = header_fill
# seen_invoices = set()
# processed_payments = set()
# for inv in invoices:
# invoice_no = inv["Invoice_No"]
# payments = payments_map.get(invoice_no, [])
# if invoice_no not in seen_invoices:
# seen_invoices.add(invoice_no)
# first_payment = payments[0] if payments else None
# row = [
# pmc_no, inv["Village_Name"], inv["Work_Type"],
# inv["Invoice_Details"], inv["Invoice_Date"], invoice_no,
# inv["Basic_Amount"], inv["Debit_Amount"],
# inv["After_Debit_Amount"], inv["GST_Amount"],
# inv["Amount"], inv["TDS_Amount"], inv["SD_Amount"],
# inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
# ]
# invoice_holds = hold_data.get(inv["Invoice_Id"], {})
# for ht_id in hold_type_map.keys():
# row.append(invoice_holds.get(ht_id, ""))
# row += [
# inv["Final_Amount"],
# first_payment["Payment_Amount"] if first_payment else "",
# first_payment["TDS_Payment_Amount"] if first_payment else "",
# first_payment["Total_amount"] if first_payment else "",
# first_payment["UTR"] if first_payment else ""
# ]
# sheet.append(row)
# workbook.save(output_file)
# workbook.close()
# return output_folder, filename
# finally:
# cursor.close()
# connection.close()

275
v-2/model/Report.py Normal file
View File

@@ -0,0 +1,275 @@
import config
from datetime import datetime
from flask import send_file
import openpyxl
from openpyxl.styles import Font
from model.FolderAndFile import FolderAndFile
class ReportHelper:
isSuccess = False
resultMessage = ""
data=[]
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
self.data = []
@staticmethod
def execute_sp(cursor, proc_name, params=[], fetch_one=False):
cursor.callproc(proc_name, params)
return (
ReportHelper.fetch_one_result(cursor)
if fetch_one else
ReportHelper.fetch_all_results(cursor)
)
@staticmethod
def fetch_all_results(cursor):
data = []
for result in cursor.stored_results():
data = result.fetchall()
return data
@staticmethod
def fetch_one_result(cursor):
data = None
for result in cursor.stored_results():
data = result.fetchone()
return data
@staticmethod
def search_contractor(request):
subcontractor_name = request.form.get("subcontractor_name")
pmc_no = request.form.get("pmc_no")
state = request.form.get("state")
district = request.form.get("district")
block = request.form.get("block")
village = request.form.get("village")
year_from = request.form.get("year_from")
year_to = request.form.get("year_to")
connection = config.get_db_connection()
if not connection:
return []
cursor = connection.cursor(dictionary=True)
try:
data = ReportHelper.execute_sp(
cursor,
"search_contractor_info",
[
subcontractor_name or None,
pmc_no or None,
state or None,
district or None,
block or None,
village or None,
year_from or None,
year_to or None
]
)
except Exception as e:
print(f"Error in search_contractor: {e}")
data = []
finally:
cursor.close()
connection.close()
return data
@staticmethod
def get_contractor_report(contractor_id):
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True, buffered=True)
try:
# Contractor Info (only one fetch)
contInfo = ReportHelper.execute_sp(cursor, 'GetContractorInfo', [contractor_id], True)
# Hold Types
hold_types = ReportHelper.execute_sp(cursor, 'GetContractorHoldTypes', [contractor_id])
# Invoices
invoices = ReportHelper.execute_sp(cursor, 'GetContractorInvoices', [contractor_id])
# GST Release
gst_rel = ReportHelper.execute_sp(cursor, 'GetGSTRelease', [contractor_id])
# Hold Release
hold_release = ReportHelper.execute_sp(cursor, 'GetHoldRelease', [contractor_id])
# Credit Note
credit_note = ReportHelper.execute_sp(cursor, 'GetCreditNote', [contractor_id])
# Payments
payments = ReportHelper.execute_sp(cursor, 'GetPayments', [contractor_id])
# Totals
total = {
"sum_invo_basic_amt": float(sum(row['Basic_Amount'] or 0 for row in invoices)),
"sum_invo_debit_amt": float(sum(row['Debit_Amount'] or 0 for row in invoices)),
"sum_invo_after_debit_amt": float(sum(row['After_Debit_Amount'] or 0 for row in invoices)),
"sum_invo_amt": float(sum(row['Amount'] or 0 for row in invoices)),
"sum_invo_gst_amt": float(sum(row['GST_Amount'] or 0 for row in invoices)),
"sum_invo_tds_amt": float(sum(row['TDS_Amount'] or 0 for row in invoices)),
"sum_invo_ds_amt": float(sum(row['SD_Amount'] or 0 for row in invoices)),
"sum_invo_on_commission": float(sum(row['On_Commission'] or 0 for row in invoices)),
"sum_invo_hydro_test": float(sum(row['Hydro_Testing'] or 0 for row in invoices)),
"sum_invo_gst_sd_amt": float(sum(row['GST_SD_Amount'] or 0 for row in invoices)),
"sum_invo_final_amt": float(sum(row['Final_Amount'] or 0 for row in invoices)),
"sum_invo_hold_amt": float(sum(row['hold_amount'] or 0 for row in invoices)),
"sum_gst_basic_amt": float(sum(row['basic_amount'] or 0 for row in gst_rel)),
"sum_gst_final_amt": float(sum(row['final_amount'] or 0 for row in gst_rel)),
"sum_pay_payment_amt": float(sum(row['Payment_Amount'] or 0 for row in payments)),
"sum_pay_tds_payment_amt": float(sum(row['TDS_Payment_Amount'] or 0 for row in payments)),
"sum_pay_total_amt": float(sum(row['Total_amount'] or 0 for row in payments))
}
current_date = datetime.now().strftime('%Y-%m-%d')
finally:
cursor.close()
connection.close()
return {
"contInfo": contInfo,
"invoices": invoices,
"hold_types": hold_types,
"gst_rel": gst_rel,
"payments": payments,
"credit_note": credit_note,
"hold_release": hold_release,
"total": total,
"current_date": current_date
}
@staticmethod
def download_report(contractor_id):
try:
connection = config.get_db_connection()
cursor = connection.cursor(dictionary=True)
# -------- Contractor Info --------
contInfo = ReportHelper.execute_sp(cursor, 'GetContractorInfo', [contractor_id], True)
if not contInfo:
return "No contractor found", 404
# -------- Invoice Data --------
cursor.callproc('FetchInvoicesByContractor', [contractor_id])
invoices = []
for result in cursor.stored_results():
invoices.extend(result.fetchall())
if not invoices:
return "No invoice data found"
# -------- Create Workbook --------
workbook = openpyxl.Workbook()
sheet = workbook.active
sheet.title = "Contractor Report"
# ================= CONTRACTOR DETAILS =================
sheet.append(["SUB CONTRACTOR DETAILS"])
sheet.cell(row=sheet.max_row, column=1).font = Font(bold=True)
sheet.append([])
sheet.append(["Name", contInfo.get("Contractor_Name") or ""])
sheet.append(["Mobile No", contInfo.get("Mobile_No") or ""])
sheet.append(["Email", contInfo.get("Email") or ""])
sheet.append(["Village", contInfo.get("Village_Name") or ""])
sheet.append(["Block", contInfo.get("Block_Name") or ""])
sheet.append(["District", contInfo.get("District_Name") or ""])
sheet.append(["State", contInfo.get("State_Name") or ""])
sheet.append(["Address", contInfo.get("Address") or ""])
sheet.append(["GST No", contInfo.get("GST_No") or ""])
sheet.append(["PAN No", contInfo.get("PAN_No") or ""])
sheet.append([])
sheet.append([])
# ================= TABLE HEADERS =================
headers = [
"PMC No", "Village", "Invoice No", "Invoice Date", "Work Type","Invoice_Details",
"Basic Amount", "Debit Amount", "After Debit Amount",
"Amount", "GST Amount", "TDS Amount", "SD Amount",
"On Commission", "Hydro Testing", "Hold Amount",
"GST SD Amount", "Final Amount",
"Payment Amount", "TDS Payment",
"Total Amount", "UTR"
]
sheet.append(headers)
for col in range(1, len(headers) + 1):
sheet.cell(row=sheet.max_row, column=col).font = Font(bold=True)
# ================= DATA =================
total_final = 0
total_payment = 0
total_amount = 0
for inv in invoices:
row = [
inv.get("PMC_No"),
inv.get("Village_Name"),
inv.get("invoice_no"),
inv.get("Invoice_Date"),
inv.get("Work_Type"),
inv.get("Invoice_Details"),
inv.get("Basic_Amount"),
inv.get("Debit_Amount"),
inv.get("After_Debit_Amount"),
inv.get("Amount"),
inv.get("GST_Amount"),
inv.get("TDS_Amount"),
inv.get("SD_Amount"),
inv.get("On_Commission"),
inv.get("Hydro_Testing"),
inv.get("Hold_Amount"),
inv.get("GST_SD_Amount"),
inv.get("Final_Amount"),
inv.get("Payment_Amount"),
inv.get("TDS_Payment_Amount"),
inv.get("Total_Amount"),
inv.get("UTR")
]
total_final += float(inv.get("Final_Amount") or 0)
total_payment += float(inv.get("Payment_Amount") or 0)
total_amount += float(inv.get("Total_Amount") or 0)
sheet.append(row)
# ================= TOTAL ROW =================
sheet.append([])
sheet.append([
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"TOTAL",
total_final,
total_payment,
"",
total_amount,
""
])
# ================= AUTO WIDTH =================
for column in sheet.columns:
max_length = 0
column_letter = column[0].column_letter
for cell in column:
if cell.value:
max_length = max(max_length, len(str(cell.value)))
sheet.column_dimensions[column_letter].width = max_length + 2
# ================= SAVE FILE =================
filename = f"Contractor_Report_{contInfo.get('Contractor_Name')}.xlsx"
output_file = FolderAndFile.get_download_path(filename)
workbook.save(output_file)
return send_file(output_file, as_attachment=True)
except Exception as e:
return str(e)

168
v-2/model/State.py Normal file
View File

@@ -0,0 +1,168 @@
from flask import request, redirect, url_for
from flask_login import current_user
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
from model.Log import LogHelper
from model.ItemCRUD import ItemCRUD
import config
import re
import mysql.connector
class State:
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
# ----------------------------------------------------------
# ADD STATE (USING ITEM CRUD)
# ----------------------------------------------------------
def AddState(self, request):
state_name = request.form['state_Name'].strip()
crud = ItemCRUD(ItemCRUDType.State)
crud.AddItem(
request=request,
childname=state_name,
storedprocfetch="CheckStateExists",
storedprocadd="SaveState"
)
self.isSuccess = crud.isSuccess
self.resultMessage = crud.resultMessage
return self.resultMessage
# ----------------------------------------------------------
# GET ALL STATES (NO CHANGE - THIS IS CORRECT)
# ----------------------------------------------------------
def GetAllStates(self, request):
connection = config.get_db_connection()
data = []
if not connection:
return []
cursor = connection.cursor()
try:
cursor.callproc("GetAllStates")
for res in cursor.stored_results():
data = res.fetchall()
self.isSuccess = True
except mysql.connector.Error as e:
print(f"Error fetching states: {e}")
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.fetch_failure("state"), 500
)
return []
finally:
cursor.close()
connection.close()
return data
# ----------------------------------------------------------
# CHECK STATE (USING ITEM CRUD)
# ----------------------------------------------------------
def CheckState(self, request):
state_name = request.json.get('state_Name', '').strip()
crud = ItemCRUD(ItemCRUDType.State)
return crud.CheckItem(
request=request,
parentid=None,
childname=state_name,
storedprocfetch="CheckStateExists"
)
# ----------------------------------------------------------
# DELETE STATE (USING ITEM CRUD)
# ----------------------------------------------------------
def DeleteState(self, request, id):
crud = ItemCRUD(ItemCRUDType.State)
crud.DeleteItem(
request=request,
itemID=id,
storedprocDelete="DeleteState"
)
self.isSuccess = crud.isSuccess
self.resultMessage = crud.resultMessage
return self.resultMessage
# ----------------------------------------------------------
# EDIT STATE (USING ITEM CRUD)
# ----------------------------------------------------------
def EditState(self, request, id):
state_name = request.form['state_Name'].strip()
crud = ItemCRUD(ItemCRUDType.State)
crud.EditItem(
request=request,
childid=id,
parentid=None,
childname=state_name,
storedprocupdate="UpdateStateById"
)
self.isSuccess = crud.isSuccess
self.resultMessage = crud.resultMessage
return redirect(url_for('state.add_state'))
# ----------------------------------------------------------
# GET STATE BY ID (KEEP SAME)
# ----------------------------------------------------------
def GetStateByID(self, request, id):
connection = config.get_db_connection()
data = None
if not connection:
return None
cursor = connection.cursor()
try:
cursor.callproc("GetStateByID", (id,))
for res in cursor.stored_results():
data = res.fetchone()
if data:
self.isSuccess = True
self.resultMessage = "Success"
else:
self.isSuccess = False
self.resultMessage = "Not Found"
except mysql.connector.Error as e:
print(f"Error fetching state: {e}")
self.isSuccess = False
finally:
cursor.close()
connection.close()
return data

140
v-2/model/Subcontractor.py Normal file
View File

@@ -0,0 +1,140 @@
from model.Utilities import ItemCRUDType
from model.ItemCRUD import ItemCRUD
class Subcontractor:
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
# ----------------------------------------------------------
# ADD
# ----------------------------------------------------------
def AddSubcontractor(self, request):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
data = {
"Contractor_Name": request.form.get('Contractor_Name', '').strip(),
"Address": request.form.get('Address', '').strip(),
"Mobile_No": request.form.get('Mobile_No', '').strip(),
"PAN_No": request.form.get('PAN_No', '').strip(),
"Email": request.form.get('Email', '').strip(),
"Gender": request.form.get('Gender', '').strip(),
"GST_Registration_Type": request.form.get('GST_Registration_Type', '').strip(),
"GST_No": request.form.get('GST_No', '').strip(),
"Contractor_password": request.form.get('Contractor_password', '').strip()
}
subcontractor.AddItem(
request=request,
data=data,
storedprocfetch="GetSubcontractorByName",
storedprocadd="SaveContractor"
)
self.isSuccess = subcontractor.isSuccess
self.resultMessage = subcontractor.resultMessage
return
# ----------------------------------------------------------
# GET ALL
# ----------------------------------------------------------
def GetAllSubcontractors(self, request):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
data = subcontractor.GetAllData(
request=request,
storedproc="GetAllSubcontractors"
)
self.isSuccess = subcontractor.isSuccess
self.resultMessage = subcontractor.resultMessage
return data
# ----------------------------------------------------------
# GET BY ID
# ----------------------------------------------------------
def GetSubcontractorByID(self, id):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
data = subcontractor.GetDataByID(
id=id,
storedproc="GetSubcontractorById"
)
if data:
self.isSuccess = True
else:
self.isSuccess = False
self.resultMessage = "Subcontractor not found"
return data
# ----------------------------------------------------------
# CHECK (Duplicate)
# ----------------------------------------------------------
def CheckSubcontractor(self, request):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
name = request.form.get('Contractor_Name', '').strip()
result = subcontractor.CheckItem(
request=request,
childname=name,
storedprocfetch="GetSubcontractorByName"
)
self.isSuccess = subcontractor.isSuccess
self.resultMessage = subcontractor.resultMessage
return result
# ----------------------------------------------------------
# EDIT
# ----------------------------------------------------------
def EditSubcontractor(self, request, subcontractor_id):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
data = {
"Contractor_Name": request.form.get('Contractor_Name', '').strip(),
"Address": request.form.get('Address', '').strip(),
"Mobile_No": request.form.get('Mobile_No', '').strip(),
"PAN_No": request.form.get('PAN_No', '').strip(),
"Email": request.form.get('Email', '').strip(),
"Gender": request.form.get('Gender', '').strip(),
"GST_Registration_Type": request.form.get('GST_Registration_Type', '').strip(),
"GST_No": request.form.get('GST_No', '').strip(),
"Contractor_password": request.form.get('Contractor_password', '').strip()
}
subcontractor.EditItem(
request=request,
childid=subcontractor_id,
data=data,
storedprocupdate="UpdateSubcontractor"
)
self.isSuccess = subcontractor.isSuccess
self.resultMessage = subcontractor.resultMessage
return
# ----------------------------------------------------------
# DELETE
# ----------------------------------------------------------
def DeleteSubcontractor(self, request, subcontractor_id):
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
subcontractor.DeleteItem(
request=request,
itemID=subcontractor_id,
storedprocDelete="DeleteSubcontractor"
)
self.isSuccess = subcontractor.isSuccess
self.resultMessage = subcontractor.resultMessage
return

66
v-2/model/Utilities.py Normal file
View File

@@ -0,0 +1,66 @@
from flask import flash, jsonify, json
from enum import Enum
class ItemCRUDType(Enum):
Village = 1
Block = 2
District = 3
State = 4
HoldType = 5
Subcontractor = 6
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

121
v-2/model/Village.py Normal file
View File

@@ -0,0 +1,121 @@
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 config
import mysql.connector
from mysql.connector import Error
from model.ItemCRUD import ItemCRUD
class Village:
isSuccess = False
resultMessage = ""
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
def AddVillage(self, request):
village = ItemCRUD(itemType=ItemCRUDType.Village)
block_id = request.form.get('block_Id')
village_name = request.form.get('Village_Name', '').strip()
village.AddItem(request=request, parentid=block_id, childname=village_name, storedprocfetch="GetVillageByNameAndBlock", storedprocadd="SaveVillage" )
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
return
#self.isSuccess = False
def GetAllVillages(self, request):
village = ItemCRUD(itemType=ItemCRUDType.Village)
villagesdata = village.GetAllData(request=request, storedproc="GetAllVillages")
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
return villagesdata
def CheckVillage(self, request):
village = ItemCRUD(itemType=ItemCRUDType.Village)
block_id = request.form.get('block_Id')
village_name = request.form.get('Village_Name', '').strip()
result = village.CheckItem(request=request, parentid=block_id, childname=village_name, storedprocfetch="GetVillageByNameAndBlocks")
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
return result
def DeleteVillage(self, request, village_id):
village = ItemCRUD(itemType=ItemCRUDType.Village)
village.DeleteItem(request=request, itemID=village_id, storedprocDelete="DeleteVillage" )
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
return
def EditVillage(self, request, village_id):
corsor=None
village = ItemCRUD(itemType=ItemCRUDType.Village)
block_id = request.form.get('block_Id')
village_name = request.form.get('Village_Name', '').strip()
village.EditItem(request=request,childid=village_id,parentid=block_id,childname=village_name,storedprocupdate="UpdateVillage" )
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
return
# def GetVillageByID(self, request, id):
# village = ItemCRUD(itemType=ItemCRUDType.Village)
# villagedetailsdata = village.GetAllData(request=request, storedproc="GetVillageDetailsById")
# self.isSuccess = village.isSuccess
# self.resultMessage = village.resultMessage
# return villagedetailsdata
def GetVillageByID(self, request, id):
village = ItemCRUD(itemType=ItemCRUDType.Village)
villagedetailsdata = village.GetDataByID(id=id,storedproc="GetVillageDetailsById")
if villagedetailsdata:
self.isSuccess = True
else:
self.isSuccess = False
self.resultMessage = "Village not found"
return villagedetailsdata
def GetAllBlocks(self, request):
blocks = []
self.isSuccess = False
self.resultMessage = ""
connection = config.get_db_connection()
if not connection:
return []
cursor = connection.cursor()
try:
cursor.callproc('GetAllBlocks')
for result in cursor.stored_results():
blocks = result.fetchall()
self.isSuccess = True
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)
finally:
cursor.close()
connection.close()
return blocks

150
v-2/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
v-2/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
v-2/requirements.txt Normal file
View File

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

89
v-2/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
v-2/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
v-2/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
v-2/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
v-2/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%;
}
}

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
v-2/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
v-2/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
v-2/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);
});
});

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();
});

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
v-2/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
v-2/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
v-2/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
v-2/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.');
}
});
});
});

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>

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 %}

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 %}

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 %}

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 %}

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>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>
<!-- 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>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>
<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') }}" alt="Edit"
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') }}" alt="Delete"
class="icon">
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</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 %}

97
v-2/templates/base.html Normal file
View File

@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}Payment Reconciliation{% endblock %}</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/base.css') }}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<!-- Sidebar Toggle Button -->
<button class="menu-icon" onclick="toggleSidebar()">
<i class="fa-solid fa-bars"></i>
</button>
<!-- Sidebar -->
<div class="sidebar" id="sidebar">
<ul class="nav-menu">
<li class="nav-item">
<a href="/" class="nav-link active">
<i class="fas fa-home"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a href="/upload_excel_file" class="nav-link">
<i class="fas fa-book"></i> Import Excel
</a>
</li>
<li class="nav-item">
<a href="/report" class="nav-link">
<i class="fas fa-cog"></i> Report
</a>
</li>
<li class="card">
<a href="/add_state" class="nav-link">
<i class="fas fa-arrow-right"></i> State
</a>
</li>
<li class="card">
<a href="/add_district" class="nav-link">
<i class="fas fa-arrow-right"></i> District
</a>
</li>
<li class="card">
<a href="/add_block" class="nav-link">
<i class="fas fa-arrow-right"></i> Blocks
</a>
</li>
<li class="card">
<a href="/add_village" class="nav-link">
<i class="fas fa-arrow-right"></i> village
</a>
</li>
<li class="card">
<a href="/subcontractor" class="nav-link">
<i class="fas fa-arrow-right"></i> Sub-Contractor
</a>
</li>
<li class="card">
<a href="/add_invoice" class="nav-link">
<i class="fas fa-arrow-right"></i> Invoice
</a>
</li>
<li class="card">
<a href="/add_payment" class="nav-link">
<i class="fas fa-arrow-right"></i> Payment
</a>
</li>
<li class="card">
<a href="/add_gst_release" class="nav-link">
<i class="fas fa-arrow-right"></i> GST Release
</a>
</li>
<li class="card">
<a href="/add_hold_type" class="nav-link">
<i class="fas fa-arrow-right"></i> Hold Types
</a>
</li>
</ul>
</div>
<!-- Main Content -->
<div class="content" id="content">
{% block content %}{% endblock %}
</div>
<!-- Sidebar Toggle Script -->
<script>
function toggleSidebar() {
document.getElementById("sidebar").classList.toggle("open");
document.getElementById("content").classList.toggle("shift");
}
</script>
</body>
</html>

View File

@@ -0,0 +1,60 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Edit Block</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script>
window.onload = function () {
const flash = document.getElementById('flash-message');
if (flash) {
alert(flash.innerText);
}
}
</script>
</head>
<body>
<h2>Edit Block</h2>
<!-- Flash Message -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div id="flash-message" style="display:none;" data-category="{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Edit Block Form -->
<form method="POST">
<label for="state_Id">State:</label>
<select name="state_Id" id="state_Id" required>
<option value="" disabled>Select State</option>
{% for state in states %}
<option value="{{ state[0] }}" {% if state[0] == block_data[1] %} selected {% endif %}>
{{ state[1] }}
</option>
{% endfor %}
</select><br><br>
<label for="district_Id">District:</label>
<select name="district_Id" id="district_Id" required>
<option value="" disabled>Select District</option>
{% for district in districts %}
<option value="{{ district[0] }}" {% if district[0] == block_data[1] %} selected {% endif %}>
{{ district[1] }}
</option>
{% endfor %}
</select><br><br>
<label for="block_Name">Block Name:</label>
<input type="text" name="block_Name" value="{{ block_data[0] }}" placeholder="Enter Block Name" required><br><br>
<button type="submit">Update Block</button>
</form>
</body>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Edit District</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit District</h2>
<form method="POST">
<label for="state_Id">State :</label>
<select name="state_Id" required>
<option value="" disabled>Select State</option>
{% for state in states %}
<option value="{{ state[0] }}" {% if state[0] == districtdata[1] %}selected{% endif %}>{{ state[1] }}</option>
{% endfor %}
</select>
<label>Enter District :</label>
<input type="text" name="district_Name" placeholder="District Name" value="{{ districtdata[0] }}" required>
<button type="submit">Update District</button>
</form>
</body>
{% endblock %}

View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head><title>Edit GRN</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit GRN</h2>
<form method="POST">
<label>GRN Date:</label>
<input type="date" name="grn_date" value="{{ grn['grn_date'] }}" required><br><br>
<label>Purchase ID:</label>
<input type="number" name="purchase_id" value="{{ grn['purchase_id'] }}" required><br><br>
<label>Supplier Name:</label>
<input type="text" name="supplier_name" value="{{ grn['supplier_name'] }}" required><br><br>
<label>Item Description:</label>
<input type="text" name="item_description" value="{{ grn['item_description'] }}" required><br><br>
<label>Received Quantity:</label>
<input type="number" name="received_quantity" value="{{ grn['received_quantity'] }}" required><br><br>
<label>Unit:</label>
<input type="text" name="unit" value="{{ grn['unit'] }}" required><br><br>
<label>Rate:</label>
<input type="number" step="0.01" name="rate" value="{{ grn['rate'] }}" required><br><br>
<label>Amount:</label>
<input type="number" step="0.01" name="amount" value="{{ grn['amount'] }}" required><br><br>
<label>Remarks:</label>
<input type="text" name="remarks" value="{{ grn['remarks'] }}"><br><br>
<input type="submit" value="Update GRN">
</form>
</body>
</html>

View File

@@ -0,0 +1,45 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit GST Release</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit GST Release</h2>
<form action="/edit_gst_release/{{ gst_release_data[0] }}" method="POST">
<!-- <label for="invoice_id">Invoice Id:</label><br>-->
<!-- <input type="number" id="invoice_id" name="invoice_id" value="{{ gst_release_data[0] }}" required><br><br>-->
<label for="PMC_No">PMC No :</label><br>
<input type="number" id="PMC_No" name="PMC_No" value="{{ gst_release_data[1] }}" required><br><br>
<label for="invoice_No">Invoice No:</label><br>
<input type="number" step="0.01" id="invoice_No" name="invoice_No" value="{{ gst_release_data[2] }}"
required><br><br>
<label for="basic_amount">Basic Amount:</label><br>
<input type="number" step="0.01" id="basic_amount" name="basic_amount" value="{{ gst_release_data[3] }}"
required><br><br>
<label for="final_amount">Final Amount:</label><br>
<input type="number" step="0.01" id="final_amount" name="final_amount" value="{{ gst_release_data[4] }}"
required><br><br>
<label for="total_amount">Total Amount:</label><br>
<input type="number" step="0.01" id="total_amount" name="total_amount" value="{{ gst_release_data[5] }}"
required><br><br>
<label for="utr">UTR:</label><br>
<input type="text" id="utr" name="utr" value="{{ gst_release_data[6] }}"
required readonly><br><br>
<button type="submit">Update GST Release</button>
</form>
</body>
{% endblock %}

View File

@@ -0,0 +1,62 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Edit Hold Type</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<h2>Edit Hold Type</h2>
<form id="updateHoldTypeForm">
<input type="hidden" id="hold_type_id" value="{{ hold_type[0] }}">
<label for="edit_hold_type">Hold Type:</label>
<input type="text" id="edit_hold_type" name="hold_type" value="{{ hold_type[1] }}" required>
<button type="submit">Update</button>
<a href="{{ url_for('hold_types.add_hold_type') }}"></a>
</form>
</body>
<script>
$(document).ready(function () {
$('#updateHoldTypeForm').submit(function (e) {
e.preventDefault();
const holdTypeId = $('#hold_type_id').val();
const newHoldType = $('#edit_hold_type').val().trim();
if (!/^[A-Za-z]/.test(newHoldType)) {
alert('Hold Type must start with a letter.');
return;
}
// Create a FormData object to properly submit form data
const formData = new FormData();
formData.append('hold_type', newHoldType);
$.ajax({
url: '/update_hold_type/' + holdTypeId,
method: 'POST',
data: formData,
processData: false,
contentType: false,
success: function () {
// The server will redirect, so we don't need to handle the response here
window.location.href = "{{ url_for('hold_types.add_hold_type') }}";
},
error: function (xhr) {
const errMsg = xhr.responseJSON?.message || 'Failed to update Hold Type';
alert('Error: ' + errMsg);
}
});
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,207 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Edit Invoice</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/invoice.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style1.css') }}">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div id="editForm">
<h2>Edit Invoice</h2>
<!-- Success Alert Box -->
<div id="invoiceSuccessAlert" class="success-alert" style="display:none;">
Invoice successfully updated!
</div>
<form id="invoiceForm" action="{{ url_for('invoice.edit_invoice', invoice_id=invoice.Invoice_Id) }}" method="POST">
<!-- Subcontractor Field -->
<div class="row1">
<div>
<label for="subcontractor">Subcontractor Name:</label>
<input class="form-control" list="subcontractor_list" id="subcontractor" name="subcontractor"
value="{{ invoice.Contractor_Name }}" placeholder="Type to search..." required>
<input type="hidden" id="subcontractor_id" name="subcontractor_id" value="{{ invoice.Subcontractor_Id }}">
<datalist id="subcontractor_list"></datalist>
</div>
</div>
<!-- Village and PMC No Fields -->
<div class="row2">
<div>
<label for="village">Village Name:</label>
<input type="text" id="village" name="village" value="{{ invoice.Village_Name }}" required/>
<datalist id="village_list"></datalist>
</div>
<div>
<label for="pmc_no">PMC No:</label>
<input type="text" id="pmc_no" name="pmc_no" value="{{ invoice.PMC_No }}" required/>
</div>
</div>
<!-- Work Type and Invoice Details -->
<div class="row2">
<div>
<label for="work_type">Work Type:</label>
<input type="text" id="work_type" name="work_type" value="{{ invoice.Work_Type }}" required/>
</div>
<div>
<label for="invoice_details">Invoice Details:</label>
<textarea id="invoice_details" name="invoice_details" required>{{ invoice.Invoice_Details }}</textarea>
</div>
</div>
<!-- Invoice No and Invoice Date -->
<div class="row2">
<div>
<label for="invoice_no">Invoice No:</label>
<input type="text" id="invoice_no" name="invoice_no" value="{{ invoice.Invoice_No }}" required/>
</div>
<div>
<label for="invoice_date">Invoice Date:</label>
<input type="date" id="invoice_date" name="invoice_date" value="{{ invoice.Invoice_Date }}" required/>
</div>
</div>
<!-- Amount Fields -->
<div class="row3">
<div>
<label for="basic_amount">Basic Amount:</label>
<input type="number" step="0.01" id="basic_amount" name="basic_amount" value="{{ invoice.Basic_Amount }}" required/>
</div>
<div>
<label for="debit_amount">Debit Amount:</label>
<input type="number" step="0.01" id="debit_amount" name="debit_amount" value="{{ invoice.Debit_Amount }}" 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" value="{{ invoice.After_Debit_Amount }}" required/>
</div>
</div>
<!-- GST, TDS, and Other Amounts -->
<div class="row3">
<div>
<label for="amount">Amount:</label>
<input type="number" step="0.01" id="amount" name="amount" value="{{ invoice.Amount }}" required/>
</div>
<div>
<label for="gst_amount">GST Amount:</label>
<input type="number" step="0.01" id="gst_amount" name="gst_amount" value="{{ invoice.GST_Amount }}" required/>
</div>
<div>
<label for="tds_amount">TDS Amount:</label>
<input type="number" step="0.01" id="tds_amount" name="tds_amount" value="{{ invoice.TDS_Amount }}" required/>
</div>
</div>
<!-- SD, On Commission, Hydro Testing -->
<div class="row3">
<div>
<label for="sd_amount">SD Amount:</label>
<input type="number" step="0.01" id="sd_amount" name="sd_amount" value="{{ invoice.SD_Amount }}" required/>
</div>
<div>
<label for="on_commission">On Commission:</label>
<input type="number" step="0.01" id="on_commission" name="on_commission" value="{{ invoice.On_Commission }}" required/>
</div>
<div>
<label for="hydro_testing">Hydro Testing:</label>
<input type="number" step="0.01" id="hydro_testing" name="hydro_testing" value="{{ invoice.Hydro_Testing }}" required/>
</div>
</div>
<!-- Hold Amount Section -->
<div id="hold_amount_container">
{% set seen_types = [] %}
{% for hold in invoice.hold_amounts %}
{% if hold.hold_type not in seen_types %}
{% set _ = seen_types.append(hold.hold_type) %}
<div class="hold-amount-row">
<label for="hold_type_{{ loop.index }}">Hold Type:</label>
<input type="text" id="hold_type_{{ loop.index }}" name="hold_type[]" value="{{ hold.hold_type }}" required/>
<label for="hold_amount_{{ loop.index }}">Hold Amount:</label>
<input type="number" step="0.01" id="hold_amount_{{ loop.index }}" name="hold_amount[]" value="{{ hold.hold_amount }}" required/>
<button type="button" class="remove-hold-amount">Remove</button>
</div>
{% endif %}
{% endfor %}
</div>
<div class="hold-row">
<button type="button" id="add_hold_amount" class="button">+ Add Hold Amount</button>
</div>
<!-- Final Amounts -->
<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" value="{{ invoice.GST_SD_Amount }}" required/>
</div>
<div>
<label for="final_amount">Final Amount:</label>
<input type="number" step="0.01" id="final_amount" name="final_amount" value="{{ invoice.Final_Amount }}" required/>
</div>
</div>
<!-- Submit Button -->
<button type="submit" class="button">Update</button>
</form>
</div>
<!-- JS for dynamic hold amount rows -->
<script>
$(document).ready(function() {
// Add new hold amount row
$("#add_hold_amount").click(function() {
const index = $("#hold_amount_container .hold-amount-row").length;
const newRow = `
<div class="hold-amount-row">
<label for="hold_type_${index}">Hold Type:</label>
<input type="text" id="hold_type_${index}" name="hold_type[]" required/>
<label for="hold_amount_${index}">Hold Amount:</label>
<input type="number" step="0.01" id="hold_amount_${index}" name="hold_amount[]" required/>
<button type="button" class="remove-hold-amount">Remove</button>
</div>
`;
$("#hold_amount_container").append(newRow);
});
// Remove hold amount row
$(document).on("click", ".remove-hold-amount", function() {
$(this).closest(".hold-amount-row").remove();
});
// Submit form via AJAX
$("#invoiceForm").submit(function(e) {
e.preventDefault();
$.ajax({
type: "POST",
url: $(this).attr("action"),
data: $(this).serialize(),
success: function(response) {
if(response.status === "success") {
$("#invoiceSuccessAlert").fadeIn().delay(3000).fadeOut();
alert("Invoice updated successfully!"); // <-- Popup alert
}
},
error: function(xhr) {
alert("Error: " + xhr.responseJSON.message);
}
});
});
});
</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>Edit Payment</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit Payment</h2>
<form action="/edit_payment/{{ payment_data[0] }}" method="POST">
<!-- <label for="invoice_id">Invoice ID:</label><br>-->
<!-- <select id="invoice_id" name="invoice_id" required>-->
<!-- {% for invoice in invoices %}-->
<!-- <option value="{{ invoice[0] }}" {% if invoice[0] == payment_data[0] %}selected{% endif %}>-->
<!-- {{ invoice[1] }}-->
<!-- </option>-->
<!-- {% endfor %}-->
<!-- </select><br><br>-->
<label for="PMC_No">PMC No:</label><br>
<input type="number" step="0.01" id="PMC_No" name="PMC_No" value="{{ payment_data[1] }}" required><br><br>
<label for="invoice_No">Invoice No:</label><br>
<input type="number" step="0.01" id="invoice_No" name="invoice_No" value="{{ payment_data[2] }}" required><br><br>
<label for="Payment_Amount">Amount:</label><br>
<input type="number" step="0.01" id="Payment_Amount" name="Payment_Amount" value="{{ payment_data[3] }}"
required><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" value="{{ payment_data[4] }}"
required><br><br>
<label for="total_amount">Total Amount:</label><br>
<input type="number" step="0.01" id="total_amount" name="total_amount" value="{{ payment_data[5] }}"
required><br><br>
<label for="utr">UTR:</label><br>
<input type="text" id="utr" name="utr" value="{{ payment_data[6] }}" readonly><br><br>
<button type="submit">Update Payment</button>
</form>
</body>
{% endblock %}

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<title>Edit Purchase</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit Purchase Order</h2>
<form method="POST">
<label>Purchase Date:</label>
<input type="date" name="purchase_date" value="{{ purchase['purchase_date'] }}" required><br><br>
<label>Supplier Name:</label>
<input type="text" name="supplier_name" value="{{ purchase['supplier_name'] }}" required><br><br>
<label>Purchase Order No:</label>
<input type="text" name="purchase_order_no" value="{{ purchase['purchase_order_no'] }}" required><br><br>
<label>Item Name:</label>
<input type="text" name="item_name" value="{{ purchase['item_name'] }}" required><br><br>
<label>Quantity:</label>
<input type="number" name="quantity" value="{{ purchase['quantity'] }}" required><br><br>
<label>Unit:</label>
<input type="text" name="unit" value="{{ purchase['unit'] }}" required><br><br>
<label>Rate:</label>
<input type="number" step="0.01" name="rate" value="{{ purchase['rate'] }}" required><br><br>
<label>Amount:</label>
<input type="number" step="0.01" name="amount" value="{{ purchase['amount'] }}" required><br><br>
<label>GST Amount:</label>
<input type="text" name="GST_Amount" value="{{ purchase['GST_Amount'] }}" required><br><br>
<label>TDS:</label>
<input type="number" step="0.01" name="TDS" value="{{ purchase['TDS'] }}" required><br><br>
<label>Final Amount:</label>
<input type="number" step="0.01" name="final_amount" value="{{ purchase['final_amount'] }}" required><br><br>
<input type="submit" value="Update">
</form>
</body>
</html>

View File

@@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Edit State</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit State</h2>
<form method="POST">
<input type="text" name="state_Name" placeholder="State Name" value="{{ state[1] }}" required>
<button type="submit">Update</button>
</form>
</body>
{% endblock %}

View File

@@ -0,0 +1,50 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<title>Edit SubContractor</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Edit Sub-Contractor</h2>
<form method="POST">
<input type="hidden" name="Contractor_Id" value="{{ subcontractor[0] }}">
<label>Enter Contractor Name :</label>
<input type="text" name="Contractor_Name" value="{{ subcontractor[1] }}" required><br>
<label>Address :</label>
<input type="text" name="Address" value="{{ subcontractor[2] }}" required><br>
<label>Mobile No :</label>
<input type="text" name="Mobile_No" value="{{ subcontractor[3] }}" required><br>
<label>PAN No :</label>
<input type="text" name="PAN_No" value="{{ subcontractor[4] }}" required><br>
<label>Email :</label>
<input type="email" name="Email" value="{{ subcontractor[5] }}" required><br>
<label>Gender :</label>
<select name="Gender" required>
<option value="Male" {% if subcontractor[6]=='Male' %}selected{% endif %}>Male</option>
<option value="Female" {% if subcontractor[6]=='Female' %}selected{% endif %}>Female</option>
<option value="Other" {% if subcontractor[6]=='Other' %}selected{% endif %}>Other</option>
</select><br>
<label>GST Registration Type :</label>
<input type="text" name="GST_Registration_Type" value="{{ subcontractor[7] }}" required><br>
<label>GST No :</label>
<input type="text" name="GST_No" value="{{ subcontractor[8] }}" required><br>
<label>Enter New Password :</label>
<input type="text" name="Contractor_password" value="{{ subcontractor[9] }}" required><br>
<button type="submit">Update</button>
</form>
</body>
{% endblock %}

View File

@@ -0,0 +1,52 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Village</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container">
<div class="form-block">
<h2>Edit Village</h2>
<form id="editVillageForm" method="POST">
<label for="block_Id">Select Block:</label>
<select id="block_Id" name="block_Id" required>
<option value="" disabled>Select Block</option>
{% for block in blocks %}
<option value="{{ block[0] }}" {% if block[0] == village_data[1] %}selected{% endif %}>
{{ block[1] }}
</option>
{% endfor %}
</select>
<label for="Village_Name">Enter Village Name:</label>
<input type="text" id="Village_Name" name="Village_Name" value="{{ village_data[0] }}" required>
<span id="villageMessage"></span>
<button type="submit">Update Village</button>
</form>
</div>
<!-- Flash Message (Hidden, used for JS popup) -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div id="flash-message" data-category="{{ category }}" style="display:none;">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<script>
window.onload = function () {
const flash = document.getElementById('flash-message');
if (flash && flash.innerText.trim() !== "") {
alert(flash.innerText);
}
};
</script>
</body>
{% endblock %}

View File

@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html>
<head>
<title>GRN Management</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h2>Add GRN</h2>
<form method="POST" action="/add_grn">
<label>GRN Date:</label>
<input type="date" name="grn_date" required><br><br>
<label>Purchase ID:</label>
<input type="number" name="purchase_id" required><br><br>
{# <label for="purchase_id">Purchase Order:</label>#}
{# <select name="purchase_id" id="purchase_id" required>#}
{# {% for order in purchase_orders %}#}
{# <option value="{{ order['purchase_id'] }}">{{ order['supplier_name'] }}</option>#}
{# {% endfor %}#}
{#</select>#}
<label>Supplier Name:</label>
<input type="text" name="supplier_name" required><br><br>
<label>Item Description:</label>
<input type="text" name="item_description" required><br><br>
<label>Received Quantity:</label>
<input type="number" name="received_quantity" required><br><br>
<label>Unit:</label>
<input type="text" name="unit" required><br><br>
<label>Rate:</label>
<input type="number" step="0.01" name="rate" required><br><br>
<label>Amount:</label>
<input type="number" step="0.01" name="amount" required><br><br>
<label>Remarks:</label>
<input type="text" name="remarks"><br><br>
<input type="submit" value="Add GRN">
</form>
<h2>All GRNs</h2>
<table border="1">
<tr>
<th>ID</th><th>Date</th><th>Purchase ID</th><th>Supplier</th><th>Item</th>
<th>Qty</th><th>Unit</th><th>Rate</th><th>Amount</th><th>Remarks</th><th>Actions</th>
</tr>
{% for grn in grns %}
<tr>
<td>{{ grn[0] }}</td>
<td>{{ grn[1] }}</td>
<td>{{ grn[2] }}</td>
<td>{{ grn[3] }}</td>
<td>{{ grn[4] }}</td>
<td>{{ grn[5] }}</td>
<td>{{ grn[6] }}</td>
<td>{{ grn[7] }}</td>
<td>{{ grn[8] }}</td>
<td>{{ grn[9] }}</td>
<td style="white-space: nowrap;">
<a href="/update_grn/{{ grn['grn_id'] }}" style="margin-right: 10px;">Edit</a>
<form action="/delete_grn/{{ grn[0] }}" method="POST" style="display:inline;">
<button type="submit" onclick="return confirm('Are you sure you want to delete this GRN?')" style="background:none;border:none;color:blue;cursor:pointer;text-decoration:underline;">
Delete
</button>
</form>
</td>
</tr>
{% endfor %}
</table>
</body>
</html>

164
v-2/templates/index.html Normal file
View File

@@ -0,0 +1,164 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Reconciliation </title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/index.css') }}">
<style>
.logout-button {
position: absolute;
top: 2cm;
right: 20px;
}
.logout-button a {
background-color: #e74c3c;
color: white;
padding: 8px 14px;
border-radius: 5px;
text-decoration: none;
font-weight: bold;
}
.logout-button a:hover {
background-color: #c0392b;
}
</style>
</head>
<body>
<div class="logout-button">
<a href="/logout">Logout</a>
</div>
<div class="sidebar">
<img src="https://lceplpmprod.btltech.xyz/assets/images/lcpl.png" alt="logo-image" class="logo">
<ul class="nav-menu">
<li class="nav-item">
<a href="#" class="nav-link active">
<i class="fas fa-home"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a href="/upload_excel_file" class="nav-link">
<i class="fas fa-book"></i> Import Excel
</a>
</li>
<li class="nav-item">
<a href="/report" class="nav-link">
<i class="fas fa-cog"></i> Report Details
</a>
</li>
<!-- <li class="nav-item">
<a href="/work_order_report" class="nav-link">
<i class="fas fa-cog"></i> Work Order Report Details
</a>
</li>
<li class="nav-item">
<a href="/purchase_order_report" class="nav-link">
<i class="fas fa-cog"></i>Purchase Order Report Details
</a>
</li> -->
<li class="nav-item">
<a href="{{ url_for('log.activity_log') }}" class="nav-link">
<i class="fas fa-cog"></i> Logs
</a>
</li>
</ul>
<div class="user-section">
<img src="{{ url_for('static', filename='images/icons/male_profile.jpg') }}" alt="User Avatar">
<div class="user-info">
<span>admin</span>
</div>
</div>
</div>
<div class="company-info">
<marquee behavior="scroll" direction="left">
<h2 class="company-name">Laxmi Civil Engineering Services Pvt. Ltd.</h2>
</marquee>
<p class="app-name">Payment Reconciliation</p>
</div>
<div class="content">
<div class="menu">
<div class="card">
<h2>Profile</h2>
<a class="btn" href="#">Go ➜</a>
</div>
<div class="card">
<h2>States</h2>
<a class="btn" href="/add_state">Go ➜</a>
</div>
<div class="card">
<h2>District</h2>
<a class="btn" href="/add_district">Go ➜</a>
</div>
<div class="card">
<h2>Blocks</h2>
<a class="btn" href="/add_block">Go ➜</a>
</div>
<div class="card">
<h2>Village</h2>
<a class="btn" href="/add_village">Go ➜</a>
</div>
<div class="card">
<h2>Sub-Contractor</h2>
<a class="btn" href="/subcontractor">Go ➜</a>
</div>
<div class="card">
<h2>Invoice</h2>
<a class="btn" href="/add_invoice">Go ➜</a>
</div>
<div class="card">
<h2>Payment</h2>
<a class="btn" href="/add_payment">Go ➜</a>
</div>
<div class="card">
<h2>GST Release</h2>
<a class="btn" href="/add_gst_release">Go ➜</a>
</div>
<div class="card">
<h2>Hold Types</h2>
<a class="btn" href="/add_hold_type">Go ➜</a>
</div>
<div class="card">
<h2>Work Order</h2>
<!-- <a class="btn" href="/add_work_order">Go ➜</a> -->
<a class="btn">Go ➜</a>
</div>
<!-- <div class="card">
<h2>Purchase Order</h2>
<a class="btn" href="/add_purchase_order">Go ➜</a>
</div> -->
<!-- <div class="card">
<h2>Goods Receive Note</h2>
<a class="btn" href="/add_grn">Go ➜</a>
</div> -->
<!-- <div class="card">
<h2>Unreleased GST</h2>
<a class="btn" href="/unreleased_gst">Go ➜</a>
</div>
{# <div class="card">#}
{# <h2>Hold Release </h2>#}
{# <a class="btn" href="/add_hold_release">Go ➜</a>#}
{# </div>#}
</div> -->
</div>
</body>
</html>

90
v-2/templates/login.html Normal file
View File

@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>LCEPL Payment Reconciliation</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f3f3f3;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.login-container {
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
width: 300px;
}
h2 {
text-align: center;
margin-bottom: 1.5rem;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 0.5rem;
margin-bottom: 1rem;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
width: 100%;
padding: 0.5rem;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.flash {
color: red;
text-align: center;
margin-bottom: 1rem;
}
.title {
text-align: center;
color: #007bff;
}
.subtitle {
text-align: center;
}
</style>
</head>
<body>
<div class="login-container">
<h1 class="title">Laxmi Civil Engineering Services</h1>
<h4 class="subtitle">LOGIN</h4>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash flash-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="post">
<input type="text" name="username" placeholder="Username" required />
<input
type="password"
name="password"
placeholder="Password"
required
/>
<button type="submit">Login</button>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,340 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PMC Report</title>
<!-- <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">-->
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/subcontractor_report.css') }}">
</head>
<body>
<div class="container">
<h2>PMC Report</h2>
<div class="info">
<h2>Contractor Details</h2>
<div class="row2">
<div>
<label for="subcontractor">Subcontractor Name:</label>
<input type="text" id="subcontractor" name="subcontractor" value="{{ info.Contractor_Name }}"
readonly/>
</div>
<div>
<label for="Address">Address :</label>
<textarea id="Address" name="Address" readonly>{{ info.Address }}</textarea>
</div>
</div>
<div class="row3">
<div>
<label for="PAN_No">PAN No :</label>
<input type="text" id="PAN_No" name="PAN_No" value="{{ info.PAN_No }}" readonly/>
</div>
<div>
<label for="Mobile_No">Mobile Number :</label>
<input type="text" id="Mobile_No" name="Mobile_No" value="{{ info.Mobile_No }}" readonly/>
</div>
<div>
<label for="Email">Email :</label>
<input type="text" id="Email" name="Email" value="{{ info.Email }}" readonly/>
</div>
</div>
<div class="row2">
<div>
<label for="GST_Registration_Type">GST Registration Type :</label>
<input type="text" id="GST_Registration_Type" name="GST_Registration_Type"
value="{{ info.GST_Registration_Type }}" readonly/>
</div>
<div>
<label for="GST_No">GST No:</label>
<input type="text" id="GST_No" name="GST_No" value="{{ info.GST_No }}" readonly/>
</div>
</div>
<h2>PMC Report for PMC No: {{ info.PMC_No}}</h2>
<div class="row3">
<div>
<label for="State">State :</label>
<input type="text" id="State" name="State" value="{{ info.State_Name }}" readonly/>
</div>
<div>
<label for="District">District :</label>
<input type="text" id="District" name="District" value="{{ info.District_Name }}" readonly/>
</div>
<div>
<label for="Block">Block :</label>
<input type="text" id="Block" name="Block" value="{{ info.Block_Name }}" readonly/>
</div>
</div>
<div class="row2">
<div>
<label for="PMC_No">PMC No:</label>
<input type="text" id="PMC_No" name="PMC_No" value="{{ info.PMC_No }}" readonly/>
</div>
<div>
<label for="Village_Name">Village Name :</label>
<input type="text" id="Village_Name" name="Village_Name"
value="{{ info.Village_Name.capitalize() }}" readonly/>
</div>
</div>
</div>
</div>
<h3>Invoice Details</h3>
<table>
<thead>
<tr>
<th>PMC No</th>
<th>Village Name</th>
<th>Work Type</th>
<th>Invoice Details</th>
<th>Invoice Date</th>
<th>Invoice No</th>
<th>Basic Amount</th>
<th>Debit</th>
<th>After Debit Amt</th>
<th>GST (18%)</th>
<th>Amount</th>
<th>TDS (1%)</th>
<th>SD (5%)</th>
<th>On Commission</th>
<th>Hydro Testing</th>
<!-- Dynamic Hold Types -->
{% set hold_types = invoices | map(attribute='hold_type') | reject('none') | unique | list %}
{% for hold in hold_types %}
<th>{{ hold }}</th>
{% endfor %}
<th>GST SD (18%)</th>
<th>Final Amount</th>
</tr>
</thead>
<tbody>
{% if invoices %}
{% for invoice in invoices %}
<tr>
<td>{{ invoice.PMC_No }}</td>
<td>{{ invoice.Village_Name.capitalize() }}</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.GST_Amount }}</td>
<td>{{ invoice.Amount }}</td>
<td>{{ invoice.TDS_Amount }}</td>
<td>{{ invoice.SD_Amount }}</td>
<td>{{ invoice.On_Commission }}</td>
<td>{{ invoice.Hydro_Testing }}</td>
<!-- Dynamic Hold Amounts -->
{% for hold in hold_types %}
<td>
{% if invoice.hold_type == hold %}
{{ invoice.hold_amount }}
{% else %}
0
{% endif %}
</td>
{% endfor %}
<td>{{ invoice.GST_SD_Amount }}</td>
<td>{{ invoice.Final_Amount }}</td>
</tr>
{% endfor %}
<tr>
<th colspan="6">Total</th>
<th>{{total["sum_invo_basic_amt"]}}</th>
<th>{{total["sum_invo_debit_amt"]}}</th>
<th>{{total["sum_invo_after_debit_amt"]}}</th>
<th>{{total["sum_invo_gst_amt"]}}</th>
<th>{{total["sum_invo_amt"]}}</th>
<th>{{total["sum_invo_tds_amt"]}}</th>
<th>{{total["sum_invo_ds_amt"]}}</th>
<th>{{total["sum_invo_on_commission"]}}</th>
<th>{{total["sum_invo_hydro_test"]}}</th>
{% set hold_types = invoices | map(attribute='hold_type') | unique | list %}
{% for hold in hold_types %}
<th>{{total["sum_invo_hold_amt"]}}</th>
{% endfor %}
<th>{{total["sum_invo_gst_sd_amt"]}}</th>
<th>{{total["sum_invo_final_amt"]}}</th>
</tr>
{% else %}
<tr>
<td colspan="{{ 17 + hold_types|length }}">No invoices found.</td>
</tr>
{% endif %}
</tbody>
</table>
<h3>Hold Release</h3>
<table>
<thead>
<tr>
<th>PMC No</th>
<th>Invoice No</th>
<th>Invoice Details</th>
<th>Basic Amount</th>
<th>Total Amount</th>
<th>UTR</th>
</tr>
</thead>
<tbody>
{%if hold_release%}
{%for hold in hold_release%}
<tr>
<td>{{ hold['PMC_No'] }}</td>
<td>{{ hold['Invoice_No'] }}</td>
<td>{{ hold['Invoice_Details'] }}</td>
<td>{{ hold['Basic_Amount'] }}</td>
<td>{{ hold['Total_Amount'] }}</td>
<td>{{ hold['UTR'] }}</td>
</tr>
{%endfor%}
{%else%}
<tr>
<td colspan="6" style="text-align:center;">No data present</td>
</tr>
{%endif%}
</tbody>
</table>
<br>
<h3>GST Release Note Details</h3>
<table>
<thead>
<tr>
<th>PMC No</th>
<th>Invoice No</th>
<th>Basic Amount</th>
<th>Final Amount</th>
</tr>
</thead>
<tbody>
{% if gst_rel %}
{% for gst in gst_rel %}
<tr>
<td>{{ gst.pmc_no }}</td>
<td>{{ gst.invoice_no }}</td>
<td>{{ gst.basic_amount }}</td>
<td>{{ gst.final_amount }}</td>
</tr>
{% endfor %}
<tr>
<th colspan="2">Total</th>
<th>{{total["sum_gst_basic_amt"]}}</th>
<th>{{total["sum_gst_final_amt"]}}</th>
</tr>
{% else %}
<tr>
<td colspan="4">No GST release found.</td>
</tr>
{% endif %}
</tbody>
</table>
<tr>
<h3>Credit Details</h3>
</tr>
<table>
<thead>
<tr>
<th>PMC No</th>
<th>Invoice Details</th>
<th>Basic Amount</th>
<th>Debit</th>
<th>After Debit Amt</th>
<th>GST Amount</th>
<th>Amount</th>
<th>Final Amount</th>
<th>Payment Amount</th>
<th>Total Amount</th>
<th>UTR</th>
</tr>
{% if credit_note %}
{% for credit in credit_note %}
<tr>
<td>{{ credit["PMC_No"] }}</td>
<td>{{ credit["Invoice_Details"] }}</td>
<td>{{ credit["Basic_Amount"] }}</td>
<td>{{ credit["Debit_Amount"] }}</td>
<td>{{ credit["After_Debit_Amount"] }}</td>
<td>{{ credit["GST_Amount"] }}</td>
<td>{{ credit["Amount"] }}</td>
<td>{{ credit["Final_Amount"] }}</td>
<td>{{ credit["Payment_Amount"] }}</td>
<td>{{ credit["Total_Amount"] }}</td>
<td>{{ credit["UTR"] }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="11">No Credit note found.</td>
</tr>
{% endif %}
</thead>
</table>
<br>
<h3>Payment Details</h3>
<table>
<thead>
<tr>
<th>PMC No</th>
<th>Invoice No</th>
<th>Amount</th>
<th>TDS Amount @ 1% on BASIC AMOUNT</th>
<th>Total Amount Paid</th>
<th>UTR</th>
</tr>
</thead>
<tbody>
{% if payments %}
{% for pay in payments %}
<tr>
<td>{{ pay.pmc_no }}</td>
<td>{{ pay.invoice_no }}</td>
<td>{{ pay.Payment_Amount }}</td>
<td>{{ pay.TDS_Payment_Amount }}</td>
<td>{{ pay.Total_amount }}</td>
<td>{{ pay.utr}}</td>
</tr>
{% endfor %}
<tr>
<th colspan="2">Total</th>
<th>{{total["sum_pay_payment_amt"]}}</th>
<th>{{total["sum_pay_tds_payment_amt"]}}</th>
<th>{{total["sum_pay_total_amt"]}}</th>
<th></th>
</tr>
{% else %}
<tr>
<td colspan="6">No payment found.</td>
</tr>
{% endif %}
</tbody>
</table>
<a href="/download_pmc_report/{{ info.PMC_No}}">
<button class="download-btn">Download PMC Report</button>
</a>
</body>
{% endblock %}

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