changes of reports and pmc reports by pankaj-dev
This commit is contained in:
9
v-2/.env
Normal file
9
v-2/.env
Normal file
@@ -0,0 +1,9 @@
|
||||
Secret_Key = 9f2a1b8c4d6e7f0123456789abcdef01
|
||||
|
||||
MYSQL_HOST=127.0.0.1
|
||||
MYSQL_USER=root
|
||||
MYSQL_PASSWORD=root
|
||||
MYSQL_DB=test
|
||||
|
||||
DEFAULT_USERNAME=admin
|
||||
DEFAULT_PASSWORD=admin123
|
||||
7
v-2/.gitignore
vendored
Normal file
7
v-2/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
venv/
|
||||
*.pyc
|
||||
__pycache__/
|
||||
.uploads
|
||||
static/download/
|
||||
|
||||
|
||||
18
v-2/Dockerfile
Normal file
18
v-2/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
# Use official Python image
|
||||
FROM python:3.9
|
||||
|
||||
# Set working directory inside container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy all files to container
|
||||
COPY . .
|
||||
|
||||
# Install dependencies
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Expose Flask's default port
|
||||
EXPOSE 5000
|
||||
|
||||
# Run Flask app
|
||||
CMD ["python", "main.py"]
|
||||
|
||||
1
v-2/README.md
Normal file
1
v-2/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# MA07-05-2025
|
||||
7989
v-2/activity.log
Normal file
7989
v-2/activity.log
Normal file
File diff suppressed because it is too large
Load Diff
114
v-2/app.log
Normal file
114
v-2/app.log
Normal 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] "[36mGET /static/css/index.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:26,315 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "[36mGET /static/css/index.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:26,504 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "[36mGET /static/css/style.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:26,504 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "[36mGET /static/css/style.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:26,626 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "[36mGET /static/js/validateFileInput.js HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:26,626 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "[36mGET /static/js/validateFileInput.js HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:26,633 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "[36mGET /static/js/searchContractor.js HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:26,633 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "[36mGET /static/js/searchContractor.js HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:26,950 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "[33mGET /favicon.ico HTTP/1.1[0m" 404 -
|
||||
2025-02-15 11:13:26,950 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:26] "[33mGET /favicon.ico HTTP/1.1[0m" 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] "[36mGET /static/css/index.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:28,933 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "[36mGET /static/css/index.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:28,952 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "[36mGET /static/js/validateFileInput.js HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:28,952 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "[36mGET /static/js/validateFileInput.js HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:28,954 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "[36mGET /static/js/searchContractor.js HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:28,955 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "[36mGET /static/css/style.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:28,954 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "[36mGET /static/js/searchContractor.js HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:28,955 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:28] "[36mGET /static/css/style.css HTTP/1.1[0m" 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] "[36mGET /static/css/style.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:31,639 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "[36mGET /static/css/style.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:31,649 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "[36mGET /static/images/icons/pen_blue_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:31,649 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "[36mGET /static/images/icons/pen_blue_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:31,967 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "[36mGET /static/images/icons/bin_red_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:13:31,967 - INFO - 127.0.0.1 - - [15/Feb/2025 11:13:31] "[36mGET /static/images/icons/bin_red_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:15:01,349 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:01] "[31m[1mPOST /check_state HTTP/1.1[0m" 409 -
|
||||
2025-02-15 11:15:01,349 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:01] "[31m[1mPOST /check_state HTTP/1.1[0m" 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] "[36mGET /static/css/style.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:15:25,716 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "[36mGET /static/css/style.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:15:25,749 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "[36mGET /static/images/icons/pen_blue_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:15:25,749 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "[36mGET /static/images/icons/pen_blue_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:15:25,752 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "[36mGET /static/images/icons/bin_red_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:15:25,752 - INFO - 127.0.0.1 - - [15/Feb/2025 11:15:25] "[36mGET /static/images/icons/bin_red_icon.png HTTP/1.1[0m" 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] "[36mGET /static/css/style.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:16:52,076 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "[36mGET /static/css/style.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:16:52,078 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "[36mGET /static/images/icons/pen_blue_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:16:52,078 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "[36mGET /static/images/icons/pen_blue_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:16:52,377 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "[36mGET /static/images/icons/bin_red_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:16:52,377 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:52] "[36mGET /static/images/icons/bin_red_icon.png HTTP/1.1[0m" 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] "[36mGET /static/css/style.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:16:57,263 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "[36mGET /static/css/style.css HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:16:57,483 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "[36mGET /static/images/icons/pen_blue_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:16:57,483 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "[36mGET /static/images/icons/pen_blue_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:16:57,567 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "[36mGET /static/images/icons/bin_red_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:16:57,567 - INFO - 127.0.0.1 - - [15/Feb/2025 11:16:57] "[36mGET /static/images/icons/bin_red_icon.png HTTP/1.1[0m" 304 -
|
||||
2025-02-15 11:20:55,547 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
|
||||
2025-02-15 11:20:55,547 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
|
||||
2025-02-15 11:20:56,800 - INFO - Logging is set up.
|
||||
2025-02-15 11:20:56,800 - INFO - Logging is set up.
|
||||
2025-02-15 11:20:56,835 - WARNING - * Debugger is active!
|
||||
2025-02-15 11:20:56,835 - WARNING - * Debugger is active!
|
||||
2025-02-15 11:20:56,837 - INFO - * Debugger PIN: 558-213-972
|
||||
2025-02-15 11:20:56,837 - INFO - * Debugger PIN: 558-213-972
|
||||
2025-02-15 11:21:04,060 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
|
||||
2025-02-15 11:21:04,060 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
|
||||
2025-02-15 11:21:05,429 - INFO - Logging is set up.
|
||||
2025-02-15 11:21:05,429 - INFO - Logging is set up.
|
||||
2025-02-15 11:21:05,461 - WARNING - * Debugger is active!
|
||||
2025-02-15 11:21:05,461 - WARNING - * Debugger is active!
|
||||
2025-02-15 11:21:05,463 - INFO - * Debugger PIN: 558-213-972
|
||||
2025-02-15 11:21:05,463 - INFO - * Debugger PIN: 558-213-972
|
||||
2025-02-15 11:21:17,911 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
|
||||
2025-02-15 11:21:17,911 - INFO - * Detected change in 'C:\\Users\\ADMIN\\PycharmProjects\\ManagementApplicationt\\main.py', reloading
|
||||
21
v-2/config.py
Normal file
21
v-2/config.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import mysql.connector
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
# Get MySQL credentials from environment variables
|
||||
MYSQL_HOST = os.getenv("MYSQL_HOST")
|
||||
MYSQL_USER = os.getenv("MYSQL_USER")
|
||||
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD")
|
||||
MYSQL_DB = os.getenv("MYSQL_DB")
|
||||
|
||||
# Connect to MySQL
|
||||
def get_db_connection():
|
||||
return mysql.connector.connect(
|
||||
host=MYSQL_HOST,
|
||||
user=MYSQL_USER,
|
||||
password=MYSQL_PASSWORD,
|
||||
database=MYSQL_DB
|
||||
)
|
||||
46
v-2/controllers/auth_controller.py
Normal file
46
v-2/controllers/auth_controller.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, session
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
|
||||
from model.Auth import LoginLDAP, User
|
||||
from model.Log import LogHelper
|
||||
|
||||
auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
|
||||
@auth_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
|
||||
if request.method == 'POST':
|
||||
|
||||
loginData = LoginLDAP(request)
|
||||
|
||||
if loginData.isValidLogin:
|
||||
|
||||
if loginData.isDefaultCredentials:
|
||||
LogHelper.log_action('Login', f"User {loginData.username} logged in (static user)")
|
||||
else:
|
||||
LogHelper.log_action('Login', f"User {loginData.username} logged in (LDAP)")
|
||||
|
||||
session['username'] = loginData.username
|
||||
|
||||
login_user(User(loginData.username))
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
else:
|
||||
flash(loginData.errorMessage, 'danger')
|
||||
|
||||
return render_template("login.html")
|
||||
|
||||
|
||||
@auth_bp.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
|
||||
LogHelper.log_action('Logout', f"User {current_user.id} logged out")
|
||||
|
||||
logout_user()
|
||||
|
||||
flash('You have been logged out.', 'info')
|
||||
|
||||
return redirect(url_for('auth.login'))
|
||||
119
v-2/controllers/block_controller.py
Normal file
119
v-2/controllers/block_controller.py
Normal file
@@ -0,0 +1,119 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, jsonify
|
||||
from flask_login import login_required
|
||||
|
||||
import config
|
||||
import mysql.connector
|
||||
|
||||
from model.Block import Block
|
||||
from model.Utilities import HtmlHelper
|
||||
|
||||
block_bp = Blueprint('block', __name__)
|
||||
|
||||
|
||||
@block_bp.route('/add_block', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_block():
|
||||
|
||||
block = Block()
|
||||
|
||||
if request.method == 'POST':
|
||||
block.AddBlock(request)
|
||||
return block.resultMessage
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.callproc("GetAllStates")
|
||||
for rs in cursor.stored_results():
|
||||
states = rs.fetchall()
|
||||
|
||||
block_data = block.GetAllBlocks(request)
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return render_template(
|
||||
'add_block.html',
|
||||
states=states,
|
||||
block_data=block_data
|
||||
)
|
||||
|
||||
|
||||
# ✅ NEW ROUTE (FIX FOR DISTRICT FETCH)
|
||||
@block_bp.route('/get_districts/<int:state_id>')
|
||||
@login_required
|
||||
def get_districts(state_id):
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.callproc("GetDistrictsByStateId", (state_id,))
|
||||
|
||||
districts = []
|
||||
for rs in cursor.stored_results():
|
||||
districts = rs.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
district_list = []
|
||||
|
||||
for district in districts:
|
||||
district_list.append({
|
||||
"id": district[0],
|
||||
"name": district[1]
|
||||
})
|
||||
|
||||
return jsonify(district_list)
|
||||
|
||||
|
||||
@block_bp.route('/check_block', methods=['POST'])
|
||||
@login_required
|
||||
def check_block():
|
||||
|
||||
block = Block()
|
||||
return block.CheckBlock(request)
|
||||
|
||||
|
||||
@block_bp.route('/edit_block/<int:block_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_block(block_id):
|
||||
|
||||
block = Block()
|
||||
|
||||
if request.method == 'POST':
|
||||
block.EditBlock(request, block_id)
|
||||
return block.resultMessage
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.callproc("GetAllStates")
|
||||
for rs in cursor.stored_results():
|
||||
states = rs.fetchall()
|
||||
|
||||
cursor.callproc("GetAllDistrictsData")
|
||||
for rs in cursor.stored_results():
|
||||
districts = rs.fetchall()
|
||||
|
||||
block_data = block.GetBlockByID(block_id)
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return render_template(
|
||||
'edit_block.html',
|
||||
block_data=block_data,
|
||||
states=states,
|
||||
districts=districts
|
||||
)
|
||||
|
||||
|
||||
@block_bp.route('/delete_block/<int:block_id>')
|
||||
@login_required
|
||||
def delete_block(block_id):
|
||||
|
||||
block = Block()
|
||||
block.DeleteBlock(request, block_id)
|
||||
|
||||
return redirect(url_for('block.add_block'))
|
||||
84
v-2/controllers/district_controller.py
Normal file
84
v-2/controllers/district_controller.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required
|
||||
|
||||
from model.District import District
|
||||
from model.State import State
|
||||
|
||||
district_bp = Blueprint('district', __name__)
|
||||
|
||||
|
||||
@district_bp.route('/add_district', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_district():
|
||||
|
||||
district = District()
|
||||
|
||||
if request.method == 'POST':
|
||||
district.AddDistrict(request=request)
|
||||
return district.resultMessage
|
||||
|
||||
state = State()
|
||||
states = state.GetAllStates(request=request)
|
||||
|
||||
districtdata = district.GetAllDistricts(request=request)
|
||||
|
||||
return render_template(
|
||||
'add_district.html',
|
||||
districtdata=districtdata,
|
||||
states=states
|
||||
)
|
||||
|
||||
|
||||
@district_bp.route('/check_district', methods=['POST'])
|
||||
@login_required
|
||||
def check_district():
|
||||
|
||||
district = District()
|
||||
|
||||
return district.CheckDistrict(request=request)
|
||||
|
||||
|
||||
@district_bp.route('/delete_district/<int:district_id>')
|
||||
@login_required
|
||||
def delete_district(district_id):
|
||||
|
||||
district = District()
|
||||
|
||||
district.DeleteDistrict(request=request, district_id=district_id)
|
||||
|
||||
if not district.isSuccess:
|
||||
return district.resultMessage
|
||||
else:
|
||||
return redirect(url_for('district.add_district'))
|
||||
|
||||
|
||||
@district_bp.route('/edit_district/<int:district_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_district(district_id):
|
||||
|
||||
district = District()
|
||||
state = State()
|
||||
|
||||
if request.method == 'POST':
|
||||
|
||||
district.EditDistrict(request=request, district_id=district_id)
|
||||
|
||||
if district.isSuccess:
|
||||
flash("District updated successfully!", "success")
|
||||
return redirect(url_for('district.add_district'))
|
||||
else:
|
||||
flash(district.resultMessage, "error")
|
||||
|
||||
districtdata = district.GetDistrictByID(request=request, district_id=district_id)
|
||||
|
||||
if not districtdata:
|
||||
flash("District not found", "error")
|
||||
return redirect(url_for('district.add_district'))
|
||||
|
||||
states = state.GetAllStates(request=request)
|
||||
|
||||
return render_template(
|
||||
'edit_district.html',
|
||||
districtdata=districtdata,
|
||||
states=states
|
||||
)
|
||||
388
v-2/controllers/excel_upload_controller.py
Normal file
388
v-2/controllers/excel_upload_controller.py
Normal file
@@ -0,0 +1,388 @@
|
||||
import os
|
||||
import ast
|
||||
import re
|
||||
from flask_login import login_required
|
||||
import openpyxl
|
||||
from flask import Blueprint, request, render_template, redirect, url_for, jsonify, current_app
|
||||
from flask_login import current_user
|
||||
from model.Log import LogHelper
|
||||
import config
|
||||
from model.FolderAndFile import FolderAndFile
|
||||
|
||||
excel_bp = Blueprint('excel', __name__)
|
||||
|
||||
# Default folder in case config not set
|
||||
# DEFAULT_UPLOAD_FOLDER = 'uploads'
|
||||
|
||||
|
||||
# def get_upload_folder():
|
||||
# """Returns the upload folder from Flask config or default, ensures it exists."""
|
||||
# folder = current_app.config.get('UPLOAD_FOLDER', DEFAULT_UPLOAD_FOLDER)
|
||||
# if not os.path.exists(folder):
|
||||
# os.makedirs(folder)
|
||||
# return folder
|
||||
|
||||
|
||||
# ---------------- Upload Excel File ----------------
|
||||
@excel_bp.route('/upload_excel_file', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def upload():
|
||||
if request.method == 'POST':
|
||||
file = request.files.get('file')
|
||||
if file and file.filename.endswith('.xlsx'):
|
||||
# upload_folder = get_upload_folder()
|
||||
# filepath = os.path.join(upload_folder, file.filename)
|
||||
|
||||
filepath =FolderAndFile.get_upload_path(file.filename)
|
||||
|
||||
file.save(filepath)
|
||||
|
||||
LogHelper.log_action(
|
||||
"Upload Excel File",
|
||||
f"User {current_user.id} Upload Excel File '{file.filename}'"
|
||||
)
|
||||
return redirect(url_for('excel.show_table', filename=file.filename))
|
||||
return render_template('uploadExcelFile.html')
|
||||
|
||||
|
||||
# ---------------- Show Excel Table ----------------
|
||||
@excel_bp.route('/show_table/<filename>')
|
||||
def show_table(filename):
|
||||
global data
|
||||
data = []
|
||||
|
||||
# filepath = os.path.join(get_upload_folder(), filename)
|
||||
filepath = FolderAndFile.get_upload_path(filename)
|
||||
wb = openpyxl.load_workbook(filepath, data_only=True)
|
||||
sheet = wb.active
|
||||
|
||||
file_info = {
|
||||
"Subcontractor": sheet.cell(row=1, column=2).value,
|
||||
"State": sheet.cell(row=2, column=2).value,
|
||||
"District": sheet.cell(row=3, column=2).value,
|
||||
"Block": sheet.cell(row=4, column=2).value,
|
||||
}
|
||||
|
||||
errors = []
|
||||
subcontractor_data = None
|
||||
state_data = None
|
||||
district_data = None
|
||||
block_data = None
|
||||
|
||||
connection = config.get_db_connection()
|
||||
if connection:
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.callproc('GetStateByName', [file_info['State']])
|
||||
for result in cursor.stored_results():
|
||||
state_data = result.fetchone()
|
||||
if not state_data:
|
||||
errors.append(f"State '{file_info['State']}' is not valid. Please add it.")
|
||||
|
||||
if state_data:
|
||||
cursor.callproc('GetDistrictByNameAndStates', [file_info['District'], state_data['State_ID']])
|
||||
for result in cursor.stored_results():
|
||||
district_data = result.fetchone()
|
||||
if not district_data:
|
||||
errors.append(f"District '{file_info['District']}' is not valid under state '{file_info['State']}'.")
|
||||
|
||||
if district_data:
|
||||
cursor.callproc('GetBlockByNameAndDistricts', [file_info['Block'], district_data['District_ID']])
|
||||
for result in cursor.stored_results():
|
||||
block_data = result.fetchone()
|
||||
if not block_data:
|
||||
errors.append(f"Block '{file_info['Block']}' is not valid under district '{file_info['District']}'.")
|
||||
|
||||
cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']])
|
||||
for result in cursor.stored_results():
|
||||
subcontractor_data = result.fetchone()
|
||||
|
||||
if not subcontractor_data:
|
||||
cursor.callproc('InsertSubcontractor', [file_info['Subcontractor']])
|
||||
connection.commit()
|
||||
cursor.callproc('GetSubcontractorByName', [file_info['Subcontractor']])
|
||||
for result in cursor.stored_results():
|
||||
subcontractor_data = result.fetchone()
|
||||
|
||||
cursor.callproc("GetAllHoldTypes")
|
||||
hold_types_data = []
|
||||
for ht in cursor.stored_results():
|
||||
hold_types_data = ht.fetchall()
|
||||
hold_types_lookup = {row['hold_type'].lower(): row['hold_type_id'] for row in hold_types_data if row['hold_type']}
|
||||
|
||||
cursor.close()
|
||||
except Exception as e:
|
||||
print(f"Database error: {e}")
|
||||
return f"Database operation failed: {e}", 500
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
variables = {}
|
||||
hold_columns = []
|
||||
hold_counter = 0
|
||||
|
||||
for j in range(1, sheet.max_column + 1):
|
||||
col_value = sheet.cell(row=5, column=j).value
|
||||
if col_value:
|
||||
variables[col_value] = j
|
||||
if 'hold' in str(col_value).lower():
|
||||
hold_counter += 1
|
||||
hold_type_key = str(col_value).lower().strip()
|
||||
hold_type_id = hold_types_lookup.get(hold_type_key)
|
||||
hold_columns.append({
|
||||
'column_name': col_value,
|
||||
'column_number': j,
|
||||
'hold_type_id': hold_type_id
|
||||
})
|
||||
|
||||
for i in range(6, sheet.max_row + 1):
|
||||
row_data = {}
|
||||
if sheet.cell(row=i, column=1).value:
|
||||
row_data["Row Number"] = i
|
||||
for var_name, col_num in variables.items():
|
||||
row_data[var_name] = sheet.cell(row=i, column=col_num).value
|
||||
if sum(1 for value in row_data.values() if value) >= 4:
|
||||
data.append(row_data)
|
||||
|
||||
for hold in hold_columns:
|
||||
if hold['hold_type_id']:
|
||||
print(f" if Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")
|
||||
else:
|
||||
errors.append(f"Hold Type not added ! Column name '{hold['column_name']}'.")
|
||||
print(f" else Column: {hold['column_name']}, Column Number: {hold['column_number']}, Hold Type ID: {hold['hold_type_id']}")
|
||||
|
||||
return render_template(
|
||||
'show_excel_file.html',
|
||||
file_info=file_info,
|
||||
variables=variables,
|
||||
data=data,
|
||||
subcontractor_data=subcontractor_data,
|
||||
state_data=state_data,
|
||||
district_data=district_data,
|
||||
block_data=block_data,
|
||||
errors=errors,
|
||||
hold_columns=hold_columns,
|
||||
hold_counter=hold_counter
|
||||
)
|
||||
|
||||
|
||||
# save Excel data
|
||||
@excel_bp.route('/save_data', methods=['POST'])
|
||||
def save_data():
|
||||
# Extract form data
|
||||
subcontractor_id = request.form.get("subcontractor_data")
|
||||
state_id = request.form.get("state_data")
|
||||
district_id = request.form.get("district_data")
|
||||
block_id = request.form.get("block_data")
|
||||
variables = request.form.getlist('variables[]')
|
||||
hold_columns = request.form.get("hold_columns")
|
||||
hold_counter = request.form.get("hold_counter")
|
||||
if not data:
|
||||
return jsonify({"error": "No data provided to save"}), 400
|
||||
if data:
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
try:
|
||||
for entry in data:
|
||||
save_data = {
|
||||
"PMC_No": entry.get("PMC_No"),
|
||||
"Invoice_Details": entry.get("Invoice_Details", ''),
|
||||
"Work_Type": 'none',
|
||||
"Invoice_Date": entry.get("Invoice_Date").strftime('%Y-%m-%d') if entry.get(
|
||||
"Invoice_Date") else None,
|
||||
"Invoice_No": entry.get("Invoice_No", ''),
|
||||
"Basic_Amount": entry.get("Basic_Amount", 0.00),
|
||||
"Debit_Amount": entry.get("Debit_Amount", 0.00),
|
||||
"After_Debit_Amount": entry.get("After_Debit_Amount", 0.00),
|
||||
"Amount": entry.get("Amount", 0.00),
|
||||
"GST_Amount": entry.get("GST_Amount", 0.00),
|
||||
"TDS_Amount": entry.get("TDS_Amount", 0.00),
|
||||
"SD_Amount": entry.get("SD_Amount", 0.00),
|
||||
"On_Commission": entry.get("On_Commission", 0.00),
|
||||
"Hydro_Testing": entry.get("Hydro_Testing", 0.00),
|
||||
"Hold_Amount": 0,
|
||||
"GST_SD_Amount": entry.get("GST_SD_Amount", 0.00),
|
||||
"Final_Amount": entry.get("Final_Amount", 0.00),
|
||||
"Payment_Amount": entry.get("Payment_Amount", 0.00),
|
||||
"Total_Amount": entry.get("Total_Amount", 0.00),
|
||||
"TDS_Payment_Amount": entry.get("TDS_Payment_Amount", 0.00),
|
||||
"UTR": entry.get("UTR", ''),
|
||||
}
|
||||
village_name, work_type = None, None
|
||||
village_id = 0
|
||||
LogHelper.log_action("Data saved", f"User {current_user.id} Data saved'{ village_name}'")
|
||||
PMC_No = save_data.get('PMC_No')
|
||||
Invoice_Details = save_data.get('Invoice_Details')
|
||||
Invoice_Date = save_data.get('Invoice_Date')
|
||||
Invoice_No = save_data.get('Invoice_No')
|
||||
Basic_Amount = save_data.get('Basic_Amount')
|
||||
Debit_Amount = save_data.get('Debit_Amount')
|
||||
After_Debit_Amount = save_data.get('After_Debit_Amount')
|
||||
Amount = save_data.get('Amount')
|
||||
GST_Amount = save_data.get('GST_Amount')
|
||||
TDS_Amount = save_data.get('TDS_Amount')
|
||||
SD_Amount = save_data.get('SD_Amount')
|
||||
On_Commission = save_data.get('On_Commission')
|
||||
Hydro_Testing = save_data.get('Hydro_Testing')
|
||||
GST_SD_Amount = save_data.get('GST_SD_Amount')
|
||||
Final_Amount = save_data.get('Final_Amount')
|
||||
Payment_Amount = save_data.get('Payment_Amount')
|
||||
Total_Amount = save_data.get('Total_Amount')
|
||||
TDS_Payment_Amount = save_data.get('TDS_Payment_Amount')
|
||||
UTR = save_data.get('UTR')
|
||||
|
||||
if Invoice_Details:
|
||||
words = Invoice_Details.lower().split()
|
||||
if 'village' in words:
|
||||
village_pos = words.index('village')
|
||||
village_name = " ".join(words[:village_pos])
|
||||
if 'work' in words:
|
||||
work_pos = words.index('work')
|
||||
if village_name:
|
||||
work_type = " ".join(words[village_pos + 1:work_pos + 1])
|
||||
else:
|
||||
work_type = " ".join(words[:work_pos + 1])
|
||||
if Invoice_Details and 'village' in Invoice_Details.lower() and 'work' in Invoice_Details.lower():
|
||||
print("village_name ::", village_name, "|| work_type ::", work_type)
|
||||
if block_id and village_name:
|
||||
village_id = None
|
||||
cursor.callproc("GetVillageId", (block_id, village_name))
|
||||
for result in cursor.stored_results():
|
||||
result = result.fetchone()
|
||||
village_id = result[0] if result else None
|
||||
if not village_id:
|
||||
cursor.callproc("SaveVillage", (village_name, block_id))
|
||||
cursor.callproc("GetVillageId", (block_id, village_name))
|
||||
for result in cursor.stored_results():
|
||||
result = result.fetchone()
|
||||
village_id = result[0] if result else None
|
||||
print("village_id :", village_id)
|
||||
print("block_id :", block_id)
|
||||
print("invoice :", PMC_No, village_id, work_type, Invoice_Details, Invoice_Date, Invoice_No,
|
||||
Basic_Amount, Debit_Amount, After_Debit_Amount, Amount, GST_Amount, TDS_Amount,
|
||||
SD_Amount, On_Commission, Hydro_Testing, GST_SD_Amount, Final_Amount)
|
||||
|
||||
args = (
|
||||
PMC_No, village_id, work_type, Invoice_Details, Invoice_Date, Invoice_No,
|
||||
Basic_Amount, Debit_Amount, After_Debit_Amount, Amount, GST_Amount, TDS_Amount,
|
||||
SD_Amount, On_Commission, Hydro_Testing, GST_SD_Amount, Final_Amount,
|
||||
subcontractor_id, 0
|
||||
)
|
||||
|
||||
print("All invoice Details ",args)
|
||||
results = cursor.callproc('SaveInvoice', args)
|
||||
invoice_id = results[-1]
|
||||
print("invoice id from the excel ", invoice_id)
|
||||
if isinstance(hold_columns, str):
|
||||
hold_columns = ast.literal_eval(hold_columns)
|
||||
if isinstance(hold_columns, list) and all(isinstance(hold, dict) for hold in hold_columns):
|
||||
for hold in hold_columns:
|
||||
print(f"Processing hold: {hold}")
|
||||
hold_column_name = hold.get('column_name') # Get column name
|
||||
hold_type_id = hold.get('hold_type_id') # Get hold_type_id
|
||||
if hold_column_name:
|
||||
hold_amount = entry.get(
|
||||
hold_column_name) # Get the value for that specific hold column
|
||||
if hold_amount is not None:
|
||||
print(f"Processing hold type: {hold_column_name}, Hold Amount: {hold_amount}")
|
||||
hold_join_data = {
|
||||
"Contractor_Id": subcontractor_id,
|
||||
"Invoice_Id": invoice_id,
|
||||
"hold_type_id": hold_type_id,
|
||||
"hold_amount": hold_amount
|
||||
}
|
||||
cursor.callproc('InsertHoldJoinData', [
|
||||
hold_join_data['Contractor_Id'], hold_join_data['Invoice_Id'],
|
||||
hold_join_data['hold_type_id'], hold_join_data['hold_amount']
|
||||
])
|
||||
connection.commit()
|
||||
print(f"Inserted hold join data: {hold_join_data}")
|
||||
else:
|
||||
print(f"Invalid hold entry: {hold}")
|
||||
else:
|
||||
print("Hold columns data is not a valid list of dictionaries.")
|
||||
#---------------------------------------------Credit Note---------------------------------------------------------------------------
|
||||
elif any(keyword in Invoice_Details.lower() for keyword in ['credit note','logging report']):
|
||||
print("Credit note found:", PMC_No, Invoice_No, Basic_Amount, Debit_Amount, Final_Amount,
|
||||
After_Debit_Amount, GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR, Invoice_No)
|
||||
cursor.callproc(
|
||||
'AddCreditNoteFromExcel',
|
||||
[
|
||||
PMC_No, Invoice_Details, Basic_Amount, Debit_Amount, After_Debit_Amount,
|
||||
GST_Amount, Amount, Final_Amount, Payment_Amount, Total_Amount, UTR,
|
||||
subcontractor_id, Invoice_No
|
||||
]
|
||||
)
|
||||
#-----------------------------------------------Hold Amount----------------------------------------------------------------------
|
||||
# Step 1: Normalize Invoice_Details: lowercase, trim, remove extra spaces
|
||||
normalized_details = re.sub(r'\s+', ' ', Invoice_Details.strip()).lower()
|
||||
# Step 2: Define lowercase keywords
|
||||
keywords = [
|
||||
'excess hold',
|
||||
'ht',
|
||||
'hold release amount',
|
||||
'dpr excess hold amount',
|
||||
'excess hold amount',
|
||||
'Multi to Single layer bill',
|
||||
'hold amount',
|
||||
'logging report'
|
||||
]
|
||||
# Step 3: Matching condition
|
||||
if any(kw in normalized_details for kw in keywords):
|
||||
print("✅ Match found. Inserting hold release for:", Invoice_Details)
|
||||
cursor.callproc(
|
||||
'AddHoldReleaseFromExcel',
|
||||
[PMC_No, Invoice_No, Invoice_Details, Basic_Amount, Final_Amount, UTR, subcontractor_id]
|
||||
)
|
||||
connection.commit()
|
||||
print("✅ Hold release inserted for:", PMC_No, Invoice_Details)
|
||||
#------------------------------------------------------------------------------------------------------------------
|
||||
elif Invoice_Details and any(
|
||||
keyword in Invoice_Details.lower() for keyword in ['gst', 'release', 'note']):
|
||||
print("Gst rels :", PMC_No, Invoice_No, Basic_Amount, Final_Amount,Total_Amount,UTR, subcontractor_id)
|
||||
cursor.callproc(
|
||||
'AddGSTReleaseFromExcel',
|
||||
[PMC_No, Invoice_No, Basic_Amount, Final_Amount, Total_Amount, UTR, subcontractor_id]
|
||||
)
|
||||
|
||||
if PMC_No and Total_Amount and UTR:
|
||||
print("Payment :", PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR )
|
||||
cursor.callproc("SavePayment",(PMC_No, Invoice_No, Payment_Amount, TDS_Payment_Amount, Total_Amount, UTR ))
|
||||
if not village_id:
|
||||
village_id = None
|
||||
cursor.callproc('InsertOrUpdateInPayment', (
|
||||
PMC_No,
|
||||
village_id,
|
||||
work_type,
|
||||
Invoice_Details,
|
||||
Invoice_Date,
|
||||
Invoice_No,
|
||||
Basic_Amount,
|
||||
Debit_Amount,
|
||||
After_Debit_Amount,
|
||||
Amount,
|
||||
GST_Amount,
|
||||
TDS_Amount,
|
||||
SD_Amount,
|
||||
On_Commission,
|
||||
Hydro_Testing,
|
||||
0,
|
||||
GST_SD_Amount,
|
||||
Final_Amount,
|
||||
Payment_Amount,
|
||||
TDS_Payment_Amount,
|
||||
Total_Amount,
|
||||
UTR,
|
||||
subcontractor_id
|
||||
))
|
||||
connection.commit()
|
||||
return jsonify({"success": "Data saved successfully!"}), 200
|
||||
except Exception as e:
|
||||
connection.rollback()
|
||||
return jsonify({"error": f"An unexpected error occurred: {e}"}), 500
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return render_template('index.html')
|
||||
# ---------------------- Report --------------------------------
|
||||
70
v-2/controllers/gst_release_controller.py
Normal file
70
v-2/controllers/gst_release_controller.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, jsonify
|
||||
from flask_login import login_required, current_user
|
||||
from model.gst_release import GSTReleasemodel
|
||||
from model.Log import LogHelper
|
||||
|
||||
gst_release_bp = Blueprint('gst_release_bp', __name__)
|
||||
|
||||
# ------------------- Add GST Release -------------------
|
||||
@gst_release_bp.route('/add_gst_release', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_gst_release():
|
||||
gst_releases_dict = GSTReleasemodel.fetch_all_gst_releases()
|
||||
gst_releases = [
|
||||
[
|
||||
g['GST_Release_Id'], g['PMC_No'], g['invoice_no'],
|
||||
g['Basic_Amount'], g['Final_Amount'], g['Total_Amount'], g['UTR']
|
||||
] for g in gst_releases_dict
|
||||
] if gst_releases_dict else []
|
||||
|
||||
if request.method == 'POST':
|
||||
pmc_no = request.form['PMC_No']
|
||||
invoice_no = request.form['invoice_No']
|
||||
basic_amount = request.form['basic_amount']
|
||||
final_amount = request.form['final_amount']
|
||||
total_amount = request.form['total_amount']
|
||||
utr = request.form['utr']
|
||||
contractor_id = request.form['subcontractor_id']
|
||||
|
||||
LogHelper.log_action("Add GST Release", f"User {current_user.id} added GST release '{pmc_no}'")
|
||||
GSTReleasemodel.insert_gst_release(pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr, contractor_id)
|
||||
|
||||
return redirect(url_for('gst_release_bp.add_gst_release'))
|
||||
|
||||
return render_template('add_gst_release.html', gst_releases=gst_releases, invoices=[])
|
||||
|
||||
|
||||
# ------------------- Edit GST Release -------------------
|
||||
@gst_release_bp.route('/edit_gst_release/<int:gst_release_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_gst_release(gst_release_id):
|
||||
gst_release_data = GSTReleasemodel.fetch_gst_release_by_id(gst_release_id)
|
||||
if not gst_release_data:
|
||||
return "GST Release not found", 404
|
||||
|
||||
if request.method == 'POST':
|
||||
pmc_no = request.form['PMC_No']
|
||||
invoice_no = request.form['invoice_No']
|
||||
basic_amount = request.form['basic_amount']
|
||||
final_amount = request.form['final_amount']
|
||||
total_amount = request.form['total_amount']
|
||||
utr = request.form['utr']
|
||||
|
||||
LogHelper.log_action("Edit GST Release", f"User {current_user.id} edited GST release '{pmc_no}'")
|
||||
GSTReleasemodel.update_gst_release(gst_release_id, pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr)
|
||||
|
||||
return redirect(url_for('gst_release_bp.add_gst_release'))
|
||||
|
||||
return render_template('edit_gst_release.html', gst_release_data=gst_release_data, invoices=[])
|
||||
|
||||
|
||||
# ------------------- Delete GST Release -------------------
|
||||
@gst_release_bp.route('/delete_gst_release/<int:gst_release_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def delete_gst_release(gst_release_id):
|
||||
success, utr = GSTReleasemodel.delete_gst_release(gst_release_id)
|
||||
if not success:
|
||||
return jsonify({"message": "GST Release not found or failed to delete", "status": "error"}), 404
|
||||
|
||||
LogHelper.log_action("Delete GST Release", f"User {current_user.id} deleted GST release '{gst_release_id}'")
|
||||
return jsonify({"message": f"GST Release {gst_release_id} deleted successfully.", "status": "success"}), 200
|
||||
77
v-2/controllers/hold_types_controller.py
Normal file
77
v-2/controllers/hold_types_controller.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from flask import Blueprint, render_template, request, jsonify, redirect, url_for
|
||||
from flask_login import login_required
|
||||
from model.HoldTypes import HoldTypes
|
||||
from model.GST import GST
|
||||
|
||||
hold_bp = Blueprint("hold_types", __name__)
|
||||
|
||||
|
||||
# ---------------- ADD HOLD TYPE ----------------
|
||||
@hold_bp.route('/add_hold_type', methods=['GET','POST'])
|
||||
@login_required
|
||||
def add_hold_type():
|
||||
|
||||
hold = HoldTypes()
|
||||
|
||||
if request.method == 'POST':
|
||||
hold.AddHoldType(request) # ✅
|
||||
return hold.resultMessage
|
||||
|
||||
hold_types = hold.GetAllHoldTypes() # ✅
|
||||
|
||||
return render_template(
|
||||
"add_hold_type.html",
|
||||
Hold_Types_data=hold_types
|
||||
)
|
||||
|
||||
|
||||
# ---------------- CHECK HOLD TYPE (OPTIONAL LIKE BLOCK) ----------------
|
||||
@hold_bp.route('/check_hold_type', methods=['POST'])
|
||||
@login_required
|
||||
def check_hold_type():
|
||||
|
||||
hold = HoldTypes()
|
||||
return hold.CheckHoldType(request) # if exists
|
||||
|
||||
|
||||
# ---------------- EDIT HOLD TYPE ----------------
|
||||
@hold_bp.route('/edit_hold_type/<int:id>', methods=['GET','POST'])
|
||||
@login_required
|
||||
def edit_hold_type(id):
|
||||
|
||||
hold = HoldTypes()
|
||||
|
||||
if request.method == 'POST':
|
||||
hold.EditHoldType(request, id) # ✅
|
||||
return hold.resultMessage
|
||||
|
||||
hold_data = hold.GetHoldTypeByID(id) # ✅
|
||||
|
||||
return render_template(
|
||||
"edit_hold_type.html",
|
||||
hold_type=hold_data
|
||||
)
|
||||
|
||||
|
||||
# ---------------- DELETE HOLD TYPE ----------------
|
||||
@hold_bp.route('/delete_hold_type/<int:id>')
|
||||
@login_required
|
||||
def delete_hold_type(id):
|
||||
|
||||
hold = HoldTypes()
|
||||
hold.DeleteHoldType(request, id) # ✅
|
||||
|
||||
return redirect(url_for("hold_types.add_hold_type"))
|
||||
|
||||
|
||||
# ---------------- GST ----------------
|
||||
@hold_bp.route('/unreleased_gst')
|
||||
@login_required
|
||||
def unreleased_gst():
|
||||
|
||||
data = GST.get_unreleased_gst()
|
||||
|
||||
return render_template(
|
||||
"unreleased_gst.html",
|
||||
data=data
|
||||
)
|
||||
98
v-2/controllers/invoice_controller.py
Normal file
98
v-2/controllers/invoice_controller.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# controllers/invoice_controller.py
|
||||
|
||||
from flask import Blueprint, request, jsonify, render_template
|
||||
from flask_login import login_required, current_user
|
||||
from model.Invoice import *
|
||||
from model.Log import LogHelper
|
||||
|
||||
invoice_bp = Blueprint('invoice', __name__)
|
||||
|
||||
# -------------------------------- Add Invoice ---------------------------------
|
||||
@invoice_bp.route('/add_invoice', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_invoice():
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
village_name = request.form.get('village')
|
||||
village_result = get_village_id(village_name)
|
||||
|
||||
if not village_result:
|
||||
return jsonify({"status": "error", "message": f"Village '{village_name}' not found"}), 400
|
||||
|
||||
village_id = village_result['Village_Id']
|
||||
data = request.form
|
||||
|
||||
invoice_id = insert_invoice(data, village_id)
|
||||
assign_subcontractor(data, village_id)
|
||||
insert_hold_types(data, invoice_id)
|
||||
|
||||
LogHelper.log_action("Add invoice", f"User {current_user.id} Added invoice '{data.get('pmc_no')}'")
|
||||
|
||||
return jsonify({"status": "success", "message": "Invoice added successfully"}), 201
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"status": "error", "message": str(e)}), 500
|
||||
|
||||
invoices = get_all_invoice_details()
|
||||
villages = get_all_villages()
|
||||
return render_template('add_invoice.html', invoices=invoices, villages=villages)
|
||||
|
||||
|
||||
# ------------------- Search Subcontractor -------------------
|
||||
@invoice_bp.route('/search_subcontractor', methods=['POST'])
|
||||
@login_required
|
||||
def search_subcontractor():
|
||||
sub_query = request.form.get("query")
|
||||
results = search_contractors(sub_query)
|
||||
|
||||
if not results:
|
||||
return "<li>No subcontractor found</li>"
|
||||
|
||||
output = "".join(
|
||||
f"<li data-id='{row['Contractor_Id']}'>{row['Contractor_Name']}</li>"
|
||||
for row in results
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
# ------------------- Get Hold Types -------------------
|
||||
@invoice_bp.route('/get_hold_types', methods=['GET'])
|
||||
@login_required
|
||||
def get_hold_types():
|
||||
hold_types = get_all_hold_types()
|
||||
LogHelper.log_action("Get hold type", f"User {current_user.id} Get hold type '{hold_types}'")
|
||||
return jsonify(hold_types)
|
||||
|
||||
|
||||
# ------------------- Edit Invoice -------------------
|
||||
@invoice_bp.route('/edit_invoice/<int:invoice_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_invoice(invoice_id):
|
||||
if request.method == 'POST':
|
||||
data = request.form
|
||||
update_invoice(data, invoice_id)
|
||||
update_inpayment(data)
|
||||
|
||||
LogHelper.log_action("Edit invoice", f"User {current_user.id} Edit invoice '{invoice_id}'")
|
||||
return jsonify({"status": "success", "message": "Invoice updated successfully"}), 200
|
||||
|
||||
invoice = get_invoice_by_id(invoice_id)
|
||||
return render_template('edit_invoice.html', invoice=invoice)
|
||||
|
||||
|
||||
# ------------------- Delete Invoice -------------------
|
||||
@invoice_bp.route('/delete_invoice/<int:invoice_id>', methods=['GET'])
|
||||
@login_required
|
||||
def delete_invoice_route(invoice_id):
|
||||
try:
|
||||
delete_invoice_data(invoice_id, current_user.id)
|
||||
LogHelper.log_action("Delete Invoice", f"User {current_user.id} deleted Invoice '{invoice_id}'")
|
||||
return jsonify({
|
||||
"message": f"Invoice {invoice_id} deleted successfully.",
|
||||
"status": "success"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"message": str(e),
|
||||
"status": "error"
|
||||
}), 500
|
||||
31
v-2/controllers/log_controller.py
Normal file
31
v-2/controllers/log_controller.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from flask import Blueprint, render_template, request
|
||||
from flask_login import login_required
|
||||
|
||||
from model.Log import LogData
|
||||
|
||||
log_bp = Blueprint('log', __name__)
|
||||
|
||||
|
||||
@log_bp.route('/activity_log', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def activity_log():
|
||||
|
||||
start_date = request.values.get("start_date")
|
||||
end_date = request.values.get("end_date")
|
||||
user_name = request.values.get("username")
|
||||
|
||||
logData = LogData()
|
||||
|
||||
filtered_logs = logData.GetFilteredActivitiesLog(
|
||||
start_date,
|
||||
end_date,
|
||||
user_name
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"activity_log.html",
|
||||
logs=filtered_logs,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
username=user_name
|
||||
)
|
||||
103
v-2/controllers/payment_controller.py
Normal file
103
v-2/controllers/payment_controller.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, jsonify
|
||||
from flask_login import login_required, current_user
|
||||
from model.payment import Paymentmodel
|
||||
from model.Log import LogHelper
|
||||
|
||||
payment_bp = Blueprint('payment_bp', __name__)
|
||||
|
||||
# ------------------- Add Payment -------------------
|
||||
@payment_bp.route('/add_payment', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_payment():
|
||||
payments_dicts = Paymentmodel.fetch_all_payments()
|
||||
# Convert to array for template
|
||||
payments = [
|
||||
[
|
||||
p['Payment_Id'], p['PMC_No'], p['Invoice_No'],
|
||||
p['Payment_Amount'], p['TDS_Payment_Amount'], p['Total_Amount'], p['UTR']
|
||||
] for p in payments_dicts
|
||||
] if payments_dicts else []
|
||||
|
||||
if request.method == 'POST':
|
||||
subcontractor_id = request.form.get('subcontractor_id')
|
||||
pmc_no = request.form['PMC_No']
|
||||
invoice_no = request.form['invoice_No']
|
||||
amount = request.form['Payment_Amount']
|
||||
tds_amount = request.form['TDS_Payment_Amount']
|
||||
total_amount = request.form['total_amount']
|
||||
utr = request.form['utr']
|
||||
|
||||
LogHelper.log_action("Add Payment", f"User {current_user.id} Add Payment '{pmc_no}'")
|
||||
Paymentmodel.insert_payment(pmc_no, invoice_no, amount, tds_amount, total_amount, utr)
|
||||
Paymentmodel.update_inpayment(subcontractor_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr)
|
||||
|
||||
return redirect(url_for('payment_bp.add_payment'))
|
||||
|
||||
return render_template('add_payment.html', payments=payments)
|
||||
|
||||
|
||||
# ------------------- Get PMC Nos -------------------
|
||||
@payment_bp.route('/get_pmc_nos_by_subcontractor/<subcontractorId>')
|
||||
@login_required
|
||||
def get_pmc_nos_by_subcontractor(subcontractorId):
|
||||
connection = Paymentmodel.get_connection()
|
||||
cur = connection.cursor()
|
||||
cur.callproc('GetDistinctPMCNoByContractorId', [subcontractorId])
|
||||
results = []
|
||||
for result in cur.stored_results():
|
||||
results = result.fetchall()
|
||||
cur.close()
|
||||
pmc_nos = [row[0] for row in results]
|
||||
return jsonify({'pmc_nos': pmc_nos})
|
||||
|
||||
|
||||
# ------------------- Edit Payment -------------------
|
||||
@payment_bp.route('/edit_payment/<int:payment_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_payment(payment_id):
|
||||
payment_data = Paymentmodel.fetch_payment_by_id(payment_id)
|
||||
|
||||
if not payment_data:
|
||||
return "Payment not found", 404
|
||||
|
||||
if request.method == 'POST':
|
||||
pmc_no = request.form['PMC_No']
|
||||
invoice_no = request.form['invoice_No']
|
||||
amount = request.form['Payment_Amount']
|
||||
tds_amount = request.form['TDS_Payment_Amount']
|
||||
total_amount = request.form['total_amount']
|
||||
utr = request.form['utr']
|
||||
|
||||
LogHelper.log_action("Edit Payment", f"User {current_user.id} Edit Payment '{pmc_no}'")
|
||||
Paymentmodel.call_update_payment_proc(payment_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr)
|
||||
|
||||
# Update inpayment
|
||||
connection = Paymentmodel.get_connection()
|
||||
cursor = connection.cursor()
|
||||
cursor.callproc(
|
||||
'UpdateInpaymentByPMCInvoiceUTR',
|
||||
[amount, tds_amount, total_amount, pmc_no, invoice_no, utr]
|
||||
)
|
||||
connection.commit()
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return redirect(url_for('payment_bp.add_payment'))
|
||||
|
||||
return render_template('edit_payment.html', payment_data=payment_data)
|
||||
|
||||
|
||||
# ------------------- Delete Payment -------------------
|
||||
@payment_bp.route('/delete_payment/<int:payment_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def delete_payment(payment_id):
|
||||
success, pmc_no, invoice_no = Paymentmodel.delete_payment(payment_id)
|
||||
if not success:
|
||||
return jsonify({"message": "Payment not found or failed to delete", "status": "error"}), 404
|
||||
|
||||
LogHelper.log_action("Delete Payment", f"User {current_user.id} deleted Payment '{payment_id}'")
|
||||
|
||||
return jsonify({
|
||||
"message": f"Payment ID {payment_id} deleted successfully.",
|
||||
"status": "success"
|
||||
}), 200
|
||||
37
v-2/controllers/pmc_report_controller.py
Normal file
37
v-2/controllers/pmc_report_controller.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from flask import Blueprint, render_template, send_from_directory
|
||||
from flask_login import login_required, current_user
|
||||
from model.PmcReport import PmcReport
|
||||
|
||||
pmc_report_bp = Blueprint("pmc_report", __name__)
|
||||
|
||||
@pmc_report_bp.route("/pmc_report/<pmc_no>")
|
||||
@login_required
|
||||
def pmc_report(pmc_no):
|
||||
data = PmcReport.get_pmc_report(pmc_no)
|
||||
if not data:
|
||||
return "No PMC found with this number", 404
|
||||
|
||||
return render_template(
|
||||
"pmc_report.html",
|
||||
info=data["info"],
|
||||
invoices=data["invoices"],
|
||||
hold_types=data["hold_types"],
|
||||
gst_rel=data["gst_rel"],
|
||||
payments=data["payments"],
|
||||
credit_note=data["credit_note"],
|
||||
hold_release=data["hold_release"],
|
||||
total=data["total"]
|
||||
)
|
||||
|
||||
@pmc_report_bp.route("/download_pmc_report/<pmc_no>")
|
||||
@login_required
|
||||
def download_pmc_report(pmc_no):
|
||||
|
||||
result = PmcReport.download_pmc_report(pmc_no)
|
||||
|
||||
if not result:
|
||||
return "No contractor found for this PMC No", 404
|
||||
|
||||
output_folder, file_name = result
|
||||
|
||||
return send_from_directory(output_folder, file_name, as_attachment=True)
|
||||
193
v-2/controllers/report_controller.py
Normal file
193
v-2/controllers/report_controller.py
Normal file
@@ -0,0 +1,193 @@
|
||||
from flask import Blueprint, render_template, request, jsonify, send_file
|
||||
from flask_login import login_required, current_user
|
||||
from model.Report import ReportHelper
|
||||
from model.Log import LogHelper
|
||||
import config
|
||||
import os
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font
|
||||
from model.ContractorInfo import ContractorInfo
|
||||
from model.FolderAndFile import FolderAndFile
|
||||
|
||||
|
||||
report_bp = Blueprint("report", __name__)
|
||||
|
||||
|
||||
# ---------------- Report Page ----------------
|
||||
@report_bp.route("/report")
|
||||
@login_required
|
||||
def report_page():
|
||||
return render_template("/report.html")
|
||||
|
||||
|
||||
# ---------------- Search Contractor ----------------
|
||||
@report_bp.route("/search_contractor", methods=["POST"])
|
||||
@login_required
|
||||
def search_contractor():
|
||||
|
||||
subcontractor_name = request.form.get("subcontractor_name")
|
||||
|
||||
LogHelper.log_action(
|
||||
"Search Contractor",
|
||||
f"User {current_user.id} searched contractor '{subcontractor_name}'"
|
||||
)
|
||||
|
||||
data = ReportHelper.search_contractor(request)
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
# ---------------- Contractor Report ----------------
|
||||
@report_bp.route('/contractor_report/<int:contractor_id>')
|
||||
@login_required
|
||||
def contractor_report(contractor_id):
|
||||
|
||||
data = ReportHelper.get_contractor_report(contractor_id)
|
||||
|
||||
return render_template(
|
||||
'subcontractor_report.html',
|
||||
contractor_id=contractor_id,
|
||||
**data
|
||||
)
|
||||
|
||||
@report_bp.route('/download_report/<int:contractor_id>')
|
||||
@login_required
|
||||
def download_report(contractor_id):
|
||||
|
||||
return ReportHelper().download_report(contractor_id=contractor_id)
|
||||
|
||||
|
||||
|
||||
|
||||
# @report_bp.route('/download_report/<int:contractor_id>')
|
||||
# @login_required
|
||||
# def download_report(contractor_id):
|
||||
# try:
|
||||
# connection = config.get_db_connection()
|
||||
# cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# # -------- Contractor Info --------
|
||||
# contractor = ContractorInfo(contractor_id)
|
||||
# contInfo = contractor.contInfo
|
||||
|
||||
# if not contInfo:
|
||||
# return "No contractor found", 404
|
||||
|
||||
# # -------- Invoice Data --------
|
||||
# cursor.callproc('FetchInvoicesByContractor', [contractor_id])
|
||||
|
||||
# invoices = []
|
||||
# for result in cursor.stored_results():
|
||||
# invoices.extend(result.fetchall())
|
||||
|
||||
# if not invoices:
|
||||
# return "No invoice data found"
|
||||
|
||||
# # -------- Create Workbook --------
|
||||
# workbook = openpyxl.Workbook()
|
||||
# sheet = workbook.active
|
||||
# sheet.title = "Contractor Report"
|
||||
|
||||
# # ================= CONTRACTOR DETAILS =================
|
||||
# sheet.append(["SUB CONTRACTOR DETAILS"])
|
||||
# sheet.cell(row=sheet.max_row, column=1).font = Font(bold=True)
|
||||
# sheet.append([])
|
||||
|
||||
# sheet.append(["Name", contInfo.get("Contractor_Name") or ""])
|
||||
# sheet.append(["Mobile No", contInfo.get("Mobile_No") or ""])
|
||||
# sheet.append(["Email", contInfo.get("Email") or ""])
|
||||
# sheet.append(["Village", contInfo.get("Village_Name") or ""])
|
||||
# sheet.append(["Block", contInfo.get("Block_Name") or ""])
|
||||
# sheet.append(["District", contInfo.get("District_Name") or ""])
|
||||
# sheet.append(["State", contInfo.get("State_Name") or ""])
|
||||
# sheet.append(["Address", contInfo.get("Address") or ""])
|
||||
# sheet.append(["GST No", contInfo.get("GST_No") or ""])
|
||||
# sheet.append(["PAN No", contInfo.get("PAN_No") or ""])
|
||||
# sheet.append([])
|
||||
# sheet.append([])
|
||||
|
||||
# # ================= TABLE HEADERS =================
|
||||
# headers = [
|
||||
# "PMC No", "Village", "Invoice No", "Invoice Date", "Work Type","Invoice_Details",
|
||||
# "Basic Amount", "Debit Amount", "After Debit Amount",
|
||||
# "Amount", "GST Amount", "TDS Amount", "SD Amount",
|
||||
# "On Commission", "Hydro Testing", "Hold Amount",
|
||||
# "GST SD Amount", "Final Amount",
|
||||
# "Payment Amount", "TDS Payment",
|
||||
# "Total Amount", "UTR"
|
||||
# ]
|
||||
# sheet.append(headers)
|
||||
# for col in range(1, len(headers) + 1):
|
||||
# sheet.cell(row=sheet.max_row, column=col).font = Font(bold=True)
|
||||
|
||||
# # ================= DATA =================
|
||||
# total_final = 0
|
||||
# total_payment = 0
|
||||
# total_amount = 0
|
||||
|
||||
# for inv in invoices:
|
||||
# row = [
|
||||
# inv.get("PMC_No"),
|
||||
# inv.get("Village_Name"),
|
||||
# inv.get("invoice_no"),
|
||||
# inv.get("Invoice_Date"),
|
||||
# inv.get("Work_Type"),
|
||||
# inv.get("Invoice_Details"),
|
||||
# inv.get("Basic_Amount"),
|
||||
# inv.get("Debit_Amount"),
|
||||
# inv.get("After_Debit_Amount"),
|
||||
# inv.get("Amount"),
|
||||
# inv.get("GST_Amount"),
|
||||
# inv.get("TDS_Amount"),
|
||||
# inv.get("SD_Amount"),
|
||||
# inv.get("On_Commission"),
|
||||
# inv.get("Hydro_Testing"),
|
||||
# inv.get("Hold_Amount"),
|
||||
# inv.get("GST_SD_Amount"),
|
||||
# inv.get("Final_Amount"),
|
||||
# inv.get("Payment_Amount"),
|
||||
# inv.get("TDS_Payment_Amount"),
|
||||
# inv.get("Total_Amount"),
|
||||
# inv.get("UTR")
|
||||
# ]
|
||||
|
||||
# total_final += float(inv.get("Final_Amount") or 0)
|
||||
# total_payment += float(inv.get("Payment_Amount") or 0)
|
||||
# total_amount += float(inv.get("Total_Amount") or 0)
|
||||
|
||||
# sheet.append(row)
|
||||
|
||||
# # ================= TOTAL ROW =================
|
||||
# sheet.append([])
|
||||
# sheet.append([
|
||||
# "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
||||
# "TOTAL",
|
||||
# total_final,
|
||||
# total_payment,
|
||||
# "",
|
||||
# total_amount,
|
||||
# ""
|
||||
# ])
|
||||
|
||||
# # ================= AUTO WIDTH =================
|
||||
# for column in sheet.columns:
|
||||
# max_length = 0
|
||||
# column_letter = column[0].column_letter
|
||||
# for cell in column:
|
||||
# if cell.value:
|
||||
# max_length = max(max_length, len(str(cell.value)))
|
||||
# sheet.column_dimensions[column_letter].width = max_length + 2
|
||||
|
||||
# # ================= SAVE FILE =================
|
||||
# # output_folder = "downloads"
|
||||
# # os.makedirs(output_folder, exist_ok=True)
|
||||
# # filename = f"Contractor_Report_{contInfo.get('Contractor_Name')}.xlsx"
|
||||
# # output_file = os.path.join(output_folder, filename)
|
||||
|
||||
# filename = f"Contractor_Report_{contInfo.get('Contractor_Name')}.xlsx"
|
||||
# output_file = FolderAndFile.get_download_path(filename)
|
||||
# workbook.save(output_file)
|
||||
|
||||
# return send_file(output_file, as_attachment=True)
|
||||
|
||||
# except Exception as e:
|
||||
# return str(e)
|
||||
69
v-2/controllers/state_controller.py
Normal file
69
v-2/controllers/state_controller.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for
|
||||
from flask_login import login_required
|
||||
from model.State import State
|
||||
|
||||
state_bp = Blueprint('state', __name__)
|
||||
|
||||
|
||||
@state_bp.route('/add_state', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_state():
|
||||
|
||||
state = State()
|
||||
|
||||
if request.method == 'POST':
|
||||
state.AddState(request=request)
|
||||
return state.resultMessage
|
||||
|
||||
statedata = state.GetAllStates(request=request)
|
||||
|
||||
return render_template('add_state.html', statedata=statedata)
|
||||
|
||||
|
||||
@state_bp.route('/check_state', methods=['POST'])
|
||||
@login_required
|
||||
def check_state():
|
||||
|
||||
state = State()
|
||||
|
||||
return state.CheckState(request=request)
|
||||
|
||||
|
||||
@state_bp.route('/delete_state/<int:id>')
|
||||
@login_required
|
||||
def deleteState(id):
|
||||
|
||||
state = State()
|
||||
|
||||
msg = state.DeleteState(request=request, id=id)
|
||||
|
||||
if not state.isSuccess:
|
||||
return state.resultMessage
|
||||
else:
|
||||
return redirect(url_for('state.add_state'))
|
||||
|
||||
|
||||
@state_bp.route('/edit_state/<int:id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def editState(id):
|
||||
|
||||
state = State()
|
||||
|
||||
if request.method == 'POST':
|
||||
|
||||
state.EditState(request=request, id=id)
|
||||
|
||||
if state.isSuccess:
|
||||
return redirect(url_for('state.add_state'))
|
||||
else:
|
||||
return state.resultMessage
|
||||
|
||||
statedata = state.GetStateByID(request=request, id=id)
|
||||
|
||||
if not state.isSuccess:
|
||||
return state.resultMessage
|
||||
|
||||
if statedata is None:
|
||||
statedata = []
|
||||
|
||||
return render_template('edit_state.html', state=statedata)
|
||||
117
v-2/controllers/subcontractor_controller.py
Normal file
117
v-2/controllers/subcontractor_controller.py
Normal file
@@ -0,0 +1,117 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, jsonify
|
||||
from flask_login import login_required
|
||||
from model.Subcontractor import Subcontractor
|
||||
|
||||
subcontractor_bp = Blueprint('subcontractor', __name__)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Helpers (unchanged)
|
||||
# ----------------------------------------------------------
|
||||
class HtmlHelper:
|
||||
@staticmethod
|
||||
def json_response(data, status=200):
|
||||
return jsonify(data), status
|
||||
|
||||
class ResponseHandler:
|
||||
@staticmethod
|
||||
def fetch_failure(entity):
|
||||
return {"status": "error", "message": f"Failed to fetch {entity}"}
|
||||
|
||||
@staticmethod
|
||||
def add_failure(entity):
|
||||
return {"status": "error", "message": f"Failed to add {entity}"}
|
||||
|
||||
@staticmethod
|
||||
def update_failure(entity):
|
||||
return {"status": "error", "message": f"Failed to update {entity}"}
|
||||
|
||||
@staticmethod
|
||||
def delete_failure(entity):
|
||||
return {"status": "error", "message": f"Failed to delete {entity}"}
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# LIST + ADD
|
||||
# ----------------------------------------------------------
|
||||
@subcontractor_bp.route('/subcontractor', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def subcontract():
|
||||
|
||||
sub = Subcontractor()
|
||||
|
||||
# ---------------- GET ----------------
|
||||
if request.method == 'GET':
|
||||
subcontractor = sub.GetAllSubcontractors(request)
|
||||
|
||||
if not sub.isSuccess:
|
||||
return HtmlHelper.json_response(
|
||||
ResponseHandler.fetch_failure("Subcontractor"), 500
|
||||
)
|
||||
|
||||
return render_template('add_subcontractor.html', subcontractor=subcontractor)
|
||||
|
||||
# ---------------- POST (ADD) ----------------
|
||||
if request.method == 'POST':
|
||||
|
||||
sub.AddSubcontractor(request)
|
||||
|
||||
if not sub.isSuccess:
|
||||
return HtmlHelper.json_response(
|
||||
ResponseHandler.add_failure("Subcontractor"), 500
|
||||
)
|
||||
|
||||
# Reload list after insert
|
||||
subcontractor = sub.GetAllSubcontractors(request)
|
||||
|
||||
return render_template('add_subcontractor.html', subcontractor=subcontractor)
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# EDIT
|
||||
# ----------------------------------------------------------
|
||||
@subcontractor_bp.route('/edit_subcontractor/<int:id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_subcontractor(id):
|
||||
|
||||
sub = Subcontractor()
|
||||
|
||||
# Fetch data
|
||||
subcontractor = sub.GetSubcontractorByID(id)
|
||||
|
||||
if not subcontractor:
|
||||
return HtmlHelper.json_response(
|
||||
ResponseHandler.fetch_failure("Subcontractor"), 404
|
||||
)
|
||||
|
||||
# ---------------- POST (UPDATE) ----------------
|
||||
if request.method == 'POST':
|
||||
|
||||
sub.EditSubcontractor(request, id)
|
||||
|
||||
if not sub.isSuccess:
|
||||
return HtmlHelper.json_response(
|
||||
ResponseHandler.update_failure("Subcontractor"), 500
|
||||
)
|
||||
|
||||
return redirect(url_for('subcontractor.subcontract'))
|
||||
|
||||
return render_template('edit_subcontractor.html', subcontractor=subcontractor)
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# DELETE
|
||||
# ----------------------------------------------------------
|
||||
@subcontractor_bp.route('/deleteSubContractor/<int:id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def deleteSubContractor(id):
|
||||
|
||||
sub = Subcontractor()
|
||||
|
||||
sub.DeleteSubcontractor(request, id)
|
||||
|
||||
if not sub.isSuccess:
|
||||
return HtmlHelper.json_response(
|
||||
ResponseHandler.delete_failure("Subcontractor"), 500
|
||||
)
|
||||
|
||||
return redirect(url_for('subcontractor.subcontract'))
|
||||
167
v-2/controllers/village_controller.py
Normal file
167
v-2/controllers/village_controller.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
|
||||
from flask_login import login_required
|
||||
|
||||
import config
|
||||
|
||||
from model.Village import Village
|
||||
from model.State import State
|
||||
|
||||
# Create Blueprint
|
||||
village_bp = Blueprint('village', __name__)
|
||||
|
||||
|
||||
# ------------------------- Add Village -------------------------
|
||||
@village_bp.route('/add_village', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_village():
|
||||
|
||||
village = Village()
|
||||
|
||||
if request.method == 'POST':
|
||||
village.AddVillage(request=request)
|
||||
return village.resultMessage
|
||||
|
||||
state = State()
|
||||
states = state.GetAllStates(request=request)
|
||||
|
||||
villages = village.GetAllVillages(request=request)
|
||||
|
||||
return render_template(
|
||||
'add_village.html',
|
||||
states=states,
|
||||
villages=villages
|
||||
)
|
||||
|
||||
|
||||
# ------------------------- Fetch Districts -------------------------
|
||||
@village_bp.route('/get_districts/<int:state_id>')
|
||||
@login_required
|
||||
def get_districts(state_id):
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.callproc("GetDistrictByStateID", [state_id])
|
||||
|
||||
districts = []
|
||||
|
||||
for rs in cursor.stored_results():
|
||||
districts = rs.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
district_list = []
|
||||
|
||||
for d in districts:
|
||||
district_list.append({
|
||||
"id": d[0],
|
||||
"name": d[1]
|
||||
})
|
||||
|
||||
return jsonify(district_list)
|
||||
|
||||
|
||||
# ------------------------- Fetch Blocks -------------------------
|
||||
@village_bp.route('/get_blocks/<int:district_id>')
|
||||
@login_required
|
||||
def get_blocks(district_id):
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.callproc("GetBlocksByDistrictID", [district_id])
|
||||
|
||||
blocks = []
|
||||
|
||||
for rs in cursor.stored_results():
|
||||
blocks = rs.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
block_list = []
|
||||
|
||||
for b in blocks:
|
||||
block_list.append({
|
||||
"id": b[0],
|
||||
"name": b[1]
|
||||
})
|
||||
|
||||
return jsonify(block_list)
|
||||
|
||||
|
||||
# ------------------------- Check Village -------------------------
|
||||
@village_bp.route('/check_village', methods=['POST'])
|
||||
@login_required
|
||||
def check_village():
|
||||
|
||||
village = Village()
|
||||
return village.CheckVillage(request=request)
|
||||
|
||||
|
||||
# ------------------------- Delete Village -------------------------
|
||||
@village_bp.route('/delete_village/<int:village_id>')
|
||||
@login_required
|
||||
def delete_village(village_id):
|
||||
|
||||
village = Village()
|
||||
|
||||
village.DeleteVillage(request=request, village_id=village_id)
|
||||
|
||||
if not village.isSuccess:
|
||||
flash(village.resultMessage, "error")
|
||||
else:
|
||||
flash(village.resultMessage, "success")
|
||||
|
||||
return redirect(url_for('village.add_village'))
|
||||
|
||||
|
||||
# ------------------------- Edit Village -------------------------
|
||||
@village_bp.route('/edit_village/<int:village_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_village(village_id):
|
||||
|
||||
village = Village()
|
||||
|
||||
if request.method == 'POST':
|
||||
|
||||
village.EditVillage(request=request, village_id=village_id)
|
||||
|
||||
if village.isSuccess:
|
||||
flash(village.resultMessage, "success")
|
||||
return redirect(url_for('village.add_village'))
|
||||
|
||||
else:
|
||||
flash(village.resultMessage, "error")
|
||||
|
||||
village_data = village.GetVillageByID(request=request, id=village_id)
|
||||
blocks = village.GetAllBlocks(request=request)
|
||||
|
||||
return render_template(
|
||||
'edit_village.html',
|
||||
village_data=village_data,
|
||||
blocks=blocks
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
village_data = village.GetVillageByID(request=request, id=village_id)
|
||||
|
||||
if not village.isSuccess:
|
||||
flash(village.resultMessage, "error")
|
||||
return redirect(url_for('village.add_village'))
|
||||
|
||||
blocks = village.GetAllBlocks(request=request)
|
||||
|
||||
if village_data is None:
|
||||
village_data = []
|
||||
|
||||
if blocks is None:
|
||||
blocks = []
|
||||
|
||||
return render_template(
|
||||
'edit_village.html',
|
||||
village_data=village_data,
|
||||
blocks=blocks
|
||||
)
|
||||
35
v-2/docker-compose.yml
Normal file
35
v-2/docker-compose.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
flask-app:
|
||||
build: .
|
||||
ports:
|
||||
- "5000:5000"
|
||||
depends_on:
|
||||
- mysql
|
||||
environment:
|
||||
- MYSQL_HOST=mysql
|
||||
- MYSQL_USER=root
|
||||
- MYSQL_PASSWORD=root
|
||||
- MYSQL_DB=test
|
||||
networks:
|
||||
- mynetwork
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: test
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- mysql-data:/var/lib/mysql
|
||||
networks:
|
||||
- mynetwork
|
||||
|
||||
volumes:
|
||||
mysql-data:
|
||||
|
||||
networks:
|
||||
mynetwork:
|
||||
0
v-2/logs/activity.log
Normal file
0
v-2/logs/activity.log
Normal file
74
v-2/logs/audit.log
Normal file
74
v-2/logs/audit.log
Normal file
@@ -0,0 +1,74 @@
|
||||
2025-08-22 13:29:24,485 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
|
||||
2025-08-22 13:41:42,046 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.180
|
||||
2025-08-22 14:07:01,924 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
|
||||
2025-08-22 15:05:59,287 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.180
|
||||
2025-08-22 15:06:05,201 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
|
||||
2025-08-23 15:58:28,248 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
|
||||
2025-08-23 17:33:06,648 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
|
||||
2025-08-23 17:39:08,442 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
|
||||
2025-08-23 18:14:51,722 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.180
|
||||
2025-08-25 11:57:12,202 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.238
|
||||
2025-08-25 12:00:17,780 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
|
||||
2025-08-25 14:09:29,385 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
|
||||
2025-08-25 14:12:35,084 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
|
||||
2025-08-25 14:23:53,539 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
|
||||
2025-08-25 14:23:54,024 | User: v.sinha | Action: Added State | Details: User MP Adding State | IP: 192.168.0.181
|
||||
2025-08-25 14:23:57,113 | User: v.sinha | Action: Deleted State | Details: User 11 Deleting State | IP: 192.168.0.181
|
||||
2025-08-25 14:31:55,715 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
|
||||
2025-08-25 14:36:21,158 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
|
||||
2025-08-25 14:36:21,496 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
|
||||
2025-08-25 14:47:08,719 | User: v.sinha | Action: Checked State | Details: User Maharashtra Checking State | IP: 192.168.0.181
|
||||
2025-08-25 14:47:14,759 | User: v.sinha | Action: Deleted State | Details: User 12 Deleting State | IP: 192.168.0.181
|
||||
2025-08-25 14:47:16,915 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
|
||||
2025-08-25 14:47:17,708 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
|
||||
2025-08-25 14:49:09,480 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
|
||||
2025-08-25 14:49:13,014 | User: v.sinha | Action: Deleted State | Details: User 13 Deleting State | IP: 192.168.0.181
|
||||
2025-08-25 14:49:14,584 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
|
||||
2025-08-25 14:49:15,055 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
|
||||
2025-08-25 14:51:55,187 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
|
||||
2025-08-25 14:51:58,463 | User: v.sinha | Action: Deleted State | Details: User 14 Deleting State | IP: 192.168.0.181
|
||||
2025-08-25 14:52:00,606 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
|
||||
2025-08-25 14:52:00,953 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
|
||||
2025-08-25 14:54:26,674 | User: v.sinha | Action: Deleted State | Details: User 15 Deleting State | IP: 192.168.0.181
|
||||
2025-08-25 14:54:28,892 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
|
||||
2025-08-25 14:54:29,553 | User: v.sinha | Action: Added State | Details: State 'MP' added. | IP: 192.168.0.181
|
||||
2025-08-25 15:18:37,773 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
|
||||
2025-08-25 15:18:43,347 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
|
||||
2025-08-25 15:20:41,331 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
|
||||
2025-08-25 15:20:47,525 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
|
||||
2025-08-25 15:20:55,687 | User: v.sinha | Action: Checked State | Details: User MP Checking State | IP: 192.168.0.181
|
||||
2025-08-25 15:20:58,544 | User: v.sinha | Action: Deleted State | Details: User 16 Deleting State | IP: 192.168.0.181
|
||||
2025-08-25 16:33:49,898 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'MP' | IP: 192.168.0.181
|
||||
2025-08-25 16:33:50,394 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'MP' | IP: 192.168.0.181
|
||||
2025-08-25 16:43:46,446 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
|
||||
2025-08-25 16:43:49,710 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
|
||||
2025-08-25 16:43:58,093 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
|
||||
2025-08-25 16:44:11,935 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'shamli' | IP: 192.168.0.181
|
||||
2025-08-25 16:44:12,466 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'shamli' | IP: 192.168.0.181
|
||||
2025-08-25 16:44:17,731 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '18' | IP: 192.168.0.181
|
||||
2025-08-25 16:57:27,983 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
|
||||
2025-08-25 16:57:33,498 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
|
||||
2025-08-25 16:57:41,438 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'shamli' | IP: 192.168.0.181
|
||||
2025-08-25 16:57:42,250 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'shamli' | IP: 192.168.0.181
|
||||
2025-08-25 16:57:45,339 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '19' | IP: 192.168.0.181
|
||||
2025-08-25 16:57:48,794 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '17' | IP: 192.168.0.181
|
||||
2025-08-25 17:04:11,021 | User: v.sinha | Action: Logout | Details: User v.sinha logged out | IP: 192.168.0.181
|
||||
2025-08-25 17:04:16,165 | User: v.sinha | Action: Login | Details: User v.sinha logged in | IP: 192.168.0.181
|
||||
2025-08-25 17:04:21,702 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'shamli' | IP: 192.168.0.181
|
||||
2025-08-25 17:04:22,159 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'shamli' | IP: 192.168.0.181
|
||||
2025-08-25 17:04:26,850 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'M' | IP: 192.168.0.181
|
||||
2025-08-25 17:04:27,076 | User: v.sinha | Action: Check State | Details: User v.sinha Checked state 'MP' | IP: 192.168.0.181
|
||||
2025-08-25 17:04:28,070 | User: v.sinha | Action: Add State | Details: User v.sinha added state 'MP' | IP: 192.168.0.181
|
||||
2025-08-25 17:04:32,165 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '21' | IP: 192.168.0.181
|
||||
2025-08-25 17:04:35,058 | User: v.sinha | Action: Delete State | Details: User v.sinha Deleted state '20' | IP: 192.168.0.181
|
||||
2025-08-25 17:06:05,113 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'Shamli' | IP: 192.168.0.181
|
||||
2025-08-25 17:06:05,114 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'Shamli' | IP: 192.168.0.181
|
||||
2025-08-25 17:06:08,040 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'p' | IP: 192.168.0.181
|
||||
2025-08-25 17:06:08,360 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pu' | IP: 192.168.0.181
|
||||
2025-08-25 17:06:08,554 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pun' | IP: 192.168.0.181
|
||||
2025-08-25 17:06:08,756 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
|
||||
2025-08-25 17:06:10,190 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
|
||||
2025-08-25 17:06:11,204 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
|
||||
2025-08-25 17:06:11,206 | User: v.sinha | Action: Check District | Details: User v.sinha Checked District 'pune' | IP: 192.168.0.181
|
||||
2025-08-25 17:06:13,085 | User: v.sinha | Action: Add District | Details: User v.sinha Added District 'pune' | IP: 192.168.0.181
|
||||
2025-08-25 17:06:19,524 | User: v.sinha | Action: Delete District | Details: User v.sinha Deleted District '5' | IP: 192.168.0.181
|
||||
68
v-2/main.py
Normal file
68
v-2/main.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# main.py
|
||||
from flask import Flask, render_template
|
||||
from flask_login import LoginManager
|
||||
from model.Auth import User
|
||||
|
||||
# Import Blueprints / Controllers
|
||||
from controllers.auth_controller import auth_bp
|
||||
from controllers.log_controller import log_bp
|
||||
from controllers.state_controller import state_bp
|
||||
from controllers.district_controller import district_bp
|
||||
from controllers.block_controller import block_bp
|
||||
from controllers.village_controller import village_bp
|
||||
from controllers.invoice_controller import invoice_bp
|
||||
from controllers.subcontractor_controller import subcontractor_bp
|
||||
from controllers.payment_controller import payment_bp
|
||||
from controllers.gst_release_controller import gst_release_bp
|
||||
from controllers.excel_upload_controller import excel_bp
|
||||
from controllers.report_controller import report_bp
|
||||
from controllers.pmc_report_controller import pmc_report_bp
|
||||
|
||||
from controllers.hold_types_controller import hold_bp
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
# ---------------- Initialize App ----------------
|
||||
app = Flask(__name__)
|
||||
|
||||
load_dotenv()
|
||||
Secret_Key = os.getenv("SECRET_KEY")
|
||||
app.secret_key = Secret_Key
|
||||
|
||||
# ---------------- Login Manager ----------------
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = 'auth.login'
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User(user_id)
|
||||
|
||||
# ---------------- Home Route ----------------
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template("index.html")
|
||||
|
||||
# ---------------- Register Blueprints ----------------
|
||||
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(log_bp)
|
||||
app.register_blueprint(state_bp)
|
||||
app.register_blueprint(district_bp)
|
||||
app.register_blueprint(block_bp)
|
||||
app.register_blueprint(village_bp)
|
||||
app.register_blueprint(invoice_bp)
|
||||
app.register_blueprint(subcontractor_bp)
|
||||
app.register_blueprint(payment_bp)
|
||||
app.register_blueprint(gst_release_bp)
|
||||
app.register_blueprint(excel_bp)
|
||||
app.register_blueprint(report_bp)
|
||||
app.register_blueprint(pmc_report_bp)
|
||||
app.register_blueprint(hold_bp)
|
||||
|
||||
# ---------------- Run App ----------------
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
|
||||
|
||||
|
||||
63
v-2/model/Auth.py
Normal file
63
v-2/model/Auth.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from flask_login import UserMixin
|
||||
from ldap3 import Server, Connection, ALL
|
||||
from ldap3.core.exceptions import LDAPBindError
|
||||
|
||||
# Load .env
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class DefaultCredentials:
|
||||
username = os.getenv("DEFAULT_USERNAME")
|
||||
password = os.getenv("DEFAULT_PASSWORD")
|
||||
|
||||
|
||||
class LoginLDAP:
|
||||
|
||||
def __init__(self, request):
|
||||
|
||||
self.username = request.form.get("username", "").strip()
|
||||
self.password = request.form.get("password", "")
|
||||
|
||||
self.isDefaultCredentials = False
|
||||
self.isValidLogin = False
|
||||
self.errorMessage = ""
|
||||
|
||||
ldap_server = "ldap://localhost:389"
|
||||
ldap_user_dn = f"uid={self.username},ou=users,dc=lcepl,dc=org"
|
||||
|
||||
# fallback admin login
|
||||
if (
|
||||
self.username == DefaultCredentials.username
|
||||
and self.password == DefaultCredentials.password
|
||||
):
|
||||
self.isDefaultCredentials = True
|
||||
self.isValidLogin = True
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
server = Server(ldap_server, get_info=ALL)
|
||||
|
||||
conn = Connection(
|
||||
server,
|
||||
user=ldap_user_dn,
|
||||
password=self.password,
|
||||
auto_bind=True
|
||||
)
|
||||
|
||||
if conn.bound:
|
||||
self.isValidLogin = True
|
||||
|
||||
except LDAPBindError:
|
||||
self.errorMessage = "Invalid LDAP credentials"
|
||||
|
||||
except Exception as e:
|
||||
self.errorMessage = str(e)
|
||||
|
||||
|
||||
class User(UserMixin):
|
||||
|
||||
def __init__(self, username):
|
||||
self.id = username
|
||||
165
v-2/model/Block.py
Normal file
165
v-2/model/Block.py
Normal file
@@ -0,0 +1,165 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json
|
||||
from flask import current_app
|
||||
|
||||
from datetime import datetime
|
||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||
|
||||
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
|
||||
from model.Log import LogData, LogHelper
|
||||
|
||||
import os
|
||||
import config
|
||||
import re
|
||||
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
|
||||
from model.ItemCRUD import ItemCRUD, itemCRUDMapping
|
||||
|
||||
|
||||
class Block:
|
||||
|
||||
isSuccess = False
|
||||
resultMessage = ""
|
||||
|
||||
def __init__(self):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Add Block
|
||||
# ----------------------------------------------------------
|
||||
def AddBlock(self, request):
|
||||
|
||||
block = ItemCRUD(itemType=ItemCRUDType.Block)
|
||||
|
||||
district_id = request.form.get('district_Id')
|
||||
block_name = request.form.get('block_Name', '').strip()
|
||||
|
||||
block.AddItem(request=request, parentid=district_id, childname=block_name, storedprocfetch="GetBlockByNameAndDistricts", storedprocadd="SaveBlock" )
|
||||
self.isSuccess = block.isSuccess
|
||||
self.resultMessage = block.resultMessage
|
||||
return
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Get All Blocks
|
||||
# ----------------------------------------------------------
|
||||
# def GetAllBlocks(self):
|
||||
|
||||
# block = ItemCRUD(itemType=ItemCRUDType.Block)
|
||||
# blocksdata = block.GetAllData(request=request, storedproc="GetAllBlock")
|
||||
# self.isSuccess = block.isSuccess
|
||||
# self.resultMessage = block.resultMessage
|
||||
# return blocksdata
|
||||
|
||||
def GetAllBlocks(self, request):
|
||||
|
||||
block = ItemCRUD(itemType=ItemCRUDType.Block)
|
||||
blocksdata = block.GetAllData(request=request, storedproc="GetAllBlock")
|
||||
|
||||
self.isSuccess = block.isSuccess
|
||||
self.resultMessage = block.resultMessage
|
||||
return blocksdata
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Check Block Exists
|
||||
# ----------------------------------------------------------
|
||||
|
||||
# def CheckBlock(self, request):
|
||||
# block = ItemCRUD(itemType=ItemCRUDType.Block)
|
||||
# block_name = request.json.get('block_Name', '').strip()
|
||||
# district_id = request.json.get('district_Id')
|
||||
# result = block.CheckItem(request=request, parentid=district_id, childname=block_name, storedprocfetch="GetBlockByNameAndDistrict")
|
||||
# self.isSuccess = block.isSuccess
|
||||
# self.resultMessage = block.resultMessage
|
||||
# return result
|
||||
def CheckBlock(self, request):
|
||||
block = ItemCRUD(itemType=ItemCRUDType.Block)
|
||||
data = request.get_json(silent=True) or request.form
|
||||
block_name = (data.get('block_Name') or '').strip()
|
||||
district_id = data.get('district_Id')
|
||||
|
||||
result = block.CheckItem(
|
||||
request=request,
|
||||
parentid=district_id,
|
||||
childname=block_name,
|
||||
storedprocfetch="GetBlockByNameAndDistrict"
|
||||
)
|
||||
self.isSuccess = block.isSuccess
|
||||
self.resultMessage = block.resultMessage
|
||||
return result
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Get Block By ID
|
||||
# ----------------------------------------------------------
|
||||
# def GetBlockByID(self, id):
|
||||
|
||||
# block = ItemCRUD(itemType=ItemCRUDType.Village)
|
||||
# blockdata = block.GetAllData(id=id,storedproc="GetBlockDataByID")
|
||||
# self.isSuccess = block.isSuccess
|
||||
# self.resultMessage = block.resultMessage
|
||||
# print("akash"+blockdata)
|
||||
# return blockdata
|
||||
|
||||
# def GetBlockByID(self,request,id):
|
||||
# block = ItemCRUD(itemType=ItemCRUDType.Block)
|
||||
# blockdata = block.GetDataByID(request=request,id=id,storedproc="GetBlockDataByID")
|
||||
# self.isSuccess = block.isSuccess
|
||||
# self.resultMessage = block.resultMessage
|
||||
# return blockdata
|
||||
def GetBlockByID(self, id):
|
||||
|
||||
block = ItemCRUD(itemType=ItemCRUDType.Block)
|
||||
|
||||
blockdata = block.GetDataByID(
|
||||
id=id,
|
||||
storedproc="GetBlockDataByID"
|
||||
)
|
||||
|
||||
self.isSuccess = block.isSuccess
|
||||
self.resultMessage = block.resultMessage
|
||||
|
||||
return blockdata
|
||||
# ----------------------------------------------------------
|
||||
# Update Block
|
||||
# ----------------------------------------------------------
|
||||
# def EditBlock(self, request, block_id):
|
||||
|
||||
# block = ItemCRUD(itemType=ItemCRUDType.Block)
|
||||
|
||||
# district_id = request.form.get('district_Id')
|
||||
# block_name = request.form.get('block_Name', '').strip()
|
||||
|
||||
# block.EditItem(request=request, childid=block_id, parentid=district_id, childname=block_name, storedprocadd="UpdateBlockById" )
|
||||
# self.isSuccess = block.isSuccess
|
||||
# self.resultMessage = block.resultMessage
|
||||
# return
|
||||
def EditBlock(self, request, block_id):
|
||||
|
||||
block = ItemCRUD(itemType=ItemCRUDType.Block)
|
||||
|
||||
district_id = request.form.get('district_Id')
|
||||
block_name = request.form.get('block_Name', '').strip()
|
||||
|
||||
block.EditItem(
|
||||
request=request,
|
||||
childid=block_id,
|
||||
parentid=district_id,
|
||||
childname=block_name,
|
||||
storedprocupdate="UpdateBlockById"
|
||||
)
|
||||
|
||||
self.isSuccess = block.isSuccess
|
||||
self.resultMessage = block.resultMessage
|
||||
return render_template('add_block.html')
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Delete Block
|
||||
# ---------------------------------------------------------
|
||||
def DeleteBlock(self,request, id):
|
||||
block = ItemCRUD(itemType=ItemCRUDType.Block)
|
||||
|
||||
block.DeleteItem(request=request,itemID=id, storedprocDelete="DeleteBlock")
|
||||
self.isSuccess = block.isSuccess
|
||||
self.resultMessage = block.resultMessage
|
||||
return
|
||||
72
v-2/model/ContractorInfo.py
Normal file
72
v-2/model/ContractorInfo.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
import config
|
||||
import openpyxl
|
||||
import os
|
||||
import re
|
||||
import ast
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class ContractorInfo:
|
||||
ID = ""
|
||||
contInfo = None
|
||||
def __init__(self, id):
|
||||
self.ID = id
|
||||
print(id)
|
||||
self.fetchData()
|
||||
|
||||
def fetchData(self):
|
||||
try:
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True, buffered=True)
|
||||
cursor.callproc('GetContractorInfoById', [self.ID])
|
||||
for result in cursor.stored_results():
|
||||
self.contInfo = result.fetchone()
|
||||
|
||||
print(self.contInfo,flush=True)
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
def fetchalldata(self):
|
||||
|
||||
try:
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True, buffered=True)
|
||||
print("here", flush=True)
|
||||
|
||||
|
||||
# ---------------- Hold Types ----------------
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.callproc('GetHoldTypesByContractor', [self.ID])
|
||||
|
||||
hold_types = []
|
||||
for result in cursor.stored_results():
|
||||
hold_types = result.fetchall()
|
||||
hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
|
||||
|
||||
# ---------------- Invoices ----------------
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.callproc('GetInvoicesByContractor', [self.ID])
|
||||
|
||||
invoices = []
|
||||
for result in cursor.stored_results():
|
||||
invoices = result.fetchall()
|
||||
|
||||
# Remove duplicate invoices
|
||||
invoice_ids_seen = set()
|
||||
unique_invoices = []
|
||||
for inv in invoices:
|
||||
if inv["Invoice_Id"] not in invoice_ids_seen:
|
||||
invoice_ids_seen.add(inv["Invoice_Id"])
|
||||
unique_invoices.append(inv)
|
||||
invoices = unique_invoices
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
100
v-2/model/District.py
Normal file
100
v-2/model/District.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json
|
||||
from flask import current_app
|
||||
|
||||
from datetime import datetime
|
||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||
|
||||
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
|
||||
from model.Log import LogData, LogHelper
|
||||
|
||||
import os
|
||||
import config
|
||||
import re
|
||||
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
|
||||
from model.ItemCRUD import ItemCRUD
|
||||
class District:
|
||||
|
||||
isSuccess = False
|
||||
resultMessage = ""
|
||||
|
||||
def __init__(self):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
|
||||
def EditDistrict(self, request, district_id):
|
||||
district = ItemCRUD(itemType=ItemCRUDType.District)
|
||||
|
||||
district_name = request.form['district_Name'].strip()
|
||||
state_id = request.form['state_Id']
|
||||
|
||||
district.EditItem(request=request, childid=district_id, parentid=state_id, childname=district_name,storedprocupdate="UpdateDistrict" )
|
||||
self.isSuccess = district.isSuccess
|
||||
self.resultMessage = district.resultMessage
|
||||
return
|
||||
|
||||
|
||||
def AddDistrict(self, request):
|
||||
|
||||
district = ItemCRUD(ItemCRUDType.District)
|
||||
|
||||
district_name = request.form['district_Name'].strip()
|
||||
state_id = request.form['state_Id']
|
||||
|
||||
district.AddItem(request=request, parentid=state_id, childname=district_name, storedprocfetch="GetDistrictByNameAndState", storedprocadd="SaveDistrict" )
|
||||
self.isSuccess = district.isSuccess
|
||||
self.resultMessage = district.resultMessage
|
||||
return
|
||||
|
||||
|
||||
|
||||
def GetAllDistricts(self, request):
|
||||
district = ItemCRUD(itemType=ItemCRUDType.District)
|
||||
districtsdata = district.GetAllData(request=request, storedproc="GetAllDistricts")
|
||||
self.isSuccess = district.isSuccess
|
||||
self.resultMessage = district.resultMessage
|
||||
return districtsdata
|
||||
|
||||
|
||||
def CheckDistrict(self, request):
|
||||
district = ItemCRUD(itemType=ItemCRUDType.District)
|
||||
district_name = request.json.get('district_Name', '').strip()
|
||||
state_id = request.json.get('state_Id', '')
|
||||
result = district.CheckItem(request=request, parentid=state_id, childname=district_name, storedprocfetch="GetDistrictByNameAndState")
|
||||
self.isSuccess = district.isSuccess
|
||||
self.resultMessage = district.resultMessage
|
||||
return result
|
||||
|
||||
|
||||
# def GetDistrictByID(self, request,district_id):
|
||||
# district = ItemCRUD(itemType=ItemCRUDType.District)
|
||||
# districtdata = district.GetAllData(id=district_id,storedproc="GetDistrictDataByID")
|
||||
# self.isSuccess = district.isSuccess
|
||||
# self.resultMessage = district.resultMessage
|
||||
# return districtdata
|
||||
def GetDistrictByID(self, request, district_id):
|
||||
district = ItemCRUD(itemType=ItemCRUDType.District)
|
||||
|
||||
districtdata = district.GetDataByID(
|
||||
id=district_id,
|
||||
storedproc="GetDistrictDataByID"
|
||||
)
|
||||
|
||||
if districtdata:
|
||||
self.isSuccess = True
|
||||
else:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = "District not found"
|
||||
|
||||
return districtdata
|
||||
|
||||
|
||||
#Delete District
|
||||
def DeleteDistrict(self, request, district_id):
|
||||
district = ItemCRUD(itemType=ItemCRUDType.District)
|
||||
district.DeleteItem(request=request,itemID=district_id,storedprocDelete="DeleteDistrict")
|
||||
self.isSuccess = district.isSuccess
|
||||
self.resultMessage = str(district.resultMessage)
|
||||
39
v-2/model/FolderAndFile.py
Normal file
39
v-2/model/FolderAndFile.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import os
|
||||
from flask import current_app
|
||||
|
||||
|
||||
class FolderAndFile:
|
||||
|
||||
# -----------------------------
|
||||
# BASE FOLDER METHODS
|
||||
# -----------------------------
|
||||
@staticmethod
|
||||
def get_download_folder():
|
||||
folder = os.path.join(current_app.root_path, "static", "downloads")
|
||||
|
||||
if not os.path.exists(folder):
|
||||
os.makedirs(folder)
|
||||
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
return folder
|
||||
|
||||
@staticmethod
|
||||
def get_upload_folder():
|
||||
folder = os.path.join(current_app.root_path, "static", "uploads")
|
||||
|
||||
if not os.path.exists(folder):
|
||||
os.makedirs(folder)
|
||||
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
return folder
|
||||
|
||||
# -----------------------------
|
||||
# FILE PATH METHODS
|
||||
# -----------------------------
|
||||
@staticmethod
|
||||
def get_download_path(filename):
|
||||
return os.path.join(FolderAndFile.get_download_folder(), filename)
|
||||
|
||||
@staticmethod
|
||||
def get_upload_path(filename):
|
||||
return os.path.join(FolderAndFile.get_upload_folder(), filename)
|
||||
55
v-2/model/GST.py
Normal file
55
v-2/model/GST.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import config
|
||||
|
||||
class GST:
|
||||
|
||||
@staticmethod
|
||||
def get_unreleased_gst():
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
try:
|
||||
# ----------- Invoices -----------
|
||||
cursor.callproc('GetAllInvoicesBasic')
|
||||
invoices = []
|
||||
for result in cursor.stored_results():
|
||||
invoices = result.fetchall()
|
||||
|
||||
|
||||
# ----------- GST Releases -----------
|
||||
cursor.callproc('GetAllGSTReleasesBasic')
|
||||
gst_releases = []
|
||||
for result in cursor.stored_results():
|
||||
gst_releases = result.fetchall()
|
||||
|
||||
gst_invoice_nos = {
|
||||
g['Invoice_No']
|
||||
for g in gst_releases
|
||||
if g['Invoice_No']
|
||||
}
|
||||
|
||||
gst_basic_amounts = {
|
||||
float(g['Basic_Amount'])
|
||||
for g in gst_releases
|
||||
if g['Basic_Amount'] is not None
|
||||
}
|
||||
|
||||
unreleased = []
|
||||
|
||||
for inv in invoices:
|
||||
|
||||
match_by_invoice = inv['Invoice_No'] in gst_invoice_nos
|
||||
|
||||
match_by_gst_amount = float(
|
||||
inv.get('GST_SD_Amount') or 0
|
||||
) in gst_basic_amounts
|
||||
|
||||
if not (match_by_invoice or match_by_gst_amount):
|
||||
unreleased.append(inv)
|
||||
|
||||
return unreleased
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
90
v-2/model/HoldTypes.py
Normal file
90
v-2/model/HoldTypes.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from flask import request
|
||||
from model.ItemCRUD import ItemCRUD
|
||||
from model.Utilities import ItemCRUDType
|
||||
|
||||
class HoldTypes:
|
||||
"""CRUD operations for Hold Types using ItemCRUD"""
|
||||
|
||||
def __init__(self):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
# ------------------- Add Hold Type -------------------
|
||||
def AddHoldType(self, request):
|
||||
hold = ItemCRUD(itemType=ItemCRUDType.HoldType)
|
||||
hold_name = request.form.get('hold_type', '').strip()
|
||||
|
||||
hold.AddItem(
|
||||
request=request,
|
||||
parentid=None,
|
||||
childname=hold_name,
|
||||
storedprocfetch="CheckHoldTypeExists",
|
||||
storedprocadd="SaveHoldType"
|
||||
)
|
||||
|
||||
self.isSuccess = hold.isSuccess
|
||||
self.resultMessage = hold.resultMessage
|
||||
|
||||
# ------------------- Get All Hold Types -------------------
|
||||
def GetAllHoldTypes(self, request=None):
|
||||
hold = ItemCRUD(itemType=ItemCRUDType.HoldType)
|
||||
rows = hold.GetAllData(request=request, storedproc="GetAllHoldTypes")
|
||||
|
||||
self.isSuccess = hold.isSuccess
|
||||
self.resultMessage = hold.resultMessage
|
||||
|
||||
# ✅ Convert tuple → dictionary
|
||||
data = []
|
||||
for row in rows:
|
||||
data.append({
|
||||
"hold_type_id": row[0],
|
||||
"hold_type": row[1]
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
# ------------------- Get Hold Type By ID -------------------
|
||||
def GetHoldTypeByID(self, hold_type_id):
|
||||
hold = ItemCRUD(itemType=ItemCRUDType.HoldType)
|
||||
row = hold.GetDataByID(hold_type_id, storedproc="GetHoldTypesById")
|
||||
|
||||
self.isSuccess = hold.isSuccess
|
||||
self.resultMessage = hold.resultMessage
|
||||
|
||||
# ✅ Convert tuple → dictionary
|
||||
if row:
|
||||
return {
|
||||
"hold_type_id": row[0],
|
||||
"hold_type": row[1]
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
# ------------------- Update Hold Type -------------------
|
||||
def EditHoldType(self, request, hold_type_id):
|
||||
hold = ItemCRUD(itemType=ItemCRUDType.HoldType)
|
||||
hold_name = request.form.get('hold_type', '').strip()
|
||||
|
||||
hold.EditItem(
|
||||
request=request,
|
||||
childid=hold_type_id,
|
||||
parentid=None,
|
||||
childname=hold_name,
|
||||
storedprocupdate="UpdateHoldTypeById"
|
||||
)
|
||||
|
||||
self.isSuccess = hold.isSuccess
|
||||
self.resultMessage = hold.resultMessage
|
||||
|
||||
# ------------------- Delete Hold Type -------------------
|
||||
def DeleteHoldType(self, request, hold_type_id):
|
||||
hold = ItemCRUD(itemType=ItemCRUDType.HoldType)
|
||||
|
||||
hold.DeleteItem(
|
||||
request=request,
|
||||
itemID=hold_type_id,
|
||||
storedprocDelete="DeleteHoldType"
|
||||
)
|
||||
|
||||
self.isSuccess = hold.isSuccess
|
||||
self.resultMessage = hold.resultMessage
|
||||
379
v-2/model/Invoice.py
Normal file
379
v-2/model/Invoice.py
Normal file
@@ -0,0 +1,379 @@
|
||||
import config
|
||||
import mysql.connector
|
||||
|
||||
# ------------------- Helper -------------------
|
||||
def clear_results(cursor):
|
||||
for r in cursor.stored_results():
|
||||
r.fetchall()
|
||||
|
||||
|
||||
# ------------------- Get Village Id -------------------
|
||||
def get_village_id(village_name):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.callproc("GetVillageIdByName", (village_name,))
|
||||
village_result = None
|
||||
|
||||
for rs in cursor.stored_results():
|
||||
village_result = rs.fetchone()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return village_result
|
||||
|
||||
|
||||
# ------------------- Insert Invoice -------------------
|
||||
def insert_invoice(data, village_id):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
try:
|
||||
# 1. Insert Invoice
|
||||
cursor.callproc('InsertInvoice', [
|
||||
data.get('pmc_no'),
|
||||
village_id,
|
||||
data.get('work_type'),
|
||||
data.get('invoice_details'),
|
||||
data.get('invoice_date'),
|
||||
data.get('invoice_no'),
|
||||
float(data.get('basic_amount') or 0),
|
||||
float(data.get('debit_amount') or 0),
|
||||
float(data.get('after_debit_amount') or 0),
|
||||
float(data.get('amount') or 0),
|
||||
float(data.get('gst_amount') or 0),
|
||||
float(data.get('tds_amount') or 0),
|
||||
float(data.get('sd_amount') or 0),
|
||||
float(data.get('on_commission') or 0),
|
||||
float(data.get('hydro_testing') or 0),
|
||||
float(data.get('gst_sd_amount') or 0),
|
||||
float(data.get('final_amount') or 0)
|
||||
])
|
||||
|
||||
invoice_id = None
|
||||
for result in cursor.stored_results():
|
||||
row = result.fetchone()
|
||||
if row:
|
||||
invoice_id = row.get('invoice_id')
|
||||
|
||||
if not invoice_id:
|
||||
raise Exception("Invoice ID not returned")
|
||||
|
||||
# 2. Insert Inpayment
|
||||
cursor.callproc('InsertInpayment', [
|
||||
data.get('pmc_no'),
|
||||
village_id,
|
||||
data.get('work_type'),
|
||||
data.get('invoice_details'),
|
||||
data.get('invoice_date'),
|
||||
data.get('invoice_no'),
|
||||
float(data.get('basic_amount') or 0),
|
||||
float(data.get('debit_amount') or 0),
|
||||
float(data.get('after_debit_amount') or 0),
|
||||
float(data.get('amount') or 0),
|
||||
float(data.get('gst_amount') or 0),
|
||||
float(data.get('tds_amount') or 0),
|
||||
float(data.get('sd_amount') or 0),
|
||||
float(data.get('on_commission') or 0),
|
||||
float(data.get('hydro_testing') or 0),
|
||||
float(data.get('gst_sd_amount') or 0),
|
||||
float(data.get('final_amount') or 0),
|
||||
data.get('subcontractor_id')
|
||||
])
|
||||
clear_results(cursor)
|
||||
|
||||
connection.commit()
|
||||
return invoice_id
|
||||
|
||||
except Exception as e:
|
||||
connection.rollback()
|
||||
raise e
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
# ------------------- Assign Subcontractor -------------------
|
||||
def assign_subcontractor(data, village_id):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
try:
|
||||
cursor.callproc('AssignSubcontractor', [
|
||||
data.get('pmc_no'),
|
||||
data.get('subcontractor_id'),
|
||||
village_id
|
||||
])
|
||||
clear_results(cursor)
|
||||
|
||||
connection.commit()
|
||||
|
||||
except Exception as e:
|
||||
connection.rollback()
|
||||
raise e
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
# ------------------- Insert Hold Types -------------------
|
||||
def insert_hold_types(data, invoice_id):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
try:
|
||||
hold_types = data.getlist('hold_type[]')
|
||||
hold_amounts = data.getlist('hold_amount[]')
|
||||
|
||||
for hold_type, hold_amount in zip(hold_types, hold_amounts):
|
||||
if not hold_type:
|
||||
continue
|
||||
|
||||
cursor.callproc('GetHoldTypeIdByName', [hold_type])
|
||||
hold_type_result = None
|
||||
|
||||
for result in cursor.stored_results():
|
||||
hold_type_result = result.fetchone()
|
||||
|
||||
if not hold_type_result:
|
||||
cursor.callproc('InsertHoldType', [hold_type, 0])
|
||||
cursor.execute("SELECT @_InsertHoldType_1")
|
||||
hold_type_id = cursor.fetchone()[0]
|
||||
else:
|
||||
hold_type_id = hold_type_result['hold_type_id']
|
||||
|
||||
hold_amount = float(hold_amount or 0)
|
||||
|
||||
cursor.callproc('InsertInvoiceSubcontractorHold', [
|
||||
data.get('subcontractor_id'),
|
||||
invoice_id,
|
||||
hold_type_id,
|
||||
hold_amount
|
||||
])
|
||||
clear_results(cursor)
|
||||
|
||||
connection.commit()
|
||||
|
||||
except Exception as e:
|
||||
connection.rollback()
|
||||
raise e
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
# ------------------- Get All Invoices -------------------
|
||||
def get_all_invoice_details():
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.callproc('GetAllInvoiceDetails')
|
||||
invoices = []
|
||||
|
||||
for result in cursor.stored_results():
|
||||
invoices = result.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return invoices
|
||||
|
||||
|
||||
# ------------------- Get All Villages -------------------
|
||||
def get_all_villages():
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.callproc("GetAllVillages")
|
||||
villages = []
|
||||
|
||||
for result in cursor.stored_results():
|
||||
villages = result.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return villages
|
||||
|
||||
|
||||
# ------------------- Search Contractors -------------------
|
||||
def search_contractors(sub_query):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.callproc('SearchContractorsByName', [sub_query])
|
||||
results = []
|
||||
|
||||
for result in cursor.stored_results():
|
||||
results = result.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return results
|
||||
|
||||
|
||||
# ------------------- Get All Hold Types -------------------
|
||||
def get_all_hold_types():
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.callproc("GetAllHoldTypes")
|
||||
hold_types = []
|
||||
|
||||
for result in cursor.stored_results():
|
||||
hold_types = result.fetchall()
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return hold_types
|
||||
|
||||
|
||||
# ------------------- Get Invoice By Id -------------------
|
||||
def get_invoice_by_id(invoice_id):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.callproc('GetInvoiceDetailsById', [invoice_id])
|
||||
invoice = None
|
||||
|
||||
for result in cursor.stored_results():
|
||||
invoice = result.fetchone()
|
||||
|
||||
cursor.callproc('GetHoldAmountsByInvoiceId', [invoice_id])
|
||||
hold_amounts = []
|
||||
|
||||
for result in cursor.stored_results():
|
||||
hold_amounts = result.fetchall()
|
||||
|
||||
if invoice:
|
||||
invoice["hold_amounts"] = hold_amounts
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return invoice
|
||||
|
||||
|
||||
# ------------------- Update Invoice -------------------
|
||||
def update_invoice(data, invoice_id):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
try:
|
||||
cursor.callproc("GetVillageIdByName", (data.get('village'),))
|
||||
village = None
|
||||
|
||||
for rs in cursor.stored_results():
|
||||
village = rs.fetchone()
|
||||
|
||||
village_id = village['Village_Id']
|
||||
|
||||
numeric = [
|
||||
float(data.get('basic_amount') or 0),
|
||||
float(data.get('debit_amount') or 0),
|
||||
float(data.get('after_debit_amount') or 0),
|
||||
float(data.get('amount') or 0),
|
||||
float(data.get('gst_amount') or 0),
|
||||
float(data.get('tds_amount') or 0),
|
||||
float(data.get('sd_amount') or 0),
|
||||
float(data.get('on_commission') or 0),
|
||||
float(data.get('hydro_testing') or 0),
|
||||
float(data.get('gst_sd_amount') or 0),
|
||||
float(data.get('final_amount') or 0),
|
||||
]
|
||||
|
||||
cursor.callproc('UpdateInvoice', [
|
||||
data.get('pmc_no'),
|
||||
village_id,
|
||||
data.get('work_type'),
|
||||
data.get('invoice_details'),
|
||||
data.get('invoice_date'),
|
||||
data.get('invoice_no'),
|
||||
*numeric,
|
||||
invoice_id
|
||||
])
|
||||
clear_results(cursor)
|
||||
|
||||
connection.commit()
|
||||
|
||||
except Exception as e:
|
||||
connection.rollback()
|
||||
raise e
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
# ------------------- Update Inpayment -------------------
|
||||
def update_inpayment(data):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
try:
|
||||
numeric = [
|
||||
float(data.get('basic_amount') or 0),
|
||||
float(data.get('debit_amount') or 0),
|
||||
float(data.get('after_debit_amount') or 0),
|
||||
float(data.get('amount') or 0),
|
||||
float(data.get('gst_amount') or 0),
|
||||
float(data.get('tds_amount') or 0),
|
||||
float(data.get('sd_amount') or 0),
|
||||
float(data.get('on_commission') or 0),
|
||||
float(data.get('hydro_testing') or 0),
|
||||
float(data.get('gst_sd_amount') or 0),
|
||||
float(data.get('final_amount') or 0),
|
||||
]
|
||||
|
||||
cursor.callproc('UpdateInpayment', [
|
||||
data.get('work_type'),
|
||||
data.get('invoice_details'),
|
||||
data.get('invoice_date'),
|
||||
*numeric,
|
||||
data.get('pmc_no'),
|
||||
data.get('invoice_no')
|
||||
])
|
||||
clear_results(cursor)
|
||||
|
||||
connection.commit()
|
||||
|
||||
except Exception as e:
|
||||
connection.rollback()
|
||||
raise e
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
# ------------------- Delete Invoice -------------------
|
||||
def delete_invoice_data(invoice_id, user_id):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
try:
|
||||
cursor.callproc('GetInvoicePMCById', [invoice_id])
|
||||
|
||||
record = {}
|
||||
for result in cursor.stored_results():
|
||||
record = result.fetchone() or {}
|
||||
if not record:
|
||||
raise Exception("Invoice not found")
|
||||
|
||||
cursor.callproc("DeleteInvoice", (invoice_id,))
|
||||
clear_results(cursor)
|
||||
|
||||
cursor.callproc(
|
||||
'DeleteInpaymentByPMCInvoice',
|
||||
[record['PMC_No'], record['invoice_no']]
|
||||
)
|
||||
|
||||
connection.commit()
|
||||
|
||||
except Exception as e:
|
||||
connection.rollback()
|
||||
raise e
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
359
v-2/model/ItemCRUD.py
Normal file
359
v-2/model/ItemCRUD.py
Normal file
@@ -0,0 +1,359 @@
|
||||
from flask_login import current_user
|
||||
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
|
||||
from model.Log import LogHelper
|
||||
|
||||
import config
|
||||
import re
|
||||
import mysql.connector
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Mapping Class
|
||||
# ----------------------------------------------------------
|
||||
class itemCRUDMapping:
|
||||
|
||||
def __init__(self, itemType):
|
||||
if itemType is ItemCRUDType.Village:
|
||||
self.name = "Village"
|
||||
elif itemType is ItemCRUDType.Block:
|
||||
self.name = "Block"
|
||||
elif itemType is ItemCRUDType.State:
|
||||
self.name = "State"
|
||||
elif itemType is ItemCRUDType.HoldType:
|
||||
self.name = "Hold Type"
|
||||
elif itemType is ItemCRUDType.Subcontractor:
|
||||
self.name = "Subcontractor"
|
||||
else:
|
||||
self.name = "Item"
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Generic CRUD Class
|
||||
# ----------------------------------------------------------
|
||||
class ItemCRUD:
|
||||
|
||||
def __init__(self, itemType):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
self.itemCRUDType = itemType
|
||||
self.itemCRUDMapping = itemCRUDMapping(itemType)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# DELETE
|
||||
# ----------------------------------------------------------
|
||||
def DeleteItem(self, request, itemID, storedprocDelete):
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
LogHelper.log_action(
|
||||
f"Delete {self.itemCRUDMapping.name}",
|
||||
f"User {current_user.id} deleted {self.itemCRUDMapping.name} '{itemID}'"
|
||||
)
|
||||
|
||||
try:
|
||||
cursor.callproc(storedprocDelete, (itemID,))
|
||||
connection.commit()
|
||||
|
||||
self.isSuccess = True
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.delete_success(self.itemCRUDMapping.name), 200
|
||||
)
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error deleting {self.itemCRUDMapping.name}: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.delete_failure(self.itemCRUDMapping.name), 500
|
||||
)
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# ADD
|
||||
# ----------------------------------------------------------
|
||||
def AddItem(self, request, parentid=None, childname=None, storedprocfetch=None, storedprocadd=None, data=None):
|
||||
|
||||
connection = config.get_db_connection()
|
||||
if not connection:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.db_connection_failure(), 500
|
||||
)
|
||||
return
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
LogHelper.log_action(
|
||||
f"Add {self.itemCRUDMapping.name}",
|
||||
f"User {current_user.id} adding '{childname if childname else (data.get('Contractor_Name') if data else '')}'"
|
||||
)
|
||||
|
||||
try:
|
||||
# ======================================================
|
||||
# SUBCONTRACTOR (MULTI-FIELD)
|
||||
# ======================================================
|
||||
if data:
|
||||
|
||||
# Duplicate check
|
||||
cursor.callproc(storedprocfetch, (data['Contractor_Name'],))
|
||||
|
||||
existing_item = None
|
||||
for rs in cursor.stored_results():
|
||||
existing_item = rs.fetchone()
|
||||
|
||||
if existing_item:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.already_exists(self.itemCRUDMapping.name), 409
|
||||
)
|
||||
return
|
||||
|
||||
# Insert
|
||||
cursor.callproc(storedprocadd, (
|
||||
data['Contractor_Name'],
|
||||
data['Address'],
|
||||
data['Mobile_No'],
|
||||
data['PAN_No'],
|
||||
data['Email'],
|
||||
data['Gender'],
|
||||
data['GST_Registration_Type'],
|
||||
data['GST_No'],
|
||||
data['Contractor_password']
|
||||
))
|
||||
|
||||
connection.commit()
|
||||
|
||||
self.isSuccess = True
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.add_success(self.itemCRUDMapping.name), 200
|
||||
)
|
||||
return
|
||||
|
||||
# ======================================================
|
||||
# NORMAL (Village / Block / State)
|
||||
# ======================================================
|
||||
if not re.match(RegEx.patternAlphabetOnly, childname):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400
|
||||
)
|
||||
return
|
||||
|
||||
# Duplicate check
|
||||
if parentid is None:
|
||||
cursor.callproc(storedprocfetch, (childname,))
|
||||
else:
|
||||
cursor.callproc(storedprocfetch, (childname, parentid))
|
||||
|
||||
existing_item = None
|
||||
for rs in cursor.stored_results():
|
||||
existing_item = rs.fetchone()
|
||||
|
||||
if existing_item:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.already_exists(self.itemCRUDMapping.name), 409
|
||||
)
|
||||
return
|
||||
|
||||
# Insert
|
||||
if parentid is None:
|
||||
cursor.callproc(storedprocadd, (childname,))
|
||||
else:
|
||||
cursor.callproc(storedprocadd, (childname, parentid))
|
||||
|
||||
connection.commit()
|
||||
|
||||
self.isSuccess = True
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
|
||||
ResponseHandler.add_success(self.itemCRUDMapping.name), 200
|
||||
)
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Database Error: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.add_failure(self.itemCRUDMapping.name), 500
|
||||
)
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# EDIT
|
||||
# ----------------------------------------------------------
|
||||
def EditItem(self, request, childid, parentid=None, childname=None, storedprocupdate=None, data=None):
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
LogHelper.log_action(
|
||||
f"Edit {self.itemCRUDMapping.name}",
|
||||
f"User {current_user.id} edited '{childid}'"
|
||||
)
|
||||
|
||||
try:
|
||||
# ======================================================
|
||||
# SUBCONTRACTOR (MULTI-FIELD)
|
||||
# ======================================================
|
||||
if data:
|
||||
cursor.callproc(storedprocupdate, (
|
||||
childid,
|
||||
data['Contractor_Name'],
|
||||
data['Address'],
|
||||
data['Mobile_No'],
|
||||
data['PAN_No'],
|
||||
data['Email'],
|
||||
data['Gender'],
|
||||
data['GST_Registration_Type'],
|
||||
data['GST_No'],
|
||||
data['Contractor_password']
|
||||
))
|
||||
|
||||
connection.commit()
|
||||
|
||||
self.isSuccess = True
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.update_success(self.itemCRUDMapping.name), 200
|
||||
)
|
||||
return
|
||||
|
||||
# ======================================================
|
||||
# NORMAL
|
||||
# ======================================================
|
||||
if not re.match(RegEx.patternAlphabetOnly, childname):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ResponseHandler.update_failure(self.itemCRUDMapping.name)['message']
|
||||
return
|
||||
|
||||
if parentid is None:
|
||||
cursor.callproc(storedprocupdate, (childid, childname))
|
||||
else:
|
||||
cursor.callproc(storedprocupdate, (childid, parentid, childname))
|
||||
|
||||
connection.commit()
|
||||
|
||||
self.isSuccess = True
|
||||
self.resultMessage = ResponseHandler.update_success(self.itemCRUDMapping.name)['message']
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error updating {self.itemCRUDMapping.name}: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.update_failure(self.itemCRUDMapping.name), 500
|
||||
)
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# GET ALL
|
||||
# ----------------------------------------------------------
|
||||
def GetAllData(self, request, storedproc):
|
||||
|
||||
data = []
|
||||
connection = config.get_db_connection()
|
||||
|
||||
if not connection:
|
||||
return []
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc(storedproc)
|
||||
|
||||
for result in cursor.stored_results():
|
||||
data = result.fetchall()
|
||||
|
||||
self.isSuccess = True
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching {self.itemCRUDMapping.name}: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500
|
||||
)
|
||||
return []
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return data
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# GET BY ID
|
||||
# ----------------------------------------------------------
|
||||
def GetDataByID(self, id, storedproc):
|
||||
|
||||
data = None
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc(storedproc, (id,))
|
||||
|
||||
for rs in cursor.stored_results():
|
||||
data = rs.fetchone()
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching {self.itemCRUDMapping.name}: {e}")
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return data
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# CHECK ITEM
|
||||
# ----------------------------------------------------------
|
||||
def CheckItem(self, request, parentid, childname, storedprocfetch):
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
LogHelper.log_action(
|
||||
f"Check {self.itemCRUDMapping.name}",
|
||||
f"User {current_user.id} checked '{childname}'"
|
||||
)
|
||||
|
||||
if not re.match(RegEx.patternAlphabetOnly, childname):
|
||||
return HtmlHelper.json_response(
|
||||
ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400
|
||||
)
|
||||
|
||||
try:
|
||||
if parentid is None:
|
||||
cursor.callproc(storedprocfetch, (childname,))
|
||||
else:
|
||||
cursor.callproc(storedprocfetch, (childname, parentid))
|
||||
|
||||
existing_item = None
|
||||
for rs in cursor.stored_results():
|
||||
existing_item = rs.fetchone()
|
||||
|
||||
if existing_item:
|
||||
return HtmlHelper.json_response(
|
||||
ResponseHandler.already_exists(self.itemCRUDMapping.name), 409
|
||||
)
|
||||
|
||||
return HtmlHelper.json_response(
|
||||
ResponseHandler.is_available(self.itemCRUDMapping.name), 200
|
||||
)
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error checking {self.itemCRUDMapping.name}: {e}")
|
||||
return HtmlHelper.json_response(
|
||||
ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500
|
||||
)
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
87
v-2/model/Log.py
Normal file
87
v-2/model/Log.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json
|
||||
from flask import current_app
|
||||
|
||||
from datetime import datetime
|
||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||
|
||||
import os
|
||||
|
||||
class LogHelper:
|
||||
@staticmethod
|
||||
def log_action(action, details=""):
|
||||
"""Log user actions with timestamp, user, action, and details."""
|
||||
logData = LogData()
|
||||
logData.WriteLog(action, details="")
|
||||
|
||||
class LogData:
|
||||
|
||||
filepath = ""
|
||||
timestamp = None
|
||||
|
||||
def __init__(self):
|
||||
self.filepath = os.path.join(current_app.root_path, 'activity.log')
|
||||
self.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
if hasattr(current_user, "cn") and current_user.cn:
|
||||
self.user = current_user.cn
|
||||
elif hasattr(current_user, "username") and current_user.username:
|
||||
self.user = current_user.username
|
||||
elif hasattr(current_user, "sAMAccountName") and current_user.sAMAccountName:
|
||||
self.user = current_user.sAMAccountName
|
||||
else:
|
||||
self.user = "Unknown"
|
||||
|
||||
def WriteLog(self, action, details=""):
|
||||
"""Log user actions with timestamp, user, action, and details."""
|
||||
|
||||
with open(self.filepath, "a", encoding="utf-8") as f:
|
||||
f.write(
|
||||
f"Timestamp: {self.timestamp} | "
|
||||
f"User: {self.user} | "
|
||||
f"Action: {action} | "
|
||||
f"Details: {details}\n"
|
||||
)
|
||||
|
||||
|
||||
def GetActivitiesLog(self):
|
||||
logs = []
|
||||
|
||||
if os.path.exists(self.filepath):
|
||||
with open(self.filepath, 'r') as f:
|
||||
for line in f:
|
||||
parts = line.strip().split(" | ")
|
||||
if len(parts) == 4:
|
||||
logs.append({
|
||||
"timestamp": parts[0].replace("Timestamp:", "").strip(),
|
||||
"user": parts[1].replace("User:", "").strip(),
|
||||
"action": parts[2].replace("Action:", "").strip(),
|
||||
"details": parts[3].replace("Details:", "").strip()
|
||||
})
|
||||
return logs
|
||||
|
||||
def GetFilteredActivitiesLog(self, startDate, endDate, userName):
|
||||
|
||||
filtered_logs = self.GetActivitiesLog()
|
||||
|
||||
# Date filter
|
||||
if startDate or endDate:
|
||||
try:
|
||||
start_dt = datetime.strptime(startDate, "%Y-%m-%d") if startDate else datetime.min
|
||||
end_dt = datetime.strptime(endDate, "%Y-%m-%d") if endDate else datetime.max
|
||||
|
||||
|
||||
filtered_logs = [
|
||||
log for log in filtered_logs
|
||||
if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt
|
||||
]
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print("Date filter error:", e)
|
||||
#Why catching all exceptions? Need to handle specific exceptions
|
||||
|
||||
# Username filter
|
||||
if userName:
|
||||
filtered_logs = [log for log in filtered_logs if userName.lower() in log["user"].lower()]
|
||||
|
||||
return filtered_logs
|
||||
456
v-2/model/PmcReport.py
Normal file
456
v-2/model/PmcReport.py
Normal file
@@ -0,0 +1,456 @@
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font, PatternFill
|
||||
import config
|
||||
from flask_login import current_user
|
||||
from model.Log import LogHelper
|
||||
|
||||
from model.Report import ReportHelper
|
||||
from model.FolderAndFile import FolderAndFile
|
||||
|
||||
class PmcReport:
|
||||
|
||||
@staticmethod
|
||||
def get_pmc_report(pmc_no):
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True, buffered=True)
|
||||
|
||||
try:
|
||||
|
||||
# cursor.callproc("GetContractorInfoByPmcNo", (pmc_no,))
|
||||
# pmc_info = next(cursor.stored_results()).fetchone()
|
||||
pmc_info = ReportHelper.execute_sp(cursor, 'GetContractorInfoByPmcNo', [pmc_no], True)
|
||||
|
||||
if not pmc_info:
|
||||
return None
|
||||
|
||||
cursor.callproc("Get_pmc_hold_types", (pmc_no, pmc_info["Contractor_Id"]))
|
||||
hold_types = next(cursor.stored_results()).fetchall()
|
||||
|
||||
# Extract hold_type_ids
|
||||
hold_type_ids = [ht['hold_type_id'] for ht in hold_types]
|
||||
|
||||
invoices = []
|
||||
hold_amount_total = 0
|
||||
if hold_type_ids:
|
||||
hold_type_ids_str = ",".join(map(str, hold_type_ids))
|
||||
cursor.callproc(
|
||||
'GetInvoices_WithHold',
|
||||
[pmc_no, pmc_info["Contractor_Id"], hold_type_ids_str]
|
||||
)
|
||||
else:
|
||||
cursor.callproc(
|
||||
'GetInvoices_NoHold',
|
||||
[pmc_no, pmc_info["Contractor_Id"]]
|
||||
)
|
||||
for result in cursor.stored_results():
|
||||
invoices = result.fetchall()
|
||||
|
||||
if hold_type_ids:
|
||||
hold_amount_total = sum(row.get('hold_amount', 0) or 0 for row in invoices)
|
||||
|
||||
total_invo_final = sum(row.get('Final_Amount', 0) or 0 for row in invoices)
|
||||
|
||||
|
||||
# GST RELEASE
|
||||
# cursor.callproc('GetGSTReleaseByPMC', [pmc_no])
|
||||
# gst_rel = []
|
||||
# for result in cursor.stored_results():
|
||||
# gst_rel = result.fetchall()
|
||||
|
||||
gst_rel = ReportHelper.execute_sp(cursor, 'GetGSTReleaseByPMC', [pmc_no])
|
||||
|
||||
total_gst_basic = sum(row.get('basic_amount', 0) or 0 for row in gst_rel)
|
||||
total_gst_final = sum(row.get('final_amount', 0) or 0 for row in gst_rel)
|
||||
|
||||
# ---------------- HOLD RELEASE ----------------
|
||||
# cursor.callproc('GetHoldReleaseByPMC', [pmc_no])
|
||||
# hold_release = []
|
||||
# for result in cursor.stored_results():
|
||||
# hold_release = result.fetchall()
|
||||
|
||||
hold_release = ReportHelper.execute_sp(cursor, 'GetHoldReleaseByPMC', [pmc_no])
|
||||
|
||||
# ---------------- CREDIT NOTE ----------------
|
||||
# cursor.callproc('GetCreditNoteByPMC', [pmc_no])
|
||||
# credit_note = []
|
||||
# for result in cursor.stored_results():
|
||||
# credit_note = result.fetchall()
|
||||
|
||||
credit_note = ReportHelper.execute_sp(cursor, 'GetCreditNoteByPMC', [pmc_no])
|
||||
|
||||
payments = ReportHelper.execute_sp(cursor, 'GetPaymentsByPMC', [pmc_no])
|
||||
|
||||
|
||||
# ---------------- PAYMENTS ----------------
|
||||
# cursor.callproc('GetPaymentsByPMC', [pmc_no])
|
||||
# payments = []
|
||||
# for result in cursor.stored_results():
|
||||
# payments = result.fetchall()
|
||||
|
||||
|
||||
|
||||
total_pay_amount = sum(row.get('Payment_Amount', 0) or 0 for row in payments)
|
||||
total_pay_total = sum(row.get('Total_amount', 0) or 0 for row in payments)
|
||||
|
||||
totals = {
|
||||
"sum_invo_basic_amt": sum(row.get('Basic_Amount', 0) or 0 for row in invoices),
|
||||
"sum_invo_debit_amt": sum(row.get('Debit_Amount', 0) or 0 for row in invoices),
|
||||
"sum_invo_after_debit_amt": sum(row.get('After_Debit_Amount', 0) or 0 for row in invoices),
|
||||
"sum_invo_amt": sum(row.get('Amount', 0) or 0 for row in invoices),
|
||||
"sum_invo_gst_amt": sum(row.get('GST_Amount', 0) or 0 for row in invoices),
|
||||
"sum_invo_tds_amt": sum(row.get('TDS_Amount', 0) or 0 for row in invoices),
|
||||
"sum_invo_ds_amt": sum(row.get('SD_Amount', 0) or 0 for row in invoices),
|
||||
"sum_invo_on_commission": sum(row.get('On_Commission', 0) or 0 for row in invoices),
|
||||
"sum_invo_hydro_test": sum(row.get('Hydro_Testing', 0) or 0 for row in invoices),
|
||||
"sum_invo_gst_sd_amt": sum(row.get('GST_SD_Amount', 0) or 0 for row in invoices),
|
||||
"sum_invo_final_amt": total_invo_final,
|
||||
"sum_invo_hold_amt": hold_amount_total,
|
||||
"sum_gst_basic_amt": total_gst_basic,
|
||||
"sum_gst_final_amt": total_gst_final,
|
||||
"sum_pay_payment_amt": total_pay_amount,
|
||||
"sum_pay_tds_payment_amt": sum(row.get('TDS_Payment_Amount', 0) or 0 for row in payments),
|
||||
"sum_pay_total_amt": total_pay_total
|
||||
}
|
||||
|
||||
return {
|
||||
"info": pmc_info,
|
||||
"invoices": invoices,
|
||||
"hold_types": hold_types,
|
||||
"gst_rel": gst_rel,
|
||||
"payments": payments,
|
||||
"credit_note": credit_note,
|
||||
"hold_release": hold_release,
|
||||
"total": totals
|
||||
}
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def download_pmc_report(pmc_no):
|
||||
|
||||
connection = config.get_db_connection()
|
||||
if not connection:
|
||||
return None
|
||||
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
try:
|
||||
# filename
|
||||
filename = f"PMC_Report_{pmc_no}.xlsx"
|
||||
|
||||
output_folder = FolderAndFile.get_download_folder()
|
||||
output_file = FolderAndFile.get_download_path(filename)
|
||||
|
||||
# ================= DATA FETCH =================
|
||||
|
||||
contractor_info = ReportHelper.execute_sp(cursor, 'GetContractorDetailsByPMC', [pmc_no], "one")
|
||||
|
||||
if not contractor_info:
|
||||
return None
|
||||
|
||||
hold_types = ReportHelper.execute_sp(cursor, 'GetHoldTypesByContractor', [contractor_info["Contractor_Id"]])
|
||||
|
||||
hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
|
||||
|
||||
invoices = ReportHelper.execute_sp(cursor, 'GetInvoicesAndGstReleaseByPmcNo', [pmc_no])
|
||||
|
||||
credit_notes = ReportHelper.execute_sp(cursor, 'GetCreditNoteByContractor', [contractor_info["Contractor_Id"]])
|
||||
|
||||
hold_amounts = ReportHelper.execute_sp(cursor, 'GetHoldAmountsByContractor', [contractor_info["Contractor_Id"]])
|
||||
|
||||
all_payments = ReportHelper.execute_sp(cursor, 'GetAllPaymentsByPMC', [pmc_no])
|
||||
|
||||
gst_releases = ReportHelper.execute_sp(cursor, 'GetGSTReleaseDetailsByPMC', [pmc_no])
|
||||
|
||||
# ================= DATA MAPPING =================
|
||||
|
||||
hold_data = {}
|
||||
for h in hold_amounts:
|
||||
hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
|
||||
|
||||
payments_map = {}
|
||||
for pay in all_payments:
|
||||
if pay['invoice_no']:
|
||||
payments_map.setdefault(pay['invoice_no'], []).append(pay)
|
||||
|
||||
# ================= LOG =================
|
||||
LogHelper.log_action(
|
||||
"Download PMC Report",
|
||||
f"User {current_user.id} Download PMC Report '{pmc_no}'"
|
||||
)
|
||||
|
||||
# ================= EXCEL =================
|
||||
workbook = openpyxl.Workbook()
|
||||
sheet = workbook.active
|
||||
sheet.title = "PMC Report"
|
||||
|
||||
# HEADER INFO
|
||||
sheet.append(["", "", "Laxmi Civil Engineering Services PVT. LTD."])
|
||||
sheet.append(["Contractor Name", contractor_info["Contractor_Name"]])
|
||||
sheet.append(["State", contractor_info["State_Name"]])
|
||||
sheet.append(["District", contractor_info["District_Name"]])
|
||||
sheet.append(["Block", contractor_info["Block_Name"]])
|
||||
sheet.append([])
|
||||
|
||||
base_headers = [
|
||||
"PMC No","Village","Work Type","Invoice Details","Invoice Date","Invoice No",
|
||||
"Basic Amount","Debit","After Debit Amount","GST","Amount","TDS",
|
||||
"SD","On Commission","Hydro Testing","GST SD Amount"
|
||||
]
|
||||
|
||||
hold_headers = [ht['hold_type'] for ht in hold_types]
|
||||
|
||||
payment_headers = [
|
||||
"Final Amount","Payment Amount","TDS Payment","Total Paid","UTR"
|
||||
]
|
||||
|
||||
headers = base_headers + hold_headers + payment_headers
|
||||
sheet.append(headers)
|
||||
|
||||
# STYLE
|
||||
for cell in sheet[sheet.max_row]:
|
||||
cell.font = Font(bold=True)
|
||||
|
||||
# DATA
|
||||
seen_invoices = set()
|
||||
|
||||
for inv in invoices:
|
||||
|
||||
invoice_no = inv["Invoice_No"]
|
||||
payments = payments_map.get(invoice_no, [])
|
||||
|
||||
if invoice_no in seen_invoices:
|
||||
continue
|
||||
|
||||
seen_invoices.add(invoice_no)
|
||||
|
||||
first_payment = payments[0] if payments else None
|
||||
|
||||
row = [
|
||||
pmc_no,
|
||||
inv["Village_Name"],
|
||||
inv["Work_Type"],
|
||||
inv["Invoice_Details"],
|
||||
inv["Invoice_Date"],
|
||||
invoice_no,
|
||||
inv["Basic_Amount"],
|
||||
inv["Debit_Amount"],
|
||||
inv["After_Debit_Amount"],
|
||||
inv["GST_Amount"],
|
||||
inv["Amount"],
|
||||
inv["TDS_Amount"],
|
||||
inv["SD_Amount"],
|
||||
inv["On_Commission"],
|
||||
inv["Hydro_Testing"],
|
||||
inv["GST_SD_Amount"]
|
||||
]
|
||||
|
||||
# HOLD DATA
|
||||
invoice_holds = hold_data.get(inv["Invoice_Id"], {})
|
||||
for ht_id in hold_type_map.keys():
|
||||
row.append(invoice_holds.get(ht_id, ""))
|
||||
|
||||
# PAYMENT DATA
|
||||
row += [
|
||||
inv["Final_Amount"],
|
||||
first_payment["Payment_Amount"] if first_payment else "",
|
||||
first_payment["TDS_Payment_Amount"] if first_payment else "",
|
||||
first_payment["Total_amount"] if first_payment else "",
|
||||
first_payment["UTR"] if first_payment else ""
|
||||
]
|
||||
|
||||
sheet.append(row)
|
||||
|
||||
# AUTO WIDTH
|
||||
for col in sheet.columns:
|
||||
max_len = max((len(str(cell.value)) for cell in col if cell.value), default=0)
|
||||
sheet.column_dimensions[col[0].column_letter].width = max_len + 2
|
||||
|
||||
# SAVE
|
||||
workbook.save(output_file)
|
||||
workbook.close()
|
||||
|
||||
return output_folder, filename
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error generating PMC report: {e}")
|
||||
return None
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# @staticmethod
|
||||
# def download_pmc_report(pmc_no):
|
||||
|
||||
# connection = config.get_db_connection()
|
||||
# cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# # output_folder = "static/download"
|
||||
# # output_file = os.path.join(output_folder, f"PMC_Report_{pmc_no}.xlsx")
|
||||
# output_folder = FolderAndFile.get_download_folder
|
||||
# filename = f"PMC_Report_{pmc_no}.xlsx"
|
||||
# output_file = FolderAndFile.get_download_path(filename)
|
||||
|
||||
# try:
|
||||
|
||||
# cursor.callproc('GetContractorDetailsByPMC', [pmc_no])
|
||||
# contractor_info = next(cursor.stored_results()).fetchone()
|
||||
|
||||
# if not contractor_info:
|
||||
# return None
|
||||
|
||||
# cursor.callproc('GetHoldTypesByContractor', [contractor_info["Contractor_Id"]])
|
||||
# hold_types = next(cursor.stored_results()).fetchall()
|
||||
|
||||
# hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
|
||||
|
||||
# cursor.callproc('GetInvoicesAndGstReleaseByPmcNo', [pmc_no])
|
||||
# invoices = next(cursor.stored_results()).fetchall()
|
||||
|
||||
# cursor.callproc('GetCreditNoteByContractor',[contractor_info["Contractor_Id"]])
|
||||
|
||||
# credit_notes = []
|
||||
# for result in cursor.stored_results():
|
||||
# credit_notes = result.fetchall()
|
||||
|
||||
# credit_note_map = {}
|
||||
# for cn in credit_notes:
|
||||
# key = (cn["PMC_No"], cn["Invoice_No"])
|
||||
# credit_note_map.setdefault(key, []).append(cn)
|
||||
|
||||
# cursor.callproc('GetHoldAmountsByContractor', [contractor_info["Contractor_Id"]])
|
||||
# hold_amounts = next(cursor.stored_results()).fetchall()
|
||||
|
||||
# hold_data = {}
|
||||
# for h in hold_amounts:
|
||||
# hold_data.setdefault(h['Invoice_Id'], {})[h['hold_type_id']] = h['hold_amount']
|
||||
|
||||
# cursor.callproc('GetAllPaymentsByPMC', [pmc_no])
|
||||
# all_payments = next(cursor.stored_results()).fetchall()
|
||||
|
||||
# payments_map = {}
|
||||
# extra_payments = []
|
||||
|
||||
# for pay in all_payments:
|
||||
# if pay['invoice_no']:
|
||||
# payments_map.setdefault(pay['invoice_no'], []).append(pay)
|
||||
# else:
|
||||
# extra_payments.append(pay)
|
||||
|
||||
# # ---------------- GST RELEASE DETAILS ----------------
|
||||
# cursor.callproc('GetGSTReleaseDetailsByPMC', [pmc_no])
|
||||
|
||||
# gst_releases = []
|
||||
# for result in cursor.stored_results():
|
||||
# gst_releases = result.fetchall()
|
||||
|
||||
# gst_release_map = {}
|
||||
|
||||
# for gr in gst_releases:
|
||||
|
||||
# invoice_nos = []
|
||||
|
||||
# if gr['Invoice_No']:
|
||||
|
||||
# cleaned = gr['Invoice_No'].replace(' ', '')
|
||||
|
||||
# if '&' in cleaned:
|
||||
# invoice_nos = cleaned.split('&')
|
||||
|
||||
# elif ',' in cleaned:
|
||||
# invoice_nos = cleaned.split(',')
|
||||
|
||||
# else:
|
||||
# invoice_nos = [cleaned]
|
||||
|
||||
# for inv_no in invoice_nos:
|
||||
# gst_release_map.setdefault(inv_no, []).append(gr)
|
||||
|
||||
# LogHelper.log_action(
|
||||
# "Download PMC Report",
|
||||
# f"User {current_user.id} Download PMC Report '{pmc_no}'"
|
||||
# )
|
||||
|
||||
# workbook = openpyxl.Workbook()
|
||||
# sheet = workbook.active
|
||||
# sheet.title = "PMC Report"
|
||||
|
||||
# sheet.append(["", "", "Laxmi Civil Engineering Services PVT. LTD."])
|
||||
# sheet.append(["Contractor Name", contractor_info["Contractor_Name"], "", "GST No", contractor_info["GST_No"], "", "GST Type", contractor_info["GST_Registration_Type"]])
|
||||
# sheet.append(["State", contractor_info["State_Name"], "", "PAN No", contractor_info["PAN_No"], "", "Address", contractor_info["Address"]])
|
||||
# sheet.append(["District", contractor_info["District_Name"], "", "Mobile No", contractor_info["Mobile_No"]])
|
||||
# sheet.append(["Block", contractor_info["Block_Name"], "", "Email", contractor_info["Email"]])
|
||||
# sheet.append([])
|
||||
|
||||
# base_headers = [
|
||||
# "PMC No","Village","Work Type","Invoice Details","Invoice Date","Invoice No",
|
||||
# "Basic Amount","Debit","After Debit Amount","GST (18%)","Amount","TDS (1%)",
|
||||
# "SD (5%)","On Commission","Hydro Testing","GST SD Amount"
|
||||
# ]
|
||||
|
||||
# hold_headers = [ht['hold_type'] for ht in hold_types]
|
||||
|
||||
# payment_headers = [
|
||||
# "Final Amount","Payment Amount","TDS Payment","Total Paid","UTR"
|
||||
# ]
|
||||
|
||||
# sheet.append(base_headers + hold_headers + payment_headers)
|
||||
|
||||
# header_fill = PatternFill(start_color="ADD8E6",end_color="ADD8E6",fill_type="solid")
|
||||
# header_font = Font(bold=True)
|
||||
|
||||
# for cell in sheet[sheet.max_row]:
|
||||
# cell.font = header_font
|
||||
# cell.fill = header_fill
|
||||
|
||||
# seen_invoices = set()
|
||||
# processed_payments = set()
|
||||
|
||||
# for inv in invoices:
|
||||
|
||||
# invoice_no = inv["Invoice_No"]
|
||||
# payments = payments_map.get(invoice_no, [])
|
||||
|
||||
# if invoice_no not in seen_invoices:
|
||||
|
||||
# seen_invoices.add(invoice_no)
|
||||
# first_payment = payments[0] if payments else None
|
||||
|
||||
# row = [
|
||||
# pmc_no, inv["Village_Name"], inv["Work_Type"],
|
||||
# inv["Invoice_Details"], inv["Invoice_Date"], invoice_no,
|
||||
# inv["Basic_Amount"], inv["Debit_Amount"],
|
||||
# inv["After_Debit_Amount"], inv["GST_Amount"],
|
||||
# inv["Amount"], inv["TDS_Amount"], inv["SD_Amount"],
|
||||
# inv["On_Commission"], inv["Hydro_Testing"], inv["GST_SD_Amount"]
|
||||
# ]
|
||||
|
||||
# invoice_holds = hold_data.get(inv["Invoice_Id"], {})
|
||||
|
||||
# for ht_id in hold_type_map.keys():
|
||||
# row.append(invoice_holds.get(ht_id, ""))
|
||||
|
||||
# row += [
|
||||
# inv["Final_Amount"],
|
||||
# first_payment["Payment_Amount"] if first_payment else "",
|
||||
# first_payment["TDS_Payment_Amount"] if first_payment else "",
|
||||
# first_payment["Total_amount"] if first_payment else "",
|
||||
# first_payment["UTR"] if first_payment else ""
|
||||
# ]
|
||||
|
||||
# sheet.append(row)
|
||||
|
||||
# workbook.save(output_file)
|
||||
# workbook.close()
|
||||
|
||||
# return output_folder, filename
|
||||
|
||||
# finally:
|
||||
|
||||
# cursor.close()
|
||||
# connection.close()
|
||||
275
v-2/model/Report.py
Normal file
275
v-2/model/Report.py
Normal file
@@ -0,0 +1,275 @@
|
||||
import config
|
||||
from datetime import datetime
|
||||
from flask import send_file
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font
|
||||
|
||||
from model.FolderAndFile import FolderAndFile
|
||||
|
||||
class ReportHelper:
|
||||
isSuccess = False
|
||||
resultMessage = ""
|
||||
data=[]
|
||||
|
||||
def __init__(self):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
self.data = []
|
||||
|
||||
@staticmethod
|
||||
def execute_sp(cursor, proc_name, params=[], fetch_one=False):
|
||||
cursor.callproc(proc_name, params)
|
||||
return (
|
||||
ReportHelper.fetch_one_result(cursor)
|
||||
if fetch_one else
|
||||
ReportHelper.fetch_all_results(cursor)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def fetch_all_results(cursor):
|
||||
data = []
|
||||
for result in cursor.stored_results():
|
||||
data = result.fetchall()
|
||||
return data
|
||||
|
||||
|
||||
@staticmethod
|
||||
def fetch_one_result(cursor):
|
||||
data = None
|
||||
for result in cursor.stored_results():
|
||||
data = result.fetchone()
|
||||
return data
|
||||
|
||||
|
||||
@staticmethod
|
||||
def search_contractor(request):
|
||||
subcontractor_name = request.form.get("subcontractor_name")
|
||||
pmc_no = request.form.get("pmc_no")
|
||||
state = request.form.get("state")
|
||||
district = request.form.get("district")
|
||||
block = request.form.get("block")
|
||||
village = request.form.get("village")
|
||||
year_from = request.form.get("year_from")
|
||||
year_to = request.form.get("year_to")
|
||||
|
||||
connection = config.get_db_connection()
|
||||
if not connection:
|
||||
return []
|
||||
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
try:
|
||||
data = ReportHelper.execute_sp(
|
||||
cursor,
|
||||
"search_contractor_info",
|
||||
[
|
||||
subcontractor_name or None,
|
||||
pmc_no or None,
|
||||
state or None,
|
||||
district or None,
|
||||
block or None,
|
||||
village or None,
|
||||
year_from or None,
|
||||
year_to or None
|
||||
]
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in search_contractor: {e}")
|
||||
data = []
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_contractor_report(contractor_id):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True, buffered=True)
|
||||
|
||||
try:
|
||||
# Contractor Info (only one fetch)
|
||||
contInfo = ReportHelper.execute_sp(cursor, 'GetContractorInfo', [contractor_id], True)
|
||||
# Hold Types
|
||||
hold_types = ReportHelper.execute_sp(cursor, 'GetContractorHoldTypes', [contractor_id])
|
||||
# Invoices
|
||||
invoices = ReportHelper.execute_sp(cursor, 'GetContractorInvoices', [contractor_id])
|
||||
# GST Release
|
||||
gst_rel = ReportHelper.execute_sp(cursor, 'GetGSTRelease', [contractor_id])
|
||||
# Hold Release
|
||||
hold_release = ReportHelper.execute_sp(cursor, 'GetHoldRelease', [contractor_id])
|
||||
# Credit Note
|
||||
credit_note = ReportHelper.execute_sp(cursor, 'GetCreditNote', [contractor_id])
|
||||
# Payments
|
||||
payments = ReportHelper.execute_sp(cursor, 'GetPayments', [contractor_id])
|
||||
|
||||
# Totals
|
||||
total = {
|
||||
"sum_invo_basic_amt": float(sum(row['Basic_Amount'] or 0 for row in invoices)),
|
||||
"sum_invo_debit_amt": float(sum(row['Debit_Amount'] or 0 for row in invoices)),
|
||||
"sum_invo_after_debit_amt": float(sum(row['After_Debit_Amount'] or 0 for row in invoices)),
|
||||
"sum_invo_amt": float(sum(row['Amount'] or 0 for row in invoices)),
|
||||
"sum_invo_gst_amt": float(sum(row['GST_Amount'] or 0 for row in invoices)),
|
||||
"sum_invo_tds_amt": float(sum(row['TDS_Amount'] or 0 for row in invoices)),
|
||||
"sum_invo_ds_amt": float(sum(row['SD_Amount'] or 0 for row in invoices)),
|
||||
"sum_invo_on_commission": float(sum(row['On_Commission'] or 0 for row in invoices)),
|
||||
"sum_invo_hydro_test": float(sum(row['Hydro_Testing'] or 0 for row in invoices)),
|
||||
"sum_invo_gst_sd_amt": float(sum(row['GST_SD_Amount'] or 0 for row in invoices)),
|
||||
"sum_invo_final_amt": float(sum(row['Final_Amount'] or 0 for row in invoices)),
|
||||
"sum_invo_hold_amt": float(sum(row['hold_amount'] or 0 for row in invoices)),
|
||||
|
||||
"sum_gst_basic_amt": float(sum(row['basic_amount'] or 0 for row in gst_rel)),
|
||||
"sum_gst_final_amt": float(sum(row['final_amount'] or 0 for row in gst_rel)),
|
||||
|
||||
"sum_pay_payment_amt": float(sum(row['Payment_Amount'] or 0 for row in payments)),
|
||||
"sum_pay_tds_payment_amt": float(sum(row['TDS_Payment_Amount'] or 0 for row in payments)),
|
||||
"sum_pay_total_amt": float(sum(row['Total_amount'] or 0 for row in payments))
|
||||
}
|
||||
|
||||
current_date = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return {
|
||||
"contInfo": contInfo,
|
||||
"invoices": invoices,
|
||||
"hold_types": hold_types,
|
||||
"gst_rel": gst_rel,
|
||||
"payments": payments,
|
||||
"credit_note": credit_note,
|
||||
"hold_release": hold_release,
|
||||
"total": total,
|
||||
"current_date": current_date
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def download_report(contractor_id):
|
||||
try:
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# -------- Contractor Info --------
|
||||
contInfo = ReportHelper.execute_sp(cursor, 'GetContractorInfo', [contractor_id], True)
|
||||
|
||||
if not contInfo:
|
||||
return "No contractor found", 404
|
||||
|
||||
# -------- Invoice Data --------
|
||||
cursor.callproc('FetchInvoicesByContractor', [contractor_id])
|
||||
|
||||
invoices = []
|
||||
for result in cursor.stored_results():
|
||||
invoices.extend(result.fetchall())
|
||||
|
||||
if not invoices:
|
||||
return "No invoice data found"
|
||||
|
||||
# -------- Create Workbook --------
|
||||
workbook = openpyxl.Workbook()
|
||||
sheet = workbook.active
|
||||
sheet.title = "Contractor Report"
|
||||
|
||||
# ================= CONTRACTOR DETAILS =================
|
||||
sheet.append(["SUB CONTRACTOR DETAILS"])
|
||||
sheet.cell(row=sheet.max_row, column=1).font = Font(bold=True)
|
||||
sheet.append([])
|
||||
|
||||
sheet.append(["Name", contInfo.get("Contractor_Name") or ""])
|
||||
sheet.append(["Mobile No", contInfo.get("Mobile_No") or ""])
|
||||
sheet.append(["Email", contInfo.get("Email") or ""])
|
||||
sheet.append(["Village", contInfo.get("Village_Name") or ""])
|
||||
sheet.append(["Block", contInfo.get("Block_Name") or ""])
|
||||
sheet.append(["District", contInfo.get("District_Name") or ""])
|
||||
sheet.append(["State", contInfo.get("State_Name") or ""])
|
||||
sheet.append(["Address", contInfo.get("Address") or ""])
|
||||
sheet.append(["GST No", contInfo.get("GST_No") or ""])
|
||||
sheet.append(["PAN No", contInfo.get("PAN_No") or ""])
|
||||
sheet.append([])
|
||||
sheet.append([])
|
||||
|
||||
# ================= TABLE HEADERS =================
|
||||
headers = [
|
||||
"PMC No", "Village", "Invoice No", "Invoice Date", "Work Type","Invoice_Details",
|
||||
"Basic Amount", "Debit Amount", "After Debit Amount",
|
||||
"Amount", "GST Amount", "TDS Amount", "SD Amount",
|
||||
"On Commission", "Hydro Testing", "Hold Amount",
|
||||
"GST SD Amount", "Final Amount",
|
||||
"Payment Amount", "TDS Payment",
|
||||
"Total Amount", "UTR"
|
||||
]
|
||||
sheet.append(headers)
|
||||
for col in range(1, len(headers) + 1):
|
||||
sheet.cell(row=sheet.max_row, column=col).font = Font(bold=True)
|
||||
|
||||
# ================= DATA =================
|
||||
total_final = 0
|
||||
total_payment = 0
|
||||
total_amount = 0
|
||||
|
||||
for inv in invoices:
|
||||
row = [
|
||||
inv.get("PMC_No"),
|
||||
inv.get("Village_Name"),
|
||||
inv.get("invoice_no"),
|
||||
inv.get("Invoice_Date"),
|
||||
inv.get("Work_Type"),
|
||||
inv.get("Invoice_Details"),
|
||||
inv.get("Basic_Amount"),
|
||||
inv.get("Debit_Amount"),
|
||||
inv.get("After_Debit_Amount"),
|
||||
inv.get("Amount"),
|
||||
inv.get("GST_Amount"),
|
||||
inv.get("TDS_Amount"),
|
||||
inv.get("SD_Amount"),
|
||||
inv.get("On_Commission"),
|
||||
inv.get("Hydro_Testing"),
|
||||
inv.get("Hold_Amount"),
|
||||
inv.get("GST_SD_Amount"),
|
||||
inv.get("Final_Amount"),
|
||||
inv.get("Payment_Amount"),
|
||||
inv.get("TDS_Payment_Amount"),
|
||||
inv.get("Total_Amount"),
|
||||
inv.get("UTR")
|
||||
]
|
||||
|
||||
total_final += float(inv.get("Final_Amount") or 0)
|
||||
total_payment += float(inv.get("Payment_Amount") or 0)
|
||||
total_amount += float(inv.get("Total_Amount") or 0)
|
||||
|
||||
sheet.append(row)
|
||||
|
||||
# ================= TOTAL ROW =================
|
||||
sheet.append([])
|
||||
sheet.append([
|
||||
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
||||
"TOTAL",
|
||||
total_final,
|
||||
total_payment,
|
||||
"",
|
||||
total_amount,
|
||||
""
|
||||
])
|
||||
|
||||
# ================= AUTO WIDTH =================
|
||||
for column in sheet.columns:
|
||||
max_length = 0
|
||||
column_letter = column[0].column_letter
|
||||
for cell in column:
|
||||
if cell.value:
|
||||
max_length = max(max_length, len(str(cell.value)))
|
||||
sheet.column_dimensions[column_letter].width = max_length + 2
|
||||
|
||||
# ================= SAVE FILE =================
|
||||
filename = f"Contractor_Report_{contInfo.get('Contractor_Name')}.xlsx"
|
||||
output_file = FolderAndFile.get_download_path(filename)
|
||||
workbook.save(output_file)
|
||||
|
||||
return send_file(output_file, as_attachment=True)
|
||||
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
168
v-2/model/State.py
Normal file
168
v-2/model/State.py
Normal file
@@ -0,0 +1,168 @@
|
||||
from flask import request, redirect, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
|
||||
from model.Log import LogHelper
|
||||
from model.ItemCRUD import ItemCRUD
|
||||
|
||||
import config
|
||||
import re
|
||||
import mysql.connector
|
||||
|
||||
|
||||
class State:
|
||||
|
||||
def __init__(self):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# ADD STATE (USING ITEM CRUD)
|
||||
# ----------------------------------------------------------
|
||||
def AddState(self, request):
|
||||
|
||||
state_name = request.form['state_Name'].strip()
|
||||
|
||||
crud = ItemCRUD(ItemCRUDType.State)
|
||||
|
||||
crud.AddItem(
|
||||
request=request,
|
||||
childname=state_name,
|
||||
storedprocfetch="CheckStateExists",
|
||||
storedprocadd="SaveState"
|
||||
)
|
||||
|
||||
self.isSuccess = crud.isSuccess
|
||||
self.resultMessage = crud.resultMessage
|
||||
|
||||
return self.resultMessage
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# GET ALL STATES (NO CHANGE - THIS IS CORRECT)
|
||||
# ----------------------------------------------------------
|
||||
def GetAllStates(self, request):
|
||||
|
||||
connection = config.get_db_connection()
|
||||
data = []
|
||||
|
||||
if not connection:
|
||||
return []
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc("GetAllStates")
|
||||
for res in cursor.stored_results():
|
||||
data = res.fetchall()
|
||||
|
||||
self.isSuccess = True
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching states: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
ResponseHandler.fetch_failure("state"), 500
|
||||
)
|
||||
return []
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# CHECK STATE (USING ITEM CRUD)
|
||||
# ----------------------------------------------------------
|
||||
def CheckState(self, request):
|
||||
|
||||
state_name = request.json.get('state_Name', '').strip()
|
||||
|
||||
crud = ItemCRUD(ItemCRUDType.State)
|
||||
|
||||
return crud.CheckItem(
|
||||
request=request,
|
||||
parentid=None,
|
||||
childname=state_name,
|
||||
storedprocfetch="CheckStateExists"
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# DELETE STATE (USING ITEM CRUD)
|
||||
# ----------------------------------------------------------
|
||||
def DeleteState(self, request, id):
|
||||
|
||||
crud = ItemCRUD(ItemCRUDType.State)
|
||||
|
||||
crud.DeleteItem(
|
||||
request=request,
|
||||
itemID=id,
|
||||
storedprocDelete="DeleteState"
|
||||
)
|
||||
|
||||
self.isSuccess = crud.isSuccess
|
||||
self.resultMessage = crud.resultMessage
|
||||
|
||||
return self.resultMessage
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# EDIT STATE (USING ITEM CRUD)
|
||||
# ----------------------------------------------------------
|
||||
def EditState(self, request, id):
|
||||
|
||||
state_name = request.form['state_Name'].strip()
|
||||
|
||||
crud = ItemCRUD(ItemCRUDType.State)
|
||||
|
||||
crud.EditItem(
|
||||
request=request,
|
||||
childid=id,
|
||||
parentid=None,
|
||||
childname=state_name,
|
||||
storedprocupdate="UpdateStateById"
|
||||
)
|
||||
|
||||
self.isSuccess = crud.isSuccess
|
||||
self.resultMessage = crud.resultMessage
|
||||
|
||||
return redirect(url_for('state.add_state'))
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# GET STATE BY ID (KEEP SAME)
|
||||
# ----------------------------------------------------------
|
||||
def GetStateByID(self, request, id):
|
||||
|
||||
connection = config.get_db_connection()
|
||||
data = None
|
||||
|
||||
if not connection:
|
||||
return None
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc("GetStateByID", (id,))
|
||||
for res in cursor.stored_results():
|
||||
data = res.fetchone()
|
||||
|
||||
if data:
|
||||
self.isSuccess = True
|
||||
self.resultMessage = "Success"
|
||||
else:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = "Not Found"
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching state: {e}")
|
||||
self.isSuccess = False
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return data
|
||||
140
v-2/model/Subcontractor.py
Normal file
140
v-2/model/Subcontractor.py
Normal file
@@ -0,0 +1,140 @@
|
||||
from model.Utilities import ItemCRUDType
|
||||
from model.ItemCRUD import ItemCRUD
|
||||
|
||||
|
||||
class Subcontractor:
|
||||
def __init__(self):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# ADD
|
||||
# ----------------------------------------------------------
|
||||
def AddSubcontractor(self, request):
|
||||
|
||||
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
|
||||
|
||||
data = {
|
||||
"Contractor_Name": request.form.get('Contractor_Name', '').strip(),
|
||||
"Address": request.form.get('Address', '').strip(),
|
||||
"Mobile_No": request.form.get('Mobile_No', '').strip(),
|
||||
"PAN_No": request.form.get('PAN_No', '').strip(),
|
||||
"Email": request.form.get('Email', '').strip(),
|
||||
"Gender": request.form.get('Gender', '').strip(),
|
||||
"GST_Registration_Type": request.form.get('GST_Registration_Type', '').strip(),
|
||||
"GST_No": request.form.get('GST_No', '').strip(),
|
||||
"Contractor_password": request.form.get('Contractor_password', '').strip()
|
||||
}
|
||||
|
||||
subcontractor.AddItem(
|
||||
request=request,
|
||||
data=data,
|
||||
storedprocfetch="GetSubcontractorByName",
|
||||
storedprocadd="SaveContractor"
|
||||
)
|
||||
|
||||
self.isSuccess = subcontractor.isSuccess
|
||||
self.resultMessage = subcontractor.resultMessage
|
||||
return
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# GET ALL
|
||||
# ----------------------------------------------------------
|
||||
def GetAllSubcontractors(self, request):
|
||||
|
||||
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
|
||||
|
||||
data = subcontractor.GetAllData(
|
||||
request=request,
|
||||
storedproc="GetAllSubcontractors"
|
||||
)
|
||||
|
||||
self.isSuccess = subcontractor.isSuccess
|
||||
self.resultMessage = subcontractor.resultMessage
|
||||
return data
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# GET BY ID
|
||||
# ----------------------------------------------------------
|
||||
def GetSubcontractorByID(self, id):
|
||||
|
||||
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
|
||||
|
||||
data = subcontractor.GetDataByID(
|
||||
id=id,
|
||||
storedproc="GetSubcontractorById"
|
||||
)
|
||||
|
||||
if data:
|
||||
self.isSuccess = True
|
||||
else:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = "Subcontractor not found"
|
||||
|
||||
return data
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# CHECK (Duplicate)
|
||||
# ----------------------------------------------------------
|
||||
def CheckSubcontractor(self, request):
|
||||
|
||||
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
|
||||
|
||||
name = request.form.get('Contractor_Name', '').strip()
|
||||
|
||||
result = subcontractor.CheckItem(
|
||||
request=request,
|
||||
childname=name,
|
||||
storedprocfetch="GetSubcontractorByName"
|
||||
)
|
||||
|
||||
self.isSuccess = subcontractor.isSuccess
|
||||
self.resultMessage = subcontractor.resultMessage
|
||||
return result
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# EDIT
|
||||
# ----------------------------------------------------------
|
||||
def EditSubcontractor(self, request, subcontractor_id):
|
||||
|
||||
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
|
||||
|
||||
data = {
|
||||
"Contractor_Name": request.form.get('Contractor_Name', '').strip(),
|
||||
"Address": request.form.get('Address', '').strip(),
|
||||
"Mobile_No": request.form.get('Mobile_No', '').strip(),
|
||||
"PAN_No": request.form.get('PAN_No', '').strip(),
|
||||
"Email": request.form.get('Email', '').strip(),
|
||||
"Gender": request.form.get('Gender', '').strip(),
|
||||
"GST_Registration_Type": request.form.get('GST_Registration_Type', '').strip(),
|
||||
"GST_No": request.form.get('GST_No', '').strip(),
|
||||
"Contractor_password": request.form.get('Contractor_password', '').strip()
|
||||
}
|
||||
|
||||
subcontractor.EditItem(
|
||||
request=request,
|
||||
childid=subcontractor_id,
|
||||
data=data,
|
||||
storedprocupdate="UpdateSubcontractor"
|
||||
)
|
||||
|
||||
self.isSuccess = subcontractor.isSuccess
|
||||
self.resultMessage = subcontractor.resultMessage
|
||||
return
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# DELETE
|
||||
# ----------------------------------------------------------
|
||||
def DeleteSubcontractor(self, request, subcontractor_id):
|
||||
|
||||
subcontractor = ItemCRUD(itemType=ItemCRUDType.Subcontractor)
|
||||
|
||||
subcontractor.DeleteItem(
|
||||
request=request,
|
||||
itemID=subcontractor_id,
|
||||
storedprocDelete="DeleteSubcontractor"
|
||||
)
|
||||
|
||||
self.isSuccess = subcontractor.isSuccess
|
||||
self.resultMessage = subcontractor.resultMessage
|
||||
return
|
||||
66
v-2/model/Utilities.py
Normal file
66
v-2/model/Utilities.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from flask import flash, jsonify, json
|
||||
from enum import Enum
|
||||
|
||||
class ItemCRUDType(Enum):
|
||||
Village = 1
|
||||
Block = 2
|
||||
District = 3
|
||||
State = 4
|
||||
HoldType = 5
|
||||
Subcontractor = 6
|
||||
|
||||
|
||||
class RegEx:
|
||||
patternAlphabetOnly = "^[A-Za-z ]+$"
|
||||
|
||||
|
||||
class ResponseHandler:
|
||||
@staticmethod
|
||||
def invalid_name(entity):
|
||||
return {'status': 'error', 'message': f'Invalid {entity} name. Only letters are allowed!'}
|
||||
|
||||
@staticmethod
|
||||
def already_exists(entity):
|
||||
return {'status': 'exists', 'message': f'{entity.capitalize()} already exists!'}
|
||||
|
||||
@staticmethod
|
||||
def add_success(entity):
|
||||
return {'status': 'success', 'message': f'{entity.capitalize()} added successfully!'}
|
||||
|
||||
@staticmethod
|
||||
def add_failure(entity):
|
||||
return {'status': 'error', 'message': f'Failed to add {entity}.'}
|
||||
|
||||
@staticmethod
|
||||
def is_available(entity):
|
||||
return {'status': 'available', 'message': f'{entity.capitalize()} name is available!'}
|
||||
|
||||
@staticmethod
|
||||
def delete_success(entity):
|
||||
return {'status': 'success', 'message': f'{entity.capitalize()} deleted successfully!'}
|
||||
|
||||
@staticmethod
|
||||
def delete_failure(entity):
|
||||
return {'status': 'error', 'message': f'Failed to delete {entity}.'}
|
||||
|
||||
@staticmethod
|
||||
def update_success(entity):
|
||||
return {'status': 'success', 'message': f'{entity.capitalize()} updated successfully!'}
|
||||
|
||||
@staticmethod
|
||||
def update_failure(entity):
|
||||
return {'status': 'error', 'message': f'Failed to update {entity}.'}
|
||||
|
||||
@staticmethod
|
||||
def fetch_failure(entity):
|
||||
return {'status': 'error', 'message': f'Failed to fetch {entity}.'}
|
||||
|
||||
|
||||
class HtmlHelper:
|
||||
# Helper: JSON Response Formatter
|
||||
|
||||
@staticmethod
|
||||
def json_response(message_obj, status_code):
|
||||
return jsonify(message_obj), status_code
|
||||
#May need to refactor further
|
||||
|
||||
121
v-2/model/Village.py
Normal file
121
v-2/model/Village.py
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||
|
||||
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
|
||||
from model.Log import LogData, LogHelper
|
||||
|
||||
import config
|
||||
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
|
||||
from model.ItemCRUD import ItemCRUD
|
||||
|
||||
|
||||
class Village:
|
||||
isSuccess = False
|
||||
resultMessage = ""
|
||||
|
||||
def __init__(self):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
def AddVillage(self, request):
|
||||
village = ItemCRUD(itemType=ItemCRUDType.Village)
|
||||
|
||||
block_id = request.form.get('block_Id')
|
||||
village_name = request.form.get('Village_Name', '').strip()
|
||||
|
||||
village.AddItem(request=request, parentid=block_id, childname=village_name, storedprocfetch="GetVillageByNameAndBlock", storedprocadd="SaveVillage" )
|
||||
self.isSuccess = village.isSuccess
|
||||
self.resultMessage = village.resultMessage
|
||||
return
|
||||
#self.isSuccess = False
|
||||
|
||||
def GetAllVillages(self, request):
|
||||
village = ItemCRUD(itemType=ItemCRUDType.Village)
|
||||
villagesdata = village.GetAllData(request=request, storedproc="GetAllVillages")
|
||||
self.isSuccess = village.isSuccess
|
||||
self.resultMessage = village.resultMessage
|
||||
return villagesdata
|
||||
|
||||
|
||||
def CheckVillage(self, request):
|
||||
village = ItemCRUD(itemType=ItemCRUDType.Village)
|
||||
block_id = request.form.get('block_Id')
|
||||
village_name = request.form.get('Village_Name', '').strip()
|
||||
result = village.CheckItem(request=request, parentid=block_id, childname=village_name, storedprocfetch="GetVillageByNameAndBlocks")
|
||||
self.isSuccess = village.isSuccess
|
||||
self.resultMessage = village.resultMessage
|
||||
return result
|
||||
|
||||
|
||||
def DeleteVillage(self, request, village_id):
|
||||
|
||||
village = ItemCRUD(itemType=ItemCRUDType.Village)
|
||||
|
||||
village.DeleteItem(request=request, itemID=village_id, storedprocDelete="DeleteVillage" )
|
||||
self.isSuccess = village.isSuccess
|
||||
self.resultMessage = village.resultMessage
|
||||
return
|
||||
|
||||
def EditVillage(self, request, village_id):
|
||||
corsor=None
|
||||
village = ItemCRUD(itemType=ItemCRUDType.Village)
|
||||
|
||||
block_id = request.form.get('block_Id')
|
||||
village_name = request.form.get('Village_Name', '').strip()
|
||||
|
||||
village.EditItem(request=request,childid=village_id,parentid=block_id,childname=village_name,storedprocupdate="UpdateVillage" )
|
||||
|
||||
self.isSuccess = village.isSuccess
|
||||
self.resultMessage = village.resultMessage
|
||||
return
|
||||
|
||||
# def GetVillageByID(self, request, id):
|
||||
|
||||
# village = ItemCRUD(itemType=ItemCRUDType.Village)
|
||||
# villagedetailsdata = village.GetAllData(request=request, storedproc="GetVillageDetailsById")
|
||||
# self.isSuccess = village.isSuccess
|
||||
# self.resultMessage = village.resultMessage
|
||||
# return villagedetailsdata
|
||||
|
||||
def GetVillageByID(self, request, id):
|
||||
village = ItemCRUD(itemType=ItemCRUDType.Village)
|
||||
villagedetailsdata = village.GetDataByID(id=id,storedproc="GetVillageDetailsById")
|
||||
if villagedetailsdata:
|
||||
self.isSuccess = True
|
||||
else:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = "Village not found"
|
||||
|
||||
return villagedetailsdata
|
||||
|
||||
|
||||
def GetAllBlocks(self, request):
|
||||
|
||||
blocks = []
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
connection = config.get_db_connection()
|
||||
|
||||
if not connection:
|
||||
return []
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc('GetAllBlocks')
|
||||
for result in cursor.stored_results():
|
||||
blocks = result.fetchall()
|
||||
self.isSuccess = True
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching blocks: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("block"), 500)
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return blocks
|
||||
150
v-2/model/gst_release.py
Normal file
150
v-2/model/gst_release.py
Normal file
@@ -0,0 +1,150 @@
|
||||
import config
|
||||
import mysql.connector
|
||||
|
||||
class GSTReleasemodel:
|
||||
|
||||
@staticmethod
|
||||
def get_connection():
|
||||
connection = config.get_db_connection()
|
||||
if not connection:
|
||||
return None
|
||||
return connection
|
||||
|
||||
@staticmethod
|
||||
def fetch_all_gst_releases():
|
||||
connection = GSTReleasemodel.get_connection()
|
||||
gst_releases = []
|
||||
if connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
try:
|
||||
cursor.callproc('GetAllGSTReleases')
|
||||
|
||||
gst_releases = []
|
||||
for result in cursor.stored_results(): # change to procedure
|
||||
gst_releases = result.fetchall()
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching GST releases: {e}")
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return gst_releases
|
||||
|
||||
@staticmethod
|
||||
def insert_gst_release(pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr, contractor_id):
|
||||
connection = GSTReleasemodel.get_connection()
|
||||
if not connection:
|
||||
return False
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Insert into gst_release
|
||||
cursor.callproc(
|
||||
'InsertGSTReleaseOnly',
|
||||
[pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr, contractor_id]
|
||||
)
|
||||
|
||||
# Insert into inpayment
|
||||
cursor.callproc(
|
||||
'InsertInpaymentOnly',
|
||||
[pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr, contractor_id]
|
||||
)
|
||||
|
||||
connection.commit()
|
||||
return True
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error inserting GST release: {e}")
|
||||
return False
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
@staticmethod
|
||||
def fetch_gst_release_by_id(gst_release_id):
|
||||
connection = GSTReleasemodel.get_connection()
|
||||
if not connection:
|
||||
return None
|
||||
data = {}
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.callproc('GetGSTReleaseById', [gst_release_id])
|
||||
|
||||
for result in cursor.stored_results():
|
||||
data = result.fetchone()
|
||||
if data:
|
||||
# Convert to array for template
|
||||
data = [
|
||||
data.get('GST_Release_Id'),
|
||||
data.get('PMC_No'),
|
||||
data.get('Invoice_No'),
|
||||
data.get('Basic_Amount'),
|
||||
data.get('Final_Amount'),
|
||||
data.get('Total_Amount'),
|
||||
data.get('UTR')
|
||||
]
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching GST release by id: {e}")
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def update_gst_release(gst_release_id, pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr):
|
||||
connection = GSTReleasemodel.get_connection()
|
||||
if not connection:
|
||||
return False
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
# Update gst_release
|
||||
cursor.callproc(
|
||||
'UpdateGSTRelease',
|
||||
[pmc_no, invoice_no, basic_amount, final_amount, total_amount, utr, gst_release_id]
|
||||
)
|
||||
# Update inpayment
|
||||
cursor.callproc(
|
||||
'UpdateInpaymentByUTR',
|
||||
[basic_amount, final_amount, total_amount, utr]
|
||||
)
|
||||
connection.commit()
|
||||
return True
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error updating GST release: {e}")
|
||||
return False
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
@staticmethod
|
||||
def delete_gst_release(gst_release_id):
|
||||
connection = GSTReleasemodel.get_connection()
|
||||
if not connection:
|
||||
return False, None
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
cursor.callproc('GetGSTReleaseUTRById', [gst_release_id])
|
||||
record = None
|
||||
for result in cursor.stored_results():
|
||||
record = result.fetchone()
|
||||
|
||||
if not record:
|
||||
return False, None
|
||||
|
||||
utr = record['UTR']
|
||||
|
||||
# Step 1: Delete gst_release
|
||||
cursor.callproc('DeleteGSTReleaseById', [gst_release_id])
|
||||
|
||||
# Step 2: Reset inpayment using UTR
|
||||
cursor.callproc('ResetInpaymentByUTR', [utr])
|
||||
|
||||
connection.commit()
|
||||
return True, utr
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error deleting GST release: {e}")
|
||||
return False, None
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
158
v-2/model/payment.py
Normal file
158
v-2/model/payment.py
Normal file
@@ -0,0 +1,158 @@
|
||||
import config
|
||||
import mysql.connector
|
||||
|
||||
class Paymentmodel:
|
||||
|
||||
@staticmethod
|
||||
def get_connection():
|
||||
connection = config.get_db_connection()
|
||||
if not connection:
|
||||
return None
|
||||
return connection
|
||||
|
||||
@staticmethod
|
||||
def fetch_all_payments():
|
||||
connection = Paymentmodel.get_connection()
|
||||
payments = []
|
||||
if connection:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
try:
|
||||
cursor.callproc('GetAllPayments')
|
||||
for result in cursor.stored_results():
|
||||
payments = result.fetchall()
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching payment history: {e}")
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return payments
|
||||
|
||||
@staticmethod
|
||||
def insert_payment(pmc_no, invoice_no, amount, tds_amount, total_amount, utr):
|
||||
connection = Paymentmodel.get_connection()
|
||||
if not connection:
|
||||
return False
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
cursor.callproc('InsertPayments', [pmc_no, invoice_no, amount, tds_amount, total_amount, utr])
|
||||
connection.commit()
|
||||
return True
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error inserting payment: {e}")
|
||||
return False
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
@staticmethod
|
||||
def update_inpayment(subcontractor_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr):
|
||||
connection = Paymentmodel.get_connection()
|
||||
if not connection:
|
||||
return False
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
cursor.callproc('UpdateInpaymentRecord', [
|
||||
subcontractor_id,
|
||||
pmc_no,
|
||||
invoice_no,
|
||||
amount,
|
||||
tds_amount,
|
||||
total_amount,
|
||||
utr
|
||||
])
|
||||
connection.commit()
|
||||
return True
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error updating inpayment: {e}")
|
||||
return False
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
@staticmethod
|
||||
def fetch_payment_by_id(payment_id):
|
||||
connection = Paymentmodel.get_connection()
|
||||
if not connection:
|
||||
return None
|
||||
payment_data = {}
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
cursor.callproc("GetPaymentById", (payment_id,))
|
||||
for result in cursor.stored_results():
|
||||
payment_data = result.fetchone()
|
||||
# Convert to array for template
|
||||
if payment_data:
|
||||
payment_data = [
|
||||
payment_data.get('Payment_Id'),
|
||||
payment_data.get('PMC_No'),
|
||||
payment_data.get('Invoice_No'),
|
||||
payment_data.get('Payment_Amount'),
|
||||
payment_data.get('TDS_Payment_Amount'),
|
||||
payment_data.get('Total_Amount'),
|
||||
payment_data.get('UTR')
|
||||
]
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching payment data: {e}")
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return payment_data
|
||||
|
||||
@staticmethod
|
||||
def call_update_payment_proc(payment_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr):
|
||||
connection = Paymentmodel.get_connection()
|
||||
if not connection:
|
||||
return False
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
cursor.callproc("UpdatePayment", (payment_id, pmc_no, invoice_no, amount, tds_amount, total_amount, utr))
|
||||
connection.commit()
|
||||
return True
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error updating payment: {e}")
|
||||
return False
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
@staticmethod
|
||||
def delete_payment(payment_id):
|
||||
"""
|
||||
Deletes a payment and resets the related inpayment fields in one go.
|
||||
Returns (success, pmc_no, invoice_no)
|
||||
"""
|
||||
connection = Paymentmodel.get_connection()
|
||||
if not connection:
|
||||
return False, None, None
|
||||
|
||||
try:
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
cursor.callproc('GetPaymentPMCInvoiceById', [payment_id])
|
||||
|
||||
record = {}
|
||||
for result in cursor.stored_results():
|
||||
record = result.fetchone() or {}
|
||||
if not record:
|
||||
return False, None, None
|
||||
|
||||
pmc_no = record['PMC_No']
|
||||
invoice_no = record['Invoice_No']
|
||||
|
||||
# Step 2: Delete the payment using the stored procedure
|
||||
cursor.callproc("DeletePayment", (payment_id,))
|
||||
connection.commit()
|
||||
|
||||
# Step 3: Reset inpayment fields using the stored procedure
|
||||
cursor.callproc("ResetInpayment", [pmc_no, invoice_no])
|
||||
connection.commit()
|
||||
|
||||
return True, pmc_no, invoice_no
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error deleting payment: {e}")
|
||||
return False, None, None
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
4
v-2/requirements.txt
Normal file
4
v-2/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Flask
|
||||
mysql-connector-python
|
||||
openpyxl
|
||||
pandas
|
||||
89
v-2/static/css/base.css
Normal file
89
v-2/static/css/base.css
Normal file
@@ -0,0 +1,89 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
background-color: #ffffff;
|
||||
position: fixed;
|
||||
left: -250px;
|
||||
transition: left 0.3s ease;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
||||
padding-top: 20px;
|
||||
}
|
||||
.sidebar.open {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* Sidebar Navigation */
|
||||
.nav-menu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
padding: 15px 20px;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.nav-link i {
|
||||
margin-right: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.nav-link:hover, .nav-link.active {
|
||||
background-color: #e6f7ff;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
/* Menu Button */
|
||||
.menu-icon {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Content Area */
|
||||
.content {
|
||||
margin-left: 0;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
.content.shift {
|
||||
margin-left: 250px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
left: -200px;
|
||||
}
|
||||
|
||||
.sidebar.open {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.content.shift {
|
||||
margin-left: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
226
v-2/static/css/index.css
Normal file
226
v-2/static/css/index.css
Normal file
@@ -0,0 +1,226 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 20px;
|
||||
position: fixed;
|
||||
}
|
||||
.logo {
|
||||
width: 80px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.nav-menu {
|
||||
width: 100%;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.nav-item {
|
||||
width: 100%;
|
||||
}
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
padding: 15px 20px;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
.nav-link:hover,
|
||||
.nav-link.active {
|
||||
background-color: #e6f7ff;
|
||||
color: #007bff;
|
||||
}
|
||||
.nav-link i {
|
||||
margin-right: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
.user-section {
|
||||
margin-top: auto;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
.user-section img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.user-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.user-info span {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
|
||||
/* Main content area */
|
||||
.content {
|
||||
margin-left: 250px;
|
||||
padding: 20px;
|
||||
width: calc(100% - 250px);
|
||||
}
|
||||
|
||||
/* Menu Cards */
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
.card {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
flex: 1 1 calc(30% - 20px);
|
||||
max-width: calc(30% - 20px);
|
||||
|
||||
}
|
||||
.card h2 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 20px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
color: #fff;
|
||||
background-color: #007bff;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
/* Company Info */
|
||||
.company-info {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background-color: Whitesmoke;
|
||||
color: blue;
|
||||
margin-left: 250px;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-weight: bold;
|
||||
app-name-shadow:0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
font-size: 24px;
|
||||
margin: 5px 0 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Responsive Design */
|
||||
@media screen and (max-width: 768px) {
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: static;
|
||||
}
|
||||
.content {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.menu {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.card {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
min-width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.nav-link {
|
||||
font-size: 14px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
.btn {
|
||||
font-size: 12px;
|
||||
padding: 8px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: static;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
.company-info {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.nav-link {
|
||||
font-size: 14px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: 12px;
|
||||
padding: 8px 15px;
|
||||
}
|
||||
|
||||
.user-section {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.user-section img {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
min-width: 90%;
|
||||
}
|
||||
}
|
||||
395
v-2/static/css/invoice.css
Normal file
395
v-2/static/css/invoice.css
Normal file
@@ -0,0 +1,395 @@
|
||||
/* General Styles */
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Form Styling */
|
||||
form {
|
||||
width:50%;
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
/* Responsive Form Layout */
|
||||
.row1,
|
||||
.row2,
|
||||
.row3 {
|
||||
display: grid;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.row2,
|
||||
.row3 {
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Button Styling */
|
||||
.button {
|
||||
padding: 12px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
/* Dynamic Hold Amount Fields */
|
||||
.hold-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.hold-amount-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hold-amount-field select,
|
||||
.hold-amount-field input {
|
||||
flex: 1;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.hold-amount-field button {
|
||||
background-color: red;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
padding: 8px 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hold-amount-field button:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
|
||||
/* Success Alert Box */
|
||||
.success-alert {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #4CAF50;
|
||||
color: #fff;
|
||||
padding: 15px 25px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.5s ease, visibility 0.5s ease;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.success-alert.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Table Styles */
|
||||
.invoice-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.invoice-table th,
|
||||
.invoice-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.invoice-table th {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.invoice-table tr:nth-child(even) {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.invoice-table tr:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* icon */
|
||||
.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
transform: scale(1.5);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.edit-icon:hover {
|
||||
filter: drop-shadow(0 0 5px #007bff); /* Blue glow for edit */
|
||||
}
|
||||
|
||||
.delete-icon:hover {
|
||||
filter: drop-shadow(0 0 5px #ff0000); /* Red glow for delete */
|
||||
}
|
||||
|
||||
|
||||
/* Center the buttons and apply consistent styling */
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: center; /* Center buttons horizontally */
|
||||
gap: 10px; /* Space between buttons */
|
||||
margin-top: 20px; /* Add some top margin */
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background-color: #007BFF; /* Blue background */
|
||||
color: white; /* White text */
|
||||
padding: 15px 30px; /* Larger padding for bigger buttons */
|
||||
border: none; /* Remove border */
|
||||
border-radius: 5px; /* Rounded corners */
|
||||
cursor: pointer; /* Pointer cursor on hover */
|
||||
font-size: 18px; /* Larger font size */
|
||||
text-align: center; /* Center text */
|
||||
text-decoration: none; /* Remove underline */
|
||||
transition: background-color 0.3s ease; /* Smooth hover transition */
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
background-color: #0056b3; /* Darker blue on hover */
|
||||
}
|
||||
|
||||
#addStateForm, #stateTable {
|
||||
display: none; /* Initially hide both the form and the table */
|
||||
}
|
||||
|
||||
/* Success Popup */
|
||||
.success-popup {
|
||||
display: none;
|
||||
color: green;
|
||||
font-size: 1.2em;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Sorting buttons */
|
||||
.sortable .sort-buttons {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
|
||||
.sortable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.sort-buttons a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.sort-buttons a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.back-button {
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
padding: 8px 15px;
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
.back-button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
span .sort-desc:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span .sort-asc:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 1024px) {
|
||||
form {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.row2,
|
||||
.row3 {
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
}
|
||||
|
||||
.invoice-table th,
|
||||
.invoice-table td {
|
||||
font-size: 12px;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.row1,
|
||||
.row2,
|
||||
.row3 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hold-amount-field {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hold-amount-field select,
|
||||
.hold-amount-field input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.invoice-table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body {
|
||||
max-width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.invoice-table th,
|
||||
.invoice-table td {
|
||||
font-size: 12px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
/* Additional Media Queries */
|
||||
|
||||
/* For tablets and medium devices */
|
||||
@media (max-width: 992px) {
|
||||
form {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
/* For smaller tablets and large phones */
|
||||
@media (max-width: 600px) {
|
||||
.button-container {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.success-alert {
|
||||
width: 90%;
|
||||
font-size: 14px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* For very small phones */
|
||||
@media (max-width: 360px) {
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.sort-buttons a {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.invoice-table th,
|
||||
.invoice-table td {
|
||||
font-size: 10px;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
313
v-2/static/css/invoice1.css
Normal file
313
v-2/static/css/invoice1.css
Normal file
@@ -0,0 +1,313 @@
|
||||
/* General Styling */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f9f9f9;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Header Styling */
|
||||
h1, h2, h3 {
|
||||
color: #0056b3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Table Styling */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
table th, table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table th {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
table tr:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
table tr:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
/* Sort Dropdown */
|
||||
.sort-options {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background: white;
|
||||
border: 1px solid black;
|
||||
padding: 5px;
|
||||
z-index: 100;
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sort-options button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sort-options button:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
/* Form Styling */
|
||||
form {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
form label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
form input[type="text"],
|
||||
form input[type="number"],
|
||||
form input[type="date"],
|
||||
form input[type="email"],
|
||||
form textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
form textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* Button Styling */
|
||||
.button, form button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button:hover, form button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
/* Back Button */
|
||||
.back-button {
|
||||
background-color: red;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: darkred;
|
||||
}
|
||||
|
||||
/* Success Alert */
|
||||
.success-alert {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.success-popup i {
|
||||
color: green;
|
||||
font-size: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* Error Message */
|
||||
.error-message {
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* Table Icons */
|
||||
td img {
|
||||
width: 20px; /* Adjust as needed */
|
||||
height: 20px; /* Adjust as needed */
|
||||
transition: transform 0.3s ease; /* Smooth transition for hover effect */
|
||||
}
|
||||
|
||||
td img:hover {
|
||||
transform: scale(1.2); /* Slight zoom effect on hover */
|
||||
}
|
||||
|
||||
/* Select Dropdown */
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
/* Custom Dropdown Arrow */
|
||||
select::-ms-expand {
|
||||
display: none;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: #4CAF50;
|
||||
}
|
||||
|
||||
select option {
|
||||
padding: 12px 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
option[disabled] {
|
||||
color: #aaa;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Save Button */
|
||||
.save-button {
|
||||
background-color: green;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 20px auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.save-button:hover {
|
||||
background-color: darkgreen;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
form {
|
||||
max-width: 100%;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
table th, table td {
|
||||
padding: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.button, form button {
|
||||
font-size: 14px;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
/* Additional Media Queries */
|
||||
|
||||
/* For tablets and medium-sized screens */
|
||||
@media (max-width: 992px) {
|
||||
form {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
table th, table td {
|
||||
font-size: 14px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.save-button, .back-button, .button, form button {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* For smaller tablets and large phones */
|
||||
@media (max-width: 600px) {
|
||||
body {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
table th, table td {
|
||||
font-size: 13px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.success-alert {
|
||||
width: 90%;
|
||||
font-size: 16px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.button, form button, .save-button, .back-button {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* For very small phones */
|
||||
@media (max-width: 360px) {
|
||||
h1, h2, h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.save-button, .back-button {
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
table th, table td {
|
||||
font-size: 12px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
form input, form select, form textarea {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
181
v-2/static/css/report.css
Normal file
181
v-2/static/css/report.css
Normal 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
v-2/static/css/show_excel.css
Normal file
212
v-2/static/css/show_excel.css
Normal file
@@ -0,0 +1,212 @@
|
||||
/* General Styles */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
background-color: #f4f4f9;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Headings */
|
||||
h1, h2 {
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* File Information Section */
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul li {
|
||||
background: #ecf0f1;
|
||||
margin: 5px 0;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Error Messages */
|
||||
.errors {
|
||||
background-color: #ffdddd;
|
||||
border: 1px solid #ff4d4d;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.errors h2 {
|
||||
color: #ff4d4d;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #d8000c;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Table Styles */
|
||||
.table-container {
|
||||
max-width: 100%;
|
||||
overflow-x: auto; /* Allows horizontal scrolling */
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #ddd; /* Optional for better visibility */
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: white;
|
||||
table-layout: auto; /* Let the columns adjust based on content */
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
text-align: left;
|
||||
word-wrap: break-word; /* Prevents text from overflowing */
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
/* Set input width to 100px */
|
||||
input[type="text"] {
|
||||
width: 200px; /* Set a fixed width of 100px */
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box; /* Ensures padding is included in width calculation */
|
||||
white-space: nowrap; /* Prevent line breaks */
|
||||
overflow: hidden; /* Prevents overflow */
|
||||
text-overflow: ellipsis; /* Shows ellipsis if the content is too long */
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
button {
|
||||
display: inline-block;
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
transition: background 0.3s ease;
|
||||
font-size: 16px;
|
||||
width: 100%; /* Ensures button is centered */
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: inline-block;
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
transition: background 0.3s ease;
|
||||
font-size: 16px;
|
||||
width: 10%; /* Ensures button is centered */
|
||||
}
|
||||
|
||||
/* Hover Effects */
|
||||
button:hover, .back-button:hover {
|
||||
background-color: #219150;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #95a5a6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Back Button */
|
||||
.back-button {
|
||||
background-color: #e74c3c;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
|
||||
/* Center Save Data Button */
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
margin-top: 20px;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.table-container {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
table-layout: auto; /* Let the table adjust to fit its content */
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100px; /* Fixed width of 100px */
|
||||
padding: 6px; /* Smaller padding on mobile */
|
||||
}
|
||||
|
||||
button, .back-button {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
body {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 80px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
font-size: 12px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 14px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.errors {
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
448
v-2/static/css/style.css
Normal file
448
v-2/static/css/style.css
Normal 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;
|
||||
}
|
||||
}
|
||||
199
v-2/static/css/subcontractor_report.css
Normal file
199
v-2/static/css/subcontractor_report.css
Normal 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;
|
||||
}
|
||||
}
|
||||
111
v-2/static/css/upload_excel_file.css
Normal file
111
v-2/static/css/upload_excel_file.css
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
v-2/static/images/icons/bin_red_icon.png
Normal file
BIN
v-2/static/images/icons/bin_red_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
BIN
v-2/static/images/icons/pen_blue_icon.png
Normal file
BIN
v-2/static/images/icons/pen_blue_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
116
v-2/static/js/block.js
Normal file
116
v-2/static/js/block.js
Normal file
@@ -0,0 +1,116 @@
|
||||
window.onload = function () {
|
||||
document.getElementById('block_Name').focus();
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
$("#block_Name").on("input", function () {
|
||||
let blockName = $(this).val();
|
||||
let cleanedName = blockName.replace(/[^A-Za-z ]/g, "");
|
||||
$(this).val(cleanedName);
|
||||
});
|
||||
|
||||
$("#block_Name, #district_Id").on("input change", function () {
|
||||
|
||||
let blockName = $("#block_Name").val().trim();
|
||||
let districtId = $("#district_Id").val();
|
||||
|
||||
if (blockName === "" || districtId === "") {
|
||||
$("#blockMessage").text("").css("color", "");
|
||||
$("#submitButton").prop("disabled", true);
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/check_block",
|
||||
type: "POST",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({
|
||||
block_Name: blockName,
|
||||
district_Id: districtId
|
||||
}),
|
||||
success: function (response) {
|
||||
|
||||
if (response.status === "available") {
|
||||
$("#blockMessage").text(response.message).css("color", "green");
|
||||
$("#submitButton").prop("disabled", false);
|
||||
}
|
||||
},
|
||||
error: function (xhr) {
|
||||
|
||||
if (xhr.status === 409) {
|
||||
$("#blockMessage").text("Block already exists!").css("color", "red");
|
||||
$("#submitButton").prop("disabled", true);
|
||||
}
|
||||
else if (xhr.status === 400) {
|
||||
$("#blockMessage").text("Invalid block name! Only letters allowed.").css("color", "red");
|
||||
$("#submitButton").prop("disabled", true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("#blockForm").on("submit", function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
$.ajax({
|
||||
url: "/add_block",
|
||||
type: "POST",
|
||||
data: $(this).serialize(),
|
||||
|
||||
success: function (response) {
|
||||
alert(response.message);
|
||||
location.reload();
|
||||
},
|
||||
|
||||
error: function (xhr) {
|
||||
alert(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// ✅ FETCH DISTRICTS WHEN STATE CHANGES
|
||||
$('#state_Id').change(function () {
|
||||
|
||||
var stateId = $(this).val();
|
||||
|
||||
if (stateId) {
|
||||
|
||||
$.ajax({
|
||||
url: '/get_districts/' + stateId,
|
||||
type: 'GET',
|
||||
|
||||
success: function (data) {
|
||||
|
||||
var districtDropdown = $('#district_Id');
|
||||
|
||||
districtDropdown.empty();
|
||||
districtDropdown.append('<option value="" disabled selected>Select District</option>');
|
||||
|
||||
data.forEach(function (district) {
|
||||
|
||||
districtDropdown.append(
|
||||
'<option value="' + district.id + '">' + district.name + '</option>'
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
districtDropdown.prop('disabled', false);
|
||||
},
|
||||
|
||||
error: function () {
|
||||
alert('Error fetching districts. Please try again.');
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
$('#district_Id').prop('disabled', true);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
62
v-2/static/js/district.js
Normal file
62
v-2/static/js/district.js
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
18
v-2/static/js/edit_hold_type.js
Normal file
18
v-2/static/js/edit_hold_type.js
Normal 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
v-2/static/js/holdAmount.js
Normal file
95
v-2/static/js/holdAmount.js
Normal 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
v-2/static/js/hold_types.js
Normal file
39
v-2/static/js/hold_types.js
Normal file
@@ -0,0 +1,39 @@
|
||||
$(document).ready(function () {
|
||||
$("#hold_type").on("input", function () {
|
||||
let holdType = $(this).val().replace(/^\s+/, "");
|
||||
$(this).val(holdType);
|
||||
|
||||
let reg = /^[A-Za-z]/;
|
||||
|
||||
if (!reg.test(holdType)) {
|
||||
$("#holdTypeMessage").text("Hold Type must start with a letter.").css("color", "red");
|
||||
$("#addButton").prop("disabled", true);
|
||||
return;
|
||||
} else {
|
||||
$("#holdTypeMessage").text("").css("color", "");
|
||||
$("#addButton").prop("disabled", false);
|
||||
}
|
||||
});
|
||||
|
||||
$("#holdTypeForm").on("submit", function (event) {
|
||||
event.preventDefault();
|
||||
$.post("/add_hold_type", $(this).serialize(), function (response) {
|
||||
alert(response.message);
|
||||
location.reload();
|
||||
}).fail(function (xhr) {
|
||||
alert(xhr.responseJSON.message);
|
||||
});
|
||||
});
|
||||
|
||||
$(".delete-button").on("click", function () {
|
||||
let id = $(this).data("id");
|
||||
if (confirm("Are you sure?")) {
|
||||
$.post(`/delete_hold_type/${id}`, function (response) {
|
||||
alert(response.message);
|
||||
location.reload();
|
||||
}).fail(function (xhr) {
|
||||
alert(xhr.responseJSON.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
62
v-2/static/js/invoice.js
Normal file
62
v-2/static/js/invoice.js
Normal 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();
|
||||
};
|
||||
39
v-2/static/js/save_data_success.js
Normal file
39
v-2/static/js/save_data_success.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
21
v-2/static/js/save_excel_file.js
Normal file
21
v-2/static/js/save_excel_file.js
Normal 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.");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
43
v-2/static/js/searchContractor.js
Normal file
43
v-2/static/js/searchContractor.js
Normal 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();
|
||||
};
|
||||
108
v-2/static/js/search_on_table.js
Normal file
108
v-2/static/js/search_on_table.js
Normal 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");
|
||||
});
|
||||
});
|
||||
8
v-2/static/js/showSuccessAlert.js
Normal file
8
v-2/static/js/showSuccessAlert.js
Normal file
@@ -0,0 +1,8 @@
|
||||
function showSuccessAlert(event) {
|
||||
event.preventDefault(); // Prevent form submission
|
||||
document.getElementById("successPopup").style.display = "block";
|
||||
setTimeout(function() {
|
||||
document.getElementById("successPopup").style.display = "none";
|
||||
event.target.submit(); // Submit the form after showing the message
|
||||
}, 2000);
|
||||
}
|
||||
66
v-2/static/js/sorting.js
Normal file
66
v-2/static/js/sorting.js
Normal file
@@ -0,0 +1,66 @@
|
||||
$(document).ready(function () {
|
||||
function fetchResults(sortBy = '', sortOrder = '') {
|
||||
let formData = $('#search-form').serialize();
|
||||
formData += &sort_by=${sortBy}&sort_order=${sortOrder};
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/search_contractor',
|
||||
data: formData,
|
||||
success: function (data) {
|
||||
let tableBody = $('#result-table tbody');
|
||||
tableBody.empty();
|
||||
|
||||
if (data.length === 0) {
|
||||
tableBody.append('<tr><td colspan="6">No data found</td></tr>');
|
||||
} else {
|
||||
data.forEach(function (row) {
|
||||
tableBody.append(`
|
||||
<tr>
|
||||
<td><a href="/contractor_report/${row.Contractor_Id}" target="_blank">${row.Contractor_Name}</a></td>
|
||||
<td><a href="/pmc_report/${row.PMC_No}" target="_blank">${row.PMC_No}</a></td>
|
||||
<td>${row.State_Name}</td>
|
||||
<td>${row.District_Name}</td>
|
||||
<td>${row.Block_Name}</td>
|
||||
<td>${row.Village_Name}</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function (xhr) {
|
||||
alert(xhr.responseJSON.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#search-form input').on('keyup change', function () {
|
||||
fetchResults();
|
||||
});
|
||||
|
||||
function showSortOptions(thElement, column) {
|
||||
let sortMenu = $('#sort-options');
|
||||
let offset = $(thElement).position();
|
||||
let thHeight = $(thElement).outerHeight();
|
||||
|
||||
sortMenu.html(`
|
||||
<button onclick="fetchResults('${column}', 'ASC')">Ascending</button>
|
||||
<button onclick="fetchResults('${column}', 'DESC')">Descending</button>
|
||||
`);
|
||||
|
||||
sortMenu.css({
|
||||
display: 'block',
|
||||
top: offset.top + thHeight + 'px',
|
||||
left: offset.left + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
$(document).click(function(event) {
|
||||
if (!$(event.target).closest('.sort-options, th').length) {
|
||||
$('#sort-options').hide();
|
||||
}
|
||||
});
|
||||
|
||||
window.fetchResults = fetchResults;
|
||||
window.showSortOptions = showSortOptions;
|
||||
});
|
||||
61
v-2/static/js/state.js
Normal file
61
v-2/static/js/state.js
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
49
v-2/static/js/subcontractor.js
Normal file
49
v-2/static/js/subcontractor.js
Normal 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();
|
||||
};
|
||||
12
v-2/static/js/validateFileInput.js
Normal file
12
v-2/static/js/validateFileInput.js
Normal file
@@ -0,0 +1,12 @@
|
||||
function validateFileInput() {
|
||||
const fileInput = document.querySelector('input[type="file"]');
|
||||
const filePath = fileInput.value;
|
||||
const allowedExtensions = /(\.xlsx|\.xls)$/i;
|
||||
|
||||
if (!allowedExtensions.exec(filePath)) {
|
||||
alert("Please upload a valid Excel file (.xlsx or .xls only).");
|
||||
fileInput.value = '';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
198
v-2/static/js/village.js
Normal file
198
v-2/static/js/village.js
Normal file
@@ -0,0 +1,198 @@
|
||||
window.onload = function () {
|
||||
document.getElementById('Village_Name').focus();
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
// STATE → DISTRICT
|
||||
$('#state_Id').change(function () {
|
||||
|
||||
var stateId = $(this).val();
|
||||
|
||||
if (stateId) {
|
||||
|
||||
$.ajax({
|
||||
url: '/get_districts/' + stateId,
|
||||
type: 'GET',
|
||||
|
||||
success: function (data) {
|
||||
|
||||
var districtDropdown = $('#district_Id');
|
||||
|
||||
districtDropdown.empty();
|
||||
districtDropdown.append('<option value="" disabled selected>Select District</option>');
|
||||
|
||||
data.forEach(function (district) {
|
||||
|
||||
districtDropdown.append(
|
||||
'<option value="' + district.id + '">' + district.name + '</option>'
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
districtDropdown.prop('disabled', false);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// DISTRICT → BLOCK
|
||||
$('#district_Id').change(function () {
|
||||
|
||||
var districtId = $(this).val();
|
||||
|
||||
if (districtId) {
|
||||
|
||||
$.ajax({
|
||||
url: '/get_blocks/' + districtId,
|
||||
type: 'GET',
|
||||
|
||||
success: function (data) {
|
||||
|
||||
var blockDropdown = $('#block_Id');
|
||||
|
||||
blockDropdown.empty();
|
||||
blockDropdown.append('<option value="" disabled selected>Select Block</option>');
|
||||
|
||||
data.forEach(function (block) {
|
||||
|
||||
blockDropdown.append(
|
||||
'<option value="' + block.id + '">' + block.name + '</option>'
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
blockDropdown.prop('disabled', false);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// VILLAGE NAME VALIDATION
|
||||
$('#Village_Name').on('input', function () {
|
||||
|
||||
var villageName = $(this).val();
|
||||
var validPattern = /^[A-Za-z ]*$/;
|
||||
|
||||
if (!validPattern.test(villageName)) {
|
||||
|
||||
$('#villageMessage')
|
||||
.text('Only letters and spaces are allowed!')
|
||||
.css('color', 'red');
|
||||
|
||||
$('#submitVillage').prop('disabled', true);
|
||||
|
||||
} else {
|
||||
|
||||
$('#villageMessage').text('');
|
||||
$('#submitVillage').prop('disabled', false);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// CHECK DUPLICATE VILLAGE
|
||||
$('#Village_Name, #block_Id').on('change keyup', function () {
|
||||
|
||||
var blockId = $('#block_Id').val();
|
||||
var villageName = $('#Village_Name').val().trim();
|
||||
|
||||
if (blockId && villageName) {
|
||||
|
||||
$.ajax({
|
||||
|
||||
url: '/check_village',
|
||||
type: 'POST',
|
||||
|
||||
data: {
|
||||
block_Id: blockId,
|
||||
Village_Name: villageName
|
||||
},
|
||||
|
||||
success: function (response) {
|
||||
|
||||
if (response.status === 'exists') {
|
||||
|
||||
$('#villageMessage')
|
||||
.text(response.message)
|
||||
.css('color', 'red');
|
||||
|
||||
$('#submitVillage').prop('disabled', true);
|
||||
|
||||
} else {
|
||||
|
||||
$('#villageMessage')
|
||||
.text(response.message)
|
||||
.css('color', 'green');
|
||||
|
||||
$('#submitVillage').prop('disabled', false);
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
error: function () {
|
||||
|
||||
$('#villageMessage')
|
||||
.text('Error checking village name')
|
||||
.css('color', 'red');
|
||||
|
||||
$('#submitVillage').prop('disabled', true);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// ADD VILLAGE
|
||||
$('#villageForm').submit(function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
$.ajax({
|
||||
|
||||
url: '/add_village',
|
||||
type: 'POST',
|
||||
data: $(this).serialize(),
|
||||
|
||||
success: function (response) {
|
||||
|
||||
if (response.status === 'success') {
|
||||
|
||||
alert('Village added successfully!');
|
||||
location.reload();
|
||||
|
||||
} else {
|
||||
|
||||
alert('Error adding village. Please try again.');
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
error: function () {
|
||||
|
||||
alert('An error occurred. Please try again.');
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
102
v-2/templates/activity_log.html
Normal file
102
v-2/templates/activity_log.html
Normal file
@@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Activity Logs</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Segoe UI", Tahoma, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
margin: 20px;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
form {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
input, button {
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
table {
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
border-collapse: collapse;
|
||||
background: white;
|
||||
box-shadow: 0 0 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
th, td {
|
||||
padding: 12px;
|
||||
border: 1px solid #ddd;
|
||||
text-align: center;
|
||||
}
|
||||
th {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
tr:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Activity Logs</h2>
|
||||
<form method="get" action="{{ url_for('activity_log') }}" class="filter-form">
|
||||
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" placeholder="Enter username" value="{{ username or '' }}">
|
||||
|
||||
<label for="start_date">Start Date:</label>
|
||||
<input type="date" id="start_date" name="start_date" value="{{ start_date or '' }}">
|
||||
|
||||
<label for="end_date">End Date:</label>
|
||||
<input type="date" id="end_date" name="end_date" value="{{ end_date or '' }}">
|
||||
|
||||
|
||||
<button type="submit" class="btn btn-primary">Filter</button>
|
||||
<!-- <button type="button" style="background-color: #6c757d;" onclick="resetFilter()">Reset</button> -->
|
||||
</form>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<th>User</th>
|
||||
<th>Action</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<td>{{ log.timestamp }}</td>
|
||||
<td>{{ log.user }}</td>
|
||||
<td>{{ log.action }}</td>
|
||||
<td>{{ log.details }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if logs|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="4">No logs found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
<script>
|
||||
function resetFilter() {
|
||||
window.location.href = "{{ url_for('activity_log') }}";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
99
v-2/templates/add_block.html
Normal file
99
v-2/templates/add_block.html
Normal file
@@ -0,0 +1,99 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Add Block</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/block.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Button Container to Center Buttons -->
|
||||
<div class="button-container">
|
||||
<button id="addButton" class="action-button">Add</button>
|
||||
<button id="displayButton" class="action-button">Display</button>
|
||||
</div>
|
||||
|
||||
<div id="addForm" style="display: none;">
|
||||
<div class="container">
|
||||
<div class="form-block">
|
||||
<h2>Create a New Block</h2>
|
||||
<form id="blockForm" method="POST">
|
||||
<!-- Select State Dropdown -->
|
||||
<label for="state_Id">State:</label>
|
||||
<select id="state_Id" name="state_Id" required>
|
||||
<option value="" disabled selected>Select State</option>
|
||||
{% for state in states %}
|
||||
<option value="{{ state[0] }}">{{ state[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<!-- Select District Dropdown -->
|
||||
<label for="district_Id">District:</label>
|
||||
<select id="district_Id" name="district_Id" required disabled>
|
||||
<option value="" disabled selected>Select District</option>
|
||||
</select>
|
||||
|
||||
<!-- Block Name Input -->
|
||||
<label for="block_Name">Enter Block Name:</label>
|
||||
<input type="text" id="block_Name" name="block_Name" placeholder="Block Name" required>
|
||||
<span id="blockMessage"></span>
|
||||
|
||||
<button type="submit" id="submitButton" disabled>Add Block</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="addTable" style="display: none;">
|
||||
<div class="search-container">
|
||||
<h2>Display Blocks</h2>
|
||||
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
|
||||
</div>
|
||||
<!-- Display Blocks -->
|
||||
<table id="sortableTable" border="1">
|
||||
<tr>
|
||||
<th>Block Sr no</th>
|
||||
<th class="sortable-header">Block Name
|
||||
<span class="sort-buttons">
|
||||
<span class="sort-asc">⬆️</span>
|
||||
<span class="sort-desc">⬇️</span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="sortable-header">District Name
|
||||
<span class="sort-buttons">
|
||||
<span class="sort-asc">⬆️</span>
|
||||
<span class="sort-desc">⬇️</span>
|
||||
</span>
|
||||
</th>
|
||||
<th>Update</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
{% for block in block_data %}
|
||||
<tr>
|
||||
<td>{{ block[0] }}</td>
|
||||
<td>{{ block[1] }}</td>
|
||||
<td>{{ block[3] }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('block.edit_block', block_id=block[0]) }}">
|
||||
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
|
||||
class="icon">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for('block.delete_block', block_id=block[0]) }}"
|
||||
onclick="return confirm('Are you sure you want to delete this block?');">
|
||||
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
|
||||
class="icon">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
{% endblock %}
|
||||
85
v-2/templates/add_district.html
Normal file
85
v-2/templates/add_district.html
Normal file
@@ -0,0 +1,85 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<title>Add District</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/district.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Button Container to Center Buttons -->
|
||||
<div class="button-container">
|
||||
<button id="addButton" class="action-button">Add</button>
|
||||
<button id="displayButton" class="action-button">Display</button>
|
||||
</div>
|
||||
|
||||
<div id="addForm" style="display: none;">
|
||||
<h2>Add District</h2>
|
||||
<form id="districtForm" method="POST">
|
||||
<label for="state_Id">State :</label>
|
||||
<select name="state_Id" id="state_Id" required>
|
||||
<option value="" disabled selected>Select State</option>
|
||||
{% for state in states %}
|
||||
<option value="{{ state[0] }}">{{ state[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>Enter District :</label>
|
||||
<input type="text" id="district_Name" name="district_Name" placeholder="District Name" required>
|
||||
<span id="districtMessage"></span>
|
||||
|
||||
<button type="submit" id="submitButton" disabled>Add District</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="addTable" style="display: none;">
|
||||
<div class="search-container">
|
||||
<h2>Display Districts</h2>
|
||||
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
|
||||
</div>
|
||||
|
||||
<table id="sortableTable" border="1">
|
||||
<tr>
|
||||
<th>District ID</th>
|
||||
<th class="sortable-header">
|
||||
District Name
|
||||
<span class="sort-buttons">
|
||||
<span class="sort-asc">⬆️</span>
|
||||
<span class="sort-desc">⬇️</span>
|
||||
</span></th>
|
||||
<th class="sortable-header">
|
||||
State Name
|
||||
<span class="sort-buttons">
|
||||
<span class="sort-asc">⬆️</span>
|
||||
<span class="sort-desc">⬇️</span>
|
||||
</span></th>
|
||||
|
||||
<th>Edit District</th>
|
||||
<th>Delete District</th>
|
||||
</tr>
|
||||
{% for district in districtdata %}
|
||||
<tr>
|
||||
<td>{{ district[0] }}</td>
|
||||
<td>{{ district[1] }}</td>
|
||||
<td>{{ district[2] }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('district.edit_district', district_id=district[0]) }}">
|
||||
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
|
||||
class="icon">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for('district.delete_district', district_id=district[0]) }}"
|
||||
onclick="return confirm('Are you sure you want to delete this district?')">
|
||||
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
|
||||
class="icon">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
{% endblock %}
|
||||
175
v-2/templates/add_gst_release.html
Normal file
175
v-2/templates/add_gst_release.html
Normal 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>✔</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 %}
|
||||
59
v-2/templates/add_hold_type.html
Normal file
59
v-2/templates/add_hold_type.html
Normal file
@@ -0,0 +1,59 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<title>Manage Hold Types</title>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<!-- <script src="{{ url_for('static', filename='js/hold_types.js') }}"></script> -->
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="button-container">
|
||||
<button id="addButton" class="action-button">Add</button>
|
||||
<button id="displayButton" class="action-button">Display</button>
|
||||
</div>
|
||||
|
||||
<div id="addForm">
|
||||
<h2>Add Hold Types</h2>
|
||||
<form id="holdTypeForm" method="POST" action="{{ url_for('hold_types.add_hold_type') }}">
|
||||
<label>Enter Hold Amount Type:</label>
|
||||
<input type="text" id="hold_type" name="hold_type" placeholder="Enter Type" required>
|
||||
<span id="holdTypeMessage"></span>
|
||||
<button type="submit" value="Add Hold Type" id="successPopup">Add Hold Type</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="addTable" style="display: none;">
|
||||
<div class="search-container">
|
||||
<h2>Hold Type List</h2>
|
||||
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
|
||||
</div>
|
||||
<table id="sortableTable" border="1">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th class="sortable-header">
|
||||
Hold Type
|
||||
<span class="sort-buttons">
|
||||
<span class="sort-asc">⬆️</span>
|
||||
<span class="sort-desc">⬇️</span>
|
||||
</span>
|
||||
</th>
|
||||
<th>Update</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
{% for htd in Hold_Types_data %}
|
||||
<tr>
|
||||
<td>{{ htd['hold_type_id'] }}</td>
|
||||
<td>{{ htd['hold_type'] }}</td>
|
||||
<td><a href="{{ url_for('hold_types.edit_hold_type', id=htd['hold_type_id']) }}">Edit</a></td>
|
||||
<td><button class="delete-button" data-id="{{ htd['hold_type_id'] }}">Delete</button></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<a href="/">Back to Dashboard</a>
|
||||
</div>
|
||||
</body>
|
||||
{% endblock %}
|
||||
|
||||
370
v-2/templates/add_invoice.html
Normal file
370
v-2/templates/add_invoice.html
Normal file
@@ -0,0 +1,370 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head xmlns="http://www.w3.org/1999/html">
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Add Invoice</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/invoice.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/invoice.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/holdAmount.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
{% if success == 'true' %}
|
||||
<div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
|
||||
✅ Invoice added successfully!
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<!-- Flash Messages -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="alert {{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<div class="button-container">
|
||||
<button id="addButton" class="action-button">Add</button>
|
||||
<button id="displayButton" class="action-button">Display</button>
|
||||
</div>
|
||||
|
||||
<div id="addForm" style="display: none;">
|
||||
<h2>Add Invoice</h2>
|
||||
|
||||
<form id="invoiceForm" action="{{ url_for('invoice.add_invoice') }}" method="POST">
|
||||
<div class="row1">
|
||||
<div>
|
||||
<label for="subcontractor">Subcontractor Name:</label>
|
||||
<input type="text" id="subcontractor" name="subcontractor" required autocomplete="off"/>
|
||||
<input type="hidden" id="subcontractor_id" name="subcontractor_id"/>
|
||||
<div id="subcontractor_list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row2">
|
||||
<div>
|
||||
<label for="village">Village Name:</label>
|
||||
<select id="village" name="village" required>
|
||||
<option value="">-- Select Village --</option>
|
||||
{% for village in villages %}
|
||||
<option value="{{ village.Village_Name }}">{{ village.Village_Name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="pmc_no">PMC No:</label>
|
||||
<input type="text" id="pmc_no" name="pmc_no" required/>
|
||||
<div id="pmc_no_list" class="autocomplete-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row2">
|
||||
<div>
|
||||
<label for="work_type">Work Type:</label>
|
||||
<input type="text" id="work_type" name="work_type" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="invoice_details">Invoice Details:</label>
|
||||
<textarea id="invoice_details" name="invoice_details" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row2">
|
||||
<div>
|
||||
<label for="invoice_no">Invoice No:</label>
|
||||
<input type="text" id="invoice_no" name="invoice_no" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="invoice_date">Invoice Date:</label>
|
||||
<input type="date" id="invoice_date" name="invoice_date" required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row3">
|
||||
<div>
|
||||
<label for="basic_amount">Basic Amount:</label>
|
||||
<input type="number" step="0.01" id="basic_amount" name="basic_amount" placeholder="₹ - 00.00" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="debit_amount">Debit Amount:</label>
|
||||
<input type="number" step="0.01" id="debit_amount" name="debit_amount" placeholder="₹ - 00.00" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="after_debit_amount">After Debit Amount:</label>
|
||||
<input type="number" step="0.01" id="after_debit_amount" name="after_debit_amount" placeholder="₹ - 00.00" readonly required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row3">
|
||||
<div class="percentage-field">
|
||||
<label for="gst_percentage">GST %:</label>
|
||||
<input type="number" step="0.01" id="gst_percentage" name="gst_percentage" placeholder="%"/>
|
||||
<label for="gst_amount">GST Amount:</label>
|
||||
<input type="number" step="0.01" id="gst_amount" name="gst_amount" placeholder="₹ - 00.00" readonly required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="amount">Amount:</label>
|
||||
<input type="number" step="0.01" id="amount" name="amount" placeholder="₹ - 00.00" readonly required/>
|
||||
</div>
|
||||
<div class="percentage-field">
|
||||
<label for="tds_percentage">TDS %:</label>
|
||||
<input type="number" step="0.01" id="tds_percentage" name="tds_percentage" placeholder="%"/>
|
||||
<label for="tds_amount">TDS Amount:</label>
|
||||
<input type="number" step="0.01" id="tds_amount" name="tds_amount" placeholder="₹ - 00.00" readonly required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row3">
|
||||
<div class="percentage-field">
|
||||
<label for="sd_percentage">SD %:</label>
|
||||
<input type="number" step="0.01" id="sd_percentage" name="sd_percentage" placeholder="%"/>
|
||||
<label for="sd_amount">SD Amount:</label>
|
||||
<input type="number" step="0.01" id="sd_amount" name="sd_amount" placeholder="₹ - 00.00" readonly required>
|
||||
</div>
|
||||
<div class="percentage-field">
|
||||
<label for="commission_percentage">On Commission %:</label>
|
||||
<input type="number" step="0.01" id="commission_percentage" name="commission_percentage" placeholder="%"/>
|
||||
<label for="on_commission">On Commission:</label>
|
||||
<input type="number" step="0.01" id="on_commission" name="on_commission" placeholder="₹ - 00.00" readonly required>
|
||||
</div>
|
||||
<div class="percentage-field">
|
||||
<label for="hydro_percentage">Hydro Testing %:</label>
|
||||
<input type="number" step="0.01" id="hydro_percentage" name="hydro_percentage" placeholder="%"/>
|
||||
<label for="hydro_testing">Hydro Testing:</label>
|
||||
<input type="number" step="0.01" id="hydro_testing" name="hydro_testing" placeholder="₹ - 00.00" readonly required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hold-row">
|
||||
<button type="button" id="add_hold_amount" class="button">+ Add Hold Amount</button>
|
||||
</div>
|
||||
|
||||
<!-- Dynamically added hold amount fields -->
|
||||
<div id="hold_amount_container"></div>
|
||||
|
||||
<div class="row2">
|
||||
<div>
|
||||
<label for="gst_sd_amount">GST SD Amount:</label>
|
||||
<input type="number" step="0.01" id="gst_sd_amount" name="gst_sd_amount" placeholder="₹ - 00.00" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="final_amount">Final Amount:</label>
|
||||
<input type="number" step="0.01" id="final_amount" name="final_amount" placeholder="₹ - 00.00" required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="addTable" style="display: none;">
|
||||
<!-- Invoice Table Section -->
|
||||
<div class="search-container">
|
||||
<h2>Invoice List</h2>
|
||||
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
|
||||
{% if invoices %}
|
||||
<table class="invoice-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Invoice Id</th>
|
||||
<th>SubContractor Name</th>
|
||||
<th>PMC No</th>
|
||||
<th>Village</th>
|
||||
<th>Work Type</th>
|
||||
<th>Invoice Details</th>
|
||||
<th>Invoice Date</th>
|
||||
<th>Invoice No</th>
|
||||
<th>Basic Amount</th>
|
||||
<th>Debit Amount</th>
|
||||
<th>After Debit Amount</th>
|
||||
<th>Amount</th>
|
||||
<th>GST Amount</th>
|
||||
<th>TDS Amount</th>
|
||||
<th>SD Amount</th>
|
||||
<th>On Commission</th>
|
||||
<th>Hydro Testing</th>
|
||||
<th>GST SD Amount</th>
|
||||
<th>Final Amount</th>
|
||||
<th>Update</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for invoice in invoices %}
|
||||
<tr>
|
||||
<td>{{ invoice.Invoice_Id }}</td>
|
||||
<td>{{ invoice.Contractor_Name }}</td>
|
||||
<td>{{ invoice.PMC_No }}</td>
|
||||
<td>{{ invoice.Village_Name or 'N/A' }}</td>
|
||||
<td>{{ invoice.Work_Type }}</td>
|
||||
<td>{{ invoice.Invoice_Details }}</td>
|
||||
<td>{{ invoice.Invoice_Date }}</td>
|
||||
<td>{{ invoice.Invoice_No }}</td>
|
||||
<td>{{ invoice.Basic_Amount }}</td>
|
||||
<td>{{ invoice.Debit_Amount }}</td>
|
||||
<td>{{ invoice.After_Debit_Amount }}</td>
|
||||
<td>{{ invoice.Amount }}</td>
|
||||
<td>{{ invoice.GST_Amount }}</td>
|
||||
<td>{{ invoice.TDS_Amount }}</td>
|
||||
<td>{{ invoice.SD_Amount }}</td>
|
||||
<td>{{ invoice.On_Commission }}</td>
|
||||
<td>{{ invoice.Hydro_Testing }}</td>
|
||||
<td>{{ invoice.GST_SD_Amount }}</td>
|
||||
<td>{{ invoice.Final_Amount }}</td>
|
||||
<td>
|
||||
<!-- Edit -->
|
||||
<a href="{{ url_for('invoice.edit_invoice', invoice_id=invoice.Invoice_Id) }}">
|
||||
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit" class="icon">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Delete -->
|
||||
<a href="{{ url_for('invoice.delete_invoice_route', invoice_id=invoice.Invoice_Id) }}" onclick="return confirm('Are you sure?')">
|
||||
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete" class="icon">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No invoices found.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Get all the input fields
|
||||
const basicAmount = document.getElementById('basic_amount');
|
||||
const debitAmount = document.getElementById('debit_amount');
|
||||
const afterDebitAmount = document.getElementById('after_debit_amount');
|
||||
const amount = document.getElementById('amount');
|
||||
|
||||
// Percentage fields
|
||||
const gstPercentage = document.getElementById('gst_percentage');
|
||||
const gstAmount = document.getElementById('gst_amount');
|
||||
const tdsPercentage = document.getElementById('tds_percentage');
|
||||
const tdsAmount = document.getElementById('tds_amount');
|
||||
const sdPercentage = document.getElementById('sd_percentage');
|
||||
const sdAmountInput = document.getElementById('sd_amount');
|
||||
const commissionPercentage = document.getElementById('commission_percentage');
|
||||
const onCommission = document.getElementById('on_commission');
|
||||
const hydroPercentage = document.getElementById('hydro_percentage');
|
||||
const hydroTesting = document.getElementById('hydro_testing');
|
||||
const gstSdAmount = document.getElementById('gst_sd_amount');
|
||||
const finalAmount = document.getElementById('final_amount');
|
||||
|
||||
// Calculate after debit amount when basic or debit amount changes
|
||||
function calculateAfterDebitAmount() {
|
||||
const basic = parseFloat(basicAmount.value) || 0;
|
||||
const debit = parseFloat(debitAmount.value) || 0;
|
||||
const afterDebit = basic - debit;
|
||||
afterDebitAmount.value = afterDebit.toFixed(2);
|
||||
calculateGST();
|
||||
}
|
||||
|
||||
// Calculate GST and Amount
|
||||
function calculateGST() {
|
||||
const baseAmount = parseFloat(afterDebitAmount.value) || 0;
|
||||
|
||||
if (gstPercentage.value) {
|
||||
const gstPerc = parseFloat(gstPercentage.value) || 0;
|
||||
const gstAmt = (baseAmount * gstPerc) / 100;
|
||||
gstAmount.value = gstAmt.toFixed(2);
|
||||
gstSdAmount.value = gstAmt.toFixed(2);
|
||||
|
||||
// Calculate Amount (After Debit + GST)
|
||||
amount.value = (baseAmount + gstAmt).toFixed(2);
|
||||
} else {
|
||||
amount.value = baseAmount.toFixed(2);
|
||||
}
|
||||
|
||||
calculateOtherDeductions();
|
||||
}
|
||||
|
||||
// Calculate other deductions (TDS, SD, Commission, Hydro)
|
||||
function calculateOtherDeductions() {
|
||||
const baseAmount = parseFloat(afterDebitAmount.value) || 0;
|
||||
|
||||
// Calculate TDS
|
||||
if (tdsPercentage.value) {
|
||||
const tdsPerc = parseFloat(tdsPercentage.value) || 0;
|
||||
const tdsAmt = (baseAmount * tdsPerc) / 100;
|
||||
tdsAmount.value = tdsAmt.toFixed(2);
|
||||
}
|
||||
|
||||
// Calculate SD
|
||||
if (sdPercentage.value) {
|
||||
const sdPerc = parseFloat(sdPercentage.value) || 0;
|
||||
const sdAmt = (baseAmount * sdPerc) / 100;
|
||||
sdAmountInput.value = sdAmt.toFixed(2);
|
||||
}
|
||||
|
||||
// Calculate Commission
|
||||
if (commissionPercentage.value) {
|
||||
const commPerc = parseFloat(commissionPercentage.value) || 0;
|
||||
const commAmt = (baseAmount * commPerc) / 100;
|
||||
onCommission.value = commAmt.toFixed(2);
|
||||
}
|
||||
|
||||
// Calculate Hydro Testing
|
||||
if (hydroPercentage.value) {
|
||||
const hydroPerc = parseFloat(hydroPercentage.value) || 0;
|
||||
const hydroAmt = (baseAmount * hydroPerc) / 100;
|
||||
hydroTesting.value = hydroAmt.toFixed(2);
|
||||
}
|
||||
|
||||
calculateFinalAmount();
|
||||
}
|
||||
|
||||
// Calculate final amount
|
||||
function calculateFinalAmount() {
|
||||
const amt = parseFloat(amount.value) || 0;
|
||||
const tds = parseFloat(tdsAmount.value) || 0;
|
||||
const sd = parseFloat(sdAmountInput.value) || 0;
|
||||
const commission = parseFloat(onCommission.value) || 0;
|
||||
const hydro = parseFloat(hydroTesting.value) || 0;
|
||||
const gstSd = parseFloat(gstSdAmount.value) || 0;
|
||||
|
||||
// Get hold amounts
|
||||
let totalHold = 0;
|
||||
document.querySelectorAll('input[name="hold_amount[]"]').forEach(input => {
|
||||
totalHold += parseFloat(input.value) || 0;
|
||||
});
|
||||
|
||||
// Final Amount = Amount - TDS - SD - Commission - Hydro - GST SD - Hold Amounts
|
||||
const final = amt - tds - sd - commission - hydro - gstSd - totalHold;
|
||||
finalAmount.value = final.toFixed(2);
|
||||
}
|
||||
|
||||
// Add event listeners
|
||||
basicAmount.addEventListener('input', calculateAfterDebitAmount);
|
||||
debitAmount.addEventListener('input', calculateAfterDebitAmount);
|
||||
|
||||
// Percentage fields
|
||||
gstPercentage.addEventListener('input', calculateGST);
|
||||
tdsPercentage.addEventListener('input', calculateOtherDeductions);
|
||||
sdPercentage.addEventListener('input', calculateOtherDeductions);
|
||||
commissionPercentage.addEventListener('input', calculateOtherDeductions);
|
||||
hydroPercentage.addEventListener('input', calculateOtherDeductions);
|
||||
|
||||
// Listen for changes in hold amounts
|
||||
document.addEventListener('holdAmountChanged', calculateFinalAmount);
|
||||
});
|
||||
|
||||
// Optional JS for auto-hiding flash
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('.alert').forEach(el => el.style.display = 'none');
|
||||
}, 5000);
|
||||
</script>
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
{% endblock %}
|
||||
193
v-2/templates/add_payment.html
Normal file
193
v-2/templates/add_payment.html
Normal file
@@ -0,0 +1,193 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Add Payment</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/invoice.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="button-container">
|
||||
<button id="addButton" class="action-button">Add</button>
|
||||
<button id="displayButton" class="action-button">Display</button>
|
||||
</div>
|
||||
|
||||
<div id="addForm" style="display: none;">
|
||||
<h2>Add Payment</h2>
|
||||
|
||||
<form action="/add_payment" method="POST" onsubmit="showSuccessAlert(event)">
|
||||
<div class="row1">
|
||||
<div>
|
||||
<label for="subcontractor">Subcontractor Name:</label>
|
||||
<input type="text" id="subcontractor" name="subcontractor" required autocomplete="off"/>
|
||||
<input type="hidden" id="subcontractor_id" name="subcontractor_id"/>
|
||||
<div id="subcontractor_list" class="autocomplete-items"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="PMC_No">PMC No:</label><br>
|
||||
<select id="PMC_No" name="PMC_No" required>
|
||||
<option value="">Select PMC No</option>
|
||||
</select><br><br>
|
||||
|
||||
<label for="invoice_No">Invoice No:</label><br>
|
||||
<input type="number" step="0.01" id="invoice_No" name="invoice_No" ><br><br>
|
||||
|
||||
<label for="Payment_Amount">Amount:</label><br>
|
||||
<input type="number" step="0.01" id="Payment_Amount" name="Payment_Amount" required oninput="calculateTDSAndTotal()"><br><br>
|
||||
|
||||
<label for="TDS_Percentage">TDS Percentage (%):</label><br>
|
||||
<input type="number" step="0.01" id="TDS_Percentage" name="TDS_Percentage" oninput="calculateTDSAndTotal()"><br><br>
|
||||
|
||||
<label for="TDS_Payment_Amount">TDS Amount:</label><br>
|
||||
<input type="number" step="0.01" id="TDS_Payment_Amount" name="TDS_Payment_Amount" required readonly><br><br>
|
||||
|
||||
<label for="total_amount">Total Amount:</label><br>
|
||||
<input type="number" step="0.01" id="total_amount" name="total_amount" required readonly><br><br>
|
||||
|
||||
|
||||
<label for="utr">UTR:</label><br>
|
||||
<input type="text" id="utr" name="utr"><br><br>
|
||||
|
||||
<button type="submit">Submit Payment</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="successPopup" class="success-popup">
|
||||
<i>✔</i> Payment added successfully!
|
||||
</div>
|
||||
|
||||
<div id="addTable" style="display: none;">
|
||||
<div class="search-container">
|
||||
<h2>Payment History</h2>
|
||||
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
|
||||
</div>
|
||||
<table id="sortableTable" border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sortable-header">Payment ID</th>
|
||||
<th class="sortable-header">PMC No</th>
|
||||
<th>Invoice No</th>
|
||||
<th>Payment Amount</th>
|
||||
<th>TDS Amount</th>
|
||||
<th>Total Amount</th>
|
||||
<th>UTR</th>
|
||||
<th>Update</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for payment in payments %}
|
||||
<tr>
|
||||
<td>{{ payment[0] }}</td>
|
||||
<td>{{ payment[1] }}</td>
|
||||
<td>{{ payment[2] }}</td>
|
||||
<td>{{ payment[3] }}</td>
|
||||
<td>{{ payment[4] }}</td>
|
||||
<td>{{ payment[5] }}</td>
|
||||
<td>{{ payment[6] }}</td>
|
||||
<td><a href="/edit_payment/{{ payment[0] }}"><img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit" class="icon"></a></td>
|
||||
<td><a href="/delete_payment/{{ payment[0] }}" onclick="return confirm('Are you sure you want to delete this payment?')"><img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete" class="icon"></a></td>
|
||||
</tr>
|
||||
<!-- <tr>
|
||||
<td>{{ payment['Payment_Id'] }}</td>
|
||||
<td>{{ payment['PMC_No'] }}</td>
|
||||
<td>{{ payment['invoice_no'] }}</td>
|
||||
<td>{{ payment['Payment_Amount'] }}</td>
|
||||
<td>{{ payment['TDS_Payment_Amount'] }}</td>
|
||||
<td>{{ payment['Total_Amount'] }}</td>
|
||||
<td>{{ payment['UTR'] }}</td>
|
||||
<td><a href="/edit_payment/{{ payment['Payment_Id'] }}"><img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit" class="icon"></a></td>
|
||||
<td><a href="/delete_payment/{{ payment['Payment_Id'] }}" onclick="return confirm('Are you sure you want to delete this payment?')"><img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete" class="icon"></a></td>
|
||||
</tr> -->
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById("subcontractor").addEventListener("input", function () {
|
||||
const query = this.value;
|
||||
const list = document.getElementById("subcontractor_list");
|
||||
|
||||
if (query.length < 2) {
|
||||
list.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/search_subcontractor?query=${encodeURIComponent(query)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
list.innerHTML = '';
|
||||
data.results.forEach(item => {
|
||||
const div = document.createElement("div");
|
||||
div.setAttribute("data-id", item.id);
|
||||
div.textContent = item.name;
|
||||
list.appendChild(div);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("subcontractor_list").addEventListener("click", function (e) {
|
||||
const selectedId = e.target.getAttribute("data-id");
|
||||
const selectedName = e.target.textContent;
|
||||
|
||||
if (selectedId) {
|
||||
document.getElementById("subcontractor_id").value = selectedId;
|
||||
document.getElementById("subcontractor").value = selectedName;
|
||||
document.getElementById("subcontractor_list").innerHTML = ""; // hide the list
|
||||
|
||||
console.log("Contractor id is", selectedId);
|
||||
|
||||
// Fetch PMC numbers
|
||||
fetch(`/get_pmc_nos_by_subcontractor/${encodeURIComponent(selectedId)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log("Fetched PMC Nos:", data.pmc_nos);
|
||||
const pmcDropdown = document.getElementById("PMC_No");
|
||||
pmcDropdown.innerHTML = "";
|
||||
|
||||
const defaultOption = document.createElement("option");
|
||||
defaultOption.value = "";
|
||||
defaultOption.textContent = "Select PMC No";
|
||||
pmcDropdown.appendChild(defaultOption);
|
||||
|
||||
data.pmc_nos.forEach(pmc => {
|
||||
const option = document.createElement("option");
|
||||
option.value = pmc;
|
||||
option.textContent = pmc;
|
||||
pmcDropdown.appendChild(option);
|
||||
});
|
||||
|
||||
if (data.pmc_nos.length === 0) {
|
||||
alert("No PMC Nos found for this subcontractor.");
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error fetching PMC Nos:", error);
|
||||
alert("Failed to fetch PMC numbers.");
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function calculateTDSAndTotal() {
|
||||
const amount = parseFloat(document.getElementById("Payment_Amount").value) || 0;
|
||||
const tdsPercent = parseFloat(document.getElementById("TDS_Percentage").value) || 0;
|
||||
|
||||
const tdsAmount = (amount * tdsPercent / 100).toFixed(2);
|
||||
const totalAmount = (amount - tdsAmount).toFixed(2);
|
||||
|
||||
document.getElementById("TDS_Payment_Amount").value = tdsAmount;
|
||||
document.getElementById("total_amount").value = totalAmount;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<script src="{{ url_for('static', filename='js/showSuccessAlert.js') }}"></script>
|
||||
</body>
|
||||
{% endblock %}
|
||||
147
v-2/templates/add_purchase_order.html
Normal file
147
v-2/templates/add_purchase_order.html
Normal file
@@ -0,0 +1,147 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<title>Manage Purchases</title>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="button-container">
|
||||
<button id="addButton" class="action-button">Add Purchase</button>
|
||||
<button id="displayButton" class="action-button">Display Purchases</button>
|
||||
</div>
|
||||
|
||||
<!-- ADD PURCHASE FORM -->
|
||||
<!-- ADD PURCHASE FORM -->
|
||||
<div id="addForm" style="display: none;">
|
||||
<h2>Add Purchase Entry</h2>
|
||||
<form action="/add_purchase_order" method="POST">
|
||||
<label for="purchase_date">Purchase Date:</label><br>
|
||||
<input type="date" id="purchase_date" name="purchase_date" required><br><br>
|
||||
|
||||
<label for="supplier_name">Supplier Name:</label><br>
|
||||
<input type="text" id="supplier_name" name="supplier_name" required><br><br>
|
||||
|
||||
<label for="Purchase Order No">Purchase Order No:</label><br>
|
||||
<input type="text" name="purchase_order_no" required><br><br>
|
||||
|
||||
<label for="unit">Item Name:</label><br>
|
||||
<input type="text" id="item_name" name="item_name" required><br><br>
|
||||
|
||||
<label for="quantity">Quantity:</label><br>
|
||||
<input type="number" id="quantity" name="quantity" step="1" required><br><br>
|
||||
|
||||
<label for="unit">Unit:</label><br>
|
||||
<input type="text" id="unit" name="unit" required><br><br>
|
||||
|
||||
<label for="rate">Rate:</label><br>
|
||||
<input type="number" step="0.01" id="rate" name="rate" required><br><br>
|
||||
|
||||
<label for="amount">Amount:</label><br>
|
||||
<input type="number" step="0.01" id="amount" name="amount" readonly><br><br>
|
||||
|
||||
<!-- GST input -->
|
||||
<label for="gst_percent">GST %:</label><br>
|
||||
<input type="number" step="0.01" id="gst_percent"><br><br>
|
||||
|
||||
<label>GST Amount:</label><br>
|
||||
<input type="number" step="0.01" id="GST_Amount" name="GST_Amount" readonly><br><br>
|
||||
|
||||
<!-- TDS input -->
|
||||
<label for="tds_percent">TDS %:</label><br>
|
||||
<input type="number" step="0.01" id="tds_percent"><br><br>
|
||||
|
||||
<label>TDS:</label><br>
|
||||
<input type="number" step="0.01" id="TDS" name="TDS" readonly><br><br>
|
||||
|
||||
<label>Final Amount:</label><br>
|
||||
<input type="number" step="0.01" id="final_amount" name="final_amount" readonly><br><br>
|
||||
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- DISPLAY TABLE -->
|
||||
<div id="displayTable" style="display: none;">
|
||||
<h2>Purchase List</h2>
|
||||
<table border="1">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Purchase Date</th>
|
||||
<th>Supplier Name</th>
|
||||
<th>Purchase Order No</th>
|
||||
<th>Item Name</th>
|
||||
<th>Quantity</th>
|
||||
<th>Unit</th>
|
||||
<th>Rate</th>
|
||||
<th>Amount</th>
|
||||
<th>GST Amount</th>
|
||||
<th>TDS</th>
|
||||
<th>Final Amount</th>
|
||||
<th>Edit</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
{% for purchase in purchases %}
|
||||
<tr>
|
||||
<td>{{ purchase['purchase_id'] }}</td>
|
||||
<td>{{ purchase['purchase_date'] }}</td>
|
||||
<td>{{ purchase['supplier_name'] }}</td>
|
||||
<td>{{ purchase['purchase_order_no'] }}</td>
|
||||
<td>{{ purchase['item_name'] }}</td>
|
||||
<td>{{ purchase['quantity'] }}</td>
|
||||
<td>{{ purchase['unit'] }}</td>
|
||||
<td>{{ purchase['rate'] }}</td>
|
||||
<td>{{ purchase['amount'] }}</td>
|
||||
<td>{{ purchase['GST_Amount'] }}</td>
|
||||
<td>{{ purchase['TDS'] }}</td>
|
||||
<td>{{ purchase['final_amount'] }}</td>
|
||||
<td><a href="{{ url_for('update_purchase', id=purchase['purchase_id']) }}">Edit</a></td>
|
||||
<td>
|
||||
<form method="POST" action="{{ url_for('delete_purchase', id=purchase['purchase_id']) }}" style="display:inline;">
|
||||
<button type="submit" onclick="return confirm('Are you sure?')">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#addButton').click(function() {
|
||||
$('#addForm').toggle();
|
||||
});
|
||||
$('#displayButton').click(function() {
|
||||
$('#displayTable').toggle();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
function calculateAmounts() {
|
||||
const qty = parseFloat(document.getElementById('quantity').value) || 0;
|
||||
const rate = parseFloat(document.getElementById('rate').value) || 0;
|
||||
const gstPercent = parseFloat(document.getElementById('gst_percent').value) || 0;
|
||||
const tdsPercent = parseFloat(document.getElementById('tds_percent').value) || 0;
|
||||
|
||||
const amount = qty * rate;
|
||||
document.getElementById('amount').value = amount.toFixed(2);
|
||||
|
||||
const gstAmount = (gstPercent / 100) * amount;
|
||||
document.getElementById('GST_Amount').value = gstAmount.toFixed(2);
|
||||
|
||||
const tdsAmount = (tdsPercent / 100) * amount;
|
||||
document.getElementById('TDS').value = tdsAmount.toFixed(2);
|
||||
|
||||
const finalAmount = amount + gstAmount - tdsAmount;
|
||||
document.getElementById('final_amount').value = finalAmount.toFixed(2);
|
||||
}
|
||||
|
||||
// Attach events
|
||||
document.getElementById('quantity').addEventListener('input', calculateAmounts);
|
||||
document.getElementById('rate').addEventListener('input', calculateAmounts);
|
||||
document.getElementById('gst_percent').addEventListener('input', calculateAmounts);
|
||||
document.getElementById('tds_percent').addEventListener('input', calculateAmounts);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
{% endblock %}
|
||||
74
v-2/templates/add_state.html
Normal file
74
v-2/templates/add_state.html
Normal file
@@ -0,0 +1,74 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
|
||||
<head>
|
||||
<title>Add State</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/state.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Button Container to Center Buttons -->
|
||||
<div class="button-container">
|
||||
<button id="addButton" class="action-button">Add</button>
|
||||
<button id="displayButton" class="action-button">Display</button>
|
||||
</div>
|
||||
|
||||
<div id="addForm" style="display: none;">
|
||||
<h2>Add State</h2>
|
||||
<form id="stateForm" method="POST">
|
||||
<label>Enter State :</label>
|
||||
<input type="text" id="state_Name" name="state_Name" placeholder="State Name" required>
|
||||
<span id="stateMessage"></span>
|
||||
<button type="submit" id="submitButton" disabled>Add State</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="addTable" style="display: none;">
|
||||
<div class="search-container">
|
||||
<h2>Display States</h2>
|
||||
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
|
||||
</div>
|
||||
<script>
|
||||
console.log(statedata)
|
||||
</script>
|
||||
<table id="sortableTable" border="1">
|
||||
<tr>
|
||||
<th>State ID</th>
|
||||
<th class="sortable-header">
|
||||
State Name
|
||||
<span class="sort-buttons">
|
||||
<span class="sort-asc">⬆️</span>
|
||||
<span class="sort-desc">⬇️</span>
|
||||
</span>
|
||||
</th>
|
||||
<th>Update State</th>
|
||||
<th>Delete State</th>
|
||||
</tr>
|
||||
{% for state in statedata %}
|
||||
<tr>
|
||||
<td>{{ state[0] }}</td>
|
||||
<td>{{ state[1]}}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('state.editState', id=state[0]) }}">
|
||||
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
|
||||
class="icon">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for('state.deleteState', id=state[0]) }}"
|
||||
onclick="return confirm('Are you sure you want to delete this state?')">
|
||||
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
|
||||
class="icon">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
{% endblock %}
|
||||
127
v-2/templates/add_subcontractor.html
Normal file
127
v-2/templates/add_subcontractor.html
Normal file
@@ -0,0 +1,127 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>SubContractor</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/subcontractor.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Button Container to Center Buttons -->
|
||||
<div class="button-container">
|
||||
<button id="addButton" class="action-button">Add</button>
|
||||
<button id="displayButton" class="action-button">Display</button>
|
||||
</div>
|
||||
|
||||
<div id="addForm" style="display: none;">
|
||||
|
||||
<h2>Add Sub-Contractor</h2>
|
||||
<form name="subcontractorForm" method="POST">
|
||||
|
||||
<label>Enter Contractor Name :</label>
|
||||
<input type="text" id="Contractor_Name" name="Contractor_Name" required onkeyup="validateInput()"><br>
|
||||
|
||||
<label>Address :</label>
|
||||
<!-- <input type="text" name="Address" required>-->
|
||||
<textarea id="Address" name="Address" required></textarea>
|
||||
<br>
|
||||
|
||||
<label>Mobile No :</label>
|
||||
<input type="text" id="Mobile_No" name="Mobile_No" required onkeyup="validateInput()" maxlength="10" placeholder="Ex - 9091012011">
|
||||
<span class="error" id="mobileError" style="color: red;"></span><br>
|
||||
|
||||
<label>PAN No :</label>
|
||||
<input type="text" id="PAN_No" name="PAN_No" required onkeyup="validateInput()" maxlength="10" placeholder="Ex - ABCDE1234F">
|
||||
<span class="error" id="panError" style="color: red;"></span><br>
|
||||
|
||||
<label>Email :</label>
|
||||
<input type="email" id="Email" name="Email" required onkeyup="validateInput()" placeholder="Ex - user@example.com">
|
||||
<span class="error" id="emailError" style="color: red;"></span><br>
|
||||
|
||||
<label>Gender :</label>
|
||||
<select name="Gender" required>
|
||||
<option value="Male">Male</option>
|
||||
<option value="Female">Female</option>
|
||||
<option value="Other">Other</option>
|
||||
</select><br>
|
||||
|
||||
<label>GST Registration Type :</label>
|
||||
<input type="text" name="GST_Registration_Type" ><br>
|
||||
|
||||
<label>GST No :</label>
|
||||
<input type="text" id="GST_No" name="GST_No" placeholder="Ex - 27AAACL5602N1ZE" ><br>
|
||||
|
||||
<label>Generated Password :</label>
|
||||
<input type="text" id="Contractor_password" name="Contractor_password" >
|
||||
<span class="error" id="passwordError" style="color: red;"></span><br>
|
||||
|
||||
<button type="submit" id="submitBtn" disabled>Submit</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="addTable" style="display: none;">
|
||||
<div class="search-container">
|
||||
<h2>Display SubContractor</h2>
|
||||
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
|
||||
</div>
|
||||
<table id="sortableTable" border="1">
|
||||
|
||||
<tr>
|
||||
<th>Contractor ID</th>
|
||||
<th class="sortable-header">Contractor Name
|
||||
<span class="sort-buttons">
|
||||
<span class="sort-asc">⬆️</span>
|
||||
<span class="sort-desc">⬇️</span>
|
||||
</span>
|
||||
|
||||
</th>
|
||||
<th>Address</th>
|
||||
<th>Mobile No</th>
|
||||
<th>PAN No</th>
|
||||
<th>Email</th>
|
||||
<th>Gender</th>
|
||||
<th>GST Registration Type</th>
|
||||
<th class="sortable-header">GST No
|
||||
<span class="sort-buttons">
|
||||
<span class="sort-asc">⬆️</span>
|
||||
<span class="sort-desc">⬇️</span>
|
||||
</span>
|
||||
</th>
|
||||
<th>Update Contractor</th>
|
||||
<th>Delete Contractor</th>
|
||||
</tr>
|
||||
{% for subc in subcontractor %}
|
||||
<tr>
|
||||
<td>{{ subc[0] }}</td>
|
||||
<td>{{ subc[1] }}</td>
|
||||
<td>{{ subc[2] }}</td>
|
||||
<td>{{ subc[3] }}</td>
|
||||
<td>{{ subc[4] }}</td>
|
||||
<td>{{ subc[5] }}</td>
|
||||
<td>{{ subc[6] }}</td>
|
||||
<td>{{ subc[7] }}</td>
|
||||
<td>{{ subc[8] }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('subcontractor.edit_subcontractor', id=subc[0]) }}">
|
||||
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
|
||||
class="icon">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{# <a href="{{ url_for('subcontractor.deleteSubContractor', id=subc[0]) }}"#}
|
||||
{# onclick="return confirm('Are you sure you want to delete?')">#}
|
||||
{# <img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"#}
|
||||
{# class="icon">#}
|
||||
{# </a>#}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
{% endblock %}
|
||||
99
v-2/templates/add_village.html
Normal file
99
v-2/templates/add_village.html
Normal file
@@ -0,0 +1,99 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Village Management</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/village.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/search_on_table.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Button Container to Center Buttons -->
|
||||
<div class="button-container">
|
||||
<button id="addButton" class="action-button">Add</button>
|
||||
<button id="displayButton" class="action-button">Display</button>
|
||||
</div>
|
||||
|
||||
<div id="addForm" style="display: none;">
|
||||
<div class="container">
|
||||
<div class="form-block">
|
||||
<h2>Add a New Village</h2>
|
||||
<form id="villageForm" method="POST">
|
||||
<label for="state_Id">State:</label>
|
||||
<select id="state_Id" name="state_Id" required>
|
||||
<option value="" disabled selected>Select State</option>
|
||||
{% for state in states %}
|
||||
<option value="{{ state[0] }}">{{ state[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label for="district_Id">District:</label>
|
||||
<select id="district_Id" name="district_Id" required disabled>
|
||||
<option value="" disabled selected>Select District</option>
|
||||
</select>
|
||||
|
||||
<label for="block_Id">Block:</label>
|
||||
<select id="block_Id" name="block_Id" required disabled>
|
||||
<option value="" disabled selected>Select Block</option>
|
||||
</select>
|
||||
|
||||
<label for="Village_Name">Village Name:</label>
|
||||
<input type="text" id="Village_Name" name="Village_Name" placeholder="Enter Village Name" required>
|
||||
<span id="villageMessage"></span>
|
||||
|
||||
<button type="submit" id="submitVillage" disabled>Add Village</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="addTable" style="display: none;">
|
||||
<div class="search-container">
|
||||
<h2>Display Villages</h2>
|
||||
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
|
||||
</div>
|
||||
|
||||
<table id="sortableTable" border="1">
|
||||
<tr>
|
||||
<th>Village Sr No</th>
|
||||
<th class="sortable-header">
|
||||
Village Name
|
||||
<span class="sort-buttons">
|
||||
<span class="sort-asc">⬆️</span>
|
||||
<span class="sort-desc">⬇️</span>
|
||||
</span></th>
|
||||
<th class="sortable-header">Block Name
|
||||
<span class="sort-buttons">
|
||||
<span class="sort-asc">⬆️</span>
|
||||
<span class="sort-desc">⬇️</span>
|
||||
</span></th>
|
||||
<th>Update</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
{% for village in villages %}
|
||||
<tr>
|
||||
<td>{{ village[0] }}</td>
|
||||
<td>{{ village[1] }}</td>
|
||||
<td>{{ village[2] }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('village.edit_village', village_id=village[0]) }}">
|
||||
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
|
||||
class="icon">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for('village.delete_village', village_id=village[0]) }}"
|
||||
onclick="return confirm('Are you sure you want to delete this village?');">
|
||||
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
|
||||
class="icon">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
{% endblock %}
|
||||
137
v-2/templates/add_work_order.html
Normal file
137
v-2/templates/add_work_order.html
Normal 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 %}
|
||||
49
v-2/templates/admin_profile.html
Normal file
49
v-2/templates/admin_profile.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Profile</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>Admin Profile</h2>
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<p class="{{ category }}">{{ message }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<form action="{{ url_for('admin_profile') }}" method="POST" enctype="multipart/form-data">
|
||||
<div class="profile-picture">
|
||||
<!-- <img id="profile-pic-preview" src="{{ url_for('static', filename='images/' + (profile['profile_picture'] or 'default.png')) }}" alt="Profile Picture" />-->
|
||||
<input type="file" name="profile_picture" id="profile-pic-input" onchange="previewImage()">
|
||||
</div>
|
||||
<label>Username:</label>
|
||||
<input type="text" name="username" value="{{ profile['user_name'] }}" required>
|
||||
<label>Phone:</label>
|
||||
<input type="tel" name="phone" value="{{ profile['mobile'] }}" required>
|
||||
<label>Email:</label>
|
||||
<input type="email" name="email" value="{{ profile['email'] }}" required>
|
||||
<label>New Password:</label>
|
||||
<input type="password" name="new_password">
|
||||
<label>Current Password (required for password change):</label>
|
||||
<input type="password" name="current_password">
|
||||
<button type="submit">Save Changes</button>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
function previewImage() {
|
||||
const file = document.getElementById('profile-pic-input').files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
document.getElementById('profile-pic-preview').src = e.target.result;
|
||||
};
|
||||
if (file) {
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
{% endblock %}
|
||||
97
v-2/templates/base.html
Normal file
97
v-2/templates/base.html
Normal file
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{% block title %}Payment Reconciliation{% endblock %}</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/base.css') }}">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar Toggle Button -->
|
||||
<button class="menu-icon" onclick="toggleSidebar()">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
</button>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar" id="sidebar">
|
||||
<ul class="nav-menu">
|
||||
<li class="nav-item">
|
||||
<a href="/" class="nav-link active">
|
||||
<i class="fas fa-home"></i> Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/upload_excel_file" class="nav-link">
|
||||
<i class="fas fa-book"></i> Import Excel
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/report" class="nav-link">
|
||||
<i class="fas fa-cog"></i> Report
|
||||
</a>
|
||||
</li>
|
||||
<li class="card">
|
||||
<a href="/add_state" class="nav-link">
|
||||
<i class="fas fa-arrow-right"></i> State
|
||||
</a>
|
||||
</li>
|
||||
<li class="card">
|
||||
<a href="/add_district" class="nav-link">
|
||||
<i class="fas fa-arrow-right"></i> District
|
||||
</a>
|
||||
</li>
|
||||
<li class="card">
|
||||
<a href="/add_block" class="nav-link">
|
||||
<i class="fas fa-arrow-right"></i> Blocks
|
||||
</a>
|
||||
</li>
|
||||
<li class="card">
|
||||
<a href="/add_village" class="nav-link">
|
||||
<i class="fas fa-arrow-right"></i> village
|
||||
</a>
|
||||
</li>
|
||||
<li class="card">
|
||||
<a href="/subcontractor" class="nav-link">
|
||||
<i class="fas fa-arrow-right"></i> Sub-Contractor
|
||||
</a>
|
||||
</li>
|
||||
<li class="card">
|
||||
<a href="/add_invoice" class="nav-link">
|
||||
<i class="fas fa-arrow-right"></i> Invoice
|
||||
</a>
|
||||
</li>
|
||||
<li class="card">
|
||||
<a href="/add_payment" class="nav-link">
|
||||
<i class="fas fa-arrow-right"></i> Payment
|
||||
</a>
|
||||
</li>
|
||||
<li class="card">
|
||||
<a href="/add_gst_release" class="nav-link">
|
||||
<i class="fas fa-arrow-right"></i> GST Release
|
||||
</a>
|
||||
</li>
|
||||
<li class="card">
|
||||
<a href="/add_hold_type" class="nav-link">
|
||||
<i class="fas fa-arrow-right"></i> Hold Types
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="content" id="content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<!-- Sidebar Toggle Script -->
|
||||
<script>
|
||||
function toggleSidebar() {
|
||||
document.getElementById("sidebar").classList.toggle("open");
|
||||
document.getElementById("content").classList.toggle("shift");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
60
v-2/templates/edit_block.html
Normal file
60
v-2/templates/edit_block.html
Normal 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 %}
|
||||
26
v-2/templates/edit_district.html
Normal file
26
v-2/templates/edit_district.html
Normal 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
v-2/templates/edit_grn.html
Normal file
39
v-2/templates/edit_grn.html
Normal 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>
|
||||
45
v-2/templates/edit_gst_release.html
Normal file
45
v-2/templates/edit_gst_release.html
Normal file
@@ -0,0 +1,45 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Edit GST Release</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Edit GST Release</h2>
|
||||
|
||||
<form action="/edit_gst_release/{{ gst_release_data[0] }}" method="POST">
|
||||
<!-- <label for="invoice_id">Invoice Id:</label><br>-->
|
||||
<!-- <input type="number" id="invoice_id" name="invoice_id" value="{{ gst_release_data[0] }}" required><br><br>-->
|
||||
|
||||
<label for="PMC_No">PMC No :</label><br>
|
||||
<input type="number" id="PMC_No" name="PMC_No" value="{{ gst_release_data[1] }}" required><br><br>
|
||||
|
||||
<label for="invoice_No">Invoice No:</label><br>
|
||||
<input type="number" step="0.01" id="invoice_No" name="invoice_No" value="{{ gst_release_data[2] }}"
|
||||
required><br><br>
|
||||
|
||||
|
||||
<label for="basic_amount">Basic Amount:</label><br>
|
||||
<input type="number" step="0.01" id="basic_amount" name="basic_amount" value="{{ gst_release_data[3] }}"
|
||||
required><br><br>
|
||||
|
||||
<label for="final_amount">Final Amount:</label><br>
|
||||
<input type="number" step="0.01" id="final_amount" name="final_amount" value="{{ gst_release_data[4] }}"
|
||||
required><br><br>
|
||||
|
||||
<label for="total_amount">Total Amount:</label><br>
|
||||
<input type="number" step="0.01" id="total_amount" name="total_amount" value="{{ gst_release_data[5] }}"
|
||||
required><br><br>
|
||||
|
||||
<label for="utr">UTR:</label><br>
|
||||
<input type="text" id="utr" name="utr" value="{{ gst_release_data[6] }}"
|
||||
required readonly><br><br>
|
||||
|
||||
<button type="submit">Update GST Release</button>
|
||||
</form>
|
||||
|
||||
|
||||
</body>
|
||||
{% endblock %}
|
||||
62
v-2/templates/edit_hold_type.html
Normal file
62
v-2/templates/edit_hold_type.html
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<head>
|
||||
<title>Edit Hold Type</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Edit Hold Type</h2>
|
||||
<form id="updateHoldTypeForm">
|
||||
<input type="hidden" id="hold_type_id" value="{{ hold_type[0] }}">
|
||||
<label for="edit_hold_type">Hold Type:</label>
|
||||
<input type="text" id="edit_hold_type" name="hold_type" value="{{ hold_type[1] }}" required>
|
||||
<button type="submit">Update</button>
|
||||
<a href="{{ url_for('hold_types.add_hold_type') }}"></a>
|
||||
</form>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#updateHoldTypeForm').submit(function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const holdTypeId = $('#hold_type_id').val();
|
||||
const newHoldType = $('#edit_hold_type').val().trim();
|
||||
|
||||
if (!/^[A-Za-z]/.test(newHoldType)) {
|
||||
alert('Hold Type must start with a letter.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a FormData object to properly submit form data
|
||||
const formData = new FormData();
|
||||
formData.append('hold_type', newHoldType);
|
||||
|
||||
$.ajax({
|
||||
url: '/update_hold_type/' + holdTypeId,
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function () {
|
||||
// The server will redirect, so we don't need to handle the response here
|
||||
window.location.href = "{{ url_for('hold_types.add_hold_type') }}";
|
||||
},
|
||||
error: function (xhr) {
|
||||
const errMsg = xhr.responseJSON?.message || 'Failed to update Hold Type';
|
||||
alert('Error: ' + errMsg);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
207
v-2/templates/edit_invoice.html
Normal file
207
v-2/templates/edit_invoice.html
Normal file
@@ -0,0 +1,207 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Edit Invoice</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/invoice.css') }}">
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style1.css') }}">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="editForm">
|
||||
<h2>Edit Invoice</h2>
|
||||
|
||||
<!-- Success Alert Box -->
|
||||
<div id="invoiceSuccessAlert" class="success-alert" style="display:none;">
|
||||
Invoice successfully updated!
|
||||
</div>
|
||||
|
||||
<form id="invoiceForm" action="{{ url_for('invoice.edit_invoice', invoice_id=invoice.Invoice_Id) }}" method="POST">
|
||||
|
||||
<!-- Subcontractor Field -->
|
||||
<div class="row1">
|
||||
<div>
|
||||
<label for="subcontractor">Subcontractor Name:</label>
|
||||
<input class="form-control" list="subcontractor_list" id="subcontractor" name="subcontractor"
|
||||
value="{{ invoice.Contractor_Name }}" placeholder="Type to search..." required>
|
||||
<input type="hidden" id="subcontractor_id" name="subcontractor_id" value="{{ invoice.Subcontractor_Id }}">
|
||||
<datalist id="subcontractor_list"></datalist>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Village and PMC No Fields -->
|
||||
<div class="row2">
|
||||
<div>
|
||||
<label for="village">Village Name:</label>
|
||||
<input type="text" id="village" name="village" value="{{ invoice.Village_Name }}" required/>
|
||||
<datalist id="village_list"></datalist>
|
||||
</div>
|
||||
<div>
|
||||
<label for="pmc_no">PMC No:</label>
|
||||
<input type="text" id="pmc_no" name="pmc_no" value="{{ invoice.PMC_No }}" required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Work Type and Invoice Details -->
|
||||
<div class="row2">
|
||||
<div>
|
||||
<label for="work_type">Work Type:</label>
|
||||
<input type="text" id="work_type" name="work_type" value="{{ invoice.Work_Type }}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="invoice_details">Invoice Details:</label>
|
||||
<textarea id="invoice_details" name="invoice_details" required>{{ invoice.Invoice_Details }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Invoice No and Invoice Date -->
|
||||
<div class="row2">
|
||||
<div>
|
||||
<label for="invoice_no">Invoice No:</label>
|
||||
<input type="text" id="invoice_no" name="invoice_no" value="{{ invoice.Invoice_No }}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="invoice_date">Invoice Date:</label>
|
||||
<input type="date" id="invoice_date" name="invoice_date" value="{{ invoice.Invoice_Date }}" required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Amount Fields -->
|
||||
<div class="row3">
|
||||
<div>
|
||||
<label for="basic_amount">Basic Amount:</label>
|
||||
<input type="number" step="0.01" id="basic_amount" name="basic_amount" value="{{ invoice.Basic_Amount }}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="debit_amount">Debit Amount:</label>
|
||||
<input type="number" step="0.01" id="debit_amount" name="debit_amount" value="{{ invoice.Debit_Amount }}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="after_debit_amount">After Debit Amount:</label>
|
||||
<input type="number" step="0.01" id="after_debit_amount" name="after_debit_amount" value="{{ invoice.After_Debit_Amount }}" required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GST, TDS, and Other Amounts -->
|
||||
<div class="row3">
|
||||
<div>
|
||||
<label for="amount">Amount:</label>
|
||||
<input type="number" step="0.01" id="amount" name="amount" value="{{ invoice.Amount }}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="gst_amount">GST Amount:</label>
|
||||
<input type="number" step="0.01" id="gst_amount" name="gst_amount" value="{{ invoice.GST_Amount }}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="tds_amount">TDS Amount:</label>
|
||||
<input type="number" step="0.01" id="tds_amount" name="tds_amount" value="{{ invoice.TDS_Amount }}" required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SD, On Commission, Hydro Testing -->
|
||||
<div class="row3">
|
||||
<div>
|
||||
<label for="sd_amount">SD Amount:</label>
|
||||
<input type="number" step="0.01" id="sd_amount" name="sd_amount" value="{{ invoice.SD_Amount }}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="on_commission">On Commission:</label>
|
||||
<input type="number" step="0.01" id="on_commission" name="on_commission" value="{{ invoice.On_Commission }}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="hydro_testing">Hydro Testing:</label>
|
||||
<input type="number" step="0.01" id="hydro_testing" name="hydro_testing" value="{{ invoice.Hydro_Testing }}" required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hold Amount Section -->
|
||||
<div id="hold_amount_container">
|
||||
{% set seen_types = [] %}
|
||||
{% for hold in invoice.hold_amounts %}
|
||||
{% if hold.hold_type not in seen_types %}
|
||||
{% set _ = seen_types.append(hold.hold_type) %}
|
||||
<div class="hold-amount-row">
|
||||
<label for="hold_type_{{ loop.index }}">Hold Type:</label>
|
||||
<input type="text" id="hold_type_{{ loop.index }}" name="hold_type[]" value="{{ hold.hold_type }}" required/>
|
||||
<label for="hold_amount_{{ loop.index }}">Hold Amount:</label>
|
||||
<input type="number" step="0.01" id="hold_amount_{{ loop.index }}" name="hold_amount[]" value="{{ hold.hold_amount }}" required/>
|
||||
<button type="button" class="remove-hold-amount">Remove</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="hold-row">
|
||||
<button type="button" id="add_hold_amount" class="button">+ Add Hold Amount</button>
|
||||
</div>
|
||||
|
||||
<!-- Final Amounts -->
|
||||
<div class="row2">
|
||||
<div>
|
||||
<label for="gst_sd_amount">GST SD Amount:</label>
|
||||
<input type="number" step="0.01" id="gst_sd_amount" name="gst_sd_amount" value="{{ invoice.GST_SD_Amount }}" required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="final_amount">Final Amount:</label>
|
||||
<input type="number" step="0.01" id="final_amount" name="final_amount" value="{{ invoice.Final_Amount }}" required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button type="submit" class="button">Update</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- JS for dynamic hold amount rows -->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Add new hold amount row
|
||||
$("#add_hold_amount").click(function() {
|
||||
const index = $("#hold_amount_container .hold-amount-row").length;
|
||||
const newRow = `
|
||||
<div class="hold-amount-row">
|
||||
<label for="hold_type_${index}">Hold Type:</label>
|
||||
<input type="text" id="hold_type_${index}" name="hold_type[]" required/>
|
||||
<label for="hold_amount_${index}">Hold Amount:</label>
|
||||
<input type="number" step="0.01" id="hold_amount_${index}" name="hold_amount[]" required/>
|
||||
<button type="button" class="remove-hold-amount">Remove</button>
|
||||
</div>
|
||||
`;
|
||||
$("#hold_amount_container").append(newRow);
|
||||
});
|
||||
|
||||
// Remove hold amount row
|
||||
$(document).on("click", ".remove-hold-amount", function() {
|
||||
$(this).closest(".hold-amount-row").remove();
|
||||
});
|
||||
|
||||
// Submit form via AJAX
|
||||
$("#invoiceForm").submit(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: $(this).attr("action"),
|
||||
data: $(this).serialize(),
|
||||
success: function(response) {
|
||||
if(response.status === "success") {
|
||||
$("#invoiceSuccessAlert").fadeIn().delay(3000).fadeOut();
|
||||
alert("Invoice updated successfully!"); // <-- Popup alert
|
||||
}
|
||||
|
||||
},
|
||||
error: function(xhr) {
|
||||
alert("Error: " + xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
{% endblock %}
|
||||
49
v-2/templates/edit_payment.html
Normal file
49
v-2/templates/edit_payment.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Edit Payment</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Edit Payment</h2>
|
||||
|
||||
<form action="/edit_payment/{{ payment_data[0] }}" method="POST">
|
||||
<!-- <label for="invoice_id">Invoice ID:</label><br>-->
|
||||
<!-- <select id="invoice_id" name="invoice_id" required>-->
|
||||
<!-- {% for invoice in invoices %}-->
|
||||
<!-- <option value="{{ invoice[0] }}" {% if invoice[0] == payment_data[0] %}selected{% endif %}>-->
|
||||
<!-- {{ invoice[1] }}-->
|
||||
<!-- </option>-->
|
||||
<!-- {% endfor %}-->
|
||||
<!-- </select><br><br>-->
|
||||
|
||||
<label for="PMC_No">PMC No:</label><br>
|
||||
<input type="number" step="0.01" id="PMC_No" name="PMC_No" value="{{ payment_data[1] }}" required><br><br>
|
||||
|
||||
<label for="invoice_No">Invoice No:</label><br>
|
||||
<input type="number" step="0.01" id="invoice_No" name="invoice_No" value="{{ payment_data[2] }}" required><br><br>
|
||||
|
||||
|
||||
<label for="Payment_Amount">Amount:</label><br>
|
||||
<input type="number" step="0.01" id="Payment_Amount" name="Payment_Amount" value="{{ payment_data[3] }}"
|
||||
required><br><br>
|
||||
|
||||
<label for="TDS_Payment_Amount">TDS Amount:</label><br>
|
||||
<input type="number" step="0.01" id="TDS_Payment_Amount" name="TDS_Payment_Amount" value="{{ payment_data[4] }}"
|
||||
required><br><br>
|
||||
|
||||
<label for="total_amount">Total Amount:</label><br>
|
||||
<input type="number" step="0.01" id="total_amount" name="total_amount" value="{{ payment_data[5] }}"
|
||||
required><br><br>
|
||||
|
||||
<label for="utr">UTR:</label><br>
|
||||
<input type="text" id="utr" name="utr" value="{{ payment_data[6] }}" readonly><br><br>
|
||||
|
||||
<button type="submit">Update Payment</button>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
{% endblock %}
|
||||
47
v-2/templates/edit_purchase.html
Normal file
47
v-2/templates/edit_purchase.html
Normal 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
v-2/templates/edit_state.html
Normal file
17
v-2/templates/edit_state.html
Normal 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 %}
|
||||
50
v-2/templates/edit_subcontractor.html
Normal file
50
v-2/templates/edit_subcontractor.html
Normal 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 %}
|
||||
52
v-2/templates/edit_village.html
Normal file
52
v-2/templates/edit_village.html
Normal 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
v-2/templates/grn_form.html
Normal file
77
v-2/templates/grn_form.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>GRN Management</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Add GRN</h2>
|
||||
<form method="POST" action="/add_grn">
|
||||
<label>GRN Date:</label>
|
||||
<input type="date" name="grn_date" required><br><br>
|
||||
|
||||
<label>Purchase ID:</label>
|
||||
<input type="number" name="purchase_id" required><br><br>
|
||||
{# <label for="purchase_id">Purchase Order:</label>#}
|
||||
{# <select name="purchase_id" id="purchase_id" required>#}
|
||||
{# {% for order in purchase_orders %}#}
|
||||
{# <option value="{{ order['purchase_id'] }}">{{ order['supplier_name'] }}</option>#}
|
||||
{# {% endfor %}#}
|
||||
{#</select>#}
|
||||
|
||||
<label>Supplier Name:</label>
|
||||
<input type="text" name="supplier_name" required><br><br>
|
||||
|
||||
<label>Item Description:</label>
|
||||
<input type="text" name="item_description" required><br><br>
|
||||
|
||||
<label>Received Quantity:</label>
|
||||
<input type="number" name="received_quantity" required><br><br>
|
||||
|
||||
<label>Unit:</label>
|
||||
<input type="text" name="unit" required><br><br>
|
||||
|
||||
<label>Rate:</label>
|
||||
<input type="number" step="0.01" name="rate" required><br><br>
|
||||
|
||||
<label>Amount:</label>
|
||||
<input type="number" step="0.01" name="amount" required><br><br>
|
||||
|
||||
<label>Remarks:</label>
|
||||
<input type="text" name="remarks"><br><br>
|
||||
|
||||
<input type="submit" value="Add GRN">
|
||||
</form>
|
||||
|
||||
<h2>All GRNs</h2>
|
||||
<table border="1">
|
||||
<tr>
|
||||
<th>ID</th><th>Date</th><th>Purchase ID</th><th>Supplier</th><th>Item</th>
|
||||
<th>Qty</th><th>Unit</th><th>Rate</th><th>Amount</th><th>Remarks</th><th>Actions</th>
|
||||
</tr>
|
||||
{% for grn in grns %}
|
||||
<tr>
|
||||
<td>{{ grn[0] }}</td>
|
||||
<td>{{ grn[1] }}</td>
|
||||
<td>{{ grn[2] }}</td>
|
||||
<td>{{ grn[3] }}</td>
|
||||
<td>{{ grn[4] }}</td>
|
||||
<td>{{ grn[5] }}</td>
|
||||
<td>{{ grn[6] }}</td>
|
||||
<td>{{ grn[7] }}</td>
|
||||
<td>{{ grn[8] }}</td>
|
||||
<td>{{ grn[9] }}</td>
|
||||
<td style="white-space: nowrap;">
|
||||
<a href="/update_grn/{{ grn['grn_id'] }}" style="margin-right: 10px;">Edit</a>
|
||||
|
||||
<form action="/delete_grn/{{ grn[0] }}" method="POST" style="display:inline;">
|
||||
<button type="submit" onclick="return confirm('Are you sure you want to delete this GRN?')" style="background:none;border:none;color:blue;cursor:pointer;text-decoration:underline;">
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
164
v-2/templates/index.html
Normal file
164
v-2/templates/index.html
Normal file
@@ -0,0 +1,164 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Payment Reconciliation </title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/index.css') }}">
|
||||
<style>
|
||||
.logout-button {
|
||||
position: absolute;
|
||||
top: 2cm;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.logout-button a {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
padding: 8px 14px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.logout-button a:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="logout-button">
|
||||
<a href="/logout">Logout</a>
|
||||
</div>
|
||||
<div class="sidebar">
|
||||
<img src="https://lceplpmprod.btltech.xyz/assets/images/lcpl.png" alt="logo-image" class="logo">
|
||||
|
||||
<ul class="nav-menu">
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link active">
|
||||
<i class="fas fa-home"></i> Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/upload_excel_file" class="nav-link">
|
||||
<i class="fas fa-book"></i> Import Excel
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/report" class="nav-link">
|
||||
<i class="fas fa-cog"></i> Report Details
|
||||
</a>
|
||||
</li>
|
||||
<!-- <li class="nav-item">
|
||||
<a href="/work_order_report" class="nav-link">
|
||||
<i class="fas fa-cog"></i> Work Order Report Details
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/purchase_order_report" class="nav-link">
|
||||
<i class="fas fa-cog"></i>Purchase Order Report Details
|
||||
</a>
|
||||
</li> -->
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('log.activity_log') }}" class="nav-link">
|
||||
<i class="fas fa-cog"></i> Logs
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="user-section">
|
||||
<img src="{{ url_for('static', filename='images/icons/male_profile.jpg') }}" alt="User Avatar">
|
||||
<div class="user-info">
|
||||
<span>admin</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="company-info">
|
||||
<marquee behavior="scroll" direction="left">
|
||||
<h2 class="company-name">Laxmi Civil Engineering Services Pvt. Ltd.</h2>
|
||||
</marquee>
|
||||
<p class="app-name">Payment Reconciliation</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="menu">
|
||||
<div class="card">
|
||||
<h2>Profile</h2>
|
||||
<a class="btn" href="#">Go ➜</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>States</h2>
|
||||
<a class="btn" href="/add_state">Go ➜</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>District</h2>
|
||||
<a class="btn" href="/add_district">Go ➜</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Blocks</h2>
|
||||
<a class="btn" href="/add_block">Go ➜</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Village</h2>
|
||||
<a class="btn" href="/add_village">Go ➜</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Sub-Contractor</h2>
|
||||
<a class="btn" href="/subcontractor">Go ➜</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Invoice</h2>
|
||||
<a class="btn" href="/add_invoice">Go ➜</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Payment</h2>
|
||||
<a class="btn" href="/add_payment">Go ➜</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>GST Release</h2>
|
||||
<a class="btn" href="/add_gst_release">Go ➜</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Hold Types</h2>
|
||||
<a class="btn" href="/add_hold_type">Go ➜</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Work Order</h2>
|
||||
<!-- <a class="btn" href="/add_work_order">Go ➜</a> -->
|
||||
<a class="btn">Go ➜</a>
|
||||
</div>
|
||||
<!-- <div class="card">
|
||||
<h2>Purchase Order</h2>
|
||||
<a class="btn" href="/add_purchase_order">Go ➜</a>
|
||||
</div> -->
|
||||
<!-- <div class="card">
|
||||
<h2>Goods Receive Note</h2>
|
||||
<a class="btn" href="/add_grn">Go ➜</a>
|
||||
</div> -->
|
||||
<!-- <div class="card">
|
||||
<h2>Unreleased GST</h2>
|
||||
<a class="btn" href="/unreleased_gst">Go ➜</a>
|
||||
</div>
|
||||
|
||||
{# <div class="card">#}
|
||||
{# <h2>Hold Release </h2>#}
|
||||
{# <a class="btn" href="/add_hold_release">Go ➜</a>#}
|
||||
{# </div>#}
|
||||
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
90
v-2/templates/login.html
Normal file
90
v-2/templates/login.html
Normal 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
v-2/templates/pmc_report.html
Normal file
340
v-2/templates/pmc_report.html
Normal file
@@ -0,0 +1,340 @@
|
||||
|
||||
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PMC Report</title>
|
||||
<!-- <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">-->
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/subcontractor_report.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h2>PMC Report</h2>
|
||||
|
||||
<div class="info">
|
||||
<h2>Contractor Details</h2>
|
||||
<div class="row2">
|
||||
<div>
|
||||
<label for="subcontractor">Subcontractor Name:</label>
|
||||
<input type="text" id="subcontractor" name="subcontractor" value="{{ info.Contractor_Name }}"
|
||||
readonly/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="Address">Address :</label>
|
||||
<textarea id="Address" name="Address" readonly>{{ info.Address }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row3">
|
||||
<div>
|
||||
<label for="PAN_No">PAN No :</label>
|
||||
<input type="text" id="PAN_No" name="PAN_No" value="{{ info.PAN_No }}" readonly/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="Mobile_No">Mobile Number :</label>
|
||||
<input type="text" id="Mobile_No" name="Mobile_No" value="{{ info.Mobile_No }}" readonly/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="Email">Email :</label>
|
||||
<input type="text" id="Email" name="Email" value="{{ info.Email }}" readonly/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row2">
|
||||
<div>
|
||||
<label for="GST_Registration_Type">GST Registration Type :</label>
|
||||
<input type="text" id="GST_Registration_Type" name="GST_Registration_Type"
|
||||
value="{{ info.GST_Registration_Type }}" readonly/>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label for="GST_No">GST No:</label>
|
||||
<input type="text" id="GST_No" name="GST_No" value="{{ info.GST_No }}" readonly/>
|
||||
</div>
|
||||
</div>
|
||||
<h2>PMC Report for PMC No: {{ info.PMC_No}}</h2>
|
||||
<div class="row3">
|
||||
<div>
|
||||
<label for="State">State :</label>
|
||||
<input type="text" id="State" name="State" value="{{ info.State_Name }}" readonly/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="District">District :</label>
|
||||
<input type="text" id="District" name="District" value="{{ info.District_Name }}" readonly/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="Block">Block :</label>
|
||||
<input type="text" id="Block" name="Block" value="{{ info.Block_Name }}" readonly/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row2">
|
||||
<div>
|
||||
<label for="PMC_No">PMC No:</label>
|
||||
<input type="text" id="PMC_No" name="PMC_No" value="{{ info.PMC_No }}" readonly/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="Village_Name">Village Name :</label>
|
||||
<input type="text" id="Village_Name" name="Village_Name"
|
||||
value="{{ info.Village_Name.capitalize() }}" readonly/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h3>Invoice Details</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PMC No</th>
|
||||
<th>Village Name</th>
|
||||
<th>Work Type</th>
|
||||
<th>Invoice Details</th>
|
||||
<th>Invoice Date</th>
|
||||
<th>Invoice No</th>
|
||||
<th>Basic Amount</th>
|
||||
<th>Debit</th>
|
||||
<th>After Debit Amt</th>
|
||||
<th>GST (18%)</th>
|
||||
<th>Amount</th>
|
||||
<th>TDS (1%)</th>
|
||||
<th>SD (5%)</th>
|
||||
<th>On Commission</th>
|
||||
<th>Hydro Testing</th>
|
||||
|
||||
<!-- Dynamic Hold Types -->
|
||||
{% set hold_types = invoices | map(attribute='hold_type') | reject('none') | unique | list %}
|
||||
{% for hold in hold_types %}
|
||||
<th>{{ hold }}</th>
|
||||
{% endfor %}
|
||||
|
||||
<th>GST SD (18%)</th>
|
||||
<th>Final Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if invoices %}
|
||||
{% for invoice in invoices %}
|
||||
<tr>
|
||||
<td>{{ invoice.PMC_No }}</td>
|
||||
<td>{{ invoice.Village_Name.capitalize() }}</td>
|
||||
<td>{{ invoice.Work_Type }}</td>
|
||||
<td>{{ invoice.Invoice_Details }}</td>
|
||||
<td>{{ invoice.Invoice_Date }}</td>
|
||||
<td>{{ invoice.Invoice_No }}</td>
|
||||
<td>{{ invoice.Basic_Amount }}</td>
|
||||
<td>{{ invoice.Debit_Amount }}</td>
|
||||
<td>{{ invoice.After_Debit_Amount }}</td>
|
||||
<td>{{ invoice.GST_Amount }}</td>
|
||||
<td>{{ invoice.Amount }}</td>
|
||||
<td>{{ invoice.TDS_Amount }}</td>
|
||||
<td>{{ invoice.SD_Amount }}</td>
|
||||
<td>{{ invoice.On_Commission }}</td>
|
||||
<td>{{ invoice.Hydro_Testing }}</td>
|
||||
|
||||
<!-- Dynamic Hold Amounts -->
|
||||
{% for hold in hold_types %}
|
||||
<td>
|
||||
{% if invoice.hold_type == hold %}
|
||||
{{ invoice.hold_amount }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
|
||||
<td>{{ invoice.GST_SD_Amount }}</td>
|
||||
<td>{{ invoice.Final_Amount }}</td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<th colspan="6">Total</th>
|
||||
<th>{{total["sum_invo_basic_amt"]}}</th>
|
||||
<th>{{total["sum_invo_debit_amt"]}}</th>
|
||||
<th>{{total["sum_invo_after_debit_amt"]}}</th>
|
||||
<th>{{total["sum_invo_gst_amt"]}}</th>
|
||||
<th>{{total["sum_invo_amt"]}}</th>
|
||||
<th>{{total["sum_invo_tds_amt"]}}</th>
|
||||
<th>{{total["sum_invo_ds_amt"]}}</th>
|
||||
<th>{{total["sum_invo_on_commission"]}}</th>
|
||||
<th>{{total["sum_invo_hydro_test"]}}</th>
|
||||
{% set hold_types = invoices | map(attribute='hold_type') | unique | list %}
|
||||
{% for hold in hold_types %}
|
||||
<th>{{total["sum_invo_hold_amt"]}}</th>
|
||||
{% endfor %}
|
||||
<th>{{total["sum_invo_gst_sd_amt"]}}</th>
|
||||
<th>{{total["sum_invo_final_amt"]}}</th>
|
||||
</tr>
|
||||
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="{{ 17 + hold_types|length }}">No invoices found.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Hold Release</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PMC No</th>
|
||||
<th>Invoice No</th>
|
||||
<th>Invoice Details</th>
|
||||
<th>Basic Amount</th>
|
||||
<th>Total Amount</th>
|
||||
<th>UTR</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%if hold_release%}
|
||||
{%for hold in hold_release%}
|
||||
<tr>
|
||||
<td>{{ hold['PMC_No'] }}</td>
|
||||
<td>{{ hold['Invoice_No'] }}</td>
|
||||
<td>{{ hold['Invoice_Details'] }}</td>
|
||||
<td>{{ hold['Basic_Amount'] }}</td>
|
||||
<td>{{ hold['Total_Amount'] }}</td>
|
||||
<td>{{ hold['UTR'] }}</td>
|
||||
</tr>
|
||||
{%endfor%}
|
||||
{%else%}
|
||||
<tr>
|
||||
<td colspan="6" style="text-align:center;">No data present</td>
|
||||
</tr>
|
||||
{%endif%}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
<h3>GST Release Note Details</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PMC No</th>
|
||||
<th>Invoice No</th>
|
||||
<th>Basic Amount</th>
|
||||
<th>Final Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if gst_rel %}
|
||||
{% for gst in gst_rel %}
|
||||
<tr>
|
||||
<td>{{ gst.pmc_no }}</td>
|
||||
<td>{{ gst.invoice_no }}</td>
|
||||
<td>{{ gst.basic_amount }}</td>
|
||||
<td>{{ gst.final_amount }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<th colspan="2">Total</th>
|
||||
|
||||
<th>{{total["sum_gst_basic_amt"]}}</th>
|
||||
<th>{{total["sum_gst_final_amt"]}}</th>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4">No GST release found.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<tr>
|
||||
<h3>Credit Details</h3>
|
||||
</tr>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PMC No</th>
|
||||
<th>Invoice Details</th>
|
||||
<th>Basic Amount</th>
|
||||
<th>Debit</th>
|
||||
<th>After Debit Amt</th>
|
||||
<th>GST Amount</th>
|
||||
<th>Amount</th>
|
||||
<th>Final Amount</th>
|
||||
<th>Payment Amount</th>
|
||||
<th>Total Amount</th>
|
||||
<th>UTR</th>
|
||||
|
||||
</tr>
|
||||
|
||||
{% if credit_note %}
|
||||
{% for credit in credit_note %}
|
||||
<tr>
|
||||
<td>{{ credit["PMC_No"] }}</td>
|
||||
<td>{{ credit["Invoice_Details"] }}</td>
|
||||
<td>{{ credit["Basic_Amount"] }}</td>
|
||||
<td>{{ credit["Debit_Amount"] }}</td>
|
||||
<td>{{ credit["After_Debit_Amount"] }}</td>
|
||||
<td>{{ credit["GST_Amount"] }}</td>
|
||||
<td>{{ credit["Amount"] }}</td>
|
||||
<td>{{ credit["Final_Amount"] }}</td>
|
||||
<td>{{ credit["Payment_Amount"] }}</td>
|
||||
<td>{{ credit["Total_Amount"] }}</td>
|
||||
<td>{{ credit["UTR"] }}</td>
|
||||
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="11">No Credit note found.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
<h3>Payment Details</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PMC No</th>
|
||||
<th>Invoice No</th>
|
||||
<th>Amount</th>
|
||||
<th>TDS Amount @ 1% on BASIC AMOUNT</th>
|
||||
<th>Total Amount Paid</th>
|
||||
<th>UTR</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if payments %}
|
||||
{% for pay in payments %}
|
||||
<tr>
|
||||
<td>{{ pay.pmc_no }}</td>
|
||||
<td>{{ pay.invoice_no }}</td>
|
||||
<td>{{ pay.Payment_Amount }}</td>
|
||||
<td>{{ pay.TDS_Payment_Amount }}</td>
|
||||
<td>{{ pay.Total_amount }}</td>
|
||||
<td>{{ pay.utr}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<th colspan="2">Total</th>
|
||||
<th>{{total["sum_pay_payment_amt"]}}</th>
|
||||
<th>{{total["sum_pay_tds_payment_amt"]}}</th>
|
||||
<th>{{total["sum_pay_total_amt"]}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{% else %}
|
||||
|
||||
<tr>
|
||||
<td colspan="6">No payment found.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="/download_pmc_report/{{ info.PMC_No}}">
|
||||
<button class="download-btn">Download PMC Report</button>
|
||||
</a>
|
||||
</body>
|
||||
{% endblock %}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user