@jackiemacklein/nettz-utils
Version:
Serviços de imagem, e-mail, códigos de barras, utilitários numéricos e componentes React para apps Node.js com TypeScript
179 lines (178 loc) • 6.65 kB
JavaScript
;
/**
* @author Jackiê Macklein
* @company Onside tecnologia/Nettz
* @copyright Todos direitos reservados.
* @description Serviço para análise e conversão de códigos de barras
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BarcodeType = void 0;
exports.analyzeBarcode = analyzeBarcode;
exports.convertLineToBarcode = convertLineToBarcode;
exports.validateDVModulo11 = validateDVModulo11;
exports.checkBarcodeType = checkBarcodeType;
var BarcodeType;
(function (BarcodeType) {
BarcodeType["BOLETO_BANCARIO"] = "boleto_bancario";
BarcodeType["TRIBUTO"] = "tributo";
BarcodeType["FORMATO_INVALIDO"] = "formato_invalido";
})(BarcodeType || (exports.BarcodeType = BarcodeType = {}));
function convertTributoLineToBarcode(line) {
const clean = line.replace(/\D/g, "");
if (clean.length !== 48)
throw new Error("Linha digitável de tributo deve ter 48 dígitos");
// remove os 4 DVs de campo
return (clean.substring(0, 11) + // bloco 1
clean.substring(12, 23) + // bloco 2
clean.substring(24, 35) + // bloco 3
clean.substring(36, 47) // bloco 4
); // → 44 dígitos
}
function modulo10(num) {
let soma = 0, peso = 2;
for (let i = num.length - 1; i >= 0; i--) {
let r = +num[i] * peso;
if (r > 9)
r -= 9;
soma += r;
peso = peso === 2 ? 1 : 2;
}
const resto = soma % 10;
return resto === 0 ? 0 : 10 - resto;
}
function validateDVTributo(barcode) {
const dv = +barcode[3]; // 4.º dígito
const corpo = barcode.slice(0, 3) + barcode.slice(4); // 43 dígitos
const idValor = barcode[2]; // 3.º dígito
const calc = idValor === "6" || idValor === "7"
? modulo10(corpo)
: calculateModulo11Boleto(corpo); // já existe na sua lib
return dv === calc;
}
/**
* Função para analisar um código de barras e retornar um resultado
* @param code - Código de barras a ser analisado
* @returns Resultado da análise do código de barras
*/
function analyzeBarcode(code) {
const cleanCode = code.replace(/\D/g, "");
if (![44, 47, 48].includes(cleanCode.length))
return {
isValid: false,
message: "Formato inválido",
type: BarcodeType.FORMATO_INVALIDO,
};
const type = checkBarcodeType(cleanCode);
// ---------------- BOLETO BANCÁRIO ---------------------------------------
if (type === BarcodeType.BOLETO_BANCARIO) {
const barcode = cleanCode.length === 47 ? convertLineToBarcode(cleanCode) : cleanCode;
if (!validateDVModulo11(barcode))
return { isValid: false, message: "Dígito verificador inválido", type };
const fatorVenc = +barcode.substring(5, 9);
const base = new Date(1997, 9, 7); // 07/10/1997
const venc = new Date(base.getTime() + fatorVenc * 86400000);
const valor = +barcode.substring(9, 19) / 100;
return {
isValid: true,
type,
value: valor.toFixed(2),
dueDate: isNaN(venc.getTime()) ? null : venc.toLocaleDateString("pt-BR"),
};
}
// ----------------- TRIBUTO ----------------------------------------------
let barcode = cleanCode;
if (cleanCode.length === 48)
barcode = convertTributoLineToBarcode(cleanCode);
if (!validateDVTributo(barcode))
return { isValid: false, message: "Dígito verificador inválido", type };
const valor = +barcode.substring(4, 15) / 100; // pos. 05-15 → 11 dígitos
// algumas entidades gravam AAAAMMDD em pos. 20-27 (campo livre)
let dueDate = null;
const dataCampoLivre = barcode.substring(19, 27);
if (/^\d{8}$/.test(dataCampoLivre) && dataCampoLivre !== "00000000") {
const y = +dataCampoLivre.slice(0, 4);
const m = +dataCampoLivre.slice(4, 6) - 1;
const d = +dataCampoLivre.slice(6, 8);
const dObj = new Date(y, m, d);
if (!isNaN(dObj.getTime()))
dueDate = dObj.toLocaleDateString("pt-BR");
}
return {
isValid: true,
type: BarcodeType.TRIBUTO,
value: valor.toFixed(2),
dueDate,
message: dueDate
? undefined
: "Tributo não possui vencimento definido no código",
};
}
/**
* Função para converter uma linha digitável para um código de barras
* @param line - Linha digitável a ser convertida
* @returns Código de barras convertido
*/
function convertLineToBarcode(line) {
const clean = line.replace(/\D/g, "");
const bloco1 = clean.substring(0, 9);
const bloco2 = clean.substring(10, 20);
const bloco3 = clean.substring(21, 31);
const bloco5 = clean.substring(33, 47); // Fator + Valor
const codeWithoutDV = bloco1.substring(0, 4) + // banco + moeda
bloco5 + // fator + valor
bloco1.substring(4) + // restante do campo 1
bloco2 + // campo 2 completo
bloco3; // campo 3 completo
const dv = calculateModulo11Boleto(codeWithoutDV);
return codeWithoutDV.substring(0, 4) + dv + codeWithoutDV.substring(4);
}
/**
* Função para calcular o dígito verificador do código de barras
* @param code - Código de barras a ser calculado
* @returns Dígito verificador calculado
*/
function calculateModulo11Boleto(code) {
const reversed = code.split("").reverse();
let soma = 0;
let peso = 2;
for (const digito of reversed) {
soma += parseInt(digito) * peso;
peso = peso === 9 ? 2 : peso + 1;
}
const resto = soma % 11;
if (resto === 0 || resto === 1)
return 0;
if (resto === 10)
return 1;
return 11 - resto;
}
/**
* Função para validar o dígito verificador do código de barras
* @param code - Código de barras a ser validado
* @returns true se o dígito verificador for válido, false caso contrário
*/
function validateDVModulo11(code) {
const dv = parseInt(code.charAt(4));
const base = code.substring(0, 4) + code.substring(5);
const calculated = calculateModulo11Boleto(base);
return dv === calculated;
}
/**
* Função para verificar o tipo de código de barras
* @param code - Código de barras a ser verificado
* @returns Tipo de código de barras
*/
function checkBarcodeType(code) {
const clean = code.replace(/\D/g, "");
if (clean.length === 44 || clean.length === 47) {
return clean.startsWith("8")
? BarcodeType.TRIBUTO
: BarcodeType.BOLETO_BANCARIO;
}
else if (clean.length === 48) {
return BarcodeType.TRIBUTO;
}
else {
return BarcodeType.FORMATO_INVALIDO;
}
}