cart and coupon
This commit is contained in:
@@ -13,7 +13,10 @@ import { SetDeliveryMethodDto } from '../dto/set-delivery-method.dto';
|
||||
import { UserAddress } from '../../users/entities/user-address.entity';
|
||||
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
||||
import { Delivery } from '../../delivery/entities/delivery.entity';
|
||||
import { Cart, CartItem, CartCoupon } from '../interfaces/cart.interface';
|
||||
import { Cart, CartItem } from '../interfaces/cart.interface';
|
||||
import { CouponService } from 'src/modules/coupons/providers/coupon.service';
|
||||
import { OrderCouponDetail } from 'src/modules/orders/interface/order-status';
|
||||
import { CouponType } from 'src/modules/coupons/entities/coupon.entity';
|
||||
|
||||
@Injectable()
|
||||
export class CartService {
|
||||
@@ -23,6 +26,7 @@ export class CartService {
|
||||
constructor(
|
||||
private readonly em: EntityManager,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly couponService: CouponService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -47,7 +51,7 @@ export class CartService {
|
||||
restaurantId,
|
||||
restaurantName: restaurant.name,
|
||||
items: [],
|
||||
shipmentFee: 0,
|
||||
deliveryFee: 0,
|
||||
subTotal: 0,
|
||||
itemsDiscount: 0,
|
||||
couponDiscount: 0,
|
||||
@@ -66,13 +70,14 @@ export class CartService {
|
||||
/**
|
||||
* Find cart by user and restaurant
|
||||
*/
|
||||
async findOne(userId: string, restaurantId: string): Promise<Cart> {
|
||||
async findOneOrFail(userId: string, restaurantId: string): Promise<Cart> {
|
||||
const cart = await this.getCartByRestaurant(userId, restaurantId);
|
||||
if (!cart) {
|
||||
throw new NotFoundException('Cart not found');
|
||||
}
|
||||
return cart;
|
||||
}
|
||||
|
||||
async findOne2(userId: string, restaurantId: string): Promise<Cart | null> {
|
||||
const cart = await this.getCartByRestaurant(userId, restaurantId);
|
||||
return cart;
|
||||
@@ -162,7 +167,7 @@ export class CartService {
|
||||
* Decrement item quantity in cart by 1 (removes item if quantity reaches 0)
|
||||
*/
|
||||
async decrementItem(userId: string, restaurantId: string, foodId: string): Promise<Cart> {
|
||||
const cart = await this.findOne(userId, restaurantId);
|
||||
const cart = await this.findOneOrFail(userId, restaurantId);
|
||||
|
||||
const itemIndex = cart.items.findIndex(item => item.foodId === foodId);
|
||||
if (itemIndex < 0) {
|
||||
@@ -287,7 +292,7 @@ export class CartService {
|
||||
foodId: string,
|
||||
updateItemDto: UpdateItemQuantityDto,
|
||||
): Promise<Cart> {
|
||||
const cart = await this.findOne(userId, restaurantId);
|
||||
const cart = await this.findOneOrFail(userId, restaurantId);
|
||||
|
||||
const itemIndex = cart.items.findIndex(item => item.foodId === foodId);
|
||||
if (itemIndex < 0) {
|
||||
@@ -331,7 +336,7 @@ export class CartService {
|
||||
* Remove item from cart
|
||||
*/
|
||||
async removeItem(userId: string, restaurantId: string, foodId: string): Promise<Cart> {
|
||||
const cart = await this.findOne(userId, restaurantId);
|
||||
const cart = await this.findOneOrFail(userId, restaurantId);
|
||||
|
||||
const itemIndex = cart.items.findIndex(item => item.foodId === foodId);
|
||||
if (itemIndex < 0) {
|
||||
@@ -352,20 +357,60 @@ export class CartService {
|
||||
* Apply coupon to cart
|
||||
*/
|
||||
async applyCoupon(userId: string, restaurantId: string, applyCouponDto: ApplyCouponDto): Promise<Cart> {
|
||||
const cart = await this.findOne(userId, restaurantId);
|
||||
const cart = await this.findOneOrFail(userId, restaurantId);
|
||||
|
||||
// TODO: Implement actual coupon validation logic
|
||||
// For now, this is a placeholder implementation
|
||||
// You should validate the coupon code against your coupon/discount system
|
||||
// Calculate current order amount (subtotal after item discounts) for coupon validation
|
||||
// const currentSubTotal = cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
||||
// const currentItemsDiscount = cart.items.reduce((sum, item) => sum + item.discount * item.quantity, 0);
|
||||
// const orderAmount = currentSubTotal - currentItemsDiscount;
|
||||
|
||||
// Example coupon structure (replace with your actual coupon validation)
|
||||
const coupon: CartCoupon = {
|
||||
code: applyCouponDto.code,
|
||||
discount: 10, // Example: 10% discount
|
||||
discountType: 'percentage', // or 'amount'
|
||||
const orderAmount = cart.subTotal - cart.itemsDiscount;
|
||||
|
||||
const { valid, coupon } = await this.couponService.validateCoupon(
|
||||
applyCouponDto.code,
|
||||
restaurantId,
|
||||
Number(orderAmount),
|
||||
);
|
||||
|
||||
if (!valid) {
|
||||
throw new BadRequestException('Invalid coupon code');
|
||||
}
|
||||
|
||||
if (!coupon) {
|
||||
throw new BadRequestException('Coupon not found');
|
||||
}
|
||||
|
||||
// Check maxUsesPerUser limit by counting how many orders this user has with this coupon
|
||||
if (coupon.maxUsesPerUser) {
|
||||
const result = await this.em.getConnection().execute<{ count: string }>(
|
||||
`SELECT COUNT(*) as count
|
||||
FROM orders
|
||||
WHERE user_id = $1
|
||||
AND coupon_detail IS NOT NULL
|
||||
AND coupon_detail->>'couponId' = $2
|
||||
AND deleted_at IS NULL`,
|
||||
[userId, coupon.id],
|
||||
);
|
||||
|
||||
const userCouponUsageCount = parseInt((result[0] as { count: string })?.count || '0', 10);
|
||||
|
||||
if (userCouponUsageCount >= coupon.maxUsesPerUser) {
|
||||
throw new BadRequestException(
|
||||
`You have reached the maximum number of uses (${coupon.maxUsesPerUser}) for this coupon`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const couponDetail: OrderCouponDetail = {
|
||||
couponId: coupon.id,
|
||||
couponName: coupon.name,
|
||||
couponCode: coupon.code,
|
||||
type: coupon.type,
|
||||
value: coupon.value,
|
||||
maxDiscount: coupon.maxDiscount,
|
||||
};
|
||||
|
||||
cart.coupon = coupon;
|
||||
cart.coupon = couponDetail;
|
||||
|
||||
// Recalculate cart totals
|
||||
await this.recalculateCartTotals(cart);
|
||||
@@ -378,7 +423,7 @@ export class CartService {
|
||||
* Remove coupon from cart
|
||||
*/
|
||||
async removeCoupon(userId: string, restaurantId: string): Promise<Cart> {
|
||||
const cart = await this.findOne(userId, restaurantId);
|
||||
const cart = await this.findOneOrFail(userId, restaurantId);
|
||||
|
||||
cart.coupon = undefined;
|
||||
|
||||
@@ -393,7 +438,7 @@ export class CartService {
|
||||
* Set address for cart
|
||||
*/
|
||||
async setAddress(userId: string, restaurantId: string, setAddressDto: SetAddressDto): Promise<Cart> {
|
||||
const cart = await this.findOne(userId, restaurantId);
|
||||
const cart = await this.findOneOrFail(userId, restaurantId);
|
||||
|
||||
// Find and validate address belongs to user
|
||||
const address = await this.em.findOne(UserAddress, { id: setAddressDto.addressId }, { populate: ['user'] });
|
||||
@@ -422,7 +467,7 @@ export class CartService {
|
||||
restaurantId: string,
|
||||
setPaymentMethodDto: SetPaymentMethodDto,
|
||||
): Promise<Cart> {
|
||||
const cart = await this.findOne(userId, restaurantId);
|
||||
const cart = await this.findOneOrFail(userId, restaurantId);
|
||||
|
||||
// Find and validate payment method belongs to restaurant
|
||||
const paymentMethod = await this.em.findOne(
|
||||
@@ -462,7 +507,7 @@ export class CartService {
|
||||
restaurantId: string,
|
||||
setDeliveryMethodDto: SetDeliveryMethodDto,
|
||||
): Promise<Cart> {
|
||||
const cart = await this.findOne(userId, restaurantId);
|
||||
const cart = await this.findOneOrFail(userId, restaurantId);
|
||||
|
||||
// Find and validate delivery method belongs to restaurant
|
||||
const deliveryMethod = await this.em.findOne(
|
||||
@@ -487,7 +532,7 @@ export class CartService {
|
||||
|
||||
cart.deliveryMethodId = setDeliveryMethodDto.deliveryMethodId;
|
||||
|
||||
// Recalculate cart totals (this will update shipmentFee based on delivery method)
|
||||
// Recalculate cart totals (this will update deliveryFee based on delivery method)
|
||||
await this.recalculateCartTotals(cart);
|
||||
await this.saveCart(cart);
|
||||
|
||||
@@ -515,14 +560,19 @@ export class CartService {
|
||||
|
||||
// Calculate coupon discount
|
||||
let couponDiscount = 0;
|
||||
|
||||
if (cart.coupon) {
|
||||
if (cart.coupon.discountType === 'percentage') {
|
||||
const priceAfterItemDiscount = subTotal - itemsDiscount;
|
||||
couponDiscount = (priceAfterItemDiscount * cart.coupon.discount) / 100;
|
||||
const priceAfterItemDiscount = subTotal - itemsDiscount;
|
||||
if (cart.coupon.type === CouponType.PERCENTAGE) {
|
||||
let discount = (priceAfterItemDiscount * cart.coupon.value) / 100;
|
||||
// Apply maxDiscount limit if set
|
||||
if (cart.coupon.maxDiscount && discount > cart.coupon.maxDiscount) {
|
||||
discount = cart.coupon.maxDiscount;
|
||||
}
|
||||
couponDiscount = discount;
|
||||
} else {
|
||||
// amount discount
|
||||
const priceAfterItemDiscount = subTotal - itemsDiscount;
|
||||
couponDiscount = Math.min(cart.coupon.discount, priceAfterItemDiscount);
|
||||
// FIXED amount discount
|
||||
couponDiscount = Math.min(cart.coupon.value, priceAfterItemDiscount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,17 +592,17 @@ export class CartService {
|
||||
cart.tax = tax;
|
||||
|
||||
// Shipment fee is calculated separately via delivery method
|
||||
let shipmentFee = 0;
|
||||
let deliveryFee = 0;
|
||||
if (cart.deliveryMethodId) {
|
||||
const deliveryMethod = await this.em.findOne(Delivery, { id: cart.deliveryMethodId });
|
||||
if (deliveryMethod && deliveryMethod.enabled) {
|
||||
shipmentFee = Number(deliveryMethod.deliveryFee) || 0;
|
||||
deliveryFee = Number(deliveryMethod.deliveryFee) || 0;
|
||||
}
|
||||
}
|
||||
cart.shipmentFee = shipmentFee;
|
||||
cart.deliveryFee = deliveryFee;
|
||||
|
||||
// Calculate total: total = subtotal – totalDiscount + tax + shipmentFee
|
||||
cart.total = subTotal - cart.totalDiscount + tax + cart.shipmentFee;
|
||||
// Calculate total: total = subtotal – totalDiscount + tax + deliveryFee
|
||||
cart.total = subTotal - cart.totalDiscount + tax + cart.deliveryFee;
|
||||
cart.totalItems = totalItems;
|
||||
cart.updatedAt = new Date().toISOString();
|
||||
}
|
||||
@@ -574,7 +624,7 @@ export class CartService {
|
||||
typeof cart.couponDiscount === 'number' &&
|
||||
typeof cart.totalDiscount === 'number' &&
|
||||
typeof cart.tax === 'number' &&
|
||||
typeof cart.shipmentFee === 'number' &&
|
||||
typeof cart.deliveryFee === 'number' &&
|
||||
typeof cart.total === 'number' &&
|
||||
typeof cart.totalItems === 'number' &&
|
||||
typeof cart.createdAt === 'string' &&
|
||||
|
||||
Reference in New Issue
Block a user