lucabook 0.2.22 → 0.2.27
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 +78 -5
- data/lib/luca_book.rb +2 -0
- data/lib/luca_book/accumulator.rb +160 -0
- data/lib/luca_book/console.rb +4 -4
- data/lib/luca_book/dict.rb +95 -8
- data/lib/luca_book/import.rb +15 -13
- data/lib/luca_book/import_jp.rb +14 -14
- data/lib/luca_book/journal.rb +90 -32
- data/lib/luca_book/list.rb +47 -32
- data/lib/luca_book/list_by_header.rb +120 -0
- data/lib/luca_book/setup.rb +2 -1
- data/lib/luca_book/state.rb +157 -189
- 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 +25 -30
- 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/util.rb +1 -1
- data/lib/luca_book/version.rb +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad4b1eba3419508d64bfeade5f776ed2ca0f32313deab25660864aff47767787
|
4
|
+
data.tar.gz: 57aef956cbc763dbf4d089d5745c9cc4d6a6ed44dd3ff726b9c80fbc6b29629d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 317b373f1a31c85c107dfa294ec6df0dcc89f13b8ab10277bb004b3bdceaaf0ec58cfa5c2220a088932965b5b156fd71fef1ce67be67c330857bbcea127de365
|
7
|
+
data.tar.gz: e7456d393a4309bafb9a3622c28da10067e1483c80b0bbbd73a05508bb3d9a6742051e2d190870a1f6e39df5ec10b5dd7d1de698faef969a3bc54efa7b46b153
|
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'], recursive: params[:recursive]).list_by_code(params[:recursive]), params)
|
28
|
+
end
|
24
29
|
else
|
25
30
|
render(LucaBook::List.term(*args).list_journals, params)
|
26
31
|
end
|
@@ -29,14 +34,30 @@ class LucaCmd
|
|
29
34
|
def self.stats(args, params)
|
30
35
|
args = gen_range(params[:n]) if args.empty?
|
31
36
|
if params['code']
|
32
|
-
render(LucaBook::State.by_code(params['code'], *args), params)
|
37
|
+
render(LucaBook::State.by_code(params['code'], *args, recursive: params[:recursive]), params)
|
33
38
|
else
|
34
39
|
render(LucaBook::State.range(*args).stats(params[:level]), params)
|
35
40
|
end
|
36
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.'
|
49
|
+
end
|
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
|
@@ -49,6 +70,12 @@ class LucaCmd
|
|
49
70
|
args = gen_range(params[:n]) if args.empty?
|
50
71
|
render(LucaBook::State.range(*args).pl(level), params)
|
51
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)
|
78
|
+
end
|
52
79
|
end
|
53
80
|
|
54
81
|
def self.gen_range(count)
|
@@ -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 = {})
|
@@ -91,9 +124,12 @@ when /journals?/, 'j'
|
|
91
124
|
LucaCmd::Journal.import(args, params)
|
92
125
|
end
|
93
126
|
when 'list'
|
127
|
+
params[:recursive] = false
|
94
128
|
OptionParser.new do |opt|
|
95
129
|
opt.banner = 'Usage: luca-book journals list [options] [YYYY M]'
|
96
|
-
opt.on('-c', '--code VAL', '
|
130
|
+
opt.on('-c', '--code VAL', 'filter with code or label') { |v| params['code'] = v }
|
131
|
+
opt.on('-r', '--recursive', 'include subaccounts') { |_v| params[:recursive] = true }
|
132
|
+
opt.on('--customer', 'categorize by x-customer header') { |_v| params['headers'] = 'x-customer' }
|
97
133
|
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
98
134
|
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
99
135
|
opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
|
@@ -101,10 +137,22 @@ when /journals?/, 'j'
|
|
101
137
|
args = opt.parse!(ARGV)
|
102
138
|
LucaCmd::Journal.list(args, params)
|
103
139
|
end
|
140
|
+
when 'set'
|
141
|
+
OptionParser.new do |opt|
|
142
|
+
opt.banner = 'Usage: luca-book journals set [options] [YYYY M]'
|
143
|
+
opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
|
144
|
+
opt.on('--header VAL', 'header key') { |v| params[:key] = v }
|
145
|
+
opt.on('--val VAL', 'header value') { |v| params[:value] = v }
|
146
|
+
opt.on_tail('set header to journals on specified code.')
|
147
|
+
args = opt.parse!(ARGV)
|
148
|
+
LucaCmd::Journal.add_header(args, params)
|
149
|
+
end
|
104
150
|
when 'stats'
|
151
|
+
params[:recursive] = false
|
105
152
|
OptionParser.new do |opt|
|
106
153
|
opt.banner = 'Usage: luca-book journals stats [options] [YYYY M]'
|
107
|
-
opt.on('-c', '--code VAL', '
|
154
|
+
opt.on('-c', '--code VAL', 'filter with code or label') { |v| params['code'] = v }
|
155
|
+
opt.on('-r', '--recursive', 'include subaccounts') { |_v| params[:recursive] = true }
|
108
156
|
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
109
157
|
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
110
158
|
opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
|
@@ -130,6 +178,13 @@ when 'new'
|
|
130
178
|
when /reports?/, 'r'
|
131
179
|
subcmd = ARGV.shift
|
132
180
|
case subcmd
|
181
|
+
when 'xbrl'
|
182
|
+
OptionParser.new do |opt|
|
183
|
+
opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
|
184
|
+
opt.on('-o', '--output VAL', 'output filename') { |v| params[:output] = v }
|
185
|
+
args = opt.parse!(ARGV)
|
186
|
+
LucaCmd::Report.xbrl(args, params)
|
187
|
+
end
|
133
188
|
when 'bs'
|
134
189
|
OptionParser.new do |opt|
|
135
190
|
opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
|
@@ -150,6 +205,14 @@ when /reports?/, 'r'
|
|
150
205
|
args = opt.parse!(ARGV)
|
151
206
|
LucaCmd::Report.profitloss(args, params)
|
152
207
|
end
|
208
|
+
when 'mail'
|
209
|
+
OptionParser.new do |opt|
|
210
|
+
opt.banner = 'Usage: luca-book reports mail [options] [YYYY M YYYY M]'
|
211
|
+
opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
|
212
|
+
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
213
|
+
args = opt.parse!(ARGV)
|
214
|
+
LucaCmd::Report.report_mail(args, params)
|
215
|
+
end
|
153
216
|
else
|
154
217
|
puts 'Proper subcommand needed.'
|
155
218
|
puts
|
@@ -158,6 +221,16 @@ when /reports?/, 'r'
|
|
158
221
|
puts ' pl: show statement of income'
|
159
222
|
exit 1
|
160
223
|
end
|
224
|
+
when /balance/
|
225
|
+
subcmd = ARGV.shift
|
226
|
+
case subcmd
|
227
|
+
when 'update'
|
228
|
+
OptionParser.new do |opt|
|
229
|
+
opt.banner = 'Usage: luca-book balance update YYYY [M]'
|
230
|
+
args = opt.parse!(ARGV)
|
231
|
+
LucaCmd::Dict.update_balance(args, params)
|
232
|
+
end
|
233
|
+
end
|
161
234
|
else
|
162
235
|
puts 'Proper subcommand needed.'
|
163
236
|
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,160 @@
|
|
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, recursive: false)
|
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.select { |idx| /^#{code}/.match(idx) }.empty?
|
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.select { |idx| /^#{code}/.match(idx) }.empty?
|
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.select { |idx| /^#{code}/.match(idx) }.empty?
|
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
|
+
return sum if code.nil?
|
127
|
+
|
128
|
+
codes = if recursive
|
129
|
+
sum[:debit].keys.concat(sum[:credit].keys).uniq.select { |k| /^#{code}/.match(k) }
|
130
|
+
else
|
131
|
+
Array(code)
|
132
|
+
end
|
133
|
+
res = { debit: 0, credit: 0, debit_count: 0, credit_count: 0 }
|
134
|
+
codes.each do |code|
|
135
|
+
res[:debit] += sum[:debit][code] || BigDecimal('0')
|
136
|
+
res[:credit] += sum[:credit][code] || BigDecimal('0')
|
137
|
+
res[:debit_count] += sum[:debit_count][code] || 0
|
138
|
+
res[:credit_count] += sum[:credit_count][code] || 0
|
139
|
+
end
|
140
|
+
res
|
141
|
+
end
|
142
|
+
|
143
|
+
# netting vouchers in specified term
|
144
|
+
#
|
145
|
+
def net(start_year, start_month, end_year = nil, end_month = nil, code: nil, date_range: nil)
|
146
|
+
g = gross(start_year, start_month, end_year, end_month, code: code, date_range: date_range)
|
147
|
+
idx = (g[:debit].keys + g[:credit].keys).uniq.sort
|
148
|
+
count = {}
|
149
|
+
diff = {}.tap do |sum|
|
150
|
+
idx.each do |code|
|
151
|
+
sum[code] = g.dig(:debit, code).nil? ? BigDecimal('0') : LucaBook::Util.calc_diff(g[:debit][code], code)
|
152
|
+
sum[code] -= g.dig(:credit, code).nil? ? BigDecimal('0') : LucaBook::Util.calc_diff(g[:credit][code], code)
|
153
|
+
count[code] = (g.dig(:debit_count, code) || 0) + (g.dig(:credit_count, code) || 0)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
[diff, count]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
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,12 +1,20 @@
|
|
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
|
13
|
+
include Accumulator
|
14
|
+
include LucaRecord::IO
|
15
|
+
|
16
|
+
@dirname = 'journals'
|
17
|
+
@record_type = 'raw'
|
10
18
|
# Column number settings for CSV/TSV convert
|
11
19
|
#
|
12
20
|
# :label
|
@@ -15,10 +23,10 @@ module LucaBook
|
|
15
23
|
# must be specified with label
|
16
24
|
# :debit_label
|
17
25
|
# for double entry data
|
18
|
-
# *
|
26
|
+
# * debit_amount
|
19
27
|
# :credit_label
|
20
28
|
# for double entry data
|
21
|
-
# *
|
29
|
+
# * credit_amount
|
22
30
|
# :note
|
23
31
|
# can be the same column as another label
|
24
32
|
#
|
@@ -41,8 +49,8 @@ module LucaBook
|
|
41
49
|
end
|
42
50
|
end
|
43
51
|
config[:type] ||= 'invalid'
|
44
|
-
config[:
|
45
|
-
config[:
|
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')
|
46
54
|
config[:note] = @config['note'] if @config.dig('note')
|
47
55
|
config[:encoding] = @config['encoding'] if @config.dig('encoding')
|
48
56
|
|
@@ -84,14 +92,93 @@ module LucaBook
|
|
84
92
|
nil
|
85
93
|
end
|
86
94
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
91
109
|
end
|
92
110
|
|
93
111
|
def self.issue_date(obj)
|
94
112
|
Date.parse(obj.dig('_date', :label))
|
95
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
|
96
183
|
end
|
97
184
|
end
|