5 Commits

11 changed files with 229 additions and 165 deletions

View File

View File

View File

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

View File

@@ -27,13 +27,27 @@ class FolderAndFile:
os.makedirs(folder, exist_ok=True) os.makedirs(folder, exist_ok=True)
return folder return folder
# ----------------------------- @staticmethod
# FILE PATH METHODS 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 @staticmethod
def get_download_path(filename): def get_download_path(filename):
return os.path.join(FolderAndFile.get_download_folder(), filename) return os.path.join(FolderAndFile.get_download_folder(), filename)
# FILE PATH METHODS - upload file
@staticmethod @staticmethod
def get_upload_path(filename): def get_upload_path(filename):
return os.path.join(FolderAndFile.get_upload_folder(), 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

@@ -232,14 +232,13 @@ class ItemCRUD:
if self.itemCRUDType.name == "GSTRelease" and data: if self.itemCRUDType.name == "GSTRelease" and data:
cursor.callproc(storedprocupdate, ( cursor.callproc(storedprocupdate, (
childid, data['p_pmc_no'], # PMC_No
data['PMC_No'], data['p_invoice_no'], # Invoice_No
data['Invoice_No'], data['p_basic_amount'], # Basic_Amount
data['Basic_Amount'], data['p_final_amount'], # Final_Amount
data['Final_Amount'], data['p_total_amount'], # Total_Amount
data['Total_Amount'], data['p_utr'], # UTR
data['UTR'], data['p_gst_release_id']# GST_Release_Id
data['Contractor_ID']
)) ))
connection.commit() connection.commit()

View File

@@ -1,29 +1,41 @@
import os import os
from datetime import datetime
from flask import current_app 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: class LogHelper:
@staticmethod @staticmethod
def log_action(action, details=""): def log_action(action, details=""):
"""Add a log entry.""" """Log user actions with timestamp, user, action, and details."""
log_data = LogData() logData = LogData()
log_data.add_log(action, details) logData.WriteLog(action, details="")
class LogData: class LogData:
filepath = ""
timestamp = None
def __init__(self): 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.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.user = getattr(current_user, "cn", None) \ self.user = LogData.get_current_user()
or getattr(current_user, "username", None) \
or getattr(current_user, "sAMAccountName", "Unknown")
def add_log(self, action, details=""): @staticmethod
"""Create/Add a log entry.""" 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: with open(self.filepath, "a", encoding="utf-8") as f:
f.write( f.write(
f"Timestamp: {self.timestamp} | " f"Timestamp: {self.timestamp} | "
@@ -32,73 +44,41 @@ class LogData:
f"Details: {details}\n" f"Details: {details}\n"
) )
def get_all_logs(self): def GetActivitiesLog(self):
"""Read all logs."""
logs = [] logs = []
if os.path.exists(self.filepath): 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: for line in f:
parts = line.strip().split(" | ") parts = line.strip().split(" | ")
if len(parts) == 4: if len(parts) == 4:
logs.append({ logs.append({
"timestamp": parts[0].split(":", 1)[1].strip(), "timestamp": parts[0].replace("Timestamp:", "").strip(),
"user": parts[1].split(":", 1)[1].strip(), "user": parts[1].replace("User:", "").strip(),
"action": parts[2].split(":", 1)[1].strip(), "action": parts[2].replace("Action:", "").strip(),
"details": parts[3].split(":", 1)[1].strip() "details": parts[3].replace("Details:", "").strip()
}) })
return logs return logs
def get_filtered_logs(self, start_date=None, end_date=None, user_name=None): def GetFilteredActivitiesLog(self, startDate, endDate, userName):
"""Filter logs by date and/or user.""" filtered_logs = self.GetActivitiesLog()
logs = self.get_all_logs()
# Filter by date # Date filter
if start_date or end_date: if startDate or endDate:
start_dt = datetime.strptime(start_date, "%Y-%m-%d") if start_date else datetime.min try:
end_dt = datetime.strptime(end_date, "%Y-%m-%d") if end_date else datetime.max start_dt = datetime.strptime(startDate, "%Y-%m-%d") if startDate else datetime.min
logs = [ end_dt = datetime.strptime(endDate, "%Y-%m-%d") if endDate else datetime.max
log for log in logs
filtered_logs = [
log for log in filtered_logs
if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt
] ]
# Filter by username except Exception as e:
if user_name: print("Date filter error:", e)
logs = [log for log in logs if user_name.lower() in log.get("user", "").lower()]
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"
)
# Username filter
if userName:
filtered_logs = [log for log in filtered_logs if userName.lower() in log["user"].lower()]
return filtered_logs

View File

@@ -14,6 +14,12 @@ class GSTRelease:
try: try:
gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease) 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 = { data = {
"PMC_No": request.form.get("PMC_No", "").strip(), "PMC_No": request.form.get("PMC_No", "").strip(),
"Invoice_No": request.form.get("Invoice_No", "").strip(), "Invoice_No": request.form.get("Invoice_No", "").strip(),
@@ -24,6 +30,10 @@ class GSTRelease:
"Contractor_ID": int(request.form.get("Contractor_ID", 0) or 0) "Contractor_ID": int(request.form.get("Contractor_ID", 0) or 0)
} }
print("===== DEBUG: PARSED DATA =====")
print(data)
print("==============================")
# Add GST Release # Add GST Release
gst.AddItem( gst.AddItem(
request=request, request=request,
@@ -32,11 +42,7 @@ class GSTRelease:
storedprocadd="AddGSTReleaseFromExcel" storedprocadd="AddGSTReleaseFromExcel"
) )
# Check if addition was successful print(f"AddItem result: isSuccess={gst.isSuccess}, message={gst.resultMessage}")
if gst.isSuccess:
print(f"GST Release Added: {data}")
else:
print(f"Failed to add GST Release: {gst.resultMessage}")
self.isSuccess = gst.isSuccess self.isSuccess = gst.isSuccess
self.resultMessage = str(gst.resultMessage) self.resultMessage = str(gst.resultMessage)
@@ -48,20 +54,26 @@ class GSTRelease:
return jsonify({"success": self.isSuccess, "message": self.resultMessage}) return jsonify({"success": self.isSuccess, "message": self.resultMessage})
# ------------------- Edit GST Release -------------------
def EditGSTRelease(self, request, gst_release_id): def EditGSTRelease(self, request, gst_release_id):
try: try:
gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease) gst = ItemCRUD(itemType=ItemCRUDType.GSTRelease)
# Map form inputs to stored procedure parameters
data = { data = {
"PMC_No": request.form.get("PMC_No", "").strip(), "p_pmc_no": request.form.get("PMC_No", "").strip(),
"Invoice_No": request.form.get("Invoice_No", "").strip(), "p_invoice_no": request.form.get("invoice_no", "").strip(),
"Basic_Amount": float(request.form.get("Basic_Amount", 0) or 0), "p_basic_amount": float(request.form.get("Basic_Amount", 0) or 0),
"Final_Amount": float(request.form.get("Final_Amount", 0) or 0), "p_final_amount": float(request.form.get("Final_Amount", 0) or 0),
"Total_Amount": float(request.form.get("Total_Amount", 0) or 0), "p_total_amount": float(request.form.get("Total_Amount", 0) or 0),
"UTR": request.form.get("UTR", "").strip() "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( gst.EditItem(
request=request, request=request,
childid=gst_release_id, childid=gst_release_id,
@@ -77,8 +89,6 @@ class GSTRelease:
self.isSuccess = False self.isSuccess = False
self.resultMessage = str(e) self.resultMessage = str(e)
return jsonify({"success": self.isSuccess, "message": self.resultMessage})
# ------------------- Delete GST Release ------------------- # ------------------- Delete GST Release -------------------
def DeleteGSTRelease(self, gst_release_id): def DeleteGSTRelease(self, gst_release_id):
try: try:

View File

@@ -1,26 +1,23 @@
// Search on table using search inpute options // Search on table using search inpute options
function searchTable() { function searchTable() {
let input = document.getElementById("searchBar").value.toLowerCase(); let input = document.getElementById("searchBar").value.toLowerCase();
let rows = document.querySelectorAll("table tbody tr"); let tables = document.querySelectorAll("table");
rows.forEach(row => { tables.forEach(table => {
let blockName = row.cells[1].textContent.toLowerCase(); let rows = table.querySelectorAll("tr");
let districtName = row.cells[2].textContent.toLowerCase();
let villageName = row.cells[3].textContent.toLowerCase();
if (blockName.includes(input) || districtName.includes(input)|| villageName.includes(input)) { rows.forEach((row, index) => {
row.style.display = ""; if (index === 0) return; // header skip
} else {
row.style.display = "none"; let text = row.textContent.toLowerCase();
}
row.style.display = text.includes(input) ? "" : "none";
});
}); });
} }
// Common Sorting Script for Tables // Common Sorting Script for Tables
function sortTable(n, dir) { function sortTable(n, dir) {
var table, rows, switching, i, x, y, shouldSwitch; var table, rows, switching, i, x, y, shouldSwitch;
@@ -106,3 +103,30 @@ document.addEventListener("DOMContentLoaded", function () {
addButton.classList.remove("active-button"); 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

@@ -22,8 +22,12 @@
<div class="row1"> <div class="row1">
<div> <div>
<label for="subcontractor">Subcontractor Name:</label> <label for="subcontractor">Subcontractor Name:</label>
<!-- Text input for user-friendly autocomplete -->
<input type="text" id="subcontractor" name="subcontractor" required autocomplete="off"/> <input type="text" id="subcontractor" name="subcontractor" required autocomplete="off"/>
<input type="hidden" id="subcontractor_id" name="subcontractor_id"/>
<!-- 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 id="subcontractor_list" class="autocomplete-items"></div>
</div> </div>
</div> </div>
@@ -37,19 +41,19 @@
</select><br><br> </select><br><br>
<label for="invoice_No">Invoice No:</label><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> <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> <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> <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> <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> <button type="submit">Submit GST Release</button>
</form> </form>
@@ -117,46 +121,61 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function() { 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 query = this.value;
const list = document.getElementById("subcontractor_list");
if (query.length < 2) { 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; return;
} }
fetch(`/search_subcontractor?query=${encodeURIComponent(query)}`) fetch(`/search_subcontractor?query=${encodeURIComponent(query)}`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
list.innerHTML = ''; subcontractorList.innerHTML = '';
data.results.forEach(item => { data.results.forEach(item => {
const div = document.createElement("div"); const div = document.createElement("div");
div.setAttribute("data-id", item.id); div.setAttribute("data-id", item.id);
div.textContent = item.name; 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 selectedId = e.target.getAttribute("data-id");
const selectedName = e.target.textContent; const selectedName = e.target.textContent;
if (selectedId) { if (selectedId) {
document.getElementById("subcontractor_id").value = selectedId; // Set hidden field for backend
document.getElementById("subcontractor").value = selectedName; subcontractorIdInput.value = selectedId;
document.getElementById("subcontractor_list").innerHTML = "";
// 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)}`) fetch(`/get_pmc_nos_by_subcontractor/${encodeURIComponent(selectedId)}`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const pmcDropdown = document.getElementById("PMC_No");
pmcDropdown.innerHTML = '<option value="">Select PMC No</option>'; pmcDropdown.innerHTML = '<option value="">Select PMC No</option>';
data.pmc_nos.forEach(pmc => { data.pmc_nos.forEach(pmc => {
const option = document.createElement("option"); const option = document.createElement("option");
option.value = pmc; option.value = pmc;
@@ -166,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> </script>

View File

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

View File

@@ -7,6 +7,7 @@
<title>Edit Invoice</title> <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/invoice.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style1.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> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head> </head>
@@ -204,15 +205,17 @@
type: "POST", type: "POST",
url: $(this).attr("action"), url: $(this).attr("action"),
data: $(this).serialize(), data: $(this).serialize(),
dataType: 'json', // ensure JSON is returned
success: function (response) { success: function (response) {
if (response.status === "success") { if (response.status === "success") {
$("#invoiceSuccessAlert").fadeIn().delay(3000).fadeOut();
alert("Invoice updated successfully!"); // <-- Popup alert alert("Invoice updated successfully!"); // <-- Popup alert
}
// ✅ Redirect to Add Invoice page (table part visible)
window.location.href = "{{ url_for('invoice.add_invoice') }}#addTable";
}
}, },
error: function (xhr) { error: function (xhr) {
alert("Error: " + xhr.responseJSON.message); alert("Error: " + xhr.responseJSON?.message || "Something went wrong!");
} }
}); });
}); });