import { Injectable } from '@nestjs/common' import { plainToClass } from 'class-transformer' import { isEmpty, pick } from 'lodash' import debug, { Debugger } from 'debug' import { Patient } from '../../models' import { CountryHelper, LanguageCountryCode } from '../../utils' import { SingleAccessToken } from '../smartfit/types' import { Person } from '../smartfit/models' import { BusinessExceptionError, UnauthorizedError, UserNotFoundError } from '../../errors' import { SmartfitApi } from '../smartfit/smartfit.api' import { TeleconsultationsCreditService } from '../credits/teleconsultations_credit.service' import { PatientRepository } from '../../repositories/patient.repository' import { SmartfitUnitRepository } from '../../repositories/smartfit_unit.repository' import { personToPatient } from './converters' import { AuthenticationService } from './authentication.service' export const SMARTFIT_TENANT = 'smartfit' export const COLLABORATOR_TELECONSULTATIONS_CREDIT = 2 @Injectable() export class AccountService { private debug: Debugger = debug(this.constructor.name) constructor( private readonly patientRepository: PatientRepository, private readonly authentication: AuthenticationService, private readonly smartfitAPI: SmartfitApi, private readonly teleconsultationsCreditService: TeleconsultationsCreditService, private readonly smartfitUnitRepository: SmartfitUnitRepository, ) {} async createOrUpdatePatient(login: string): Promise { const patient = await this.patientRepository.findByLogin(login) if (!patient) return this.createPatientIfStudentOrCollaborator(login) return this.checkAndUpdatePatient(patient) } async createPatientAndAuth(patient: Patient, signUpAt = new Date()): Promise { const exists = await this.patientRepository.findByLogin( patient.login, CountryHelper.parseLanguageCode(patient.country_code), ) if (exists) throw new UnauthorizedError(patient.email, 'Patient is already registered') patient.sign_up_at = signUpAt if (!patient.tenant) patient.tenant = SMARTFIT_TENANT const id = await this.createEmailSafeAuthentication(patient) try { await this.createOnRepositoryAndAddCredit(patient, id) } catch (error) { await this.authentication.delete(id) throw error } return patient } async createPatientIfStudentOrCollaborator(login: string, signUpAt = new Date()): Promise { let accessToken: SingleAccessToken let person: Person try { accessToken = await this.smartfitAPI.getByLogin(login) if (!accessToken?.token) throw new UserNotFoundError(login) person = await this.smartfitAPI.getPersonByToken(accessToken) } catch (error) { if (error instanceof UnauthorizedError) throw error throw new UserNotFoundError(login) } const patient = personToPatient(person, login) const smartfitUnit = await this.smartfitUnitRepository.findByAcronym(patient.smartfit_unit_acronym) if (!smartfitUnit) patient.smartfit_unit_acronym = null return this.createPatientAndAuth(patient, signUpAt) } async checkAndUpdatePatient(patient: Patient): Promise { await this.createAuthenticationIfNotExists(patient) if (process.env.STAGE !== 'prod') return patient try { const person = await this.smartfitAPI.getPersonByLogin(patient.login) if (!person) return patient return await this.updatePatientByPerson(patient, person) } catch (error) { this.debug(error.message) return patient } } private async updatePatientByPerson(patient: Patient, person: Person): Promise { const update: Partial = plainToClass(Patient, { cell_phone: person.phone, email: person.email, country_code: patient.country_code, tenant: patient.tenant || SMARTFIT_TENANT, }) if (patient.cell_phone === update.cell_phone) delete update.cell_phone if (patient.email === update.email || Patient.loginField(patient.country_code) === 'email') delete update.email if (patient.tenant === update.tenant) delete update.tenant delete update.country_code if (isEmpty(update)) return patient if ( patient.document_number && update.tenant && (await this.patientRepository.findByDocumentNumberAndTenant(patient.document_number, update.tenant)) ) throw new BusinessExceptionError('Already exists an user with the document number and tenant') const updatedPatient = plainToClass(Patient, { ...patient, ...update }, { ignoreDecorators: true }) if (Object.keys(update).length > 0) await this.patientRepository.updateBasicData(updatedPatient) return updatedPatient } private async createOnRepositoryAndAddCredit(patient: Patient, id: string): Promise { await this.patientRepository.create( Object.assign(patient, { id, country_code: CountryHelper.parseCode(patient.country_code, LanguageCountryCode.PT_BR), }), ) if (patient.collaborator) await this.teleconsultationsCreditService.createTransaction({ patientId: patient.id, value: COLLABORATOR_TELECONSULTATIONS_CREDIT, reason: 'collaborator_signup', author: patient, }) } private async createEmailSafeAuthentication(patient: { email?: string name: string id?: string country_code?: string }): Promise { const properties = ['name', 'country_code'] if (patient.id) properties.push('id') if (!(await this.authentication.getUserByEmail(patient.email))) properties.push('email') const user = await this.authentication.createPatient(pick(patient, properties)) if (!user) throw new Error('Authentication was not correctly created') return user.uid } private async createAuthenticationIfNotExists(patient: Patient): Promise { const user = await this.authentication.getUser(patient.id) if (user) return await this.createEmailSafeAuthentication(patient) } }