diff --git a/src/bank-statements/bank-statements-repository.service.ts b/src/bank-statements/bank-statements-repository.service.ts index 1ecdacb5..3632e95f 100644 --- a/src/bank-statements/bank-statements-repository.service.ts +++ b/src/bank-statements/bank-statements-repository.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { nextFriday } from 'date-fns'; -import { TicketRevenuesService } from 'src/ticket-revenues/ticket-revenues.service'; +import { TicketRevenuesRepositoryService } from 'src/ticket-revenues/ticket-revenues-repository.service'; import { User } from 'src/users/entities/user.entity'; import { getDateYMDString, isPaymentWeekComplete } from 'src/utils/date-utils'; import { TimeIntervalEnum } from 'src/utils/enums/time-interval.enum'; @@ -18,7 +18,9 @@ import { IBSGetMeDayValidArgs } from './interfaces/bs-get-me-day-args.interface' */ @Injectable() export class BankStatementsRepositoryService { - constructor(private readonly ticketRevenuesService: TicketRevenuesService) {} + constructor( + private readonly ticketRevenuesRepository: TicketRevenuesRepositoryService, + ) {} public async getPreviousDays( validArgs: IBSGetMePreviousDaysValidArgs, @@ -54,7 +56,7 @@ export class BankStatementsRepositoryService { const pagination = validArgs.paginationArgs ? validArgs.paginationArgs : { limit: 9999, page: 1 }; - const revenues = await this.ticketRevenuesService.fetchTicketRevenues({ + const revenues = await this.ticketRevenuesRepository.fetchTicketRevenues({ startDate: new Date(validArgs.endDate), endDate: new Date(validArgs.endDate), cpfCnpj: validArgs.user.getCpfCnpj(), diff --git a/src/bank-statements/bank-statements.service.spec.ts b/src/bank-statements/bank-statements.service.spec.ts index 17dadb43..d02ee5ce 100644 --- a/src/bank-statements/bank-statements.service.spec.ts +++ b/src/bank-statements/bank-statements.service.spec.ts @@ -1,6 +1,8 @@ import { Provider } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; +import { BigqueryService } from 'src/bigquery/bigquery.service'; import { ITicketRevenuesGroup } from 'src/ticket-revenues/interfaces/ticket-revenues-group.interface'; +import { TicketRevenuesRepositoryService } from 'src/ticket-revenues/ticket-revenues-repository.service'; import { TicketRevenuesService } from 'src/ticket-revenues/ticket-revenues.service'; import { User } from 'src/users/entities/user.entity'; import { UsersService } from 'src/users/users.service'; @@ -27,6 +29,12 @@ describe('BankStatementsService', () => { let ticketRevenuesService: TicketRevenuesService; beforeEach(async () => { + // class TicketRevenuesRepositoryMock { + // async fetchTicketRevenues(): any[] { + // return []; + // } + // } + const usersServiceMock = { provide: UsersService, useValue: { @@ -37,6 +45,12 @@ describe('BankStatementsService', () => { softDelete: jest.fn(), }, } as Provider; + const bigqueryServiceMock = { + provide: BigqueryService, + useValue: { + runQuery: jest.fn(), + }, + } as Provider; const ticketRevenuesServiceMock = { provide: TicketRevenuesService, useValue: { @@ -49,6 +63,8 @@ describe('BankStatementsService', () => { providers: [ BankStatementsService, BankStatementsRepositoryService, + TicketRevenuesRepositoryService, + bigqueryServiceMock, usersServiceMock, ticketRevenuesServiceMock, ], diff --git a/src/ticket-revenues/interfaces/tr-get-me-individual-args.interface.ts b/src/ticket-revenues/interfaces/tr-get-me-individual-args.interface.ts index 4b45b56e..994d4ded 100644 --- a/src/ticket-revenues/interfaces/tr-get-me-individual-args.interface.ts +++ b/src/ticket-revenues/interfaces/tr-get-me-individual-args.interface.ts @@ -1,4 +1,6 @@ -import { TRTimeIntervalEnum as TRTimeIntervalEnum } from '../enums/tr-time-interval.enum'; +import { User } from 'src/users/entities/user.entity'; +import { TimeIntervalEnum } from 'src/utils/enums/time-interval.enum'; +import { TRTimeIntervalEnum } from '../enums/tr-time-interval.enum'; export interface ITRGetMeIndividualArgs { startDate?: string; @@ -6,3 +8,10 @@ export interface ITRGetMeIndividualArgs { timeInterval?: TRTimeIntervalEnum; userId?: number; } + +export interface ITRGetMeIndividualValidArgs { + user: User; + startDate?: string; + endDate?: string; + timeInterval?: TimeIntervalEnum; +} diff --git a/src/ticket-revenues/ticket-revenues-repository.service.ts b/src/ticket-revenues/ticket-revenues-repository.service.ts new file mode 100644 index 00000000..1a55920b --- /dev/null +++ b/src/ticket-revenues/ticket-revenues-repository.service.ts @@ -0,0 +1,293 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { endOfDay, isToday, startOfDay } from 'date-fns'; +import { BQSInstances, BigqueryService } from 'src/bigquery/bigquery.service'; +import { isCpfOrCnpj } from 'src/utils/cpf-cnpj'; +import { getPagination } from 'src/utils/get-pagination'; +import { getPaymentDates } from 'src/utils/payment-date-utils'; +import { QueryBuilder } from 'src/utils/query-builder/query-builder'; +import { PaginationOptions } from 'src/utils/types/pagination-options'; +import { Pagination } from 'src/utils/types/pagination.type'; +import { IFetchTicketRevenues } from './interfaces/fetch-ticket-revenues.interface'; +import { ITicketRevenue } from './interfaces/ticket-revenue.interface'; +import { ITicketRevenuesGroup } from './interfaces/ticket-revenues-group.interface'; +import { ITRGetMeIndividualValidArgs } from './interfaces/tr-get-me-individual-args.interface'; +import { ITRGetMeIndividualResponse } from './interfaces/tr-get-me-individual-response.interface'; +import { + TRIntegrationTypeMap as IntegrationType, + TRPaymentTypeMap as PaymentType, +} from './maps/ticket-revenues.map'; + +@Injectable() +export class TicketRevenuesRepositoryService { + private logger: Logger = new Logger('TicketRevenuesService', { + timestamp: true, + }); + + constructor(private readonly bigqueryService: BigqueryService) {} + + /** + * TODO: use it only for repository services + * + * Filter: used by: + * - ticket-revenues/get/me + */ + removeTodayData( + data: T[], + endDate: Date, + ): T[] { + const mostRecentDate = startOfDay(new Date(data[0].partitionDate)); + if (mostRecentDate > endOfDay(endDate)) { + return data.filter((i) => !isToday(new Date(i.partitionDate))); + } else { + return data; + } + } + + /** + * TODO: use it only in repository + * + * Repository: query `ITicketRevenue[]` with pagination + * + * Used by: + * - ticket-revenues/me/individual + * - ticket-revenues/me (day gorup) + * - ticket-revenues/me/grouped (sum all group) + * - bank-statements/me/previous-days + */ + async fetchTicketRevenues( + args?: IFetchTicketRevenues, + ): Promise<{ data: ITicketRevenue[]; countAll: number }> { + const IS_PROD = process.env.NODE_ENV === 'production'; + const Q_CONSTS = { + bucket: IS_PROD ? 'rj-smtr' : 'rj-smtr-dev', + transacao: IS_PROD + ? 'rj-smtr.br_rj_riodejaneiro_bilhetagem.transacao' + : 'rj-smtr-dev.br_rj_riodejaneiro_bilhetagem_cct.transacao', + integracao: IS_PROD + ? 'rj-smtr.br_rj_riodejaneiro_bilhetagem.integracao' + : 'rj-smtr-dev.br_rj_riodejaneiro_bilhetagem_cct.integracao', + tTipoPgto: IS_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 !== undefined) { + const startDate = args.startDate.toISOString().slice(0, 10); + queryBuilder.pushAND( + `DATE(t.datetime_processamento) >= DATE('${startDate}')`, + ); + } + if (args?.endDate !== undefined) { + 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` : ''); + const query = + ` + SELECT + CAST(t.data AS STRING) AS partitionDate, + t.hora AS processingHour, + CAST(t.datetime_transacao AS STRING) AS transactionDateTime, + CAST(t.datetime_processamento AS STRING) AS processingDateTime, + t.datetime_captura AS captureDateTime, + t.modo AS transportType, + t.servico AS vehicleService, + t.sentido AS directionId, + t.id_veiculo AS vehicleId, + t.id_cliente AS clientId, + t.id_transacao AS transactionId, + t.${Q_CONSTS.tTipoPgto} AS paymentMediaType, + t.tipo_transacao AS transactionType, + t.id_tipo_integracao AS transportIntegrationType, + t.id_integracao AS integrationId, + t.latitude AS transactionLat, + t.longitude AS transactionLon, + t.stop_id AS stopId, + t.stop_lat AS stopLat, + t.stop_lon AS stopLon, + CASE WHEN t.tipo_transacao = 'Integração' THEN i.valor_transacao_total ELSE t.valor_transacao END AS transactionValue, + t.versao AS bqDataVersion, + (${countQuery}) AS count, + 'ok' AS status + FROM \`${Q_CONSTS.transacao}\` t\n` + + joinCpfCnpj + + '\n' + + joinIntegracao + + '\n' + + (qWhere.length ? `WHERE ${qWhere}\n` : '') + + `UNION ALL + SELECT ${'null, '.repeat(22)} + (${countQuery}) AS count, 'empty' AS status` + + `\nORDER BY partitionDate DESC, processingHour DESC` + + (args?.limit !== undefined ? `\nLIMIT ${args.limit + 1}` : '') + + (offset !== undefined ? `\nOFFSET ${offset}` : ''); + const queryResult = await this.bigqueryService.runQuery( + BQSInstances.smtr, + query, + ); + + const count: number = queryResult[0].count; + // Remove unwanted keys and remove last item (all null if empty) + let ticketRevenues: ITicketRevenue[] = queryResult.map((i) => { + delete i.status; + delete i.count; + return i; + }); + ticketRevenues.pop(); + ticketRevenues = this.mapTicketRevenues(ticketRevenues); + + return { + data: ticketRevenues, + countAll: count, + }; + } + + /** + * Convert id or some values into desired string values + */ + private mapTicketRevenues( + ticketRevenues: ITicketRevenue[], + ): ITicketRevenue[] { + return ticketRevenues.map((item: ITicketRevenue) => { + const paymentType = item.paymentMediaType; + const integrationType = item.transportIntegrationType; + return { + ...item, + paymentMediaType: + paymentType !== null + ? PaymentType?.[paymentType] || paymentType + : paymentType, + transportIntegrationType: + integrationType !== null + ? IntegrationType?.[integrationType] || integrationType + : integrationType, + }; + }); + } + + public async getMeIndividual( + validArgs: ITRGetMeIndividualValidArgs, + paginationArgs: PaginationOptions, + ): Promise> { + const { startDate, endDate } = getPaymentDates({ + endpoint: 'ticket-revenues', + startDateStr: validArgs.startDate, + endDateStr: validArgs.endDate, + timeInterval: validArgs.timeInterval, + }); + + const result = await this.fetchTicketRevenues({ + cpfCnpj: validArgs.user.getCpfCnpj(), + startDate, + endDate, + getToday: true, + limit: paginationArgs.limit, + offset: (paginationArgs.page - 1) * paginationArgs.limit, + }); + let ticketRevenuesResponse = result.data; + + if (ticketRevenuesResponse.length === 0) { + return getPagination( + { + amountSum: 0, + data: [], + }, + { + dataLenght: 0, + maxCount: 0, + }, + paginationArgs, + ); + } + + ticketRevenuesResponse = this.removeTodayData( + ticketRevenuesResponse, + endDate, + ); + + return getPagination( + { + amountSum: this.getAmountSum(ticketRevenuesResponse), + data: ticketRevenuesResponse, + }, + { + dataLenght: ticketRevenuesResponse.length, + maxCount: result.countAll, + }, + paginationArgs, + ); + } + + /** + * TODO: use it only in repository + */ + getAmountSum( + data: T[], + ): number { + return Number( + data + .reduce((sum, i) => sum + (this.getTransactionValue(i) || 0), 0) + .toFixed(2), + ); + } + + private getTransactionValue( + item: ITicketRevenue | ITicketRevenuesGroup, + ): number { + if ('transactionValue' in item) { + return item.transactionValue || 0; + } else if ('transactionValueSum' in item) { + return item.transactionValueSum || 0; + } else { + return 0; + } + } +} diff --git a/src/ticket-revenues/ticket-revenues.module.ts b/src/ticket-revenues/ticket-revenues.module.ts index baa69337..d25da5ca 100644 --- a/src/ticket-revenues/ticket-revenues.module.ts +++ b/src/ticket-revenues/ticket-revenues.module.ts @@ -1,13 +1,14 @@ import { Module } from '@nestjs/common'; import { BigqueryModule } from 'src/bigquery/bigquery.module'; import { UsersModule } from 'src/users/users.module'; +import { TicketRevenuesRepositoryService } from './ticket-revenues-repository.service'; import { TicketRevenuesController } from './ticket-revenues.controller'; import { TicketRevenuesService } from './ticket-revenues.service'; @Module({ imports: [UsersModule, BigqueryModule, UsersModule], - providers: [TicketRevenuesService], + providers: [TicketRevenuesService, TicketRevenuesRepositoryService], controllers: [TicketRevenuesController], - exports: [TicketRevenuesService], + exports: [TicketRevenuesService, TicketRevenuesRepositoryService], }) export class TicketRevenuesModule {} diff --git a/src/ticket-revenues/ticket-revenues.service.spec.ts b/src/ticket-revenues/ticket-revenues.service.spec.ts index 761a1a2d..b5a00144 100644 --- a/src/ticket-revenues/ticket-revenues.service.spec.ts +++ b/src/ticket-revenues/ticket-revenues.service.spec.ts @@ -4,10 +4,12 @@ import { BigqueryService } from 'src/bigquery/bigquery.service'; import { User } from 'src/users/entities/user.entity'; import { UsersService } from 'src/users/users.service'; import { ITicketRevenue } from './interfaces/ticket-revenue.interface'; +import { TicketRevenuesRepositoryService } from './ticket-revenues-repository.service'; import { TicketRevenuesService } from './ticket-revenues.service'; describe('TicketRevenuesService', () => { let ticketRevenuesService: TicketRevenuesService; + let ticketRevenuesRepository: TicketRevenuesRepositoryService; let usersService: UsersService; beforeEach(async () => { @@ -24,7 +26,12 @@ describe('TicketRevenuesService', () => { }, } as Provider; const module: TestingModule = await Test.createTestingModule({ - providers: [TicketRevenuesService, usersServiceMock, bigqueryServiceMock], + providers: [ + TicketRevenuesService, + TicketRevenuesRepositoryService, + usersServiceMock, + bigqueryServiceMock, + ], }).compile(); jest .spyOn(global.Date, 'now') @@ -33,6 +40,9 @@ describe('TicketRevenuesService', () => { ticketRevenuesService = module.get( TicketRevenuesService, ); + ticketRevenuesRepository = module.get( + TicketRevenuesRepositoryService, + ); usersService = module.get(UsersService); }); @@ -124,7 +134,7 @@ describe('TicketRevenuesService', () => { user.cpfCnpj = 'cpfCnpj_1'; jest.spyOn(usersService, 'getOne').mockResolvedValue(user); jest - .spyOn(ticketRevenuesService as any, 'fetchTicketRevenues') + .spyOn(ticketRevenuesRepository as any, 'fetchTicketRevenues') .mockResolvedValue({ data: revenues, countAll: revenues.length, @@ -216,7 +226,7 @@ describe('TicketRevenuesService', () => { user.cpfCnpj = 'cpfCnpj_1'; jest.spyOn(usersService, 'getOne').mockResolvedValue(user); jest - .spyOn(ticketRevenuesService as any, 'fetchTicketRevenues') + .spyOn(ticketRevenuesRepository as any, 'fetchTicketRevenues') .mockResolvedValue({ data: revenues, countAll: revenues.length, diff --git a/src/ticket-revenues/ticket-revenues.service.ts b/src/ticket-revenues/ticket-revenues.service.ts index b93d2615..19329629 100644 --- a/src/ticket-revenues/ticket-revenues.service.ts +++ b/src/ticket-revenues/ticket-revenues.service.ts @@ -1,19 +1,16 @@ import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; -import { endOfDay, isToday, startOfDay } from 'date-fns'; -import { BQSInstances, BigqueryService } from 'src/bigquery/bigquery.service'; +import { isToday } from 'date-fns'; import { User } from 'src/users/entities/user.entity'; import { UsersService } from 'src/users/users.service'; import { getDateNthWeek } from 'src/utils/date-utils'; import { TimeIntervalEnum } from 'src/utils/enums/time-interval.enum'; import { WeekdayEnum } from 'src/utils/enums/weekday.enum'; -import { getPagination } from 'src/utils/get-pagination'; import { formatLog } from 'src/utils/logging'; import { PAYMENT_START_WEEKDAY, PaymentEndpointType, getPaymentDates, } from 'src/utils/payment-date-utils'; -import { QueryBuilder } from 'src/utils/query-builder/query-builder'; import { PaginationOptions } from 'src/utils/types/pagination-options'; import { Pagination } from 'src/utils/types/pagination.type'; import { IFetchTicketRevenues } from './interfaces/fetch-ticket-revenues.interface'; @@ -23,14 +20,10 @@ import { ITRGetMeGroupedArgs } from './interfaces/tr-get-me-grouped-args.interfa import { ITRGetMeGroupedResponse } from './interfaces/tr-get-me-grouped-response.interface'; import { ITRGetMeIndividualArgs } from './interfaces/tr-get-me-individual-args.interface'; import { ITRGetMeIndividualResponse } from './interfaces/tr-get-me-individual-response.interface'; -import { - TRIntegrationTypeMap as IntegrationType, - TRPaymentTypeMap as PaymentType, -} from './maps/ticket-revenues.map'; import { TicketRevenuesGroup } from './objs/TicketRevenuesGroup'; +import { TicketRevenuesRepositoryService } from './ticket-revenues-repository.service'; import { TicketRevenuesGroupsType } from './types/ticket-revenues-groups.type'; import * as TicketRevenuesGroupList from './utils/ticket-revenues-groups.utils'; -import { isCpfOrCnpj } from 'src/utils/cpf-cnpj'; @Injectable() export class TicketRevenuesService { @@ -39,15 +32,22 @@ export class TicketRevenuesService { }); constructor( - private readonly bigqueryService: BigqueryService, private readonly usersService: UsersService, + private readonly ticketRevenuesRepository: TicketRevenuesRepositoryService, ) {} + /** + * TODO: refactor - use repository method + * + * Service method + */ public async getMeGrouped( args: ITRGetMeGroupedArgs, ): Promise { // Args - const user = await this.validateUser(args); + const user = await this.validateGetMeGrouped(args); + + // Repository tasks const { startDate, endDate } = getPaymentDates({ endpoint: 'ticket-revenues', startDateStr: args.startDate, @@ -63,7 +63,7 @@ export class TicketRevenuesService { }; const ticketRevenuesResponse: ITicketRevenue[] = ( - await this.fetchTicketRevenues(fetchArgs) + await this.ticketRevenuesRepository.fetchTicketRevenues(fetchArgs) ).data; if (ticketRevenuesResponse.length === 0) { @@ -74,13 +74,23 @@ export class TicketRevenuesService { return ticketRevenuesGroupSum; } + private async validateGetMeGrouped(args: ITRGetMeGroupedArgs): Promise { + const user = await this.usersService.getOne({ id: args?.userId }); + return user; + } + + /** + * TODO: refactor - use repository method + * + * Service method + */ public async getMe( args: ITRGetMeGroupedArgs, pagination: PaginationOptions, endpoint: PaymentEndpointType, ): Promise { - const user = await this.validateUser(args); - const GET_TODAY = true; + // TODO: set groupBy as validation response + const user = await this.validateGetMe(args); const { startDate, endDate } = getPaymentDates({ endpoint: endpoint, startDateStr: args.startDate, @@ -89,16 +99,18 @@ export class TicketRevenuesService { }); const groupBy = args?.groupBy || 'day'; - // Get data + // Repository tasks let ticketRevenuesResponse: ITicketRevenue[] = []; const fetchArgs: IFetchTicketRevenues = { cpfCnpj: user.getCpfCnpj(), startDate, endDate, - getToday: GET_TODAY, + getToday: true, }; - ticketRevenuesResponse = (await this.fetchTicketRevenues(fetchArgs)).data; + ticketRevenuesResponse = ( + await this.ticketRevenuesRepository.fetchTicketRevenues(fetchArgs) + ).data; if (ticketRevenuesResponse.length === 0) { return { @@ -132,29 +144,22 @@ export class TicketRevenuesService { .toFixed(2), ); - const mostRecentResponseDate = startOfDay( - new Date(ticketRevenuesResponse[0].partitionDate), + ticketRevenuesResponse = this.ticketRevenuesRepository.removeTodayData( + ticketRevenuesResponse, + endDate, ); - if (GET_TODAY && mostRecentResponseDate > endOfDay(endDate)) { - ticketRevenuesResponse = this.removeTicketRevenueToday( - ticketRevenuesResponse, - ) as ITicketRevenue[]; - ticketRevenuesGroups = this.removeTicketRevenueToday( - ticketRevenuesGroups, - ) as ITicketRevenuesGroup[]; - } - - const amountSum = Number( - ticketRevenuesGroups - .reduce((sum, i) => sum + (i?.transactionValueSum || 0), 0) - .toFixed(2), + ticketRevenuesGroups = this.ticketRevenuesRepository.removeTodayData( + ticketRevenuesGroups, + endDate, ); + const amountSum = + this.ticketRevenuesRepository.getAmountSum(ticketRevenuesGroups); + const ticketCount = ticketRevenuesGroups.reduce( (sum, i) => sum + i.count, 0, ); - console.log('TR response', ticketRevenuesResponse.length); return { startDate: @@ -169,6 +174,11 @@ export class TicketRevenuesService { }; } + private async validateGetMe(args: ITRGetMeGroupedArgs): Promise { + const user = await this.usersService.getOne({ id: args?.userId }); + return user; + } + private getGroupSum(data: ITicketRevenue[]): ITicketRevenuesGroup { const groupSums = this.getTicketRevenuesGroups(data, 'all'); if (groupSums.length >= 1) { @@ -193,17 +203,11 @@ export class TicketRevenuesService { } } - private async validateUser(args: ITRGetMeGroupedArgs): Promise { - const user = await this.usersService.getOne({ id: args?.userId }); - return user; - } - - private removeTicketRevenueToday( - list: (ITicketRevenue | ITicketRevenuesGroup)[], - ): ITicketRevenue[] | (ITicketRevenue | ITicketRevenuesGroup)[] { - return list.filter((i) => !isToday(new Date(i.partitionDate))); - } - + /** + * TODO: refactor - use it in repository + * + * Filter method: ticket-revenues/me + */ private getTicketRevenuesGroups( ticketRevenues: ITicketRevenue[], groupBy: 'day' | 'week' | 'month' | 'all' | string, @@ -255,260 +259,18 @@ export class TicketRevenuesService { return resultList; } - /** - * TODO: refactor - * - * Repository: query `ITicketRevenue[]` with pagination - * - * Used by: - * - ticket-revenues/me/individual - * - ticket-revenues/me (day gorup) - * - ticket-revenues/me/grouped (sum all group) - * - bank-statements/me/previous-days - */ - async fetchTicketRevenues( - args?: IFetchTicketRevenues, - ): Promise<{ data: ITicketRevenue[]; countAll: number }> { - const IS_PROD = process.env.NODE_ENV === 'production'; - const Q_CONSTS = { - bucket: IS_PROD ? 'rj-smtr' : 'rj-smtr-dev', - transacao: IS_PROD - ? 'rj-smtr.br_rj_riodejaneiro_bilhetagem.transacao' - : 'rj-smtr-dev.br_rj_riodejaneiro_bilhetagem_cct.transacao', - integracao: IS_PROD - ? 'rj-smtr.br_rj_riodejaneiro_bilhetagem.integracao' - : 'rj-smtr-dev.br_rj_riodejaneiro_bilhetagem_cct.integracao', - tTipoPgto: IS_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 !== undefined) { - const startDate = args.startDate.toISOString().slice(0, 10); - queryBuilder.pushAND( - `DATE(t.datetime_processamento) >= DATE('${startDate}')`, - ); - } - if (args?.endDate !== undefined) { - 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` : ''); - const query = - ` - SELECT - CAST(t.data AS STRING) AS partitionDate, - t.hora AS processingHour, - CAST(t.datetime_transacao AS STRING) AS transactionDateTime, - CAST(t.datetime_processamento AS STRING) AS processingDateTime, - t.datetime_captura AS captureDateTime, - t.modo AS transportType, - t.servico AS vehicleService, - t.sentido AS directionId, - t.id_veiculo AS vehicleId, - t.id_cliente AS clientId, - t.id_transacao AS transactionId, - t.${Q_CONSTS.tTipoPgto} AS paymentMediaType, - t.tipo_transacao AS transactionType, - t.id_tipo_integracao AS transportIntegrationType, - t.id_integracao AS integrationId, - t.latitude AS transactionLat, - t.longitude AS transactionLon, - t.stop_id AS stopId, - t.stop_lat AS stopLat, - t.stop_lon AS stopLon, - CASE WHEN t.tipo_transacao = 'Integração' THEN i.valor_transacao_total ELSE t.valor_transacao END AS transactionValue, - t.versao AS bqDataVersion, - (${countQuery}) AS count, - 'ok' AS status - FROM \`${Q_CONSTS.transacao}\` t\n` + - joinCpfCnpj + - '\n' + - joinIntegracao + - '\n' + - (qWhere.length ? `WHERE ${qWhere}\n` : '') + - `UNION ALL - SELECT ${'null, '.repeat(22)} - (${countQuery}) AS count, 'empty' AS status` + - `\nORDER BY partitionDate DESC, processingHour DESC` + - (args?.limit !== undefined ? `\nLIMIT ${args.limit + 1}` : '') + - (offset !== undefined ? `\nOFFSET ${offset}` : ''); - const queryResult = await this.bigqueryService.runQuery( - BQSInstances.smtr, - query, - ); - - const count: number = queryResult[0].count; - // Remove unwanted keys and remove last item (all null if empty) - let ticketRevenues: ITicketRevenue[] = queryResult.map((i) => { - delete i.status; - delete i.count; - return i; - }); - ticketRevenues.pop(); - ticketRevenues = this.mapTicketRevenues(ticketRevenues); - - return { - data: ticketRevenues, - countAll: count, - }; - } - - /** - * Convert id values into string values - */ - private mapTicketRevenues( - ticketRevenues: ITicketRevenue[], - ): ITicketRevenue[] { - return ticketRevenues.map((item: ITicketRevenue) => { - const paymentType = item.paymentMediaType; - const integrationType = item.transportIntegrationType; - return { - ...item, - paymentMediaType: - paymentType !== null - ? PaymentType?.[paymentType] || paymentType - : paymentType, - transportIntegrationType: - integrationType !== null - ? IntegrationType?.[integrationType] || integrationType - : integrationType, - }; - }); - } - - /** - * Service method: ticket-revenues/me - */ public async getMeIndividual( args: ITRGetMeIndividualArgs, - paginationArgs: PaginationOptions, + paginationOptions: PaginationOptions, ): Promise> { - const GET_TODAY = true; - const validArgs = await this.validateGetMeIndividualArgs(args); - const { startDate, endDate } = getPaymentDates({ - endpoint: 'ticket-revenues', - startDateStr: validArgs.startDate, - endDateStr: validArgs.endDate, - timeInterval: validArgs.timeInterval, - }); - - const result = await this.fetchTicketRevenues({ - cpfCnpj: validArgs.user.getCpfCnpj(), - startDate, - endDate, - getToday: GET_TODAY, - limit: paginationArgs.limit, - offset: (paginationArgs.page - 1) * paginationArgs.limit, - }); - let ticketRevenuesResponse = result.data; - - if (ticketRevenuesResponse.length === 0) { - return getPagination( - { - amountSum: 0, - data: [], - }, - { - dataLenght: 0, - maxCount: 0, - }, - paginationArgs, - ); - } - - const mostRecentResponseDate = startOfDay( - new Date(ticketRevenuesResponse[0].partitionDate), - ); - if (GET_TODAY && mostRecentResponseDate > endOfDay(endDate)) { - ticketRevenuesResponse = this.removeTicketRevenueToday( - ticketRevenuesResponse, - ) as ITicketRevenue[]; - } - - return getPagination( - { - amountSum: this.getAmountSum(ticketRevenuesResponse), - data: ticketRevenuesResponse, - }, - { - dataLenght: ticketRevenuesResponse.length, - maxCount: result.countAll, - }, - paginationArgs, - ); - } - - private renoveTodayIfNecessary( - getToday: boolean, - mostRecentResponseDate: Date, - endDate: Date, - data: ITicketRevenue[], - ): ITicketRevenue[] { - if (getToday && mostRecentResponseDate > endOfDay(endDate)) { - return this.removeTicketRevenueToday(data) as ITicketRevenue[]; - } else { - return data; - } - } - - private getAmountSum(data: ITicketRevenue[]): number { - return Number( - data.reduce((sum, i) => sum + (i?.transactionValue || 0), 0).toFixed(2), + const validArgs = await this.validateGetMeIndividual(args); + return await this.ticketRevenuesRepository.getMeIndividual( + validArgs, + paginationOptions, ); } - private async validateGetMeIndividualArgs( - args: ITRGetMeIndividualArgs, - ): Promise<{ + private async validateGetMeIndividual(args: ITRGetMeIndividualArgs): Promise<{ user: User; startDate?: string; endDate?: string;