vat_calculator 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|