lucadeal 0.2.15 → 0.2.22

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: 9d81788d16b07f572c9d07bf998cdaa5a8e55db76ea5b1b513eebaae3c783c61
4
- data.tar.gz: d148d1079305e88354c47c3ce5699e2c656a40099228b9c1653488b451a41afb
3
+ metadata.gz: 364352a3a1e23daeffffcd2750f711617677866abf7576fe1f821404f35fa439
4
+ data.tar.gz: f7d5eb22d3df29386be2bac8fd6809af96f78663342e91f16a56bda8d2e64e58
5
5
  SHA512:
6
- metadata.gz: 1f41b4156fb05131e969b0490937c766076e1795a9a6c725e657e1f533333532436b34bb362f42aa9600dcb67e25f080a7a9f6b3bf8ce4a9c2f276990c69e478
7
- data.tar.gz: ae87eae9ca9af5459e3082bf517feb98ec4a98e0af87bb52e625e4067212dd9d6743644a57383c092555d1e90fef4009871a13b6eefc17d1c0f8d5806e76f58d
6
+ metadata.gz: 2cffac9d4b4092a312311d8096d7e77c1b9b2e945d4f31a6a0d81bf0842d137ddf0e3dd04e261f07a051ca634c89d10f8020211aed9d9dc9339a2926002d7a56
7
+ data.tar.gz: 1ab6a3eb3cab72dcdc19efed5a417be613d8ca8f2aded7258b6ad806cec9b0096d81b879763d978809099307db10260087c1fc98df1331c6a7d880811bec5122
@@ -0,0 +1,39 @@
1
+ ## LucaDeal 0.2.25
2
+
3
+ * Breaking change: export key 'value' -> 'amount'
4
+
5
+ ## LucaDeal 0.2.21
6
+
7
+ * Implement `luca-deal fee` subcommands.
8
+ * single invoice creation with contract id fragment.
9
+
10
+ ## LucaDeal 0.2.20
11
+
12
+ * CLI provides `--nu` option. Add JSON output.
13
+ * `luca-deal invoices list --html`, rendering HTML to stdout.
14
+
15
+ ## LucaDeal 0.2.19
16
+
17
+ * CLI id completion on Customer delete, Contract create/delete
18
+ * add `describe` to Customer / Contract
19
+
20
+ ## LucaDeal 0.2.18
21
+
22
+ * Breaking change: restructure CLI in sub-sub command format.
23
+ * Add 'x-customer', 'x-editor' on export to LucaBook
24
+
25
+ ## LucaDeal 0.2.17
26
+
27
+ * `luca-deal export` export JSON for LucaBook
28
+
29
+ ## LucaDeal 0.2.14
30
+
31
+ * Introduce Product for selling items template.
32
+
33
+ ## LucaDeal 0.2.12
34
+
35
+ * Introduce Sales fee calculation.
36
+
37
+ ## LucaDeal 0.2.10
38
+
39
+ * 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,67 @@ 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.
71
+
72
+
73
+ ### Send Invoice
74
+
75
+ Invoice is implemented with HTML & ERB. Copy [default template](lib/luca_deal/templates/invoice.html.erb) to `templates/` in the data directory, and customize.
76
+ If you want to send invoices in PDF, you need to install `wkhtmltopdf command separately. Send mail command is as bellows:`
77
+
78
+ ```
79
+ $ luca-deal invoice mail yyyy m
80
+ ```
81
+
82
+
83
+ ### Sales Fee
84
+
85
+ You can also manage revenue share program with Fee object. Setup proper contract structure.
24
86
 
25
87
 
26
88
  ## 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,28 +102,60 @@ 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' => '売掛金', 'amount' => readable(sub['items']) }
128
+ item['debit'] << { 'label' => '売掛金', 'amount' => readable(sub['tax']) }
129
+ item['credit'] << { 'label' => '売上高', 'amount' => readable(sub['items']) }
130
+ item['credit'] << { 'label' => '売上高', 'amount' => 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(LucaSupport::Code.readable(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)
@@ -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
 
@@ -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.15'
4
+ VERSION = '0.2.22'
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.15
4
+ version: 0.2.22
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: 2021-01-12 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:
@@ -112,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
116
  - !ruby/object:Gem::Version
113
117
  version: '0'
114
118
  requirements: []
115
- rubygems_version: 3.1.2
119
+ rubygems_version: 3.2.3
116
120
  signing_key:
117
121
  specification_version: 4
118
122
  summary: Deal with contracts