Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/pt-br/utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,21 @@ parseCurrency('10.756,11'); // 10756.11
parseCurrency('R$ 10.59'); // 10.59
```

## describeCurrency

Transforma uma string ou numero para uma string descritiva

```javascript
import { describeCurrency } from '@brazilian-utils/brazilian-utils';

describeCurrency(10); // dez reais e zero centavos
describeCurrency(10.75); // dez reais e setenta e cinco centavos
describeCurrency('10.75'); // dez reais e setenta e cinco centavos
describeCurrency('R$ 10,75'); // dez reais e setenta e cinco centavos
describeCurrency('R$ 10.756', false); // dez mil setecentos e cinquenta e seis
describeCurrency('R$ 10.756,11', false); // dez mil setecentos e cinquenta e seis
```

## getStates

Retorna todos os estados brasileiros.
Expand Down
15 changes: 15 additions & 0 deletions docs/utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,21 @@ parseCurrency('10.756,11'); // 10756.11
parseCurrency('R$ 10.59'); // 10.59
```

## describeCurrency

Transforms a string or number to an described string

```javascript
import { describeCurrency } from '@brazilian-utils/brazilian-utils';

describeCurrency(10); // dez reais e zero centavos
describeCurrency(10.75); // dez reais e setenta e cinco centavos
describeCurrency('10.75'); // dez reais e setenta e cinco centavos
describeCurrency('R$ 10,75'); // dez reais e setenta e cinco centavos
describeCurrency('R$ 10.756', false); // dez mil setecentos e cinquenta e seis
describeCurrency('R$ 10.756,11', false); // dez mil setecentos e cinquenta e seis
```

## getStates

Get all Brazilian states.
Expand Down
1 change: 1 addition & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('Public API', () => {
'capitalize',
'formatCurrency',
'parseCurrency',
'describeCurrency'
];

Object.keys(API).forEach((method) => {
Expand Down
128 changes: 127 additions & 1 deletion src/utilities/currency/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,130 @@
import { format, parse } from '.';
import { describe as describeCurrency, format, parse, STRINGS } from '.';

describe('describe', () => {
test('should describe irregular numbers', () => {
expect.assertions(STRINGS.irregular.length * 3);
for (let i = 0; i < 20; i++) {
const irregular = STRINGS.irregular[i];
expect(describeCurrency(i, false)).toBe(irregular);
expect(describeCurrency(i)).toBe(`${irregular} reais e zero centavos`);
expect(describeCurrency(i + 0.05)).toBe(`${irregular} reais e cinco centavos`);
}
});

test('should describe rounded ten numbers', () => {
expect.assertions(STRINGS.ten.length * 2);
for (let i = 0; i < 10; i++) {
const n = i * 10;
const ten = STRINGS.irregular[n] ?? STRINGS.ten[i];
expect(describeCurrency(n, false)).toBe(ten);
expect(describeCurrency(n)).toBe(`${ten} reais e zero centavos`);
}
});

test('should describe composed ten numbers', () => {
expect.assertions((STRINGS.ten.length - 2) * 3);
for (let i = 2; i < 10; i++) {
const n = i * 10;
const singular = STRINGS.irregular[i];
const ten = STRINGS.irregular[n] ?? STRINGS.ten[i];
const composed = ten === singular ? ten : `${ten} e ${singular}`;
expect(describeCurrency(n + 0.55)).toBe(`${ten} reais e cinquenta e cinco centavos`);
expect(describeCurrency(n + i)).toBe(`${composed} reais e zero centavos`);
expect(describeCurrency(n + i + 0.55)).toBe(`${composed} reais e cinquenta e cinco centavos`);
}
});

test('should describe hundred numbers', () => {
expect.assertions((STRINGS.hundred.length - 1) * 7);
for (let i = 1; i < 10; i++) {
const n = i * 100;
const singular = STRINGS.irregular[i];
const hundred = STRINGS.hundred[i];
const singularHundred = STRINGS.hundred[i].replace(/nto$/i, 'm');
expect(describeCurrency(n, false)).toBe(singularHundred);
expect(describeCurrency(n)).toBe(`${singularHundred} reais e zero centavos`);
expect(describeCurrency(n + 0.66)).toBe(`${singularHundred} reais e sessenta e seis centavos`);
expect(describeCurrency(n + i)).toBe(`${hundred} e ${singular} reais e zero centavos`);
expect(describeCurrency(n + 20)).toBe(`${hundred} e vinte reais e zero centavos`);
expect(describeCurrency(n + i + 20)).toBe(`${hundred} e vinte e ${singular} reais e zero centavos`);
expect(describeCurrency(n + i + 40 + 0.67)).toBe(
`${hundred} e quarenta e ${singular} reais e sessenta e sete centavos`
);
}
});

test('should describe rounded thousand numbers', () => {
expect.assertions(100 * 3);
for (let i = 1; i <= 100; i++) {
const n = i * 1000;
const thousand = describeCurrency(i, false);
expect(describeCurrency(n, false)).toBe(`${thousand} mil`);
expect(describeCurrency(n)).toBe(`${thousand} mil reais e zero centavos`);
expect(describeCurrency(n + 0.5)).toBe(`${thousand} mil reais e cinquenta centavos`);
}
});

test('should describe composed thousand numbers', () => {
expect.assertions((STRINGS.hundred.length - 1) * 6);
for (let i = 1; i < 10; i++) {
const n = i * 1000;
const singular = STRINGS.irregular[i];
const thousand = describeCurrency(n, false);
expect(describeCurrency(n + 0.66)).toBe(`${thousand} reais e sessenta e seis centavos`);
expect(describeCurrency(n + i)).toBe(`${thousand} e ${singular} reais e zero centavos`);
expect(describeCurrency(n + 20)).toBe(`${thousand} e vinte reais e zero centavos`);
expect(describeCurrency(n + 20 + i)).toBe(`${thousand} e vinte e ${singular} reais e zero centavos`);
expect(describeCurrency(n + 120 + i)).toBe(`${thousand} cento e vinte e ${singular} reais e zero centavos`);
expect(describeCurrency(n + i + 20 + 0.67)).toBe(
`${thousand} e vinte e ${singular} reais e sessenta e sete centavos`
);
}
});

test('should describe rounded million numbers', () => {
expect.assertions(100 * 3);
for (let i = 1; i <= 100; i++) {
const n = i * 1000000;
const million = describeCurrency(i, false);
const assignment = i === 1 ? 'milhão' : 'milhões';
expect(describeCurrency(n, false)).toBe(`${million} ${assignment}`);
expect(describeCurrency(n)).toBe(`${million} ${assignment} de reais e zero centavos`);
expect(describeCurrency(n + 0.5)).toBe(`${million} ${assignment} de reais e cinquenta centavos`);
}
});

test('should describe composed million numbers', () => {
expect.assertions(9 * 6);
for (let i = 1; i < 10; i++) {
const n = i * 1000000;
const singular = STRINGS.irregular[i];
const million = describeCurrency(i, false);
const assignment = i === 1 ? 'milhão' : 'milhões';
expect(describeCurrency(n + 0.66)).toBe(`${million} ${assignment} de reais e sessenta e seis centavos`);
expect(describeCurrency(n + i)).toBe(`${million} ${assignment} e ${singular} reais e zero centavos`);
expect(describeCurrency(n + 20)).toBe(`${million} ${assignment} e vinte reais e zero centavos`);
expect(describeCurrency(n + 20 + i)).toBe(`${million} ${assignment} e vinte e ${singular} reais e zero centavos`);
expect(describeCurrency(n + 120 + i)).toBe(
`${million} ${assignment} cento e vinte e ${singular} reais e zero centavos`
);
expect(describeCurrency(n + i + 20 + 0.67)).toBe(
`${million} ${assignment} e vinte e ${singular} reais e sessenta e sete centavos`
);
}
});

test('should describe formatted numbers', () => {
expect(describeCurrency('R$ 1', false)).toBe('um');
expect(describeCurrency('R$ 2,00', false)).toBe('dois');
expect(describeCurrency('R$ 10.00')).toBe('dez reais e zero centavos');
expect(describeCurrency('R$ 105,01')).toBe('cento e cinco reais e um centavos');
expect(describeCurrency('R$ 105,1')).toBe('cento e cinco reais e dez centavos');
expect(describeCurrency('R$ 105,10')).toBe('cento e cinco reais e dez centavos');
expect(describeCurrency('R$ 1050,25')).toBe('um mil e cinquenta reais e vinte e cinco centavos');
expect(describeCurrency('R$ 105000,99')).toBe('cento e cinco mil reais e noventa e nove centavos');
expect(describeCurrency('R$ 105000000,00')).toBe('cento e cinco milhões de reais e zero centavos');
});
});

describe('format', () => {
test('should format Currency into BRL', () => {
Expand Down
146 changes: 146 additions & 0 deletions src/utilities/currency/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,149 @@
import { onlyNumbers } from '../../helpers';

export const STRINGS = {
irregular: [
'zero',
'um',
'dois',
'três',
'quatro',
'cinco',
'seis',
'sete',
'oito',
'nove',
'dez',
'onze',
'doze',
'treze',
'catorze',
'quinze',
'dezesseis',
'dezessete',
'dezoito',
'dezenove',
],
ten: ['zero', 'dez', 'vinte', 'trinta', 'quarenta', 'cinquenta', 'sessenta', 'setenta', 'oitenta', 'noventa'],
hundred: [
'zero',
'cento',
'duzentos',
'trezentos',
'quatrocentos',
'quinhentos',
'seiscentos',
'setecentos',
'oitocentos',
'novecentos',
],
};

function conditionalValue<TResult>(isTruthy: unknown, result: TResult): TResult | undefined {
return isTruthy ? result : undefined;
}

function describeIrregular(digits: string): string {
const index = Number(digits);
return STRINGS.irregular[index];
}

function describeTen(digits: string): string {
const isIrregular = Number(digits) < 20;
if (isIrregular) return describeIrregular(digits);

const isRounded = /^\d0$/i.test(digits);
const [tenValue, unitValue] = digits.split('');
const ten = STRINGS.ten[Number(tenValue)];
if (isRounded) return ten;

const unit = describeIrregular(unitValue);
return `${ten} e ${unit}`;
}

function describeHundred(digits: string): string {
const isRounded = /^\d00$/i.test(digits);
const hundredValue = digits.slice(0, 1);
const hundred = STRINGS.hundred[Number(hundredValue)];
if (isRounded) return hundred.replace(/nto$/i, 'm');

const tenValue = digits.slice(1);
const ten = describeTen(tenValue);
return `${hundred} e ${ten}`;
}

function describeThousand(digits: string): string {
const isRounded = /^\d{1,3}000$/i.test(digits);
const thousandValue = digits.replace(/\d{3}$/i, '');
const thousand = switchNumber(thousandValue);
if (isRounded) return `${thousand} mil`;

const hundredValue = digits.replace(/.+(\d{3})$/i, '$1');
const hundred = switchNumber(Number(hundredValue));
const goesHundred = Number(hundredValue) > 0;
const goesAnd = goesHundred && Number(hundredValue) <= 100;
return [thousand, ' mil', conditionalValue(goesAnd, ' e'), conditionalValue(goesHundred, ` ${hundred}`)].join('');
}

function describeMillion(digits: string): string {
const isRounded = /^\d{1,3}000000$/i.test(digits);
const millionValue = digits.replace(/\d{6}$/i, '');
const assignment = Number(millionValue) === 1 ? 'milhão' : 'milhões';
const million = switchNumber(millionValue);
if (isRounded) return `${million} ${assignment}`;

const thousandValue = digits.replace(/.+(\d{6})$/i, '$1');
const thousand = switchNumber(Number(thousandValue));
const goesThousand = Number(thousandValue) > 0;
const goesAnd = goesThousand && Number(thousandValue) <= 100;
return [
million,
` ${assignment}`,
conditionalValue(goesAnd, ' e'),
conditionalValue(goesThousand, ` ${thousand}`),
].join('');
}

function switchNumber(value: string | number): string {
const digits = onlyNumbers(value);

const isMillion = /^\d{7,9}$/i.test(digits);
if (isMillion) return describeMillion(digits);

const isThousand = /^\d{4,6}$/i.test(digits);
if (isThousand) return describeThousand(digits);

const isHundred = /^\d{3}$/i.test(digits);
if (isHundred) return describeHundred(digits);

const isTen = /^\d{2}$/i.test(digits);
if (isTen) return describeTen(digits);

return describeIrregular(digits);
}

export function describe(value: string | number, isCash: boolean = true): string {
if (typeof value === 'number') value = String(value);

let centavos = '';
const centavosExpression = /[,.]\d{1,2}$/i;
const hasCentavos = centavosExpression.test(value);
if (isCash) {
if (hasCentavos) {
const centavosValue = value.replace(/.+[,.](\d{1,2})$/i, '$1').padEnd(2, '0');
centavos = `${switchNumber(centavosValue)} centavos`;
} else {
centavos = 'zero centavos';
}
}

const reaisValue = value.replace(centavosExpression, '');
const reaisString = switchNumber(reaisValue);
const goesOf = /milh.{2,3}$/i.test(reaisString);
const reais = [reaisString, conditionalValue(goesOf && isCash, ' de'), conditionalValue(isCash, ' reais')].join('');

return [reais, conditionalValue(centavos, ` e ${centavos}`)].join('');
}

type FormatOptions = {
precision?: number;
};
Expand Down
2 changes: 1 addition & 1 deletion src/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export { isValid as isValidEmail } from './email';
export { format as formatProcessoJuridico, isValid as isValidProcessoJuridico } from './processo-juridico';
export { format as formatCEP, isValid as isValidCEP } from './cep';
export { format as formatBoleto, isValid as isValidBoleto } from './boleto';
export { format as formatCurrency, parse as parseCurrency } from './currency';
export { format as formatCurrency, parse as parseCurrency, describe as describeCurrency } from './currency';
export { format as formatCPF, generate as generateCPF, isValid as isValidCPF } from './cpf';
export { format as formatCNPJ, generate as generateCNPJ, isValid as isValidCNPJ } from './cnpj';
export { capitalize } from './capitalize';
Expand Down