lucabook 0.2.23 → 0.2.28
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 +84 -9
- data/lib/luca_book.rb +2 -0
- data/lib/luca_book/accumulator.rb +212 -0
- data/lib/luca_book/console.rb +4 -4
- data/lib/luca_book/dict.rb +86 -7
- data/lib/luca_book/import.rb +15 -13
- data/lib/luca_book/import_jp.rb +14 -14
- data/lib/luca_book/journal.rb +52 -15
- data/lib/luca_book/list.rb +47 -32
- data/lib/luca_book/list_by_header.rb +4 -4
- data/lib/luca_book/state.rb +158 -196
- 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/dict-jp-edinet.tsv +167 -0
- data/lib/luca_book/templates/dict-jp.tsv +192 -166
- data/lib/luca_book/templates/monthly-report.html.erb +67 -0
- data/lib/luca_book/test.rb +17 -0
- data/lib/luca_book/util.rb +11 -1
- data/lib/luca_book/version.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0b2cc0f6aefd92521a518af25010f4fcb125e32aff9b71c9a18304b606451556
|
|
4
|
+
data.tar.gz: 6689918d842fca5a64060f998ea419aeb2f8db4350c7b0073e39b6a07b0dbb43
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 192298f7a6cd006f5699ec02a109c6f4e10f9240ebc7fc228af277265eab5b467e90bcbd58063f8381b61f258d99732d69155c90af4fac619d9c94da43a2fb27
|
|
7
|
+
data.tar.gz: 1231261012e602b438469cbcd357dc64a58c14357b8a0c63c4b5fa7dbe398f2b5080a3d53c404c63f543ff87f681f7fc38cb183ea35d5528ccbc905cdcebfaa1
|
data/exe/luca-book
CHANGED
|
@@ -19,12 +19,12 @@ class LucaCmd
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def self.list(args, params)
|
|
22
|
-
args = gen_range(params[:n]
|
|
22
|
+
args = gen_range(params[:n]) if args.empty?
|
|
23
23
|
if params['code']
|
|
24
24
|
if params['headers']
|
|
25
25
|
render(LucaBook::ListByHeader.term(*args, code: params['code'], header: params['headers']).list_by_code, params)
|
|
26
26
|
else
|
|
27
|
-
render(LucaBook::List.term(*args, code: params['code']).list_by_code, params)
|
|
27
|
+
render(LucaBook::List.term(*args, code: params['code'], recursive: params[:recursive]).list_by_code(params[:recursive]), params)
|
|
28
28
|
end
|
|
29
29
|
else
|
|
30
30
|
render(LucaBook::List.term(*args).list_journals, params)
|
|
@@ -34,14 +34,30 @@ class LucaCmd
|
|
|
34
34
|
def self.stats(args, params)
|
|
35
35
|
args = gen_range(params[:n]) if args.empty?
|
|
36
36
|
if params['code']
|
|
37
|
-
render(LucaBook::State.by_code(params['code'], *args), params)
|
|
37
|
+
render(LucaBook::State.by_code(params['code'], *args, recursive: params[:recursive]), params)
|
|
38
38
|
else
|
|
39
39
|
render(LucaBook::State.range(*args).stats(params[:level]), params)
|
|
40
40
|
end
|
|
41
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
|
|
42
51
|
end
|
|
43
52
|
|
|
44
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
|
+
|
|
45
61
|
def self.balancesheet(args, params)
|
|
46
62
|
level = params[:level] || 3
|
|
47
63
|
legal = params[:legal] || false
|
|
@@ -54,13 +70,27 @@ class LucaCmd
|
|
|
54
70
|
args = gen_range(params[:n]) if args.empty?
|
|
55
71
|
render(LucaBook::State.range(*args).pl(level), params)
|
|
56
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
|
|
57
79
|
end
|
|
58
80
|
|
|
59
|
-
def self.gen_range(count)
|
|
60
|
-
count ||= 3
|
|
81
|
+
def self.gen_range(count = nil)
|
|
61
82
|
today = Date.today
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
if count
|
|
84
|
+
start = today.prev_month(count - 1)
|
|
85
|
+
[start.year, start.month, today.year, today.month]
|
|
86
|
+
else
|
|
87
|
+
start_year = if today.month >= LucaSupport::CONFIG['fy_start'].to_i
|
|
88
|
+
today.year
|
|
89
|
+
else
|
|
90
|
+
today.year - 1
|
|
91
|
+
end
|
|
92
|
+
[start_year, LucaSupport::CONFIG['fy_start'], start_year + 1, LucaSupport::CONFIG['fy_start'].to_i - 1]
|
|
93
|
+
end
|
|
64
94
|
end
|
|
65
95
|
|
|
66
96
|
def self.render(dat, params)
|
|
@@ -73,6 +103,12 @@ class LucaCmd
|
|
|
73
103
|
puts YAML.dump(dat)
|
|
74
104
|
end
|
|
75
105
|
end
|
|
106
|
+
|
|
107
|
+
class Dict < LucaCmd
|
|
108
|
+
def self.update_balance(args, params)
|
|
109
|
+
LucaBook::Dict.generate_balance(*args)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
76
112
|
end
|
|
77
113
|
|
|
78
114
|
def new_pj(args = nil, params = {})
|
|
@@ -96,9 +132,11 @@ when /journals?/, 'j'
|
|
|
96
132
|
LucaCmd::Journal.import(args, params)
|
|
97
133
|
end
|
|
98
134
|
when 'list'
|
|
135
|
+
params[:recursive] = false
|
|
99
136
|
OptionParser.new do |opt|
|
|
100
137
|
opt.banner = 'Usage: luca-book journals list [options] [YYYY M]'
|
|
101
|
-
opt.on('-c', '--code VAL', '
|
|
138
|
+
opt.on('-c', '--code VAL', 'filter with code or label') { |v| params['code'] = v }
|
|
139
|
+
opt.on('-r', '--recursive', 'include subaccounts') { |_v| params[:recursive] = true }
|
|
102
140
|
opt.on('--customer', 'categorize by x-customer header') { |_v| params['headers'] = 'x-customer' }
|
|
103
141
|
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
|
104
142
|
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
|
@@ -107,10 +145,22 @@ when /journals?/, 'j'
|
|
|
107
145
|
args = opt.parse!(ARGV)
|
|
108
146
|
LucaCmd::Journal.list(args, params)
|
|
109
147
|
end
|
|
148
|
+
when 'set'
|
|
149
|
+
OptionParser.new do |opt|
|
|
150
|
+
opt.banner = 'Usage: luca-book journals set [options] [YYYY M]'
|
|
151
|
+
opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
|
|
152
|
+
opt.on('--header VAL', 'header key') { |v| params[:key] = v }
|
|
153
|
+
opt.on('--val VAL', 'header value') { |v| params[:value] = v }
|
|
154
|
+
opt.on_tail('set header to journals on specified code.')
|
|
155
|
+
args = opt.parse!(ARGV)
|
|
156
|
+
LucaCmd::Journal.add_header(args, params)
|
|
157
|
+
end
|
|
110
158
|
when 'stats'
|
|
159
|
+
params[:recursive] = false
|
|
111
160
|
OptionParser.new do |opt|
|
|
112
161
|
opt.banner = 'Usage: luca-book journals stats [options] [YYYY M]'
|
|
113
|
-
opt.on('-c', '--code VAL', '
|
|
162
|
+
opt.on('-c', '--code VAL', 'filter with code or label') { |v| params['code'] = v }
|
|
163
|
+
opt.on('-r', '--recursive', 'include subaccounts') { |_v| params[:recursive] = true }
|
|
114
164
|
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
|
115
165
|
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
|
116
166
|
opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
|
|
@@ -136,6 +186,13 @@ when 'new'
|
|
|
136
186
|
when /reports?/, 'r'
|
|
137
187
|
subcmd = ARGV.shift
|
|
138
188
|
case subcmd
|
|
189
|
+
when 'xbrl'
|
|
190
|
+
OptionParser.new do |opt|
|
|
191
|
+
opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
|
|
192
|
+
opt.on('-o', '--output VAL', 'output filename') { |v| params[:output] = v }
|
|
193
|
+
args = opt.parse!(ARGV)
|
|
194
|
+
LucaCmd::Report.xbrl(args, params)
|
|
195
|
+
end
|
|
139
196
|
when 'bs'
|
|
140
197
|
OptionParser.new do |opt|
|
|
141
198
|
opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
|
|
@@ -156,6 +213,14 @@ when /reports?/, 'r'
|
|
|
156
213
|
args = opt.parse!(ARGV)
|
|
157
214
|
LucaCmd::Report.profitloss(args, params)
|
|
158
215
|
end
|
|
216
|
+
when 'mail'
|
|
217
|
+
OptionParser.new do |opt|
|
|
218
|
+
opt.banner = 'Usage: luca-book reports mail [options] [YYYY M YYYY M]'
|
|
219
|
+
opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
|
|
220
|
+
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
|
221
|
+
args = opt.parse!(ARGV)
|
|
222
|
+
LucaCmd::Report.report_mail(args, params)
|
|
223
|
+
end
|
|
159
224
|
else
|
|
160
225
|
puts 'Proper subcommand needed.'
|
|
161
226
|
puts
|
|
@@ -164,6 +229,16 @@ when /reports?/, 'r'
|
|
|
164
229
|
puts ' pl: show statement of income'
|
|
165
230
|
exit 1
|
|
166
231
|
end
|
|
232
|
+
when /balance/
|
|
233
|
+
subcmd = ARGV.shift
|
|
234
|
+
case subcmd
|
|
235
|
+
when 'update'
|
|
236
|
+
OptionParser.new do |opt|
|
|
237
|
+
opt.banner = 'Usage: luca-book balance update YYYY [M]'
|
|
238
|
+
args = opt.parse!(ARGV)
|
|
239
|
+
LucaCmd::Dict.update_balance(args, params)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
167
242
|
else
|
|
168
243
|
puts 'Proper subcommand needed.'
|
|
169
244
|
puts
|
data/lib/luca_book.rb
CHANGED
|
@@ -5,6 +5,7 @@ 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'
|
|
@@ -12,5 +13,6 @@ module LucaBook
|
|
|
12
13
|
autoload :ListByHeader, 'luca_book/list_by_header'
|
|
13
14
|
autoload :Setup, 'luca_book/setup'
|
|
14
15
|
autoload :State, 'luca_book/state'
|
|
16
|
+
autoload :Test, 'luca_book/test'
|
|
15
17
|
autoload :Util, 'luca_book/util'
|
|
16
18
|
end
|
|
@@ -0,0 +1,212 @@
|
|
|
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, 'journals') 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: { code => 0 }, credit: { code => 0 }, debit_count: { code => 0 }, credit_count: { code => 0 } }
|
|
134
|
+
codes.each do |cd|
|
|
135
|
+
res[:debit][code] += sum[:debit][cd] || BigDecimal('0')
|
|
136
|
+
res[:credit][code] += sum[:credit][cd] || BigDecimal('0')
|
|
137
|
+
res[:debit_count][code] += sum[:debit_count][cd] || 0
|
|
138
|
+
res[:credit_count][code] += sum[:credit_count][cd] || 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, recursive: false)
|
|
146
|
+
g = gross(start_year, start_month, end_year, end_month, code: code, date_range: date_range, recursive: recursive)
|
|
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
|
+
|
|
159
|
+
# Override LucaRecord::IO.load_data
|
|
160
|
+
#
|
|
161
|
+
def load_data(io, path = nil)
|
|
162
|
+
io
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def net_amount(code, start_year = nil, start_month = nil, end_year = nil, end_month = nil, recursive: true)
|
|
167
|
+
start_year ||= @cursor_start&.year || @start_date.year
|
|
168
|
+
start_month ||= @cursor_start&.month || @start_date.month
|
|
169
|
+
end_year ||= @cursor_end&.year || @end_date.year
|
|
170
|
+
end_month ||= @cursor_end&.month || @end_date.month
|
|
171
|
+
self.class.net(start_year, start_month, end_year, end_month, code: code, recursive: recursive)[0][code]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def debit_amount(code, start_year = nil, start_month = nil, end_year = nil, end_month = nil, recursive: true)
|
|
175
|
+
gross_amount(code, start_year, start_month, end_year, end_month, recursive: recursive)[0]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def credit_amount(code, start_year = nil, start_month = nil, end_year = nil, end_month = nil, recursive: true)
|
|
179
|
+
gross_amount(code, start_year, start_month, end_year, end_month, recursive: recursive)[1]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def gross_amount(code, start_year = nil, start_month = nil, end_year = nil, end_month = nil, recursive: true)
|
|
183
|
+
start_year ||= @cursor_start&.year || @start_date.year
|
|
184
|
+
start_month ||= @cursor_start&.month || @start_date.month
|
|
185
|
+
end_year ||= @cursor_end&.year || @end_date.year
|
|
186
|
+
end_month ||= @cursor_end&.month || @end_date.month
|
|
187
|
+
g = self.class.gross(start_year, start_month, end_year, end_month, code: code, recursive: recursive)
|
|
188
|
+
[g[:debit][code], g[:credit][code]]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def debit_count(code, start_year = nil, start_month = nil, end_year = nil, end_month = nil, recursive: true)
|
|
192
|
+
gross_count(code, start_year, start_month, end_year, end_month, recursive: recursive)[0]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def credit_count(code, start_year = nil, start_month = nil, end_year = nil, end_month = nil, recursive: true)
|
|
196
|
+
gross_count(code, start_year, start_month, end_year, end_month, recursive: recursive)[1]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def gross_count(code, start_year = nil, start_month = nil, end_year = nil, end_month = nil, recursive: true)
|
|
200
|
+
start_year ||= @cursor_start&.year || @start_date.year
|
|
201
|
+
start_month ||= @cursor_start&.month || @start_date.month
|
|
202
|
+
end_year ||= @cursor_end&.year || @end_date.year
|
|
203
|
+
end_month ||= @cursor_end&.month || @end_date.month
|
|
204
|
+
g = self.class.gross(start_year, start_month, end_year, end_month, code: code, recursive: recursive)
|
|
205
|
+
[g[:debit_count][code], g[:credit_count][code]]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def each_month
|
|
209
|
+
yield
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
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,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'luca_support/code'
|
|
3
4
|
require 'luca_support/config'
|
|
5
|
+
require 'luca_support/range'
|
|
4
6
|
require 'luca_record/dict'
|
|
7
|
+
require 'luca_record/io'
|
|
8
|
+
require 'luca_book'
|
|
5
9
|
require 'date'
|
|
6
10
|
require 'pathname'
|
|
7
11
|
|
|
8
12
|
module LucaBook
|
|
9
13
|
class Dict < LucaRecord::Dict
|
|
14
|
+
include LucaSupport::Range
|
|
15
|
+
include LucaBook::Util
|
|
16
|
+
include LucaRecord::IO
|
|
17
|
+
include Accumulator
|
|
18
|
+
|
|
19
|
+
@dirname = 'journals'
|
|
10
20
|
# Column number settings for CSV/TSV convert
|
|
11
21
|
#
|
|
12
22
|
# :label
|
|
@@ -15,10 +25,10 @@ module LucaBook
|
|
|
15
25
|
# must be specified with label
|
|
16
26
|
# :debit_label
|
|
17
27
|
# for double entry data
|
|
18
|
-
# *
|
|
28
|
+
# * debit_amount
|
|
19
29
|
# :credit_label
|
|
20
30
|
# for double entry data
|
|
21
|
-
# *
|
|
31
|
+
# * credit_amount
|
|
22
32
|
# :note
|
|
23
33
|
# can be the same column as another label
|
|
24
34
|
#
|
|
@@ -41,8 +51,8 @@ module LucaBook
|
|
|
41
51
|
end
|
|
42
52
|
end
|
|
43
53
|
config[:type] ||= 'invalid'
|
|
44
|
-
config[:
|
|
45
|
-
config[:
|
|
54
|
+
config[:debit_amount] = @config['debit_amount'].to_i if @config.dig('debit_amount')
|
|
55
|
+
config[:credit_amount] = @config['credit_amount'].to_i if @config.dig('credit_amount')
|
|
46
56
|
config[:note] = @config['note'] if @config.dig('note')
|
|
47
57
|
config[:encoding] = @config['encoding'] if @config.dig('encoding')
|
|
48
58
|
|
|
@@ -84,14 +94,83 @@ module LucaBook
|
|
|
84
94
|
nil
|
|
85
95
|
end
|
|
86
96
|
|
|
87
|
-
|
|
97
|
+
# Find balance at financial year start by given date.
|
|
98
|
+
# If not found 'start-yyyy-mm-*.tsv', use 'start.tsv' as default.
|
|
99
|
+
#
|
|
100
|
+
def self.latest_balance(date)
|
|
101
|
+
load_tsv_dict(latest_balance_path(date))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.latest_balance_path(date)
|
|
105
|
+
start_year = date.month >= LucaSupport::CONFIG['fy_start'] ? date.year : date.year - 1
|
|
106
|
+
latest = Date.new(start_year, LucaSupport::CONFIG['fy_start'], 1).prev_month
|
|
88
107
|
dict_dir = Pathname(LucaSupport::PJDIR) / 'data' / 'balance'
|
|
89
|
-
|
|
90
|
-
|
|
108
|
+
fileglob = %Q(start-#{latest.year}-#{format("%02d", latest.month)}-*)
|
|
109
|
+
path = Dir.glob(fileglob, base: dict_dir)[0] || 'start.tsv'
|
|
110
|
+
dict_dir / path
|
|
91
111
|
end
|
|
92
112
|
|
|
93
113
|
def self.issue_date(obj)
|
|
94
114
|
Date.parse(obj.dig('_date', :label))
|
|
95
115
|
end
|
|
116
|
+
|
|
117
|
+
def self.generate_balance(year, month = nil)
|
|
118
|
+
start_date = Date.new((year.to_i - 1), LucaSupport::CONFIG['fy_start'], 1)
|
|
119
|
+
month ||= LucaSupport::CONFIG['fy_start'] - 1
|
|
120
|
+
end_date = Date.new(year.to_i, month, -1)
|
|
121
|
+
labels = load('base.tsv')
|
|
122
|
+
bs = load_balance(start_date, end_date)
|
|
123
|
+
fy_digest = checksum(start_date, end_date)
|
|
124
|
+
current_ref = gitref
|
|
125
|
+
csv = CSV.generate(String.new, col_sep: "\t", headers: false) do |f|
|
|
126
|
+
f << ['code', 'label', 'balance']
|
|
127
|
+
f << ['_date', end_date]
|
|
128
|
+
f << ['_digest', fy_digest]
|
|
129
|
+
f << ['_gitref', current_ref] if current_ref
|
|
130
|
+
bs.each do |code, balance|
|
|
131
|
+
next if LucaSupport::Code.readable(balance) == 0
|
|
132
|
+
|
|
133
|
+
f << [code, labels.dig(code, :label), LucaSupport::Code.readable(balance)]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
dict_dir = Pathname(LucaSupport::PJDIR) / 'data' / 'balance'
|
|
137
|
+
filepath = dict_dir / "start-#{end_date.to_s}.tsv"
|
|
138
|
+
|
|
139
|
+
File.open(filepath, 'w') { |f| f.write csv }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.load_balance(start_date, end_date)
|
|
143
|
+
base = latest_balance(start_date).each_with_object({}) do |(k, v), h|
|
|
144
|
+
h[k] = BigDecimal(v[:balance].to_s) if v[:balance]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
search_range = term_by_month(start_date, end_date)
|
|
148
|
+
bs = search_range.each_with_object(base) do |date, h|
|
|
149
|
+
net(date.year, date.month)[0].each do |code, amount|
|
|
150
|
+
next if /^[^1-9]/.match(code)
|
|
151
|
+
|
|
152
|
+
h[code] ||= BigDecimal('0')
|
|
153
|
+
h[code] += amount
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
bs['9142'] ||= BigDecimal('0')
|
|
157
|
+
bs['9142'] += LucaBook::State
|
|
158
|
+
.range(start_date.year, start_date.month, end_date.year, end_date.month)
|
|
159
|
+
.net_income
|
|
160
|
+
bs.sort
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def self.checksum(start_date, end_date)
|
|
164
|
+
digest = update_digest(String.new, File.read(latest_balance_path(start_date)))
|
|
165
|
+
term_by_month(start_date, end_date)
|
|
166
|
+
.map { |date| dir_digest(date.year, date.month) }
|
|
167
|
+
.each { |month_digest| digest = update_digest(digest, month_digest) }
|
|
168
|
+
digest
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def self.gitref
|
|
172
|
+
digest = `git rev-parse HEAD`
|
|
173
|
+
$?.exitstatus == 0 ? digest.strip : nil
|
|
174
|
+
end
|
|
96
175
|
end
|
|
97
176
|
end
|