lucadeal 0.2.24 → 0.2.25

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: 0e4fa52c476838906c3ad849ab1bc4a9bf58926a526034553a90d6c6299d7945
4
- data.tar.gz: c3e434b8677a0b502cfbef70dc8d936bbc2dd3d7f0be0b6b1e2edea8294baa2c
3
+ metadata.gz: 68b497e68738101eafbfe6eab73c3e0cf8d297cf4676ccfb8cd05efdadc273a3
4
+ data.tar.gz: fb8ed0c2d5eb0f7621c4436ddad91ece16aa723f2861ecd661423802ec01d0c4
5
5
  SHA512:
6
- metadata.gz: ae66f02ef1861eeb9bf217dc5547b05bdd384a9d25bddd8d12dfd62ab2fea63e3f051c699dc267620488949659c98e772799d027dd27101b2d214b8517b55ba2
7
- data.tar.gz: 5ec46b9f38cb9bfda178896e2319d728cd41df86d6fb389342a4e9759351e64608abe33980a7cf35e5a8f25459223521c9a69bf356dc61fad55d8cb83588cdda
6
+ metadata.gz: 5c8941a80f26d67b4855f2757e00f8241dd0b73f37f0fe2ab8799500388118bec4513fcb398116ba8072889bedf97a083fbb2c4ce3fdd4219cb7a15844785b1c
7
+ data.tar.gz: 91a84225d3d46201f85e9e38bbedbc102b2a3aaf2ccbf0a812db56bc3b502b78b7f89cb4087ec09642db5ff49b4abe58c15f6632bcbee18d471e95f3c1ff2a20
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## LucaDeal 0.2.25
2
+
3
+ * implement deduction rate for fee calculation.
4
+ * implement `luca-deal fee export`
5
+ * refine export label for luca-book compatibility
6
+ * add `luca-deal invoice create --monthly --with-fee` option.
7
+ * preview_mail can deliver regardless of `mail_delivered` status
8
+ * `luca-deal fee mail` skip no item record by default.
9
+
1
10
  ## LucaDeal 0.2.24
2
11
 
3
12
  * add `luca-deal invoices create --monthly --mail`, send payment list after monthly invoice creation.
data/README.md CHANGED
@@ -155,9 +155,11 @@ Fields for sales fee are as bellows:
155
155
  | terms | | | | |
156
156
  | | category | | | If 'sales_fee', contract is treated as selling commission. |
157
157
  | | limit | | | If set, fees are calculated as mas as `limit` months. |
158
+ | | deduction_label | | | Label for deduction. Used on export |
158
159
  | rate | | optional | | |
159
160
  | | default | | | sales fee rate. |
160
161
  | | initial | | | sales fee rate for items of type=initial. |
162
+ | | deduction | | | deduction rate(if any) multiplied by fee |
161
163
 
162
164
 
163
165
  ### Invoice
data/exe/luca-deal CHANGED
@@ -124,11 +124,12 @@ class LucaCmd
124
124
 
125
125
  class Invoice < LucaCmd
126
126
  def self.create(args = nil, params = {})
127
- case params['mode']
127
+ case params[:mode]
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
131
  LucaDeal::NoInvoice.new(date).monthly_invoice
132
+ LucaDeal::Fee.new(date).monthly_fee if params[:fee]
132
133
  if params[:mail]
133
134
  LucaDeal::Invoice.new(date).stats_email
134
135
  end
@@ -198,7 +199,7 @@ class LucaCmd
198
199
 
199
200
  class Fee < LucaCmd
200
201
  def self.create(args = nil, params = {})
201
- case params['mode']
202
+ case params[:mode]
202
203
  when 'monthly'
203
204
  date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
204
205
  LucaDeal::Fee.new(date).monthly_fee
@@ -217,6 +218,15 @@ class LucaCmd
217
218
  end
218
219
  end
219
220
 
221
+ def self.export(args = nil, _params = nil)
222
+ if args
223
+ args << 28 if args.length == 2 # specify safe last day
224
+ LucaDeal::Fee.new(args.join('-')).export_json
225
+ else
226
+ LucaDeal::Fee.new.export_json
227
+ end
228
+ end
229
+
220
230
  def self.list(args = nil, params = {})
221
231
  date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
222
232
  if args.empty?
@@ -338,8 +348,9 @@ when /invoices?/, 'i'
338
348
  when 'create'
339
349
  OptionParser.new do |opt|
340
350
  opt.banner = 'Usage: luca-deal invoices create [options] --monthly|contract_id year month [date]'
341
- opt.on('--monthly', 'generate monthly data') { |_v| params['mode'] = 'monthly' }
342
351
  opt.on('--mail', 'send payment list by email. Only works with --monthly') { |_v| params[:mail] = true }
352
+ opt.on('--monthly', 'generate monthly data') { |_v| params[:mode] = 'monthly' }
353
+ opt.on('--with-fee', 'generate sales fee data after monthly invoice creation') { |_v| params[:fee] = true }
343
354
  args = opt.parse(ARGV)
344
355
  LucaCmd::Invoice.create(args, params)
345
356
  end
@@ -385,12 +396,14 @@ when /fee/
385
396
  when 'create'
386
397
  OptionParser.new do |opt|
387
398
  opt.banner = 'Usage: luca-deal fee create [options] year month [date]'
388
- opt.on('--monthly', 'generate monthly data') { |_v| params['mode'] = 'monthly' }
399
+ opt.on('--monthly', 'generate monthly data') { |_v| params[:mode] = 'monthly' }
389
400
  args = opt.parse(ARGV)
390
401
  LucaCmd::Fee.create(args, params)
391
402
  end
392
403
  when 'delete'
393
404
  LucaCmd::Fee.delete(ARGV)
405
+ when 'export'
406
+ LucaCmd::Fee.export(ARGV)
394
407
  when 'list'
395
408
  OptionParser.new do |opt|
396
409
  opt.banner = 'Usage: luca-deal fee list [options] year month [date]'
@@ -424,6 +437,7 @@ else
424
437
  puts ' customers'
425
438
  puts ' contracts'
426
439
  puts ' invoices'
440
+ puts ' fee'
427
441
  puts ' new: initialize project dir'
428
442
  puts ' export: puts invoice data for LucaBook import'
429
443
  exit 1
@@ -69,11 +69,11 @@ module LucaDeal
69
69
  end
70
70
 
71
71
  def active_period?(dat)
72
- unless dat.dig('defunct').nil?
73
- defunct = dat.dig('defunct').respond_to?(:year) ? dat.dig('defunct') : Date.parse(dat.dig('defunct'))
72
+ unless dat['defunct'].nil?
73
+ defunct = dat['defunct'].respond_to?(:year) ? dat['defunct'] : Date.parse(dat['defunct'])
74
74
  return false if @date > defunct
75
75
  end
76
- effective = dat.dig('effective').respond_to?(:year) ? dat.dig('effective') : Date.parse(dat.dig('effective'))
76
+ effective = dat['effective'].respond_to?(:year) ? dat['effective'] : Date.parse(dat['effective'])
77
77
  @date >= effective
78
78
  end
79
79
 
data/lib/luca_deal/fee.rb CHANGED
@@ -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'
@@ -27,7 +28,16 @@ module LucaDeal
27
28
  @rate['initial'] = contract.dig('rate', 'initial') ? BigDecimal(contract.dig('rate', 'initial')) : @rate['default']
28
29
  limit = contract.dig('terms', 'limit')
29
30
 
30
- fee = { 'contract_id' => contract['id'], 'items' => [] }
31
+ fee = {
32
+ 'contract_id' => contract['id'],
33
+ 'items' => [],
34
+ 'sales_fee' => {
35
+ 'fee' => 0,
36
+ 'tax' => 0,
37
+ 'deduction' => 0,
38
+ 'deduction_label' => contract.dig('terms', 'deduction_label')
39
+ }
40
+ }
31
41
  fee['customer'] = get_customer(contract['customer_id'])
32
42
  fee['issue_date'] = @date
33
43
  Invoice.asof(@date.year, @date.month) do |invoice|
@@ -38,33 +48,36 @@ module LucaDeal
38
48
  rate = item['type'] == 'initial' ? @rate['initial'] : @rate['default']
39
49
  fee['items'] << fee_record(invoice, item, rate)
40
50
  end
41
- fee['sales_fee'] = subtotal(fee['items'])
51
+ subtotal(fee['items']).each{ |k, v| fee['sales_fee'][k] += v }
42
52
  end
43
53
  NoInvoice.asof(@date.year, @date.month) do |no_invoice|
44
54
  next if no_invoice.dig('sales_fee', 'id') != contract['id']
45
- next if exceed_limit?(invoice, limit)
55
+ next if exceed_limit?(no_invoice, limit)
46
56
 
47
57
  no_invoice['items'].each do |item|
48
58
  rate = item['type'] == 'initial' ? @rate['initial'] : @rate['default']
49
59
  fee['items'] << fee_record(no_invoice, item, rate)
50
60
  end
51
- fee['sales_fee'] = subtotal(fee['items'])
61
+ subtotal(fee['items']).each{ |k, v| fee['sales_fee'][k] += v }
52
62
  end
63
+ deduction_rate = contract.dig('rate', 'deduction')
64
+ fee['sales_fee']['deduction'] = -1 * (fee['sales_fee']['fee'] * deduction_rate).floor if deduction_rate
53
65
  self.class.create(fee, date: @date, codes: Array(contract['id']))
54
66
  end
55
67
  end
56
68
 
57
- def deliver_mail(attachment_type = nil, mode: nil)
69
+ def deliver_mail(attachment_type = nil, mode: nil, skip_no_item: true)
58
70
  attachment_type = CONFIG.dig('fee', 'attachment') || :html
59
71
  fees = self.class.asof(@date.year, @date.month)
60
72
  raise "No report for #{@date.year}/#{@date.month}" if fees.count.zero?
61
73
 
62
74
  fees.each do |dat, path|
63
75
  next if has_status?(dat, 'mail_delivered')
76
+ next if skip_no_item && dat['items'].empty?
64
77
 
65
78
  mail = compose_mail(dat, mode: mode, attachment: attachment_type.to_sym)
66
79
  LucaSupport::Mail.new(mail, PJDIR).deliver
67
- self.class.add_status!(path, 'mail_delivered')
80
+ self.class.add_status!(path, 'mail_delivered') if mode.nil?
68
81
  end
69
82
  end
70
83
 
@@ -88,7 +101,7 @@ module LucaDeal
88
101
 
89
102
  mail = Mail.new
90
103
  mail.to = dat.dig('customer', 'to') if mode.nil?
91
- mail.subject = CONFIG.dig('invoice', 'mail_subject') || 'Your Report is available'
104
+ mail.subject = CONFIG.dig('fee', 'mail_subject') || 'Your Report is available'
92
105
  if mode == :preview
93
106
  mail.cc = CONFIG.dig('mail', 'preview') || CONFIG.dig('mail', 'from')
94
107
  mail.subject = '[preview] ' + mail.subject
@@ -139,6 +152,35 @@ module LucaDeal
139
152
  end
140
153
  end
141
154
 
155
+ def export_json
156
+ labels = export_labels
157
+ [].tap do |res|
158
+ self.class.asof(@date.year, @date.month) do |dat|
159
+ item = {}
160
+ item['date'] = dat['issue_date']
161
+ item['debit'] = []
162
+ item['credit'] = []
163
+ sub = dat['sales_fee']
164
+ if readable(sub['fee']) != 0
165
+ item['debit'] << { 'label' => labels[:debit][:fee], 'amount' => readable(sub['fee']) }
166
+ item['credit'] << { 'label' => labels[:credit][:fee], 'amount' => readable(sub['fee']) }
167
+ end
168
+ if readable(sub['tax']) != 0
169
+ item['debit'] << { 'label' => labels[:debit][:tax], 'amount' => readable(sub['tax']) }
170
+ item['credit'] << { 'label' => labels[:credit][:tax], 'amount' => readable(sub['tax']) }
171
+ end
172
+ if readable(sub['deduction']) != 0
173
+ item['debit'] << { 'label' => labels[:debit][:deduction], 'amount' => readable(sub['deduction'] * -1) }
174
+ item['credit'] << { 'label' => sub['deduction_label'] || labels[:credit][:deduction], 'amount' => readable(sub['deduction'] * -1) }
175
+ end
176
+ item['x-customer'] = dat['customer']['name'] if dat.dig('customer', 'name')
177
+ item['x-editor'] = 'LucaDeal'
178
+ res << item
179
+ end
180
+ puts JSON.dump(res)
181
+ end
182
+ end
183
+
142
184
  def render_report(file_type = :html)
143
185
  case file_type
144
186
  when :html
@@ -173,13 +215,32 @@ module LucaDeal
173
215
  @sales_fee = readable(fee_dat['sales_fee'])
174
216
  @issue_date = fee_dat['issue_date']
175
217
  @due_date = fee_dat['due_date']
176
- @amount = readable(fee_dat['sales_fee'].inject(0) { |sum, (_k, v)| sum + v })
218
+ @amount = readable(fee_dat['sales_fee']
219
+ .reject{ |k, _v| k == 'deduction_label' }
220
+ .inject(0) { |sum, (_k, v)| sum + v })
177
221
  end
178
222
 
179
223
  def lib_path
180
224
  __dir__
181
225
  end
182
226
 
227
+ # TODO: load labels from CONFIG before country defaults
228
+ #
229
+ def export_labels
230
+ case CONFIG['country']
231
+ when 'jp'
232
+ {
233
+ debit: { fee: '支払手数料', tax: '支払手数料', deduction: '未払費用' },
234
+ credit: { fee: '未払費用', tax: '未払費用', deduction: '雑収入' }
235
+ }
236
+ else
237
+ {
238
+ debit: { fee: 'Fees and commisions', tax: 'Fees and commisions', deduction: 'Accounts payable - other' },
239
+ credit: { fee: 'Accounts payable - other', tax: 'Accounts payable - other', deduction: 'Miscellaneous income' }
240
+ }
241
+ end
242
+ end
243
+
183
244
  # load user company profile from config.
184
245
  #
185
246
  def set_company
@@ -201,6 +262,11 @@ module LucaDeal
201
262
  end
202
263
  end
203
264
 
265
+ def attachment_name(dat, type)
266
+ id = %r{/}.match(dat['id']) ? dat['id'].gsub('/', '') : dat['id'][0, 7]
267
+ "feereport-#{id}.#{type}"
268
+ end
269
+
204
270
  def issue_date(date)
205
271
  base = date.nil? ? Date.today : Date.parse(date)
206
272
  Date.new(base.year, base.month, -1)
@@ -20,21 +20,23 @@ module LucaDeal
20
20
  end
21
21
 
22
22
  def deliver_mail(attachment_type = nil, mode: nil)
23
- attachment_type = CONFIG.dig('invoice', 'attachment') || :html
24
23
  invoices = self.class.asof(@date.year, @date.month)
25
24
  raise "No invoice for #{@date.year}/#{@date.month}" if invoices.count.zero?
26
25
 
27
26
  invoices.each do |dat, path|
28
27
  next if has_status?(dat, 'mail_delivered')
29
28
 
30
- mail = compose_mail(dat, mode: mode, attachment: attachment_type.to_sym)
31
- LucaSupport::Mail.new(mail, PJDIR).deliver
32
- self.class.add_status!(path, 'mail_delivered')
29
+ deliver_one(dat, path, mode: mode, attachment_type: attachment_type)
33
30
  end
34
31
  end
35
32
 
36
33
  def preview_mail(attachment_type = nil)
37
- deliver_mail(attachment_type, mode: :preview)
34
+ invoices = self.class.asof(@date.year, @date.month)
35
+ raise "No invoice for #{@date.year}/#{@date.month}" if invoices.count.zero?
36
+
37
+ invoices.each do |dat, path|
38
+ deliver_one(dat, path, mode: :preview, attachment_type: attachment_type)
39
+ end
38
40
  end
39
41
 
40
42
  # Render HTML to console
@@ -146,6 +148,7 @@ module LucaDeal
146
148
  end
147
149
 
148
150
  def export_json
151
+ labels = export_labels
149
152
  [].tap do |res|
150
153
  self.class.asof(@date.year, @date.month) do |dat|
151
154
  item = {}
@@ -153,10 +156,14 @@ module LucaDeal
153
156
  item['debit'] = []
154
157
  item['credit'] = []
155
158
  dat['subtotal'].map do |sub|
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']) }
159
+ if readable(sub['items']) != 0
160
+ item['debit'] << { 'label' => labels[:debit][:items], 'amount' => readable(sub['items']) }
161
+ item['credit'] << { 'label' => labels[:credit][:items], 'amount' => readable(sub['items']) }
162
+ end
163
+ if readable(sub['tax']) != 0
164
+ item['debit'] << { 'label' => labels[:debit][:tax], 'amount' => readable(sub['tax']) }
165
+ item['credit'] << { 'label' => labels[:credit][:tax], 'amount' => readable(sub['tax']) }
166
+ end
160
167
  end
161
168
  item['x-customer'] = dat['customer']['name'] if dat.dig('customer', 'name')
162
169
  item['x-editor'] = 'LucaDeal'
@@ -257,10 +264,34 @@ module LucaDeal
257
264
  @amount = readable(invoice_dat['subtotal'].inject(0) { |sum, i| sum + i['items'] + i['tax'] })
258
265
  end
259
266
 
267
+ def deliver_one(invoice, path, mode: nil, attachment_type: nil)
268
+ attachment_type ||= CONFIG.dig('invoice', 'attachment') || :html
269
+ mail = compose_mail(invoice, mode: mode, attachment: attachment_type.to_sym)
270
+ LucaSupport::Mail.new(mail, PJDIR).deliver
271
+ self.class.add_status!(path, 'mail_delivered') if mode.nil?
272
+ end
273
+
260
274
  def lib_path
261
275
  __dir__
262
276
  end
263
277
 
278
+ # TODO: load labels from CONFIG before country defaults
279
+ #
280
+ def export_labels
281
+ case CONFIG['country']
282
+ when 'jp'
283
+ {
284
+ debit: { items: '売掛金', tax: '売掛金' },
285
+ credit: { items: '売上高', tax: '売上高' }
286
+ }
287
+ else
288
+ {
289
+ debit: { items: 'Accounts receivable - trade', tax: 'Accounts receivable - trade' },
290
+ credit: { items: 'Amount of Sales', tax: 'Amount of Sales' }
291
+ }
292
+ end
293
+ end
294
+
264
295
  # load user company profile from config.
265
296
  #
266
297
  def set_company
@@ -39,9 +39,13 @@
39
39
  <td class="price" colspan="3">Tax</td>
40
40
  <td class="price"><%= delimit_num( @sales_fee['tax'] ) %></td>
41
41
  </tr>
42
+ <tr>
43
+ <td class="price" colspan="3">Deduction</td>
44
+ <td class="price"><%= delimit_num( @sales_fee['deduction'] ) %></td>
45
+ </tr>
42
46
  <tr>
43
47
  <td class="price" colspan="3">Total</td>
44
- <td class="price"><%= delimit_num( @sales_fee['fee'] + @sales_fee['tax'] ) %></td>
48
+ <td class="price"><%= delimit_num(@sales_fee['fee'] + @sales_fee['tax'] + @sales_fee['deduction']) %></td>
45
49
  </tr>
46
50
  </table>
47
51
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LucaDeal
4
- VERSION = '0.2.24'
4
+ VERSION = '0.2.25'
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.24
4
+ version: 0.2.25
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-02-25 00:00:00.000000000 Z
11
+ date: 2021-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lucarecord