class Dinheiro include Comparable attr_reader :quantia FORMATO_VALIDO_BR = /^([R|r]\$\s*)?(([+-]?\d{1,3}(\.?\d{3})*))?(\,\d{0,2})?$/ FORMATO_VALIDO_EUA = /^([R|r]\$\s*)?(([+-]?\d{1,3}(\,?\d{3})*))?(\.\d{0,2})?$/ SEPARADOR_MILHAR = "." SEPARADOR_FRACIONARIO = "," QUANTIDADE_DIGITOS = 3 PRECISAO_DECIMAL = 100 def initialize(quantia) self.quantia = quantia end # Retorna o valor armazenado em string. # # Exemplo: # 1000.to_s ==> '1.000,00' def to_s inteiro_com_milhar(parte_inteira) + parte_decimal end # Compara com outro dinheiro se eh igual. # # Exemplo: # um_real = Dinheiro.new(1) # um_real == Dinheiro.new(1) ==> true # um_real == Dinheiro.new(2) ==> false def ==(outro_dinheiro) outro_dinheiro = Dinheiro.new(outro_dinheiro) unless outro_dinheiro.kind_of?(Dinheiro) @quantia == outro_dinheiro.quantia end # Compara com outro dinheiro se eh maior ou menor. # # Exemplo: # 1.real < 2.reais ==> true # 1.real > 2.reais ==> false # 2.real < 1.reais ==> false # 2.real > 1.reais ==> true def <=>(outro_dinheiro) outro_dinheiro = Dinheiro.new(outro_dinheiro) unless outro_dinheiro.kind_of?(Dinheiro) @quantia <=> outro_dinheiro.quantia end # Retorna a adicao entre dinheiros. def +(outro) Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia + quantia_de(outro))) end # Retorna a subtracao entre dinheiros. def -(outro) Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia - quantia_de(outro))) end # Retorna a multiplicacao entre dinheiros. def *(outro) return Dinheiro.new(to_f * outro) unless outro.kind_of? Dinheiro outro * to_f end # Retorna a divisao entre dinheiros. def /(outro) raise DivisaPorNaoEscalarError unless outro.kind_of?(Numeric) return @quantia/outro if outro == 0 soma_parcial = Dinheiro.new(0) parcelas = [] (outro-1).times do parcela = Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia/outro)) parcelas << parcela soma_parcial += parcela end parcelas << Dinheiro.new(transforma_em_string_que_represente_a_quantia(@quantia - quantia_de(soma_parcial))) end # Escreve o valor por extenso. # # Exemplo: # 1.real.por_extenso ==> 'um real' # (100.58).por_extenso ==> 'cem reais e cinquenta e oito centavos' def por_extenso (@quantia/100.0).por_extenso_em_reais end # Alias para o metodo por_extenso. alias_method :por_extenso_em_reais, :por_extenso # DEPRECATION WARNING: use por_extenso ou por_extenso_em_reais, pois este sera removido no proximo release. def to_extenso warn("DEPRECATION WARNING: use por_extenso ou por_extenso_em_reais, pois este sera removido no proximo release.") self.por_extenso end # Retorna um Float. def to_f to_s.gsub(',', '.').to_f end def coerce(outro) [ Dinheiro.new(outro), self ] end # Retorna uma string formatada em valor monetario. # # Exemplo: # Dinheiro.new(1).real ==> 'R$ 1,00' # Dinheiro.new(-1).real ==> 'R$ -1,00' def real "R$ " + to_s end # Retorna uma string formatada em valor monetario. # # Exemplo: # Dinheiro.new(1).real ==> 'R$ 1,00' # Dinheiro.new(-1).real ==> 'R$ (1,00)' def real_contabil "R$ " + contabil end # Alias para real. alias_method :reais, :real # Alias para real_contabil. alias_method :reais_contabeis, :real_contabil # Retorna uma string formatada. # # Exemplo: # Dinheiro.new(1).contabil ==> '1,00' # Dinheiro.new(-1).contabil ==> '(1,00)' def contabil if @quantia >= 0 to_s else "(" + to_s[1..-1] + ")" end end # Retorna um BigDecinal. def valor_decimal BigDecimal.new quantia_sem_separacao_milhares.gsub(',','.') end private def quantia_de(outro) outro = outro.to_f if outro.kind_of?(BigDecimal) return outro.quantia if outro.kind_of?(Dinheiro) (outro * 100).round end def transforma_em_string_que_represente_a_quantia(quantia) if /^([+-]?)(\d)$/ =~ quantia.to_s return "#{$1}0.0#{$2}" end /^([+-]?)(\d*)(\d\d)$/ =~ quantia.to_s "#{$1}#{$2.to_i}.#{$3}" end def quantia=(quantia) raise DinheiroInvalidoError unless quantia_valida?(quantia) quantia = quantia.to_f if quantia.kind_of?(BigDecimal) @quantia = (quantia * 100).round if quantia.kind_of?(Numeric) @quantia = extrai_quantia_como_inteiro(quantia) if quantia.kind_of?(String) end def parte_inteira quantia_sem_separacao_milhares[0,quantia_sem_separacao_milhares.length-QUANTIDADE_DIGITOS] end def parte_decimal quantia_sem_separacao_milhares[-QUANTIDADE_DIGITOS, QUANTIDADE_DIGITOS] end def inteiro_com_milhar(inteiro) return inteiro if quantidade_de_passos(inteiro) == 0 resultado = "" quantidade_de_passos(inteiro).times do |passo| resultado = "." + inteiro[-QUANTIDADE_DIGITOS + passo * -QUANTIDADE_DIGITOS, QUANTIDADE_DIGITOS] + resultado end resultado = inteiro[0, digitos_que_sobraram(inteiro)] + resultado resultado.gsub(/^(-?)\./, '\1') end def quantia_sem_separacao_milhares sprintf("%.2f", (@quantia.to_f / PRECISAO_DECIMAL)).gsub(SEPARADOR_MILHAR, SEPARADOR_FRACIONARIO) end def quantidade_de_passos(inteiro) resultado = inteiro.length / QUANTIDADE_DIGITOS resultado = (resultado - 1) if inteiro.length % QUANTIDADE_DIGITOS == 0 resultado end def digitos_que_sobraram(inteiro) inteiro.length - (quantidade_de_passos(inteiro) * QUANTIDADE_DIGITOS) end def quantia_valida?(quantia) return false if quantia.kind_of?(String) && !quantia_respeita_formato?(quantia) quantia.kind_of?(String) || quantia.kind_of?(Numeric) end def extrai_quantia_como_inteiro(quantia) if FORMATO_VALIDO_BR =~ quantia return sem_milhar($2, $5, '.') end if FORMATO_VALIDO_EUA =~ quantia return sem_milhar($2, $5, ',') end end def sem_milhar(parte_inteira, parte_decimal, delimitador_de_milhar) (inteiro(parte_inteira, delimitador_de_milhar) + decimal(parte_decimal)).to_i end def inteiro(inteiro_com_separador_milhar, separador) return inteiro_com_separador_milhar.gsub(separador, '') unless inteiro_com_separador_milhar.blank? "" end def decimal(parte_fracionaria) unless parte_fracionaria.blank? return sem_delimitador_decimal(parte_fracionaria) if parte_fracionaria.length == 3 return sem_delimitador_decimal(parte_fracionaria) + "0" if parte_fracionaria.length == 2 end "00" end def sem_delimitador_decimal(parte_fracionaria) "#{parte_fracionaria}".gsub(/[.|,]/, '') end def quantia_respeita_formato?(quantia) return true if FORMATO_VALIDO_BR.match(quantia) || FORMATO_VALIDO_EUA.match(quantia) false end end