diff --git a/app_main.py b/app_main.py index 6bfa90d..f88b8dc 100644 --- a/app_main.py +++ b/app_main.py @@ -7,7 +7,10 @@ from aviza.gls_single import extract_and_process_zip_gls_single from csv2gpc import convert_csv_to_gpc, mapping_sparkasse, mapping_pko, mapping_wise from allegro import convert_csv_to_gpc_allegro, mapping_allegro from aviza.przelewy24_report_single import extract_and_process_zip_przelewy_report_single -from aviza.paynl_auto import convert_csv_to_gpc_paynl_auto, mapping_paynl_avizo +from aviza.paynl_auto import extract_and_process_zip_paynl_auto, mapping_paynl_avizo +from aviza.przelewy24_auto import extract_and_process_zip_przelewy24_auto, mapping_przelewy_avizo +from aviza.packeta_comgate_convert import extract_and_process_zip_packeta2comgate, mapping_payu +from aviza.przelewy24_transactions import extract_and_process_csv_przelewy24_transactions, mapping_przelewy_transactions import tempfile import datetime @@ -125,10 +128,52 @@ def index(): ) elif option == "paynl_auto": - result_data = convert_csv_to_gpc_paynl_auto(tmp_file_path, bank_statement_file_path, f"{option}_vypis_{day_of_year}.gpc", + result_data = extract_and_process_zip_paynl_auto(tmp_file_path, bank_statement_file_path, f"{option}_avizo_{day_of_year}.gpc", account_number=3498710000999125, currency="EUR", mapping=mapping_paynl_avizo) + return send_file( + result_data, + download_name=f"paynl_auto_{day_of_year}.zip", + as_attachment=True, + mimetype="application/zip" + ) + + elif option == "przelewy24_auto": + + result_data = extract_and_process_zip_przelewy24_auto(tmp_file_path, bank_statement_file_path, f"{option}_avizo_{day_of_year}.gpc", + account_number=3498710000999133, + currency="PLN", mapping=mapping_przelewy_avizo) + + return send_file( + result_data, + download_name=f"przelewy24_auto_{day_of_year}.zip", + as_attachment=True, + mimetype="application/zip" + ) + elif option == "przelewy24_transactions": + + result_data = extract_and_process_csv_przelewy24_transactions(tmp_file_path, bank_statement_file_path, f"{option}_avizo_{day_of_year}.gpc", + account_number=3498710000999133, + currency="PLN", mapping=mapping_przelewy_transactions) + + return send_file( + result_data, + download_name=f"przelewy24_auto_{day_of_year}.zip", + as_attachment=True, + mimetype="application/zip" + ) + elif option == "packeta2comgate": + + result_data = extract_and_process_zip_packeta2comgate(tmp_file_path, f"{option}_avizo_{day_of_year}.gpc", mapping=mapping_payu) + + return send_file( + result_data, + download_name=f"packeta_auto_{day_of_year}.zip", + as_attachment=True, + mimetype="application/zip" + ) + if isinstance(result_data, str): @@ -142,4 +187,4 @@ def index(): if __name__ == '__main__': - app.run(debug=True) + app.run(debug=True, port=5002) diff --git a/aviza/helpers.py b/aviza/helpers.py index 43fbf8b..b70da3d 100644 --- a/aviza/helpers.py +++ b/aviza/helpers.py @@ -28,4 +28,19 @@ def write_output_csv_to_zip(output_file, transformed_data): zf.writestr(filename, csv_content) mem_zip.seek(0) # Reset pointer to the beginning of the ZIP archive + return mem_zip + +def write_output_gpc_to_zip(output_file, transformed_data): + # Create an in-memory ZIP archive + mem_zip = io.BytesIO() + with zipfile.ZipFile(mem_zip, mode="w", compression=zipfile.ZIP_DEFLATED) as zf: + for idx, file_content in enumerate(transformed_data): + filename = f"{idx}_{output_file}" + # Check if file_content is a string; if so, encode it + if isinstance(file_content, str): + encoded_content = file_content.encode("windows-1250") + else: + encoded_content = file_content + zf.writestr(filename, encoded_content) + mem_zip.seek(0) # Reset pointer to the beginning of the ZIP archive return mem_zip \ No newline at end of file diff --git a/aviza/packeta_comgate_convert.py b/aviza/packeta_comgate_convert.py new file mode 100644 index 0000000..6f560f5 --- /dev/null +++ b/aviza/packeta_comgate_convert.py @@ -0,0 +1,228 @@ +import csv +import pandas as pd +import chardet +import os +import io +import zipfile +import shutil +from datetime import datetime +from gpc import Data, Header, TransactionCode, CURRENCIES_GPC +from utils import extract_order_number, extract_numbers, parse_date +from decimal import Decimal, ROUND_HALF_UP +from aviza.helpers import write_output_gpc_to_zip + +def extract_and_process_zip_packeta2comgate(zip_file_path, output_file, mapping): + + all_transformed_data = [] + + # Create a temporary folder for extraction + base_dir = os.path.dirname(zip_file_path) + extract_folder = os.path.join(base_dir, "extracted_temp") + os.makedirs(extract_folder, exist_ok=True) + + # Extract the provided outer zip file + with zipfile.ZipFile(zip_file_path, 'r') as zip_ref: + zip_ref.extractall(extract_folder) + + currency_folders = { + "EUR": {"currency": "EUR", "account_number": "3498710000999125"}, + "HUF": {"currency": "HUF", "account_number": "3498710000999141"}, + "RON": {"currency": "RON", "account_number": "3498710000999149"}, + } + + all_transformed_data = [] + + # Walk through extract_folder to find the currency folders + for root, dirs, files in os.walk(extract_folder): + for folder in dirs: + if folder in currency_folders: + folder_path = os.path.join(root, folder) + currency_info = currency_folders[folder] + currency = currency_info["currency"] + account_number = currency_info["account_number"] + + # Process CSV files inside the currency folder + for file in os.listdir(folder_path): + if file.endswith(".csv") and not file.startswith("._"): + file_path = os.path.join(folder_path, file) + try: + transformed_data = convert_payu2comgate(file_path, account_number, currency, mapping) + all_transformed_data.append(transformed_data) + except Exception as e: + print(f"Error processing {file}: {e}") + + + # Clean up the outer extraction folder + shutil.rmtree(extract_folder) + print(f"Processed and cleaned up: {zip_file_path}") + + # Return a ZIP archive (in memory) containing all the output CSV files. + return write_output_gpc_to_zip(output_file, all_transformed_data) + + +def convert_payu2comgate(csv_file_path, account_number, currency, mapping): + + gpc_lines = [] + + if mapping['forced_encoding'] is not None: + detected_encoding = mapping['forced_encoding'] + print(f"Forced encoding: {detected_encoding}") + else: + with open(csv_file_path, 'rb') as f: + rawdata = f.read(1024) # Read a small part of the file + result = chardet.detect(rawdata) + detected_encoding = result['encoding'] + print(f"Detected encoding: {detected_encoding}") + + + with open(csv_file_path, mode='r', encoding=detected_encoding) as csv_file: + + + reader = csv.DictReader(csv_file, delimiter=mapping['delimiter']) if mapping['is_dict_mapping'] else csv.reader( + csv_file, delimiter=mapping['delimiter']) + + + if not mapping['is_dict_mapping']: + next(reader) + + rows = list(reader) # Convert to list to easily access the last row + if not rows: # Ensure there's at least one row + raise ValueError("CSV file is empty after processing.") + + total_payout = 0.0 + total_payout_debet = 0.0 + total_payout_credit = 0.0 + total_payout_abs = 0.0 + first = True + created_on = None + for row in rows[:-1]: + + if first: + first = False + reference = extract_order_number(row[mapping['reference']]) + transaction_id = extract_numbers(row[mapping['transaction_id']]) if mapping['use_transaction_id'] else reference + direction = row[mapping['direction']].lower() if mapping['direction'] is not None else None + source_name = row[mapping['source_name']].replace("Nazwa nadawcy: ", "")[:20].ljust(20) if mapping['use_source_name'] else "" + payer_account = extract_numbers(row[mapping['payer_account']].replace("Rachunek nadawcy: ", "").replace(" ", ""))[:16].ljust(16) if mapping['payer_number_exists'] else 0 + if payer_account != 0: + if payer_account.strip() == "": + payer_account = 0 + created_on = parse_date(row[mapping['created_on']]) + + # Convert the value using Decimal instead of float + source_str = row[mapping['source_amount']].replace(',', '.') + source_amount = Decimal(source_str) + + # Quantize to 2 decimal places (rounding if necessary) + source_amount = source_amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) + + # Multiply by 100 to get cents (if that's what you need) and convert to integer + source_amount_cents = int(source_amount * 100) + + # Determine transaction type + if(direction is None): + if source_amount_cents > 0: + transaction_code = TransactionCode.CREDIT + else: + transaction_code = TransactionCode.DEBET + else: + if direction == "out": + transaction_code = TransactionCode.DEBET + else: + transaction_code = TransactionCode.CREDIT + + # Convert currency + currency_code = "100" + str(transaction_code.value) + + # Create GPC Data object + gpc_data = Data( + account=account_number, + payer_account=payer_account, + no=transaction_id, + balance=source_amount_cents, + code=transaction_code, + variable=int(reference) if reference.isdigit() else 0, + constant_symbol=0, + bank_code=0, + specific_symbol=0, + client_name="CG ABCD-EFGH-IJKL" if transaction_code == TransactionCode.CREDIT else "CG refundace", + currency=currency_code, + date=created_on + ) + + gpc_lines.append(gpc_data.to_string()) + total_payout += source_amount_cents + total_payout_abs += abs(source_amount_cents) + total_payout_debet += abs(source_amount_cents) if transaction_code == TransactionCode.DEBET else 0 + total_payout_credit += abs(source_amount_cents) if transaction_code == TransactionCode.CREDIT else 0 + # break + + # vyuctovani row + payout_data = Data( + account=account_number, + payer_account=account_number, + no=int(total_payout), + balance=total_payout, + code=TransactionCode.DEBET, + variable=int(total_payout), + # variable=corresponding_transaction['Zpráva pro příjemce'].split(',')[-1].strip(), + constant_symbol=0, + bank_code=0, + specific_symbol=0, + client_name="CG vyúčtování", + currency="1001", + date=created_on + ) + + total_payout_credit += abs(total_payout) + total_payout_abs += abs(total_payout) + + gpc_lines.append(payout_data.to_string()) + + # Create and add the header + header = Header( + account=account_number, + account_name=currency.ljust(20), + old_date=datetime.strptime("01-03-20", "%d-%m-%y"), + old_balance=0, + old_sign='+', + new_balance=0, + new_sign='+', + turnover_debet=total_payout, + turnover_debet_sign='+', + turnover_credit=total_payout, + turnover_credit_sign='+', + transaction_list_no=3, + date=datetime.now() + ) + gpc_lines.insert(0, header.to_string()) + + + file_content = "".join(gpc_lines) + print(f"GPC file content successfully created") + return file_content.encode("windows-1250") + + +mapping_payu = { + 'transaction_id': 'ID transakce', + 'reference': 'ID objednávky', + 'direction': None, + 'source_name': 'Jméno a příjmení', + 'source_amount': 'Částka', + 'source_currency': 'Měna', + 'payer_account': None, + 'created_on': 'Datum', + 'fees': 'Provize', + 'delimiter': ";", + 'is_dict_mapping': True, + 'use_transaction_id': True, + 'payer_number_exists': False, + 'use_source_name': False, + 'forced_encoding': "windows-1250" +} + + +# Example usage: +# convert_csv_to_gpc("../Specification clearing 2024-05-10.csv", "avizo_przelewy24_test.gpc", account_number=3498710000999117, currency="EUR", mapping=mapping_przelewy24_avizo) +# convert_csv_to_gpc("pko_input.csv", "pko_output.gpc", account_number=95102013900000630206821286, currency="PLN", mapping=mapping_pko) +# convert_csv_to_gpc("sparkasse_input.csv", "sparkasse_output.gpc", account_number=95850503000221267034, currency="EUR", mapping=mapping_sparkasse) diff --git a/aviza/paynl_auto.py b/aviza/paynl_auto.py index 5837194..b0773b0 100644 --- a/aviza/paynl_auto.py +++ b/aviza/paynl_auto.py @@ -1,10 +1,15 @@ import csv import pandas as pd import chardet +import os +import io +import zipfile +import shutil from datetime import datetime from gpc import Data, Header, TransactionCode, CURRENCIES_GPC from utils import extract_order_number, extract_numbers, parse_date from decimal import Decimal, ROUND_HALF_UP +from aviza.helpers import write_output_gpc_to_zip def load_bank_transactions(csv_file): """ @@ -35,10 +40,71 @@ def search_bank_transaction(search_string): return matching_row.iloc[0].to_dict() if not matching_row.empty else None -def convert_csv_to_gpc_paynl_auto(csv_file_path, bank_statement_file_path, gpc_file_path, account_number, currency, mapping): - gpc_lines = [] +def extract_and_process_zip_paynl_auto(zip_file_path, bank_statement_file_path, output_file, account_number, currency, mapping): global transactions_df transactions_df = load_bank_transactions(bank_statement_file_path) + all_transformed_data = [] + + # Create a temporary folder for extraction + base_dir = os.path.dirname(zip_file_path) + extract_folder = os.path.join(base_dir, "extracted_temp") + os.makedirs(extract_folder, exist_ok=True) + + # Extract the provided outer zip file + with zipfile.ZipFile(zip_file_path, 'r') as zip_ref: + zip_ref.extractall(extract_folder) + + # Check if a top-level "Transactions" folder exists + transactions_folder = os.path.join(extract_folder, "Transactions") + if os.path.exists(transactions_folder): + # Look for a CSV file inside the Transactions folder + csv_filename = next( + (f for f in os.listdir(transactions_folder) + if f.startswith("Specification clearing") and f.endswith(".csv")), + None + ) + if csv_filename: + csv_path = os.path.join(transactions_folder, csv_filename) + transformed_data = convert_csv_to_gpc_paynl_auto(csv_path, account_number, currency, mapping) + all_transformed_data.append(transformed_data) + else: + # Otherwise, look for inner zip files named "Specification clearing*.zip" + for root, dirs, files in os.walk(extract_folder): + for file in files: + if file.startswith("Specification clearing") and file.endswith(".zip"): + inner_zip_path = os.path.join(root, file) + # Create a temporary folder for the inner zip extraction + inner_extract_folder = os.path.join(root, "inner_extracted") + os.makedirs(inner_extract_folder, exist_ok=True) + with zipfile.ZipFile(inner_zip_path, 'r') as inner_zip: + inner_zip.extractall(inner_extract_folder) + + # Expect the inner zip to contain a "Transactions" folder with the CSV + inner_transactions_folder = os.path.join(inner_extract_folder, "Transactions") + if os.path.exists(inner_transactions_folder): + inner_csv_filename = next( + (f for f in os.listdir(inner_transactions_folder) + if f.startswith("Specification clearing") and f.endswith(".csv")), + None + ) + if inner_csv_filename: + inner_csv_path = os.path.join(inner_transactions_folder, inner_csv_filename) + transformed_data = convert_csv_to_gpc_paynl_auto(inner_csv_path, account_number, currency, mapping) + all_transformed_data.append(transformed_data) + # Clean up inner extraction folder + shutil.rmtree(inner_extract_folder) + + # Clean up the outer extraction folder + shutil.rmtree(extract_folder) + print(f"Processed and cleaned up: {zip_file_path}") + + # Return a ZIP archive (in memory) containing all the output CSV files. + return write_output_gpc_to_zip(output_file, all_transformed_data) + + +def convert_csv_to_gpc_paynl_auto(csv_file_path, account_number, currency, mapping): + gpc_lines = [] + global transactions_df if mapping['forced_encoding'] is not None: @@ -151,10 +217,10 @@ def convert_csv_to_gpc_paynl_auto(csv_file_path, bank_statement_file_path, gpc_f payout_data = Data( account=account_number, payer_account=2801379531, - no=666111222, + no=int(total_payout), balance=total_payout, code=TransactionCode.DEBET, - variable=666111222, + variable=int(total_payout), # variable=corresponding_transaction['Zpráva pro příjemce'].split(',')[-1].strip(), constant_symbol=0, bank_code=0, @@ -187,13 +253,9 @@ def convert_csv_to_gpc_paynl_auto(csv_file_path, bank_statement_file_path, gpc_f ) gpc_lines.insert(0, header.to_string()) - # with open(gpc_file_path, mode='w', encoding='utf-8') as gpc_file: - # gpc_file.writelines(gpc_lines) - # - # print(f"GPC file successfully created: {gpc_file_path}") file_content = "".join(gpc_lines) - print(f"GPC file content successfully created for: {gpc_file_path}") + print(f"GPC file content successfully created") return file_content.encode("windows-1250") # Example mappings @@ -216,6 +278,25 @@ mapping_paynl_avizo = { 'forced_encoding': None } +mapping_przelewy_avizo = { + 'transaction_id': 'Transaction id', + 'reference': 'Title', + 'direction': None, + 'source_name': 'Payment method name', + 'source_amount': 'Amount', + 'source_currency': '', + 'payer_account': 'Session id', + 'created_on': 'Creation date', + 'CLEARING_ID': 'CLEARING_ID', + 'fees': 'Commission', + 'delimiter': ",", + 'is_dict_mapping': True, + 'use_transaction_id': True, + 'payer_number_exists': False, + 'use_source_name': False, + 'forced_encoding': None +} + # Example usage: # convert_csv_to_gpc("../Specification clearing 2024-05-10.csv", "avizo_paynl_test.gpc", account_number=3498710000999117, currency="EUR", mapping=mapping_paynl_avizo) diff --git a/aviza/przelewy24_auto.py b/aviza/przelewy24_auto.py new file mode 100644 index 0000000..ffee614 --- /dev/null +++ b/aviza/przelewy24_auto.py @@ -0,0 +1,264 @@ +import csv +import pandas as pd +import chardet +import os +import io +import zipfile +import shutil +from datetime import datetime +from gpc import Data, Header, TransactionCode, CURRENCIES_GPC +from utils import extract_order_number, extract_numbers, parse_date +from decimal import Decimal, ROUND_HALF_UP +from aviza.helpers import write_output_gpc_to_zip + +def load_bank_transactions(csv_file): + """ + Loads the bank transactions CSV file into a DataFrame and returns it. + + :param csv_file: Path to the bank transactions CSV file. + :return: A pandas DataFrame containing the transactions. + """ + df = pd.read_csv(csv_file, delimiter=';', dtype=str) + + # Ensure the required column exists + if 'Zpráva pro příjemce' not in df.columns: + raise ValueError("The CSV file does not contain the required column 'Zpráva pro příjemce'.") + + return df + +def search_bank_transaction(search_string): + """ + Searches for a given string in the 'Zpráva pro příjemce' column of the loaded DataFrame. + + :param df: Pandas DataFrame containing bank transactions. + :param search_string: String to search for in the 'Zpráva pro příjemce' column. + :return: The first matching row as a dictionary or None if not found. + """ + global transactions_df + matching_row = transactions_df[transactions_df['Zpráva pro příjemce'].str.contains(search_string, na=False, case=False)] + + return matching_row.iloc[0].to_dict() if not matching_row.empty else None + + +def extract_and_process_zip_przelewy24_auto(zip_file_path, bank_statement_file_path, output_file, account_number, currency, mapping): + global transactions_df + transactions_df = load_bank_transactions(bank_statement_file_path) + all_transformed_data = [] + + # Create a temporary folder for extraction + base_dir = os.path.dirname(zip_file_path) + extract_folder = os.path.join(base_dir, "extracted_temp") + os.makedirs(extract_folder, exist_ok=True) + + # Extract the provided outer zip file + with zipfile.ZipFile(zip_file_path, 'r') as zip_ref: + zip_ref.extractall(extract_folder) + + + for files in os.walk(extract_folder): + for file in files: + try: + if file.endswith(".csv"): + + transformed_data = convert_csv_to_gpc_przelewy24_auto(file, account_number, currency, mapping) + all_transformed_data.append(transformed_data) + except: + continue + + + # Clean up the outer extraction folder + shutil.rmtree(extract_folder) + print(f"Processed and cleaned up: {zip_file_path}") + + # Return a ZIP archive (in memory) containing all the output CSV files. + return write_output_gpc_to_zip(output_file, all_transformed_data) + + +def convert_csv_to_gpc_przelewy24_auto(csv_file_path, account_number, currency, mapping): + gpc_lines = [] + global transactions_df + + + if mapping['forced_encoding'] is not None: + detected_encoding = mapping['forced_encoding'] + print(f"Forced encoding: {detected_encoding}") + else: + with open(csv_file_path, 'rb') as f: + rawdata = f.read(1024) # Read a small part of the file + result = chardet.detect(rawdata) + detected_encoding = result['encoding'] + print(f"Detected encoding: {detected_encoding}") + + + with open(csv_file_path, mode='r', encoding=detected_encoding) as csv_file: + # Skip the first 3 rows + for _ in range(3): + next(csv_file) + + reader = csv.DictReader(csv_file, delimiter=mapping['delimiter']) if mapping['is_dict_mapping'] else csv.reader( + csv_file, delimiter=mapping['delimiter']) + + + if not mapping['is_dict_mapping']: + next(reader) + + total_payout = 0.0 + total_payout_debet = 0.0 + total_payout_credit = 0.0 + total_payout_abs = 0.0 + first = True + clearing_id = "" + created_on = None + for row in reader: + + if first: + # clearing_id = row[mapping['CLEARING_ID']] + first = False + reference = extract_order_number(row[mapping['reference']]) + transaction_id = extract_numbers(row[mapping['transaction_id']]) if mapping['use_transaction_id'] else reference + direction = row[mapping['direction']].lower() if mapping['direction'] is not None else None + source_name = row[mapping['source_name']].replace("Nazwa nadawcy: ", "")[:20].ljust(20) if mapping['use_source_name'] else "" + payer_account = extract_numbers(row[mapping['payer_account']].replace("Rachunek nadawcy: ", "").replace(" ", ""))[:16].ljust(16) if mapping['payer_number_exists'] else 0 + if payer_account != 0: + if payer_account.strip() == "": + payer_account = 0 + created_on = parse_date(row[mapping['created_on']]) + + # Convert the value using Decimal instead of float + source_str = row[mapping['source_amount']].replace(',', '.') + source_amount = Decimal(source_str) + + # Quantize to 2 decimal places (rounding if necessary) + source_amount = source_amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) + + # Multiply by 100 to get cents (if that's what you need) and convert to integer + source_amount_cents = int(source_amount * 100) + + # Determine transaction type + if(direction is None): + if source_amount_cents > 0: + transaction_code = TransactionCode.CREDIT + else: + transaction_code = TransactionCode.DEBET + else: + if direction == "out": + transaction_code = TransactionCode.DEBET + else: + transaction_code = TransactionCode.CREDIT + + # Convert currency + currency_code = "100" + str(transaction_code.value) + + # Create GPC Data object + gpc_data = Data( + account=account_number, + payer_account=payer_account, + no=transaction_id, + balance=source_amount_cents, + code=transaction_code, + variable=int(reference) if reference.isdigit() else 0, + constant_symbol=0, + bank_code=0, + specific_symbol=0, + client_name="CG ABCD-EFGH-IJKL" if transaction_code == TransactionCode.CREDIT else "CG refundace", + currency=currency_code, + date=created_on + ) + + gpc_lines.append(gpc_data.to_string()) + total_payout += source_amount_cents + total_payout_abs += abs(source_amount_cents) + total_payout_debet += abs(source_amount_cents) if transaction_code == TransactionCode.DEBET else 0 + total_payout_credit += abs(source_amount_cents) if transaction_code == TransactionCode.CREDIT else 0 + # break + + # add fees + # fees_data = Data( + # account=account_number, + # payer_account=0, + # no=0, + # balance=total_fees, + # code=transaction_code, + # variable=0, + # constant_symbol=0, + # bank_code=0, + # specific_symbol=0, + # client_name="", + # currency=CURRENCIES_GPC.get("CZK", "0000"), + # date=created_on + # ) + # + # gpc_lines.append(gpc_data.to_string()) + + # corresponding_transaction = search_bank_transaction(clearing_id) + + # vyuctovani row + payout_data = Data( + account=account_number, + payer_account=95102013900000630206821286, + no=int(total_payout), + balance=total_payout, + code=TransactionCode.DEBET, + variable=int(total_payout), + # variable=corresponding_transaction['Zpráva pro příjemce'].split(',')[-1].strip(), + constant_symbol=0, + bank_code=0, + specific_symbol=0, + client_name="CG vyúčtování", + currency="1001", + date=created_on + ) + + total_payout_credit += abs(total_payout) + total_payout_abs += abs(total_payout) + + gpc_lines.append(payout_data.to_string()) + + # Create and add the header + header = Header( + account=account_number, + account_name=currency.ljust(20), + old_date=datetime.strptime("01-03-20", "%d-%m-%y"), + old_balance=0, + old_sign='+', + new_balance=0, + new_sign='+', + turnover_debet=total_payout, + turnover_debet_sign='+', + turnover_credit=total_payout, + turnover_credit_sign='+', + transaction_list_no=3, + date=datetime.now() + ) + gpc_lines.insert(0, header.to_string()) + + + file_content = "".join(gpc_lines) + print(f"GPC file content successfully created") + return file_content.encode("windows-1250") + + +mapping_przelewy_avizo = { + 'transaction_id': 'Transaction id', + 'reference': 'Title', + 'direction': None, + 'source_name': 'Payment method name', + 'source_amount': 'Amount', + 'source_currency': '', + 'payer_account': 'Session id', + 'created_on': 'Creation date', + 'CLEARING_ID': 'CLEARING_ID', + 'fees': 'Commission', + 'delimiter': ",", + 'is_dict_mapping': True, + 'use_transaction_id': True, + 'payer_number_exists': False, + 'use_source_name': False, + 'forced_encoding': "UTF-8" +} + + +# Example usage: +# convert_csv_to_gpc("../Specification clearing 2024-05-10.csv", "avizo_przelewy24_test.gpc", account_number=3498710000999117, currency="EUR", mapping=mapping_przelewy24_avizo) +# convert_csv_to_gpc("pko_input.csv", "pko_output.gpc", account_number=95102013900000630206821286, currency="PLN", mapping=mapping_pko) +# convert_csv_to_gpc("sparkasse_input.csv", "sparkasse_output.gpc", account_number=95850503000221267034, currency="EUR", mapping=mapping_sparkasse) diff --git a/aviza/przelewy24_transactions.py b/aviza/przelewy24_transactions.py new file mode 100644 index 0000000..7414db8 --- /dev/null +++ b/aviza/przelewy24_transactions.py @@ -0,0 +1,248 @@ +import csv +import pandas as pd +import chardet +import os +import io +import zipfile +import shutil +from datetime import datetime +from gpc import Data, Header, TransactionCode, CURRENCIES_GPC +from utils import extract_order_number, extract_numbers, parse_date +from decimal import Decimal, ROUND_HALF_UP +from aviza.helpers import write_output_gpc_to_zip +from collections import defaultdict + + +def load_bank_transactions(csv_file): + """ + Loads the bank transactions CSV file into a DataFrame and returns it. + + :param csv_file: Path to the bank transactions CSV file. + :return: A pandas DataFrame containing the transactions. + """ + df = pd.read_csv(csv_file, delimiter=';', dtype=str) + + # Ensure the required column exists + if 'Zpráva pro příjemce' not in df.columns: + raise ValueError("The CSV file does not contain the required column 'Zpráva pro příjemce'.") + + return df + +def search_bank_transaction(search_string): + """ + Searches for a given string in the 'Zpráva pro příjemce' column of the loaded DataFrame. + + :param df: Pandas DataFrame containing bank transactions. + :param search_string: String to search for in the 'Zpráva pro příjemce' column. + :return: The first matching row as a dictionary or None if not found. + """ + global transactions_df + matching_row = transactions_df[transactions_df['Zpráva pro příjemce'].str.contains(search_string, na=False, case=False)] + + return matching_row.iloc[0].to_dict() if not matching_row.empty else None + + +def extract_and_process_csv_przelewy24_transactions(csv_file_path, bank_statement_file_path, output_file, account_number, currency, mapping): + global transactions_df + transactions_df = load_bank_transactions(bank_statement_file_path) + all_transformed_data = [] + grouped_data = defaultdict(list) + + if mapping['forced_encoding'] is not None: + detected_encoding = mapping['forced_encoding'] + print(f"Forced encoding: {detected_encoding}") + else: + with open(csv_file_path, 'rb') as f: + rawdata = f.read(1024) # Read a small part of the file + result = chardet.detect(rawdata) + detected_encoding = result['encoding'] + print(f"Detected encoding: {detected_encoding}") + + + try: + with open(csv_file_path, mode="r", encoding=detected_encoding) as csv_file: + reader = csv.DictReader(csv_file, delimiter=mapping['delimiter']) + + for row in reader: + payout_number = row["Numer wypłaty"] # Get payout number from column "N" + grouped_data[payout_number].append(row) # Store row under its payout number + + except Exception as e: + print(f"Error processing {csv_file_path}: {e}") + + for payout_number, rows in grouped_data.items(): + try: + transformed_data = convert_csv_to_gpc_przelewy24_transactions(rows, payout_number, account_number, currency, mapping) + all_transformed_data.append(transformed_data) + + except Exception as e: + print(f"Error processing payout number {payout_number}: {e}") + + # Return a ZIP archive (in memory) containing all the output CSV files. + return write_output_gpc_to_zip(output_file, all_transformed_data) + + +def convert_csv_to_gpc_przelewy24_transactions(rows, payout_id, account_number, currency, mapping): + gpc_lines = [] + global transactions_df + + + total_payout = 0.0 + total_payout_debet = 0.0 + total_payout_credit = 0.0 + total_payout_abs = 0.0 + first = True + clearing_id = "" + created_on = None + + + for row in rows: + + if first: + # clearing_id = row[mapping['CLEARING_ID']] + first = False + reference = extract_order_number(row[mapping['reference']]) + transaction_id = extract_numbers(row[mapping['transaction_id']]) if mapping['use_transaction_id'] else reference + direction = row[mapping['direction']].lower() if mapping['direction'] is not None else None + source_name = row[mapping['source_name']].replace("Nazwa nadawcy: ", "")[:20].ljust(20) if mapping['use_source_name'] else "" + payer_account = extract_numbers(row[mapping['payer_account']].replace("Rachunek nadawcy: ", "").replace(" ", ""))[:16].ljust(16) if mapping['payer_number_exists'] else 0 + if payer_account != 0: + if payer_account.strip() == "": + payer_account = 0 + created_on = parse_date(row[mapping['created_on']]) + + # Convert the value using Decimal instead of float + source_amount_cents = int(row[mapping['source_amount']]) + refund_amount_cents = int(row[mapping['refund_amount']]) + + + # Determine transaction type + if(direction is None): + if source_amount_cents > 0: + transaction_code = TransactionCode.CREDIT + else: + transaction_code = TransactionCode.DEBET + else: + if direction == "out": + transaction_code = TransactionCode.DEBET + else: + transaction_code = TransactionCode.CREDIT + + # Convert currency + currency_code = "100" + str(TransactionCode.CREDIT) + + # Create GPC Data object + gpc_data = Data( + account=account_number, + payer_account=payer_account, + no=transaction_id, + balance=source_amount_cents, + code=TransactionCode.CREDIT, + variable=int(reference) if reference.isdigit() else 0, + constant_symbol=0, + bank_code=0, + specific_symbol=0, + client_name="CG ABCD-EFGH-IJKL", + currency="1002", + date=created_on + ) + + gpc_lines.append(gpc_data.to_string()) + total_payout += source_amount_cents + total_payout_abs += abs(source_amount_cents) + total_payout_debet += abs(source_amount_cents) if transaction_code == TransactionCode.DEBET else 0 + total_payout_credit += abs(source_amount_cents) if transaction_code == TransactionCode.CREDIT else 0 + + if refund_amount_cents != 0: + gpc_data_refund = Data( + account=account_number, + payer_account=payer_account, + no=transaction_id, + balance=refund_amount_cents, + code=TransactionCode.DEBET, + variable=int(reference) if reference.isdigit() else 0, + constant_symbol=0, + bank_code=0, + specific_symbol=0, + client_name="CG refundace", + currency="1001", + date=created_on + ) + + gpc_lines.append(gpc_data_refund.to_string()) + total_payout += refund_amount_cents + total_payout_abs += abs(refund_amount_cents) + total_payout_debet += abs(refund_amount_cents) if transaction_code == TransactionCode.DEBET else 0 + + + # use payout_id instead as variable + + # vyuctovani row + payout_data = Data( + account=account_number, + payer_account=95102013900000630206821286, + no=int(total_payout), + balance=total_payout, + code=TransactionCode.DEBET, + variable=int(total_payout), + # variable=corresponding_transaction['Zpráva pro příjemce'].split(',')[-1].strip(), + constant_symbol=0, + bank_code=0, + specific_symbol=0, + client_name="CG vyúčtování", + currency="1001", + date=created_on + ) + + total_payout_credit += abs(total_payout) + total_payout_abs += abs(total_payout) + + gpc_lines.append(payout_data.to_string()) + + # Create and add the header + header = Header( + account=account_number, + account_name=currency.ljust(20), + old_date=datetime.strptime("01-03-20", "%d-%m-%y"), + old_balance=0, + old_sign='+', + new_balance=0, + new_sign='+', + turnover_debet=total_payout, + turnover_debet_sign='+', + turnover_credit=total_payout, + turnover_credit_sign='+', + transaction_list_no=3, + date=datetime.now() + ) + gpc_lines.insert(0, header.to_string()) + + + file_content = "".join(gpc_lines) + print(f"GPC file content successfully created") + return file_content.encode("windows-1250") + + +mapping_przelewy_transactions = { + 'transaction_id': 'Numeryczne ID transakcji', + 'reference': 'Opis', + 'direction': None, + 'source_name': 'Metoda płatności', + 'source_amount': 'Kwota', + 'refund_amount': 'Zwroty', + 'source_currency': '', + 'payer_account': 'Numeryczne ID transakcji', + 'created_on': 'Przyjęcie', + 'delimiter': ",", + 'is_dict_mapping': True, + 'use_transaction_id': True, + 'payer_number_exists': False, + 'use_source_name': False, + 'forced_encoding': "UTF-8" +} + + +# Example usage: +# convert_csv_to_gpc("../Specification clearing 2024-05-10.csv", "avizo_przelewy24_test.gpc", account_number=3498710000999117, currency="EUR", mapping=mapping_przelewy24_avizo) +# convert_csv_to_gpc("pko_input.csv", "pko_output.gpc", account_number=95102013900000630206821286, currency="PLN", mapping=mapping_pko) +# convert_csv_to_gpc("sparkasse_input.csv", "sparkasse_output.gpc", account_number=95850503000221267034, currency="EUR", mapping=mapping_sparkasse) diff --git a/templates/index.html b/templates/index.html index 9a0198f..bc852d8 100644 --- a/templates/index.html +++ b/templates/index.html @@ -82,18 +82,30 @@
Pouzijte .zip se vsemi zip soubory z exportu
.gpc aviza pro EUR ucet
-Pouzijte .zip se vsemi .csv soubory z reportu
.gpc aviza pro Przelewy24
Pouzijte jeden .csv soubor z transakci
+.gpc aviza pro Przelewy24
+Pouzijte .zip se vsemi zip soubory z exportu (z disku zazipovat slozku Aviza GLS)
.gpc aviza pro HUF dobirky
Pouzijte .zip se vsemi avizy ze zasilkovny
+.gpc aviza pro dobirky z packety
+