Skip to content

Instantly share code, notes, and snippets.

@annibal
Last active November 10, 2023 15:54
Show Gist options
  • Save annibal/2fcec8401707ad66c43cb77692ba7274 to your computer and use it in GitHub Desktop.
Save annibal/2fcec8401707ad66c43cb77692ba7274 to your computer and use it in GitHub Desktop.
Functions to generate Brazil's Document validator number - CPF / CNPJ
/**
* Receives a part of the document and returns a validation digit
* Used mostly for CPF or CNPJ
*
* Ex.:
* getVerificationDigit("000000001") // 9
* getVerificationDigit("0000000019") // 1
* getVerificationDigit("100200300") // 8
* getVerificationDigit(1, 9) // 9
* getVerificationDigit(19, 9) // 1
* @param partialDoc String|Number - the document number
* @param minLen Number - how many digits are expected (in case the doc was parsed from a string with leading zeros)
* @returns Number
*/
function getVerificationDigit(partialDoc, minLen) {
const str = partialDoc.toString();
let len = str.length;
if (minLen) {
len = minLen;
}
let digit = str.split("").reduce((all, digit, index) => {
return all + digit * (len + 1 - index);
}, 0);
digit = 11 - (digit % 11);
if (digit > 9) digit = 0;
return digit;
}
/**
* Removes ".", "-", "/" and anything else that is not a number from the document
*
* Ex.:
* parseDoc("000.000.001-91") // 191
* parseDoc("192.168.0.1") // 19216801
* @param strDoc String - The document with all visual aid characters
* @returns Number
*/
function parseDoc(strDoc) {
const strDocOnlyNumbers = (strDoc + "").replace(/[^0-9]/gi, "");
return parseInt(strDocOnlyNumbers);
}
/**
* Adds ".", "-", "/" and other aiding characters to a document.
* The character "0" in format will be replaced by the corresponding
* number at the same index in intDoc, traversing from the last to
* the first character in format.
*
* Ex.:
* formatDoc(191, "000.000.000-00") // "000.000.001-91"
* formatDoc(11122233300, "000.000.000-00") // "111.222.333-00"
* formatDoc(123456, "0 / 0 [0,0]") // "3 / 4 [5,6]"
* formatDoc(999, "0 / 0 [0,0]") // "0 / 9 [9,9]"
* @param intDoc Number - The document as a number
* @param format String - Format that will receive the numbers of the doc
* @returns String
*/
function formatDoc(intDoc, format) {
const strDoc = intDoc.toString();
let pos = strDoc.length;
return format
.split("")
.reverse()
.map((fChar) => {
if (fChar === "0") {
pos -= 1;
if (pos < 0) {
return fChar;
} else {
return strDoc.charAt(pos);
}
} else {
return fChar;
}
})
.reverse()
.join("");
}
/**
* Generates a random document number with a specific length.
*
* Ex.:
* generateRandomDoc(9) // "523784190"
* generateRandomDoc(9) // "154768391"
* generateRandomDoc(3) // "110"
* @param length Number - how many chars should the doc have
* @returns String - a string of numbers with the provided length
*/
function generateRandomDoc(length) {
return Array(length)
.fill("")
.map(() => Math.round(Math.random()))
.join("");
}
/**
* Adds the verification digits to the document and returns the whole string.
*
* Ex.:
* addVerificationDigits("000000001", 2) // "00000000191"
* addVerificationDigits("100200300", 2) // "10020030088"
* @param doc String|Number - the document. Must be a string because of the leading zeros
* @param amount Number - How many digits to add
*/
function addVerificationDigits(doc, amount) {
let newDoc = doc.toString();
for (let i = 0; i < amount; i++) {
newDoc = newDoc + "" + getVerificationDigit(newDoc);
}
return newDoc;
}
/**
* Checks if the document string is valid
*
* Ex.:
* validateDoc("00000000191", 2) // true
* validateDoc("00000000155", 2) // false
* validateDoc("10020030088", 2) // true
*
* If passing digits as null or 0, will always return isValid=true
*
* If using debug=true, will return an object with more info:
* validateDoc("00000000155", 2, true) // {doc: '00000000155', partial: '000000001', compare: '00000000191', isValid: false }
* @param doc String
* @param digits Number - default 2
* @param debug Boolean - default false - if true, the return value will be an object
* @returns Boolean - true or false, or Object({ doc, partial, compare, isValid }) if debug=true
*/
function validateDoc(doc, digits = 2, debug = false) {
const partialDoc = (digits === null || digits === 0)
? doc.toString()
: doc.toString().slice(0, digits * -1);
const compareDoc = addVerificationDigits(partialDoc, digits);
const isValid = doc === compareDoc;
if (debug) {
return { doc, partial: partialDoc, compare: compareDoc, isValid };
}
return isValid;
}
// validateCPF("000.000.001-91") // {valid: true, formatted: '000.000.001-91'}
// validateCPF("000000001-91") // {valid: true, formatted: '000.000.001-91'}
// validateCPF(00000000191) // {valid: true, formatted: '000.000.001-91'}
// validateCPF(191) // {valid: true, formatted: '000.000.001-91'}
// validateCPF("000.000.001-55") // {valid: false, formatted: '000.000.001-55'}
// validateCPF("155") // {valid: false, formatted: '000.000.001-55'}
// validateCPF("19") // {valid: false, formatted: '000.000.000-19'}
function validateCPF(CPF) {
const doc = parseDoc(CPF);
const docZero = doc.toString().padStart(11, "0");
const valid = validateDoc(docZero, 2);
const formatted = formatDoc(doc, "000.000.000-00");
return { valid, formatted };
}
// validateCNPJ("00.000.000/0001-91") // { valid: true, formatted: '00.000.001/0001-36'}
// validateCNPJ("00.000.001/0001-36") // { valid: true, formatted: '00.000.001/0001-91'}
// validateCNPJ(191) // {valid: true, formatted: '00.000.000/0001-91'}
// validateCNPJ(1000136) // {valid: true, formatted: '00.000.001/0001-36'}
// validateCNPJ("12312312312") // {valid: false, formatted: '00.012.312/3123-12'}
function validateCNPJ(CNPJ) {
const doc = parseDoc(CNPJ);
const docZero = doc.toString().padStart(14, "0");
const valid = validateDoc(docZero, 2);
const formatted = formatDoc(doc, "00.000.000/0000-00");
return { valid, formatted };
}
/**
* Gets a part of a document and generates all possible full documents
*
* Ex.:
* getAllPossibleDocs("123.***.789-**", 2) // Array[100] like ["123.000.789-02", "123.001.789-58"]
* @param part String - what part of the doc you have, and the rest filled with "*"
* @param digits Number - how many chars of the "part" are the verification digits, counting from the end
* @param format String - how to format the returning value
* @param start Number - paginate results, starting index
* @param limit Number - paginate results, max return size
*/
function getAllPossibleDocs({ part, digits, format, start = 0, limit }) {
const paramDoc = part.toString().replace(/[^0-9*]/gi, "");
const wildChars = paramDoc.replace(/[^*]/gi, "").length;
const possibilities = 10 ** wildChars;
// console.log({ paramDoc, wildChars, possibilities, start, limit })
const res = [];
for (let idx = start; idx < possibilities; idx++) {
const strIdx = idx.toString().padStart(wildChars, "0");
let pos = strIdx.length;
const doc = paramDoc
.split("")
.reverse()
.map((fChar) => {
if (fChar === "*") {
pos -= 1;
return pos < 0 ? "0" : strIdx.charAt(pos);
} else {
return fChar;
}
})
.reverse()
.join("");
const isValid = validateDoc(doc, digits)
// console.log(idx, strIdx, doc, formatDoc(doc, format), isValid);
if (isValid) {
if (format) {
res.push(formatDoc(doc, format));
} else {
res.push(doc);
}
}
if (limit != null && res.length >= limit) break;
}
return res;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment