From 08db2ec1c9874eb59126323c7be2f28b11a0f370 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 18 Jan 2020 12:15:52 -0500 Subject: [PATCH 1/6] Update Test_Procedures.py --- samples/Test_Procedures.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/samples/Test_Procedures.py b/samples/Test_Procedures.py index 2ecaaea..d5d9159 100644 --- a/samples/Test_Procedures.py +++ b/samples/Test_Procedures.py @@ -5,14 +5,21 @@ # Decorator for printing function results. We return a results value to enable # automated testing of the methods upon refactoring. -def output_decorator(func): - def inner(*args, **kwargs): - print(f'{func.__name__} is now started') - t = func(*args, **kwargs) - print(f'{t["results"]} instances detected') - print(f'Results saved at {t["output"]}') - return t["results"] - return inner +def output_decorator(msg=None): + def wrapper(func): + def inner(*args, **kwargs): + + if msg: + print(msg) + else: + print(f'{func.__name__} is now started') + + t = func(*args, **kwargs) + print(f'{t["results"]} instances detected') + print(f'Results saved at {t["output"]}') + return t["results"] + return inner + return wrapper class Test_1_Procedures: From 38eb5e9e078110a0018a139766ddc1c4291a2155 Mon Sep 17 00:00:00 2001 From: mrcrnkovich Date: Sun, 19 Jan 2020 18:47:42 -0500 Subject: [PATCH 2/6] Refactoring Test Procedures and Adding Automated Tests for refactor --- README.md | 4 +- requirements.txt | 3 +- samples/Test_Procedures.py | 45 +++++++++++------- samples/__init__.py | 1 + .../Test_Procedures.cpython-38.pyc | Bin 8604 -> 8481 bytes samples/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 190 bytes samples/data/__init__.py | 1 + .../data/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 155 bytes .../__pycache__/load_files.cpython-38.pyc | Bin 0 -> 293 bytes tests/__init__.py | 1 + tests/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 167 bytes ..._JE_procedures.cpython-38-pytest-5.3.3.pyc | Bin 0 -> 2695 bytes tests/test_JE_procedures.py | 39 +++++++++++++++ 13 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 samples/__init__.py create mode 100644 samples/__pycache__/__init__.cpython-38.pyc create mode 100644 samples/data/__init__.py create mode 100644 samples/data/__pycache__/__init__.cpython-38.pyc create mode 100644 samples/data/__pycache__/load_files.cpython-38.pyc create mode 100644 tests/__init__.py create mode 100644 tests/__pycache__/__init__.cpython-38.pyc create mode 100644 tests/__pycache__/test_JE_procedures.cpython-38-pytest-5.3.3.pyc create mode 100644 tests/test_JE_procedures.py diff --git a/README.md b/README.md index 8774b1c..6006094 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ If you haven't already installed Python 3 and Jupyter, the easiest way to instal TODO: Write usage instructions 1. [How to reshape SAP Data for your audit](https://github.com/AICPA-AuditDataAnalytics2018/ADS---Python-Example-/blob/master/samples/reshape_rename_sap_data.ipynb) -2. [How to reshape Quickbooks General Ledger Data for your audit](https://github.com/AICPA-AuditDataAnalytics2018/ADS---Python-Example-/tree/master/samples) +2. [How to reshape Quickbooks General Ledger Data for your audit](https://github.com/AICPA-AuditDataAnalytics2018/ADS---Python-Example-/blob/master/samples/quickbooksGLtoDatabase.py) 3. [How to split a DataFrame (csv, xlsx, other) using pandas and groupby](https://github.com/AICPA-AuditDataAnalytics2018/ADS---Python-Example-/blob/master/samples/Split%20Dataframe%20with%20Groupby.ipynb) @@ -22,7 +22,7 @@ TODO: Write usage instructions 3. Add your changes: `git add *` 4. Commit your changes: `git commit -am 'Add some feature'` 5. Push to the branch: `git push origin my-new-feature` -6. Submit a pull request :D +6. Submit a pull request :smile: ## History diff --git a/requirements.txt b/requirements.txt index 7bf65c7..7cb99d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ pandas numpy -xlrd -xlsxwriter +pytest diff --git a/samples/Test_Procedures.py b/samples/Test_Procedures.py index d5d9159..5b0f73a 100644 --- a/samples/Test_Procedures.py +++ b/samples/Test_Procedures.py @@ -27,10 +27,11 @@ class Test_1_Procedures: # 3.1.1 - Test 1.1 Check for gaps in journal entry numbers # This method assumes JE's are already sorted in ascending order - @output_decorator - def check_for_gaps_in_JE_ID(GL_Detail, - Journal_ID_Column = 'Journal_ID', - output_file = 'Output_Folder/Test_3_1_1_check_for_gaps_in_JE_ID.csv'): + @output_decorator() + def check_for_gaps_in_JE_ID( + GL_Detail, Journal_ID_Column = 'Journal_ID', + output_file = 'Output_Folder/Test_3_1_1_check_for_gaps_in_JE_ID.csv'): + gaps = [] previous = None @@ -54,12 +55,13 @@ def check_for_gaps_in_JE_ID(GL_Detail, # 3.1.2 Compare listing of journal entry numbers from system to log file - @output_decorator - def comparison_of_entries_of_GL_and_log_file(GL_Detail_YYYYMMDD_YYYYMMDD, - Log_File_YYYYMMDD_YYYYMMDD, output_file = "Output_Folder/Test_3_1_2_Comparison_of_Entries_of_GL_and_Log_File.csv"): + @output_decorator() + def comparison_of_entries_of_GL_and_log_file( + GL_Detail, Log_File, Journal_ID_Column = 'Journal_ID', + output_file = "Output_Folder/Test_3_1_2_Comparison_of_Entries_of_GL_and_Log_File.csv"): - In_GL_not_in_LOG = set(GL_Detail_YYYYMMDD_YYYYMMDD['Journal_ID']) - set(Log_File_YYYYMMDD_YYYYMMDD['Journal_ID']) - In_LOG_not_in_GL = set(Log_File_YYYYMMDD_YYYYMMDD['Journal_ID']) - set(GL_Detail_YYYYMMDD_YYYYMMDD['Journal_ID']) + In_GL_not_in_LOG = set(GL_Detail[Journal_ID_Column]) - set(Log_File[Journal_ID_Column]) + In_LOG_not_in_GL = set(Log_File[Journal_ID_Column]) - set(GL_Detail[Journal_ID_Column]) if output_file: with open(output_file, 'w') as file: @@ -71,26 +73,35 @@ def comparison_of_entries_of_GL_and_log_file(GL_Detail_YYYYMMDD_YYYYMMDD, writer.writerow(['Amounts of following %a journal entries do not match their amounts in Log File:' %(len(In_LOG_not_in_GL))]) writer.writerow(list(In_LOG_not_in_GL)) + return ({"results": (len(In_LOG_not_in_GL) + len(In_GL_not_in_LOG)), "output": output_file}) # 3.1.3 Test 1.3 Compare total debit amounts and credit amounts of journal entries to system control totals by entry type - def comparison_of_amounts_of_GL_and_log_file(GL_Detail_YYYYMMDD_YYYYMMDD, Log_File_YYYYMMDD_YYYYMMDD): + @output_decorator() + def comparison_of_amounts_of_GL_and_log_file( + GL_Detail, Log_File, Journal_ID_Column = 'Journal_ID', + Cr_Db_Indicator = 'Amount_Credit_Debit_Indicator', + output_file = 'Output_Folder/Test_3_1_3_comparison_of_amounts_of_GL_and_log_file.csv'): - gl_totals_pivot = GL_Detail_YYYYMMDD_YYYYMMDD.pivot_table(index=['Journal_ID', 'Amount_Credit_Debit_Indicator'], - values='Net', - aggfunc=sum).reset_index() - recon_gl_to_log = gl_totals_pivot.merge(Log_File_YYYYMMDD_YYYYMMDD, on = ['Journal_ID', 'Amount_Credit_Debit_Indicator'], - how = 'outer').fillna(0) + index_cols = [Journal_ID_Column, Cr_Db_Indicator] + + recon_gl_to_log = GL_Detail.pivot_table( + index = index_cols, values = 'Net', + aggfunc = sum).reset_index().merge( + Log_File, on = index_cols, + how = 'outer').fillna(0) + recon_gl_to_log['Comparison'] = round(abs(recon_gl_to_log['Net']), 2) - round(abs(recon_gl_to_log['Total']), 2) recon_gl_to_log = recon_gl_to_log.drop('Entered_Date', axis=1) recon_gl_to_log = recon_gl_to_log.drop('Entered_Time', axis=1) + failed_test = recon_gl_to_log.loc[recon_gl_to_log['Comparison'] != 0] if output_file: - failed_test.to_csv('Output_Folder/Test_3_1_3_comparison_of_amounts_of_GL_and_log_file.csv') + failed_test.to_csv(output_file) - return ({"results": len(In_LOG_not_in_GL), "output": output_file}) + return ({"results": len(failed_test), "output": output_file}) class Test_2_Procedures: # 3.2.1 - Examine population for missing or incomplete journal entries diff --git a/samples/__init__.py b/samples/__init__.py new file mode 100644 index 0000000..78854c5 --- /dev/null +++ b/samples/__init__.py @@ -0,0 +1 @@ +from . import Test_Procedures diff --git a/samples/__pycache__/Test_Procedures.cpython-38.pyc b/samples/__pycache__/Test_Procedures.cpython-38.pyc index c6206daf403bc776391ef06babd0256a374bb36e..ce43551a0a3ba4027de31f1da7ae9cf788a6caba 100644 GIT binary patch delta 2770 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 dX7}@t)bC#7I!ZEJvYuiM<$oPJOL+hQ diff --git a/samples/__pycache__/__init__.cpython-38.pyc b/samples/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ced60bb2d3fd45e15c32fdc576f728134472293a GIT binary patch literal 190 zcmWIL<>g`kf-kpK;^ctzV-N=!FabFZKwK;UBvKes7;_kM8KW2(8B&;n88n$+G6ID) z8E^52q!yRN2NdNer>2w^r55{XGTmY*0%=~!P{abHz{D?W{fzwFRQ=q{>{R{a{FKbR zbbUvcU|n6^fXb4L{5)OPip1Q4oK#)?VlZ8-A0MBYmst`YuUAlci^C>2KczG$)edCm IXCP((02oLv6aWAK literal 0 HcmV?d00001 diff --git a/samples/data/__init__.py b/samples/data/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/samples/data/__init__.py @@ -0,0 +1 @@ + diff --git a/samples/data/__pycache__/__init__.cpython-38.pyc b/samples/data/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02dd4eda8f1edc22fdcdb8e6d204a6f517ecc3b6 GIT binary patch literal 155 zcmWIL<>g`kf(y4*;uwMSV-N=!fCL?YxR?bH3Z?!MeJ-0hJ{g`FXmo6^Xe8IjOq(#bCNvKP9mwQ9nLDGcU6wK3=b& V@)n0pZhlH>PO2Tqg`kg0;6);+lcB0?mqD@sU?Y-Iq{J|;Opz+0%zzY7nfD>>FMX>r^lyd=A?r7 znyk0j3KH{D5{qv!6{JLQ6s0Dn!~?~ncv8~h({tjB6LSl4Qlof*JdidBcO^p+3(!6= z@ykg+BR@A)KQ}WwRX;gDB{MHw-_a#lS64TnvLquvPuH~qY=o{pL~pTvN@7VO&<#L) fK#nNZE2zB1VUwGmQks)$#|SjG7-TjF6Bi=@bJ9^q literal 0 HcmV?d00001 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..2025927 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +import samples diff --git a/tests/__pycache__/__init__.cpython-38.pyc b/tests/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b6ab3dbb1013a2af22e6ae0010c041e5389dee5 GIT binary patch literal 167 zcmWIL<>g`k0_ImLar{8~F^Gcr5S|%5R{O9M=j8+vFc7G0QQjqlaI!;EcAk~ELM0r~Aye{>)>f>Q#LO(% zDJ=(*6IHoUoH*D=^8@%Xx^nVg@B!03@~$6c6IY=s_4Z6l)7|Rt>)!9i#%fbrS6@uJ zg#1koT^6iw!>t~G5Jb?J_|*Kep2Y|yJ==F^X>t9Ev3R}*zbvlyYJQE9`$Sm6enW)) zn)!7>pOL1s1*egPCM_nOhf4G7{HaVkq3Fv{br~JUI-r`pv~oBb60qJ{119W& zm9yMhVFNq2)vwxKcZ8GMT`FAM@3IYBRJ6NZ*&r*fdJc9kXB$+$0IiBz!oZHYwLyjV z2fIK9j;M-S?nD%HSJ%K=&)p6Bn*IWx)+Ph+DG20IU)XN9T5T2dvN%+25$K?O|1qBr zbr8k;d$^uFnV%mit&aN9uRTipG6`b-aK6d>iM8mrJUdz_LXsW5%*(p?SG*#h3Mf$PcWWEJ96gRfV1EoT#%`-RQ8Q8qpY!oaL zg(t&6aEL=<)%?lMa@bkn-Bj|WAX7X__@lcJSy8*4t!b=$Ad3m!3F4T`Fq56MmjyB` z8ery&{V3K^0x`rXJfVtlSeQroQk*VAnn}wSV#TCXpY& ztJ`z!<+K;Jd(leR?xZ40mfCmbpG{9sKV8$yX)=9x)%cliW9G_8{2bW``mJoOIBz~n z`Q0G2Fwrs!6{1yL1VL!caw#Kh$F*$Y0b1^n1{o)>K0A!~){zGt;DHC{jd-vQi+wym zMgV#T%rJrhCrU;Dm4OLXZUZBn+)=*)dXW*1VFc_SVg&NQL+s1tAqz%~ut$t=4I^B` zh{`S_D*G7W4D2BzE+1w@oGuwKBmAWeS!`N{=Rq5`MD+mZ{?yL8 zi!BsDS4S`!&^2^r-7{@igWWV-`7>xWgdc#eVRQuDm}$eV7#FAVA?WI0ZRAIR?(~BE z2% Date: Sun, 19 Jan 2020 22:21:00 -0500 Subject: [PATCH 3/6] Refactoring Test Procedures and Adding Automated Tests for refactor --- samples/Test_Procedures.py | 182 +++++++++++------- .../Test_Procedures.cpython-38.pyc | Bin 8481 -> 7605 bytes ..._JE_procedures.cpython-38-pytest-5.3.3.pyc | Bin 2695 -> 8805 bytes tests/test_JE_procedures.py | 76 +++++--- 4 files changed, 166 insertions(+), 92 deletions(-) diff --git a/samples/Test_Procedures.py b/samples/Test_Procedures.py index 5b0f73a..26033fb 100644 --- a/samples/Test_Procedures.py +++ b/samples/Test_Procedures.py @@ -1,6 +1,7 @@ import csv import pandas as pd import numpy as np +from datetime import datetime # Decorator for printing function results. We return a results value to enable # automated testing of the methods upon refactoring. @@ -55,6 +56,7 @@ def check_for_gaps_in_JE_ID( # 3.1.2 Compare listing of journal entry numbers from system to log file + @output_decorator() def comparison_of_entries_of_GL_and_log_file( GL_Detail, Log_File, Journal_ID_Column = 'Journal_ID', @@ -78,6 +80,7 @@ def comparison_of_entries_of_GL_and_log_file( "output": output_file}) # 3.1.3 Test 1.3 Compare total debit amounts and credit amounts of journal entries to system control totals by entry type + @output_decorator() def comparison_of_amounts_of_GL_and_log_file( GL_Detail, Log_File, Journal_ID_Column = 'Journal_ID', @@ -106,118 +109,157 @@ def comparison_of_amounts_of_GL_and_log_file( class Test_2_Procedures: # 3.2.1 - Examine population for missing or incomplete journal entries # Pivot by Journal_ID and make sure Net is 0 for each Journal ID, to check if debits and credits are equal for each entry - def check_for_incomplete_entries(GL_Detail_YYYYMMDD_YYYYMMDD, - output_file='', Journal_ID_Column = 'Journal_ID'): + + @output_decorator() + def check_for_incomplete_entries(GL_Detail, + output_file='Output_Folder/Test_3_2_1_check_for_incomplete_entries.csv', + Journal_ID_Column = 'Journal_ID'): - GL_Pivot = GL_Detail_YYYYMMDD_YYYYMMDD.pivot_table(index=Journal_ID_Column, values='Net', aggfunc=sum) + GL_Pivot = GL_Detail.pivot_table(index=Journal_ID_Column, values='Net', aggfunc=sum) failed_test = GL_Pivot.loc[round(GL_Pivot['Net'], 2) != 0] failed_test = pd.DataFrame(failed_test.to_records()) if output_file: - failed_test.to_csv('Output_Folder/Test_3_2_1_check_for_incomplete_entries.csv') + failed_test.to_csv(output_file) return ({"results": len(failed_test[Journal_ID_Column]), "output": output_file}) # 3.2.2 - Examine possible duplicate account entries # Check for Journal Entries that have same account and amount in the same period - def check_for_duplicate_entries(GL_Detail_YYYYMMDD_YYYYMMDD): - print('Checking for Duplicate Entries is started') - import pandas as pd - import numpy as np - GL_Pivot = GL_Detail_YYYYMMDD_YYYYMMDD.pivot_table(index=['GL_Account_Number', 'Period', 'Net'], - values='Journal_ID', aggfunc= np.count_nonzero) + + @output_decorator() + def check_for_duplicate_entries(GL_Detail, Journal_ID_Column = 'Journal_ID', + output_file='Output_Folder/Test_3_2_2_check_for_duplicate_entries.csv'): + + GL_Pivot = GL_Detail.pivot_table(index=['GL_Account_Number', 'Period', 'Net'], + values=Journal_ID_Column, aggfunc= np.count_nonzero) GL_Pivot.columns = ['Journal_Entry_Count'] Duplicates = GL_Pivot.loc[GL_Pivot['Journal_Entry_Count'] != 1] Duplicates = pd.DataFrame(Duplicates.to_records()) - GL_Copy = GL_Detail_YYYYMMDD_YYYYMMDD[['Journal_ID', 'GL_Account_Number', 'Period', 'Net']].copy() + GL_Copy = GL_Detail[['Journal_ID', 'GL_Account_Number', 'Period', 'Net']].copy() failed_test = GL_Copy.merge(Duplicates, on = ['GL_Account_Number', 'Period', 'Net'], how = 'right').fillna(0) - failed_test.to_csv('Output_Folder/Test_3_2_2_check_for_duplicate_entries.csv') - print('%d instances detected' %len(failed_test['Journal_ID'])) - print('Results saved at Output_Folder/Test_3_2_2_check_for_duplicate_entries.csv') - + + if output_file: + failed_test.to_csv(output_file) + + return ({'results':len(failed_test[Journal_ID_Column]), + 'output':output_file}) - #3.2.3 - Examine round-dollar entries + # 3.2.3 - Examine round-dollar entries # Devide Amounts by 1000 and look for remainder of 0 to check for journal entries with exact amounts in '000s - def check_for_round_dollar_entries(GL_Detail_YYYYMMDD_YYYYMMDD): - print('Checking for Round Dollar Entries is started') - GL_Copy = GL_Detail_YYYYMMDD_YYYYMMDD[['Journal_ID', 'GL_Account_Number', 'Period', 'Net']].copy() + + @output_decorator() + def check_for_round_dollar_entries(GL_Detail, + Journal_ID_Column='Journal_ID', + output_file='Output_Folder/Test_3_2_3_check_for_round_dollar_entries.csv'): + + GL_Copy = GL_Detail[[Journal_ID_Column, 'GL_Account_Number', 'Period', 'Net']].copy() GL_Copy['1000s Remainder'] = GL_Copy['Net'] % 1000 failed_test = GL_Copy.loc[GL_Copy['1000s Remainder'] == 0] - failed_test.to_csv('Output_Folder/Test_3_2_3_check_for_round_dollar_entries.csv') - print('%d instances detected' %len(failed_test['Journal_ID'])) - print('Results saved at Output_Folder/Test_3_2_3_check_for_round_dollar_entries.csv') - - - #3.2.4 - Examine post-date entries: - #Check if Document Date was later than Entry Date - #Document_Date does not appear in Data Standards - #optimize&clarify - def check_for_post_date_entries(GL_Detail_YYYYMMDD_YYYYMMDD): - print('Checking for Post Date Entries is started') - GL_Copy = GL_Detail_YYYYMMDD_YYYYMMDD[['Journal_ID', 'Document_Date', 'Entered_Date', 'Period', 'Net']].copy() - failed_test = GL_Copy.loc[GL_Copy['Document_Date'] > (GL_Copy['Entered_Date'] + 100)] #optimize&"accurify" - failed_test.to_csv('Output_Folder/Test_3_2_4_check_for_post_date_entries.csv') - print('%d instances detected' %len(failed_test['Journal_ID'])) - print('Results saved at Output_Folder/Test_3_2_4_check_for_post_date_entries.csv') + + if output_file: + failed_test.to_csv(output_file) + + return ({'results':len(failed_test[Journal_ID_Column]), + 'output':output_file}) + # 3.2.4 - Examine post-date entries: + # Check if Document Date was later than Entry Date + # Document_Date does not appear in Data Standards + # optimize&clarify + + @output_decorator() + def check_for_post_date_entries(GL_Detail, + Journal_ID_Column='Journal_ID', + output_file='Output_Folder/Test_3_2_4_check_for_post_date_entries.csv'): + + GL_Copy = GL_Detail[['Journal_ID', 'Document_Date', 'Entered_Date', 'Period', 'Net']].copy() + failed_test = GL_Copy.loc[GL_Copy['Document_Date'] > (GL_Copy['Entered_Date'] + 100)] #optimize&"accurify" + + if output_file: + failed_test.to_csv(output_file) + + return ({'results':len(failed_test[Journal_ID_Column]), + 'output':output_file}) - #3.2.5 - Examine entries posted on weekends/nights + # 3.2.5 - Examine entries posted on weekends/nights # Check if Entry Date falls on Saturday or Sunday - def check_for_weekend_entries(GL_Detail_YYYYMMDD_YYYYMMDD): - print('Checking for Weekend Entries is started') - from datetime import datetime - import pandas as pd - GL_Copy = GL_Detail_YYYYMMDD_YYYYMMDD[['Journal_ID', 'Entered_Date', 'Entered_Time']].copy() + + @output_decorator() + def check_for_weekend_entries(GL_Detail, + Journal_ID_Column='Journal_ID', + output_file='Output_Folder/Test_3_2_5.1_check_for_weekend_entries.csv'): + + GL_Copy = GL_Detail[[Journal_ID_Column, 'Entered_Date', 'Entered_Time']].copy() GL_Copy['Entry_Date_Time_Formatted'] = pd.to_datetime(GL_Copy['Entered_Date'].astype(str) + GL_Copy['Entered_Time'].astype(str), format='%Y%m%d%H%M%S') GL_Copy['WeekDayNo'] = GL_Copy['Entry_Date_Time_Formatted'].apply(lambda x: x.isoweekday()) failed_test = GL_Copy.loc[GL_Copy['WeekDayNo'] >= 6] - failed_test.to_csv('Output_Folder/Test_3_2_5.1_check_for_weekend_entries.csv') - print('%d instances detected' %len(failed_test['Journal_ID'])) - print('Results saved at Output_Folder/Test_3_2_5.1_check_for_weekend_entries.csv') + + if output_file: + failed_test.to_csv(output_file) + + return ({'results':len(failed_test[Journal_ID_Column]), + 'output':output_file}) # Check if Entry Time falls on between 8pm and 6am - def check_for_nights_entries(GL_Detail_YYYYMMDD_YYYYMMDD): - print('Checking for Night Entries is started') - from datetime import datetime - GL_Copy = GL_Detail_YYYYMMDD_YYYYMMDD[['Journal_ID', 'Entered_Date', 'Entered_Time']].copy() + + @output_decorator() + def check_for_nights_entries(GL_Detail, + Journal_ID_Column='Journal_ID', + output_file='Output_Folder/Test_3_2_5.2_check_for_nights_entries.csv'): + + GL_Copy = GL_Detail[['Journal_ID', 'Entered_Date', 'Entered_Time']].copy() GL_Copy['Entry_Date_Time_Formatted'] = pd.to_datetime(GL_Copy['Entered_Date'].astype(str) + GL_Copy['Entered_Time'].astype(str), format='%Y%m%d%H%M%S') GL_Copy['Hour'] = GL_Copy['Entry_Date_Time_Formatted'].dt.hour failed_test = GL_Copy.loc[(GL_Copy['Hour'] >= 20) | (GL_Copy['Hour'] <= 5)] - failed_test.to_csv('Output_Folder/Test_3_2_5.2_check_for_nights_entries.csv') - print('%d instances detected' %len(failed_test['Journal_ID'])) - print('Results saved at Output_Folder/Test_3_2_5.2_check_for_nights_entries.csv') - - - #3.2.6 - Summarize by person, type and period in order to identify individuals who normally do not post entries, - #and to identify accounts that are normally not used. + + if output_file: + failed_test.to_csv(output_file) - #Check for individuals who posted 10 or fewer entries and identify entries made by these individuals - def check_for_rare_users(GL_Detail_YYYYMMDD_YYYYMMDD): + return ({'results':len(failed_test[Journal_ID_Column]), + 'output':output_file}) - print('Checking for Rare Users is started') - GL_Pivot = GL_Detail_YYYYMMDD_YYYYMMDD.pivot_table(index=['Entered_By'], values='Journal_ID', + # 3.2.6 - Summarize by person, type and period in order to identify individuals who normally do not post entries, + # and to identify accounts that are normally not used. + # Check for individuals who posted 10 or fewer entries and identify entries made by these individuals + + @output_decorator() + def check_for_rare_users(GL_Detail, + Journal_ID_Column='Journal_ID', + output_file='Output_Folder/Test_3_2_6.1_check_for_rare_users.csv'): + + GL_Pivot = GL_Detail.pivot_table(index=['Entered_By'], values='Journal_ID', aggfunc=np.count_nonzero).fillna(0) Rare_Users = GL_Pivot.loc[GL_Pivot['Journal_ID'] <= 10] Rare_Users = pd.DataFrame(Rare_Users.to_records()) - GL_Copy = GL_Detail_YYYYMMDD_YYYYMMDD[['Journal_ID', 'GL_Account_Number', 'Entered_By']].copy() + GL_Copy = GL_Detail[['Journal_ID', 'GL_Account_Number', 'Entered_By']].copy() failed_test = GL_Copy.merge(Rare_Users, on = ['Entered_By'], how = 'right').fillna(0) - failed_test.to_csv('Output_Folder/Test_3_2_6.1_check_for_rare_users.csv') - print('%d instances detected' %len(failed_test['Entered_By'])) - print('Results saved at Output_Folder/Test_3_2_6.1_check_for_rare_users.csv') + + if output_file: + failed_test.to_csv(output_file) + + return ({'results':len(failed_test['Entered_By']), + 'output':output_file}) # Check for accounts that were used 3 or fewer times and identify entries made to these accounts - def check_for_rare_accounts(GL_Detail_YYYYMMDD_YYYYMMDD): + + @output_decorator() + def check_for_rare_accounts(GL_Detail, + Journal_ID_Column='Journal_ID', + output_file='Output_Folder/Test_3_2_6.2_check_for_rare_accounts.csv'): - print('Checking for Rare Accounts is started') - GL_Pivot = GL_Detail_YYYYMMDD_YYYYMMDD.pivot_table(index=['GL_Account_Number'], values='Journal_ID', + GL_Pivot = GL_Detail.pivot_table(index=['GL_Account_Number'], values='Journal_ID', aggfunc=np.count_nonzero).fillna(0) Rare_Accounts = GL_Pivot.loc[GL_Pivot['Journal_ID'] <= 3] Rare_Accounts = pd.DataFrame(Rare_Accounts.to_records()) - GL_Copy = GL_Detail_YYYYMMDD_YYYYMMDD[['Journal_ID', 'GL_Account_Number', 'Entered_By']].copy() + GL_Copy = GL_Detail[['Journal_ID', 'GL_Account_Number', 'Entered_By']].copy() failed_test = GL_Copy.merge(Rare_Accounts, on = ['GL_Account_Number'], how = 'right').fillna(0) - failed_test.to_csv('Output_Folder/Test_3_2_6.2_check_for_rare_accounts.csv') - print('%d instances detected' %len(failed_test['GL_Account_Number'])) - print('Results saved at Output_Folder/Test_3_2_6.2_check_for_rare_accounts.csv') + + if output_file: + failed_test.to_csv(output_file) + + return ({'results':len(failed_test['GL_Account_Number']), + 'output':output_file}) diff --git a/samples/__pycache__/Test_Procedures.cpython-38.pyc b/samples/__pycache__/Test_Procedures.cpython-38.pyc index ce43551a0a3ba4027de31f1da7ae9cf788a6caba..807b29d3e8f4589a21b608ec16c90272d1e90cd5 100644 GIT binary patch literal 7605 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 literal 8481 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 diff --git a/tests/__pycache__/test_JE_procedures.cpython-38-pytest-5.3.3.pyc b/tests/__pycache__/test_JE_procedures.cpython-38-pytest-5.3.3.pyc index 5a55549c38a750b9a32b3365e2eb462a17a8e150..dad62c416b7d9d3741442e6ff5273f58d2ef001e 100644 GIT binary patch literal 8805 zcmeHNTW=f372f+UmlQ4ErN(iUrcTW?B27uY6_#tma#E*>kisd@E{Y6RoR!3A$z^Aj zj-`@qV7ovbnxH7qw>(IFD4@6ef}p>k&&58reNlgcQNTTCme(a2IVSz2QrI(RXJ%(+ zXTI;8voren)Rg_J=?fn{uqEkVQsSq<{Vllo0}xcAvM)KZNR^7JNU~JX99@n}4965D zmSaJx_^FER*p;-CmL<>J%}{kucBX`;ftK~MA0MMSHJ(V+q}Jz(m!+v2(mN8hX&Um< zj}>Q{%J-#Q<^cMZR&z>p{6mk2q)0wsLCK?a=J8h6LQmGtxqQCB-Ac{(c!9d1TkwMo zvR?MRe2G8UDI`nZd5^4kpn)8dG{`< zbtWla+m5UozFS$N?oC)_i5nm!*?yLl8Qve@dqKCF`>@O-b=3(Q!fH-k>RJLu%4A%|L3=DVl{eMUT;GNNswYo`5t>PtsG6 zX6R{p2GS{d7G`cHnik95S!`15TZim$Q)?)p(oh>3 zRXLG1~pAl6M!#LfW1g{c+&dPJR>ztoS}wUmO;0|6qMQx zYfpYGKLV0zQd5S@fJ=c(g-dJd6j7C4J;=ZtNA|lxomE|*+*?6WTt;yP#kEL-f=fa!>>P7B_gEOe$5M_vSh$o+$4#CcFNA9vOZBm3cHn_-0V|sdx&89<0GiRNaaf zWp9g7m4oVC#(?7|amFfcNW6zN->teKl;l#8#yx*MN@L^kYh3ERS?ayH+Iw@|5d(^! zDHeN+7UHqS-4z$(imkiLU8BN~J<)0L_VFCS%zM?4l|6n8iuf5262Q&?7-~{hvOs<& zWkB0`BT38r&}RC`YC&rqvi=utddLdUMxuslL!}B3RRf}GKvcb<^M8lhu1?j44n!6C zf~eZAA&9Dn=B_1(>Oj4eASwmTXc1LMT@cmi)O;dpB2m8(HDjV0K-G3ze-$-=wCpQU z12jQpYzwK3DNwmZVPWJNhM=$++EAKqmUx}Di|z)KZ>gY$YmZ>NwD5tT~wh{w@OLdF9LZx!w|F-G;R+^ z2S5 znHKu)mXg$JapE~wfS-cqg=ljwX53$5#{GB%_#r)EMh|r)GO?6yL9339!TI|&W{ASx%SOJngSMoUNN+9@Zxf0NxH|#4{(IWd^ z=(Rn{aQBf6^&tMkNKeI4Vf3|c4D${QLBU=D(eD=^LS^S57XmwMAv)I$>;?xE!}&$M z0L$SJ-b68A^o#5*%unF{wVwC~yaNQq$tjwHr~06t5dqbwv3Xw@Vct8T_F#iY7TskN zz`<_dDzvQiqlv(p)@wd~kdKLKwu~dYiJ}*-uXN!$(9yR-E4zhFC-66yz~4X@810OP zU-2;b4YYS;(Rc7qoc><){eB|oW3WL)2@QPTW#gIE5db{j1>ms0#>e362@DSF1#RkR z7%m+K!(mMxT_pZFiNwj+!CCsJ(01vf;1MMAI^oi|@ zK0Em7MC{-vp10+JC2Z|Py;x*9!UWECO<&(6iiBt84F zFZ2=3;CzA9;oyQ#)Wn_YqOIG*_(+>gAg$Xre&~!gY;PWhvTj317h!Wrg#C6RHcd>e zTPoqn^JGERbEekU$@n-sHw%IqihLB&^T^|qe2x0TMHdj&n2*qs_I1ctUihC%2ieeST&rp1bqWgI+G~9S(SrAij ziGwvuv2@FTKfU)))X5!>toA{pIJt;sNqkC~kB|G{r5S|%5R{O9M=j8+vFc7G0QQjqlaI!;EcAk~ELM0r~Aye{>)>f>Q#LO(% zDJ=(*6IHoUoH*D=^8@%Xx^nVg@B!03@~$6c6IY=s_4Z6l)7|Rt>)!9i#%fbrS6@uJ zg#1koT^6iw!>t~G5Jb?J_|*Kep2Y|yJ==F^X>t9Ev3R}*zbvlyYJQE9`$Sm6enW)) zn)!7>pOL1s1*egPCM_nOhf4G7{HaVkq3Fv{br~JUI-r`pv~oBb60qJ{119W& zm9yMhVFNq2)vwxKcZ8GMT`FAM@3IYBRJ6NZ*&r*fdJc9kXB$+$0IiBz!oZHYwLyjV z2fIK9j;M-S?nD%HSJ%K=&)p6Bn*IWx)+Ph+DG20IU)XN9T5T2dvN%+25$K?O|1qBr zbr8k;d$^uFnV%mit&aN9uRTipG6`b-aK6d>iM8mrJUdz_LXsW5%*(p?SG*#h3Mf$PcWWEJ96gRfV1EoT#%`-RQ8Q8qpY!oaL zg(t&6aEL=<)%?lMa@bkn-Bj|WAX7X__@lcJSy8*4t!b=$Ad3m!3F4T`Fq56MmjyB` z8ery&{V3K^0x`rXJfVtlSeQroQk*VAnn}wSV#TCXpY& ztJ`z!<+K;Jd(leR?xZ40mfCmbpG{9sKV8$yX)=9x)%cliW9G_8{2bW``mJoOIBz~n z`Q0G2Fwrs!6{1yL1VL!caw#Kh$F*$Y0b1^n1{o)>K0A!~){zGt;DHC{jd-vQi+wym zMgV#T%rJrhCrU;Dm4OLXZUZBn+)=*)dXW*1VFc_SVg&NQL+s1tAqz%~ut$t=4I^B` zh{`S_D*G7W4D2BzE+1w@oGuwKBmAWeS!`N{=Rq5`MD+mZ{?yL8 zi!BsDS4S`!&^2^r-7{@igWWV-`7>xWgdc#eVRQuDm}$eV7#FAVA?WI0ZRAIR?(~BE z2% Date: Mon, 20 Jan 2020 12:39:42 -0500 Subject: [PATCH 4/6] Update Test_Procedures.py --- samples/Test_Procedures.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/Test_Procedures.py b/samples/Test_Procedures.py index 26033fb..4a9ea19 100644 --- a/samples/Test_Procedures.py +++ b/samples/Test_Procedures.py @@ -68,12 +68,12 @@ def comparison_of_entries_of_GL_and_log_file( if output_file: with open(output_file, 'w') as file: writer = csv.writer(file) - writer.writerow(['Following %a journal entries exist in General Ledger, but missing from the Log File:' - %(len(In_GL_not_in_LOG))]) + writer.writerow([f'Following {len(In_GL_not_in_LOG)} journal entries exist'+ + 'in General Ledger, but are missing from the Log File:']) writer.writerow(list(In_GL_not_in_LOG)) writer.writerow(['-'*85]) - writer.writerow(['Amounts of following %a journal entries do not match their amounts in Log File:' - %(len(In_LOG_not_in_GL))]) + writer.writerow([f'Following {len(In_LOG_not_in_GL)} journal entries exist in Log File,'+ + ' but are missing from the General Ledger:']) writer.writerow(list(In_LOG_not_in_GL)) return ({"results": (len(In_LOG_not_in_GL) + len(In_GL_not_in_LOG)), From a92c983c9eadd987ab45583927f3fe2ed991013b Mon Sep 17 00:00:00 2001 From: mrcrnkovich Date: Sun, 26 Jan 2020 19:23:15 -0500 Subject: [PATCH 5/6] Linting files --- samples/Test_Procedures.py | 111 +++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 48 deletions(-) diff --git a/samples/Test_Procedures.py b/samples/Test_Procedures.py index 4a9ea19..41969cf 100644 --- a/samples/Test_Procedures.py +++ b/samples/Test_Procedures.py @@ -6,15 +6,16 @@ # Decorator for printing function results. We return a results value to enable # automated testing of the methods upon refactoring. + def output_decorator(msg=None): def wrapper(func): def inner(*args, **kwargs): - + if msg: print(msg) else: print(f'{func.__name__} is now started') - + t = func(*args, **kwargs) print(f'{t["results"]} instances detected') print(f'Results saved at {t["output"]}') @@ -24,108 +25,122 @@ def inner(*args, **kwargs): class Test_1_Procedures: - + # 3.1.1 - Test 1.1 Check for gaps in journal entry numbers # This method assumes JE's are already sorted in ascending order - + @output_decorator() def check_for_gaps_in_JE_ID( - GL_Detail, Journal_ID_Column = 'Journal_ID', - output_file = 'Output_Folder/Test_3_1_1_check_for_gaps_in_JE_ID.csv'): - + GL_Detail, Journal_ID_Column='Journal_ID', + output_file='Output_Folder/' + + 'Test_3_1_1_check_for_gaps_in_JE_ID.csv'): + gaps = [] previous = None - + # Loop through each Journal ID, compare to previous for item in GL_Detail[Journal_ID_Column]: if previous and (item - previous > 1): gaps.append([previous, item]) - previous = item - + previous = item + # Write results to the output csv file, set output_file = None for no # output_file. if output_file: with open(output_file, 'w') as file: writer = csv.writer(file) - writer.writerow([f'Gap identified! Start gap number is followed by end gap number']) + writer.writerow([f'Gap identified!' + + 'Start gap number is followed by end gap number']) writer.writerows(gaps) - writer.writerow(['Test Results:']) + writer.writerow(['Test Results:']) writer.writerow([f'Total of {len(gaps)} gaps found']) - - return ({"results":len(gaps), "output":output_file}) + return ({"results": len(gaps), "output": output_file}) # 3.1.2 Compare listing of journal entry numbers from system to log file - @output_decorator() def comparison_of_entries_of_GL_and_log_file( - GL_Detail, Log_File, Journal_ID_Column = 'Journal_ID', - output_file = "Output_Folder/Test_3_1_2_Comparison_of_Entries_of_GL_and_Log_File.csv"): - - In_GL_not_in_LOG = set(GL_Detail[Journal_ID_Column]) - set(Log_File[Journal_ID_Column]) - In_LOG_not_in_GL = set(Log_File[Journal_ID_Column]) - set(GL_Detail[Journal_ID_Column]) - + GL_Detail, Log_File, Journal_ID_Column='Journal_ID', + output_file="Output_Folder/" + + "Test_3_1_2_Comparison_of_Entries_of_GL_and_Log_File.csv"): + + In_GL_not_in_LOG = (set(GL_Detail[Journal_ID_Column]) + - set(Log_File[Journal_ID_Column])) + In_LOG_not_in_GL = (set(Log_File[Journal_ID_Column]) + - set(GL_Detail[Journal_ID_Column])) + if output_file: with open(output_file, 'w') as file: writer = csv.writer(file) - writer.writerow([f'Following {len(In_GL_not_in_LOG)} journal entries exist'+ - 'in General Ledger, but are missing from the Log File:']) + writer.writerow([f'Following {len(In_GL_not_in_LOG)}' + + ' journal entries exist in General Ledger, but' + + ' are missing from the Log File:']) writer.writerow(list(In_GL_not_in_LOG)) writer.writerow(['-'*85]) - writer.writerow([f'Following {len(In_LOG_not_in_GL)} journal entries exist in Log File,'+ - ' but are missing from the General Ledger:']) + writer.writerow([f'Following {len(In_LOG_not_in_GL)}' + + ' journal entries exist in Log File, but' + + ' are missing from the General Ledger:']) writer.writerow(list(In_LOG_not_in_GL)) return ({"results": (len(In_LOG_not_in_GL) + len(In_GL_not_in_LOG)), "output": output_file}) - # 3.1.3 Test 1.3 Compare total debit amounts and credit amounts of journal entries to system control totals by entry type - + # 3.1.3 Test 1.3 Compare total debit amounts and credit amounts of + # journal entries to system control totals by entry type + @output_decorator() def comparison_of_amounts_of_GL_and_log_file( - GL_Detail, Log_File, Journal_ID_Column = 'Journal_ID', - Cr_Db_Indicator = 'Amount_Credit_Debit_Indicator', - output_file = 'Output_Folder/Test_3_1_3_comparison_of_amounts_of_GL_and_log_file.csv'): - + GL_Detail, Log_File, Journal_ID_Column='Journal_ID', + Cr_Db_Indicator='Amount_Credit_Debit_Indicator', + output_file='Output_Folder/Test_3_1_3_comparison_' + + 'of_amounts_of_GL_and_log_file.csv'): + index_cols = [Journal_ID_Column, Cr_Db_Indicator] recon_gl_to_log = GL_Detail.pivot_table( - index = index_cols, values = 'Net', - aggfunc = sum).reset_index().merge( - Log_File, on = index_cols, - how = 'outer').fillna(0) + index=index_cols, values='Net', + aggfunc=sum).reset_index().merge( + Log_File, on=index_cols, + how='outer').fillna(0) - recon_gl_to_log['Comparison'] = round(abs(recon_gl_to_log['Net']), 2) - round(abs(recon_gl_to_log['Total']), 2) + recon_gl_to_log['Comparison'] = (round(abs(recon_gl_to_log['Net']), 2) + - round(abs(recon_gl_to_log['Total']), 2)) recon_gl_to_log = recon_gl_to_log.drop('Entered_Date', axis=1) recon_gl_to_log = recon_gl_to_log.drop('Entered_Time', axis=1) - + failed_test = recon_gl_to_log.loc[recon_gl_to_log['Comparison'] != 0] - + if output_file: failed_test.to_csv(output_file) - + return ({"results": len(failed_test), "output": output_file}) + class Test_2_Procedures: # 3.2.1 - Examine population for missing or incomplete journal entries - # Pivot by Journal_ID and make sure Net is 0 for each Journal ID, to check if debits and credits are equal for each entry + # Pivot by Journal_ID and make sure Net is 0 for each Journal ID, + # to check if debits and credits are equal for each entry @output_decorator() def check_for_incomplete_entries(GL_Detail, - output_file='Output_Folder/Test_3_2_1_check_for_incomplete_entries.csv', - Journal_ID_Column = 'Journal_ID'): - - GL_Pivot = GL_Detail.pivot_table(index=Journal_ID_Column, values='Net', aggfunc=sum) + output_file='Output_Folder/' + + 'Test_3_2_1_check_for_incomplete_entries.csv', + Journal_ID_Column='Journal_ID'): + + GL_Pivot = GL_Detail.pivot_table(index=Journal_ID_Column, + values='Net', aggfunc=sum) failed_test = GL_Pivot.loc[round(GL_Pivot['Net'], 2) != 0] failed_test = pd.DataFrame(failed_test.to_records()) - + if output_file: failed_test.to_csv(output_file) - - return ({"results": len(failed_test[Journal_ID_Column]), "output": output_file}) + + return ({"results": len(failed_test[Journal_ID_Column]), + "output": output_file}) # 3.2.2 - Examine possible duplicate account entries - # Check for Journal Entries that have same account and amount in the same period + # Check for Journal Entries that have same account + # and amount in the same period. @output_decorator() def check_for_duplicate_entries(GL_Detail, Journal_ID_Column = 'Journal_ID', From 226af6a3c47bf93ac3b980a62d13a6d961328c43 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 10 Feb 2020 17:11:53 -0500 Subject: [PATCH 6/6] Update README.md --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 6006094..06498e8 100644 --- a/README.md +++ b/README.md @@ -28,17 +28,6 @@ TODO: Write usage instructions TODO: Write history -** Consider adding an __init__ method to Test_Procedures, to reduce data entry: -```python -def __init__(self, GL_Detail, Log_File=None, JE_Column=None, Output=None): - # Checks to make sure data is valid - assert JE_Column in GL_Detail.columns - self.GL_Detail = GL_Detail - ... -def run(): - # Execute all procedures in module -``` - ## Credits TODO: Write credits