vatcalc 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +189 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/vatcalc.rb +51 -0
- data/lib/vatcalc/acts_as_bill_element.rb +57 -0
- data/lib/vatcalc/base_element.rb +56 -0
- data/lib/vatcalc/bill.rb +227 -0
- data/lib/vatcalc/gnv.rb +122 -0
- data/lib/vatcalc/service_element.rb +93 -0
- data/lib/vatcalc/util.rb +67 -0
- data/lib/vatcalc/vat_percentage.rb +110 -0
- data/lib/vatcalc/version.rb +3 -0
- data/vatcalc.gemspec +39 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e13c738c08997c3f9eed7520fca91a0ce0d8396e
|
4
|
+
data.tar.gz: 03c1ab0f632e87dd5211887c5a09376b57e6925c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 77a06947b030edb11ce582f640d589318de1d99c37159088491348bcaf4ef9945c81ab3a556a8a9b64df4ecfdfeb263a267b905db99a418f34c474368a0b6eaa
|
7
|
+
data.tar.gz: 86fbacf991bdb51c976e6a3d8e9b036390d9df8baf15a6113d2f48e85dbf57f054409d0d32851097eea552f18edd090e2a48cbfaf5cf988d455a8aebd56bb34d
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at TODO: Write your email address. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 TODO: Write your name
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
# Vatcalc
|
2
|
+
|
3
|
+
A gem to calculate VAT of multiple products with differently VAT percentages.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'vatcalc'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install vatcalc
|
20
|
+
|
21
|
+
## Include
|
22
|
+
|
23
|
+
Include Vatcalc::ActsAsBillElement in your models / Classes
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
|
27
|
+
class Product < ActiveRecord::Base
|
28
|
+
include Vatcalc::ActsAsBillElement
|
29
|
+
|
30
|
+
acts_as_bill_element(amount: price, vat_percentage: :my_vat_percentage_field, currency: "EUR", prefix: :bill)
|
31
|
+
|
32
|
+
#you can pass the following key value pairs
|
33
|
+
|
34
|
+
# amount: the price for the product [required]
|
35
|
+
# you can pass:
|
36
|
+
# symbol => for a method
|
37
|
+
# lambda => ->(obj) {obj.price * 3}
|
38
|
+
# Float, Integer for a fix amount.
|
39
|
+
# NOTE:
|
40
|
+
# if the value is an Integer it is assumed that the amount is given in cents.
|
41
|
+
#
|
42
|
+
#
|
43
|
+
# currency:
|
44
|
+
# self-descriptive [optional]
|
45
|
+
# standard value is the money default currency
|
46
|
+
# => https://github.com/RubyMoney/money
|
47
|
+
#
|
48
|
+
# you can pass:
|
49
|
+
# symbol => for a method
|
50
|
+
# lambda => ->(obj) {obj.other_object.find_my_currency}
|
51
|
+
# String => "EUR", "USD"
|
52
|
+
#
|
53
|
+
#
|
54
|
+
# vat_percentage:
|
55
|
+
# the VAT percentage for the given product. [optional]
|
56
|
+
# This option will be ignored if you set the service option to true. +see below+
|
57
|
+
# Default is Vatcalc.vat_percentage => @see section "Configuration"
|
58
|
+
# you can pass:
|
59
|
+
# symbol => for a method
|
60
|
+
# lambda => ->(obj) {obj.find_my_currency}
|
61
|
+
# String => "EUR", "USD"
|
62
|
+
# NOTE:
|
63
|
+
# if the value is between 1 and 100 the value will be divided by 100.
|
64
|
+
# For example if you pass a value like 19 it is assumed that you mean 19%
|
65
|
+
# if the value is between 0 and 100 the value won't be divided.
|
66
|
+
#
|
67
|
+
# prefix: the prefix to call gross,vat,net,vat_splitted, and vat_percentage on your object.
|
68
|
+
# @see below.
|
69
|
+
#
|
70
|
+
#
|
71
|
+
# net:
|
72
|
+
# is the amount given as net amount ? [optional]
|
73
|
+
# Default => false
|
74
|
+
#
|
75
|
+
#
|
76
|
+
#
|
77
|
+
# service:
|
78
|
+
# is the object a service like a Coupon or a Fee ? If this option is set to true
|
79
|
+
# the object has not a fix VAT percentage.
|
80
|
+
# the vat will be calculated by the non-service net rates in a bill.
|
81
|
+
|
82
|
+
|
83
|
+
....
|
84
|
+
|
85
|
+
#now you can call
|
86
|
+
product.bill_gross #=> #<Money fractional:1000 currency:EUR>
|
87
|
+
product.bill_net #=> #<Money fractional:840 currency:EUR>
|
88
|
+
product.bill_vat #=> #<Money fractional:160 currency:EUR>
|
89
|
+
|
90
|
+
|
91
|
+
product.bill_vat_splitted #=> {#<Vatcalc::VATPercentage vat_percentage:19%> => #<Money fractional:160 currency:EUR>}
|
92
|
+
# the key in the result hash is a Vatcalc::VATPercentage object it responds to to_f, to_s, to_d
|
93
|
+
# @example
|
94
|
+
# vp.to_s => "19%"
|
95
|
+
# vp.to_f => 1.19
|
96
|
+
# vp.to_d => #<BigDecimal:7f7ee20989a0,'0.119E1',18(36)>
|
97
|
+
|
98
|
+
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
## Bill
|
103
|
+
|
104
|
+
Creating a new bill object
|
105
|
+
```ruby
|
106
|
+
|
107
|
+
bill = Vatcalc::Bill.new(elements: [product1,product2,fee])
|
108
|
+
|
109
|
+
#NOTE:
|
110
|
+
# If you pass an Array of 2D arrays it is assumed that the first element in 2D Array is the object
|
111
|
+
# and the second element is the quantity.
|
112
|
+
# @example elements:
|
113
|
+
# [
|
114
|
+
# [ product1, 2 ],
|
115
|
+
# [ product2, 1 ]
|
116
|
+
# ]
|
117
|
+
|
118
|
+
#now you can call
|
119
|
+
bill.gross #=> #<Money fractional:15000 currency:EUR>
|
120
|
+
bill.vat #=> #<Money fractional:1925 currency:EUR>
|
121
|
+
bill.net #=> #<Money fractional:13075 currency:EUR>
|
122
|
+
|
123
|
+
# if you only want to get amounts of the base.
|
124
|
+
bill.base.gross
|
125
|
+
...
|
126
|
+
|
127
|
+
# if you only want to get amounts of the services.
|
128
|
+
bill.services.gross
|
129
|
+
...
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
bill.vat_splitted #=>
|
134
|
+
#
|
135
|
+
# {
|
136
|
+
# #<Vatcalc::VATPercentage vat_percentage:7%> => #<Money fractional:325 currency:EUR>,
|
137
|
+
# #<Vatcalc::VATPercentage vat_percentage:19%> => #<Money fractional:1600 currency:EUR>
|
138
|
+
# }
|
139
|
+
|
140
|
+
# To get the vat rates
|
141
|
+
# These vat rates are the net sums of the non-service elements grouped by vat_percentage and divided by total
|
142
|
+
# non-service elements net amount
|
143
|
+
|
144
|
+
bill.vat_rates # =>
|
145
|
+
# {
|
146
|
+
# #<Vatcalc::VATPercentage vat_percentage:19%>=>0.6424,
|
147
|
+
# #<Vatcalc::VATPercentage vat_percentage:7%>=>0.3576,
|
148
|
+
# }
|
149
|
+
|
150
|
+
## NOTE: Each service Element will be taxed by these rates.
|
151
|
+
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
bill.each do |obj, quantity, gross, vat, net|
|
156
|
+
# NOTE: gross, vat, net are already multiplied by quantity
|
157
|
+
# do stuff ..
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
|
163
|
+
|
164
|
+
```
|
165
|
+
|
166
|
+
|
167
|
+
## Configuration
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
gem 'vatcalc'
|
171
|
+
```
|
172
|
+
|
173
|
+
## Development
|
174
|
+
|
175
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
176
|
+
|
177
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
178
|
+
|
179
|
+
## Contributing
|
180
|
+
|
181
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/vatcalc. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
182
|
+
|
183
|
+
## License
|
184
|
+
|
185
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
186
|
+
|
187
|
+
## Code of Conduct
|
188
|
+
|
189
|
+
Everyone interacting in the Vatcalc project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/vatcalc/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "vatcalc"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/vatcalc.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require "vatcalc/version"
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/core_ext'
|
4
|
+
|
5
|
+
require "vatcalc/util"
|
6
|
+
require "vatcalc/vat_percentage"
|
7
|
+
|
8
|
+
require "vatcalc/gnv"
|
9
|
+
require "vatcalc/base_element"
|
10
|
+
|
11
|
+
require "vatcalc/service_element"
|
12
|
+
require "vatcalc/bill"
|
13
|
+
|
14
|
+
require "vatcalc/acts_as_bill_element"
|
15
|
+
|
16
|
+
module Vatcalc
|
17
|
+
mattr_accessor :currency
|
18
|
+
|
19
|
+
class << self
|
20
|
+
|
21
|
+
def vat_percentage
|
22
|
+
@vat_percentage
|
23
|
+
end
|
24
|
+
|
25
|
+
def vat_percentage=(v)
|
26
|
+
@vat_percentage = VATPercentage.new(v)
|
27
|
+
end
|
28
|
+
|
29
|
+
alias :percentage :vat_percentage
|
30
|
+
alias :percentage= :vat_percentage=
|
31
|
+
|
32
|
+
|
33
|
+
def vat_of(v,**args)
|
34
|
+
BaseElement.new(v,**args).vat
|
35
|
+
end
|
36
|
+
|
37
|
+
def net_of(v,**args)
|
38
|
+
BaseElement.new(v,**args).net
|
39
|
+
end
|
40
|
+
|
41
|
+
def gross_of(v,**args)
|
42
|
+
BaseElement.new(v,**args).gross
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
#German Standard Settings
|
48
|
+
self.currency = "EUR"
|
49
|
+
self.vat_percentage= 19.00
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Vatcalc
|
2
|
+
|
3
|
+
|
4
|
+
def self.acts_as_bill_element?
|
5
|
+
@acts_as_bill_element ||= ->(obj) { obj.class.respond_to?(:acts_as_bill_element) && obj.respond_to?(:as_vatcalc_bill_element) }
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
module ActsAsBillElement
|
10
|
+
|
11
|
+
def self.included(mod)
|
12
|
+
mod.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def acts_as_bill_element(amount:, service: false, currency: nil, vat_percentage: nil, prefix: :bill, net: false)
|
17
|
+
|
18
|
+
args_to_convert = {amount: amount,currency: currency,net: net}
|
19
|
+
delegators = [:gross,:net,:vat,:vat_splitted]
|
20
|
+
|
21
|
+
if service
|
22
|
+
klass = Vatcalc::ServiceElement
|
23
|
+
else
|
24
|
+
klass = Vatcalc::BaseElement
|
25
|
+
args_to_convert[:vat_percentage] = vat_percentage
|
26
|
+
delegators << :vat_percentage
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
delegate *delegators, prefix: prefix, to: :as_vatcalc_bill_element
|
31
|
+
v_name = :@as_vatcalc_bill_element
|
32
|
+
|
33
|
+
define_method(:as_vatcalc_bill_element) do
|
34
|
+
unless instance_variable_get(v_name)
|
35
|
+
args = args_to_convert.inject({}) do |h,(k,v)|
|
36
|
+
case v
|
37
|
+
when Proc
|
38
|
+
h[k] = v.call(self)
|
39
|
+
when Symbol
|
40
|
+
h[k] = send(v)
|
41
|
+
else
|
42
|
+
h[k] = v
|
43
|
+
end
|
44
|
+
h
|
45
|
+
end
|
46
|
+
instance_variable_set v_name, klass.new( args.delete(:amount), **args)
|
47
|
+
end
|
48
|
+
instance_variable_get(v_name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# A Base Element Object inherits from GNV
|
2
|
+
#@see Vatcalc::GNV
|
3
|
+
#
|
4
|
+
# A BaseElement always needs an VAT percentage and an amount
|
5
|
+
# if no VAT Percentage is given it takes the default VAT Percentage
|
6
|
+
module Vatcalc
|
7
|
+
class BaseElement < GNV
|
8
|
+
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
|
12
|
+
attr_reader :vat_percentage
|
13
|
+
alias_method :percentage, :vat_percentage
|
14
|
+
alias_method :vat_p, :vat_percentage
|
15
|
+
|
16
|
+
|
17
|
+
#Initalizes a new Object of an BaseElement
|
18
|
+
#@param amount = [Money,Numeric]
|
19
|
+
#@param options = [Hash]
|
20
|
+
#
|
21
|
+
# Assumes that the amount is a gross value but you can pass a net value as well if you pass the
|
22
|
+
# option net: true
|
23
|
+
#
|
24
|
+
#@example
|
25
|
+
#
|
26
|
+
# => b = BaseElement.new 10.00, vat_percentage: 19, currency: "EUR"
|
27
|
+
# b.net.to_f = 8.40
|
28
|
+
# => b = BaseElement.new 10.00, vat_percentage: 7, currency: "USD"
|
29
|
+
# b.net.to_f = 9.35
|
30
|
+
# => b = BaseElement.new 10.00, vat_percentage: 7, currency: "USD", net: true
|
31
|
+
# => b.gross = 10.70
|
32
|
+
def initialize(amount,currency: nil, vat_percentage: nil, net: false)
|
33
|
+
@currency = currency || Vatcalc.currency
|
34
|
+
amount = Util.to_money(amount,@currency)
|
35
|
+
vp = Util.to_vat_percentage(vat_percentage)
|
36
|
+
@vector = net ? Vector[amount * vp, amount] : Vector[amount, amount / vp]
|
37
|
+
@vat_percentage = vp
|
38
|
+
@vat_splitted = {@vat_percentage => vat}
|
39
|
+
end
|
40
|
+
|
41
|
+
def hash
|
42
|
+
#vector comes from GNV
|
43
|
+
[@vector,@vat_percentage].hash
|
44
|
+
end
|
45
|
+
|
46
|
+
def ==(oth)
|
47
|
+
oth.is_a?(BaseElement) && (oth.vector == @vector) && (vat_p == oth.vat_p)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def inspect
|
52
|
+
"#<#{self.class.name} vat_percentage:#{vat_p} gross:#{gross} net: #{net} vat:#{vat} currency:#{@currency}>"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
data/lib/vatcalc/bill.rb
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
|
2
|
+
module Vatcalc
|
3
|
+
class Bill
|
4
|
+
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :currency
|
8
|
+
attr_reader :services
|
9
|
+
attr_reader :base
|
10
|
+
|
11
|
+
alias_method :service_elements, :services
|
12
|
+
alias_method :base_elements, :base
|
13
|
+
|
14
|
+
delegate :rates,:rates!,:human_rates,:vat_percentages, to: :@base
|
15
|
+
delegate :each, to: :all
|
16
|
+
|
17
|
+
def initialize(elements: [],currency: nil)
|
18
|
+
@base = Base.new #defined at the bottom
|
19
|
+
@services = Services.new #defined at the bottom
|
20
|
+
@currency = currency
|
21
|
+
insert(elements)
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
#inserts one or more element which included the Vatcalc::ActsAsBillElement module
|
26
|
+
#@param obj = [Array,Object]
|
27
|
+
#@result = [Vatcalc::Bill]
|
28
|
+
def insert(obj, quantity = 1)
|
29
|
+
case obj
|
30
|
+
when Array
|
31
|
+
return (obj.each { |obj, quantity| insert(obj, quantity)}.last || self)
|
32
|
+
when Vatcalc.acts_as_bill_element?
|
33
|
+
gnv = obj.as_vatcalc_bill_element
|
34
|
+
else raise ArgumentError.new ("Can't insert a #{obj.class} into #{self}. #{obj.class} must include Vatcalc::ActsAsBillElement")
|
35
|
+
end
|
36
|
+
if (quantity ||= 1) > 0
|
37
|
+
gnv.source = obj
|
38
|
+
@currency = gnv.currency
|
39
|
+
case gnv
|
40
|
+
when BaseElement
|
41
|
+
@base.insert(gnv,quantity)
|
42
|
+
# if an base element is inserted after services already in here.
|
43
|
+
@services.rates_changed!(@base.rates) if @services.length > 0
|
44
|
+
when ServiceElement
|
45
|
+
@services.insert(gnv,quantity)
|
46
|
+
# the service gets now the rates of the base
|
47
|
+
gnv.change_rates(@base.rates)
|
48
|
+
end
|
49
|
+
|
50
|
+
@base.currency = @currency
|
51
|
+
@services.currency = @currency
|
52
|
+
end
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
[:gross,:vat,:net].each do |m|
|
57
|
+
define_method(m) { base.send(m) + services.send(m) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def vat_splitted
|
61
|
+
all.vat_splitted
|
62
|
+
end
|
63
|
+
|
64
|
+
def all
|
65
|
+
(base + services)
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
RoundPrecision = 4
|
70
|
+
#will only be used in rspec for test
|
71
|
+
Tolerance = BigDecimal("1E-#{RoundPrecision}")
|
72
|
+
#@see +rates+
|
73
|
+
|
74
|
+
alias_method :percentages, :vat_percentages
|
75
|
+
alias_method :vat_rates, :rates
|
76
|
+
alias_method :elements, :all
|
77
|
+
|
78
|
+
# A GNVCollection consists basically of a an 2D Array of GNV
|
79
|
+
# GNV Objects +@see Vatcalc::GNV+
|
80
|
+
# It's a helper class to calculate amounts and iterate through
|
81
|
+
# specific GNV objects.
|
82
|
+
class GNVCollection
|
83
|
+
include Enumerable
|
84
|
+
|
85
|
+
attr_reader :collection
|
86
|
+
attr_accessor :currency
|
87
|
+
|
88
|
+
delegate :length, :first, :last, to: :@collection
|
89
|
+
|
90
|
+
# which class can be inserted in the collection
|
91
|
+
def self.for
|
92
|
+
GNV
|
93
|
+
end
|
94
|
+
|
95
|
+
def initialize(col=[],currency = nil)
|
96
|
+
@collection = col
|
97
|
+
@currency = currency
|
98
|
+
end
|
99
|
+
|
100
|
+
def insert(gnv,quantity)
|
101
|
+
raise(TypeError.new) unless gnv.is_a?(self.class.for)
|
102
|
+
@currency = gnv.currency
|
103
|
+
@vat_splitted = nil
|
104
|
+
@collection << [gnv,quantity]
|
105
|
+
@gross, @vat, @net = [nil] * 3
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
def <<(arg)
|
110
|
+
insert(arg,1)
|
111
|
+
end
|
112
|
+
|
113
|
+
def vat_splitted
|
114
|
+
@vat_splitted ||= @collection.inject(GNV.new(0,0,@currency)){|sum,(gnv,q)| sum += (gnv * q) }.vat_splitted
|
115
|
+
end
|
116
|
+
|
117
|
+
[:gross,:vat,:net].each do |it|
|
118
|
+
define_method(it) { instance_variable_get("@#{it}") || instance_variable_set( "@#{it}", @collection.inject(new_money) {|sum,(gnv,q)| sum += (gnv.send(it) * q) } ) }
|
119
|
+
end
|
120
|
+
|
121
|
+
def +(other)
|
122
|
+
raise(TypeError.new) unless other.is_a?(GNVCollection)
|
123
|
+
GNVCollection.new(@collection + other.collection,@currency)
|
124
|
+
end
|
125
|
+
|
126
|
+
def each
|
127
|
+
result = []
|
128
|
+
@collection.each do |gnv,quantity|
|
129
|
+
arr = [gnv.source, quantity, gross*quantity, net*quantity, vat*quantity]
|
130
|
+
result << arr
|
131
|
+
yield *arr
|
132
|
+
end
|
133
|
+
result
|
134
|
+
end
|
135
|
+
|
136
|
+
def each_gnv
|
137
|
+
@collection.each {|gnv,quantity| yield gnv, quantity }
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def money_hash
|
143
|
+
Hash.new(new_money)
|
144
|
+
end
|
145
|
+
|
146
|
+
def new_money
|
147
|
+
Money.new(0,@currency)
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
class Base < GNVCollection
|
155
|
+
|
156
|
+
attr_reader :vat_percentages
|
157
|
+
|
158
|
+
def initialize(*args)
|
159
|
+
super
|
160
|
+
@vat_percentages = Set.new
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.for
|
164
|
+
BaseElement
|
165
|
+
end
|
166
|
+
|
167
|
+
def insert(gnv,quantity)
|
168
|
+
super
|
169
|
+
@rates = nil
|
170
|
+
@vat_percentages << gnv.vat_p
|
171
|
+
self
|
172
|
+
end
|
173
|
+
|
174
|
+
# Output of rates in form of
|
175
|
+
# key is VAT Percentage and Value is the rate
|
176
|
+
# "{1.0=>0.0092, 1.19=>0.8804, 1.07=>0.1104}"
|
177
|
+
def rates
|
178
|
+
@rates ||= rates!
|
179
|
+
end
|
180
|
+
|
181
|
+
def rates!
|
182
|
+
@rates = Hash.new(0.00)
|
183
|
+
if net.to_f != 0
|
184
|
+
left_over = 1.00
|
185
|
+
grouped_amounts = @collection.inject(money_hash){ |h,(gnv,q)| h[gnv.vat_p] += gnv.net * q; h}.sort
|
186
|
+
|
187
|
+
grouped_amounts.each_with_index do |(vp,amount),i|
|
188
|
+
if i == (grouped_amounts.length - 1)
|
189
|
+
#last element
|
190
|
+
@rates[vp] = left_over.round(4)
|
191
|
+
else
|
192
|
+
@rates[vp] = (amount / net).round(4)
|
193
|
+
left_over -= @rates[vp]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
else
|
197
|
+
max_p = @vat_percentages.max
|
198
|
+
@rates[max_p] = 1.00 if max_p
|
199
|
+
end
|
200
|
+
@rates = @rates.sort.reverse.to_h #sorted by vat percentage
|
201
|
+
end
|
202
|
+
|
203
|
+
# Output of rates in form of
|
204
|
+
# key is VAT Percentage and Value is the rate in decimal form
|
205
|
+
# {"19%"=>"69.81%", "7%"=>"21.74%", "0%"=>"8.45%"}
|
206
|
+
def human_rates
|
207
|
+
#example ((1.19 - 1.00)*100).round(2) => 19.0
|
208
|
+
rates.inject({}){|h,(pr,v)| h[pr.to_s] = Util.human_percentage_value(v,4); h}
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
class Services < GNVCollection
|
214
|
+
def self.for
|
215
|
+
ServiceElement
|
216
|
+
end
|
217
|
+
|
218
|
+
def rates_changed!(rates)
|
219
|
+
@vat_splitted = nil
|
220
|
+
@gross, @vat, @net = [nil] * 3
|
221
|
+
each_gnv {|gnv,_| gnv.change_rates(rates)}
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
end
|
227
|
+
end
|
data/lib/vatcalc/gnv.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require "matrix"
|
2
|
+
|
3
|
+
# A GNV Object consists basically of a 2D Vector
|
4
|
+
# First value is gross, second is net.
|
5
|
+
# Vat is calculated by gross - net
|
6
|
+
#
|
7
|
+
# GNV is an abstract Object and should only used for internal calculations in this library.
|
8
|
+
#
|
9
|
+
#@example
|
10
|
+
# GNV.new(10.00,9.00)
|
11
|
+
#
|
12
|
+
# You can add or subtract two GNVs
|
13
|
+
# GNV.new(10.00,9.00) + GNV.new(9.00,0.00)
|
14
|
+
module Vatcalc
|
15
|
+
class GNV
|
16
|
+
|
17
|
+
include Comparable
|
18
|
+
|
19
|
+
attr_reader :vector,:currency
|
20
|
+
attr_accessor :source
|
21
|
+
|
22
|
+
def initialize(gross,net,cur=nil)
|
23
|
+
@currency ||= (cur || Vatcalc.currency)
|
24
|
+
init_vector(gross,net)
|
25
|
+
end
|
26
|
+
|
27
|
+
[:+,:-].each do |m_name|
|
28
|
+
define_method(m_name) do |oth|
|
29
|
+
gnv = oth.is_a?(GNV) ? to_gnv(@vector.send(m_name,oth.vector)) : raise(TypeError.new)
|
30
|
+
gnv.tap do |g|
|
31
|
+
g.vat_splitted = oth.vat_splitted.merge(self.vat_splitted) {|vp,m1,m2| m1 + m2}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def *(oth)
|
37
|
+
gnv = oth.is_a?(Numeric) ? to_gnv(@vector * oth) : raise(TypeError.new)
|
38
|
+
gnv.tap do |g|
|
39
|
+
h = new_money_hash
|
40
|
+
self.vat_splitted.each {|vp,m| h[vp] = m*oth}
|
41
|
+
g.vat_splitted = h
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
#For usage of => - GNV.new(100.00,90.00)
|
46
|
+
def -@
|
47
|
+
to_gnv(-@vector)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def ==(oth)
|
52
|
+
oth.is_a?(GNV) && oth.vector == @vector
|
53
|
+
end
|
54
|
+
|
55
|
+
alias_method :eql?, :==
|
56
|
+
|
57
|
+
def <=>(other)
|
58
|
+
if other.respond_to?(:net)
|
59
|
+
net <=> other.net
|
60
|
+
else
|
61
|
+
net <=> other
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
#@see https://www.mutuallyhuman.com/blog/2011/01/25/class-coercion-in-ruby
|
68
|
+
def coerce(oth)
|
69
|
+
[self,oth]
|
70
|
+
end
|
71
|
+
|
72
|
+
def gross
|
73
|
+
@vector[0]
|
74
|
+
end
|
75
|
+
|
76
|
+
def net
|
77
|
+
@vector[1]
|
78
|
+
end
|
79
|
+
|
80
|
+
def vat_splitted
|
81
|
+
@vat_splitted ||= new_money_hash
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns a Integer hash value based on the +value+
|
85
|
+
# in order to use functions like & (intersection), group_by, etc.
|
86
|
+
#
|
87
|
+
# @return [Integer]
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# GNV.new(19,11).hash #=> 908351
|
91
|
+
def hash
|
92
|
+
@vector.hash
|
93
|
+
end
|
94
|
+
|
95
|
+
#Always gross - net
|
96
|
+
def vat
|
97
|
+
gross - net
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_gnv(v=@vector)
|
101
|
+
GNV.new(v[0],v[1],@currency)
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
def vat_splitted=(h)
|
107
|
+
@vat_splitted = h
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def init_vector(g,n)
|
113
|
+
@vector = Vector[*[g,n].map{|i| Util.to_money(i,@currency)}]
|
114
|
+
end
|
115
|
+
|
116
|
+
def new_money_hash
|
117
|
+
Hash.new(Money.new(0,@currency))
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Vatcalc
|
2
|
+
class ServiceElement < GNV
|
3
|
+
|
4
|
+
|
5
|
+
attr_reader :vat_splitted,:rates
|
6
|
+
|
7
|
+
#Initalizes a new Object of an ServiceElement
|
8
|
+
#@param amount = [Money,Numeric]
|
9
|
+
#@param options = [Hash]
|
10
|
+
#
|
11
|
+
# Assumes that the amount is a gross value but you can pass a net value as well if you pass the
|
12
|
+
# option net: true
|
13
|
+
#
|
14
|
+
#@example
|
15
|
+
# => b = ServiceElement.new 10.00, currency: "EUR"
|
16
|
+
# b.net.to_f = 8.40
|
17
|
+
# => b = ServiceElement.new 10.00, currency: "USD"
|
18
|
+
# b.net.to_f = 9.35
|
19
|
+
# => b = ServiceElement.new 10.00, currency: "USD", net: true
|
20
|
+
# => b.gross = 10.70
|
21
|
+
def initialize(amount,net: false, currency: nil, rates: {})
|
22
|
+
@net_service = net
|
23
|
+
#if an service element is initialized # => gross equals net
|
24
|
+
super amount, amount, currency
|
25
|
+
change_rates(rates)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Allocates net or gross by new vat_percentage rates and calculates the vat splitted by given rates
|
29
|
+
# @param rates [Hash]
|
30
|
+
# =>
|
31
|
+
#
|
32
|
+
# @return [Hash]
|
33
|
+
# @example
|
34
|
+
# => {#<Vatcalc::VATPercentage vat_percentage:19%>=>#<Money fractional:64 currency:EUR>,
|
35
|
+
# #<Vatcalc::VATPercentage vat_percentage:7%>=>#<Money fractional:39 currency:EUR>}
|
36
|
+
#
|
37
|
+
def change_rates(new_rates)
|
38
|
+
if new_rates.is_a? Hash
|
39
|
+
if !new_rates.empty?
|
40
|
+
# Using basically the allocate function of the Money gem here.
|
41
|
+
# EXPLANATION FROM MONEY GEM:
|
42
|
+
#
|
43
|
+
# Allocates money between different parties without losing pennies.
|
44
|
+
# After the mathematical split has been performed, leftover pennies will
|
45
|
+
# be distributed round-robin amongst the parties. This means that parties
|
46
|
+
# listed first will likely receive more pennies than ones that are listed later
|
47
|
+
|
48
|
+
# @param [Array<Numeric>] splits [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% to party2, and 25% to party3.
|
49
|
+
|
50
|
+
# @return [Array<Money>]
|
51
|
+
|
52
|
+
# @example
|
53
|
+
# Money.new(5, "USD").allocate([0.3, 0.7]) #=> [Money.new(2), Money.new(3)]
|
54
|
+
# Money.new(100, "USD").allocate([0.33, 0.33, 0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
|
55
|
+
#
|
56
|
+
allocated = (@net_service ? net : gross).allocate(new_rates.values)
|
57
|
+
# Init new vector after the allocate calculation
|
58
|
+
# Comes from superclass GNV
|
59
|
+
init_vector(0,0)
|
60
|
+
@vat_splitted = {}
|
61
|
+
new_rates.keys.zip(allocated).each do |vp,splitted|
|
62
|
+
#creating a new base element
|
63
|
+
b = BaseElement.new(splitted, net: @net_service,vat_percentage: vp, currency: @currency)
|
64
|
+
@vector += b.vector
|
65
|
+
@vat_splitted[b.vat_percentage] = b.vat
|
66
|
+
end
|
67
|
+
@rates = new_rates
|
68
|
+
else
|
69
|
+
@vat_splitted = {}
|
70
|
+
end
|
71
|
+
@rates = new_rates
|
72
|
+
else
|
73
|
+
ArgumentError.new "Hash must be given not #{arg.class}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def hash
|
78
|
+
#vector comes from GNV
|
79
|
+
[@vector,@vat_splitted].hash
|
80
|
+
end
|
81
|
+
|
82
|
+
def ==(oth)
|
83
|
+
oth.is_a?(ServiceElement) && oth.gross == gross && oth.net == net && (@vat_splitted == oth.vat_splitted)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def inspect
|
88
|
+
"#<#{self.class.name} vat_splitted:#{vat_splitted} gross:#{gross} net: #{net} vat:#{vat} currency: #{@currency}>"
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
data/lib/vatcalc/util.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
require "money"
|
3
|
+
|
4
|
+
|
5
|
+
module Vatcalc
|
6
|
+
class Util
|
7
|
+
class << self
|
8
|
+
|
9
|
+
#Converts an Object into a Money object
|
10
|
+
#@return [Money]
|
11
|
+
#@example
|
12
|
+
# => Vatcalc::Util.convert_to_money(10.00)
|
13
|
+
def convert_to_money(obj,curr=nil)
|
14
|
+
curr ||= Vatcalc.currency
|
15
|
+
case obj
|
16
|
+
when Money
|
17
|
+
obj
|
18
|
+
when Fixnum
|
19
|
+
Money.new(obj,curr)
|
20
|
+
when Numeric
|
21
|
+
Money.new(obj*100,curr)
|
22
|
+
else
|
23
|
+
raise InvalidAmountError.new "Can't convert #{obj.class} to an Money instance"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# ALIAS for convert_to_money method
|
28
|
+
alias_method :conv_to_money, :convert_to_money
|
29
|
+
alias_method :conv_to_m, :convert_to_money
|
30
|
+
alias_method :to_money, :convert_to_money
|
31
|
+
|
32
|
+
#Converts an Object into an VATPercentage Object
|
33
|
+
#@return [VATPercentage]
|
34
|
+
#
|
35
|
+
#@example
|
36
|
+
# => Vatcalc::Util.to_vat_percentage
|
37
|
+
def convert_to_vat_percentage(vat_percentage)
|
38
|
+
case vat_percentage
|
39
|
+
when VATPercentage
|
40
|
+
vat_percentage
|
41
|
+
when nil
|
42
|
+
Vatcalc.vat_percentage
|
43
|
+
else
|
44
|
+
VATPercentage.new(vat_percentage)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# ALIAS for convert_to_vat_percentage method
|
49
|
+
alias_method :to_vat_percentage, :convert_to_vat_percentage
|
50
|
+
alias_method :to_vat_p, :convert_to_vat_percentage
|
51
|
+
|
52
|
+
#Returns a human friendly percentage value
|
53
|
+
#@param value = [Float,Integer,String]
|
54
|
+
# => human_percentage_value(0.19) => 19%
|
55
|
+
def human_percentage_value(value,precision=2)
|
56
|
+
full, fraction = ((value.to_f)*100).to_f.round(precision).divmod(1)
|
57
|
+
full.to_s + (fraction > 0.00 ? ("," + fraction.round(precision).to_s[2..-1]) : "") + "%"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
class InvalidAmountError < TypeError
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Vatcalc
|
2
|
+
class VATPercentage < Numeric
|
3
|
+
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
attr_reader :value
|
7
|
+
|
8
|
+
#Init a VATPercentage object by
|
9
|
+
# => Integer
|
10
|
+
# => VATPercentage.new 19
|
11
|
+
# => Float
|
12
|
+
# => VATPercentage.new 0.19
|
13
|
+
# => String
|
14
|
+
# => VATPercentage.new 19%
|
15
|
+
# => VATPercentage.new 19,1%
|
16
|
+
# => VATPercentage.new 19,1%
|
17
|
+
# => VATPercentage.new 19.1%
|
18
|
+
def initialize(obj)
|
19
|
+
@value = case obj
|
20
|
+
when VATPercentage
|
21
|
+
obj.value
|
22
|
+
when 0.00..0.99
|
23
|
+
as_d(obj.to_f + 1.00)
|
24
|
+
when 1..100.00
|
25
|
+
as_d((obj.to_f / 100 ) + 1.00)
|
26
|
+
else
|
27
|
+
if obj.is_a?(String) && obj.match(/[0-9]{0,2}\.|\,{0,1}[0-9]{0,2}/)
|
28
|
+
as_d((obj.gsub("," , ".").to_f / 100) + 1.00)
|
29
|
+
else
|
30
|
+
raise TypeError.new("Can't convert #{obj.class} #{obj} to an valid #{self.class}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
delegate :to_i,:to_f, to: :to_d
|
36
|
+
|
37
|
+
|
38
|
+
#For comparisaon between a value or a +VATPercentage+
|
39
|
+
#@return [Intger]
|
40
|
+
#@see module Comparable
|
41
|
+
def <=>(other)
|
42
|
+
to_d <=> as_d(other)
|
43
|
+
end
|
44
|
+
|
45
|
+
#Returns a gross value
|
46
|
+
#@return [Money]
|
47
|
+
#@example
|
48
|
+
# => 10.00 * VATPercentage.new(19) #=> #<Money fractional:1190 currency:EUR>
|
49
|
+
def *(other)
|
50
|
+
case other
|
51
|
+
when Money
|
52
|
+
other * @value
|
53
|
+
when Numeric
|
54
|
+
Util.convert_to_money(other) * @value
|
55
|
+
when VATPercentage
|
56
|
+
raise TypeError.new "Can't multiply a VATPercentage by another VATPercentage"
|
57
|
+
else
|
58
|
+
if other.respond_to?(:coerce)
|
59
|
+
a,b = other.coerce(self)
|
60
|
+
a * b
|
61
|
+
else
|
62
|
+
raise TypeError.new "Can't multiply #{other.class} by VATPercentage"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
#@see https://www.mutuallyhuman.com/blog/2011/01/25/class-coercion-in-ruby
|
68
|
+
#Basic usage of coerce. Now you can write:
|
69
|
+
# + 10.00 * VATPercentage.new(19) +
|
70
|
+
# and:
|
71
|
+
# + VATPercentage.new(19) * 10.00 +
|
72
|
+
def coerce(other)
|
73
|
+
[self,other]
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_d
|
77
|
+
@value
|
78
|
+
end
|
79
|
+
|
80
|
+
def inspect
|
81
|
+
"#<#{self.class.name} vat_percentage:#{to_s}>"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns a Integer hash value based on the +value+
|
85
|
+
# in order to use functions like & (intersection), group_by, etc.
|
86
|
+
#
|
87
|
+
# @return [Integer]
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# VATPercentage.new(19).hash #=> 908351
|
91
|
+
def hash
|
92
|
+
[@value,self.class].hash
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_s
|
96
|
+
Util.human_percentage_value(@value-1.00)
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
private
|
101
|
+
def as_d(num)
|
102
|
+
if num.respond_to?(:to_d)
|
103
|
+
num.is_a?(Rational) ? num.to_d(5) : num.to_d
|
104
|
+
else
|
105
|
+
BigDecimal.new(num.to_s.empty? ? 0 : num.to_s)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
data/vatcalc.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "vatcalc/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "vatcalc"
|
8
|
+
spec.version = Vatcalc::VERSION
|
9
|
+
spec.authors = ["Christopher Geduhn"]
|
10
|
+
spec.email = ["christopher.geduhn@googlemail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{ A gem to calculate VAT of multiple products with differently VAT percentages. }
|
13
|
+
spec.description = %q{ A gem to calculate VAT of multiple products with differently VAT percentages. }
|
14
|
+
spec.homepage = "https://github.com/cgeduhn/vatcalc"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
# if spec.respond_to?(:metadata)
|
20
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
# else
|
22
|
+
# raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
# "public gem pushes."
|
24
|
+
# end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
spec.add_dependency "money"
|
34
|
+
spec.add_dependency 'activesupport'
|
35
|
+
|
36
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
37
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
38
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vatcalc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christopher Geduhn
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: money
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.15'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.15'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
description: " A gem to calculate VAT of multiple products with differently VAT percentages. "
|
84
|
+
email:
|
85
|
+
- christopher.geduhn@googlemail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".travis.yml"
|
93
|
+
- CODE_OF_CONDUCT.md
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- bin/console
|
99
|
+
- bin/setup
|
100
|
+
- lib/vatcalc.rb
|
101
|
+
- lib/vatcalc/acts_as_bill_element.rb
|
102
|
+
- lib/vatcalc/base_element.rb
|
103
|
+
- lib/vatcalc/bill.rb
|
104
|
+
- lib/vatcalc/gnv.rb
|
105
|
+
- lib/vatcalc/service_element.rb
|
106
|
+
- lib/vatcalc/util.rb
|
107
|
+
- lib/vatcalc/vat_percentage.rb
|
108
|
+
- lib/vatcalc/version.rb
|
109
|
+
- vatcalc.gemspec
|
110
|
+
homepage: https://github.com/cgeduhn/vatcalc
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
metadata: {}
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 2.6.13
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: A gem to calculate VAT of multiple products with differently VAT percentages.
|
134
|
+
test_files: []
|