invoice event

This commit is contained in:
2026-02-22 14:44:04 +03:30
parent eee5de8d00
commit cc8f96a99b
6 changed files with 104 additions and 6 deletions
@@ -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,
// ) { }
// }
+6 -3
View File
@@ -6,9 +6,11 @@ import { Invoice } from './entities/invoice.entity';
import { InvoiceItem } from './entities/invoice-item.entity'; import { InvoiceItem } from './entities/invoice-item.entity';
import { ProductModule } from '../product/product.module'; import { ProductModule } from '../product/product.module';
import { InvoiceRepository } from './repositories/invoice.repository'; import { InvoiceRepository } from './repositories/invoice.repository';
import { InvoiceListeners } from './listeners/invoice.listeners';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { RolesModule } from '../roles/roles.module'; import { RolesModule } from '../roles/roles.module';
import { AuthModule } from '../auth/auth.module'; import { AuthModule } from '../auth/auth.module';
import { NotificationsModule } from '../notification/notifications.module';
@Module({ @Module({
imports: [ imports: [
@@ -16,10 +18,11 @@ import { AuthModule } from '../auth/auth.module';
ProductModule, ProductModule,
RolesModule, RolesModule,
JwtModule, JwtModule,
AuthModule AuthModule,
NotificationsModule,
], ],
controllers: [InvoiceController], controllers: [InvoiceController],
providers: [InvoiceService, InvoiceRepository], providers: [InvoiceService, InvoiceRepository, InvoiceListeners],
exports: [InvoiceService, InvoiceRepository], exports: [InvoiceService, InvoiceRepository],
}) })
export class InvoiceModule { } export class InvoiceModule {}
+17 -2
View File
@@ -1,5 +1,6 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { CreateInvoiceDto } from './dto/create-invoice.dto'; import { CreateInvoiceDto } from './dto/create-invoice.dto';
import { UpdateInvoiceDto } from './dto/update-invoice.dto'; import { UpdateInvoiceDto } from './dto/update-invoice.dto';
import { FindInvoicesDto } from './dto/find-invoices.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 { User } from '../user/entities/user.entity';
import { ProductService } from '../product/providers/product.service'; import { ProductService } from '../product/providers/product.service';
import { InvoiceRepository } from './repositories/invoice.repository'; import { InvoiceRepository } from './repositories/invoice.repository';
import { InvoiceCreatedEvent } from './events/invoice.events';
const TAX_RATE = 0.1; const TAX_RATE = 0.1;
@@ -18,7 +20,8 @@ export class InvoiceService {
private readonly em: EntityManager, private readonly em: EntityManager,
private readonly productService: ProductService, private readonly productService: ProductService,
private readonly invoiceRepository: InvoiceRepository, private readonly invoiceRepository: InvoiceRepository,
) { } private readonly eventEmitter: EventEmitter2,
) {}
async create(dto: CreateInvoiceDto) { async create(dto: CreateInvoiceDto) {
@@ -49,7 +52,7 @@ export class InvoiceService {
const discount = dto.items.reduce((acc, item) => acc + (item.discount ?? 0), 0); 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, { const invoice = em.create(Invoice, {
user, user,
...(request && { request }), ...(request && { request }),
@@ -98,6 +101,18 @@ export class InvoiceService {
await em.flush(); await em.flush();
return invoice; 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) { findAll(userId: string, dto: FindInvoicesDto) {
@@ -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<string>('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),
);
}
}
}
@@ -64,6 +64,15 @@ export class LearningController {
// ----------------- Admin routes ----------------- // ----------------- 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') @Post('admin/learnings')
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@Permissions(PermissionEnum.MANAGE_LEARNINGS) @Permissions(PermissionEnum.MANAGE_LEARNINGS)
@@ -96,6 +105,14 @@ export class LearningController {
return this.learningService.createLearningCategory(createDto); 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') @Patch('admin/learnings/category/:id')
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@Permissions(PermissionEnum.MANAGE_LEARNINGS) @Permissions(PermissionEnum.MANAGE_LEARNINGS)
@@ -60,7 +60,7 @@ export class LearningService {
} }
async findLearningsVideos() { async findLearningsVideos() {
const learnings = await this.learningRepository.findAll(); const learnings = await this.learningRepository.findAll({populate: ['category']});
return { learnings }; return { learnings };
} }