This commit is contained in:
mahyargdz
2025-09-14 15:15:03 +03:30
commit be82059172
534 changed files with 51310 additions and 0 deletions
+181
View File
@@ -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 };