From cc8f96a99bc49f806e03be93cf655b4c91da3df1 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Sun, 22 Feb 2026 14:44:04 +0330 Subject: [PATCH] invoice event --- src/modules/invoice/events/invoice.events.ts | 19 ++++++++ src/modules/invoice/invoice.module.ts | 9 ++-- src/modules/invoice/invoice.service.ts | 19 +++++++- .../invoice/listeners/invoice.listeners.ts | 44 +++++++++++++++++++ src/modules/learnings/learning.controller.ts | 17 +++++++ .../learnings/providers/learning.service.ts | 2 +- 6 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 src/modules/invoice/events/invoice.events.ts create mode 100644 src/modules/invoice/listeners/invoice.listeners.ts diff --git a/src/modules/invoice/events/invoice.events.ts b/src/modules/invoice/events/invoice.events.ts new file mode 100644 index 0000000..685c19c --- /dev/null +++ b/src/modules/invoice/events/invoice.events.ts @@ -0,0 +1,19 @@ +export class InvoiceCreatedEvent { + constructor( + public readonly invoiceId: string, + public readonly invoiceNumber: string, + public readonly total: number, + public readonly userId: string, + ) {} +} + +// export class OrderStatusChangedEvent { +// constructor( +// public readonly orderId: string, +// public readonly userId: string, +// public readonly orderNumber: string, +// public readonly: string, +// public readonly previousStatus: OrderStatusEnum, +// public readonly newStatus: OrderStatusEnum, +// ) { } +// } diff --git a/src/modules/invoice/invoice.module.ts b/src/modules/invoice/invoice.module.ts index 868ad1e..e242e6a 100644 --- a/src/modules/invoice/invoice.module.ts +++ b/src/modules/invoice/invoice.module.ts @@ -6,9 +6,11 @@ import { Invoice } from './entities/invoice.entity'; import { InvoiceItem } from './entities/invoice-item.entity'; import { ProductModule } from '../product/product.module'; import { InvoiceRepository } from './repositories/invoice.repository'; +import { InvoiceListeners } from './listeners/invoice.listeners'; import { JwtModule } from '@nestjs/jwt'; import { RolesModule } from '../roles/roles.module'; import { AuthModule } from '../auth/auth.module'; +import { NotificationsModule } from '../notification/notifications.module'; @Module({ imports: [ @@ -16,10 +18,11 @@ import { AuthModule } from '../auth/auth.module'; ProductModule, RolesModule, JwtModule, - AuthModule + AuthModule, + NotificationsModule, ], controllers: [InvoiceController], - providers: [InvoiceService, InvoiceRepository], + providers: [InvoiceService, InvoiceRepository, InvoiceListeners], exports: [InvoiceService, InvoiceRepository], }) -export class InvoiceModule { } +export class InvoiceModule {} diff --git a/src/modules/invoice/invoice.service.ts b/src/modules/invoice/invoice.service.ts index 070ef5b..112ace8 100644 --- a/src/modules/invoice/invoice.service.ts +++ b/src/modules/invoice/invoice.service.ts @@ -1,5 +1,6 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { EntityManager } from '@mikro-orm/postgresql'; +import { EventEmitter2 } from '@nestjs/event-emitter'; import { CreateInvoiceDto } from './dto/create-invoice.dto'; import { UpdateInvoiceDto } from './dto/update-invoice.dto'; import { FindInvoicesDto } from './dto/find-invoices.dto'; @@ -9,6 +10,7 @@ import { Request } from '../request/entities/request.entity'; import { User } from '../user/entities/user.entity'; import { ProductService } from '../product/providers/product.service'; import { InvoiceRepository } from './repositories/invoice.repository'; +import { InvoiceCreatedEvent } from './events/invoice.events'; const TAX_RATE = 0.1; @@ -18,7 +20,8 @@ export class InvoiceService { private readonly em: EntityManager, private readonly productService: ProductService, private readonly invoiceRepository: InvoiceRepository, - ) { } + private readonly eventEmitter: EventEmitter2, + ) {} async create(dto: CreateInvoiceDto) { @@ -49,7 +52,7 @@ export class InvoiceService { const discount = dto.items.reduce((acc, item) => acc + (item.discount ?? 0), 0); - return this.em.transactional(async (em) => { + const invoice = await this.em.transactional(async (em) => { const invoice = em.create(Invoice, { user, ...(request && { request }), @@ -98,6 +101,18 @@ export class InvoiceService { await em.flush(); return invoice; }); + + this.eventEmitter.emit( + InvoiceCreatedEvent.name, + new InvoiceCreatedEvent( + invoice.id, + String(invoice.invoiceNumber), + invoice.total!, + user.id, + ), + ); + + return invoice; } findAll(userId: string, dto: FindInvoicesDto) { diff --git a/src/modules/invoice/listeners/invoice.listeners.ts b/src/modules/invoice/listeners/invoice.listeners.ts new file mode 100644 index 0000000..ffef235 --- /dev/null +++ b/src/modules/invoice/listeners/invoice.listeners.ts @@ -0,0 +1,44 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { ConfigService } from '@nestjs/config'; +import { NotificationQueueService } from 'src/modules/notification/services/notification-queue.service'; +import { InvoiceCreatedEvent } from '../events/invoice.events'; + +@Injectable() +export class InvoiceListeners { + private readonly logger = new Logger(InvoiceListeners.name); + private invoiceCreatedSmsTemplateId: string; + + constructor( + private readonly notificationQueueService: NotificationQueueService, + private readonly configService: ConfigService, + ) { + this.invoiceCreatedSmsTemplateId = + this.configService.get('SMS_PATTERN_INVOICE_CREATED') ?? 'invoice-created'; + } + + @OnEvent(InvoiceCreatedEvent.name) + async handleInvoiceCreated(event: InvoiceCreatedEvent) { + try { + this.logger.log( + `Invoice created event received: ${event.invoiceId} for user: ${event.userId}, invoice number: ${event.invoiceNumber}`, + ); + + await this.notificationQueueService.addSmsNotification({ + recipient: { userId: event.userId }, + templateId: this.invoiceCreatedSmsTemplateId, + parameters: { + invoiceNumber: event.invoiceNumber, + total: event.total.toString(), + }, + }); + + this.logger.log(`Invoice created notification queued for user ${event.userId}`); + } catch (error) { + this.logger.error( + `Failed to send notification for invoice created event: ${event.invoiceId}`, + error instanceof Error ? error.stack : String(error), + ); + } + } +} diff --git a/src/modules/learnings/learning.controller.ts b/src/modules/learnings/learning.controller.ts index ba27ca0..2f21be7 100755 --- a/src/modules/learnings/learning.controller.ts +++ b/src/modules/learnings/learning.controller.ts @@ -64,6 +64,15 @@ export class LearningController { // ----------------- Admin routes ----------------- + @Get('admin/learnings') + @UseGuards(AdminAuthGuard) + @Permissions(PermissionEnum.MANAGE_LEARNINGS) + @ApiOperation({ summary: 'Find all learnings videos as admin' }) + findAllLearningsAsAdmin() { + return this.learningService.findLearningsVideos(); + } + + @Post('admin/learnings') @UseGuards(AdminAuthGuard) @Permissions(PermissionEnum.MANAGE_LEARNINGS) @@ -96,6 +105,14 @@ export class LearningController { return this.learningService.createLearningCategory(createDto); } + @Get('admin/learnings/category') + @UseGuards(AdminAuthGuard) + @Permissions(PermissionEnum.MANAGE_LEARNINGS) + @ApiOperation({ summary: 'Get all learning categories' }) + getCategories() { + return this.learningService.findCategories() + } + @Patch('admin/learnings/category/:id') @UseGuards(AdminAuthGuard) @Permissions(PermissionEnum.MANAGE_LEARNINGS) diff --git a/src/modules/learnings/providers/learning.service.ts b/src/modules/learnings/providers/learning.service.ts index 7c18c61..43d1052 100755 --- a/src/modules/learnings/providers/learning.service.ts +++ b/src/modules/learnings/providers/learning.service.ts @@ -60,7 +60,7 @@ export class LearningService { } async findLearningsVideos() { - const learnings = await this.learningRepository.findAll(); + const learnings = await this.learningRepository.findAll({populate: ['category']}); return { learnings }; }