lucadeal 0.2.23 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f3d23a1f082ee44833db42f3cf4dbdecc0d078ea3ca6499d5cf77e6d235580a
4
- data.tar.gz: c2ea468ea064bb865d0b481d9717a03f11afe212f0bfd3b58ac43722116a95e7
3
+ metadata.gz: 0e4fa52c476838906c3ad849ab1bc4a9bf58926a526034553a90d6c6299d7945
4
+ data.tar.gz: c3e434b8677a0b502cfbef70dc8d936bbc2dd3d7f0be0b6b1e2edea8294baa2c
5
5
  SHA512:
6
- metadata.gz: 545d6acb5b7837497c30d17a42c6a32611186f2e12f9821d116c9aa3ed9735dc1c8bc2c072d1439c9e21e1fd38d7398de770ffd9fd835352f4565f96a18a19ca
7
- data.tar.gz: c8cf01067781332a241138ab9b253d282902a3838a19d2f9623d9eb29c6c4a3432e7d0e6818bc67a3e3b3e682e5ba3c4c5c128a90f93b3f91dcadca0285ea2f4
6
+ metadata.gz: ae66f02ef1861eeb9bf217dc5547b05bdd384a9d25bddd8d12dfd62ab2fea63e3f051c699dc267620488949659c98e772799d027dd27101b2d214b8517b55ba2
7
+ data.tar.gz: 5ec46b9f38cb9bfda178896e2319d728cd41df86d6fb389342a4e9759351e64608abe33980a7cf35e5a8f25459223521c9a69bf356dc61fad55d8cb83588cdda
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## LucaDeal 0.2.24
2
+
3
+ * add `luca-deal invoices create --monthly --mail`, send payment list after monthly invoice creation.
4
+ * add 'other_payments' tracking with no invoices.
5
+ * can have limit on fee calculation.
6
+ * initial implment of `luca-deal fee list`
7
+
1
8
  ## LucaDeal 0.2.23
2
9
 
3
10
  * implement `luca-deal invoices list --mail`: payment list via HTML mail
data/README.md CHANGED
@@ -135,7 +135,7 @@ Fields for subscription customers are as bellows:
135
135
  | Top level | Second level | | historical | Description |
136
136
  |-----------|---------------|----------|------------|------------------------------------------------------------------------------------------------------|
137
137
  | terms | | | | |
138
- | | billing_cycle | optional | | If 'monthly', invoices are generated on each month. |
138
+ | | billing_cycle | optional | | If 'monthly', invoices are generated on each month. If 'other_payments', no_invoices are generated on each month. `no_invoices` are mostly same as invoices, but not sending email. |
139
139
  | | category | optional | | Default: 'subscription' |
140
140
  | products | | | | Array of products. |
141
141
  | | id | | | reference for Product |
@@ -154,6 +154,7 @@ Fields for sales fee are as bellows:
154
154
  |-----------|--------------|----------|------------|-------------------------------------------------------------------------------------|
155
155
  | terms | | | | |
156
156
  | | category | | | If 'sales_fee', contract is treated as selling commission. |
157
+ | | limit | | | If set, fees are calculated as mas as `limit` months. |
157
158
  | rate | | optional | | |
158
159
  | | default | | | sales fee rate. |
159
160
  | | initial | | | sales fee rate for items of type=initial. |
data/exe/luca-deal CHANGED
@@ -128,6 +128,10 @@ class LucaCmd
128
128
  when 'monthly'
129
129
  date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
130
130
  LucaDeal::Invoice.new(date).monthly_invoice
131
+ LucaDeal::NoInvoice.new(date).monthly_invoice
132
+ if params[:mail]
133
+ LucaDeal::Invoice.new(date).stats_email
134
+ end
131
135
  else
132
136
  date = "#{args[1]}-#{args[2]}-#{args[3] || '1'}" if !args.empty?
133
137
  list = LucaDeal::Contract.id_completion(args[0] || '', label: 'customer_name')
@@ -335,6 +339,7 @@ when /invoices?/, 'i'
335
339
  OptionParser.new do |opt|
336
340
  opt.banner = 'Usage: luca-deal invoices create [options] --monthly|contract_id year month [date]'
337
341
  opt.on('--monthly', 'generate monthly data') { |_v| params['mode'] = 'monthly' }
342
+ opt.on('--mail', 'send payment list by email. Only works with --monthly') { |_v| params[:mail] = true }
338
343
  args = opt.parse(ARGV)
339
344
  LucaCmd::Invoice.create(args, params)
340
345
  end
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/fee.rb CHANGED
@@ -25,23 +25,28 @@ module LucaDeal
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' => [] }
30
31
  fee['customer'] = get_customer(contract['customer_id'])
31
32
  fee['issue_date'] = @date
32
33
  Invoice.asof(@date.year, @date.month) do |invoice|
33
34
  next if invoice.dig('sales_fee', 'id') != contract['id']
35
+ next if exceed_limit?(invoice, limit)
34
36
 
35
37
  invoice['items'].each do |item|
36
38
  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
+ 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)
45
50
  end
46
51
  fee['sales_fee'] = subtotal(fee['items'])
47
52
  end
@@ -93,6 +98,47 @@ module LucaDeal
93
98
  mail
94
99
  end
95
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
+ }
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)
137
+ end
138
+ end
139
+ end
140
+ end
141
+
96
142
  def render_report(file_type = :html)
97
143
  case file_type
98
144
  when :html
@@ -174,11 +220,32 @@ module LucaDeal
174
220
  BigDecimal(take_current(CONFIG['tax_rate'], name).to_s)
175
221
  end
176
222
 
223
+ # Fees are unique contract_id in each month
224
+ # If update needed, remove the target fee file.
225
+ #
177
226
  def duplicated_contract?(id)
178
227
  self.class.asof(@date.year, @date.month, @date.day) do |_f, path|
179
228
  return true if path.include?(id)
180
229
  end
181
230
  false
182
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
183
250
  end
184
251
  end
@@ -120,17 +120,23 @@ module LucaDeal
120
120
  #
121
121
  def stats_email
122
122
  {}.tap do |res|
123
- stats(2).each.with_index(1) do |stat, i|
124
- @issue_date = stat['issue_date'] if i == 1
123
+ stats(3).each.with_index(1) do |stat, i|
125
124
  stat['records'].each do |record|
126
125
  res[record['customer']] ||= {}
127
126
  res[record['customer']]['customer_name'] ||= record['customer']
128
127
  res[record['customer']]["amount#{i}"] ||= record['subtotal']
129
128
  res[record['customer']]["tax#{i}"] ||= record['tax']
130
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']
135
+ end
131
136
  end
132
137
  @invoices = res.values
133
138
  end
139
+ @company = CONFIG.dig('company', 'name')
134
140
 
135
141
  mail = Mail.new
136
142
  mail.to = CONFIG.dig('mail', 'preview') || CONFIG.dig('mail', 'from')
@@ -167,9 +173,9 @@ module LucaDeal
167
173
  gen_invoice!(invoice_object(contract))
168
174
  end
169
175
 
170
- def monthly_invoice
176
+ def monthly_invoice(target = 'monthly')
171
177
  Contract.new(@date.to_s).active do |contract|
172
- next if contract.dig('terms', 'billing_cycle') != 'monthly'
178
+ next if contract.dig('terms', 'billing_cycle') != target
173
179
  # TODO: provide another I/F for force re-issue if needed
174
180
  next if duplicated_contract? contract['id']
175
181
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'luca_deal/invoice'
4
+
5
+ module LucaDeal #:nodoc:
6
+ # Invoice compatible transactions for other payment methods.
7
+ #
8
+ class NoInvoice < Invoice
9
+ @dirname = 'no_invoices'
10
+
11
+ def monthly_invoice
12
+ super('other_payments')
13
+ end
14
+
15
+ # Override not to send mail to customer.
16
+ #
17
+ def deliver_mail(attachment_type = nil, mode: nil)
18
+ end
19
+ end
20
+ end
@@ -12,7 +12,7 @@ module LucaDeal
12
12
  FileUtils.cp("#{__dir__}/templates/config.yml", 'config.yml') unless File.exist?('config.yml')
13
13
  Dir.mkdir('data') unless Dir.exist?('data')
14
14
  Dir.chdir('data') do
15
- %w[contracts customers invoices].each do |subdir|
15
+ %w[contracts customers invoices no_invoices].each do |subdir|
16
16
  Dir.mkdir(subdir) unless Dir.exist?(subdir)
17
17
  end
18
18
  end
@@ -3,37 +3,51 @@
3
3
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
4
4
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
5
5
  <style>
6
- td { text-align: right; line-height: 2em; min-width: 7em }
6
+ td { text-align: right; line-height: 2em; min-width: 6em }
7
7
  thead th, thead td { text-align: center }
8
+ thead { border-bottom: solid 1px #aaa }
9
+ tr#total { border-top: solid 1px #aaa }
8
10
  tr.sub { font-size: .8em; color: #aaa }
11
+ .past { color: #777 }
9
12
  </style>
10
13
  </head>
11
14
  <body>
15
+ <div style="margin: 1em 0"><%= @company %></div>
12
16
  <div style="margin: 1em 0">Issue date: <%= @issue_date %></div>
13
17
  <table>
14
18
  <thead>
15
19
  <tr>
16
20
  <th>#</th>
17
21
  <th>Customer</th>
18
- <th>Amount</th><th>Tax</th>
19
- <th>Amount</th><th>Tax</th>
22
+ <th>This month</th>
23
+ <th>Last Month</th>
24
+ <th>2 Month ago</th>
20
25
  </tr>
21
26
  <tr class="sub">
22
27
  <th></th>
23
28
  <th></th>
24
- <th>This month</th><th>This month</th>
25
- <th>Last Month</th><th>Last Month</th>
29
+ <th>Amount / Tax</th>
30
+ <th class="past">Amount / Tax</th>
31
+ <th class="past">Amount / Tax</th>
26
32
  </tr>
27
33
  </thead>
28
34
  <tbody>
29
35
  <% @invoices.each.with_index(1) do |invoice, i| %>
30
36
  <tr>
31
- <td><%= i %></td>
37
+ <th><%= i %></th>
32
38
  <td><%= invoice["customer_name"] %></td>
33
- <td><%= invoice["amount1"] %></td><td><%= invoice["tax1"] %></td>
34
- <td><%= invoice["amount2"] %></td><td><%= invoice["tax2"] %></td>
39
+ <td><%= delimit_num(invoice["amount1"]) %><br /><%= delimit_num(invoice["tax1"]) %></td>
40
+ <td class="past"><%= delimit_num(invoice["amount2"]) %><br /><%= delimit_num(invoice["tax2"]) %></td>
41
+ <td class="past"><%= delimit_num(invoice["amount3"]) %><br /><%= delimit_num(invoice["tax3"]) %></td>
35
42
  </tr>
36
43
  <% end %>
44
+ <tr id="total">
45
+ <td></td>
46
+ <td>Total (<%= @total_count %> records)</td>
47
+ <td><%= delimit_num(@total_amount) %><br /><%= delimit_num(@total_tax) %></td>
48
+ <td></td>
49
+ <td></td>
50
+ </tr>
37
51
  </tbody>
38
52
  </table>
39
53
  </body>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LucaDeal
4
- VERSION = '0.2.23'
4
+ VERSION = '0.2.24'
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.23
4
+ version: 0.2.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuma Takahiro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-31 00:00:00.000000000 Z
11
+ date: 2021-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lucarecord
@@ -85,6 +85,7 @@ files:
85
85
  - lib/luca_deal/customer.rb
86
86
  - lib/luca_deal/fee.rb
87
87
  - lib/luca_deal/invoice.rb
88
+ - lib/luca_deal/no_invoice.rb
88
89
  - lib/luca_deal/product.rb
89
90
  - lib/luca_deal/setup.rb
90
91
  - lib/luca_deal/templates/.keep