first
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
import { inject, injectable } from "inversify";
|
||||
import { ClientSession, Types, startSession } from "mongoose";
|
||||
|
||||
import { CartRepository, CartShipItemRepo } from "./cart.repository";
|
||||
import { AddToCartDTO } from "./DTO/addToCart.dto";
|
||||
import { CartDTO } from "./DTO/cart.dto";
|
||||
import { RemoveFromCartDTO } from "./DTO/removeFromCart.dto";
|
||||
import { UpdateCartDTO } from "./DTO/updateCart.dto";
|
||||
import { CartMessage, ProductMessage } from "../../common/enums/message.enum";
|
||||
import { BadRequestError, InternalError } from "../../core/app/app.errors";
|
||||
import { IOCTYPES } from "../../IOC/ioc.types";
|
||||
import { ICart } from "./models/Abstraction/ICart";
|
||||
import { IProduct } from "../product/models/Abstraction/IProduct";
|
||||
import { ProductRepository } from "../product/Repository/product";
|
||||
import { ProductVariantRepository } from "../product/Repository/productVarinat";
|
||||
|
||||
@injectable()
|
||||
class CartService {
|
||||
@inject(IOCTYPES.CartRepository) cartRepo: CartRepository;
|
||||
@inject(IOCTYPES.CartShipItemRepo) cartShipItemRepo: CartShipItemRepo;
|
||||
@inject(IOCTYPES.ProductRepository) productRepo: ProductRepository;
|
||||
@inject(IOCTYPES.ProductVariantRepository) productVariantRepo: ProductVariantRepository;
|
||||
|
||||
async getCartS(userId: string) {
|
||||
const doc = (await this.cartRepo.getCartWithAggregate(userId))[0] as ICart;
|
||||
const cart = CartDTO.transformCart(doc);
|
||||
const recommended = await this.getRecommendedProducts(doc);
|
||||
return { cart, recommended };
|
||||
}
|
||||
|
||||
async clearCart(userId: string) {
|
||||
const deletedCart = await this.cartRepo.model.findOneAndDelete({ user: userId });
|
||||
if (!deletedCart) throw new BadRequestError(CartMessage.CartNotFound);
|
||||
return {
|
||||
message: CartMessage.ItemDeleted,
|
||||
};
|
||||
}
|
||||
|
||||
async addToCartS(addToCartDto: AddToCartDTO, userId: string) {
|
||||
const session = await startSession();
|
||||
session.startTransaction();
|
||||
try {
|
||||
const { productId, variantId } = addToCartDto;
|
||||
|
||||
// Get or create cart
|
||||
let cart = await this.cartRepo.model.findOne({ user: userId });
|
||||
if (!cart) {
|
||||
cart = (await this.cartRepo.model.create([{ user: userId }], { session }))[0];
|
||||
}
|
||||
|
||||
const existingCartItem = cart.items.find((item) => item.product === productId && item.variant.toString() === variantId);
|
||||
const existingQuantity = existingCartItem ? existingCartItem.quantity : 0;
|
||||
|
||||
await this.validateProduct(productId, variantId, existingQuantity + 1);
|
||||
|
||||
if (existingCartItem) {
|
||||
// Update quantity if item already exists in the cart
|
||||
existingCartItem.quantity += 1;
|
||||
} else {
|
||||
// Add new item to the cart
|
||||
cart.items.push({ product: productId, quantity: 1, variant: new Types.ObjectId(variantId) });
|
||||
}
|
||||
|
||||
await cart.save({ session });
|
||||
|
||||
// Commit the transaction
|
||||
await session.commitTransaction();
|
||||
session.endSession();
|
||||
|
||||
return {
|
||||
message: CartMessage.ItemAdded,
|
||||
cart: await this.getCartS(userId),
|
||||
};
|
||||
} catch (error) {
|
||||
// Rollback any changes made in the transaction
|
||||
await session.abortTransaction();
|
||||
session.endSession();
|
||||
throw error; // Re-throw the error to be handled by the calling errorHandler
|
||||
}
|
||||
}
|
||||
|
||||
async updateCartS(updateCartDto: UpdateCartDTO, userId: string) {
|
||||
const { productId, variantId, quantity } = updateCartDto;
|
||||
|
||||
const cart = await this.cartRepo.model.findOne({ user: userId });
|
||||
if (!cart) throw new BadRequestError(CartMessage.CartNotFound);
|
||||
|
||||
const existItem = cart.items.find((item) => item.product === productId && item.variant.toString() === variantId.toString());
|
||||
if (!existItem) throw new BadRequestError(CartMessage.ItemNotFound);
|
||||
|
||||
// validate the stock
|
||||
await this.validateProduct(productId, variantId, quantity);
|
||||
|
||||
// update the quantity
|
||||
existItem.quantity = quantity;
|
||||
await cart.save();
|
||||
|
||||
return {
|
||||
message: CartMessage.CartUpdated,
|
||||
cart: await this.getCartS(userId),
|
||||
};
|
||||
}
|
||||
|
||||
async removeFromCartS(removeFromCartDto: RemoveFromCartDTO, userId: string) {
|
||||
const { productId, variantId } = removeFromCartDto;
|
||||
|
||||
await this.validateProduct(productId, variantId);
|
||||
|
||||
// Find the cart for the user
|
||||
const cart = await this.cartRepo.model.findOne({ user: userId });
|
||||
if (!cart) throw new BadRequestError(CartMessage.CartNotFound);
|
||||
|
||||
// Find the item in the cart
|
||||
const itemIndex = cart.items.findIndex((item) => item.product === productId && item.variant.toString() === variantId.toString());
|
||||
if (itemIndex === -1) throw new BadRequestError(CartMessage.ItemNotFound);
|
||||
|
||||
// Remove the item from the cart
|
||||
cart.items.splice(itemIndex, 1);
|
||||
await cart.save();
|
||||
|
||||
return {
|
||||
message: CartMessage.ItemDeleted,
|
||||
cart: await this.getCartS(userId),
|
||||
};
|
||||
}
|
||||
|
||||
async clearCartAndShipmentItems(userId: string, session: ClientSession) {
|
||||
const cart = await this.cartRepo.model.findOneAndDelete({ user: userId }, { session });
|
||||
|
||||
if (cart) {
|
||||
await this.cartShipItemRepo.model.deleteMany({ cart: cart._id }, { session });
|
||||
} else {
|
||||
throw new InternalError();
|
||||
}
|
||||
}
|
||||
|
||||
async getRecommendedProducts(cart: ICart) {
|
||||
if (!cart) return;
|
||||
const items = cart.items as unknown as { product: IProduct }[];
|
||||
|
||||
const productIds = items.map((item) => item.product._id);
|
||||
const categoryIds = items.map((item) => item.product.category.toString());
|
||||
const tags = items.flatMap((item) => item.product.tags);
|
||||
|
||||
const recommendedProducts = await this.productRepo.getRecommendedProducts(productIds, categoryIds, tags);
|
||||
|
||||
return recommendedProducts;
|
||||
}
|
||||
|
||||
///==============================> helper methods
|
||||
// Helper method to check if the product exists and if the stock is sufficient
|
||||
private async validateProduct(productId: number, variantId: string, totalQuantity?: number) {
|
||||
// Check if product exists
|
||||
const product = await this.productRepo.findById(productId);
|
||||
if (!product) throw new BadRequestError(ProductMessage.ProductNotFound);
|
||||
|
||||
// Check if product variant exists
|
||||
const productVariant = await this.productVariantRepo.findById(variantId);
|
||||
if (!productVariant) throw new BadRequestError(ProductMessage.ProductNotFound);
|
||||
|
||||
if (!product.variants.includes(productVariant._id)) throw new BadRequestError(CartMessage.ProductNotBelongToVariant);
|
||||
if (productVariant.product !== product._id) throw new BadRequestError(CartMessage.ProductNotBelongToVariant);
|
||||
|
||||
if (totalQuantity && totalQuantity > productVariant.price.order_limit) {
|
||||
// Validate order limit
|
||||
throw new BadRequestError(CartMessage.OrderLimitExceeded);
|
||||
}
|
||||
|
||||
if (totalQuantity && productVariant.stock < totalQuantity) {
|
||||
// Validate stock
|
||||
throw new BadRequestError(CartMessage.StockNotEnough);
|
||||
}
|
||||
|
||||
return {
|
||||
product,
|
||||
productVariant,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { CartService };
|
||||
Reference in New Issue
Block a user