lucadeal 0.2.25 → 0.3.0

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: 68b497e68738101eafbfe6eab73c3e0cf8d297cf4676ccfb8cd05efdadc273a3
4
- data.tar.gz: fb8ed0c2d5eb0f7621c4436ddad91ece16aa723f2861ecd661423802ec01d0c4
3
+ metadata.gz: 060bcea890091dbe70e03718d1a8871bccf4873db86922d505e0e2c3ffefd94a
4
+ data.tar.gz: 3d202e5711a7e1d28cadb5a4debbda18900b7ab7ea4d6e5e413d52980c1cb84f
5
5
  SHA512:
6
- metadata.gz: 5c8941a80f26d67b4855f2757e00f8241dd0b73f37f0fe2ab8799500388118bec4513fcb398116ba8072889bedf97a083fbb2c4ce3fdd4219cb7a15844785b1c
7
- data.tar.gz: 91a84225d3d46201f85e9e38bbedbc102b2a3aaf2ccbf0a812db56bc3b502b78b7f89cb4087ec09642db5ff49b4abe58c15f6632bcbee18d471e95f3c1ff2a20
6
+ metadata.gz: 7a61af30d3b434edf10ab8553b3f878646354dd6e838a93405483b476ab3d8456713a745c0eb2a99eefdbc233ed662a87d4645c7a94b375924e2d8f347208c00
7
+ data.tar.gz: ca88eff9ed7c7c8dd815089ccdb34bd8dcd0813ae58dbb3b026db4833373a4cb33cb2b91eea27fce67b599a9a43b6e27796ed14ef1c2da6c60cf7308e389ea7f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## LucaDeal 0.3.0
2
+
3
+ * implement `luca-deal reports balance` for unsettled balance by customer
4
+ * implement `luca-deal invoices settle` for import payment data from LucaBook
5
+
1
6
  ## LucaDeal 0.2.25
2
7
 
3
8
  * implement deduction rate for fee calculation.
data/README.md CHANGED
@@ -167,7 +167,7 @@ Fields for sales fee are as bellows:
167
167
  Invoice is basically auto generated from Customer and Contract objects.
168
168
 
169
169
  | Top level | Second level | Description |
170
- |------------|--------------|------------------------------------------|
170
+ |------------+--------------+------------------------------------------|
171
171
  | id | | uuid |
172
172
  | issue_date | | |
173
173
  | due_date | | |
@@ -183,6 +183,10 @@ Invoice is basically auto generated from Customer and Contract objects.
183
183
  | | qty | quantity. Default: 1. |
184
184
  | | type | |
185
185
  | | product_id | refrence for Product |
186
+ | settled | | |
187
+ | | id | data source id for duplication check |
188
+ | | date | payment date |
189
+ | | amount | payment amount |
186
190
  | subtotal | | Array of subtotal by tax category. |
187
191
  | | items | amount of items |
188
192
  | | tax | amount of tax |
data/exe/luca-deal CHANGED
@@ -186,6 +186,11 @@ class LucaCmd
186
186
  end
187
187
  end
188
188
 
189
+ def self.report(args = nil, params = {})
190
+ date = Date.new(args[0].to_i, args[1].to_i, 1)
191
+ render(LucaDeal::Invoice.report(date, detail: params[:detail], due: params[:due]), params)
192
+ end
193
+
189
194
  def self.mail(args = nil, params = {})
190
195
  date = "#{args[0]}-#{args[1]}-#{args[2] || '1'}" if !args.empty?
191
196
  case params['mode']
@@ -195,6 +200,11 @@ class LucaCmd
195
200
  LucaDeal::Invoice.new(date).deliver_mail
196
201
  end
197
202
  end
203
+
204
+ def self.settle(args = nil, _params = nil)
205
+ str = args[0].nil? ? STDIN.read : File.read(args[0])
206
+ LucaDeal::Invoice.settle(str)
207
+ end
198
208
  end
199
209
 
200
210
  class Fee < LucaCmd
@@ -257,6 +267,12 @@ class LucaCmd
257
267
  puts JSON.dump(dat)
258
268
  when 'nu'
259
269
  LucaSupport::View.nushell(YAML.dump(dat))
270
+ when 'csv'
271
+ str = CSV.generate(String.new, col_sep: "\t") do |row|
272
+ row << dat.first.keys
273
+ dat.each { |d| row << d.values }
274
+ end
275
+ puts str
260
276
  else
261
277
  puts YAML.dump(dat)
262
278
  end
@@ -373,6 +389,12 @@ when /invoices?/, 'i'
373
389
  args = opt.parse(ARGV)
374
390
  LucaCmd::Invoice.mail(args, params)
375
391
  end
392
+ when 'settle'
393
+ OptionParser.new do |opt|
394
+ opt.banner = 'Usage: luca-deal invoices settle [filepath]'
395
+ args = opt.parse(ARGV)
396
+ end
397
+ LucaCmd::Invoice.settle(ARGV)
376
398
  else
377
399
  puts 'Proper subcommand needed.'
378
400
  puts
@@ -381,6 +403,29 @@ when /invoices?/, 'i'
381
403
  puts ' delete'
382
404
  puts ' list'
383
405
  puts ' mail: send mail with invoice'
406
+ puts ' settle'
407
+ exit 1
408
+ end
409
+ when /reports?/, 'r'
410
+ subcmd = ARGV.shift
411
+ case subcmd
412
+ when 'balance'
413
+ params[:detail] = false
414
+ params[:due] = false
415
+ OptionParser.new do |opt|
416
+ opt.banner = 'Usage: luca-deal r[eports] balance [options] [year month]'
417
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
418
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
419
+ opt.on('--detail', 'show detail info') { |_v| params[:detail] = true }
420
+ opt.on('--force-due', 'respect due date over actual payment') { |_v| params[:due] = true }
421
+ args = opt.parse(ARGV)
422
+ LucaCmd::Invoice.report(args, params)
423
+ end
424
+ else
425
+ puts 'Proper subcommand needed.'
426
+ puts
427
+ puts 'Usage: luca-deal r[eports] subcommand [--help|options]'
428
+ puts ' balance'
384
429
  exit 1
385
430
  end
386
431
  when 'new'
@@ -436,8 +481,9 @@ else
436
481
  puts 'Usage: luca-deal subcommand [options]'
437
482
  puts ' customers'
438
483
  puts ' contracts'
439
- puts ' invoices'
484
+ puts ' i[nvoices]'
440
485
  puts ' fee'
486
+ puts ' r[eports]'
441
487
  puts ' new: initialize project dir'
442
488
  puts ' export: puts invoice data for LucaBook import'
443
489
  exit 1
@@ -5,6 +5,7 @@ require 'json'
5
5
  require 'yaml'
6
6
  require 'pathname'
7
7
  require 'bigdecimal'
8
+ require 'luca_support/code'
8
9
  require 'luca_support/config'
9
10
  require 'luca_support/mail'
10
11
  require 'luca_deal/contract'
@@ -76,6 +77,113 @@ module LucaDeal
76
77
  end
77
78
  end
78
79
 
80
+ def self.report(date, scan_years = 10, detail: false, due: false)
81
+ fy_end = Date.new(date.year, date.month, -1)
82
+ if detail
83
+ customers = {}.tap do |h|
84
+ Customer.all.each { |c| h[c['name']] = c }
85
+ end
86
+ end
87
+ [].tap do |res|
88
+ items = {}
89
+ head = date.prev_year(scan_years)
90
+ e = Enumerator.new do |yielder|
91
+ while head <= date
92
+ yielder << head
93
+ head = head.next_month
94
+ end
95
+ end
96
+ e.each do |d|
97
+ asof(d.year, d.month).map do |invoice|
98
+ if invoice['settled']
99
+ next if !due
100
+ settle_date = invoice['settled']['date'].class.name == "String" ? Date.parse(invoice['settled']['date']) : invoice['settled']['date']
101
+ next if (settle_date && settle_date <= fy_end)
102
+ end
103
+
104
+ customer = invoice.dig('customer', 'name')
105
+ items[customer] ||= { 'unsettled' => BigDecimal('0'), 'invoices' => [] }
106
+ items[customer]['unsettled'] += (invoice.dig('subtotal', 0, 'items') + invoice.dig('subtotal', 0, 'tax')||0)
107
+ items[customer]['invoices'] << invoice
108
+ end
109
+ end
110
+ items.each do |k, item|
111
+ row = {
112
+ 'customer' => k,
113
+ 'unsettled' => LucaSupport::Code.readable(item['unsettled']),
114
+ }
115
+ if detail
116
+ row['address'] = %Q(#{customers.dig(k, 'address')}#{customers.dig(k, 'address2')})
117
+ row['invoices'] = item['invoices'].map{ |i| { 'id' => i['id'], 'issue' => i['issue_date'].to_s } }
118
+ end
119
+ res << row
120
+ end
121
+ res.sort! { |a, b| b['unsettled'] <=> a['unsettled'] }
122
+ end
123
+ end
124
+
125
+ # === JSON Format:
126
+ # [
127
+ # {
128
+ # "journals" : [
129
+ # {
130
+ # "id": "2021A/U001",
131
+ # "header": "customer name",
132
+ # "diff": -20000
133
+ # }
134
+ # ]
135
+ # }
136
+ # ]
137
+ #
138
+ def self.settle(io, payment_terms = 1)
139
+ customers = {}.tap do |h|
140
+ Customer.all.each { |c| h[c['name']] = c }
141
+ end
142
+ contracts = {}.tap do |h|
143
+ Contract.all.each { |c| h[c['customer_id']] ||= []; h[c['customer_id']] << c }
144
+ end
145
+ JSON.parse(io).each do |d|
146
+ next if d['journals'].nil?
147
+
148
+ d['journals'].each do |j|
149
+ next if j['diff'] >= 0
150
+
151
+ if j['header'] == 'others'
152
+ STDERR.puts "#{j['id']}: no customer header found. skip"
153
+ next
154
+ end
155
+
156
+ ord = customers.map do |k, v|
157
+ [v, LucaSupport::Code.match_score(j['header'], k, 2)]
158
+ end
159
+ customer = ord.max { |x, y| x[1] <=> y[1] }.dig(0, 'id')
160
+
161
+ if customer
162
+ contract = contracts[customer].length == 1 ? contracts.dig(customer, 0, 'id') : nil
163
+ date = Date.parse(j['date'])
164
+ invoices = term(date.prev_month(payment_terms).year, date.prev_month(payment_terms).month, date.year, date.month, contract)
165
+ invoices.each do |invoice, _path|
166
+ next if invoice['customer']['id'] != customer
167
+ next if invoice['issue_date'] > date
168
+ if Regexp.new("^LucaBook/#{j['id']}").match invoice.dig('settled', 'id')||''
169
+ break
170
+ end
171
+
172
+ invoice['settled'] = {
173
+ 'id' => "LucaBook/#{j['id']}",
174
+ 'date' => j['date'],
175
+ 'amount' => j['diff']
176
+ }
177
+ save(invoice)
178
+ break
179
+ end
180
+ else
181
+ STDERR.puts "#{j['id']}: no customer found"
182
+ end
183
+ end
184
+ end
185
+ end
186
+
79
187
  # Output seriarized invoice data to stdout.
80
188
  # Returns previous N months on multiple count
81
189
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LucaDeal
4
- VERSION = '0.2.25'
4
+ VERSION = '0.3.0'
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.25
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuma Takahiro
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-30 00:00:00.000000000 Z
11
+ date: 2022-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lucarecord
@@ -68,7 +68,7 @@ dependencies:
68
68
  version: 12.3.3
69
69
  description: 'Deal with contracts
70
70
 
71
- '
71
+ '
72
72
  email:
73
73
  - co.chuma@gmail.com
74
74
  executables:
@@ -103,7 +103,7 @@ metadata:
103
103
  homepage_uri: https://github.com/chumaltd/luca/tree/master/lucadeal
104
104
  source_code_uri: https://github.com/chumaltd/luca/tree/master/lucadeal
105
105
  changelog_uri: https://github.com/chumaltd/luca/tree/master/lucadeal/CHANGELOG.md
106
- post_install_message:
106
+ post_install_message:
107
107
  rdoc_options: []
108
108
  require_paths:
109
109
  - lib
@@ -118,8 +118,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
118
  - !ruby/object:Gem::Version
119
119
  version: '0'
120
120
  requirements: []
121
- rubygems_version: 3.2.3
122
- signing_key:
121
+ rubygems_version: 3.2.5
122
+ signing_key:
123
123
  specification_version: 4
124
124
  summary: Deal with contracts
125
125
  test_files: []