modify create order
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-06-23 10:30:29 +03:30
parent 556c35d1df
commit 9bbab2f400
3 changed files with 41 additions and 65 deletions
@@ -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[];
}
+33 -18
View File
@@ -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);
}