From 91d37a4faa24fcd106618322095d4f247820fce0 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 23 Dec 2025 22:01:03 +0330 Subject: [PATCH] cron for complete order --- src/modules/orders/crone/order.crone.ts | 100 +++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/src/modules/orders/crone/order.crone.ts b/src/modules/orders/crone/order.crone.ts index 4a0964d..028c6fd 100644 --- a/src/modules/orders/crone/order.crone.ts +++ b/src/modules/orders/crone/order.crone.ts @@ -1,10 +1,13 @@ 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 '../../payments/entities/payment.entity'; import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/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 { @@ -13,7 +16,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 * * * *', { @@ -86,4 +90,98 @@ export class OrdersCrone { 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 restaurantId = + 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) || '', + // restaurantId, + // 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); + } + } }