vat_calculator 1.2.2
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.
- data/README.md +66 -0
- data/Rakefile +16 -0
- data/lib/vat_calculator.rb +131 -0
- data/lib/vat_calculator/railtie.rb +16 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/vat_calculator_spec.rb +68 -0
- metadata +101 -0
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Description
|
2
|
+
|
3
|
+
Use this plugin to calculate the VAT rate depending on the country of the buyer.
|
4
|
+
|
5
|
+
# Configuration
|
6
|
+
|
7
|
+
First set the country of the seller in your config/application.rb file
|
8
|
+
|
9
|
+
config.vat_calculator_base_country = 'FR' # or 'BE', etc....
|
10
|
+
|
11
|
+
Note: by default, the value of the vat_calculator_base_country option is set to 'FR'
|
12
|
+
|
13
|
+
# Basic usage
|
14
|
+
|
15
|
+
Let's review the different cases for a seller in France:
|
16
|
+
|
17
|
+
1. the buyer is outside Europe
|
18
|
+
|
19
|
+
VatCalculator.get('US') # returns 0.0
|
20
|
+
|
21
|
+
2. the buyer is in France
|
22
|
+
|
23
|
+
VatCalculator.get('FR') # returns 19.6
|
24
|
+
|
25
|
+
3. the buyer is in Europe without a vat number
|
26
|
+
|
27
|
+
VatCalculator.get('BE') # returns 19.6
|
28
|
+
|
29
|
+
4. the buyer is in Europe with an almost correct vat number
|
30
|
+
|
31
|
+
VatCalculator.get('BE', { vat_number: 'a_valid_vat_number' }) # returns 0.0
|
32
|
+
|
33
|
+
5. the buyer is in Europe with an almost correct vat number
|
34
|
+
|
35
|
+
VatCalculator.get('BE', { vat_number: 'bla bla', validation: :simple }) # returns 19.6
|
36
|
+
|
37
|
+
6. the buyer is in Europe with an almost correct vat number
|
38
|
+
|
39
|
+
VatCalculator.get('BE', { vat_number: 'BE00000000000', validation: :full }) # returns 0.0
|
40
|
+
|
41
|
+
7. the buyer is in Martinique, Guadeloupe or la Réunion
|
42
|
+
|
43
|
+
VatCalculator.get('MQ') # returns 8.5
|
44
|
+
|
45
|
+
8. the buyer is in French Guyana
|
46
|
+
|
47
|
+
VatCalculator.get('GF') # returns 0.0
|
48
|
+
|
49
|
+
# Installation
|
50
|
+
|
51
|
+
In your project's Gemfile :
|
52
|
+
|
53
|
+
gem 'vat_calculator', git: 'git://github.com/did/vat_calculator.git'
|
54
|
+
|
55
|
+
# Tests
|
56
|
+
|
57
|
+
If you want to run the specs :
|
58
|
+
|
59
|
+
bundle exec rake spec
|
60
|
+
|
61
|
+
# Credits
|
62
|
+
|
63
|
+
This plugin in released under MIT license by Didier Lafforgue (see MIT-LICENSE
|
64
|
+
file).
|
65
|
+
|
66
|
+
(c) http://www.nocoffee.fr
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rdoc/task'
|
3
|
+
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
|
8
|
+
desc 'Run specs'
|
9
|
+
task :spec do
|
10
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
11
|
+
t.rspec_opts = %w{--colour --format progress}
|
12
|
+
t.pattern = 'spec/*_spec.rb'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => [ :spec ]
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'vat_validator'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
6
|
+
|
7
|
+
module VatCalculator
|
8
|
+
|
9
|
+
DEFAULT_BASE_COUNTRY = 'FR'
|
10
|
+
|
11
|
+
mattr_accessor :base_country
|
12
|
+
|
13
|
+
mattr_accessor :current_rate_rule
|
14
|
+
|
15
|
+
# EUROPEAN_COUNTRIES = %w(DE AT BE BG CY DK ES EE FI FR EL HU IE IT LV LT LU MT NL PL GB RO SK SI SE CZ) # @deprecated
|
16
|
+
|
17
|
+
VAT_RATES = {
|
18
|
+
'FR' => {
|
19
|
+
:rate => 19.6
|
20
|
+
# FIXME (Did): no exceptions an accouting person told me (to be double checked)
|
21
|
+
# :exceptions => {
|
22
|
+
# 'GF' => 0.0, # French Guyana
|
23
|
+
# 'GP' => 8.5, # Guadeloupe
|
24
|
+
# 'MQ' => 8.5, # Martinique
|
25
|
+
# 'RE' => 8.5 # Réunion
|
26
|
+
# }
|
27
|
+
},
|
28
|
+
|
29
|
+
'GP' => 8.5,
|
30
|
+
'MQ' => 8.5,
|
31
|
+
'RE' => 8.5,
|
32
|
+
|
33
|
+
'BE' => 21
|
34
|
+
}
|
35
|
+
|
36
|
+
def self.formalize_rate_rule(data)
|
37
|
+
unless data.is_a?(Hash)
|
38
|
+
data = { :rate => data.to_f }
|
39
|
+
end
|
40
|
+
|
41
|
+
OpenStruct.new({
|
42
|
+
:exceptions => {},
|
43
|
+
:rate => 0.0,
|
44
|
+
:no_rate => 0.0
|
45
|
+
}.merge(data))
|
46
|
+
end
|
47
|
+
|
48
|
+
class NoCountryException < Exception; end
|
49
|
+
|
50
|
+
class NoRuleFoundException < Exception; end
|
51
|
+
|
52
|
+
# Method arguments:
|
53
|
+
# country_code the country of the buyer [required]
|
54
|
+
# options see below
|
55
|
+
|
56
|
+
# Possible options are:
|
57
|
+
# :base_country_code the country of the seller, if not given, then take the one by default [optional]
|
58
|
+
# :vat_number in case the buyer left his vat number. Validation should have been done before [optional]
|
59
|
+
# :validation :none, :simple (just check the format of the vat number), :full (format + existence of the vat number). By default, :none
|
60
|
+
#
|
61
|
+
def self.get(country_code, options = {})
|
62
|
+
raise NoCountryException if country_code.nil?
|
63
|
+
|
64
|
+
country_code.upcase!
|
65
|
+
|
66
|
+
base_country_code = self.base_country
|
67
|
+
|
68
|
+
if options[:base_country_code] && options[:base_country_code] != self.base_country # different country code for the seller ?
|
69
|
+
if rule_data = VAT_RATES[options[:base_country_code]]
|
70
|
+
base_country_code = options[:base_country_code]
|
71
|
+
rule = self.formalize_rate_rule(rule_data)
|
72
|
+
end
|
73
|
+
else
|
74
|
+
rule = self.current_rate_rule
|
75
|
+
end
|
76
|
+
|
77
|
+
raise NoRuleFoundException if rule.nil?
|
78
|
+
|
79
|
+
# same country for both the buyer and the seller, no need to go further
|
80
|
+
return rule.rate if base_country_code == country_code
|
81
|
+
|
82
|
+
if self.is_an_european_country?(country_code)
|
83
|
+
# case when the country of the buyer is in Europe, check if he has a vat_number
|
84
|
+
if options[:vat_number].present? && self.validate_vat_number?(options[:vat_number], options[:validation])
|
85
|
+
rule.no_rate
|
86
|
+
else
|
87
|
+
rule.rate
|
88
|
+
end
|
89
|
+
elsif rate = rule.exceptions[country_code] # exceptions (France for instance has many exceptions for the DOM-TOM territories)
|
90
|
+
rate
|
91
|
+
else
|
92
|
+
rule.no_rate
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.is_an_european_country?(country_code)
|
97
|
+
VatValidator::VAT_PATTERNS.keys.include?(country_code.upcase)
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.validate_vat_number?(number, level = :none)
|
101
|
+
case level
|
102
|
+
when :simple then self.vat_format_valid?(number)
|
103
|
+
when :full then self.vat_format_valid?(number) && self.vat_number_existence?(number)
|
104
|
+
else
|
105
|
+
true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.vat_format_valid?(number)
|
110
|
+
number =~ VatValidator::VAT_PATTERNS.values.detect { |p| number.to_s =~ p }
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.vat_number_existence?(number)
|
114
|
+
VatValidator::ViesChecker.check(number)
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
if defined?(Rails)
|
120
|
+
if Rails::VERSION::MAJOR >= 3
|
121
|
+
require 'vat_calculator/railtie'
|
122
|
+
else
|
123
|
+
puts "[Warning] VatCalculator does not work with Rails < 3"
|
124
|
+
end
|
125
|
+
else
|
126
|
+
VatCalculator.base_country ||= VatCalculator::DEFAULT_BASE_COUNTRY
|
127
|
+
|
128
|
+
if rule_data = VatCalculator::VAT_RATES[VatCalculator.base_country]
|
129
|
+
VatCalculator.current_rate_rule = VatCalculator.formalize_rate_rule(rule_data)
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module VatCalculator
|
2
|
+
|
3
|
+
class Railtie < Rails::Railtie
|
4
|
+
|
5
|
+
config.vat_calculator_base_country = DEFAULT_BASE_COUNTRY
|
6
|
+
|
7
|
+
initializer 'vat_calculator.initialize' do |app|
|
8
|
+
VatCalculator.base_country = app.config.vat_calculator_base_country.upcase
|
9
|
+
|
10
|
+
if rule = VatCalculator::VAT_RATES[VatCalculator.base_country]
|
11
|
+
VatCalculator.current_rate_rule = VatCalculator.formalize_rate_rule(rule)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler'
|
6
|
+
|
7
|
+
Bundler.setup
|
8
|
+
Bundler.require(:test)
|
9
|
+
|
10
|
+
require 'mocha'
|
11
|
+
require 'rspec'
|
12
|
+
require 'vat_calculator'
|
13
|
+
|
14
|
+
RSpec.configure do |config|
|
15
|
+
config.mock_with :mocha
|
16
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Processing tax rate' do
|
4
|
+
|
5
|
+
it 'should raise an exception of no country is provided' do
|
6
|
+
lambda {
|
7
|
+
VatCalculator.get(nil)
|
8
|
+
}.should raise_exception(VatCalculator::NoCountryException)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should raise an exception of the country of the seller is not recorded' do
|
12
|
+
lambda {
|
13
|
+
VatCalculator.get('FR', { base_country_code: 'DE' })
|
14
|
+
}.should raise_exception(VatCalculator::NoRuleFoundException)
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'Seller in France' do
|
18
|
+
|
19
|
+
it 'should apply tax since the customer is in France' do
|
20
|
+
VatCalculator.get('FR').should == 19.6
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should apply tax if the buyer is in Europe BUT didn\'t give his vta number in CEE' do
|
24
|
+
VatCalculator.get('IT').should == 19.6
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should not apply tax if the buyer is in Europe AND gave his vta number in CEE' do
|
28
|
+
VatCalculator.get('IT', { vat_number: 'valid_number' }).should == 0.0
|
29
|
+
end
|
30
|
+
|
31
|
+
# Note (Did): see the vat_calculator.rb file for more explanations
|
32
|
+
# %w(GP MQ RE).each do |code|
|
33
|
+
# it "should apply a different tax rate if the buyer is in the country with the '#{code}' code" do
|
34
|
+
# VatCalculator.get(code).should == 8.5
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
|
38
|
+
it 'should not apply tax if the buyer lives in French Guyana' do
|
39
|
+
VatCalculator.get('GF').should == 0.0
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should not apply tax if the buyer lives is in the United States' do
|
43
|
+
VatCalculator.get('US').should == 0.0
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'Validating the VAT number' do
|
49
|
+
|
50
|
+
it 'refuses a wrong VAT number' do
|
51
|
+
VatCalculator.get('IT', { vat_number: 'invalid_number', validation: :simple }).should == 19.6
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'accepts a wrong VAT number which looks like almost valid' do
|
55
|
+
VatCalculator.get('IT', { vat_number: 'IT12345678923', validation: :simple }).should == 0.0
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'refuses a VAT number by calling an external service' do
|
59
|
+
VatCalculator.get('IT', { vat_number: 'IT12345678923', validation: :full }).should == 19.6
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'accepts a VAT number by calling an external service' do
|
63
|
+
VatCalculator.get('SE', { vat_number: 'SE556866920301', validation: :full }).should == 0.0
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vat_calculator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.2.2
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Didier Lafforgue
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-05-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
type: :runtime
|
16
|
+
version_requirements: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.2.0
|
22
|
+
name: savon
|
23
|
+
prerelease: false
|
24
|
+
requirement: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
name: activesupport
|
39
|
+
prerelease: false
|
40
|
+
requirement: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
name: vat_validator
|
55
|
+
prerelease: false
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Helper to calculate the VAT rate
|
63
|
+
email: didier@nocoffee.fr
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- lib/vat_calculator/railtie.rb
|
69
|
+
- lib/vat_calculator.rb
|
70
|
+
- spec/spec_helper.rb
|
71
|
+
- spec/vat_calculator_spec.rb
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
homepage: http://github.com/did/vat_calculator
|
75
|
+
licenses: []
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
hash: 2311850019608209240
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 1.3.6
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 1.8.23
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: Helper to calculate the VAT rate
|
101
|
+
test_files: []
|