notification
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
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<Salon>,
|
||||
@InjectRepository(Reserve)
|
||||
private readonly reservesRepository: Repository<Reserve>,
|
||||
) {}
|
||||
|
||||
async notifyReserveCreated(reserveId: string): Promise<void> {
|
||||
await this.notifyForReserve(
|
||||
reserveId,
|
||||
NotificationScenarioKey.RESERVE_CREATED,
|
||||
`/work-schedule`,
|
||||
);
|
||||
}
|
||||
|
||||
async notifyReserveConfirmed(reserveId: string): Promise<void> {
|
||||
await this.notifyForReserve(
|
||||
reserveId,
|
||||
NotificationScenarioKey.RESERVE_CONFIRMED,
|
||||
`/work-schedule`,
|
||||
);
|
||||
}
|
||||
|
||||
async notifyReserveCancelled(reserveId: string): Promise<void> {
|
||||
await this.notifyForReserve(
|
||||
reserveId,
|
||||
NotificationScenarioKey.RESERVE_CANCELLED,
|
||||
`/work-schedule`,
|
||||
);
|
||||
}
|
||||
|
||||
async notifyReserveRemindersSent(params: {
|
||||
salonId: string;
|
||||
sentCount: number;
|
||||
appointmentDate: string;
|
||||
}): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<string, string>;
|
||||
link?: string | null;
|
||||
relatedEntityType?: string | null;
|
||||
relatedEntityId?: string | null;
|
||||
}): Promise<void> {
|
||||
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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user