appydave-tools 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2822e09cd6989d2fc8576f8d6b842e6ad40429d9931ae6bbdb557d4bb3a2991c
4
- data.tar.gz: 4a9bc9044bcde2bd179a66349b5544430fb9fc8cb1a7b7a656fc5ed3ba8bd405
3
+ metadata.gz: 14a6bd2963b58f85f247282a6cf8c537495b9a18300b04c3a3794ef5df4ecaa1
4
+ data.tar.gz: 5ec47924ab4268377a297e1f02f6fa6b4f62207179f21fe8ccdcd69d62cd4f8d
5
5
  SHA512:
6
- metadata.gz: 4c619668264809fc3be5b5bab73f360dc0480b7dde5b9e232e336edd62a3f416110dbf59ba705174b6b5f8f158819b01817ccacbb123498ad2e17af1330bcaef
7
- data.tar.gz: 4f4c8c4ffbce01ac24a7860939b13f403e9e729d75cb3d7bd68e17a9c37580f2605db3b9667ab4f43a5236ff5803639721ce2452580884c2fe23a3414f031fd9
6
+ metadata.gz: 253546f33096bad9f9daaa5776fa9d98142a4e7cb43607bf2040ab648d49f83712c8f491b8730de4bd602bcd4080a9360cb9bdd082a1b249df4210d66083ebf6
7
+ data.tar.gz: f1f638a4a299df238694c8b4e42c008e8663a08cb2b83931a953796ec6679fa83038bebe3f749b0351a7edd848220cb906c53ff076abbf953564ff0356347b29
data/.rubocop.yml CHANGED
@@ -80,6 +80,7 @@ Style/EmptyMethod:
80
80
  Metrics/ParameterLists:
81
81
  Exclude:
82
82
  - "**/spec/**/*"
83
+ - "**/models/**/*"
83
84
  Layout/EmptyLineBetweenDefs:
84
85
  Exclude:
85
86
  - "**/spec/**/*"
@@ -118,3 +119,14 @@ RSpec/PendingWithoutReason:
118
119
 
119
120
  Metrics/AbcSize:
120
121
  Max: 25
122
+ Exclude:
123
+ - "bin/*"
124
+ Metrics/CyclomaticComplexity:
125
+ Exclude:
126
+ - "**/models/**/*"
127
+ - "lib/appydave/tools/bank_reconciliation/clean/mapper.rb"
128
+ Metrics/PerceivedComplexity:
129
+ Exclude:
130
+ - "**/models/**/*"
131
+ RSpec/MultipleMemoizedHelpers:
132
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [0.6.1](https://github.com/klueless-io/appydave-tools/compare/v0.6.0...v0.6.1) (2024-05-26)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * improved configuration printing ([89d769d](https://github.com/klueless-io/appydave-tools/commit/89d769d0741fc75b44db90931cf981feea83027f))
7
+
1
8
  # [0.6.0](https://github.com/klueless-io/appydave-tools/compare/v0.5.0...v0.6.0) (2024-05-26)
2
9
 
3
10
 
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+
6
+ require 'pry'
7
+ require 'appydave/tools'
8
+
9
+ # !/usr/bin/env ruby
10
+ # frozen_string_literal: true
11
+
12
+ # Process command line arguments for any bank reconciliation operations
13
+ class BankReconciliationCLI
14
+ def initialize
15
+ @commands = {
16
+ 'clean' => method(:clean_transactions),
17
+ 'process' => method(:process_transactions),
18
+ 'filter' => method(:filter_transactions)
19
+ }
20
+ end
21
+
22
+ def run
23
+ command, *args = ARGV
24
+ if @commands.key?(command)
25
+ @commands[command].call(args)
26
+ else
27
+ puts "Unknown command: #{command}"
28
+ print_help
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def clean_transactions(args)
35
+ options = {}
36
+ OptionParser.new do |opts|
37
+ opts.banner = 'Usage: bank_reconciliation.rb clean [options]'
38
+ opts.on('-i', '--include PATTERN', 'GLOB pattern for source transaction files') { |v| options[:include] = v }
39
+ opts.on('-f', '--transaction FOLDER', 'Transaction CSV folder where original banking CSV files are stored') { |v| options[:transaction_folder] = v }
40
+ opts.on('-o', '--output FILE', 'Output CSV file name') { |v| options[:output] = v }
41
+ opts.on_tail('-h', '--help', 'Show this message') do
42
+ puts opts
43
+ exit
44
+ end
45
+ end.parse!(args)
46
+
47
+ # Implement cleaning and normalizing transactions
48
+ puts "Cleaning transactions with options: #{options}"
49
+ end
50
+
51
+ def process_transactions(args)
52
+ options = {}
53
+ OptionParser.new do |opts|
54
+ opts.banner = 'Usage: bank_reconciliation.rb process [options]'
55
+ opts.on('-i', '--input FILE', 'Input CSV file with transactions') { |v| options[:input] = v }
56
+ opts.on_tail('-h', '--help', 'Show this message') do
57
+ puts opts
58
+ exit
59
+ end
60
+ end.parse!(args)
61
+
62
+ # Implement processing transactions with chart of accounts lookup
63
+ puts "Processing transactions with options: #{options}"
64
+ end
65
+
66
+ def filter_transactions(args)
67
+ options = {}
68
+ OptionParser.new do |opts|
69
+ opts.banner = 'Usage: bank_reconciliation.rb filter [options]'
70
+ opts.on('-i', '--input FILE', 'Input CSV file with processed transactions') { |v| options[:input] = v }
71
+ opts.on('-y', '--year YEAR', 'Filter by financial year') { |v| options[:year] = v }
72
+ opts.on('-b', '--begin DATE', 'Filter by dates greater than or eqaul to DDMMYY') { |v| options[:year] = v }
73
+ opts.on('-e', '--end DATE', 'Filter by dates less than or eqaul to DDMMYY') { |v| options[:year] = v }
74
+ opts.on('-c', '--codes CODES', 'Filter by chart of account codes (comma-separated)') { |v| options[:codes] = v }
75
+ opts.on('-w', '--wild TEXT', 'Wildcard text match') { |v| options[:text] = v }
76
+ opts.on('-d', '--display', 'Display filtered transactions in table format') { |v| options[:display] = v }
77
+ opts.on('-o', '--output FILE', 'Output CSV file name') { |v| options[:output] = v }
78
+ opts.on_tail('-h', '--help', 'Show this message') do
79
+ puts opts
80
+ exit
81
+ end
82
+ end.parse!(args)
83
+
84
+ # Implement filtering of processed transactions
85
+ puts "Filtering transactions with options: #{options}"
86
+ end
87
+
88
+ def print_help
89
+ puts 'Usage: bank_reconciliation.rb [command] [options]'
90
+ puts 'Commands:'
91
+ puts ' clean Clean and normalize transaction files'
92
+ puts ' process Process transaction list via chart of accounts lookup'
93
+ puts ' filter Filter processed transaction list'
94
+ puts "Run 'bank_reconciliation.rb [command] --help' for more information on a command."
95
+ end
96
+ end
97
+
98
+ BankReconciliationCLI.new.run
99
+ # BankReconciliationCLI.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,36 @@
1
+ # Bank reconciliation
2
+
3
+ [ChatGPT conversation](https://chatgpt.com/c/5d382562-95e5-4243-9b74-c3807d363486)
4
+
5
+
6
+ ## Code structure
7
+
8
+ ```bash
9
+ ├─ lib
10
+ │ ├─ appydave
11
+ │ │ └─ tools
12
+ │ │ ├─ bank_reconciliation
13
+ │ │ │ ├─ clean
14
+ │ │ │ │ ├─ read_transactions.rb
15
+ │ │ │ │ ├─ transaction_cleaner.rb
16
+ │ │ │ ├─ models
17
+ │ │ │ │ ├─ raw_transaction.rb
18
+ │ │ │ │ └─ reconciled_transaction.rb
19
+ │ │ └─ configuration
20
+ │ │ └─ models
21
+ │ │ └─ bank_reconciliation_config.rb
22
+ └─ spec
23
+ ├─ appydave
24
+ │ ├─ tools
25
+ │ │ ├─ bank_reconciliation
26
+ │ │ │ ├─ clean
27
+ │ │ │ │ ├─ read_transactions_spec.rb
28
+ │ │ │ ├─ models
29
+ │ │ │ │ └─ raw_transaction_spec.rb
30
+ │ │ └─ configuration
31
+ │ │ └─ models
32
+ │ │ └─ bank_reconciliation_config_spec.rb
33
+ └─ fixtures
34
+ └─ bank-reconciliation
35
+ └─ bank-west.csv
36
+ ```
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module BankReconciliation
6
+ module Clean
7
+ # Clean transactions
8
+ class CleanTransactions
9
+ include Appydave::Tools::Configuration::Configurable
10
+ include KLog::Logging
11
+
12
+ attr_reader :transaction_folder
13
+ attr_reader :transactions
14
+
15
+ # (config_file)
16
+ def initialize(transaction_folder: '/Volumes/Expansion/Sync/bank-reconciliation/original-transactions')
17
+ @transaction_folder = transaction_folder
18
+ end
19
+
20
+ def clean_transactions(input_globs, _output_file)
21
+ raw_transactions = grab_raw_transactions(input_globs)
22
+ transactions, duplicates_count = deduplicate(raw_transactions)
23
+
24
+ transactions = Mapper.new.map(transactions)
25
+ # tp transactions
26
+
27
+ log.kv 'Deduped consolidated transactions', duplicates_count if duplicates_count.positive?
28
+
29
+ # transactions = normalize(transactions)
30
+ # save_to_csv(transactions, output_file)
31
+ @transactions = transactions
32
+ end
33
+
34
+ private
35
+
36
+ def grab_raw_transactions(input_globs)
37
+ original_dir = Dir.pwd
38
+ transactions = []
39
+
40
+ begin
41
+ Dir.chdir(transaction_folder)
42
+
43
+ input_globs.each do |glob|
44
+ Dir.glob(glob).each do |file|
45
+ raw_transactions = ReadTransactions.new(file).read
46
+ deduped_transactions, duplicates_count = deduplicate(raw_transactions)
47
+
48
+ if duplicates_count.positive?
49
+ log.kv 'Duplicates count', duplicates_count
50
+ log.kv 'File', file
51
+ end
52
+
53
+ transactions += deduped_transactions
54
+ end
55
+ end
56
+ ensure
57
+ Dir.chdir(original_dir)
58
+ end
59
+
60
+ transactions
61
+ end
62
+
63
+ def deduplicate(transactions)
64
+ unique_transactions = transactions.uniq do |transaction|
65
+ [
66
+ transaction.bsb_number,
67
+ transaction.account_number,
68
+ transaction.transaction_date,
69
+ transaction.narration,
70
+ transaction.cheque_number,
71
+ transaction.debit,
72
+ transaction.credit,
73
+ transaction.balance,
74
+ transaction.transaction_type
75
+ ]
76
+ end
77
+
78
+ duplicates = transactions.size - unique_transactions.size
79
+
80
+ [unique_transactions, duplicates]
81
+ end
82
+
83
+ def save_to_csv(transactions, output_file)
84
+ CSV.open(output_file, 'w') do |csv|
85
+ csv << ReconciledTransaction.csv_headers
86
+ transactions.each do |transaction|
87
+ csv << transaction.to_csv_row
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module BankReconciliation
6
+ module Clean
7
+ # Map transactions to chart of accounts and bank accounts
8
+ class Mapper
9
+ include Appydave::Tools::Configuration::Configurable
10
+
11
+ # "bank_accounts": [
12
+ # {
13
+ # "account_number": "5435 6859 0116 7736",
14
+ # "bsb": "",
15
+ # "name": "Mastercard",
16
+ # "platform": "Bankwest"
17
+ # },
18
+ # {
19
+ # "account_number": "303-092",
20
+ # "bsb": "1361644",
21
+ # "name": "atcall",
22
+ # "platform": "Bankwest"
23
+ # },
24
+
25
+ def map(transactions)
26
+ transactions.map do |original_transaction|
27
+ transaction = original_transaction.dup
28
+
29
+ transaction = map_chart_of_account(transaction)
30
+ map_bank_account(transaction)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def map_chart_of_account(transaction)
37
+ equality_match(transaction) ||
38
+ trigram_match(transaction, 0.9, '90%') ||
39
+ trigram_match(transaction, 0.8, '80%') ||
40
+ trigram_match(transaction, 0.7, '70%') ||
41
+ trigram_match(transaction, 0.6, '60%') ||
42
+ trigram_match(transaction, 0.5, '50%') ||
43
+ transaction
44
+ end
45
+
46
+ def map_bank_account(transaction)
47
+ bank_account = config.bank_reconciliation.get_bank_account(transaction.account_number, transaction.bsb_number)
48
+
49
+ if bank_account
50
+ transaction.account_name = bank_account.name
51
+ transaction.platform = bank_account.platform
52
+ end
53
+
54
+ transaction
55
+ end
56
+
57
+ def equality_match(transaction)
58
+ coa = config.bank_reconciliation.chart_of_accounts.find do |chart_of_account|
59
+ chart_of_account.narration.to_s.delete(' ') == transaction.narration.delete(' ')
60
+ end
61
+
62
+ return nil unless coa
63
+
64
+ transaction.coa_match_type = 'equality'
65
+ transaction.coa_code = coa.code
66
+ transaction
67
+ end
68
+
69
+ def trigram_match(transaction, score_threshold, match_type)
70
+ scored_transactions = config.bank_reconciliation.chart_of_accounts.map do |coa|
71
+ {
72
+ coa: coa,
73
+ score: compare(coa.narration, transaction.narration)
74
+ }
75
+ end
76
+
77
+ scored_transactions.sort_by! { |t| t[:score] }.reverse!
78
+
79
+ best = scored_transactions.first
80
+
81
+ return nil unless best
82
+ return nil if best[:score] < score_threshold
83
+
84
+ coa = best[:coa]
85
+
86
+ transaction.coa_match_type = match_type
87
+ transaction.coa_code = coa.code
88
+ transaction
89
+ end
90
+
91
+ def compare(text1, text2)
92
+ text1_trigs = trigramify(text1)
93
+ text2_trigs = trigramify(text2)
94
+
95
+ all_cnt = (text1_trigs | text2_trigs).size
96
+ same_cnt = (text1_trigs & text2_trigs).size
97
+
98
+ same_cnt.to_f / all_cnt
99
+ end
100
+
101
+ def trigramify(text)
102
+ trigs = []
103
+ text.chars.each_cons(3) { |v| trigs << v.join }
104
+ trigs
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module BankReconciliation
6
+ module Clean
7
+ # Read transactions from a CSV file
8
+ class ReadTransactions
9
+ attr_reader :platform
10
+ attr_reader :transactions
11
+
12
+ def initialize(file)
13
+ @file = file
14
+ end
15
+
16
+ def read
17
+ csv_lines = File.read(@file).lines
18
+
19
+ @platform = detect_platform(csv_lines)
20
+
21
+ case platform
22
+ when :bankwest
23
+ read_bankwest(csv_lines)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def read_bankwest(csv_lines)
30
+ @transactions = []
31
+
32
+ # Skip the header line and parse each subsequent line
33
+ CSV.parse(csv_lines.join, headers: true).each do |row|
34
+ transaction = Models::Transaction.new(
35
+ bsb_number: row['BSB Number'],
36
+ account_number: row['Account Number'],
37
+ transaction_date: row['Transaction Date'],
38
+ narration: row['Narration'],
39
+ cheque_number: row['Cheque Number'],
40
+ debit: row['Debit'],
41
+ credit: row['Credit'],
42
+ balance: row['Balance'],
43
+ transaction_type: row['Transaction Type']
44
+ )
45
+ @transactions << transaction
46
+ end
47
+
48
+ @transactions
49
+ end
50
+
51
+ # For bankwest the first row is the CSV will look like:
52
+ # BSB Number,Account Number,Transaction Date,Narration,Cheque Number,Debit,Credit,Balance,Transaction Type
53
+ def detect_platform(csv_lines)
54
+ return :bankwest if csv_lines.first.start_with?('BSB Number,Account Number,Transaction Date,Narration,Cheque Number,Debit,Credit,Balance,Transaction Type')
55
+
56
+ raise Appydave::Tools::Error, 'Unknown platform'
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module BankReconciliation
6
+ module Models
7
+ # Unified transaction model for raw and reconciled data
8
+ class Transaction
9
+ attr_accessor :bsb_number,
10
+ :account_number,
11
+ :transaction_date,
12
+ :narration,
13
+ :cheque_number,
14
+ :debit,
15
+ :credit,
16
+ :balance,
17
+ :transaction_type,
18
+ :platform,
19
+ :coa_code,
20
+ :coa_match_type,
21
+ :account_name
22
+
23
+ def initialize(bsb_number: nil,
24
+ account_number: nil,
25
+ transaction_date: nil,
26
+ narration: nil,
27
+ cheque_number: nil,
28
+ debit: nil,
29
+ credit: nil,
30
+ balance: nil,
31
+ transaction_type: nil,
32
+ platform: nil,
33
+ coa_code: nil,
34
+ coa_match_type: nil,
35
+ account_name: nil)
36
+ @bsb_number = bsb_number&.strip
37
+ @account_number = account_number&.strip
38
+ @transaction_date = transaction_date&.strip
39
+ @transaction_date = Date.strptime(@transaction_date, '%d/%m/%Y')
40
+ @narration = narration&.gsub(/\s{2,}/, ' ')&.strip
41
+ @cheque_number = cheque_number&.strip
42
+ @debit = debit&.strip
43
+ @credit = credit&.strip
44
+ @balance = balance&.strip
45
+ @transaction_type = transaction_type&.strip
46
+ @platform = platform
47
+ @coa_code = coa_code
48
+ @coa_match_type = coa_match_type
49
+ @account_name = account_name
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -6,6 +6,7 @@ module Appydave
6
6
  module Models
7
7
  # Bank reconciliation configuration
8
8
  class BankReconciliationConfig < ConfigBase
9
+ # def
9
10
  # Retrieve all bank accounts
10
11
  def bank_accounts
11
12
  data['bank_accounts'].map do |account|
@@ -54,13 +55,13 @@ module Appydave
54
55
 
55
56
  # Inner class to represent a bank account
56
57
  class BankAccount
57
- attr_accessor :account_number, :bsb, :name, :bank
58
+ attr_accessor :account_number, :bsb, :name, :platform
58
59
 
59
60
  def initialize(data)
60
61
  @account_number = data['account_number']
61
62
  @bsb = data['bsb']
62
63
  @name = data['name']
63
- @bank = data['bank']
64
+ @platform = data['platform']
64
65
  end
65
66
 
66
67
  def to_h
@@ -68,7 +69,7 @@ module Appydave
68
69
  'account_number' => @account_number,
69
70
  'bsb' => @bsb,
70
71
  'name' => @name,
71
- 'bank' => @bank
72
+ 'platform' => @platform
72
73
  }
73
74
  end
74
75
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- VERSION = '0.6.1'
5
+ VERSION = '0.7.0'
6
6
  end
7
7
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'clipboard'
4
+ require 'csv'
4
5
  require 'fileutils'
5
6
  require 'json'
6
7
  require 'open3'
@@ -19,6 +20,10 @@ require 'appydave/tools/configuration/models/settings_config'
19
20
  require 'appydave/tools/configuration/models/bank_reconciliation_config'
20
21
  require 'appydave/tools/configuration/models/channels_config'
21
22
  require 'appydave/tools/name_manager/project_name'
23
+ require 'appydave/tools/bank_reconciliation/clean/clean_transactions'
24
+ require 'appydave/tools/bank_reconciliation/clean/read_transactions'
25
+ require 'appydave/tools/bank_reconciliation/clean/mapper'
26
+ require 'appydave/tools/bank_reconciliation/models/transaction'
22
27
 
23
28
  Appydave::Tools::Configuration::Config.set_default do |config|
24
29
  config.config_path = File.expand_path('~/.config/appydave')
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "appydave-tools",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "appydave-tools",
9
- "version": "0.6.1",
9
+ "version": "0.7.0",
10
10
  "devDependencies": {
11
11
  "@klueless-js/semantic-release-rubygem": "github:klueless-js/semantic-release-rubygem",
12
12
  "@semantic-release/changelog": "^6.0.3",
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appydave-tools",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "AppyDave YouTube Automation Tools",
5
5
  "scripts": {
6
6
  "release": "semantic-release"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appydave-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cruwys
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-26 00:00:00.000000000 Z
11
+ date: 2024-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: clipboard
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: csv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: dotenv
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -86,12 +100,18 @@ files:
86
100
  - LICENSE.txt
87
101
  - README.md
88
102
  - Rakefile
103
+ - bin/bank_reconciliation.rb
89
104
  - bin/configuration.rb
90
105
  - bin/console
91
106
  - bin/gpt_context.rb
92
107
  - bin/setup
93
108
  - images.log
94
109
  - lib/appydave/tools.rb
110
+ - lib/appydave/tools/bank_reconciliation/_doc.md
111
+ - lib/appydave/tools/bank_reconciliation/clean/clean_transactions.rb
112
+ - lib/appydave/tools/bank_reconciliation/clean/mapper.rb
113
+ - lib/appydave/tools/bank_reconciliation/clean/read_transactions.rb
114
+ - lib/appydave/tools/bank_reconciliation/models/transaction.rb
95
115
  - lib/appydave/tools/configuration/_doc.md
96
116
  - lib/appydave/tools/configuration/config.rb
97
117
  - lib/appydave/tools/configuration/configurable.rb