swapnil-dev #3
24
.env
24
.env
@@ -1,24 +0,0 @@
|
||||
# -----------------------------
|
||||
# Flask App Configuration
|
||||
# -----------------------------
|
||||
FLASK_ENV=development
|
||||
FLASK_DEBUG=True
|
||||
FLASK_HOST=0.0.0.0
|
||||
FLASK_PORT=5010
|
||||
|
||||
# -----------------------------
|
||||
# Security
|
||||
# -----------------------------
|
||||
SECRET_KEY=secret1234
|
||||
|
||||
# -----------------------------
|
||||
# Database Configuration
|
||||
# -----------------------------
|
||||
DB_DIALECT=mysql
|
||||
# DB_DRIVER=pymysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_NAME=test_income_taxdb
|
||||
DB_USER=root
|
||||
DB_PASSWORD=root
|
||||
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -15,3 +15,13 @@ logs/
|
||||
|
||||
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
# Python cache
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# OS / Editor
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
@@ -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
|
||||
import pandas as pd
|
||||
import os
|
||||
@@ -17,11 +19,40 @@ class DocumentHandler:
|
||||
self.isSuccess = False
|
||||
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):
|
||||
year = request.args.get('year', '')
|
||||
year_raw = request.args.get('year', '')
|
||||
stage = request.args.get('stage', '')
|
||||
|
||||
year = self.parse_year(year_raw)
|
||||
|
||||
dbconfig = DBConfig()
|
||||
connection = dbconfig.get_db_connection()
|
||||
|
||||
@@ -30,15 +61,13 @@ class DocumentHandler:
|
||||
return
|
||||
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
# --- FILTER QUERY ---
|
||||
|
||||
cursor.callproc("GetDocuments", [year, stage])
|
||||
|
||||
# fetch first result set
|
||||
for result in cursor.stored_results():
|
||||
self.documents = result.fetchall()
|
||||
break
|
||||
|
||||
# ---- GET YEARS FROM STORED PROCEDURE ----
|
||||
cursor.callproc("GetYear")
|
||||
|
||||
for result in cursor.stored_results():
|
||||
@@ -49,55 +78,72 @@ class DocumentHandler:
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
self.isSuccess = True
|
||||
|
||||
# =========================
|
||||
# Upload Documents
|
||||
# =========================
|
||||
def Upload(self, request):
|
||||
dbconfig = DBConfig()
|
||||
connection = dbconfig.get_db_connection()
|
||||
|
||||
if connection:
|
||||
cursor = connection.cursor()
|
||||
files = request.files.getlist('documents')
|
||||
year = request.form['year']
|
||||
stage = request.form['stage']
|
||||
if not connection:
|
||||
return
|
||||
|
||||
for file in files:
|
||||
extension = file.filename.rsplit('.', 1)[1]
|
||||
if extension not in FileHandler.ALLOWED_EXTENSIONS:
|
||||
print("Skip invalid file type : ",extension)
|
||||
continue
|
||||
cursor = connection.cursor()
|
||||
|
||||
filename = secure_filename(file.filename)
|
||||
filepath = os.path.join(FileHandler.UPLOAD_FOLDER, filename)
|
||||
file.save(filepath)
|
||||
files = request.files.getlist('documents')
|
||||
year_raw = request.form.get('year')
|
||||
stage = request.form.get('stage')
|
||||
|
||||
cursor.callproc('InsertDocument', [ filename, filepath, extension, year, stage ])
|
||||
year = self.parse_year(year_raw)
|
||||
|
||||
connection.commit()
|
||||
cursor.close()
|
||||
connection.close()
|
||||
# return redirect(url_for('view_documents'))
|
||||
if not year:
|
||||
self.resultMessage = "Invalid year selected."
|
||||
return
|
||||
|
||||
def Summary_report(self, request):
|
||||
dbconfig = DBConfig()
|
||||
connection = dbconfig.get_db_connection()
|
||||
for file in files:
|
||||
if '.' not in file.filename:
|
||||
continue
|
||||
|
||||
year_str = request.args.get('year')
|
||||
extension = file.filename.rsplit('.', 1)[1].lower()
|
||||
|
||||
# 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."
|
||||
if extension not in FileHandler.ALLOWED_EXTENSIONS:
|
||||
print("Skipping invalid file:", extension)
|
||||
continue
|
||||
|
||||
filename = secure_filename(file.filename)
|
||||
filepath = os.path.join(FileHandler.UPLOAD_FOLDER, filename)
|
||||
|
||||
file.save(filepath)
|
||||
|
||||
cursor.callproc(
|
||||
'InsertDocument',
|
||||
[filename, filepath, extension, year, stage]
|
||||
)
|
||||
|
||||
# Convert year to int (IMPORTANT FIX)
|
||||
year = int(year_str)
|
||||
connection.commit()
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# =========================
|
||||
# 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()
|
||||
connection = dbconfig.get_db_connection()
|
||||
if not connection:
|
||||
return jsonify([])
|
||||
|
||||
try:
|
||||
stages = {
|
||||
@@ -112,11 +158,111 @@ class DocumentHandler:
|
||||
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()
|
||||
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()
|
||||
cursor.close()
|
||||
|
||||
@@ -124,25 +270,45 @@ class DocumentHandler:
|
||||
return df[col].values[0] if col in df.columns and not df.empty else "-"
|
||||
|
||||
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", " ",
|
||||
"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"
|
||||
"Tax on Regular Assessment",
|
||||
"Refund", "Remarks"
|
||||
]
|
||||
|
||||
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'
|
||||
'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'
|
||||
]
|
||||
|
||||
data = {
|
||||
@@ -155,56 +321,44 @@ class DocumentHandler:
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
|
||||
# ===== Excel Export =====
|
||||
output = io.BytesIO()
|
||||
|
||||
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)
|
||||
|
||||
workbook = writer.book
|
||||
worksheet = writer.sheets[sheet_name]
|
||||
|
||||
# ===== Company Heading =====
|
||||
company_heading = workbook.add_format({
|
||||
title = workbook.add_format({
|
||||
'bold': True,
|
||||
'font_size': 14,
|
||||
'font_color': 'black',
|
||||
'align': 'center',
|
||||
'valign': 'middle'
|
||||
'align': 'center'
|
||||
})
|
||||
|
||||
worksheet.merge_range(
|
||||
0, 0, 0, len(df.columns) - 1,
|
||||
"Laxmi Civil Engineering Services Pvt Ltd",
|
||||
company_heading
|
||||
title
|
||||
)
|
||||
|
||||
# ===== Header Format =====
|
||||
header = workbook.add_format({
|
||||
'bold': True,
|
||||
'align': 'center',
|
||||
'valign': 'middle',
|
||||
'bg_color': '#007bff',
|
||||
'font_color': 'white',
|
||||
'border': 1
|
||||
})
|
||||
|
||||
cell = workbook.add_format({
|
||||
'border': 1,
|
||||
'align': 'left',
|
||||
'valign': 'middle'
|
||||
})
|
||||
cell = workbook.add_format({'border': 1})
|
||||
|
||||
# Write headers
|
||||
for col_num, col_name in enumerate(df.columns):
|
||||
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, max_len)
|
||||
worksheet.set_column(col_num, col_num, 25)
|
||||
|
||||
# Write data rows
|
||||
for row in range(3, len(df) + 3):
|
||||
for row in range(len(df)):
|
||||
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)
|
||||
|
||||
@@ -219,3 +373,4 @@ class DocumentHandler:
|
||||
|
||||
finally:
|
||||
connection.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.
Binary file not shown.
17
main.py
17
main.py
@@ -443,6 +443,17 @@ def summary_report():
|
||||
docHandler = DocumentHandler()
|
||||
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.
|
||||
# @app.route('/check_year', methods=['POST'])
|
||||
@@ -513,6 +524,12 @@ def save_mat_row():
|
||||
finally:
|
||||
mat.close()
|
||||
|
||||
@app.route("/summary/preview")
|
||||
def summary_preview_route():
|
||||
handler = DocumentHandler()
|
||||
return handler.Summary_preview(request)
|
||||
|
||||
|
||||
# save mat credit bulk data
|
||||
# @app.route("/save_mat_all", methods=["POST"])
|
||||
# @auth.login_required
|
||||
|
||||
@@ -1,153 +1,278 @@
|
||||
/* ================= 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 {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
/* restrict width on desktop/laptop */
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
margin-top: 6px;
|
||||
font-size: 15px;
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
/* ================= 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;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
||||
outline: none;
|
||||
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 {
|
||||
margin-top: 20px;
|
||||
padding: 10px 18px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
transition: 0.3s;
|
||||
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: #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);
|
||||
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: white;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
transition: 0.3s;
|
||||
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;
|
||||
background-color: #006ae6;
|
||||
}
|
||||
|
||||
/* ================= MESSAGES ================= */
|
||||
/* ================= 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 {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
margin-top: 6px;
|
||||
font-size: 15px;
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 5px rgba(0,123,255,0.5);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* ================= DOWNLOAD BUTTON ================= */
|
||||
#downloadBtn {
|
||||
display: none;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
background-color: #28a745;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
#downloadBtn:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
/* ================= 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 {
|
||||
margin-top: 15px;
|
||||
color: #555;
|
||||
font-size: 14px;
|
||||
margin-top: 15px;
|
||||
color: #555;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ================= RESPONSIVE ================= */
|
||||
|
||||
/* Tablets (<= 992px) */
|
||||
@media (max-width: 992px) {
|
||||
.main {
|
||||
margin-left: 0;
|
||||
/* remove sidebar spacing */
|
||||
padding: 50px 20px 20px 20px;
|
||||
}
|
||||
.main {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 25px 20px;
|
||||
}
|
||||
|
||||
select {
|
||||
max-width: 100%;
|
||||
/* full width */
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
/* full width */
|
||||
padding: 12px 0;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.container h2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.form-group.inline-2 {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile (<= 576px) */
|
||||
@media (max-width: 576px) {
|
||||
.main {
|
||||
padding: 40px 15px 15px 15px;
|
||||
}
|
||||
.main {
|
||||
padding: 15px;
|
||||
margin-top: 70px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
#previewContent table {
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
#previewContent th,
|
||||
#previewContent td {
|
||||
font-size: 13px;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 14px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.message {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 420px) {
|
||||
form input,
|
||||
form select {
|
||||
font-size: 13px;
|
||||
padding: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
});
|
||||
@@ -1,32 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Download Summary Report{% endblock %}
|
||||
|
||||
{% extends "base.html" %} {% block title %}Download Summary Report{% endblock %}
|
||||
{% block extra_css %}
|
||||
<!-- Optional: Add page-specific CSS -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/summary.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ url_for('static', filename='css/summary.css') }}"
|
||||
/>
|
||||
{% endblock %} {% block content %}
|
||||
<div class="container">
|
||||
<div class="head">
|
||||
<h2>Download Year-wise Summary Report</h2>
|
||||
|
||||
{% if message %}
|
||||
<p class="message">{{ message }}</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="GET" action="{{ url_for('summary_report') }}">
|
||||
<label>Select Year:</label>
|
||||
<select name="year" id="year" required>
|
||||
<option value="">-- Select Year --</option>
|
||||
{% for year in years %}
|
||||
<option value="{{ year }}">AY {{ year }}-{{ year + 1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="select-download-wrapper">
|
||||
<select name="year" id="year" required>
|
||||
<option value="">-- Select Year --</option>
|
||||
{% for year in years %}
|
||||
<option value="{{ year }}">AY {{ year }}-{{ year + 1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<button type="submit">Download Summary Report</button>
|
||||
</form>
|
||||
<a id="downloadBtn" href="#">Download Summary Report</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview Section -->
|
||||
<div id="preview" style="display: none">
|
||||
<h3>Summary Preview</h3>
|
||||
|
||||
<div id="previewContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %} {% block extra_js %}
|
||||
<script src="{{ url_for('static', filename='js/summary_preview.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h2>Upload Income Tax Documents</h2>
|
||||
<form id="income_tax_documents" method="POST" enctype="multipart/form-data">
|
||||
<form id="documents" method="POST" enctype="multipart/form-data">
|
||||
<label>Year:</label>
|
||||
<select id="year" name="year" required></select>
|
||||
<div id="yearError" style="color: red; display: none; margin-bottom: 10px;"></div>
|
||||
|
||||
Reference in New Issue
Block a user