update itr and ao from and auto save mat ceadit and utility
This commit is contained in:
10
.gitignore
vendored
10
.gitignore
vendored
@@ -15,3 +15,13 @@ logs/
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Python cache
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# OS / Editor
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ class AOHandler:
|
|||||||
|
|
||||||
# Add AO record
|
# Add AO record
|
||||||
def add_ao(self, data):
|
def add_ao(self, data):
|
||||||
|
|
||||||
|
try:
|
||||||
fields = [
|
fields = [
|
||||||
'year', 'gross_total_income', 'disallowance_14a', 'disallowance_37',
|
'year', 'gross_total_income', 'disallowance_14a', 'disallowance_37',
|
||||||
'deduction_80ia_business', 'deduction_80ia_misc', 'deduction_80ia_other',
|
'deduction_80ia_business', 'deduction_80ia_misc', 'deduction_80ia_other',
|
||||||
@@ -40,13 +42,18 @@ class AOHandler:
|
|||||||
'tax_payable', 'surcharge', 'edu_cess',
|
'tax_payable', 'surcharge', 'edu_cess',
|
||||||
'total_tax_payable', 'mat_credit_created', 'mat_credit_utilized',
|
'total_tax_payable', 'mat_credit_created', 'mat_credit_utilized',
|
||||||
'interest_234c', 'total_tax', 'advance_tax', 'tds', 'tcs',
|
'interest_234c', 'total_tax', 'advance_tax', 'tds', 'tcs',
|
||||||
'sat', 'tax_on_assessment', 'refund', 'Remarks'
|
'sat', 'tax_on_assessment', 'refund', 'Remarks','created_at'
|
||||||
]
|
]
|
||||||
|
|
||||||
values = [data.get(f, 0) for f in fields]
|
values = [data.get(f, 0) for f in fields]
|
||||||
|
|
||||||
self.cursor.callproc("InsertAO", values)
|
self.cursor.callproc("InsertAO", values)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
self.conn.rollback()
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
self.cursor.close()
|
||||||
|
self.conn.close()
|
||||||
|
|
||||||
# UPDATE AO RECORD by AO id
|
# UPDATE AO RECORD by AO id
|
||||||
def update_ao(self, id, data):
|
def update_ao(self, id, data):
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, abort, flash,send_file
|
from flask import (
|
||||||
|
render_template, request, send_file, jsonify
|
||||||
|
)
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import os
|
import os
|
||||||
@@ -17,11 +19,40 @@ class DocumentHandler:
|
|||||||
self.isSuccess = False
|
self.isSuccess = False
|
||||||
self.resultMessage = ""
|
self.resultMessage = ""
|
||||||
|
|
||||||
# VIEW DOCUMENTS
|
# =========================
|
||||||
|
# Utility: Parse Year
|
||||||
|
# =========================
|
||||||
|
def parse_year(self, year_value):
|
||||||
|
"""
|
||||||
|
Accepts:
|
||||||
|
- '2026'
|
||||||
|
- 'AY 2026-2027'
|
||||||
|
Returns:
|
||||||
|
- 2026 (int)
|
||||||
|
"""
|
||||||
|
if not year_value:
|
||||||
|
return None
|
||||||
|
|
||||||
|
year_value = year_value.strip()
|
||||||
|
|
||||||
|
if year_value.isdigit():
|
||||||
|
return int(year_value)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# "AY 2026-2027" → 2026
|
||||||
|
return int(year_value.split()[1].split('-')[0])
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# View Documents
|
||||||
|
# =========================
|
||||||
def View(self, request):
|
def View(self, request):
|
||||||
year = request.args.get('year', '')
|
year_raw = request.args.get('year', '')
|
||||||
stage = request.args.get('stage', '')
|
stage = request.args.get('stage', '')
|
||||||
|
|
||||||
|
year = self.parse_year(year_raw)
|
||||||
|
|
||||||
dbconfig = DBConfig()
|
dbconfig = DBConfig()
|
||||||
connection = dbconfig.get_db_connection()
|
connection = dbconfig.get_db_connection()
|
||||||
|
|
||||||
@@ -30,15 +61,13 @@ class DocumentHandler:
|
|||||||
return
|
return
|
||||||
|
|
||||||
cursor = connection.cursor(dictionary=True)
|
cursor = connection.cursor(dictionary=True)
|
||||||
# --- FILTER QUERY ---
|
|
||||||
cursor.callproc("GetDocuments", [year, stage])
|
cursor.callproc("GetDocuments", [year, stage])
|
||||||
|
|
||||||
# fetch first result set
|
|
||||||
for result in cursor.stored_results():
|
for result in cursor.stored_results():
|
||||||
self.documents = result.fetchall()
|
self.documents = result.fetchall()
|
||||||
break
|
break
|
||||||
|
|
||||||
# ---- GET YEARS FROM STORED PROCEDURE ----
|
|
||||||
cursor.callproc("GetYear")
|
cursor.callproc("GetYear")
|
||||||
|
|
||||||
for result in cursor.stored_results():
|
for result in cursor.stored_results():
|
||||||
@@ -49,55 +78,72 @@ class DocumentHandler:
|
|||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
self.isSuccess = True
|
self.isSuccess = True
|
||||||
|
|
||||||
|
# =========================
|
||||||
# Upload Documents
|
# Upload Documents
|
||||||
|
# =========================
|
||||||
def Upload(self, request):
|
def Upload(self, request):
|
||||||
dbconfig = DBConfig()
|
dbconfig = DBConfig()
|
||||||
connection = dbconfig.get_db_connection()
|
connection = dbconfig.get_db_connection()
|
||||||
|
|
||||||
if connection:
|
if not connection:
|
||||||
|
return
|
||||||
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
||||||
files = request.files.getlist('documents')
|
files = request.files.getlist('documents')
|
||||||
year = request.form['year']
|
year_raw = request.form.get('year')
|
||||||
stage = request.form['stage']
|
stage = request.form.get('stage')
|
||||||
|
|
||||||
|
year = self.parse_year(year_raw)
|
||||||
|
|
||||||
|
if not year:
|
||||||
|
self.resultMessage = "Invalid year selected."
|
||||||
|
return
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
extension = file.filename.rsplit('.', 1)[1]
|
if '.' not in file.filename:
|
||||||
|
continue
|
||||||
|
|
||||||
|
extension = file.filename.rsplit('.', 1)[1].lower()
|
||||||
|
|
||||||
if extension not in FileHandler.ALLOWED_EXTENSIONS:
|
if extension not in FileHandler.ALLOWED_EXTENSIONS:
|
||||||
print("Skip invalid file type : ",extension)
|
print("Skipping invalid file:", extension)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
filename = secure_filename(file.filename)
|
filename = secure_filename(file.filename)
|
||||||
filepath = os.path.join(FileHandler.UPLOAD_FOLDER, filename)
|
filepath = os.path.join(FileHandler.UPLOAD_FOLDER, filename)
|
||||||
|
|
||||||
file.save(filepath)
|
file.save(filepath)
|
||||||
|
|
||||||
cursor.callproc('InsertDocument', [ filename, filepath, extension, year, stage ])
|
cursor.callproc(
|
||||||
|
'InsertDocument',
|
||||||
|
[filename, filepath, extension, year, stage]
|
||||||
|
)
|
||||||
|
|
||||||
connection.commit()
|
connection.commit()
|
||||||
cursor.close()
|
cursor.close()
|
||||||
connection.close()
|
connection.close()
|
||||||
# return redirect(url_for('view_documents'))
|
|
||||||
|
|
||||||
def Summary_report(self, request):
|
# =========================
|
||||||
|
# Summary Preview (JSON)
|
||||||
|
# =========================
|
||||||
|
def Summary_preview(self, request):
|
||||||
|
"""
|
||||||
|
Returns JSON preview of summary report for selected year.
|
||||||
|
"""
|
||||||
|
year_raw = request.args.get("year")
|
||||||
|
year = self.parse_year(year_raw)
|
||||||
|
|
||||||
|
if not year:
|
||||||
|
return jsonify([])
|
||||||
|
|
||||||
dbconfig = DBConfig()
|
dbconfig = DBConfig()
|
||||||
connection = dbconfig.get_db_connection()
|
connection = dbconfig.get_db_connection()
|
||||||
|
if not connection:
|
||||||
year_str = request.args.get('year')
|
return jsonify([])
|
||||||
|
|
||||||
# If year not selected
|
|
||||||
if not year_str or not year_str.isdigit():
|
|
||||||
yearGetter = YearGet()
|
|
||||||
allYears = yearGetter.get_year_by_model("AllYearsInAllModel")
|
|
||||||
yearGetter.close()
|
|
||||||
return render_template(
|
|
||||||
'summary_reports.html',
|
|
||||||
years=allYears,
|
|
||||||
message="Please select a valid year to download."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convert year to int (IMPORTANT FIX)
|
|
||||||
year = int(year_str)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stages = {
|
stages = {
|
||||||
@@ -112,11 +158,111 @@ class DocumentHandler:
|
|||||||
for stage_name, table_name in stages.items():
|
for stage_name, table_name in stages.items():
|
||||||
cursor = connection.cursor(dictionary=True)
|
cursor = connection.cursor(dictionary=True)
|
||||||
cursor.callproc("sp_get_stage_data", [table_name, year])
|
cursor.callproc("sp_get_stage_data", [table_name, year])
|
||||||
|
|
||||||
rows = []
|
rows = []
|
||||||
for result in cursor.stored_results():
|
for result in cursor.stored_results():
|
||||||
rows = result.fetchall()
|
rows = result.fetchall()
|
||||||
|
stage_data[stage_name] = pd.DataFrame(rows) if rows else pd.DataFrame()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
columns = [
|
||||||
|
'gross_total_income', 'disallowance_14a',
|
||||||
|
'disallowance_37', '-',
|
||||||
|
'deduction_80ia_business',
|
||||||
|
'deduction_80ia_misc',
|
||||||
|
'deduction_80ia_other',
|
||||||
|
'deduction_sec37_disallowance',
|
||||||
|
'deduction_80g', '-',
|
||||||
|
'net_taxable_income',
|
||||||
|
'tax_30_percent',
|
||||||
|
'tax_book_profit_18_5',
|
||||||
|
'tax_payable', 'surcharge',
|
||||||
|
'edu_cess', 'total_tax_payable',
|
||||||
|
'mat_credit_created',
|
||||||
|
'mat_credit_utilized',
|
||||||
|
'interest_234c', 'total_tax',
|
||||||
|
'-', 'advance_tax', 'tds',
|
||||||
|
'tcs', 'sat',
|
||||||
|
'tax_on_assessment',
|
||||||
|
'refund', 'Remarks'
|
||||||
|
]
|
||||||
|
|
||||||
|
particulars = [
|
||||||
|
"Gross Total Income", "Add: Disallowance u/s 14A",
|
||||||
|
"Add: Disallowance u/s 37", "GTI as per",
|
||||||
|
"Less: Deduction u/s 80IA - On Business Income",
|
||||||
|
"- On Misc Receipts", "- On Other",
|
||||||
|
"- On Sec 37 Disallowance",
|
||||||
|
"Less: Deduction u/s 80G", " ",
|
||||||
|
"Net Taxable Income", "Tax @ 30%",
|
||||||
|
"Tax @ 18.5% on Book Profit",
|
||||||
|
"Tax Payable", "Surcharge @ %",
|
||||||
|
"Education Cess @ %", "Total Tax Payable",
|
||||||
|
"Add: MAT Credit Created",
|
||||||
|
"Less: MAT Credit Utilized",
|
||||||
|
"Add: Interest u/s 234C",
|
||||||
|
"Total Tax", " ",
|
||||||
|
"Advance Tax", "TDS", "TCS", "SAT",
|
||||||
|
"Tax on Regular Assessment",
|
||||||
|
"Refund", "Remarks"
|
||||||
|
]
|
||||||
|
|
||||||
|
def safe_get(df, col):
|
||||||
|
return df[col].values[0] if col in df.columns and not df.empty else 0
|
||||||
|
|
||||||
|
preview = []
|
||||||
|
for i, part in enumerate(particulars):
|
||||||
|
preview.append({
|
||||||
|
"Particular": part,
|
||||||
|
"ITR": safe_get(stage_data['ITR'], columns[i]),
|
||||||
|
"AO": safe_get(stage_data['AO'], columns[i]),
|
||||||
|
"CIT": safe_get(stage_data['CIT'], columns[i]),
|
||||||
|
"ITAT": safe_get(stage_data['ITAT'], columns[i]),
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify(preview)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
def Summary_report(self, request):
|
||||||
|
dbconfig = DBConfig()
|
||||||
|
connection = dbconfig.get_db_connection()
|
||||||
|
|
||||||
|
year_raw = request.args.get('year')
|
||||||
|
|
||||||
|
# Safely parse year to int
|
||||||
|
try:
|
||||||
|
year = int(year_raw)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
year = None
|
||||||
|
|
||||||
|
if not year:
|
||||||
|
yearGetter = YearGet()
|
||||||
|
allYears = yearGetter.get_year_by_model("AllYearsInAllModel")
|
||||||
|
yearGetter.close()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'summary_reports.html',
|
||||||
|
years=allYears,
|
||||||
|
message="Please select a valid year to download."
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
stages = {
|
||||||
|
"ITR": "itr",
|
||||||
|
"AO": "ao",
|
||||||
|
"CIT": "cit",
|
||||||
|
"ITAT": "itat",
|
||||||
|
}
|
||||||
|
|
||||||
|
stage_data = {}
|
||||||
|
|
||||||
|
for stage_name, table_name in stages.items():
|
||||||
|
cursor = connection.cursor(dictionary=True)
|
||||||
|
cursor.callproc("sp_get_stage_data", [table_name, year])
|
||||||
|
rows = []
|
||||||
|
for result in cursor.stored_results():
|
||||||
|
rows = result.fetchall()
|
||||||
stage_data[stage_name] = pd.DataFrame(rows) if rows else pd.DataFrame()
|
stage_data[stage_name] = pd.DataFrame(rows) if rows else pd.DataFrame()
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
@@ -124,25 +270,45 @@ class DocumentHandler:
|
|||||||
return df[col].values[0] if col in df.columns and not df.empty else "-"
|
return df[col].values[0] if col in df.columns and not df.empty else "-"
|
||||||
|
|
||||||
particulars = [
|
particulars = [
|
||||||
"Gross Total Income", "Add: Disallowance u/s 14A", "Add: Disallowance u/s 37",
|
"Gross Total Income", "Add: Disallowance u/s 14A",
|
||||||
"GTI as per", "Less: Deduction u/s 80IA - On Business Income", "- On Misc Receipts",
|
"Add: Disallowance u/s 37", "GTI as per",
|
||||||
"- On Other", "- On Sec 37 Disallowance", "Less: Deduction u/s 80G", " ",
|
"Less: Deduction u/s 80IA - On Business Income",
|
||||||
"Net Taxable Income", "Tax @ 30%", "Tax @ 18.5% on Book Profit",
|
"- On Misc Receipts", "- On Other",
|
||||||
"Tax Payable", "Surcharge @ %", "Education Cess @ %", "Total Tax Payable","Add: MAT Credit Created",
|
"- On Sec 37 Disallowance",
|
||||||
"Less: MAT Credit Utilized", "Add: Interest u/s 234C", "Total Tax", " ",
|
"Less: Deduction u/s 80G", " ",
|
||||||
|
"Net Taxable Income", "Tax @ 30%",
|
||||||
|
"Tax @ 18.5% on Book Profit",
|
||||||
|
"Tax Payable", "Surcharge @ %",
|
||||||
|
"Education Cess @ %", "Total Tax Payable",
|
||||||
|
"Add: MAT Credit Created",
|
||||||
|
"Less: MAT Credit Utilized",
|
||||||
|
"Add: Interest u/s 234C",
|
||||||
|
"Total Tax", " ",
|
||||||
"Advance Tax", "TDS", "TCS", "SAT",
|
"Advance Tax", "TDS", "TCS", "SAT",
|
||||||
"Tax on Regular Assessment", "Refund" , "Remarks"
|
"Tax on Regular Assessment",
|
||||||
|
"Refund", "Remarks"
|
||||||
]
|
]
|
||||||
|
|
||||||
columns = [
|
columns = [
|
||||||
'gross_total_income', 'disallowance_14a', 'disallowance_37',
|
'gross_total_income', 'disallowance_14a',
|
||||||
'-', 'deduction_80ia_business','deduction_80ia_misc',
|
'disallowance_37', '-',
|
||||||
'deduction_80ia_other', 'deduction_sec37_disallowance','deduction_80g', '-',
|
'deduction_80ia_business',
|
||||||
'net_taxable_income', 'tax_30_percent','tax_book_profit_18_5',
|
'deduction_80ia_misc',
|
||||||
'tax_payable','surcharge', 'edu_cess', 'total_tax_payable',
|
'deduction_80ia_other',
|
||||||
'mat_credit_created','mat_credit_utilized' , 'interest_234c','total_tax', '-',
|
'deduction_sec37_disallowance',
|
||||||
'advance_tax', 'tds', 'tcs', 'sat',
|
'deduction_80g', '-',
|
||||||
'tax_on_assessment', 'refund', 'Remarks'
|
'net_taxable_income',
|
||||||
|
'tax_30_percent',
|
||||||
|
'tax_book_profit_18_5',
|
||||||
|
'tax_payable', 'surcharge',
|
||||||
|
'edu_cess', 'total_tax_payable',
|
||||||
|
'mat_credit_created',
|
||||||
|
'mat_credit_utilized',
|
||||||
|
'interest_234c', 'total_tax',
|
||||||
|
'-', 'advance_tax', 'tds',
|
||||||
|
'tcs', 'sat',
|
||||||
|
'tax_on_assessment',
|
||||||
|
'refund', 'Remarks'
|
||||||
]
|
]
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@@ -155,56 +321,44 @@ class DocumentHandler:
|
|||||||
|
|
||||||
df = pd.DataFrame(data)
|
df = pd.DataFrame(data)
|
||||||
|
|
||||||
# ===== Excel Export =====
|
|
||||||
output = io.BytesIO()
|
output = io.BytesIO()
|
||||||
|
|
||||||
with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
|
with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
|
||||||
sheet_name = f"AY {year} - {year + 1}"
|
sheet_name = f"AY {year}-{year + 1}"
|
||||||
df.to_excel(writer, index=False, sheet_name=sheet_name, startrow=2)
|
df.to_excel(writer, index=False, sheet_name=sheet_name, startrow=2)
|
||||||
|
|
||||||
workbook = writer.book
|
workbook = writer.book
|
||||||
worksheet = writer.sheets[sheet_name]
|
worksheet = writer.sheets[sheet_name]
|
||||||
|
|
||||||
# ===== Company Heading =====
|
title = workbook.add_format({
|
||||||
company_heading = workbook.add_format({
|
|
||||||
'bold': True,
|
'bold': True,
|
||||||
'font_size': 14,
|
'font_size': 14,
|
||||||
'font_color': 'black',
|
'align': 'center'
|
||||||
'align': 'center',
|
|
||||||
'valign': 'middle'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
worksheet.merge_range(
|
worksheet.merge_range(
|
||||||
0, 0, 0, len(df.columns) - 1,
|
0, 0, 0, len(df.columns) - 1,
|
||||||
"Laxmi Civil Engineering Services Pvt Ltd",
|
"Laxmi Civil Engineering Services Pvt Ltd",
|
||||||
company_heading
|
title
|
||||||
)
|
)
|
||||||
|
|
||||||
# ===== Header Format =====
|
|
||||||
header = workbook.add_format({
|
header = workbook.add_format({
|
||||||
'bold': True,
|
'bold': True,
|
||||||
'align': 'center',
|
'align': 'center',
|
||||||
'valign': 'middle',
|
|
||||||
'bg_color': '#007bff',
|
'bg_color': '#007bff',
|
||||||
'font_color': 'white',
|
'font_color': 'white',
|
||||||
'border': 1
|
'border': 1
|
||||||
})
|
})
|
||||||
|
|
||||||
cell = workbook.add_format({
|
cell = workbook.add_format({'border': 1})
|
||||||
'border': 1,
|
|
||||||
'align': 'left',
|
|
||||||
'valign': 'middle'
|
|
||||||
})
|
|
||||||
|
|
||||||
# Write headers
|
|
||||||
for col_num, col_name in enumerate(df.columns):
|
for col_num, col_name in enumerate(df.columns):
|
||||||
worksheet.write(2, col_num, col_name, header)
|
worksheet.write(2, col_num, col_name, header)
|
||||||
max_len = max(df[col_name].astype(str).map(len).max(), len(col_name)) + 2
|
worksheet.set_column(col_num, col_num, 25)
|
||||||
worksheet.set_column(col_num, col_num, max_len)
|
|
||||||
|
|
||||||
# Write data rows
|
for row in range(len(df)):
|
||||||
for row in range(3, len(df) + 3):
|
|
||||||
for col in range(len(df.columns)):
|
for col in range(len(df.columns)):
|
||||||
worksheet.write(row, col, df.iloc[row - 3, col], cell)
|
worksheet.write(row + 3, col, df.iloc[row, col], cell)
|
||||||
|
|
||||||
worksheet.freeze_panes(3, 1)
|
worksheet.freeze_panes(3, 1)
|
||||||
|
|
||||||
@@ -219,3 +373,4 @@ class DocumentHandler:
|
|||||||
|
|
||||||
finally:
|
finally:
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class ITRHandler:
|
|||||||
# INSERT ITR RECORD using procedure "add_itr"
|
# INSERT ITR RECORD using procedure "add_itr"
|
||||||
def add_itr(self, data):
|
def add_itr(self, data):
|
||||||
|
|
||||||
|
try:
|
||||||
columns = [
|
columns = [
|
||||||
'year', 'gross_total_income', 'disallowance_14a', 'disallowance_37',
|
'year', 'gross_total_income', 'disallowance_14a', 'disallowance_37',
|
||||||
'deduction_80ia_business', 'deduction_80ia_misc', 'deduction_80ia_other',
|
'deduction_80ia_business', 'deduction_80ia_misc', 'deduction_80ia_other',
|
||||||
@@ -56,6 +57,13 @@ class ITRHandler:
|
|||||||
# Call your stored procedure
|
# Call your stored procedure
|
||||||
self.cursor.callproc("InsertITR", values)
|
self.cursor.callproc("InsertITR", values)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
self.conn.rollback()
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
self.cursor.close()
|
||||||
|
self.conn.close()
|
||||||
|
|
||||||
|
|
||||||
# update itr by id
|
# update itr by id
|
||||||
def update(self, id, data):
|
def update(self, id, data):
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
from AppCode.Config import DBConfig
|
from AppCode.Config import DBConfig
|
||||||
|
|
||||||
|
|
||||||
class MatCreditHandler:
|
class MatCreditHandler:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# db = DBConfig()
|
|
||||||
self.conn = DBConfig.get_db_connection()
|
self.conn = DBConfig.get_db_connection()
|
||||||
self.cursor = self.conn.cursor(dictionary=True)
|
self.cursor = self.conn.cursor(dictionary=True)
|
||||||
|
|
||||||
# get all Mat credit data
|
# --------------------------------------------------
|
||||||
|
# FETCH ALL MAT CREDIT + UTILIZATION (For UI Display)
|
||||||
|
# --------------------------------------------------
|
||||||
def fetch_all(self):
|
def fetch_all(self):
|
||||||
try:
|
try:
|
||||||
|
|
||||||
self.cursor.callproc("GetMatCedit")
|
self.cursor.callproc("GetMatCedit")
|
||||||
result_sets = self.cursor.stored_results()
|
result_sets = self.cursor.stored_results()
|
||||||
mat_rows = next(result_sets).fetchall()
|
mat_rows = next(result_sets).fetchall()
|
||||||
@@ -18,27 +21,35 @@ class MatCreditHandler:
|
|||||||
return mat_rows, utilization_rows
|
return mat_rows, utilization_rows
|
||||||
finally:
|
finally:
|
||||||
self.cursor.close()
|
self.cursor.close()
|
||||||
|
self.conn.close()
|
||||||
|
|
||||||
# Save Mat credit data single row
|
# --------------------------------------------------
|
||||||
|
# SAVE / UPDATE SINGLE MAT ROW (FROM MANUAL UI)
|
||||||
|
# --------------------------------------------------
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def save_single(data):
|
def save_single(data):
|
||||||
conn = DBConfig.get_db_connection()
|
conn = DBConfig.get_db_connection()
|
||||||
cur = conn.cursor(dictionary=True)
|
cur = conn.cursor(dictionary=True)
|
||||||
try:
|
|
||||||
|
|
||||||
cur.callproc("SaveOrUpdateMatCredit",(
|
try:
|
||||||
|
# Save / Update MAT Credit
|
||||||
|
cur.callproc(
|
||||||
|
"SaveOrUpdateMatCredit",
|
||||||
|
(
|
||||||
data["financial_year"],
|
data["financial_year"],
|
||||||
data["mat_credit"],
|
data["mat_credit"],
|
||||||
data["balance"]
|
data["balance"],
|
||||||
))
|
data.get("remarks", "")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
result = next(cur.stored_results()).fetchone()
|
mat_id = None
|
||||||
mat_id = result["mat_id"]
|
for result in cur.stored_results():
|
||||||
|
mat_id = result.fetchone()["mat_id"]
|
||||||
if not mat_id:
|
|
||||||
raise Exception("mat_id not returned from procedure")
|
|
||||||
|
|
||||||
|
# Save utilization rows
|
||||||
for u in data.get("utilization", []):
|
for u in data.get("utilization", []):
|
||||||
|
if float(u["amount"]) > 0:
|
||||||
cur.callproc(
|
cur.callproc(
|
||||||
"InsertMatUtilization",
|
"InsertMatUtilization",
|
||||||
(mat_id, u["year"], u["amount"])
|
(mat_id, u["year"], u["amount"])
|
||||||
@@ -49,56 +60,69 @@ class MatCreditHandler:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
# save all Mat credit data
|
# --------------------------------------------------
|
||||||
# @staticmethod
|
# AUTO SAVE MAT FROM ITR (MAIN LOGIC)
|
||||||
# def save_bulk(rows):
|
# --------------------------------------------------
|
||||||
# conn = DBConfig.get_db_connection()
|
@staticmethod
|
||||||
# cur = conn.cursor()
|
def save_from_itr(year, mat_created, mat_utilized, remarks="Auto from"):
|
||||||
# skipped = []
|
conn = DBConfig.get_db_connection()
|
||||||
|
cur = conn.cursor(dictionary=True)
|
||||||
|
|
||||||
# try:
|
try:
|
||||||
# for row in rows:
|
mat_created = float(mat_created or 0)
|
||||||
# cur.execute(
|
mat_utilized = float(mat_utilized or 0)
|
||||||
# "SELECT id FROM mat_credit WHERE financial_year=%s",
|
|
||||||
# (row["financial_year"],)
|
|
||||||
# )
|
|
||||||
# if cur.fetchone():
|
|
||||||
# skipped.append(row["financial_year"])
|
|
||||||
# continue
|
|
||||||
|
|
||||||
# cur.execute("""
|
balance = mat_created - mat_utilized
|
||||||
# INSERT INTO mat_credit (financial_year, mat_credit, balance)
|
|
||||||
# VALUES (%s,%s,%s)
|
|
||||||
# """, (row["financial_year"], row["mat_credit"], row["balance"]))
|
|
||||||
|
|
||||||
# mat_id = cur.lastrowid
|
# Save / Update MAT Credit
|
||||||
|
cur.callproc(
|
||||||
|
"SaveOrUpdateMatCredit",
|
||||||
|
(year, mat_created, balance, remarks)
|
||||||
|
)
|
||||||
|
|
||||||
# for u in row["utilization"]:
|
mat_id = None
|
||||||
# cur.execute("""
|
for result in cur.stored_results():
|
||||||
# INSERT INTO mat_utilization
|
mat_id = result.fetchone()["mat_id"]
|
||||||
# (mat_credit_id, utilized_year, utilized_amount)
|
|
||||||
# VALUES (%s,%s,%s)
|
|
||||||
# """, (mat_id, u["year"], u["amount"]))
|
|
||||||
|
|
||||||
# conn.commit()
|
# Save utilization only if used
|
||||||
# return skipped
|
if mat_utilized > 0:
|
||||||
|
cur.callproc(
|
||||||
|
"InsertMatUtilization",
|
||||||
|
(mat_id, year, mat_utilized)
|
||||||
|
)
|
||||||
|
|
||||||
# except Exception:
|
conn.commit()
|
||||||
# conn.rollback()
|
|
||||||
# raise
|
|
||||||
|
|
||||||
# finally:
|
except Exception as e:
|
||||||
# cur.close()
|
conn.rollback()
|
||||||
# conn.close()
|
raise e
|
||||||
|
finally:
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
# CLOSE CONNECTION
|
# --------------------------------------------------
|
||||||
def close(self):
|
# DELETE MAT CREDIT SAFELY (OPTIONAL)
|
||||||
|
# --------------------------------------------------
|
||||||
|
def delete_by_year(self, financial_year):
|
||||||
|
try:
|
||||||
|
self.cursor.execute(
|
||||||
|
"DELETE FROM mat_credit WHERE financial_year=%s",
|
||||||
|
(financial_year,)
|
||||||
|
)
|
||||||
|
self.conn.commit()
|
||||||
|
finally:
|
||||||
self.cursor.close()
|
self.cursor.close()
|
||||||
self.conn.close()
|
self.conn.close()
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# CLOSE CONNECTION (MANUAL USE)
|
||||||
|
# --------------------------------------------------
|
||||||
|
def close(self):
|
||||||
|
if self.cursor:
|
||||||
|
self.cursor.close()
|
||||||
|
if self.conn:
|
||||||
|
self.conn.close()
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
59
main.py
59
main.py
@@ -15,6 +15,7 @@ from AppCode.AOHandler import AOHandler
|
|||||||
from AppCode.CITHandler import CITHandler
|
from AppCode.CITHandler import CITHandler
|
||||||
from AppCode.ITATHandler import ITATHandler
|
from AppCode.ITATHandler import ITATHandler
|
||||||
from AppCode.MatCreditHandler import MatCreditHandler
|
from AppCode.MatCreditHandler import MatCreditHandler
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -59,7 +60,6 @@ def view_documents():
|
|||||||
docHandler.View(request=request)
|
docHandler.View(request=request)
|
||||||
return render_template('view_docs.html', documents=docHandler.documents, years=docHandler.years)
|
return render_template('view_docs.html', documents=docHandler.documents, years=docHandler.years)
|
||||||
|
|
||||||
|
|
||||||
# Upload file documents
|
# Upload file documents
|
||||||
@app.route('/uploads/<filename>')
|
@app.route('/uploads/<filename>')
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
@@ -68,20 +68,26 @@ def uploaded_file(filename):
|
|||||||
filepath = os.path.join(FileHandler.UPLOAD_FOLDER, secure_filename(filename))
|
filepath = os.path.join(FileHandler.UPLOAD_FOLDER, secure_filename(filename))
|
||||||
|
|
||||||
if not os.path.exists(filepath):
|
if not os.path.exists(filepath):
|
||||||
abort(404)
|
flash("Unsupported file type for viewing", "warning")
|
||||||
|
return redirect(url_for('view_documents'))
|
||||||
|
|
||||||
file_ext = filename.rsplit('.', 1)[-1].lower()
|
file_ext = filename.rsplit('.', 1)[-1].lower()
|
||||||
# --- View Mode ---
|
# --- View Mode ---
|
||||||
if mode == 'view':
|
if mode == 'view':
|
||||||
|
# pdf
|
||||||
if file_ext == 'pdf':
|
if file_ext == 'pdf':
|
||||||
return send_file(filepath, mimetype='application/pdf')
|
return send_file(filepath, mimetype='application/pdf')
|
||||||
|
# Word
|
||||||
|
elif file_ext in ['doc', 'docx']:
|
||||||
|
return send_file(filepath, as_attachment=True)
|
||||||
|
# Excel
|
||||||
elif file_ext in ['xls', 'xlsx']:
|
elif file_ext in ['xls', 'xlsx']:
|
||||||
# Excel cannot be rendered in-browser by Flask; trigger download instead
|
|
||||||
return send_file(filepath, as_attachment=False, download_name=filename, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
return send_file(filepath, as_attachment=False, download_name=filename, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||||
else:
|
else:
|
||||||
return abort(415) # Unsupported type for viewing
|
flash("Unsupported file type for viewing", "warning")
|
||||||
|
return redirect(url_for('view_documents'))
|
||||||
|
|
||||||
return send_file(filepath, as_attachment=True)
|
return send_file(filepath, as_attachment=True, download_name=filename)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -105,13 +111,21 @@ def display_itr():
|
|||||||
def add_itr():
|
def add_itr():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
itr = ITRHandler()
|
itr = ITRHandler()
|
||||||
|
mat = MatCreditHandler()
|
||||||
itr.add_itr(request.form)
|
itr.add_itr(request.form)
|
||||||
itr.close()
|
itr.close()
|
||||||
|
|
||||||
if 'documents' in request.files:
|
if 'documents' in request.files:
|
||||||
doc = DocumentHandler()
|
doc = DocumentHandler()
|
||||||
doc.Upload(request)
|
doc.Upload(request)
|
||||||
|
|
||||||
|
# AUTO SAVE MAT FROM ITR
|
||||||
|
mat.save_from_itr(
|
||||||
|
year=request.form["year"],
|
||||||
|
mat_created=float(request.form.get("mat_credit_created", 0)),
|
||||||
|
mat_utilized=float(request.form.get("mat_credit_utilized", 0)),
|
||||||
|
remarks="Created via ITR"
|
||||||
|
)
|
||||||
|
|
||||||
# flash("ITR record added successfully!", "success")
|
# flash("ITR record added successfully!", "success")
|
||||||
flash("ITR record and documents uploaded successfully!", "success")
|
flash("ITR record and documents uploaded successfully!", "success")
|
||||||
return redirect(url_for('display_itr'))
|
return redirect(url_for('display_itr'))
|
||||||
@@ -166,11 +180,25 @@ def display_ao():
|
|||||||
def add_ao():
|
def add_ao():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
ao = AOHandler()
|
ao = AOHandler()
|
||||||
|
mat = MatCreditHandler()
|
||||||
ao.add_ao(request.form)
|
ao.add_ao(request.form)
|
||||||
ao.close()
|
ao.close()
|
||||||
|
|
||||||
|
if 'documents' in request.files:
|
||||||
|
doc = DocumentHandler()
|
||||||
|
doc.Upload(request)
|
||||||
|
|
||||||
|
# AUTO SAVE MAT FROM ITR
|
||||||
|
mat.save_from_itr(
|
||||||
|
year=request.form["year"],
|
||||||
|
mat_created=float(request.form.get("mat_credit_created", 0)),
|
||||||
|
mat_utilized=float(request.form.get("mat_credit_utilized", 0)),
|
||||||
|
remarks="Created via ITR"
|
||||||
|
)
|
||||||
|
|
||||||
flash("AO record added successfully!", "success")
|
flash("AO record added successfully!", "success")
|
||||||
return redirect(url_for('display_ao'))
|
return redirect(url_for('display_ao'))
|
||||||
return render_template('add_ao.html')
|
return render_template('add_ao.html',current_date=date.today().isoformat())
|
||||||
|
|
||||||
# 3. UPDATE AO record
|
# 3. UPDATE AO record
|
||||||
@app.route('/ao/update/<int:id>', methods=['GET', 'POST'])
|
@app.route('/ao/update/<int:id>', methods=['GET', 'POST'])
|
||||||
@@ -449,6 +477,17 @@ def summary_report():
|
|||||||
docHandler = DocumentHandler()
|
docHandler = DocumentHandler()
|
||||||
return docHandler.Summary_report(request=request)
|
return docHandler.Summary_report(request=request)
|
||||||
|
|
||||||
|
@app.route('/summary/download', methods=['GET'])
|
||||||
|
@auth.login_required
|
||||||
|
def download_summary():
|
||||||
|
year_raw = request.args.get('year')
|
||||||
|
if not year_raw:
|
||||||
|
return "Year parameter is required", 400
|
||||||
|
|
||||||
|
docHandler = DocumentHandler()
|
||||||
|
# reuse your existing Summary_report method
|
||||||
|
return docHandler.Summary_report(request=request)
|
||||||
|
|
||||||
|
|
||||||
# check year in table existe or not by using ajax calling.
|
# check year in table existe or not by using ajax calling.
|
||||||
# @app.route('/check_year', methods=['POST'])
|
# @app.route('/check_year', methods=['POST'])
|
||||||
@@ -519,6 +558,12 @@ def save_mat_row():
|
|||||||
finally:
|
finally:
|
||||||
mat.close()
|
mat.close()
|
||||||
|
|
||||||
|
@app.route("/summary/preview")
|
||||||
|
def summary_preview_route():
|
||||||
|
handler = DocumentHandler()
|
||||||
|
return handler.Summary_preview(request)
|
||||||
|
|
||||||
|
|
||||||
# save mat credit bulk data
|
# save mat credit bulk data
|
||||||
# @app.route("/save_mat_all", methods=["POST"])
|
# @app.route("/save_mat_all", methods=["POST"])
|
||||||
# @auth.login_required
|
# @auth.login_required
|
||||||
|
|||||||
@@ -1,15 +1,157 @@
|
|||||||
/* ================= GLOBAL FORM ELEMENTS ================= */
|
/* ================= PAGE WRAPPER ================= */
|
||||||
|
.main {
|
||||||
|
margin-left: 260px;
|
||||||
|
width: calc(100% - 260px);
|
||||||
|
margin-top: 80px;
|
||||||
|
padding: 20px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= CONTAINER ================= */
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 25px 30px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= PAGE TITLE ================= */
|
||||||
|
.container h2 {
|
||||||
|
text-align: center;
|
||||||
|
color: #007bff;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= FORM ================= */
|
||||||
|
form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
form label {
|
form label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================= INPUTS ================= */
|
||||||
|
form input,
|
||||||
|
form select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-top: 6px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #f8f9ff;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input:focus,
|
||||||
|
form select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #007bff;
|
||||||
|
box-shadow: 0 0 6px rgba(0, 123, 255, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= AUTO FIELDS ================= */
|
||||||
|
.auto {
|
||||||
|
background-color: #d5edd7;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= FORM GROUP ================= */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline two columns */
|
||||||
|
.form-group.inline-2 {
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group.inline-2 > div {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= BUTTONS ================= */
|
||||||
|
button {
|
||||||
|
display: block;
|
||||||
|
width: 60%;
|
||||||
|
margin: 25px auto 0;
|
||||||
|
padding: 12px 20px;
|
||||||
|
background-color: #28a745;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #218838;
|
||||||
|
box-shadow: 0 4px 10px rgba(40, 167, 69, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= BACK BUTTON ================= */
|
||||||
|
.back-btn {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
background-color: #006ae6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= STICKY FILTER BAR ================= */
|
||||||
|
.head {
|
||||||
|
position: sticky;
|
||||||
|
top: 60px;
|
||||||
|
background: #fff;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 15px 0;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= SELECT + DOWNLOAD ================= */
|
||||||
|
.select-download-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-download-wrapper select {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
/* restrict width on desktop/laptop */
|
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -22,68 +164,60 @@ select {
|
|||||||
|
|
||||||
select:focus {
|
select:focus {
|
||||||
border-color: #007bff;
|
border-color: #007bff;
|
||||||
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
box-shadow: 0 0 5px rgba(0,123,255,0.5);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================= BUTTONS ================= */
|
/* ================= DOWNLOAD BUTTON ================= */
|
||||||
button {
|
#downloadBtn {
|
||||||
margin-top: 20px;
|
display: none;
|
||||||
padding: 10px 18px;
|
padding: 10px 20px;
|
||||||
background-color: #007bff;
|
font-size: 16px;
|
||||||
color: white;
|
background-color: #28a745;
|
||||||
border: none;
|
color: #fff;
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: #0069d9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================= MAIN CONTAINER ================= */
|
|
||||||
.main {
|
|
||||||
margin-left: 260px;
|
|
||||||
/* sidebar width if exists */
|
|
||||||
padding: 70px 30px 40px 30px;
|
|
||||||
/* top padding for navbar */
|
|
||||||
margin-top: 50px;
|
|
||||||
/* extra top spacing */
|
|
||||||
width: auto;
|
|
||||||
transition: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: #ffffff;
|
|
||||||
padding: 35px 40px;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================= BACK BUTTON ================= */
|
|
||||||
.back-btn {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 10px 18px;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 6px;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-btn:hover {
|
#downloadBtn:hover {
|
||||||
background-color: #006ae6;
|
background-color: #218838;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================= MESSAGES ================= */
|
/* ================= TABLE PREVIEW ================= */
|
||||||
|
#previewContent {
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#previewContent table {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 600px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
#previewContent th,
|
||||||
|
#previewContent td {
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sticky table header */
|
||||||
|
#previewContent th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= MESSAGE ================= */
|
||||||
.message {
|
.message {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
color: #555;
|
color: #555;
|
||||||
@@ -91,63 +225,54 @@ button:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ================= RESPONSIVE ================= */
|
/* ================= RESPONSIVE ================= */
|
||||||
|
|
||||||
/* Tablets (<= 992px) */
|
|
||||||
@media (max-width: 992px) {
|
@media (max-width: 992px) {
|
||||||
.main {
|
.main {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
/* remove sidebar spacing */
|
|
||||||
padding: 50px 20px 20px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: 25px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
max-width: 100%;
|
|
||||||
/* full width */
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
/* full width */
|
|
||||||
padding: 12px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile (<= 576px) */
|
|
||||||
@media (max-width: 576px) {
|
|
||||||
.main {
|
|
||||||
padding: 40px 15px 15px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 22px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
font-size: 14px;
|
|
||||||
padding: 12px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-btn {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
}
|
||||||
padding: 12px 0;
|
}
|
||||||
}
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
.message {
|
.container {
|
||||||
font-size: 13px;
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group.inline-2 {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.main {
|
||||||
|
padding: 15px;
|
||||||
|
margin-top: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#previewContent table {
|
||||||
|
min-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#previewContent th,
|
||||||
|
#previewContent td {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 420px) {
|
||||||
|
form input,
|
||||||
|
form select {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 9px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,16 +46,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
var tax_payable = (tax30 > tax185) ? tax30 : tax185;
|
var tax_payable = (tax30 > tax185) ? tax30 : tax185;
|
||||||
setValue("tax_payable", tax_payable);
|
setValue("tax_payable", tax_payable);
|
||||||
|
|
||||||
// // --- SURCHARGE ---
|
|
||||||
// var percent = getValue("persentage");
|
|
||||||
// var surcharge = tax_payable * (percent / 100);
|
|
||||||
// setValue("surcharge", surcharge);
|
|
||||||
|
|
||||||
// // --- edu_cess ---
|
|
||||||
// var per_cess = getValue("persentage_cess")
|
|
||||||
// var edu_cess = (tax_payable + surcharge) * (per_cess / 100);
|
|
||||||
// setValue("edu_cess", edu_cess);
|
|
||||||
|
|
||||||
// --- SURCHARGE ---
|
// --- SURCHARGE ---
|
||||||
var percent = getValue("persentage");
|
var percent = getValue("persentage");
|
||||||
var surcharge = tax_payable * (percent / 100);
|
var surcharge = tax_payable * (percent / 100);
|
||||||
@@ -70,11 +60,37 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
var total_tax_payable = tax_payable + surcharge + edu_cess;
|
var total_tax_payable = tax_payable + surcharge + edu_cess;
|
||||||
setValue("total_tax_payable", total_tax_payable);
|
setValue("total_tax_payable", total_tax_payable);
|
||||||
|
|
||||||
|
// // --- mat_credit_created --- new
|
||||||
|
// setValue("mat_credit_created", Math.max(tax185 - total_tax_payable, 0));
|
||||||
|
// // --- mat credit_utilized --- new
|
||||||
|
// setValue("mat_credit_utilized", Math.max(total_tax_payable - tax185, 0));
|
||||||
|
|
||||||
|
// --- MAT credit and utilized ---
|
||||||
|
var a = tax185
|
||||||
|
var b = total_tax_payable
|
||||||
|
var result = 0
|
||||||
|
if (a > b) {
|
||||||
|
result = a - b
|
||||||
|
setValue("mat_credit_created", result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setValue("mat_credit_created", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b > a) {
|
||||||
|
result = b - a
|
||||||
|
setValue("mat_credit_utilized", result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setValue("mat_credit_utilized", result);
|
||||||
|
}
|
||||||
|
|
||||||
// --- FINAL TAX ---
|
// --- FINAL TAX ---
|
||||||
var mat_credit = getValue("mat_credit_utilized");
|
var mat_credit = getValue("mat_credit_utilized");
|
||||||
var interest_234c = getValue("interest_234c");
|
var interest_234c = getValue("interest_234c");
|
||||||
|
|
||||||
var total_tax = total_tax_payable + mat_credit + interest_234c;
|
// var total_tax = total_tax_payable + mat_credit + interest_234c;
|
||||||
|
var total_tax = total_tax_payable - mat_credit + interest_234c;
|
||||||
setValue("total_tax", total_tax);
|
setValue("total_tax", total_tax);
|
||||||
|
|
||||||
// --- ASSESSMENT ---
|
// --- ASSESSMENT ---
|
||||||
|
|||||||
@@ -61,16 +61,36 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
setValue("total_tax_payable", total_tax_payable);
|
setValue("total_tax_payable", total_tax_payable);
|
||||||
|
|
||||||
// --- mat_credit_created --- new
|
// --- mat_credit_created --- new
|
||||||
setValue("mat_credit_created", Math.max(tax185 - total_tax_payable, 0));
|
// setValue("mat_credit_created", Math.max(tax185 - total_tax_payable, 0));
|
||||||
// --- mat credit_utilized --- new
|
// // --- mat credit_utilized --- new
|
||||||
setValue("mat_credit_utilized", Math.max(total_tax_payable - tax185, 0));
|
// setValue("mat_credit_utilized", Math.max(total_tax_payable - tax185, 0));
|
||||||
|
|
||||||
|
// --- mat credit_utilized ---
|
||||||
|
var a = tax185
|
||||||
|
var b = total_tax_payable
|
||||||
|
var result = 0
|
||||||
|
if (a > b) {
|
||||||
|
result = a - b
|
||||||
|
setValue("mat_credit_created", result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setValue("mat_credit_created", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b > a) {
|
||||||
|
result = b - a
|
||||||
|
setValue("mat_credit_utilized", result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setValue("mat_credit_utilized", result);
|
||||||
|
}
|
||||||
|
|
||||||
// --- FINAL TAX ---
|
// --- FINAL TAX ---
|
||||||
var mat_credit = getValue("mat_credit_utilized");
|
var mat_credit = getValue("mat_credit_utilized");
|
||||||
var interest_234c = getValue("interest_234c");
|
var interest_234c = getValue("interest_234c");
|
||||||
|
|
||||||
// var total_tax = total_tax_payable + mat_credit + interest_234c;
|
// var total_tax = total_tax_payable + mat_credit + interest_234c;
|
||||||
var total_tax = total_tax_payable + mat_credit + interest_234c;
|
var total_tax = total_tax_payable + interest_234c;
|
||||||
setValue("total_tax", total_tax);
|
setValue("total_tax", total_tax);
|
||||||
|
|
||||||
// --- ASSESSMENT ---
|
// --- ASSESSMENT ---
|
||||||
|
|||||||
55
static/js/summary_preview.js
Normal file
55
static/js/summary_preview.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
document.getElementById("year").addEventListener("change", function () {
|
||||||
|
const year = this.value;
|
||||||
|
|
||||||
|
const downloadBtn = document.getElementById("downloadBtn");
|
||||||
|
const previewDiv = document.getElementById("preview");
|
||||||
|
const contentDiv = document.getElementById("previewContent");
|
||||||
|
|
||||||
|
if (!year) {
|
||||||
|
downloadBtn.style.display = "none";
|
||||||
|
previewDiv.style.display = "none";
|
||||||
|
contentDiv.innerHTML = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadBtn.href = `/summary/download?year=${year}`;
|
||||||
|
downloadBtn.style.display = "inline-block";
|
||||||
|
|
||||||
|
fetch(`/summary/preview?year=${year}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
let html = `<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Particular</th>
|
||||||
|
<th>ITR</th>
|
||||||
|
<th>AO</th>
|
||||||
|
<th>CIT</th>
|
||||||
|
<th>ITAT</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>`;
|
||||||
|
|
||||||
|
data.forEach(row => {
|
||||||
|
html += `<tr>
|
||||||
|
<td>${row.Particular}</td>
|
||||||
|
<td>${row.ITR}</td>
|
||||||
|
<td>${row.AO}</td>
|
||||||
|
<td>${row.CIT}</td>
|
||||||
|
<td>${row.ITAT}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `</tbody></table>`;
|
||||||
|
contentDiv.innerHTML = html;
|
||||||
|
|
||||||
|
// Show preview
|
||||||
|
previewDiv.style.display = "block";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(err => console.error("Preview load error:", err));
|
||||||
|
});
|
||||||
@@ -10,7 +10,8 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<h2 style="text-align:center;">New Assessing Officer Form</h2>
|
<h2 style="text-align:center;">New Assessing Officer Form</h2>
|
||||||
<form id="ao" method="POST">
|
<form id="ao" method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="stage" value="ao">
|
||||||
<div class="form-group full-width inline-2">
|
<div class="form-group full-width inline-2">
|
||||||
<div>
|
<div>
|
||||||
<label>Assessment Year:</label>
|
<label>Assessment Year:</label>
|
||||||
@@ -19,11 +20,12 @@
|
|||||||
-- Please select Assessment Year --
|
-- Please select Assessment Year --
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="yearError" style="color:red; display:none; margin-bottom:10px;"></div>
|
<div id="yearError" style="color:red; display:none; margin-bottom:10px;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Record Created Date:</label>
|
||||||
|
<input type="date" name="created_at" value="{{ current_date }}" required>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group full-width inline-2">
|
<div class="form-group full-width inline-2">
|
||||||
@@ -170,10 +172,16 @@
|
|||||||
<input type="number" name="refund" class="auto" step="any" value="0.00" readonly>
|
<input type="number" name="refund" class="auto" step="any" value="0.00" readonly>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group full-width inline-2">
|
||||||
|
<div>
|
||||||
|
<label>Select Documents:</label>
|
||||||
|
<input type="file" name="documents" multiple>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<label>Remarks:</label>
|
<label>Remarks:</label>
|
||||||
<input type="text" name="Remarks">
|
<input type="text" name="Remarks">
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<div id="yearError" style="color:red; display:none; margin-bottom:10px;"></div>
|
<div id="yearError" style="color:red; display:none; margin-bottom:10px;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>Created Date:</label>
|
<label>Record Created Date:</label>
|
||||||
<input type="date" name="created_at" value="{{ current_date }}" required>
|
<input type="date" name="created_at" value="{{ current_date }}" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<th>Gross Total Income</th>
|
<th>Gross Total Income</th>
|
||||||
<th>Net Taxable Income</th>
|
<th>Net Taxable Income</th>
|
||||||
<th>Total Tax</th>
|
<th>Total Tax</th>
|
||||||
|
<th>Created Record Date</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
<td>{{ ao.gross_total_income }}</td>
|
<td>{{ ao.gross_total_income }}</td>
|
||||||
<td>{{ ao.net_taxable_income }}</td>
|
<td>{{ ao.net_taxable_income }}</td>
|
||||||
<td>{{ ao.total_tax }}</td>
|
<td>{{ ao.total_tax }}</td>
|
||||||
|
<td>{{ ao.created_at.strftime('%Y-%m-%d') }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('update_ao', id=ao.id) }}" class="btn btn-update">Edit</a>
|
<a href="{{ url_for('update_ao', id=ao.id) }}" class="btn btn-update">Edit</a>
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for row in mat_rows %}
|
{% for row in mat_rows %}
|
||||||
<tr>
|
<tr>
|
||||||
<td contenteditable="true">{{ row.financial_year }}</td>
|
<td contenteditable="false">{{ row.financial_year }}-{{ row.financial_year | int + 1 }}</td>
|
||||||
<td><input value="{{ row.mat_credit }}"></td>
|
<td><input value="{{ row.mat_credit }}"></td>
|
||||||
|
|
||||||
{% for y in added_years %}
|
{% for y in added_years %}
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %} {% block title %}Download Summary Report{% endblock %}
|
||||||
|
|
||||||
{% block title %}Download Summary Report{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
<!-- Optional: Add page-specific CSS -->
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/summary.css') }}" />
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/summary.css') }}">
|
{% endblock %} {% block content %}
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="head">
|
||||||
<h2>Download Year-wise Summary Report</h2>
|
<h2>Download Year-wise Summary Report</h2>
|
||||||
|
|
||||||
{% if message %}
|
{% if message %}
|
||||||
<p class="message">{{ message }}</p>
|
<p class="message">{{ message }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form method="GET" action="{{ url_for('summary_report') }}">
|
<div class="select-download-wrapper">
|
||||||
<label>Select Year:</label>
|
|
||||||
<select name="year" id="year" required>
|
<select name="year" id="year" required>
|
||||||
<option value="">-- Select Year --</option>
|
<option value="">-- Select Year --</option>
|
||||||
{% for year in years %}
|
{% for year in years %}
|
||||||
@@ -24,9 +18,17 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<button type="submit">Download Summary Report</button>
|
<a id="downloadBtn" href="#">Download Summary Report</a>
|
||||||
</form>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Preview Section -->
|
||||||
|
<div id="preview" style="display: none">
|
||||||
|
<h3>Summary Preview</h3>
|
||||||
|
|
||||||
|
<div id="previewContent"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %} {% block extra_js %}
|
||||||
|
<script src="{{ url_for('static', filename='js/summary_preview.js') }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Update AO Record for Year {{ record.year }}--{{ record.year + 1 }}</h2>
|
<h2>Update AO Record for Year {{ record.year }} - {{ record.year + 1 }}</h2>
|
||||||
<form method="POST" action="{{ url_for('update_ao', id=record.id) }}">
|
<form method="POST" action="{{ url_for('update_ao', id=record.id) }}">
|
||||||
<div class="form-group full-width inline-2">
|
<div class="form-group full-width inline-2">
|
||||||
<div>
|
<div>
|
||||||
@@ -169,6 +169,23 @@
|
|||||||
<input type="text" name="Remarks" value="{{ record.remarks}}">
|
<input type="text" name="Remarks" value="{{ record.remarks}}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-group full-width inline-2">
|
||||||
|
<div>
|
||||||
|
<label>Created Date:</label>
|
||||||
|
<input type="date" name="created_at"
|
||||||
|
value="{{ record.created_at.strftime('%Y-%m-%d') if record.created_at else current_date }}"
|
||||||
|
readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Last Updated:</label>
|
||||||
|
<input type="date" name="updated_at"
|
||||||
|
value="{{ record.updated_at.strftime('%Y-%m-%d') if record.updated_at else current_date }}"
|
||||||
|
readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit">Update Record</button>
|
<button type="submit">Update Record</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -27,7 +27,11 @@
|
|||||||
</select>
|
</select>
|
||||||
<button type="submit">Apply</button>
|
<button type="submit">Apply</button>
|
||||||
</form>
|
</form>
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{ category }}">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endwith %}
|
||||||
<!-- DOCUMENT TABLE -->
|
<!-- DOCUMENT TABLE -->
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table>
|
<table>
|
||||||
|
|||||||
Reference in New Issue
Block a user