Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: bq repository #197

Merged
merged 1 commit into from
Feb 28, 2024
Merged
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
2 changes: 1 addition & 1 deletion src/bigquery/bigquery.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class BigqueryService {
* Run bigquery query with complete log and error handling
* @throws `HttpException`
*/
public async runQuery(bqInstance: BQSInstances, query: string) {
public async query(bqInstance: BQSInstances, query: string) {
this.logger.debug('Query fetch started');
console.log('bigquery:', query);
try {
Expand Down
30 changes: 30 additions & 0 deletions src/bigquery/entities/transacao.bq-entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export class BqTransacao {
id: number;
data: Date;
hora: number;
datetime_transacao: Date;
datetime_processamento: Date;
datetime_captura: Date;
modo: string;
id_consorcio: string;
consorcio: string;
id_operadora: string;
operadora: string;
servico: string;
sentido: string;
id_veiculo: number;
id_cliente: string;
id_transacao: string;
tipo_pagamento: string;
tipo_transacao: string;
tipo_gratuidade: string;
tipo_integracao: string;
id_integracao: number;
latitude: number;
longitude: number;
stop_id: number;
stop_lat: number;
stop_lon: number;
valor_transacao: number;
versao: string;
}
9 changes: 9 additions & 0 deletions src/bigquery/interfaces/bq-find-transacao-by.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface IBqFetchTransacao {
cpfCnpj?: string;
startDate?: Date;
endDate?: Date;
limit?: number;
offset?: number;
getToday?: boolean;
previousDays?: boolean;
}
10 changes: 10 additions & 0 deletions src/bigquery/maps/bq-transacao-tipo-integracao.map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Ticket revenues integration type map
*/
export const BqTsansacaoTipoIntegracaoMap = {
3: 'Bu municipal',
2: 'Integração',
1: 'Transferência',
0: 'Sem integração',
4: 'Bu intermunicipal',
};
8 changes: 8 additions & 0 deletions src/bigquery/maps/bq-transacao-tipo-pagamento.map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Ticket revenues payment type map
*/
export const BqTransacaoTipoPagamentoMap = {
1: 'Cartão',
2: 'QRCode',
3: 'NFC',
};
25 changes: 25 additions & 0 deletions src/bigquery/maps/bq-transacao-tipo-transacao.map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Ticket revenues transaction type map
*
* Business rules:
* - "Integral" = Débito + Botoeria (both are considered "Integral" type).
* See {@link https://github.com/RJ-SMTR/api-cct/issues/177#issuecomment-1934531824 Issue #177, item 1 - GitHub}
*
* Matching id or literal values.
* See {@link https://github.com/RJ-SMTR/api-cct/issues/168#issuecomment-1900546567 Issue #168 - GitHub}
*/
export const BqTransacaoTipoTransacaoMap = {
/** Originally 1 = Débito */
1: 'Integral',
2: 'Recarga',
98: 'Riocard',
6: 'Bloqueio',
/** Originally 99 = Botoeria */
99: 'Integral',
21: 'Gratuidade',
3: 'Cancelamento',
4: 'Integração',
Débito: 'Integral',
/** Botoeria = payment in cash */
Botoeria: 'Integral',
};
228 changes: 228 additions & 0 deletions src/bigquery/repositories/bq-transacao.repository.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { Injectable, Logger } from '@nestjs/common';
import { BQSInstances, BigqueryService } from '../bigquery.service';
import { BqTransacao } from '../entities/transacao.bq-entity';
import { IBqFetchTransacao } from '../interfaces/bq-find-transacao-by.interface';
import { SettingsService } from 'src/settings/settings.service';
import { appSettings } from 'src/settings/app.settings';
import { BigqueryEnvironment } from 'src/settings/enums/bigquery-env.enum';
import { QueryBuilder } from 'src/utils/query-builder/query-builder';
import { isCpfOrCnpj } from 'src/utils/cpf-cnpj';
import { TRIntegrationTypeMap } from 'src/ticket-revenues/maps/ticket-revenues.map';
import { BqTsansacaoTipoIntegracaoMap } from '../maps/bq-transacao-tipo-integracao.map';
import { BqTransacaoTipoTransacaoMap } from '../maps/bq-transacao-tipo-transacao.map';
import { BqTransacaoTipoPagamentoMap } from '../maps/bq-transacao-tipo-pagamento.map';

@Injectable()
export class BqTransacaoRepositoryService {
private logger: Logger = new Logger('BqTransacaoRepositoryService', {
timestamp: true,
});

constructor(
private readonly bigqueryService: BigqueryService,
private readonly settingsService: SettingsService,
) {}

public async findTransacaoBy(
filter?: IBqFetchTransacao,
): Promise<BqTransacao[]> {
const transacoes: BqTransacao[] = (await this.fetchTransacao(filter)).data;
return transacoes;
}

private async fetchTransacao(
args?: IBqFetchTransacao,
): Promise<{ data: BqTransacao[]; countAll: number }> {
const qArgs = await this.getQueryArgs(args);
const query =
`
SELECT
CAST(t.data AS STRING) AS partitionDate,
t.hora AS processingHour,
CAST(t.datetime_transacao AS STRING) AS datetime_transacao,
CAST(t.datetime_processamento AS STRING) AS datetime_processamento,
t.datetime_captura AS captureDateTime,
t.modo AS modo,
t.servico AS servico,
t.sentido AS sentido,
t.id_veiculo AS id_veiculo,
t.id_cliente AS id_cliente,
t.id_transacao AS id_transacao,
t.${qArgs.tTipoPgto} AS tipo_pagamento,
t.tipo_transacao AS tipo_transacao,
t.id_tipo_integracao AS id_tipo_integracao,
t.id_integracao AS id_integracao,
t.latitude AS latitude,
t.longitude AS longitude,
t.stop_id AS stop_id,
t.stop_lat AS stop_lat,
t.stop_lon AS stop_lon,
CASE WHEN t.tipo_transacao = 'Integração' THEN i.valor_transacao_total ELSE t.valor_transacao END AS valor_transacao,
t.versao AS bqDataVersion,
(${qArgs.countQuery}) AS count,
'ok' AS status
FROM \`${qArgs.transacao}\` t\n` +
qArgs.joinCpfCnpj +
'\n' +
qArgs.joinIntegracao +
'\n' +
(qArgs.qWhere.length ? `WHERE ${qArgs.qWhere}\n` : '') +
`UNION ALL
SELECT ${'null, '.repeat(22)}
(${qArgs.countQuery}) AS count, 'empty' AS status` +
`\nORDER BY t.data DESC, t.hora DESC` +
(qArgs?.limit !== undefined ? `\nLIMIT ${qArgs.limit + 1}` : '') +
(qArgs?.offset !== undefined ? `\nOFFSET ${qArgs.offset}` : '');
const queryResult = await this.bigqueryService.query(
BQSInstances.smtr,
query,
);

const count: number = queryResult[0].count;
// Remove unwanted keys and remove last item (all null if empty)
let transacoes: BqTransacao[] = queryResult.map((i) => {
delete i.status;
delete i.count;
return i;
});
transacoes.pop();
transacoes = this.mapBqTransacao(transacoes);

return {
data: transacoes,
countAll: count,
};
}

private async getQueryArgs(args?: IBqFetchTransacao): Promise<{
qWhere: string;
bucket: string;
transacao: string;
integracao: string;
tTipoPgto: string;
joinCpfCnpj: string;
joinIntegracao: string;
countQuery: string;
offset?: number;
limit?: number;
}> {
const IS_BQ_PROD =
(
await this.settingsService.getOneBySettingData(
appSettings.any__bigquery_env,
true,
)
).getValueAsString() === BigqueryEnvironment.Production;
const Q_CONSTS = {
bucket: IS_BQ_PROD ? 'rj-smtr' : 'rj-smtr-dev',
transacao: IS_BQ_PROD
? 'rj-smtr.br_rj_riodejaneiro_bilhetagem.transacao'
: 'rj-smtr-dev.br_rj_riodejaneiro_bilhetagem_cct.transacao',
integracao: IS_BQ_PROD
? 'rj-smtr.br_rj_riodejaneiro_bilhetagem.integracao'
: 'rj-smtr-dev.br_rj_riodejaneiro_bilhetagem_cct.integracao',
tTipoPgto: IS_BQ_PROD ? 'tipo_pagamento' : 'id_tipo_pagamento',
};
// Args
let offset = args?.offset;
const queryBuilder = new QueryBuilder();
queryBuilder.pushOR([]);
if (args?.offset !== undefined && args.limit === undefined) {
this.logger.warn(
"fetchTicketRevenues(): 'offset' is defined but 'limit' is not." +
" 'offset' will be ignored to prevent query fail",
);
offset = undefined;
}

if (args?.startDate) {
const startDate = args.startDate.toISOString().slice(0, 10);
queryBuilder.pushAND(
`DATE(t.datetime_processamento) >= DATE('${startDate}')`,
);
}
if (args?.endDate) {
const endDate = args.endDate.toISOString().slice(0, 10);
queryBuilder.pushAND(
`DATE(t.datetime_processamento) <= DATE('${endDate}')`,
);
}
if (args?.previousDays === true) {
queryBuilder.pushAND(
'DATE(t.datetime_processamento) > DATE(t.datetime_transacao)',
);
}

queryBuilder.pushOR([]);
if (args?.getToday) {
const nowStr = new Date(Date.now()).toISOString().slice(0, 10);
queryBuilder.pushAND(
`DATE(t.datetime_processamento) = DATE('${nowStr}')`,
);
}

let qWhere = queryBuilder.toSQL();
if (args?.cpfCnpj !== undefined) {
const cpfCnpj = args.cpfCnpj;
qWhere =
isCpfOrCnpj(args?.cpfCnpj) === 'cpf'
? `b.documento = '${cpfCnpj}' AND (${qWhere})`
: `b.cnpj = '${cpfCnpj}' AND (${qWhere})`;
}

// Query
const joinCpfCnpj =
isCpfOrCnpj(args?.cpfCnpj) === 'cpf'
? `LEFT JOIN \`${Q_CONSTS.bucket}.cadastro.operadoras\` b ON b.id_operadora = t.id_operadora `
: `LEFT JOIN \`${Q_CONSTS.bucket}.cadastro.consorcios\` b ON b.id_consorcio = t.id_consorcio `;
const joinIntegracao = `INNER JOIN ${Q_CONSTS.integracao} i ON i.id_transacao = t.id_transacao`;

const countQuery =
'SELECT COUNT(*) AS count ' +
`FROM \`${Q_CONSTS.transacao}\` t\n` +
joinCpfCnpj +
'\n' +
joinIntegracao +
'\n' +
(qWhere.length ? ` WHERE ${qWhere}\n` : '');
return {
qWhere,
bucket: Q_CONSTS.bucket,
transacao: Q_CONSTS.transacao,
integracao: Q_CONSTS.integracao,
tTipoPgto: Q_CONSTS.tTipoPgto,
joinCpfCnpj,
joinIntegracao,
countQuery,
offset,
limit: args?.limit,
};
}

/**
* Convert id or some values into desired string values
*/
private mapBqTransacao(transacoes: BqTransacao[]): BqTransacao[] {
return transacoes.map((item: BqTransacao) => {
const tipo_transacao = item.tipo_transacao;
const tipo_pagamento = item.tipo_pagamento;
const tipo_integracao = item.tipo_integracao;
Object.values(TRIntegrationTypeMap[0]);
return {
...item,
paymentMediaType:
tipo_pagamento !== null
? BqTransacaoTipoPagamentoMap?.[tipo_pagamento] || tipo_pagamento
: tipo_pagamento,
transportIntegrationType:
tipo_integracao !== null
? BqTsansacaoTipoIntegracaoMap?.[tipo_integracao] || tipo_integracao
: tipo_integracao,
transactionType:
tipo_transacao !== null
? BqTransacaoTipoTransacaoMap?.[tipo_transacao] || tipo_transacao
: tipo_transacao,
};
});
}
}
26 changes: 26 additions & 0 deletions src/cnab/entities/detalhe-a.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { EntityHelper } from '../../utils/entity-helper';

@Entity()
export class DetalheA extends EntityHelper {
id_detalhe_a: number;
id_header_lote: number;
lote_servico: string;
id_cliente_favorecido: number;
tipo_finalidade_conta: string;
dt_vencimento: Date;
tipo_moeda: string;
qtde_moeda: number;
valor_lancamento: number;
num_doc_lancamento: string;
qtde_parcelas: number;
indicador_bloqueio: string;
indicador_forma_parcelamento: string;
periodo_vencimento: Date;
num_parcela: number;
data_efetivacao: Date;
valor_real_efetivado: number;
}

function Entity(): (target: typeof DetalheA) => void | typeof DetalheA {
throw new Error('Function not implemented.');
}
13 changes: 13 additions & 0 deletions src/cnab/entities/detalhe-b.entiy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { EntityHelper } from 'src/utils/entity-helper';

@Entity()
export class DetalheB extends EntityHelper {
id_detalhe_b: number;
id_detalhe_a: number;
nsr: string;
data_vencimento: Date;
}

function Entity(): (target: typeof DetalheB) => void | typeof DetalheB {
throw new Error('Function not implemented.');
}
26 changes: 26 additions & 0 deletions src/cnab/entities/header-arquivo.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { EntityHelper } from 'src/utils/entity-helper';

@Entity()
export class HeaderArquivo extends EntityHelper {
id_header_arquivo: number;
tipo_arquivo: string;
cod_banco: string;
tipo_inscricao: string;
num_inscricao: string;
cod_convenio: string;
param_transmissao: string;
agencia: string;
dv_agencia: string;
num_conta: string;
dv_conta: string;
nome_empresa: string;
dt_geracao: Date;
hr_geracao: Date;
id_transacao: number;
}

function Entity(): (
target: typeof HeaderArquivo,
) => void | typeof HeaderArquivo {
throw new Error('Function not implemented.');
}
Loading
Loading