lucadeal 0.2.19 → 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: 36ce9f92d92079f0049ec346da2921116e7cde669314bccca3653c28dca33b51
4
- data.tar.gz: b2f591e9bfb9710fa45cdb1d3582f19997d392e84e639531e4b15e77d6b284c7
3
+ metadata.gz: f73de0d5baa72b53fcaeb3ec5ef85c0d3d80a21b7d7531f914892ff476d8eee0
4
+ data.tar.gz: 3ecfc52d4a1e2257e926773f3e7db455dccce71e8fe89822fccb91402d0a979e
5
5
  SHA512:
6
- metadata.gz: 8e2ca7f2a7063d44d29013cdd7448f807c58dbabca341cdeb1a8a118e24ca826ca94ac608f1c4a56eb16cb41cb039a0b39d2b07cb011fdd4a57e4b04c24a7d92
7
- data.tar.gz: 692b888b1209de7fa6329ffbc7edd1ef7ad84be530b2dad7fcaad64b5c621d0da424e3ac90f21e9db7113057599497c91b61d70f479cf48bdea6bee2c49e1f0e
6
+ metadata.gz: 1f8c7a3301a69e5891736379fb9cf4fac3d1cda121e295d77903bb36f9f87ab6485be8d69e4bb398b23cf18af80f47f3d1b4a2f67d2dc3cfba5cc1f9c6fcf5cf
7
+ data.tar.gz: 1515a346552be7a7ec33a37bd3af941b57f8be9c890d67ccfde40bf0f0ece6f678a405d0723ad1d04649ae70e745e4dfa5d3a869400314b366fddf576c31c21a
@@ -1,3 +1,13 @@
1
+ ## LucaDeal 0.2.21
2
+
3
+ * Implement `luca-deal fee` subcommands.
4
+ * single invoice creation with contract id fragment.
5
+
6
+ ## LucaDeal 0.2.20
7
+
8
+ * CLI provides `--nu` option. Add JSON output.
9
+ * `luca-deal invoices list --html`, rendering HTML to stdout.
10
+
1
11
  ## LucaDeal 0.2.19
2
12
 
3
13
  * CLI id completion on Customer delete, Contract create/delete
data/README.md CHANGED
@@ -61,6 +61,12 @@ Monthly invoices are generated with `invoice create --monthly` sub command. Targ
61
61
  $ luca-deal invoice create --monthly [yyyy m]
62
62
  ```
63
63
 
64
+ Or, any invoice can be created with contract ID. Contract ID is just unique fragment of uuid, listed with `luca-deal invoice create` with no args.
65
+
66
+ ```
67
+ $ luca-deal invoice create 1d3 yyyy m
68
+ ```
69
+
64
70
  Invoice conditions are defined by contracts.
65
71
 
66
72
 
@@ -5,8 +5,8 @@ require 'date'
5
5
  require 'optparse'
6
6
  require 'luca_deal'
7
7
 
8
- module LucaCmd
9
- class Customer
8
+ class LucaCmd
9
+ class Customer < LucaCmd
10
10
  def self.create(args = nil, params = {})
11
11
  if args
12
12
  id = LucaDeal::Customer.create(name: args[0])
@@ -28,7 +28,7 @@ module LucaCmd
28
28
  list = LucaDeal::Customer.id_completion(args[0])
29
29
  case list.length
30
30
  when 1
31
- id = LucaDeal::Customer.new.describe(list.first)
31
+ render(LucaDeal::Customer.new.describe(list.first), params)
32
32
  else
33
33
  puts 'found multiple contract id. exit'
34
34
  list.each { |item| puts " #{item[:id]} #{item[:label]}" }
@@ -57,11 +57,11 @@ module LucaCmd
57
57
  end
58
58
 
59
59
  def self.list(args = nil, params = {})
60
- LucaDeal::Customer.new.list_name
60
+ render(LucaDeal::Customer.new.list_name, params)
61
61
  end
62
62
  end
63
63
 
64
- class Contract
64
+ class Contract < LucaCmd
65
65
  def self.create(args = nil, params = {})
66
66
  if args.empty?
67
67
  list = LucaDeal::Customer.id_completion('')
@@ -93,7 +93,7 @@ module LucaCmd
93
93
  list = LucaDeal::Contract.id_completion(args[0])
94
94
  case list.length
95
95
  when 1
96
- id = LucaDeal::Contract.new.describe(list.first)
96
+ render(LucaDeal::Contract.new.describe(list.first), params)
97
97
  else
98
98
  puts 'found multiple contract id. exit'
99
99
  list.each { |item| puts " #{item[:id]} #{item[:label]}" }
@@ -122,14 +122,29 @@ module LucaCmd
122
122
  end
123
123
  end
124
124
 
125
- class Invoice
125
+ class Invoice < LucaCmd
126
126
  def self.create(args = nil, params = {})
127
- date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
128
127
  case params['mode']
129
128
  when 'monthly'
129
+ date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
130
130
  LucaDeal::Invoice.new(date).monthly_invoice
131
131
  else
132
- puts 'not implemented mode'
132
+ date = "#{args[1]}-#{args[2]}-#{args[3] || '1'}" if !args.empty?
133
+ list = LucaDeal::Contract.id_completion(args[0] || '', label: 'customer_name')
134
+ if args.length != 3
135
+ puts 'requires contract id & year month. exit'
136
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
137
+ exit 1
138
+ else
139
+ case list.length
140
+ when 1
141
+ id = LucaDeal::Invoice.new(date).single_invoice(list.first)
142
+ else
143
+ puts 'found multiple contract id. exit'
144
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
145
+ exit 1
146
+ end
147
+ end
133
148
  end
134
149
  end
135
150
 
@@ -157,7 +172,11 @@ module LucaCmd
157
172
  date = "#{Date.today.year}-#{Date.today.month}-1"
158
173
  count = 3
159
174
  end
160
- LucaDeal::Invoice.new(date).stats(count || 1)
175
+ if params[:html]
176
+ LucaDeal::Invoice.new(date).preview_stdout
177
+ else
178
+ render(LucaDeal::Invoice.new(date).stats(count || 1), params)
179
+ end
161
180
  end
162
181
 
163
182
  def self.mail(args = nil, params = {})
@@ -170,6 +189,62 @@ module LucaCmd
170
189
  end
171
190
  end
172
191
  end
192
+
193
+ class Fee < LucaCmd
194
+ def self.create(args = nil, params = {})
195
+ case params['mode']
196
+ when 'monthly'
197
+ date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
198
+ LucaDeal::Fee.new(date).monthly_fee
199
+ else
200
+ puts "not implemented yet"
201
+ exit 1
202
+ end
203
+ end
204
+
205
+ def self.delete(args = nil, params = {})
206
+ if args
207
+ id = LucaDeal::Fee.delete(args[0])
208
+ else
209
+ puts 'requires contract id. exit'
210
+ exit 1
211
+ end
212
+ end
213
+
214
+ def self.list(args = nil, params = {})
215
+ date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
216
+ if args.empty?
217
+ date = "#{Date.today.year}-#{Date.today.month}-1"
218
+ count = 3
219
+ end
220
+ if params[:html]
221
+ LucaDeal::Fee.new(date).preview_stdout
222
+ else
223
+ render(LucaDeal::Fee.new(date).stats(count || 1), params)
224
+ end
225
+ end
226
+
227
+ def self.mail(args = nil, params = {})
228
+ date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
229
+ case params['mode']
230
+ when 'preview'
231
+ LucaDeal::Fee.new(date).preview_mail
232
+ else
233
+ LucaDeal::Fee.new(date).deliver_mail
234
+ end
235
+ end
236
+ end
237
+
238
+ def self.render(dat, params)
239
+ case params[:output]
240
+ when 'json'
241
+ puts JSON.dump(dat)
242
+ when 'nu'
243
+ LucaSupport::View.nushell(YAML.dump(dat))
244
+ else
245
+ puts YAML.dump(dat)
246
+ end
247
+ end
173
248
  end
174
249
 
175
250
  def new_pj(args = nil, params = {})
@@ -184,20 +259,28 @@ case cmd
184
259
  when /customers?/
185
260
  subcmd = ARGV.shift
186
261
  case subcmd
262
+ when 'create'
263
+ OptionParser.new do |opt|
264
+ opt.banner = 'Usage: luca-deal customers create CustomerName'
265
+ args = opt.parse(ARGV)
266
+ LucaCmd::Customer.create(args, params)
267
+ end
187
268
  when 'list'
188
269
  OptionParser.new do |opt|
189
270
  opt.banner = 'Usage: luca-deal customers list [options]'
271
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
272
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
190
273
  args = opt.parse(ARGV)
191
274
  LucaCmd::Customer.list(args, params)
192
275
  end
193
- when 'create'
276
+ when 'describe'
194
277
  OptionParser.new do |opt|
195
- opt.banner = 'Usage: luca-deal customers create CustomerName'
278
+ opt.banner = 'Usage: luca-deal customers describe [options] customer_id'
279
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
280
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
196
281
  args = opt.parse(ARGV)
197
- LucaCmd::Customer.create(args, params)
282
+ LucaCmd::Customer.describe(args, params)
198
283
  end
199
- when 'describe'
200
- LucaCmd::Customer.describe(ARGV)
201
284
  when 'delete'
202
285
  LucaCmd::Customer.delete(ARGV)
203
286
  else
@@ -223,7 +306,13 @@ when /contracts?/
223
306
  LucaCmd::Contract.create(args, params)
224
307
  end
225
308
  when 'describe'
226
- LucaCmd::Contract.describe(ARGV)
309
+ OptionParser.new do |opt|
310
+ opt.banner = 'Usage: luca-deal contracts describe [options] customer_id'
311
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
312
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
313
+ args = opt.parse(ARGV)
314
+ LucaCmd::Contract.describe(args, params)
315
+ end
227
316
  when 'delete'
228
317
  LucaCmd::Contract.delete(ARGV)
229
318
  else
@@ -242,7 +331,7 @@ when /invoices?/, 'i'
242
331
  case subcmd
243
332
  when 'create'
244
333
  OptionParser.new do |opt|
245
- opt.banner = 'Usage: luca-deal invoices create [options] year month [date]'
334
+ opt.banner = 'Usage: luca-deal invoices create [options] --monthly|contract_id year month [date]'
246
335
  opt.on('--monthly', 'generate monthly data') { |_v| params['mode'] = 'monthly' }
247
336
  args = opt.parse(ARGV)
248
337
  LucaCmd::Invoice.create(args, params)
@@ -252,6 +341,9 @@ when /invoices?/, 'i'
252
341
  when 'list'
253
342
  OptionParser.new do |opt|
254
343
  opt.banner = 'Usage: luca-deal invoices list [options] year month [date]'
344
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
345
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
346
+ opt.on('--html', 'output html invoices') { |_v| params[:html] = 'monthly' }
255
347
  args = opt.parse(ARGV)
256
348
  LucaCmd::Invoice.list(args, params)
257
349
  end
@@ -279,6 +371,44 @@ when 'new'
279
371
  args = opt.parse(ARGV)
280
372
  new_pj(args, params)
281
373
  end
374
+ when /fee/
375
+ subcmd = ARGV.shift
376
+ case subcmd
377
+ when 'create'
378
+ OptionParser.new do |opt|
379
+ opt.banner = 'Usage: luca-deal fee create [options] year month [date]'
380
+ opt.on('--monthly', 'generate monthly data') { |_v| params['mode'] = 'monthly' }
381
+ args = opt.parse(ARGV)
382
+ LucaCmd::Fee.create(args, params)
383
+ end
384
+ when 'delete'
385
+ LucaCmd::Fee.delete(ARGV)
386
+ when 'list'
387
+ OptionParser.new do |opt|
388
+ opt.banner = 'Usage: luca-deal fee list [options] year month [date]'
389
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
390
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
391
+ opt.on('--html', 'output html invoices') { |_v| params[:html] = 'monthly' }
392
+ args = opt.parse(ARGV)
393
+ LucaCmd::Fee.list(args, params)
394
+ end
395
+ when 'mail'
396
+ OptionParser.new do |opt|
397
+ opt.banner = 'Usage: luca-deal fee mail [options] year month [date]'
398
+ opt.on('--preview', 'send to preview user') { |_v| params['mode'] = 'preview' }
399
+ args = opt.parse(ARGV)
400
+ LucaCmd::Fee.mail(args, params)
401
+ end
402
+ else
403
+ puts 'Proper subcommand needed.'
404
+ puts
405
+ puts 'Usage: luca-deal fee subcommand [--help|options]'
406
+ puts ' create'
407
+ puts ' delete'
408
+ puts ' list'
409
+ puts ' mail: send mail with report'
410
+ exit 1
411
+ end
282
412
  else
283
413
  puts 'Proper subcommand needed.'
284
414
  puts
@@ -47,14 +47,14 @@ module LucaDeal
47
47
  contract = parse_current(self.class.find(id))
48
48
  if contract['products']
49
49
  contract['products'] = contract['products'].map do |product|
50
- LucaDeal::Product.find(product['id'])
50
+ Product.find(product['id'])
51
51
  end
52
52
  end
53
- YAML.dump(readable(contract)).tap{ |d| puts d }
53
+ readable(contract)
54
54
  end
55
55
 
56
56
  def generate!(customer_id, mode = 'subscription')
57
- LucaDeal::Customer.find(customer_id) do |customer|
57
+ Customer.find(customer_id) do |customer|
58
58
  current_customer = parse_current(customer)
59
59
  if mode == 'sales_fee'
60
60
  obj = salesfee_template
@@ -12,19 +12,17 @@ module LucaDeal
12
12
  @dirname = 'customers'
13
13
  @required = ['name']
14
14
 
15
- def initialize(pjdir = nil)
15
+ def initialize
16
16
  @date = Date.today
17
- @pjdir = pjdir || Dir.pwd
18
17
  end
19
18
 
20
19
  def list_name
21
- list = self.class.all.map { |dat| parse_current(dat).sort.to_h }
22
- YAML.dump(list).tap { |l| puts l }
20
+ self.class.all.map { |dat| parse_current(dat).sort.to_h }
23
21
  end
24
22
 
25
23
  def describe(id)
26
24
  customer = parse_current(self.class.find(id))
27
- contracts = LucaDeal::Contract.all.select { |contract| contract['customer_id'] == customer['id'] }
25
+ contracts = Contract.all.select { |contract| contract['customer_id'] == customer['id'] }
28
26
  if !contracts.empty?
29
27
  customer['contracts'] = contracts.map do |c|
30
28
  {
@@ -34,7 +32,7 @@ module LucaDeal
34
32
  }
35
33
  end
36
34
  end
37
- YAML.dump(readable(customer)).tap{ |d| puts d }
35
+ readable(customer)
38
36
  end
39
37
 
40
38
  def self.create(obj)
@@ -14,40 +14,99 @@ module LucaDeal
14
14
 
15
15
  def initialize(date = nil)
16
16
  @date = issue_date(date)
17
- @config = load_config('config.yml')
18
17
  end
19
18
 
20
19
  # calculate fee, based on invoices
21
20
  #
22
21
  def monthly_fee
23
- LucaDeal::Contract.asof(@date.year, @date.month, @date.day) do |contract|
22
+ Contract.asof(@date.year, @date.month, @date.day) do |contract|
24
23
  next if contract.dig('terms', 'category') != 'sales_fee'
24
+ next if duplicated_contract? contract['id']
25
25
 
26
26
  @rate = { 'default' => BigDecimal(contract.dig('rate', 'default')) }
27
27
  @rate['initial'] = contract.dig('rate', 'initial') ? BigDecimal(contract.dig('rate', 'initial')) : @rate['default']
28
28
 
29
- LucaDeal::Invoice.asof(@date.year, @date.month) do |invoice|
29
+ fee = { 'contract_id' => contract['id'], 'items' => [] }
30
+ fee['customer'] = get_customer(contract['customer_id'])
31
+ fee['issue_date'] = @date
32
+ Invoice.asof(@date.year, @date.month) do |invoice|
30
33
  next if invoice.dig('sales_fee', 'id') != contract['id']
31
- next if duplicated_contract? invoice['contract_id']
32
-
33
- fee = invoice.dup
34
- fee['invoice'] = {}.tap do |f_invoice|
35
- %w[id contract_id issue_date due_date].each do |i|
36
- f_invoice[i] = invoice[i]
37
- fee.delete i
38
- end
34
+
35
+ invoice['items'].each do |item|
36
+ rate = item['type'] == 'initial' ? @rate['initial'] : @rate['default']
37
+ fee['items'] << {
38
+ 'invoice_id' => invoice['id'],
39
+ 'customer_name' => invoice.dig('customer', 'name'),
40
+ 'name' => item['name'],
41
+ 'price' => item['price'],
42
+ 'qty' => item['qty'],
43
+ 'fee' => item['price'] * item['qty'] * rate
44
+ }
39
45
  end
40
- fee['id'] = issue_random_id
41
- fee['customer'].delete('to')
42
- fee['sales_fee'].merge! subtotal(fee['items'])
43
- gen_fee!(fee)
46
+ fee['sales_fee'] = subtotal(fee['items'])
44
47
  end
48
+ self.class.create(fee, date: @date, codes: Array(contract['id']))
49
+ end
50
+ end
51
+
52
+ def deliver_mail(attachment_type = nil, mode: nil)
53
+ attachment_type = CONFIG.dig('fee', 'attachment') || :html
54
+ fees = self.class.asof(@date.year, @date.month)
55
+ raise "No report for #{@date.year}/#{@date.month}" if fees.count.zero?
56
+
57
+ fees.each do |dat, path|
58
+ next if has_status?(dat, 'mail_delivered')
59
+
60
+ mail = compose_mail(dat, mode: mode, attachment: attachment_type.to_sym)
61
+ LucaSupport::Mail.new(mail, PJDIR).deliver
62
+ self.class.add_status!(path, 'mail_delivered')
63
+ end
64
+ end
65
+
66
+ def preview_mail(attachment_type = nil)
67
+ deliver_mail(attachment_type, mode: :preview)
68
+ end
69
+
70
+ # Render HTML to console
71
+ #
72
+ def preview_stdout
73
+ self.class.asof(@date.year, @date.month) do |dat, _|
74
+ @company = set_company
75
+ fee_vars(dat)
76
+ puts render_report
77
+ end
78
+ end
79
+
80
+ def compose_mail(dat, mode: nil, attachment: :html)
81
+ @company = set_company
82
+ fee_vars(dat)
83
+
84
+ mail = Mail.new
85
+ mail.to = dat.dig('customer', 'to') if mode.nil?
86
+ mail.subject = CONFIG.dig('invoice', 'mail_subject') || 'Your Report is available'
87
+ if mode == :preview
88
+ mail.cc = CONFIG.dig('mail', 'preview') || CONFIG.dig('mail', 'from')
89
+ mail.subject = '[preview] ' + mail.subject
90
+ end
91
+ mail.text_part = Mail::Part.new(body: render_erb(search_template('fee-report-mail.txt.erb')), charset: 'UTF-8')
92
+ mail.attachments[attachment_name(dat, attachment)] = render_report(attachment)
93
+ mail
94
+ end
95
+
96
+ def render_report(file_type = :html)
97
+ case file_type
98
+ when :html
99
+ render_erb(search_template('fee-report.html.erb'))
100
+ when :pdf
101
+ erb2pdf(search_template('fee-report.html.erb'))
102
+ else
103
+ raise 'This filetype is not supported.'
45
104
  end
46
105
  end
47
106
 
48
107
  def get_customer(id)
49
108
  {}.tap do |res|
50
- LucaDeal::Customer.find(id) do |dat|
109
+ Customer.find(id) do |dat|
51
110
  customer = parse_current(dat)
52
111
  res['id'] = customer['id']
53
112
  res['name'] = customer.dig('name')
@@ -58,13 +117,19 @@ module LucaDeal
58
117
  end
59
118
  end
60
119
 
61
- def gen_fee!(fee)
62
- id = fee.dig('invoice', 'contract_id')
63
- self.class.create(fee, date: @date, codes: Array(id))
64
- end
65
-
66
120
  private
67
121
 
122
+ # set variables for ERB template
123
+ #
124
+ def fee_vars(fee_dat)
125
+ @customer = fee_dat['customer']
126
+ @items = readable(fee_dat['items'])
127
+ @sales_fee = readable(fee_dat['sales_fee'])
128
+ @issue_date = fee_dat['issue_date']
129
+ @due_date = fee_dat['due_date']
130
+ @amount = readable(fee_dat['sales_fee'].inject(0) { |sum, (_k, v)| sum + v })
131
+ end
132
+
68
133
  def lib_path
69
134
  __dir__
70
135
  end
@@ -73,25 +138,20 @@ module LucaDeal
73
138
  #
74
139
  def set_company
75
140
  {}.tap do |h|
76
- h['name'] = @config.dig('company', 'name')
77
- h['address'] = @config.dig('company', 'address')
78
- h['address2'] = @config.dig('company', 'address2')
141
+ h['name'] = CONFIG.dig('company', 'name')
142
+ h['address'] = CONFIG.dig('company', 'address')
143
+ h['address2'] = CONFIG.dig('company', 'address2')
79
144
  end
80
145
  end
81
146
 
82
147
  # calc fee & tax amount by tax category
83
148
  #
84
149
  def subtotal(items)
85
- {}.tap do |subtotal|
150
+ { 'fee' => 0, 'tax' => 0 }.tap do |subtotal|
86
151
  items.each do |i|
87
- rate = i.dig('type') || 'default'
88
- subtotal[rate] = { 'fee' => 0, 'tax' => 0 } if subtotal.dig(rate).nil?
89
- subtotal[rate]['fee'] += i['qty'] * i['price'] * @rate[rate]
90
- end
91
- subtotal.each do |rate, amount|
92
- amount['tax'] = (amount['fee'] * load_tax_rate(rate)).to_i
93
- amount['fee'] = amount['fee'].to_i
152
+ subtotal['fee'] += i['fee']
94
153
  end
154
+ subtotal['tax'] = (subtotal['fee'] * load_tax_rate('default')).to_i
95
155
  end
96
156
  end
97
157
 
@@ -100,12 +160,18 @@ module LucaDeal
100
160
  Date.new(base.year, base.month, -1)
101
161
  end
102
162
 
163
+ # TODO: support due_date variation
164
+ def due_date(date)
165
+ next_month = date.next_month
166
+ Date.new(next_month.year, next_month.month, -1)
167
+ end
168
+
103
169
  # load Tax Rate from config.
104
170
  #
105
171
  def load_tax_rate(name)
106
- return 0 if @config.dig('tax_rate', name).nil?
172
+ return 0 if CONFIG.dig('tax_rate', name).nil?
107
173
 
108
- BigDecimal(take_current(@config['tax_rate'], name).to_s)
174
+ BigDecimal(take_current(CONFIG['tax_rate'], name).to_s)
109
175
  end
110
176
 
111
177
  def duplicated_contract?(id)
@@ -17,26 +17,33 @@ module LucaDeal
17
17
 
18
18
  def initialize(date = nil)
19
19
  @date = issue_date(date)
20
- @pjdir = Pathname(LucaSupport::Config::Pjdir)
21
- @config = load_config(@pjdir / 'config.yml')
22
20
  end
23
21
 
24
- def deliver_mail
25
- attachment_type = @config.dig('invoice', 'attachment') || :html
26
- self.class.asof(@date.year, @date.month) do |dat, path|
22
+ def deliver_mail(attachment_type = nil, mode: nil)
23
+ attachment_type = CONFIG.dig('invoice', 'attachment') || :html
24
+ invoices = self.class.asof(@date.year, @date.month)
25
+ raise "No invoice for #{@date.year}/#{@date.month}" if invoices.count.zero?
26
+
27
+ invoices.each do |dat, path|
27
28
  next if has_status?(dat, 'mail_delivered')
28
29
 
29
- mail = compose_mail(dat, attachment: attachment_type.to_sym)
30
- LucaSupport::Mail.new(mail, @pjdir).deliver
30
+ mail = compose_mail(dat, mode: mode, attachment: attachment_type.to_sym)
31
+ LucaSupport::Mail.new(mail, PJDIR).deliver
31
32
  self.class.add_status!(path, 'mail_delivered')
32
33
  end
33
34
  end
34
35
 
35
36
  def preview_mail(attachment_type = nil)
36
- attachment_type ||= @config.dig('invoice', 'attachment') || :html
37
- self.class.asof(@date.year, @date.month) do |dat, _path|
38
- mail = compose_mail(dat, mode: :preview, attachment: attachment_type.to_sym)
39
- LucaSupport::Mail.new(mail, @pjdir).deliver
37
+ deliver_mail(attachment_type, mode: :preview)
38
+ end
39
+
40
+ # Render HTML to console
41
+ #
42
+ def preview_stdout
43
+ self.class.asof(@date.year, @date.month) do |dat, _|
44
+ @company = set_company
45
+ invoice_vars(dat)
46
+ puts render_invoice
40
47
  end
41
48
  end
42
49
 
@@ -46,20 +53,27 @@ module LucaDeal
46
53
 
47
54
  mail = Mail.new
48
55
  mail.to = dat.dig('customer', 'to') if mode.nil?
49
- mail.subject = @config.dig('invoice', 'mail_subject') || 'Your Invoice is available'
56
+ mail.subject = CONFIG.dig('invoice', 'mail_subject') || 'Your Invoice is available'
50
57
  if mode == :preview
51
- mail.cc = @config.dig('mail', 'preview') || @config.dig('mail', 'from')
58
+ mail.cc = CONFIG.dig('mail', 'preview') || CONFIG.dig('mail', 'from')
52
59
  mail.subject = '[preview] ' + mail.subject
53
60
  end
54
61
  mail.text_part = Mail::Part.new(body: render_erb(search_template('invoice-mail.txt.erb')), charset: 'UTF-8')
55
- if attachment == :html
56
- mail.attachments[attachment_name(dat, attachment)] = render_erb(search_template('invoice.html.erb'))
57
- elsif attachment == :pdf
58
- mail.attachments[attachment_name(dat, attachment)] = erb2pdf(search_template('invoice.html.erb'))
59
- end
62
+ mail.attachments[attachment_name(dat, attachment)] = render_invoice(attachment)
60
63
  mail
61
64
  end
62
65
 
66
+ def render_invoice(file_type = :html)
67
+ case file_type
68
+ when :html
69
+ render_erb(search_template('invoice.html.erb'))
70
+ when :pdf
71
+ erb2pdf(search_template('invoice.html.erb'))
72
+ else
73
+ raise 'This filetype is not supported.'
74
+ end
75
+ end
76
+
63
77
  # Output seriarized invoice data to stdout.
64
78
  # Returns previous N months on multiple count
65
79
  #
@@ -96,10 +110,9 @@ module LucaDeal
96
110
  stat['count'] = stat['records'].count
97
111
  stat['total'] = stat['records'].inject(0) { |sum, rec| sum + rec.dig('subtotal') }
98
112
  stat['tax'] = stat['records'].inject(0) { |sum, rec| sum + rec.dig('tax') }
99
- collection << stat
113
+ collection << readable(stat)
100
114
  end
101
115
  end
102
- puts YAML.dump(LucaSupport::Code.readable(collection))
103
116
  end
104
117
  end
105
118
 
@@ -111,10 +124,10 @@ module LucaDeal
111
124
  item['debit'] = []
112
125
  item['credit'] = []
113
126
  dat['subtotal'].map do |sub|
114
- item['debit'] << { 'label' => '売掛金', 'value' => LucaSupport::Code.readable(sub['items']) }
115
- item['debit'] << { 'label' => '売掛金', 'value' => LucaSupport::Code.readable(sub['tax']) }
116
- item['credit'] << { 'label' => '売上高', 'value' => LucaSupport::Code.readable(sub['items']) }
117
- item['credit'] << { 'label' => '売上高', 'value' => LucaSupport::Code.readable(sub['tax']) }
127
+ item['debit'] << { 'label' => '売掛金', 'value' => readable(sub['items']) }
128
+ item['debit'] << { 'label' => '売掛金', 'value' => readable(sub['tax']) }
129
+ item['credit'] << { 'label' => '売上高', 'value' => readable(sub['items']) }
130
+ item['credit'] << { 'label' => '売上高', 'value' => readable(sub['tax']) }
118
131
  end
119
132
  item['x-customer'] = dat['customer']['name'] if dat.dig('customer', 'name')
120
133
  item['x-editor'] = 'LucaDeal'
@@ -124,14 +137,25 @@ module LucaDeal
124
137
  end
125
138
  end
126
139
 
140
+ def single_invoice(contract_id)
141
+ contract = Contract.find(contract_id)
142
+ raise "Invoice already exists for #{contract_id}. exit" if duplicated_contract? contract['id']
143
+
144
+ gen_invoice!(invoice_object(contract))
145
+ end
146
+
127
147
  def monthly_invoice
128
- LucaDeal::Contract.new(@date.to_s).active do |contract|
148
+ Contract.new(@date.to_s).active do |contract|
129
149
  next if contract.dig('terms', 'billing_cycle') != 'monthly'
130
150
  # TODO: provide another I/F for force re-issue if needed
131
151
  next if duplicated_contract? contract['id']
132
152
 
133
- invoice = {}
134
- invoice['id'] = issue_random_id
153
+ gen_invoice!(invoice_object(contract))
154
+ end
155
+ end
156
+
157
+ def invoice_object(contract)
158
+ {}.tap do |invoice|
135
159
  invoice['contract_id'] = contract['id']
136
160
  invoice['customer'] = get_customer(contract.dig('customer_id'))
137
161
  invoice['due_date'] = due_date(@date)
@@ -144,25 +168,13 @@ module LucaDeal
144
168
  item.dig('type') == 'initial' && subsequent_month?(contract.dig('terms', 'effective'))
145
169
  end
146
170
  invoice['subtotal'] = subtotal(invoice['items'])
147
- .map { |k, v| v.tap { |dat| dat['rate'] = k } }
148
- gen_invoice!(invoice)
171
+ .map { |k, v| v.tap { |dat| dat['rate'] = k } }
149
172
  end
150
173
  end
151
174
 
152
- # set variables for ERB template
153
- #
154
- def invoice_vars(invoice_dat)
155
- @customer = invoice_dat['customer']
156
- @items = invoice_dat['items']
157
- @subtotal = invoice_dat['subtotal']
158
- @issue_date = invoice_dat['issue_date']
159
- @due_date = invoice_dat['due_date']
160
- @amount = @subtotal.inject(0) { |sum, i| sum + i['items'] + i['tax'] }
161
- end
162
-
163
175
  def get_customer(id)
164
176
  {}.tap do |res|
165
- LucaDeal::Customer.find(id) do |dat|
177
+ Customer.find(id) do |dat|
166
178
  customer = parse_current(dat)
167
179
  res['id'] = customer['id']
168
180
  res['name'] = customer.dig('name')
@@ -178,7 +190,7 @@ module LucaDeal
178
190
 
179
191
  [].tap do |res|
180
192
  products.each do |product|
181
- LucaDeal::Product.find(product['id'])['items'].each do |item|
193
+ Product.find(product['id'])['items'].each do |item|
182
194
  item['product_id'] = product['id']
183
195
  item['qty'] ||= 1
184
196
  res << item
@@ -199,11 +211,23 @@ module LucaDeal
199
211
 
200
212
  # TODO: support due_date variation
201
213
  def due_date(date)
202
- Date.new(date.year, date.month + 1, -1)
214
+ next_month = date.next_month
215
+ Date.new(next_month.year, next_month.month, -1)
203
216
  end
204
217
 
205
218
  private
206
219
 
220
+ # set variables for ERB template
221
+ #
222
+ def invoice_vars(invoice_dat)
223
+ @customer = invoice_dat['customer']
224
+ @items = readable(invoice_dat['items'])
225
+ @subtotal = readable(invoice_dat['subtotal'])
226
+ @issue_date = invoice_dat['issue_date']
227
+ @due_date = invoice_dat['due_date']
228
+ @amount = readable(invoice_dat['subtotal'].inject(0) { |sum, i| sum + i['items'] + i['tax'] })
229
+ end
230
+
207
231
  def lib_path
208
232
  __dir__
209
233
  end
@@ -212,9 +236,9 @@ module LucaDeal
212
236
  #
213
237
  def set_company
214
238
  {}.tap do |h|
215
- h['name'] = @config.dig('company', 'name')
216
- h['address'] = @config.dig('company', 'address')
217
- h['address2'] = @config.dig('company', 'address2')
239
+ h['name'] = CONFIG.dig('company', 'name')
240
+ h['address'] = CONFIG.dig('company', 'address')
241
+ h['address2'] = CONFIG.dig('company', 'address2')
218
242
  end
219
243
  end
220
244
 
@@ -237,13 +261,14 @@ module LucaDeal
237
261
  # load Tax Rate from config.
238
262
  #
239
263
  def load_tax_rate(name)
240
- return 0 if @config.dig('tax_rate', name).nil?
264
+ return 0 if CONFIG.dig('tax_rate', name).nil?
241
265
 
242
- BigDecimal(take_current(@config['tax_rate'], name).to_s)
266
+ BigDecimal(take_current(CONFIG['tax_rate'], name).to_s)
243
267
  end
244
268
 
245
269
  def attachment_name(dat, type)
246
- "invoice-#{dat.dig('id')[0, 7]}.#{type}"
270
+ id = %r{/}.match(dat['id']) ? dat['id'].gsub('/', '') : dat['id'][0, 7]
271
+ "invoice-#{id}.#{type}"
247
272
  end
248
273
 
249
274
  def duplicated_contract?(id)
@@ -0,0 +1,6 @@
1
+ Your monthly fee report is available.
2
+ Please find the document attaced at the bottom of this email.
3
+ --
4
+ <%= @company['name'] %>
5
+ <%= @company['address'] %>
6
+ <%= @company['address2'] %>
@@ -0,0 +1,54 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <style>
5
+ body { size: A4 }
6
+ </style>
7
+ </head>
8
+ <body>
9
+ <div style="text-align: right">Issue date: <%= @issue_date %></div>
10
+ <h1 style="display: block; margin: auto; text-align: center">Fee Report</h1>
11
+ <section><%= @customer["name"] %></section>
12
+ <section>Total amount: <%= delimit_num(@amount) %></section>
13
+
14
+ <table style="width: 100%">
15
+ <thead>
16
+ <th>#</th>
17
+ <th>Customer Name</th>
18
+ <th>Item Name</th>
19
+ <th>qty</th>
20
+ <th>Sales</th>
21
+ <th>Fee</th>
22
+ </thead>
23
+ <tbody>
24
+ <% @items.each.with_index(1) do |item, i| %>
25
+ <tr class="item">
26
+ <td class="unit"><%= i %></td>
27
+ <td><%= item["customer_name"] %></td>
28
+ <td><%= item["name"] %></td>
29
+ <td class="unit"><%= item["qty"] %></td>
30
+ <td class="price"><%= delimit_num( item["price"] * item["qty"] ) %></td>
31
+ <td class="price"><%= delimit_num( item["fee"] ) %></td>
32
+ </tr>
33
+ <% end %>
34
+ <tr>
35
+ <td class="price" colspan="3">Subtotal</td>
36
+ <td class="price"><%= delimit_num( @sales_fee['fee'] ) %></td>
37
+ </tr>
38
+ <tr>
39
+ <td class="price" colspan="3">Tax</td>
40
+ <td class="price"><%= delimit_num( @sales_fee['tax'] ) %></td>
41
+ </tr>
42
+ <tr>
43
+ <td class="price" colspan="3">Total</td>
44
+ <td class="price"><%= delimit_num( @sales_fee['fee'] + @sales_fee['tax'] ) %></td>
45
+ </tr>
46
+ </table>
47
+
48
+ <section>
49
+ <div><%= @company["name"] %></div>
50
+ <div><%= @company["address"] %></div>
51
+ <div><%= @company["address2"] %></div>
52
+ </section>
53
+ </body>
54
+ </html>
@@ -0,0 +1,6 @@
1
+ Your monthly invoice is available.
2
+ Please find the document attaced at the bottom of this email.
3
+ --
4
+ <%= @company['name'] %>
5
+ <%= @company['address'] %>
6
+ <%= @company['address2'] %>
@@ -0,0 +1,52 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <style>
5
+ body { size: A4 }
6
+ </style>
7
+ </head>
8
+ <body>
9
+ <div style="text-align: right">Issue date: <%= @issue_date %></div>
10
+ <h1 style="display: block; margin: auto; text-align: center">Invoice</h1>
11
+ <section><%= @customer["name"] %></section>
12
+ <section>Total amount: <%= delimit_num(@amount) %></section>
13
+
14
+ <table style="width: 100%">
15
+ <thead>
16
+ <th>#</th>
17
+ <th>Item</th>
18
+ <th>qty</th>
19
+ <th>Amount</th>
20
+ </thead>
21
+ <tbody>
22
+ <% @items.each.with_index(1) do |item, i| %>
23
+ <tr class="item">
24
+ <td class="unit"><%= i %></td>
25
+ <td><%= item["name"] %></td>
26
+ <td class="unit"><%= item["qty"] %></td>
27
+ <td class="price"><%= delimit_num( item["price"] * item["qty"] ) %></td>
28
+ </tr>
29
+ <% end %>
30
+ <% @subtotal.each.with_index(1) do |sub, i| %>
31
+ <tr>
32
+ <td class="price" colspan="3">Subtotal</td>
33
+ <td class="price"><%= delimit_num( sub['items'] ) %></td>
34
+ </tr>
35
+ <tr>
36
+ <td class="price" colspan="3">Tax</td>
37
+ <td class="price"><%= delimit_num( sub['tax'] ) %></td>
38
+ </tr>
39
+ <tr>
40
+ <td class="price" colspan="3">Total</td>
41
+ <td class="price"><%= delimit_num( sub['items'] + sub['tax'] ) %></td>
42
+ </tr>
43
+ <% end %>
44
+ </table>
45
+
46
+ <section>
47
+ <div><%= @company["name"] %></div>
48
+ <div><%= @company["address"] %></div>
49
+ <div><%= @company["address2"] %></div>
50
+ </section>
51
+ </body>
52
+ </html>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LucaDeal
4
- VERSION = '0.2.19'
4
+ VERSION = '0.2.21'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lucadeal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.19
4
+ version: 0.2.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuma Takahiro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-19 00:00:00.000000000 Z
11
+ date: 2020-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lucarecord
@@ -89,6 +89,10 @@ files:
89
89
  - lib/luca_deal/setup.rb
90
90
  - lib/luca_deal/templates/.keep
91
91
  - lib/luca_deal/templates/config.yml
92
+ - lib/luca_deal/templates/fee-report-mail.txt.erb
93
+ - lib/luca_deal/templates/fee-report.html.erb
94
+ - lib/luca_deal/templates/invoice-mail.txt.erb
95
+ - lib/luca_deal/templates/invoice.html.erb
92
96
  - lib/luca_deal/version.rb
93
97
  homepage: https://github.com/chumaltd/luca/tree/master/lucadeal
94
98
  licenses: