Files
negareh-api/src/modules/order/repositories/order.repository.ts
T
2026-01-07 12:24:55 +03:30

185 lines
5.6 KiB
TypeScript

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<Order> {
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<PaginatedResult<Order>> {
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<Order> = { 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<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) {
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,
},
};
}
}