from ILE_MainWindow import Ui_MainWindow import sys import os import pandas as pd import json from PyQt5 import QtWidgets from datetime import datetime as dt import ILExtract as ilx from logging import debug, DEBUG, basicConfig with open("settings.json") as s: settings = json.loads(s.read()) if settings["debug"]: basicConfig(filename='debug.log', encoding='utf-8', level=DEBUG) debug("\n\n\n########################### VERSION = 3.10 ###########################\n\n\n") debug("Running main.py...") class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): """ This is the main window that the users see. It's where they select the report file and report type. """ def __init__(self, *args, obj=None, **kwargs): debug("MainWindow class init..") super(MainWindow, self).__init__(*args, **kwargs) self.setupUi(self) # Allows the window to accept drag and drop files # See dragEnterEvent an dropEvent self.setAcceptDrops(True) # Will hold the inputFile that is processed to create the ouput self.inputFile = "" # The location that the outputfile will be saved at self.outputFile = "" # Load the settings.json with open("settings.json") as s: self.settings = json.loads(s.read()) # Set the current report type to ACH as default self.curReportType = "ach" # Set the extract function to match self.extract_function = ilx.ach # Run the report_type change to check for saved input file paths self.report_type_change() # # Actions # # Opens the file selection window and sets the input file accordingly self.inputFileButton.clicked.connect(self.getfile) # Opens file location dialog and sets the ouput accordingly self.outputFileButton.clicked.connect(self.setOutput) # Initates the processing of the selected file self.processReportButton.clicked.connect(self.process_selection) # Copies the last processed dataframe into the users clipboard self.copyButton.clicked.connect(self.to_clipboard) # Adjusts the extract function and file paths based on new report type in combo box self.reportTypeCB.currentTextChanged.connect(self.report_type_change) # Opens the folder specified in output file self.openFolderButton.clicked.connect(lambda: self.openWithDefaultApp(self.outputFile.removesuffix(self.outputFile.split('/')[-1]))) # Opens the last processed excel document self.openExcelButton.clicked.connect(lambda: self.openWithDefaultApp(self.outputFile)) # Sets the default text of the preview box self.inputFilePreview.setText("Drag & Drop Input file here!") def set_input(self, ifile): """ Triggerd by getFile() associated with the inputFile button Reads the input file, sets the preview box to that excel doc and sets the line edit and inputFile Also sets the output file to a default structure. By default the output is put in the same folder as the input file ! If the report type is minv_C we will save as .txt not .xlsx Disables buttons since it is assumed this file has not yet been processed - open excel button - open folder button - copy button """ debug(f"Executing set_input | input file: {ifile}") with open(ifile, errors="replace") as inF: txt = inF.read() self.inputFilePreview.setText(txt) self.inputFileLE.setText(ifile) self.inputFile = ifile # This gets the actual file name inFileEnd = ifile.split('/')[-1] # Takes just the root of the input file outputRoot = self.inputFile.removesuffix(inFileEnd) # Automatically sets output to be in the same file as input, with a naming scheme # The report type selected in the combo box will dictate the naming if self.reportTypeCB.currentText() == "Minv_C": debug(f"MINV_C file selected ({self.inputFile})! Using .txt for output.") self.outputFile = f"{outputRoot}{self.reportTypeCB.currentText()}_{dt.now().strftime('%Y%m%d_%H%M')}.txt" else: self.outputFile = f"{outputRoot}{self.reportTypeCB.currentText().replace('/','')}_{dt.now().strftime('%Y%m%d_%H%M')}.xlsx" self.outputFileLE.setText(self.outputFile) self.openExcelButton.setEnabled(False) self.copyButton.setEnabled(False) self.openFolderButton.setEnabled(True) # Enables the process button self.check_ready_to_process() def getfile(self): """ Launches the file selection dialog then inits the set_input function """ debug("Launching getfile.") inFile = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file',directory=self.settings["defaultLocations"][self.curReportType]) # If the user does not select a file we just exit and do not change anything if inFile[0] == '': debug(f"User did not select an input file! {inFile}") return 1 else: self.set_input(inFile[0]) def dragEnterEvent(self, event): """ Allow users to drag file into the application """ debug(f"Drag and enter event triggered: {event}.") if event.mimeData().hasUrls(): event.accept() else: event.ignore() def dropEvent(self, event): """ Allow users to drag file into the application """ file = [u.toLocalFile() for u in event.mimeData().urls()][0] debug(f"Drop event: {event} | {file}\n Running set_input") self.set_input(file) def setOutput(self): """ Launches dialog to select output location and naming. """ # This allows the user to change the automatic naming and location outFile = QtWidgets.QFileDialog.getSaveFileName(self, "Output file name") if outFile[0] == '': return '' if self.reportTypeCB.currentText() == "Minv_C": self.outputFileLE.setText(f"{outFile[0]}__{dt.now().strftime('%Y%m%d_%H_%M')}.txt") else: self.outputFileLE.setText(f"{outFile[0]}__{dt.now().strftime('%Y%m%d_%H_%M')}.xlsx") debug(f"setOutput: {outFile}") self.outputFile = f"{outFile[0]}__{dt.now().strftime('%Y%m%d_%H_%M')}.xlsx" debug(f"setOutput | output file: {self.outputFile}") self.check_ready_to_process() def check_ready_to_process(self): """ Makes sure there is an input and output selected before allowing processing Disables the button of outputFile is blank """ # Makes sure there is an input and output selected before allowing processing rtp = True if ((self.inputFile != "") & (self.outputFile != "")) else False if rtp : self.processReportButton.setEnabled(True) debug("check_ready_to_process: Process button enabled") if self.outputFile == "": self.openFolderButton.setEnabled(False) debug("check_ready_to_process: Process button disabled") def process_selection(self): """ Kicks off the actual processing of the selected files """ debug(f"""Beginning file processing: \nInput File: {self.inputFile} | Extract Function: {self.extract_function} | Report Type: {self.curReportType}""") self.inputFilePreview.setText("Processing file...") # Use the ILReport class to process the file\ # need to set the input file, extract function to use, and output location dataframe = ilx.ILReport( location= self.inputFile, extraction_function=self.extract_function, output_location=self.outputFile, ).process() debug(f"Process Selction | dataframe:\n{dataframe}") # The text preview box can have trouble loading the larger dataframes so # they are trimmed to 500 so that the users can see if anything got messed up if dataframe.empty: self.inputFilePreview.setText("Failed to create dataframe!") return None smallDF = dataframe.iloc[0:500,:] self.inputFilePreview.setText(smallDF.to_html(index=False)) # Enable the excel button so users can open the file self.openExcelButton.setEnabled(True) # Enable the dopy button to copy this dataframe self.copyButton.setEnabled(True) # Checks if this input location is saved in settings.json. # If there is no location for this report type it will be saved in the deafult self.checked_for_saved() # Allows user to open the output folder self.openFolderButton.setEnabled(True) def to_clipboard(self): """ Copies the file in output to the users clipboard """ df = pd.read_excel(self.outputFile) df.to_clipboard(excel=True) def openFolder(self): outputRoot = self.outputFile.removesuffix(self.outputFile.split('/')[-1]) os.startfile(outputRoot) def openWithDefaultApp(self, item): """ Opens the linked item with it's default application (excel) """ debug(f"openWithDefaultApp: {item}") os.startfile(item) def checked_for_saved(self): """ Checks if a report type already has a default location If the default location is blank, the current inputFileLocation will be saved """ if self.settings["defaultLocations"][self.curReportType] == '': self.settings["defaultLocations"][self.curReportType] = ('/').join(self.inputFile.split('/')[:-1]) debug(f"checked_for_saved: saved new deafult location | {self.curReportType} | {self.settings['defaultLocations'][self.curReportType]}") with open('settings.json', 'w') as s: # Save changes to the setting json.dump(self.settings, s) def report_type_change(self): debug(f"Changing report type | Was: {self.curReportType} -> {self.reportTypeCB.currentText()}") # Adjust the report type according to the combo box # This will be used in settings the the extract function and determining file locations # Disable the process report button since the input and output fields are now blank self.processReportButton.setEnabled(False) self.openExcelButton.setEnabled(False) self.copyButton.setEnabled(False) self.openFolderButton.setEnabled(False) if self.reportTypeCB.currentText() == "ACH": self.curReportType = "ach" self.extract_function = ilx.ach elif self.reportTypeCB.currentText() == "Disposition": self.curReportType = "disp" self.extract_function = ilx.disposition elif self.reportTypeCB.currentText() == "Gain Loss": self.curReportType = "gl" self.extract_function = ilx.gainloss elif self.reportTypeCB.currentText() == "Lock Box": self.curReportType = "lb" self.extract_function = ilx.lockbox elif self.reportTypeCB.currentText() == "Minv_C": self.curReportType = "minv" self.extract_function = ilx.minv elif self.reportTypeCB.currentText() == "Net Inv. Loans": self.curReportType = "niv" self.extract_function = ilx.net_invest_trial_balance elif self.reportTypeCB.currentText() == "NI Renewal": self.curReportType = "ren" self.extract_function = ilx.renewal_net_invest_trial_balance elif self.reportTypeCB.currentText() == "NIV After": self.curReportType = "niv" self.extract_function = ilx.net_invest_trial_balance elif self.reportTypeCB.currentText() == "PBP / Epay": self.curReportType = "pymt" self.extract_function = ilx.payment_transactions elif self.reportTypeCB.currentText() == "Unapplied": self.curReportType = "uap" self.extract_function = ilx.unapplied elif self.reportTypeCB.currentText() == "Past Due": self.curReportType = "pastdue" self.extract_function = ilx.pastdue elif self.reportTypeCB.currentText() == "VMCC": self.curReportType = "pymt" self.extract_function = ilx.payment_transactions elif self.reportTypeCB.currentText() == "Wires": self.curReportType = "pymt" self.extract_function = ilx.payment_transactions elif self.reportTypeCB.currentText() == "Returned Check": self.curReportType = "pymt" self.extract_function = ilx.payment_transactions self.inputFile = "" self.inputFileLE.setText(self.inputFile) # Automatically sets output to be in the same file as input, with a naming scheme # The report type selected in the combo box will dictate the naming if self.curReportType == "minv": self.outputFile = f"{self.reportTypeCB.currentText()}_{dt.now().strftime('%Y%m%d_%H%M')}.txt" else: self.outputFile = f"{self.reportTypeCB.currentText().replace('/','')}_{dt.now().strftime('%Y%m%d_%H%M')}.xlsx" self.outputFileLE.setText(self.outputFile) debug(f"report_type_change | inputFile: {self.inputFile}") debug(f"report_type_change | outputFile: {self.outputFile}") self.check_ready_to_process() # Defines the app app = QtWidgets.QApplication(sys.argv) # Sets the style app.setStyle("Fusion") # Builds the main window window = MainWindow() window.setWindowTitle("IL Extract") window.show() # Starts the app app.exec()