From 77acab935ad5fd5104ca5528d6e918772c8499e2 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Wed, 1 Jul 2026 09:47:58 +0330 Subject: [PATCH] add discount --- database/migrations/.snapshot-dmenu.json | 3 ++- ...tion20260701052836_optionalUserForOrder.ts | 21 +++++++++++++++++++ src/common/enums/message.enum.ts | 2 ++ .../orders/dto/admin-create-order.dto.ts | 12 ++++++++--- .../orders/providers/orders.service.ts | 20 ++++++++++++------ 5 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 database/migrations/Migration20260701052836_optionalUserForOrder.ts diff --git a/database/migrations/.snapshot-dmenu.json b/database/migrations/.snapshot-dmenu.json index 7bcefce..02a8e79 100644 --- a/database/migrations/.snapshot-dmenu.json +++ b/database/migrations/.snapshot-dmenu.json @@ -4143,7 +4143,7 @@ "unsigned": false, "autoincrement": false, "primary": false, - "nullable": false, + "nullable": true, "length": 26, "mappedType": "character" }, @@ -4431,6 +4431,7 @@ "id" ], "referencedTableName": "public.users", + "deleteRule": "set null", "updateRule": "cascade" }, "orders_restaurant_id_foreign": { diff --git a/database/migrations/Migration20260701052836_optionalUserForOrder.ts b/database/migrations/Migration20260701052836_optionalUserForOrder.ts new file mode 100644 index 0000000..fabd11f --- /dev/null +++ b/database/migrations/Migration20260701052836_optionalUserForOrder.ts @@ -0,0 +1,21 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20260701052836_optionalUserForOrder extends Migration { + + override async up(): Promise { + this.addSql(`alter table "orders" drop constraint "orders_user_id_foreign";`); + + this.addSql(`alter table "orders" alter column "user_id" type char(26) using ("user_id"::char(26));`); + this.addSql(`alter table "orders" alter column "user_id" drop not null;`); + this.addSql(`alter table "orders" add constraint "orders_user_id_foreign" foreign key ("user_id") references "users" ("id") on update cascade on delete set null;`); + } + + override async down(): Promise { + this.addSql(`alter table "orders" drop constraint "orders_user_id_foreign";`); + + this.addSql(`alter table "orders" alter column "user_id" type char(26) using ("user_id"::char(26));`); + this.addSql(`alter table "orders" alter column "user_id" set not null;`); + this.addSql(`alter table "orders" add constraint "orders_user_id_foreign" foreign key ("user_id") references "users" ("id") on update cascade;`); + } + +} diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index bcb60ff..837f12c 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -677,6 +677,7 @@ export const enum GroupMessage { } export const enum OrderMessage { + USER_REQUIRED = 'کاربر الزامی است', NOT_FOUND = 'سفارش یافت نشد', PAYMENT_METHOD_MISSING = 'روش پرداخت سفارش مشخص نشده است', INVALID_STATUS_TRANSITION = 'انتقال وضعیت نامعتبر است', @@ -695,6 +696,7 @@ export const enum OrderMessage { FOOD_NOT_FOUND = 'غذا یافت نشد', FOOD_NOT_BELONGS_TO_RESTAURANT = 'غذا به این رستوران تعلق ندارد', CREDIT_CARD_PAYMENT_DETAILS_REQUIRED = 'حداقل یکی از توضیحات پرداخت یا پیوست رسید کارت به کارت الزامی است', + DISCOUNT_AMOUNT_EXCEEDS_ORDER_TOTAL = 'مبلغ تخفیف بیشتر از مبلغ سفارش است', } export const enum CartMessage { diff --git a/src/modules/orders/dto/admin-create-order.dto.ts b/src/modules/orders/dto/admin-create-order.dto.ts index fb7b05c..e354d69 100644 --- a/src/modules/orders/dto/admin-create-order.dto.ts +++ b/src/modules/orders/dto/admin-create-order.dto.ts @@ -25,10 +25,10 @@ export class AdminOrderItemDto { } export class AdminCreateOrderDto { - @ApiProperty({ description: 'User ID to create the order for' }) + @ApiPropertyOptional({ description: 'User ID to create the order for' }) @IsString() - @IsNotEmpty() - userId: string; + @IsOptional() + userId?: string; @ApiProperty({ type: [AdminOrderItemDto], description: 'Order items' }) @IsArray() @@ -47,6 +47,12 @@ export class AdminCreateOrderDto { @IsNotEmpty() paymentMethodId: string; + @ApiPropertyOptional({ description: 'Extra discount amount to apply on the order', minimum: 0 }) + @IsOptional() + @IsNumber() + @Min(0) + discountAmount?: number; + @ApiPropertyOptional({ description: 'Order description or notes' }) @IsOptional() @IsString() diff --git a/src/modules/orders/providers/orders.service.ts b/src/modules/orders/providers/orders.service.ts index 16589ea..d98298b 100644 --- a/src/modules/orders/providers/orders.service.ts +++ b/src/modules/orders/providers/orders.service.ts @@ -67,7 +67,6 @@ export class OrdersService { async checkout(userId: string, restaurantId: string) { const cart = await this.cartService.findOneOrFail(userId, restaurantId); const validated = await this.validateCartForOrder(userId, restaurantId, cart); -console.log(cart); const order = await this.em.transactional(async em => { const order = em.create(Order, { @@ -148,7 +147,7 @@ console.log(cart); } async createOrderForUser(restaurantId: string, dto: AdminCreateOrderDto): Promise { - const user = await this.getUserOrFail(dto.userId); + const user = dto.userId ? await this.getUserOrFail(dto.userId) : null; const [restaurant, delivery] = await Promise.all([ this.getRestaurantOrFail(restaurantId), @@ -162,7 +161,7 @@ console.log(cart); const userAddress = delivery.method === DeliveryMethodEnum.DeliveryCourier - ? await this.resolveUserAddressForOrder(user, dto.addressId!) + ? await this.resolveUserAddressForOrder(user!, dto.addressId!) : null; const orderItemsData = await this.buildOrderItemsDataFromDto(dto.items, restaurantId); @@ -170,7 +169,12 @@ console.log(cart); const subTotal = orderItemsData.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0); const itemsDiscount = orderItemsData.reduce((sum, item) => sum + item.discount * item.quantity, 0); const deliveryFee = delivery.deliveryFeeType === DeliveryFeeTypeEnum.FIXED ? Number(delivery.deliveryFee) : 0; - const totalDiscount = itemsDiscount; + const adminDiscount = dto.discountAmount ?? 0; + const maxDiscount = subTotal - itemsDiscount; + if (adminDiscount > maxDiscount) { + throw new BadRequestException(OrderMessage.DISCOUNT_AMOUNT_EXCEEDS_ORDER_TOTAL); + } + const totalDiscount = itemsDiscount + adminDiscount; const total = subTotal - totalDiscount + deliveryFee; const totalItems = orderItemsData.reduce((sum, item) => sum + item.quantity, 0); @@ -182,7 +186,7 @@ console.log(cart); userAddress, carAddress: delivery.method === DeliveryMethodEnum.DeliveryCar ? (dto.carAddress ?? null) : null, paymentMethod, - couponDiscount: 0, + couponDiscount: adminDiscount, couponDetail: null, itemsDiscount, totalDiscount, @@ -222,7 +226,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} (restaurant: ${restaurantId})`); + this.logger.debug(`Admin created order ${order.id} for user ${user?.id || 'anonymous'} (restaurant: ${restaurantId})`); return order; }); @@ -639,6 +643,10 @@ console.log(cart); if (delivery.method === DeliveryMethodEnum.DeliveryCar && !dto.carAddress) { throw new BadRequestException(OrderMessage.CAR_ADDRESS_REQUIRED); } + + if (delivery.method === DeliveryMethodEnum.DeliveryCourier && !dto.userId) { + throw new BadRequestException(OrderMessage.USER_REQUIRED); + } }