From b663bfa5926b01a31b457954e689c2625dd1cd94 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Wed, 17 Dec 2025 21:28:29 +0330 Subject: [PATCH] remove reservation table --- src/modules/inventory/const/reservation.ts | 7 - .../inventory/entities/reservation.entity.ts | 23 --- src/modules/inventory/inventory.service.ts | 153 ++++++------------ src/modules/orders/orders.module.ts | 2 +- .../orders/providers/orders.service.ts | 2 +- .../{services => gateways}/gateway.manager.ts | 2 +- src/modules/payments/payments.module.ts | 2 +- .../payments/services/payments.service.ts | 9 +- 8 files changed, 57 insertions(+), 143 deletions(-) delete mode 100644 src/modules/inventory/const/reservation.ts delete mode 100644 src/modules/inventory/entities/reservation.entity.ts rename src/modules/payments/{services => gateways}/gateway.manager.ts (90%) diff --git a/src/modules/inventory/const/reservation.ts b/src/modules/inventory/const/reservation.ts deleted file mode 100644 index 18f394a..0000000 --- a/src/modules/inventory/const/reservation.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { PaymentMethodEnum } from "src/modules/payments/interface/payment"; - -export const STOCK_RESERVE_MINUTES: Record = { - [PaymentMethodEnum.Online]: 15, - [PaymentMethodEnum.Cash]: 120, - [PaymentMethodEnum.Wallet]: 15, - }; \ No newline at end of file diff --git a/src/modules/inventory/entities/reservation.entity.ts b/src/modules/inventory/entities/reservation.entity.ts deleted file mode 100644 index d73dacc..0000000 --- a/src/modules/inventory/entities/reservation.entity.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Entity, Enum, ManyToOne, Property } from '@mikro-orm/core'; -import { BaseEntity } from '../../../common/entities/base.entity'; -import { Food } from '../../foods/entities/food.entity'; -import { Order } from '../../orders/entities/order.entity'; -import { ReservationStatus } from '../inteface/reservation'; - -@Entity({ tableName: 'reservations' }) -export class Reservation extends BaseEntity { - @ManyToOne(() => Food) - food!: Food; - - @ManyToOne(() => Order) - order!: Order; - - @Property({ type: 'int' }) - quantity!: number; - - @Property({ type: 'date' }) - expiresAt!: Date; - - @Enum(() => ReservationStatus) - status: ReservationStatus = ReservationStatus.ACTIVE; -} diff --git a/src/modules/inventory/inventory.service.ts b/src/modules/inventory/inventory.service.ts index aa9ad9a..812af3b 100644 --- a/src/modules/inventory/inventory.service.ts +++ b/src/modules/inventory/inventory.service.ts @@ -4,17 +4,14 @@ import { SetStockDto } from './dto/set-stock.dto'; import { BulkSetStockDto } from './dto/bulk-set-stock.dto'; import { BulkReserveFoodDto } from './dto/bulk-reserve-food.dto'; import { Inventory } from './entities/inventory.entity'; -import { Reservation } from './entities/reservation.entity'; import { Food } from '../foods/entities/food.entity'; import { Restaurant } from '../restaurants/entities/restaurant.entity'; -import { Order } from '../orders/entities/order.entity'; -import { ReservationStatus } from './inteface/reservation'; -import { STOCK_RESERVE_MINUTES } from './const/reservation'; -import { PaymentMethodEnum } from '../payments/interface/payment'; +import { LockMode } from '@mikro-orm/core'; + @Injectable() export class InventoryService { - constructor(private readonly em: EntityManager) {} + constructor(private readonly em: EntityManager) { } async setStockForFood(foodId: string, restaurantId: string, setStockDto: SetStockDto): Promise { // Validate that availableStock doesn't exceed totalStock @@ -61,7 +58,7 @@ export class InventoryService { } async bulkSetStockForFoods(restaurantId: string, bulkSetStockDto: BulkSetStockDto): Promise { - + const { items } = bulkSetStockDto; // Validate all items first for (const item of items) { @@ -135,114 +132,64 @@ export class InventoryService { return results; } - async tempBulkReserveFood( + async deductFromInventory( em: EntityManager, - restaurantId: string, - orderId: string, - paymentMethod: PaymentMethodEnum, bulkReserveFoodDto: BulkReserveFoodDto, - ): Promise { - const { items } = bulkReserveFoodDto; - const expiresAt = this.getReservationExpiry(paymentMethod); - // Get all unique food IDs - const foodIds = [...new Set(items.map(item => item.foodId))]; + ): Promise { + return em.transactional(async em => { + + const { items } = bulkReserveFoodDto; + // Get all unique food IDs + const foodIds = [...new Set(items.map(item => item.foodId))]; - // Load all foods in one query - const foods = await em.find(Food, { id: { $in: foodIds } }, { populate: ['restaurant'] }); + // Load all foods in one query + const foods = await em.find(Food, { id: { $in: foodIds } }, { lockMode: LockMode.PESSIMISTIC_WRITE }); - // Verify all foods exist and belong to the restaurant - const foodMap = new Map(); - for (const food of foods) { - if (food.restaurant.id !== restaurantId) { - throw new BadRequestException(`Food ${food.id} does not belong to restaurant ${restaurantId}`); - } - foodMap.set(food.id, food); - } - - // Check for missing foods - const missingFoodIds = foodIds.filter(id => !foodMap.has(id)); - if (missingFoodIds.length > 0) { - throw new NotFoundException(`Foods not found: ${missingFoodIds.join(', ')}`); - } - - // Load order - const order = await em.findOne(Order, { id: orderId }); - if (!order) { - throw new NotFoundException(`Order with ID ${orderId} not found`); - } - - // Load all existing inventories in one query - const existingInventories = await em.find(Inventory, { - food: { id: { $in: foodIds }, restaurant: { id: restaurantId } }, - }); - - const inventoryMap = new Map(); - for (const inventory of existingInventories) { - inventoryMap.set(inventory.food.id, inventory); - } - - // Validate stock availability and create reservations - const reservations: Reservation[] = []; - for (const item of items) { - const food = foodMap.get(item.foodId)!; - const inventory = inventoryMap.get(item.foodId); - - // Check if inventory exists - if (!inventory) { - throw new NotFoundException(`Inventory not found for food ${item.foodId}`); + // Verify all foods exist and belong to the restaurant + const foodMap = new Map(); + for (const food of foods) { + foodMap.set(food.id, food); } - // Check if available stock is sufficient - if (inventory.availableStock < item.quantity) { - throw new BadRequestException( - `Insufficient stock for food ${item.foodId}. Available: ${inventory.availableStock}, Requested: ${item.quantity}`, - ); + // Check for missing foods + const missingFoodIds = foodIds.filter(id => !foodMap.has(id)); + if (missingFoodIds.length > 0) { + throw new NotFoundException(`Foods not found: ${missingFoodIds.join(', ')}`); } - // Create reservation record - const reservation = em.create(Reservation, { - food, - order, - quantity: item.quantity, - expiresAt, - status: ReservationStatus.ACTIVE, + // Load all existing inventories in one query + const existingInventories = await em.find(Inventory, { + food: { id: { $in: foodIds } }, }); - reservations.push(reservation); + const inventoryMap = new Map(); + for (const inventory of existingInventories) { + inventoryMap.set(inventory.food.id, inventory); + } - // Update available stock (decrease by quantity) - inventory.availableStock -= item.quantity; - } - + // Validate stock availability and create reservations + const inventories: Inventory[] = []; + for (const item of items) { + const inventory = inventoryMap.get(item.foodId); + + // Check if inventory exists + if (!inventory) { + throw new NotFoundException(`Inventory not found for food ${item.foodId}`); + } + + // Check if available stock is sufficient + if (inventory.availableStock < item.quantity) { + throw new BadRequestException( + `Insufficient stock for food ${item.foodId}. Available: ${inventory.availableStock}, Requested: ${item.quantity}`, + ); + } + inventory.availableStock -= item.quantity; + inventories.push(inventory); + em.persist(inventory); + } + return inventories; + }); - return reservations; } - async confirmReservationByOrderId(em: EntityManager, orderId: string): Promise { - const reservations = await em.find(Reservation, { order: { id: orderId } }); - if (!reservations) { - throw new NotFoundException(`Reservations with order ID ${orderId} not found`); - } - for (const reservation of reservations) { - reservation.status = ReservationStatus.CONFIRMED; - } - return reservations; - } - - async releaseReservationByOrderId(em: EntityManager, orderId: string): Promise { - const reservations = await em.find(Reservation, { order: { id: orderId } }); - if (!reservations) { - throw new NotFoundException(`Reservations with order ID ${orderId} not found`); - } - for (const reservation of reservations) { - em.remove(reservation); - } - return reservations; - } - - getReservationExpiry(method: PaymentMethodEnum): Date { - const minutes = STOCK_RESERVE_MINUTES[method] ?? 15; - return new Date(Date.now() + minutes * 60 * 1000); - } - } diff --git a/src/modules/orders/orders.module.ts b/src/modules/orders/orders.module.ts index 04df38d..f941afc 100644 --- a/src/modules/orders/orders.module.ts +++ b/src/modules/orders/orders.module.ts @@ -19,7 +19,7 @@ import { OrderListeners } from './listeners/order.listeners'; import { AdminModule } from '../admin/admin.module'; import { NotificationsModule } from '../notifications/notifications.module'; import { InventoryModule } from '../inventory/inventory.module'; - + @Module({ imports: [ MikroOrmModule.forFeature([Order, OrderItem, User, Restaurant, Food, UserAddress, PaymentMethod]), diff --git a/src/modules/orders/providers/orders.service.ts b/src/modules/orders/providers/orders.service.ts index 817d6f8..06ba01f 100644 --- a/src/modules/orders/providers/orders.service.ts +++ b/src/modules/orders/providers/orders.service.ts @@ -121,7 +121,7 @@ export class OrdersService { quantity: item.quantity, })), }; - await this.inventoryService.tempBulkReserveFood(em, restaurantId, order.id, order.paymentMethod.method, bulkReserveFoodDto); + await this.inventoryService.deductFromInventory(em, bulkReserveFoodDto); await em.flush(); this.logger.debug(`Order ${order.id} created for user ${userId} (restaurant ${restaurantId})`); return order; diff --git a/src/modules/payments/services/gateway.manager.ts b/src/modules/payments/gateways/gateway.manager.ts similarity index 90% rename from src/modules/payments/services/gateway.manager.ts rename to src/modules/payments/gateways/gateway.manager.ts index 7292888..b441fe8 100644 --- a/src/modules/payments/services/gateway.manager.ts +++ b/src/modules/payments/gateways/gateway.manager.ts @@ -1,6 +1,6 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { PaymentGatewayEnum } from '../interface/payment'; -import { ZarinpalGateway } from '../gateways/zarinpal.gateway'; +import { ZarinpalGateway } from './zarinpal.gateway'; import { IPaymentGateway } from '../interface/gateway'; @Injectable() diff --git a/src/modules/payments/payments.module.ts b/src/modules/payments/payments.module.ts index e8b2fa3..664da99 100644 --- a/src/modules/payments/payments.module.ts +++ b/src/modules/payments/payments.module.ts @@ -10,7 +10,7 @@ import { AuthModule } from '../auth/auth.module'; import { JwtModule } from '@nestjs/jwt'; import { Payment } from './entities/payment.entity'; import { ZarinpalGateway } from './gateways/zarinpal.gateway'; -import { GatewayManager } from './services/gateway.manager'; +import { GatewayManager } from './gateways/gateway.manager'; import { PaymentRepository } from './repositories/payment.repository'; import { InventoryModule } from '../inventory/inventory.module'; diff --git a/src/modules/payments/services/payments.service.ts b/src/modules/payments/services/payments.service.ts index 8d40e63..29391e3 100644 --- a/src/modules/payments/services/payments.service.ts +++ b/src/modules/payments/services/payments.service.ts @@ -4,7 +4,7 @@ import { Payment } from '../entities/payment.entity'; import { EntityManager } from '@mikro-orm/postgresql'; import { Order } from '../../orders/entities/order.entity'; import { Logger } from '@nestjs/common'; -import { GatewayManager } from './gateway.manager'; +import { GatewayManager } from '../gateways/gateway.manager'; import { UserWallet } from 'src/modules/users/entities/user-wallet.entity'; import { OrderPaymentContext } from '../interface/payment'; import { InventoryService } from 'src/modules/inventory/inventory.service'; @@ -209,8 +209,7 @@ export class PaymentsService { } this.markPaid(payment); this.confirmOrder(payment.order); - await this.confirmStock(orderId, em); - + await em.flush(); return payment; }); @@ -241,9 +240,7 @@ export class PaymentsService { order.status = OrderStatus.PAID; } - private async confirmStock(orderId: string, em: EntityManager) { - await this.inventoryService.confirmReservationByOrderId(em, orderId); - } + private async getOrCreateLatestPendingPayment(