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) @UseGuards(AdminAuthGuard)
@Permissions(Permission.MANAGE_ORDERS) @Permissions(Permission.MANAGE_ORDERS)
@Post('admin/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 }) @ApiBody({ type: AdminCreateOrderDto })
createOrderForUser(@RestId() restId: string, @Body() dto: AdminCreateOrderDto) { createOrderForUser(@RestId() restId: string, @Body() dto: AdminCreateOrderDto) {
return this.ordersService.createOrderForUserByPhone(restId, dto); return this.ordersService.createOrderForUser(restId, dto);
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@@ -10,7 +10,7 @@ import {
ArrayMinSize, ArrayMinSize,
IsNotEmpty, IsNotEmpty,
} from 'class-validator'; } from 'class-validator';
import type { OrderCarAddress, OrderUserAddress } from '../interface/order.interface'; import type { OrderCarAddress } from '../interface/order.interface';
export class AdminOrderItemDto { export class AdminOrderItemDto {
@ApiProperty({ description: 'Food ID' }) @ApiProperty({ description: 'Food ID' })
@@ -25,20 +25,10 @@ export class AdminOrderItemDto {
} }
export class AdminCreateOrderDto { 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() @IsString()
@IsNotEmpty() @IsNotEmpty()
userPhone: string; userId: 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;
@ApiProperty({ type: [AdminOrderItemDto], description: 'Order items' }) @ApiProperty({ type: [AdminOrderItemDto], description: 'Order items' })
@IsArray() @IsArray()
@@ -67,21 +57,10 @@ export class AdminCreateOrderDto {
@IsString() @IsString()
tableNumber?: string; tableNumber?: string;
@ApiPropertyOptional({ @ApiPropertyOptional({ description: 'Saved user address ID (required for courier delivery)' })
description: 'User delivery address (required for courier delivery)',
example: {
fullName: 'علی محمدی',
phone: '09121234567',
city: 'تهران',
province: 'تهران',
address: 'خیابان ولیعصر، پلاک ۱۲',
postalCode: '1234567890',
latitude: 35.6892,
longitude: 51.389,
},
})
@IsOptional() @IsOptional()
userAddress?: OrderUserAddress; @IsString()
addressId?: string;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Car address (required for car delivery)', description: 'Car address (required for car delivery)',
@@ -94,22 +73,4 @@ export class AdminCreateOrderDto {
}) })
@IsOptional() @IsOptional()
carAddress?: OrderCarAddress; 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 { Order } from '../entities/order.entity';
import { OrderItem } from '../entities/order-item.entity'; import { OrderItem } from '../entities/order-item.entity';
import { User } from '../../users/entities/user.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 { UserRestaurant } from '../../users/entities/user-restuarant.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Restaurant } from '../../restaurants/entities/restaurant.entity';
import { Food } from '../../foods/entities/food.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 { AdminCreateOrderDto } from '../dto/admin-create-order.dto';
import { FoodOrderReportDto, FoodOrderReportSortBy } from '../dto/food-order-report.dto'; import { FoodOrderReportDto, FoodOrderReportSortBy } from '../dto/food-order-report.dto';
import { DailyOrderReportDto, DailyOrderReportSortBy } from '../dto/daily-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 OrderItemData = { food: Food; quantity: number; unitPrice: number; discount: number };
type ValidatedCartForOrder = { type ValidatedCartForOrder = {
@@ -147,18 +147,8 @@ console.log(cart);
return { paymentUrl, order }; return { paymentUrl, order };
} }
async createOrderForUserByPhone(restaurantId: string, dto: AdminCreateOrderDto): Promise<Order> { async createOrderForUser(restaurantId: string, dto: AdminCreateOrderDto): Promise<Order> {
const normalizedPhone = normalizePhone(dto.userPhone); const user = await this.getUserOrFail(dto.userId);
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`);
}
const [restaurant, delivery] = await Promise.all([ const [restaurant, delivery] = await Promise.all([
this.getRestaurantOrFail(restaurantId), this.getRestaurantOrFail(restaurantId),
@@ -167,10 +157,14 @@ console.log(cart);
const paymentMethod = await this.getPaymentMethodOrFail(dto.paymentMethodId, restaurantId); const paymentMethod = await this.getPaymentMethodOrFail(dto.paymentMethodId, restaurantId);
this.assertPaymentMethodEnabled(paymentMethod); this.assertPaymentMethodEnabled(paymentMethod);
this.assertCreditCardPaymentDetails(paymentMethod, dto.paymentDesc, dto.paymentAttachments);
this.assertAdminDeliveryRequirements(dto, delivery); 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 orderItemsData = await this.buildOrderItemsDataFromDto(dto.items, restaurantId);
const subTotal = orderItemsData.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0); const subTotal = orderItemsData.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0);
@@ -185,7 +179,7 @@ console.log(cart);
user, user,
restaurant, restaurant,
deliveryMethod: delivery, deliveryMethod: delivery,
userAddress: delivery.method === DeliveryMethodEnum.DeliveryCourier ? (dto.userAddress ?? null) : null, userAddress,
carAddress: delivery.method === DeliveryMethodEnum.DeliveryCar ? (dto.carAddress ?? null) : null, carAddress: delivery.method === DeliveryMethodEnum.DeliveryCar ? (dto.carAddress ?? null) : null,
paymentMethod, paymentMethod,
couponDiscount: 0, couponDiscount: 0,
@@ -219,7 +213,6 @@ console.log(cart);
status: PaymentStatusEnum.Pending, status: PaymentStatusEnum.Pending,
method: order.paymentMethod.method, method: order.paymentMethod.method,
gateway: order.paymentMethod.gateway ?? null, gateway: order.paymentMethod.gateway ?? null,
...this.buildCreditCardPaymentFields(paymentMethod, dto.paymentDesc, dto.paymentAttachments),
}); });
em.persist(payment); em.persist(payment);
@@ -229,7 +222,7 @@ console.log(cart);
await this.inventoryService.deductFromInventory(em, bulkReserveFoodDto); await this.inventoryService.deductFromInventory(em, bulkReserveFoodDto);
await em.flush(); 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; return order;
}); });
@@ -467,6 +460,28 @@ console.log(cart);
return user; 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> { private async getRestaurantOrFail(restaurantId: string): Promise<Restaurant> {
const restaurant = await this.em.findOne(Restaurant, { id: restaurantId }); const restaurant = await this.em.findOne(Restaurant, { id: restaurantId });
if (!restaurant) throw new NotFoundException(OrderMessage.RESTAURANT_NOT_FOUND); 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); throw new BadRequestException(OrderMessage.ADDRESS_REQUIRED);
} }