lucabook 0.2.21 → 0.2.26
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/exe/luca-book +76 -7
- data/lib/luca_book.rb +2 -0
- data/lib/luca_book/accumulator.rb +152 -0
- data/lib/luca_book/console.rb +4 -4
- data/lib/luca_book/dict.rb +168 -4
- data/lib/luca_book/import.rb +36 -37
- data/lib/luca_book/import_jp.rb +83 -0
- data/lib/luca_book/journal.rb +98 -23
- data/lib/luca_book/list.rb +32 -17
- data/lib/luca_book/list_by_header.rb +120 -0
- data/lib/luca_book/setup.rb +2 -1
- data/lib/luca_book/state.rb +142 -217
- data/lib/luca_book/templates/base-jp.xbrl.erb +23 -48
- data/lib/luca_book/templates/base-jp.xsd.erb +17 -0
- data/lib/luca_book/templates/config.yml +4 -0
- data/lib/luca_book/templates/dict-en.tsv +45 -50
- data/lib/luca_book/templates/dict-jp-edinet.tsv +167 -0
- data/lib/luca_book/templates/dict-jp.tsv +192 -162
- data/lib/luca_book/templates/monthly-report.html.erb +67 -0
- data/lib/luca_book/version.rb +1 -1
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 933e3d04315c04b7f6e998b16499b458ceeeafe8bfae9ce4ef4422018eb17040
|
4
|
+
data.tar.gz: 2e243b4e85cdc50bd45e39ed311cf78a8aa12e1956be0d0273c2a634147be16c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2292a4972ce1fcf883a828d17479f1580e3145e0db2d26ff6044a73495d2e151d60f0bd98fd5b2f945c3b74cdff879f9f7779073487c05c70a078306b069bbe
|
7
|
+
data.tar.gz: 27bf6b4bcea1b2eb9d5dac761d5465ce478af02a4074f0a20038b7f8a21c528b66faf399d4364702ebf22f06cc0a0112d13a4ee2e10acb22eb16b52859aaf4b6
|
data/exe/luca-book
CHANGED
@@ -10,7 +10,8 @@ class LucaCmd
|
|
10
10
|
if params['config']
|
11
11
|
LucaBook::Import.new(args[0], params['config']).import_csv
|
12
12
|
elsif params['json']
|
13
|
-
|
13
|
+
str = args[0].nil? ? STDIN.read : File.read(args[0])
|
14
|
+
LucaBook::Import.import_json(str)
|
14
15
|
else
|
15
16
|
puts 'Usage: luca-book import -c import_config'
|
16
17
|
exit 1
|
@@ -20,7 +21,11 @@ class LucaCmd
|
|
20
21
|
def self.list(args, params)
|
21
22
|
args = gen_range(params[:n] || 1) if args.empty?
|
22
23
|
if params['code']
|
23
|
-
|
24
|
+
if params['headers']
|
25
|
+
render(LucaBook::ListByHeader.term(*args, code: params['code'], header: params['headers']).list_by_code, params)
|
26
|
+
else
|
27
|
+
render(LucaBook::List.term(*args, code: params['code']).list_by_code, params)
|
28
|
+
end
|
24
29
|
else
|
25
30
|
render(LucaBook::List.term(*args).list_journals, params)
|
26
31
|
end
|
@@ -31,23 +36,45 @@ class LucaCmd
|
|
31
36
|
if params['code']
|
32
37
|
render(LucaBook::State.by_code(params['code'], *args), params)
|
33
38
|
else
|
34
|
-
render(LucaBook::State.
|
39
|
+
render(LucaBook::State.range(*args).stats(params[:level]), params)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.add_header(args, params)
|
44
|
+
args = gen_range(params[:n] || 1) if args.empty?
|
45
|
+
if params['code']
|
46
|
+
LucaBook::List.add_header(*args, code: params['code'], header_key: params[:key], header_val: params[:value])
|
47
|
+
else
|
48
|
+
puts 'no code specified.'
|
35
49
|
end
|
36
50
|
end
|
37
51
|
end
|
38
52
|
|
39
53
|
class Report < LucaCmd
|
54
|
+
def self.xbrl(args, params)
|
55
|
+
level = params[:level] || 3
|
56
|
+
legal = params[:legal] || false
|
57
|
+
args = gen_range(params[:n] || 1) if args.empty?
|
58
|
+
LucaBook::State.range(*args).render_xbrl(params[:output])
|
59
|
+
end
|
60
|
+
|
40
61
|
def self.balancesheet(args, params)
|
41
62
|
level = params[:level] || 3
|
42
63
|
legal = params[:legal] || false
|
43
64
|
args = gen_range(params[:n] || 1) if args.empty?
|
44
|
-
render(LucaBook::State.
|
65
|
+
render(LucaBook::State.range(*args).bs(level, legal: legal), params)
|
45
66
|
end
|
46
67
|
|
47
68
|
def self.profitloss(args, params)
|
48
69
|
level = params[:level] || 2
|
49
70
|
args = gen_range(params[:n]) if args.empty?
|
50
|
-
render(LucaBook::State.
|
71
|
+
render(LucaBook::State.range(*args).pl(level), params)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.report_mail(args, params)
|
75
|
+
level = params[:level] || 3
|
76
|
+
args = gen_range(params[:n] || 12) if args.empty?
|
77
|
+
render(LucaBook::State.range(*args).report_mail(level), params)
|
51
78
|
end
|
52
79
|
end
|
53
80
|
|
@@ -68,6 +95,12 @@ class LucaCmd
|
|
68
95
|
puts YAML.dump(dat)
|
69
96
|
end
|
70
97
|
end
|
98
|
+
|
99
|
+
class Dict < LucaCmd
|
100
|
+
def self.update_balance(args, params)
|
101
|
+
LucaBook::Dict.generate_balance(*args)
|
102
|
+
end
|
103
|
+
end
|
71
104
|
end
|
72
105
|
|
73
106
|
def new_pj(args = nil, params = {})
|
@@ -93,7 +126,8 @@ when /journals?/, 'j'
|
|
93
126
|
when 'list'
|
94
127
|
OptionParser.new do |opt|
|
95
128
|
opt.banner = 'Usage: luca-book journals list [options] [YYYY M]'
|
96
|
-
opt.on('-c', '--code VAL', '
|
129
|
+
opt.on('-c', '--code VAL', 'filter with code or label') { |v| params['code'] = v }
|
130
|
+
opt.on('--customer', 'categorize by x-customer header') { |_v| params['headers'] = 'x-customer' }
|
97
131
|
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
98
132
|
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
99
133
|
opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
|
@@ -101,10 +135,20 @@ when /journals?/, 'j'
|
|
101
135
|
args = opt.parse!(ARGV)
|
102
136
|
LucaCmd::Journal.list(args, params)
|
103
137
|
end
|
138
|
+
when 'set'
|
139
|
+
OptionParser.new do |opt|
|
140
|
+
opt.banner = 'Usage: luca-book journals set [options] [YYYY M]'
|
141
|
+
opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
|
142
|
+
opt.on('--header VAL', 'header key') { |v| params[:key] = v }
|
143
|
+
opt.on('--val VAL', 'header value') { |v| params[:value] = v }
|
144
|
+
opt.on_tail('set header to journals on specified code.')
|
145
|
+
args = opt.parse!(ARGV)
|
146
|
+
LucaCmd::Journal.add_header(args, params)
|
147
|
+
end
|
104
148
|
when 'stats'
|
105
149
|
OptionParser.new do |opt|
|
106
150
|
opt.banner = 'Usage: luca-book journals stats [options] [YYYY M]'
|
107
|
-
opt.on('-c', '--code VAL', '
|
151
|
+
opt.on('-c', '--code VAL', 'filter with code or label') { |v| params['code'] = v }
|
108
152
|
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
109
153
|
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
110
154
|
opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
|
@@ -130,6 +174,13 @@ when 'new'
|
|
130
174
|
when /reports?/, 'r'
|
131
175
|
subcmd = ARGV.shift
|
132
176
|
case subcmd
|
177
|
+
when 'xbrl'
|
178
|
+
OptionParser.new do |opt|
|
179
|
+
opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
|
180
|
+
opt.on('-o', '--output VAL', 'output filename') { |v| params[:output] = v }
|
181
|
+
args = opt.parse!(ARGV)
|
182
|
+
LucaCmd::Report.xbrl(args, params)
|
183
|
+
end
|
133
184
|
when 'bs'
|
134
185
|
OptionParser.new do |opt|
|
135
186
|
opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
|
@@ -150,6 +201,14 @@ when /reports?/, 'r'
|
|
150
201
|
args = opt.parse!(ARGV)
|
151
202
|
LucaCmd::Report.profitloss(args, params)
|
152
203
|
end
|
204
|
+
when 'mail'
|
205
|
+
OptionParser.new do |opt|
|
206
|
+
opt.banner = 'Usage: luca-book reports mail [options] [YYYY M YYYY M]'
|
207
|
+
opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
|
208
|
+
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
209
|
+
args = opt.parse!(ARGV)
|
210
|
+
LucaCmd::Report.report_mail(args, params)
|
211
|
+
end
|
153
212
|
else
|
154
213
|
puts 'Proper subcommand needed.'
|
155
214
|
puts
|
@@ -158,6 +217,16 @@ when /reports?/, 'r'
|
|
158
217
|
puts ' pl: show statement of income'
|
159
218
|
exit 1
|
160
219
|
end
|
220
|
+
when /balance/
|
221
|
+
subcmd = ARGV.shift
|
222
|
+
case subcmd
|
223
|
+
when 'update'
|
224
|
+
OptionParser.new do |opt|
|
225
|
+
opt.banner = 'Usage: luca-book balance update YYYY [M]'
|
226
|
+
args = opt.parse!(ARGV)
|
227
|
+
LucaCmd::Dict.update_balance(args, params)
|
228
|
+
end
|
229
|
+
end
|
161
230
|
else
|
162
231
|
puts 'Proper subcommand needed.'
|
163
232
|
puts
|
data/lib/luca_book.rb
CHANGED
@@ -5,10 +5,12 @@ require 'luca_record'
|
|
5
5
|
require 'luca_book/version'
|
6
6
|
|
7
7
|
module LucaBook
|
8
|
+
autoload :Accumulator, 'luca_book/accumulator'
|
8
9
|
autoload :Dict, 'luca_book/dict'
|
9
10
|
autoload :Import, 'luca_book/import'
|
10
11
|
autoload :Journal, 'luca_book/journal'
|
11
12
|
autoload :List, 'luca_book/list'
|
13
|
+
autoload :ListByHeader, 'luca_book/list_by_header'
|
12
14
|
autoload :Setup, 'luca_book/setup'
|
13
15
|
autoload :State, 'luca_book/state'
|
14
16
|
autoload :Util, 'luca_book/util'
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'luca_book/util'
|
4
|
+
require 'luca_support/config'
|
5
|
+
|
6
|
+
module LucaBook
|
7
|
+
module Accumulator
|
8
|
+
def self.included(klass) # :nodoc:
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def accumulate_month(year, month)
|
14
|
+
monthly_record, count = net(year, month)
|
15
|
+
[total_subaccount(monthly_record), count]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Accumulate Level 2, 3 account.
|
19
|
+
#
|
20
|
+
def total_subaccount(report)
|
21
|
+
{}.tap do |res|
|
22
|
+
res['A0'] = sum_matched(report, /^[A][0-9A-Z]{2,}/)
|
23
|
+
res['B0'] = sum_matched(report, /^[B][0-9A-Z]{2,}/)
|
24
|
+
res['BA'] = res['A0'] - res['B0']
|
25
|
+
res['C0'] = sum_matched(report, /^[C][0-9A-Z]{2,}/)
|
26
|
+
res['CA'] = res['BA'] - res['C0']
|
27
|
+
res['D0'] = sum_matched(report, /^[D][0-9A-Z]{2,}/)
|
28
|
+
res['E0'] = sum_matched(report, /^[E][0-9A-Z]{2,}/)
|
29
|
+
res['EA'] = res['CA'] + res['D0'] - res['E0']
|
30
|
+
res['F0'] = sum_matched(report, /^[F][0-9A-Z]{2,}/)
|
31
|
+
res['G0'] = sum_matched(report, /^[G][0-9][0-9A-Z]{1,}/)
|
32
|
+
res['GA'] = res['EA'] + res['F0'] - res['G0']
|
33
|
+
res['H0'] = sum_matched(report, /^[H][0-9][0-9A-Z]{1,}/)
|
34
|
+
res['HA'] = res['GA'] - res['H0']
|
35
|
+
|
36
|
+
report['9142'] = (report['9142'] || BigDecimal('0')) + res['HA']
|
37
|
+
res['9142'] = report['9142']
|
38
|
+
res['10'] = sum_matched(report, /^[12][0-9A-Z]{2,}/)
|
39
|
+
jp_4v = sum_matched(report, /^[4][V]{2,}/) # deferred assets for JP GAAP
|
40
|
+
res['30'] = sum_matched(report, /^[34][0-9A-Z]{2,}/) - jp_4v
|
41
|
+
res['4V'] = jp_4v if LucaSupport::CONFIG['country'] == 'jp'
|
42
|
+
res['50'] = sum_matched(report, /^[56][0-9A-Z]{2,}/)
|
43
|
+
res['70'] = sum_matched(report, /^[78][0-9A-Z]{2,}/)
|
44
|
+
res['91'] = sum_matched(report, /^91[0-9A-Z]{1,}/)
|
45
|
+
res['8ZZ'] = res['50'] + res['70']
|
46
|
+
res['9ZZ'] = sum_matched(report, /^[9][0-9A-Z]{2,}/)
|
47
|
+
|
48
|
+
res['1'] = res['10'] + res['30']
|
49
|
+
res['5'] = res['8ZZ'] + res['9ZZ']
|
50
|
+
res['_d'] = report['_d']
|
51
|
+
|
52
|
+
report.each do |k, v|
|
53
|
+
res[k] ||= sum_matched(report, /^#{k}[0-9A-Z]{1,}/) if k.length == 2
|
54
|
+
res[k] = v if k.length == 3
|
55
|
+
end
|
56
|
+
|
57
|
+
report.each do |k, v|
|
58
|
+
if k.length >= 4
|
59
|
+
if res[k[0, 3]]
|
60
|
+
res[k[0, 3]] += v
|
61
|
+
else
|
62
|
+
res[k[0, 3]] = v
|
63
|
+
end
|
64
|
+
res[k] = v
|
65
|
+
end
|
66
|
+
end
|
67
|
+
res.sort.to_h
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def sum_matched(report, reg)
|
72
|
+
report.select { |k, v| reg.match(k)}.values.sum
|
73
|
+
end
|
74
|
+
|
75
|
+
# for assert purpose
|
76
|
+
#
|
77
|
+
def gross(start_year, start_month, end_year = nil, end_month = nil, code: nil, date_range: nil, rows: 4)
|
78
|
+
if ! date_range.nil?
|
79
|
+
raise if date_range.class != Range
|
80
|
+
# TODO: date based range search
|
81
|
+
end
|
82
|
+
|
83
|
+
end_year ||= start_year
|
84
|
+
end_month ||= start_month
|
85
|
+
sum = { debit: {}, credit: {}, debit_count: {}, credit_count: {} }
|
86
|
+
idx_memo = []
|
87
|
+
term(start_year, start_month, end_year, end_month, code) do |f, _path|
|
88
|
+
CSV.new(f, headers: false, col_sep: "\t", encoding: 'UTF-8')
|
89
|
+
.each_with_index do |row, i|
|
90
|
+
break if i >= rows
|
91
|
+
|
92
|
+
case i
|
93
|
+
when 0
|
94
|
+
idx_memo = row.map(&:to_s)
|
95
|
+
next if code && !idx_memo.include?(code)
|
96
|
+
|
97
|
+
idx_memo.each do |r|
|
98
|
+
sum[:debit][r] ||= BigDecimal('0')
|
99
|
+
sum[:debit_count][r] ||= 0
|
100
|
+
end
|
101
|
+
when 1
|
102
|
+
next if code && !idx_memo.include?(code)
|
103
|
+
|
104
|
+
row.each_with_index do |r, j|
|
105
|
+
sum[:debit][idx_memo[j]] += BigDecimal(r.to_s)
|
106
|
+
sum[:debit_count][idx_memo[j]] += 1
|
107
|
+
end
|
108
|
+
when 2
|
109
|
+
idx_memo = row.map(&:to_s)
|
110
|
+
break if code && !idx_memo.include?(code)
|
111
|
+
|
112
|
+
idx_memo.each do |r|
|
113
|
+
sum[:credit][r] ||= BigDecimal('0')
|
114
|
+
sum[:credit_count][r] ||= 0
|
115
|
+
end
|
116
|
+
when 3
|
117
|
+
row.each_with_index do |r, j|
|
118
|
+
sum[:credit][idx_memo[j]] += BigDecimal(r.to_s)
|
119
|
+
sum[:credit_count][idx_memo[j]] += 1
|
120
|
+
end
|
121
|
+
else
|
122
|
+
puts row # for debug
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
if code
|
127
|
+
sum[:debit] = sum[:debit][code] || BigDecimal('0')
|
128
|
+
sum[:credit] = sum[:credit][code] || BigDecimal('0')
|
129
|
+
sum[:debit_count] = sum[:debit_count][code] || 0
|
130
|
+
sum[:credit_count] = sum[:credit_count][code] || 0
|
131
|
+
end
|
132
|
+
sum
|
133
|
+
end
|
134
|
+
|
135
|
+
# netting vouchers in specified term
|
136
|
+
#
|
137
|
+
def net(start_year, start_month, end_year = nil, end_month = nil, code: nil, date_range: nil)
|
138
|
+
g = gross(start_year, start_month, end_year, end_month, code: code, date_range: date_range)
|
139
|
+
idx = (g[:debit].keys + g[:credit].keys).uniq.sort
|
140
|
+
count = {}
|
141
|
+
diff = {}.tap do |sum|
|
142
|
+
idx.each do |code|
|
143
|
+
sum[code] = g.dig(:debit, code).nil? ? BigDecimal('0') : LucaBook::Util.calc_diff(g[:debit][code], code)
|
144
|
+
sum[code] -= g.dig(:credit, code).nil? ? BigDecimal('0') : LucaBook::Util.calc_diff(g[:credit][code], code)
|
145
|
+
count[code] = (g.dig(:debit_count, code) || 0) + (g.dig(:credit_count, code) || 0)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
[diff, count]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/luca_book/console.rb
CHANGED
@@ -49,7 +49,7 @@ class LucaBookConsole
|
|
49
49
|
print " " if h[:code].length > 3
|
50
50
|
end
|
51
51
|
puts cnsl_label(h[:label], h[:code])
|
52
|
-
h[:
|
52
|
+
h[:amount].each_slice(6) do |v|
|
53
53
|
puts "#{cnsl_fmt("", 14)} #{v.map{|v| cnsl_fmt(v, 14)}.join}"
|
54
54
|
end
|
55
55
|
end
|
@@ -72,13 +72,13 @@ class LucaBookConsole
|
|
72
72
|
end
|
73
73
|
convert_collection(report).each do |h|
|
74
74
|
if /^[A-Z]/.match(h[:code])
|
75
|
-
total = [h[:
|
75
|
+
total = [h[:amount].inject(:+)] + Array.new(h[:amount].length)
|
76
76
|
if /[^0]$/.match(h[:code])
|
77
77
|
print " "
|
78
78
|
print " " if h[:code].length > 3
|
79
79
|
end
|
80
80
|
puts cnsl_label(h[:label], h[:code])
|
81
|
-
h[:
|
81
|
+
h[:amount].each_slice(6).with_index(0) do |v, i|
|
82
82
|
puts "#{cnsl_fmt(total[i], 14)} #{v.map{|v| cnsl_fmt(v, 14)}.join}"
|
83
83
|
end
|
84
84
|
end
|
@@ -98,7 +98,7 @@ class LucaBookConsole
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
}.sort.map do |k,v|
|
101
|
-
{code: k, label: @report.dict.dig(k, :label),
|
101
|
+
{code: k, label: @report.dict.dig(k, :label), amount: v}
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
data/lib/luca_book/dict.rb
CHANGED
@@ -1,20 +1,184 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'luca_support/code'
|
3
4
|
require 'luca_support/config'
|
4
5
|
require 'luca_record/dict'
|
6
|
+
require 'luca_record/io'
|
7
|
+
require 'luca_book'
|
5
8
|
require 'date'
|
6
9
|
require 'pathname'
|
7
10
|
|
8
11
|
module LucaBook
|
9
12
|
class Dict < LucaRecord::Dict
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
13
|
+
include Accumulator
|
14
|
+
include LucaRecord::IO
|
15
|
+
|
16
|
+
@dirname = 'journals'
|
17
|
+
@record_type = 'raw'
|
18
|
+
# Column number settings for CSV/TSV convert
|
19
|
+
#
|
20
|
+
# :label
|
21
|
+
# for double entry data
|
22
|
+
# :counter_label
|
23
|
+
# must be specified with label
|
24
|
+
# :debit_label
|
25
|
+
# for double entry data
|
26
|
+
# * debit_amount
|
27
|
+
# :credit_label
|
28
|
+
# for double entry data
|
29
|
+
# * credit_amount
|
30
|
+
# :note
|
31
|
+
# can be the same column as another label
|
32
|
+
#
|
33
|
+
# :encoding
|
34
|
+
# file encoding
|
35
|
+
#
|
36
|
+
def csv_config
|
37
|
+
{}.tap do |config|
|
38
|
+
if @config.dig('label')
|
39
|
+
config[:label] = @config['label'].to_i
|
40
|
+
if @config.dig('counter_label')
|
41
|
+
config[:counter_label] = @config['counter_label']
|
42
|
+
config[:type] = 'single'
|
43
|
+
end
|
44
|
+
elsif @config.dig('debit_label')
|
45
|
+
config[:debit_label] = @config['debit_label'].to_i
|
46
|
+
if @config.dig('credit_label')
|
47
|
+
config[:credit_label] = @config['credit_label'].to_i
|
48
|
+
config[:type] = 'double'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
config[:type] ||= 'invalid'
|
52
|
+
config[:debit_amount] = @config['debit_amount'].to_i if @config.dig('debit_amount')
|
53
|
+
config[:credit_amount] = @config['credit_amount'].to_i if @config.dig('credit_amount')
|
54
|
+
config[:note] = @config['note'] if @config.dig('note')
|
55
|
+
config[:encoding] = @config['encoding'] if @config.dig('encoding')
|
56
|
+
|
57
|
+
config[:year] = @config['year'] if @config.dig('year')
|
58
|
+
config[:month] = @config['month'] if @config.dig('month')
|
59
|
+
config[:day] = @config['day'] if @config.dig('day')
|
60
|
+
config[:default_debit] = @config['default_debit'] if @config.dig('default_debit')
|
61
|
+
config[:default_credit] = @config['default_credit'] if @config.dig('default_credit')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def search(word, default_word = nil, amount = nil)
|
66
|
+
res = super(word, default_word, main_key: 'account_label')
|
67
|
+
if res.is_a?(Array) && res[0].is_a?(Array)
|
68
|
+
filter_amount(res, amount)
|
69
|
+
else
|
70
|
+
res
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Choose setting on Big or small condition.
|
75
|
+
#
|
76
|
+
def filter_amount(settings, amount = nil)
|
77
|
+
return settings[0] if amount.nil?
|
78
|
+
|
79
|
+
settings.each do |item|
|
80
|
+
return item unless item[1].keys.include?(:on_amount)
|
81
|
+
|
82
|
+
condition = item.dig(1, :on_amount)
|
83
|
+
case condition[0]
|
84
|
+
when '>'
|
85
|
+
return item if amount > BigDecimal(condition[1..])
|
86
|
+
when '<'
|
87
|
+
return item if amount < BigDecimal(condition[1..])
|
88
|
+
else
|
89
|
+
return item
|
90
|
+
end
|
91
|
+
end
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
# Find balance at financial year start by given date.
|
96
|
+
# If not found 'start-yyyy-mm-*.tsv', use 'start.tsv' as default.
|
97
|
+
#
|
98
|
+
def self.latest_balance(date)
|
99
|
+
load_tsv_dict(latest_balance_path(date))
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.latest_balance_path(date)
|
103
|
+
start_year = date.month >= LucaSupport::CONFIG['fy_start'] ? date.year : date.year - 1
|
104
|
+
latest = Date.new(start_year, LucaSupport::CONFIG['fy_start'], 1).prev_month
|
105
|
+
dict_dir = Pathname(LucaSupport::PJDIR) / 'data' / 'balance'
|
106
|
+
fileglob = %Q(start-#{latest.year}-#{format("%02d", latest.month)}-*)
|
107
|
+
path = Dir.glob(fileglob, base: dict_dir)[0] || 'start.tsv'
|
108
|
+
dict_dir / path
|
14
109
|
end
|
15
110
|
|
16
111
|
def self.issue_date(obj)
|
17
112
|
Date.parse(obj.dig('_date', :label))
|
18
113
|
end
|
114
|
+
|
115
|
+
def self.generate_balance(year, month = nil)
|
116
|
+
start_date = Date.new((year.to_i - 1), LucaSupport::CONFIG['fy_start'], 1)
|
117
|
+
month ||= LucaSupport::CONFIG['fy_start'] - 1
|
118
|
+
end_date = Date.new(year.to_i, month, -1)
|
119
|
+
labels = load('base.tsv')
|
120
|
+
bs = load_balance(start_date, end_date)
|
121
|
+
fy_digest = checksum(start_date, end_date)
|
122
|
+
current_ref = gitref
|
123
|
+
csv = CSV.generate(String.new, col_sep: "\t", headers: false) do |f|
|
124
|
+
f << ['code', 'label', 'balance']
|
125
|
+
f << ['_date', end_date]
|
126
|
+
f << ['_digest', fy_digest]
|
127
|
+
f << ['_gitref', current_ref] if current_ref
|
128
|
+
bs.each do |code, balance|
|
129
|
+
next if LucaSupport::Code.readable(balance) == 0
|
130
|
+
|
131
|
+
f << [code, labels.dig(code, :label), LucaSupport::Code.readable(balance)]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
dict_dir = Pathname(LucaSupport::PJDIR) / 'data' / 'balance'
|
135
|
+
filepath = dict_dir / "start-#{end_date.to_s}.tsv"
|
136
|
+
|
137
|
+
File.open(filepath, 'w') { |f| f.write csv }
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.load_balance(start_date, end_date)
|
141
|
+
base = latest_balance(start_date).each_with_object({}) do |(k, v), h|
|
142
|
+
h[k] = BigDecimal(v[:balance].to_s) if v[:balance]
|
143
|
+
end
|
144
|
+
|
145
|
+
search_range = term_by_month(start_date, end_date)
|
146
|
+
bs = search_range.each_with_object(base) do |date, h|
|
147
|
+
net(date.year, date.month)[0].each do |code, amount|
|
148
|
+
next if /^[^1-9]/.match(code)
|
149
|
+
|
150
|
+
h[code] ||= BigDecimal('0')
|
151
|
+
h[code] += amount
|
152
|
+
end
|
153
|
+
end
|
154
|
+
bs['9142'] ||= BigDecimal('0')
|
155
|
+
bs['9142'] += LucaBook::State
|
156
|
+
.range(start_date.year, start_date.month, end_date.year, end_date.month)
|
157
|
+
.net_income
|
158
|
+
bs.sort
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.checksum(start_date, end_date)
|
162
|
+
digest = update_digest(String.new, File.read(latest_balance_path(start_date)))
|
163
|
+
term_by_month(start_date, end_date)
|
164
|
+
.map { |date| dir_digest(date.year, date.month) }
|
165
|
+
.each { |month_digest| digest = update_digest(digest, month_digest) }
|
166
|
+
digest
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.gitref
|
170
|
+
digest = `git rev-parse HEAD`
|
171
|
+
$?.exitstatus == 0 ? digest.strip : nil
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.term_by_month(start_date, end_date)
|
175
|
+
Enumerator.new do |yielder|
|
176
|
+
each_month = start_date
|
177
|
+
while each_month <= end_date
|
178
|
+
yielder << each_month
|
179
|
+
each_month = each_month.next_month
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
19
183
|
end
|
20
184
|
end
|