cleaned up main, added comments to ILextract

v3.1
Griffiths Lott 4 years ago
parent 3baea9331e
commit df96574a98
  1. 175
      ILExtract.py
  2. 13
      RenewalTest.py
  3. 87
      ach_special.py
  4. 60
      copy.svg
  5. BIN
      extract.ico
  6. 1
      extract.svg
  7. 2
      folder.svg
  8. 25
      main.py
  9. 268
      mainWindow.ui
  10. 1
      process.svg

@ -5,45 +5,33 @@ import sys, getopt
import re
from pathlib import Path
import time
import numpy as np
# contract numbers are a common feature in many reports to it's
# useful to have the regex for them globally avaiable
contract_number_regex = "\d{3}-\d{7}-\d{3}"
class ILReport:
"""
InfoLease Report class will be used to work with the files.
It makes it easier to add new reports to the workflow and to make it more clear where
the reports are coming from. It also helps with tracking reports that may not be ready yet.
"""
def __init__(self, location, extraction_function = None, output_location = None, output_name = None):
def __init__(self, location, extraction_function, output_location = None):
# The location where the InfoLease report is stored
self.location = location
# The base name of the file, corresponds to the report type
# If output location not specified, save to the input location
if output_location == None:
self.output_location = Path(location).parent.absolute()
else:
self.output_location = output_location
# This is optional but has a default
if output_name == None:
# Get the file name of the input and remove the date
self.output_name = os.path.basename(f"{self.location}")\
.replace(f"{(dt.now() - timedelta(days=+1)).strftime('%Y.%m.%d')}","")
else:
self.output_name = output_name
# The function used to extract the data from the report
self.x_method = extraction_function
# Tracks whether the data was successfully exctracted
self.successful = False
def run(self) -> int:
"""
This method is what actully run the report. I uses the specidied extraction function to create and save an excel document.
SUCESS returns 0
ERROR returns 1
Failure is also noted by self.success == False
"""
def process(self):
try:
# Open the file and read it to a string | errors = 'replace' deals with non UTF-8 characters (no affect on output)
with open(self.location, errors="replace") as ifile:
@ -55,48 +43,16 @@ class ILReport:
try:
# Run the associated method to extract the data and get the dataframe
dataframe = self.x_method(report, self.output_location)
try:
assert(len(dataframe) > 1)
except Exception as e:
print(f"Data Length Error: {self.output_name} is empty:\n{dataframe}")
self.successful = False
return 1
except Exception as e:
print(f"{self.output_name} failed to process:\n{e}")
print(f"Failed to create dataframe: {self.output_name}\n{e}")
self.successful = False
return 1
try:
# Save the dataframe as an excel document
dataframe.to_excel(self.output_location, index = False, engine="openpyxl")
except Exception as e:
self.successful = False
print(f"{self.output_location} failed to save to excel!\n{dataframe}\n{e}")
return 1
self.successful = True
return dataframe
def process(self):
try:
# Open the file and read it to a string | errors = 'replace' deals with non UTF-8 characters (no affect on output)
with open(self.location, errors="replace") as ifile:
report = ifile.read()
except IOError as ioe:
print(f"Failed to open file: {self.location}\n{ioe}")
self.successful = False
return 1
#try:
# Run the associated method to extract the data and get the dataframe
dataframe = self.x_method(report, self.output_location)
try:
assert(len(dataframe) > 1)
except Exception as e:
print(f"Data Length Error: {self.output_name} is empty:\n{dataframe}")
self.successful = False
return 1
#except Exception as e:
# print(f"{self.output_name} failed to process:\n{e}")
# self.successful = False
# return 1
return dataframe
@ -117,9 +73,11 @@ def create_line_divider(breakage_list: list):
ONLY USE THIS FUNCTION THROUGH CREATION USING 'create_line_extractor'
Will automatically convert numbers to floats
"""
# We can't have a slot number higher than the number of slots
assert(slot_num < len(breakage_list)+1)
low_range = 0 if slot_num == 0 else breakage_list[slot_num-1]
high_range = len(line_string) if slot_num == len(breakage_list) else breakage_list[slot_num]
# In order to create a float we need to remove the , from the string
data = line_string[low_range:high_range].strip().replace(",", "")
try: data = float(data)
except: pass
@ -188,21 +146,36 @@ def ach(report: str, save_name: str):
bank_number_regex = "\d{9}"
batch_num_regex = "BATCH \d{4} TOTAL"
for line in enumerate(lines):
# Check for a contract number and a bank number in the line
if (re.search(contract_number_regex, line[1]) != None) & (re.search(bank_number_regex, line[1]) != None):
# Iterates through the columns list and adds the corresponding slot number to the dictonary for the column
# Here the order of the columns (keys in dictonary) matter since they need to be in the same order as
# the slot numbers
[extracted_data_dict[columns[c]].append(data_extractor(c, line[1])) for c in range(0, len(columns)-3)]
# This searches for a statement that looks like a batch number
# This sums the contracts by thier lessor code. A feature requested by cash apps
if re.search(batch_num_regex, line[1]) != None:
# Batch number is always in characters 96 to 101
batches["batch_num"].append(line[1][96:101])
# Payment date will be 2 lines below that between charactes 114 and 125
batches["payment_date"].append(lines[line[0]+2][114:125])
# Lessor is just the first three number sof the contract number
batches["lessor"].append(extracted_data_dict["ContractNumber"][-1][0:3])
# Total is a number given by the report for that batch. ',' is removed so that it can be transformed into a float
batches["total"].append(float(line[1][107:125].strip().replace(",", "")))
batches["count"].append(float(lines[line[0]+6][107:125].strip().replace(",", "")))
# Any time there's a new batch we need to add this data to the dictionary up up to the currrent place
# So we iterate over the number of contracts and add in the newest value for each that don't have one of these values already
[extracted_data_dict["Batch"].append(batches["batch_num"][-1]) for _ in range(0, (len(extracted_data_dict["BankCode"]) - len(extracted_data_dict["Batch"])))]
[extracted_data_dict["Lessor"].append(batches["lessor"][-1]) for _ in range(0, (len(extracted_data_dict["BankCode"]) - len(extracted_data_dict["Lessor"])))]
[extracted_data_dict["PaymentDate"].append(batches["payment_date"][-1]) for _ in range(0, (len(extracted_data_dict["BankCode"]) - len(extracted_data_dict["PaymentDate"])))]
# Now the dictioanry lists should all be equal lengths and we can create a dataframe
dataframe = pd.DataFrame(extracted_data_dict)
# We're creating two sheets: data & summary so we need to open and excel writer
# This also helps with a bug caused by larger dataframes
with pd.ExcelWriter(save_name) as writer:
dataframe.to_excel(writer, index=False, sheet_name="data")
# The batches dictioanry is converted to a dataframe and added as it's own sheet
pd.DataFrame(batches).to_excel(writer, index=False, sheet_name="Summary")
return dataframe
@ -226,6 +199,7 @@ def disposition(report: str, save_name: str):
for line in enumerate(lines):
if re.search(contract_number_regex, data_extractor(0,line[1])):
[extracted_data_dict[columns[c]].append(data_extractor(c,line[1])) for c in range(0, len(columns)-1)]
# Customer name is on a seperate line so we need to grab that seperately
extracted_data_dict["Customer Name"].append(lines[line[0]+1].strip())
dataframe = pd.DataFrame(extracted_data_dict)
dataframe.to_excel(save_name, index=False)
@ -284,8 +258,11 @@ def gainloss(report: str, save_name: str):
# L5: CTD OPER
columns = list(extracted_data_dict.keys())
# These line data are used to tell the data extrator which values to pull for each line of
# relevant data. It parits dictionary keys with thier corresponding data slot in the line
# So that they can be iterated through during data extraction
# relevant data. It pairs dictionary keys with thier corresponding data slot in the line
# so that they can be iterated through during data extraction
#
# It looks confusing but makes more sense if you look at the actual Info Lease reports
# This is one of the messiest reports
line0 = list(zip(columns[0:7],[i for i in range(1,8)]))
line1 = list(zip(columns[7:15],[i for i in range(0,8)]))
line2 = list(zip(columns[15:23], [i for i in range(0,8)]))
@ -294,6 +271,7 @@ def gainloss(report: str, save_name: str):
line5 = list(zip(columns[36:], [i for i in range(1,4)]))
data_extractor = create_line_divider([27,43,58,74,88,105,120])
for line in enumerate(lines):
# The line must contain a contract number and the first data slot should be a float
if (re.search(contract_number_regex, data_extractor(0,line[1])) != None)&\
(type(data_extractor(1,line[1])) == float) :
data_section = lines[line[0]-1:line[0]+5]
@ -345,25 +323,74 @@ def net_invest_trial_balance(report: str, save_name: str):
'UNPAID INT' : [],
'NET INV' : [],
'UNEARNED IDC' : [],
"LESSOR": []
}
lessors = []
columns = list(extracted_data_dict.keys())
line0 = list(zip(columns[0:4], [0,3,4,5]))
line1 = list(zip(columns[4:12], [i for i in range(0,8)]))
line2 = list(zip(columns[12:19], [i for i in range(0,7)]))
line3 = list(zip(columns[19:], [i for i in range(1,6)]))
line3 = list(zip(columns[19:-1], [i for i in range(1,6)]))
data_extractor = create_line_divider([18,35,53,67,87,106,117])
for l in [line0,line1,line2,line3]:
print(f"\n{l}")
data_extractor = create_line_divider([18,32,50,66,84,100,117])
for line in enumerate(lines):
slot1 = data_extractor(0,line[1],False)
if type(slot1) != str : continue
if re.search(contract_number_regex, slot1) != None:
data_section = lines[line[0]-1:line[0]+4]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[0])) for c in line0]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[1])) for c in line1]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[2])) for c in line2]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[3])) for c in line3]
data_section = lines[line[0]-1:line[0]+3]
# There were issues were the IL Report would have random blank lines so that needs to be checked
# and adjusted for
# A dead give away of an empty line in a data section is a line without a '.'
# Check the first data line
if data_section[0].find(".") == -1:
# Move it back if empty
data_section[0] = lines[line[0]-2]
# Now we go through each relevant data line and make sure they're not blank
for ds in enumerate(data_section):
if ds[1].find(".") == -1:
if ds[0] < len(data_section) -1:
for i in range(ds[0], len(data_section)-1):
# This allows us to move down all the data lines after a blank data line
data_section[i] = data_section[i+1]
# This handles the last data line which goes 'out-of-bounds' of the existing data selection
data_section[3] = lines[line[0]+3]
else:
data_section[3] = lines[line[0]+3]
# Now that the datasection is sorted we can extract the data
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[0], False)) for c in line0]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[1], False)) for c in line1]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[2], False)) for c in line2]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[3], False)) for c in line3]
extracted_data_dict["LESSOR"].append(extracted_data_dict["LEASE NUMBER"][-1][0:3])
# We keep track of when we see new lessors for a summary tab
if extracted_data_dict["LESSOR"][-1] not in lessors:
lessors.append(extracted_data_dict["LESSOR"][-1])
dataframe = pd.DataFrame(extracted_data_dict)
dataframe.to_excel(save_name, index=False)
summary_series = []
for lessor in lessors:
reduced_df = dataframe.loc[dataframe["LESSOR"] == lessor]
# Delete columns that are strings as we don't need to sum them
del reduced_df["CUSTOMER NAME"]
del reduced_df["LEASE NUMBER"]
del reduced_df["CONTRACT STAT"]
reduced_df = reduced_df.replace("", np.NaN)
# There can sometimes be REVOLVING ACCOUNT over part of the data
# Just get rid of it
reduced_df = reduced_df.replace("REVOLV", np.NaN)
reduced_df = reduced_df.replace("ING ACCOUNT", np.NaN)
summation = reduced_df.sum(skipna=True, axis=0)
summation["LESSOR"] = lessor
summation["CONTRACT COUNT"] = len(reduced_df.index)
summary_series.append(summation)
summary_df = pd.concat(summary_series, axis=1).transpose().set_index("LESSOR")
with pd.ExcelWriter(save_name) as writer:
dataframe.to_excel(writer, index=False, sheet_name="data")
pd.DataFrame(summary_df).to_excel(writer, index=True, sheet_name="Summary")
return dataframe
@ -493,21 +520,21 @@ def renewal_net_invest_trial_balance(report: str, save_name: str):
'CUSTOMER NAME' : [],
'TYPE' : [],
'GROSS RENEWAL' : [],
'CUR RENT RCVB' : [],
'UNEARNED RIN' : [],
'REMAINING BAL' : [],
'FINANCED RES' : [],
'REMAINING RES' : [],
'LEASE PYMTS' : [],
'CONTRACT NUMBER' : [],
'RENEWAL' : [],
'PAYMENTS RCVD' : [],
'REM RENT RCVB' : [],
'UNPAID RES' : [],
'CUR RENT RCVB' : [],
'UNEARNED RIN' : [],
'SECURITY DEP' : [],
'NET INVEST' : [],
'UNEARN INCOME' : [],
'TOTAL' : [],
'REMAINING BAL' : [],
'FINANCED RES' : [],
'REM RENT RCVB' : [],
'UNPAID RES' : [],
}
columns = list(extracted_data_dict.keys())
line0 = list(zip(columns[0:7], [0,1,2,3,4,5,7]))
@ -518,7 +545,19 @@ def renewal_net_invest_trial_balance(report: str, save_name: str):
slot1 = data_extractor(0,line[1],False)
if type(slot1) != str : continue
if re.search(contract_number_regex, slot1) != None:
data_section = lines[line[0]-1:line[0]+4]
data_section = lines[line[0]-1:line[0]+2]
# SEE net_invest_trial_balance FOR EXPLAINATION
if data_section[0].find(".") == -1:
data_section[0] = lines[line[0]-2]
for ds in enumerate(data_section):
if ds[1].find(".") == -1:
if ds[0] < len(data_section) -1:
for i in range(ds[0], len(data_section)-1):
data_section[i] = data_section[i+1]
data_section[2] = lines[line[0]+2]
else:
data_section[2] = lines[line[0]+2]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[0])) for c in line0]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[1])) for c in line1]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[2])) for c in line2]

@ -72,11 +72,16 @@ def renewal_net_invest_trial_balance(report: str, save_name: str):
if re.search(contract_number_regex, slot1) != None:
data_section = lines[line[0]-1:line[0]+2]
if data_section[0].find(".") == -1:
data_section[0] = lines[line[0]-2]
for ds in enumerate(data_section):
print(ds[1])
if ds[1].find(".") == -1:
[print(f"\n{d[0]}: {d[1]}") for d in enumerate(data_section)]
print('\n')
if ds[0] < len(data_section) -1:
for i in range(ds[0], len(data_section)-1):
data_section[i] = data_section[i+1]
data_section[2] = lines[line[0]+2]
else:
data_section[2] = lines[line[0]+2]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[0])) for c in line0]
[extracted_data_dict[c[0]].append(data_extractor(c[1], data_section[1])) for c in line1]
@ -89,4 +94,4 @@ def renewal_net_invest_trial_balance(report: str, save_name: str):
with open("/config/workspace/LEAF/IL Extract SRC/2022.05.20 Renewal Net Investment", errors="replace") as rep_file:
report = rep_file.read()
prt(renewal_net_invest_trial_balance(report, "rn_TESTING.xlsx"))
prt(renewal_net_invest_trial_balance(report, "RN_TEST_0606.xlsx"))

@ -0,0 +1,87 @@
import os
import pandas as pd
from datetime import datetime as dt, timedelta
import sys, getopt
import re
from pathlib import Path
import time
from pprint import pprint as prt
contract_number_regex = "\d{3}-\d{7}-\d{3}"
def create_line_divider(breakage_list: list):
"""
This allows for the creation of a custom data extractor
Breakage list defines the split points that will be used for the line
Example
Given breakage_list [10, 20, 30]
using slot_num 0 in the resulting extract_line_slot will yield
characters 0 - 10 from the string.
Slot 1 would give characters 10 - 20
"""
def extract_line_slot(slot_num : int, line_string: str, debug : bool = False):
"""
Pulls data from a line/string using break points defined by the
parent function.
ONLY USE THIS FUNCTION THROUGH CREATION USING 'create_line_extractor'
Will automatically convert numbers to floats
"""
assert(slot_num < len(breakage_list)+1)
low_range = 0 if slot_num == 0 else breakage_list[slot_num-1]
high_range = len(line_string) if slot_num == len(breakage_list) else breakage_list[slot_num]
data = line_string[low_range:high_range].strip().replace(",", "")
try: data = float(data)
except: pass
if debug:
print(f"Slot num: {slot_num} | Low: {low_range} | High: {high_range} | Data: {data}")
return data
return extract_line_slot
def ach(report: str, save_name: str):
lines = report.splitlines()
extracted_data_dict = {
"ContractNumber" : [],
"CustomerName" : [],
"BankCode" : [],
"BankNumber": [],
"AccountNumber" : [],
"Payment" : [],
"Batch": [],
"Lessor": [],
"PaymentDate": [],
}
columns = list(extracted_data_dict.keys())
batches = {
"batch_num": [],
"payment_date": [],
"lessor": [],
"count": [],
"total": []
}
data_extractor = create_line_divider([19,57,67,82,104])
bank_number_regex = "\d{9}"
batch_num_regex = "BATCH \d{4} TOTAL"
for line in enumerate(lines):
if (re.search(contract_number_regex, line[1]) != None) & (re.search(bank_number_regex, line[1]) != None):
[extracted_data_dict[columns[c]].append(data_extractor(c, line[1])) for c in range(0, len(columns)-3)]
if re.search(batch_num_regex, line[1]) != None:
batches["batch_num"].append(line[1][96:101])
batches["payment_date"].append(lines[line[0]+2][114:125])
batches["lessor"].append(extracted_data_dict["ContractNumber"][-1][0:3])
batches["total"].append(float(line[1][107:125].strip().replace(",", "")))
batches["count"].append(float(lines[line[0]+6][107:125].strip().replace(",", "")))
[extracted_data_dict["Batch"].append(batches["batch_num"][-1]) for _ in range(0, (len(extracted_data_dict["BankCode"]) - len(extracted_data_dict["Batch"])))]
[extracted_data_dict["Lessor"].append(batches["lessor"][-1]) for _ in range(0, (len(extracted_data_dict["BankCode"]) - len(extracted_data_dict["Lessor"])))]
[extracted_data_dict["PaymentDate"].append(batches["payment_date"][-1]) for _ in range(0, (len(extracted_data_dict["BankCode"]) - len(extracted_data_dict["PaymentDate"])))]
dataframe = pd.DataFrame(extracted_data_dict)
return dataframe
with open("/config/workspace/LEAF/IL Extract SRC/2022.05.04_ACH_C") as rep_file:
report = rep_file.read()
prt(ach(report, "ACH_TESTING.xlsx"))

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
<g id="Text-files">
<path d="M53.9791489,9.1429005H50.010849c-0.0826988,0-0.1562004,0.0283995-0.2331009,0.0469999V5.0228
C49.7777481,2.253,47.4731483,0,44.6398468,0h-34.422596C7.3839517,0,5.0793519,2.253,5.0793519,5.0228v46.8432999
c0,2.7697983,2.3045998,5.0228004,5.1378999,5.0228004h6.0367002v2.2678986C16.253952,61.8274002,18.4702511,64,21.1954517,64
h32.783699c2.7252007,0,4.9414978-2.1725998,4.9414978-4.8432007V13.9861002
C58.9206467,11.3155003,56.7043495,9.1429005,53.9791489,9.1429005z M7.1110516,51.8661003V5.0228
c0-1.6487999,1.3938999-2.9909999,3.1062002-2.9909999h34.422596c1.7123032,0,3.1062012,1.3422,3.1062012,2.9909999v46.8432999
c0,1.6487999-1.393898,2.9911003-3.1062012,2.9911003h-34.422596C8.5049515,54.8572006,7.1110516,53.5149002,7.1110516,51.8661003z
M56.8888474,59.1567993c0,1.550602-1.3055,2.8115005-2.9096985,2.8115005h-32.783699
c-1.6042004,0-2.9097996-1.2608986-2.9097996-2.8115005v-2.2678986h26.3541946
c2.8333015,0,5.1379013-2.2530022,5.1379013-5.0228004V11.1275997c0.0769005,0.0186005,0.1504021,0.0469999,0.2331009,0.0469999
h3.9682999c1.6041985,0,2.9096985,1.2609005,2.9096985,2.8115005V59.1567993z"/>
<path d="M38.6031494,13.2063999H16.253952c-0.5615005,0-1.0159006,0.4542999-1.0159006,1.0158005
c0,0.5615997,0.4544001,1.0158997,1.0159006,1.0158997h22.3491974c0.5615005,0,1.0158997-0.4542999,1.0158997-1.0158997
C39.6190491,13.6606998,39.16465,13.2063999,38.6031494,13.2063999z"/>
<path d="M38.6031494,21.3334007H16.253952c-0.5615005,0-1.0159006,0.4542999-1.0159006,1.0157986
c0,0.5615005,0.4544001,1.0159016,1.0159006,1.0159016h22.3491974c0.5615005,0,1.0158997-0.454401,1.0158997-1.0159016
C39.6190491,21.7877007,39.16465,21.3334007,38.6031494,21.3334007z"/>
<path d="M38.6031494,29.4603004H16.253952c-0.5615005,0-1.0159006,0.4543991-1.0159006,1.0158997
s0.4544001,1.0158997,1.0159006,1.0158997h22.3491974c0.5615005,0,1.0158997-0.4543991,1.0158997-1.0158997
S39.16465,29.4603004,38.6031494,29.4603004z"/>
<path d="M28.4444485,37.5872993H16.253952c-0.5615005,0-1.0159006,0.4543991-1.0159006,1.0158997
s0.4544001,1.0158997,1.0159006,1.0158997h12.1904964c0.5615025,0,1.0158005-0.4543991,1.0158005-1.0158997
S29.0059509,37.5872993,28.4444485,37.5872993z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" id="Outline" viewBox="0 0 24 24" width="512" height="512"><path d="M19,3H12.472a1.019,1.019,0,0,1-.447-.1L8.869,1.316A3.014,3.014,0,0,0,7.528,1H5A5.006,5.006,0,0,0,0,6V18a5.006,5.006,0,0,0,5,5H19a5.006,5.006,0,0,0,5-5V8A5.006,5.006,0,0,0,19,3ZM5,3H7.528a1.019,1.019,0,0,1,.447.1l3.156,1.579A3.014,3.014,0,0,0,12.472,5H19a3,3,0,0,1,2.779,1.882L2,6.994V6A3,3,0,0,1,5,3ZM19,21H5a3,3,0,0,1-3-3V8.994l20-.113V18A3,3,0,0,1,19,21Z"/></svg>

After

Width:  |  Height:  |  Size: 512 B

@ -28,16 +28,20 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
inFile = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file')
self.inputFileLE.setText(inFile[0])
if inFile[0] == '' : return ''
print(f"Input File: {inFile}")
# Replacing errors fixes some UTF-8 character issues
with open(inFile[0], errors="replace") as inF:
txt = inF.read()
#print(txt)
self.inputFilePreview.setText(txt)
self.inputFile = inFile[0]
# This gets the actual file name
inFileEnd = inFile[0].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
self.outputFile = f"{outputRoot}{self.reportTypeCB.currentText()}_{dt.now().strftime('%Y%m%d_%H%M')}.xlsx"
self.outputFileLE.setText(self.outputFile)
# Check to make sure the user has selected the correct report type
if self.reportTypeCB.currentText().split(" ")[-1].lower() not in self.inputFile.lower():
print("Possibly wrong file type")
warning = QtWidgets.QMessageBox()
@ -45,9 +49,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
warning.setText(f"Selected report type is {self.reportTypeCB.currentText()} but input file did not contain '{self.reportTypeCB.currentText().split(' ')[-1].lower()}'!\n\
Make sure you select the correct report type before processing!")
s = warning.exec()
# Enables the process button
self.check_ready_to_process()
def setOutput(self):
# This allows the user to change the automatic naming and location
outFile = QtWidgets.QFileDialog.getSaveFileName(self, "Output file name")
if outFile[0] == '': return ''
self.outputFileLE.setText(f"{outFile[0]}__{dt.now().strftime('%Y%m%d_%H_%M')}.xlsx")
@ -56,15 +62,17 @@ Make sure you select the correct report type before processing!")
self.check_ready_to_process()
def check_ready_to_process(self):
# Makes sure there is an input and output selected before allowing processing
self.rtp = True if ((self.inputFile != "") & (self.outputFile != "")) else False
if self.rtp :
self.processReportButton.setEnabled(True)
def process_selection(self):
self.inputFilePreview.setText("Processing file...")
with open(self.inputFile, errors="replace") as inF:
reportString = inF.read()
# If only this was python 3.10 and we could use switch statments
# but this should get the job done
try:
# Here we set the extraction function that will be used on the report
if self.reportTypeCB.currentText() == "ACH":
extract_function = ilx.ach
elif self.reportTypeCB.currentText() == "Disposition":
@ -91,18 +99,17 @@ Make sure you select the correct report type before processing!")
extract_function = ilx.payment_transactions
elif self.reportTypeCB.currentText() == "Returned Check":
extract_function = ilx.payment_transactions
# This is where the actual processing happens
# We create an ILReport object and pass in the nessecary information
dataframe = ilx.ILReport(
location= self.inputFile,
extraction_function=extract_function,
output_location=self.outputFile,
).process()
#dataframe.to_excel("test_name.xlsx", index=False, engine="xlsxwriter")
# 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
smallDF = dataframe.iloc[0:500,:]
self.inputFilePreview.setText(smallDF.to_html(index=False))
print("Fin")
self.openReportButton.setEnabled(True)
except:
error = QtWidgets.QMessageBox()
error.setWindowTitle('Error Processing File!')

@ -0,0 +1,268 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1001</width>
<height>664</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QTextBrowser" name="inputFilePreview">
<property name="geometry">
<rect>
<x>20</x>
<y>220</y>
<width>951</width>
<height>391</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="processReportButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>20</x>
<y>180</y>
<width>250</width>
<height>36</height>
</rect>
</property>
<property name="text">
<string>&amp;Process Report</string>
</property>
</widget>
<widget class="QPushButton" name="openReportButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>280</x>
<y>180</y>
<width>241</width>
<height>36</height>
</rect>
</property>
<property name="text">
<string>&amp;Copy to Clipboard</string>
</property>
</widget>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>21</x>
<y>90</y>
<width>951</width>
<height>84</height>
</rect>
</property>
<layout class="QVBoxLayout" name="fileSettingsBox">
<item>
<layout class="QHBoxLayout" name="inputFileBox">
<item>
<widget class="QPushButton" name="inputFileButton">
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>250</width>
<height>36</height>
</size>
</property>
<property name="text">
<string>Select &amp;InfoLease Report</string>
</property>
<property name="icon">
<iconset>
<normaloff>../../.designer/backup/Pictures/svgs/folder.svg</normaloff>../../.designer/backup/Pictures/svgs/folder.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="inputFileLE">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>No file selected</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="outFileLocation">
<item>
<widget class="QPushButton" name="outputFileButton">
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>250</width>
<height>36</height>
</size>
</property>
<property name="text">
<string>Select &amp;Report Output Location</string>
</property>
<property name="icon">
<iconset>
<normaloff>../../.designer/backup/Pictures/svgs/folder.svg</normaloff>../../.designer/backup/Pictures/svgs/folder.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="outputFileLE">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>No location selected</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QComboBox" name="reportTypeCB">
<property name="geometry">
<rect>
<x>21</x>
<y>51</y>
<width>250</width>
<height>37</height>
</rect>
</property>
<item>
<property name="text">
<string>ACH</string>
</property>
</item>
<item>
<property name="text">
<string>Disposition</string>
</property>
</item>
<item>
<property name="text">
<string>Gain Loss</string>
</property>
</item>
<item>
<property name="text">
<string>Lock Box</string>
</property>
</item>
<item>
<property name="text">
<string>Minv_C</string>
</property>
</item>
<item>
<property name="text">
<string>Net Inv. Loans</string>
</property>
</item>
<item>
<property name="text">
<string>NI Renewal</string>
</property>
</item>
<item>
<property name="text">
<string>NIV After</string>
</property>
</item>
<item>
<property name="text">
<string>PBP / Epay</string>
</property>
</item>
<item>
<property name="text">
<string>Returned Check</string>
</property>
</item>
<item>
<property name="text">
<string>Unapplied</string>
</property>
</item>
<item>
<property name="text">
<string>VMCC</string>
</property>
</item>
<item>
<property name="text">
<string>Wires</string>
</property>
</item>
</widget>
<widget class="QLabel" name="reportTypeL">
<property name="geometry">
<rect>
<x>21</x>
<y>21</y>
<width>144</width>
<height>24</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Infolease Report</string>
</property>
<property name="buddy">
<cstring>reportTypeCB</cstring>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1001</width>
<height>29</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<tabstops>
<tabstop>reportTypeCB</tabstop>
<tabstop>inputFileButton</tabstop>
<tabstop>outputFileButton</tabstop>
<tabstop>processReportButton</tabstop>
<tabstop>openReportButton</tabstop>
<tabstop>inputFileLE</tabstop>
<tabstop>outputFileLE</tabstop>
<tabstop>inputFilePreview</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

@ -0,0 +1 @@
<svg width="16px" height="16px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2h13l.5.5V9h-1V6H2v7h7v1H1.5l-.5-.5v-11l.5-.5zM2 5h12V3H2v2zm5 7v-1.094a1.633 1.633 0 0 1-.469-.265l-.945.539-.5-.86.937-.547a1.57 1.57 0 0 1 0-.547l-.937-.546.5-.86.945.54c.151-.12.308-.209.469-.266V7h1v1.094a1.48 1.48 0 0 1 .469.265l.945-.539.5.86-.937.547a1.57 1.57 0 0 1 0 .546l.937.547-.5.86-.945-.54a1.807 1.807 0 0 1-.469.266V12H7zm-.25-2.5c0 .208.073.385.219.531a.723.723 0 0 0 .531.219.723.723 0 0 0 .531-.219.723.723 0 0 0 .219-.531.723.723 0 0 0-.219-.531.723.723 0 0 0-.531-.219.723.723 0 0 0-.531.219.723.723 0 0 0-.219.531zm5.334 5.5v-1.094a1.634 1.634 0 0 1-.469-.265l-.945.539-.5-.86.938-.547a1.572 1.572 0 0 1 0-.547l-.938-.546.5-.86.945.54c.151-.12.308-.209.47-.266V10h1v1.094a1.486 1.486 0 0 1 .468.265l.945-.539.5.86-.937.547a1.562 1.562 0 0 1 0 .546l.937.547-.5.86-.945-.54a1.806 1.806 0 0 1-.469.266V15h-1zm-.25-2.5c0 .208.073.385.219.531a.723.723 0 0 0 .531.219.723.723 0 0 0 .531-.219.723.723 0 0 0 .22-.531.723.723 0 0 0-.22-.531.723.723 0 0 0-.53-.219.723.723 0 0 0-.532.219.723.723 0 0 0-.219.531z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Loading…
Cancel
Save