emit order created event
This commit is contained in:
@@ -58,7 +58,7 @@ export class OrdersCrone {
|
|||||||
payment.failedAt = new Date();
|
payment.failedAt = new Date();
|
||||||
|
|
||||||
if (payment.order) {
|
if (payment.order) {
|
||||||
payment.order.status = OrderStatus.FAILED;
|
payment.order.status = OrderStatus.CANCELED;
|
||||||
|
|
||||||
// prepare restore payload
|
// prepare restore payload
|
||||||
const items = (payment.order as any).items || [];
|
const items = (payment.order as any).items || [];
|
||||||
|
|||||||
@@ -39,3 +39,5 @@ export interface OrderCouponDetail {
|
|||||||
|
|
||||||
maxDiscount?: number;
|
maxDiscount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type StatusTransitionRef = 'user' | 'admin';
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
|
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
|
||||||
import { OrderCreatedEvent } from '../events/order.events';
|
import { OrderCreatedEvent, OrderStatusChangedEvent } from '../events/order.events';
|
||||||
import { Permission } from 'src/common/enums/permission.enum';
|
import { Permission } from 'src/common/enums/permission.enum';
|
||||||
import { NotifTitleEnum } from 'src/modules/notifications/interfaces/notification.interface';
|
import { NotifTitleEnum } from 'src/modules/notifications/interfaces/notification.interface';
|
||||||
import { NotificationService } from 'src/modules/notifications/services/notification.service';
|
import { NotificationService } from 'src/modules/notifications/services/notification.service';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { OrderRepository } from '../repositories/order.repository';
|
||||||
|
import { OrderStatus } from '../interface/order.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrderListeners {
|
export class OrderListeners {
|
||||||
@@ -13,6 +15,7 @@ export class OrderListeners {
|
|||||||
private orderCreatedSmsTemplateId: string;
|
private orderCreatedSmsTemplateId: string;
|
||||||
constructor(
|
constructor(
|
||||||
private readonly adminService: AdminRepository,
|
private readonly adminService: AdminRepository,
|
||||||
|
private readonly OrderRepository: OrderRepository,
|
||||||
private readonly notificationService: NotificationService,
|
private readonly notificationService: NotificationService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
) {
|
) {
|
||||||
@@ -63,4 +66,178 @@ export class OrderListeners {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnEvent(OrderStatusChangedEvent.name)
|
||||||
|
async handleOrderStatusChanged(event: OrderStatusChangedEvent) {
|
||||||
|
try {
|
||||||
|
this.logger.log(
|
||||||
|
`Order status changed event received: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// const order = await this.OrderRepository.findOneById(event.orderId, { populate: ['user'] });
|
||||||
|
// if (!order) {
|
||||||
|
// this.logger.warn(`Order not found: ${event.orderId}`);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if (order.user && order.user.phoneNumber) {
|
||||||
|
// this.logger.warn(`Order not found: ${event.orderId}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const recipients = [
|
||||||
|
{
|
||||||
|
userId: event.userId,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
await this.notificationService.sendNotification({
|
||||||
|
restaurantId: event.restaurantId,
|
||||||
|
message: this.factory(event.newStatus, event)?.message,
|
||||||
|
recipients,
|
||||||
|
metadata: {
|
||||||
|
priority: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to send notification for order status changed event: ${event.restaurantId}`,
|
||||||
|
error instanceof Error ? error.stack : String(error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private factory(orderStatus: OrderStatus, event: OrderStatusChangedEvent) {
|
||||||
|
switch (orderStatus) {
|
||||||
|
case OrderStatus.CONFIRMED:
|
||||||
|
return {
|
||||||
|
message: {
|
||||||
|
title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
content: `Order ${event.orderNumber} confirmed .`,
|
||||||
|
sms: {
|
||||||
|
templateId: this.orderCreatedSmsTemplateId,
|
||||||
|
parameters: {
|
||||||
|
orderNumber: event.orderNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushNotif: {
|
||||||
|
title: `Order ${event.orderNumber} has been confirmed successfully`,
|
||||||
|
content: `Order ${event.orderNumber} has been confirmed successfully`,
|
||||||
|
icon: `Order ${event.orderNumber} has been confirmed successfully`,
|
||||||
|
action: {
|
||||||
|
type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
url: ``,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case OrderStatus.PREPARING:
|
||||||
|
return {
|
||||||
|
message: {
|
||||||
|
title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
content: `Order ${event.orderNumber} is preparing.`,
|
||||||
|
sms: {
|
||||||
|
templateId: this.orderCreatedSmsTemplateId,
|
||||||
|
parameters: {
|
||||||
|
orderNumber: event.orderNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushNotif: {
|
||||||
|
title: `Order ${event.orderNumber} is preparing`,
|
||||||
|
content: `Order ${event.orderNumber} is preparing`,
|
||||||
|
icon: `Order ${event.orderNumber} is preparing`,
|
||||||
|
action: {
|
||||||
|
type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
url: ``,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case OrderStatus.READY:
|
||||||
|
return {
|
||||||
|
message: {
|
||||||
|
title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
content: `Order ${event.orderNumber} is ready.`,
|
||||||
|
sms: {
|
||||||
|
templateId: this.orderCreatedSmsTemplateId,
|
||||||
|
parameters: {
|
||||||
|
orderNumber: event.orderNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushNotif: {
|
||||||
|
title: `Order ${event.orderNumber} is ready`,
|
||||||
|
content: `Order ${event.orderNumber} is ready`,
|
||||||
|
icon: `Order ${event.orderNumber} is ready`,
|
||||||
|
action: {
|
||||||
|
type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
url: ``,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case OrderStatus.SHIPPED:
|
||||||
|
return {
|
||||||
|
message: {
|
||||||
|
title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
content: `Order ${event.orderNumber} is shipped.`,
|
||||||
|
sms: {
|
||||||
|
templateId: this.orderCreatedSmsTemplateId,
|
||||||
|
parameters: {
|
||||||
|
orderNumber: event.orderNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushNotif: {
|
||||||
|
title: `Order ${event.orderNumber} is shipped`,
|
||||||
|
content: `Order ${event.orderNumber} is shipped`,
|
||||||
|
icon: `Order ${event.orderNumber} is shipped`,
|
||||||
|
action: {
|
||||||
|
type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
url: ``,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case OrderStatus.CANCELED:
|
||||||
|
return {
|
||||||
|
message: {
|
||||||
|
title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
content: `Order ${event.orderNumber} is canceled.`,
|
||||||
|
sms: {
|
||||||
|
templateId: this.orderCreatedSmsTemplateId,
|
||||||
|
parameters: {
|
||||||
|
orderNumber: event.orderNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushNotif: {
|
||||||
|
title: `Order ${event.orderNumber} is canceled`,
|
||||||
|
content: `Order ${event.orderNumber} is canceled`,
|
||||||
|
icon: `Order ${event.orderNumber} is canceled`,
|
||||||
|
action: {
|
||||||
|
type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
url: ``,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
message: {
|
||||||
|
title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
content: `Order ${event.orderNumber} is ....`,
|
||||||
|
sms: {
|
||||||
|
templateId: this.orderCreatedSmsTemplateId,
|
||||||
|
parameters: {
|
||||||
|
orderNumber: event.orderNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushNotif: {
|
||||||
|
title: `Order ${event.orderNumber} is ...`,
|
||||||
|
content: `Order ${event.orderNumber} is ...`,
|
||||||
|
icon: `Order ${event.orderNumber} is ...`,
|
||||||
|
action: {
|
||||||
|
type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
|
url: ``,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
|||||||
import { Payment } from 'src/modules/payments/entities/payment.entity';
|
import { Payment } from 'src/modules/payments/entities/payment.entity';
|
||||||
import { InventoryService } from 'src/modules/inventory/inventory.service';
|
import { InventoryService } from 'src/modules/inventory/inventory.service';
|
||||||
import { BulkReserveFoodDto } from 'src/modules/inventory/dto/bulk-reserve-food.dto';
|
import { BulkReserveFoodDto } from 'src/modules/inventory/dto/bulk-reserve-food.dto';
|
||||||
|
import { StatusTransitionRef } from '../interface/order.interface';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { OrderCreatedEvent, OrderStatusChangedEvent } from '../events/order.events';
|
||||||
type OrderItemData = { food: Food; quantity: number; unitPrice: number; discount: number };
|
type OrderItemData = { food: Food; quantity: number; unitPrice: number; discount: number };
|
||||||
|
|
||||||
type ValidatedCartForOrder = {
|
type ValidatedCartForOrder = {
|
||||||
@@ -32,8 +34,6 @@ type ValidatedCartForOrder = {
|
|||||||
orderItemsData: OrderItemData[];
|
orderItemsData: OrderItemData[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StatusTransitionRef = 'user' | 'admin';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrdersService {
|
export class OrdersService {
|
||||||
private readonly logger = new Logger(OrdersService.name);
|
private readonly logger = new Logger(OrdersService.name);
|
||||||
@@ -55,6 +55,7 @@ export class OrdersService {
|
|||||||
private readonly orderRepository: OrderRepository,
|
private readonly orderRepository: OrderRepository,
|
||||||
private readonly paymentsService: PaymentsService,
|
private readonly paymentsService: PaymentsService,
|
||||||
private readonly inventoryService: InventoryService,
|
private readonly inventoryService: InventoryService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async checkout(userId: string, restaurantId: string) {
|
async checkout(userId: string, restaurantId: string) {
|
||||||
@@ -123,6 +124,10 @@ export class OrdersService {
|
|||||||
await this.inventoryService.deductFromInventory(em, bulkReserveFoodDto);
|
await this.inventoryService.deductFromInventory(em, bulkReserveFoodDto);
|
||||||
await em.flush();
|
await em.flush();
|
||||||
this.logger.debug(`Order ${order.id} created for user ${userId} (restaurant ${restaurantId})`);
|
this.logger.debug(`Order ${order.id} created for user ${userId} (restaurant ${restaurantId})`);
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
OrderCreatedEvent.name,
|
||||||
|
new OrderCreatedEvent(order.id, restaurantId, String(order?.orderNumber) || ''),
|
||||||
|
);
|
||||||
return order;
|
return order;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -262,9 +267,21 @@ export class OrdersService {
|
|||||||
|
|
||||||
order.status = toStatus;
|
order.status = toStatus;
|
||||||
await this.em.persistAndFlush(order);
|
await this.em.persistAndFlush(order);
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
OrderStatusChangedEvent.name,
|
||||||
|
new OrderStatusChangedEvent(
|
||||||
|
orderId,
|
||||||
|
order.user?.id || '',
|
||||||
|
String(order?.orderNumber) || '',
|
||||||
|
restId,
|
||||||
|
order.status,
|
||||||
|
toStatus,
|
||||||
|
ref,
|
||||||
|
),
|
||||||
|
);
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
/* Helpers */
|
||||||
private assertStatusTransitionAllowed(order: Order, to: OrderStatus, ref: StatusTransitionRef) {
|
private assertStatusTransitionAllowed(order: Order, to: OrderStatus, ref: StatusTransitionRef) {
|
||||||
const paymentMethod = order.paymentMethod?.method;
|
const paymentMethod = order.paymentMethod?.method;
|
||||||
if (!paymentMethod) {
|
if (!paymentMethod) {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export class paymentSucceedEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly orderId: string,
|
||||||
|
public readonly restaurantId: string,
|
||||||
|
public readonly orderNumber: string,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
|
||||||
|
import { paymentSucceedEvent } from '../events/payment.events';
|
||||||
|
import { Permission } from 'src/common/enums/permission.enum';
|
||||||
|
import { NotifTitleEnum } from 'src/modules/notifications/interfaces/notification.interface';
|
||||||
|
import { NotificationService } from 'src/modules/notifications/services/notification.service';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class OrderListeners {
|
||||||
|
private readonly logger = new Logger(OrderListeners.name);
|
||||||
|
private orderCreatedSmsTemplateId: string;
|
||||||
|
constructor(
|
||||||
|
private readonly adminService: AdminRepository,
|
||||||
|
private readonly notificationService: NotificationService,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
// private readonly orderSer
|
||||||
|
) {
|
||||||
|
this.orderCreatedSmsTemplateId = this.configService.get<string>('ORDER_CREATED_SMS_TEMPLATE_ID') ?? '123';
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnEvent(paymentSucceedEvent.name)
|
||||||
|
async handleOrderCreated(event: paymentSucceedEvent) {
|
||||||
|
try {
|
||||||
|
this.logger.log(
|
||||||
|
`Order created event received: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`,
|
||||||
|
);
|
||||||
|
// get admnin os restuaraant that have order permissuins
|
||||||
|
const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_ORDERS);
|
||||||
|
// const order=await
|
||||||
|
const recipients = admins.map(admin => ({
|
||||||
|
adminId: admin.id,
|
||||||
|
}));
|
||||||
|
await this.notificationService.sendNotification({
|
||||||
|
restaurantId: event.restaurantId,
|
||||||
|
message: {
|
||||||
|
title: NotifTitleEnum.ORDER_CREATED,
|
||||||
|
content: `Order ${event.orderNumber} has been created successfully`,
|
||||||
|
sms: {
|
||||||
|
templateId: this.orderCreatedSmsTemplateId,
|
||||||
|
parameters: {
|
||||||
|
orderNumber: event.orderNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushNotif: {
|
||||||
|
title: `Order ${event.orderNumber} has been created successfully`,
|
||||||
|
content: `Order ${event.orderNumber} has been created successfully`,
|
||||||
|
icon: `Order ${event.orderNumber} has been created successfully`,
|
||||||
|
action: {
|
||||||
|
type: `Order ${event.orderNumber} has been created successfully`,
|
||||||
|
url: `Order ${event.orderNumber} has been created successfully`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
recipients,
|
||||||
|
metadata: {
|
||||||
|
priority: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to send notification for order created event: ${event.restaurantId}`,
|
||||||
|
error instanceof Error ? error.stack : String(error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -221,7 +221,7 @@ export class PaymentsService {
|
|||||||
private failPayment(payment: Payment) {
|
private failPayment(payment: Payment) {
|
||||||
payment.status = PaymentStatusEnum.Failed;
|
payment.status = PaymentStatusEnum.Failed;
|
||||||
payment.failedAt = new Date();
|
payment.failedAt = new Date();
|
||||||
payment.order.status = OrderStatus.FAILED;
|
payment.order.status = OrderStatus.CANCELED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private confirmOrder(order: Order) {
|
private confirmOrder(order: Order) {
|
||||||
|
|||||||
Reference in New Issue
Block a user