vat_calculator 1.2.2

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