add max purchase count
This commit is contained in:
@@ -705,6 +705,7 @@ export const enum CartMessage {
|
||||
PRODUCT_NOT_BELONGS_TO_SHOP = 'محصول به این فروشگاه تعلق ندارد',
|
||||
PRODUCT_NO_INVENTORY = 'محصول موجودی ندارد',
|
||||
INSUFFICIENT_STOCK = 'موجودی کافی نیست',
|
||||
MAX_PURCHASE_EXCEEDED = 'تعداد درخواستی بیش از حداکثر مجاز خرید این محصول است',
|
||||
USER_NOT_FOUND = 'کاربر یافت نشد',
|
||||
ADDRESS_NOT_FOUND = 'آدرس یافت نشد',
|
||||
SHOP_NOT_FOUND = 'فروشگاه یافت نشد',
|
||||
|
||||
@@ -68,6 +68,7 @@ export class CartItemService {
|
||||
shopId,
|
||||
quantityToAdd,
|
||||
existingQuantityInCart,
|
||||
cart,
|
||||
);
|
||||
|
||||
if (index < 0) {
|
||||
|
||||
@@ -29,14 +29,16 @@ export class CartValidationService {
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Validate variant exists, belongs to shop, and has sufficient stock.
|
||||
* Validate variant exists, belongs to shop, has sufficient stock, and respects max purchase per product.
|
||||
* @param existingQuantityInCart - current quantity of this variant already in cart (for add/increment)
|
||||
* @param cart - current cart to compute total product quantity (for max purchase validation)
|
||||
*/
|
||||
async validateAndGetVariant(
|
||||
variantId: string,
|
||||
shopId: string,
|
||||
quantityToAdd: number,
|
||||
existingQuantityInCart: number = 0,
|
||||
cart?: { items: { variantId: string; quantity: number }[] },
|
||||
): Promise<Variant> {
|
||||
const variant = await this.productService.findOrFailVariant(variantId);
|
||||
|
||||
@@ -51,6 +53,20 @@ export class CartValidationService {
|
||||
}
|
||||
}
|
||||
|
||||
const maxPurchase = variant.product.maxPurchaseQuantity;
|
||||
if (maxPurchase != null && maxPurchase > 0) {
|
||||
const productVariantIds = variant.product.variants.getItems().map((v) => v.id);
|
||||
const totalProductQuantityInCart =
|
||||
cart?.items?.reduce(
|
||||
(sum, item) => (productVariantIds.includes(item.variantId) ? sum + item.quantity : sum),
|
||||
0,
|
||||
) ?? 0;
|
||||
const newTotalProductQuantity = totalProductQuantityInCart - existingQuantityInCart + quantityToAdd;
|
||||
if (newTotalProductQuantity > maxPurchase) {
|
||||
throw new BadRequestException(CartMessage.MAX_PURCHASE_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
return variant;
|
||||
}
|
||||
|
||||
|
||||
@@ -361,9 +361,12 @@ export class OrdersService {
|
||||
|
||||
private async buildOrderItemsData(cart: Cart, shopId: string): Promise<OrderItemData[]> {
|
||||
const orderItemsData: OrderItemData[] = [];
|
||||
const productQuantityMap = new Map<string, number>();
|
||||
|
||||
for (const cartItem of cart.items) {
|
||||
const variant = await this.em.findOne(Variant, { id: cartItem.variantId }, { populate: ['product', 'product.shop'] });
|
||||
const variant = await this.em.findOne(Variant, { id: cartItem.variantId }, {
|
||||
populate: ['product', 'product.shop', 'product.variants'],
|
||||
});
|
||||
if (!variant) throw new NotFoundException(OrderMessage.PRODUCT_NOT_FOUND);
|
||||
|
||||
if (variant.product.shop.id !== shopId) {
|
||||
@@ -374,6 +377,17 @@ export class OrdersService {
|
||||
throw new BadRequestException(CartMessage.INSUFFICIENT_STOCK);
|
||||
}
|
||||
|
||||
const maxPurchase = variant.product.maxPurchaseQuantity;
|
||||
if (maxPurchase != null && maxPurchase > 0) {
|
||||
const productId = variant.product.id;
|
||||
const currentProductTotal = productQuantityMap.get(productId) ?? 0;
|
||||
const newProductTotal = currentProductTotal + cartItem.quantity;
|
||||
if (newProductTotal > maxPurchase) {
|
||||
throw new BadRequestException(CartMessage.MAX_PURCHASE_EXCEEDED);
|
||||
}
|
||||
productQuantityMap.set(productId, newProductTotal);
|
||||
}
|
||||
|
||||
orderItemsData.push({
|
||||
variant,
|
||||
quantity: cartItem.quantity,
|
||||
|
||||
@@ -118,4 +118,10 @@ export class CreateProductDto {
|
||||
@ApiPropertyOptional({ example: 1 })
|
||||
order?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Type(() => Number)
|
||||
@ApiPropertyOptional({ description: 'Maximum quantity per order; omit or null for unlimited', example: 5 })
|
||||
maxPurchaseQuantity?: number | null;
|
||||
}
|
||||
|
||||
@@ -56,4 +56,8 @@ export class Product extends BaseEntity {
|
||||
|
||||
@Property({ type: 'decimal', precision: 10, scale: 0, nullable: true })
|
||||
price?: number;
|
||||
|
||||
/** Maximum quantity per order; null means unlimited */
|
||||
@Property({ type: 'int', nullable: true, default: null })
|
||||
maxPurchaseQuantity: number | null = null;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ export class ProductService {
|
||||
discount: rest.discount ?? 0,
|
||||
isSpecialOffer: rest.isSpecialOffer ?? false,
|
||||
order: rest.order ?? null,
|
||||
maxPurchaseQuantity: rest.maxPurchaseQuantity ?? null,
|
||||
// map single-title/content DTO to localized fields
|
||||
title: rest.title,
|
||||
attribute: rest.attribute,
|
||||
@@ -240,10 +241,12 @@ export class ProductService {
|
||||
}
|
||||
|
||||
async findOrFailVariant(variantId: string) {
|
||||
const variant = await this.em.findOne(Variant, { id: variantId }, { populate: ['product', 'product.shop',] });
|
||||
const variant = await this.em.findOne(Variant, { id: variantId }, {
|
||||
populate: ['product', 'product.shop', 'product.variants'],
|
||||
});
|
||||
|
||||
if (!variant) throw new NotFoundException('Variant not found');
|
||||
|
||||
return variant
|
||||
return variant;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user