31 Commits

Author SHA1 Message Date
Swapnil9693
d84ba520cf GST Release Add, Edit, Delete Working 2026-03-24 18:46:07 +05:30
0aeaf775dd Merge pull request 'pankaj-dev - all done' (#18) from pankaj-dev into main
Reviewed-on: #18
2026-03-24 11:27:55 +00:00
88e8771b51 add log file function 2026-03-24 16:56:23 +05:30
6c74b5d3bf Merge branch 'main' of https://gitea.lcepl.org/pjpatil12/Payment_Reconciliation into pankaj-dev 2026-03-24 16:29:32 +05:30
47ba78d72c search on table 2026-03-24 16:29:18 +05:30
1946a98d59 Merge pull request 'Invoice Add Edit and Delete Done' (#17) from swapnil-dev into main
Reviewed-on: #17
2026-03-24 10:51:43 +00:00
8f35e08429 Merge pull request 'GST Release Display Chages' (#16) from laxmi-dev into main
Reviewed-on: #16
Reviewed-by: Pankaj J Patil <pankajjpatil2001@gmail.com>
2026-03-24 10:51:05 +00:00
82bedc3117 Update activity.log 2026-03-24 10:49:06 +00:00
cb68e454bc Update .env 2026-03-24 10:47:05 +00:00
Swapnil9693
91b078a0c3 edit_invoice html removed 2026-03-24 16:16:43 +05:30
Swapnil9693
eb46929893 New invoice commit 2026-03-24 16:13:04 +05:30
675301df7f GST Release Display Chages 2026-03-24 16:08:37 +05:30
Swapnil9693
14e799a1d4 Inoive add update delete messages shown properly 2026-03-24 15:51:55 +05:30
8ab1b69033 Merge pull request 'hold type updated , delete done by prajkta' (#13) from pankaj-dev into main
Reviewed-on: #13
2026-03-24 09:51:42 +00:00
dbeec9962d hold type updated , delete done by prajkta 2026-03-24 15:19:38 +05:30
8750f268db Merge pull request 'Village Add, Edit and Delete Messages done' (#12) from swapnil-dev into main
Reviewed-on: #12
2026-03-24 09:47:16 +00:00
Swapnil9693
b78526ad9f Village Add, Edit and Delete Messages done 2026-03-24 13:41:00 +05:30
7146391c18 Merge pull request 'delete payment code updated by prajakta' (#11) from pankaj-dev into main
Reviewed-on: #11
2026-03-24 07:21:43 +00:00
94b5563d15 delete payment code updated by prajakta 2026-03-24 12:50:44 +05:30
937018dc16 Merge pull request 'add item crude' (#10) from pankaj-dev into main
Reviewed-on: #10
2026-03-24 07:07:31 +00:00
eda238c235 add itemcrude 2026-03-24 12:36:15 +05:30
f184d6cecc Merge pull request 'update block redirect added' (#9) from pankaj-dev into main
Reviewed-on: #9
2026-03-24 06:26:39 +00:00
e7646eee76 Merge pull request 'Village module delete issue resolved and also message shown in alert' (#8) from swapnil-dev into main
Reviewed-on: #8
2026-03-24 06:25:19 +00:00
64ca39944b update block redirect added 2026-03-24 11:47:57 +05:30
Swapnil9693
6b4beb5af8 Village module delete issue resolved and also message shown on alert 2026-03-24 11:43:16 +05:30
Swapnil9693
d092eb0527 Sync .env and activity.log with main 2026-03-24 11:39:08 +05:30
Swapnil9693
f9e9612df5 Re-add .env and activity.log 2026-03-24 11:37:08 +05:30
Swapnil9693
46ec2c0276 Remove sensitive files (.env, activity.log) from repo 2026-03-24 11:32:00 +05:30
Swapnil9693
630ee1744f Backup Before Changes village Task 2026-03-24 11:28:14 +05:30
2679554e98 Merge pull request 'edit comments main code' (#7) from pankaj-dev into main
Reviewed-on: #7
2026-03-24 04:55:48 +00:00
0912aef85e edit comments 2026-03-23 18:45:16 +05:30
65 changed files with 1833 additions and 1341 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ __pycache__/
static/downloads/
static/uploads/

Binary file not shown.

View File

View File

View File

@@ -1,7 +1,7 @@
from flask import Blueprint, render_template, request, redirect, url_for, jsonify
import config
from flask import Blueprint, render_template, request, redirect, url_for, jsonify, flash
from flask_login import login_required
import config
from model.State import State
from model.Block import Block
from model.Utilities import HtmlHelper
@@ -74,7 +74,14 @@ def edit_block(block_id):
if request.method == 'POST':
block.EditBlock(request, block_id)
return block.resultMessage
block.resultMessage
if block.resultMessage:
flash("Block updated successfully!", "success")
return redirect(url_for('block.add_block'))
else:
flash(block.resultMessage, "error")
connection = config.get_db_connection()
cursor = connection.cursor()

View File

@@ -6,11 +6,10 @@ from model.State import State
district_bp = Blueprint('district', __name__)
# ------- District page --------
@district_bp.route('/add_district', methods=['GET', 'POST'])
@login_required
def add_district():
district = District()
if request.method == 'POST':
@@ -28,7 +27,7 @@ def add_district():
states=states
)
# ------- District check --------
@district_bp.route('/check_district', methods=['POST'])
@login_required
def check_district():
@@ -37,7 +36,7 @@ def check_district():
return district.CheckDistrict(request=request)
# ------- District delete by district id --------
@district_bp.route('/delete_district/<int:district_id>')
@login_required
def delete_district(district_id):
@@ -52,6 +51,7 @@ def delete_district(district_id):
return redirect(url_for('district.add_district'))
# ------- District update by district id --------
@district_bp.route('/edit_district/<int:district_id>', methods=['GET', 'POST'])
@login_required
def edit_district(district_id):

View File

@@ -1,8 +1,8 @@
from flask import Blueprint, render_template, request, redirect, url_for
# routes/gst_release_routes.py
from flask import Blueprint, render_template, request, redirect, url_for, flash
from flask_login import login_required
from model.gst_release import GSTRelease
from model.Log import LogHelper
from flask import flash, current_app
gst_release_bp = Blueprint('gst_release_bp', __name__)
gst_service = GSTRelease()
@@ -13,7 +13,7 @@ gst_service = GSTRelease()
def add_gst_release():
if request.method == 'POST':
gst_service.AddGSTRelease(request)
LogHelper.log_action("Add GST Release", f"User added GST release")
LogHelper.log_action("Add GST Release", "User added GST release")
flash(gst_service.resultMessage, 'success' if gst_service.isSuccess else 'error')
return redirect(url_for('gst_release_bp.add_gst_release'))
@@ -30,7 +30,7 @@ def edit_gst_release(gst_release_id):
if request.method == 'POST':
gst_service.EditGSTRelease(request, gst_release_id)
LogHelper.log_action("Edit GST Release", f"User edited GST release")
LogHelper.log_action("Edit GST Release", "User edited GST release")
flash(gst_service.resultMessage, 'success' if gst_service.isSuccess else 'error')
return redirect(url_for('gst_release_bp.add_gst_release'))
@@ -40,7 +40,7 @@ def edit_gst_release(gst_release_id):
@gst_release_bp.route('/delete_gst_release/<int:gst_release_id>', methods=['GET', 'POST'])
@login_required
def delete_gst_release(gst_release_id):
gst_service.DeleteGSTRelease(gst_release_id) # remove request
LogHelper.log_action("Delete GST Release", f"User deleted GST release")
gst_service.DeleteGSTRelease(gst_release_id)
LogHelper.log_action("Delete GST Release", "User deleted GST release")
flash(gst_service.resultMessage, 'success' if gst_service.isSuccess else 'error')
return redirect(url_for('gst_release_bp.add_gst_release'))

View File

@@ -10,21 +10,21 @@ hold_bp = Blueprint("hold_types", __name__)
@hold_bp.route('/add_hold_type', methods=['GET','POST'])
@login_required
def add_hold_type():
hold = HoldTypes()
if request.method == 'POST':
hold.AddHoldType(request) # ✅
return hold.resultMessage
hold.AddHoldType(request)
# ✅ Always redirect to same page (NO JSON)
return redirect(url_for("hold_types.add_hold_type"))
hold_types = hold.GetAllHoldTypes() # ✅
# GET request → show data
hold_types = hold.GetAllHoldTypes()
return render_template(
"add_hold_type.html",
Hold_Types_data=hold_types
)
# ---------------- CHECK HOLD TYPE (OPTIONAL LIKE BLOCK) ----------------
@hold_bp.route('/check_hold_type', methods=['POST'])
@login_required

View File

@@ -21,7 +21,6 @@ def activity_log():
end_date,
user_name
)
return render_template(
"activity_log.html",
logs=filtered_logs,

View File

@@ -86,12 +86,12 @@ def edit_payment(payment_id):
return render_template('edit_payment.html', payment_data=payment_data)
# ------------------- Delete Payment -------------------
@payment_bp.route('/delete_payment/<int:payment_id>', methods=['POST'])
@payment_bp.route('/delete_payment/<int:payment_id>', methods=['GET'])
@login_required
def delete_payment(payment_id):
success, pmc_no, invoice_no = Paymentmodel.delete_payment(payment_id)
if not success:
flash("Payment not found or failed to delete", "error")
else:

View File

@@ -4,7 +4,7 @@ from model.State import State
state_bp = Blueprint('state', __name__)
# ----- State page ------
@state_bp.route('/add_state', methods=['GET', 'POST'])
@login_required
def add_state():
@@ -19,7 +19,7 @@ def add_state():
return render_template('add_state.html', statedata=statedata)
# ----- State check ------
@state_bp.route('/check_state', methods=['POST'])
@login_required
def check_state():
@@ -28,21 +28,21 @@ def check_state():
return state.CheckState(request=request)
# ----- State delete by state id ------
@state_bp.route('/delete_state/<int:id>')
@login_required
def deleteState(id):
state = State()
msg = state.DeleteState(request=request, id=id)
state.DeleteState(request=request, id=id)
if not state.isSuccess:
return state.resultMessage
else:
return redirect(url_for('state.add_state'))
# ----- State update by state id ------
@state_bp.route('/edit_state/<int:id>', methods=['GET', 'POST'])
@login_required
def editState(id):

View File

@@ -83,17 +83,29 @@ def check_village():
return village.CheckVillage(request=request)
# ------------------------- Delete Village -------------------------
@village_bp.route('/delete_village/<int:village_id>')
@login_required
def delete_village(village_id):
village = Village()
village.DeleteVillage(request=request, village_id=village_id)
flash(village.resultMessage, "success" if village.isSuccess else "error")
return redirect(url_for('village.add_village'))
# ✅ Convert resultMessage to string if it's a Response or tuple
raw_msg = village.resultMessage
if isinstance(raw_msg, tuple):
# e.g., (<Response ...>, 200)
msg = "Village deleted successfully!"
elif hasattr(raw_msg, 'get_data'):
# Flask Response object
msg = raw_msg.get_data(as_text=True) # get raw text
else:
# fallback
msg = str(raw_msg) if raw_msg else "Village deleted successfully!"
return jsonify({
"status": "success" if village.isSuccess else "error",
"message": msg
})
# ------------------------- Edit Village -------------------------
@village_bp.route('/edit_village/<int:village_id>', methods=['GET', 'POST'])

View File

@@ -1,8 +1,4 @@
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 flask import Blueprint, render_template, request, redirect, url_for, jsonify
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
from model.Log import LogData, LogHelper
@@ -151,7 +147,7 @@ class Block:
self.isSuccess = block.isSuccess
self.resultMessage = block.resultMessage
return render_template('add_block.html')
return
# ----------------------------------------------------------
# Delete Block

View File

@@ -1,20 +1,6 @@
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash, jsonify, json
from flask import current_app
from datetime import datetime
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
from model.Log import LogData, LogHelper
import os
import config
import re
import mysql.connector
from mysql.connector import Error
from model.Utilities import ItemCRUDType
from model.ItemCRUD import ItemCRUD
class District:
isSuccess = False
@@ -24,7 +10,7 @@ class District:
self.isSuccess = False
self.resultMessage = ""
# edit district
def EditDistrict(self, request, district_id):
district = ItemCRUD(itemType=ItemCRUDType.District)
@@ -36,7 +22,7 @@ class District:
self.resultMessage = district.resultMessage
return
# add district
def AddDistrict(self, request):
district = ItemCRUD(ItemCRUDType.District)
@@ -50,7 +36,7 @@ class District:
return
# get all district data
def GetAllDistricts(self, request):
district = ItemCRUD(itemType=ItemCRUDType.District)
districtsdata = district.GetAllData(request=request, storedproc="GetAllDistricts")
@@ -58,7 +44,7 @@ class District:
self.resultMessage = district.resultMessage
return districtsdata
# check district validation
def CheckDistrict(self, request):
district = ItemCRUD(itemType=ItemCRUDType.District)
district_name = request.json.get('district_Name', '').strip()
@@ -68,13 +54,7 @@ class District:
self.resultMessage = district.resultMessage
return result
# def GetDistrictByID(self, request,district_id):
# district = ItemCRUD(itemType=ItemCRUDType.District)
# districtdata = district.GetAllData(id=district_id,storedproc="GetDistrictDataByID")
# self.isSuccess = district.isSuccess
# self.resultMessage = district.resultMessage
# return districtdata
# get district by district id
def GetDistrictByID(self, request, district_id):
district = ItemCRUD(itemType=ItemCRUDType.District)
@@ -92,7 +72,7 @@ class District:
return districtdata
#Delete District
# Delete District by district id
def DeleteDistrict(self, request, district_id):
district = ItemCRUD(itemType=ItemCRUDType.District)
district.DeleteItem(request=request,itemID=district_id,storedprocDelete="DeleteDistrict")

View File

@@ -27,13 +27,27 @@ class FolderAndFile:
os.makedirs(folder, exist_ok=True)
return folder
# -----------------------------
# FILE PATH METHODS
# -----------------------------
@staticmethod
def get_logs_folder():
folder = os.path.join(current_app.root_path, "logs")
if not os.path.exists(folder):
os.makedirs(folder)
os.makedirs(folder, exist_ok=True)
return folder
# FILE PATH METHODS - download
@staticmethod
def get_download_path(filename):
return os.path.join(FolderAndFile.get_download_folder(), filename)
# FILE PATH METHODS - upload file
@staticmethod
def get_upload_path(filename):
return os.path.join(FolderAndFile.get_upload_folder(), filename)
# FILE PATH METHODS - activity log file
@staticmethod
def get_activity_log_path(filename):
return os.path.join(FolderAndFile.get_logs_folder(), filename)

View File

@@ -6,7 +6,6 @@ import config
import re
import mysql.connector
# ----------------------------------------------------------
# Mapping Class
# ----------------------------------------------------------
@@ -23,10 +22,11 @@ class itemCRUDMapping:
self.name = "Hold Type"
elif itemType is ItemCRUDType.Subcontractor:
self.name = "Subcontractor"
elif itemType.name == "GSTRelease":
self.name = "GSTRelease"
else:
self.name = "Item"
# ----------------------------------------------------------
# Generic CRUD Class
# ----------------------------------------------------------
@@ -93,13 +93,47 @@ class ItemCRUD:
try:
# ======================================================
# SUBCONTRACTOR (MULTI-FIELD)
# GSTRelease MULTI-FIELD
# ======================================================
if data:
if self.itemCRUDType.name == "GSTRelease" and data:
# Duplicate check (PMC_No + Invoice_No)
if storedprocfetch:
cursor.callproc(storedprocfetch, (data['PMC_No'], data['Invoice_No']))
existing_item = None
for rs in cursor.stored_results():
existing_item = rs.fetchone()
if existing_item:
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.already_exists(self.itemCRUDMapping.name), 409
)
return
# Insert GSTRelease
cursor.callproc(storedprocadd, (
data['PMC_No'],
data['Invoice_No'],
data['Basic_Amount'],
data['Final_Amount'],
data['Total_Amount'],
data['UTR'],
data['Contractor_ID']
))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.add_success(self.itemCRUDMapping.name), 200
)
return
# ======================================================
# SUBCONTRACTOR MULTI-FIELD
# ======================================================
if self.itemCRUDType.name == "Subcontractor" and data:
# Duplicate check
cursor.callproc(storedprocfetch, (data['Contractor_Name'],))
existing_item = None
for rs in cursor.stored_results():
existing_item = rs.fetchone()
@@ -111,7 +145,6 @@ class ItemCRUD:
)
return
# Insert
cursor.callproc(storedprocadd, (
data['Contractor_Name'],
data['Address'],
@@ -123,26 +156,24 @@ class ItemCRUD:
data['GST_No'],
data['Contractor_password']
))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.add_success(self.itemCRUDMapping.name), 200
)
return
# ======================================================
# NORMAL (Village / Block / State)
# NORMAL SINGLE-FIELD (Village / Block / State)
# ======================================================
if not re.match(RegEx.patternAlphabetOnly, childname):
if not re.match(RegEx.allPattern, childname):
self.isSuccess = False
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400
)
return
# Duplicate check
if parentid is None:
cursor.callproc(storedprocfetch, (childname,))
else:
@@ -159,17 +190,14 @@ class ItemCRUD:
)
return
# Insert
if parentid is None:
cursor.callproc(storedprocadd, (childname,))
else:
cursor.callproc(storedprocadd, (childname, parentid))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.add_success(self.itemCRUDMapping.name), 200
)
@@ -199,9 +227,32 @@ class ItemCRUD:
try:
# ======================================================
# SUBCONTRACTOR (MULTI-FIELD)
# GSTRelease MULTI-FIELD
# ======================================================
if data:
if self.itemCRUDType.name == "GSTRelease" and data:
cursor.callproc(storedprocupdate, (
data['p_pmc_no'], # PMC_No
data['p_invoice_no'], # Invoice_No
data['p_basic_amount'], # Basic_Amount
data['p_final_amount'], # Final_Amount
data['p_total_amount'], # Total_Amount
data['p_utr'], # UTR
data['p_gst_release_id']# GST_Release_Id
))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.update_success(self.itemCRUDMapping.name), 200
)
return
# ======================================================
# SUBCONTRACTOR MULTI-FIELD
# ======================================================
if self.itemCRUDType.name == "Subcontractor" and data:
cursor.callproc(storedprocupdate, (
childid,
data['Contractor_Name'],
@@ -214,9 +265,7 @@ class ItemCRUD:
data['GST_No'],
data['Contractor_password']
))
connection.commit()
self.isSuccess = True
self.resultMessage = HtmlHelper.json_response(
ResponseHandler.update_success(self.itemCRUDMapping.name), 200
@@ -224,9 +273,9 @@ class ItemCRUD:
return
# ======================================================
# NORMAL
# NORMAL SINGLE-FIELD
# ======================================================
if not re.match(RegEx.patternAlphabetOnly, childname):
if not re.match(RegEx.allPattern, childname):
self.isSuccess = False
self.resultMessage = ResponseHandler.update_failure(self.itemCRUDMapping.name)['message']
return
@@ -237,7 +286,6 @@ class ItemCRUD:
cursor.callproc(storedprocupdate, (childid, parentid, childname))
connection.commit()
self.isSuccess = True
self.resultMessage = ResponseHandler.update_success(self.itemCRUDMapping.name)['message']
@@ -259,20 +307,15 @@ class ItemCRUD:
data = []
connection = config.get_db_connection()
if not connection:
return []
cursor = connection.cursor()
try:
cursor.callproc(storedproc)
for result in cursor.stored_results():
data = result.fetchall()
self.isSuccess = True
except mysql.connector.Error as e:
print(f"Error fetching {self.itemCRUDMapping.name}: {e}")
self.isSuccess = False
@@ -280,7 +323,6 @@ class ItemCRUD:
ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500
)
return []
finally:
cursor.close()
connection.close()
@@ -298,13 +340,10 @@ class ItemCRUD:
try:
cursor.callproc(storedproc, (id,))
for rs in cursor.stored_results():
data = rs.fetchone()
except mysql.connector.Error as e:
print(f"Error fetching {self.itemCRUDMapping.name}: {e}")
finally:
cursor.close()
connection.close()
@@ -324,7 +363,7 @@ class ItemCRUD:
f"User {current_user.id} checked '{childname}'"
)
if not re.match(RegEx.patternAlphabetOnly, childname):
if not re.match(RegEx.allPattern, childname):
return HtmlHelper.json_response(
ResponseHandler.invalid_name(self.itemCRUDMapping.name), 400
)
@@ -353,7 +392,6 @@ class ItemCRUD:
return HtmlHelper.json_response(
ResponseHandler.fetch_failure(self.itemCRUDMapping.name), 500
)
finally:
cursor.close()
connection.close()

View File

@@ -1,29 +1,41 @@
import os
from datetime import datetime
from flask import current_app
from flask_login import current_user
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from datetime import datetime
from model.FolderAndFile import FolderAndFile
class LogHelper:
@staticmethod
def log_action(action, details=""):
"""Add a log entry."""
log_data = LogData()
log_data.add_log(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.filepath = FolderAndFile.get_activity_log_path('activity.log')
self.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.user = getattr(current_user, "cn", None) \
or getattr(current_user, "username", None) \
or getattr(current_user, "sAMAccountName", "Unknown")
self.user = LogData.get_current_user()
def add_log(self, action, details=""):
"""Create/Add a log entry."""
@staticmethod
def get_current_user():
if hasattr(current_user, "cn") and current_user.cn:
return current_user.cn
elif hasattr(current_user, "username") and current_user.username:
return current_user.username
elif hasattr(current_user, "sAMAccountName") and current_user.sAMAccountName:
return current_user.sAMAccountName
return "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} | "
@@ -32,73 +44,41 @@ class LogData:
f"Details: {details}\n"
)
def get_all_logs(self):
"""Read all logs."""
def GetActivitiesLog(self):
logs = []
if os.path.exists(self.filepath):
with open(self.filepath, 'r', encoding="utf-8") as f:
with open(self.filepath, 'r') as f:
for line in f:
parts = line.strip().split(" | ")
if len(parts) == 4:
logs.append({
"timestamp": parts[0].split(":", 1)[1].strip(),
"user": parts[1].split(":", 1)[1].strip(),
"action": parts[2].split(":", 1)[1].strip(),
"details": parts[3].split(":", 1)[1].strip()
"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 get_filtered_logs(self, start_date=None, end_date=None, user_name=None):
"""Filter logs by date and/or user."""
logs = self.get_all_logs()
def GetFilteredActivitiesLog(self, startDate, endDate, userName):
filtered_logs = self.GetActivitiesLog()
# Filter by date
if start_date or end_date:
start_dt = datetime.strptime(start_date, "%Y-%m-%d") if start_date else datetime.min
end_dt = datetime.strptime(end_date, "%Y-%m-%d") if end_date else datetime.max
logs = [
log for log in logs
if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt
]
# 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
# Filter by username
if user_name:
logs = [log for log in logs if user_name.lower() in log.get("user", "").lower()]
filtered_logs = [
log for log in filtered_logs
if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt
]
return logs
def update_log(self, index, action=None, details=None):
"""Update a specific log entry by index (0-based)."""
logs = self.get_all_logs()
if 0 <= index < len(logs):
if action:
logs[index]["action"] = action
if details:
logs[index]["details"] = details
self._rewrite_logs_file(logs)
return True
return False
def delete_log(self, index):
"""Delete a specific log entry by index (0-based)."""
logs = self.get_all_logs()
if 0 <= index < len(logs):
logs.pop(index)
self._rewrite_logs_file(logs)
return True
return False
# ------------------- INTERNAL HELPER -------------------
def _rewrite_logs_file(self, logs):
"""Overwrite the log file with current logs."""
with open(self.filepath, "w", encoding="utf-8") as f:
for log in logs:
f.write(
f"Timestamp: {log['timestamp']} | "
f"User: {log['user']} | "
f"Action: {log['action']} | "
f"Details: {log['details']}\n"
)
except Exception as e:
print("Date filter error:", e)
# Username filter
if userName:
filtered_logs = [log for log in filtered_logs if userName.lower() in log["user"].lower()]
return filtered_logs

View File

@@ -1,13 +1,9 @@
from flask import request, redirect, url_for
from flask_login import current_user
from model.Utilities import RegEx, ResponseHandler, HtmlHelper, ItemCRUDType
from model.Log import LogHelper
from model.ItemCRUD import ItemCRUD
import config
import re
import mysql.connector
from flask import request, redirect, url_for
from model.Utilities import ResponseHandler, HtmlHelper, ItemCRUDType
from model.ItemCRUD import ItemCRUD
class State:
@@ -20,7 +16,6 @@ class State:
# ADD STATE (USING ITEM CRUD)
# ----------------------------------------------------------
def AddState(self, request):
state_name = request.form['state_Name'].strip()
crud = ItemCRUD(ItemCRUDType.State)
@@ -42,16 +37,14 @@ class State:
# GET ALL STATES (NO CHANGE - THIS IS CORRECT)
# ----------------------------------------------------------
def GetAllStates(self, request):
connection = config.get_db_connection()
data = []
if not connection:
return []
cursor = connection.cursor()
try:
cursor = connection.cursor()
cursor.callproc("GetAllStates")
for res in cursor.stored_results():
data = res.fetchall()
@@ -77,7 +70,6 @@ class State:
# CHECK STATE (USING ITEM CRUD)
# ----------------------------------------------------------
def CheckState(self, request):
state_name = request.json.get('state_Name', '').strip()
crud = ItemCRUD(ItemCRUDType.State)
@@ -94,7 +86,6 @@ class State:
# DELETE STATE (USING ITEM CRUD)
# ----------------------------------------------------------
def DeleteState(self, request, id):
crud = ItemCRUD(ItemCRUDType.State)
crud.DeleteItem(
@@ -113,7 +104,6 @@ class State:
# EDIT STATE (USING ITEM CRUD)
# ----------------------------------------------------------
def EditState(self, request, id):
state_name = request.form['state_Name'].strip()
crud = ItemCRUD(ItemCRUDType.State)
@@ -136,16 +126,14 @@ class State:
# GET STATE BY ID (KEEP SAME)
# ----------------------------------------------------------
def GetStateByID(self, request, id):
connection = config.get_db_connection()
data = None
if not connection:
return None
cursor = connection.cursor()
try:
cursor = connection.cursor()
cursor.callproc("GetStateByID", (id,))
for res in cursor.stored_results():
data = res.fetchone()

View File

@@ -8,10 +8,11 @@ class ItemCRUDType(Enum):
State = 4
HoldType = 5
Subcontractor = 6
GSTRelease = 7
class RegEx:
patternAlphabetOnly = "^[A-Za-z ]+$"
allPattern = "^(?!\s*$).+"
class ResponseHandler:
@@ -62,5 +63,5 @@ class HtmlHelper:
@staticmethod
def json_response(message_obj, status_code):
return jsonify(message_obj), status_code
#May need to refactor further

View File

@@ -1,4 +1,5 @@
# return blocks
from model.Utilities import ResponseHandler, HtmlHelper, ItemCRUDType
import config
import mysql.connector
@@ -12,12 +13,19 @@ class Village:
def __init__(self):
self.isSuccess = False
self.resultMessage = ""
self.response = {} # ✅ ADDED
self.village = ItemCRUD(itemType=ItemCRUDType.Village)
# 🔹 Helper: sync status
def _set_status(self, village):
self.isSuccess = village.isSuccess
self.resultMessage = village.resultMessage
# ✅ UPDATED (safe handling)
if hasattr(village, "response"):
self.response = village.response
self.resultMessage = village.response.get("message", "")
else:
self.resultMessage = village.resultMessage
# 🔹 Helper: get request data
def _get_form_data(self, request):
@@ -29,8 +37,9 @@ class Village:
block_id, village_name = self._get_form_data(request)
if not village_name:
self.response = ResponseHandler.invalid_name("village") # ✅ UPDATED
self.resultMessage = self.response["message"]
self.isSuccess = False
self.resultMessage = "Village name cannot be empty"
return
try:
@@ -66,8 +75,9 @@ class Village:
block_id, village_name = self._get_form_data(request)
if not village_name:
self.response = ResponseHandler.invalid_name("village") # ✅ UPDATED
self.resultMessage = self.response["message"]
self.isSuccess = False
self.resultMessage = "Village name cannot be empty"
return None
try:
@@ -103,8 +113,9 @@ class Village:
block_id, village_name = self._get_form_data(request)
if not village_name:
self.response = ResponseHandler.invalid_name("village") # ✅ UPDATED
self.resultMessage = self.response["message"]
self.isSuccess = False
self.resultMessage = "Village name cannot be empty"
return
try:
@@ -164,9 +175,11 @@ class Village:
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
)
# ✅ FIXED (removed jsonify response)
self.response = ResponseHandler.fetch_failure("block")
self.resultMessage = self.response["message"]
return []
finally:

View File

@@ -1,109 +1,8 @@
# from flask import request
# from model.ItemCRUD import ItemCRUD
# from model.Utilities import ItemCRUDType
# class GSTRelease:
# """CRUD operations for GST Release using ItemCRUD"""
# def __init__(self):
# self.isSuccess = False
# self.resultMessage = ""
# # ------------------- Add GST Release -------------------
# def AddGSTRelease(self, request):
# pmc_no = request.form.get('PMC_No', '').strip()
# invoice_no = request.form.get('invoice_No', '').strip()
# gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# gst.AddItem(
# request=request,
# parentid=None,
# childname=f"{pmc_no}-{invoice_no}",
# storedprocfetch="CheckGSTReleaseExists",
# storedprocadd="AddGSTReleaseFromExcel" # your stored procedure handles extra fields
# )
# self.isSuccess = gst.isSuccess
# self.resultMessage = str(gst.resultMessage)
# # ------------------- Get All GST Releases -------------------
# def GetAllGSTReleases(self):
# gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# # Pass request=None for fetch
# rows = gst.GetAllData(request=None, storedproc="GetAllGSTReleases")
# self.isSuccess = gst.isSuccess
# self.resultMessage = str(gst.resultMessage)
# data = []
# for row in rows:
# data.append({
# "gst_release_id": row[0],
# "pmc_no": row[1],
# "invoice_no": row[2],
# "basic_amount": row[3],
# "final_amount": row[4],
# "total_amount": row[5],
# "utr": row[6],
# "contractor_id": row[7]
# })
# return data
# # ------------------- Get GST Release By ID -------------------
# def GetGSTReleaseByID(self, gst_release_id):
# gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# row = gst.GetDataByID(gst_release_id, request=None, storedproc="GetGSTReleaseById")
# self.isSuccess = gst.isSuccess
# self.resultMessage = str(gst.resultMessage)
# if row:
# return {
# "gst_release_id": row[0],
# "pmc_no": row[1],
# "invoice_no": row[2],
# "basic_amount": row[3],
# "final_amount": row[4],
# "total_amount": row[5],
# "utr": row[6],
# "contractor_id": row[7]
# }
# return None
# # ------------------- Edit GST Release -------------------
# def EditGSTRelease(self, request, gst_release_id):
# pmc_no = request.form.get('PMC_No', '').strip()
# invoice_no = request.form.get('invoice_No', '').strip()
# gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# gst.EditItem(
# request=request,
# childid=gst_release_id,
# parentid=None,
# childname=f"{pmc_no}-{invoice_no}",
# storedprocupdate="UpdateGSTRelease" # stored procedure handles extra fields
# )
# self.isSuccess = gst.isSuccess
# self.resultMessage = str(gst.resultMessage)
# # ------------------- Delete GST Release -------------------
# def DeleteGSTRelease(self, gst_release_id):
# gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# gst.DeleteItem(
# itemID=gst_release_id,
# request=None,
# storedprocDelete="DeleteGSTReleaseById"
# )
# self.isSuccess = gst.isSuccess
# self.resultMessage = str(gst.resultMessage)
# model/gst_release.py
from flask import request, jsonify
from model.ItemCRUD import ItemCRUD
from model.Utilities import ItemCRUDType
class GSTRelease:
def __init__(self):
@@ -115,6 +14,12 @@ class GSTRelease:
try:
gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# Print the full form data
print("===== DEBUG: FORM DATA =====")
for key, value in request.form.items():
print(f"{key} : {value}")
print("=============================")
data = {
"PMC_No": request.form.get("PMC_No", "").strip(),
"Invoice_No": request.form.get("Invoice_No", "").strip(),
@@ -125,6 +30,11 @@ class GSTRelease:
"Contractor_ID": int(request.form.get("Contractor_ID", 0) or 0)
}
print("===== DEBUG: PARSED DATA =====")
print(data)
print("==============================")
# Add GST Release
gst.AddItem(
request=request,
data=data,
@@ -132,6 +42,8 @@ class GSTRelease:
storedprocadd="AddGSTReleaseFromExcel"
)
print(f"AddItem result: isSuccess={gst.isSuccess}, message={gst.resultMessage}")
self.isSuccess = gst.isSuccess
self.resultMessage = str(gst.resultMessage)
@@ -142,20 +54,26 @@ class GSTRelease:
return jsonify({"success": self.isSuccess, "message": self.resultMessage})
# ------------------- Edit GST Release -------------------
def EditGSTRelease(self, request, gst_release_id):
try:
gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# Map form inputs to stored procedure parameters
data = {
"PMC_No": request.form.get("PMC_No", "").strip(),
"Invoice_No": request.form.get("Invoice_No", "").strip(),
"Basic_Amount": float(request.form.get("Basic_Amount", 0) or 0),
"Final_Amount": float(request.form.get("Final_Amount", 0) or 0),
"Total_Amount": float(request.form.get("Total_Amount", 0) or 0),
"UTR": request.form.get("UTR", "").strip()
"p_pmc_no": request.form.get("PMC_No", "").strip(),
"p_invoice_no": request.form.get("invoice_no", "").strip(),
"p_basic_amount": float(request.form.get("Basic_Amount", 0) or 0),
"p_final_amount": float(request.form.get("Final_Amount", 0) or 0),
"p_total_amount": float(request.form.get("Total_Amount", 0) or 0),
"p_utr": request.form.get("UTR", "").strip(),
"p_gst_release_id": gst_release_id
}
print("===== DEBUG: UPDATE DATA =====")
print(data)
print("==============================")
# Call your stored procedure
gst.EditItem(
request=request,
childid=gst_release_id,
@@ -171,8 +89,6 @@ class GSTRelease:
self.isSuccess = False
self.resultMessage = str(e)
return jsonify({"success": self.isSuccess, "message": self.resultMessage})
# ------------------- Delete GST Release -------------------
def DeleteGSTRelease(self, gst_release_id):
try:
@@ -198,7 +114,6 @@ class GSTRelease:
def GetAllGSTReleases(self):
try:
gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
rows = gst.GetAllData(None, "GetAllGSTReleases")
data = []
@@ -224,7 +139,6 @@ class GSTRelease:
def GetGSTReleaseByID(self, gst_release_id):
try:
gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
row = gst.GetDataByID(gst_release_id, "GetGSTReleaseById")
if row:

View File

@@ -3,7 +3,7 @@ $(document).ready(function () {
let holdType = $(this).val().replace(/^\s+/, "");
$(this).val(holdType);
let reg = /^[A-Za-z]/;
let reg = /^.+$/; // all pattern allow
if (!reg.test(holdType)) {
$("#holdTypeMessage").text("Hold Type must start with a letter.").css("color", "red");

View File

@@ -1,62 +1,271 @@
// Subcontractor autocomplete functionality
$(document).ready(function () {
$("#subcontractor").keyup(function () {
// $(document).ready(function () {
// // ===============================
// // FORM / TABLE TOGGLE
// // ===============================
// if ($('#addForm').length && $('#addTable').length) {
// $('#addForm').show();
// $('#addTable').hide();
// $('#addButton').click(function () {
// $('#addForm').show();
// $('#addTable').hide();
// });
// $('#displayButton').click(function () {
// $('#addForm').hide();
// $('#addTable').show();
// });
// }
// // ===============================
// // Subcontractor autocomplete
// // ===============================
// $("#subcontractor").keyup(function () {
// let query = $(this).val();
// if (query !== "") {
// $.ajax({
// url: "/search_subcontractor",
// method: "POST",
// data: { query: query },
// success: function (data) {
// $("#subcontractor_list").fadeIn().html(data);
// }
// });
// } else {
// $("#subcontractor_list").fadeOut();
// }
// });
// $(document).on("click", "li", function () {
// $("#subcontractor").val($(this).text());
// $("#subcontractor_id").val($(this).attr("data-id"));
// $("#subcontractor_list").fadeOut();
// });
// // Focus
// if (document.getElementById('subcontractor')) {
// document.getElementById('subcontractor').focus();
// }
// // ===============================
// // ADD INVOICE
// // ===============================
// if ($('#invoiceForm').length && window.location.href.includes('add_invoice')) {
// $("#invoiceForm").on("submit", function (e) {
// e.preventDefault();
// let formData = $(this).serialize();
// $.ajax({
// url: '/add_invoice',
// method: 'POST',
// data: formData,
// dataType: 'json',
// success: function (response) {
// if (response.status === "success") {
// alert(response.message || "Invoice added successfully!");
// $('#invoiceForm')[0].reset(); // clear form
// $('#addForm').hide();
// $('#addTable').show(); // switch to table
// location.reload(); // optional refresh
// } else {
// alert(response.message || "Error adding invoice.");
// }
// },
// error: function (xhr) {
// alert(xhr.responseJSON?.message || "Submission failed. Please try again.");
// }
// });
// });
// }
// // Example AJAX update function
// function updateInvoice(invoiceId, formElement) {
// const formData = $(formElement).serialize();
// $.ajax({
// url: '/update_invoice/' + invoiceId,
// method: 'POST',
// data: formData,
// dataType: 'json',
// success: function(response) {
// if(response.status === "success") {
// alert(response.message || "Invoice updated successfully!");
// // ✅ Hide Add Form, Show Table
// $('#addForm').hide();
// $('#addTable').show();
// // Optional: reload table or refresh page
// location.reload();
// } else {
// alert(response.message || "Update failed. Please try again.");
// }
// },
// error: function(xhr) {
// alert(xhr.responseJSON?.message || "Error updating invoice.");
// }
// });
// }
// // ===============================
// // DELETE INVOICE
// // ===============================
// function deleteInvoice(invoiceId, element) {
// if (!confirm("Are you sure you want to delete this invoice?")) return;
// $.ajax({
// url: '/delete_invoice/' + invoiceId,
// method: 'GET',
// dataType: 'json',
// success: function (response) {
// alert(response.message || "Invoice deleted successfully!");
// if (element) $(element).closest("tr").remove();
// },
// error: function (xhr) {
// alert(xhr.responseJSON?.message || "Error deleting invoice. Please try again.");
// }
// });
// }
$(document).ready(function () {
// ===============================
// FORM / TABLE TOGGLE
// ===============================
if ($('#addForm').length && $('#addTable').length) {
// Default: show form, hide table
$('#addForm').show();
$('#addTable').hide();
// ✅ Check URL hash to show table instead
if (window.location.hash === "#addTable") {
$('#addForm').hide();
$('#addTable').show();
}
$('#addButton').click(function () {
$('#addForm').show();
$('#addTable').hide();
});
$('#displayButton').click(function () {
$('#addForm').hide();
$('#addTable').show();
});
}
// ===============================
// Subcontractor autocomplete
// ===============================
$("#subcontractor").keyup(function () {
let query = $(this).val();
if (query !== "") {
$.ajax({
url: "/search_subcontractor",
method: "POST",
data: { query: query },
success: function (data) {
$("#subcontractor_list").fadeIn().html(data);
}
});
$.ajax({
url: "/search_subcontractor",
method: "POST",
data: { query: query },
success: function (data) {
$("#subcontractor_list").fadeIn().html(data);
}
});
} else {
$("#subcontractor_list").fadeOut();
$("#subcontractor_list").fadeOut();
}
});
});
$(document).on("click", "li", function () {
$(document).on("click", "li", function () {
$("#subcontractor").val($(this).text());
$("#subcontractor_id").val($(this).attr("data-id"));
$("#subcontractor_list").fadeOut();
});
});
// Success Alert: show alert and reload after 3 seconds
function showSuccessAlert() {
const alertBox = document.getElementById("invoiceSuccessAlert");
alertBox.classList.add("show");
setTimeout(() => {
alertBox.classList.remove("show");
// Reload page or redirect after alert hides
window.location.href = '/add_invoice';
}, 3000);
// Focus
if (document.getElementById('subcontractor')) {
document.getElementById('subcontractor').focus();
}
// Submit form via AJAX
$("#invoiceForm").on("submit", function (e) {
e.preventDefault();
let formData = $(this).serialize();
$.ajax({
url: '/add_invoice',
method: 'POST',
data: formData,
success: function (response) {
if(response.status === "success") {
showSuccessAlert();
} else {
alert(response.message);
}
},
error: function (xhr, status, error) {
alert("Submission failed: " + error);
}
});
});
// ===============================
// ADD INVOICE
// ===============================
if ($('#invoiceForm').length && window.location.href.includes('add_invoice')) {
$("#invoiceForm").on("submit", function (e) {
e.preventDefault();
let formData = $(this).serialize();
$.ajax({
url: '/add_invoice',
method: 'POST',
data: formData,
dataType: 'json',
success: function (response) {
if (response.status === "success") {
alert(response.message || "Invoice added successfully!");
$('#invoiceForm')[0].reset(); // clear form
$('#addForm').hide();
$('#addTable').show(); // switch to table
location.reload(); // optional refresh
} else {
alert(response.message || "Error adding invoice.");
}
},
error: function (xhr) {
let msg = xhr.responseJSON?.message || "Submission failed. Please try again.";
alert(msg);
}
});
});
}
// ===============================
// UPDATE INVOICE
// ===============================
function updateInvoice(invoiceId, formElement) {
const formData = $(formElement).serialize();
window.onload = function () {
document.getElementById('subcontractor').focus();
};
$.ajax({
url: '/update_invoice/' + invoiceId,
method: 'POST',
data: formData,
dataType: 'json',
success: function(response) {
if(response.status === "success") {
alert(response.message || "Invoice updated successfully!");
// Redirect to Add Invoice page's table part
window.location.href = "/add_invoice#addTable";
} else {
alert(response.message || "Update failed. Please try again.");
}
},
error: function(xhr) {
let msg = xhr.responseJSON?.message || "Error updating invoice.";
alert(msg);
}
});
}
window.updateInvoice = updateInvoice; // make globally accessible
// ===============================
// DELETE INVOICE
// ===============================
function deleteInvoice(invoiceId, element) {
if (!confirm("Are you sure you want to delete this invoice?")) return;
$.ajax({
url: '/delete_invoice/' + invoiceId,
method: 'GET',
dataType: 'json',
success: function (response) {
if (response.status === "success") {
alert(response.message || "Invoice deleted successfully!");
if (element) $(element).closest("tr").remove();
} else {
alert(response.message || "Error deleting invoice.");
}
},
error: function (xhr) {
let msg = xhr.responseJSON?.message || "Error deleting invoice. Please try again.";
alert(msg);
}
});
}
window.deleteInvoice = deleteInvoice; // make globally accessible
});

View File

@@ -1,26 +1,23 @@
// Search on table using search inpute options
function searchTable() {
let input = document.getElementById("searchBar").value.toLowerCase();
let rows = document.querySelectorAll("table tbody tr");
let tables = document.querySelectorAll("table");
rows.forEach(row => {
let blockName = row.cells[1].textContent.toLowerCase();
let districtName = row.cells[2].textContent.toLowerCase();
let villageName = row.cells[3].textContent.toLowerCase();
tables.forEach(table => {
let rows = table.querySelectorAll("tr");
if (blockName.includes(input) || districtName.includes(input)|| villageName.includes(input)) {
row.style.display = "";
} else {
row.style.display = "none";
}
rows.forEach((row, index) => {
if (index === 0) return; // header skip
let text = row.textContent.toLowerCase();
row.style.display = text.includes(input) ? "" : "none";
});
});
}
// Common Sorting Script for Tables
function sortTable(n, dir) {
var table, rows, switching, i, x, y, shouldSwitch;
@@ -57,14 +54,14 @@ function sortTable(n, dir) {
}
// Attach sorting functionality to all sortable tables
document.addEventListener("DOMContentLoaded", function() {
document.addEventListener("DOMContentLoaded", function () {
// Find all elements with the class "sortable-header"
var sortableHeaders = document.querySelectorAll(".sortable-header");
sortableHeaders.forEach(function(header) {
sortableHeaders.forEach(function (header) {
// Attach click event for ascending sort
if (header.querySelector(".sort-asc")) {
header.querySelector(".sort-asc").addEventListener("click", function() {
header.querySelector(".sort-asc").addEventListener("click", function () {
var columnIndex = Array.from(header.parentNode.children).indexOf(header);
sortTable(columnIndex, "asc");
});
@@ -72,7 +69,7 @@ document.addEventListener("DOMContentLoaded", function() {
// Attach click event for descending sort
if (header.querySelector(".sort-desc")) {
header.querySelector(".sort-desc").addEventListener("click", function() {
header.querySelector(".sort-desc").addEventListener("click", function () {
var columnIndex = Array.from(header.parentNode.children).indexOf(header);
sortTable(columnIndex, "desc");
});
@@ -106,3 +103,30 @@ document.addEventListener("DOMContentLoaded", function () {
addButton.classList.remove("active-button");
});
});
document.addEventListener("DOMContentLoaded", function () {
let tables = document.querySelectorAll("table");
tables.forEach(table => {
let header = table.querySelector("tr:first-child");
if (header) {
header.style.position = "sticky";
header.style.top = "0";
header.style.background = "#fff";
header.style.zIndex = "2";
}
if (!table.parentElement.classList.contains("table-wrapper")) {
let wrapper = document.createElement("div");
wrapper.classList.add("table-wrapper");
wrapper.style.maxHeight = "65vh"
wrapper.style.overflowY = "auto";
table.parentNode.insertBefore(wrapper, table);
wrapper.appendChild(table);
}
});
});

View File

@@ -1,198 +1,250 @@
window.onload = function () {
document.getElementById('Village_Name').focus();
if (document.getElementById('Village_Name')) {
document.getElementById('Village_Name').focus();
}
};
$(document).ready(function () {
// ✅ RUN ONLY IF THIS PAGE HAS FORM/TABLE
if ($('#addForm').length && $('#addTable').length) {
// ✅ DEFAULT VIEW → SHOW FORM
$('#addForm').show();
$('#addTable').hide();
// 🔥 BUTTON TOGGLE
$('#addButton').click(function () {
$('#addForm').show();
$('#addTable').hide();
});
$('#displayButton').click(function () {
$('#addForm').hide();
$('#addTable').show();
});
}
// ===============================
// STATE → DISTRICT
$('#state_Id').change(function () {
// ===============================
if ($('#state_Id').length) {
var stateId = $(this).val();
$('#state_Id').change(function () {
if (stateId) {
var stateId = $(this).val();
$.ajax({
url: '/get_districts/' + stateId,
type: 'GET',
if (stateId) {
success: function (data) {
$.ajax({
url: '/get_districts/' + stateId,
type: 'GET',
var districtDropdown = $('#district_Id');
success: function (data) {
districtDropdown.empty();
districtDropdown.append('<option value="" disabled selected>Select District</option>');
var districtDropdown = $('#district_Id');
data.forEach(function (district) {
districtDropdown.empty();
districtDropdown.append('<option value="" disabled selected>Select District</option>');
districtDropdown.append(
'<option value="' + district.id + '">' + district.name + '</option>'
);
data.forEach(function (district) {
districtDropdown.append(
'<option value="' + district.id + '">' + district.name + '</option>'
);
});
});
districtDropdown.prop('disabled', false);
}
});
}
});
districtDropdown.prop('disabled', false);
}
});
}
});
}
// ===============================
// DISTRICT → BLOCK
$('#district_Id').change(function () {
// ===============================
if ($('#district_Id').length) {
var districtId = $(this).val();
$('#district_Id').change(function () {
if (districtId) {
var districtId = $(this).val();
$.ajax({
url: '/get_blocks/' + districtId,
type: 'GET',
if (districtId) {
success: function (data) {
$.ajax({
url: '/get_blocks/' + districtId,
type: 'GET',
var blockDropdown = $('#block_Id');
success: function (data) {
blockDropdown.empty();
blockDropdown.append('<option value="" disabled selected>Select Block</option>');
var blockDropdown = $('#block_Id');
data.forEach(function (block) {
blockDropdown.empty();
blockDropdown.append('<option value="" disabled selected>Select Block</option>');
blockDropdown.append(
'<option value="' + block.id + '">' + block.name + '</option>'
);
data.forEach(function (block) {
blockDropdown.append(
'<option value="' + block.id + '">' + block.name + '</option>'
);
});
});
blockDropdown.prop('disabled', false);
}
});
}
});
blockDropdown.prop('disabled', false);
}
});
}
});
}
// ===============================
// VILLAGE NAME VALIDATION
$('#Village_Name').on('input', function () {
// ===============================
if ($('#Village_Name').length) {
var villageName = $(this).val();
var validPattern = /^[A-Za-z ]*$/;
$('#Village_Name').on('input', function () {
if (!validPattern.test(villageName)) {
var villageName = $(this).val();
var validPattern = /^[A-Za-z ]*$/;
$('#villageMessage')
.text('Only letters and spaces are allowed!')
.css('color', 'red');
if (!validPattern.test(villageName)) {
$('#submitVillage').prop('disabled', true);
$('#villageMessage')
.text('Only letters and spaces are allowed!')
.css('color', 'red');
} else {
$('#submitVillage').prop('disabled', true);
$('#villageMessage').text('');
$('#submitVillage').prop('disabled', false);
} else {
}
});
$('#villageMessage').text('');
$('#submitVillage').prop('disabled', false);
}
});
}
// ===============================
// CHECK DUPLICATE VILLAGE
$('#Village_Name, #block_Id').on('change keyup', function () {
// ===============================
if ($('#Village_Name').length && $('#block_Id').length) {
var blockId = $('#block_Id').val();
var villageName = $('#Village_Name').val().trim();
$('#Village_Name, #block_Id').on('change keyup', function () {
if (blockId && villageName) {
var blockId = $('#block_Id').val();
var villageName = $('#Village_Name').val().trim();
$.ajax({
if (blockId && villageName) {
url: '/check_village',
type: 'POST',
$.ajax({
url: '/check_village',
type: 'POST',
data: {
block_Id: blockId,
Village_Name: villageName
},
data: {
block_Id: blockId,
Village_Name: villageName
},
success: function (response) {
success: function (response) {
if (response.status === 'exists') {
if (response.status === 'exists') {
$('#villageMessage')
.text(response.message)
.css('color', 'red');
$('#submitVillage').prop('disabled', true);
} else {
$('#villageMessage')
.text(response.message)
.css('color', 'green');
$('#submitVillage').prop('disabled', false);
}
},
error: function () {
$('#villageMessage')
.text(response.message)
.text('Error checking village name')
.css('color', 'red');
$('#submitVillage').prop('disabled', true);
}
});
}
});
}
// ===============================
// ADD VILLAGE (SAFE SCOPE FIX)
// ===============================
if ($('#villageForm').length) {
$('#villageForm').submit(function (event) {
event.preventDefault();
$.ajax({
url: '/add_village',
type: 'POST',
data: $(this).serialize(),
success: function (response) {
if (response && response.status === 'success') {
alert(response.message || 'Village added successfully!');
// ✅ clear form
$('#villageForm')[0].reset();
// ✅ switch to table
$('#addForm').hide();
$('#addTable').show();
// optional refresh
location.reload();
} else {
$('#villageMessage')
.text(response.message)
.css('color', 'green');
$('#submitVillage').prop('disabled', false);
alert(response.message || 'Error adding village. Please try again.');
}
},
error: function () {
$('#villageMessage')
.text('Error checking village name')
.css('color', 'red');
$('#submitVillage').prop('disabled', true);
alert('An error occurred. Please try again.');
}
});
}
});
// ADD VILLAGE
$('#villageForm').submit(function (event) {
event.preventDefault();
$.ajax({
url: '/add_village',
type: 'POST',
data: $(this).serialize(),
success: function (response) {
if (response.status === 'success') {
alert('Village added successfully!');
location.reload();
} else {
alert('Error adding village. Please try again.');
}
},
error: function () {
alert('An error occurred. Please try again.');
}
});
});
}
});
// ===============================
// DELETE FUNCTION (SAFE)
// ===============================
function deleteVillage(villageId, element) {
if (!confirm("Are you sure you want to delete this village?")) return;
$.ajax({
url: '/delete_village/' + villageId,
type: 'GET',
dataType: 'json',
success: function (response) {
alert(response.message); // ✅ now shows "Village deleted successfully!"
if (element) $(element).closest("tr").remove();
},
error: function () {
alert("Error deleting village. Please try again.");
}
});
}

View File

@@ -20,12 +20,16 @@
<h2>Add GST Release</h2>
<form action="/add_gst_release" method="POST" onsubmit="showSuccessAlert(event)">
<div class="row1">
<div>
<label for="subcontractor">Subcontractor Name:</label>
<input type="text" id="subcontractor" name="subcontractor" required autocomplete="off"/>
<input type="hidden" id="subcontractor_id" name="subcontractor_id"/>
<div id="subcontractor_list" class="autocomplete-items"></div>
</div>
<div>
<label for="subcontractor">Subcontractor Name:</label>
<!-- Text input for user-friendly autocomplete -->
<input type="text" id="subcontractor" name="subcontractor" required autocomplete="off"/>
<!-- Hidden input for backend; must match model's Contractor_ID -->
<input type="hidden" id="subcontractor_id" name="Contractor_ID"/>
<div id="subcontractor_list" class="autocomplete-items"></div>
</div>
</div>
<label for="PMC_No">PMC No:</label><br>
@@ -37,19 +41,19 @@
</select><br><br>
<label for="invoice_No">Invoice No:</label><br>
<input type="text" id="invoice_No" name="invoice_No" required><br><br>
<input type="text" id="invoice_No" name="Invoice_No" required><br><br>
<label for="basic_amount">Basic Amount:</label><br>
<input type="number" step="0.01" id="basic_amount" name="basic_amount" placeholder="₹ - 00.00" required><br><br>
<input type="number" step="0.01" id="basic_amount" name="Basic_Amount" placeholder="₹ - 00.00" required><br><br>
<label for="final_amount">Final Amount:</label><br>
<input type="number" step="0.01" id="final_amount" name="final_amount" placeholder="₹ - 00.00" required><br><br>
<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>
<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>
<input type="text" id="utr" name="UTR" required><br><br>
<button type="submit">Submit GST Release</button>
</form>
@@ -68,96 +72,110 @@
{% endwith %}
</div>
<!-- GST Release History Table -->
<div id="addTable" style="display: none;">
<div class="search-container">
<h2>GST Release History</h2>
<input type="text" id="searchBar" placeholder="Searching..." onkeyup="searchTable()">
<input type="text" id="searchBar" placeholder="Search..." onkeyup="searchTable()">
</div>
<table id="sortableTable" border="1">
<thead>
<tr>
<th class="sortable-header">GST_Release_Id</th>
<th class="sortable-header">PMC_No</th>
<th>Invoice_No</th>
<th>Basic_Amount</th>
<th>Final_Amount</th>
<th>Total_Amount</th>
<th>UTR</th>
<th>Update</th>
<th>Delete</th>
</tr>
<tr>
<th class="sortable-header">GST_Release_Id</th>
<th class="sortable-header">PMC_No</th>
<th>Invoice_No</th>
<th>Basic_Amount</th>
<th>Final_Amount</th>
<th>Total_Amount</th>
<th>UTR</th>
<th>Update</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for gst_rel in gst_releases %}
<tr>
<td>{{ gst_rel[0] }}</td>
<td>{{ gst_rel[1] }}</td>
<td>{{ gst_rel[2] }}</td>
<td>{{ gst_rel[3] }}</td>
<td>{{ gst_rel[4] }}</td>
<td>{{ gst_rel[5] }}</td>
<td>{{ gst_rel[6] }}</td>
<td>
<a href="/edit_gst_release/{{ gst_rel[0] }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit"
class="icon">
</a>
</td>
<td>
<a href="/delete_gst_release/{{ gst_rel[0] }}"
onclick="return confirm('Are you sure you want to delete this GST Release?')">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete"
class="icon">
</a>
</td>
</tr>
{% endfor %}
{% for gst_rel in gst_releases %}
<tr>
<td>{{ gst_rel.gst_release_id }}</td>
<td>{{ gst_rel.pmc_no }}</td>
<td>{{ gst_rel.invoice_no }}</td>
<td>{{ gst_rel.basic_amount }}</td>
<td>{{ gst_rel.final_amount }}</td>
<td>{{ gst_rel.total_amount }}</td>
<td>{{ gst_rel.utr }}</td>
<td>
<a href="{{ url_for('gst_release_bp.edit_gst_release', gst_release_id=gst_rel.gst_release_id) }}">
<img src="{{ url_for('static', filename='images/icons/pen_blue_icon.png') }}" alt="Edit" class="icon">
</a>
</td>
<td>
<a href="{{ url_for('gst_release_bp.delete_gst_release', gst_release_id=gst_rel.gst_release_id) }}"
onclick="return confirm('Are you sure you want to delete this GST Release?')">
<img src="{{ url_for('static', filename='images/icons/bin_red_icon.png') }}" alt="Delete" class="icon">
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Handle subcontractor autocomplete
document.getElementById("subcontractor").addEventListener("input", function () {
const subcontractorInput = document.getElementById("subcontractor");
const subcontractorIdInput = document.getElementById("subcontractor_id");
const subcontractorList = document.getElementById("subcontractor_list");
const pmcDropdown = document.getElementById("PMC_No");
const form = document.querySelector('form');
// --------------------------
// Subcontractor autocomplete
// --------------------------
subcontractorInput.addEventListener("input", function () {
const query = this.value;
const list = document.getElementById("subcontractor_list");
if (query.length < 2) {
list.innerHTML = '';
subcontractorList.innerHTML = '';
subcontractorIdInput.value = ''; // reset hidden id
pmcDropdown.innerHTML = '<option value="">Select PMC No</option>'; // reset PMC dropdown
return;
}
fetch(`/search_subcontractor?query=${encodeURIComponent(query)}`)
.then(response => response.json())
.then(data => {
list.innerHTML = '';
subcontractorList.innerHTML = '';
data.results.forEach(item => {
const div = document.createElement("div");
div.setAttribute("data-id", item.id);
div.textContent = item.name;
list.appendChild(div);
subcontractorList.appendChild(div);
});
});
});
// Handle subcontractor selection
document.getElementById("subcontractor_list").addEventListener("click", function (e) {
// --------------------------
// Subcontractor selection
// --------------------------
subcontractorList.addEventListener("click", function (e) {
const selectedId = e.target.getAttribute("data-id");
const selectedName = e.target.textContent;
if (selectedId) {
document.getElementById("subcontractor_id").value = selectedId;
document.getElementById("subcontractor").value = selectedName;
document.getElementById("subcontractor_list").innerHTML = "";
// Set hidden field for backend
subcontractorIdInput.value = selectedId;
// Update PMC dropdown for selected subcontractor
// Set text input to selected name
subcontractorInput.value = selectedName;
// Clear the autocomplete list
subcontractorList.innerHTML = "";
// Fetch and populate PMC dropdown
fetch(`/get_pmc_nos_by_subcontractor/${encodeURIComponent(selectedId)}`)
.then(response => response.json())
.then(data => {
const pmcDropdown = document.getElementById("PMC_No");
pmcDropdown.innerHTML = '<option value="">Select PMC No</option>';
data.pmc_nos.forEach(pmc => {
const option = document.createElement("option");
option.value = pmc;
@@ -167,6 +185,22 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
});
// --------------------------
// Form submit validation
// --------------------------
form.addEventListener('submit', function(e) {
if (!subcontractorIdInput.value) {
e.preventDefault();
alert("Please select a subcontractor from the list.");
subcontractorInput.focus();
} else if (!pmcDropdown.value) {
e.preventDefault();
alert("Please select a PMC No.");
pmcDropdown.focus();
}
});
});
</script>

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,37 +9,37 @@
<body>
<h2>Edit GST Release</h2>
<form action="/edit_gst_release/{{ gst_release_data[0] }}" method="POST">
<!-- <label for="invoice_id">Invoice Id:</label><br>-->
<!-- <input type="number" id="invoice_id" name="invoice_id" value="{{ gst_release_data[0] }}" required><br><br>-->
<form action="/edit_gst_release/{{ gst_release_data.gst_release_id }}" method="POST">
<!-- PMC Number -->
<label for="PMC_No">PMC No :</label><br>
<input type="number" id="PMC_No" name="PMC_No" value="{{ gst_release_data[1] }}" required><br><br>
<input type="text" id="PMC_No" name="PMC_No" value="{{ gst_release_data.pmc_no }}" required><br><br>
<label for="invoice_No">Invoice No:</label><br>
<input type="number" step="0.01" id="invoice_No" name="invoice_No" value="{{ gst_release_data[2] }}"
required><br><br>
<!-- Invoice Number -->
<label for="invoice_no">Invoice No:</label><br>
<input type="text" id="invoice_no" name="invoice_no" value="{{ gst_release_data.invoice_no }}" required><br><br>
<!-- Basic Amount -->
<label for="Basic_Amount">Basic Amount:</label><br>
<input type="number" step="0.01" id="Basic_Amount" name="Basic_Amount" value="{{ gst_release_data.basic_amount }}" required><br><br>
<label for="basic_amount">Basic Amount:</label><br>
<input type="number" step="0.01" id="basic_amount" name="basic_amount" value="{{ gst_release_data[3] }}"
required><br><br>
<!-- Final Amount -->
<label for="Final_Amount">Final Amount:</label><br>
<input type="number" step="0.01" id="Final_Amount" name="Final_Amount" value="{{ gst_release_data.final_amount }}" required><br><br>
<label for="final_amount">Final Amount:</label><br>
<input type="number" step="0.01" id="final_amount" name="final_amount" value="{{ gst_release_data[4] }}"
required><br><br>
<!-- Total Amount -->
<label for="Total_Amount">Total Amount:</label><br>
<input type="number" step="0.01" id="Total_Amount" name="Total_Amount" value="{{ gst_release_data.total_amount }}" 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>
<!-- UTR -->
<label for="UTR">UTR:</label><br>
<input type="text" id="UTR" name="UTR" value="{{ gst_release_data.utr }}" readonly required><br><br>
<label for="utr">UTR:</label><br>
<input type="text" id="utr" name="utr" value="{{ gst_release_data[6] }}"
required readonly><br><br>
<!-- Hidden Contractor ID -->
<input type="hidden" id="Contractor_ID" name="Contractor_ID" value="{{ gst_release_data.contractor_id }}">
<button type="submit">Update GST Release</button>
</form>
</body>
{% endblock %}

View File

@@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<head>
<title>Edit Hold Type</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}">
@@ -10,10 +10,10 @@
<body>
<h2>Edit Hold Type</h2>
<form id="updateHoldTypeForm">
<input type="hidden" id="hold_type_id" value="{{ hold_type[0] }}">
<form id="updateHoldTypeForm" method="POST">
<input type="hidden" id="hold_type_id" value="{{ hold_type.hold_type_id }}">
<label for="edit_hold_type">Hold Type:</label>
<input type="text" id="edit_hold_type" name="hold_type" value="{{ hold_type[1] }}" required>
<input type="text" id="edit_hold_type" name="hold_type" value="{{ hold_type.hold_type }}" required>
<button type="submit">Update</button>
<a href="{{ url_for('hold_types.add_hold_type') }}"></a>
</form>
@@ -37,7 +37,7 @@
formData.append('hold_type', newHoldType);
$.ajax({
url: '/update_hold_type/' + holdTypeId,
url: '/edit_hold_type/' + holdTypeId,
method: 'POST',
data: formData,
processData: false,
@@ -55,8 +55,3 @@
});
</script>
{% endblock %}

View File

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

View File

@@ -28,25 +28,17 @@
<button type="submit">Update Village</button>
</form>
</div>
<!-- Flash Message (Hidden, used for JS popup) -->
<!-- Flash Messages (hidden, used for JS popup) -->
<div id="flash-messages-container" style="display:none;">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div id="flash-message" data-category="{{ category }}" style="display:none;">{{ message }}</div>
{% endfor %}
{% endif %}
{% if messages %}
{% for category, message in messages %}
<div class="flash-message" data-category="{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<script>
window.onload = function () {
const flash = document.getElementById('flash-message');
if (flash && flash.innerText.trim() !== "") {
alert(flash.innerText);
}
};
</script>
</body>
{% endblock %}