cart refactored
This commit is contained in:
@@ -1,37 +1,40 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import { Product } from 'src/modules/products/entities/product.entity';
|
||||
import { Shop } from 'src/modules/shops/entities/shop.entity';
|
||||
import { UserAddress } from 'src/modules/users/entities/user-address.entity';
|
||||
import { User } from 'src/modules/users/entities/user.entity';
|
||||
import { PaymentMethod } from 'src/modules/payments/entities/payment-method.entity';
|
||||
import { Delivery } from 'src/modules/delivery/entities/delivery.entity';
|
||||
import { DeliveryMethodEnum } from 'src/modules/delivery/interface/delivery';
|
||||
import { PointTransaction } from 'src/modules/users/entities/point-transaction.entity';
|
||||
import { Order } from 'src/modules/orders/entities/order.entity';
|
||||
import { OrderStatus } from 'src/modules/orders/interface/order.interface';
|
||||
import { Cart } from '../interfaces/cart.interface';
|
||||
import { GeographicUtils } from '../utils/geographic.utils';
|
||||
import { CartMessage } from 'src/common/enums/message.enum';
|
||||
import { WalletTransactionRepository } from 'src/modules/users/repositories/wallet-transaction.repository';
|
||||
import { ProductService } from 'src/modules/products/providers/product.service';
|
||||
import { ShopService } from 'src/modules/shops/providers/shops.service';
|
||||
import { DeliveryService } from 'src/modules/delivery/providers/delivery.service';
|
||||
import { PaymentsService } from 'src/modules/payments/services/payments.service';
|
||||
import { PaymentMethodService } from 'src/modules/payments/services/payment-method.service';
|
||||
|
||||
@Injectable()
|
||||
export class CartValidationService {
|
||||
constructor(
|
||||
private readonly em: EntityManager,
|
||||
private readonly walletTransactionRepository: WalletTransactionRepository,
|
||||
private readonly productService: ProductService,
|
||||
private readonly shopService: ShopService,
|
||||
private readonly deliveryService: DeliveryService,
|
||||
private readonly paymentMethodService: PaymentMethodService,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Validate product exists and belongs to shop
|
||||
*/
|
||||
async validateAndGetFood(foodId: string, restaurantId: string, quantity: number): Promise<Product> {
|
||||
const product = await this.em.findOne(Product, { id: foodId }, { populate: ['shop'] });
|
||||
if (!product) {
|
||||
throw new NotFoundException(CartMessage.PRODUCT_NOT_FOUND);
|
||||
}
|
||||
async validateAndGetFood(productId: string, shopId: string, quantity: number): Promise<Product> {
|
||||
const product = await this.productService.findOrFail(productId)
|
||||
|
||||
if (product.shop.id !== restaurantId) {
|
||||
if (product.shop.id !== shopId) {
|
||||
throw new BadRequestException(CartMessage.PRODUCT_NOT_BELONGS_TO_SHOP);
|
||||
}
|
||||
|
||||
@@ -39,50 +42,6 @@ export class CartValidationService {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get user or throw if not found
|
||||
*/
|
||||
async getUserOrFail(userId: string): Promise<User> {
|
||||
const user = await this.em.findOne(User, { id: userId });
|
||||
if (!user) {
|
||||
throw new NotFoundException(CartMessage.USER_NOT_FOUND);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user address or throw if not found
|
||||
*/
|
||||
async getUserAddressOrFail(addressId: string): Promise<UserAddress> {
|
||||
const address = await this.em.findOne(UserAddress, { id: addressId }, { populate: ['user'] });
|
||||
if (!address) {
|
||||
throw new NotFoundException(CartMessage.ADDRESS_NOT_FOUND);
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product or throw if not found
|
||||
*/
|
||||
async getFoodOrFail(foodId: string): Promise<Product> {
|
||||
const product = await this.em.findOne(Product, { id: foodId });
|
||||
if (!product) {
|
||||
throw new NotFoundException(CartMessage.PRODUCT_NOT_FOUND);
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shop or throw if not found
|
||||
*/
|
||||
async getRestaurantOrFail(restaurantId: string): Promise<Shop> {
|
||||
const shop = await this.em.findOne(Shop, { id: restaurantId });
|
||||
if (!shop) {
|
||||
throw new NotFoundException(CartMessage.SHOP_NOT_FOUND);
|
||||
}
|
||||
return shop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate address belongs to user
|
||||
*/
|
||||
@@ -96,7 +55,7 @@ export class CartValidationService {
|
||||
* Assert address is inside shop service area
|
||||
*/
|
||||
async assertAddressInsideServiceArea(
|
||||
restaurantId: string,
|
||||
shopId: string,
|
||||
latitude?: number | null,
|
||||
longitude?: number | null,
|
||||
): Promise<void> {
|
||||
@@ -104,7 +63,7 @@ export class CartValidationService {
|
||||
throw new BadRequestException(CartMessage.ADDRESS_NO_COORDINATES);
|
||||
}
|
||||
|
||||
const shop = await this.getRestaurantOrFail(restaurantId);
|
||||
const shop = await this.shopService.findOrFail(shopId);
|
||||
|
||||
const serviceArea = shop.serviceArea;
|
||||
// If no service area is defined, assume service is available everywhere
|
||||
@@ -130,34 +89,13 @@ export class CartValidationService {
|
||||
/**
|
||||
* Get delivery method for shop or throw if not found
|
||||
*/
|
||||
async getDeliveryMethodForRestaurantOrFail(
|
||||
restaurantId: string,
|
||||
deliveryMethodId: string,
|
||||
): Promise<Delivery> {
|
||||
const deliveryMethod = await this.em.findOne(Delivery, {
|
||||
id: deliveryMethodId,
|
||||
shop: { id: restaurantId },
|
||||
});
|
||||
|
||||
if (!deliveryMethod) {
|
||||
throw new NotFoundException(CartMessage.DELIVERY_METHOD_NOT_FOUND_FOR_SHOP);
|
||||
}
|
||||
|
||||
return deliveryMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enabled delivery method or throw if not found or disabled
|
||||
*/
|
||||
async getEnabledDeliveryMethodOrFail(restaurantId: string, deliveryMethodId: string): Promise<Delivery> {
|
||||
const deliveryMethod = await this.em.findOne(
|
||||
Delivery,
|
||||
{ id: deliveryMethodId, shop: { id: restaurantId } },
|
||||
{ populate: ['shop'] },
|
||||
);
|
||||
if (!deliveryMethod) {
|
||||
throw new NotFoundException(CartMessage.DELIVERY_METHOD_NOT_FOUND);
|
||||
}
|
||||
async getEnabledDeliveryMethodOrFail(shopId: string, deliveryMethodId: string): Promise<Delivery> {
|
||||
const deliveryMethod = await this.deliveryService.findOrFail(shopId, deliveryMethodId)
|
||||
if (!deliveryMethod.enabled) {
|
||||
throw new BadRequestException(CartMessage.DELIVERY_METHOD_NOT_ENABLED);
|
||||
}
|
||||
@@ -186,14 +124,10 @@ export class CartValidationService {
|
||||
/**
|
||||
* Get enabled payment method or throw if not found or disabled
|
||||
*/
|
||||
async getEnabledPaymentMethodOrFail(restaurantId: string, paymentMethodId: string): Promise<PaymentMethod> {
|
||||
const paymentMethod = await this.em.findOne(
|
||||
PaymentMethod,
|
||||
{ id: paymentMethodId, shop: { id: restaurantId } },
|
||||
{ populate: ['shop'] },
|
||||
);
|
||||
if (!paymentMethod) {
|
||||
throw new NotFoundException(CartMessage.PAYMENT_METHOD_NOT_FOUND);
|
||||
async getEnabledPaymentMethodOrFail(shopId: string, paymentMethodId: string): Promise<PaymentMethod> {
|
||||
const paymentMethod = await this.paymentMethodService.findOneOrFail(paymentMethodId)
|
||||
if (paymentMethod.shop.id !== shopId) {
|
||||
throw new BadRequestException('CartMessage.PAYMENT_METHOD_NOT_BELONGS_TO_SHOP');
|
||||
}
|
||||
if (!paymentMethod.enabled) {
|
||||
throw new BadRequestException(CartMessage.PAYMENT_METHOD_NOT_ENABLED);
|
||||
@@ -204,8 +138,8 @@ export class CartValidationService {
|
||||
/**
|
||||
* Assert wallet has enough balance
|
||||
*/
|
||||
async assertWalletHasEnoughBalance(userId: string, restaurantId: string, amount: number): Promise<void> {
|
||||
const balance = await this.walletTransactionRepository.getCurrentWalletBalance(userId, restaurantId);
|
||||
async assertWalletHasEnoughBalance(userId: string, shopId: string, amount: number): Promise<void> {
|
||||
const balance = await this.walletTransactionRepository.getCurrentWalletBalance(userId, shopId);
|
||||
|
||||
if (balance < amount) {
|
||||
throw new BadRequestException(CartMessage.WALLET_INSUFFICIENT);
|
||||
@@ -247,7 +181,7 @@ export class CartValidationService {
|
||||
const foodMap = await this.getFoodsInCartWithCategories(cart);
|
||||
|
||||
for (const item of cart.items) {
|
||||
const product = foodMap.get(item.foodId);
|
||||
const product = foodMap.get(item.productId);
|
||||
if (!product) continue;
|
||||
|
||||
const matchesCategory = hasCategoryRestriction && product.category && categoryRestriction.includes(product.category.id);
|
||||
@@ -262,19 +196,14 @@ export class CartValidationService {
|
||||
* Get products in cart with categories
|
||||
*/
|
||||
async getFoodsInCartWithCategories(cart: Cart): Promise<Map<string, Product>> {
|
||||
const foodIds = cart.items.map(item => item.foodId);
|
||||
if (foodIds.length === 0) return new Map();
|
||||
const productIds = cart.items.map(item => item.productId);
|
||||
if (productIds.length === 0) return new Map();
|
||||
|
||||
const products = await this.em.find(Product, { id: { $in: foodIds } }, { populate: ['category'] });
|
||||
const products = await this.em.find(Product, { id: { $in: productIds } }, { populate: ['category'] });
|
||||
return new Map(products.map(f => [f.id, f]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate stock for a product
|
||||
*/
|
||||
validateStock(product: Product, quantity: number): void {
|
||||
// TODO: Implement stock validation logic
|
||||
// For now, assume unlimited stock
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user