up
This commit is contained in:
@@ -22,7 +22,6 @@ import { NotificationsModule } from './modules/notifications/notifications.modul
|
|||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
import { PagerModule } from './modules/pager/pager.module';
|
import { PagerModule } from './modules/pager/pager.module';
|
||||||
import { ContactModule } from './modules/contact/contact.module';
|
import { ContactModule } from './modules/contact/contact.module';
|
||||||
import { ReservationsModule } from './modules/reservations/reservations.module';
|
|
||||||
import { InventoryModule } from './modules/inventory/inventory.module';
|
import { InventoryModule } from './modules/inventory/inventory.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -54,7 +53,6 @@ import { InventoryModule } from './modules/inventory/inventory.module';
|
|||||||
EventEmitterModule.forRoot(),
|
EventEmitterModule.forRoot(),
|
||||||
PagerModule,
|
PagerModule,
|
||||||
ContactModule,
|
ContactModule,
|
||||||
ReservationsModule,
|
|
||||||
InventoryModule,
|
InventoryModule,
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
|||||||
|
|
||||||
export function getSwaggerConfig(app: INestApplication) {
|
export function getSwaggerConfig(app: INestApplication) {
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
.setTitle('Tahavol API')
|
.setTitle('D-menu API')
|
||||||
.setDescription('API documentation for Tahavol backend')
|
.setDescription('API documentation for D-menu backend')
|
||||||
.setVersion('1.0')
|
.setVersion('1.0')
|
||||||
.addBearerAuth() // optional: for JWT endpoints
|
.addBearerAuth() // optional: for JWT endpoints
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { Cart, CartItem } from '../interfaces/cart.interface';
|
|||||||
import { CouponService } from 'src/modules/coupons/providers/coupon.service';
|
import { CouponService } from 'src/modules/coupons/providers/coupon.service';
|
||||||
import { OrderCouponDetail } from 'src/modules/orders/interface/order-status';
|
import { OrderCouponDetail } from 'src/modules/orders/interface/order-status';
|
||||||
import { CouponType } from 'src/modules/coupons/interface/coupon';
|
import { CouponType } from 'src/modules/coupons/interface/coupon';
|
||||||
import { Order } from '../../orders/entities/order.entity';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CartService {
|
export class CartService {
|
||||||
@@ -95,7 +94,7 @@ export class CartService {
|
|||||||
const cart = await this.getOrCreateCart(userId, restaurantId);
|
const cart = await this.getOrCreateCart(userId, restaurantId);
|
||||||
|
|
||||||
// Find food
|
// Find food
|
||||||
const food = await this.em.findOne(Food, { id: foodId }, { populate: ['restaurant'] });
|
const food = await this.em.findOne(Food, { id: foodId }, { populate: ['restaurant', 'inventory'] });
|
||||||
if (!food) {
|
if (!food) {
|
||||||
throw new NotFoundException(`Food with ID ${foodId} not found`);
|
throw new NotFoundException(`Food with ID ${foodId} not found`);
|
||||||
}
|
}
|
||||||
@@ -106,8 +105,9 @@ export class CartService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check stock availability
|
// Check stock availability
|
||||||
if (food.stock < addItemDto.quantity) {
|
const availableStock = food.inventory?.availableStock ?? 0;
|
||||||
throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${food.stock}`);
|
if (availableStock < addItemDto.quantity) {
|
||||||
|
throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${availableStock}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if item already exists in cart
|
// Check if item already exists in cart
|
||||||
@@ -119,8 +119,8 @@ export class CartService {
|
|||||||
const newQuantity = existingItem.quantity + addItemDto.quantity;
|
const newQuantity = existingItem.quantity + addItemDto.quantity;
|
||||||
|
|
||||||
// Check stock for new total quantity
|
// Check stock for new total quantity
|
||||||
if (food.stock < newQuantity) {
|
if (availableStock < newQuantity) {
|
||||||
throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${food.stock}`);
|
throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${availableStock}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemPrice = food.price || 0;
|
const itemPrice = food.price || 0;
|
||||||
@@ -221,7 +221,7 @@ export class CartService {
|
|||||||
// Process each item
|
// Process each item
|
||||||
for (const addItemDto of bulkAddItemsDto.items) {
|
for (const addItemDto of bulkAddItemsDto.items) {
|
||||||
// Find food
|
// Find food
|
||||||
const food = await this.em.findOne(Food, { id: addItemDto.foodId }, { populate: ['restaurant'] });
|
const food = await this.em.findOne(Food, { id: addItemDto.foodId }, { populate: ['restaurant', 'inventory'] });
|
||||||
if (!food) {
|
if (!food) {
|
||||||
throw new NotFoundException(`Food with ID ${addItemDto.foodId} not found`);
|
throw new NotFoundException(`Food with ID ${addItemDto.foodId} not found`);
|
||||||
}
|
}
|
||||||
@@ -232,8 +232,9 @@ export class CartService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check stock availability
|
// Check stock availability
|
||||||
if (food.stock < addItemDto.quantity) {
|
const availableStock = food.inventory?.availableStock ?? 0;
|
||||||
throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${food.stock}`);
|
if (availableStock < addItemDto.quantity) {
|
||||||
|
throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${availableStock}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if item already exists in cart
|
// Check if item already exists in cart
|
||||||
@@ -245,8 +246,8 @@ export class CartService {
|
|||||||
const newQuantity = existingItem.quantity + addItemDto.quantity;
|
const newQuantity = existingItem.quantity + addItemDto.quantity;
|
||||||
|
|
||||||
// Check stock for new total quantity
|
// Check stock for new total quantity
|
||||||
if (food.stock < newQuantity) {
|
if (availableStock < newQuantity) {
|
||||||
throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${food.stock}`);
|
throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${availableStock}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemPrice = food.price || 0;
|
const itemPrice = food.price || 0;
|
||||||
@@ -307,14 +308,15 @@ export class CartService {
|
|||||||
const item = cart.items[itemIndex];
|
const item = cart.items[itemIndex];
|
||||||
|
|
||||||
// Find food to check stock
|
// Find food to check stock
|
||||||
const food = await this.em.findOne(Food, { id: item.foodId }, { populate: ['restaurant'] });
|
const food = await this.em.findOne(Food, { id: item.foodId }, { populate: ['restaurant', 'inventory'] });
|
||||||
if (!food) {
|
if (!food) {
|
||||||
throw new NotFoundException(`Food with ID ${item.foodId} not found`);
|
throw new NotFoundException(`Food with ID ${item.foodId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check stock availability
|
// Check stock availability
|
||||||
if (food.stock < updateItemDto.quantity) {
|
const availableStock = food.inventory?.availableStock ?? 0;
|
||||||
throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${food.stock}`);
|
if (availableStock < updateItemDto.quantity) {
|
||||||
|
throw new BadRequestException(`Insufficient stock for food ${food.title}. Available: ${availableStock}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update quantity
|
// Update quantity
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Cascade, Collection, Entity, Index, ManyToOne, OneToMany, Property } from '@mikro-orm/core';
|
import { Cascade, Collection, Entity, Index, ManyToOne, OneToMany, Property, OneToOne } from '@mikro-orm/core';
|
||||||
import { Category } from './category.entity';
|
import { Category } from './category.entity';
|
||||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity';
|
import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity';
|
||||||
@@ -14,13 +14,17 @@ export class Food extends BaseEntity {
|
|||||||
restaurant: Restaurant;
|
restaurant: Restaurant;
|
||||||
|
|
||||||
@ManyToOne(() => Category)
|
@ManyToOne(() => Category)
|
||||||
category!: Category;
|
category: Category;
|
||||||
|
|
||||||
@OneToMany(() => Review, review => review.food, { cascade: [Cascade.ALL], orphanRemoval: true })
|
@OneToMany(() => Review, review => review.food, { cascade: [Cascade.ALL], orphanRemoval: true })
|
||||||
reviews = new Collection<Review>(this);
|
reviews = new Collection<Review>(this);
|
||||||
|
|
||||||
@OneToMany(() => Inventory, inventory => inventory.food, { cascade: [Cascade.ALL], orphanRemoval: true })
|
@OneToOne(() => Inventory, inventory => inventory.food, {
|
||||||
inventory = new Collection<Inventory>(this);
|
nullable: true,
|
||||||
|
cascade: [Cascade.ALL],
|
||||||
|
orphanRemoval: true,
|
||||||
|
})
|
||||||
|
inventory?: Inventory;
|
||||||
|
|
||||||
@Property({ nullable: true })
|
@Property({ nullable: true })
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|||||||
@@ -44,9 +44,6 @@ export class FoodService {
|
|||||||
noon: rest.noon ?? false,
|
noon: rest.noon ?? false,
|
||||||
dinner: rest.dinner ?? false,
|
dinner: rest.dinner ?? false,
|
||||||
desc: rest.desc,
|
desc: rest.desc,
|
||||||
// pickup/stock
|
|
||||||
stock: rest.stock ?? 0,
|
|
||||||
stockDefault: rest.stockDefault ?? 0,
|
|
||||||
isActive: rest.isActive ?? true,
|
isActive: rest.isActive ?? true,
|
||||||
inPlaceServe: rest.inPlaceServe ?? false,
|
inPlaceServe: rest.inPlaceServe ?? false,
|
||||||
pickupServe: rest.pickupServe ?? false,
|
pickupServe: rest.pickupServe ?? false,
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
import { Entity, ManyToOne, Property, Unique } from '@mikro-orm/core';
|
import { Entity, Property, OneToOne } from '@mikro-orm/core';
|
||||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
|
||||||
import { Food } from '../../foods/entities/food.entity';
|
import { Food } from '../../foods/entities/food.entity';
|
||||||
|
|
||||||
@Entity({ tableName: 'inventory' })
|
@Entity({ tableName: 'inventory' })
|
||||||
@Unique({ properties: ['food', 'restaurant'] })
|
|
||||||
export class Inventory extends BaseEntity {
|
export class Inventory extends BaseEntity {
|
||||||
@ManyToOne(() => Food, { unique: true })
|
@OneToOne(() => Food, food => food.inventory)
|
||||||
food!: Food;
|
food!: Food;
|
||||||
|
|
||||||
@ManyToOne(() => Restaurant)
|
|
||||||
restaurant!: Restaurant;
|
|
||||||
|
|
||||||
@Property({ type: 'int' })
|
@Property({ type: 'int' })
|
||||||
totalStock!: number;
|
totalStock!: number;
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ export class InventoryService {
|
|||||||
|
|
||||||
// Find or create inventory record
|
// Find or create inventory record
|
||||||
let inventory = await this.em.findOne(Inventory, {
|
let inventory = await this.em.findOne(Inventory, {
|
||||||
food: { id: foodId },
|
food: { id: foodId, restaurant: { id: restaurantId } },
|
||||||
restaurant: { id: restaurantId },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!inventory) {
|
if (!inventory) {
|
||||||
@@ -45,7 +44,6 @@ export class InventoryService {
|
|||||||
|
|
||||||
inventory = this.em.create(Inventory, {
|
inventory = this.em.create(Inventory, {
|
||||||
food,
|
food,
|
||||||
restaurant,
|
|
||||||
totalStock: setStockDto.totalStock,
|
totalStock: setStockDto.totalStock,
|
||||||
availableStock: setStockDto.availableStock,
|
availableStock: setStockDto.availableStock,
|
||||||
});
|
});
|
||||||
@@ -93,8 +91,7 @@ export class InventoryService {
|
|||||||
|
|
||||||
// Load all existing inventories in one query
|
// Load all existing inventories in one query
|
||||||
const existingInventories = await this.em.find(Inventory, {
|
const existingInventories = await this.em.find(Inventory, {
|
||||||
food: { id: { $in: foodIds } },
|
food: { id: { $in: foodIds }, restaurant: { id: restaurantId } },
|
||||||
restaurant: { id: restaurantId },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const inventoryMap = new Map<string, Inventory>();
|
const inventoryMap = new Map<string, Inventory>();
|
||||||
@@ -118,7 +115,6 @@ export class InventoryService {
|
|||||||
// Create new inventory record
|
// Create new inventory record
|
||||||
inventory = this.em.create(Inventory, {
|
inventory = this.em.create(Inventory, {
|
||||||
food,
|
food,
|
||||||
restaurant,
|
|
||||||
totalStock: item.totalStock,
|
totalStock: item.totalStock,
|
||||||
availableStock: item.availableStock,
|
availableStock: item.availableStock,
|
||||||
});
|
});
|
||||||
@@ -173,8 +169,7 @@ export class InventoryService {
|
|||||||
|
|
||||||
// Load all existing inventories in one query
|
// Load all existing inventories in one query
|
||||||
const existingInventories = await this.em.find(Inventory, {
|
const existingInventories = await this.em.find(Inventory, {
|
||||||
food: { id: { $in: foodIds } },
|
food: { id: { $in: foodIds }, restaurant: { id: restaurantId } },
|
||||||
restaurant: { id: restaurantId },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const inventoryMap = new Map<string, Inventory>();
|
const inventoryMap = new Map<string, Inventory>();
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export class OrdersController {
|
|||||||
@ApiOperation({ summary: 'Accept an order' })
|
@ApiOperation({ summary: 'Accept an order' })
|
||||||
@ApiParam({ name: 'orderId', description: 'Order ID' })
|
@ApiParam({ name: 'orderId', description: 'Order ID' })
|
||||||
acceptOrder(@Param('orderId') orderId: string, @RestId() restId: string) {
|
acceptOrder(@Param('orderId') orderId: string, @RestId() restId: string) {
|
||||||
return this.ordersService.acceptOrder(orderId, restId);
|
return this.ordersService.confirmOrder(orderId, restId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { UserAddress } from '../../users/entities/user-address.entity';
|
|||||||
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
||||||
import { OrderItem } from './order-item.entity';
|
import { OrderItem } from './order-item.entity';
|
||||||
import { Delivery } from '../../delivery/entities/delivery.entity';
|
import { Delivery } from '../../delivery/entities/delivery.entity';
|
||||||
|
import { Payment } from 'src/modules/payments/entities/payment.entity';
|
||||||
|
|
||||||
@Entity({ tableName: 'orders' })
|
@Entity({ tableName: 'orders' })
|
||||||
@Unique({ properties: ['restaurant', 'orderNumber'] })
|
@Unique({ properties: ['restaurant', 'orderNumber'] })
|
||||||
@@ -47,8 +48,8 @@ export class Order extends BaseEntity {
|
|||||||
@ManyToOne(() => UserAddress, { nullable: true })
|
@ManyToOne(() => UserAddress, { nullable: true })
|
||||||
address?: UserAddress;
|
address?: UserAddress;
|
||||||
|
|
||||||
@ManyToOne(() => PaymentMethod, { nullable: true })
|
@OneToOne(() => Payment, payment => payment.order)
|
||||||
paymentMethod?: PaymentMethod;
|
paymentMethod: PaymentMethod;
|
||||||
|
|
||||||
@Property({ type: 'int', nullable: true })
|
@Property({ type: 'int', nullable: true })
|
||||||
orderNumber?: number;
|
orderNumber?: number;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import type { CouponType } from 'src/modules/coupons/interface/coupon';
|
|||||||
// Delivered = 'delivered',
|
// Delivered = 'delivered',
|
||||||
// }
|
// }
|
||||||
export enum OrderStatus {
|
export enum OrderStatus {
|
||||||
CREATED = 'created',
|
CREATED = 'new',
|
||||||
PENDING_PAYMENT = 'pendingPayment',
|
PENDING_PAYMENT = 'pendingPayment',
|
||||||
PAID = 'paid',
|
PAID = 'paid',
|
||||||
CONFIRMED = 'confirmed',
|
CONFIRMED = 'confirmed',
|
||||||
@@ -32,6 +32,7 @@ export enum OrderStatus {
|
|||||||
READY = 'ready',
|
READY = 'ready',
|
||||||
SHIPPED = 'shipped',
|
SHIPPED = 'shipped',
|
||||||
COMPLETED = 'completed',
|
COMPLETED = 'completed',
|
||||||
|
|
||||||
CANCELED = 'canceled',
|
CANCELED = 'canceled',
|
||||||
FAILED = 'failed',
|
FAILED = 'failed',
|
||||||
REFUNDED = 'refunded',
|
REFUNDED = 'refunded',
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class OrdersService {
|
|||||||
totalItems: cart.totalItems || 0,
|
totalItems: cart.totalItems || 0,
|
||||||
description: cart.description,
|
description: cart.description,
|
||||||
tableNumber: cart.tableNumber,
|
tableNumber: cart.tableNumber,
|
||||||
status: OrderStatus.Pending,
|
status: OrderStatus.NEW,
|
||||||
paymentStatus: PaymentStatusEnum.Pending,
|
paymentStatus: PaymentStatusEnum.Pending,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ export class OrdersService {
|
|||||||
const { food, quantity, unitPrice, discount } = itemData;
|
const { food, quantity, unitPrice, discount } = itemData;
|
||||||
|
|
||||||
// Stock check
|
// Stock check
|
||||||
if (food.stock < quantity) {
|
if (food.inventory.availableStock < quantity) {
|
||||||
throw new BadRequestException(`${food.title} is out of stock`);
|
throw new BadRequestException(`${food.title} is out of stock`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ export class OrdersService {
|
|||||||
const orderItemsData: Array<{ food: Food; quantity: number; unitPrice: number; discount: number }> = [];
|
const orderItemsData: Array<{ food: Food; quantity: number; unitPrice: number; discount: number }> = [];
|
||||||
|
|
||||||
for (const cartItem of cart.items) {
|
for (const cartItem of cart.items) {
|
||||||
const food = await this.em.findOne(Food, { id: cartItem.foodId }, { populate: ['restaurant'] });
|
const food = await this.em.findOne(Food, { id: cartItem.foodId }, { populate: ['restaurant', 'inventory'] });
|
||||||
if (!food) {
|
if (!food) {
|
||||||
throw new NotFoundException(`Food with ID ${cartItem.foodId} not found`);
|
throw new NotFoundException(`Food with ID ${cartItem.foodId} not found`);
|
||||||
}
|
}
|
||||||
@@ -232,9 +232,10 @@ export class OrdersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Final stock validation
|
// Final stock validation
|
||||||
if (food.stock < cartItem.quantity) {
|
const availableStock = food.inventory?.availableStock ?? 0;
|
||||||
|
if (availableStock < cartItem.quantity) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`Insufficient stock for food ${food.title || food.id}. Available: ${food.stock}, Requested: ${cartItem.quantity}`,
|
`Insufficient stock for food ${food.title || food.id}. Available: ${availableStock}, Requested: ${cartItem.quantity}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,13 +304,13 @@ export class OrdersService {
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
async acceptOrder(orderId: string, restId: string) {
|
async confirmOrder(orderId: string, restId: string) {
|
||||||
const order = await this.em.findOne(Order, { id: orderId, restaurant: { id: restId } });
|
const order = await this.em.findOne(Order, { id: orderId, restaurant: { id: restId } });
|
||||||
if (!order) {
|
if (!order) {
|
||||||
throw new NotFoundException('Order not found');
|
throw new NotFoundException('Order not found');
|
||||||
}
|
}
|
||||||
if (order.status !== OrderStatus.Pending) {
|
if (order.status !== OrderStatus.CREATED) {
|
||||||
throw new BadRequestException('Order must be pending before accepting');
|
throw new BadRequestException('Order must be created before confirming');
|
||||||
}
|
}
|
||||||
// Wallet payments should be paid before accepting (like Online)
|
// Wallet payments should be paid before accepting (like Online)
|
||||||
if (order.paymentMethod?.method === PaymentMethodEnum.Online && order.paymentStatus !== PaymentStatusEnum.Paid) {
|
if (order.paymentMethod?.method === PaymentMethodEnum.Online && order.paymentStatus !== PaymentStatusEnum.Paid) {
|
||||||
@@ -319,7 +320,7 @@ export class OrdersService {
|
|||||||
// Cash, CardOnDelivery, and Wallet can be accepted even if payment status is pending
|
// Cash, CardOnDelivery, and Wallet can be accepted even if payment status is pending
|
||||||
// (they're paid on delivery or already deducted)
|
// (they're paid on delivery or already deducted)
|
||||||
|
|
||||||
order.status = OrderStatus.Confirmed;
|
order.status = OrderStatus.CONFIRMED;
|
||||||
await this.em.persistAndFlush(order);
|
await this.em.persistAndFlush(order);
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
@@ -329,10 +330,13 @@ export class OrdersService {
|
|||||||
if (!order) {
|
if (!order) {
|
||||||
throw new NotFoundException('Order not found');
|
throw new NotFoundException('Order not found');
|
||||||
}
|
}
|
||||||
if (order.status !== OrderStatus.Confirmed) {
|
if (!this.canTransition(order.status, OrderStatus.PREPARING, order.paymentMethod?.method)) {
|
||||||
|
throw new BadRequestException('Invalid status transition');
|
||||||
|
}
|
||||||
|
if (order.status !== OrderStatus.CONFIRMED) {
|
||||||
throw new BadRequestException('Order must be confirmed before preparing');
|
throw new BadRequestException('Order must be confirmed before preparing');
|
||||||
}
|
}
|
||||||
order.status = OrderStatus.Preparing;
|
order.status = OrderStatus.PREPARING;
|
||||||
await this.em.persistAndFlush(order);
|
await this.em.persistAndFlush(order);
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
@@ -342,7 +346,7 @@ export class OrdersService {
|
|||||||
if (!order) {
|
if (!order) {
|
||||||
throw new NotFoundException('Order not found');
|
throw new NotFoundException('Order not found');
|
||||||
}
|
}
|
||||||
order.status = OrderStatus.RejectedByRestaurant;
|
order.status = OrderStatus.CANCELED;
|
||||||
await this.em.persistAndFlush(order);
|
await this.em.persistAndFlush(order);
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
@@ -381,10 +385,10 @@ export class OrdersService {
|
|||||||
if (!order) {
|
if (!order) {
|
||||||
throw new NotFoundException('Order not found');
|
throw new NotFoundException('Order not found');
|
||||||
}
|
}
|
||||||
if (order.status !== OrderStatus.Pending) {
|
if (order.status !== OrderStatus.) {
|
||||||
throw new BadRequestException('Order is not pending');
|
throw new BadRequestException('Order is not pending');
|
||||||
}
|
}
|
||||||
order.status = OrderStatus.CancelledByUser;
|
order.status = OrderStatus.CANCELED;
|
||||||
await this.em.persistAndFlush(order);
|
await this.em.persistAndFlush(order);
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
@@ -446,6 +450,30 @@ export class OrdersService {
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transitions = {
|
||||||
|
CREATED: [OrderStatus.PENDING_PAYMENT, OrderStatus.CONFIRMED, OrderStatus.CANCELED],
|
||||||
|
PENDING_PAYMENT: [OrderStatus.PAID, OrderStatus.FAILED, OrderStatus.CANCELED],
|
||||||
|
PAID: [OrderStatus.CONFIRMED, OrderStatus.REFUNDED],
|
||||||
|
CONFIRMED: [OrderStatus.PREPARING, OrderStatus.CANCELED],
|
||||||
|
PREPARING: [OrderStatus.READY, OrderStatus.SHIPPED, OrderStatus.CANCELED],
|
||||||
|
READY: [OrderStatus.COMPLETED, OrderStatus.FAILED],
|
||||||
|
SHIPPED: [OrderStatus.COMPLETED, OrderStatus.FAILED],
|
||||||
|
}
|
||||||
|
|
||||||
|
canTransition(from: OrderStatus, to: OrderStatus, paymentMethod: PaymentMethodEnum) {
|
||||||
|
if (!this.transitions[from]?.includes(to)) return false;
|
||||||
|
|
||||||
|
if (paymentMethod === PaymentMethodEnum.Cash) {
|
||||||
|
if ([OrderStatus.PENDING_PAYMENT, OrderStatus.PAID].includes(to)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paymentMethod === PaymentMethodEnum.Online) {
|
||||||
|
if (to === OrderStatus.CONFIRMED && from !== OrderStatus.PAID) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleanup job to handle abandoned orders (pending payment for >30 minutes)
|
* Cleanup job to handle abandoned orders (pending payment for >30 minutes)
|
||||||
* Runs every 10 minutes to check for abandoned orders
|
* Runs every 10 minutes to check for abandoned orders
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export enum PaymentMethodEnum {
|
export enum PaymentMethodEnum {
|
||||||
Online = 'Online',
|
Online = 'Online',
|
||||||
Cash = 'Cash',
|
Cash = 'Cash',
|
||||||
CardOnDelivery = 'CardOnDelivery',
|
|
||||||
Wallet = 'Wallet',
|
Wallet = 'Wallet',
|
||||||
}
|
}
|
||||||
export enum PaymentStatusEnum {
|
export enum PaymentStatusEnum {
|
||||||
|
|||||||
Reference in New Issue
Block a user