Last active
June 27, 2016 22:43
-
-
Save fidelisrafael/ebf9403f42dba89100d12a95e8a3ed06 to your computer and use it in GitHub Desktop.
CPF & CNPJ validator and formatter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require './documents/base' | |
| require './documents/cpf' | |
| require './documents/cnpj' | |
| module BRDocuments | |
| VERSION = '0.0.1' | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module BRDocuments | |
| class Base | |
| VERSION = '0.0.1' | |
| CONSTANTS_MAP = [ | |
| :first_digit_verify_mask, | |
| :last_digit_verify_mask, | |
| :division_factor_modulo, | |
| :valid_format_regexp, | |
| :pretty_format_mask | |
| ] | |
| class << self | |
| def valid?(document_number) | |
| # remove all non digits and return an array to be matched with mask | |
| normalized_document = normalize_document_number(document_number) | |
| # remove last two digits to be verified | |
| last_digits = normalized_document.slice!(-2,2).map(&:to_i) | |
| digits = calculate_last_two_verify_digits(normalized_document) | |
| last_digits == digits | |
| end | |
| def invalid?(document_number) | |
| !valid?(document_number) | |
| end | |
| def calculate_last_two_verify_digits(document_number) | |
| first_mask, last_mask = get_first_digit_verify_mask, get_last_digit_verify_mask | |
| division_modulo = get_division_factor_modulo | |
| document_number = normalize_document_number(document_number, first_mask.size) | |
| first_verify_digit = calculate_verify_digit(document_number, first_mask, division_modulo) | |
| # Add first verify digit to document number to match second digit verify mask | |
| current_document = document_number.dup << first_verify_digit | |
| last_verify_digit = calculate_verify_digit(current_document, last_mask, division_modulo) | |
| [first_verify_digit, last_verify_digit] | |
| end | |
| def calculate_verify_digit(document_number, mask, division_factor) | |
| digits = calculate_digits_data(document_number, mask, division_factor) | |
| digits[:verify_digit].to_i | |
| end | |
| def calculate_digits_data(document_number, mask, division_factor) | |
| sum = calculate_digits_sum(document_number, mask) | |
| quotient = (sum / division_factor) | |
| rest = (sum % division_factor) | |
| { sum: sum, quotient: quotient, rest: rest, verify_digit: digit_verify(rest, division_factor) } | |
| end | |
| def calculate_digits_sum(document_number, mask) | |
| document_number.each_with_index.map {|n,i| n.to_i * mask[i].to_i }.reduce do |value, total| | |
| total += value | |
| end.to_f | |
| end | |
| def digit_verify(quotient_rest, division_factor) | |
| # thats the rule, if quotient_rest < 2, so its became 0 | |
| return 0 if quotient_rest < 2 | |
| (division_factor - quotient_rest).to_i | |
| end | |
| def normalize_document_number(document_number, length = nil) | |
| document_number = clear_document_number(document_number).split(//).map(&:to_i) | |
| length.nil? ? document_number : document_number[0, length] | |
| end | |
| # remove any non digit from document number(this will help regexp to be validated) | |
| def clear_document_number(document_number) | |
| document_number.to_s.gsub(/[^(\d+)]/, '') | |
| end | |
| def pretty_formatted(document_number) | |
| return "" if normalize_document_number(document_number).empty? | |
| numbers = document_number.to_s.scan(get_valid_format_regexp).flatten | |
| # document has a value but it's not valid | |
| return "" if numbers.empty? | |
| get_pretty_format_mask % numbers | |
| end | |
| CONSTANTS_MAP.each do |const_identifier| | |
| define_method "get_#{const_identifier}" do | |
| const_name = const_identifier.to_s.upcase | |
| self.const_get(const_identifier.upcase) | |
| end | |
| define_method const_identifier do |arg| | |
| const_name = const_identifier.to_s.upcase | |
| self.const_set(const_name, arg) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| module BRDocuments | |
| class CPF < Base | |
| # MOD 11 | |
| division_factor_modulo 11 | |
| first_digit_verify_mask %w(10 9 8 7 6 5 4 3 2) | |
| last_digit_verify_mask %w(11 10 9 8 7 6 5 4 3 2) | |
| # match format such as: 999.999.999-99 | 999-999-999-99 | 99999999999 | |
| valid_format_regexp %r{(\d{3})[-.]?(\d{3})[-.]?(\d{3})[-.]?(\d{2})} | |
| pretty_format_mask %(%s.%s.%s-%s) | |
| end | |
| end | |
| module BRDocuments | |
| class CNPJ < Base | |
| # MOD 11 | |
| division_factor_modulo 11 | |
| first_digit_verify_mask %w(5 4 3 2 9 8 7 6 5 4 3 2) | |
| last_digit_verify_mask %w(6 5 4 3 2 9 8 7 6 5 4 3 2) | |
| # match format such as: 99.999.999/9999-99 | 99-999-999/9999-99 | 99999999/999999 | 99999999999999 | |
| valid_format_regexp %r{(\d{2})[-.]?(\d{3})[-.]?(\d{3})[\/]?(\d{4})[-.]?(\d{2})} | |
| pretty_format_mask %(%s.%s.%s/%s-%s) | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module BRDocuments | |
| class Base | |
| VERSION = '0.0.1' | |
| CONSTANTS_MAP = [ | |
| :first_digit_verify_mask, | |
| :last_digit_verify_mask, | |
| :division_factor_modulo, | |
| :valid_format_regexp, | |
| :pretty_format_mask | |
| ] | |
| class << self | |
| def valid?(document_number) | |
| # remove all non digits and return an array to be matched with mask | |
| normalized_document = normalize_document_number(document_number) | |
| # remove last two digits to be verified | |
| last_digits = normalized_document.slice!(-2,2).map(&:to_i) | |
| digits = calculate_last_two_verify_digits(normalized_document) | |
| last_digits == digits | |
| end | |
| def invalid?(document_number) | |
| !valid?(document_number) | |
| end | |
| def calculate_last_two_verify_digits(document_number) | |
| first_mask, last_mask = get_first_digit_verify_mask, get_last_digit_verify_mask | |
| division_modulo = get_division_factor_modulo | |
| document_number = normalize_document_number(document_number, first_mask.size) | |
| first_verify_digit = calculate_verify_digit(document_number, first_mask, division_modulo) | |
| # Add first verify digit to document number to match second digit verify mask | |
| current_document = document_number.dup << first_verify_digit | |
| last_verify_digit = calculate_verify_digit(current_document, last_mask, division_modulo) | |
| [first_verify_digit, last_verify_digit] | |
| end | |
| def calculate_verify_digit(document_number, mask, division_factor) | |
| digits = calculate_digits_data(document_number, mask, division_factor) | |
| digits[:verify_digit].to_i | |
| end | |
| def calculate_digits_data(document_number, mask, division_factor) | |
| sum = calculate_digits_sum(document_number, mask) | |
| quotient = (sum / division_factor) | |
| rest = (sum % division_factor) | |
| { sum: sum, quotient: quotient, rest: rest, verify_digit: digit_verify(rest, division_factor) } | |
| end | |
| def calculate_digits_sum(document_number, mask) | |
| document_number.each_with_index.map {|n,i| n.to_i * mask[i].to_i }.reduce do |value, total| | |
| total += value | |
| end.to_f | |
| end | |
| def digit_verify(quotient_rest, division_factor) | |
| # thats the rule, if quotient_rest < 2, so its became 0 | |
| return 0 if quotient_rest < 2 | |
| (division_factor - quotient_rest).to_i | |
| end | |
| def normalize_document_number(document_number, length = nil) | |
| document_number = clear_document_number(document_number).split(//).map(&:to_i) | |
| length.nil? ? document_number : document_number[0, length] | |
| end | |
| # remove any non digit from document number(this will help regexp to be validated) | |
| def clear_document_number(document_number) | |
| document_number.to_s.gsub(/[^(\d+)]/, '') | |
| end | |
| def pretty_formatted(document_number) | |
| return "" if normalize_document_number(document_number).empty? | |
| numbers = document_number.to_s.scan(get_valid_format_regexp).flatten | |
| # document has a value but it's not valid | |
| return "" if numbers.empty? | |
| get_pretty_format_mask % numbers | |
| end | |
| CONSTANTS_MAP.each do |const_identifier| | |
| define_method "get_#{const_identifier}" do | |
| const_name = const_identifier.to_s.upcase | |
| self.const_get(const_identifier.upcase) | |
| end | |
| define_method const_identifier do |arg| | |
| const_name = const_identifier.to_s.upcase | |
| self.const_set(const_name, arg) | |
| end | |
| end | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module BRDocuments | |
| class CNPJ < Base | |
| # MOD 11 | |
| division_factor_modulo 11 | |
| first_digit_verify_mask %w(5 4 3 2 9 8 7 6 5 4 3 2) | |
| last_digit_verify_mask %w(6 5 4 3 2 9 8 7 6 5 4 3 2) | |
| # match format such as: 99.999.999/9999-99 | 99-999-999/9999-99 | 99999999/999999 | 99999999999999 | |
| valid_format_regexp %r{(\d{2})[-.]?(\d{3})[-.]?(\d{3})[\/]?(\d{4})[-.]?(\d{2})} | |
| pretty_format_mask %(%s.%s.%s/%s-%s) | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module BRDocuments | |
| class CPF < Base | |
| # MOD 11 | |
| division_factor_modulo 11 | |
| first_digit_verify_mask %w(10 9 8 7 6 5 4 3 2) | |
| last_digit_verify_mask %w(11 10 9 8 7 6 5 4 3 2) | |
| # match format such as: 999.999.999-99 | 999-999-999-99 | 99999999999 | |
| valid_format_regexp %r{(\d{3})[-.]?(\d{3})[-.]?(\d{3})[-.]?(\d{2})} | |
| pretty_format_mask %(%s.%s.%s-%s) | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| FILENAME = 'br_documents_all.rb' | |
| files = ['base', 'cpf', 'cnpj'] | |
| file_data = '' | |
| files.each do |file| | |
| file_data << File.read(File.join('documents', "#{file}.rb")) | |
| end | |
| File.write(FILENAME, file_data) | |
| puts "File #{FILENAME} generated" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require './br_documents' | |
| module Expectations | |
| def _assert(expression, expected, should_be) | |
| return (expression == expected) == should_be | |
| end | |
| def _assert_p(expression, expected, should_be) | |
| puts _assert(expression, expected, should_be) | |
| end | |
| end | |
| extend Expectations | |
| ### CNPJ | |
| _assert_p(BRDocuments::CNPJ.calculate_last_two_verify_digits("112223330001"), [8,1], true) # true | |
| _assert_p(BRDocuments::CNPJ.calculate_last_two_verify_digits("112223330001"), [8,2], false) # false | |
| _assert_p(BRDocuments::CNPJ.valid?("18781203000128"), true, true) # true | |
| _assert_p(BRDocuments::CNPJ.invalid?("18781203000127"), true, true) # true | |
| _assert_p(BRDocuments::CNPJ.calculate_last_two_verify_digits("193894970001"), [0,0], true) # true | |
| _assert_p(BRDocuments::CNPJ.valid?("19389497000100"), true, true) # true | |
| _assert_p(BRDocuments::CNPJ.invalid?("193894970001001"), true, true) # true | |
| _assert_p(BRDocuments::CNPJ.invalid?(193894970001001), true, true) # true | |
| _assert_p(BRDocuments::CNPJ.pretty_formatted("193894970001001"), "19.389.497/0001-00", true) # true | |
| _assert_p(BRDocuments::CNPJ.pretty_formatted(""), "", true) # true | |
| ### CPF | |
| _assert_p(BRDocuments::CPF.calculate_last_two_verify_digits("110882416"), [1, 1], true) # true | |
| _assert_p(BRDocuments::CPF.valid?("110882416-11"), true, true) # true | |
| _assert_p(BRDocuments::CPF.invalid?("110882416-12"), true, true) # true | |
| _assert_p(BRDocuments::CPF.pretty_formatted("110882416-11"), "110.882.416-11", true) # true | |
| _assert_p(BRDocuments::CPF.pretty_formatted(""), "", true) # true |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
http://www.macoratti.net/alg_cnpj.htm