br-utils 0.1.1 → 0.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 +446 -22
- data/lib/brazilian-utils/area-code-utils.rb +60 -0
- data/lib/brazilian-utils/bank-account-utils.rb +82 -0
- data/lib/brazilian-utils/bank-utils.rb +61 -0
- data/lib/brazilian-utils/boleto-utils.rb +139 -0
- data/lib/brazilian-utils/caepf-utils.rb +91 -0
- data/lib/brazilian-utils/cbo-utils.rb +74 -0
- data/lib/brazilian-utils/cei-utils.rb +79 -0
- data/lib/brazilian-utils/cep-utils.rb +25 -1
- data/lib/brazilian-utils/certidao-utils.rb +171 -0
- data/lib/brazilian-utils/cfop-utils.rb +74 -0
- data/lib/brazilian-utils/cnae-utils.rb +104 -0
- data/lib/brazilian-utils/cnh-utils.rb +53 -0
- data/lib/brazilian-utils/cno-utils.rb +76 -0
- data/lib/brazilian-utils/cnpj-utils.rb +125 -12
- data/lib/brazilian-utils/cns-utils.rb +110 -0
- data/lib/brazilian-utils/cpf-utils.rb +13 -0
- data/lib/brazilian-utils/credit-card-utils.rb +47 -0
- data/lib/brazilian-utils/csosn-utils.rb +55 -0
- data/lib/brazilian-utils/cst-utils.rb +103 -0
- data/lib/brazilian-utils/currency-utils.rb +347 -228
- data/lib/brazilian-utils/data/area_codes.json +676 -0
- data/lib/brazilian-utils/data/banks.json +357 -0
- data/lib/brazilian-utils/data/cbo.json +1 -0
- data/lib/brazilian-utils/data/cfop.json +1 -0
- data/lib/brazilian-utils/data/cnae.json +1 -0
- data/lib/brazilian-utils/data/csosn.json +42 -0
- data/lib/brazilian-utils/data/cst.json +294 -0
- data/lib/brazilian-utils/data/legal_nature.json +900 -0
- data/lib/brazilian-utils/data/municipalities.json +1 -0
- data/lib/brazilian-utils/data/ncm.json +1 -0
- data/lib/brazilian-utils/data/states.json +218 -0
- data/lib/brazilian-utils/date-utils.rb +504 -256
- data/lib/brazilian-utils/iban-utils.rb +120 -0
- data/lib/brazilian-utils/ie-utils.rb +84 -0
- data/lib/brazilian-utils/legal-nature-utils.rb +238 -235
- data/lib/brazilian-utils/legal-process-utils.rb +31 -3
- data/lib/brazilian-utils/license-plate-utils.rb +21 -5
- data/lib/brazilian-utils/municipality-utils.rb +58 -0
- data/lib/brazilian-utils/ncm-utils.rb +91 -0
- data/lib/brazilian-utils/nfe-key-utils.rb +158 -0
- data/lib/brazilian-utils/number-utils.rb +123 -0
- data/lib/brazilian-utils/passport-utils.rb +65 -0
- data/lib/brazilian-utils/phone-utils.rb +491 -280
- data/lib/brazilian-utils/pis-utils.rb +16 -1
- data/lib/brazilian-utils/pix-key-utils.rb +70 -0
- data/lib/brazilian-utils/pix-payload-utils.rb +253 -0
- data/lib/brazilian-utils/registro-profissional-utils.rb +112 -0
- data/lib/brazilian-utils/renavam-utils.rb +14 -0
- data/lib/brazilian-utils/state-utils.rb +105 -0
- data/lib/brazilian-utils/text-utils.rb +105 -0
- data/lib/brazilian-utils/vin-utils.rb +46 -0
- data/lib/brazilian-utils/voter-id-utils.rb +36 -0
- metadata +39 -1
|
@@ -1,228 +1,347 @@
|
|
|
1
|
-
require 'bigdecimal'
|
|
2
|
-
|
|
3
|
-
module BrazilianUtils
|
|
4
|
-
module CurrencyUtils
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
def self.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
1
|
+
require 'bigdecimal'
|
|
2
|
+
|
|
3
|
+
module BrazilianUtils
|
|
4
|
+
module CurrencyUtils
|
|
5
|
+
# Splits a BRL-ish amount string into its integer and decimal parts.
|
|
6
|
+
#
|
|
7
|
+
# The last `,` or `.` followed by 1 to `decimal_max` digits and then the
|
|
8
|
+
# end of the string is treated as the decimal separator; every other `,`
|
|
9
|
+
# or `.` found before it is treated as a thousands separator and removed.
|
|
10
|
+
#
|
|
11
|
+
# @param str [String] The (already symbol-stripped) amount string.
|
|
12
|
+
# @param decimal_max [Integer] Maximum length of a trailing decimal run.
|
|
13
|
+
# @return [Array(String, String, Boolean, Boolean)] integer part (digits
|
|
14
|
+
# only), decimal part (digits only, '' when none), whether a decimal
|
|
15
|
+
# separator was found, and whether the value is negative.
|
|
16
|
+
#
|
|
17
|
+
# @private
|
|
18
|
+
def self.split_amount(str, decimal_max)
|
|
19
|
+
cleaned = str.to_s.gsub(/[^\d.,\-]/, '')
|
|
20
|
+
negative = cleaned.start_with?('-')
|
|
21
|
+
cleaned = cleaned.sub(/\A-/, '')
|
|
22
|
+
|
|
23
|
+
return ['0', '', false, negative] if cleaned.empty?
|
|
24
|
+
|
|
25
|
+
last_sep_idx = cleaned.rindex(/[.,]/)
|
|
26
|
+
integer_part = cleaned
|
|
27
|
+
decimal_part = ''
|
|
28
|
+
has_decimal = false
|
|
29
|
+
|
|
30
|
+
if last_sep_idx
|
|
31
|
+
after = cleaned[(last_sep_idx + 1)..-1]
|
|
32
|
+
if after.length.between?(1, decimal_max) && after.match?(/\A\d+\z/)
|
|
33
|
+
has_decimal = true
|
|
34
|
+
decimal_part = after
|
|
35
|
+
integer_part = cleaned[0...last_sep_idx]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
integer_part = integer_part.gsub(/[.,]/, '')
|
|
40
|
+
integer_part = '0' if integer_part.empty?
|
|
41
|
+
|
|
42
|
+
[integer_part, decimal_part, has_decimal, negative]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private_class_method :split_amount
|
|
46
|
+
|
|
47
|
+
# Clamps a requested decimal precision to the 0..20 range, defaulting to 2.
|
|
48
|
+
#
|
|
49
|
+
# @private
|
|
50
|
+
def self.clamp_precision(precision)
|
|
51
|
+
value = precision.nil? ? 2 : precision.to_i
|
|
52
|
+
value.clamp(0, 20)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private_class_method :clamp_precision
|
|
56
|
+
|
|
57
|
+
# Parses a Brazilian currency string (e.g. `"R$ 1.234,56"`) into a Float.
|
|
58
|
+
#
|
|
59
|
+
# A value with no thousands/decimal separator at all is read as cents
|
|
60
|
+
# (divided by 10 to the power of the precision); an empty string is 0.
|
|
61
|
+
#
|
|
62
|
+
# @param value [String] The value to parse.
|
|
63
|
+
# @param options [Hash] `:precision` sets the number of decimal places
|
|
64
|
+
# assumed for a separator-less value (default 2).
|
|
65
|
+
# @return [Float] The parsed amount.
|
|
66
|
+
#
|
|
67
|
+
# @example
|
|
68
|
+
# parse_currency("R$ 1.234,56") #=> 1234.56
|
|
69
|
+
# parse_currency("1234") #=> 12.34
|
|
70
|
+
# parse_currency("") #=> 0
|
|
71
|
+
def self.parse_currency(value, options = {})
|
|
72
|
+
precision = clamp_precision(options[:precision] || options['precision'])
|
|
73
|
+
str = value.to_s
|
|
74
|
+
return 0 if str.strip.empty?
|
|
75
|
+
|
|
76
|
+
integer_part, decimal_part, has_decimal, negative = split_amount(str, [precision, 2].max)
|
|
77
|
+
|
|
78
|
+
amount = if has_decimal
|
|
79
|
+
"#{integer_part}.#{decimal_part}".to_f
|
|
80
|
+
else
|
|
81
|
+
integer_part.to_i / (10.0**precision)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
amount = -amount if negative
|
|
85
|
+
amount
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class << self
|
|
89
|
+
alias parse parse_currency
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Formats a numeric value (or a currency-like string) as Brazilian
|
|
93
|
+
# currency, e.g. `1234.56` becomes `"1.234,56"`.
|
|
94
|
+
#
|
|
95
|
+
# No currency symbol is added by default; pass `options[:symbol] = true`
|
|
96
|
+
# to prefix the result with `"R$ "`.
|
|
97
|
+
#
|
|
98
|
+
# @param value [Float, Integer, String, BigDecimal] The value to format.
|
|
99
|
+
# @param options [Hash] `:precision` (default 2, clamped to 0..20) sets
|
|
100
|
+
# the number of decimal places; `:symbol` (default false) prefixes
|
|
101
|
+
# `"R$ "`.
|
|
102
|
+
# @return [String, nil] Formatted currency string, or nil if invalid.
|
|
103
|
+
#
|
|
104
|
+
# @example
|
|
105
|
+
# format_currency(1234.56) #=> "1.234,56"
|
|
106
|
+
# format_currency(1234.56, symbol: true) #=> "R$ 1.234,56"
|
|
107
|
+
# format_currency("1.234,56") #=> "1.234,56"
|
|
108
|
+
# format_currency("invalid") #=> nil
|
|
109
|
+
def self.format_currency(value, options = {})
|
|
110
|
+
precision = clamp_precision(options[:precision] || options['precision'])
|
|
111
|
+
symbol = options[:symbol] || options['symbol']
|
|
112
|
+
|
|
113
|
+
amount =
|
|
114
|
+
case value
|
|
115
|
+
when Numeric
|
|
116
|
+
value.to_f
|
|
117
|
+
when String
|
|
118
|
+
return nil if value.strip.empty?
|
|
119
|
+
return nil unless value.match?(/\d/)
|
|
120
|
+
|
|
121
|
+
integer_part, decimal_part, has_decimal, negative = split_amount(value, [precision, 2].max)
|
|
122
|
+
n = has_decimal ? "#{integer_part}.#{decimal_part}".to_f : integer_part.to_f
|
|
123
|
+
negative ? -n : n
|
|
124
|
+
else
|
|
125
|
+
return nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
return nil unless amount.finite?
|
|
129
|
+
|
|
130
|
+
formatted = sprintf("%.#{precision}f", amount)
|
|
131
|
+
sign = ''
|
|
132
|
+
if formatted.start_with?('-')
|
|
133
|
+
sign = '-'
|
|
134
|
+
formatted = formatted[1..-1]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
integer_str, decimal_str = formatted.split('.')
|
|
138
|
+
integer_str = integer_str.chars.reverse.each_slice(3).map(&:join).join('.').reverse
|
|
139
|
+
|
|
140
|
+
result = "#{sign}#{integer_str}"
|
|
141
|
+
result += ",#{decimal_str}" if decimal_str
|
|
142
|
+
symbol ? "R$ #{result}" : result
|
|
143
|
+
rescue ArgumentError, TypeError, FloatDomainError
|
|
144
|
+
nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
class << self
|
|
148
|
+
alias format format_currency
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Converts a monetary value in Brazilian Reais to textual representation.
|
|
152
|
+
#
|
|
153
|
+
# @param amount [BigDecimal, Float, Integer, String] Monetary value to convert.
|
|
154
|
+
# @return [String, nil] Textual representation in Brazilian Portuguese
|
|
155
|
+
# (all lower case), or nil if invalid.
|
|
156
|
+
#
|
|
157
|
+
# @note
|
|
158
|
+
# - Values are truncated (not rounded) to 2 decimal places
|
|
159
|
+
# - Maximum supported value is 1 quadrillion reais
|
|
160
|
+
# - Negative values are prefixed with "menos"
|
|
161
|
+
#
|
|
162
|
+
# @example
|
|
163
|
+
# convert_real_to_text(1523.45)
|
|
164
|
+
# #=> "mil quinhentos e vinte e três reais e quarenta e cinco centavos"
|
|
165
|
+
#
|
|
166
|
+
# convert_real_to_text(1.00)
|
|
167
|
+
# #=> "um real"
|
|
168
|
+
#
|
|
169
|
+
# convert_real_to_text(0.50)
|
|
170
|
+
# #=> "cinquenta centavos"
|
|
171
|
+
#
|
|
172
|
+
# convert_real_to_text(0.00)
|
|
173
|
+
# #=> "zero reais"
|
|
174
|
+
def self.convert_real_to_text(amount)
|
|
175
|
+
# Convert to BigDecimal and round down to 2 decimal places
|
|
176
|
+
decimal_amount = BigDecimal(amount.to_s)
|
|
177
|
+
decimal_amount = decimal_amount.truncate(2)
|
|
178
|
+
|
|
179
|
+
# Check for invalid values
|
|
180
|
+
return nil if decimal_amount.nan? || decimal_amount.infinite?
|
|
181
|
+
return nil if decimal_amount.abs > BigDecimal('1000000000000000.00') # 1 quadrillion
|
|
182
|
+
|
|
183
|
+
negative = decimal_amount < 0
|
|
184
|
+
decimal_amount = decimal_amount.abs
|
|
185
|
+
|
|
186
|
+
reais = decimal_amount.to_i
|
|
187
|
+
centavos = ((decimal_amount - reais) * 100).to_i
|
|
188
|
+
|
|
189
|
+
parts = []
|
|
190
|
+
|
|
191
|
+
if reais > 0
|
|
192
|
+
reais_text = number_to_words(reais)
|
|
193
|
+
currency_text = reais == 1 ? 'real' : 'reais'
|
|
194
|
+
# "de" only applies when the text ends in "milhão(ões)"/"bilhão(ões)"/...
|
|
195
|
+
# on its own (e.g. "um milhão de reais"), not when it's followed by
|
|
196
|
+
# more words (e.g. "um milhão e um reais", no "de").
|
|
197
|
+
conector = reais_text.match?(/lhão$|lhões$/) ? 'de ' : ''
|
|
198
|
+
parts << "#{reais_text} #{conector}#{currency_text}"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if centavos > 0
|
|
202
|
+
centavos_text = "#{number_to_words(centavos)} #{centavos == 1 ? 'centavo' : 'centavos'}"
|
|
203
|
+
if reais > 0
|
|
204
|
+
parts << "e #{centavos_text}"
|
|
205
|
+
else
|
|
206
|
+
parts << centavos_text
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
if reais == 0 && centavos == 0
|
|
211
|
+
parts << 'zero reais'
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
result = parts.join(' ')
|
|
215
|
+
result = "menos #{result}" if negative
|
|
216
|
+
|
|
217
|
+
result
|
|
218
|
+
rescue ArgumentError, TypeError
|
|
219
|
+
nil
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Converts a number to its textual representation in Brazilian Portuguese.
|
|
223
|
+
#
|
|
224
|
+
# @param number [Integer] The number to convert (0 to 999,999,999,999,999,999)
|
|
225
|
+
# @return [String] The textual representation
|
|
226
|
+
#
|
|
227
|
+
# @private
|
|
228
|
+
def self.number_to_words(number)
|
|
229
|
+
number = number.to_i.abs
|
|
230
|
+
return 'zero' if number.zero?
|
|
231
|
+
|
|
232
|
+
# Scale names
|
|
233
|
+
scales = [
|
|
234
|
+
'',
|
|
235
|
+
'mil',
|
|
236
|
+
'milhão',
|
|
237
|
+
'bilhão',
|
|
238
|
+
'trilhão',
|
|
239
|
+
'quadrilhão'
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
scales_plural = [
|
|
243
|
+
'',
|
|
244
|
+
'mil',
|
|
245
|
+
'milhões',
|
|
246
|
+
'bilhões',
|
|
247
|
+
'trilhões',
|
|
248
|
+
'quadrilhões'
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
# Break number into groups of 3 digits, lowest order first
|
|
252
|
+
# (groups[0] is units-hundreds, groups[1] is thousands, ...)
|
|
253
|
+
groups = []
|
|
254
|
+
temp = number
|
|
255
|
+
while temp > 0
|
|
256
|
+
groups << temp % 1000
|
|
257
|
+
temp /= 1000
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
parts = []
|
|
261
|
+
groups.each_with_index do |group, index|
|
|
262
|
+
next if group.zero?
|
|
263
|
+
|
|
264
|
+
group_text = convert_group(group)
|
|
265
|
+
scale_name = group == 1 ? scales[index] : scales_plural[index]
|
|
266
|
+
|
|
267
|
+
parts << if scale_name.empty?
|
|
268
|
+
group_text
|
|
269
|
+
elsif index == 1 && group == 1 # "mil" doesn't need "um" before it
|
|
270
|
+
scale_name
|
|
271
|
+
else
|
|
272
|
+
"#{group_text} #{scale_name}"
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# parts was built lowest-order group first; the spoken form reads
|
|
277
|
+
# highest order first (e.g. "mil duzentos e trinta e quatro", not
|
|
278
|
+
# "duzentos e trinta e quatro, mil").
|
|
279
|
+
parts.reverse!
|
|
280
|
+
return parts.first if parts.length == 1
|
|
281
|
+
|
|
282
|
+
# The lowest-order non-zero group (spoken last) gets an "e" in front
|
|
283
|
+
# when it reads as a single small/round term: below 100, or an exact
|
|
284
|
+
# multiple of 100 (e.g. "mil e quatrocentos", "mil e um"); otherwise
|
|
285
|
+
# groups are simply concatenated with a space, no comma
|
|
286
|
+
# (Manual de Redação da Presidência: "mil duzentos e cinquenta reais").
|
|
287
|
+
final_group = groups.find { |g| !g.zero? }
|
|
288
|
+
last = parts.pop
|
|
289
|
+
separator = (final_group < 100 || (final_group % 100).zero?) ? ' e ' : ' '
|
|
290
|
+
"#{parts.join(' ')}#{separator}#{last}"
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Converts a group of 3 digits (0-999) to words.
|
|
294
|
+
#
|
|
295
|
+
# @param number [Integer] Number between 0 and 999
|
|
296
|
+
# @return [String] The textual representation
|
|
297
|
+
#
|
|
298
|
+
# @private
|
|
299
|
+
def self.convert_group(number)
|
|
300
|
+
ones = %w[zero um dois três quatro cinco seis sete oito nove]
|
|
301
|
+
tens = %w[dez onze doze treze quatorze quinze dezesseis dezessete dezoito dezenove]
|
|
302
|
+
tens_multiples = %w[_ _ vinte trinta quarenta cinquenta sessenta setenta oitenta noventa]
|
|
303
|
+
hundreds = %w[
|
|
304
|
+
_
|
|
305
|
+
cento
|
|
306
|
+
duzentos
|
|
307
|
+
trezentos
|
|
308
|
+
quatrocentos
|
|
309
|
+
quinhentos
|
|
310
|
+
seiscentos
|
|
311
|
+
setecentos
|
|
312
|
+
oitocentos
|
|
313
|
+
novecentos
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
return ones[number] if number < 10
|
|
317
|
+
|
|
318
|
+
if number < 20
|
|
319
|
+
return tens[number - 10]
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
if number < 100
|
|
323
|
+
tens_digit = number / 10
|
|
324
|
+
ones_digit = number % 10
|
|
325
|
+
if ones_digit.zero?
|
|
326
|
+
return tens_multiples[tens_digit]
|
|
327
|
+
else
|
|
328
|
+
return "#{tens_multiples[tens_digit]} e #{ones[ones_digit]}"
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# 100-999
|
|
333
|
+
hundreds_digit = number / 100
|
|
334
|
+
remainder = number % 100
|
|
335
|
+
|
|
336
|
+
if number == 100
|
|
337
|
+
'cem'
|
|
338
|
+
elsif remainder.zero?
|
|
339
|
+
hundreds[hundreds_digit]
|
|
340
|
+
else
|
|
341
|
+
"#{hundreds[hundreds_digit]} e #{convert_group(remainder)}"
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
private_class_method :number_to_words, :convert_group
|
|
346
|
+
end
|
|
347
|
+
end
|