appydave-tools 0.85.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/docs/guides/tools/dam/dam-usage.md +261 -471
- data/lib/appydave/tools/bank_reconciliation/_doc.md +36 -0
- data/lib/appydave/tools/bank_reconciliation/clean/clean_transactions.rb +159 -0
- data/lib/appydave/tools/bank_reconciliation/clean/mapper.rb +133 -0
- data/lib/appydave/tools/bank_reconciliation/clean/read_transactions.rb +258 -0
- data/lib/appydave/tools/bank_reconciliation/models/transaction.rb +158 -0
- data/lib/appydave/tools/configuration/models/bank_reconciliation_config.rb +152 -0
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools.rb +7 -0
- data/package.json +1 -1
- metadata +22 -2
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
:source_files,
|
|
23
|
+
:fin_year
|
|
24
|
+
|
|
25
|
+
def initialize(bsb_number: nil,
|
|
26
|
+
account_number: nil,
|
|
27
|
+
transaction_date: nil,
|
|
28
|
+
narration: nil,
|
|
29
|
+
cheque_number: nil,
|
|
30
|
+
debit: nil,
|
|
31
|
+
credit: nil,
|
|
32
|
+
balance: nil,
|
|
33
|
+
transaction_type: nil,
|
|
34
|
+
platform: nil,
|
|
35
|
+
coa_code: nil,
|
|
36
|
+
coa_match_type: nil,
|
|
37
|
+
account_name: nil)
|
|
38
|
+
account_number = account_number&.strip
|
|
39
|
+
@bsb_number = bsb_number&.strip
|
|
40
|
+
@account_number = account_number
|
|
41
|
+
@transaction_date = parse_date(transaction_date)
|
|
42
|
+
@narration = narration&.gsub(/\s{2,}/, ' ')&.strip
|
|
43
|
+
@cheque_number = cheque_number&.strip
|
|
44
|
+
@debit = debit # clean_amount(debit, account_number, coa_code)
|
|
45
|
+
@credit = credit # clean_amount(credit, account_number, coa_code)
|
|
46
|
+
@balance = balance&.strip
|
|
47
|
+
@transaction_type = transaction_type&.strip
|
|
48
|
+
@platform = platform
|
|
49
|
+
@coa_code = coa_code
|
|
50
|
+
@coa_match_type = coa_match_type
|
|
51
|
+
@account_name = account_name
|
|
52
|
+
@source_files = []
|
|
53
|
+
@fin_year = determine_fin_year(@transaction_date)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def add_source_file(source_file)
|
|
57
|
+
@source_files << source_file.strip unless @source_files.include?(source_file.strip)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.csv_headers
|
|
61
|
+
%i[
|
|
62
|
+
platform
|
|
63
|
+
account_name
|
|
64
|
+
bsb_number
|
|
65
|
+
account_number
|
|
66
|
+
transaction_date
|
|
67
|
+
narration
|
|
68
|
+
debit
|
|
69
|
+
credit
|
|
70
|
+
balance
|
|
71
|
+
transaction_type
|
|
72
|
+
coa_code
|
|
73
|
+
coa_match_type
|
|
74
|
+
source_files
|
|
75
|
+
fin_year
|
|
76
|
+
]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_csv_row
|
|
80
|
+
[
|
|
81
|
+
@platform,
|
|
82
|
+
@account_name,
|
|
83
|
+
@bsb_number,
|
|
84
|
+
@account_number,
|
|
85
|
+
@transaction_date,
|
|
86
|
+
@narration,
|
|
87
|
+
@debit ? format('%.2f', @debit) : '',
|
|
88
|
+
@credit ? format('%.2f', @credit) : '',
|
|
89
|
+
@balance,
|
|
90
|
+
@transaction_type,
|
|
91
|
+
@coa_code,
|
|
92
|
+
@coa_match_type,
|
|
93
|
+
@source_files.join('; '),
|
|
94
|
+
@fin_year
|
|
95
|
+
]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def clean_amount(amount)
|
|
99
|
+
return nil if amount.nil? || amount.strip.empty?
|
|
100
|
+
|
|
101
|
+
cleaned_amount = amount.to_f.round(2)
|
|
102
|
+
|
|
103
|
+
if swap_plus_minus_for_transactions
|
|
104
|
+
if cleaned_amount.negative?
|
|
105
|
+
cleaned_amount = cleaned_amount.abs
|
|
106
|
+
# else
|
|
107
|
+
# cleaned_amount *= -1
|
|
108
|
+
end
|
|
109
|
+
puts "Fixed negative amount for account: #{account_number} and COA: #{coa_code}, original amount: #{amount}, fixed amount: #{cleaned_amount}" if coa_code == 'DANCE'
|
|
110
|
+
end
|
|
111
|
+
cleaned_amount
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Returns true if this transaction's amount should have its sign flipped
|
|
115
|
+
# based on the per-account, per-FY, per-COA rules stored in config.
|
|
116
|
+
#
|
|
117
|
+
# Rules live in ~/.config/appydave/bank-reconciliation.json under
|
|
118
|
+
# `sign_flip_rules` — see BankReconciliationConfig::SignFlipRule.
|
|
119
|
+
# Personal account numbers MUST NOT be hardcoded here; the config file
|
|
120
|
+
# is local-only and never committed to the repo.
|
|
121
|
+
def swap_plus_minus_for_transactions
|
|
122
|
+
sign_flip_rules.any? do |rule|
|
|
123
|
+
(rule.fin_year.nil? || rule.fin_year == fin_year) &&
|
|
124
|
+
rule.account_number == account_number &&
|
|
125
|
+
rule.coa_codes.include?(coa_code)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def sign_flip_rules
|
|
132
|
+
Appydave::Tools::Configuration::Config.bank_reconciliation.sign_flip_rules
|
|
133
|
+
rescue StandardError
|
|
134
|
+
[]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def parse_date(date_string)
|
|
138
|
+
formats = ['%d/%m/%Y', '%Y-%m-%d %H:%M:%S']
|
|
139
|
+
formats.each do |format|
|
|
140
|
+
return Date.strptime(date_string, format)
|
|
141
|
+
rescue Date::Error
|
|
142
|
+
next
|
|
143
|
+
end
|
|
144
|
+
raise Date::Error, "Invalid date format: #{date_string}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def determine_fin_year(date)
|
|
148
|
+
if date.month >= 7
|
|
149
|
+
"#{date.year}-#{date.year + 1}"
|
|
150
|
+
else
|
|
151
|
+
"#{date.year - 1}-#{date.year}"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appydave
|
|
4
|
+
module Tools
|
|
5
|
+
module Configuration
|
|
6
|
+
module Models
|
|
7
|
+
# Bank reconciliation configuration
|
|
8
|
+
class BankReconciliationConfig < ConfigBase
|
|
9
|
+
# def
|
|
10
|
+
# Retrieve all bank accounts
|
|
11
|
+
def bank_accounts
|
|
12
|
+
data['bank_accounts'].map do |account|
|
|
13
|
+
BankAccount.new(account)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def chart_of_accounts
|
|
18
|
+
data['chart_of_accounts'].map do |entry|
|
|
19
|
+
ChartOfAccount.new(entry)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Sign-flip rules — per-account, per-fin-year, per-COA exceptions where the
|
|
24
|
+
# raw bank-export sign is wrong and needs flipping during clean_amount.
|
|
25
|
+
# Personal account numbers belong here (in config), not in source.
|
|
26
|
+
def sign_flip_rules
|
|
27
|
+
(data['sign_flip_rules'] || []).map do |rule|
|
|
28
|
+
SignFlipRule.new(rule)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def get_bank_account(account_number, bsb = nil)
|
|
33
|
+
normalized_bsb = bsb.to_s.empty? ? nil : bsb
|
|
34
|
+
account_data = data['bank_accounts'].find do |account|
|
|
35
|
+
acct_bsb = account['bsb'].to_s.empty? ? nil : account['bsb']
|
|
36
|
+
account['account_number'] == account_number && (normalized_bsb.nil? || acct_bsb == normalized_bsb)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
BankAccount.new(account_data) if account_data
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Retrieve a chart of account entry by code
|
|
43
|
+
def get_chart_of_account(code)
|
|
44
|
+
entry_data = data['chart_of_accounts'].find { |entry| entry['code'] == code }
|
|
45
|
+
ChartOfAccount.new(entry_data) if entry_data
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def print
|
|
49
|
+
log.subheading 'Bank Reconciliation - Accounts'
|
|
50
|
+
|
|
51
|
+
tp bank_accounts, :account_number, :bsb, :name, :bank
|
|
52
|
+
|
|
53
|
+
log.subheading 'Bank Reconciliation - Chart of Accounts'
|
|
54
|
+
|
|
55
|
+
tp chart_of_accounts, :code, :narration
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def coa_to_csv
|
|
59
|
+
csv_file_path = File.join(Config.config_path, 'bank_reconciliation.chart_of_accounts.csv')
|
|
60
|
+
|
|
61
|
+
CSV.open(csv_file_path, 'w') do |csv|
|
|
62
|
+
csv << %w[code narration]
|
|
63
|
+
|
|
64
|
+
chart_of_accounts.sort_by(&:code).each do |entry|
|
|
65
|
+
csv << [entry.code, entry.narration]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def coa_csv_to_json
|
|
71
|
+
csv_file_path = File.join(Config.config_path, 'bank_reconciliation.chart_of_accounts.csv')
|
|
72
|
+
|
|
73
|
+
coa_data = CSV.read(csv_file_path, headers: true).map(&:to_h)
|
|
74
|
+
|
|
75
|
+
data['chart_of_accounts'] = coa_data
|
|
76
|
+
|
|
77
|
+
save
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def default_data
|
|
83
|
+
{
|
|
84
|
+
'bank_accounts' => [],
|
|
85
|
+
'chart_of_accounts' => [],
|
|
86
|
+
'sign_flip_rules' => []
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Inner class to represent a bank account
|
|
91
|
+
class BankAccount
|
|
92
|
+
attr_accessor :account_number, :bsb, :name, :platform
|
|
93
|
+
|
|
94
|
+
def initialize(data)
|
|
95
|
+
@account_number = data['account_number']
|
|
96
|
+
@bsb = data['bsb']
|
|
97
|
+
@name = data['name']
|
|
98
|
+
@platform = data['platform']
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def to_h
|
|
102
|
+
{
|
|
103
|
+
'account_number' => @account_number,
|
|
104
|
+
'bsb' => @bsb,
|
|
105
|
+
'name' => @name,
|
|
106
|
+
'platform' => @platform
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Inner class to represent a chart of account entry
|
|
112
|
+
class ChartOfAccount
|
|
113
|
+
attr_accessor :code, :narration
|
|
114
|
+
|
|
115
|
+
def initialize(data)
|
|
116
|
+
@code = data['code']
|
|
117
|
+
@narration = data['narration']
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def to_h
|
|
121
|
+
{
|
|
122
|
+
'code' => @code,
|
|
123
|
+
'narration' => @narration
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Inner class to represent a sign-flip rule.
|
|
129
|
+
# Matches a transaction by (optional fin_year, account_number, coa_code).
|
|
130
|
+
# When nil, fin_year matches any year.
|
|
131
|
+
class SignFlipRule
|
|
132
|
+
attr_accessor :fin_year, :account_number, :coa_codes
|
|
133
|
+
|
|
134
|
+
def initialize(data)
|
|
135
|
+
@fin_year = data['fin_year']
|
|
136
|
+
@account_number = data['account_number']
|
|
137
|
+
@coa_codes = data['coa_codes'] || []
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def to_h
|
|
141
|
+
{
|
|
142
|
+
'fin_year' => @fin_year,
|
|
143
|
+
'account_number' => @account_number,
|
|
144
|
+
'coa_codes' => @coa_codes
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
data/lib/appydave/tools.rb
CHANGED
|
@@ -56,7 +56,13 @@ require 'appydave/tools/configuration/models/settings_config'
|
|
|
56
56
|
require 'appydave/tools/configuration/models/brands_config'
|
|
57
57
|
require 'appydave/tools/configuration/models/channels_config'
|
|
58
58
|
require 'appydave/tools/configuration/models/youtube_automation_config'
|
|
59
|
+
require 'appydave/tools/configuration/models/bank_reconciliation_config'
|
|
59
60
|
require 'appydave/tools/configuration/example_installer'
|
|
61
|
+
|
|
62
|
+
require 'appydave/tools/bank_reconciliation/models/transaction'
|
|
63
|
+
require 'appydave/tools/bank_reconciliation/clean/read_transactions'
|
|
64
|
+
require 'appydave/tools/bank_reconciliation/clean/mapper'
|
|
65
|
+
require 'appydave/tools/bank_reconciliation/clean/clean_transactions'
|
|
60
66
|
require 'appydave/tools/name_manager/project_name'
|
|
61
67
|
|
|
62
68
|
require 'appydave/tools/prompt_tools/prompt_completion'
|
|
@@ -132,6 +138,7 @@ Appydave::Tools::Configuration::Config.set_default do |config|
|
|
|
132
138
|
config.register(:brands, Appydave::Tools::Configuration::Models::BrandsConfig)
|
|
133
139
|
config.register(:channels, Appydave::Tools::Configuration::Models::ChannelsConfig)
|
|
134
140
|
config.register(:youtube_automation, Appydave::Tools::Configuration::Models::YoutubeAutomationConfig)
|
|
141
|
+
config.register(:bank_reconciliation, Appydave::Tools::Configuration::Models::BankReconciliationConfig)
|
|
135
142
|
end
|
|
136
143
|
|
|
137
144
|
module Appydave
|
data/package.json
CHANGED
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.
|
|
4
|
+
version: 0.86.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: 2026-
|
|
11
|
+
date: 2026-06-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activemodel
|
|
@@ -136,6 +136,20 @@ dependencies:
|
|
|
136
136
|
- - "~>"
|
|
137
137
|
- !ruby/object:Gem::Version
|
|
138
138
|
version: '0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: multi_json
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '1.15'
|
|
146
|
+
type: :runtime
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '1.15'
|
|
139
153
|
- !ruby/object:Gem::Dependency
|
|
140
154
|
name: pstore
|
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -369,6 +383,11 @@ files:
|
|
|
369
383
|
- lib/appydave/tools/app_context/app_finder.rb
|
|
370
384
|
- lib/appydave/tools/app_context/globs_loader.rb
|
|
371
385
|
- lib/appydave/tools/app_context/options.rb
|
|
386
|
+
- lib/appydave/tools/bank_reconciliation/_doc.md
|
|
387
|
+
- lib/appydave/tools/bank_reconciliation/clean/clean_transactions.rb
|
|
388
|
+
- lib/appydave/tools/bank_reconciliation/clean/mapper.rb
|
|
389
|
+
- lib/appydave/tools/bank_reconciliation/clean/read_transactions.rb
|
|
390
|
+
- lib/appydave/tools/bank_reconciliation/models/transaction.rb
|
|
372
391
|
- lib/appydave/tools/brain_context/brain_finder.rb
|
|
373
392
|
- lib/appydave/tools/brain_context/omi_finder.rb
|
|
374
393
|
- lib/appydave/tools/brain_context/options.rb
|
|
@@ -381,6 +400,7 @@ files:
|
|
|
381
400
|
- lib/appydave/tools/configuration/config.rb
|
|
382
401
|
- lib/appydave/tools/configuration/configurable.rb
|
|
383
402
|
- lib/appydave/tools/configuration/example_installer.rb
|
|
403
|
+
- lib/appydave/tools/configuration/models/bank_reconciliation_config.rb
|
|
384
404
|
- lib/appydave/tools/configuration/models/brands_config.rb
|
|
385
405
|
- lib/appydave/tools/configuration/models/channels_config.rb
|
|
386
406
|
- lib/appydave/tools/configuration/models/config_base.rb
|