import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CampaignType } from '../campaigns/enums/campaign-type.enum'; import { getCampaignTypeDefinition } from '../campaigns/constants/campaign-types.constants'; import { Reserve } from '../reserves/entities/reserve.entity'; import { Salon } from '../salons/entities/salon.entity'; import { buildReserveSmsVariables } from '../sms-settings/utils/build-reserve-sms-variables.util'; import { NotificationScenarioKey } from './enums/notification-scenario-key.enum'; import { NotificationDispatchService } from './notification-dispatch.service'; @Injectable() export class SalonNotificationsService { private readonly logger = new Logger(SalonNotificationsService.name); constructor( private readonly notificationDispatchService: NotificationDispatchService, @InjectRepository(Salon) private readonly salonsRepository: Repository, @InjectRepository(Reserve) private readonly reservesRepository: Repository, ) {} async notifyReserveCreated(reserveId: string): Promise { await this.notifyForReserve( reserveId, NotificationScenarioKey.RESERVE_CREATED, `/work-schedule`, ); } async notifyReserveConfirmed(reserveId: string): Promise { await this.notifyForReserve( reserveId, NotificationScenarioKey.RESERVE_CONFIRMED, `/work-schedule`, ); } async notifyReserveCancelled(reserveId: string): Promise { await this.notifyForReserve( reserveId, NotificationScenarioKey.RESERVE_CANCELLED, `/work-schedule`, ); } async notifyReserveRemindersSent(params: { salonId: string; sentCount: number; appointmentDate: string; }): Promise { if (params.sentCount <= 0) { return; } const salon = await this.salonsRepository.findOne({ where: { id: params.salonId }, }); if (!salon) { return; } const appointmentDate = this.formatDate(params.appointmentDate); await this.safeCreate({ salonId: salon.id, userId: salon.ownerId, key: NotificationScenarioKey.RESERVE_REMINDER_SENT, variables: { salonName: salon.name, sentCount: params.sentCount.toLocaleString('fa-IR'), appointmentDate, }, link: '/work-schedule', relatedEntityType: 'reserve_reminder', relatedEntityId: null, }); } async notifyCampaignSent(params: { salon: Salon; type: CampaignType; campaignRunId: string; sentCount: number; failedCount: number; targetCount: number; estimatedRevenue: number; }): Promise { const definition = getCampaignTypeDefinition(params.type); const key = params.sentCount === 0 ? NotificationScenarioKey.CAMPAIGN_FAILED : NotificationScenarioKey.CAMPAIGN_SENT; await this.safeCreate({ salonId: params.salon.id, userId: params.salon.ownerId, key, variables: { salonName: params.salon.name, campaignLabel: definition.label, campaignType: params.type, sentCount: params.sentCount.toLocaleString('fa-IR'), failedCount: params.failedCount.toLocaleString('fa-IR'), targetCount: params.targetCount.toLocaleString('fa-IR'), estimatedRevenue: params.estimatedRevenue.toLocaleString('fa-IR'), }, link: `/campaigns/results/${params.campaignRunId}`, relatedEntityType: 'campaign_run', relatedEntityId: params.campaignRunId, }); } async notifyCampaignFailed(params: { salon: Salon; type: CampaignType; targetCount: number; }): Promise { const definition = getCampaignTypeDefinition(params.type); await this.safeCreate({ salonId: params.salon.id, userId: params.salon.ownerId, key: NotificationScenarioKey.CAMPAIGN_FAILED, variables: { salonName: params.salon.name, campaignLabel: definition.label, campaignType: params.type, targetCount: params.targetCount.toLocaleString('fa-IR'), }, link: '/campaigns', relatedEntityType: 'campaign', relatedEntityId: null, }); } private async notifyForReserve( reserveId: string, key: NotificationScenarioKey, link: string, ): Promise { const reserve = await this.reservesRepository.findOne({ where: { id: reserveId }, relations: { salon: true, customer: { user: true }, stylist: { user: true }, }, }); if (!reserve?.salon) { return; } const variables = buildReserveSmsVariables(reserve); await this.safeCreate({ salonId: reserve.salonId, userId: reserve.salon.ownerId, key, variables, link, relatedEntityType: 'reserve', relatedEntityId: reserve.id, }); } private async safeCreate(params: { salonId: string; userId: string; key: NotificationScenarioKey; variables: Record; link?: string | null; relatedEntityType?: string | null; relatedEntityId?: string | null; }): Promise { try { await this.notificationDispatchService.createForSalon(params); } catch (error) { this.logger.error( `Failed to create notification ${params.key} for salon ${params.salonId}`, error instanceof Error ? error.stack : undefined, ); } } private formatDate(date: string): string { const parsed = new Date(`${date}T00:00:00`); if (Number.isNaN(parsed.getTime())) { return date; } return parsed.toLocaleDateString('fa-IR'); } }