added full project files

This commit is contained in:
2025-11-25 13:33:49 +05:30
parent 211a0a970e
commit 9b71ab6716
538 changed files with 17470 additions and 0 deletions

8
.gitignore vendored Normal file
View File

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

18
Dockerfile Normal file
View File

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

26
MAIN2.PY Normal file
View File

@@ -0,0 +1,26 @@
from flask import Blueprint, request, jsonify, send_file, current_app, render_template
from datetime import datetime
import ast
import os
from db import DB # Your existing DB wrapper
# =============================================================
# Base Service (common DB operations)
# =============================================================
class BaseService:
def __init__(self):
self.db = DB()
def fetch_all(self, proc, params=None):
return self.db.fetch_all_proc(proc, params)
def fetch_one(self, proc, params=None):
return self.db.fetch_one_proc(proc, params)
def execute_proc(self, proc, params=None):
return self.db.exec_proc(proc, params)
def get_conn(self):
return self.db.get_connection()

View File

@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

5017
activity.log Normal file

File diff suppressed because it is too large Load Diff

114
app.log Normal file
View File

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

18
config.py Normal file
View File

@@ -0,0 +1,18 @@
import mysql.connector
import os
# Get MySQL credentials from environment variables
MYSQL_HOST = os.getenv("MYSQL_HOST", "127.0.0.1")
MYSQL_USER = os.getenv("MYSQL_USER", "root")
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "tiger")
MYSQL_DB = os.getenv("MYSQL_DB", "test")
# Connect to MySQL
def get_db_connection():
return mysql.connector.connect(
host=MYSQL_HOST,
user=MYSQL_USER,
password=MYSQL_PASSWORD,
database=MYSQL_DB
)

35
docker-compose.yml Normal file
View File

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

0
logs/activity.log Normal file
View File

74
logs/audit.log Normal file
View File

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

4005
main.py Normal file

File diff suppressed because it is too large Load Diff

933
main1.py Normal file
View File

@@ -0,0 +1,933 @@
# PART 1 of main.py
# Imports, DB helper, app init, auth/login, logging & small helpers
from decimal import Decimal
from datetime import datetime
import os
import re
import logging
from logging.handlers import RotatingFileHandler
from flask import (
Flask, render_template, request, redirect, url_for, send_from_directory,
flash, jsonify, json, session, current_app
)
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
import mysql.connector
from mysql.connector import Error
import config
import openpyxl
import ast
import pandas as pd
from openpyxl.styles import Font
from ldap3 import Server, Connection, ALL, SUBTREE
from ldap3.core.exceptions import LDAPBindError
# ---------------------------
# App and Login manager init
# ---------------------------
app = Flask(__name__)
app.secret_key = os.environ.get('FLASK_SECRET_KEY', '9f2a1b8c4d6e7f0123456789abcdef01')
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
# ---------------------------
# DB helper (dictionary cursor)
# ---------------------------
class DB:
"""
Lightweight DB helper to call stored procedures with dictionary=True cursors.
Usage:
db = DB()
rows = db.fetch_all_proc('GetAllStates')
single = db.fetch_one_proc('GetStateByID', [id])
db.exec_proc('SaveState', [state_name])
db.close()
"""
def __init__(self):
self.conn = None
self.cursor = None
try:
self.conn = config.get_db_connection()
except Exception as e:
# Keep None and let callers handle connection absence
self.conn = None
app.logger.exception("DB connection failed: %s", e)
def _ensure_cursor(self, dict_mode=True):
if not self.conn:
raise mysql.connector.Error("No DB connection")
self.cursor = self.conn.cursor(dictionary=dict_mode)
def fetch_all_proc(self, proc_name, params=None):
try:
self._ensure_cursor(dict_mode=True)
self.cursor.callproc(proc_name, params or [])
results = []
for res in self.cursor.stored_results():
results = res.fetchall()
return results
finally:
self.close_cursor()
def fetch_one_proc(self, proc_name, params=None):
rows = self.fetch_all_proc(proc_name, params)
return rows[0] if rows else None
def exec_proc(self, proc_name, params=None):
try:
self._ensure_cursor(dict_mode=True)
self.cursor.callproc(proc_name, params or [])
# advance through any results to avoid unread result issues
for _ in self.cursor.stored_results():
pass
if self.conn:
self.conn.commit()
finally:
self.close_cursor()
def close_cursor(self):
try:
if self.cursor:
self.cursor.close()
except Exception:
pass
self.cursor = None
def close(self):
self.close_cursor()
try:
if self.conn:
self.conn.close()
except Exception:
pass
self.conn = None
# ---------------------------
# ResponseHandler (centralized messages)
# ---------------------------
class ResponseHandler:
@staticmethod
def invalid_name(entity):
return {'status': 'error', 'message': f'Invalid {entity} name. Only letters and spaces 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}.'}
# JSON response helper
def json_response(message_obj, status_code=200):
return jsonify(message_obj), status_code
# ---------------------------
# Logging helper
# ---------------------------
if not app.debug:
log_file = os.path.join(app.root_path, 'app.log')
handler = RotatingFileHandler(log_file, maxBytes=5*1024*1024, backupCount=2)
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')
handler.setFormatter(formatter)
app.logger.addHandler(handler)
def log_action(action, details=""):
"""Write a user action to activity.log with timestamp and user info."""
try:
log_file = os.path.join(current_app.root_path, 'activity.log')
except RuntimeError:
# current_app not available (like unit tests). Use app.root_path fallback.
log_file = os.path.join(app.root_path, 'activity.log')
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
user = "Unknown"
try:
if current_user and hasattr(current_user, "id"):
user = getattr(current_user, "id", "Unknown")
elif session.get('username'):
user = session.get('username')
except Exception:
pass
try:
with open(log_file, "a", encoding="utf-8") as f:
f.write(f"Timestamp: {timestamp} | User: {user} | Action: {action} | Details: {details}\n")
except Exception:
app.logger.exception("Failed to write activity log.")
# ---------------------------
# Simple User class for flask-login
# ---------------------------
class User(UserMixin):
def __init__(self, id, cn=None, username=None, sAMAccountName=None):
self.id = id
self.cn = cn
self.username = username
self.sAMAccountName = sAMAccountName
@login_manager.user_loader
def load_user(user_id):
# Minimal loader: return User object with id.
return User(user_id)
# ---------------------------
# Constants & simple validators
# ---------------------------
STR_NAME_PATTERN = r"^[A-Za-z\s]+$"
def is_valid_name(value):
return bool(re.match(STR_NAME_PATTERN, value.strip())) if value else False
# ---------------------------
# Basic Routes: index, login, logout
# ---------------------------
@app.route('/')
@login_required
def index():
return render_template('index.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
"""
Login supports:
- static admin/admin123 fallback
- LDAP bind if not static
"""
if request.method == 'POST':
username = request.form.get('username', '').strip()
password = request.form.get('password', '')
# static fallback
if username == 'admin' and password == 'admin123':
session['username'] = username
login_user(User(username))
log_action('Login', f"Static admin logged in: {username}")
return redirect(url_for('index'))
ldap_user_dn = f"uid={username},ou=users,dc=lcepl,dc=org"
try:
conn = Connection(Server('ldap://localhost:389', get_info=ALL), user=ldap_user_dn, password=password, auto_bind=True)
session['username'] = username
login_user(User(username))
log_action('Login', f"LDAP login: {username}")
conn.unbind()
return redirect(url_for('index'))
except LDAPBindError:
flash('Invalid credentials.', 'danger')
except Exception as e:
app.logger.exception("LDAP error during login")
flash(f'LDAP error: {str(e)}', 'danger')
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
try:
user_id = getattr(current_user, 'id', 'Unknown')
except Exception:
user_id = session.get('username', 'Unknown')
log_action('Logout', f"User {user_id} logged out")
logout_user()
flash('You have been logged out.', 'info')
return redirect(url_for('login'))
# ---------------------------
# Activity log viewer
# ---------------------------
@app.route('/activity_log', methods=['GET', 'POST'])
@login_required
def activity_log():
logs = []
log_file = os.path.join(current_app.root_path, 'activity.log')
if os.path.exists(log_file):
with open(log_file, 'r', encoding='utf-8') 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()
})
# Filters
start_date = request.values.get("start_date")
end_date = request.values.get("end_date")
username = request.values.get("username")
filtered_logs = logs
# date filter
if start_date or end_date:
try:
start_dt = datetime.strptime(start_date, "%Y-%m-%d") if start_date else datetime.min
end_dt = datetime.strptime(end_date, "%Y-%m-%d") if end_date else datetime.max
end_dt = end_dt.replace(hour=23, minute=59, second=59)
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:
app.logger.exception("activity_log date filter parse error")
if username:
filtered_logs = [log for log in filtered_logs if username.lower() in log["user"].lower()]
return render_template('activity_log.html', logs=filtered_logs, start_date=start_date, end_date=end_date, username=username)
# PART 2 of main.py
# --------------------------------------------
# STATE, DISTRICT, BLOCK, VILLAGE MANAGEMENT
# --------------------------------------------
# ---------------- STATE ----------------
@app.route('/add_state', methods=['GET', 'POST'])
@login_required
def add_state():
db = DB()
if request.method == 'POST':
state_name = request.form['state_Name'].strip()
if not is_valid_name(state_name):
db.close()
return json_response(ResponseHandler.invalid_name("state"), 400)
existing = db.fetch_one_proc('CheckStateExists', [state_name])
if existing:
db.close()
return json_response(ResponseHandler.already_exists("state"), 409)
db.exec_proc('SaveState', [state_name])
log_action('Add State', f"State added: {state_name}")
db.close()
return json_response(ResponseHandler.add_success("state"), 200)
statedata = db.fetch_all_proc('GetAllStates')
db.close()
return render_template('add_state.html', statedata=statedata)
@app.route('/delete_state/<int:id>', methods=['POST'])
@login_required
def delete_state(id):
db = DB()
try:
db.exec_proc('DeleteState', [id])
log_action('Delete State', f"Deleted state id={id}")
return json_response(ResponseHandler.delete_success("state"))
except Exception as e:
app.logger.exception("DeleteState failed: %s", e)
return json_response(ResponseHandler.delete_failure("state"), 500)
finally:
db.close()
@app.route('/update_state/<int:id>', methods=['POST'])
@login_required
def update_state(id):
db = DB()
state_name = request.form.get('state_Name', '').strip()
if not is_valid_name(state_name):
db.close()
return json_response(ResponseHandler.invalid_name("state"), 400)
try:
db.exec_proc('UpdateStateById', [id, state_name])
log_action('Update State', f"Updated state id={id}, name={state_name}")
return json_response(ResponseHandler.update_success("state"))
except Exception as e:
app.logger.exception("UpdateState failed: %s", e)
return json_response(ResponseHandler.update_failure("state"), 500)
finally:
db.close()
# ---------------- DISTRICT ----------------
@app.route('/add_district', methods=['GET', 'POST'])
@login_required
def add_district():
db = DB()
if request.method == 'POST':
state_id = request.form.get('state_id')
district_name = request.form.get('district_Name', '').strip()
if not is_valid_name(district_name):
db.close()
return json_response(ResponseHandler.invalid_name("district"), 400)
existing = db.fetch_one_proc('CheckDistrictExists', [state_id, district_name])
if existing:
db.close()
return json_response(ResponseHandler.already_exists("district"), 409)
db.exec_proc('SaveDistrict', [state_id, district_name])
log_action('Add District', f"District added: {district_name} (state_id={state_id})")
db.close()
return json_response(ResponseHandler.add_success("district"), 200)
statedata = db.fetch_all_proc('GetAllStates')
districtdata = db.fetch_all_proc('GetAllDistricts')
db.close()
return render_template('add_district.html', statedata=statedata, districtdata=districtdata)
@app.route('/delete_district/<int:id>', methods=['POST'])
@login_required
def delete_district(id):
db = DB()
try:
db.exec_proc('DeleteDistrict', [id])
log_action('Delete District', f"Deleted district id={id}")
return json_response(ResponseHandler.delete_success("district"))
except Exception as e:
app.logger.exception("DeleteDistrict failed: %s", e)
return json_response(ResponseHandler.delete_failure("district"), 500)
finally:
db.close()
@app.route('/update_district/<int:id>', methods=['POST'])
@login_required
def update_district(id):
db = DB()
state_id = request.form.get('state_id')
district_name = request.form.get('district_Name', '').strip()
if not is_valid_name(district_name):
db.close()
return json_response(ResponseHandler.invalid_name("district"), 400)
try:
db.exec_proc('UpdateDistrictById', [id, state_id, district_name])
log_action('Update District', f"Updated district id={id}, name={district_name}")
return json_response(ResponseHandler.update_success("district"))
except Exception as e:
app.logger.exception("UpdateDistrict failed: %s", e)
return json_response(ResponseHandler.update_failure("district"), 500)
finally:
db.close()
# ---------------- BLOCK ----------------
@app.route('/add_block', methods=['GET', 'POST'])
@login_required
def add_block():
db = DB()
if request.method == 'POST':
district_id = request.form.get('district_id')
block_name = request.form.get('block_Name', '').strip()
if not is_valid_name(block_name):
db.close()
return json_response(ResponseHandler.invalid_name("block"), 400)
existing = db.fetch_one_proc('CheckBlockExists', [district_id, block_name])
if existing:
db.close()
return json_response(ResponseHandler.already_exists("block"), 409)
db.exec_proc('SaveBlock', [district_id, block_name])
log_action('Add Block', f"Block added: {block_name} (district_id={district_id})")
db.close()
return json_response(ResponseHandler.add_success("block"), 200)
districtdata = db.fetch_all_proc('GetAllDistricts')
blockdata = db.fetch_all_proc('GetAllBlocks')
db.close()
return render_template('add_block.html', districtdata=districtdata, blockdata=blockdata)
@app.route('/delete_block/<int:id>', methods=['POST'])
@login_required
def delete_block(id):
db = DB()
try:
db.exec_proc('DeleteBlock', [id])
log_action('Delete Block', f"Deleted block id={id}")
return json_response(ResponseHandler.delete_success("block"))
except Exception as e:
app.logger.exception("DeleteBlock failed: %s", e)
return json_response(ResponseHandler.delete_failure("block"), 500)
finally:
db.close()
@app.route('/update_block/<int:id>', methods=['POST'])
@login_required
def update_block(id):
db = DB()
district_id = request.form.get('district_id')
block_name = request.form.get('block_Name', '').strip()
if not is_valid_name(block_name):
db.close()
return json_response(ResponseHandler.invalid_name("block"), 400)
try:
db.exec_proc('UpdateBlockById', [id, district_id, block_name])
log_action('Update Block', f"Updated block id={id}, name={block_name}")
return json_response(ResponseHandler.update_success("block"))
except Exception as e:
app.logger.exception("UpdateBlock failed: %s", e)
return json_response(ResponseHandler.update_failure("block"), 500)
finally:
db.close()
# ---------------- VILLAGE ----------------
@app.route('/add_village', methods=['GET', 'POST'])
@login_required
def add_village():
db = DB()
if request.method == 'POST':
block_id = request.form.get('block_id')
village_name = request.form.get('village_Name', '').strip()
if not is_valid_name(village_name):
db.close()
return json_response(ResponseHandler.invalid_name("village"), 400)
existing = db.fetch_one_proc('CheckVillageExists', [block_id, village_name])
if existing:
db.close()
return json_response(ResponseHandler.already_exists("village"), 409)
db.exec_proc('SaveVillage', [block_id, village_name])
log_action('Add Village', f"Village added: {village_name} (block_id={block_id})")
db.close()
return json_response(ResponseHandler.add_success("village"), 200)
blockdata = db.fetch_all_proc('GetAllBlocks')
villagedata = db.fetch_all_proc('GetAllVillages')
db.close()
return render_template('add_village.html', blockdata=blockdata, villagedata=villagedata)
@app.route('/delete_village/<int:id>', methods=['POST'])
@login_required
def delete_village(id):
db = DB()
try:
db.exec_proc('DeleteVillage', [id])
log_action('Delete Village', f"Deleted village id={id}")
return json_response(ResponseHandler.delete_success("village"))
except Exception as e:
app.logger.exception("DeleteVillage failed: %s", e)
return json_response(ResponseHandler.delete_failure("village"), 500)
finally:
db.close()
@app.route('/update_village/<int:id>', methods=['POST'])
@login_required
def update_village(id):
db = DB()
block_id = request.form.get('block_id')
village_name = request.form.get('village_Name', '').strip()
if not is_valid_name(village_name):
db.close()
return json_response(ResponseHandler.invalid_name("village"), 400)
try:
db.exec_proc('UpdateVillageById', [id, block_id, village_name])
log_action('Update Village', f"Updated village id={id}, name={village_name}")
return json_response(ResponseHandler.update_success("village"))
except Exception as e:
app.logger.exception("UpdateVillage failed: %s", e)
return json_response(ResponseHandler.update_failure("village"), 500)
finally:
db.close()
# -----------------------------------
# INVOICE ENTRY
# -----------------------------------
@app.route('/invoice_entry', methods=['GET', 'POST'])
@login_required
def invoice_entry():
db = DB()
if request.method == 'POST':
inv_no = request.form['Invoice_No'].strip()
inv_date = request.form['Invoice_Date']
state_id = request.form['state']
district_id = request.form['district']
block_id = request.form['block']
village_id = request.form['village']
subcontractor_id = request.form['subcontractor']
work_type_id = request.form['work_type']
final_amount = request.form['Final_Amount']
# Check duplicate invoice number
exists = db.fetch_one_proc("CheckInvoiceExists", [inv_no])
if exists:
db.close()
return json_response(ResponseHandler.already_exists("invoice"), 409)
db.exec_proc("InsertInvoice", [
inv_no, inv_date,
state_id, district_id, block_id, village_id,
subcontractor_id, work_type_id, final_amount
])
db.close()
log_action("Add Invoice", f"Invoice: {inv_no}")
return json_response(ResponseHandler.add_success("invoice"), 200)
# GET REQUEST → Load dropdown data
statedata = db.fetch_all_proc("GetAllStates")
districtdata = db.fetch_all_proc("GetDistrictDetails")
blockdata = db.fetch_all_proc("GetBlockDetails")
villagedata = db.fetch_all_proc("GetVillageDetails")
subcontractordata = db.fetch_all_proc("GetSubcontractorDetails")
worktypedata = db.fetch_all_proc("GetWorkTypeDetails")
invoicedata = db.fetch_all_proc("GetInvoiceDetails")
db.close()
return render_template('invoice_entry.html',
statedata=statedata, districtdata=districtdata,
blockdata=blockdata, villagedata=villagedata,
subcontractordata=subcontractordata,
worktypedata=worktypedata,
invoicedata=invoicedata)
@app.route('/delete_invoice/<int:id>', methods=['POST'])
@login_required
def delete_invoice(id):
db = DB()
db.exec_proc("DeleteInvoice", [id])
db.close()
log_action("Delete Invoice", f"Invoice ID: {id}")
return json_response(ResponseHandler.delete_success("invoice"), 200)
@app.route('/update_invoice', methods=['POST'])
@login_required
def update_invoice():
invoice_id = request.form['invoice_id']
inv_no = request.form['Invoice_No'].strip()
inv_date = request.form['Invoice_Date']
state_id = request.form['state']
district_id = request.form['district']
block_id = request.form['block']
village_id = request.form['village']
subcontractor_id = request.form['subcontractor']
work_type_id = request.form['work_type']
final_amount = request.form['Final_Amount']
db = DB()
db.exec_proc("UpdateInvoice", [
invoice_id, inv_no, inv_date,
state_id, district_id, block_id, village_id,
subcontractor_id, work_type_id, final_amount
])
db.close()
log_action("Update Invoice", f"Invoice ID {invoice_id}")
return json_response(ResponseHandler.update_success("invoice"), 200)
# -----------------------------------
# HOLD ENTRY
# -----------------------------------
@app.route('/hold_entry', methods=['GET', 'POST'])
@login_required
def hold_entry():
db = DB()
if request.method == 'POST':
invoice_id = request.form['invoice']
hold_type_id = request.form['hold_type']
remarks = request.form['Remarks'].strip()
db.exec_proc("InsertHold", [invoice_id, hold_type_id, remarks])
db.close()
log_action("Add Hold", f"Invoice ID: {invoice_id}")
return json_response(ResponseHandler.add_success("hold"), 200)
invoicedata = db.fetch_all_proc("GetInvoiceDetails")
holdtypedata = db.fetch_all_proc("GetHoldTypeDetails")
holddata = db.fetch_all_proc("GetHoldDetails")
db.close()
return render_template('hold_entry.html',
invoicedata=invoicedata,
holdtypedata=holdtypedata,
holddata=holddata)
@app.route('/delete_hold/<int:id>', methods=['POST'])
@login_required
def delete_hold(id):
db = DB()
db.exec_proc("DeleteHold", [id])
db.close()
log_action("Delete Hold", f"Hold ID: {id}")
return json_response(ResponseHandler.delete_success("hold"), 200)
# -----------------------------------
# PAYMENT ENTRY
# -----------------------------------
@app.route('/payment_entry', methods=['GET', 'POST'])
@login_required
def payment_entry():
db = DB()
if request.method == 'POST':
invoice_id = request.form['invoice']
payment_date = request.form['Payment_Date']
payment_amt = request.form['Payment_Amount']
tds_amt = request.form['TDS_Payment_Amount']
total = request.form['Total_amount']
utr = request.form['utr'].strip()
db.exec_proc("InsertPayment", [
invoice_id, payment_date, payment_amt, tds_amt, total, utr
])
db.close()
log_action("Add Payment", f"Invoice ID: {invoice_id}")
return json_response(ResponseHandler.add_success("payment"), 200)
invoicedata = db.fetch_all_proc("GetInvoiceDetails")
paymentdata = db.fetch_all_proc("GetPaymentDetails")
db.close()
return render_template('payment_entry.html',
invoicedata=invoicedata,
paymentdata=paymentdata)
@app.route('/delete_payment/<int:id>', methods=['POST'])
@login_required
def delete_payment(id):
db = DB()
db.exec_proc("DeletePayment", [id])
db.close()
log_action("Delete Payment", f"Payment ID: {id}")
return json_response(ResponseHandler.delete_success("payment"), 200)
# -----------------------------------
# SUBCONTRACTOR MANAGEMENT
# -----------------------------------
@app.route('/add_subcontractor', methods=['GET', 'POST'])
@login_required
def add_subcontractor():
db = DB()
if request.method == 'POST':
subcontractor_name = request.form['subcontractor_Name'].strip()
mobile_no = request.form['Mobile_No'].strip()
if not subcontractor_name:
return json_response(ResponseHandler.invalid_name("subcontractor"), 400)
exists = db.fetch_one_proc("CheckSubcontractorExists", [subcontractor_name])
if exists:
db.close()
return json_response(ResponseHandler.already_exists("subcontractor"), 409)
db.exec_proc("SaveSubcontractor", [subcontractor_name, mobile_no])
db.close()
log_action("Add Subcontractor", f"Name: {subcontractor_name}")
return json_response(ResponseHandler.add_success("subcontractor"), 200)
subcontractordata = db.fetch_all_proc("GetSubcontractorDetails")
db.close()
return render_template('add_subcontractor.html', subcontractordata=subcontractordata)
@app.route('/delete_subcontractor/<int:id>', methods=['POST'])
@login_required
def delete_subcontractor(id):
db = DB()
db.exec_proc("DeleteSubcontractor", [id])
db.close()
log_action("Delete Subcontractor", f"ID: {id}")
return json_response(ResponseHandler.delete_success("subcontractor"), 200)
@app.route('/update_subcontractor', methods=['POST'])
@login_required
def update_subcontractor():
subcontractor_id = request.form['subcontractor_id']
subcontractor_name = request.form['subcontractor_Name'].strip()
mobile_no = request.form['Mobile_No'].strip()
db = DB()
db.exec_proc("UpdateSubcontractor", [subcontractor_id, subcontractor_name, mobile_no])
db.close()
log_action("Update Subcontractor", f"ID {subcontractor_id}")
return json_response(ResponseHandler.update_success("subcontractor"), 200)
# -----------------------------------
# WORK TYPE MANAGEMENT
# -----------------------------------
@app.route('/add_work_type', methods=['GET', 'POST'])
@login_required
def add_work_type():
db = DB()
if request.method == 'POST':
work_type_name = request.form['worktype_Name'].strip()
if not is_valid_name(work_type_name):
return json_response(ResponseHandler.invalid_name("work type"), 400)
exists = db.fetch_one_proc("CheckWorkTypeExists", [work_type_name])
if exists:
db.close()
return json_response(ResponseHandler.already_exists("work type"), 409)
db.exec_proc("SaveWorkType", [work_type_name])
db.close()
log_action("Add Work Type", f"Name: {work_type_name}")
return json_response(ResponseHandler.add_success("work type"), 200)
worktypedata = db.fetch_all_proc("GetWorkTypeDetails")
db.close()
return render_template('add_work_type.html', worktypedata=worktypedata)
@app.route('/delete_work_type/<int:id>', methods=['POST'])
@login_required
def delete_work_type(id):
db = DB()
db.exec_proc("DeleteWorkType", [id])
db.close()
log_action("Delete Work Type", f"ID: {id}")
return json_response(ResponseHandler.delete_success("work type"), 200)
@app.route('/update_work_type', methods=['POST'])
@login_required
def update_work_type():
work_type_id = request.form['worktype_id']
work_type_name = request.form['worktype_Name'].strip()
if not is_valid_name(work_type_name):
return json_response(ResponseHandler.invalid_name("work type"), 400)
db = DB()
db.exec_proc("UpdateWorkTypeById", [work_type_id, work_type_name])
db.close()
log_action("Update Work Type", f"ID {work_type_id}")
return json_response(ResponseHandler.update_success("work type"), 200)
# -----------------------------------
# EXCEL BULK UPLOAD (INVOICE)
# -----------------------------------
@app.route('/upload_excel', methods=['GET', 'POST'])
@login_required
def upload_excel():
if request.method == 'POST':
file = request.files.get("file")
if not file:
flash("No file selected.", "danger")
return redirect(url_for("upload_excel"))
filepath = os.path.join(app.root_path, "uploads", file.filename)
os.makedirs(os.path.dirname(filepath), exist_ok=True)
file.save(filepath)
workbook = openpyxl.load_workbook(filepath)
sheet = workbook.active
db = DB()
for row in sheet.iter_rows(min_row=2, values_only=True):
inv_no, inv_date, state, district, block, village, subcontractor, work_type, amount = row
db.exec_proc("InsertInvoiceBulk", [
inv_no, inv_date, state, district, block, village, subcontractor, work_type, amount
])
db.close()
log_action("Bulk Invoice Upload", f"File: {file.filename}")
flash("Excel uploaded successfully!", "success")
return redirect(url_for("invoice_entry"))
return render_template('uploadExcelFile.html')
# -----------------------------------
# REPORTS
# -----------------------------------
@app.route('/report', methods=['GET', 'POST'])
@login_required
def report():
db = DB()
statedata = db.fetch_all_proc("GetAllStates")
subcontractordata = db.fetch_all_proc( "GetSubcontractorDetails",
["1=1", "{}"] )
db.close()
return render_template('report.html', statedata=statedata, subcontractordata=subcontractordata)
@app.route('/view_report', methods=['POST'])
@login_required
def view_report():
state_id = request.form.get("state", "")
subcontractor_id = request.form.get("subcontractor", "")
db = DB()
reportdata = db.fetch_all_proc("GetReportData", [state_id, subcontractor_id])
db.close()
log_action("Generate Report", f"State {state_id}, Subcontractor {subcontractor_id}")
return render_template("report_table.html", reportdata=reportdata)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000, debug=True)

4
requirements.txt Normal file
View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

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

@@ -0,0 +1,87 @@
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 are 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);
}
});
});
$('#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.districts.forEach(function(district) {
districtDropdown.append('<option value="' + district.District_id + '">' + district.District_Name + '</option>');
});
districtDropdown.prop('disabled', false);
},
error: function() {
alert('Error fetching districts. Please try again.');
}
});
} else {
$('#district_Id').prop('disabled', true);
}
});
});

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

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

View File

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

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

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

@@ -0,0 +1,102 @@
window.onload = function () {
document.getElementById('Village_Name').focus();
};
$(document).ready(function () {
$('#state_Id').change(function () {
var stateId = $(this).val();
if (stateId) {
$.ajax({
url: '/get_districts/' + stateId,
type: 'GET',
success: function (data) {
var districtDropdown = $('#district_Id');
districtDropdown.empty().append('<option value="" disabled selected>Select District</option>');
data.districts.forEach(function (district) {
districtDropdown.append('<option value="' + district.District_id + '">' + district.District_Name + '</option>');
});
districtDropdown.prop('disabled', false);
}
});
}
});
$('#district_Id').change(function () {
var districtId = $(this).val();
if (districtId) {
$.ajax({
url: '/get_blocks/' + districtId,
type: 'GET',
success: function (data) {
var blockDropdown = $('#block_Id');
blockDropdown.empty().append('<option value="" disabled selected>Select Block</option>');
data.blocks.forEach(function (block) {
blockDropdown.append('<option value="' + block.Block_Id + '">' + block.Block_Name + '</option>');
});
blockDropdown.prop('disabled', false);
}
});
}
});
$('#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);
}
});
$('#Village_Name, #block_Id').on('change keyup', function () {
var blockId = $('#block_Id').val();
var villageName = $('#Village_Name').val().trim();
if (blockId && villageName) {
$.ajax({
url: '/check_village',
type: 'POST',
data: { block_Id: blockId, Village_Name: villageName },
success: function (response) {
if (response.status === 'exists') {
$('#villageMessage').text(response.message).css('color', 'red');
$('#submitVillage').prop('disabled', true);
} else {
$('#villageMessage').text(response.message).css('color', 'green');
$('#submitVillage').prop('disabled', false);
}
},
error: function () {
$('#villageMessage').text('Error checking village name').css('color', 'red');
$('#submitVillage').prop('disabled', true);
}
});
}
});
$('#villageForm').submit(function (event) {
event.preventDefault(); // Prevent default form submission
$.ajax({
url: '/add_village',
type: 'POST',
data: $(this).serialize(),
success: function (response) {
if (response.status === 'success') {
alert('Village added successfully!');
location.reload(); // Refresh the page to show the updated list
} else {
alert('Error adding village. Please try again.');
}
},
error: function () {
alert('An error occurred. Please try again.');
}
});
});
});

102
templates/activity_log.html Normal file
View File

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

97
templates/add_block.html Normal file
View File

@@ -0,0 +1,97 @@
{% 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[2] }}</td>
<td>
<a href="{{ url_for('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('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('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('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" style="display: none;">
<h2>Add Hold Types</h2>
<form id="holdTypeForm" method="POST">
<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('update_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 %}

369
templates/add_invoice.html Normal file
View File

@@ -0,0 +1,369 @@
{% 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('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>
<a href="{{ url_for('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>
<a href="{{ url_for('delete_invoice', 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 %}

182
templates/add_payment.html Normal file
View File

@@ -0,0 +1,182 @@
{% 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>
{% 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 %}

69
templates/add_state.html Normal file
View File

@@ -0,0 +1,69 @@
{% 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>
<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('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('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('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('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('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('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
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" 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>

60
templates/edit_block.html Normal file
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 %}

39
templates/edit_grn.html Normal file
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><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('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('add_hold_type') }}";
},
error: function (xhr) {
const errMsg = xhr.responseJSON?.message || 'Failed to update Hold Type';
alert('Error: ' + errMsg);
}
});
});
});
</script>
{% endblock %}

207
templates/edit_invoice.html Normal file
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('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] }}"><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>

17
templates/edit_state.html Normal file
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 %}

77
templates/grn_form.html Normal file
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>

161
templates/index.html Normal file
View File

@@ -0,0 +1,161 @@
<!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" 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('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>
</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
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>

340
templates/pmc_report.html Normal file
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 %}

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Purchase Order</title>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,205 @@
<!DOCTYPE html>
<html>
<head>
<title>Purchase Order Report</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/subcontractor_report.css') }}">
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<style>
h2.report-title {
font-size: 28px;
color: #1a73e8;
text-align: center;
margin-top: 20px;
margin-bottom: 30px;
}
.filter-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
margin-bottom: 20px;
}
label {
font-weight: bold;
margin-right: 10px;
}
select {
padding: 6px;
}
#downloadBtn, #searchBtn {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
margin-top: 20px;
}
#downloadBtn:hover, #searchBtn:hover {
background-color: #45a049;
}
table {
margin: 0 auto;
margin-top: 30px;
border-collapse: collapse;
width: 95%;
}
th, td {
padding: 10px;
text-align: center;
}
</style>
</head>
<body>
<h2 class="report-title">Purchase Order Report</h2>
<div class="filter-section">
<div>
<label for="supplier_name">Select Supplier Name:</label>
<select id="supplier_name" name="supplier_name" style="width: 300px;"></select>
</div>
<div>
<label for="purchase_order_no">Select Purchase Order Number:</label>
<select id="purchase_order_no" name="purchase_order_no" style="width: 300px;"></select>
</div>
<button id="searchBtn">Search</button>
<button id="downloadBtn" style="display: none;">Download Report</button>
</div>
<table border="1" id="purchaseorderTable" style="display: none;">
<thead>
<tr>
<th>Purchase 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>
</tr>
</thead>
<tbody></tbody>
</table>
<!-- JS Scripts -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
$(document).ready(function () {
// Initialize Select2 for supplier_name
$('#supplier_name').select2({
placeholder: 'Search Supplier Name',
ajax: {
url: '/get_supplier_names',
data: function (params) {
return { q: params.term };
},
processResults: function (data) {
return {
results: data.map(name => ({ id: name, text: name }))
};
},
delay: 250,
cache: true
}
});
// Initialize Select2 for purchase_order_no
$('#purchase_order_no').select2({
placeholder: 'Search Purchase Order No',
ajax: {
url: '/get_purchase_order_numbers',
data: function (params) {
return {
q: params.term,
supplier_name: $('#supplier_name').val()
};
},
processResults: function (data) {
return {
results: data.map(no => ({ id: no, text: no }))
};
},
delay: 250,
cache: true
}
});
// Search button click
$('#searchBtn').click(function () {
let supplier = $('#supplier_name').val();
let poNumber = $('#purchase_order_no').val();
if (!supplier && !poNumber) {
alert("Please select Supplier or Purchase Order No or both.");
return;
}
$.ajax({
url: '/get_purchase_order_data',
data: {
supplier_name: supplier,
purchase_order_no: poNumber
},
success: function (data) {
let tbody = $('#purchaseorderTable tbody');
tbody.empty();
if (data.length > 0) {
$('#purchaseorderTable').show();
$('#downloadBtn').show();
data.forEach(function (row) {
tbody.append(`
<tr>
<td>${row.purchase_id}</td>
<td>${row.purchase_date}</td>
<td>${row.supplier_name}</td>
<td>${row.purchase_order_no}</td>
<td>${row.item_name}</td>
<td>${row.quantity}</td>
<td>${row.unit}</td>
<td>${row.rate}</td>
<td>${row.amount}</td>
<td>${row.GST_Amount}</td>
<td>${row.TDS}</td>
<td>${row.final_amount}</td>
</tr>
`);
});
} else {
alert("No matching purchase orders found.");
$('#purchaseorderTable').hide();
$('#downloadBtn').hide();
}
}
});
});
// Download report
$('#downloadBtn').click(function () {
let supplier = $('#supplier_name').val();
let poNumber = $('#purchase_order_no').val();
if (!supplier && !poNumber) {
alert("Please select filters before downloading.");
return;
}
let url = '/download_purchase_order_report?';
if (supplier) url += 'supplier_name=' + encodeURIComponent(supplier);
if (poNumber) url += '&purchase_order_no=' + encodeURIComponent(poNumber);
window.location.href = url;
});
});
</script>
</body>
</html>

108
templates/report.html Normal file
View File

@@ -0,0 +1,108 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contractor Search</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/report.css') }}">
<script src="{{ url_for('static', filename='js/searchContractor.js') }}"></script>
</head>
<body>
<div id="report" class="page">
<h2>Search Contractor Report</h2>
<div>
<form id="search-form">
<div class="row2">
<div>
<label for="subcontractor_name">Subcontractor Name:</label>
<input type="text" id="subcontractor_name" name="subcontractor_name">
</div>
<div>
<label for="pmc_no">PMC No:</label>
<input type="text" id="pmc_no" name="pmc_no">
</div>
</div>
<div class="row2">
<div>
<label for="state">State:</label>
<input type="text" id="state" name="state">
</div>
<div>
<label for="district">District:</label>
<input type="text" id="district" name="district">
</div>
</div>
<div class="row2">
<div>
<label for="block">Block:</label>
<input type="text" id="block" name="block">
</div>
<div>
<label for="village">Village:</label>
<input type="text" id="village" name="village">
</div>
</div>
<div class="row2">
<div>
<label for="year_from">Year From:</label>
<input type="date" id="year_from" name="year_from">
</div>
<div>
<label for="year_to">Year To:</label>
<input type="date" id="year_to" name="year_to">
</div>
</div>
</form>
<h2>Contractor List</h2>
<table border="1" id="result-table">
<thead>
<tr>
<th class="sortable">Contractor Name
<select>
<option value="">🔽</option>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</th>
<th>PMC No</th>
<th class="sortable">State
<select>
<option value="">🔽</option>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</th>
<th class="sortable">District
<select>
<option value="">🔽</option>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</th>
<th class="sortable">Block
<select>
<option value="">🔽</option>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</th>
<th class="sortable">Village
<select>
<option value="">🔽</option>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</th>
</tr>
</thead>
<tbody>
<!-- Results will be dynamically populated -->
</tbody>
</table>
</div>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<head>
<title>Excel Data</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/show_excel.css') }}">
<!-- <script src="{{ url_for('static', filename='js/save_excel_file.js') }}"></script>-->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="{{ url_for('static', filename='js/save_data_success.js') }}"></script>
</head>
<body>
<h1>Excel Data</h1>
<h2>File Information:</h2>
<ul>
<li>Subcontractor: {{ file_info['Subcontractor'] }}</li>
<li>State: {{ file_info['State'] }}</li>
<li>District: {{ file_info['District'] }}</li>
<li>Block: {{ file_info['Block'] }}</li>
</ul>
{% if errors %}
<div class="errors">
<h2>Validation Errors:</h2>
<ul>
{% for error in errors %}
<li class="error">{{ error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<h2>Data Table:</h2>
<form id="saveForm">
<div class="table-container">
<table>
<thead>
<tr>
<th>Row No</th>
{% for var in variables %}
<th>{{ var }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in data %}
<tr>
<td>{{ row['Row Number'] }}</td>
{% for var in variables %}
<td><input type="text" value="{{ row[var] }}"/></td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Hidden fields for other necessary information -->
<input type="text" name="subcontractor_data" value="{{ subcontractor_data['Contractor_Id'] }}">
<input type="text" name="state_data" value="{{ state_data['State_ID'] }}">
<input type="text" name="district_data" value="{{ district_data['District_ID'] }}">
<input type="text" name="block_data" value="{{ block_data['Block_Id'] }}">
<input type="text" name="file_info" value="{{ file_info }}">
<input type="text" name="hold_columns" value="{{ hold_columns }}">
<input type="text" name="hold_counter" value="{{ hold_counter }}">
<input type="text" name="variables[]" value="{{ variables }}">
<input type="text" name="data[]" value="{{ data }}">
<!-- {% for row in data %}-->
<!-- <input type="text" name="data[]" value="{{ row | join(',') }}">-->
<!-- {% endfor %}-->
<button type="submit" class="save-button">Save Data</button>
</form>
<a href="/" class="back-button">Back to Dashboard</a>
</body>
</html>

View File

@@ -0,0 +1,372 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contractor 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>Contractor 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="{{ contInfo.Contractor_Name }}"
readonly/>
</div>
<div>
<label for="Address">Address :</label>
<textarea id="Address" name="Address" readonly>{{ contInfo.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="{{ contInfo.PAN_No }}" readonly/>
</div>
<div>
<label for="Mobile_No">Mobile Number :</label>
<input type="text" id="Mobile_No" name="Mobile_No" value="{{ contInfo.Mobile_No }}" readonly/>
</div>
<div>
<label for="Email">Email :</label>
<input type="text" id="Email" name="Email" value="{{ contInfo.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="{{ contInfo.GST_Registration_Type }}" readonly/>
</div>
<div>
<label for="GST_No">GST No:</label>
<input type="text" id="GST_No" name="GST_No" value="{{ contInfo.GST_No }}" readonly/>
</div>
</div>
<div class="row3">
<div>
<label for="State">State :</label>
<input type="text" id="State" name="State" value="{{ contInfo.State_Name }}" readonly/>
</div>
<div>
<label for="District">District :</label>
<input type="text" id="District" name="District" value="{{ contInfo.District_Name }}" readonly/>
</div>
<div>
<label for="Block">Block :</label>
<input type="text" id="Block" name="Block" value="{{ contInfo.Block_Name }}" readonly/>
</div>
</div>
</div>
<h3>Total</h3>
<table class="total-table">
<tr>
<th colspan="2">{{current_date}}</th>
</tr>
<!-- <tr>-->
<!-- <th>Total Hold Amounnt</th>-->
<!-- <td>0</td>-->
<!-- </tr>-->
<tr>
<th>Advance / Suplus</th>
<td>{{total["sum_invo_final_amt"]+total["sum_gst_final_amt"]-total["sum_pay_total_amt"]}}</td>
</tr>
{% if hold_types %}
<tr>
{% for hold in hold_types %}
<th>{{ hold.hold_type }}</th>
<td>{{total["sum_invo_hold_amt"]}}</td>
{% endfor %}
</tr>
{% endif %}
<tr>
<th>Amount With TDS</th>
<td>{{total["sum_invo_tds_amt"]}}</td>
</tr>
</table>
<!-- {% if hold_types %}-->
<!-- <h3>Hold Types</h3>-->
<!-- <ul>-->
<!-- {% for hold in hold_types %}-->
<!-- <li>{{ hold.hold_type }}</li>-->
<!-- {% endfor %}-->
<!-- </ul>-->
<!-- {% endif %}-->
<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') | 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 -->
{% set hold_types = invoices | map(attribute='hold_type') | unique | list %}
{% for hold in hold_types %}
<td>
{% if invoice.hold_type == hold %}
{{ invoice.hold_amount }}
{% else %}
0.00
{% 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>
<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>
<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>
<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="{{ url_for('download_report', contractor_id=contractor_id) }}" class="download-btn">Download Report</a>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<h2>🚫 GST Release Not Filled</h2>
<table border="1" cellpadding="8" cellspacing="0">
<thead>
<tr>
<th>PMC No</th>
<th>Invoice No</th>
<th>Invoice Details</th>
<th>GST_SD_Amount</th>
</tr>
</thead>
<tbody>
{% for row in data %}
<tr>
<td>{{ row.PMC_No }}</td>
<td>{{ row.Invoice_No }}</td>
<td>{{ row.Invoice_Details }}</td>
<td>{{ row.GST_SD_Amount }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if data|length == 0 %}
<p><strong>✅ All invoices have GST releases.</strong></p>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% block content %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upload Excel File</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/validateFileInput.js') }}"></script>
</head>
<body>
<h2>Upload Excel File</h2>
<div class="container">
<form action="/upload_excel" method="post" enctype="multipart/form-data"
onsubmit="return validateFileInput()">
<input type="file" name="file" accept=".xlsx,.xls">
<br><br>
<button type="submit">Upload</button>
</form>
</div>
</body>
{% endblock %}

View File

@@ -0,0 +1,209 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Work Order Report</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/subcontractor_report.css') }}">
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<style>
h2.report-title {
font-size: 28px;
color: #1a73e8;
text-align: center;
margin-top: 20px;
margin-bottom: 30px;
}
.filter-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
margin-bottom: 20px;
}
label {
font-weight: bold;
margin-right: 10px;
}
select {
padding: 6px;
}
#downloadBtn, #searchBtn {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
margin-top: 20px;
}
#downloadBtn:hover, #searchBtn:hover {
background-color: #45a049;
}
table {
margin: 0 auto;
margin-top: 30px;
border-collapse: collapse;
width: 95%;
}
th, td {
padding: 10px;
text-align: center;
}
</style>
</head>
<body>
<h2 class="report-title">Work Order Report</h2>
<div class="filter-section">
<div>
<label for="vendor_name">Select Vendor Name:</label>
<select id="vendor_name" name="vendor_name" style="width: 300px;"></select>
</div>
<div>
<label for="work_order_number">Select Work Order Number:</label>
<select id="work_order_number" name="work_order_number" style="width: 300px;"></select>
</div>
<button id="searchBtn">Search</button>
<button id="downloadBtn" style="display: none;">Download Report</button>
</div>
<table border="1" id="workOrderTable" style="display: none;">
<thead>
<tr>
<th>ID</th>
<th>Vendor Name</th>
<th>Work Order Type</th>
<th>Amount</th>
<th>BOQ</th>
<th>Done %</th>
<th>GST</th>
<th>TDS</th>
<th>Security Deposit</th>
<th>SD GST</th>
<th>Final Total</th>
<th>TDS of GST</th>
</tr>
</thead>
<tbody></tbody>
</table>
<!-- JS Scripts -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
$(document).ready(function () {
$('#vendor_name, #work_order_number').select2({
placeholder: 'Search',
minimumInputLength: 1,
ajax: {
delay: 250,
dataType: 'json',
cache: true
}
});
$('#vendor_name').select2({
placeholder: 'Search Vendor',
ajax: {
url: '/get_vendor_names',
data: function (params) {
return { q: params.term };
},
processResults: function (data) {
return {
results: data.map(v => ({ id: v, text: v }))
};
}
}
});
$('#work_order_number').select2({
placeholder: 'Search Work Order',
ajax: {
url: '/get_work_order_numbers',
data: function (params) {
return {
q: params.term,
vendor_name: $('#vendor_name').val()
};
},
processResults: function (data) {
return {
results: data.map(o => ({ id: o, text: o }))
};
}
}
});
// Fetch and display table
$('#searchBtn').click(function () {
let vendor = $('#vendor_name').val();
let order = $('#work_order_number').val();
if (!vendor && !order) {
alert("Please select Vendor or Work Order Number or both.");
return;
}
$.ajax({
url: '/get_work_order_data',
data: {
vendor_name: vendor,
work_order_number: order
},
success: function (data) {
let tbody = $('#workOrderTable tbody');
tbody.empty();
if (data.length > 0) {
$('#workOrderTable').show();
$('#downloadBtn').show();
data.forEach(function (row) {
tbody.append(`
<tr>
<td>${row.work_order_id}</td>
<td>${row.vendor_name}</td>
<td>${row.work_order_type}</td>
<td>${row.work_order_amount}</td>
<td>${row.boq_amount}</td>
<td>${row.work_done_percentage}</td>
<td>${row.gst_amount}</td>
<td>${row.tds_amount}</td>
<td>${row.security_deposit}</td>
<td>${row.sd_against_gst}</td>
<td>${row.final_total}</td>
<td>${row.tds_of_gst}</td>
</tr>
`);
});
} else {
alert("No data found for selected filters.");
$('#workOrderTable').hide();
$('#downloadBtn').hide();
}
}
});
});
// Download report
$('#downloadBtn').click(function () {
let vendor = $('#vendor_name').val();
let order = $('#work_order_number').val();
if (!vendor && !order) {
alert("Please select filters before downloading.");
return;
}
let url = '/download_work_order_report?';
if (vendor) url += 'vendor_name=' + encodeURIComponent(vendor);
if (order) url += '&work_order_number=' + encodeURIComponent(order);
window.location.href = url;
});
});
</script>
</body>
{% endblock %}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
uploads/(DONE) Akram.xlsx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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