order cleanup

This commit is contained in:
2025-12-03 12:01:23 +03:30
parent e8566aa389
commit 8a2390ea8e
4 changed files with 114 additions and 4 deletions
+2
View File
@@ -1,5 +1,6 @@
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { ScheduleModule } from '@nestjs/schedule';
import { OrdersService } from './orders.service';
import { OrdersController } from './orders.controller';
import { Order } from './entities/order.entity';
@@ -17,6 +18,7 @@ import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
ScheduleModule,
MikroOrmModule.forFeature([Order, OrderItem, User, Restaurant, Food, UserAddress, PaymentMethod]),
CartModule,
UtilsModule,
+78 -3
View File
@@ -1,4 +1,4 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { Injectable, NotFoundException, BadRequestException, Logger } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { Order } from './entities/order.entity';
import { OrderItem } from './entities/order-item.entity';
@@ -15,9 +15,12 @@ import { PaymentMethod } from '../payments/entities/payment-method.entity';
import { PaymentsService } from '../payments/services/payments.service';
import { DeliveryMethodEnum } from '../delivery/interface/delivery';
import { Delivery } from '../delivery/entities/delivery.entity';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class OrdersService {
private readonly logger = new Logger(OrdersService.name);
constructor(
private readonly em: EntityManager,
private readonly cartService: CartService,
@@ -142,12 +145,19 @@ export class OrdersService {
throw new NotFoundException('Restaurant not found');
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const delivery = await this.em.findOne(Delivery, { id: cart.deliveryMethodId! });
const delivery = await this.em.findOne(Delivery, { id: cart.deliveryMethodId });
if (!delivery) {
throw new NotFoundException('Delivery not found');
}
// Validate minimum order price for delivery method
const minOrderPrice = Number(delivery.minOrderPrice) || 0;
if (minOrderPrice > 0 && cart.total < minOrderPrice) {
throw new BadRequestException(
`Minimum order amount for this delivery method is ${minOrderPrice}. Current total is ${cart.total}.`,
);
}
if (delivery.method === DeliveryMethodEnum.DeliveryCourier && !cart.addressId) {
throw new BadRequestException('Address is required. Please set a delivery address before creating an order.');
}
@@ -248,4 +258,69 @@ export class OrdersService {
remove(id: number) {
return `This action removes a #${id} order`;
}
/**
* Cleanup job to handle abandoned orders (pending payment for >30 minutes)
* Runs every 10 minutes to check for abandoned orders
*/
@Cron(CronExpression.EVERY_10_MINUTES)
async cleanupAbandonedOrders() {
this.logger.log('Starting cleanup of abandoned orders...');
try {
const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000);
// Find abandoned orders: Pending status, Pending payment, created >30 minutes ago
const abandonedOrders = await this.em.find(
Order,
{
status: OrderStatus.Pending,
paymentStatus: PaymentStatusEnum.Pending,
createdAt: { $lt: thirtyMinutesAgo },
},
{ populate: ['items', 'items.food'] },
);
if (abandonedOrders.length === 0) {
this.logger.log('No abandoned orders found');
return;
}
this.logger.log(`Found ${abandonedOrders.length} abandoned order(s) to cleanup`);
// Process each abandoned order in a transaction
for (const order of abandonedOrders) {
await this.em.transactional(async em => {
// Load order items with food relations
await order.items.loadItems();
// Restore stock for each item
for (const item of order.items) {
const food = item.food;
if (food) {
food.stock += item.quantity;
em.persist(food);
this.logger.debug(
`Restored ${item.quantity} units of stock for food ${food.id} (${food.title || 'N/A'})`,
);
}
}
// Update order status to Cancelled
order.status = OrderStatus.Cancelled;
em.persist(order);
await em.flush();
this.logger.log(`Cancelled abandoned order ${order.id}`);
});
}
this.logger.log(`Successfully cleaned up ${abandonedOrders.length} abandoned order(s)`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
const errorStack = error instanceof Error ? error.stack : undefined;
this.logger.error(`Error during cleanup of abandoned orders: ${errorMessage}`, errorStack);
throw error;
}
}
}