lucadeal 0.2.7 → 0.2.16
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 +11 -0
- data/README.md +135 -6
- data/exe/luca-deal +5 -2
- data/lib/luca_deal.rb +2 -0
- data/lib/luca_deal/contract.rb +56 -9
- data/lib/luca_deal/customer.rb +13 -9
- data/lib/luca_deal/fee.rb +118 -0
- data/lib/luca_deal/invoice.rb +31 -15
- data/lib/luca_deal/product.rb +50 -0
- data/lib/luca_deal/setup.rb +1 -1
- data/lib/luca_deal/version.rb +1 -1
- metadata +20 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f17d75af6f101c60eced0ecfb770e39ac66b0f35725a6976f135216a0c75099
|
4
|
+
data.tar.gz: d3e60d51908e78ab72e177f29affc26864744afb584d1c5dadd507b18ff888f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd534c31a33405478cdeba88777481f5c17af6337cf743e2d16a8e5027209a65d1efb8329cf7f6e72318dfea8c3daa1965d9cf1e833ce93b85d24925c17638f6
|
7
|
+
data.tar.gz: f1d809de08ce94cf7da913c5bc733515f95bba596dc155596880cb4776837113c36f93ee4247c35fe34990f2a45fd758df1bddec3d2e587d182062cdb5893c60
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
#
|
1
|
+
# LucaDeal
|
2
2
|
|
3
|
-
|
3
|
+
[](https://badge.fury.io/rb/lucadeal)
|
4
4
|
|
5
|
-
|
5
|
+
LucaDeal is Sales contract management application.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
9
9
|
Add this line to your application's Gemfile:
|
10
10
|
|
11
11
|
```ruby
|
12
|
-
gem '
|
12
|
+
gem 'lucadeal'
|
13
13
|
```
|
14
14
|
|
15
15
|
And then execute:
|
@@ -18,12 +18,141 @@ And then execute:
|
|
18
18
|
|
19
19
|
Or install it yourself as:
|
20
20
|
|
21
|
-
$ gem install
|
21
|
+
$ gem install lucadeal
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
25
|
TODO: Write usage instructions here
|
26
26
|
|
27
|
+
|
28
|
+
## Data Structure
|
29
|
+
|
30
|
+
Records are stored in YAML format. On historical records, see [LucaRecord](../lucarecord/README.md#historical-field).
|
31
|
+
|
32
|
+
### Customer
|
33
|
+
|
34
|
+
Customer consists of label information.
|
35
|
+
|
36
|
+
| Top level | Second level | | historical | Description |
|
37
|
+
|-----------|--------------|------|------------|--------------------------------|
|
38
|
+
| id | | auto | | uuid |
|
39
|
+
| name | | must | x | customer's name |
|
40
|
+
| address | | | x | |
|
41
|
+
| address2 | | | x | |
|
42
|
+
| contacts | | | | Array of contact information |
|
43
|
+
| | mail | | | mail address receiving invoice |
|
44
|
+
|
45
|
+
|
46
|
+
### Product
|
47
|
+
|
48
|
+
Product is items template referred by Contract.
|
49
|
+
|
50
|
+
| Top level | Second level | | historical | Description |
|
51
|
+
|-----------|--------------|----------|------------|------------------------------------------------------------------------------------------------------|
|
52
|
+
| id | | auto | | uuid |
|
53
|
+
| name | | | x | Product name. |
|
54
|
+
| items | | | | Array of items. |
|
55
|
+
| | name | | x | Item name. |
|
56
|
+
| | price | | x | Item price. |
|
57
|
+
| | qty | optional | x | quantity. Default: 1. |
|
58
|
+
| | type | optional | | If 'initial', this item is treated as initial cost, applied only on the first month of the contract. |
|
59
|
+
|
60
|
+
|
61
|
+
### Contract
|
62
|
+
|
63
|
+
Contract is core object for calculation. Common fields are as follows:
|
64
|
+
|
65
|
+
| Top level | Second level | | historical | Description |
|
66
|
+
|-------------|---------------|----------|------------|------------------------------------------------------------------------------------------------------|
|
67
|
+
| id | | auto | | uuid |
|
68
|
+
| customer_id | | must | x | customer's uuid |
|
69
|
+
| terms | | | | |
|
70
|
+
| | effective | must | | Start date of the contract. |
|
71
|
+
| | defunct | | | End date of the contract. |
|
72
|
+
|
73
|
+
Fields for subscription customers are as bellows:
|
74
|
+
|
75
|
+
| Top level | Second level | | historical | Description |
|
76
|
+
|-----------|---------------|----------|------------|------------------------------------------------------------------------------------------------------|
|
77
|
+
| terms | | | | |
|
78
|
+
| | billing_cycle | optional | | If 'monthly', invoices are generated on each month. |
|
79
|
+
| | category | optional | | Default: 'subscription' |
|
80
|
+
| products | | | | Array of products. |
|
81
|
+
| | id | | | reference for Product |
|
82
|
+
| items | | | | Array of items. |
|
83
|
+
| | name | | x | Item name. |
|
84
|
+
| | price | | x | Item price. |
|
85
|
+
| | qty | optional | x | quantity. Default: 1. |
|
86
|
+
| | type | optional | | If 'initial', this item is treated as initial cost, applied only on the first month of the contract. |
|
87
|
+
| sales_fee | | optional | | |
|
88
|
+
| | id | | | contract id of fee with sales partner. |
|
89
|
+
|
90
|
+
|
91
|
+
Fields for sales fee are as bellows:
|
92
|
+
|
93
|
+
| Top level | Second level | | historical | Description |
|
94
|
+
|-----------|--------------|----------|------------|-------------------------------------------------------------------------------------|
|
95
|
+
| terms | | | | |
|
96
|
+
| | category | | | If 'sales_fee', contract is treated as selling commission. |
|
97
|
+
| rate | | optional | | |
|
98
|
+
| | default | | | sales fee rate. |
|
99
|
+
| | initial | | | sales fee rate for items of type=initial. |
|
100
|
+
|
101
|
+
|
102
|
+
### Invoice
|
103
|
+
|
104
|
+
Invoice is basically auto generated from Customer and Contract objects.
|
105
|
+
|
106
|
+
| Top level | Second level | Description |
|
107
|
+
|------------|--------------|------------------------------------------|
|
108
|
+
| id | | uuid |
|
109
|
+
| issue_date | | |
|
110
|
+
| due_date | | |
|
111
|
+
| customer | | |
|
112
|
+
| | id | customer's uuid |
|
113
|
+
| | name | customer name |
|
114
|
+
| | address | |
|
115
|
+
| | address2 | |
|
116
|
+
| | to | Array of mail addresses |
|
117
|
+
| items | | Array of items. |
|
118
|
+
| | name | Item name. |
|
119
|
+
| | price | Item price. |
|
120
|
+
| | qty | quantity. Default: 1. |
|
121
|
+
| | type | |
|
122
|
+
| | product_id | refrence for Product |
|
123
|
+
| subtotal | | Array of subtotal by tax category. |
|
124
|
+
| | items | amount of items |
|
125
|
+
| | tax | amount of tax |
|
126
|
+
| | rate | applied tax category. Default: 'default' |
|
127
|
+
| sales_fee | | |
|
128
|
+
| | id | contract id of fee with sales partner. |
|
129
|
+
| status | | Array of status with timestamp. |
|
130
|
+
|
131
|
+
|
132
|
+
### Fee
|
133
|
+
|
134
|
+
Fee is basically auto generated from Contract and Invoice objects.
|
135
|
+
|
136
|
+
| Top level | Second level | Description |
|
137
|
+
|-----------|--------------|----------------------------------------------|
|
138
|
+
| id | | uuid |
|
139
|
+
| sales_fee | | |
|
140
|
+
| | id | contract id with sales partner. |
|
141
|
+
| | default.fee | Amount of fee on dafault rate. |
|
142
|
+
| | default.tax | Amount of tax for default.fee. |
|
143
|
+
| | initial.fee | Amount of fee on initial cost. |
|
144
|
+
| | initial.tax | Amount of tax for initial.fee. |
|
145
|
+
| invoice | | Carbon copy of Invoice attributes. |
|
146
|
+
| | id | |
|
147
|
+
| | contract_id | |
|
148
|
+
| | issue_date | |
|
149
|
+
| | due_date | |
|
150
|
+
| customer | | Carbon copy of Invoice customer except 'to'. |
|
151
|
+
| items | | Carbon copy of Invoice items. |
|
152
|
+
| subtotal | | Carbon copy of Invoice subtotal. |
|
153
|
+
| status | | Array of status with timestamp. |
|
154
|
+
|
155
|
+
|
27
156
|
## Development
|
28
157
|
|
29
158
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -32,4 +161,4 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
32
161
|
|
33
162
|
## Contributing
|
34
163
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
164
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/chumaltd/luca .
|
data/exe/luca-deal
CHANGED
@@ -11,7 +11,7 @@ def customer(args = nil, params = {})
|
|
11
11
|
LucaDeal::Customer.new.list_name
|
12
12
|
when 'create'
|
13
13
|
if params['name']
|
14
|
-
id = LucaDeal::Customer.
|
14
|
+
id = LucaDeal::Customer.create(name: params['name'])
|
15
15
|
puts "Successfully generated Customer #{id}" if id
|
16
16
|
puts 'Edit customer detail.' if id
|
17
17
|
else
|
@@ -28,7 +28,7 @@ def contract(args = nil, params = {})
|
|
28
28
|
case params['mode']
|
29
29
|
when 'create'
|
30
30
|
if params['customer_id']
|
31
|
-
id = LucaDeal::Contract.new.generate!(params['customer_id'])
|
31
|
+
id = LucaDeal::Contract.new.generate!(params['customer_id'], params['category'])
|
32
32
|
puts "Successfully generated Contract #{id}" if id
|
33
33
|
puts 'Conditions are tentative. Edit contract detail.' if id
|
34
34
|
else
|
@@ -87,6 +87,9 @@ when 'contract'
|
|
87
87
|
params['mode'] = 'create'
|
88
88
|
params['customer_id'] = v
|
89
89
|
end
|
90
|
+
opt.on('--salesfee', 'create contract as sales fee definition') do |_v|
|
91
|
+
params['category'] = 'sales_fee'
|
92
|
+
end
|
90
93
|
args = opt.parse(ARGV)
|
91
94
|
contract(args, params)
|
92
95
|
end
|
data/lib/luca_deal.rb
CHANGED
@@ -6,6 +6,8 @@ require 'luca_deal/version'
|
|
6
6
|
module LucaDeal
|
7
7
|
autoload :Customer, 'luca_deal/customer'
|
8
8
|
autoload :Contract, 'luca_deal/contract'
|
9
|
+
autoload :Fee, 'luca_deal/fee'
|
9
10
|
autoload :Invoice, 'luca_deal/invoice'
|
11
|
+
autoload :Product, 'luca_deal/product'
|
10
12
|
autoload :Setup, 'luca_deal/setup'
|
11
13
|
end
|
data/lib/luca_deal/contract.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'luca_deal/version'
|
2
4
|
|
3
5
|
require 'yaml'
|
@@ -7,40 +9,85 @@ require 'luca_record'
|
|
7
9
|
module LucaDeal
|
8
10
|
class Contract < LucaRecord::Base
|
9
11
|
@dirname = 'contracts'
|
12
|
+
@required = ['customer_id', 'terms']
|
10
13
|
|
11
14
|
def initialize(date = nil)
|
12
15
|
@date = date ? Date.parse(date) : Date.today
|
13
16
|
@pjdir = Pathname(Dir.pwd)
|
14
17
|
end
|
15
18
|
|
19
|
+
# returns active contracts on specified date.
|
20
|
+
#
|
21
|
+
def self.asof(year, month, day)
|
22
|
+
return enum_for(:asof, year, month, day) unless block_given?
|
23
|
+
|
24
|
+
new("#{year}-#{month}-#{day}").active do |contract|
|
25
|
+
yield contract
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
16
29
|
#
|
17
30
|
# collect active contracts
|
18
31
|
#
|
19
32
|
def active
|
33
|
+
return enum_for(:active) unless block_given?
|
34
|
+
|
20
35
|
self.class.all do |data|
|
21
|
-
|
22
|
-
next if !self.class.active_period?(contract)
|
36
|
+
next if !active_period?(data.dig('terms'))
|
23
37
|
|
24
|
-
|
38
|
+
contract = parse_current(data)
|
39
|
+
contract['items'] = contract['items']&.map { |item| parse_current(item) }
|
40
|
+
# TODO: handle sales_fee rate change
|
41
|
+
contract['rate'] = contract['rate']
|
42
|
+
yield contract.compact
|
25
43
|
end
|
26
44
|
end
|
27
45
|
|
28
|
-
def generate!(customer_id)
|
46
|
+
def generate!(customer_id, mode = 'subscription')
|
29
47
|
LucaDeal::Customer.find(customer_id) do |customer|
|
30
48
|
current_customer = parse_current(customer)
|
31
|
-
|
49
|
+
if mode == 'sales_fee'
|
50
|
+
obj = salesfee_template
|
51
|
+
else
|
52
|
+
obj = monthly_template
|
53
|
+
end
|
54
|
+
obj.merge!({ 'customer_id' => current_customer['id'], 'customer_name' => current_customer['name'] })
|
55
|
+
obj['terms'] ||= {}
|
56
|
+
obj['terms']['effective'] = @date
|
57
|
+
self.class.create(obj)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def active_period?(dat)
|
62
|
+
unless dat.dig('defunct').nil?
|
63
|
+
defunct = dat.dig('defunct').respond_to?(:year) ? dat.dig('defunct') : Date.parse(dat.dig('defunct'))
|
64
|
+
return false if @date > defunct
|
65
|
+
end
|
66
|
+
effective = dat.dig('effective').respond_to?(:year) ? dat.dig('effective') : Date.parse(dat.dig('effective'))
|
67
|
+
@date >= effective
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def monthly_template
|
73
|
+
{}.tap do |obj|
|
32
74
|
obj['items'] = [{
|
33
75
|
'name' => '_ITEM_NAME_FOR_INVOICE_',
|
34
76
|
'qty' => 1,
|
35
77
|
'price' => 0
|
36
78
|
}]
|
37
|
-
obj['terms'] = { 'billing_cycle' => 'monthly'
|
38
|
-
self.class.create(obj)
|
79
|
+
obj['terms'] = { 'billing_cycle' => 'monthly' }
|
39
80
|
end
|
40
81
|
end
|
41
82
|
|
42
|
-
def
|
43
|
-
|
83
|
+
def salesfee_template
|
84
|
+
{}.tap do |obj|
|
85
|
+
obj['rate'] = {
|
86
|
+
'default' => '0.2',
|
87
|
+
'initial' => '0.2'
|
88
|
+
}
|
89
|
+
obj['terms'] = { 'category' => 'sales_fee' }
|
90
|
+
end
|
44
91
|
end
|
45
92
|
end
|
46
93
|
end
|
data/lib/luca_deal/customer.rb
CHANGED
@@ -10,6 +10,7 @@ require 'luca_record'
|
|
10
10
|
module LucaDeal
|
11
11
|
class Customer < LucaRecord::Base
|
12
12
|
@dirname = 'customers'
|
13
|
+
@required = ['name']
|
13
14
|
|
14
15
|
def initialize(pjdir = nil)
|
15
16
|
@date = Date.today
|
@@ -21,17 +22,20 @@ module LucaDeal
|
|
21
22
|
YAML.dump(list).tap { |l| puts l }
|
22
23
|
end
|
23
24
|
|
24
|
-
def
|
25
|
-
|
25
|
+
def self.create(obj)
|
26
|
+
raise ':name is required' if obj[:name].nil?
|
27
|
+
|
28
|
+
contacts = obj[:contact]&.map { |c| { 'mail' => c[:mail] } }&.compact
|
29
|
+
contacts ||= [{
|
26
30
|
'mail' => '_MAIL_ADDRESS_FOR_CONTACT_'
|
31
|
+
}]
|
32
|
+
h = {
|
33
|
+
'name' => obj[:name],
|
34
|
+
'address' => obj[:address] || '_CUSTOMER_ADDRESS_FOR_INVOICE_',
|
35
|
+
'address2' => obj[:address2] || '_CUSTOMER_ADDRESS_FOR_INVOICE_',
|
36
|
+
'contacts' => contacts
|
27
37
|
}
|
28
|
-
|
29
|
-
'name' => name,
|
30
|
-
'address' => '_CUSTOMER_ADDRESS_FOR_INVOICE_',
|
31
|
-
'address2' => '_CUSTOMER_ADDRESS_FOR_INVOICE_',
|
32
|
-
'contacts' => [contact]
|
33
|
-
}
|
34
|
-
self.class.create(obj)
|
38
|
+
super(h)
|
35
39
|
end
|
36
40
|
end
|
37
41
|
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'luca_deal/version'
|
2
|
+
|
3
|
+
require 'mail'
|
4
|
+
require 'yaml'
|
5
|
+
require 'pathname'
|
6
|
+
require 'bigdecimal'
|
7
|
+
require 'luca_support/config'
|
8
|
+
require 'luca_support/mail'
|
9
|
+
require 'luca_deal'
|
10
|
+
|
11
|
+
module LucaDeal
|
12
|
+
class Fee < LucaRecord::Base
|
13
|
+
@dirname = 'fees'
|
14
|
+
|
15
|
+
def initialize(date = nil)
|
16
|
+
@date = issue_date(date)
|
17
|
+
@config = load_config('config.yml')
|
18
|
+
end
|
19
|
+
|
20
|
+
# calculate fee, based on invoices
|
21
|
+
#
|
22
|
+
def monthly_fee
|
23
|
+
LucaDeal::Contract.asof(@date.year, @date.month, @date.day) do |contract|
|
24
|
+
next if contract.dig('terms', 'category') != 'sales_fee'
|
25
|
+
|
26
|
+
@rate = { 'default' => BigDecimal(contract.dig('rate', 'default')) }
|
27
|
+
@rate['initial'] = contract.dig('rate', 'initial') ? BigDecimal(contract.dig('rate', 'initial')) : @rate['default']
|
28
|
+
|
29
|
+
LucaDeal::Invoice.asof(@date.year, @date.month) do |invoice|
|
30
|
+
next if invoice.dig('sales_fee', 'id') != contract['id']
|
31
|
+
next if duplicated_contract? invoice['contract_id']
|
32
|
+
|
33
|
+
fee = invoice.dup
|
34
|
+
fee['invoice'] = {}.tap do |f_invoice|
|
35
|
+
%w[id contract_id issue_date due_date].each do |i|
|
36
|
+
f_invoice[i] = invoice[i]
|
37
|
+
fee.delete i
|
38
|
+
end
|
39
|
+
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
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_customer(id)
|
49
|
+
{}.tap do |res|
|
50
|
+
LucaDeal::Customer.find(id) do |dat|
|
51
|
+
customer = parse_current(dat)
|
52
|
+
res['id'] = customer['id']
|
53
|
+
res['name'] = customer.dig('name')
|
54
|
+
res['address'] = customer.dig('address')
|
55
|
+
res['address2'] = customer.dig('address2')
|
56
|
+
res['to'] = customer.dig('contacts').map { |h| take_current(h, 'mail') }.compact
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
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
|
+
private
|
67
|
+
|
68
|
+
def lib_path
|
69
|
+
__dir__
|
70
|
+
end
|
71
|
+
|
72
|
+
# load user company profile from config.
|
73
|
+
#
|
74
|
+
def set_company
|
75
|
+
{}.tap do |h|
|
76
|
+
h['name'] = @config.dig('company', 'name')
|
77
|
+
h['address'] = @config.dig('company', 'address')
|
78
|
+
h['address2'] = @config.dig('company', 'address2')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# calc fee & tax amount by tax category
|
83
|
+
#
|
84
|
+
def subtotal(items)
|
85
|
+
{}.tap do |subtotal|
|
86
|
+
items.each do |i|
|
87
|
+
rate = i.dig('type') || 'default'
|
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
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def issue_date(date)
|
99
|
+
base = date.nil? ? Date.today : Date.parse(date)
|
100
|
+
Date.new(base.year, base.month, -1)
|
101
|
+
end
|
102
|
+
|
103
|
+
# load Tax Rate from config.
|
104
|
+
#
|
105
|
+
def load_tax_rate(name)
|
106
|
+
return 0 if @config.dig('tax_rate', name).nil?
|
107
|
+
|
108
|
+
BigDecimal(take_current(@config['tax_rate'], name).to_s)
|
109
|
+
end
|
110
|
+
|
111
|
+
def duplicated_contract?(id)
|
112
|
+
self.class.asof(@date.year, @date.month, @date.day) do |_f, path|
|
113
|
+
return true if path.include?(id)
|
114
|
+
end
|
115
|
+
false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/luca_deal/invoice.rb
CHANGED
@@ -12,6 +12,7 @@ require 'luca_record'
|
|
12
12
|
module LucaDeal
|
13
13
|
class Invoice < LucaRecord::Base
|
14
14
|
@dirname = 'invoices'
|
15
|
+
@required = ['issue_date', 'customer', 'items', 'subtotal']
|
15
16
|
|
16
17
|
def initialize(date = nil)
|
17
18
|
@date = issue_date(date)
|
@@ -58,7 +59,6 @@ module LucaDeal
|
|
58
59
|
mail
|
59
60
|
end
|
60
61
|
|
61
|
-
#
|
62
62
|
# Output seriarized invoice data to stdout.
|
63
63
|
# Returns previous N months on multiple count
|
64
64
|
#
|
@@ -97,7 +97,7 @@ module LucaDeal
|
|
97
97
|
collection << stat
|
98
98
|
end
|
99
99
|
end
|
100
|
-
puts YAML.dump(collection)
|
100
|
+
puts YAML.dump(LucaSupport::Code.readable(collection))
|
101
101
|
end
|
102
102
|
end
|
103
103
|
|
@@ -113,12 +113,12 @@ module LucaDeal
|
|
113
113
|
invoice['customer'] = get_customer(contract.dig('customer_id'))
|
114
114
|
invoice['due_date'] = due_date(@date)
|
115
115
|
invoice['issue_date'] = @date
|
116
|
-
invoice['
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
116
|
+
invoice['sales_fee'] = contract['sales_fee'] if contract.dig('sales_fee')
|
117
|
+
invoice['items'] = get_products(contract['products'])
|
118
|
+
.concat(contract['items']&.map { |i| i['qty'] ||= 1; i } || [])
|
119
|
+
.compact
|
120
|
+
invoice['items'].reject! do |item|
|
121
|
+
item.dig('type') == 'initial' && subsequent_month?(contract.dig('terms', 'effective'))
|
122
122
|
end
|
123
123
|
invoice['subtotal'] = subtotal(invoice['items'])
|
124
124
|
.map { |k, v| v.tap { |dat| dat['rate'] = k } }
|
@@ -126,7 +126,6 @@ module LucaDeal
|
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
129
|
-
#
|
130
129
|
# set variables for ERB template
|
131
130
|
#
|
132
131
|
def invoice_vars(invoice_dat)
|
@@ -151,9 +150,23 @@ module LucaDeal
|
|
151
150
|
end
|
152
151
|
end
|
153
152
|
|
153
|
+
def get_products(products)
|
154
|
+
return [] if products.nil?
|
155
|
+
|
156
|
+
[].tap do |res|
|
157
|
+
products.each do |product|
|
158
|
+
LucaDeal::Product.find(product['id'])['items'].each do |item|
|
159
|
+
item['product_id'] = product['id']
|
160
|
+
item['qty'] ||= 1
|
161
|
+
res << item
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
154
167
|
def gen_invoice!(invoice)
|
155
168
|
id = invoice.dig('contract_id')
|
156
|
-
self.class.
|
169
|
+
self.class.create(invoice, date: @date, codes: Array(id))
|
157
170
|
end
|
158
171
|
|
159
172
|
def issue_date(date)
|
@@ -172,7 +185,6 @@ module LucaDeal
|
|
172
185
|
__dir__
|
173
186
|
end
|
174
187
|
|
175
|
-
#
|
176
188
|
# load user company profile from config.
|
177
189
|
#
|
178
190
|
def set_company
|
@@ -183,23 +195,22 @@ module LucaDeal
|
|
183
195
|
end
|
184
196
|
end
|
185
197
|
|
186
|
-
#
|
187
198
|
# calc items & tax amount by tax category
|
188
199
|
#
|
189
200
|
def subtotal(items)
|
190
201
|
{}.tap do |subtotal|
|
191
202
|
items.each do |i|
|
192
203
|
rate = i.dig('tax') || 'default'
|
204
|
+
qty = i['qty'] || BigDecimal('1')
|
193
205
|
subtotal[rate] = { 'items' => 0, 'tax' => 0 } if subtotal.dig(rate).nil?
|
194
|
-
subtotal[rate]['items'] += i['
|
206
|
+
subtotal[rate]['items'] += i['price'] * qty
|
195
207
|
end
|
196
208
|
subtotal.each do |rate, amount|
|
197
|
-
amount['tax'] = (amount['items'] * load_tax_rate(rate))
|
209
|
+
amount['tax'] = (amount['items'] * load_tax_rate(rate))
|
198
210
|
end
|
199
211
|
end
|
200
212
|
end
|
201
213
|
|
202
|
-
#
|
203
214
|
# load Tax Rate from config.
|
204
215
|
#
|
205
216
|
def load_tax_rate(name)
|
@@ -218,5 +229,10 @@ module LucaDeal
|
|
218
229
|
end
|
219
230
|
false
|
220
231
|
end
|
232
|
+
|
233
|
+
def subsequent_month?(effective_date)
|
234
|
+
effective_date = Date.parse(effective_date) unless effective_date.respond_to? :year
|
235
|
+
effective_date.year != @date.year || effective_date.month != @date.month
|
236
|
+
end
|
221
237
|
end
|
222
238
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'luca_deal/version'
|
4
|
+
|
5
|
+
require 'date'
|
6
|
+
require 'yaml'
|
7
|
+
require 'pathname'
|
8
|
+
require 'luca_record'
|
9
|
+
|
10
|
+
module LucaDeal
|
11
|
+
class Product < LucaRecord::Base
|
12
|
+
@dirname = 'products'
|
13
|
+
@required = ['items']
|
14
|
+
|
15
|
+
def list_name
|
16
|
+
list = self.class.all.map { |dat| parse_current(dat) }
|
17
|
+
YAML.dump(list).tap { |l| puts l }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Save data with hash in Product format. Simple format is also available as bellows:
|
21
|
+
# {
|
22
|
+
# name: 'item_name(required)', price: 'item_price', qty: 'item_qty',
|
23
|
+
# initial: { name: 'item_name', price: 'item_price', qty: 'item_qty' }
|
24
|
+
# }
|
25
|
+
def self.create(obj)
|
26
|
+
if obj[:name].nil?
|
27
|
+
h = obj
|
28
|
+
else
|
29
|
+
items = [{
|
30
|
+
'name' => obj[:name],
|
31
|
+
'price' => obj[:price] || 0,
|
32
|
+
'qty' => obj[:qty] || 1
|
33
|
+
}]
|
34
|
+
if obj[:initial]
|
35
|
+
items << {
|
36
|
+
'name' => obj.dig(:initial, :name),
|
37
|
+
'price' => obj.dig(:initial, :price) || 0,
|
38
|
+
'qty' => obj.dig(:initial, :qty) || 1,
|
39
|
+
'type' => 'initial'
|
40
|
+
}
|
41
|
+
end
|
42
|
+
h = {
|
43
|
+
'name' => obj[:name],
|
44
|
+
'items' => items
|
45
|
+
}
|
46
|
+
end
|
47
|
+
super(h)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/luca_deal/setup.rb
CHANGED
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.2.
|
4
|
+
version: 0.2.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chuma Takahiro
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: lucarecord
|
@@ -28,14 +28,14 @@ dependencies:
|
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '1.17'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.17'
|
41
41
|
- !ruby/object:Gem::Dependency
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 12.3.3
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 12.3.3
|
55
69
|
description: 'Deal with contracts
|
56
70
|
|
57
71
|
'
|
@@ -69,7 +83,9 @@ files:
|
|
69
83
|
- lib/luca_deal.rb
|
70
84
|
- lib/luca_deal/contract.rb
|
71
85
|
- lib/luca_deal/customer.rb
|
86
|
+
- lib/luca_deal/fee.rb
|
72
87
|
- lib/luca_deal/invoice.rb
|
88
|
+
- lib/luca_deal/product.rb
|
73
89
|
- lib/luca_deal/setup.rb
|
74
90
|
- lib/luca_deal/templates/.keep
|
75
91
|
- lib/luca_deal/templates/config.yml
|