lucabook 0.2.15 → 0.2.21

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db7564331de8046e8a6507c2ccf7dc099382ff3fc1c6a603f9ecc290755398ba
4
- data.tar.gz: 5fb64e6fbccc5a5e693211ae89d8bad6560593add1f26261879b2ae4bc67fa00
3
+ metadata.gz: 3f7366698bee6b770fbae39f8fe650e1f460b04dec25c8036c84013f970adaa3
4
+ data.tar.gz: 8ecbf412b31f001f0cfa2de27ceb879463d17f28e3db552770866b44c0b73114
5
5
  SHA512:
6
- metadata.gz: 4cee8065abe4d479dc909e2e9f12f0fdb942e7b3c51b2f5f5e993598080db28411625d990c7f7a7ec85ebc96686a4dcf6d0173e8fed58fd3a3261731ea3da831
7
- data.tar.gz: 1cd81e73a4dfcf3eef52a8105b7e1dc921f9d79d8b9945bf2b9e74d80134d2ca157346d69d98a26037d0f087fa4475e05938ae0044a7b1fcb3120b744f3a9ba8
6
+ metadata.gz: e30fabd50119ad470c90512bcf066b3992b5df2d67139f437a5f4336d73c843fb65dab2a70b5914c190155de68f82ec722840e0f056079061634f77bb5da4964
7
+ data.tar.gz: 3e8775200e05f8bfe0923b53690fa5245441ea4d71768246a7309ee503294d847a466e82da85568d3071f47eccd4f9783a79199b4d4eb630c961de84488a31ed
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
+ require 'json'
3
4
  require 'optparse'
4
5
  require 'luca_book'
5
6
 
6
- module LucaCmd
7
- class Journal
7
+ class LucaCmd
8
+ class Journal < LucaCmd
8
9
  def self.import(args, params)
9
10
  if params['config']
10
11
  LucaBook::Import.new(args[0], params['config']).import_csv
@@ -12,31 +13,66 @@ module LucaCmd
12
13
  LucaBook::Import.import_json(STDIN.read)
13
14
  else
14
15
  puts 'Usage: luca-book import -c import_config'
16
+ exit 1
15
17
  end
16
18
  end
17
19
 
18
20
  def self.list(args, params)
21
+ args = gen_range(params[:n] || 1) if args.empty?
19
22
  if params['code']
20
- LucaBook::List.term(*args, code: params['code']).flat_list.to_yaml
21
- elsif args.length > 0
22
- LucaBook::List.term(*args).flat_list.to_yaml
23
+ render(LucaBook::List.term(*args, code: params['code']).list_on_code, params)
23
24
  else
24
- # TODO: define default function
25
+ render(LucaBook::List.term(*args).list_journals, params)
26
+ end
27
+ end
28
+
29
+ def self.stats(args, params)
30
+ args = gen_range(params[:n]) if args.empty?
31
+ if params['code']
32
+ render(LucaBook::State.by_code(params['code'], *args), params)
33
+ else
34
+ render(LucaBook::State.term(*args).stats(params[:level]), params)
25
35
  end
26
36
  end
27
37
  end
28
38
 
29
- class Report
39
+ class Report < LucaCmd
30
40
  def self.balancesheet(args, params)
31
- LucaBook::State.term(*args).bs.to_yaml
41
+ level = params[:level] || 3
42
+ legal = params[:legal] || false
43
+ args = gen_range(params[:n] || 1) if args.empty?
44
+ render(LucaBook::State.term(*args).bs(level, legal: legal), params)
32
45
  end
33
46
 
34
47
  def self.profitloss(args, params)
35
- LucaBook::State.term(*args).pl.to_yaml
48
+ level = params[:level] || 2
49
+ args = gen_range(params[:n]) if args.empty?
50
+ render(LucaBook::State.term(*args).pl(level), params)
51
+ end
52
+ end
53
+
54
+ def self.gen_range(count)
55
+ count ||= 3
56
+ today = Date.today
57
+ start = today.prev_month(count - 1)
58
+ [start.year, start.month, today.year, today.month]
59
+ end
60
+
61
+ def self.render(dat, params)
62
+ case params[:output]
63
+ when 'json'
64
+ puts JSON.dump(dat)
65
+ when 'nu'
66
+ LucaSupport::View.nushell(YAML.dump(dat))
67
+ else
68
+ puts YAML.dump(dat)
36
69
  end
37
70
  end
38
71
  end
39
72
 
73
+ def new_pj(args = nil, params = {})
74
+ LucaBook::Setup.create_project params['country'], args[0]
75
+ end
40
76
 
41
77
  LucaRecord::Base.valid_project?
42
78
  cmd = ARGV.shift
@@ -48,41 +84,85 @@ when /journals?/, 'j'
48
84
  case subcmd
49
85
  when 'import'
50
86
  OptionParser.new do |opt|
51
- opt.banner = 'Usage: luca import filepath'
52
- opt.on('-c', '--config VAL', 'import definition'){|v| params['config'] = v }
53
- opt.on('-j', '--json', 'import via json format'){|_v| params['json'] = true }
87
+ opt.banner = 'Usage: luca-book journals import [options] filepath'
88
+ opt.on('-c', '--config VAL', 'import definition'){ |v| params['config'] = v }
89
+ opt.on('-j', '--json', 'import via json format'){ |_v| params['json'] = true }
54
90
  args = opt.parse!(ARGV)
55
91
  LucaCmd::Journal.import(args, params)
56
92
  end
57
93
  when 'list'
58
94
  OptionParser.new do |opt|
59
- opt.banner = 'Usage: luca list [year month]'
95
+ opt.banner = 'Usage: luca-book journals list [options] [YYYY M]'
60
96
  opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
97
+ opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
98
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
99
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
61
100
  opt.on_tail('List records. If you specify code and/or month, search on each criteria.')
62
101
  args = opt.parse!(ARGV)
63
102
  LucaCmd::Journal.list(args, params)
64
103
  end
104
+ when 'stats'
105
+ OptionParser.new do |opt|
106
+ opt.banner = 'Usage: luca-book journals stats [options] [YYYY M]'
107
+ opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
108
+ opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
109
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
110
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
111
+ args = opt.parse!(ARGV)
112
+ LucaCmd::Journal.stats(args, params)
113
+ end
114
+ else
115
+ puts 'Proper subcommand needed.'
116
+ puts
117
+ puts 'Usage: luca-book (j|journal[s]) subcommand [options] [YYYY M YYYY M]'
118
+ puts ' import: import journals from JSON/TSV'
119
+ puts ' list: list journals'
120
+ puts ' stats: list account statistics'
121
+ exit 1
122
+ end
123
+ when 'new'
124
+ OptionParser.new do |opt|
125
+ opt.banner = 'Usage: luca-book new [options] Dir'
126
+ opt.on('-c', '--country VAL', 'specify country code') { |v| params['coountry'] = v }
127
+ args = opt.parse(ARGV)
128
+ new_pj(args, params)
65
129
  end
66
130
  when /reports?/, 'r'
67
131
  subcmd = ARGV.shift
68
132
  case subcmd
69
133
  when 'bs'
70
134
  OptionParser.new do |opt|
71
- opt.banner = 'Usage: luca-book reports bs'
135
+ opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
136
+ opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
137
+ opt.on('--legal', 'show legal mandatory account') { |_v| params[:legal] = true }
138
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
139
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
72
140
  args = opt.parse!(ARGV)
73
141
  LucaCmd::Report.balancesheet(args, params)
74
142
  end
75
143
  when 'pl'
76
144
  OptionParser.new do |opt|
77
- opt.banner = 'Usage: luca-book reports pl'
145
+ opt.banner = 'Usage: luca-book reports pl [options] [YYYY M YYYY M]'
146
+ opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
147
+ opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
148
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
149
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
78
150
  args = opt.parse!(ARGV)
79
151
  LucaCmd::Report.profitloss(args, params)
80
152
  end
153
+ else
154
+ puts 'Proper subcommand needed.'
155
+ puts
156
+ puts 'Usage: luca-book (r|report[s]) (bs|pl) [options] YYYY M'
157
+ puts ' bs: show balance sheet'
158
+ puts ' pl: show statement of income'
159
+ exit 1
81
160
  end
82
- when '--help'
83
- puts 'Usage: luca-book subcommand'
161
+ else
162
+ puts 'Proper subcommand needed.'
163
+ puts
164
+ puts 'Usage: luca-book (j[ournals]|r[eports]) subcommand'
84
165
  puts ' journals: operate journal records'
85
166
  puts ' reports: show reports'
86
- else
87
- puts 'Invalid subcommand'
167
+ exit 1
88
168
  end
@@ -5,9 +5,11 @@ require 'luca_record'
5
5
  require 'luca_book/version'
6
6
 
7
7
  module LucaBook
8
+ autoload :Dict, 'luca_book/dict'
8
9
  autoload :Import, 'luca_book/import'
9
10
  autoload :Journal, 'luca_book/journal'
10
11
  autoload :List, 'luca_book/list'
11
12
  autoload :Setup, 'luca_book/setup'
12
13
  autoload :State, 'luca_book/state'
14
+ autoload :Util, 'luca_book/util'
13
15
  end
@@ -29,6 +29,7 @@ class LucaBookConsole
29
29
  end
30
30
  end
31
31
 
32
+ # TODO: deprecated. accumulate_all() already removed.
32
33
  def bs
33
34
  target = []
34
35
  report = []
@@ -56,6 +57,7 @@ class LucaBookConsole
56
57
  puts "---- ----"
57
58
  end
58
59
 
60
+ # TODO: deprecated. accumulate_all() already removed.
59
61
  def pl
60
62
  target = []
61
63
  report = []
@@ -2,12 +2,19 @@
2
2
 
3
3
  require 'luca_support/config'
4
4
  require 'luca_record/dict'
5
+ require 'date'
6
+ require 'pathname'
5
7
 
6
8
  module LucaBook
7
9
  class Dict < LucaRecord::Dict
10
+ def self.latest_balance
11
+ dict_dir = Pathname(LucaSupport::Config::Pjdir) / 'data' / 'balance'
12
+ # TODO: search latest balance dictionary
13
+ load_tsv_dict(dict_dir / 'start.tsv')
14
+ end
8
15
 
9
- @filename = 'dict.tsv'
10
-
11
- Data = load
16
+ def self.issue_date(obj)
17
+ Date.parse(obj.dig('_date', :label))
18
+ end
12
19
  end
13
20
  end
@@ -9,14 +9,15 @@ require 'luca_record'
9
9
 
10
10
  module LucaBook
11
11
  class Import
12
- DEBIT_DEFAULT = '仮払金'
13
- CREDIT_DEFAULT = '仮受金'
12
+ DEBIT_DEFAULT = '10XX'
13
+ CREDIT_DEFAULT = '50XX'
14
14
 
15
15
  def initialize(path, dict)
16
16
  raise 'no such file' unless FileTest.file?(path)
17
17
 
18
18
  @target_file = path
19
19
  # TODO: yaml need to be configurable
20
+ @dict_name = dict
20
21
  @dict = LucaRecord::Dict.new("import-#{dict}.yaml")
21
22
  @code_map = LucaRecord::Dict.reverse(LucaRecord::Dict.load('base.tsv'))
22
23
  @config = @dict.csv_config if dict
@@ -59,7 +60,7 @@ module LucaBook
59
60
  if @config[:type] == 'single'
60
61
  LucaBook::Journal.create(parse_single(row))
61
62
  elsif @config[:type] == 'double'
62
- p parse_double(row)
63
+ p parse_double(row) # TODO: Not implemented yet
63
64
  else
64
65
  p row
65
66
  end
@@ -85,7 +86,7 @@ module LucaBook
85
86
  d['date'] = parse_date(row)
86
87
  if row.dig(@config[:credit_value])&.empty?
87
88
  d['debit'] = [
88
- { 'code' => search_code(row[@config[:label]], DEBIT_DEFAULT) }
89
+ { 'code' => search_code(row[@config[:label]], @config.dig(:default_debit)) || DEBIT_DEFAULT }
89
90
  ]
90
91
  d['credit'] = [
91
92
  { 'code' => @code_map.dig(@config[:counter_label]) }
@@ -95,12 +96,13 @@ module LucaBook
95
96
  { 'code' => @code_map.dig(@config[:counter_label]) }
96
97
  ]
97
98
  d['credit'] = [
98
- { 'code' => search_code(row[@config[:label]], CREDIT_DEFAULT) }
99
+ { 'code' => search_code(row[@config[:label]], @config.dig(:default_credit)) || CREDIT_DEFAULT }
99
100
  ]
100
101
  end
101
102
  d['debit'][0]['value'] = value
102
103
  d['credit'][0]['value'] = value
103
104
  d['note'] = Array(@config[:note]).map{ |col| row[col] }.join(' ')
105
+ d['x-editor'] = "LucaBook::Import/#{@dict_name}"
104
106
  end
105
107
  end
106
108
 
@@ -111,14 +113,15 @@ module LucaBook
111
113
  {}.tap do |d|
112
114
  d['date'] = parse_date(row)
113
115
  d['debit'] = {
114
- 'code' => search_code(row[@config[:debit_label]], DEBIT_DEFAULT),
116
+ 'code' => search_code(row[@config[:label]], @config.dig(:default_debit)) || DEBIT_DEFAULT,
115
117
  'value' => row.dig(@config[:debit_value])
116
118
  }
117
119
  d['credit'] = {
118
- 'code' => search_code(row[@config[:credit_label]], CREDIT_DEFAULT),
120
+ 'code' => search_code(row[@config[:label]], @config.dig(:default_credit)) || CREDIT_DEFAULT,
119
121
  'value' => row.dig(@config[:credit_value])
120
122
  }
121
123
  d['note'] = Array(@config[:note]).map{ |col| row[col] }.join(' ')
124
+ d['x-editor'] = "LucaBook::Import/#{@dict_name}"
122
125
  end
123
126
  end
124
127
 
@@ -13,8 +13,10 @@ module LucaBook
13
13
  class List < LucaBook::Journal
14
14
  @dirname = 'journals'
15
15
 
16
- def initialize(data)
16
+ def initialize(data, start_date, code = nil)
17
17
  @data = data
18
+ @code = code
19
+ @start = start_date
18
20
  @dict = LucaRecord::Dict.load('base.tsv')
19
21
  end
20
22
 
@@ -26,44 +28,111 @@ module LucaBook
26
28
  [:debit, :credit].map { |key| serialize_on_key(dat[key], :code) }.flatten.include?(code)
27
29
  end
28
30
  end
29
- new data
31
+ new data, Date.new(from_year.to_i, from_month.to_i, 1), code
30
32
  end
31
33
 
32
- def convert_label
33
- @data.each do |dat|
34
- dat[:debit].each { |debit| debit[:code] = "#{debit[:code]} #{@dict.dig(debit[:code], :label)}" }
35
- dat[:credit].each { |credit| credit[:code] = "#{credit[:code]} #{@dict.dig(credit[:code], :label)}" }
34
+ def list_on_code
35
+ calc_code
36
+ convert_label
37
+ @data = [code_header] + @data.map do |dat|
38
+ date, txid = LucaSupport::Code.decode_id(dat[:id])
39
+ {}.tap do |res|
40
+ res['code'] = dat[:code]
41
+ res['date'] = date
42
+ res['no'] = txid
43
+ res['id'] = dat[:id]
44
+ res['diff'] = dat[:diff]
45
+ res['balance'] = dat[:balance]
46
+ res['counter_code'] = dat[:counter_code].length == 1 ? dat[:counter_code].first : dat[:counter_code]
47
+ res['note'] = dat[:note]
48
+ end
36
49
  end
37
- self
50
+ readable(@data)
38
51
  end
39
52
 
40
- def flat_list
53
+ def list_journals
41
54
  convert_label
42
55
  @data = @data.map do |dat|
43
- idx = dat[:debit].length >= dat[:credit].length ? :debit : :credit
44
- dat[idx].map.with_index do |_k, i|
45
- date, txid = LucaSupport::Code.decode_id(dat[:id])
46
- {}.tap do |res|
47
- res['date'] = date
48
- res['no'] = txid
49
- res['id'] = dat[:id]
50
- res['debit_code'] = dat[:debit][i][:code] if dat[:debit][i]
51
- res['debit_amount'] = dat[:debit][i][:amount] if dat[:debit][i]
52
- res['credit_code'] = dat[:credit][i][:code] if dat[:credit][i]
53
- res['credit_amount'] = dat[:credit][i][:amount] if dat[:credit][i]
54
- res['note'] = dat[:note]
55
- end
56
+ date, txid = LucaSupport::Code.decode_id(dat[:id])
57
+ {}.tap do |res|
58
+ res['date'] = date
59
+ res['no'] = txid
60
+ res['id'] = dat[:id]
61
+ res['debit_code'] = dat[:debit].length == 1 ? dat[:debit][0][:code] : dat[:debit].map { |d| d[:code] }
62
+ res['debit_amount'] = dat[:debit].inject(0) { |sum, d| sum + d[:amount] }
63
+ res['credit_code'] = dat[:credit].length == 1 ? dat[:credit][0][:code] : dat[:credit].map { |d| d[:code] }
64
+ res['credit_amount'] = dat[:credit].inject(0) { |sum, d| sum + d[:amount] }
65
+ res['note'] = dat[:note]
56
66
  end
57
- end.flatten
58
- self
67
+ end
68
+ readable(@data)
69
+ end
70
+
71
+ def accumulate_code
72
+ @data.inject(BigDecimal('0')) do |sum, dat|
73
+ sum + Util.diff_by_code(dat[:debit], @code) - Util.diff_by_code(dat[:credit], @code)
74
+ end
59
75
  end
60
76
 
61
77
  def to_yaml
62
78
  YAML.dump(LucaSupport::Code.readable(@data)).tap { |data| puts data }
63
79
  end
64
80
 
81
+ private
82
+
83
+ def set_balance
84
+ return BigDecimal('0') if @code.nil? || /^[A-H]/.match(@code)
85
+
86
+ balance_dict = Dict.latest_balance
87
+ start_balance = BigDecimal(balance_dict.dig(@code.to_s, :balance) || '0')
88
+ start = Dict.issue_date(balance_dict)&.next_month
89
+ last = @start.prev_month
90
+ if last.year >= start.year && last.month >= start.month
91
+ start_balance + self.class.term(start.year, start.month, last.year, last.month, code: @code).accumulate_code
92
+ else
93
+ start_balance
94
+ end
95
+ end
96
+
97
+ def calc_code
98
+ @balance = set_balance
99
+ if @code
100
+ balance = @balance
101
+ @data.each do |dat|
102
+ dat[:diff] = Util.diff_by_code(dat[:debit], @code) - Util.diff_by_code(dat[:credit], @code)
103
+ balance += dat[:diff]
104
+ dat[:balance] = balance
105
+ dat[:code] = @code
106
+ counter = dat[:diff] * Util.pn_debit(@code) > 0 ? :credit : :debit
107
+ dat[:counter_code] = dat[counter].map { |d| d[:code] }
108
+ end
109
+ end
110
+ self
111
+ end
112
+
113
+ def convert_label
114
+ @data.each do |dat|
115
+ if @code
116
+ dat[:code] = "#{dat[:code]} #{@dict.dig(dat[:code], :label)}"
117
+ dat[:counter_code] = dat[:counter_code].map { |counter| "#{counter} #{@dict.dig(counter, :label)}" }
118
+ else
119
+ dat[:debit].each { |debit| debit[:code] = "#{debit[:code]} #{@dict.dig(debit[:code], :label)}" }
120
+ dat[:credit].each { |credit| credit[:code] = "#{credit[:code]} #{@dict.dig(credit[:code], :label)}" }
121
+ end
122
+ end
123
+ self
124
+ end
125
+
65
126
  def dict
66
127
  LucaBook::Dict::Data
67
128
  end
129
+
130
+ def code_header
131
+ {}.tap do |h|
132
+ %w[code date no id diff balance counter_code note].each do |k|
133
+ h[k] = k == 'balance' ? @balance : ''
134
+ end
135
+ end
136
+ end
68
137
  end
69
138
  end