import { Injectable } from '@nestjs/common' import { addSeconds } from 'date-fns' import { debug, Debugger } from 'debug' import { isNil } from 'lodash' import { NotFoundError } from '../../../errors' import { Patient, PaymentProviders, Plan, Subscription, SubscriptionInstallment, SubscriptionTransaction, } from '../../../models' import { PatientRepository, PlanRepository, SubscriptionInstallmentRepository, SubscriptionRepository, SubscriptionTransactionRepository, } from '../../../repositories' import { OmieService, Person, SmartfitApi } from '../../../services' import { OmieConfigService } from '../../../services/omie/service' type ServiceOrderData = { code: number customerCode: number } type DataNeedsToBeGenerated = { patient: Patient plan: Plan subscriptionTransaction: SubscriptionTransaction totalInstallments: number person: Person } const SECONDS_INTERVAL_BETWEEN_REQUESTS = 2 @Injectable() export class Contract { private debug: Debugger = debug(this.constructor.name) private plans: { [key: string]: Plan } = {} constructor( private readonly patientRepository: PatientRepository, private readonly planRepository: PlanRepository, private readonly subscriptionRepository: SubscriptionRepository, private readonly subscriptionTransactionRepository: SubscriptionTransactionRepository, private readonly subscriptionInstallmentRepository: SubscriptionInstallmentRepository, private readonly omieService: OmieService, private readonly omieConfigService: OmieConfigService, private readonly smartfitApi: SmartfitApi, ) {} async checkActivatedSubscriptions(date: Date): Promise { const subscriptions = await this.findActivatedSubscription(date) const now = new Date() await Promise.allSettled( subscriptions?.map(async (subscription, index) => { try { const lastPaidsubscriptionInstallment = subscription.installments.find( installment => installment.installment === 1 && !isNil(installment.paid_at), ) if (!this.isAllowToEmit(subscription, lastPaidsubscriptionInstallment)) return await this.subscriptionInstallmentRepository.scheduleServiceOrderInvoicing( lastPaidsubscriptionInstallment, addSeconds(now, SECONDS_INTERVAL_BETWEEN_REQUESTS * index), ) } catch (error) { this.debug(error) } }) || [], ) } async emitServiceOrderForInstallment(subscriptionInstallment: SubscriptionInstallment): Promise { if (isNil(subscriptionInstallment.paid_at)) return const subscription = await this.subscriptionRepository.get(subscriptionInstallment.subscription_id) await this.emitServiceOrder(subscription, subscriptionInstallment) } async cancel(subscription: Subscription): Promise { if (!subscription.isCancelled) return if (subscription.paymentProvider !== PaymentProviders.N2B) return const { enable_cancel } = await this.omieConfigService.getConfig() if (!enable_cancel) return if (subscription.omieOSCode) await this.omieService.cancelInvoice(Number(subscription.omieOSCode)) if (subscription.omieContractCode) await this.omieService.cancelContract(Number(subscription.omieContractCode)) } private async emitServiceOrder( subscription: Subscription, paidSubscriptionInstallment: SubscriptionInstallment, ): Promise { if (!this.isAllowToEmit(subscription, paidSubscriptionInstallment)) return if (!isNil(paidSubscriptionInstallment.omie_os_code) && !paidSubscriptionInstallment.omie_is_invoiced) { await this.invoiceServiceOrder(paidSubscriptionInstallment, { code: +paidSubscriptionInstallment.omie_os_code }) return } const serviceOrder = await this.generateServiceOrder(subscription, paidSubscriptionInstallment) try { await this.invoiceServiceOrder(paidSubscriptionInstallment, serviceOrder) } catch (error) { await this.subscriptionInstallmentRepository.save( { id: paidSubscriptionInstallment.id, omie_os_code: serviceOrder.code.toString(), }, ['omie_os_code'], ) } } private async generateServiceOrder( subscription: Subscription, paidSubscriptionInstallment: SubscriptionInstallment, ): Promise { const { patient, plan, subscriptionTransaction, totalInstallments, person, } = await this.fetchDataToGenerateServiceOrder(subscription) const serviceOrder = await this.omieService.generateServiceOrder({ patient, plan, amount: paidSubscriptionInstallment.amount, subscription: patient.subscription, address: { street: person.address.street, number: person.address.number, complement: person.address.complement, neighborhood: person.address.region, city: person.address.city, state: person.address.uf, zipCode: person.address.zip, }, installment: paidSubscriptionInstallment, totalInstallments, cardType: subscriptionTransaction.card_type, }) if (isNil(patient.omie_code)) await this.patientRepository.save({ id: patient.id, omie_code: serviceOrder.customerCode.toString() }, [ 'omie_code', ]) return serviceOrder } private async fetchDataToGenerateServiceOrder(subscription: Subscription): Promise { const [patient, plan, subscriptionTransaction, totalInstallments] = await Promise.all([ this.patientRepository.get(subscription.patientId), this.getPlan(subscription.planId), this.subscriptionTransactionRepository.findLastBySubscription(subscription.id), this.subscriptionInstallmentRepository.getTotalBySubscription(subscription.id), ]) const person = await this.smartfitApi.getPersonByLogin(patient.login) return { patient, plan, subscriptionTransaction, totalInstallments, person } } private async getPlan(planId: string): Promise { if (!isNil(this.plans[planId])) return this.plans[planId] const plan = await this.planRepository.get(planId) if (isNil(plan)) throw new NotFoundError(`Plano ${planId} não encontrado`) return (this.plans[planId] = plan) } private async invoiceServiceOrder( paidSubscriptionInstallment: SubscriptionInstallment, serviceOrder: { code: number }, ): Promise { await this.omieService.invoiceServiceOrder(serviceOrder.code) await this.subscriptionInstallmentRepository.save( { id: paidSubscriptionInstallment.id, omie_os_code: serviceOrder.code.toString(), omie_is_invoiced: true, }, ['omie_os_code', 'omie_is_invoiced'], ) } private findActivatedSubscription(date = new Date()): Promise { return this.subscriptionRepository.findActiveByTrialExpiresAtAndFirstInstallmentNotInvoiced(date) } private isAllowToEmit = (subscription: Subscription, installment: SubscriptionInstallment): boolean => { if (subscription.paymentProvider !== PaymentProviders.N2B) return false if (isNil(installment.paid_at)) return false if (!isNil(installment.omie_os_code) && installment.omie_is_invoiced) return false if (installment.installment > 1) return true return !subscription.isTrial && !subscription.isCancelled } }