import { Injectable } from '@nestjs/common'; import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; import { FilterQuery } from '@mikro-orm/core'; 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; limit?: number; statuses?: OrderStatus[]; paymentStatus?: PaymentStatusEnum; search?: string; startDate?: string; endDate?: string; orderBy?: string; order?: 'asc' | 'desc'; userId?: string; excludeOnlinePendingPayment?: boolean; }; @Injectable() export class OrderRepository extends EntityRepository { constructor(readonly em: EntityManager) { super(em, Order); } /** * Find orders with pagination and optional filters. * Supports: statuses, paymentStatus, search (orderNumber), date range, ordering. */ async findAllPaginated(restId: string, opts: FindOrdersOpts = {}): Promise> { const { page = 1, limit = 10, statuses, search, startDate, endDate, orderBy = 'createdAt', order = 'desc', userId, excludeOnlinePendingPayment = false, } = opts; const offset = (page - 1) * limit; const where: FilterQuery = { restaurant: { id: restId } }; // Filter by statuses if (statuses) { // Ensure statuses is always an array and normalize it const normalizedStatuses = Array.isArray(statuses) ? statuses : [statuses]; // Filter out any empty values const validStatuses = normalizedStatuses.filter((s): s is OrderStatus => !!s); if (validStatuses.length > 0) { // Use direct equality for single value, $in for multiple values to avoid SQL generation issues if (validStatuses.length === 1) { where.status = validStatuses[0]; } else { where.status = { $in: validStatuses }; } } } if (userId) { where.user = { id: userId }; } // Filter by date range if (startDate || endDate) { where.createdAt = {}; if (startDate) { where.createdAt.$gte = new Date(startDate); } if (endDate) { where.createdAt.$lte = new Date(endDate); } } // Search by order number or user information if (search) { const searchPattern = `%${search}%`; const searchConditions: any[] = [ { user: { firstName: { $ilike: searchPattern } } }, { user: { lastName: { $ilike: searchPattern } } }, { user: { phone: { $ilike: searchPattern } } }, ]; // If search is numeric, also search by orderNumber const numericSearch = Number(search); if (!isNaN(numericSearch) && isFinite(numericSearch)) { searchConditions.push({ orderNumber: numericSearch }); } 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, { limit, offset, orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' }, populate: ['user', 'restaurant', 'deliveryMethod', 'paymentMethod', 'items', 'items.product'] as never, }); // Collect all (orderId, productId) pairs for efficient review lookup const orderproductPairs: Array<{ orderId: string; productId: string }> = []; for (const order of data) { for (const item of order.items.getItems()) { if (item.product?.id) { orderproductPairs.push({ orderId: order.id, productId: item.product.id }); } } } // Fetch all relevant reviews in a single query const reviewsMap: Map = 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) { for (const item of order.items.getItems()) { if (item.product) { const key = `${order.id}-${item.product.id}`; // 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; } } } const totalPages = Math.ceil(total / limit); return { data, meta: { total, page, limit, totalPages, }, }; } }