Initial commit
This commit is contained in:
54
AppCode/Auth.py
Normal file
54
AppCode/Auth.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json
|
||||
from flask_login import LoginManager, UserMixin
|
||||
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from ldap3 import Server, Connection, ALL, SUBTREE
|
||||
|
||||
from ldap3 import Server, Connection, ALL
|
||||
from ldap3.core.exceptions import LDAPBindError
|
||||
|
||||
|
||||
|
||||
class DefaultCredentials:
|
||||
username = 'admin'
|
||||
password = 'admin123'
|
||||
|
||||
class LoginLDAP:
|
||||
def __init__(self, request):
|
||||
self.username = request.form['username'].strip()
|
||||
self.password = request.form['password']
|
||||
self.isDefaultCredentials = False
|
||||
self.isValidLogin = False
|
||||
self.errorMessage = ""
|
||||
ldap_user_dn = f"uid={self.username},ou=users,dc=lcepl,dc=org"
|
||||
ldap_server = 'ldap://localhost:389'
|
||||
#Need to re-factor further
|
||||
|
||||
|
||||
# Static fallback user
|
||||
if self.username == DefaultCredentials.username and self.password == DefaultCredentials.password:
|
||||
self.isDefaultCredentials = True
|
||||
self.isValidLogin = True
|
||||
return
|
||||
|
||||
try:
|
||||
# LDAP authentication
|
||||
conn = Connection(
|
||||
Server(self.ldap_server, get_info=ALL),
|
||||
user=self.ldap_user_dn,
|
||||
password=self.password,
|
||||
auto_bind=True
|
||||
)
|
||||
|
||||
self.isValidLogin = True
|
||||
return
|
||||
|
||||
except LDAPBindError:
|
||||
self.errorMessage = "Invalid credentials."
|
||||
except Exception as e:
|
||||
self.errorMessage = str(e)
|
||||
|
||||
|
||||
class User(UserMixin):
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
208
AppCode/Block.py
Normal file
208
AppCode/Block.py
Normal file
@@ -0,0 +1,208 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json
|
||||
from flask import current_app
|
||||
|
||||
from datetime import datetime
|
||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||
|
||||
from AppCode.Utilities import RegEx, ResponseHandler, HtmlHelper
|
||||
from AppCode.Log import LogData, LogHelper
|
||||
|
||||
import os
|
||||
import config
|
||||
import re
|
||||
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
|
||||
|
||||
class Block:
|
||||
|
||||
isSuccess = False
|
||||
resultMessage = ""
|
||||
|
||||
def __init__(self):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Add Block
|
||||
# ----------------------------------------------------------
|
||||
def AddBlock(self, request):
|
||||
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()
|
||||
|
||||
block_name = request.form['block_Name'].strip()
|
||||
district_id = request.form['district_Id']
|
||||
|
||||
LogHelper.log_action("Add Block", f"User {current_user.id} Added Block '{block_name}'")
|
||||
|
||||
if not re.match(RegEx.patternAlphabetOnly, block_name):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("block"), 400)
|
||||
return
|
||||
|
||||
try:
|
||||
cursor.callproc("GetBlockByNameAndDistrict", (block_name, district_id))
|
||||
for rs in cursor.stored_results():
|
||||
existing_block = rs.fetchone()
|
||||
|
||||
if existing_block:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists("block"), 409)
|
||||
return
|
||||
|
||||
cursor.callproc("SaveBlock", (block_name, district_id))
|
||||
connection.commit()
|
||||
|
||||
self.isSuccess = True
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_success("block"), 200)
|
||||
return
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error inserting block: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("block"), 500)
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Get All Blocks
|
||||
# ----------------------------------------------------------
|
||||
def GetAllBlocks(self):
|
||||
blockdata = []
|
||||
connection = config.get_db_connection()
|
||||
|
||||
if not connection:
|
||||
return []
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc("GetAllBlocks")
|
||||
for rs in cursor.stored_results():
|
||||
blockdata = rs.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("blocks"), 500)
|
||||
return []
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return blockdata
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Check Block Exists
|
||||
# ----------------------------------------------------------
|
||||
def CheckBlock(self, request):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
block_name = request.json.get('block_Name', '').strip()
|
||||
district_id = request.json.get('district_Id')
|
||||
|
||||
LogHelper.log_action("Check Block", f"User {current_user.id} Checked block '{block_name}'")
|
||||
|
||||
if not re.match(RegEx.patternAlphabetOnly, block_name):
|
||||
return HtmlHelper.json_response(ResponseHandler.invalid_name("block"), 400)
|
||||
|
||||
try:
|
||||
cursor.callproc("GetBlockByNameAndDistrict", (block_name, district_id))
|
||||
for rs in cursor.stored_results():
|
||||
existing_block = rs.fetchone()
|
||||
|
||||
if existing_block:
|
||||
return HtmlHelper.json_response(ResponseHandler.already_exists("block"), 409)
|
||||
|
||||
return HtmlHelper.json_response(ResponseHandler.is_available("block"), 200)
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error checking block: {e}")
|
||||
return HtmlHelper.json_response(ResponseHandler.fetch_failure("block"), 500)
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Get Block By ID
|
||||
# ----------------------------------------------------------
|
||||
def GetBlockByID(self, id):
|
||||
blockdata = None
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc("GetBlockDataByID", (id,))
|
||||
for rs in cursor.stored_results():
|
||||
blockdata = rs.fetchone()
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching block data: {e}")
|
||||
return None
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return blockdata
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Update Block
|
||||
# ----------------------------------------------------------
|
||||
def EditBlock(self, request, id):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
block_name = request.form['block_Name'].strip()
|
||||
district_id = request.form['district_Id']
|
||||
|
||||
LogHelper.log_action("Edit Block", f"User {current_user.id} Edited block '{id}'")
|
||||
|
||||
if not re.match(RegEx.patternAlphabetOnly, block_name):
|
||||
return HtmlHelper.json_response(ResponseHandler.invalid_name("block"), 400)
|
||||
|
||||
try:
|
||||
cursor.callproc("UpdateBlockById", (block_name, district_id, id))
|
||||
connection.commit()
|
||||
return "Successfully Updated"
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error updating block: {e}")
|
||||
return HtmlHelper.json_response(ResponseHandler.update_failure("block"), 500)
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Delete Block
|
||||
# ----------------------------------------------------------
|
||||
def DeleteBlock(self, id):
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
LogHelper.log_action("Delete Block", f"User {current_user.id} Deleted block '{id}'")
|
||||
|
||||
try:
|
||||
cursor.callproc("DeleteBlock", (id,))
|
||||
connection.commit()
|
||||
return "Successfully Deleted"
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error deleting block: {e}")
|
||||
return HtmlHelper.json_response(ResponseHandler.delete_failure("block"), 500)
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
76
AppCode/ContractorInfo.py
Normal file
76
AppCode/ContractorInfo.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
import config
|
||||
import openpyxl
|
||||
import os
|
||||
import re
|
||||
import ast
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class ContractorInfo:
|
||||
ID = ""
|
||||
contInfo = None
|
||||
def __init__(self, id):
|
||||
self.ID = id
|
||||
print(id)
|
||||
self.fetchData()
|
||||
|
||||
def fetchData(self):
|
||||
try:
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor(dictionary=True, buffered=True)
|
||||
print("here", flush=True)
|
||||
|
||||
cursor.callproc('GetContractorInfoById', [self.contInfo])
|
||||
#self.contInfo = next(cursor.stored_results()).fetchone()
|
||||
self.contInfo = cursor.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.execute("""
|
||||
SELECT DISTINCT ht.hold_type_id, ht.hold_type
|
||||
FROM invoice_subcontractor_hold_join h
|
||||
JOIN hold_types ht ON h.hold_type_id = ht.hold_type_id
|
||||
WHERE h.Contractor_Id = %s
|
||||
""", (self.contractor_id,))
|
||||
hold_types = cursor.fetchall()
|
||||
hold_type_map = {ht['hold_type_id']: ht['hold_type'] for ht in hold_types}
|
||||
|
||||
# ---------------- Invoices ----------------
|
||||
cursor.execute("""
|
||||
SELECT i.*, v.Village_Name
|
||||
FROM assign_subcontractors asg
|
||||
INNER JOIN invoice i ON i.PMC_No = asg.PMC_No AND i.Village_Id = asg.Village_Id
|
||||
LEFT JOIN villages v ON i.Village_Id = v.Village_Id
|
||||
WHERE asg.Contractor_Id = %s
|
||||
ORDER BY i.PMC_No, i.Invoice_No
|
||||
""", (self.contractor_id,))
|
||||
invoices = cursor.fetchall()
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
236
AppCode/District.py
Normal file
236
AppCode/District.py
Normal file
@@ -0,0 +1,236 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json
|
||||
from flask import current_app
|
||||
|
||||
from datetime import datetime
|
||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||
|
||||
from AppCode.Utilities import RegEx, ResponseHandler, HtmlHelper
|
||||
from AppCode.Log import LogData, LogHelper
|
||||
|
||||
import os
|
||||
import config
|
||||
import re
|
||||
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
|
||||
class District:
|
||||
|
||||
isSuccess = False
|
||||
resultMessage = ""
|
||||
|
||||
def __init__(self):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
def AddDistrict(self, request):
|
||||
"""Handles the logic for adding a new district."""
|
||||
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()
|
||||
|
||||
district_name = request.form['district_Name'].strip()
|
||||
state_id = request.form['state_Id']
|
||||
LogHelper.log_action("Add District", f"User {current_user.id} Added District '{district_name}'")
|
||||
|
||||
if not re.match(RegEx.patternAlphabetOnly, district_name):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("district"), 400)
|
||||
return
|
||||
|
||||
try:
|
||||
cursor.callproc("GetDistrictByNameAndState", (district_name, state_id))
|
||||
for data in cursor.stored_results():
|
||||
rs = data.fetchone()
|
||||
|
||||
if rs:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists("district"), 409)
|
||||
return
|
||||
|
||||
cursor.callproc('SaveDistrict', (district_name, state_id))
|
||||
connection.commit()
|
||||
self.isSuccess = True
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_success("district"), 200)
|
||||
return
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error inserting district: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("district"), 500)
|
||||
return
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
def GetAllDistricts(self, request):
|
||||
"""Fetches all districts with their state names."""
|
||||
districtdata = []
|
||||
connection = config.get_db_connection()
|
||||
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
if not connection:
|
||||
return []
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc("GetAllDistricts")
|
||||
for dis in cursor.stored_results():
|
||||
districtdata = dis.fetchall()
|
||||
self.isSuccess = True
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching districts: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("districts"), 500)
|
||||
return []
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return districtdata
|
||||
|
||||
|
||||
def CheckDistrict(self, request):
|
||||
"""Checks if a district name already exists within a specific state."""
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
connection = config.get_db_connection()
|
||||
|
||||
if not connection:
|
||||
return HtmlHelper.json_response(ResponseHandler.db_connection_failure(), 500)
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
district_name = request.json.get('district_Name', '').strip()
|
||||
state_id = request.json.get('state_Id', '')
|
||||
LogHelper.log_action("Check District", f"User {current_user.id} Checked District '{district_name}'")
|
||||
|
||||
if not re.match(RegEx.patternAlphabetOnly, district_name):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("district"), 400)
|
||||
return self.resultMessage
|
||||
|
||||
try:
|
||||
cursor.callproc("GetDistrictByNameAndState", (district_name, state_id,))
|
||||
for result in cursor.stored_results():
|
||||
existing_district = result.fetchone()
|
||||
|
||||
if existing_district:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists("district"), 409)
|
||||
else:
|
||||
self.isSuccess = True
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.is_available("district"), 200)
|
||||
|
||||
return self.resultMessage
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error checking district: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("district"), 500)
|
||||
return self.resultMessage
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
def DeleteDistrict(self, request, id):
|
||||
"""Deletes a district by its ID."""
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
LogHelper.log_action("Delete District", f"User {current_user.id} Deleted District '{id}'")
|
||||
|
||||
try:
|
||||
cursor.callproc("DeleteDistrict", (id,))
|
||||
connection.commit()
|
||||
self.resultMessage = "Successfully Deleted"
|
||||
self.isSuccess = True
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error deleting district: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.delete_failure("district"), 500)
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return self.resultMessage
|
||||
|
||||
|
||||
def EditDistrict(self, request, id):
|
||||
"""Handles the POST logic for updating a district."""
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
district_name = request.form['district_Name'].strip()
|
||||
state_id = request.form['state_Id']
|
||||
LogHelper.log_action("Edit District", f"User {current_user.id} Edited District '{id}'")
|
||||
|
||||
# Added validation consistent with your other Edit methods
|
||||
if not re.match(RegEx.patternAlphabetOnly, district_name):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ResponseHandler.invalid_name("district"), 400
|
||||
return self.resultMessage
|
||||
|
||||
try:
|
||||
cursor.callproc("UpdateDistrict", (id, state_id, district_name,))
|
||||
connection.commit()
|
||||
self.isSuccess = True
|
||||
self.resultMessage = "Successfully Edited"
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error updating district: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ResponseHandler.update_failure("district"), 500
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return self.resultMessage
|
||||
|
||||
|
||||
def GetDistrictByID(self, request, id):
|
||||
"""Fetches a single district's details by its ID."""
|
||||
districtdata = None
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
connection = config.get_db_connection()
|
||||
|
||||
if not connection:
|
||||
return None
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc("GetDistrictDataByID", (id,))
|
||||
for rs in cursor.stored_results():
|
||||
districtdata = rs.fetchone()
|
||||
|
||||
if districtdata:
|
||||
self.isSuccess = True
|
||||
self.resultMessage = "Success in Fetching"
|
||||
else:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = "District Not Found"
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching district data: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("district"), 500)
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return districtdata
|
||||
87
AppCode/Log.py
Normal file
87
AppCode/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
|
||||
0
AppCode/ReportGenerator.py
Normal file
0
AppCode/ReportGenerator.py
Normal file
246
AppCode/State.py
Normal file
246
AppCode/State.py
Normal file
@@ -0,0 +1,246 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json
|
||||
from flask import current_app
|
||||
|
||||
from datetime import datetime
|
||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||
|
||||
from AppCode.Utilities import RegEx, ResponseHandler, HtmlHelper
|
||||
from AppCode.Log import LogData, LogHelper
|
||||
|
||||
import os
|
||||
import config
|
||||
import re
|
||||
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
|
||||
class State:
|
||||
|
||||
isSuccess = False
|
||||
resultMessage = ""
|
||||
|
||||
def __init__(self):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
def AddState(self, request):
|
||||
"""Log user actions with timestamp, user, action, and details."""
|
||||
statedata = []
|
||||
|
||||
connection = config.get_db_connection()
|
||||
|
||||
if connection:
|
||||
cursor = connection.cursor()
|
||||
state_name = request.form['state_Name'].strip()
|
||||
LogHelper.log_action("Add State", f"User {current_user.id} added state '{state_name}'")
|
||||
|
||||
if not re.match(RegEx.patternAlphabetOnly, state_name):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("state"), 400)
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
cursor.callproc("CheckStateExists", (state_name,))
|
||||
for data in cursor.stored_results():
|
||||
existing_state = data.fetchone()
|
||||
|
||||
if existing_state:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists("state"), 409)
|
||||
return
|
||||
|
||||
# cursor.execute("call SaveState (%s)", (state_name,))
|
||||
cursor.callproc("SaveState", (state_name,))
|
||||
connection.commit()
|
||||
self.isSuccess = True
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_success("state"), 200)
|
||||
return
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error inserting state: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("state"), 500)
|
||||
return
|
||||
|
||||
#Need to make this seperate
|
||||
|
||||
|
||||
|
||||
def GetAllStates(self, request):
|
||||
"""Log user actions with timestamp, user, action, and details."""
|
||||
statedata = []
|
||||
connection = config.get_db_connection()
|
||||
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
if not connection:
|
||||
return []
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc("GetAllStates")
|
||||
for res in cursor.stored_results():
|
||||
statedata = res.fetchall()
|
||||
self.isSuccess = True
|
||||
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching states: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("state"), 500)
|
||||
return []
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return statedata
|
||||
|
||||
|
||||
|
||||
def CheckState(self, request):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
connection = config.get_db_connection()
|
||||
#connection closing needs to be verified
|
||||
if connection:
|
||||
cursor = connection.cursor()
|
||||
state_name = request.json.get('state_Name', '').strip()
|
||||
LogHelper.log_action("Check State", f"User {current_user.id} Checked state '{state_name}'")
|
||||
if not re.match(RegEx.patternAlphabetOnly, state_name):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("state"), 400)
|
||||
return HtmlHelper.json_response(ResponseHandler.invalid_name("state"), 400)
|
||||
|
||||
try:
|
||||
# cursor.execute("SELECT * FROM states WHERE State_Name = %s", (state_name,))
|
||||
# existing_state = cursor.fetchone()
|
||||
|
||||
cursor.callproc("CheckStateExists", (state_name,))
|
||||
for data in cursor.stored_results():
|
||||
existing_state = data.fetchone()
|
||||
|
||||
if existing_state:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists("state"), 409)
|
||||
return HtmlHelper.json_response(ResponseHandler.already_exists("state"), 409)
|
||||
else:
|
||||
self.isSuccess = True
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.is_available("state"), 200)
|
||||
return HtmlHelper.json_response(ResponseHandler.is_available("state"), 200)
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("state"), 500)
|
||||
|
||||
print(f"Error checking state: {e}")
|
||||
return HtmlHelper.json_response(ResponseHandler.add_failure("state"), 500)
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
|
||||
def DeleteState(self, request, id):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
LogHelper.log_action("Delete State", f"User {current_user.id} Deleted state '{id}'")
|
||||
try:
|
||||
cursor.callproc('DeleteState', (id,))
|
||||
connection.commit()
|
||||
|
||||
self.resultMessage = "Successfully Deleted"
|
||||
self.isSuccess = True
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error deleting data: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.delete_failure("state"), 500)
|
||||
return HtmlHelper.json_response(ResponseHandler.delete_failure("state"), 500)
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
return self.resultMessage
|
||||
|
||||
|
||||
def EditState(self, request, id):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
# str_pattern_reg = r"^[A-Za-z\s]+$"
|
||||
|
||||
state_name = request.form['state_Name'].strip()
|
||||
LogHelper.log_action("Edit State", f"User {current_user.id} Edited state '{state_name}'")
|
||||
if not re.match(RegEx.patternAlphabetOnly, state_name):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ResponseHandler.invalid_name("state"), 400
|
||||
return ResponseHandler.invalid_name("state"), 400
|
||||
|
||||
try:
|
||||
# cursor.execute("UPDATE states SET State_Name = %s WHERE State_ID = %s", (state_name, id))
|
||||
cursor.callproc("UpdateStateById", (id, state_name))
|
||||
connection.commit()
|
||||
self.isSuccess = True
|
||||
self.resultMessage = "Successfully Edited"
|
||||
return redirect(url_for('add_state'))
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error updating data: {e}")
|
||||
self.isSuccess = True
|
||||
self.resultMessage = ResponseHandler.add_failure("state"), 500
|
||||
|
||||
return ResponseHandler.add_failure("state"), 500
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def GetStateByID(self, request, id):
|
||||
"""Log user actions with timestamp, user, action, and details."""
|
||||
statedata = []
|
||||
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
connection = config.get_db_connection()
|
||||
if not connection:
|
||||
return []
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc("GetStateByID", (id,))
|
||||
for res in cursor.stored_results():
|
||||
statedata = res.fetchone()
|
||||
|
||||
if statedata:
|
||||
self.isSuccess = True
|
||||
self.resultMessage = "Success in Fetching"
|
||||
else:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = "State Not Found"
|
||||
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching states: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("state"), 500)
|
||||
return []
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return statedata
|
||||
|
||||
|
||||
57
AppCode/Utilities.py
Normal file
57
AppCode/Utilities.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from flask import flash, jsonify, json
|
||||
|
||||
|
||||
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
|
||||
|
||||
274
AppCode/Village.py
Normal file
274
AppCode/Village.py
Normal file
@@ -0,0 +1,274 @@
|
||||
|
||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||
|
||||
from AppCode.Utilities import RegEx, ResponseHandler, HtmlHelper
|
||||
from AppCode.Log import LogData, LogHelper
|
||||
|
||||
import os
|
||||
import config
|
||||
import re
|
||||
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
|
||||
|
||||
class Village:
|
||||
isSuccess = False
|
||||
resultMessage = ""
|
||||
|
||||
def __init__(self):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
def AddVillage(self, request):
|
||||
|
||||
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()
|
||||
|
||||
block_id = request.form.get('block_Id')
|
||||
village_name = request.form.get('Village_Name', '').strip()
|
||||
LogHelper.log_action("Add Village",
|
||||
f"User {current_user.id} adding village '{village_name}' to block '{block_id}'")
|
||||
|
||||
if not block_id:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("block"),
|
||||
400) # Assuming this is a valid response
|
||||
return
|
||||
|
||||
if not re.match(RegEx.patternAlphabetOnly, village_name):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("village"), 400)
|
||||
return
|
||||
|
||||
try:
|
||||
# Check if the village already exists in the block
|
||||
cursor.callproc("GetVillageByNameAndBlock", (village_name, block_id,))
|
||||
for rs in cursor.stored_results():
|
||||
existing_village = rs.fetchone()
|
||||
|
||||
if existing_village:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists("village"), 409)
|
||||
return
|
||||
|
||||
# Insert new village
|
||||
cursor.callproc('SaveVillage', (village_name, block_id))
|
||||
connection.commit()
|
||||
self.isSuccess = True
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_success("village"), 200)
|
||||
return
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Database Error: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("village"), 500)
|
||||
return
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
def GetAllVillages(self, request):
|
||||
|
||||
villages = []
|
||||
connection = config.get_db_connection()
|
||||
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
|
||||
if not connection:
|
||||
return []
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc("GetAllVillages")
|
||||
for result in cursor.stored_results():
|
||||
villages = result.fetchall()
|
||||
self.isSuccess = True
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching villages: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("village"), 500)
|
||||
return []
|
||||
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return villages
|
||||
|
||||
def CheckVillage(self, request):
|
||||
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
connection = config.get_db_connection()
|
||||
|
||||
if not connection:
|
||||
return HtmlHelper.json_response(ResponseHandler.db_connection_failure(), 500)
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
block_id = request.form.get('block_Id')
|
||||
village_name = request.form.get('Village_Name', '').strip()
|
||||
LogHelper.log_action("Check Village",
|
||||
f"User {current_user.id} checked village '{village_name}' in block '{block_id}'")
|
||||
|
||||
if not re.match(RegEx.patternAlphabetOnly, village_name):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("village"), 400)
|
||||
return self.resultMessage
|
||||
|
||||
if not block_id or not village_name:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(
|
||||
{'status': 'error', 'message': 'Block and Village Name are required!'}, 400)
|
||||
return self.resultMessage
|
||||
|
||||
try:
|
||||
cursor.callproc("GetVillageByNameAndBlocks", (village_name, block_id))
|
||||
for rs in cursor.stored_results():
|
||||
existing_village = rs.fetchone()
|
||||
|
||||
if existing_village:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.already_exists("village"), 409)
|
||||
else:
|
||||
self.isSuccess = True
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.is_available("village"), 200)
|
||||
|
||||
return self.resultMessage
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error checking village: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.add_failure("village"), 500)
|
||||
return self.resultMessage
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
def DeleteVillage(self, request, village_id):
|
||||
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
LogHelper.log_action("Delete Village", f"User {current_user.id} deleted village '{village_id}'")
|
||||
|
||||
try:
|
||||
cursor.callproc("DeleteVillage", (village_id,))
|
||||
connection.commit()
|
||||
self.resultMessage = ResponseHandler.delete_success("village") # Simple message, route will handle redirect
|
||||
self.isSuccess = True
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error deleting village: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.delete_failure("village"), 500)
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return self.resultMessage
|
||||
|
||||
def EditVillage(self, request, village_id):
|
||||
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
connection = config.get_db_connection()
|
||||
cursor = connection.cursor()
|
||||
|
||||
village_name = request.form['Village_Name'].strip()
|
||||
block_id = request.form['block_Id']
|
||||
LogHelper.log_action("Edit Village",
|
||||
f"User {current_user.id} edited village '{village_id}' to name '{village_name}'")
|
||||
|
||||
if not re.match(RegEx.patternAlphabetOnly, village_name):
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.invalid_name("village"), 400)
|
||||
return self.resultMessage
|
||||
|
||||
try:
|
||||
# Using the stored procedure pattern from your State class
|
||||
cursor.callproc("UpdateVillage", (village_id, block_id, village_name,))
|
||||
|
||||
connection.commit()
|
||||
self.isSuccess = True
|
||||
self.resultMessage = ResponseHandler.update_success("village")
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error updating data: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.update_failure("village"), 500)
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return self.resultMessage
|
||||
|
||||
def GetVillageByID(self, request, id):
|
||||
|
||||
village_data = None
|
||||
self.isSuccess = False
|
||||
self.resultMessage = ""
|
||||
connection = config.get_db_connection()
|
||||
|
||||
if not connection:
|
||||
return None
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.callproc("GetVillageDetailsById", (id,))
|
||||
for rs in cursor.stored_results():
|
||||
village_data = rs.fetchone()
|
||||
|
||||
if village_data:
|
||||
self.isSuccess = True
|
||||
self.resultMessage = "Success in Fetching"
|
||||
else:
|
||||
self.isSuccess = False
|
||||
self.resultMessage = "Village Not Found"
|
||||
|
||||
except mysql.connector.Error as e:
|
||||
print(f"Error fetching village: {e}")
|
||||
self.isSuccess = False
|
||||
self.resultMessage = HtmlHelper.json_response(ResponseHandler.fetch_failure("village"), 500)
|
||||
finally:
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
return village_data
|
||||
|
||||
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
|
||||
0
AppRoutes/StateRoute.py
Normal file
0
AppRoutes/StateRoute.py
Normal file
8
Reconciliation-Product.code-workspace
Normal file
8
Reconciliation-Product.code-workspace
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
8
Version-1.code-workspace
Normal file
8
Version-1.code-workspace
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
4972
activity.log
Normal file
4972
activity.log
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ import os
|
||||
# Get MySQL credentials from environment variables
|
||||
MYSQL_HOST = os.getenv("MYSQL_HOST", "127.0.0.1")
|
||||
MYSQL_USER = os.getenv("MYSQL_USER", "root")
|
||||
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "admin")
|
||||
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "root")
|
||||
MYSQL_DB = os.getenv("MYSQL_DB", "test")
|
||||
|
||||
# Connect to MySQL
|
||||
|
||||
0
logs/activity.log
Normal file
0
logs/activity.log
Normal file
74
logs/audit.log
Normal file
74
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
|
||||
102
templates/activity_log.html
Normal file
102
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>
|
||||
@@ -39,12 +39,18 @@
|
||||
<label for="invoice_No">Invoice No:</label><br>
|
||||
<input type="text" id="invoice_No" name="invoice_No" required><br><br>
|
||||
|
||||
<label for="basic_amount">Amount:</label><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">Total Amount:</label><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>
|
||||
@@ -75,6 +81,8 @@
|
||||
<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>
|
||||
@@ -87,6 +95,8 @@
|
||||
<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"
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</select><br><br>
|
||||
|
||||
<label for="invoice_No">Invoice No:</label><br>
|
||||
<input type="number" step="0.01" id="invoice_No" name="invoice_No" required><br><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>
|
||||
|
||||
147
templates/add_purchase_order.html
Normal file
147
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 %}
|
||||
@@ -29,6 +29,9 @@
|
||||
<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>
|
||||
|
||||
@@ -49,10 +49,10 @@
|
||||
</select><br>
|
||||
|
||||
<label>GST Registration Type :</label>
|
||||
<input type="text" name="GST_Registration_Type" required><br>
|
||||
<input type="text" name="GST_Registration_Type" ><br>
|
||||
|
||||
<label>GST No :</label>
|
||||
<input type="text" id="GST_No" name="GST_No" placeholder="Ex - 27AAACL5602N1ZE" required ><br>
|
||||
<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" >
|
||||
@@ -112,11 +112,11 @@
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for('deleteSubContractor', id=subc[0]) }}"
|
||||
onclick="return confirm('Are you sure you want to delete?')">
|
||||
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
|
||||
class="icon">
|
||||
</a>
|
||||
{# <a href="{{ url_for('deleteSubContractor', id=subc[0]) }}"#}
|
||||
{# onclick="return confirm('Are you sure you want to delete?')">#}
|
||||
{# <img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"#}
|
||||
{# class="icon">#}
|
||||
{# </a>#}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
137
templates/add_work_order.html
Normal file
137
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 %}
|
||||
39
templates/edit_grn.html
Normal file
39
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>
|
||||
@@ -21,14 +21,22 @@
|
||||
required><br><br>
|
||||
|
||||
|
||||
<label for="basic_amount">Amount:</label><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">Total Amount:</label><br>
|
||||
<label for="final_amount">Final Amount:</label><br>
|
||||
<input type="number" step="0.01" id="final_amount" name="final_amount" value="{{ gst_release_data[4] }}"
|
||||
required><br><br>
|
||||
|
||||
<label for="total_amount">Total Amount:</label><br>
|
||||
<input type="number" step="0.01" id="total_amount" name="total_amount" value="{{ gst_release_data[5] }}"
|
||||
required><br><br>
|
||||
|
||||
<label for="utr">UTR:</label><br>
|
||||
<input type="text" id="utr" name="utr" value="{{ gst_release_data[6] }}"
|
||||
required><br><br>
|
||||
|
||||
<button type="submit">Update GST Release</button>
|
||||
</form>
|
||||
|
||||
|
||||
47
templates/edit_purchase.html
Normal file
47
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>
|
||||
77
templates/grn_form.html
Normal file
77
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>
|
||||
@@ -6,8 +6,31 @@
|
||||
<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">
|
||||
|
||||
@@ -27,6 +50,22 @@
|
||||
<i class="fas fa-cog"></i> Report Details
|
||||
</a>
|
||||
</li>
|
||||
<!-- <li class="nav-item">
|
||||
<a href="/work_order_report" class="nav-link">
|
||||
<i class="fas fa-cog"></i> Work Order Report Details
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/purchase_order_report" class="nav-link">
|
||||
<i class="fas fa-cog"></i>Purchase Order Report Details
|
||||
</a>
|
||||
</li> -->
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('activity_log') }}" class="nav-link">
|
||||
<i class="fas fa-cog"></i> Logs
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="user-section">
|
||||
<img src="{{ url_for('static', filename='images/icons/male_profile.jpg') }}" alt="User Avatar">
|
||||
@@ -92,14 +131,31 @@
|
||||
<h2>Hold Types</h2>
|
||||
<a class="btn" href="/add_hold_type">Go ➜</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Hold Release Types</h2>
|
||||
<a class="btn" href="/add_hold_release">Go ➜</a>
|
||||
<h2>Work Order</h2>
|
||||
<a class="btn" href="/add_work_order">Go ➜</a>
|
||||
</div>
|
||||
<!-- <div class="card">
|
||||
<h2>Purchase Order</h2>
|
||||
<a class="btn" href="/add_purchase_order">Go ➜</a>
|
||||
</div> -->
|
||||
<!-- <div class="card">
|
||||
<h2>Goods Receive Note</h2>
|
||||
<a class="btn" href="/add_grn">Go ➜</a>
|
||||
</div> -->
|
||||
<!-- <div class="card">
|
||||
<h2>Unreleased GST</h2>
|
||||
<a class="btn" href="/unreleased_gst">Go ➜</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{# <div class="card">#}
|
||||
{# <h2>Hold Release </h2>#}
|
||||
{# <a class="btn" href="/add_hold_release">Go ➜</a>#}
|
||||
{# </div>#}
|
||||
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
90
templates/login.html
Normal file
90
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>
|
||||
10
templates/purchase_order.html
Normal file
10
templates/purchase_order.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Purchase Order</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
205
templates/purchase_order_report.html
Normal file
205
templates/purchase_order_report.html
Normal file
@@ -0,0 +1,205 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Purchase Order Report</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/subcontractor_report.css') }}">
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<style>
|
||||
h2.report-title {
|
||||
font-size: 28px;
|
||||
color: #1a73e8;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.filter-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
select {
|
||||
padding: 6px;
|
||||
}
|
||||
#downloadBtn, #searchBtn {
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
}
|
||||
#downloadBtn:hover, #searchBtn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
table {
|
||||
margin: 0 auto;
|
||||
margin-top: 30px;
|
||||
border-collapse: collapse;
|
||||
width: 95%;
|
||||
}
|
||||
th, td {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2 class="report-title">Purchase Order Report</h2>
|
||||
|
||||
<div class="filter-section">
|
||||
<div>
|
||||
<label for="supplier_name">Select Supplier Name:</label>
|
||||
<select id="supplier_name" name="supplier_name" style="width: 300px;"></select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="purchase_order_no">Select Purchase Order Number:</label>
|
||||
<select id="purchase_order_no" name="purchase_order_no" style="width: 300px;"></select>
|
||||
</div>
|
||||
|
||||
<button id="searchBtn">Search</button>
|
||||
<button id="downloadBtn" style="display: none;">Download Report</button>
|
||||
</div>
|
||||
|
||||
<table border="1" id="purchaseorderTable" style="display: none;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Purchase ID</th>
|
||||
<th>Purchase Date</th>
|
||||
<th>Supplier Name</th>
|
||||
<th>Purchase Order No</th>
|
||||
<th>Item Name</th>
|
||||
<th>Quantity</th>
|
||||
<th>Unit</th>
|
||||
<th>Rate</th>
|
||||
<th>Amount</th>
|
||||
<th>GST Amount</th>
|
||||
<th>TDS</th>
|
||||
<th>Final Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
// Initialize Select2 for supplier_name
|
||||
$('#supplier_name').select2({
|
||||
placeholder: 'Search Supplier Name',
|
||||
ajax: {
|
||||
url: '/get_supplier_names',
|
||||
data: function (params) {
|
||||
return { q: params.term };
|
||||
},
|
||||
processResults: function (data) {
|
||||
return {
|
||||
results: data.map(name => ({ id: name, text: name }))
|
||||
};
|
||||
},
|
||||
delay: 250,
|
||||
cache: true
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize Select2 for purchase_order_no
|
||||
$('#purchase_order_no').select2({
|
||||
placeholder: 'Search Purchase Order No',
|
||||
ajax: {
|
||||
url: '/get_purchase_order_numbers',
|
||||
data: function (params) {
|
||||
return {
|
||||
q: params.term,
|
||||
supplier_name: $('#supplier_name').val()
|
||||
};
|
||||
},
|
||||
processResults: function (data) {
|
||||
return {
|
||||
results: data.map(no => ({ id: no, text: no }))
|
||||
};
|
||||
},
|
||||
delay: 250,
|
||||
cache: true
|
||||
}
|
||||
});
|
||||
|
||||
// Search button click
|
||||
$('#searchBtn').click(function () {
|
||||
let supplier = $('#supplier_name').val();
|
||||
let poNumber = $('#purchase_order_no').val();
|
||||
|
||||
if (!supplier && !poNumber) {
|
||||
alert("Please select Supplier or Purchase Order No or both.");
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/get_purchase_order_data',
|
||||
data: {
|
||||
supplier_name: supplier,
|
||||
purchase_order_no: poNumber
|
||||
},
|
||||
success: function (data) {
|
||||
let tbody = $('#purchaseorderTable tbody');
|
||||
tbody.empty();
|
||||
|
||||
if (data.length > 0) {
|
||||
$('#purchaseorderTable').show();
|
||||
$('#downloadBtn').show();
|
||||
|
||||
data.forEach(function (row) {
|
||||
tbody.append(`
|
||||
<tr>
|
||||
<td>${row.purchase_id}</td>
|
||||
<td>${row.purchase_date}</td>
|
||||
<td>${row.supplier_name}</td>
|
||||
<td>${row.purchase_order_no}</td>
|
||||
<td>${row.item_name}</td>
|
||||
<td>${row.quantity}</td>
|
||||
<td>${row.unit}</td>
|
||||
<td>${row.rate}</td>
|
||||
<td>${row.amount}</td>
|
||||
<td>${row.GST_Amount}</td>
|
||||
<td>${row.TDS}</td>
|
||||
<td>${row.final_amount}</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
} else {
|
||||
alert("No matching purchase orders found.");
|
||||
$('#purchaseorderTable').hide();
|
||||
$('#downloadBtn').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Download report
|
||||
$('#downloadBtn').click(function () {
|
||||
let supplier = $('#supplier_name').val();
|
||||
let poNumber = $('#purchase_order_no').val();
|
||||
|
||||
if (!supplier && !poNumber) {
|
||||
alert("Please select filters before downloading.");
|
||||
return;
|
||||
}
|
||||
|
||||
let url = '/download_purchase_order_report?';
|
||||
if (supplier) url += 'supplier_name=' + encodeURIComponent(supplier);
|
||||
if (poNumber) url += '&purchase_order_no=' + encodeURIComponent(poNumber);
|
||||
|
||||
window.location.href = url;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
35
templates/unreleased_gst.html
Normal file
35
templates/unreleased_gst.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h2>🚫 GST Release Not Filled</h2>
|
||||
|
||||
<table border="1" cellpadding="8" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PMC No</th>
|
||||
<th>Invoice No</th>
|
||||
<th>Invoice Details</th>
|
||||
<th>GST_SD_Amount</th>
|
||||
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in data %}
|
||||
<tr>
|
||||
<td>{{ row.PMC_No }}</td>
|
||||
<td>{{ row.Invoice_No }}</td>
|
||||
<td>{{ row.Invoice_Details }}</td>
|
||||
<td>{{ row.GST_SD_Amount }}</td>
|
||||
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if data|length == 0 %}
|
||||
<p><strong>✅ All invoices have GST releases.</strong></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
209
templates/work_order_report.html
Normal file
209
templates/work_order_report.html
Normal file
@@ -0,0 +1,209 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<title>Work Order Report</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/subcontractor_report.css') }}">
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<style>
|
||||
h2.report-title {
|
||||
font-size: 28px;
|
||||
color: #1a73e8;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.filter-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
select {
|
||||
padding: 6px;
|
||||
}
|
||||
#downloadBtn, #searchBtn {
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
}
|
||||
#downloadBtn:hover, #searchBtn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
table {
|
||||
margin: 0 auto;
|
||||
margin-top: 30px;
|
||||
border-collapse: collapse;
|
||||
width: 95%;
|
||||
}
|
||||
th, td {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2 class="report-title">Work Order Report</h2>
|
||||
|
||||
<div class="filter-section">
|
||||
<div>
|
||||
<label for="vendor_name">Select Vendor Name:</label>
|
||||
<select id="vendor_name" name="vendor_name" style="width: 300px;"></select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="work_order_number">Select Work Order Number:</label>
|
||||
<select id="work_order_number" name="work_order_number" style="width: 300px;"></select>
|
||||
</div>
|
||||
|
||||
<button id="searchBtn">Search</button>
|
||||
<button id="downloadBtn" style="display: none;">Download Report</button>
|
||||
</div>
|
||||
|
||||
<table border="1" id="workOrderTable" style="display: none;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Vendor Name</th>
|
||||
<th>Work Order Type</th>
|
||||
<th>Amount</th>
|
||||
<th>BOQ</th>
|
||||
<th>Done %</th>
|
||||
<th>GST</th>
|
||||
<th>TDS</th>
|
||||
<th>Security Deposit</th>
|
||||
<th>SD GST</th>
|
||||
<th>Final Total</th>
|
||||
<th>TDS of GST</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<!-- JS Scripts -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#vendor_name, #work_order_number').select2({
|
||||
placeholder: 'Search',
|
||||
minimumInputLength: 1,
|
||||
ajax: {
|
||||
delay: 250,
|
||||
dataType: 'json',
|
||||
cache: true
|
||||
}
|
||||
});
|
||||
|
||||
$('#vendor_name').select2({
|
||||
placeholder: 'Search Vendor',
|
||||
ajax: {
|
||||
url: '/get_vendor_names',
|
||||
data: function (params) {
|
||||
return { q: params.term };
|
||||
},
|
||||
processResults: function (data) {
|
||||
return {
|
||||
results: data.map(v => ({ id: v, text: v }))
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#work_order_number').select2({
|
||||
placeholder: 'Search Work Order',
|
||||
ajax: {
|
||||
url: '/get_work_order_numbers',
|
||||
data: function (params) {
|
||||
return {
|
||||
q: params.term,
|
||||
vendor_name: $('#vendor_name').val()
|
||||
};
|
||||
},
|
||||
processResults: function (data) {
|
||||
return {
|
||||
results: data.map(o => ({ id: o, text: o }))
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch and display table
|
||||
$('#searchBtn').click(function () {
|
||||
let vendor = $('#vendor_name').val();
|
||||
let order = $('#work_order_number').val();
|
||||
|
||||
if (!vendor && !order) {
|
||||
alert("Please select Vendor or Work Order Number or both.");
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/get_work_order_data',
|
||||
data: {
|
||||
vendor_name: vendor,
|
||||
work_order_number: order
|
||||
},
|
||||
success: function (data) {
|
||||
let tbody = $('#workOrderTable tbody');
|
||||
tbody.empty();
|
||||
|
||||
if (data.length > 0) {
|
||||
$('#workOrderTable').show();
|
||||
$('#downloadBtn').show();
|
||||
|
||||
data.forEach(function (row) {
|
||||
tbody.append(`
|
||||
<tr>
|
||||
<td>${row.work_order_id}</td>
|
||||
<td>${row.vendor_name}</td>
|
||||
<td>${row.work_order_type}</td>
|
||||
<td>${row.work_order_amount}</td>
|
||||
<td>${row.boq_amount}</td>
|
||||
<td>${row.work_done_percentage}</td>
|
||||
<td>${row.gst_amount}</td>
|
||||
<td>${row.tds_amount}</td>
|
||||
<td>${row.security_deposit}</td>
|
||||
<td>${row.sd_against_gst}</td>
|
||||
<td>${row.final_total}</td>
|
||||
<td>${row.tds_of_gst}</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
} else {
|
||||
alert("No data found for selected filters.");
|
||||
$('#workOrderTable').hide();
|
||||
$('#downloadBtn').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Download report
|
||||
$('#downloadBtn').click(function () {
|
||||
let vendor = $('#vendor_name').val();
|
||||
let order = $('#work_order_number').val();
|
||||
|
||||
if (!vendor && !order) {
|
||||
alert("Please select filters before downloading.");
|
||||
return;
|
||||
}
|
||||
|
||||
let url = '/download_work_order_report?';
|
||||
if (vendor) url += 'vendor_name=' + encodeURIComponent(vendor);
|
||||
if (order) url += '&work_order_number=' + encodeURIComponent(order);
|
||||
|
||||
window.location.href = url;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
{% endblock %}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user