add discount
This commit is contained in:
@@ -4143,7 +4143,7 @@
|
|||||||
"unsigned": false,
|
"unsigned": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"nullable": false,
|
"nullable": true,
|
||||||
"length": 26,
|
"length": 26,
|
||||||
"mappedType": "character"
|
"mappedType": "character"
|
||||||
},
|
},
|
||||||
@@ -4431,6 +4431,7 @@
|
|||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"referencedTableName": "public.users",
|
"referencedTableName": "public.users",
|
||||||
|
"deleteRule": "set null",
|
||||||
"updateRule": "cascade"
|
"updateRule": "cascade"
|
||||||
},
|
},
|
||||||
"orders_restaurant_id_foreign": {
|
"orders_restaurant_id_foreign": {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Migration } from '@mikro-orm/migrations';
|
||||||
|
|
||||||
|
export class Migration20260701052836_optionalUserForOrder extends Migration {
|
||||||
|
|
||||||
|
override async up(): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
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;`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -677,6 +677,7 @@ export const enum GroupMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const enum OrderMessage {
|
export const enum OrderMessage {
|
||||||
|
USER_REQUIRED = 'کاربر الزامی است',
|
||||||
NOT_FOUND = 'سفارش یافت نشد',
|
NOT_FOUND = 'سفارش یافت نشد',
|
||||||
PAYMENT_METHOD_MISSING = 'روش پرداخت سفارش مشخص نشده است',
|
PAYMENT_METHOD_MISSING = 'روش پرداخت سفارش مشخص نشده است',
|
||||||
INVALID_STATUS_TRANSITION = 'انتقال وضعیت نامعتبر است',
|
INVALID_STATUS_TRANSITION = 'انتقال وضعیت نامعتبر است',
|
||||||
@@ -695,6 +696,7 @@ export const enum OrderMessage {
|
|||||||
FOOD_NOT_FOUND = 'غذا یافت نشد',
|
FOOD_NOT_FOUND = 'غذا یافت نشد',
|
||||||
FOOD_NOT_BELONGS_TO_RESTAURANT = 'غذا به این رستوران تعلق ندارد',
|
FOOD_NOT_BELONGS_TO_RESTAURANT = 'غذا به این رستوران تعلق ندارد',
|
||||||
CREDIT_CARD_PAYMENT_DETAILS_REQUIRED = 'حداقل یکی از توضیحات پرداخت یا پیوست رسید کارت به کارت الزامی است',
|
CREDIT_CARD_PAYMENT_DETAILS_REQUIRED = 'حداقل یکی از توضیحات پرداخت یا پیوست رسید کارت به کارت الزامی است',
|
||||||
|
DISCOUNT_AMOUNT_EXCEEDS_ORDER_TOTAL = 'مبلغ تخفیف بیشتر از مبلغ سفارش است',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum CartMessage {
|
export const enum CartMessage {
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ export class AdminOrderItemDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AdminCreateOrderDto {
|
export class AdminCreateOrderDto {
|
||||||
@ApiProperty({ description: 'User ID to create the order for' })
|
@ApiPropertyOptional({ description: 'User ID to create the order for' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsOptional()
|
||||||
userId: string;
|
userId?: string;
|
||||||
|
|
||||||
@ApiProperty({ type: [AdminOrderItemDto], description: 'Order items' })
|
@ApiProperty({ type: [AdminOrderItemDto], description: 'Order items' })
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@@ -47,6 +47,12 @@ export class AdminCreateOrderDto {
|
|||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
paymentMethodId: string;
|
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' })
|
@ApiPropertyOptional({ description: 'Order description or notes' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ export class OrdersService {
|
|||||||
async checkout(userId: string, restaurantId: string) {
|
async checkout(userId: string, restaurantId: string) {
|
||||||
const cart = await this.cartService.findOneOrFail(userId, restaurantId);
|
const cart = await this.cartService.findOneOrFail(userId, restaurantId);
|
||||||
const validated = await this.validateCartForOrder(userId, restaurantId, cart);
|
const validated = await this.validateCartForOrder(userId, restaurantId, cart);
|
||||||
console.log(cart);
|
|
||||||
|
|
||||||
const order = await this.em.transactional(async em => {
|
const order = await this.em.transactional(async em => {
|
||||||
const order = em.create(Order, {
|
const order = em.create(Order, {
|
||||||
@@ -148,7 +147,7 @@ console.log(cart);
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createOrderForUser(restaurantId: string, dto: AdminCreateOrderDto): Promise<Order> {
|
async createOrderForUser(restaurantId: string, dto: AdminCreateOrderDto): Promise<Order> {
|
||||||
const user = await this.getUserOrFail(dto.userId);
|
const user = dto.userId ? await this.getUserOrFail(dto.userId) : null;
|
||||||
|
|
||||||
const [restaurant, delivery] = await Promise.all([
|
const [restaurant, delivery] = await Promise.all([
|
||||||
this.getRestaurantOrFail(restaurantId),
|
this.getRestaurantOrFail(restaurantId),
|
||||||
@@ -162,7 +161,7 @@ console.log(cart);
|
|||||||
|
|
||||||
const userAddress =
|
const userAddress =
|
||||||
delivery.method === DeliveryMethodEnum.DeliveryCourier
|
delivery.method === DeliveryMethodEnum.DeliveryCourier
|
||||||
? await this.resolveUserAddressForOrder(user, dto.addressId!)
|
? await this.resolveUserAddressForOrder(user!, dto.addressId!)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const orderItemsData = await this.buildOrderItemsDataFromDto(dto.items, restaurantId);
|
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 subTotal = orderItemsData.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0);
|
||||||
const itemsDiscount = orderItemsData.reduce((sum, item) => sum + item.discount * 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 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 total = subTotal - totalDiscount + deliveryFee;
|
||||||
const totalItems = orderItemsData.reduce((sum, item) => sum + item.quantity, 0);
|
const totalItems = orderItemsData.reduce((sum, item) => sum + item.quantity, 0);
|
||||||
|
|
||||||
@@ -182,7 +186,7 @@ console.log(cart);
|
|||||||
userAddress,
|
userAddress,
|
||||||
carAddress: delivery.method === DeliveryMethodEnum.DeliveryCar ? (dto.carAddress ?? null) : null,
|
carAddress: delivery.method === DeliveryMethodEnum.DeliveryCar ? (dto.carAddress ?? null) : null,
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
couponDiscount: 0,
|
couponDiscount: adminDiscount,
|
||||||
couponDetail: null,
|
couponDetail: null,
|
||||||
itemsDiscount,
|
itemsDiscount,
|
||||||
totalDiscount,
|
totalDiscount,
|
||||||
@@ -222,7 +226,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} (restaurant: ${restaurantId})`);
|
this.logger.debug(`Admin created order ${order.id} for user ${user?.id || 'anonymous'} (restaurant: ${restaurantId})`);
|
||||||
return order;
|
return order;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -639,6 +643,10 @@ console.log(cart);
|
|||||||
if (delivery.method === DeliveryMethodEnum.DeliveryCar && !dto.carAddress) {
|
if (delivery.method === DeliveryMethodEnum.DeliveryCar && !dto.carAddress) {
|
||||||
throw new BadRequestException(OrderMessage.CAR_ADDRESS_REQUIRED);
|
throw new BadRequestException(OrderMessage.CAR_ADDRESS_REQUIRED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (delivery.method === DeliveryMethodEnum.DeliveryCourier && !dto.userId) {
|
||||||
|
throw new BadRequestException(OrderMessage.USER_REQUIRED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user