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 +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +5 -1
- data/exe/luca-deal +47 -1
- data/lib/luca_deal/invoice.rb +108 -0
- data/lib/luca_deal/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 060bcea890091dbe70e03718d1a8871bccf4873db86922d505e0e2c3ffefd94a
|
4
|
+
data.tar.gz: 3d202e5711a7e1d28cadb5a4debbda18900b7ab7ea4d6e5e413d52980c1cb84f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a61af30d3b434edf10ab8553b3f878646354dd6e838a93405483b476ab3d8456713a745c0eb2a99eefdbc233ed662a87d4645c7a94b375924e2d8f347208c00
|
7
|
+
data.tar.gz: ca88eff9ed7c7c8dd815089ccdb34bd8dcd0813ae58dbb3b026db4833373a4cb33cb2b91eea27fce67b599a9a43b6e27796ed14ef1c2da6c60cf7308e389ea7f
|
data/CHANGELOG.md
CHANGED
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 '
|
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
|
data/lib/luca_deal/invoice.rb
CHANGED
@@ -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
|
#
|
data/lib/luca_deal/version.rb
CHANGED
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.
|
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:
|
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.
|
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: []
|