From b9de362b43fc40d90f89370e8ad9f5d51b161979 Mon Sep 17 00:00:00 2001 From: pjpatil12 Date: Sat, 27 Dec 2025 16:53:14 +0530 Subject: [PATCH] Mdified comparison route and roundoff values checking also. --- AppCode/FileHandler.py | 14 ++++++++ AppCode/ServerPort.py | 5 +++ AppCode/compare_excel.py | 74 +++++++++++++++++++++++++++++++++++++++ app.py | 42 ++++++++++++++-------- compare_gst_excel.py | 64 --------------------------------- unmatched_result.xlsx | Bin 5593 -> 6757 bytes 6 files changed, 121 insertions(+), 78 deletions(-) create mode 100644 AppCode/FileHandler.py create mode 100644 AppCode/ServerPort.py create mode 100644 AppCode/compare_excel.py delete mode 100644 compare_gst_excel.py diff --git a/AppCode/FileHandler.py b/AppCode/FileHandler.py new file mode 100644 index 0000000..543cabb --- /dev/null +++ b/AppCode/FileHandler.py @@ -0,0 +1,14 @@ +import os + + +class FileHandler: + + UPLOAD_FOLDER = 'uploads' + RESULT_FILE = 'unmatched_result.xlsx' + + @staticmethod + def check_or_create_folder_exists(): + + if not os.path.exists(FileHandler.UPLOAD_FOLDER): + os.makedirs(FileHandler.UPLOAD_FOLDER) + diff --git a/AppCode/ServerPort.py b/AppCode/ServerPort.py new file mode 100644 index 0000000..6ded969 --- /dev/null +++ b/AppCode/ServerPort.py @@ -0,0 +1,5 @@ + +# Server config. +class CompGSTServer: + host='0.0.0.0' + port=5001 diff --git a/AppCode/compare_excel.py b/AppCode/compare_excel.py new file mode 100644 index 0000000..baab192 --- /dev/null +++ b/AppCode/compare_excel.py @@ -0,0 +1,74 @@ +import pandas as pd + +class Comparison: + + @staticmethod + def normalize_value(val): + """Normalize non-numeric values (date, string).""" + if pd.isna(val): + return "" + + # Normalize dates + if isinstance(val, pd.Timestamp): + return val.date().isoformat() + + # Normalize strings + return str(val).strip().upper().replace(" ", "") + + @staticmethod + def round_amount(val, decimals=0): + """Round numeric values safely for comparison.""" + if pd.isna(val): + return "" + try: + return round(float(val), decimals) + except Exception: + return val + + @staticmethod + def find_unmatched_rows(sheet1_df, sheet2_df): + # Clean column names + sheet1_df.columns = sheet1_df.columns.str.strip() + sheet2_df.columns = sheet2_df.columns.str.strip() + + comparison_columns = [ + 'Date', + 'GSTIN/UIN', + 'Voucher Ref. No.', + 'Total Tax', + 'Total Amount' + ] + + amount_columns = ['Total Tax', 'Total Amount'] + + # Validate required columns + for col in comparison_columns: + if col not in sheet1_df.columns or col not in sheet2_df.columns: + raise ValueError(f"Missing column '{col}' in one of the sheets.") + + # Normalize values + for col in comparison_columns: + if col in amount_columns: + sheet1_df[col] = sheet1_df[col].apply( + lambda x: Comparison.round_amount(x, 0) + ) + sheet2_df[col] = sheet2_df[col].apply( + lambda x: Comparison.round_amount(x, 0) + ) + else: + sheet1_df[col] = sheet1_df[col].apply(Comparison.normalize_value) + sheet2_df[col] = sheet2_df[col].apply(Comparison.normalize_value) + + # Create comparison keys + sheet1_keys = sheet1_df[comparison_columns].apply(tuple, axis=1) + sheet2_keys = sheet2_df[comparison_columns].apply(tuple, axis=1) + + # Find unmatched rows + unmatched_sheet1 = sheet1_df[~sheet1_keys.isin(sheet2_keys)].copy() + unmatched_sheet2 = sheet2_df[~sheet2_keys.isin(sheet1_keys)].copy() + + # Add source column + unmatched_sheet1["Source"] = "Portal" + unmatched_sheet2["Source"] = "Tally" + + return unmatched_sheet1, unmatched_sheet2 diff --git a/app.py b/app.py index ff642bd..a647425 100644 --- a/app.py +++ b/app.py @@ -1,42 +1,56 @@ from flask import Flask, render_template, request, send_file import pandas as pd -from compare_gst_excel import find_unmatched_rows import os from werkzeug.utils import secure_filename +from AppCode.FileHandler import FileHandler +from AppCode.ServerPort import CompGSTServer +from AppCode.compare_excel import Comparison app = Flask(__name__) -UPLOAD_FOLDER = 'uploads' -RESULT_FILE = 'unmatched_result.xlsx' -if not os.path.exists(UPLOAD_FOLDER): - os.makedirs(UPLOAD_FOLDER) +# check file Folder valid +FileHandler.check_or_create_folder_exists() +# upload page show @app.route('/') def index(): return render_template('upload.html') +# camparison route @app.route('/upload', methods=['POST']) def upload_file(): - file = request.files['excel_file'] + file = request.files.get('excel_file') if not file: return "No file uploaded.", 400 filename = secure_filename(file.filename) - filepath = os.path.join(UPLOAD_FOLDER, filename) + filepath = os.path.join(FileHandler.UPLOAD_FOLDER, filename) file.save(filepath) try: - # Read Excel with header in row 8 (0-indexed), so header=7 - df1 = pd.read_excel(filepath, sheet_name=0, header=7) - df2 = pd.read_excel(filepath, sheet_name=1, header=7) + # Read first two sheets + df1 = pd.read_excel(filepath, sheet_name=0, header=0) + df2 = pd.read_excel(filepath, sheet_name=1, header=0) - unmatched = find_unmatched_rows(df1, df2) - unmatched.to_excel(RESULT_FILE, index=False) + # Get unmatched rows separately + unmatched_sheet1, unmatched_sheet2 = Comparison.find_unmatched_rows(df1, df2) - return send_file(RESULT_FILE, as_attachment=True) + # Write result into two Excel sheets + with pd.ExcelWriter(FileHandler.RESULT_FILE, engine="openpyxl") as writer: + unmatched_sheet1.to_excel( + writer, sheet_name="Not In Tally", index=False + ) + unmatched_sheet2.to_excel( + writer, sheet_name="Not In Portal", index=False + ) + + return send_file(FileHandler.RESULT_FILE, as_attachment=True) except Exception as e: return f"Error processing file: {e}", 500 +# run if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000,debug=True) + app.run(host=CompGSTServer.host, port=CompGSTServer.port, debug=True) + + diff --git a/compare_gst_excel.py b/compare_gst_excel.py deleted file mode 100644 index cda5a58..0000000 --- a/compare_gst_excel.py +++ /dev/null @@ -1,64 +0,0 @@ -# import pandas as pd - -# def normalize_row(row): -# return tuple( -# str(cell).strip().replace(".0", "") if isinstance(cell, float) and cell.is_integer() else str(cell).strip() -# for cell in row -# ) - -# def find_unmatched_rows(sheet1_df, sheet2_df): -# # Ensure column names are clean -# sheet1_df.columns = sheet1_df.columns.str.strip() -# sheet2_df.columns = sheet2_df.columns.str.strip() - -# # Normalize rows for comparison -# sheet1_normalized = sheet1_df.apply(normalize_row, axis=1) -# sheet2_normalized = sheet2_df.apply(normalize_row, axis=1) - -# # Find unmatched rows -# unmatched_in_sheet1 = sheet1_df[~sheet1_normalized.isin(sheet2_normalized)] -# unmatched_in_sheet2 = sheet2_df[~sheet2_normalized.isin(sheet1_normalized)] - -# # Mark source -# unmatched_in_sheet1["Source"] = "Sheet1" -# unmatched_in_sheet2["Source"] = "Sheet2" - -# # Combine -# unmatched_combined = pd.concat([unmatched_in_sheet1, unmatched_in_sheet2], ignore_index=True) -# return unmatched_combined -import pandas as pd - -def normalize_row(row): - return tuple( - str(cell).strip().replace(".0", "") if isinstance(cell, float) and cell.is_integer() else str(cell).strip() - for cell in row - ) - -def find_unmatched_rows(sheet1_df, sheet2_df): - # Clean column names - sheet1_df.columns = sheet1_df.columns.str.strip() - sheet2_df.columns = sheet2_df.columns.str.strip() - - # Choose the comparison columns - comparison_columns = ['Date', 'GSTIN/UIN'] - - # Ensure required columns exist - for col in comparison_columns: - if col not in sheet1_df.columns or col not in sheet2_df.columns: - raise ValueError(f"Missing column '{col}' in one of the sheets.") - - # Create keys for comparison - sheet1_keys = sheet1_df[comparison_columns].apply(normalize_row, axis=1) - sheet2_keys = sheet2_df[comparison_columns].apply(normalize_row, axis=1) - - # Find unmatched rows - unmatched_in_sheet1 = sheet1_df[~sheet1_keys.isin(sheet2_keys)].copy() - unmatched_in_sheet2 = sheet2_df[~sheet2_keys.isin(sheet1_keys)].copy() - - # Mark source - unmatched_in_sheet1["Source"] = "Sheet1" - unmatched_in_sheet2["Source"] = "Sheet2" - - # Combine - unmatched_combined = pd.concat([unmatched_in_sheet1, unmatched_in_sheet2], ignore_index=True) - return unmatched_combined diff --git a/unmatched_result.xlsx b/unmatched_result.xlsx index e28f58684c15caa54d0076cf185c461bf67f47e8..937509f51a033c3352eba1bee668401879c03c63 100644 GIT binary patch delta 4391 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