lucadeal 0.2.18 → 0.2.24
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 +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +23 -1
- data/exe/luca-deal +265 -30
- data/lib/luca_deal.rb +1 -0
- data/lib/luca_deal/contract.rb +11 -1
- data/lib/luca_deal/customer.rb +17 -4
- data/lib/luca_deal/fee.rb +165 -32
- data/lib/luca_deal/invoice.rb +107 -52
- data/lib/luca_deal/no_invoice.rb +20 -0
- data/lib/luca_deal/setup.rb +1 -1
- data/lib/luca_deal/templates/fee-report-mail.txt.erb +6 -0
- data/lib/luca_deal/templates/fee-report.html.erb +54 -0
- data/lib/luca_deal/templates/invoice-mail.txt.erb +6 -0
- data/lib/luca_deal/templates/invoice.html.erb +52 -0
- data/lib/luca_deal/templates/monthly-payment-list.html.erb +54 -0
- data/lib/luca_deal/version.rb +1 -1
- metadata +9 -3
data/lib/luca_deal.rb
CHANGED
@@ -8,6 +8,7 @@ module LucaDeal
|
|
8
8
|
autoload :Contract, 'luca_deal/contract'
|
9
9
|
autoload :Fee, 'luca_deal/fee'
|
10
10
|
autoload :Invoice, 'luca_deal/invoice'
|
11
|
+
autoload :NoInvoice, 'luca_deal/no_invoice'
|
11
12
|
autoload :Product, 'luca_deal/product'
|
12
13
|
autoload :Setup, 'luca_deal/setup'
|
13
14
|
end
|
data/lib/luca_deal/contract.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/luca_deal/customer.rb
CHANGED
@@ -12,14 +12,27 @@ module LucaDeal
|
|
12
12
|
@dirname = 'customers'
|
13
13
|
@required = ['name']
|
14
14
|
|
15
|
-
def initialize
|
15
|
+
def initialize
|
16
16
|
@date = Date.today
|
17
|
-
@pjdir = pjdir || Dir.pwd
|
18
17
|
end
|
19
18
|
|
20
19
|
def list_name
|
21
|
-
|
22
|
-
|
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)
|
data/lib/luca_deal/fee.rb
CHANGED
@@ -14,40 +14,145 @@ 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
|
-
|
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
|
+
limit = contract.dig('terms', 'limit')
|
28
29
|
|
29
|
-
|
30
|
+
fee = { 'contract_id' => contract['id'], 'items' => [] }
|
31
|
+
fee['customer'] = get_customer(contract['customer_id'])
|
32
|
+
fee['issue_date'] = @date
|
33
|
+
Invoice.asof(@date.year, @date.month) do |invoice|
|
30
34
|
next if invoice.dig('sales_fee', 'id') != contract['id']
|
31
|
-
next if
|
35
|
+
next if exceed_limit?(invoice, limit)
|
32
36
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
37
|
+
invoice['items'].each do |item|
|
38
|
+
rate = item['type'] == 'initial' ? @rate['initial'] : @rate['default']
|
39
|
+
fee['items'] << fee_record(invoice, item, rate)
|
40
|
+
end
|
41
|
+
fee['sales_fee'] = subtotal(fee['items'])
|
42
|
+
end
|
43
|
+
NoInvoice.asof(@date.year, @date.month) do |no_invoice|
|
44
|
+
next if no_invoice.dig('sales_fee', 'id') != contract['id']
|
45
|
+
next if exceed_limit?(invoice, limit)
|
46
|
+
|
47
|
+
no_invoice['items'].each do |item|
|
48
|
+
rate = item['type'] == 'initial' ? @rate['initial'] : @rate['default']
|
49
|
+
fee['items'] << fee_record(no_invoice, item, rate)
|
50
|
+
end
|
51
|
+
fee['sales_fee'] = subtotal(fee['items'])
|
52
|
+
end
|
53
|
+
self.class.create(fee, date: @date, codes: Array(contract['id']))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def deliver_mail(attachment_type = nil, mode: nil)
|
58
|
+
attachment_type = CONFIG.dig('fee', 'attachment') || :html
|
59
|
+
fees = self.class.asof(@date.year, @date.month)
|
60
|
+
raise "No report for #{@date.year}/#{@date.month}" if fees.count.zero?
|
61
|
+
|
62
|
+
fees.each do |dat, path|
|
63
|
+
next if has_status?(dat, 'mail_delivered')
|
64
|
+
|
65
|
+
mail = compose_mail(dat, mode: mode, attachment: attachment_type.to_sym)
|
66
|
+
LucaSupport::Mail.new(mail, PJDIR).deliver
|
67
|
+
self.class.add_status!(path, 'mail_delivered')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def preview_mail(attachment_type = nil)
|
72
|
+
deliver_mail(attachment_type, mode: :preview)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Render HTML to console
|
76
|
+
#
|
77
|
+
def preview_stdout
|
78
|
+
self.class.asof(@date.year, @date.month) do |dat, _|
|
79
|
+
@company = set_company
|
80
|
+
fee_vars(dat)
|
81
|
+
puts render_report
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def compose_mail(dat, mode: nil, attachment: :html)
|
86
|
+
@company = set_company
|
87
|
+
fee_vars(dat)
|
88
|
+
|
89
|
+
mail = Mail.new
|
90
|
+
mail.to = dat.dig('customer', 'to') if mode.nil?
|
91
|
+
mail.subject = CONFIG.dig('invoice', 'mail_subject') || 'Your Report is available'
|
92
|
+
if mode == :preview
|
93
|
+
mail.cc = CONFIG.dig('mail', 'preview') || CONFIG.dig('mail', 'from')
|
94
|
+
mail.subject = '[preview] ' + mail.subject
|
95
|
+
end
|
96
|
+
mail.text_part = Mail::Part.new(body: render_erb(search_template('fee-report-mail.txt.erb')), charset: 'UTF-8')
|
97
|
+
mail.attachments[attachment_name(dat, attachment)] = render_report(attachment)
|
98
|
+
mail
|
99
|
+
end
|
100
|
+
|
101
|
+
# Output seriarized fee data to stdout.
|
102
|
+
# Returns previous N months on multiple count
|
103
|
+
#
|
104
|
+
# === Example YAML output
|
105
|
+
# ---
|
106
|
+
# - records:
|
107
|
+
# - customer: Example Co.
|
108
|
+
# subtotal: 100000
|
109
|
+
# tax: 10000
|
110
|
+
# due: 2020-10-31
|
111
|
+
# issue_date: '2020-09-30'
|
112
|
+
# count: 1
|
113
|
+
# total: 100000
|
114
|
+
# tax: 10000
|
115
|
+
#
|
116
|
+
def stats(count = 1)
|
117
|
+
[].tap do |collection|
|
118
|
+
scan_date = @date.next_month
|
119
|
+
count.times do
|
120
|
+
scan_date = scan_date.prev_month
|
121
|
+
{}.tap do |stat|
|
122
|
+
stat['records'] = self.class.asof(scan_date.year, scan_date.month).map do |fee|
|
123
|
+
{
|
124
|
+
'customer' => fee.dig('customer', 'name'),
|
125
|
+
'client' => fee['items'].map{ |item| item.dig('customer_name') }.join(' / '),
|
126
|
+
'subtotal' => fee.dig('sales_fee', 'fee'),
|
127
|
+
'tax' => fee.dig('sales_fee', 'tax'),
|
128
|
+
'due' => fee.dig('due_date'),
|
129
|
+
'mail' => fee.dig('status')&.select { |a| a.keys.include?('mail_delivered') }&.first
|
130
|
+
}
|
38
131
|
end
|
132
|
+
stat['issue_date'] = scan_date.to_s
|
133
|
+
stat['count'] = stat['records'].count
|
134
|
+
stat['total'] = stat['records'].inject(0) { |sum, rec| sum + rec.dig('subtotal') }
|
135
|
+
stat['tax'] = stat['records'].inject(0) { |sum, rec| sum + rec.dig('tax') }
|
136
|
+
collection << readable(stat)
|
39
137
|
end
|
40
|
-
fee['id'] = issue_random_id
|
41
|
-
fee['customer'].delete('to')
|
42
|
-
fee['sales_fee'].merge! subtotal(fee['items'])
|
43
|
-
gen_fee!(fee)
|
44
138
|
end
|
45
139
|
end
|
46
140
|
end
|
47
141
|
|
142
|
+
def render_report(file_type = :html)
|
143
|
+
case file_type
|
144
|
+
when :html
|
145
|
+
render_erb(search_template('fee-report.html.erb'))
|
146
|
+
when :pdf
|
147
|
+
erb2pdf(search_template('fee-report.html.erb'))
|
148
|
+
else
|
149
|
+
raise 'This filetype is not supported.'
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
48
153
|
def get_customer(id)
|
49
154
|
{}.tap do |res|
|
50
|
-
|
155
|
+
Customer.find(id) do |dat|
|
51
156
|
customer = parse_current(dat)
|
52
157
|
res['id'] = customer['id']
|
53
158
|
res['name'] = customer.dig('name')
|
@@ -58,13 +163,19 @@ module LucaDeal
|
|
58
163
|
end
|
59
164
|
end
|
60
165
|
|
61
|
-
def gen_fee!(fee)
|
62
|
-
id = fee.dig('invoice', 'contract_id')
|
63
|
-
self.class.create(fee, date: @date, codes: Array(id))
|
64
|
-
end
|
65
|
-
|
66
166
|
private
|
67
167
|
|
168
|
+
# set variables for ERB template
|
169
|
+
#
|
170
|
+
def fee_vars(fee_dat)
|
171
|
+
@customer = fee_dat['customer']
|
172
|
+
@items = readable(fee_dat['items'])
|
173
|
+
@sales_fee = readable(fee_dat['sales_fee'])
|
174
|
+
@issue_date = fee_dat['issue_date']
|
175
|
+
@due_date = fee_dat['due_date']
|
176
|
+
@amount = readable(fee_dat['sales_fee'].inject(0) { |sum, (_k, v)| sum + v })
|
177
|
+
end
|
178
|
+
|
68
179
|
def lib_path
|
69
180
|
__dir__
|
70
181
|
end
|
@@ -73,25 +184,20 @@ module LucaDeal
|
|
73
184
|
#
|
74
185
|
def set_company
|
75
186
|
{}.tap do |h|
|
76
|
-
h['name'] =
|
77
|
-
h['address'] =
|
78
|
-
h['address2'] =
|
187
|
+
h['name'] = CONFIG.dig('company', 'name')
|
188
|
+
h['address'] = CONFIG.dig('company', 'address')
|
189
|
+
h['address2'] = CONFIG.dig('company', 'address2')
|
79
190
|
end
|
80
191
|
end
|
81
192
|
|
82
193
|
# calc fee & tax amount by tax category
|
83
194
|
#
|
84
195
|
def subtotal(items)
|
85
|
-
{}.tap do |subtotal|
|
196
|
+
{ 'fee' => 0, 'tax' => 0 }.tap do |subtotal|
|
86
197
|
items.each do |i|
|
87
|
-
|
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
|
198
|
+
subtotal['fee'] += i['fee']
|
94
199
|
end
|
200
|
+
subtotal['tax'] = (subtotal['fee'] * load_tax_rate('default')).to_i
|
95
201
|
end
|
96
202
|
end
|
97
203
|
|
@@ -100,19 +206,46 @@ module LucaDeal
|
|
100
206
|
Date.new(base.year, base.month, -1)
|
101
207
|
end
|
102
208
|
|
209
|
+
# TODO: support due_date variation
|
210
|
+
def due_date(date)
|
211
|
+
next_month = date.next_month
|
212
|
+
Date.new(next_month.year, next_month.month, -1)
|
213
|
+
end
|
214
|
+
|
103
215
|
# load Tax Rate from config.
|
104
216
|
#
|
105
217
|
def load_tax_rate(name)
|
106
|
-
return 0 if
|
218
|
+
return 0 if CONFIG.dig('tax_rate', name).nil?
|
107
219
|
|
108
|
-
BigDecimal(take_current(
|
220
|
+
BigDecimal(take_current(CONFIG['tax_rate'], name).to_s)
|
109
221
|
end
|
110
222
|
|
223
|
+
# Fees are unique contract_id in each month
|
224
|
+
# If update needed, remove the target fee file.
|
225
|
+
#
|
111
226
|
def duplicated_contract?(id)
|
112
227
|
self.class.asof(@date.year, @date.month, @date.day) do |_f, path|
|
113
228
|
return true if path.include?(id)
|
114
229
|
end
|
115
230
|
false
|
116
231
|
end
|
232
|
+
|
233
|
+
def fee_record(invoice, item, rate)
|
234
|
+
{
|
235
|
+
'invoice_id' => invoice['id'],
|
236
|
+
'customer_name' => invoice.dig('customer', 'name'),
|
237
|
+
'name' => item['name'],
|
238
|
+
'price' => item['price'],
|
239
|
+
'qty' => item['qty'],
|
240
|
+
'fee' => item['price'] * item['qty'] * rate
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
def exceed_limit?(invoice, limit)
|
245
|
+
return false if limit.nil?
|
246
|
+
|
247
|
+
contract_start = Contract.find(invoice['contract_id']).dig('terms', 'effective')
|
248
|
+
contract_start.next_month(limit).prev_day < @date
|
249
|
+
end
|
117
250
|
end
|
118
251
|
end
|
data/lib/luca_deal/invoice.rb
CHANGED
@@ -17,26 +17,33 @@ module LucaDeal
|
|
17
17
|
|
18
18
|
def initialize(date = nil)
|
19
19
|
@date = issue_date(date)
|
20
|
-
@pjdir = Pathname(LucaSupport::Config::Pjdir)
|
21
|
-
@config = load_config(@pjdir / 'config.yml')
|
22
20
|
end
|
23
21
|
|
24
|
-
def deliver_mail
|
25
|
-
attachment_type =
|
26
|
-
self.class.asof(@date.year, @date.month)
|
22
|
+
def deliver_mail(attachment_type = nil, mode: nil)
|
23
|
+
attachment_type = CONFIG.dig('invoice', 'attachment') || :html
|
24
|
+
invoices = self.class.asof(@date.year, @date.month)
|
25
|
+
raise "No invoice for #{@date.year}/#{@date.month}" if invoices.count.zero?
|
26
|
+
|
27
|
+
invoices.each do |dat, path|
|
27
28
|
next if has_status?(dat, 'mail_delivered')
|
28
29
|
|
29
|
-
mail = compose_mail(dat, attachment: attachment_type.to_sym)
|
30
|
-
LucaSupport::Mail.new(mail,
|
30
|
+
mail = compose_mail(dat, mode: mode, attachment: attachment_type.to_sym)
|
31
|
+
LucaSupport::Mail.new(mail, PJDIR).deliver
|
31
32
|
self.class.add_status!(path, 'mail_delivered')
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
35
36
|
def preview_mail(attachment_type = nil)
|
36
|
-
attachment_type
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
deliver_mail(attachment_type, mode: :preview)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Render HTML to console
|
41
|
+
#
|
42
|
+
def preview_stdout
|
43
|
+
self.class.asof(@date.year, @date.month) do |dat, _|
|
44
|
+
@company = set_company
|
45
|
+
invoice_vars(dat)
|
46
|
+
puts render_invoice
|
40
47
|
end
|
41
48
|
end
|
42
49
|
|
@@ -46,20 +53,27 @@ module LucaDeal
|
|
46
53
|
|
47
54
|
mail = Mail.new
|
48
55
|
mail.to = dat.dig('customer', 'to') if mode.nil?
|
49
|
-
mail.subject =
|
56
|
+
mail.subject = CONFIG.dig('invoice', 'mail_subject') || 'Your Invoice is available'
|
50
57
|
if mode == :preview
|
51
|
-
mail.cc =
|
58
|
+
mail.cc = CONFIG.dig('mail', 'preview') || CONFIG.dig('mail', 'from')
|
52
59
|
mail.subject = '[preview] ' + mail.subject
|
53
60
|
end
|
54
61
|
mail.text_part = Mail::Part.new(body: render_erb(search_template('invoice-mail.txt.erb')), charset: 'UTF-8')
|
55
|
-
|
56
|
-
mail.attachments[attachment_name(dat, attachment)] = render_erb(search_template('invoice.html.erb'))
|
57
|
-
elsif attachment == :pdf
|
58
|
-
mail.attachments[attachment_name(dat, attachment)] = erb2pdf(search_template('invoice.html.erb'))
|
59
|
-
end
|
62
|
+
mail.attachments[attachment_name(dat, attachment)] = render_invoice(attachment)
|
60
63
|
mail
|
61
64
|
end
|
62
65
|
|
66
|
+
def render_invoice(file_type = :html)
|
67
|
+
case file_type
|
68
|
+
when :html
|
69
|
+
render_erb(search_template('invoice.html.erb'))
|
70
|
+
when :pdf
|
71
|
+
erb2pdf(search_template('invoice.html.erb'))
|
72
|
+
else
|
73
|
+
raise 'This filetype is not supported.'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
63
77
|
# Output seriarized invoice data to stdout.
|
64
78
|
# Returns previous N months on multiple count
|
65
79
|
#
|
@@ -88,18 +102,47 @@ module LucaDeal
|
|
88
102
|
'customer' => invoice.dig('customer', 'name'),
|
89
103
|
'subtotal' => amount,
|
90
104
|
'tax' => tax,
|
91
|
-
'due' => invoice.dig('due_date')
|
105
|
+
'due' => invoice.dig('due_date'),
|
106
|
+
'mail' => invoice.dig('status')&.select { |a| a.keys.include?('mail_delivered') }&.first
|
92
107
|
}
|
93
108
|
end
|
94
109
|
stat['issue_date'] = scan_date.to_s
|
95
110
|
stat['count'] = stat['records'].count
|
96
111
|
stat['total'] = stat['records'].inject(0) { |sum, rec| sum + rec.dig('subtotal') }
|
97
112
|
stat['tax'] = stat['records'].inject(0) { |sum, rec| sum + rec.dig('tax') }
|
98
|
-
collection << stat
|
113
|
+
collection << readable(stat)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# send payment list to preview address or from address.
|
120
|
+
#
|
121
|
+
def stats_email
|
122
|
+
{}.tap do |res|
|
123
|
+
stats(3).each.with_index(1) do |stat, i|
|
124
|
+
stat['records'].each do |record|
|
125
|
+
res[record['customer']] ||= {}
|
126
|
+
res[record['customer']]['customer_name'] ||= record['customer']
|
127
|
+
res[record['customer']]["amount#{i}"] ||= record['subtotal']
|
128
|
+
res[record['customer']]["tax#{i}"] ||= record['tax']
|
129
|
+
end
|
130
|
+
if i == 1
|
131
|
+
@issue_date = stat['issue_date']
|
132
|
+
@total_amount = stat['total']
|
133
|
+
@total_tax = stat['tax']
|
134
|
+
@total_count = stat['count']
|
99
135
|
end
|
100
136
|
end
|
101
|
-
|
137
|
+
@invoices = res.values
|
102
138
|
end
|
139
|
+
@company = CONFIG.dig('company', 'name')
|
140
|
+
|
141
|
+
mail = Mail.new
|
142
|
+
mail.to = CONFIG.dig('mail', 'preview') || CONFIG.dig('mail', 'from')
|
143
|
+
mail.subject = 'Check monthly payment list'
|
144
|
+
mail.html_part = Mail::Part.new(body: render_erb(search_template('monthly-payment-list.html.erb')), content_type: 'text/html; charset=UTF-8')
|
145
|
+
LucaSupport::Mail.new(mail, PJDIR).deliver
|
103
146
|
end
|
104
147
|
|
105
148
|
def export_json
|
@@ -110,10 +153,10 @@ module LucaDeal
|
|
110
153
|
item['debit'] = []
|
111
154
|
item['credit'] = []
|
112
155
|
dat['subtotal'].map do |sub|
|
113
|
-
item['debit'] << { 'label' => '売掛金', '
|
114
|
-
item['debit'] << { 'label' => '売掛金', '
|
115
|
-
item['credit'] << { 'label' => '売上高', '
|
116
|
-
item['credit'] << { 'label' => '売上高', '
|
156
|
+
item['debit'] << { 'label' => '売掛金', 'amount' => readable(sub['items']) }
|
157
|
+
item['debit'] << { 'label' => '売掛金', 'amount' => readable(sub['tax']) }
|
158
|
+
item['credit'] << { 'label' => '売上高', 'amount' => readable(sub['items']) }
|
159
|
+
item['credit'] << { 'label' => '売上高', 'amount' => readable(sub['tax']) }
|
117
160
|
end
|
118
161
|
item['x-customer'] = dat['customer']['name'] if dat.dig('customer', 'name')
|
119
162
|
item['x-editor'] = 'LucaDeal'
|
@@ -123,14 +166,25 @@ module LucaDeal
|
|
123
166
|
end
|
124
167
|
end
|
125
168
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
169
|
+
def single_invoice(contract_id)
|
170
|
+
contract = Contract.find(contract_id)
|
171
|
+
raise "Invoice already exists for #{contract_id}. exit" if duplicated_contract? contract['id']
|
172
|
+
|
173
|
+
gen_invoice!(invoice_object(contract))
|
174
|
+
end
|
175
|
+
|
176
|
+
def monthly_invoice(target = 'monthly')
|
177
|
+
Contract.new(@date.to_s).active do |contract|
|
178
|
+
next if contract.dig('terms', 'billing_cycle') != target
|
129
179
|
# TODO: provide another I/F for force re-issue if needed
|
130
180
|
next if duplicated_contract? contract['id']
|
131
181
|
|
132
|
-
|
133
|
-
|
182
|
+
gen_invoice!(invoice_object(contract))
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def invoice_object(contract)
|
187
|
+
{}.tap do |invoice|
|
134
188
|
invoice['contract_id'] = contract['id']
|
135
189
|
invoice['customer'] = get_customer(contract.dig('customer_id'))
|
136
190
|
invoice['due_date'] = due_date(@date)
|
@@ -143,25 +197,13 @@ module LucaDeal
|
|
143
197
|
item.dig('type') == 'initial' && subsequent_month?(contract.dig('terms', 'effective'))
|
144
198
|
end
|
145
199
|
invoice['subtotal'] = subtotal(invoice['items'])
|
146
|
-
|
147
|
-
gen_invoice!(invoice)
|
200
|
+
.map { |k, v| v.tap { |dat| dat['rate'] = k } }
|
148
201
|
end
|
149
202
|
end
|
150
203
|
|
151
|
-
# set variables for ERB template
|
152
|
-
#
|
153
|
-
def invoice_vars(invoice_dat)
|
154
|
-
@customer = invoice_dat['customer']
|
155
|
-
@items = invoice_dat['items']
|
156
|
-
@subtotal = invoice_dat['subtotal']
|
157
|
-
@issue_date = invoice_dat['issue_date']
|
158
|
-
@due_date = invoice_dat['due_date']
|
159
|
-
@amount = @subtotal.inject(0) { |sum, i| sum + i['items'] + i['tax'] }
|
160
|
-
end
|
161
|
-
|
162
204
|
def get_customer(id)
|
163
205
|
{}.tap do |res|
|
164
|
-
|
206
|
+
Customer.find(id) do |dat|
|
165
207
|
customer = parse_current(dat)
|
166
208
|
res['id'] = customer['id']
|
167
209
|
res['name'] = customer.dig('name')
|
@@ -177,7 +219,7 @@ module LucaDeal
|
|
177
219
|
|
178
220
|
[].tap do |res|
|
179
221
|
products.each do |product|
|
180
|
-
|
222
|
+
Product.find(product['id'])['items'].each do |item|
|
181
223
|
item['product_id'] = product['id']
|
182
224
|
item['qty'] ||= 1
|
183
225
|
res << item
|
@@ -198,11 +240,23 @@ module LucaDeal
|
|
198
240
|
|
199
241
|
# TODO: support due_date variation
|
200
242
|
def due_date(date)
|
201
|
-
|
243
|
+
next_month = date.next_month
|
244
|
+
Date.new(next_month.year, next_month.month, -1)
|
202
245
|
end
|
203
246
|
|
204
247
|
private
|
205
248
|
|
249
|
+
# set variables for ERB template
|
250
|
+
#
|
251
|
+
def invoice_vars(invoice_dat)
|
252
|
+
@customer = invoice_dat['customer']
|
253
|
+
@items = readable(invoice_dat['items'])
|
254
|
+
@subtotal = readable(invoice_dat['subtotal'])
|
255
|
+
@issue_date = invoice_dat['issue_date']
|
256
|
+
@due_date = invoice_dat['due_date']
|
257
|
+
@amount = readable(invoice_dat['subtotal'].inject(0) { |sum, i| sum + i['items'] + i['tax'] })
|
258
|
+
end
|
259
|
+
|
206
260
|
def lib_path
|
207
261
|
__dir__
|
208
262
|
end
|
@@ -211,9 +265,9 @@ module LucaDeal
|
|
211
265
|
#
|
212
266
|
def set_company
|
213
267
|
{}.tap do |h|
|
214
|
-
h['name'] =
|
215
|
-
h['address'] =
|
216
|
-
h['address2'] =
|
268
|
+
h['name'] = CONFIG.dig('company', 'name')
|
269
|
+
h['address'] = CONFIG.dig('company', 'address')
|
270
|
+
h['address2'] = CONFIG.dig('company', 'address2')
|
217
271
|
end
|
218
272
|
end
|
219
273
|
|
@@ -236,13 +290,14 @@ module LucaDeal
|
|
236
290
|
# load Tax Rate from config.
|
237
291
|
#
|
238
292
|
def load_tax_rate(name)
|
239
|
-
return 0 if
|
293
|
+
return 0 if CONFIG.dig('tax_rate', name).nil?
|
240
294
|
|
241
|
-
BigDecimal(take_current(
|
295
|
+
BigDecimal(take_current(CONFIG['tax_rate'], name).to_s)
|
242
296
|
end
|
243
297
|
|
244
298
|
def attachment_name(dat, type)
|
245
|
-
|
299
|
+
id = %r{/}.match(dat['id']) ? dat['id'].gsub('/', '') : dat['id'][0, 7]
|
300
|
+
"invoice-#{id}.#{type}"
|
246
301
|
end
|
247
302
|
|
248
303
|
def duplicated_contract?(id)
|