steuer 1.0.4 → 1.2.0
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/README.md +50 -0
- data/lib/steuer/data/finanzaemter.yml +1711 -0
- data/lib/steuer/finanzamt_registry.rb +50 -0
- data/lib/steuer/state_mapping.rb +60 -0
- data/lib/steuer/steuernummer.rb +39 -4
- data/lib/steuer/version.rb +1 -1
- data/lib/steuer.rb +1 -0
- metadata +4 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Steuer
|
|
6
|
+
# Lookup of Bundesfinanzamtsnummer (BUFA-Nr, the four-digit prefix of the
|
|
7
|
+
# federal-13 form) to the office's name, backed by the BZSt BUFA-Tabelle
|
|
8
|
+
# bundled in data/finanzaemter.yml.
|
|
9
|
+
#
|
|
10
|
+
# Unlike the structural data in StateMapping, these names go stale: offices
|
|
11
|
+
# merge and get renamed several times a year. Lookups therefore return nil
|
|
12
|
+
# rather than raising, so a number issued after the bundled table's revision
|
|
13
|
+
# still converts -- it just has no name yet.
|
|
14
|
+
module FinanzamtRegistry
|
|
15
|
+
DATA_PATH = File.expand_path('data/finanzaemter.yml', __dir__)
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def name_for(code)
|
|
19
|
+
entries[code.to_s]&.fetch(:name, nil)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def state_for(code)
|
|
23
|
+
entries[code.to_s]&.fetch(:state, nil)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def known?(code)
|
|
27
|
+
entries.key?(code.to_s)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def revision
|
|
31
|
+
data[:revision]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def entries
|
|
35
|
+
@entries ||= data[:finanzaemter].to_h do |code, attrs|
|
|
36
|
+
[code.to_s, { name: attrs['name'], state: attrs['state'] }.freeze]
|
|
37
|
+
end.freeze
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def data
|
|
43
|
+
@data ||= begin
|
|
44
|
+
loaded = YAML.safe_load_file(DATA_PATH)
|
|
45
|
+
{ revision: loaded['revision'], finanzaemter: loaded['finanzaemter'] || {} }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/steuer/state_mapping.rb
CHANGED
|
@@ -119,6 +119,66 @@ module Steuer
|
|
|
119
119
|
},
|
|
120
120
|
}.freeze
|
|
121
121
|
class << self
|
|
122
|
+
def standard_group_sizes(state_code)
|
|
123
|
+
config = STATES[state_code]
|
|
124
|
+
return unless config
|
|
125
|
+
|
|
126
|
+
sizes = config[:standard_pattern].source.delete_prefix('^').delete_suffix('$')
|
|
127
|
+
.split('/')
|
|
128
|
+
.map { |group| standard_group_size(group) }
|
|
129
|
+
|
|
130
|
+
return if sizes.any?(&:nil?) || sizes.empty?
|
|
131
|
+
|
|
132
|
+
sizes
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Ranges such as Hessen's 0(1[3-9]) and Saarland's (01[0-2]) stand in
|
|
136
|
+
# for single digits, so groups are measured rather than read off \d{n}.
|
|
137
|
+
def standard_group_size(group)
|
|
138
|
+
size = 0
|
|
139
|
+
scanner = group.dup
|
|
140
|
+
until scanner.empty?
|
|
141
|
+
case scanner
|
|
142
|
+
when /\A\\d\{(\d+)\}/
|
|
143
|
+
size += Regexp.last_match(1).to_i
|
|
144
|
+
scanner = scanner.sub(/\A\\d\{\d+\}/, '')
|
|
145
|
+
when /\A\[[^\]]*\]/
|
|
146
|
+
size += 1
|
|
147
|
+
scanner = scanner.sub(/\A\[[^\]]*\]/, '')
|
|
148
|
+
when /\A\\d/
|
|
149
|
+
size += 1
|
|
150
|
+
scanner = scanner.delete_prefix('\\d')
|
|
151
|
+
when /\A\d/
|
|
152
|
+
size += 1
|
|
153
|
+
scanner = scanner[1..]
|
|
154
|
+
when /\A[()]/
|
|
155
|
+
scanner = scanner[1..]
|
|
156
|
+
else
|
|
157
|
+
return
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
size
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Separators are presentation only: Baden-Württemberg prints 93815/08152
|
|
164
|
+
# for the ten digits the BZSt Standardschema groups as 93/815/08152.
|
|
165
|
+
def canonicalize_standard_format(tax_number, state_code)
|
|
166
|
+
sizes = standard_group_sizes(state_code)
|
|
167
|
+
return unless sizes
|
|
168
|
+
|
|
169
|
+
digits = tax_number.gsub(/\D/, '')
|
|
170
|
+
return unless digits.length == sizes.sum
|
|
171
|
+
|
|
172
|
+
offset = 0
|
|
173
|
+
groups = sizes.map do |size|
|
|
174
|
+
part = digits[offset, size]
|
|
175
|
+
offset += size
|
|
176
|
+
part
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
groups.join('/')
|
|
180
|
+
end
|
|
181
|
+
|
|
122
182
|
def find_state_by_standard_format(tax_number)
|
|
123
183
|
# Try more specific patterns first to avoid conflicts with general patterns
|
|
124
184
|
# Priority: character classes with ranges > specific digit patterns > general digit patterns
|
data/lib/steuer/steuernummer.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'state_mapping'
|
|
4
|
+
require_relative 'finanzamt_registry'
|
|
4
5
|
|
|
5
6
|
module Steuer
|
|
6
7
|
class Steuernummer
|
|
@@ -8,7 +9,8 @@ module Steuer
|
|
|
8
9
|
|
|
9
10
|
def initialize(tax_number, state: nil)
|
|
10
11
|
@original_input = tax_number.to_s.strip
|
|
11
|
-
@
|
|
12
|
+
@explicit_state_code = normalize_state_code(state)
|
|
13
|
+
@state_code = @explicit_state_code || auto_detect_state
|
|
12
14
|
|
|
13
15
|
validate_tax_number!
|
|
14
16
|
end
|
|
@@ -61,7 +63,7 @@ module Steuer
|
|
|
61
63
|
|
|
62
64
|
case format_type
|
|
63
65
|
when :standard
|
|
64
|
-
@original_input
|
|
66
|
+
canonical_standard_input || @original_input
|
|
65
67
|
when :federal_12
|
|
66
68
|
convert_federal_12_to_standard
|
|
67
69
|
when :federal_13
|
|
@@ -75,6 +77,19 @@ module Steuer
|
|
|
75
77
|
StateMapping::STATES[@state_code][:name]
|
|
76
78
|
end
|
|
77
79
|
|
|
80
|
+
# The BUFA-Nr: the leading four digits of the federal-13 form, identifying
|
|
81
|
+
# the issuing Finanzamt (the Empfaenger in an ELSTER transmission).
|
|
82
|
+
def finanzamt_code
|
|
83
|
+
to_federal_13&.slice(0, 4)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def finanzamt_name
|
|
87
|
+
code = finanzamt_code
|
|
88
|
+
return unless code
|
|
89
|
+
|
|
90
|
+
FinanzamtRegistry.name_for(code)
|
|
91
|
+
end
|
|
92
|
+
|
|
78
93
|
def format_type
|
|
79
94
|
@format_type ||= if normalized_input.include?('/')
|
|
80
95
|
:standard
|
|
@@ -82,6 +97,8 @@ module Steuer
|
|
|
82
97
|
:federal_12
|
|
83
98
|
elsif normalized_input.length == 13 && normalized_input.match?(/^\d{13}$/)
|
|
84
99
|
:federal_13
|
|
100
|
+
elsif unseparated_standard?
|
|
101
|
+
:standard
|
|
85
102
|
end
|
|
86
103
|
|
|
87
104
|
@format_type
|
|
@@ -178,7 +195,24 @@ module Steuer
|
|
|
178
195
|
|
|
179
196
|
def validate_standard_format
|
|
180
197
|
config = StateMapping::STATES[@state_code]
|
|
181
|
-
normalized_input.match?(config[:standard_pattern])
|
|
198
|
+
return true if normalized_input.match?(config[:standard_pattern])
|
|
199
|
+
|
|
200
|
+
canonical = canonical_standard_input
|
|
201
|
+
!canonical.nil? && canonical.match?(config[:standard_pattern])
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def unseparated_standard?
|
|
205
|
+
return false unless @explicit_state_code
|
|
206
|
+
return false unless normalized_input.match?(/\A\d+\z/)
|
|
207
|
+
|
|
208
|
+
sizes = StateMapping.standard_group_sizes(@explicit_state_code)
|
|
209
|
+
!sizes.nil? && normalized_input.length == sizes.sum
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def canonical_standard_input
|
|
213
|
+
return unless @state_code
|
|
214
|
+
|
|
215
|
+
StateMapping.canonicalize_standard_format(normalized_input, @state_code)
|
|
182
216
|
end
|
|
183
217
|
|
|
184
218
|
def validate_federal_12_format
|
|
@@ -200,7 +234,8 @@ module Steuer
|
|
|
200
234
|
def convert_standard_to_federal_12
|
|
201
235
|
config = StateMapping::STATES[@state_code]
|
|
202
236
|
|
|
203
|
-
match = normalized_input.match(config[:standard_pattern])
|
|
237
|
+
match = normalized_input.match(config[:standard_pattern]) ||
|
|
238
|
+
canonical_standard_input&.match(config[:standard_pattern])
|
|
204
239
|
return unless match
|
|
205
240
|
|
|
206
241
|
parts = match.captures
|
data/lib/steuer/version.rb
CHANGED
data/lib/steuer.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: steuer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Olumuyiwa Osiname
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-09-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: pry
|
|
@@ -133,7 +133,9 @@ files:
|
|
|
133
133
|
- LICENSE
|
|
134
134
|
- README.md
|
|
135
135
|
- lib/steuer.rb
|
|
136
|
+
- lib/steuer/data/finanzaemter.yml
|
|
136
137
|
- lib/steuer/errors.rb
|
|
138
|
+
- lib/steuer/finanzamt_registry.rb
|
|
137
139
|
- lib/steuer/state_mapping.rb
|
|
138
140
|
- lib/steuer/steuernummer.rb
|
|
139
141
|
- lib/steuer/version.rb
|