payment
This commit is contained in:
@@ -2,12 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron } from '@nestjs/schedule';
|
||||
import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Payment } from '../../payment/entities/payment.entity';
|
||||
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payment/interface/payment';
|
||||
import { InventoryService } from '../../inventory/inventory.service';
|
||||
import { OrderStatus } from '../interface/order.interface';
|
||||
import { Order } from '../entities/order.entity';
|
||||
import { OrderStatusChangedEvent } from '../events/order.events';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class OrdersCrone {
|
||||
@@ -15,173 +10,8 @@ export class OrdersCrone {
|
||||
|
||||
constructor(
|
||||
private readonly em: EntityManager,
|
||||
private readonly inventoryService: InventoryService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) { }
|
||||
|
||||
// run every minute and fail pending online payments older than 15 minutes
|
||||
@Cron('*/1 * * * *', {
|
||||
name: 'failOldOnlinePayments',
|
||||
timeZone: 'UTC',
|
||||
})
|
||||
async cancelOldOnlinePendingOrders() {
|
||||
try {
|
||||
const cutoff = new Date(Date.now() - 15 * 60 * 1000);
|
||||
|
||||
this.logger.debug('Searching for pending online payments older than 15 minutes');
|
||||
|
||||
const payments = await this.em.find(
|
||||
Payment,
|
||||
{
|
||||
method: PaymentMethodEnum.Online,
|
||||
status: PaymentStatusEnum.Pending,
|
||||
createdAt: { $lte: cutoff },
|
||||
},
|
||||
{ populate: ['order', 'order.items', 'order.items.product'] },
|
||||
);
|
||||
|
||||
if (!payments || payments.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(`Found ${payments.length} stale pending online payments`);
|
||||
|
||||
for (const p of payments) {
|
||||
try {
|
||||
await this.em.transactional(async em => {
|
||||
// reload inside transaction to avoid concurrency issues
|
||||
const payment = await em.findOne(
|
||||
Payment,
|
||||
{ id: p.id },
|
||||
{ populate: ['order', 'order.items', 'order.items.product'] },
|
||||
);
|
||||
if (!payment) return;
|
||||
if (payment.status !== PaymentStatusEnum.Pending) return;
|
||||
|
||||
payment.status = PaymentStatusEnum.Failed;
|
||||
payment.failedAt = new Date();
|
||||
|
||||
if (payment.order) {
|
||||
payment.order.status = OrderStatus.CANCELED;
|
||||
|
||||
// prepare restore payload
|
||||
const items = (payment.order as any).items || [];
|
||||
const restorePayload = {
|
||||
items: items.map((it: any) => ({ productId: it.product.id, quantity: it.quantity })),
|
||||
};
|
||||
|
||||
if (restorePayload.items.length > 0) {
|
||||
await this.inventoryService.restoreToInventory(em, restorePayload);
|
||||
}
|
||||
}
|
||||
|
||||
em.persist(payment);
|
||||
if (payment.order) em.persist(payment.order);
|
||||
await em.flush();
|
||||
this.logger.log(
|
||||
`Marked payment ${payment.id} and order ${payment.order?.id} as failed and restored inventory`,
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.error(`Error processing payment ${p.id}: ${err.message}`, err.stack);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(`OrdersCrone failed: ${err.message}`, err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
// run every 15 minutes to complete orders that have been in shipped/delivered statuses for more than 3 hours
|
||||
@Cron('*/15 * * * *', {
|
||||
name: 'completeOldDeliveredOrders',
|
||||
timeZone: 'UTC',
|
||||
})
|
||||
async completeOldDeliveredOrders() {
|
||||
try {
|
||||
const cutoff = new Date(Date.now() - 3 * 60 * 60 * 1000); // 3 hours ago
|
||||
|
||||
this.logger.debug('Searching for orders in shipped/delivered statuses older than 3 hours');
|
||||
|
||||
const orders = await this.em.find(
|
||||
Order,
|
||||
{
|
||||
status: {
|
||||
$in: [
|
||||
OrderStatus.SHIPPED,
|
||||
OrderStatus.DELIVERED_TO_WAITER,
|
||||
OrderStatus.DELIVERED_TO_RECEPTIONIST,
|
||||
],
|
||||
},
|
||||
updatedAt: { $lte: cutoff },
|
||||
},
|
||||
{ populate: ['restaurant'] },
|
||||
);
|
||||
|
||||
if (!orders || orders.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(`Found ${orders.length} orders to mark as completed`);
|
||||
|
||||
for (const order of orders) {
|
||||
try {
|
||||
await this.em.transactional(async em => {
|
||||
// reload inside transaction to avoid concurrency issues
|
||||
const reloadedOrder = await em.findOne(
|
||||
Order,
|
||||
{ id: order.id },
|
||||
{ populate: ['restaurant', 'user'] },
|
||||
);
|
||||
if (!reloadedOrder) return;
|
||||
if (
|
||||
![
|
||||
OrderStatus.SHIPPED,
|
||||
OrderStatus.DELIVERED_TO_WAITER,
|
||||
OrderStatus.DELIVERED_TO_RECEPTIONIST,
|
||||
].includes(reloadedOrder.status)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousStatus = reloadedOrder.status;
|
||||
const =
|
||||
typeof reloadedOrder.restaurant === 'string'
|
||||
? reloadedOrder.restaurant
|
||||
: reloadedOrder.restaurant.id;
|
||||
|
||||
// Update order status and history
|
||||
reloadedOrder.status = OrderStatus.COMPLETED;
|
||||
reloadedOrder.history.push({
|
||||
status: OrderStatus.COMPLETED,
|
||||
changedAt: new Date(),
|
||||
desc: 'تکمیل سفارش توسط سیستم',
|
||||
});
|
||||
|
||||
em.persist(reloadedOrder);
|
||||
await em.flush();
|
||||
|
||||
// // Emit event after transaction completes
|
||||
this.eventEmitter.emit(
|
||||
OrderStatusChangedEvent.name,
|
||||
new OrderStatusChangedEvent(
|
||||
reloadedOrder.id,
|
||||
reloadedOrder.user?.id || '',
|
||||
String(reloadedOrder.orderNumber) || '',
|
||||
,
|
||||
previousStatus,
|
||||
OrderStatus.COMPLETED,
|
||||
'admin',
|
||||
),
|
||||
);
|
||||
|
||||
this.logger.log(`Marked order ${reloadedOrder.id} as completed`);
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.error(`Error processing order ${order.id}: ${err.message}`, err.stack);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(`completeOldDeliveredOrders cron failed: ${err.message}`, err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Entity, Enum, Index, ManyToOne, Property } from '@mikro-orm/core';
|
||||
import { Entity, Enum, Index, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Order } from './order.entity';
|
||||
import { Product } from 'src/modules/product/entities/product.entity';
|
||||
@@ -8,6 +8,9 @@ import { OrderItemStatus } from '../interface/order.interface';
|
||||
@Index({ properties: ['order'] })
|
||||
@Index({ properties: ['product'] })
|
||||
export class OrderItem extends BaseEntity {
|
||||
@PrimaryKey({ type: 'bigint', autoincrement: true })
|
||||
id: bigint
|
||||
|
||||
@ManyToOne(() => Order)
|
||||
order!: Order;
|
||||
|
||||
|
||||
@@ -7,17 +7,22 @@ import {
|
||||
Collection,
|
||||
Cascade,
|
||||
Enum,
|
||||
PrimaryKey,
|
||||
} from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { OrderStatus } from '../interface/order.interface';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { OrderItem } from './order-item.entity';
|
||||
import { Payment } from 'src/modules/payment/entities/payment.entity';
|
||||
import { ulid } from 'ulid';
|
||||
|
||||
@Entity({ tableName: 'orders' })
|
||||
@Index({ properties: ['user', 'status'] })
|
||||
@Index({ properties: ['status'] })
|
||||
export class Order extends BaseEntity {
|
||||
@PrimaryKey({type:'string',columnType:'char(26)'})
|
||||
id:string=ulid()
|
||||
|
||||
@ManyToOne(() => User)
|
||||
user!: User;
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ import { OrderRepository } from '../repositories/order.repository';
|
||||
import { OrderStatus } from '../interface/order.interface';
|
||||
import { PaymentMethodEnum } from 'src/modules/payment/interface/payment';
|
||||
import { UserService } from 'src/modules/user/providers/user.service';
|
||||
import { WalletTransactionReason, WalletTransactionType } from 'src/modules/user/interface/wallet';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class OrderListeners {
|
||||
private readonly logger = new Logger(OrderListeners.name);
|
||||
@@ -30,184 +29,184 @@ export class OrderListeners {
|
||||
// this.orderCompletedSmsTemplateId = this.configService.get<string>('SMS_PATTERN_ORDER_STATUS_COMPLETED') ?? '123';
|
||||
}
|
||||
|
||||
private getStatusFarsi(status: OrderStatus): string {
|
||||
const statusMap: Record<OrderStatus, string> = {
|
||||
[OrderStatus.PENDING_PAYMENT]: 'در انتظار پرداخت',
|
||||
[OrderStatus.PAID]: 'پرداخت شده',
|
||||
[OrderStatus.PREPARING]: 'در حال آمادهسازی',
|
||||
[OrderStatus.DELIVERED_TO_RECEPTIONIST]: 'تحویل به پذیرش',
|
||||
[OrderStatus.DELIVERED_TO_WAITER]: 'تحویل به گارسون',
|
||||
[OrderStatus.SHIPPED]: 'ارسال شده',
|
||||
[OrderStatus.COMPLETED]: 'تکمیل شده',
|
||||
[OrderStatus.CANCELED]: 'لغو شده',
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
}
|
||||
// private getStatusFarsi(status: OrderStatus): string {
|
||||
// const statusMap: Record<OrderStatus, string> = {
|
||||
// [OrderStatus.PENDING_PAYMENT]: 'در انتظار پرداخت',
|
||||
// [OrderStatus.PAID]: 'پرداخت شده',
|
||||
// [OrderStatus.PREPARING]: 'در حال آمادهسازی',
|
||||
// [OrderStatus.DELIVERED_TO_RECEPTIONIST]: 'تحویل به پذیرش',
|
||||
// [OrderStatus.DELIVERED_TO_WAITER]: 'تحویل به گارسون',
|
||||
// [OrderStatus.SHIPPED]: 'ارسال شده',
|
||||
// [OrderStatus.COMPLETED]: 'تکمیل شده',
|
||||
// [OrderStatus.CANCELED]: 'لغو شده',
|
||||
// };
|
||||
// return statusMap[status] || status;
|
||||
// }
|
||||
|
||||
@OnEvent(OrderCreatedEvent.name)
|
||||
async handleOrderCreated(event: OrderCreatedEvent) {
|
||||
try {
|
||||
this.logger.log(
|
||||
`Order created event received: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
);
|
||||
// @OnEvent(OrderCreatedEvent.name)
|
||||
// async handleOrderCreated(event: OrderCreatedEvent) {
|
||||
// try {
|
||||
// this.logger.log(
|
||||
// `Order created event received: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
// );
|
||||
|
||||
const order = await this.OrderRepository.findOne(event.orderId);
|
||||
if (order?.paymentMethod.method === PaymentMethodEnum.Online) {
|
||||
return;
|
||||
}
|
||||
// const order = await this.OrderRepository.findOne(event.orderId);
|
||||
// if (order?.paymentMethod.method === PaymentMethodEnum.Online) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
|
||||
// get admnin os restuaraant that have order permissuins
|
||||
const admins = await this.adminService.findAdminsWithPermission(event., Permission.MANAGE_ORDERS);
|
||||
const recipients = admins.map(admin => ({
|
||||
adminId: admin.id,
|
||||
}));
|
||||
// // get admnin os restuaraant that have order permissuins
|
||||
// const admins = await this.adminService.findAdminsWithPermission(event., Permission.MANAGE_ORDERS);
|
||||
// const recipients = admins.map(admin => ({
|
||||
// adminId: admin.id,
|
||||
// }));
|
||||
|
||||
await this.notificationService.sendNotification({
|
||||
: event.,
|
||||
message: {
|
||||
title: NotifTitleEnum.ORDER_CREATED,
|
||||
content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`,
|
||||
sms: {
|
||||
templateId: this.orderCreatedSmsTemplateId,
|
||||
parameters: {
|
||||
orderNumber: event.orderNumber,
|
||||
total: event.total.toString(),
|
||||
},
|
||||
},
|
||||
pushNotif: {
|
||||
title: `سفارش جدید`,
|
||||
content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`,
|
||||
icon: `/`,
|
||||
action: {
|
||||
type: NotifTitleEnum.ORDER_CREATED,
|
||||
url: `/`,
|
||||
},
|
||||
},
|
||||
},
|
||||
recipients,
|
||||
metadata: {
|
||||
priority: 1,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to send notification for order created event: ${event.}`,
|
||||
error instanceof Error ? error.stack : String(error),
|
||||
);
|
||||
}
|
||||
}
|
||||
// await this.notificationService.sendNotification({
|
||||
|
||||
// message: {
|
||||
// title: NotifTitleEnum.ORDER_CREATED,
|
||||
// content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`,
|
||||
// sms: {
|
||||
// templateId: this.orderCreatedSmsTemplateId,
|
||||
// parameters: {
|
||||
// orderNumber: event.orderNumber,
|
||||
// total: event.total.toString(),
|
||||
// },
|
||||
// },
|
||||
// pushNotif: {
|
||||
// title: `سفارش جدید`,
|
||||
// content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`,
|
||||
// icon: `/`,
|
||||
// action: {
|
||||
// type: NotifTitleEnum.ORDER_CREATED,
|
||||
// url: `/`,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// recipients,
|
||||
// metadata: {
|
||||
// priority: 1,
|
||||
// },
|
||||
// });
|
||||
// } catch (error) {
|
||||
// this.logger.error(
|
||||
// `Failed to send notification for order created event: ${event.}`,
|
||||
// error instanceof Error ? error.stack : String(error),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
@OnEvent(OrderStatusChangedEvent.name)
|
||||
async handleOrderStatusChanged(event: OrderStatusChangedEvent) {
|
||||
try {
|
||||
this.logger.log(
|
||||
`Order status changed event received: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
);
|
||||
//TODO : REFACTOR to use queue or other way to handle this
|
||||
const recipients = [
|
||||
{
|
||||
userId: event.userId,
|
||||
},
|
||||
];
|
||||
if (event.newStatus === OrderStatus.COMPLETED) {
|
||||
// @OnEvent(OrderStatusChangedEvent.name)
|
||||
// async handleOrderStatusChanged(event: OrderStatusChangedEvent) {
|
||||
// try {
|
||||
// this.logger.log(
|
||||
// `Order status changed event received: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
// );
|
||||
// //TODO : REFACTOR to use queue or other way to handle this
|
||||
// const recipients = [
|
||||
// {
|
||||
// userId: event.userId,
|
||||
// },
|
||||
// ];
|
||||
// if (event.newStatus === OrderStatus.COMPLETED) {
|
||||
|
||||
if (!event?.userId) {
|
||||
this.logger.log(
|
||||
`User not found for order: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
);
|
||||
}
|
||||
// if (!event?.userId) {
|
||||
// this.logger.log(
|
||||
// `User not found for order: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
// );
|
||||
// }
|
||||
|
||||
// const restaurant = await this.RestaurantRepository.findOne(event.);
|
||||
// if (!restaurant) {
|
||||
// this.logger.log(
|
||||
// `Restaurant not found for order: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
// const score = restaurant.score;
|
||||
// if (!score) {
|
||||
// this.logger.log(
|
||||
// `Score not found for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
// // const restaurant = await this.RestaurantRepository.findOne(event.);
|
||||
// // if (!restaurant) {
|
||||
// // this.logger.log(
|
||||
// // `Restaurant not found for order: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
// // );
|
||||
// // return;
|
||||
// // }
|
||||
// // const score = restaurant.score;
|
||||
// // if (!score) {
|
||||
// // this.logger.log(
|
||||
// // `Score not found for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
// // );
|
||||
// // return;
|
||||
// // }
|
||||
|
||||
// increase score for user
|
||||
// const order = await this.OrderRepository.findOne(event.orderId);
|
||||
// if (!order) {
|
||||
// this.logger.log(
|
||||
// `Order not found for order: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
// this.userService.createWalletTransaction(event.userId, event., {
|
||||
// amount: order.subTotal,
|
||||
// type: WalletTransactionType.CREDIT,
|
||||
// reason: WalletTransactionReason.ORDER_COMPLETED_DEPOSIT,
|
||||
// });
|
||||
// // increase score for user
|
||||
// // const order = await this.OrderRepository.findOne(event.orderId);
|
||||
// // if (!order) {
|
||||
// // this.logger.log(
|
||||
// // `Order not found for order: ${event.orderId} for restaurant: ${event.} and order number: ${event.orderNumber}`,
|
||||
// // );
|
||||
// // return;
|
||||
// // }
|
||||
// // this.userService.createWalletTransaction(event.userId, event., {
|
||||
// // amount: order.subTotal,
|
||||
// // type: WalletTransactionType.CREDIT,
|
||||
// // reason: WalletTransactionReason.ORDER_COMPLETED_DEPOSIT,
|
||||
// // });
|
||||
|
||||
await this.notificationService.sendNotification({
|
||||
: event.,
|
||||
message: {
|
||||
title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||
content: `لطفابرای ثبت نظر سفارش ${event.orderNumber} به اپ مراجعه کنید`,
|
||||
sms: {
|
||||
templateId: this.orderCreatedSmsTemplateId,
|
||||
parameters: {
|
||||
orderNumber: event.orderNumber,
|
||||
},
|
||||
},
|
||||
pushNotif: {
|
||||
title: `تغییر وضعیت سفارش`,
|
||||
content: `لطفا برای ثبت نظر سفارش ${event.orderNumber} به اپ مراجعه کنید`,
|
||||
icon: `/`,
|
||||
action: {
|
||||
type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||
url: ``,
|
||||
},
|
||||
},
|
||||
},
|
||||
recipients,
|
||||
metadata: {
|
||||
priority: 1,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await this.notificationService.sendNotification({
|
||||
: event.,
|
||||
message: {
|
||||
title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||
content: `وضعیت سفارش شماره ${event.orderNumber} به ${this.getStatusFarsi(event.newStatus)} تغییر کرد`,
|
||||
sms: {
|
||||
templateId: this.orderStatusChangedSmsTemplateId,
|
||||
parameters: {
|
||||
orderNumber: event.orderNumber,
|
||||
status: this.getStatusFarsi(event.newStatus),
|
||||
},
|
||||
},
|
||||
pushNotif: {
|
||||
title: `تغییر وضعیت سفارش`,
|
||||
content: `وضعیت سفارش شماره ${event.orderNumber} به ${this.getStatusFarsi(event.newStatus)} تغییر کرد`,
|
||||
icon: `/`,
|
||||
action: {
|
||||
type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||
url: ``,
|
||||
},
|
||||
},
|
||||
},
|
||||
recipients,
|
||||
metadata: {
|
||||
priority: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to send notification for order status changed event: ${event.}`,
|
||||
error instanceof Error ? error.stack : String(error),
|
||||
);
|
||||
}
|
||||
}
|
||||
// await this.notificationService.sendNotification({
|
||||
// : event.,
|
||||
// message: {
|
||||
// title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||
// content: `لطفابرای ثبت نظر سفارش ${event.orderNumber} به اپ مراجعه کنید`,
|
||||
// sms: {
|
||||
// templateId: this.orderCreatedSmsTemplateId,
|
||||
// parameters: {
|
||||
// orderNumber: event.orderNumber,
|
||||
// },
|
||||
// },
|
||||
// pushNotif: {
|
||||
// title: `تغییر وضعیت سفارش`,
|
||||
// content: `لطفا برای ثبت نظر سفارش ${event.orderNumber} به اپ مراجعه کنید`,
|
||||
// icon: `/`,
|
||||
// action: {
|
||||
// type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||
// url: ``,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// recipients,
|
||||
// metadata: {
|
||||
// priority: 1,
|
||||
// },
|
||||
// });
|
||||
// } else {
|
||||
// await this.notificationService.sendNotification({
|
||||
// : event.,
|
||||
// message: {
|
||||
// title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||
// content: `وضعیت سفارش شماره ${event.orderNumber} به ${this.getStatusFarsi(event.newStatus)} تغییر کرد`,
|
||||
// sms: {
|
||||
// templateId: this.orderStatusChangedSmsTemplateId,
|
||||
// parameters: {
|
||||
// orderNumber: event.orderNumber,
|
||||
// status: this.getStatusFarsi(event.newStatus),
|
||||
// },
|
||||
// },
|
||||
// pushNotif: {
|
||||
// title: `تغییر وضعیت سفارش`,
|
||||
// content: `وضعیت سفارش شماره ${event.orderNumber} به ${this.getStatusFarsi(event.newStatus)} تغییر کرد`,
|
||||
// icon: `/`,
|
||||
// action: {
|
||||
// type: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||
// url: ``,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// recipients,
|
||||
// metadata: {
|
||||
// priority: 1,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// } catch (error) {
|
||||
// this.logger.error(
|
||||
// `Failed to send notification for order status changed event: ${event.}`,
|
||||
// error instanceof Error ? error.stack : String(error),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Order } from '../entities/order.entity';
|
||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||
import { OrderStatus } from '../interface/order.interface';
|
||||
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payment/interface/payment';
|
||||
import { Review } from '../../review/entities/review.entity';
|
||||
|
||||
|
||||
type FindOrdersOpts = {
|
||||
page?: number;
|
||||
@@ -31,7 +31,7 @@ export class OrderRepository extends EntityRepository<Order> {
|
||||
* Find orders with pagination and optional filters.
|
||||
* Supports: statuses, paymentStatus, search (orderNumber), date range, ordering.
|
||||
*/
|
||||
async findAllPaginated(: string, opts: FindOrdersOpts = {}): Promise<PaginatedResult<Order>> {
|
||||
async findAllPaginated( opts: FindOrdersOpts = {}): Promise<PaginatedResult<Order>> {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
@@ -47,7 +47,7 @@ export class OrderRepository extends EntityRepository<Order> {
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const where: FilterQuery<Order> = { restaurant: { id: } };
|
||||
const where: FilterQuery<Order> = { };
|
||||
|
||||
// Filter by statuses
|
||||
if (statuses) {
|
||||
@@ -99,19 +99,7 @@ export class OrderRepository extends EntityRepository<Order> {
|
||||
where.$or = searchConditions;
|
||||
}
|
||||
|
||||
// Filter: Exclude orders with payment method Online and status pending_payment
|
||||
if (excludeOnlinePendingPayment) {
|
||||
const existingConditions = where.$and || [];
|
||||
where.$and = [
|
||||
...existingConditions,
|
||||
{
|
||||
$or: [
|
||||
{ paymentMethod: { method: { $ne: PaymentMethodEnum.Online } } },
|
||||
{ status: { $ne: OrderStatus.PENDING_PAYMENT } },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// First, fetch orders without reviews
|
||||
const [data, total] = await this.findAndCount(where, {
|
||||
@@ -122,7 +110,7 @@ export class OrderRepository extends EntityRepository<Order> {
|
||||
});
|
||||
|
||||
// Collect all (orderId, productId) pairs for efficient review lookup
|
||||
const orderproductPairs: Array<{ orderId: string; productId: string }> = [];
|
||||
const orderproductPairs: Array<{ orderId: string; productId: bigint }> = [];
|
||||
for (const order of data) {
|
||||
for (const item of order.items.getItems()) {
|
||||
if (item.product?.id) {
|
||||
@@ -131,30 +119,7 @@ export class OrderRepository extends EntityRepository<Order> {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all relevant reviews in a single query
|
||||
const reviewsMap: Map<string, string> = new Map();
|
||||
if (orderproductPairs.length > 0) {
|
||||
const orderIds = [...new Set(orderproductPairs.map(p => p.orderId))];
|
||||
const productIds = [...new Set(orderproductPairs.map(p => p.productId))];
|
||||
|
||||
const reviews = await this.em.find(
|
||||
Review,
|
||||
{
|
||||
order: { id: { $in: orderIds } },
|
||||
product: { id: { $in: productIds } },
|
||||
},
|
||||
{
|
||||
fields: ['id', 'order', 'product'],
|
||||
populate: ['order', 'product'],
|
||||
},
|
||||
);
|
||||
|
||||
// Create a map: key = `${orderId}-${productId}`, value = reviewId
|
||||
for (const review of reviews) {
|
||||
const key = `${review.order.id}-${review.product.id}`;
|
||||
reviewsMap.set(key, review.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Map reviewIds to products
|
||||
for (const order of data) {
|
||||
@@ -164,7 +129,7 @@ export class OrderRepository extends EntityRepository<Order> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const product = item.product as any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
product.reviewId = reviewsMap.get(key) || null;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user