From 4fed66696e304cb88c6a56009c7d386d5e559674 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Fri, 2 Feb 2024 17:33:17 -0300 Subject: [PATCH] fix: show different mails for non registered users --- src/config/app.config.ts | 1 + src/cron-jobs/cron-jobs.service.ts | 114 ++++++++++-------- .../mail-history-seed-data.service.ts | 10 ++ .../seeds/user/user-seed-data.service.ts | 13 ++ src/database/seeds/user/user-seed.service.ts | 18 +-- src/mail-history/mail-history.service.ts | 53 +++----- ...sent_email.hbs => user-daily-conclude.hbs} | 15 ++- src/mail/mail.service.ts | 19 ++- src/test/test.controller.ts | 23 ++++ src/test/test.module.ts | 27 +++++ src/users/entities/user.entity.ts | 21 +++- src/users/interfaces/user-data.interface.ts | 4 + src/users/users.service.ts | 61 +++++++++- 13 files changed, 271 insertions(+), 108 deletions(-) rename src/mail/mail-templates/{report_resent_email.hbs => user-daily-conclude.hbs} (68%) create mode 100644 src/test/test.controller.ts create mode 100644 src/test/test.module.ts diff --git a/src/config/app.config.ts b/src/config/app.config.ts index 87eba7ed..276550e3 100644 --- a/src/config/app.config.ts +++ b/src/config/app.config.ts @@ -14,6 +14,7 @@ import { enum Environment { Development = 'development', Production = 'production', + Local = 'local', Test = 'test', } diff --git a/src/cron-jobs/cron-jobs.service.ts b/src/cron-jobs/cron-jobs.service.ts index 84a543c3..43b8d652 100644 --- a/src/cron-jobs/cron-jobs.service.ts +++ b/src/cron-jobs/cron-jobs.service.ts @@ -4,6 +4,7 @@ import { CronExpression, SchedulerRegistry } from '@nestjs/schedule'; import { CronJob, CronJobParameters } from 'cron'; import { CoreBankService } from 'src/core-bank/core-bank.service'; import { JaeService } from 'src/jae/jae.service'; +import { InviteStatus } from 'src/mail-history-statuses/entities/mail-history-status.entity'; import { InviteStatusEnum } from 'src/mail-history-statuses/mail-history-status.enum'; import { MailHistory } from 'src/mail-history/entities/mail-history.entity'; import { MailHistoryService } from 'src/mail-history/mail-history.service'; @@ -12,6 +13,7 @@ import { appSettings } from 'src/settings/app.settings'; import { SettingEntity } from 'src/settings/entities/setting.entity'; import { ISettingData } from 'src/settings/interfaces/setting-data.interface'; import { SettingsService } from 'src/settings/settings.service'; +import { User } from 'src/users/entities/user.entity'; import { UsersService } from 'src/users/users.service'; import { formatErrorMessage as formatErrorLog, @@ -28,7 +30,7 @@ export enum CrobJobsEnum { updateCoreBankMockedData = 'updateCoreBankMockedData', sendStatusReport = 'sendStatusReport', pollDb = 'pollDb', - bulkReSendInvites = 'bulkReSendInvites', + bulkResendInvites = 'bulkResendInvites', } interface ICronJob { @@ -57,7 +59,7 @@ export class CronJobsService implements OnModuleInit { private jaeService: JaeService, private coreBankService: CoreBankService, private usersService: UsersService, - ) { } + ) {} onModuleInit() { const THIS_CLASS_WITH_METHOD = `${CronJobsService.name}.${this.onModuleInit.name}`; @@ -117,10 +119,10 @@ export class CronJobsService implements OnModuleInit { }, }, { - name: CrobJobsEnum.bulkReSendInvites, + name: CrobJobsEnum.bulkResendInvites, cronJobParameters: { - cronTime: '45 14 * * *', // 14:45 GMT = 11:45BRT (GMT-3) - onTick: async () => this.bulkReSendInvites(), + cronTime: '* * * * *', //'45 14 * * *', // 14:45 GMT = 11:45BRT (GMT-3) + onTick: async () => this.bulkResendInvites(), }, }, ); @@ -177,7 +179,7 @@ export class CronJobsService implements OnModuleInit { this.logger.log( formatLog( `Tarefa cancelada pois 'setting.${appSettings.any__activate_auto_send_invite.name}' = 'false'.` + - ` Para ativar, altere na tabela 'setting'`, + ` Para ativar, altere na tabela 'setting'`, THIS_METHOD, ), ); @@ -193,8 +195,8 @@ export class CronJobsService implements OnModuleInit { this.logger.log( formatLog( `Iniciando tarefa - a enviar: ${unsent.length},` + - ` enviado: ${sentToday.length}/${dailyQuota()},` + - ` falta enviar: ${remainingQuota}`, + ` enviado: ${sentToday.length}/${dailyQuota()},` + + ` falta enviar: ${remainingQuota}`, THIS_METHOD, ), ); @@ -354,7 +356,7 @@ export class CronJobsService implements OnModuleInit { this.logger.log( formatLog( `Tarefa cancelada pois 'setting.${appSettings.any__mail_report_enabled.name}' = 'false'.` + - ` Para ativar, altere na tabela 'setting'`, + ` Para ativar, altere na tabela 'setting'`, THIS_METHOD, ), ); @@ -373,7 +375,7 @@ export class CronJobsService implements OnModuleInit { this.logger.log( formatLog( `Tarefa cancelada pois 'setting.${appSettings.any__mail_report_enabled.name}' = 'false'.` + - ` Para ativar, altere na tabela 'setting'`, + ` Para ativar, altere na tabela 'setting'`, THIS_METHOD, ), ); @@ -389,7 +391,7 @@ export class CronJobsService implements OnModuleInit { this.logger.error( formatLog( `Tarefa cancelada pois a configuração 'mail.statusReportRecipients'` + - ` não foi encontrada (retornou: ${mailRecipients}).`, + ` não foi encontrada (retornou: ${mailRecipients}).`, 'sendStatusReport()', ), ); @@ -400,7 +402,7 @@ export class CronJobsService implements OnModuleInit { this.logger.error( formatLog( `Tarefa cancelada pois a configuração 'mail.statusReportRecipients'` + - ` não contém uma lista de emails válidos. Retornou: ${mailRecipients}.`, + ` não contém uma lista de emails válidos. Retornou: ${mailRecipients}.`, THIS_METHOD, ), ); @@ -474,7 +476,7 @@ export class CronJobsService implements OnModuleInit { this.logger.log( formatLog( `Tarefa cancelada pois setting.${appSettings.any__poll_db_enabled.name}' = 'false'` + - ` Para ativar, altere na tabela 'setting'`, + ` Para ativar, altere na tabela 'setting'`, THIS_METHOD, ), ); @@ -531,8 +533,8 @@ export class CronJobsService implements OnModuleInit { this.logger.log( formatLog( `Alteração encontrada em` + - ` setting.'${args.setting.name}': ` + - `${job?.cronJobParameters.cronTime} --> ${setting}.`, + ` setting.'${args.setting.name}': ` + + `${job?.cronJobParameters.cronTime} --> ${setting}.`, thisMethod, ), ); @@ -597,57 +599,67 @@ export class CronJobsService implements OnModuleInit { }; } - async getEmail() { - return await this.mailHistoryService.emailsNaoCadastrados(); - } - - async bulkReSendInvites() { - const THIS_METHOD = `${this.bulkReSendInvites.name}()`; - const emails: string[] = ( - await this.mailHistoryService.emailsNaoCadastrados() - ).reduce((emails: string[], user) => [...emails, String(user.email)], []); + async bulkResendInvites() { + const THIS_METHOD = `${this.bulkResendInvites.name}()`; + const notRegisteredUsers = await this.usersService.getNotRegisteredUsers(); - if (emails.length === 0) { + if (notRegisteredUsers.length === 0) { this.logger.log( formatLog('Não há usuários para enviar, abortando...', THIS_METHOD), ); return; } + this.logger.log( + formatLog( + String( + 'Enviando emails específicos para ' + + `${notRegisteredUsers.length} usuários não totalmente registrados`, + ), + THIS_METHOD, + ), + ); + for (const user of notRegisteredUsers) { + await this.resendInvite(user, THIS_METHOD); + } + } - for (const email of emails) { - try { - const mailSentInfo = await this.mailService.reSendEmailBank({ - to: email, - data: null, - }); + async resendInvite(user: User, outerMethod: string) { + const THIS_METHOD = `${outerMethod} > ${this.resendInvite.name}`; + try { + const mailSentInfo = await this.mailService.reSendEmailBank({ + to: user.email as string, + data: { + hash: user.aux_inviteHash as string, + inviteStatus: user.aux_inviteStatus as InviteStatus, + }, + }); - // Success - if (mailSentInfo.success) { - this.logger.log(formatLog('Email enviado com sucesso.', THIS_METHOD)); - } + // Success + if (mailSentInfo.success) { + this.logger.log(formatLog('Email enviado com sucesso.', THIS_METHOD)); + } - // SMTP error - else { - this.logger.error( - formatErrorLog( - 'Email enviado retornou erro.', - mailSentInfo, - new Error(), - THIS_METHOD, - ), - ); - } - } catch (httpException) { - // API error + // SMTP error + else { this.logger.error( formatErrorLog( - 'Email falhou ao enviar.', - httpException, - httpException as Error, + 'Email enviado retornou erro.', + mailSentInfo, + new Error(), THIS_METHOD, ), ); } + } catch (httpException) { + // API error + this.logger.error( + formatErrorLog( + 'Email falhou ao enviar.', + httpException, + httpException as Error, + THIS_METHOD, + ), + ); } } } diff --git a/src/database/seeds/mail-history/mail-history-seed-data.service.ts b/src/database/seeds/mail-history/mail-history-seed-data.service.ts index 9bf47593..26c73f59 100644 --- a/src/database/seeds/mail-history/mail-history-seed-data.service.ts +++ b/src/database/seeds/mail-history/mail-history-seed-data.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { subDays } from 'date-fns'; import { InviteStatus } from 'src/mail-history-statuses/entities/mail-history-status.entity'; import { InviteStatusEnum } from 'src/mail-history-statuses/mail-history-status.enum'; import { IMailSeedData } from 'src/mail-history/interfaces/mail-history-data.interface'; @@ -22,6 +23,15 @@ export class MailHistorySeedDataService { inviteStatus: new InviteStatus(InviteStatusEnum.used), } as IMailSeedData), ); + for (let i = 0; i < mailSeedData.length; i++) { + const mail = mailSeedData[i]; + if (mail.email === 'registered.user@example.com') { + mail[i] = { + ...mail, + sentAt: subDays(new Date(), 16), + } as IMailSeedData; + } + } return mailSeedData; } } diff --git a/src/database/seeds/user/user-seed-data.service.ts b/src/database/seeds/user/user-seed-data.service.ts index 202cb1dd..b9d44fd5 100644 --- a/src/database/seeds/user/user-seed-data.service.ts +++ b/src/database/seeds/user/user-seed-data.service.ts @@ -160,6 +160,19 @@ export class UserSeedDataService { status: { id: StatusEnum.active } as Status, inviteStatus: new InviteStatus(InviteStatusEnum.used), }, + { + fullName: 'Used registered user', + email: 'registered.user@example.com', + password: 'secret', + permitCode: '319274392832024', + role: { id: RoleEnum.user } as Role, + status: { id: StatusEnum.active } as Status, + inviteStatus: new InviteStatus(InviteStatusEnum.used), + bankCode: 104, + bankAgency: '1234', + bankAccount: '12345', + bankAccountDigit: '1', + }, ] as UserDataInterface[]) : []), ]; diff --git a/src/database/seeds/user/user-seed.service.ts b/src/database/seeds/user/user-seed.service.ts index c102f71f..db8a275a 100644 --- a/src/database/seeds/user/user-seed.service.ts +++ b/src/database/seeds/user/user-seed.service.ts @@ -18,14 +18,8 @@ export class UserSeedService { ) {} async run() { - if (!(await this.validateRun())) { - this.logger.log('Database is not empty. Aborting seed...'); - return; - } - this.logger.log( - `run() ${this.userSeedDataService.getDataFromConfig().length} items`, - ); - for (const item of this.userSeedDataService.getDataFromConfig()) { + const userFixtures = await this.userSeedDataService.getDataFromConfig(); + for (const item of userFixtures) { const foundItem = await this.userSeedRepository.findOne({ where: { email: item.email, @@ -35,13 +29,13 @@ export class UserSeedService { let createdItem: User; if (foundItem) { const newItem = new User(foundItem); - newItem.update(item); + newItem.update(item, true); await this.userSeedRepository.save( this.userSeedRepository.create(newItem), ); createdItem = (await this.userSeedRepository.findOne({ where: { - email: newItem.email as string, + email: item.email as string, }, })) as User; } else { @@ -89,8 +83,4 @@ export class UserSeedService { } return hash; } - - async validateRun() { - return global.force || (await this.userSeedRepository.count()) === 0; - } } diff --git a/src/mail-history/mail-history.service.ts b/src/mail-history/mail-history.service.ts index 4a6f9248..8af2c4c0 100644 --- a/src/mail-history/mail-history.service.ts +++ b/src/mail-history/mail-history.service.ts @@ -1,7 +1,7 @@ import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util'; import { ConfigService } from '@nestjs/config'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; import * as crypto from 'crypto'; import { startOfDay } from 'date-fns'; import { IMailHistoryStatusCount } from 'src/mail-history-statuses/interfaces/mail-history-status-group.interface'; @@ -13,12 +13,11 @@ import { formatLog } from 'src/utils/logging'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; import { NullableType } from 'src/utils/types/nullable.type'; import { - DataSource, DeepPartial, + EntityManager, Equal, MoreThanOrEqual, Repository, - EntityManager, } from 'typeorm'; import { MailHistory } from './entities/mail-history.entity'; @@ -32,10 +31,8 @@ export class MailHistoryService { @InjectRepository(MailHistory) private inviteRepository: Repository, private configService: ConfigService, - @InjectDataSource() - private readonly dataSource: DataSource, private readonly entityManager: EntityManager, - ) { } + ) {} async create( data: DeepPartial, @@ -47,7 +44,7 @@ export class MailHistoryService { this.logger.log( formatLog( `Histórico de email ${createdMail.getLogInfoStr()}` + - ` criado com sucesso.`, + ` criado com sucesso.`, 'create()', logContext, ), @@ -157,7 +154,7 @@ export class MailHistoryService { this.logger.log( formatLog( `Histórico de email ${updatedMail.getLogInfoStr()}` + - ` teve os campos atualizados: [ ${Object.keys(payload)} ]`, + ` teve os campos atualizados: [ ${Object.keys(payload)} ]`, 'update()', logContext, ), @@ -204,20 +201,20 @@ export class MailHistoryService { 'invite.inviteStatus as status_id', 'COUNT(invite.inviteStatus) as status_count', `CASE ` + - `WHEN ( ` + - `"user"."fullName" IS NOT NULL AND "user"."fullName" != '' AND ` + - `"user"."cpfCnpj" IS NOT NULL AND "user"."cpfCnpj" != '' AND ` + - `"user"."permitCode" IS NOT NULL AND "user"."permitCode" != '' AND ` + - `"user"."email" IS NOT NULL AND "user"."email" != '' AND ` + - `"user"."phone" IS NOT NULL AND "user"."phone" != '' AND ` + - `"user"."bankCode" IS NOT NULL AND ` + - `"user"."bankAgency" IS NOT NULL AND "user"."bankAgency" != '' AND ` + - `"user"."bankAccount" IS NOT NULL AND "user"."bankAccount" != '' AND ` + - `"user"."bankAccountDigit" IS NOT NULL AND "user"."bankAccountDigit" != '' ` + - ')' + - 'THEN true ' + - 'ELSE false ' + - 'END AS is_filled', + `WHEN ( ` + + `"user"."fullName" IS NOT NULL AND "user"."fullName" != '' AND ` + + `"user"."cpfCnpj" IS NOT NULL AND "user"."cpfCnpj" != '' AND ` + + `"user"."permitCode" IS NOT NULL AND "user"."permitCode" != '' AND ` + + `"user"."email" IS NOT NULL AND "user"."email" != '' AND ` + + `"user"."phone" IS NOT NULL AND "user"."phone" != '' AND ` + + `"user"."bankCode" IS NOT NULL AND ` + + `"user"."bankAgency" IS NOT NULL AND "user"."bankAgency" != '' AND ` + + `"user"."bankAccount" IS NOT NULL AND "user"."bankAccount" != '' AND ` + + `"user"."bankAccountDigit" IS NOT NULL AND "user"."bankAccountDigit" != '' ` + + ')' + + 'THEN true ' + + 'ELSE false ' + + 'END AS is_filled', ]) .leftJoin('invite.user', 'user') .leftJoin('user.role', 'role') @@ -268,16 +265,4 @@ export class MailHistoryService { resultReturn.queued + resultReturn.sent + resultReturn.used; return resultReturn; } - - async emailsNaoCadastrados(): Promise { - return await this.entityManager.query( - ' SELECT u.* FROM public."user" u ' + - ' INNER JOIN invite i ON u.id = i."userId" ' + - ' WHERE "bankCode" IS NULL ' + - ' AND i."sentAt" IS NOT NULL ' + - ' AND i."sentAt" < now() - INTERVAL \'10 DAYS\' ' + - ' AND u."roleId" = 2' + - ' ORDER BY i."sentAt","fullName" ', - ); - } } diff --git a/src/mail/mail-templates/report_resent_email.hbs b/src/mail/mail-templates/user-daily-conclude.hbs similarity index 68% rename from src/mail/mail-templates/report_resent_email.hbs rename to src/mail/mail-templates/user-daily-conclude.hbs index f7aa97f0..f2b00b01 100644 --- a/src/mail/mail-templates/report_resent_email.hbs +++ b/src/mail/mail-templates/user-daily-conclude.hbs @@ -40,10 +40,17 @@ -

O Sistema CCT Mobilidade RIO detectou que seus Dados Bancários ainda não foram cadastrados.

-

Por favor, solicitamos que atualize o cadastro pelo link abaixo!

-

https://cct.mobilidade.rio/sign-in

-

Obrigado!

+

+ O Sistema CCT Mobilidade RIO detectou que seus Dados Bancários ainda não foram cadastrados. + Por favor, solicitamos que atualize o cadastro pelo link abaixo! +

+

{{userLink}}

+

Solicitamos que anote e guarde sua senha com segurança, para evitar futuros problemas de acesso.

+

+ Caso aconteça, entre em contato com o suporte https://secretariamunicipaldetransportes.movidesk.com/form/6594/. + +

+

Obrigado!

diff --git a/src/mail/mail.service.ts b/src/mail/mail.service.ts index c2138369..4637203c 100644 --- a/src/mail/mail.service.ts +++ b/src/mail/mail.service.ts @@ -3,7 +3,9 @@ import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { I18nContext } from 'nestjs-i18n'; import { AllConfigType } from 'src/config/config.type'; +import { InviteStatus } from 'src/mail-history-statuses/entities/mail-history-status.entity'; import { IMailHistoryStatusCount } from 'src/mail-history-statuses/interfaces/mail-history-status-group.interface'; +import { InviteStatusEnum } from 'src/mail-history-statuses/mail-history-status.enum'; import { SmtpStatus } from 'src/utils/enums/smtp-status.enum'; import { formatLog } from 'src/utils/logging'; import { MaybeType } from '../utils/types/maybe.type'; @@ -216,12 +218,21 @@ export class MailService { /** * @throws `HttpException` */ - async reSendEmailBank(mailData: MailData): Promise { + async reSendEmailBank( + mailData: MailData<{ + inviteStatus: InviteStatus; + hash?: string; + }>, + ): Promise { const mailTitle = 'SMTR - Prefeitura do Município do Rio de Janeiro - Comunicado Importante!'; const from = this.configService.get('mail.senderNotification', { infer: true, }); + const frontendDomain = this.configService.get('app.frontendDomain', { + infer: true, + }); + const inviteStatus = mailData.data.inviteStatus; if (!from) { throw new HttpException( { @@ -243,9 +254,13 @@ export class MailService { to: mailData.to, subject: mailTitle, text: mailTitle, - template: 'report_resent_email', + template: 'user-daily-conclude', context: { title: 'Confirme seu email', + userLink: + inviteStatus.id === InviteStatusEnum.used + ? `${frontendDomain}sign-in` + : `${frontendDomain}conclude-registration/${mailData.data.hash}`, headerTitle: appName, }, }); diff --git a/src/test/test.controller.ts b/src/test/test.controller.ts new file mode 100644 index 00000000..73ad31ec --- /dev/null +++ b/src/test/test.controller.ts @@ -0,0 +1,23 @@ +// import { Controller, Get, Param } from '@nestjs/common'; +// import { ApiTags, ApiParam } from '@nestjs/swagger'; +// import { NullableType } from 'src/utils/types/nullable.type'; +// import { CronJobsService } from '../cron-jobs/cron-jobs.service'; + +// @ApiTags('Test') +// @Controller('test') +// export class TestController { +// constructor(private readonly cronJobsService: CronJobsService) { } + +// @Get() +// async getAll(): Promise> { +// return this.infoService.find(); +// } + +// @Get('v:version') +// @ApiParam({ name: 'version', example: '1' }) +// getByVersion( +// @Param('version') version: string, +// ): Promise> { +// return this.infoService.findByVersion(version); +// } +// } diff --git a/src/test/test.module.ts b/src/test/test.module.ts new file mode 100644 index 00000000..e0a2cdfa --- /dev/null +++ b/src/test/test.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; +import { ScheduleModule } from '@nestjs/schedule'; +import { MailHistoryModule } from 'src/mail-history/mail-history.module'; +import { MailModule } from 'src/mail/mail.module'; +import { SettingsModule } from 'src/settings/settings.module'; +import { ConfigModule } from '@nestjs/config'; +import { JaeModule } from 'src/jae/jae.module'; +import { CoreBankModule } from 'src/core-bank/core-bank.module'; +import { UsersModule } from 'src/users/users.module'; +import { MailCountModule } from 'src/mail-count/mail-count.module'; +import { CronJobsModule } from 'src/cron-jobs/cron-jobs.module'; + +@Module({ + imports: [ + ScheduleModule.forRoot(), + ConfigModule, + SettingsModule, + MailHistoryModule, + MailModule, + UsersModule, + JaeModule, + CoreBankModule, + MailCountModule, + CronJobsModule, + ], +}) +export class TestModule {} diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index 77b4fe7a..31eb997e 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -190,10 +190,27 @@ export class User extends EntityHelper { aux_inviteStatus?: InviteStatus | null; + @Exclude() + aux_inviteHash?: string | null; + aux_bank?: Bank | null; - update(userProps: DeepPartial) { - Object.assign(this, userProps); + /** + * + * @param userProps Properties to update + * @param removeConstraintKeys remove redundant keys to avoid database update + */ + update(userProps: DeepPartial, asUpdateObject = false) { + const props = new User(userProps); + if (asUpdateObject) { + if (props?.email === this?.email) { + (props.email as any) = undefined; + } + if (props?.password === undefined || props?.password === this.password) { + (props.password as any) = undefined; + } + } + Object.assign(this, props); } getLogInfo(showRole?: boolean): string { diff --git a/src/users/interfaces/user-data.interface.ts b/src/users/interfaces/user-data.interface.ts index a3b3ce0d..8114668e 100644 --- a/src/users/interfaces/user-data.interface.ts +++ b/src/users/interfaces/user-data.interface.ts @@ -12,4 +12,8 @@ export interface UserDataInterface { role: Role; status: Status; inviteStatus?: InviteStatus; + bankCode?: number; + bankAgency?: string; + bankAccount?: string; + bankAccountDigit?: string; } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index ddf5ca25..80894d79 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -17,12 +17,14 @@ import { isArrayContainEqual } from 'src/utils/array-utils'; import { Enum } from 'src/utils/enum'; import { HttpErrorMessages } from 'src/utils/enums/http-error-messages.enum'; import { formatLog } from 'src/utils/logging'; +import { stringUppercaseUnaccent } from 'src/utils/string-utils'; import { EntityCondition } from 'src/utils/types/entity-condition.type'; import { InvalidRowsType } from 'src/utils/types/invalid-rows.type'; import { IPaginationOptions } from 'src/utils/types/pagination-options'; import { Brackets, DeepPartial, + EntityManager, FindOptionsWhere, ILike, Repository, @@ -38,7 +40,6 @@ import { IFileUser } from './interfaces/file-user.interface'; import { IFindUserPaginated } from './interfaces/find-user-paginated.interface'; import { IUserUploadResponse } from './interfaces/user-upload-response.interface'; import { FileUserMap } from './mappings/user-file.map'; -import { stringUppercaseUnaccent } from 'src/utils/string-utils'; export enum userUploadEnum { DUPLICATED_FIELD = 'Campo duplicado no arquivo de upload', @@ -54,6 +55,7 @@ export class UsersService { private usersRepository: Repository, private mailHistoryService: MailHistoryService, private banksService: BanksService, + private readonly entityManager: EntityManager, ) {} async create(createProfileDto: CreateUserDto): Promise { @@ -607,4 +609,61 @@ export class UsersService { ); return result; } + + /** + * Get users with status = SENT who didn't create login/password + */ + async getUnregisteredUsers(): Promise { + const results: any[] = await this.entityManager.query( + 'SELECT u."fullName", u."email", u."phone", i."sentAt", i."inviteStatusId", i."hash" ' + + 'FROM public."user" u ' + + 'INNER JOIN invite i ON u.id = i."userId" ' + + `WHERE i."inviteStatusId" = ${InviteStatusEnum.sent} ` + + 'AND i."sentAt" <= NOW() - INTERVAL \'15 DAYS\' ' + + `AND u."roleId" = ${RoleEnum.user} ` + + 'ORDER BY U."fullName", i."sentAt"', + ); + const users: User[] = []; + for (const result of results) { + users.push( + new User({ + fullName: result.fullName, + email: result.email, + phone: result.phone, + aux_inviteStatus: new InviteStatus(Number(result.inviteStatusId)), + aux_inviteHash: result.hash, + }), + ); + } + return users; + } + + /** + * Get users with status = USED who created login/password but didn't fill bank fields + */ + async getNotRegisteredUsers(): Promise { + const results: any[] = await this.entityManager.query( + 'SELECT U."fullName", u.email, u.phone, iv."name", i."sentAt", i."inviteStatusId", i."hash" ' + + 'FROM public."user" U inner join invite i on U.id = i."userId" ' + + 'inner join invite_status iv on iv.id = i."inviteStatusId" ' + + 'where u."bankCode" is null ' + + 'and i."sentAt" <= now() - INTERVAL \'15 DAYS\' ' + + 'and "roleId" <> 1 ' + + 'and i."inviteStatusId" != 2 ' + + 'order by U."fullName", i."sentAt" ', + ); + const users: User[] = []; + for (const result of results) { + users.push( + new User({ + fullName: result.fullName, + email: result.email, + phone: result.phone, + aux_inviteStatus: new InviteStatus(Number(result.inviteStatusId)), + aux_inviteHash: result.hash, + }), + ); + } + return users; + } }