extensobr 1.0.0 → 1.2.1

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.
data/lib/extensobr.rb CHANGED
@@ -1,447 +1,479 @@
1
- require "extensobr/version"
2
- class Extenso
3
-
4
- BRL = {:delimiter => ".", :separator => ",", :unit => "R$", :precision => 2, :position => "before"}
5
-
6
- NUM_SING = 0
7
- NUM_PLURAL = 1
8
- POS_GENERO = 2
9
- GENERO_MASC = 0
10
- GENERO_FEM = 1
11
-
12
- VALOR_MAXIMO = 999999999
13
-
14
- # As unidades 1 e 2 variam em gênero, pelo que precisamos de dois conjuntos de strings (masculinas e femininas) para as unidades
15
- UNIDADES = {
16
- GENERO_MASC => {
17
- 1 => 'Um',
18
- 2 => 'Dois',
19
- 3 => 'Três',
20
- 4 => 'Quatro',
21
- 5 => 'Cinco',
22
- 6 => 'Seis',
23
- 7 => 'Sete',
24
- 8 => 'Oito',
25
- 9 => 'Nove'
26
- },
27
- GENERO_FEM => {
28
- 1 => 'Uma',
29
- 2 => 'Duas',
30
- 3 => 'Três',
31
- 4 => 'Quatro',
32
- 5 => 'Cinco',
33
- 6 => 'Seis',
34
- 7 => 'Sete',
35
- 8 => 'Oito',
36
- 9 => 'Nove'
37
- }
38
- }
39
-
40
- DE11A19 = {
41
- 11 => 'Onze',
42
- 12 => 'Doze',
43
- 13 => 'Treze',
44
- 14 => 'Quatorze',
45
- 15 => 'Quinze',
46
- 16 => 'Dezesseis',
47
- 17 => 'Dezessete',
48
- 18 => 'Dezoito',
49
- 19 => 'Dezenove'
50
- }
51
-
52
- DEZENAS = {
53
- 10 => 'Dez',
54
- 20 => 'Vinte',
55
- 30 => 'Trinta',
56
- 40 => 'Quarenta',
57
- 50 => 'Cinquenta',
58
- 60 => 'Sessenta',
59
- 70 => 'Setenta',
60
- 80 => 'Oitenta',
61
- 90 => 'Noventa'
62
- }
63
-
64
- CENTENA_EXATA = 'Cem'
65
-
66
- # As centenas, com exceção de 'cento', também variam em gênero. Aqui também se faz
67
- # necessário dois conjuntos de strings (masculinas e femininas).
68
-
69
- CENTENAS = {
70
- GENERO_MASC => {
71
- 100 => 'Cento',
72
- 200 => 'Duzentos',
73
- 300 => 'Trezentos',
74
- 400 => 'Quatrocentos',
75
- 500 => 'Quinhentos',
76
- 600 => 'Seiscentos',
77
- 700 => 'Setecentos',
78
- 800 => 'Oitocentos',
79
- 900 => 'Novecentos'
80
- },
81
- GENERO_FEM => {
82
- 100 => 'Cento',
83
- 200 => 'Duzentas',
84
- 300 => 'Trezentas',
85
- 400 => 'Quatrocentas',
86
- 500 => 'Quinhentas',
87
- 600 => 'Seiscentas',
88
- 700 => 'Setecentas',
89
- 800 => 'Oitocentas',
90
- 900 => 'Novecentas'
91
- }
92
- }
93
-
94
- #'Mil' é invariável, seja em gênero, seja em número
95
- MILHAR = 'mil'
96
-
97
- MILHOES = {
98
- NUM_SING => 'milhão',
99
- NUM_PLURAL => 'milhões'
100
- }
101
-
102
- UNIDADES_ORDINAL = {
103
- GENERO_MASC => {
104
- 1 => 'Primeiro',
105
- 2 => 'Segundo',
106
- 3 => 'Terceiro',
107
- 4 => 'Quarto',
108
- 5 => 'Quinto',
109
- 6 => 'Sexto',
110
- 7 => 'Sétimo',
111
- 8 => 'Oitavo',
112
- 9 => 'Nono'},
113
- GENERO_FEM => {
114
- 1 => 'Primeira',
115
- 2 => 'Segunda',
116
- 3 => 'Terceira',
117
- 4 => 'Quarta',
118
- 5 => 'Quinta',
119
- 6 => 'Sexta',
120
- 7 => 'Sétima',
121
- 8 => 'Oitava',
122
- 9 => 'Nona'}}
123
-
124
- DEZENAS_ORDINAL = {
125
- GENERO_MASC => {
126
- 10 => 'Décimo',
127
- 20 => 'Vigésimo',
128
- 30 => 'Trigésimo',
129
- 40 => 'Quadragésimo',
130
- 50 => 'Quinquagésimo',
131
- 60 => 'Sexagésimo',
132
- 70 => 'Septuagésimo',
133
- 80 => 'Octogésimo',
134
- 90 => 'Nonagésimo'},
135
- GENERO_FEM => {
136
- 10 => 'Décima',
137
- 20 => 'Vigésima',
138
- 30 => 'Trigésima',
139
- 40 => 'Quadragésima',
140
- 50 => 'Quinquagésima',
141
- 60 => 'Sexagésima',
142
- 70 => 'Septuagésima',
143
- 80 => 'Octogésima',
144
- 90 => 'Nonagésima'}}
145
-
146
- CENTENAS_ORDINAL = {
147
- GENERO_MASC => {
148
- 100 => 'Centésimo',
149
- 200 => 'Ducentésimo',
150
- 300 => 'Trecentésimo',
151
- 400 => 'Quadringentésimo',
152
- 500 => 'Quingentésimo',
153
- 600 => 'Seiscentésimo',
154
- 700 => 'Septingentésimo',
155
- 800 => 'Octingentésimo',
156
- 900 => 'Noningentésimo'},
157
- GENERO_FEM => {
158
- 100 => 'Centésima',
159
- 200 => 'Ducentésima',
160
- 300 => 'Trecentésima',
161
- 400 => 'Quadringentésima',
162
- 500 => 'Quingentésima',
163
- 600 => 'Seiscentésima',
164
- 700 => 'Septingentésima',
165
- 800 => 'Octingentésima',
166
- 900 => 'Noningentésima'}}
167
-
168
-
169
- MILHAR_ORDINAL = {
170
- GENERO_MASC => {
171
- 1000 => 'Milésimo'},
172
- GENERO_FEM =>{
173
- 1000 => 'Milésima'}}
174
-
175
- def self.is_int(s)
176
- Integer(s) != nil rescue false
177
- end
178
-
179
- def self.is_float?(str)
180
- !!Float(str) rescue false
181
- end
182
-
183
- #######################################################################################################################################
184
-
185
- def self.numero (valor, genero = GENERO_MASC)
186
-
187
- # Gera a representação por extenso de um número inteiro, maior que zero e menor ou igual a VALOR_MAXIMO.
188
- #
189
- # PARÂMETROS:
190
- # valor (Integer) O valor numérico cujo extenso se deseja gerar
191
- #
192
- # genero (Integer) [Opcional; valor padrão: Extenso::GENERO_MASC] O gênero gramatical (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
193
- # do extenso a ser gerado. Isso possibilita distinguir, por exemplo, entre 'duzentos e dois homens' e 'duzentas e duas mulheres'.
194
- #
195
- # VALOR DE RETORNO:
196
- # (String) O número por extenso
197
-
198
- # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
199
-
200
- if !is_int(valor)
201
- raise "[Exceção em Extenso.numero] Parâmetro 'valor' não é numérico (recebido: '#{valor}')"
202
- elsif valor <= 0
203
- 'Zero'
204
- elsif valor > VALOR_MAXIMO
205
- raise "[Exceção em Extenso.numero] Parâmetro '#{valor} deve ser um inteiro entre 1 e #{VALOR_MAXIMO.to_s} (recebido: '#{valor}')"
206
- elsif genero != GENERO_MASC && genero != GENERO_FEM
207
- raise "Exceção em Extenso: valor incorreto para o parâmetro 'genero' (recebido: '#{genero}')"
208
-
209
- # ------------------------------------------------
210
-
211
- elsif valor >= 1 && valor <= 9
212
- UNIDADES[genero][valor]
213
-
214
- elsif valor == 10
215
- DEZENAS[valor]
216
-
217
- elsif valor >= 11 && valor <= 19
218
- DE11A19[valor]
219
-
220
- elsif valor >= 20 && valor <= 99
221
- dezena = valor - (valor % 10)
222
- ret = DEZENAS[dezena]
223
- # Chamada recursiva à função para processar resto se este for maior que zero.
224
- # O conectivo 'e' é utilizado entre dezenas e unidades.
225
- resto = valor - dezena
226
- if resto > 0
227
- ret += ' e ' + self.numero(resto, genero)
228
- end
229
- ret
230
-
231
- elsif valor == 100
232
- CENTENA_EXATA
233
-
234
- elsif valor >= 101 && valor <= 999
235
- centena = valor - (valor % 100)
236
- ret = CENTENAS[genero][centena] # As centenas (exceto 'cento') variam em gênero
237
- # Chamada recursiva à função para processar resto se este for maior que zero.
238
- # O conectivo 'e' é utilizado entre centenas e dezenas.
239
- resto = valor - centena
240
- if resto > 0
241
- ret += ' e ' + self.numero(resto, genero)
242
- end
243
- ret
244
-
245
- elsif valor >= 1000 && valor <= 999999
246
- # A função 'floor' é utilizada para encontrar o inteiro da divisão de valor por 1000,
247
- # assim determinando a quantidade de milhares. O resultado é enviado a uma chamada recursiva
248
- # da função. A palavra 'mil' não se flexiona.
249
- milhar = (valor / 1000).floor
250
- ret = self.numero(milhar, GENERO_MASC) + ' ' + MILHAR # 'Mil' é do gênero masculino
251
- resto = valor % 1000
252
- # Chamada recursiva à função para processar resto se este for maior que zero.
253
- # O conectivo 'e' é utilizado entre milhares e números entre 1 e 99, bem como antes de centenas exatas.
254
- if resto > 0 && ((resto >= 1 && resto <= 99) || resto % 100 == 0)
255
- ret += ' e ' + self.numero(resto, genero)
256
- # Nos demais casos, após o milhar é utilizada a vírgula.
257
- elsif (resto > 0)
258
- ret += ', ' + self.numero(resto, genero)
259
- end
260
- ret
261
-
262
- elsif valor >= 100000 && valor <= VALOR_MAXIMO
263
- # A função 'floor' é utilizada para encontrar o inteiro da divisão de valor por 1000000,
264
- # assim determinando a quantidade de milhões. O resultado é enviado a uma chamada recursiva
265
- # da função. A palavra 'milhão' flexiona-se no plural.
266
- milhoes = (valor / 1000000).floor
267
- ret = self.numero(milhoes, GENERO_MASC) + ' ' # Milhão e milhões são do gênero masculino
268
-
269
- # Se a o número de milhões for maior que 1, deve-se utilizar a forma flexionada no plural
270
- ret += milhoes == 1 ? MILHOES[NUM_SING] : MILHOES[NUM_PLURAL]
271
-
272
- resto = valor % 1000000
273
-
274
- # Chamada recursiva à função para processar resto se este for maior que zero.
275
- # O conectivo 'e' é utilizado entre milhões e números entre 1 e 99, bem como antes de centenas exatas.
276
- if resto && (resto >= 1 && resto <= 99)
277
- ret += ' e ' + self.numero(resto, genero)
278
- # Nos demais casos, após o milhão é utilizada a vírgula.
279
- elsif resto > 0
280
- ret += ', ' + self.numero(resto, genero)
281
- end
282
- ret
283
-
284
- end
285
-
286
- end
287
-
288
- #######################################################################################################################################
289
-
290
- def self.moeda(
291
- valor,
292
- casas_decimais = 2,
293
- info_unidade = ['Real', 'Reais', GENERO_MASC],
294
- info_fracao = ['Centavo', 'Centavos', GENERO_MASC]
295
- )
296
-
297
- # Gera a representação por extenso de um valor monetário, maior que zero e menor ou igual a Extenso::VALOR_MAXIMO.
298
- #
299
- #
300
- # PARÂMETROS:
301
- # valor (Float) O valor monetário cujo extenso se deseja gerar.
302
- # casas_decimais (Integer) [Opcional; valor padrão: 2] Número de casas decimais a serem consideradas como parte fracionária (centavos)
303
- #
304
- # info_unidade (Array) [Opcional; valor padrão: ['real', 'reais', Extenso::GENERO_MASC]] Fornece informações sobre a moeda a ser
305
- # utilizada. O primeiro valor da matriz corresponde ao nome da moeda no singular, o segundo ao nome da moeda no plural e o terceiro
306
- # ao gênero gramatical do nome da moeda (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
307
- #
308
- # info_fracao (Array) [Opcional; valor padrão: ['centavo', 'centavos', Extenso::GENERO_MASC]] Provê informações sobre a parte fracionária
309
- # da moeda. O primeiro valor da matriz corresponde ao nome da parte fracionária no singular, o segundo ao nome da parte fracionária no plural
310
- # e o terceiro ao gênero gramatical da parte fracionária (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
311
- #
312
- # VALOR DE RETORNO:
313
- # (String) O valor monetário por extenso
314
-
315
- # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
316
-
317
- if ! self.is_float?(valor.to_f.round(casas_decimais).to_s)
318
- raise "[Exceção em Extenso.moeda] Parâmetro 'valor' não é numérico (recebido: '#{valor}')"
319
-
320
- elsif valor <= 0
321
- "Zero"
322
-
323
- elsif ! self.is_int(casas_decimais) || casas_decimais < 0
324
- raise "[Exceção em Extenso.moeda] Parâmetro 'casas_decimais' não é numérico ou é menor que zero (recebido: '#{casas_decimais}')"
325
-
326
- elsif info_unidade.class != Array || info_unidade.length < 3
327
- temp = info_unidade.class == Array ? '[' + info_unidade.join(', ') + ']' : "'#{info_unidade}'"
328
- raise "[Exceção em Extenso.moeda] Parâmetro 'info_unidade' não é uma matriz com 3 (três) elementos (recebido: #{temp})"
329
-
330
- elsif info_unidade[POS_GENERO] != GENERO_MASC && info_unidade[POS_GENERO] != GENERO_FEM
331
- raise "Exceção em Extenso: valor incorreto para o parâmetro 'info_unidade[POS_GENERO]' (recebido: '#{info_unidade[POS_GENERO]}')"
332
-
333
- elsif info_fracao.class != Array || info_fracao.length < 3
334
- temp = info_fracao.class == Array ? '[' + info_fracao.join(', ') + ']' : "'#{info_fracao}'"
335
- raise "[Exceção em Extenso.moeda] Parâmetro 'info_fracao' não é uma matriz com 3 (três) elementos (recebido: #{temp})"
336
-
337
- elsif info_fracao[POS_GENERO] != GENERO_MASC && info_fracao[POS_GENERO] != GENERO_FEM
338
- raise "[Exceção em Extenso.moeda] valor incorreto para o parâmetro 'info_fracao[POS_GENERO]' (recebido: '#{info_fracao[POS_GENERO]}')."
339
-
340
- end
341
-
342
- # -----------------------------------------------
343
-
344
- ret = ''
345
-
346
- valor = sprintf("%#{casas_decimais.to_f / 100}f", valor)
347
- # A parte inteira do valor monetário corresponde ao valor passado antes do '.' no tipo float.
348
- parte_inteira = valor.split('.')[0].to_i
349
-
350
- # A parte fracionária ('centavos'), por seu turno, corresponderá ao valor passado depois do '.'
351
- fracao = valor.to_s.split('.')[1].to_i
352
-
353
- # os préstimos do método Extenso::numero().
354
- if parte_inteira > 0
355
- ret = self.numero(parte_inteira, info_unidade[POS_GENERO]) + ((parte_inteira >= 1000000 && (parte_inteira.to_s.chars.reverse[5] == "0" ) ) ? ' de ' : ' ')
356
- ret += parte_inteira == 1 ? info_unidade[NUM_SING] : info_unidade[NUM_PLURAL]
357
- ret
358
- end
359
-
360
- # De forma semelhante, o extenso da fracao somente será gerado se esta for maior que zero. */
361
- if fracao > 0
362
- # Se a parte_inteira for maior que zero, o extenso para ela terá sido gerado. Antes de juntar os
363
- # centavos, precisamos colocar o conectivo 'e'.
364
- if parte_inteira > 0
365
- ret += ' e '
366
- end
367
- ret += self.numero(fracao, info_fracao[POS_GENERO]) + ' '
368
- ret += fracao == 1 ? info_fracao[NUM_SING] : info_fracao[NUM_PLURAL]
369
- end
370
-
371
- if valor.to_f == 0
372
- ret += self.numero(fracao, info_fracao[POS_GENERO]) + ' '
373
- ret += parte_inteira == 1 ? info_fracao[NUM_SING] : info_fracao[NUM_PLURAL]
374
- end
375
-
376
- ret
377
-
378
- end
379
-
380
- ######################################################################################################################################################
381
- def self.ordinal (valor, genero = GENERO_MASC)
382
-
383
- # Gera a representação ordinal de um número inteiro de 1 à 1000
384
-
385
- # PARÂMETROS:
386
- # valor (Integer) O valor numérico cujo extenso se deseja gerar
387
- #
388
- # genero (Integer) [Opcional; valor padrão: Extenso::GENERO_MASC] O gênero gramatical (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
389
- # do extenso a ser gerado. Isso possibilita distinguir, por exemplo, entre 'duzentos e dois homens' e 'duzentas e duas mulheres'.
390
- #
391
- # VALOR DE RETORNO:
392
- # (String) O número por extenso
393
-
394
- # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
395
-
396
- if !is_int(valor)
397
- raise "[Exceção em Extenso.numero] Parâmetro 'valor' não é numérico (recebido: '#{valor}')"
398
- elsif valor <= 0
399
- 'Zero'
400
- # raise "[Exceção em Extenso.numero] Parâmetro 'valor' igual a ou menor que zero (recebido: '#{valor}')"
401
- elsif valor > VALOR_MAXIMO
402
- raise '[Exceção em Extenso::numero] Parâmetro ''valor'' deve ser um inteiro entre 1 e ' + VALOR_MAXIMO.to_s + " (recebido: '#{valor}')"
403
- elsif genero != GENERO_MASC && genero != GENERO_FEM
404
- raise "Exceção em Extenso: valor incorreto para o parâmetro 'genero' (recebido: '#{genero}')"
405
- # ------------------------------------------------
406
- elsif valor >= 1 && valor <= 9
407
- return UNIDADES_ORDINAL[genero][valor]
408
- elsif valor >= 10 && valor <= 99
409
- dezena = valor - (valor % 10)
410
- resto = valor - dezena
411
- ret = DEZENAS_ORDINAL[genero][dezena]+" "
412
- if resto > 0 then ret+= self.ordinal(resto,genero); end
413
- return ret
414
- elsif valor >= 100 && valor <= 999
415
- centena = valor - (valor % 100)
416
- resto = valor - centena
417
- ret = CENTENAS_ORDINAL[genero][centena]+" "
418
- if resto > 0 then ret += self.ordinal(resto, genero); end
419
- return ret
420
- elsif valor == 1000
421
- return MILHAR_ORDINAL[genero][valor]+" "
422
- end
423
- end
424
-
425
- # Gera o valor em formato de Real
426
- #
427
- # Exemplo:
428
- # Extenso.real_formatado(10) - R$ 10,00
429
- # Extenso.real_formatado(1.55) - R$ 1,55
430
- #
431
- # @params[Object]
432
- def self.real_formatado(valor)
433
- float_valor = sprintf("%#0.02f", valor)
434
- if float_valor.chars.count >= 7
435
- float_valor = float_valor.chars.reverse.insert(6, '.').reverse.join
436
- end
437
-
438
- if float_valor.chars.count >= 11
439
- float_valor = float_valor.chars.reverse.insert(10, '.').reverse.join
440
- end
441
-
442
- float_valor = float_valor.chars.reverse
443
- float_valor[2] = ','
444
-
445
- "R$ #{float_valor.reverse.join}"
446
- end
447
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'extensobr/version'
4
+ require 'settings'
5
+
6
+ # ----- CARREGA CONFIGURAÇÃO -----
7
+ Settings.load_extensobr_settings
8
+
9
+ # ----- CARREGA CORE EXTENSÕES -----
10
+ if Settings.extensobr_settings[:use_core_exts] == 'true'
11
+ require 'core_exts/float'
12
+ require 'core_exts/integer'
13
+ require 'core_exts/string'
14
+ end
15
+
16
+ class Extenso
17
+ BRL = { delimiter: '.', separator: ',', unit: 'R$', precision: 2, position: 'before' }.freeze unless Extenso.const_defined?('BRL')
18
+
19
+ NUM_SING = 0 unless Extenso.const_defined?('NUM_SING')
20
+ NUM_PLURAL = 1 unless Extenso.const_defined?('NUM_PLURAL')
21
+ POS_GENERO = 2 unless Extenso.const_defined?('POS_GENERO')
22
+ GENERO_MASC = 0 unless Extenso.const_defined?('GENERO_MASC')
23
+ GENERO_FEM = 1 unless Extenso.const_defined?('GENERO_FEM')
24
+
25
+ VALOR_MAXIMO = 999_999_999 unless Extenso.const_defined?('VALOR_MAXIMO')
26
+
27
+ # As unidades 1 e 2 variam em gênero, pelo que precisamos de dois conjuntos de strings (masculinas e femininas) para as unidades
28
+ UNIDADES = {
29
+ GENERO_MASC => {
30
+ 1 => 'Um',
31
+ 2 => 'Dois',
32
+ 3 => 'Três',
33
+ 4 => 'Quatro',
34
+ 5 => 'Cinco',
35
+ 6 => 'Seis',
36
+ 7 => 'Sete',
37
+ 8 => 'Oito',
38
+ 9 => 'Nove'
39
+ },
40
+ GENERO_FEM => {
41
+ 1 => 'Uma',
42
+ 2 => 'Duas',
43
+ 3 => 'Três',
44
+ 4 => 'Quatro',
45
+ 5 => 'Cinco',
46
+ 6 => 'Seis',
47
+ 7 => 'Sete',
48
+ 8 => 'Oito',
49
+ 9 => 'Nove'
50
+ }
51
+ }.freeze unless Extenso.const_defined?('UNIDADES')
52
+
53
+ DE11A19 = {
54
+ 11 => 'Onze',
55
+ 12 => 'Doze',
56
+ 13 => 'Treze',
57
+ 14 => 'Quatorze',
58
+ 15 => 'Quinze',
59
+ 16 => 'Dezesseis',
60
+ 17 => 'Dezessete',
61
+ 18 => 'Dezoito',
62
+ 19 => 'Dezenove'
63
+ }.freeze unless Extenso.const_defined?('DE11A19')
64
+
65
+ DEZENAS = {
66
+ 10 => 'Dez',
67
+ 20 => 'Vinte',
68
+ 30 => 'Trinta',
69
+ 40 => 'Quarenta',
70
+ 50 => 'Cinquenta',
71
+ 60 => 'Sessenta',
72
+ 70 => 'Setenta',
73
+ 80 => 'Oitenta',
74
+ 90 => 'Noventa'
75
+ }.freeze unless Extenso.const_defined?('DEZENAS')
76
+
77
+ CENTENA_EXATA = 'Cem' unless Extenso.const_defined?('CENTENA_EXATA')
78
+
79
+ # As centenas, com exceção de 'cento', também variam em gênero. Aqui também se faz
80
+ # necessário dois conjuntos de strings (masculinas e femininas).
81
+
82
+ CENTENAS = {
83
+ GENERO_MASC => {
84
+ 100 => 'Cento',
85
+ 200 => 'Duzentos',
86
+ 300 => 'Trezentos',
87
+ 400 => 'Quatrocentos',
88
+ 500 => 'Quinhentos',
89
+ 600 => 'Seiscentos',
90
+ 700 => 'Setecentos',
91
+ 800 => 'Oitocentos',
92
+ 900 => 'Novecentos'
93
+ },
94
+ GENERO_FEM => {
95
+ 100 => 'Cento',
96
+ 200 => 'Duzentas',
97
+ 300 => 'Trezentas',
98
+ 400 => 'Quatrocentas',
99
+ 500 => 'Quinhentas',
100
+ 600 => 'Seiscentas',
101
+ 700 => 'Setecentas',
102
+ 800 => 'Oitocentas',
103
+ 900 => 'Novecentas'
104
+ }
105
+ }.freeze unless Extenso.const_defined?('CENTENAS')
106
+
107
+ # 'Mil' é invariável, seja em gênero, seja em número
108
+ MILHAR = 'mil' unless Extenso.const_defined?('MILHAR')
109
+
110
+ MILHOES = {
111
+ NUM_SING => 'milhão',
112
+ NUM_PLURAL => 'milhões'
113
+ }.freeze unless Extenso.const_defined?('MILHOES')
114
+
115
+ UNIDADES_ORDINAL = {
116
+ GENERO_MASC => {
117
+ 1 => 'Primeiro',
118
+ 2 => 'Segundo',
119
+ 3 => 'Terceiro',
120
+ 4 => 'Quarto',
121
+ 5 => 'Quinto',
122
+ 6 => 'Sexto',
123
+ 7 => 'Sétimo',
124
+ 8 => 'Oitavo',
125
+ 9 => 'Nono'
126
+ },
127
+ GENERO_FEM => {
128
+ 1 => 'Primeira',
129
+ 2 => 'Segunda',
130
+ 3 => 'Terceira',
131
+ 4 => 'Quarta',
132
+ 5 => 'Quinta',
133
+ 6 => 'Sexta',
134
+ 7 => 'Sétima',
135
+ 8 => 'Oitava',
136
+ 9 => 'Nona'
137
+ }
138
+ }.freeze unless Extenso.const_defined?('UNIDADES_ORDINAL')
139
+
140
+ DEZENAS_ORDINAL = {
141
+ GENERO_MASC => {
142
+ 10 => 'Décimo',
143
+ 20 => 'Vigésimo',
144
+ 30 => 'Trigésimo',
145
+ 40 => 'Quadragésimo',
146
+ 50 => 'Quinquagésimo',
147
+ 60 => 'Sexagésimo',
148
+ 70 => 'Septuagésimo',
149
+ 80 => 'Octogésimo',
150
+ 90 => 'Nonagésimo'
151
+ },
152
+ GENERO_FEM => {
153
+ 10 => 'Décima',
154
+ 20 => 'Vigésima',
155
+ 30 => 'Trigésima',
156
+ 40 => 'Quadragésima',
157
+ 50 => 'Quinquagésima',
158
+ 60 => 'Sexagésima',
159
+ 70 => 'Septuagésima',
160
+ 80 => 'Octogésima',
161
+ 90 => 'Nonagésima'
162
+ }
163
+ }.freeze unless Extenso.const_defined?('DEZENAS_ORDINAL')
164
+
165
+ CENTENAS_ORDINAL = {
166
+ GENERO_MASC => {
167
+ 100 => 'Centésimo',
168
+ 200 => 'Ducentésimo',
169
+ 300 => 'Trecentésimo',
170
+ 400 => 'Quadringentésimo',
171
+ 500 => 'Quingentésimo',
172
+ 600 => 'Seiscentésimo',
173
+ 700 => 'Septingentésimo',
174
+ 800 => 'Octingentésimo',
175
+ 900 => 'Noningentésimo'
176
+ },
177
+ GENERO_FEM => {
178
+ 100 => 'Centésima',
179
+ 200 => 'Ducentésima',
180
+ 300 => 'Trecentésima',
181
+ 400 => 'Quadringentésima',
182
+ 500 => 'Quingentésima',
183
+ 600 => 'Seiscentésima',
184
+ 700 => 'Septingentésima',
185
+ 800 => 'Octingentésima',
186
+ 900 => 'Noningentésima'
187
+ }
188
+ }.freeze unless Extenso.const_defined?('CENTENAS_ORDINAL')
189
+
190
+ MILHAR_ORDINAL = {
191
+ GENERO_MASC => {
192
+ 1000 => 'Milésimo'
193
+ },
194
+ GENERO_FEM => {
195
+ 1000 => 'Milésima'
196
+ }
197
+ }.freeze unless Extenso.const_defined?('MILHAR_ORDINAL')
198
+
199
+ def self.int?(payload)
200
+ !Integer(payload).nil?
201
+ rescue StandardError
202
+ false
203
+ end
204
+
205
+ def self.float?(payload)
206
+ !!Float(payload)
207
+ rescue StandardError
208
+ false
209
+ end
210
+
211
+ #######################################################################################################################################
212
+
213
+ def self.numero(valor, genero = GENERO_MASC)
214
+ # Gera a representação por extenso de um número inteiro, maior que zero e menor ou igual a VALOR_MAXIMO.
215
+ #
216
+ # PARÂMETROS:
217
+ # valor (Integer) O valor numérico cujo extenso se deseja gerar
218
+ #
219
+ # genero (Integer) [Opcional; valor padrão: Extenso::GENERO_MASC] O gênero gramatical (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
220
+ # do extenso a ser gerado. Isso possibilita distinguir, por exemplo, entre 'duzentos e dois homens' e 'duzentas e duas mulheres'.
221
+ #
222
+ # VALOR DE RETORNO:
223
+ # (String) O número por extenso
224
+
225
+ # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
226
+ if valor.nil? || valor == ''
227
+ raise "[Exceção em Extenso.numero] Parâmetro 'valor' é nulo" if Settings&.extensobr_settings&.dig(:raise_for_nil) == 'true'
228
+
229
+ return 'Zero'
230
+ end
231
+
232
+ unless int?(valor) && !valor.nil?
233
+ "[Exceção em Extenso.numero] Parâmetro 'valor' não é numérico (recebido: '#{valor}')"
234
+ end
235
+
236
+ if valor <= 0
237
+ 'Zero'
238
+ elsif valor > VALOR_MAXIMO
239
+ raise "[Exceção em Extenso.numero] Parâmetro '#{valor} deve ser um inteiro entre 1 e #{VALOR_MAXIMO} (recebido: '#{valor}')"
240
+ elsif genero != GENERO_MASC && genero != GENERO_FEM
241
+ raise "Exceção em Extenso: valor incorreto para o parâmetro 'genero' (recebido: '#{genero}')"
242
+
243
+ # ------------------------------------------------
244
+
245
+ elsif valor >= 1 && valor <= 9
246
+ UNIDADES[genero][valor]
247
+
248
+ elsif valor == 10
249
+ DEZENAS[valor]
250
+
251
+ elsif valor >= 11 && valor <= 19
252
+ DE11A19[valor]
253
+
254
+ elsif valor >= 20 && valor <= 99
255
+ dezena = valor - (valor % 10)
256
+ ret = DEZENAS[dezena]
257
+ # Chamada recursiva à função para processar resto se este for maior que zero.
258
+ # O conectivo 'e' é utilizado entre dezenas e unidades.
259
+ resto = valor - dezena
260
+ ret += " e #{numero(resto, genero)}" if resto.positive?
261
+ ret
262
+
263
+ elsif valor == 100
264
+ CENTENA_EXATA
265
+
266
+ elsif valor >= 101 && valor <= 999
267
+ centena = valor - (valor % 100)
268
+ ret = CENTENAS[genero][centena] # As centenas (exceto 'cento') variam em gênero
269
+ # Chamada recursiva à função para processar resto se este for maior que zero.
270
+ # O conectivo 'e' é utilizado entre centenas e dezenas.
271
+ resto = valor - centena
272
+ ret += " e #{numero(resto, genero)}" if resto.positive?
273
+ ret
274
+
275
+ elsif valor >= 1000 && valor <= 999_999
276
+ # A função 'floor' é utilizada para encontrar o inteiro da divisão de valor por 1000,
277
+ # assim determinando a quantidade de milhares. O resultado é enviado a uma chamada recursiva
278
+ # da função. A palavra 'mil' não se flexiona.
279
+ milhar = (valor / 1000).floor
280
+ ret = "#{numero(milhar, GENERO_MASC)} #{MILHAR}" # 'Mil' é do gênero masculino
281
+ resto = valor % 1000
282
+ # Chamada recursiva à função para processar resto se este for maior que zero.
283
+ # O conectivo 'e' é utilizado entre milhares e números entre 1 e 99, bem como antes de centenas exatas.
284
+ if resto.positive? && ((resto >= 1 && resto <= 99) || (resto % 100).zero?)
285
+ ret += " e #{numero(resto, genero)}"
286
+ # Nos demais casos, após o milhar é utilizada a vírgula.
287
+ elsif resto.positive?
288
+ ret += ", #{numero(resto, genero)}"
289
+ end
290
+ ret
291
+
292
+ elsif valor >= 100_000 && valor <= VALOR_MAXIMO
293
+ # A função 'floor' é utilizada para encontrar o inteiro da divisão de valor por 1000000,
294
+ # assim determinando a quantidade de milhões. O resultado é enviado a uma chamada recursiva
295
+ # da função. A palavra 'milhão' flexiona-se no plural.
296
+ milhoes = (valor / 1_000_000).floor
297
+ ret = "#{numero(milhoes, GENERO_MASC)} " # Milhão e milhões são do gênero masculino
298
+
299
+ # Se a o número de milhões for maior que 1, deve-se utilizar a forma flexionada no plural
300
+ ret += milhoes == 1 ? MILHOES[NUM_SING] : MILHOES[NUM_PLURAL]
301
+
302
+ resto = valor % 1_000_000
303
+
304
+ # Chamada recursiva à função para processar resto se este for maior que zero.
305
+ # O conectivo 'e' é utilizado entre milhões e números entre 1 e 99, bem como antes de centenas exatas.
306
+ if resto && (resto >= 1 && resto <= 99)
307
+ ret += " e #{numero(resto, genero)}"
308
+ # Nos demais casos, após o milhão é utilizada a vírgula.
309
+ elsif resto.positive?
310
+ ret += ", #{numero(resto, genero)}"
311
+ end
312
+ ret
313
+
314
+ end
315
+ end
316
+
317
+ #######################################################################################################################################
318
+
319
+ def self.moeda(
320
+ valor,
321
+ casas_decimais = 2,
322
+ info_unidade = ['Real', 'Reais', GENERO_MASC],
323
+ info_fracao = ['Centavo', 'Centavos', GENERO_MASC]
324
+ )
325
+ # Gera a representação por extenso de um valor monetário, maior que zero e menor ou igual a Extenso::VALOR_MAXIMO.
326
+ #
327
+ #
328
+ # PARÂMETROS:
329
+ # valor (Float) O valor monetário cujo extenso se deseja gerar.
330
+ # casas_decimais (Integer) [Opcional; valor padrão: 2] Número de casas decimais a serem consideradas como parte fracionária (centavos)
331
+ #
332
+ # info_unidade (Array) [Opcional; valor padrão: ['real', 'reais', Extenso::GENERO_MASC]] Fornece informações sobre a moeda a ser
333
+ # utilizada. O primeiro valor da matriz corresponde ao nome da moeda no singular, o segundo ao nome da moeda no plural e o terceiro
334
+ # ao gênero gramatical do nome da moeda (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
335
+ #
336
+ # info_fracao (Array) [Opcional; valor padrão: ['centavo', 'centavos', Extenso::GENERO_MASC]] Provê informações sobre a parte fracionária
337
+ # da moeda. O primeiro valor da matriz corresponde ao nome da parte fracionária no singular, o segundo ao nome da parte fracionária no plural
338
+ # e o terceiro ao gênero gramatical da parte fracionária (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
339
+ #
340
+ # VALOR DE RETORNO:
341
+ # (String) O valor monetário por extenso
342
+
343
+ # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
344
+ if valor.nil? || valor == ''
345
+ raise "[Exceção em Extenso.moeda] Parâmetro 'valor' é nulo" if Settings&.extensobr_settings&.dig(:raise_for_nil) == 'true'
346
+
347
+ return 'Zero Centavos'
348
+ end
349
+
350
+ unless float?(valor.to_f.round(casas_decimais).to_s)
351
+ raise "[Exceção em Extenso.moeda] Parâmetro 'valor' não é numérico (recebido: '#{valor}')"
352
+ end
353
+
354
+ if valor <= 0
355
+ 'Zero'
356
+
357
+ elsif !int?(casas_decimais) || casas_decimais.negative?
358
+ raise "[Exceção em Extenso.moeda] Parâmetro 'casas_decimais' não é numérico ou é menor que zero (recebido: '#{casas_decimais}')"
359
+
360
+ elsif info_unidade.class != Array || info_unidade.length < 3
361
+ temp = info_unidade.instance_of?(Array) ? "[#{info_unidade.join(', ')}]" : "'#{info_unidade}'"
362
+ raise "[Exceção em Extenso.moeda] Parâmetro 'info_unidade' não é uma matriz com 3 (três) elementos (recebido: #{temp})"
363
+
364
+ elsif info_unidade[POS_GENERO] != GENERO_MASC && info_unidade[POS_GENERO] != GENERO_FEM
365
+ raise "Exceção em Extenso: valor incorreto para o parâmetro 'info_unidade[POS_GENERO]' (recebido: '#{info_unidade[POS_GENERO]}')"
366
+
367
+ elsif info_fracao.class != Array || info_fracao.length < 3
368
+ temp = info_fracao.instance_of?(Array) ? "[#{info_fracao.join(', ')}]" : "'#{info_fracao}'"
369
+ raise "[Exceção em Extenso.moeda] Parâmetro 'info_fracao' não é uma matriz com 3 (três) elementos (recebido: #{temp})"
370
+
371
+ elsif info_fracao[POS_GENERO] != GENERO_MASC && info_fracao[POS_GENERO] != GENERO_FEM
372
+ raise "[Exceção em Extenso.moeda] valor incorreto para o parâmetro 'info_fracao[POS_GENERO]' (recebido: '#{info_fracao[POS_GENERO]}')."
373
+
374
+ end
375
+
376
+ # -----------------------------------------------
377
+
378
+ ret = ''
379
+
380
+ valor = format("%#{casas_decimais.to_f / 100}f", valor)
381
+ # A parte inteira do valor monetário corresponde ao valor passado antes do '.' no tipo float.
382
+ parte_inteira = valor.split('.')[0].to_i
383
+
384
+ # A parte fracionária ('centavos'), por seu turno, corresponderá ao valor passado depois do '.'
385
+ fracao = valor.to_s.split('.')[1].to_i
386
+
387
+ # os préstimos do método Extenso::numero().
388
+ if parte_inteira.positive?
389
+ ret = numero(parte_inteira,
390
+ info_unidade[POS_GENERO]) + (parte_inteira >= 1_000_000 && (parte_inteira.to_s.chars.reverse[5] == '0') ? ' de ' : ' ')
391
+ ret += parte_inteira == 1 ? info_unidade[NUM_SING] : info_unidade[NUM_PLURAL]
392
+ ret
393
+ end
394
+
395
+ # De forma semelhante, o extenso da fracao somente será gerado se esta for maior que zero. */
396
+ if fracao.positive?
397
+ # Se a parte_inteira for maior que zero, o extenso para ela já terá sido gerado. Antes de juntar os
398
+ # centavos, precisamos colocar o conectivo 'e'.
399
+ ret += ' e ' if parte_inteira.positive?
400
+ ret += "#{numero(fracao, info_fracao[POS_GENERO])} "
401
+ ret += fracao == 1 ? info_fracao[NUM_SING] : info_fracao[NUM_PLURAL]
402
+ end
403
+
404
+ if valor.to_f.zero?
405
+ ret += "#{numero(fracao, info_fracao[POS_GENERO])} "
406
+ ret += parte_inteira == 1 ? info_fracao[NUM_SING] : info_fracao[NUM_PLURAL]
407
+ end
408
+
409
+ ret
410
+ end
411
+
412
+ ######################################################################################################################################################
413
+ def self.ordinal(valor, genero = GENERO_MASC)
414
+ # Gera a representação ordinal de um número inteiro de 1 à 1000
415
+
416
+ # PARÂMETROS:
417
+ # valor (Integer) O valor numérico cujo extenso se deseja gerar
418
+ #
419
+ # genero (Integer) [Opcional; valor padrão: Extenso::GENERO_MASC] O gênero gramatical (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
420
+ # do extenso a ser gerado. Isso possibilita distinguir, por exemplo, entre 'duzentos e dois homens' e 'duzentas e duas mulheres'.
421
+ #
422
+ # VALOR DE RETORNO:
423
+ # (String) O número por extenso
424
+
425
+ # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
426
+ if valor.nil? || valor == ''
427
+ raise "[Exceção em Extenso.ordinal] Parâmetro 'valor' é nulo" if Settings&.extensobr_settings&.dig(:raise_for_nil) == 'true'
428
+
429
+ return 'Zero'
430
+ end
431
+
432
+ raise "[Exceção em Extenso.numero] Parâmetro 'valor' não é numérico (recebido: '#{valor}')" unless int?(valor)
433
+
434
+ if valor <= 0
435
+ 'Zero'
436
+ # raise "[Exceção em Extenso.numero] Parâmetro 'valor' igual a ou menor que zero (recebido: '#{valor}')"
437
+ elsif valor > VALOR_MAXIMO
438
+ raise "[Exceção em Extenso::numero] Parâmetro ''valor'' deve ser um inteiro entre 1 e #{VALOR_MAXIMO} (recebido: '#{valor}')"
439
+ elsif genero != GENERO_MASC && genero != GENERO_FEM
440
+ raise "Exceção em Extenso: valor incorreto para o parâmetro 'genero' (recebido: '#{genero}')"
441
+ # ------------------------------------------------
442
+ elsif valor >= 1 && valor <= 9
443
+ UNIDADES_ORDINAL[genero][valor]
444
+ elsif valor >= 10 && valor <= 99
445
+ dezena = valor - (valor % 10)
446
+ resto = valor - dezena
447
+ ret = "#{DEZENAS_ORDINAL[genero][dezena]} "
448
+ ret += ordinal(resto, genero) if resto.positive?
449
+ ret.rstrip
450
+ elsif valor >= 100 && valor <= 999
451
+ centena = valor - (valor % 100)
452
+ resto = valor - centena
453
+ ret = "#{CENTENAS_ORDINAL[genero][centena]} "
454
+ ret += ordinal(resto, genero) if resto.positive?
455
+ ret.rstrip
456
+ elsif valor == 1000
457
+ MILHAR_ORDINAL[genero][valor]
458
+ end
459
+ end
460
+
461
+ # Gera o valor em formato de Real
462
+ #
463
+ # Exemplo:
464
+ # Extenso.real_formatado(10) - R$ 10,00
465
+ # Extenso.real_formatado(1.55) - R$ 1,55
466
+ #
467
+ # @params[Object]
468
+ def self.real_formatado(valor)
469
+ float_valor = format('%#0.02f', valor)
470
+ float_valor = float_valor.chars.reverse.insert(6, '.').reverse.join if float_valor.chars.count >= 7
471
+
472
+ float_valor = float_valor.chars.reverse.insert(10, '.').reverse.join if float_valor.chars.count >= 11
473
+
474
+ float_valor = float_valor.chars.reverse
475
+ float_valor[2] = ','
476
+
477
+ "R$ #{float_valor.reverse.join}"
478
+ end
479
+ end