steuer 1.0.3 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 932dd3009320b708e4141a8e90092717d30efc0792550dcaf2161ab16f4bcc7a
4
- data.tar.gz: '0219a890034b58541a5f797fa62ab6174186ff7be964c0946bacda4a122f3272'
3
+ metadata.gz: ecfb962a41af7819208aaf3938a9534f9c80b17ccb596e96dfaf0fb02a267d15
4
+ data.tar.gz: 7a6c99cd8e357c641d55dfc3be85e9ada4425b62b00e12e0890b5510c0f66e94
5
5
  SHA512:
6
- metadata.gz: 8d978823b3e9a45da0f859f2acdf1a0bcaebc0b67c94f9d41229f6be5dc1a73c150806facbcf9286661c180f46cc537c038c2c077c46ca989221b2def85a2ee9
7
- data.tar.gz: a560bef4902b511e2befb2b6ffe367ffae2758ecc7cea147a05fbde419561737dfa7ebc0d64032f3ca776ab6c367a044177f8045fb565b98bc1376519354c252
6
+ metadata.gz: 1ef79e5aa9d45bd9d379d39156c2c3291c005c078f66cded75fe22a896d03e5b00e95fd2826c871e272f81651cc7eddde287f8f7c170daea0df711795f1c6e6c
7
+ data.tar.gz: 309aabc613b97fe54337e86f0620b90356b1bd25859cdd2a5e6b352cd4b0509481b2cc2f1091719cbb3250588c04b264d44098a0b89d1fd66c15f48a469a8e23
@@ -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
@@ -8,7 +8,8 @@ module Steuer
8
8
 
9
9
  def initialize(tax_number, state: nil)
10
10
  @original_input = tax_number.to_s.strip
11
- @state_code = normalize_state_code(state) || auto_detect_state
11
+ @explicit_state_code = normalize_state_code(state)
12
+ @state_code = @explicit_state_code || auto_detect_state
12
13
 
13
14
  validate_tax_number!
14
15
  end
@@ -61,7 +62,7 @@ module Steuer
61
62
 
62
63
  case format_type
63
64
  when :standard
64
- @original_input
65
+ canonical_standard_input || @original_input
65
66
  when :federal_12
66
67
  convert_federal_12_to_standard
67
68
  when :federal_13
@@ -82,6 +83,8 @@ module Steuer
82
83
  :federal_12
83
84
  elsif normalized_input.length == 13 && normalized_input.match?(/^\d{13}$/)
84
85
  :federal_13
86
+ elsif unseparated_standard?
87
+ :standard
85
88
  end
86
89
 
87
90
  @format_type
@@ -178,7 +181,24 @@ module Steuer
178
181
 
179
182
  def validate_standard_format
180
183
  config = StateMapping::STATES[@state_code]
181
- normalized_input.match?(config[:standard_pattern])
184
+ return true if normalized_input.match?(config[:standard_pattern])
185
+
186
+ canonical = canonical_standard_input
187
+ !canonical.nil? && canonical.match?(config[:standard_pattern])
188
+ end
189
+
190
+ def unseparated_standard?
191
+ return false unless @explicit_state_code
192
+ return false unless normalized_input.match?(/\A\d+\z/)
193
+
194
+ sizes = StateMapping.standard_group_sizes(@explicit_state_code)
195
+ !sizes.nil? && normalized_input.length == sizes.sum
196
+ end
197
+
198
+ def canonical_standard_input
199
+ return unless @state_code
200
+
201
+ StateMapping.canonicalize_standard_format(normalized_input, @state_code)
182
202
  end
183
203
 
184
204
  def validate_federal_12_format
@@ -200,7 +220,8 @@ module Steuer
200
220
  def convert_standard_to_federal_12
201
221
  config = StateMapping::STATES[@state_code]
202
222
 
203
- match = normalized_input.match(config[:standard_pattern])
223
+ match = normalized_input.match(config[:standard_pattern]) ||
224
+ canonical_standard_input&.match(config[:standard_pattern])
204
225
  return unless match
205
226
 
206
227
  parts = match.captures
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Steuer
4
- VERSION = '1.0.3'
4
+ VERSION = '1.1.0'
5
5
  end
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.3
4
+ version: 1.1.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: 2025-12-23 00:00:00.000000000 Z
11
+ date: 2026-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry