This commit is contained in:
@@ -70,10 +70,10 @@ export class OrdersController {
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@Permissions(Permission.MANAGE_ORDERS)
|
||||
@Post('admin/orders')
|
||||
@ApiOperation({ summary: 'Create an order for a user (by phone) from admin side' })
|
||||
@ApiOperation({ summary: 'Create an order for a user from admin side' })
|
||||
@ApiBody({ type: AdminCreateOrderDto })
|
||||
createOrderForUser(@RestId() restId: string, @Body() dto: AdminCreateOrderDto) {
|
||||
return this.ordersService.createOrderForUserByPhone(restId, dto);
|
||||
return this.ordersService.createOrderForUser(restId, dto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ArrayMinSize,
|
||||
IsNotEmpty,
|
||||
} from 'class-validator';
|
||||
import type { OrderCarAddress, OrderUserAddress } from '../interface/order.interface';
|
||||
import type { OrderCarAddress } from '../interface/order.interface';
|
||||
|
||||
export class AdminOrderItemDto {
|
||||
@ApiProperty({ description: 'Food ID' })
|
||||
@@ -25,20 +25,10 @@ export class AdminOrderItemDto {
|
||||
}
|
||||
|
||||
export class AdminCreateOrderDto {
|
||||
@ApiProperty({ description: 'Phone number of the user to create the order for' })
|
||||
@ApiProperty({ description: 'User ID to create the order for' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
userPhone: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'First name — used when creating a new user' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
firstName?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Last name — used when creating a new user' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
lastName?: string;
|
||||
userId: string;
|
||||
|
||||
@ApiProperty({ type: [AdminOrderItemDto], description: 'Order items' })
|
||||
@IsArray()
|
||||
@@ -67,21 +57,10 @@ export class AdminCreateOrderDto {
|
||||
@IsString()
|
||||
tableNumber?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'User delivery address (required for courier delivery)',
|
||||
example: {
|
||||
fullName: 'علی محمدی',
|
||||
phone: '09121234567',
|
||||
city: 'تهران',
|
||||
province: 'تهران',
|
||||
address: 'خیابان ولیعصر، پلاک ۱۲',
|
||||
postalCode: '1234567890',
|
||||
latitude: 35.6892,
|
||||
longitude: 51.389,
|
||||
},
|
||||
})
|
||||
@ApiPropertyOptional({ description: 'Saved user address ID (required for courier delivery)' })
|
||||
@IsOptional()
|
||||
userAddress?: OrderUserAddress;
|
||||
@IsString()
|
||||
addressId?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Car address (required for car delivery)',
|
||||
@@ -94,22 +73,4 @@ export class AdminCreateOrderDto {
|
||||
})
|
||||
@IsOptional()
|
||||
carAddress?: OrderCarAddress;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Payment description. At least one of paymentDesc or paymentAttachments is required for credit card.',
|
||||
example: 'Transfer from account ending 1234',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
paymentDesc?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Payment receipt attachments (URLs). At least one of paymentDesc or paymentAttachments is required for credit card.',
|
||||
type: [String],
|
||||
example: ['https://example.com/receipt.jpg'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
paymentAttachments?: string[];
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import { Order } from '../entities/order.entity';
|
||||
import { OrderItem } from '../entities/order-item.entity';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
import { UserAddress } from '../../users/entities/user-address.entity';
|
||||
import { UserRestaurant } from '../../users/entities/user-restuarant.entity';
|
||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||
import { Food } from '../../foods/entities/food.entity';
|
||||
@@ -27,7 +28,6 @@ import { OrderMessage } from 'src/common/enums/message.enum';
|
||||
import { AdminCreateOrderDto } from '../dto/admin-create-order.dto';
|
||||
import { FoodOrderReportDto, FoodOrderReportSortBy } from '../dto/food-order-report.dto';
|
||||
import { DailyOrderReportDto, DailyOrderReportSortBy } from '../dto/daily-order-report.dto';
|
||||
import { normalizePhone } from 'src/modules/utils/phone.util';
|
||||
type OrderItemData = { food: Food; quantity: number; unitPrice: number; discount: number };
|
||||
|
||||
type ValidatedCartForOrder = {
|
||||
@@ -147,18 +147,8 @@ console.log(cart);
|
||||
return { paymentUrl, order };
|
||||
}
|
||||
|
||||
async createOrderForUserByPhone(restaurantId: string, dto: AdminCreateOrderDto): Promise<Order> {
|
||||
const normalizedPhone = normalizePhone(dto.userPhone);
|
||||
let user = await this.em.findOne(User, { phone: normalizedPhone });
|
||||
if (!user) {
|
||||
user = this.em.create(User, {
|
||||
phone: normalizedPhone,
|
||||
firstName: dto.firstName?.trim() || normalizedPhone,
|
||||
lastName: dto.lastName?.trim(),
|
||||
});
|
||||
await this.em.persistAndFlush(user);
|
||||
this.logger.debug(`Auto-created user ${user.id} with phone ${normalizedPhone} via admin order`);
|
||||
}
|
||||
async createOrderForUser(restaurantId: string, dto: AdminCreateOrderDto): Promise<Order> {
|
||||
const user = await this.getUserOrFail(dto.userId);
|
||||
|
||||
const [restaurant, delivery] = await Promise.all([
|
||||
this.getRestaurantOrFail(restaurantId),
|
||||
@@ -167,10 +157,14 @@ console.log(cart);
|
||||
|
||||
const paymentMethod = await this.getPaymentMethodOrFail(dto.paymentMethodId, restaurantId);
|
||||
this.assertPaymentMethodEnabled(paymentMethod);
|
||||
this.assertCreditCardPaymentDetails(paymentMethod, dto.paymentDesc, dto.paymentAttachments);
|
||||
|
||||
this.assertAdminDeliveryRequirements(dto, delivery);
|
||||
|
||||
const userAddress =
|
||||
delivery.method === DeliveryMethodEnum.DeliveryCourier
|
||||
? await this.resolveUserAddressForOrder(user, dto.addressId!)
|
||||
: null;
|
||||
|
||||
const orderItemsData = await this.buildOrderItemsDataFromDto(dto.items, restaurantId);
|
||||
|
||||
const subTotal = orderItemsData.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0);
|
||||
@@ -185,7 +179,7 @@ console.log(cart);
|
||||
user,
|
||||
restaurant,
|
||||
deliveryMethod: delivery,
|
||||
userAddress: delivery.method === DeliveryMethodEnum.DeliveryCourier ? (dto.userAddress ?? null) : null,
|
||||
userAddress,
|
||||
carAddress: delivery.method === DeliveryMethodEnum.DeliveryCar ? (dto.carAddress ?? null) : null,
|
||||
paymentMethod,
|
||||
couponDiscount: 0,
|
||||
@@ -219,7 +213,6 @@ console.log(cart);
|
||||
status: PaymentStatusEnum.Pending,
|
||||
method: order.paymentMethod.method,
|
||||
gateway: order.paymentMethod.gateway ?? null,
|
||||
...this.buildCreditCardPaymentFields(paymentMethod, dto.paymentDesc, dto.paymentAttachments),
|
||||
});
|
||||
em.persist(payment);
|
||||
|
||||
@@ -229,7 +222,7 @@ console.log(cart);
|
||||
await this.inventoryService.deductFromInventory(em, bulkReserveFoodDto);
|
||||
await em.flush();
|
||||
|
||||
this.logger.debug(`Admin created order ${order.id} for user ${user.id} (phone: ${normalizedPhone}, restaurant: ${restaurantId})`);
|
||||
this.logger.debug(`Admin created order ${order.id} for user ${user.id} (restaurant: ${restaurantId})`);
|
||||
return order;
|
||||
});
|
||||
|
||||
@@ -467,6 +460,28 @@ console.log(cart);
|
||||
return user;
|
||||
}
|
||||
|
||||
private async resolveUserAddressForOrder(user: User, addressId: string): Promise<OrderUserAddress> {
|
||||
const address = await this.em.findOne(UserAddress, {
|
||||
id: addressId,
|
||||
user: { id: user.id },
|
||||
});
|
||||
|
||||
if (!address) {
|
||||
throw new NotFoundException(`Address with ID ${addressId} not found for user ${user.id}.`);
|
||||
}
|
||||
|
||||
return {
|
||||
fullName: [user.firstName, user.lastName].filter(Boolean).join(' '),
|
||||
phone: address.phone || user.phone,
|
||||
city: address.city,
|
||||
province: address.province || '',
|
||||
address: address.address,
|
||||
postalCode: address.postalCode ?? undefined,
|
||||
latitude: address.latitude,
|
||||
longitude: address.longitude,
|
||||
};
|
||||
}
|
||||
|
||||
private async getRestaurantOrFail(restaurantId: string): Promise<Restaurant> {
|
||||
const restaurant = await this.em.findOne(Restaurant, { id: restaurantId });
|
||||
if (!restaurant) throw new NotFoundException(OrderMessage.RESTAURANT_NOT_FOUND);
|
||||
@@ -619,7 +634,7 @@ console.log(cart);
|
||||
}
|
||||
}
|
||||
|
||||
if (delivery.method === DeliveryMethodEnum.DeliveryCourier && !dto.userAddress) {
|
||||
if (delivery.method === DeliveryMethodEnum.DeliveryCourier && !dto.addressId) {
|
||||
throw new BadRequestException(OrderMessage.ADDRESS_REQUIRED);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user