inss_calculator 0.4.0 → 0.4.2

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: a05163c92393153a574007ef8c0f932f0f203d94563ba4757c94b8250ae019b5
4
- data.tar.gz: cc17641c475339eb14d34b8a66d6f691d5a16afc00773c2da021b742249a5d1c
3
+ metadata.gz: b0c5d044bf05b0f50ac8c9b4b6fe0634e0f809fd18918fd84a8a15a5fcbfd09a
4
+ data.tar.gz: 56906b9378630a07cb21ab2fab31caf77511a2808b78d347270ccde37df6ea08
5
5
  SHA512:
6
- metadata.gz: 1dbe2a8d65e5c6c6bc96fc31a5bdafa456b5cb216bb8758b0076ba8651e5b44e73de7355a9bfd84b582d285c3be1b345aab4255838942a1739fb5045ad5c4da1
7
- data.tar.gz: 42a4e83b651b23f20108dd414a93ac2ec308a8e03e686e13ab1c61f5b24c8e534d734ce5b55658a82c0e33a3634e428d63c57670091d0db3a87e893bed7c5f32
6
+ metadata.gz: 54a10e9bda6faff9e6ce0765aa5ae6ceb92dbd0fe333995312860acc68bb15a6c085eb0a17097a0937fdb56d392f4fb61f4912676ae77e80d2bfd387e17e244a
7
+ data.tar.gz: 1ed31865a00db8bd38a9b782bcd0d7cd2e899e3743b221de2de85e8ffcf391e3dfaa5fb7308d7763c0df02c777fbbbad1554ecf82a6bf61b087f39166c732cd3
data/.rubocop.yml CHANGED
@@ -1,7 +1,8 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 3.2.2
3
3
  Include:
4
- - 'lib/**/*.rb'
4
+ - 'lib/inss_calculator/*.rb'
5
+ - 'inss_calculator.rb'
5
6
 
6
7
  Style/StringLiterals:
7
8
  Enabled: true
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- inss_calculator (0.3.2)
4
+ inss_calculator (0.4.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -10,7 +10,7 @@ GEM
10
10
  diff-lcs (1.5.1)
11
11
  json (2.7.2)
12
12
  language_server-protocol (3.17.0.3)
13
- parallel (1.26.0)
13
+ parallel (1.26.3)
14
14
  parser (3.3.4.2)
15
15
  ast (~> 2.4.1)
16
16
  racc
data/README.md CHANGED
@@ -78,6 +78,21 @@ Desta forma, uma requisição que busca somente a primeira faixa salarial seria:
78
78
  YourModel.where("salary <= ?", InssCalculator::FIRST_SALARY_LIMIT)
79
79
  ```
80
80
 
81
+ ## Decorators
82
+
83
+ `InssCalculator::Decorator::Text` explica no formato de texto o que o trabalhador precisa saber.
84
+ Ideal para uso no parágrafo do HTML. Retire da view esta responsabildade e deixe com este decorator.
85
+ Você ainda tem acesso à classe original com `#calculator`.
86
+
87
+ ```
88
+ calculator = InssCalculator::DiscountPrevidenceCalculator.new(3000)
89
+ text_decorator = InssCalculator::Decorator::Text.new(calculator)
90
+ text_decorator.present => "Com o salário de R$ 3.000,00, sua contribuição é de R$ 258,81. Seu salário líquido, portanto, é de R$ 2.741,19."
91
+
92
+ text_decorator.calculator => InssCalculator::DiscountPrevidenceCalculator.new(3000)
93
+
94
+ ```
95
+
81
96
  ## Nota sobre trabalhar com números decimais em Ruby
82
97
 
83
98
  Após investigar os resultados dos exemplos contábeis, concluiu-se que os números são truncados.
data/lib/dinheiro.rb ADDED
@@ -0,0 +1,303 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Com base em https://github.com/tapajos/brazilian-rails/blob/master/brdinheiro/lib/brdinheiro/dinheiro.rb
4
+ class Dinheiro
5
+ include Comparable
6
+
7
+ attr_reader :quantia
8
+
9
+ FORMATO_VALIDO_BR = /^([R|r]\$\s*)?(([+-]?\d{1,3}(\.?\d{3})*))?(,\d{0,2})?$/
10
+ FORMATO_VALIDO_EUA = /^([R|r]\$\s*)?(([+-]?\d{1,3}(,?\d{3})*))?(\.\d{0,2})?$/
11
+ SEPARADOR_MILHAR = '.'
12
+ SEPARADOR_FRACIONARIO = ','
13
+ QUANTIDADE_DIGITOS = 3
14
+ PRECISAO_DECIMAL = 100
15
+
16
+ def initialize(quantia)
17
+ self.quantia = quantia
18
+ end
19
+
20
+ # Retorna o valor em Float quando uma coleção
21
+ # ou objeto é convertido para JSON
22
+ #
23
+ # Exemplo:
24
+ # produto = Produto.find 1
25
+ # produto.to_json // {"nome": "MacBook", "valor": 3500.0}
26
+ def as_json
27
+ to_f
28
+ end
29
+
30
+ # Retorna o valor armazenado em string.
31
+ #
32
+ # Exemplo:
33
+ # 1000.to_s ==> '1.000,00'
34
+ def to_s
35
+ inteiro_com_milhar(parte_inteira) + parte_decimal
36
+ end
37
+
38
+ # Compara com outro dinheiro se eh igual.
39
+ #
40
+ # Exemplo:
41
+ # um_real = Dinheiro.new(1)
42
+ # um_real == Dinheiro.new(1) ==> true
43
+ # um_real == Dinheiro.new(2) ==> false
44
+ def ==(other)
45
+ begin
46
+ other = Dinheiro.new(other) unless other.is_a?(Dinheiro)
47
+ rescue StandardError
48
+ return false
49
+ end
50
+ @quantia == other.quantia
51
+ end
52
+
53
+ # Compara com outro dinheiro se eh maior ou menor.
54
+ #
55
+ # Exemplo:
56
+ # 1.real < 2.reais ==> true
57
+ # 1.real > 2.reais ==> false
58
+ # 2.real < 1.reais ==> false
59
+ # 2.real > 1.reais ==> true
60
+ def <=>(other)
61
+ other = Dinheiro.new(other) unless other.is_a?(Dinheiro)
62
+ @quantia <=> other.quantia
63
+ end
64
+
65
+ # Retorna a adicao entre dinheiros.
66
+ #
67
+ # Exemplo:
68
+ # 1.real + 1.real == 2.reais
69
+ # 1.real + 1 == 2.reais
70
+ def +(other)
71
+ Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia + quantia_de(other)))
72
+ end
73
+
74
+ # Retorna a subtracao entre dinheiros.
75
+ #
76
+ # Exemplo:
77
+ # 10.reais - 2.reais == 8.reais
78
+ # 10.reais - 2 == 8.reais
79
+ def -(other)
80
+ Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia - quantia_de(other)))
81
+ end
82
+
83
+ # Retorna a multiplicacao entre dinheiros.
84
+ #
85
+ # Exemplo:
86
+ # 5.reais * 2 == 10.reais
87
+ # 5.reais * 2.reais == 10.reais
88
+ def *(other)
89
+ return Dinheiro.new(to_f * other) unless other.is_a? Dinheiro
90
+
91
+ other * to_f
92
+ end
93
+
94
+ # Retorna a divisao entre dinheiros.
95
+ #
96
+ # Exemplo:
97
+ # 5.reais / 2 == (2.5).reais
98
+ # 5.reais / 2.reais == DivisaPorNaoEscalarError
99
+ # 5.reais / 0 == ZeroDivisionError
100
+ #
101
+ # Veja também o método parcelar
102
+ def /(other)
103
+ raise DivisaPorNaoEscalarError unless other.is_a?(Numeric)
104
+ return @quantia / other if other.zero?
105
+
106
+ Dinheiro.new(to_f / other)
107
+ end
108
+
109
+ # Retorna um array de dinheiro com as parcelas
110
+ #
111
+ # Exemplo:
112
+ # 6.reais.parcelar(2) == [3.reais, 3.reais]
113
+ # 6.reais.parcelar(2.reais) == DisivaPorNaoEscalarError
114
+ # 6.reais.parcelar(0) == ZeroDivisionError
115
+ def parcelar(numero_de_parcelar)
116
+ raise DivisaPorNaoEscalarError unless numero_de_parcelar.is_a?(Integer)
117
+
118
+ resto = @quantia % numero_de_parcelar
119
+ valor_menor = Dinheiro.new((@quantia / numero_de_parcelar) / 100.0)
120
+ valor_maior = Dinheiro.new((@quantia / numero_de_parcelar + 1) / 100.0)
121
+ [valor_menor] * (numero_de_parcelar - resto) + [valor_maior] * resto
122
+ end
123
+
124
+ # Escreve o valor por extenso.
125
+ #
126
+ # Exemplo:
127
+ # 1.real.to_extenso ==> 'um real'
128
+ # (100.58).to_extenso ==> 'cem reais e cinquenta e oito centavos'
129
+ def to_extenso
130
+ (@quantia / 100.0).por_extenso_em_reais
131
+ end
132
+
133
+ # Alias para o metodo to_extenso.
134
+ alias por_extenso to_extenso
135
+
136
+ # Alias para o metodo to_extenso.
137
+ alias por_extenso_em_reais to_extenso
138
+
139
+ # Verifica se o valor é zero.
140
+ def zero?
141
+ to_f.zero?
142
+ end
143
+
144
+ # Retorna um Float.
145
+ def to_f
146
+ to_s.gsub('.', '').gsub(',', '.').to_f
147
+ end
148
+
149
+ def coerce(outro) # :nodoc:
150
+ [Dinheiro.new(outro), self]
151
+ end
152
+
153
+ # Retorna a própria instância/
154
+ def real
155
+ self
156
+ end
157
+
158
+ # Alias para real.
159
+ alias reais real
160
+
161
+ # Retorna uma string formatada com sigla em valor monetário.
162
+ # Exemplo:
163
+ # Dinheiro.new(1).real_formatado ==> 'R$ 1,00'
164
+ # Dinheiro.new(-1).real_formatado ==> 'R$ -1,00'
165
+ def real_formatado
166
+ "R$ #{self}"
167
+ end
168
+
169
+ # Alias para real_formatado.
170
+ alias reais_formatado real_formatado
171
+
172
+ # Retorna uma string formatada com sigla em valor contábil.
173
+ #
174
+ # Exemplo:
175
+ # Dinheiro.new(1).real_contabil ==> 'R$ 1,00'
176
+ # Dinheiro.new(-1).real_contabil ==> 'R$ (1,00)'
177
+ def real_contabil
178
+ "R$ #{contabil}"
179
+ end
180
+
181
+ # Alias para real_contabil.
182
+ alias reais_contabeis real_contabil
183
+
184
+ # Retorna uma string formatada sem sigla.
185
+ #
186
+ # Exemplo:
187
+ # Dinheiro.new(1).contabil ==> '1,00'
188
+ # Dinheiro.new(-1).contabil ==> '(1,00)'
189
+ def contabil
190
+ if @quantia >= 0
191
+ to_s
192
+ else
193
+ "(#{to_s[1..]})"
194
+ end
195
+ end
196
+
197
+ # Method missing para retorna um BigDecinal quando chamada .
198
+ def method_missing(symbol, *args) # :nodoc:
199
+ # Ex: Chama ao método valor_decimal()
200
+ if (symbol.to_s =~ /^(.*)_decimal$/) && args.empty?
201
+ BigDecimal quantia_sem_separacao_milhares.gsub(',', '.')
202
+ else
203
+ super.method_missing(symbol, *args)
204
+ end
205
+ end
206
+
207
+ private
208
+
209
+ def quantia_de(outro)
210
+ outro = outro.to_f if outro.is_a?(BigDecimal)
211
+ return outro.quantia if outro.is_a?(Dinheiro)
212
+
213
+ (outro * 100).round
214
+ end
215
+
216
+ def transforma_em_string_que_represente_a_quantia(quantia)
217
+ if /^([+-]?)(\d)$/ =~ quantia.to_s
218
+ return "#{::Regexp.last_match(1)}0.0#{::Regexp.last_match(2)}"
219
+ end
220
+
221
+ /^([+-]?)(\d*)(\d\d)$/ =~ quantia.to_s
222
+ "#{::Regexp.last_match(1)}#{::Regexp.last_match(2).to_i}.#{::Regexp.last_match(3)}"
223
+ end
224
+
225
+ def quantia=(quantia)
226
+ @quantia = (quantia * 100).round if quantia.is_a?(Numeric)
227
+ @quantia = extrai_quantia_como_inteiro(quantia) if quantia.is_a?(String)
228
+ end
229
+
230
+ def parte_inteira
231
+ quantia_sem_separacao_milhares[0, quantia_sem_separacao_milhares.length - QUANTIDADE_DIGITOS]
232
+ end
233
+
234
+ def parte_decimal
235
+ quantia_sem_separacao_milhares[-QUANTIDADE_DIGITOS, QUANTIDADE_DIGITOS]
236
+ end
237
+
238
+ def inteiro_com_milhar(inteiro)
239
+ return inteiro if quantidade_de_passos(inteiro).zero?
240
+
241
+ resultado = ''
242
+ quantidade_de_passos(inteiro).times do |passo|
243
+ resultado = ".#{inteiro[-QUANTIDADE_DIGITOS + passo * -QUANTIDADE_DIGITOS, QUANTIDADE_DIGITOS]}#{resultado}"
244
+ end
245
+ resultado = inteiro[0, digitos_que_sobraram(inteiro)] + resultado
246
+ resultado.gsub(/^(-?)\./, '\1')
247
+ end
248
+
249
+ def quantia_sem_separacao_milhares
250
+ format('%.2f', (@quantia.to_f / PRECISAO_DECIMAL)).gsub(SEPARADOR_MILHAR, SEPARADOR_FRACIONARIO)
251
+ end
252
+
253
+ def quantidade_de_passos(inteiro)
254
+ resultado = inteiro.length / QUANTIDADE_DIGITOS
255
+ resultado = (resultado - 1) if (inteiro.length % QUANTIDADE_DIGITOS).zero?
256
+ resultado
257
+ end
258
+
259
+ def digitos_que_sobraram(inteiro)
260
+ inteiro.length - (quantidade_de_passos(inteiro) * QUANTIDADE_DIGITOS)
261
+ end
262
+
263
+ def quantia_valida?(quantia)
264
+ return false if quantia.is_a?(String) && !quantia_respeita_formato?(quantia)
265
+
266
+ quantia.is_a?(String) || quantia.is_a?(Numeric)
267
+ end
268
+
269
+ def extrai_quantia_como_inteiro(quantia)
270
+ return sem_milhar(::Regexp.last_match(2), ::Regexp.last_match(5), '.') if FORMATO_VALIDO_BR =~ quantia
271
+ return unless FORMATO_VALIDO_EUA =~ quantia
272
+
273
+ sem_milhar(::Regexp.last_match(2), ::Regexp.last_match(5), ',')
274
+ end
275
+
276
+ def sem_milhar(parte_inteira, parte_decimal, delimitador_de_milhar)
277
+ (inteiro(parte_inteira, delimitador_de_milhar) + decimal(parte_decimal)).to_i
278
+ end
279
+
280
+ def inteiro(inteiro_com_separador_milhar, separador)
281
+ return inteiro_com_separador_milhar.gsub(separador, '') unless inteiro_com_separador_milhar.blank?
282
+
283
+ ''
284
+ end
285
+
286
+ def decimal(parte_fracionaria)
287
+ unless parte_fracionaria.blank?
288
+ return sem_delimitador_decimal(parte_fracionaria) if parte_fracionaria.length == 3
289
+ return "#{sem_delimitador_decimal(parte_fracionaria)}0" if parte_fracionaria.length == 2
290
+ end
291
+ '00'
292
+ end
293
+
294
+ def sem_delimitador_decimal(parte_fracionaria)
295
+ parte_fracionaria.to_s.gsub(/[.|,]/, '')
296
+ end
297
+
298
+ def quantia_respeita_formato?(quantia)
299
+ return true if FORMATO_VALIDO_BR.match(quantia) || FORMATO_VALIDO_EUA.match(quantia)
300
+
301
+ false
302
+ end
303
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InssCalculator
4
+ # It extends Calculator contribution to a presentable format.
5
+ module Decorator
6
+ require 'forwardable'
7
+ # InssCalculator::Decorator::Text implements the same Calculator interface
8
+ # and its #present method presents the contribution result in a text format.
9
+ class Text
10
+ extend Forwardable
11
+
12
+ def_delegators :@calculator, :salary, :net_salary
13
+
14
+ attr_reader :calculator
15
+
16
+ def initialize(inss_calculator)
17
+ @calculator = inss_calculator
18
+ end
19
+
20
+ def contribution
21
+ Dinheiro.new(calculator.contribution).real_formatado
22
+ end
23
+
24
+ def present
25
+ "#{salary_text} #{contribution_text} #{net_salary_text}"
26
+ end
27
+
28
+ private
29
+
30
+ def salary_text
31
+ "Com o salário de #{Dinheiro.new(calculator.salary).real_formatado},"
32
+ end
33
+
34
+ def contribution_text
35
+ "sua contribuição é de #{Dinheiro.new(calculator.contribution).real_formatado}."
36
+ end
37
+
38
+ def net_salary_text
39
+ "Seu salário líquido, portanto, é de #{Dinheiro.new(calculator.net_salary).real_formatado}."
40
+ end
41
+ end
42
+ end
43
+ end
@@ -22,5 +22,9 @@ module InssCalculator
22
22
  def net_salary
23
23
  (salary - contribution).round(2)
24
24
  end
25
+
26
+ def present
27
+ raise InssCalculator::NoMethodError, message: 'Use a decorator instead!'
28
+ end
25
29
  end
26
30
  end
@@ -0,0 +1,5 @@
1
+ module InssCalculator
2
+ class NoMethodError < InssCalculator::Error
3
+ end
4
+ end
5
+
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InssCalculator
4
- VERSION = '0.4.0'
4
+ VERSION = '0.4.2'
5
5
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'inss_calculator/version'
4
+ require_relative './dinheiro'
4
5
  require_relative 'inss_calculator/discount_calculator_base'
5
6
  require_relative 'inss_calculator/discount_previdence_calculator'
6
7
  require_relative 'inss_calculator/public_inss_calculator'
@@ -12,6 +13,7 @@ require_relative 'inss_calculator/fifth_discount_calculator'
12
13
  require_relative 'inss_calculator/sixth_discount_calculator'
13
14
  require_relative 'inss_calculator/seventh_discount_calculator'
14
15
  require_relative 'inss_calculator/eigth_discount_calculator'
16
+ require_relative 'inss_calculator/decorator/text'
15
17
 
16
18
  module InssCalculator
17
19
  class Error < StandardError; end
@@ -40,3 +42,5 @@ module InssCalculator
40
42
  EIGTH_SALARY_BASE = 52_000.55
41
43
  EIGTH_SALARY_LIMIT = Float::INFINITY
42
44
  end
45
+
46
+ require_relative 'inss_calculator/error/no_method_error'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inss_calculator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Felipe Souza
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-08 00:00:00.000000000 Z
11
+ date: 2025-01-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Calcula o valor a descontar de acordo com a faixa salarial. Os novos
14
14
  valores corrente em 2024.
@@ -29,10 +29,13 @@ files:
29
29
  - README.md
30
30
  - Rakefile
31
31
  - inss_calculator.gemspec
32
+ - lib/dinheiro.rb
32
33
  - lib/inss_calculator.rb
34
+ - lib/inss_calculator/decorator/text.rb
33
35
  - lib/inss_calculator/discount_calculator_base.rb
34
36
  - lib/inss_calculator/discount_previdence_calculator.rb
35
37
  - lib/inss_calculator/eigth_discount_calculator.rb
38
+ - lib/inss_calculator/error/no_method_error.rb
36
39
  - lib/inss_calculator/fifth_discount_calculator.rb
37
40
  - lib/inss_calculator/first_discount_calculator.rb
38
41
  - lib/inss_calculator/fourth_discount_calculator.rb