lucadeal 0.2.14 → 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: e1b2a723a3fc191616f5a602a58428797afe69b19f9ee7e8d78d7dd187ae9b73
4
- data.tar.gz: b45c547d86312b94b702c68b94999be576c4f14fb55258b7961aa218ff4f74ef
3
+ metadata.gz: f73de0d5baa72b53fcaeb3ec5ef85c0d3d80a21b7d7531f914892ff476d8eee0
4
+ data.tar.gz: 3ecfc52d4a1e2257e926773f3e7db455dccce71e8fe89822fccb91402d0a979e
5
5
  SHA512:
6
- metadata.gz: 9daff536435c5bcbcc9f6694b4068df22f108d667283eb48754fdda02121f3724312ca0257d7029ec09f27034112af72123e734b2262c6f91c3b93f56a099b4e
7
- data.tar.gz: bdd40230688a1080fee49472adc22e8a98e53988aa2822688efc9e8537a0095de7919ec6c2c4a810da8f82e315fa714b2507d1e417dc07140fa0f1f897a31b0e
6
+ metadata.gz: 1f8c7a3301a69e5891736379fb9cf4fac3d1cda121e295d77903bb36f9f87ab6485be8d69e4bb398b23cf18af80f47f3d1b4a2f67d2dc3cfba5cc1f9c6fcf5cf
7
+ data.tar.gz: 1515a346552be7a7ec33a37bd3af941b57f8be9c890d67ccfde40bf0f0ece6f678a405d0723ad1d04649ae70e745e4dfa5d3a869400314b366fddf576c31c21a
@@ -0,0 +1,35 @@
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
+
11
+ ## LucaDeal 0.2.19
12
+
13
+ * CLI id completion on Customer delete, Contract create/delete
14
+ * add `describe` to Customer / Contract
15
+
16
+ ## LucaDeal 0.2.18
17
+
18
+ * Breaking change: restructure CLI in sub-sub command format.
19
+ * Add 'x-customer', 'x-editor' on export to LucaBook
20
+
21
+ ## LucaDeal 0.2.17
22
+
23
+ * `luca-deal export` export JSON for LucaBook
24
+
25
+ ## LucaDeal 0.2.14
26
+
27
+ * Introduce Product for selling items template.
28
+
29
+ ## LucaDeal 0.2.12
30
+
31
+ * Introduce Sales fee calculation.
32
+
33
+ ## LucaDeal 0.2.10
34
+
35
+ * items can have one time cost at initial month of contract.
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # LucaDeal
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/lucadeal.svg)](https://badge.fury.io/rb/lucadeal)
4
+
3
5
  LucaDeal is Sales contract management application.
4
6
 
5
7
  ## Installation
@@ -20,7 +22,52 @@ Or install it yourself as:
20
22
 
21
23
  ## Usage
22
24
 
23
- TODO: Write usage instructions here
25
+ You can create project skelton with `new` sub command.
26
+
27
+ ```
28
+ $ luca-deal new Dir
29
+ ```
30
+
31
+ Example assumes setup with bundler shim. `bundle exec` prefix may be needed in most cases.
32
+
33
+
34
+ ### Manage Contract
35
+
36
+ Customer object can be created by `customer create` subcommand with name.
37
+
38
+ ```
39
+ $ luca-deal customer create CustomerName
40
+ Successfully generated Customer 5976652cc2d9c0ebf4a8646f7a28aa8d6bd2d606
41
+ Edit customer detail.
42
+ ```
43
+
44
+ Customer is filed under `data/customers` as YAML. Detail need to be editted.
45
+ Then, Contract object is created by `contract create` sub command with customer id.
46
+
47
+ ```
48
+ $ luca-deal contract create 5976652cc2d9c0ebf4a8646f7a28aa8d6bd2d606
49
+ uccessfully generated Contract 814c6fc9fffe5566fe8e7ef683b439b355d612dc
50
+ Conditions are tentative. Edit contract detail.
51
+ ```
52
+
53
+ Contract is filed under `data/contracts` as YAML. Detail need to be editted.
54
+
55
+
56
+ ### Issue invoice
57
+
58
+ Monthly invoices are generated with `invoice create --monthly` sub command. Target month is optional. Without month, this month including today is the target.
59
+
60
+ ```
61
+ $ luca-deal invoice create --monthly [yyyy m]
62
+ ```
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
+
70
+ Invoice conditions are defined by contracts.
24
71
 
25
72
 
26
73
  ## Data Structure
@@ -5,57 +5,245 @@ require 'date'
5
5
  require 'optparse'
6
6
  require 'luca_deal'
7
7
 
8
- def customer(args = nil, params = {})
9
- case params['mode']
10
- when 'list'
11
- LucaDeal::Customer.new.list_name
12
- when 'create'
13
- if params['name']
14
- id = LucaDeal::Customer.create(name: params['name'])
15
- puts "Successfully generated Customer #{id}" if id
16
- puts 'Edit customer detail.' if id
17
- else
18
- puts 'requires customer\'s name. exit'
19
- exit 1
8
+ class LucaCmd
9
+ class Customer < LucaCmd
10
+ def self.create(args = nil, params = {})
11
+ if args
12
+ id = LucaDeal::Customer.create(name: args[0])
13
+ puts "Successfully generated Customer #{id}" if id
14
+ puts 'Edit customer detail.' if id
15
+ else
16
+ puts 'requires customer\'s name. exit'
17
+ exit 1
18
+ end
19
+ end
20
+
21
+ def self.describe(args = nil, params = {})
22
+ if args.empty?
23
+ list = LucaDeal::Customer.id_completion('')
24
+ puts 'requires customer\'s id. exit'
25
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
26
+ exit 1
27
+ else
28
+ list = LucaDeal::Customer.id_completion(args[0])
29
+ case list.length
30
+ when 1
31
+ render(LucaDeal::Customer.new.describe(list.first), params)
32
+ else
33
+ puts 'found multiple contract id. exit'
34
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
35
+ exit 1
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.delete(args = nil, params = {})
41
+ if args.empty?
42
+ list = LucaDeal::Customer.id_completion('')
43
+ puts 'requires customer\'s id. exit'
44
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
45
+ exit 1
46
+ else
47
+ list = LucaDeal::Customer.id_completion(args[0])
48
+ case list.length
49
+ when 1
50
+ id = LucaDeal::Customer.delete(list.first)
51
+ else
52
+ puts 'found multiple customer\'s id. exit'
53
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
54
+ exit 1
55
+ end
56
+ end
57
+ end
58
+
59
+ def self.list(args = nil, params = {})
60
+ render(LucaDeal::Customer.new.list_name, params)
20
61
  end
21
- else
22
- puts 'invalid option. --help for usage'
23
- exit 1
24
62
  end
25
- end
26
63
 
27
- def contract(args = nil, params = {})
28
- case params['mode']
29
- when 'create'
30
- if params['customer_id']
31
- id = LucaDeal::Contract.new.generate!(params['customer_id'], params['category'])
32
- puts "Successfully generated Contract #{id}" if id
33
- puts 'Conditions are tentative. Edit contract detail.' if id
34
- else
35
- puts 'requires customer\'s id. exit'
36
- exit 1
64
+ class Contract < LucaCmd
65
+ def self.create(args = nil, params = {})
66
+ if args.empty?
67
+ list = LucaDeal::Customer.id_completion('')
68
+ puts 'requires customer\'s id. exit'
69
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
70
+ exit 1
71
+ else
72
+ list = LucaDeal::Customer.id_completion(args[0])
73
+ case list.length
74
+ when 1
75
+ id = LucaDeal::Contract.new.generate!(list.first, params['category'])
76
+ puts "Successfully generated Contract #{id}" if id
77
+ puts 'Conditions are tentative. Edit contract detail.' if id
78
+ else
79
+ puts 'found multiple customer\'s id. exit'
80
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
81
+ exit 1
82
+ end
83
+ end
84
+ end
85
+
86
+ def self.describe(args = nil, params = {})
87
+ if args.empty?
88
+ list = LucaDeal::Contract.id_completion('', label: 'customer_name')
89
+ puts 'requires contract id. exit'
90
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
91
+ exit 1
92
+ else
93
+ list = LucaDeal::Contract.id_completion(args[0])
94
+ case list.length
95
+ when 1
96
+ render(LucaDeal::Contract.new.describe(list.first), params)
97
+ else
98
+ puts 'found multiple contract id. exit'
99
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
100
+ exit 1
101
+ end
102
+ end
103
+ end
104
+
105
+ def self.delete(args = nil, params = {})
106
+ if args.empty?
107
+ list = LucaDeal::Contract.id_completion('', label: 'customer_name')
108
+ puts 'requires contract id. exit'
109
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
110
+ exit 1
111
+ else
112
+ list = LucaDeal::Contract.id_completion(args[0])
113
+ case list.length
114
+ when 1
115
+ id = LucaDeal::Contract.delete(list.first)
116
+ else
117
+ puts 'found multiple contract id. exit'
118
+ list.each { |item| puts " #{item[:id]} #{item[:label]}" }
119
+ exit 1
120
+ end
121
+ end
37
122
  end
38
- else
39
123
  end
40
- end
41
124
 
42
- def invoice(args = nil, params = {})
43
- date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
44
- case params['mode']
45
- when 'monthly'
46
- LucaDeal::Invoice.new(date).monthly_invoice
47
- when 'mail'
48
- LucaDeal::Invoice.new(date).deliver_mail
49
- when 'preview'
50
- LucaDeal::Invoice.new(date).preview_mail
51
- when 'stats'
52
- if args.empty?
53
- date = "#{Date.today.year}-#{Date.today.month}-1"
54
- count = 3
55
- end
56
- LucaDeal::Invoice.new(date).stats(count || 1)
57
- else
58
- puts 'not implemented mode'
125
+ class Invoice < LucaCmd
126
+ def self.create(args = nil, params = {})
127
+ case params['mode']
128
+ when 'monthly'
129
+ date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
130
+ LucaDeal::Invoice.new(date).monthly_invoice
131
+ else
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
148
+ end
149
+ end
150
+
151
+ def self.delete(args = nil, params = {})
152
+ if args
153
+ id = LucaDeal::Invoice.delete(args[0])
154
+ else
155
+ puts 'requires contract id. exit'
156
+ exit 1
157
+ end
158
+ end
159
+
160
+ def self.export(args = nil, _params = nil)
161
+ if args
162
+ args << 28 if args.length == 2 # specify safe last day
163
+ LucaDeal::Invoice.new(args.join('-')).export_json
164
+ else
165
+ LucaDeal::Invoice.new.export_json
166
+ end
167
+ end
168
+
169
+ def self.list(args = nil, params = {})
170
+ date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
171
+ if args.empty?
172
+ date = "#{Date.today.year}-#{Date.today.month}-1"
173
+ count = 3
174
+ end
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
180
+ end
181
+
182
+ def self.mail(args = nil, params = {})
183
+ date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
184
+ case params['mode']
185
+ when 'preview'
186
+ LucaDeal::Invoice.new(date).preview_mail
187
+ else
188
+ LucaDeal::Invoice.new(date).deliver_mail
189
+ end
190
+ end
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
59
247
  end
60
248
  end
61
249
 
@@ -65,54 +253,170 @@ end
65
253
 
66
254
  LucaRecord::Base.valid_project?
67
255
  cmd = ARGV.shift
256
+ params = {}
68
257
 
69
258
  case cmd
70
- when 'customer'
71
- params = {}
72
- OptionParser.new do |opt|
73
- opt.banner = 'Usage: luca-deal customer [options]'
74
- opt.on('--list', 'list all customers') { |v| params['mode'] = 'list' }
75
- opt.on('--create CustomerName', 'register new customer') do |v|
76
- params['mode'] = 'create'
77
- params['name'] = v
259
+ when /customers?/
260
+ subcmd = ARGV.shift
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)
78
267
  end
79
- args = opt.parse(ARGV)
80
- customer(args, params)
268
+ when 'list'
269
+ OptionParser.new do |opt|
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 }
273
+ args = opt.parse(ARGV)
274
+ LucaCmd::Customer.list(args, params)
275
+ end
276
+ when 'describe'
277
+ OptionParser.new do |opt|
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 }
281
+ args = opt.parse(ARGV)
282
+ LucaCmd::Customer.describe(args, params)
283
+ end
284
+ when 'delete'
285
+ LucaCmd::Customer.delete(ARGV)
286
+ else
287
+ puts 'Proper subcommand needed.'
288
+ puts
289
+ puts 'Usage: luca-deal customer[s] subcommands [--help|options]'
290
+ puts ' create'
291
+ puts ' list'
292
+ puts ' describe: show customer with contracts info'
293
+ puts ' delete'
294
+ exit 1
81
295
  end
82
- when 'contract'
83
- params = {}
84
- OptionParser.new do |opt|
85
- opt.banner = 'Usage: luca-deal contract [options]'
86
- opt.on('--create CustomerId', 'register new contract') do |v|
87
- params['mode'] = 'create'
88
- params['customer_id'] = v
296
+ when /contracts?/
297
+ subcmd = ARGV.shift
298
+ case subcmd
299
+ when 'create'
300
+ OptionParser.new do |opt|
301
+ opt.banner = 'Usage: luca-deal contracts create [options] CustomerId'
302
+ opt.on('--salesfee', 'create contract as sales fee definition') do |_v|
303
+ params['category'] = 'sales_fee'
304
+ end
305
+ args = opt.parse(ARGV)
306
+ LucaCmd::Contract.create(args, params)
89
307
  end
90
- opt.on('--salesfee', 'create contract as sales fee definition') do |_v|
91
- params['category'] = 'sales_fee'
308
+ when 'describe'
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)
92
315
  end
93
- args = opt.parse(ARGV)
94
- contract(args, params)
316
+ when 'delete'
317
+ LucaCmd::Contract.delete(ARGV)
318
+ else
319
+ puts 'Proper subcommand needed.'
320
+ puts
321
+ puts 'Usage: luca-deal contract[s] subcommand [--help|options]'
322
+ puts ' create'
323
+ puts ' describe: show contract with puroducts or items info'
324
+ puts ' delete'
325
+ exit 1
95
326
  end
96
- when 'invoice'
97
- params = {}
98
- OptionParser.new do |opt|
99
- opt.banner = 'Usage: luca-deal invoice [options] year month [date]'
100
- opt.on('--monthly', 'generate monthly data') { |v| params['mode'] = 'monthly' }
101
- opt.on('--mail', 'send to customers') { |v| params['mode'] = 'mail' }
102
- opt.on('--preview', 'send to preview user') { |v| params['mode'] = 'preview' }
103
- opt.on('--stats', 'list invoices') { |v| params['mode'] = 'stats' }
104
- args = opt.parse(ARGV)
105
- invoice(args, params)
327
+ when 'export'
328
+ LucaCmd::Invoice.export(ARGV)
329
+ when /invoices?/, 'i'
330
+ subcmd = ARGV.shift
331
+ case subcmd
332
+ when 'create'
333
+ OptionParser.new do |opt|
334
+ opt.banner = 'Usage: luca-deal invoices create [options] --monthly|contract_id year month [date]'
335
+ opt.on('--monthly', 'generate monthly data') { |_v| params['mode'] = 'monthly' }
336
+ args = opt.parse(ARGV)
337
+ LucaCmd::Invoice.create(args, params)
338
+ end
339
+ when 'delete'
340
+ LucaCmd::Invoice.delete(ARGV)
341
+ when 'list'
342
+ OptionParser.new do |opt|
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' }
347
+ args = opt.parse(ARGV)
348
+ LucaCmd::Invoice.list(args, params)
349
+ end
350
+ when 'mail'
351
+ OptionParser.new do |opt|
352
+ opt.banner = 'Usage: luca-deal invoices mail [options] year month [date]'
353
+ opt.on('--preview', 'send to preview user') { |_v| params['mode'] = 'preview' }
354
+ args = opt.parse(ARGV)
355
+ LucaCmd::Invoice.mail(args, params)
356
+ end
357
+ else
358
+ puts 'Proper subcommand needed.'
359
+ puts
360
+ puts 'Usage: luca-deal invoices subcommand [--help|options]'
361
+ puts ' create'
362
+ puts ' delete'
363
+ puts ' list'
364
+ puts ' mail: send mail with invoice'
365
+ exit 1
106
366
  end
107
367
  when 'new'
108
368
  params = {}
109
369
  OptionParser.new do |opt|
370
+ opt.banner = 'Usage: luca-deal new DIR'
110
371
  args = opt.parse(ARGV)
111
372
  new_pj(args, params)
112
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
113
412
  else
114
- puts 'Usage: luca-deal sub-command [--help|options]'
115
- puts ' customer'
116
- puts ' contract'
117
- puts ' invoice'
413
+ puts 'Proper subcommand needed.'
414
+ puts
415
+ puts 'Usage: luca-deal subcommand [options]'
416
+ puts ' customers'
417
+ puts ' contracts'
418
+ puts ' invoices'
419
+ puts ' new: initialize project dir'
420
+ puts ' export: puts invoice data for LucaBook import'
421
+ exit 1
118
422
  end
@@ -43,8 +43,18 @@ module LucaDeal
43
43
  end
44
44
  end
45
45
 
46
+ def describe(id)
47
+ contract = parse_current(self.class.find(id))
48
+ if contract['products']
49
+ contract['products'] = contract['products'].map do |product|
50
+ Product.find(product['id'])
51
+ end
52
+ end
53
+ readable(contract)
54
+ end
55
+
46
56
  def generate!(customer_id, mode = 'subscription')
47
- LucaDeal::Customer.find(customer_id) do |customer|
57
+ Customer.find(customer_id) do |customer|
48
58
  current_customer = parse_current(customer)
49
59
  if mode == 'sales_fee'
50
60
  obj = salesfee_template
@@ -12,14 +12,27 @@ 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) }
22
- YAML.dump(list).tap { |l| puts l }
20
+ self.class.all.map { |dat| parse_current(dat).sort.to_h }
21
+ end
22
+
23
+ def describe(id)
24
+ customer = parse_current(self.class.find(id))
25
+ contracts = Contract.all.select { |contract| contract['customer_id'] == customer['id'] }
26
+ if !contracts.empty?
27
+ customer['contracts'] = contracts.map do |c|
28
+ {
29
+ 'id' => c['id'],
30
+ 'effective' => c['terms']['effective'],
31
+ 'defunct' => c['terms']['defunct']
32
+ }
33
+ end
34
+ end
35
+ readable(customer)
23
36
  end
24
37
 
25
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_record!(fee, @date, 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)
@@ -1,6 +1,7 @@
1
1
  require 'luca_deal/version'
2
2
 
3
3
  require 'mail'
4
+ require 'json'
4
5
  require 'yaml'
5
6
  require 'pathname'
6
7
  require 'bigdecimal'
@@ -16,26 +17,33 @@ module LucaDeal
16
17
 
17
18
  def initialize(date = nil)
18
19
  @date = issue_date(date)
19
- @pjdir = Pathname(LucaSupport::Config::Pjdir)
20
- @config = load_config(@pjdir / 'config.yml')
21
20
  end
22
21
 
23
- def deliver_mail
24
- attachment_type = @config.dig('invoice', 'attachment') || :html
25
- 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|
26
28
  next if has_status?(dat, 'mail_delivered')
27
29
 
28
- mail = compose_mail(dat, attachment: attachment_type.to_sym)
29
- 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
30
32
  self.class.add_status!(path, 'mail_delivered')
31
33
  end
32
34
  end
33
35
 
34
36
  def preview_mail(attachment_type = nil)
35
- attachment_type ||= @config.dig('invoice', 'attachment') || :html
36
- self.class.asof(@date.year, @date.month) do |dat, _path|
37
- mail = compose_mail(dat, mode: :preview, attachment: attachment_type.to_sym)
38
- 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
39
47
  end
40
48
  end
41
49
 
@@ -45,20 +53,27 @@ module LucaDeal
45
53
 
46
54
  mail = Mail.new
47
55
  mail.to = dat.dig('customer', 'to') if mode.nil?
48
- mail.subject = @config.dig('invoice', 'mail_subject') || 'Your Invoice is available'
56
+ mail.subject = CONFIG.dig('invoice', 'mail_subject') || 'Your Invoice is available'
49
57
  if mode == :preview
50
- mail.cc = @config.dig('mail', 'preview') || @config.dig('mail', 'from')
58
+ mail.cc = CONFIG.dig('mail', 'preview') || CONFIG.dig('mail', 'from')
51
59
  mail.subject = '[preview] ' + mail.subject
52
60
  end
53
61
  mail.text_part = Mail::Part.new(body: render_erb(search_template('invoice-mail.txt.erb')), charset: 'UTF-8')
54
- if attachment == :html
55
- mail.attachments[attachment_name(dat, attachment)] = render_erb(search_template('invoice.html.erb'))
56
- elsif attachment == :pdf
57
- mail.attachments[attachment_name(dat, attachment)] = erb2pdf(search_template('invoice.html.erb'))
58
- end
62
+ mail.attachments[attachment_name(dat, attachment)] = render_invoice(attachment)
59
63
  mail
60
64
  end
61
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
+
62
77
  # Output seriarized invoice data to stdout.
63
78
  # Returns previous N months on multiple count
64
79
  #
@@ -87,33 +102,65 @@ module LucaDeal
87
102
  'customer' => invoice.dig('customer', 'name'),
88
103
  'subtotal' => amount,
89
104
  'tax' => tax,
90
- 'due' => invoice.dig('due_date')
105
+ 'due' => invoice.dig('due_date'),
106
+ 'mail' => invoice.dig('status')&.select { |a| a.keys.include?('mail_delivered') }&.first
91
107
  }
92
108
  end
93
109
  stat['issue_date'] = scan_date.to_s
94
110
  stat['count'] = stat['records'].count
95
111
  stat['total'] = stat['records'].inject(0) { |sum, rec| sum + rec.dig('subtotal') }
96
112
  stat['tax'] = stat['records'].inject(0) { |sum, rec| sum + rec.dig('tax') }
97
- collection << stat
113
+ collection << readable(stat)
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ def export_json
120
+ [].tap do |res|
121
+ self.class.asof(@date.year, @date.month) do |dat|
122
+ item = {}
123
+ item['date'] = dat['issue_date']
124
+ item['debit'] = []
125
+ item['credit'] = []
126
+ dat['subtotal'].map do |sub|
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']) }
98
131
  end
132
+ item['x-customer'] = dat['customer']['name'] if dat.dig('customer', 'name')
133
+ item['x-editor'] = 'LucaDeal'
134
+ res << item
99
135
  end
100
- puts YAML.dump(collection)
136
+ puts JSON.dump(res)
101
137
  end
102
138
  end
103
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
+
104
147
  def monthly_invoice
105
- LucaDeal::Contract.new(@date.to_s).active do |contract|
148
+ Contract.new(@date.to_s).active do |contract|
106
149
  next if contract.dig('terms', 'billing_cycle') != 'monthly'
107
150
  # TODO: provide another I/F for force re-issue if needed
108
151
  next if duplicated_contract? contract['id']
109
152
 
110
- invoice = {}
111
- 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|
112
159
  invoice['contract_id'] = contract['id']
113
160
  invoice['customer'] = get_customer(contract.dig('customer_id'))
114
161
  invoice['due_date'] = due_date(@date)
115
162
  invoice['issue_date'] = @date
116
- invoice['sales_fee'] = contract.dig('sales_fee')
163
+ invoice['sales_fee'] = contract['sales_fee'] if contract.dig('sales_fee')
117
164
  invoice['items'] = get_products(contract['products'])
118
165
  .concat(contract['items']&.map { |i| i['qty'] ||= 1; i } || [])
119
166
  .compact
@@ -121,25 +168,13 @@ module LucaDeal
121
168
  item.dig('type') == 'initial' && subsequent_month?(contract.dig('terms', 'effective'))
122
169
  end
123
170
  invoice['subtotal'] = subtotal(invoice['items'])
124
- .map { |k, v| v.tap { |dat| dat['rate'] = k } }
125
- gen_invoice!(invoice)
171
+ .map { |k, v| v.tap { |dat| dat['rate'] = k } }
126
172
  end
127
173
  end
128
174
 
129
- # set variables for ERB template
130
- #
131
- def invoice_vars(invoice_dat)
132
- @customer = invoice_dat['customer']
133
- @items = invoice_dat['items']
134
- @subtotal = invoice_dat['subtotal']
135
- @issue_date = invoice_dat['issue_date']
136
- @due_date = invoice_dat['due_date']
137
- @amount = @subtotal.inject(0) { |sum, i| sum + i['items'] + i['tax'] }
138
- end
139
-
140
175
  def get_customer(id)
141
176
  {}.tap do |res|
142
- LucaDeal::Customer.find(id) do |dat|
177
+ Customer.find(id) do |dat|
143
178
  customer = parse_current(dat)
144
179
  res['id'] = customer['id']
145
180
  res['name'] = customer.dig('name')
@@ -155,7 +190,7 @@ module LucaDeal
155
190
 
156
191
  [].tap do |res|
157
192
  products.each do |product|
158
- LucaDeal::Product.find(product['id'])['items'].each do |item|
193
+ Product.find(product['id'])['items'].each do |item|
159
194
  item['product_id'] = product['id']
160
195
  item['qty'] ||= 1
161
196
  res << item
@@ -166,7 +201,7 @@ module LucaDeal
166
201
 
167
202
  def gen_invoice!(invoice)
168
203
  id = invoice.dig('contract_id')
169
- self.class.create_record!(invoice, @date, Array(id))
204
+ self.class.create(invoice, date: @date, codes: Array(id))
170
205
  end
171
206
 
172
207
  def issue_date(date)
@@ -176,11 +211,23 @@ module LucaDeal
176
211
 
177
212
  # TODO: support due_date variation
178
213
  def due_date(date)
179
- Date.new(date.year, date.month + 1, -1)
214
+ next_month = date.next_month
215
+ Date.new(next_month.year, next_month.month, -1)
180
216
  end
181
217
 
182
218
  private
183
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
+
184
231
  def lib_path
185
232
  __dir__
186
233
  end
@@ -189,9 +236,9 @@ module LucaDeal
189
236
  #
190
237
  def set_company
191
238
  {}.tap do |h|
192
- h['name'] = @config.dig('company', 'name')
193
- h['address'] = @config.dig('company', 'address')
194
- 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')
195
242
  end
196
243
  end
197
244
 
@@ -201,12 +248,12 @@ module LucaDeal
201
248
  {}.tap do |subtotal|
202
249
  items.each do |i|
203
250
  rate = i.dig('tax') || 'default'
204
- qty = i['qty'] || 1
251
+ qty = i['qty'] || BigDecimal('1')
205
252
  subtotal[rate] = { 'items' => 0, 'tax' => 0 } if subtotal.dig(rate).nil?
206
- subtotal[rate]['items'] += qty * i['price']
253
+ subtotal[rate]['items'] += i['price'] * qty
207
254
  end
208
255
  subtotal.each do |rate, amount|
209
- amount['tax'] = (amount['items'] * load_tax_rate(rate)).to_i
256
+ amount['tax'] = (amount['items'] * load_tax_rate(rate))
210
257
  end
211
258
  end
212
259
  end
@@ -214,13 +261,14 @@ module LucaDeal
214
261
  # load Tax Rate from config.
215
262
  #
216
263
  def load_tax_rate(name)
217
- return 0 if @config.dig('tax_rate', name).nil?
264
+ return 0 if CONFIG.dig('tax_rate', name).nil?
218
265
 
219
- BigDecimal(take_current(@config['tax_rate'], name).to_s)
266
+ BigDecimal(take_current(CONFIG['tax_rate'], name).to_s)
220
267
  end
221
268
 
222
269
  def attachment_name(dat, type)
223
- "invoice-#{dat.dig('id')[0, 7]}.#{type}"
270
+ id = %r{/}.match(dat['id']) ? dat['id'].gsub('/', '') : dat['id'][0, 7]
271
+ "invoice-#{id}.#{type}"
224
272
  end
225
273
 
226
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.14'
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.14
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-07 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: