lucabook 0.2.20 → 0.2.25

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: 82bb098ac3acfbb37c600908b75174c34a665f4d7c9003d9956738783bb9a617
4
- data.tar.gz: b963a24873e226c898c7b14dba4cf21edaacf4cfdc9f7e6eeab0d04959fa788b
3
+ metadata.gz: c77f6025962d1697b99602db386367b08f037b4c9651c277729e3c16ae058dfa
4
+ data.tar.gz: eb3fa1abeda269e967699b974d5a3f1be504e85d39ddec9e080c4ceb5c28eabe
5
5
  SHA512:
6
- metadata.gz: b56ef4dd42ee782bf488cd50e6992415d878f388663ed0fb4f983f7353310ea53a2a0a4e1d94be5f06b51102eb73f7e9d0a7404a05cdccf6487248a1041196d3
7
- data.tar.gz: 12cfe5f1194d34236ebe69541a9d4b0adf3333a39c1e73024ce0d3ad43639e4147ab48c19c5fc52e08bcbe6473821c850189c0b81e252101456ce70eea80a3c4
6
+ metadata.gz: 960e0c3a36e2c2da145f398417813424820b924a34b7926b001ba024e0409246ed51dfc7922be8576ee9e58da8648ef7a89e38e4f0afcaa1cc85ba8c6cb164c9
7
+ data.tar.gz: ec88e8230a332139b4a136d6f28745cf0f733286f5023263d29036a7b41e2a6e5577b51bf9cb2374cb49aef4d0521b094f7d242c6a40fd6de00d4e03452bd5ff
data/exe/luca-book CHANGED
@@ -1,15 +1,17 @@
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
11
12
  elsif params['json']
12
- LucaBook::Import.import_json(STDIN.read)
13
+ str = args[0].nil? ? STDIN.read : File.read(args[0])
14
+ LucaBook::Import.import_json(str)
13
15
  else
14
16
  puts 'Usage: luca-book import -c import_config'
15
17
  exit 1
@@ -17,43 +19,82 @@ module LucaCmd
17
19
  end
18
20
 
19
21
  def self.list(args, params)
20
- args = LucaCmd.gen_range(params[:n] || 1) if args.empty?
22
+ args = gen_range(params[:n] || 1) if args.empty?
21
23
  if params['code']
22
- LucaBook::List.term(*args, code: params['code']).list_on_code.to_yaml
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
23
29
  else
24
- LucaBook::List.term(*args).list_journals.to_yaml
30
+ render(LucaBook::List.term(*args).list_journals, params)
25
31
  end
26
32
  end
27
33
 
28
34
  def self.stats(args, params)
29
- args = LucaCmd.gen_range(params[:n]) if args.empty?
30
- LucaBook::State.term(*args).stats(params[:level])
35
+ args = gen_range(params[:n]) if args.empty?
36
+ if params['code']
37
+ render(LucaBook::State.by_code(params['code'], *args), params)
38
+ else
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.'
49
+ end
31
50
  end
32
51
  end
33
52
 
34
- class Report
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
+
35
61
  def self.balancesheet(args, params)
36
62
  level = params[:level] || 3
37
63
  legal = params[:legal] || false
38
- args = LucaCmd.gen_range(params[:n] || 1) if args.empty?
39
- LucaBook::State.term(*args).bs(level, legal: legal)
64
+ args = gen_range(params[:n] || 1) if args.empty?
65
+ render(LucaBook::State.range(*args).bs(level, legal: legal), params)
40
66
  end
41
67
 
42
68
  def self.profitloss(args, params)
43
69
  level = params[:level] || 2
44
- args = LucaCmd.gen_range(params[:n]) if args.empty?
45
- LucaBook::State.term(*args).pl(level).to_yaml
70
+ args = gen_range(params[:n]) if args.empty?
71
+ render(LucaBook::State.range(*args).pl(level), params)
46
72
  end
47
- end
48
73
 
49
- module_function
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
79
+ end
50
80
 
51
- def gen_range(count)
81
+ def self.gen_range(count)
52
82
  count ||= 3
53
83
  today = Date.today
54
84
  start = today.prev_month(count - 1)
55
85
  [start.year, start.month, today.year, today.month]
56
86
  end
87
+
88
+ def self.render(dat, params)
89
+ case params[:output]
90
+ when 'json'
91
+ puts JSON.dump(dat)
92
+ when 'nu'
93
+ LucaSupport::View.nushell(YAML.dump(dat))
94
+ else
95
+ puts YAML.dump(dat)
96
+ end
97
+ end
57
98
  end
58
99
 
59
100
  def new_pj(args = nil, params = {})
@@ -80,15 +121,31 @@ when /journals?/, 'j'
80
121
  OptionParser.new do |opt|
81
122
  opt.banner = 'Usage: luca-book journals list [options] [YYYY M]'
82
123
  opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
124
+ opt.on('--customer', 'categorize by x-customer header') { |_v| params['headers'] = 'x-customer' }
83
125
  opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
126
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
127
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
84
128
  opt.on_tail('List records. If you specify code and/or month, search on each criteria.')
85
129
  args = opt.parse!(ARGV)
86
130
  LucaCmd::Journal.list(args, params)
87
131
  end
132
+ when 'set'
133
+ OptionParser.new do |opt|
134
+ opt.banner = 'Usage: luca-book journals set [options] [YYYY M]'
135
+ opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
136
+ opt.on('--header VAL', 'header key') { |v| params[:key] = v }
137
+ opt.on('--val VAL', 'header value') { |v| params[:value] = v }
138
+ opt.on_tail('set header to journals on specified code.')
139
+ args = opt.parse!(ARGV)
140
+ LucaCmd::Journal.add_header(args, params)
141
+ end
88
142
  when 'stats'
89
143
  OptionParser.new do |opt|
90
144
  opt.banner = 'Usage: luca-book journals stats [options] [YYYY M]'
145
+ opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
91
146
  opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
147
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
148
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
92
149
  args = opt.parse!(ARGV)
93
150
  LucaCmd::Journal.stats(args, params)
94
151
  end
@@ -111,11 +168,20 @@ when 'new'
111
168
  when /reports?/, 'r'
112
169
  subcmd = ARGV.shift
113
170
  case subcmd
171
+ when 'xbrl'
172
+ OptionParser.new do |opt|
173
+ opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
174
+ opt.on('-o', '--output VAL', 'output filename') { |v| params[:output] = v }
175
+ args = opt.parse!(ARGV)
176
+ LucaCmd::Report.xbrl(args, params)
177
+ end
114
178
  when 'bs'
115
179
  OptionParser.new do |opt|
116
180
  opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
117
181
  opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
118
182
  opt.on('--legal', 'show legal mandatory account') { |_v| params[:legal] = true }
183
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
184
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
119
185
  args = opt.parse!(ARGV)
120
186
  LucaCmd::Report.balancesheet(args, params)
121
187
  end
@@ -124,9 +190,19 @@ when /reports?/, 'r'
124
190
  opt.banner = 'Usage: luca-book reports pl [options] [YYYY M YYYY M]'
125
191
  opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
126
192
  opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
193
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
194
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
127
195
  args = opt.parse!(ARGV)
128
196
  LucaCmd::Report.profitloss(args, params)
129
197
  end
198
+ when 'mail'
199
+ OptionParser.new do |opt|
200
+ opt.banner = 'Usage: luca-book reports mail [options] [YYYY M YYYY M]'
201
+ opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
202
+ opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
203
+ args = opt.parse!(ARGV)
204
+ LucaCmd::Report.report_mail(args, params)
205
+ end
130
206
  else
131
207
  puts 'Proper subcommand needed.'
132
208
  puts
data/lib/luca_book.rb CHANGED
@@ -9,6 +9,7 @@ module LucaBook
9
9
  autoload :Import, 'luca_book/import'
10
10
  autoload :Journal, 'luca_book/journal'
11
11
  autoload :List, 'luca_book/list'
12
+ autoload :ListByHeader, 'luca_book/list_by_header'
12
13
  autoload :Setup, 'luca_book/setup'
13
14
  autoload :State, 'luca_book/state'
14
15
  autoload :Util, 'luca_book/util'
@@ -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[:value].each_slice(6) do |v|
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[:value].inject(:+)] + Array.new(h[:value].length)
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[:value].each_slice(6).with_index(0) do |v, i|
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), value: v}
101
+ {code: k, label: @report.dict.dig(k, :label), amount: v}
102
102
  end
103
103
  end
104
104
 
@@ -7,8 +7,85 @@ require 'pathname'
7
7
 
8
8
  module LucaBook
9
9
  class Dict < LucaRecord::Dict
10
+ # Column number settings for CSV/TSV convert
11
+ #
12
+ # :label
13
+ # for double entry data
14
+ # :counter_label
15
+ # must be specified with label
16
+ # :debit_label
17
+ # for double entry data
18
+ # * debit_amount
19
+ # :credit_label
20
+ # for double entry data
21
+ # * credit_amount
22
+ # :note
23
+ # can be the same column as another label
24
+ #
25
+ # :encoding
26
+ # file encoding
27
+ #
28
+ def csv_config
29
+ {}.tap do |config|
30
+ if @config.dig('label')
31
+ config[:label] = @config['label'].to_i
32
+ if @config.dig('counter_label')
33
+ config[:counter_label] = @config['counter_label']
34
+ config[:type] = 'single'
35
+ end
36
+ elsif @config.dig('debit_label')
37
+ config[:debit_label] = @config['debit_label'].to_i
38
+ if @config.dig('credit_label')
39
+ config[:credit_label] = @config['credit_label'].to_i
40
+ config[:type] = 'double'
41
+ end
42
+ end
43
+ config[:type] ||= 'invalid'
44
+ config[:debit_amount] = @config['debit_amount'].to_i if @config.dig('debit_amount')
45
+ config[:credit_amount] = @config['credit_amount'].to_i if @config.dig('credit_amount')
46
+ config[:note] = @config['note'] if @config.dig('note')
47
+ config[:encoding] = @config['encoding'] if @config.dig('encoding')
48
+
49
+ config[:year] = @config['year'] if @config.dig('year')
50
+ config[:month] = @config['month'] if @config.dig('month')
51
+ config[:day] = @config['day'] if @config.dig('day')
52
+ config[:default_debit] = @config['default_debit'] if @config.dig('default_debit')
53
+ config[:default_credit] = @config['default_credit'] if @config.dig('default_credit')
54
+ end
55
+ end
56
+
57
+ def search(word, default_word = nil, amount = nil)
58
+ res = super(word, default_word, main_key: 'account_label')
59
+ if res.is_a?(Array) && res[0].is_a?(Array)
60
+ filter_amount(res, amount)
61
+ else
62
+ res
63
+ end
64
+ end
65
+
66
+ # Choose setting on Big or small condition.
67
+ #
68
+ def filter_amount(settings, amount = nil)
69
+ return settings[0] if amount.nil?
70
+
71
+ settings.each do |item|
72
+ return item unless item[1].keys.include?(:on_amount)
73
+
74
+ condition = item.dig(1, :on_amount)
75
+ case condition[0]
76
+ when '>'
77
+ return item if amount > BigDecimal(condition[1..])
78
+ when '<'
79
+ return item if amount < BigDecimal(condition[1..])
80
+ else
81
+ return item
82
+ end
83
+ end
84
+ nil
85
+ end
86
+
10
87
  def self.latest_balance
11
- dict_dir = Pathname(LucaSupport::Config::Pjdir) / 'data' / 'balance'
88
+ dict_dir = Pathname(LucaSupport::PJDIR) / 'data' / 'balance'
12
89
  # TODO: search latest balance dictionary
13
90
  load_tsv_dict(dict_dir / 'start.tsv')
14
91
  end
@@ -4,9 +4,14 @@ require 'date'
4
4
  require 'json'
5
5
  require 'luca_book'
6
6
  require 'luca_support'
7
- #require 'luca_book/dict'
8
7
  require 'luca_record'
9
8
 
9
+ begin
10
+ require "luca_book/import_#{LucaSupport::CONFIG['country']}"
11
+ rescue LoadError => e
12
+ e.message
13
+ end
14
+
10
15
  module LucaBook
11
16
  class Import
12
17
  DEBIT_DEFAULT = '10XX'
@@ -18,7 +23,7 @@ module LucaBook
18
23
  @target_file = path
19
24
  # TODO: yaml need to be configurable
20
25
  @dict_name = dict
21
- @dict = LucaRecord::Dict.new("import-#{dict}.yaml")
26
+ @dict = LucaBook::Dict.new("import-#{dict}.yaml")
22
27
  @code_map = LucaRecord::Dict.reverse(LucaRecord::Dict.load('base.tsv'))
23
28
  @config = @dict.csv_config if dict
24
29
  end
@@ -30,13 +35,13 @@ module LucaBook
30
35
  # "debit" : [
31
36
  # {
32
37
  # "label": "savings accounts",
33
- # "value": 20000
38
+ # "amount": 20000
34
39
  # }
35
40
  # ],
36
41
  # "credit" : [
37
42
  # {
38
43
  # "label": "trade notes receivable",
39
- # "value": 20000
44
+ # "amount": 20000
40
45
  # }
41
46
  # ],
42
47
  # "note": "settlement for the last month trade"
@@ -45,8 +50,6 @@ module LucaBook
45
50
  #
46
51
  def self.import_json(io)
47
52
  JSON.parse(io).each do |d|
48
- validate(d)
49
-
50
53
  code_map = LucaRecord::Dict.reverse(LucaRecord::Dict.load('base.tsv'))
51
54
  d['debit'].each { |h| h['code'] = code_map.dig(h['label']) || DEBIT_DEFAULT }
52
55
  d['credit'].each { |h| h['code'] = code_map.dig(h['label']) || CREDIT_DEFAULT }
@@ -67,46 +70,41 @@ module LucaBook
67
70
  end
68
71
  end
69
72
 
70
- def self.validate(obj)
71
- raise 'NoDateKey' unless obj.key?('date')
72
- raise 'NoDebitKey' unless obj.key?('debit')
73
- raise 'NoDebitValue' if obj['debit'].empty?
74
- raise 'NoCreditKey' unless obj.key?('credit')
75
- raise 'NoCreditValue' if obj['credit'].empty?
76
- end
77
-
78
73
  private
79
74
 
80
- #
81
75
  # convert single entry data
82
76
  #
83
77
  def parse_single(row)
84
- value = row.dig(@config[:credit_value])&.empty? ? row[@config[:debit_value]] : row[@config[:credit_value]]
78
+ if (row.dig(@config[:credit_amount]) || []).empty?
79
+ amount = BigDecimal(row[@config[:debit_amount]])
80
+ debit = true
81
+ else
82
+ amount = BigDecimal(row[@config[:credit_amount]])
83
+ end
84
+ default_label = debit ? @config.dig(:default_debit) : @config.dig(:default_credit)
85
+ code, options = search_code(row[@config[:label]], default_label, amount)
86
+ counter_code = @code_map.dig(@config[:counter_label])
87
+ if options
88
+ x_customer = options[:'x-customer'] if options[:'x-customer']
89
+ data, data_c = tax_extension(code, counter_code, amount, options) if respond_to? :tax_extension
90
+ end
91
+ data ||= [{ 'code' => code, 'amount' => amount }]
92
+ data_c ||= [{ 'code' => counter_code, 'amount' => amount }]
85
93
  {}.tap do |d|
86
94
  d['date'] = parse_date(row)
87
- if row.dig(@config[:credit_value])&.empty?
88
- d['debit'] = [
89
- { 'code' => search_code(row[@config[:label]], @config.dig(:default_debit)) || DEBIT_DEFAULT }
90
- ]
91
- d['credit'] = [
92
- { 'code' => @code_map.dig(@config[:counter_label]) }
93
- ]
95
+ if debit
96
+ d['debit'] = data
97
+ d['credit'] = data_c
94
98
  else
95
- d['debit'] = [
96
- { 'code' => @code_map.dig(@config[:counter_label]) }
97
- ]
98
- d['credit'] = [
99
- { 'code' => search_code(row[@config[:label]], @config.dig(:default_credit)) || CREDIT_DEFAULT }
100
- ]
99
+ d['debit'] = data_c
100
+ d['credit'] = data
101
101
  end
102
- d['debit'][0]['value'] = value
103
- d['credit'][0]['value'] = value
104
102
  d['note'] = Array(@config[:note]).map{ |col| row[col] }.join(' ')
105
- d['x-editor'] = "LucaBook::Import/#{@dict_name}"
103
+ d['headers'] = { 'x-editor' => "LucaBook::Import/#{@dict_name}" }
104
+ d['headers']['x-customer'] = x_customer if x_customer
106
105
  end
107
106
  end
108
107
 
109
- #
110
108
  # convert double entry data
111
109
  #
112
110
  def parse_double(row)
@@ -114,19 +112,20 @@ module LucaBook
114
112
  d['date'] = parse_date(row)
115
113
  d['debit'] = {
116
114
  'code' => search_code(row[@config[:label]], @config.dig(:default_debit)) || DEBIT_DEFAULT,
117
- 'value' => row.dig(@config[:debit_value])
115
+ 'amount' => row.dig(@config[:debit_amount])
118
116
  }
119
117
  d['credit'] = {
120
118
  'code' => search_code(row[@config[:label]], @config.dig(:default_credit)) || CREDIT_DEFAULT,
121
- 'value' => row.dig(@config[:credit_value])
119
+ 'amount' => row.dig(@config[:credit_amount])
122
120
  }
123
121
  d['note'] = Array(@config[:note]).map{ |col| row[col] }.join(' ')
124
122
  d['x-editor'] = "LucaBook::Import/#{@dict_name}"
125
123
  end
126
124
  end
127
125
 
128
- def search_code(label, default_label)
129
- @code_map.dig(@dict.search(label, default_label))
126
+ def search_code(label, default_label, amount = nil)
127
+ label, options = @dict.search(label, default_label, amount)
128
+ [@code_map.dig(label), options]
130
129
  end
131
130
 
132
131
  def parse_date(row)