diff --git a/AppCode/MatCreditHandler.py b/AppCode/MatCreditHandler.py new file mode 100644 index 0000000..892ef12 --- /dev/null +++ b/AppCode/MatCreditHandler.py @@ -0,0 +1,123 @@ +from AppCode.Config import DBConfig +import mysql.connector + + + +class MatCreditHandler: + + def __init__(self): + db = DBConfig() + self.conn = db.get_db_connection() + self.cursor = self.conn.cursor(dictionary=True) + + @staticmethod + def fetch_all(): + conn = DBConfig.get_db_connection() + cur = conn.cursor(dictionary=True) + + try: + # Stored Procedure returns TWO result sets + cur.callproc("GetMatCedit") + + result_sets = cur.stored_results() + + mat_rows = next(result_sets).fetchall() + utilization_rows = next(result_sets).fetchall() + + return mat_rows, utilization_rows + + finally: + cur.close() + conn.close() + + @staticmethod + def save_single(data): + conn = DBConfig.get_db_connection() + cur = conn.cursor() + + try: + cur.execute( + "SELECT id FROM mat_credit WHERE financial_year=%s", + (data["financial_year"],) + ) + row = cur.fetchone() + + if row: + mat_id = row[0] + + cur.execute(""" + UPDATE mat_credit + SET mat_credit=%s, balance=%s + WHERE id=%s + """, (data["mat_credit"], data["balance"], mat_id)) + + cur.execute( + "DELETE FROM mat_utilization WHERE mat_credit_id=%s", + (mat_id,) + ) + else: + cur.execute(""" + INSERT INTO mat_credit (financial_year, mat_credit, balance) + VALUES (%s,%s,%s) + """, (data["financial_year"], data["mat_credit"], data["balance"])) + + mat_id = cur.lastrowid + + for u in data["utilization"]: + cur.execute(""" + INSERT INTO mat_utilization + (mat_credit_id, utilized_year, utilized_amount) + VALUES (%s,%s,%s) + """, (mat_id, u["year"], u["amount"])) + + conn.commit() + + except Exception: + conn.rollback() + raise + + finally: + cur.close() + conn.close() + + + @staticmethod + def save_bulk(rows): + conn = DBConfig.get_db_connection() + cur = conn.cursor() + skipped = [] + + try: + for row in rows: + cur.execute( + "SELECT id FROM mat_credit WHERE financial_year=%s", + (row["financial_year"],) + ) + if cur.fetchone(): + skipped.append(row["financial_year"]) + continue + + cur.execute(""" + 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 + + for u in row["utilization"]: + cur.execute(""" + INSERT INTO mat_utilization + (mat_credit_id, utilized_year, utilized_amount) + VALUES (%s,%s,%s) + """, (mat_id, u["year"], u["amount"])) + + conn.commit() + return skipped + + except Exception: + conn.rollback() + raise + + finally: + cur.close() + conn.close() \ No newline at end of file diff --git a/main.py b/main.py index f57112a..85df8a3 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,11 @@ -from flask import Flask, render_template, request, redirect, url_for, send_from_directory, abort, flash,send_file +from flask import Flask, render_template, request, redirect, url_for, send_from_directory, abort, flash,send_file ,jsonify import os import pandas as pd import pymysql import io import mysql.connector from werkzeug.utils import secure_filename - +from datetime import datetime from AppCode.Config import DBConfig from AppCode.FileHandler import FileHandler @@ -16,6 +16,8 @@ from AppCode.CITHandler import CITHandler from AppCode.ITATHandler import ITATHandler from AppCode.YearGet import YearGet from AppCode.LoginAuth import LoginAuth +from AppCode.MatCreditHandler import MatCreditHandler + # Server @@ -433,8 +435,8 @@ def check_year(): conn = DBConfig.get_db_connection() cursor = conn.cursor() - query = f"SELECT COUNT(*) FROM {table_name} WHERE year = %s" - cursor.execute(query, (year,)) + sqlstr = f"SELECT COUNT(*) FROM {table_name} WHERE year = %s" + cursor.execute(sqlstr, (year,)) result = cursor.fetchone()[0] cursor.close() @@ -442,6 +444,54 @@ def check_year(): return {"exists": result > 0} +# new new + + +# Mat credit from +@app.route("/mat_credit", methods=["GET"]) +def mat_credit(): + + mat= MatCreditHandler() + + mat_rows, utilization_rows = mat.fetch_all() + + utilization_map = {} + all_years = set() + + for u in utilization_rows: + all_years.add(u["utilized_year"]) + utilization_map.setdefault( + u["mat_credit_id"], {} + )[u["utilized_year"]] = u["utilized_amount"] + + return render_template( + "mat_credit.html", + mat_rows=mat_rows, + utilization_map=utilization_map, + added_years=sorted(all_years) + ) + +# save mat credit row data +@app.route("/save_mat_row", methods=["POST"]) +def save_mat_row(): + mat= MatCreditHandler() + try: + mat.save_single(request.json) + return jsonify({"message": "Row saved successfully"}) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +# save mat credit bulk data +@app.route("/save_mat_all", methods=["POST"]) +def save_mat_all(): + mat= MatCreditHandler() + try: + skipped = mat.save_bulk(request.json) + return jsonify({"message": "Saved successfully", "skipped": skipped}) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + # run if __name__ == '__main__': diff --git a/static/css/mat_credit.css b/static/css/mat_credit.css new file mode 100644 index 0000000..8a49df6 --- /dev/null +++ b/static/css/mat_credit.css @@ -0,0 +1,146 @@ +/* ===== CONTAINER ===== */ +.container { + max-width: 1200px; + margin: 30px auto; + padding: 25px; + background: #ffffff; + border-radius: 10px; + box-shadow: 0 4px 18px rgba(0, 0, 0, 0.08); +} + +/* ===== TABLE ===== */ +#matTable { + width: 100%; + border-collapse: collapse; + font-size: 14px; + text-align: center; +} + +/* ===== HEADER ===== */ +#matTable thead th { + background: linear-gradient(135deg, #0d6efd, #0a58ca); + color: #ffffff; + padding: 12px 8px; + border: 1px solid #0a58ca; + font-weight: 600; + white-space: nowrap; +} + +/* ===== BODY CELLS ===== */ +#matTable tbody td { + padding: 8px; + border: 1px solid #dcdcdc; + background-color: #ffffff; +} + +/* ===== FY COLUMN ===== */ +#matTable tbody td:first-child { + font-weight: 600; + background-color: #f5f8ff; +} + +/* ===== INPUT FIELDS ===== */ +#matTable input { + width: 100%; + padding: 6px 6px; + border: 1px solid #cfd8dc; + border-radius: 4px; + font-size: 13px; + text-align: right; + box-sizing: border-box; +} + +/* TEXT INPUT (Unutilized) */ +#matTable input[type="text"] { + text-align: center; +} + +/* INPUT FOCUS */ +#matTable input:focus { + border-color: #0d6efd; + box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.15); + outline: none; +} + +/* ===== ACTION BUTTON ===== */ +#matTable button { + background-color: #198754; + border: none; + color: white; + padding: 6px 14px; + border-radius: 4px; + cursor: pointer; + font-size: 13px; + transition: 0.2s; +} + +#matTable button:hover { + background-color: #157347; +} + +/* ===== SAVE ALL BUTTON ===== */ +button[onclick="saveAll()"] { + display: block; + width: 300px; + margin: 25px auto 0; + background-color: #28a745; + color: #fff; + font-size: 16px; + font-weight: 600; + padding: 12px; + border-radius: 8px; + border: none; + cursor: pointer; +} + +button[onclick="saveAll()"]:hover { + background-color: #218838; +} + +/* ===== ROW HOVER ===== */ +#matTable tbody tr:hover { + background-color: #f1f6ff; +} + +/* ===== ERROR HIGHLIGHT ===== */ +.input-error { + border-color: #dc3545 !important; + background-color: #fff5f5; +} + +/* ===== SUCCESS HIGHLIGHT ===== */ +.row-saved { + background-color: #e9f7ef !important; +} + +/* ===== RESPONSIVE ===== */ +@media (max-width: 768px) { + #matTable { + font-size: 12px; + } + + #matTable thead { + display: none; + } + + #matTable tbody tr { + display: block; + margin-bottom: 15px; + border: 1px solid #ccc; + border-radius: 6px; + padding: 10px; + } + + #matTable tbody td { + display: flex; + justify-content: space-between; + padding: 6px 8px; + border: none; + } + + #matTable tbody td::before { + content: attr(data-label); + font-weight: 600; + color: #0d6efd; + } +} \ No newline at end of file diff --git a/static/js/mat_credit.js b/static/js/mat_credit.js new file mode 100644 index 0000000..e98bb7f --- /dev/null +++ b/static/js/mat_credit.js @@ -0,0 +1,184 @@ +/* ===================================================== + GLOBAL STATE +===================================================== */ +let addedYears = []; + +/* ===================================================== + INITIALIZE YEARS FROM DATABASE HEADERS +===================================================== */ +document.addEventListener("DOMContentLoaded", () => { + const headers = document.querySelectorAll("#tableHeader th"); + + headers.forEach(th => { + if (th.innerText.startsWith("Utilized")) { + const year = th.innerText.replace("Utilized", "").trim(); + if (!addedYears.includes(year)) { + addedYears.push(year); + } + } + }); +}); + +/* ===================================================== + ADD YEAR COLUMN (DYNAMIC) +===================================================== */ +function addYearColumn() { + const yearSelect = document.getElementById("yearSelect"); + const year = yearSelect.value; + + if (!year) { + alert("Please select AY"); + return; + } + + if (addedYears.includes(year)) { + alert("This AY is already added"); + return; + } + + addedYears.push(year); + + // Add header column + const headerRow = document.getElementById("tableHeader"); + const th = document.createElement("th"); + th.innerText = "Utilized " + year; + + // Insert before Balance column + headerRow.insertBefore(th, headerRow.children[headerRow.children.length - 2]); + + // Add input cells to all existing rows + document.querySelectorAll("#matTable tbody tr").forEach(tr => { + const td = document.createElement("td"); + td.innerHTML = ``; + tr.insertBefore(td, tr.children[tr.children.length - 2]); + }); +} + +/* ===================================================== + ADD NEW ROW +===================================================== */ +function addRow() { + const tbody = document.querySelector("#matTable tbody"); + const tr = document.createElement("tr"); + + let utilizedCols = ""; + addedYears.forEach(() => { + utilizedCols += `