lucabook 0.2.24 → 0.2.29
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 +69 -9
- data/lib/luca_book.rb +2 -0
- data/lib/luca_book/accumulator.rb +212 -0
- data/lib/luca_book/dict.rb +82 -3
- data/lib/luca_book/journal.rb +2 -2
- data/lib/luca_book/list.rb +147 -29
- data/lib/luca_book/list_by_header.rb +1 -1
- 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/journals.html.erb +23 -0
- 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 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6e3ddc43f78a55be0357756cc617efd9fc798836cd771e4814a058e62abc6eb
|
|
4
|
+
data.tar.gz: b5b6d3419d2dbfcee9f859744c7513830a6576868a6a51241ea0ee04633c0c5f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f2c7c3b7043a60f39379be1c6e36b02367e2ec31261b0268768934c2f258cba62d3fc042f6286c5ac62b15289a89d1ac6e05143dcb9f2ecc04b5aa8d37bb9a89
|
|
7
|
+
data.tar.gz: 653e43c3f11785dabf83e8997810ae3316b8549b8370854e42bcc170ba54729dc50762e11e0733fbff2994fc15a37d6ac1eeb60c2581885f451acafeb57f5cdc
|
data/exe/luca-book
CHANGED
|
@@ -19,13 +19,15 @@ 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
|
+
elsif params['render']
|
|
30
|
+
puts LucaBook::List.term(*args).render_html(params['render'])
|
|
29
31
|
else
|
|
30
32
|
render(LucaBook::List.term(*args).list_journals, params)
|
|
31
33
|
end
|
|
@@ -34,7 +36,7 @@ class LucaCmd
|
|
|
34
36
|
def self.stats(args, params)
|
|
35
37
|
args = gen_range(params[:n]) if args.empty?
|
|
36
38
|
if params['code']
|
|
37
|
-
render(LucaBook::State.by_code(params['code'], *args), params)
|
|
39
|
+
render(LucaBook::State.by_code(params['code'], *args, recursive: params[:recursive]), params)
|
|
38
40
|
else
|
|
39
41
|
render(LucaBook::State.range(*args).stats(params[:level]), params)
|
|
40
42
|
end
|
|
@@ -51,6 +53,13 @@ class LucaCmd
|
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
class Report < LucaCmd
|
|
56
|
+
def self.xbrl(args, params)
|
|
57
|
+
level = params[:level] || 3
|
|
58
|
+
legal = params[:legal] || false
|
|
59
|
+
args = gen_range(params[:n] || 1) if args.empty?
|
|
60
|
+
LucaBook::State.range(*args).render_xbrl(params[:output])
|
|
61
|
+
end
|
|
62
|
+
|
|
54
63
|
def self.balancesheet(args, params)
|
|
55
64
|
level = params[:level] || 3
|
|
56
65
|
legal = params[:legal] || false
|
|
@@ -63,13 +72,27 @@ class LucaCmd
|
|
|
63
72
|
args = gen_range(params[:n]) if args.empty?
|
|
64
73
|
render(LucaBook::State.range(*args).pl(level), params)
|
|
65
74
|
end
|
|
75
|
+
|
|
76
|
+
def self.report_mail(args, params)
|
|
77
|
+
level = params[:level] || 3
|
|
78
|
+
args = gen_range(params[:n] || 12) if args.empty?
|
|
79
|
+
render(LucaBook::State.range(*args).report_mail(level), params)
|
|
80
|
+
end
|
|
66
81
|
end
|
|
67
82
|
|
|
68
|
-
def self.gen_range(count)
|
|
69
|
-
count ||= 3
|
|
83
|
+
def self.gen_range(count = nil)
|
|
70
84
|
today = Date.today
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
if count
|
|
86
|
+
start = today.prev_month(count - 1)
|
|
87
|
+
[start.year, start.month, today.year, today.month]
|
|
88
|
+
else
|
|
89
|
+
start_year = if today.month >= LucaSupport::CONFIG['fy_start'].to_i
|
|
90
|
+
today.year
|
|
91
|
+
else
|
|
92
|
+
today.year - 1
|
|
93
|
+
end
|
|
94
|
+
[start_year, LucaSupport::CONFIG['fy_start'], start_year + 1, LucaSupport::CONFIG['fy_start'].to_i - 1]
|
|
95
|
+
end
|
|
73
96
|
end
|
|
74
97
|
|
|
75
98
|
def self.render(dat, params)
|
|
@@ -82,6 +105,12 @@ class LucaCmd
|
|
|
82
105
|
puts YAML.dump(dat)
|
|
83
106
|
end
|
|
84
107
|
end
|
|
108
|
+
|
|
109
|
+
class Dict < LucaCmd
|
|
110
|
+
def self.update_balance(args, params)
|
|
111
|
+
LucaBook::Dict.generate_balance(*args)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
85
114
|
end
|
|
86
115
|
|
|
87
116
|
def new_pj(args = nil, params = {})
|
|
@@ -105,13 +134,17 @@ when /journals?/, 'j'
|
|
|
105
134
|
LucaCmd::Journal.import(args, params)
|
|
106
135
|
end
|
|
107
136
|
when 'list'
|
|
137
|
+
params[:recursive] = false
|
|
108
138
|
OptionParser.new do |opt|
|
|
109
139
|
opt.banner = 'Usage: luca-book journals list [options] [YYYY M]'
|
|
110
|
-
opt.on('-c', '--code VAL', '
|
|
140
|
+
opt.on('-c', '--code VAL', 'filter with code or label') { |v| params['code'] = v }
|
|
141
|
+
opt.on('-r', '--recursive', 'include subaccounts') { |_v| params[:recursive] = true }
|
|
111
142
|
opt.on('--customer', 'categorize by x-customer header') { |_v| params['headers'] = 'x-customer' }
|
|
112
143
|
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
|
113
144
|
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
|
114
145
|
opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
|
|
146
|
+
opt.on('--html', 'output journals html') { |_v| params['render'] = :html }
|
|
147
|
+
opt.on('--pdf', 'output journals PDF') { |_v| params['render'] = :pdf }
|
|
115
148
|
opt.on_tail('List records. If you specify code and/or month, search on each criteria.')
|
|
116
149
|
args = opt.parse!(ARGV)
|
|
117
150
|
LucaCmd::Journal.list(args, params)
|
|
@@ -127,9 +160,11 @@ when /journals?/, 'j'
|
|
|
127
160
|
LucaCmd::Journal.add_header(args, params)
|
|
128
161
|
end
|
|
129
162
|
when 'stats'
|
|
163
|
+
params[:recursive] = false
|
|
130
164
|
OptionParser.new do |opt|
|
|
131
165
|
opt.banner = 'Usage: luca-book journals stats [options] [YYYY M]'
|
|
132
|
-
opt.on('-c', '--code VAL', '
|
|
166
|
+
opt.on('-c', '--code VAL', 'filter with code or label') { |v| params['code'] = v }
|
|
167
|
+
opt.on('-r', '--recursive', 'include subaccounts') { |_v| params[:recursive] = true }
|
|
133
168
|
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
|
134
169
|
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
|
135
170
|
opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
|
|
@@ -155,6 +190,13 @@ when 'new'
|
|
|
155
190
|
when /reports?/, 'r'
|
|
156
191
|
subcmd = ARGV.shift
|
|
157
192
|
case subcmd
|
|
193
|
+
when 'xbrl'
|
|
194
|
+
OptionParser.new do |opt|
|
|
195
|
+
opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
|
|
196
|
+
opt.on('-o', '--output VAL', 'output filename') { |v| params[:output] = v }
|
|
197
|
+
args = opt.parse!(ARGV)
|
|
198
|
+
LucaCmd::Report.xbrl(args, params)
|
|
199
|
+
end
|
|
158
200
|
when 'bs'
|
|
159
201
|
OptionParser.new do |opt|
|
|
160
202
|
opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
|
|
@@ -175,6 +217,14 @@ when /reports?/, 'r'
|
|
|
175
217
|
args = opt.parse!(ARGV)
|
|
176
218
|
LucaCmd::Report.profitloss(args, params)
|
|
177
219
|
end
|
|
220
|
+
when 'mail'
|
|
221
|
+
OptionParser.new do |opt|
|
|
222
|
+
opt.banner = 'Usage: luca-book reports mail [options] [YYYY M YYYY M]'
|
|
223
|
+
opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
|
|
224
|
+
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
|
225
|
+
args = opt.parse!(ARGV)
|
|
226
|
+
LucaCmd::Report.report_mail(args, params)
|
|
227
|
+
end
|
|
178
228
|
else
|
|
179
229
|
puts 'Proper subcommand needed.'
|
|
180
230
|
puts
|
|
@@ -183,6 +233,16 @@ when /reports?/, 'r'
|
|
|
183
233
|
puts ' pl: show statement of income'
|
|
184
234
|
exit 1
|
|
185
235
|
end
|
|
236
|
+
when /balance/
|
|
237
|
+
subcmd = ARGV.shift
|
|
238
|
+
case subcmd
|
|
239
|
+
when 'update'
|
|
240
|
+
OptionParser.new do |opt|
|
|
241
|
+
opt.banner = 'Usage: luca-book balance update YYYY [M]'
|
|
242
|
+
args = opt.parse!(ARGV)
|
|
243
|
+
LucaCmd::Dict.update_balance(args, params)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
186
246
|
else
|
|
187
247
|
puts 'Proper subcommand needed.'
|
|
188
248
|
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/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
|
|
@@ -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
|