Skip to content

Commit

Permalink
wip: move templates to folder, test picture regex
Browse files Browse the repository at this point in the history
  • Loading branch information
yxuo committed Feb 20, 2024
1 parent 6262362 commit 1ef919e
Show file tree
Hide file tree
Showing 14 changed files with 262 additions and 169 deletions.
23 changes: 5 additions & 18 deletions src/cnab/cnab-remessa.service.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,17 @@
import { Injectable } from '@nestjs/common';
import { CnabFile } from './types/cnab-file.type';
import { CnabRegistro } from './types/cnab-registro.type';
import { Cnab } from './cnab';
import { getPlainRegistros, getRegistroLine } from './cnab-utils';

@Injectable()
export class CnabRemessaService {
/**
* Gerar arquivo remessa
* Generate CNAB Remessa text content from CnabFile
*/
generateRemessaCnab(cnab: CnabFile) {
const plainCnab: CnabRegistro[] = [
cnab.headerArquivo,
...cnab.lotes.reduce(
(l: CnabRegistro[], i) => [
...l,
i.headerLote,
...i.registros,
i.trailerLote,
],
[],
),
cnab.trailerArquivo,
];
generateRemessaCnab(cnab: CnabFile): string {
const plainCnab = getPlainRegistros(cnab);
const cnabTextList: string[] = [];
for (const registro of plainCnab) {
cnabTextList.push(Cnab.getRegistroLine(registro));
cnabTextList.push(getRegistroLine(registro));
}
const CNAB_EOL = '\r\n';
return cnabTextList.join(CNAB_EOL);
Expand Down
49 changes: 49 additions & 0 deletions src/cnab/cnab-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { getPictureNumberSize } from './cnab-utils';

describe('cnab-utils.ts', () => {
describe('getPictureNumberSize()', () => {
it('should return correct integer and decimal', () => {
// Act
const result9Num = getPictureNumberSize('9(15)');
const result9NumV9Num = getPictureNumberSize('9(3)V9(8)');
const result9NumInvalidDecimal = getPictureNumberSize('9(3)V');
const result9NumV9 = getPictureNumberSize('9(4)V99');
const result9NumV99 = getPictureNumberSize('9(5)V999');

// Assert
expect(result9Num).toEqual({
integer: 15,
decimal: 0,
});
expect(result9NumV9Num).toEqual({
integer: 3,
decimal: 8,
});
expect(result9NumInvalidDecimal).toEqual({
integer: 3,
decimal: 0,
});
expect(result9NumV9).toEqual({
integer: 4,
decimal: 1,
});
expect(result9NumV99).toEqual({
integer: 5,
decimal: 2,
});
});
});

// describe('getCnabPictureValue()', () => {
// it('should return Currency value', () => {
// // Act
// const result = getCnabPictureValue({
// pos: [163, 177],
// picture: '9(013)V99',
// value: '512.3',
// });
// // Assert
// expect(result).toEqual("0000000051230");
// });
// });
});
186 changes: 186 additions & 0 deletions src/cnab/cnab-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { isDate } from 'date-fns';
import format from 'date-fns/format';
import { CnabFieldType } from './enums/cnab-field-type.enum';
import { CnabField } from './types/cnab-field.type';
import { CnabRegistro } from './types/cnab-registro.type';
import { CnabLote, isCnabLote } from './types/cnab-lote.type';

/**
* Convert CNAB Registro into CNAB file line
*/
export function getRegistroLine(registro: CnabRegistro) {
let line = '';
for (const value of Object.values(registro)) {
line += getCnabPictureValue(value);
}
return line;
}

/**
* From CnabField get formatted value applying Picture
*/
export function getCnabPictureValue(item: CnabField) {
if (getCnabFieldType(item) === CnabFieldType.Currency) {
return formatCurrency(item);
} else if (getCnabFieldType(item) === CnabFieldType.Date) {
return formatDate(item);
} else if (getCnabFieldType(item) === CnabFieldType.Number) {
return formatNumber(item);
} else {
// Text
return formatCnabText(item);
}
}
export function getCnabFieldType(item: CnabField): CnabFieldType {
let result: CnabFieldType | undefined = undefined;
if (item.picture.indexOf('V9') > 0) {
result = CnabFieldType.Currency;
} else if (item.picture.startsWith('9')) {
if (item.dateFormat) {
result = CnabFieldType.Date;
} else {
result = CnabFieldType.Number;
}
} else if (item.picture.startsWith('X')) {
result = CnabFieldType.Text;
}

if (!result) {
throw new Error(`Cant recognize picture for ${item.picture}`);
}
validateFieldFormat(item);

return result;
}

export function validateFieldFormat(item: CnabField) {
if (item.value === null) {
throw new Error('No formats allow null item value');
} else if (isNaN(Number(item.value))) {
throw new Error('Number format cant represent NaN value');
}
}

export function regexPicture(exp: any, picture: any) {
const regexResult = new RegExp(exp).exec(picture) || [];
const lenghtResult = [
Number(regexResult[1]) || 0,
Number(regexResult[3]) || 0,
];
return lenghtResult;
}

/**
* Integer: `9(<size number>)`.
*
* Decimal: There are two formats for decimal:
* 1. "V9" + 9999... The number of characters "9 after "V9" is the lenght of decimal;
* 2. `V9(<size number>)`.
*
* If regex doesn't find anything, the value is 0.
*/
export function getPictureNumberSize(picture: string): {
integer: number;
decimal: number;
} {
const regexResult =
new RegExp(/9\((\d+)\)(V9(9+|\((\d+)\)))?/g).exec(picture) || [];
return {
integer: Number(regexResult[1]) || 0,
decimal:
Number(regexResult[4]) ||
((i = regexResult[2] || '') => (/^V999*$/g.test(i) ? i.length - 2 : 0))(),
};
}

/**
* Text size: `X(<sie number>)`.
*
* If regex doesn't find anything, the value is 0.
*/
export function getPictureTextSize(item: CnabField): number {
const regexResult = new RegExp(/X\((\d+?)\)/g).exec(item.picture) || [];
return Number(regexResult[0]) || 0;
}

/**
* Alphanumeric (picture X): text on the left, fill spaces on the right, uppercase only.
*
* Recommended no special characters (e.g. "Ç", "?", "Á" etc).
*
* Unused fields must be filled with spaces.
*/
export function formatCnabText(item: CnabField) {
const out = regexPicture(/X\((\w+?)\)/g, item.picture);
const size = Number(out[0]);
return String(item.value).slice(0, size).padEnd(size, ' ');
}

/**
* Numeric (picture 9): numeric text on the right, zeroes on the left,
* unused fields must be filled with zeroes.
*
* Decimal indicator (picture V): indicates number of decimal places.
* Example: if picture is "9(5)V9(2)" or "9(5)V999" the number "876,54" must be "0087654".
*/
export function formatNumber(item: CnabField): string {
const out = regexPicture(/9\((\d+?)\)/g, item.picture);
const size = Number(out[0]);
return String(Number(item.value).toFixed(0)).padStart(size, '0');
}

/**
* Format cnab field as date string
*
* @param item for string data inputs use only numbers:
* - `HHMMSS` = "992359"
* - `ddMMyyyy` = "31121030"
* - `ddMMyy` = "311230"
*/
export function formatDate(item: CnabField) {
if (isDate(item.value) && item.dateFormat) {
return format(item.value, item.dateFormat);
} else {
const out = regexPicture(/9\((\w+?)\)/g, item.picture);
const itemValue = String(item.value);
const size = Number(out[0]);
return itemValue.slice(size).padStart(size, '0');
}
}

export function formatCurrency(item: CnabField) {
const out = regexPicture(/9\((\d+)\)(V(9+))?/g, item.picture);
const integer = Number(out[0]);
const decimal = Number(out[1]) || 0;
const result = String(Number(item.value).toFixed(decimal))
.replace('.', '')
.padStart(integer + decimal, '0');
if (result.length > integer + decimal) {
throw new Error(
`Number "${result}" is too big to fit Currency Picture ` +
`${item.picture} (picture lenght: ${integer + decimal})`,
);
}
return result;
}

export function getPlainRegistros(
cnab: Record<string, CnabRegistro | CnabRegistro[] | CnabLote[]>,
): CnabRegistro[] {
// return
const plainCnab: CnabRegistro[] = [];
for (const value of Object.values(cnab)) {
if (Array.isArray(value)) {
if (isCnabLote(value)) {
for (const lote of value as CnabLote[]) {
plainCnab.push(...getPlainRegistros(lote));
}
} else {
plainCnab.push(...(value as CnabRegistro[]));
}
} else {
plainCnab.push(value);
}
}
return plainCnab;
}
3 changes: 0 additions & 3 deletions src/cnab/cnab.spec.ts

This file was deleted.

Loading

0 comments on commit 1ef919e

Please sign in to comment.