lucadeal 0.2.13 → 0.2.19

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: 9a2f79f88f7a7047f07f4c2137888f43e29cec70129e0b8d79544d4c8137e2f4
4
- data.tar.gz: 8d3d2dbf4908f7e5851f64003eaf39fd455f34c1e8f51c07dff7685fa913e036
3
+ metadata.gz: 36ce9f92d92079f0049ec346da2921116e7cde669314bccca3653c28dca33b51
4
+ data.tar.gz: b2f591e9bfb9710fa45cdb1d3582f19997d392e84e639531e4b15e77d6b284c7
5
5
  SHA512:
6
- metadata.gz: feb4db73cf4f5d59a1caafb5e18c7e00460e9903244ba9a43ac1781b8a2ffd7b6465d95a309211e98e487b5b77daa5e1463c4647313f001b80f779c2e23354ad
7
- data.tar.gz: 6204831c6bb54534a3527c7280a5c761bfb422a52831d1dd4d26b484fc221ddc78a979d5b66e42263c1a6d76ebeb140fefcb54c63a203d8b292e6ce40e5e2e05
6
+ metadata.gz: 8e2ca7f2a7063d44d29013cdd7448f807c58dbabca341cdeb1a8a118e24ca826ca94ac608f1c4a56eb16cb41cb039a0b39d2b07cb011fdd4a57e4b04c24a7d92
7
+ data.tar.gz: 692b888b1209de7fa6329ffbc7edd1ef7ad84be530b2dad7fcaad64b5c621d0da424e3ac90f21e9db7113057599497c91b61d70f479cf48bdea6bee2c49e1f0e
@@ -0,0 +1,25 @@
1
+ ## LucaDeal 0.2.19
2
+
3
+ * CLI id completion on Customer delete, Contract create/delete
4
+ * add `describe` to Customer / Contract
5
+
6
+ ## LucaDeal 0.2.18
7
+
8
+ * Breaking change: restructure CLI in sub-sub command format.
9
+ * Add 'x-customer', 'x-editor' on export to LucaBook
10
+
11
+ ## LucaDeal 0.2.17
12
+
13
+ * `luca-deal export` export JSON for LucaBook
14
+
15
+ ## LucaDeal 0.2.14
16
+
17
+ * Introduce Product for selling items template.
18
+
19
+ ## LucaDeal 0.2.12
20
+
21
+ * Introduce Sales fee calculation.
22
+
23
+ ## LucaDeal 0.2.10
24
+
25
+ * 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,46 @@ 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
+ Invoice conditions are defined by contracts.
24
65
 
25
66
 
26
67
  ## Data Structure
@@ -75,6 +116,8 @@ Fields for subscription customers are as bellows:
75
116
  | terms | | | | |
76
117
  | | billing_cycle | optional | | If 'monthly', invoices are generated on each month. |
77
118
  | | category | optional | | Default: 'subscription' |
119
+ | products | | | | Array of products. |
120
+ | | id | | | reference for Product |
78
121
  | items | | | | Array of items. |
79
122
  | | name | | x | Item name. |
80
123
  | | price | | x | Item price. |
@@ -115,6 +158,7 @@ Invoice is basically auto generated from Customer and Contract objects.
115
158
  | | price | Item price. |
116
159
  | | qty | quantity. Default: 1. |
117
160
  | | type | |
161
+ | | product_id | refrence for Product |
118
162
  | subtotal | | Array of subtotal by tax category. |
119
163
  | | items | amount of items |
120
164
  | | tax | amount of tax |
@@ -5,57 +5,170 @@ 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
+ module LucaCmd
9
+ class Customer
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
+ id = LucaDeal::Customer.new.describe(list.first)
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
+ LucaDeal::Customer.new.list_name
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
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
+ id = LucaDeal::Contract.new.describe(list.first)
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
126
+ def self.create(args = nil, params = {})
127
+ date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
128
+ case params['mode']
129
+ when 'monthly'
130
+ LucaDeal::Invoice.new(date).monthly_invoice
131
+ else
132
+ puts 'not implemented mode'
133
+ end
134
+ end
135
+
136
+ def self.delete(args = nil, params = {})
137
+ if args
138
+ id = LucaDeal::Invoice.delete(args[0])
139
+ else
140
+ puts 'requires contract id. exit'
141
+ exit 1
142
+ end
143
+ end
144
+
145
+ def self.export(args = nil, _params = nil)
146
+ if args
147
+ args << 28 if args.length == 2 # specify safe last day
148
+ LucaDeal::Invoice.new(args.join('-')).export_json
149
+ else
150
+ LucaDeal::Invoice.new.export_json
151
+ end
152
+ end
153
+
154
+ def self.list(args = nil, params = {})
155
+ date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
156
+ if args.empty?
157
+ date = "#{Date.today.year}-#{Date.today.month}-1"
158
+ count = 3
159
+ end
160
+ LucaDeal::Invoice.new(date).stats(count || 1)
161
+ end
162
+
163
+ def self.mail(args = nil, params = {})
164
+ date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
165
+ case params['mode']
166
+ when 'preview'
167
+ LucaDeal::Invoice.new(date).preview_mail
168
+ else
169
+ LucaDeal::Invoice.new(date).deliver_mail
170
+ end
171
+ end
59
172
  end
60
173
  end
61
174
 
@@ -65,54 +178,115 @@ end
65
178
 
66
179
  LucaRecord::Base.valid_project?
67
180
  cmd = ARGV.shift
181
+ params = {}
68
182
 
69
183
  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
184
+ when /customers?/
185
+ subcmd = ARGV.shift
186
+ case subcmd
187
+ when 'list'
188
+ OptionParser.new do |opt|
189
+ opt.banner = 'Usage: luca-deal customers list [options]'
190
+ args = opt.parse(ARGV)
191
+ LucaCmd::Customer.list(args, params)
78
192
  end
79
- args = opt.parse(ARGV)
80
- customer(args, params)
81
- 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
193
+ when 'create'
194
+ OptionParser.new do |opt|
195
+ opt.banner = 'Usage: luca-deal customers create CustomerName'
196
+ args = opt.parse(ARGV)
197
+ LucaCmd::Customer.create(args, params)
89
198
  end
90
- opt.on('--salesfee', 'create contract as sales fee definition') do |_v|
91
- params['category'] = 'sales_fee'
199
+ when 'describe'
200
+ LucaCmd::Customer.describe(ARGV)
201
+ when 'delete'
202
+ LucaCmd::Customer.delete(ARGV)
203
+ else
204
+ puts 'Proper subcommand needed.'
205
+ puts
206
+ puts 'Usage: luca-deal customer[s] subcommands [--help|options]'
207
+ puts ' create'
208
+ puts ' list'
209
+ puts ' describe: show customer with contracts info'
210
+ puts ' delete'
211
+ exit 1
212
+ end
213
+ when /contracts?/
214
+ subcmd = ARGV.shift
215
+ case subcmd
216
+ when 'create'
217
+ OptionParser.new do |opt|
218
+ opt.banner = 'Usage: luca-deal contracts create [options] CustomerId'
219
+ opt.on('--salesfee', 'create contract as sales fee definition') do |_v|
220
+ params['category'] = 'sales_fee'
221
+ end
222
+ args = opt.parse(ARGV)
223
+ LucaCmd::Contract.create(args, params)
92
224
  end
93
- args = opt.parse(ARGV)
94
- contract(args, params)
225
+ when 'describe'
226
+ LucaCmd::Contract.describe(ARGV)
227
+ when 'delete'
228
+ LucaCmd::Contract.delete(ARGV)
229
+ else
230
+ puts 'Proper subcommand needed.'
231
+ puts
232
+ puts 'Usage: luca-deal contract[s] subcommand [--help|options]'
233
+ puts ' create'
234
+ puts ' describe: show contract with puroducts or items info'
235
+ puts ' delete'
236
+ exit 1
95
237
  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)
238
+ when 'export'
239
+ LucaCmd::Invoice.export(ARGV)
240
+ when /invoices?/, 'i'
241
+ subcmd = ARGV.shift
242
+ case subcmd
243
+ when 'create'
244
+ OptionParser.new do |opt|
245
+ opt.banner = 'Usage: luca-deal invoices create [options] year month [date]'
246
+ opt.on('--monthly', 'generate monthly data') { |_v| params['mode'] = 'monthly' }
247
+ args = opt.parse(ARGV)
248
+ LucaCmd::Invoice.create(args, params)
249
+ end
250
+ when 'delete'
251
+ LucaCmd::Invoice.delete(ARGV)
252
+ when 'list'
253
+ OptionParser.new do |opt|
254
+ opt.banner = 'Usage: luca-deal invoices list [options] year month [date]'
255
+ args = opt.parse(ARGV)
256
+ LucaCmd::Invoice.list(args, params)
257
+ end
258
+ when 'mail'
259
+ OptionParser.new do |opt|
260
+ opt.banner = 'Usage: luca-deal invoices mail [options] year month [date]'
261
+ opt.on('--preview', 'send to preview user') { |_v| params['mode'] = 'preview' }
262
+ args = opt.parse(ARGV)
263
+ LucaCmd::Invoice.mail(args, params)
264
+ end
265
+ else
266
+ puts 'Proper subcommand needed.'
267
+ puts
268
+ puts 'Usage: luca-deal invoices subcommand [--help|options]'
269
+ puts ' create'
270
+ puts ' delete'
271
+ puts ' list'
272
+ puts ' mail: send mail with invoice'
273
+ exit 1
106
274
  end
107
275
  when 'new'
108
276
  params = {}
109
277
  OptionParser.new do |opt|
278
+ opt.banner = 'Usage: luca-deal new DIR'
110
279
  args = opt.parse(ARGV)
111
280
  new_pj(args, params)
112
281
  end
113
282
  else
114
- puts 'Usage: luca-deal sub-command [--help|options]'
115
- puts ' customer'
116
- puts ' contract'
117
- puts ' invoice'
283
+ puts 'Proper subcommand needed.'
284
+ puts
285
+ puts 'Usage: luca-deal subcommand [options]'
286
+ puts ' customers'
287
+ puts ' contracts'
288
+ puts ' invoices'
289
+ puts ' new: initialize project dir'
290
+ puts ' export: puts invoice data for LucaBook import'
291
+ exit 1
118
292
  end
@@ -9,6 +9,7 @@ require 'luca_record'
9
9
  module LucaDeal
10
10
  class Contract < LucaRecord::Base
11
11
  @dirname = 'contracts'
12
+ @required = ['customer_id', 'terms']
12
13
 
13
14
  def initialize(date = nil)
14
15
  @date = date ? Date.parse(date) : Date.today
@@ -42,6 +43,16 @@ module LucaDeal
42
43
  end
43
44
  end
44
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
+ LucaDeal::Product.find(product['id'])
51
+ end
52
+ end
53
+ YAML.dump(readable(contract)).tap{ |d| puts d }
54
+ end
55
+
45
56
  def generate!(customer_id, mode = 'subscription')
46
57
  LucaDeal::Customer.find(customer_id) do |customer|
47
58
  current_customer = parse_current(customer)
@@ -10,6 +10,7 @@ require 'luca_record'
10
10
  module LucaDeal
11
11
  class Customer < LucaRecord::Base
12
12
  @dirname = 'customers'
13
+ @required = ['name']
13
14
 
14
15
  def initialize(pjdir = nil)
15
16
  @date = Date.today
@@ -17,10 +18,25 @@ module LucaDeal
17
18
  end
18
19
 
19
20
  def list_name
20
- list = self.class.all.map { |dat| parse_current(dat) }
21
+ list = self.class.all.map { |dat| parse_current(dat).sort.to_h }
21
22
  YAML.dump(list).tap { |l| puts l }
22
23
  end
23
24
 
25
+ def describe(id)
26
+ customer = parse_current(self.class.find(id))
27
+ contracts = LucaDeal::Contract.all.select { |contract| contract['customer_id'] == customer['id'] }
28
+ if !contracts.empty?
29
+ customer['contracts'] = contracts.map do |c|
30
+ {
31
+ 'id' => c['id'],
32
+ 'effective' => c['terms']['effective'],
33
+ 'defunct' => c['terms']['defunct']
34
+ }
35
+ end
36
+ end
37
+ YAML.dump(readable(customer)).tap{ |d| puts d }
38
+ end
39
+
24
40
  def self.create(obj)
25
41
  raise ':name is required' if obj[:name].nil?
26
42
 
@@ -60,7 +60,7 @@ module LucaDeal
60
60
 
61
61
  def gen_fee!(fee)
62
62
  id = fee.dig('invoice', 'contract_id')
63
- self.class.create_record!(fee, @date, Array(id))
63
+ self.class.create(fee, date: @date, codes: Array(id))
64
64
  end
65
65
 
66
66
  private
@@ -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'
@@ -12,6 +13,7 @@ require 'luca_record'
12
13
  module LucaDeal
13
14
  class Invoice < LucaRecord::Base
14
15
  @dirname = 'invoices'
16
+ @required = ['issue_date', 'customer', 'items', 'subtotal']
15
17
 
16
18
  def initialize(date = nil)
17
19
  @date = issue_date(date)
@@ -86,7 +88,8 @@ module LucaDeal
86
88
  'customer' => invoice.dig('customer', 'name'),
87
89
  'subtotal' => amount,
88
90
  'tax' => tax,
89
- 'due' => invoice.dig('due_date')
91
+ 'due' => invoice.dig('due_date'),
92
+ 'mail' => invoice.dig('status')&.select { |a| a.keys.include?('mail_delivered') }&.first
90
93
  }
91
94
  end
92
95
  stat['issue_date'] = scan_date.to_s
@@ -96,7 +99,28 @@ module LucaDeal
96
99
  collection << stat
97
100
  end
98
101
  end
99
- puts YAML.dump(collection)
102
+ puts YAML.dump(LucaSupport::Code.readable(collection))
103
+ end
104
+ end
105
+
106
+ def export_json
107
+ [].tap do |res|
108
+ self.class.asof(@date.year, @date.month) do |dat|
109
+ item = {}
110
+ item['date'] = dat['issue_date']
111
+ item['debit'] = []
112
+ item['credit'] = []
113
+ 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']) }
118
+ end
119
+ item['x-customer'] = dat['customer']['name'] if dat.dig('customer', 'name')
120
+ item['x-editor'] = 'LucaDeal'
121
+ res << item
122
+ end
123
+ puts JSON.dump(res)
100
124
  end
101
125
  end
102
126
 
@@ -112,17 +136,13 @@ module LucaDeal
112
136
  invoice['customer'] = get_customer(contract.dig('customer_id'))
113
137
  invoice['due_date'] = due_date(@date)
114
138
  invoice['issue_date'] = @date
115
- invoice['sales_fee'] = contract.dig('sales_fee')
116
- invoice['items'] = contract.dig('items').map do |item|
117
- next if item.dig('type') == 'initial' && subsequent_month?(contract.dig('terms', 'effective'))
118
-
119
- {}.tap do |h|
120
- h['name'] = item.dig('name')
121
- h['price'] = item.dig('price')
122
- h['qty'] = item.dig('qty') || 1
123
- h['type'] = item.dig('type') if item.dig('type')
124
- end
125
- end.compact
139
+ invoice['sales_fee'] = contract['sales_fee'] if contract.dig('sales_fee')
140
+ invoice['items'] = get_products(contract['products'])
141
+ .concat(contract['items']&.map { |i| i['qty'] ||= 1; i } || [])
142
+ .compact
143
+ invoice['items'].reject! do |item|
144
+ item.dig('type') == 'initial' && subsequent_month?(contract.dig('terms', 'effective'))
145
+ end
126
146
  invoice['subtotal'] = subtotal(invoice['items'])
127
147
  .map { |k, v| v.tap { |dat| dat['rate'] = k } }
128
148
  gen_invoice!(invoice)
@@ -153,9 +173,23 @@ module LucaDeal
153
173
  end
154
174
  end
155
175
 
176
+ def get_products(products)
177
+ return [] if products.nil?
178
+
179
+ [].tap do |res|
180
+ products.each do |product|
181
+ LucaDeal::Product.find(product['id'])['items'].each do |item|
182
+ item['product_id'] = product['id']
183
+ item['qty'] ||= 1
184
+ res << item
185
+ end
186
+ end
187
+ end
188
+ end
189
+
156
190
  def gen_invoice!(invoice)
157
191
  id = invoice.dig('contract_id')
158
- self.class.create_record!(invoice, @date, Array(id))
192
+ self.class.create(invoice, date: @date, codes: Array(id))
159
193
  end
160
194
 
161
195
  def issue_date(date)
@@ -190,11 +224,12 @@ module LucaDeal
190
224
  {}.tap do |subtotal|
191
225
  items.each do |i|
192
226
  rate = i.dig('tax') || 'default'
227
+ qty = i['qty'] || BigDecimal('1')
193
228
  subtotal[rate] = { 'items' => 0, 'tax' => 0 } if subtotal.dig(rate).nil?
194
- subtotal[rate]['items'] += i['qty'] * i['price']
229
+ subtotal[rate]['items'] += i['price'] * qty
195
230
  end
196
231
  subtotal.each do |rate, amount|
197
- amount['tax'] = (amount['items'] * load_tax_rate(rate)).to_i
232
+ amount['tax'] = (amount['items'] * load_tax_rate(rate))
198
233
  end
199
234
  end
200
235
  end
@@ -10,32 +10,40 @@ require 'luca_record'
10
10
  module LucaDeal
11
11
  class Product < LucaRecord::Base
12
12
  @dirname = 'products'
13
+ @required = ['items']
13
14
 
14
15
  def list_name
15
16
  list = self.class.all.map { |dat| parse_current(dat) }
16
17
  YAML.dump(list).tap { |l| puts l }
17
18
  end
18
19
 
20
+ # Save data with hash in Product format. Simple format is also available as bellows:
21
+ # {
22
+ # name: 'item_name(required)', price: 'item_price', qty: 'item_qty',
23
+ # initial: { name: 'item_name', price: 'item_price', qty: 'item_qty' }
24
+ # }
19
25
  def self.create(obj)
20
- raise ':name is required' if obj[:name].nil?
21
-
22
- items = [{
23
- 'name' => obj[:name],
24
- 'price' => obj[:price] || 0,
25
- 'qty' => obj[:qty] || 1
26
- }]
27
- if obj[:initial]
28
- items << {
29
- 'name' => obj.dig(:initial, :name),
30
- 'price' => obj.dig(:initial, :price) || 0,
31
- 'qty' => obj.dig(:initial, :qty) || 1,
32
- 'type' => 'initial'
26
+ if obj[:name].nil?
27
+ h = obj
28
+ else
29
+ items = [{
30
+ 'name' => obj[:name],
31
+ 'price' => obj[:price] || 0,
32
+ 'qty' => obj[:qty] || 1
33
+ }]
34
+ if obj[:initial]
35
+ items << {
36
+ 'name' => obj.dig(:initial, :name),
37
+ 'price' => obj.dig(:initial, :price) || 0,
38
+ 'qty' => obj.dig(:initial, :qty) || 1,
39
+ 'type' => 'initial'
40
+ }
41
+ end
42
+ h = {
43
+ 'name' => obj[:name],
44
+ 'items' => items
33
45
  }
34
46
  end
35
- h = {
36
- 'name' => obj[:name],
37
- 'items' => items
38
- }
39
47
  super(h)
40
48
  end
41
49
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LucaDeal
4
- VERSION = '0.2.13'
4
+ VERSION = '0.2.19'
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.13
4
+ version: 0.2.19
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-05 00:00:00.000000000 Z
11
+ date: 2020-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lucarecord