lucabook 0.2.20 → 0.2.25

Sign up to get free protection for your applications and to get access to all the features.
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)