remove inventory

This commit is contained in:
2026-02-08 10:49:16 +03:30
parent 9a7010dcea
commit 8aac87add1
76 changed files with 125 additions and 639 deletions
-2
View File
@@ -22,7 +22,6 @@ import { NotificationsModule } from './modules/notifications/notifications.modul
import { EventEmitterModule } from '@nestjs/event-emitter';
import { PagerModule } from './modules/pager/pager.module';
import { ContactModule } from './modules/contact/contact.module';
import { InventoryModule } from './modules/inventory/inventory.module';
import { IconsModule } from './modules/icons/icons.module';
import { CacheModule } from '@nestjs/cache-manager';
import { cacheConfig } from './config/cache.config';
@@ -57,7 +56,6 @@ import { cacheConfig } from './config/cache.config';
EventEmitterModule.forRoot(),
PagerModule,
ContactModule,
InventoryModule,
IconsModule,
],
controllers: [],
-7
View File
@@ -753,13 +753,6 @@ export const enum DeliveryMessage {
DELIVERY_METHOD_NOT_FOUND = 'روش ارسال یافت نشد',
}
export const enum InventoryMessage {
AVAILABLE_STOCK_EXCEEDS_TOTAL = 'موجودی موجود نمی‌تواند از موجودی کل بیشتر باشد',
PRODUCT_NOT_FOUND = 'محصول یافت نشد',
PRODUCT_NOT_BELONGS_TO_SHOP = 'محصول به این فروشگاه تعلق ندارد',
SHOP_NOT_FOUND = 'فروشگاه یافت نشد',
PRODUCTS_NOT_FOUND = 'محصولات یافت نشدند',
}
export const enum PagerMessage {
+1 -1
View File
@@ -9,7 +9,7 @@ import { Permission } from '../roles/entities/permission.entity';
import { RolePermission } from '../roles/entities/rolePermission.entity';
import { AdminController } from './controllers/admin.controller';
import { UtilsModule } from '../utils/utils.module';
import { RestaurantsModule } from ../../..shops.module';
import { ShopsModule } from '../../../shops/shops.module';
import { AdminRole } from './entities/adminRole.entity';
@Module({
@@ -1,7 +1,7 @@
import { Entity, Index, ManyToOne, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Role } from '../../../roles/entities/role.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from '../../../shops/entities/shop.entity';
import { Admin } from './admin.entity';
@Entity({ tableName: 'admin_roles' })
+1 -1
View File
@@ -3,7 +3,7 @@ import { InjectRepository } from '@mikro-orm/nestjs';
import { EntityRepository } from '@mikro-orm/core';
import { Admin } from '../entities/admin.entity';
import { Role } from '../../../roles/entities/role.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { EntityManager } from '@mikro-orm/postgresql';
import { CacheService } from '../../../utils/cache.service';
import { CreateMyRestaurantAdminDto } from '../dto/create-my-shop-admin.dto';
@@ -1,7 +1,7 @@
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
import { Admin } from '../entities/admin.entity';
import { AdminRole } from '../entities/adminRole.entity';
import { RestRepository } from ../../..shops/repositories/rest.repository';
import { RestRepository } from ../../../shops/repositories/rest.repository';
import { Injectable } from '@nestjs/common';
import { normalizePhone } from '../../../utils/phone.util';
+1 -1
View File
@@ -7,7 +7,7 @@ import { JwtModule } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { AdminModule } from '../admin/admin.module';
import { TokensService } from './services/tokens.service';
import { RestaurantsModule } from ../../..shops.module';
import { RestaurantsModule } from ../../../shops.module';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { User } from '../users/entities/user.entity';
import { AdminAuthGuard } from './guards/adminAuth.guard';
+1 -1
View File
@@ -5,7 +5,7 @@ import { SmsService } from '../../../notifications/services/sms.service';
import { UserService } from '../../../users/providers/user.service';
import { randomInt } from 'crypto';
import { TokensService } from './tokens.service';
import { RestRepository } from ../../..shops/repositories/rest.repository';
import { RestRepository } from ../../../shops/repositories/rest.repository';
import { AuthMessage, RestMessage } from 'src/common/enums/message.enum';
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
import { AdminLoginTransformer } from '../transformers/admin-login.transformer';
+1 -1
View File
@@ -9,7 +9,7 @@ import dayjs from 'dayjs';
import { AuthMessage } from '../../../../common/enums/message.enum';
import { RefreshToken, RefreshTokenType } from '../entities/refresh-token.entity';
import { IAdminTokenPayload, ITokenPayload } from '../interfaces/IToken-payload';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
@Injectable()
export class TokensService {
@@ -1,5 +1,5 @@
import type { Admin } from '../../../admin/entities/admin.entity';
import type { Shop } from ../../..shops/entities/shop.entity';
import type { Shop } from ../../../shops/entities/shop.entity';
export interface AdminLoginResponse {
id: string;
@@ -1,5 +1,5 @@
import type { User } from '../../../users/entities/user.entity';
import type { Shop } from ../../..shops/entities/shop.entity';
import type { Shop } from ../../../shops/entities/shop.entity';
export interface UserLoginResponse {
id: string;
@@ -1,5 +1,5 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { Product } from ../../..products/entities/product.entity';
import { Product } from ../../../products/entities/product.entity';
import { Cart, CartItem } from '../interfaces/cart.interface';
import { CartValidationService } from './cart-validation.service';
import { CartCalculationService } from './cart-calculation.service';
@@ -1,7 +1,7 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { Product } from ../../..products/entities/product.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Product } from ../../../products/entities/product.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { UserAddress } from '../../../users/entities/user-address.entity';
import { User } from '../../../users/entities/user.entity';
import { PaymentMethod } from '../../../payments/entities/payment-method.entity';
@@ -23,10 +23,10 @@ export class CartValidationService {
) { }
/**
* Validate product exists, belongs to shop, has inventory, and check stock
* 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', 'inventory'] });
const product = await this.em.findOne(Product, { id: foodId }, { populate: ['shop'] });
if (!product) {
throw new NotFoundException(CartMessage.FOOD_NOT_FOUND);
}
@@ -35,23 +35,9 @@ export class CartValidationService {
throw new BadRequestException(CartMessage.FOOD_NOT_BELONGS_TO_RESTAURANT);
}
if (!product.inventory) {
throw new BadRequestException(CartMessage.FOOD_NO_INVENTORY);
}
this.validateStock(product, quantity);
return product;
}
/**
* Validate stock availability for product
*/
validateStock(product: Product, quantity: number): void {
const availableStock = product.inventory!.availableStock;
if (availableStock < quantity) {
throw new BadRequestException(CartMessage.INSUFFICIENT_STOCK + product.title);
}
}
/**
* Get user or throw if not found
@@ -1,7 +1,7 @@
import { Entity, Enum, ManyToOne, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { ContactScope, ContactStatusEnum } from '../interface/interface';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
@Entity({ tableName: 'contacts' })
export class Contact extends BaseEntity {
@@ -8,7 +8,7 @@ import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { ContactStatusEnum } from '../interface/interface';
import { EntityManager } from '@mikro-orm/postgresql';
import { ContactScope } from '../interface/interface';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
@Injectable()
export class ContactService {
+1 -1
View File
@@ -4,7 +4,7 @@ import { CouponController } from './controllers/coupon.controller';
import { CouponRepository } from './repositories/coupon.repository';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Coupon } from './entities/coupon.entity';
import { RestaurantsModule } from ../../..shops.module';
import { RestaurantsModule } from ../../../shops.module';
import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt';
@@ -1,6 +1,6 @@
import { Entity, Index, ManyToOne, Property, Enum, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { normalizePhone } from '../../../utils/phone.util';
import { CouponType } from '../interface/coupon';
@@ -3,7 +3,7 @@ import { CreateCouponDto } from '../dto/create-coupon.dto';
import { UpdateCouponDto } from '../dto/update-coupon.dto';
import { FindCouponsDto } from '../dto/find-coupons.dto';
import { CouponRepository } from '../repositories/coupon.repository';
import { RestRepository } from ../../..shops/repositories/rest.repository';
import { RestRepository } from ../../../shops/repositories/rest.repository';
import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core';
import { Coupon } from '../entities/coupon.entity';
+1 -1
View File
@@ -3,7 +3,7 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
import { JwtModule } from '@nestjs/jwt';
import { DeliveryController } from './controllers/delivery.controller';
import { DeliveryService } from './providers/delivery.service';
import { RestaurantsModule } from ../../..shops.module';
import { RestaurantsModule } from ../../../shops.module';
import { Delivery } from './entities/delivery.entity';
import { DeliveryRepository } from './repositories/delivery.repository';
@@ -4,7 +4,7 @@ import { Delivery } from '../entities/delivery.entity';
import { DeliveryRepository } from '../repositories/delivery.repository';
import { CreateDeliveryDto } from '../dto/create-delivery.dto';
import { UpdateDeliveryDto } from '../dto/update-delivery.dto';
import { RestRepository } from ../../..shops/repositories/rest.repository';
import { RestRepository } from ../../../shops/repositories/rest.repository';
import { DeliveryMethodEnum } from '../interface/delivery';
import { DeliveryMessage } from 'src/common/enums/message.enum';
@@ -3,7 +3,6 @@ import { Category } from './category.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Shop } from '../../../modules/shops/entities/shop.entity';
import { Review } from 'src/modules/review/entities/review.entity';
import { Inventory } from 'src/modules/inventory/entities/inventory.entity';
import { Favorite } from './favorite.entity';
@Entity({ tableName: 'products' })
@@ -20,11 +19,6 @@ export class Product extends BaseEntity {
@OneToMany(() => Review, review => review.product, { cascade: [Cascade.ALL], orphanRemoval: true })
reviews = new Collection<Review>(this);
@OneToOne(() => Inventory, {
mappedBy: 'product',
nullable: true,
})
inventory?: Inventory;
@OneToMany(() => Favorite, favorite => favorite.product)
favorites = new Collection<Favorite>(this);
@@ -1,34 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsArray, ValidateNested, ArrayMinSize, IsString, IsNumber, Min } from 'class-validator';
import { Type } from 'class-transformer';
export class BulkReserveFoodItemDto {
@ApiProperty({ example: 'product-123', description: 'Product ID' })
@IsNotEmpty()
@IsString()
foodId!: string;
@ApiProperty({ example: 5, description: 'Quantity to reserve' })
@IsNotEmpty()
@IsNumber()
@Min(1)
@Type(() => Number)
quantity!: number;
}
export class BulkReserveFoodDto {
@ApiProperty({
description: 'Array of product reservations to create',
type: [BulkReserveFoodItemDto],
example: [
{ foodId: 'product-123', quantity: 5 },
{ foodId: 'product-789', quantity: 3 },
],
})
@IsNotEmpty()
@IsArray()
@ArrayMinSize(1, { message: 'At least one item is required' })
@ValidateNested({ each: true })
@Type(() => BulkReserveFoodItemDto)
items!: BulkReserveFoodItemDto[];
}
@@ -1,27 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsArray, ValidateNested, ArrayMinSize } from 'class-validator';
import { Type } from 'class-transformer';
import { SetStockDto } from './set-stock.dto';
export class BulkSetStockItemDto extends SetStockDto {
@ApiProperty({ example: 'product-123', description: 'Product ID' })
@IsNotEmpty()
foodId!: string;
}
export class BulkSetStockDto {
@ApiProperty({
description: 'Array of stock items to set',
type: [BulkSetStockItemDto],
example: [
{ foodId: 'product-123', totalStock: 100, availableStock: 80 },
{ foodId: 'product-456', totalStock: 50, availableStock: 45 },
],
})
@IsNotEmpty()
@IsArray()
@ArrayMinSize(1, { message: 'At least one item is required' })
@ValidateNested({ each: true })
@Type(() => BulkSetStockItemDto)
items!: BulkSetStockItemDto[];
}
@@ -1 +0,0 @@
export class CreateInventoryDto {}
@@ -1,17 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, Min } from 'class-validator';
import { Type } from 'class-transformer';
export class SetStockDto {
@ApiProperty({ example: 100, description: 'Total stock quantity' })
@IsNumber()
@Min(0)
@Type(() => Number)
totalStock!: number;
@ApiProperty({ example: 80, description: 'Available stock quantity' })
@IsNumber()
@Min(0)
@Type(() => Number)
availableStock!: number;
}
@@ -1,18 +0,0 @@
import { Cascade, Entity, Property, OneToOne } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Product } from '../../../products/entities/product.entity';
@Entity({ tableName: 'inventory' })
export class Inventory extends BaseEntity {
@OneToOne(() => Product, {
cascade: [Cascade.ALL],
orphanRemoval: true,
})
product!: Product;
@Property({ type: 'int' })
totalStock!: number;
@Property({ type: 'int' })
availableStock!: number;
}
@@ -1,6 +0,0 @@
export enum ReservationStatus {
ACTIVE = 'active',
CONFIRMED = 'confirmed',
}
@@ -1,38 +0,0 @@
import { Controller, Body, Patch, Param, Post, UseGuards } from '@nestjs/common';
import { InventoryService } from './inventory.service';
import { SetStockDto } from './dto/set-stock.dto';
import { BulkSetStockDto } from './dto/bulk-set-stock.dto';
import { ApiTags, ApiOperation, ApiBody, ApiParam, ApiBearerAuth } from '@nestjs/swagger';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import { RestId } from 'src/common/decorators';
import { Inventory } from './entities/inventory.entity';
import { Permissions } from 'src/common/decorators/permissions.decorator';
import { Permission } from 'src/common/enums/permission.enum';
@ApiTags('inventory')
@UseGuards(AdminAuthGuard)
@ApiBearerAuth()
@Permissions(Permission.MANAGE_FOODS)
@Controller()
export class InventoryController {
constructor(private readonly inventoryService: InventoryService) { }
@Patch('admin/inventory/product/:foodId/stock')
@ApiOperation({ summary: 'Set available and total stock for a product item' })
@ApiParam({ name: 'foodId', description: 'Product ID' })
@ApiBody({ type: SetStockDto })
setStockForFood(
@Param('foodId') foodId: string,
@RestId() restaurantId: string,
@Body() setStockDto: SetStockDto,
): Promise<Inventory> {
return this.inventoryService.setStockForFood(foodId, restaurantId, setStockDto);
}
@Post('admin/inventory/products/stock/bulk')
@ApiOperation({ summary: 'Bulk set available and total stock for multiple product items' })
@ApiBody({ type: BulkSetStockDto })
bulkSetStockForFoods(@RestId() restaurantId: string, @Body() bulkSetStockDto: BulkSetStockDto): Promise<Inventory[]> {
return this.inventoryService.bulkSetStockForFoods(restaurantId, bulkSetStockDto);
}
}
-15
View File
@@ -1,15 +0,0 @@
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { InventoryService } from './inventory.service';
import { InventoryController } from './inventory.controller';
import { Inventory } from './entities/inventory.entity';
import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [MikroOrmModule.forFeature([Inventory]), AuthModule, JwtModule],
controllers: [InventoryController],
providers: [InventoryService],
exports: [InventoryService],
})
export class InventoryModule {}
-227
View File
@@ -1,227 +0,0 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { SetStockDto } from './dto/set-stock.dto';
import { BulkSetStockDto } from './dto/bulk-set-stock.dto';
import { BulkReserveFoodDto } from './dto/bulk-reserve-product.dto';
import { Inventory } from './entities/inventory.entity';
import { Product } from ../../..products/entities/product.entity';
import { Shop } from ../../..shops/entities/shop.entity';
@Injectable()
export class InventoryService {
constructor(private readonly em: EntityManager) {}
async setStockForFood(foodId: string, restaurantId: string, setStockDto: SetStockDto): Promise<Inventory> {
// Validate that availableStock doesn't exceed totalStock
if (setStockDto.availableStock > setStockDto.totalStock) {
throw new BadRequestException('Available stock cannot exceed total stock');
}
// Find product and verify it belongs to the shop
const product = await this.em.findOne(Product, { id: foodId }, { populate: ['shop'] });
if (!product) {
throw new NotFoundException(`Product with ID ${foodId} not found`);
}
if (product.shop.id !== restaurantId) {
throw new BadRequestException(`Product does not belong to shop ${restaurantId}`);
}
// Find or create inventory record
let inventory = await this.em.findOne(Inventory, {
product: { id: foodId, shop: { id: restaurantId } },
});
if (!inventory) {
// Create new inventory record
const shop = await this.em.findOne(Shop, { id: restaurantId });
if (!shop) {
throw new NotFoundException(`Shop with ID ${restaurantId} not found`);
}
inventory = this.em.create(Inventory, {
product,
totalStock: setStockDto.totalStock,
availableStock: setStockDto.availableStock,
});
} else {
// Update existing inventory record
inventory.totalStock = setStockDto.totalStock;
inventory.availableStock = setStockDto.availableStock;
}
await this.em.flush();
return inventory;
}
async bulkSetStockForFoods(restaurantId: string, bulkSetStockDto: BulkSetStockDto): Promise<Inventory[]> {
const { items } = bulkSetStockDto;
// Validate all items first
for (const item of items) {
if (item.availableStock > item.totalStock) {
throw new BadRequestException(`Available stock cannot exceed total stock for product ${item.foodId}`);
}
}
// Get all product IDs
const foodIds = items.map(item => item.foodId);
// Load all products in one query
const products = await this.em.find(Product, { id: { $in: foodIds } }, { populate: ['shop'] });
// Verify all products exist and belong to the shop
const foodMap = new Map<string, Product>();
for (const product of products) {
if (product.shop.id !== restaurantId) {
throw new BadRequestException(`Product ${product.id} does not belong to shop ${restaurantId}`);
}
foodMap.set(product.id, product);
}
// Check for missing products
const missingFoodIds = foodIds.filter(id => !foodMap.has(id));
if (missingFoodIds.length > 0) {
throw new NotFoundException(`Products not found: ${missingFoodIds.join(', ')}`);
}
// Load all existing inventories in one query
const existingInventories = await this.em.find(Inventory, {
product: { id: { $in: foodIds }, shop: { id: restaurantId } },
});
const inventoryMap = new Map<string, Inventory>();
for (const inventory of existingInventories) {
inventoryMap.set(inventory.product.id, inventory);
}
// Get shop once
const shop = await this.em.findOne(Shop, { id: restaurantId });
if (!shop) {
throw new NotFoundException(`Shop with ID ${restaurantId} not found`);
}
// Process all items
const results: Inventory[] = [];
for (const item of items) {
const product = foodMap.get(item.foodId)!;
let inventory = inventoryMap.get(item.foodId);
if (!inventory) {
// Create new inventory record
inventory = this.em.create(Inventory, {
product,
totalStock: item.totalStock,
availableStock: item.availableStock,
});
} else {
// Update existing inventory record
inventory.totalStock = item.totalStock;
inventory.availableStock = item.availableStock;
}
results.push(inventory);
}
// Flush all changes at once
await this.em.flush();
return results;
}
async deductFromInventory(em: EntityManager, bulkReserveFoodDto: BulkReserveFoodDto): Promise<Inventory[]> {
return em.transactional(async em => {
const { items } = bulkReserveFoodDto;
// Get all unique product IDs
const foodIds = [...new Set(items.map(item => item.foodId))];
// Load all products in one query
const products = await em.find(Product, { id: { $in: foodIds } });
// Verify all products exist and belong to the shop
const foodMap = new Map<string, Product>();
for (const product of products) {
foodMap.set(product.id, product);
}
// Check for missing products
const missingFoodIds = foodIds.filter(id => !foodMap.has(id));
if (missingFoodIds.length > 0) {
throw new NotFoundException(`Products not found: ${missingFoodIds.join(', ')}`);
}
// Load all existing inventories in one query
const existingInventories = await em.find(Inventory, {
product: { id: { $in: foodIds } },
});
const inventoryMap = new Map<string, Inventory>();
for (const inventory of existingInventories) {
inventoryMap.set(inventory.product.id, inventory);
}
// Validate stock availability and create reservations
const inventories: Inventory[] = [];
for (const item of items) {
const inventory = inventoryMap.get(item.foodId);
// Check if inventory exists
if (!inventory) {
throw new NotFoundException(`Inventory not found for product ${item.foodId}`);
}
// Check if available stock is sufficient
if (inventory.availableStock < item.quantity) {
throw new BadRequestException(
`Insufficient stock for product ${item.foodId}. Available: ${inventory.availableStock}, Requested: ${item.quantity}`,
);
}
inventory.availableStock -= item.quantity;
inventories.push(inventory);
em.persist(inventory);
}
return inventories;
});
}
async restoreToInventory(em: EntityManager, bulkRestoreDto: BulkReserveFoodDto): Promise<Inventory[]> {
return em.transactional(async em => {
const { items } = bulkRestoreDto;
const foodIds = [...new Set(items.map(item => item.foodId))];
const products = await em.find(Product, { id: { $in: foodIds } });
const foodMap = new Map<string, Product>();
for (const product of products) {
foodMap.set(product.id, product);
}
const missingFoodIds = foodIds.filter(id => !foodMap.has(id));
if (missingFoodIds.length > 0) {
throw new NotFoundException(`Products not found: ${missingFoodIds.join(', ')}`);
}
const existingInventories = await em.find(Inventory, {
product: { id: { $in: foodIds } },
});
const inventoryMap = new Map<string, Inventory>();
for (const inventory of existingInventories) {
inventoryMap.set(inventory.product.id, inventory);
}
const inventories: Inventory[] = [];
for (const item of items) {
const inventory = inventoryMap.get(item.foodId);
if (!inventory) {
throw new NotFoundException(`Inventory not found for product ${item.foodId}`);
}
inventory.availableStock += item.quantity;
inventories.push(inventory);
em.persist(inventory);
}
return inventories;
});
}
}
@@ -1,6 +1,6 @@
import { Entity, Property, ManyToOne, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { NotifTitleEnum } from '../interfaces/notification.interface';
import { NotifChannelEnum } from '../interfaces/notification.interface';
@@ -1,6 +1,6 @@
import { Entity, Property, ManyToOne, Enum } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { User } from '../../../users/entities/user.entity';
import { NotifTitleEnum } from '../interfaces/notification.interface';
import { Admin } from 'src/modules/admin/entities/admin.entity';
@@ -1,5 +1,5 @@
import { Entity, Property, ManyToOne, PrimaryKey } from '@mikro-orm/core';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
@Entity({ tableName: 'sms_logs' })
export class SmsLog {
@@ -1,7 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { SmsSentEvent } from '../events/sms.events';
import { RestRepository } from ../../..shops/repositories/rest.repository';
import { RestRepository } from ../../../shops/repositories/rest.repository';
import { EntityManager } from '@mikro-orm/postgresql';
import { SmsLog } from '../entities/smsLogs.entity';
@@ -12,7 +12,7 @@ import { SmsProcessor } from './processors/sms.processor';
import { PushProcessor } from './processors/push.processor';
import { NotificationsController } from './controllers/notifications.controller';
import { User } from '../users/entities/user.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { AuthModule } from '../auth/auth.module';
import { ConfigService } from '@nestjs/config';
import { NotificationQueueNameEnum } from './constants/queue';
@@ -26,7 +26,7 @@ import { NotificationCrone } from './crone/notification.crone';
import { SmsService } from './services/sms.service';
import { SmsLog } from './entities/smsLogs.entity';
import { SmsLogRepository } from './repositories/sms-log.repository';
import { RestaurantsModule } from ../../..shops.module';
import { RestaurantsModule } from ../../../shops.module';
import { SmsListeners } from './listeners/sms.listeners';
@Module({
@@ -1,7 +1,7 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { NotificationPreference } from '../entities/notification-preference.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { CreatePreferenceDto } from '../dto/create-preference.dto';
import { UpdatePreferenceDto } from '../dto/update-preference.dto';
import { NotifTitleEnum } from '../interfaces/notification.interface';
+1 -13
View File
@@ -4,7 +4,6 @@ import { EntityManager } from '@mikro-orm/postgresql';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Payment } from '../../../payments/entities/payment.entity';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../../payments/interface/payment';
import { InventoryService } from '../../../inventory/inventory.service';
import { OrderStatus } from '../interface/order.interface';
import { Order } from '../entities/order.entity';
import { OrderStatusChangedEvent } from '../events/order.events';
@@ -15,7 +14,6 @@ export class OrdersCrone {
constructor(
private readonly em: EntityManager,
private readonly inventoryService: InventoryService,
private readonly eventEmitter: EventEmitter2,
) { }
@@ -63,23 +61,13 @@ export class OrdersCrone {
if (payment.order) {
payment.order.status = OrderStatus.CANCELED;
// prepare restore payload
const items = (payment.order as any).items || [];
const restorePayload = {
items: items.map((it: any) => ({ foodId: it.product.id, quantity: it.quantity })),
};
if (restorePayload.items.length > 0) {
await this.inventoryService.restoreToInventory(em, restorePayload);
}
}
em.persist(payment);
if (payment.order) em.persist(payment.order);
await em.flush();
this.logger.log(
`Marked payment ${payment.id} and order ${payment.order?.id} as failed and restored inventory`,
`Marked payment ${payment.id} and order ${payment.order?.id} as failed`,
);
});
} catch (err) {
@@ -1,7 +1,7 @@
import { Entity, Index, ManyToOne, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Order } from './order.entity';
import { Product } from ../../..products/entities/product.entity';
import { Product } from ../../../products/entities/product.entity';
@Entity({ tableName: 'order_items' })
@Index({ properties: ['order'] })
+1 -1
View File
@@ -14,7 +14,7 @@ import {
import { BaseEntity } from '../../../../common/entities/base.entity';
import { OrderCouponDetail, OrderStatus } from '../interface/order.interface';
import { User } from '../../../users/entities/user.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { PaymentMethod } from '../../../payments/entities/payment-method.entity';
import { OrderItem } from './order-item.entity';
import { Delivery } from '../../../delivery/entities/delivery.entity';
+2 -4
View File
@@ -5,8 +5,8 @@ import { OrdersController } from './controllers/orders.controller';
import { Order } from './entities/order.entity';
import { OrderItem } from './entities/order-item.entity';
import { User } from '../users/entities/user.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Product } from ../../..products/entities/product.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { Product } from ../../../products/entities/product.entity';
import { UserAddress } from '../users/entities/user-address.entity';
import { PaymentMethod } from '../payments/entities/payment-method.entity';
import { CartModule } from '../cart/cart.module';
@@ -19,7 +19,6 @@ import { OrderListeners } from './listeners/order.listeners';
import { OrdersCrone } from './crone/order.crone';
import { AdminModule } from '../admin/admin.module';
import { NotificationsModule } from '../notifications/notifications.module';
import { InventoryModule } from '../inventory/inventory.module';
import { UserModule } from '../users/user.module';
@Module({
@@ -32,7 +31,6 @@ import { UserModule } from '../users/user.module';
JwtModule,
AdminModule,
NotificationsModule,
InventoryModule,
forwardRef(() => UserModule)
],
controllers: [OrdersController],
+3 -22
View File
@@ -3,8 +3,8 @@ import { EntityManager } from '@mikro-orm/postgresql';
import { Order } from '../entities/order.entity';
import { OrderItem } from '../entities/order-item.entity';
import { User } from '../../../users/entities/user.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Product } from ../../..products/entities/product.entity';
import { Shop } from '../../../shops/entities/shop.entity';
import { Product } from '../../../products/entities/product.entity';
import { CartService } from '../../../cart/providers/cart.service';
import { OrderStatus, OrderUserAddress, OrderCarAddress } from '../interface/order.interface';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../../payments/interface/payment';
@@ -17,8 +17,6 @@ import { OrderRepository } from '../repositories/order.repository';
import { FindOrdersDto } from '../dto/find-orders.dto';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { Payment } from 'src/modules/payments/entities/payment.entity';
import { InventoryService } from 'src/modules/inventory/inventory.service';
import { BulkReserveFoodDto } from 'src/modules/inventory/dto/bulk-reserve-product.dto';
import { StatusTransitionRef } from '../interface/order.interface';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { OrderCreatedEvent, OrderStatusChangedEvent } from '../events/order.events';
@@ -55,7 +53,6 @@ export class OrdersService {
private readonly cartService: CartService,
private readonly orderRepository: OrderRepository,
private readonly paymentsService: PaymentsService,
private readonly inventoryService: InventoryService,
private readonly eventEmitter: EventEmitter2,
) { }
@@ -116,14 +113,6 @@ export class OrdersService {
});
em.persist(payment);
// reserve stock based on payment method.
const bulkReserveFoodDto: BulkReserveFoodDto = {
items: validated.orderItemsData.map(item => ({
foodId: item.product.id,
quantity: item.quantity,
})),
};
await this.inventoryService.deductFromInventory(em, bulkReserveFoodDto);
await em.flush();
this.logger.debug(`Order ${order.id} created for user ${userId} (shop ${restaurantId})`);
return order;
@@ -426,15 +415,13 @@ export class OrdersService {
const orderItemsData: OrderItemData[] = [];
for (const cartItem of cart.items) {
const product = await this.em.findOne(Product, { id: cartItem.foodId }, { populate: ['shop', 'inventory'] });
const product = await this.em.findOne(Product, { id: cartItem.foodId }, { populate: ['shop'] });
if (!product) throw new NotFoundException(OrderMessage.FOOD_NOT_FOUND);
if (product.shop.id !== restaurantId) {
throw new BadRequestException(OrderMessage.FOOD_NOT_BELONGS_TO_RESTAURANT);
}
this.assertFoodHasSufficientStock(product, cartItem.quantity);
orderItemsData.push({
product,
quantity: cartItem.quantity,
@@ -446,12 +433,6 @@ export class OrdersService {
return orderItemsData;
}
private assertFoodHasSufficientStock(product: Product, quantity: number) {
const availableStock = product.inventory?.availableStock ?? 0;
if (availableStock < quantity) {
throw new BadRequestException(OrderMessage.FOOD_NOT_FOUND);
}
}
async getStats(restId: string) {
+1 -1
View File
@@ -1,7 +1,7 @@
import { Entity, ManyToOne, Property, Enum } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { User } from '../../../users/entities/user.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { PagerStatus } from '../interface/pager';
import { Admin } from 'src/modules/admin/entities/admin.entity';
+1 -1
View File
@@ -2,7 +2,7 @@ import { BadRequestException, Injectable, NotFoundException, Inject, forwardRef
import { CreatePagerDto } from '../dto/create-pager.dto';
import { User } from '../../../users/entities/user.entity';
import { EntityManager } from '@mikro-orm/postgresql';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { Pager } from '../entities/pager.entity';
import { PagerStatus } from '../interface/pager';
import { ulid } from 'ulid';
@@ -1,6 +1,6 @@
import { Entity, Enum, Index, ManyToOne, Property, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { PaymentGatewayEnum, PaymentMethodEnum } from '../interface/payment';
@Entity({ tableName: 'payment_methods' })
+1 -1
View File
@@ -27,7 +27,7 @@ export interface IPaymentVerifyData {
cardPan?: string;
raw: Record<string, any>;
}
////////////////////////// Zarinpal API v4 //////////////////////////
../../../../../../////// Zarinpal API v../../../../../../////////
export interface IZarinpalRequestPayment {
amount: number;
merchant_id: string;
+2 -3
View File
@@ -4,7 +4,7 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
import { PaymentMethod } from './entities/payment-method.entity';
import { PaymentMethodRepository } from './repositories/payment-method.repository';
import { PaymentMethodService } from './services/payment-method.service';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from '../../../shops/entities/shop.entity';
import { PaymentsController } from './controllers/payments.controller';
import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt';
@@ -12,7 +12,6 @@ import { Payment } from './entities/payment.entity';
import { ZarinpalGateway } from './gateways/zarinpal.gateway';
import { GatewayManager } from './gateways/gateway.manager';
import { PaymentRepository } from './repositories/payment.repository';
import { InventoryModule } from '../inventory/inventory.module';
import { PaymentListeners } from './listeners/payment.listeners';
import { AdminModule } from '../admin/admin.module';
import { NotificationsModule } from '../notifications/notifications.module';
@@ -20,7 +19,7 @@ import { OrdersModule } from '../orders/orders.module';
@Module({
imports: [MikroOrmModule.forFeature([PaymentMethod, Payment, Shop]),
AuthModule, JwtModule, InventoryModule, AdminModule, NotificationsModule,
AuthModule, JwtModule, AdminModule, NotificationsModule,
forwardRef(() => OrdersModule)],
controllers: [PaymentsController],
providers: [PaymentsService, PaymentMethodService, PaymentMethodRepository,
@@ -5,7 +5,7 @@ import { PaymentMethodRepository } from '../repositories/payment-method.reposito
import { CreatePaymentMethodDto } from '../dto/create-payment-method.dto';
import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto';
import { RequiredEntityData } from '@mikro-orm/core';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { RestMessage, PaymentMessage } from 'src/common/enums/message.enum';
@Injectable()
@@ -2,7 +2,7 @@ import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } f
import { FoodService } from '../providers/product.service';
import { CreateFoodDto } from '../dto/create-product.dto';
import { UpdateFoodDto } from '../dto/update-product.dto';
import { FindFoodsDto } from ../../..products.dto';
import { FindFoodsDto } from ../../../products.dto';
import {
ApiTags,
ApiOperation,
+29 -30
View File
@@ -1,7 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { EntityManager } from '@mikro-orm/postgresql';
import { Inventory } from '../../../inventory/entities/inventory.entity';
@Injectable()
export class FoodStockCrone {
@@ -9,37 +8,37 @@ export class FoodStockCrone {
constructor(private readonly em: EntityManager) {}
// run every day at 00:03
@Cron('3 0 * * *', {
name: 'resetAvailableStock',
timeZone: 'UTC',
})
async handleCron() {
try {
this.logger.debug('Starting daily inventory reset (availableStock = totalStock)');
// Inventory cron job disabled - inventory module removed
// @Cron('3 0 * * *', {
// name: 'resetAvailableStock',
// timeZone: 'UTC',
// })
// async handleCron() {
// try {
// this.logger.debug('Starting daily inventory reset (availableStock = totalStock)');
const inventories = await this.em.find(Inventory, {});
if (!inventories || inventories.length === 0) {
this.logger.debug('No inventory records found to reset');
return;
}
// const inventories = await this.em.find(Inventory, {});
// if (!inventories || inventories.length === 0) {
// this.logger.debug('No inventory records found to reset');
// return;
// }
this.logger.log(`Resetting available stock for ${inventories.length} inventory records`);
// this.logger.log(`Resetting available stock for ${inventories.length} inventory records`);
await this.em.transactional(async em => {
for (const inv of inventories) {
// reload inside transaction to avoid concurrency issues
const record = await em.findOne(Inventory, { id: inv.id });
if (!record) continue;
record.availableStock = record.totalStock;
em.persist(record);
}
await em.flush();
});
// await this.em.transactional(async em => {
// for (const inv of inventories) {
// // reload inside transaction to avoid concurrency issues
// const record = await em.findOne(Inventory, { id: inv.id });
// if (!record) continue;
// record.availableStock = record.totalStock;
// em.persist(record);
// }
// await em.flush();
// });
this.logger.log('Daily inventory reset completed');
} catch (err) {
this.logger.error(`FoodStockCrone failed: ${err?.message}`, err);
}
}
// this.logger.log('Daily inventory reset completed');
// } catch (err) {
// this.logger.error(`FoodStockCrone failed: ${err?.message}`, err);
// }
// }
}
@@ -1,7 +1,7 @@
import { Entity, Index, Property, Collection, OneToMany, ManyToOne } from '@mikro-orm/core';
import { Product } from './product.entity';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
@Entity({ tableName: 'categories' })
@Index({ properties: ['shop', 'isActive'] })
@@ -3,7 +3,6 @@ import { Category } from './category.entity';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from '../../../../modules/shops/entities/shop.entity';
import { Review } from 'src/modules/review/entities/review.entity';
import { Inventory } from 'src/modules/inventory/entities/inventory.entity';
import { Favorite } from './favorite.entity';
@Entity({ tableName: 'products' })
@@ -20,11 +19,6 @@ export class Product extends BaseEntity {
@OneToMany(() => Review, review => review.product, { cascade: [Cascade.ALL], orphanRemoval: true })
reviews = new Collection<Review>(this);
@OneToOne(() => Inventory, {
mappedBy: 'product',
nullable: true,
})
inventory?: Inventory;
@OneToMany(() => Favorite, favorite => favorite.product)
favorites = new Collection<Favorite>(this);
@@ -6,7 +6,7 @@ import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core';
import { Category } from '../entities/category.entity';
import { CategoryMessage, RestMessage } from 'src/common/enums/message.enum';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
@Injectable()
export class CategoryService {
@@ -10,7 +10,6 @@ import { CategoryMessage, FoodMessage, RestMessage } from 'src/common/enums/mess
import { RestRepository } from ../../..shops/repositories/rest.repository';
import { CacheService } from '../../../utils/cache.service';
import { Favorite } from '../entities/favorite.entity';
import { Inventory } from 'src/modules/inventory/entities/inventory.entity';
@Injectable()
export class FoodService {
@@ -22,7 +21,7 @@ export class FoodService {
) { }
async create(restId: string, createFoodDto: CreateFoodDto) {
const { categoryId, dailyStock = 0, ...rest } = createFoodDto;
const { categoryId, ...rest } = createFoodDto;
const shop = await this.restRepository.findOne({ id: restId });
if (!shop) {
throw new NotFoundException(RestMessage.NOT_FOUND);
@@ -32,7 +31,7 @@ export class FoodService {
throw new NotFoundException(CategoryMessage.NOT_FOUND);
}
const { product, inventory } = await this.em.transactional(async em => {
const product = await this.em.transactional(async em => {
// prepare data with defaults for required fields so repository.create typing is satisfied
const data: RequiredEntityData<Product> = {
desc: rest.desc,
@@ -51,23 +50,16 @@ export class FoodService {
};
const product = em.create(Product, data);
const newInventoryRecord = em.create(Inventory, {
product: product,
availableStock: dailyStock,
totalStock: dailyStock,
});
await em.flush();
return { product, inventory: newInventoryRecord };
return product;
});
// Re-load created entities with the primary EM to ensure they're attached
const savedFood = product?.id
? await this.foodRepository.findOne({ id: product.id }, { populate: ['category', 'shop'] })
: null;
const savedInventory = inventory?.id ? await this.em.findOne(Inventory, { id: inventory.id }) : inventory;
return { product: savedFood ?? product, inventory: savedInventory ?? inventory };
return savedFood ?? product;
}
findAll(restId: string, dto: FindFoodsDto) {
@@ -78,7 +70,7 @@ export class FoodService {
* Public product detail (only active products are visible).
*/
async findPublicById(foodId: string, userId?: string): Promise<any> {
const product = await this.foodRepository.findOne({ id: foodId, isActive: true }, { populate: ['category', 'inventory'] });
const product = await this.foodRepository.findOne({ id: foodId, isActive: true }, { populate: ['category'] });
if (!product) throw new NotFoundException(FoodMessage.NOT_FOUND);
let isFavorite = false;
if (userId) {
@@ -94,7 +86,7 @@ export class FoodService {
* Admin product detail (scoped to the authenticated shop).
*/
async findAdminById(restId: string, id: string): Promise<Product> {
const product = await this.foodRepository.findOne({ id, shop: { id: restId } }, { populate: ['category', 'inventory'] });
const product = await this.foodRepository.findOne({ id, shop: { id: restId } }, { populate: ['category'] });
if (!product) throw new NotFoundException(FoodMessage.NOT_FOUND);
return product;
@@ -123,7 +115,7 @@ export class FoodService {
}
async update(restId: string, id: string, dto: Partial<CreateFoodDto>): Promise<Product> {
const { categoryId, dailyStock, ...rest } = dto;
const { categoryId, ...rest } = dto;
const product = await this.foodRepository.findOne({ id, shop: { id: restId } }, { populate: ['shop'] });
if (!product) {
throw new NotFoundException(FoodMessage.NOT_FOUND);
@@ -143,25 +135,7 @@ export class FoodService {
// assign other fields from DTO (excluding dailyStock handled below)
this.em.assign(product, rest);
// Persist product and update/create related inventory atomically
await this.em.transactional(async em => {
await em.persistAndFlush(product);
if (typeof dailyStock !== 'undefined') {
const inventoryRecord = await em.findOne(Inventory, { product: product });
if (inventoryRecord) {
inventoryRecord.totalStock = dailyStock;
} else {
em.create(Inventory, {
product: product,
availableStock: dailyStock,
totalStock: dailyStock,
});
}
}
await em.flush();
});
await this.em.persistAndFlush(product);
// Re-load the product to ensure populated relations and stable instance
const savedFood = await this.foodRepository.findOne({ id: product.id }, { populate: ['category', 'shop'] });
@@ -48,7 +48,7 @@ export class FoodRepository extends EntityRepository<Product> {
limit,
offset,
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
populate: ['category','inventory'],
populate: ['category'],
});
const totalPages = Math.ceil(total / limit);
@@ -1,7 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { EntityManager } from '@mikro-orm/postgresql';
import { FoodRepository } from ../../..products/repositories/product.repository';
import { FoodRepository } from ../../../products/repositories/product.repository';
import { ReviewRepository } from '../repositories/review.repository';
import { ReviewStatus } from '../enums/review-status.enum';
@@ -27,7 +27,7 @@ export class FoodRatingCronService {
this.logger.log('Starting daily product rating calculation cron job...');
try {
// Get all products from ../../..shops
// Get all products from ../../../shops
const products = await this.foodRepository.find({});
this.logger.log(`Found ${products.length} products to process`);
@@ -3,7 +3,7 @@ import { CreateReviewDto } from '../dto/create-review.dto';
import { UpdateReviewDto } from '../dto/update-review.dto';
import { FindRestuarantReviewsDto, FindReviewsDto } from '../dto/find-reviews.dto';
import { ReviewRepository } from '../repositories/review.repository';
import { FoodRepository } from ../../..products/repositories/product.repository';
import { FoodRepository } from ../../../products/repositories/product.repository';
import { UserRepository } from '../../../users/repositories/user.repository';
import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core';
@@ -11,7 +11,7 @@ import { Review } from '../entities/review.entity';
import { FoodMessage, UserMessage, ReviewMessage } from 'src/common/enums/message.enum';
import { Order } from '../../../orders/entities/order.entity';
import { OrderItem } from '../../../orders/entities/order-item.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { ReviewStatus } from '../enums/review-status.enum';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ReviewCreatedEvent } from '../events/review.events';
+1 -1
View File
@@ -5,7 +5,7 @@ import { ReviewRepository } from './repositories/review.repository';
import { FoodRatingCronService } from './providers/product-rating-cron.service';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Review } from './entities/review.entity';
import { FoodModule } from ../../..products/product.module';
import { FoodModule } from ../../../products/product.module';
import { UserModule } from '../users/user.module';
import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt';
+1 -1
View File
@@ -1,7 +1,7 @@
import { Collection, Entity, Index, ManyToMany, OneToMany, ManyToOne, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Permission } from './permission.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { RolePermission } from './rolePermission.entity';
import { AdminRole } from 'src/modules/admin/entities/adminRole.entity';
+1 -1
View File
@@ -3,7 +3,7 @@ import { InjectRepository } from '@mikro-orm/nestjs';
import { EntityRepository, FilterQuery } from '@mikro-orm/core';
import { Role } from '../entities/role.entity';
import { Permission } from '../entities/permission.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { EntityManager } from '@mikro-orm/postgresql';
import { CreateRoleDto } from '../dto/create-role.dto';
import { UpdateRoleDto } from '../dto/update-role.dto';
@@ -1,9 +1,9 @@
import { Controller, Get, Post, Body, Patch, Param, UseGuards, Delete, Query } from '@nestjs/common';
import { RestaurantsService } from ../../..shops.service';
import { RestaurantsService } from ../../../shops.service';
import { CreateRestaurantDto } from '../dto/create-shop.dto';
import { UpdateRestaurantDto } from '../dto/update-shop.dto';
import { UpgradeSubscriptionDto } from '../dto/upgrade-subscription.dto';
import { FindRestaurantsDto } from ../../..shops.dto';
import { FindRestaurantsDto } from ../../../shops.dto';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import {
ApiBearerAuth,
+1 -1
View File
@@ -240,7 +240,7 @@ export class RestaurantsService {
// Delete coupons
await em.nativeDelete(Coupon, { shop: id });
// Delete products (will cascade to reviews, favorites, and inventory due to cascade settings)
// Delete products (will cascade to reviews and favorites due to cascade settings)
await em.nativeDelete(Product, { shop: id });
// Delete categories (products should be deleted by cascade, but delete explicitly to be safe)
@@ -1,6 +1,6 @@
import { Entity, Property, ManyToOne, Index, Enum } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { User } from './user.entity';
import { PointTransactionReason } from '../interface/point';
import { PointTransactionType } from '../interface/point';
@@ -2,7 +2,7 @@ import { Entity, Index, Property, ManyToOne, Enum } from '@mikro-orm/core';
import { BaseEntity } from '../../../../common/entities/base.entity';
import { User } from './user.entity';
import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
@Entity({ tableName: 'wallet_transactions' })
@Index({ properties: ['user', 'shop'] })
+2 -2
View File
@@ -2,7 +2,7 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/comm
import { FilterQuery, RequiredEntityData } from '@mikro-orm/core';
import { User } from '../entities/user.entity';
import { UserAddress } from '../entities/user-address.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { EntityManager } from '@mikro-orm/postgresql';
import { UpdateUserDto } from '../dto/update-user.dto';
import { FindUsersDto } from '../dto/find-user.dto';
@@ -13,7 +13,7 @@ import { UpdateUserAddressDto } from '../dto/update-user-address.dto';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { UserRepository } from '../repositories/user.repository';
import { normalizePhone } from '../../../utils/phone.util';
import { RestRepository } from ../../..shops/repositories/rest.repository';
import { RestRepository } from ../../../shops/repositories/rest.repository';
import { WalletTransactionRepository } from '../repositories/wallet-transaction.repository';
import { WalletService } from './wallet.service';
import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet';
+1 -1
View File
@@ -6,7 +6,7 @@ import { User } from './entities/user.entity';
import { UserAddress } from './entities/user-address.entity';
import { JwtModule } from '@nestjs/jwt';
import { UserRepository } from './repositories/user.repository';
import { RestaurantsModule } from ../../..shops.module';
import { RestaurantsModule } from ../../../shops.module';
import { WalletTransactionRepository } from './repositories/wallet-transaction.repository';
import { WalletService } from './providers/wallet.service';
import { WalletTransaction } from './entities/wallet-transaction.entity';
+15 -21
View File
@@ -14,12 +14,11 @@ import { UsersSeeder } from './users.seeder';
import { CouponsSeeder } from './coupons.seeder';
import { NotificationPreferencesSeeder } from './notification-preferences.seeder';
import { UserWalletsSeeder } from './user-wallets.seeder';
import { InventorySeeder } from './inventory.seeder';
// import { NotificationsSeeder } from './notifications.seeder';
export class DatabaseSeeder extends Seeder {
async run(em: EntityManager) {
const TOTAL_STEPS = 15;
const TOTAL_STEPS = 14;
console.info('[Seeder] Starting database seed');
// 1. Create Permissions
console.info(`[Seeder] Step 1/${TOTAL_STEPS}: Creating permissions`);
@@ -76,38 +75,33 @@ export class DatabaseSeeder extends Seeder {
await productsSeeder.run(em, shopsMap, categoriesMap);
console.info(`[Seeder] Step 9/${TOTAL_STEPS}: Done`);
// 10. Create Inventory for all Products
console.info(`[Seeder] Step 10/${TOTAL_STEPS}: Creating inventory`);
const inventorySeeder = new InventorySeeder();
await inventorySeeder.run(em);
console.info(`[Seeder] Step 10/${TOTAL_STEPS}: Done`);
// 11. Create Coupons
console.info(`[Seeder] Step 11/${TOTAL_STEPS}: Creating coupons`);
// 10. Create Coupons
console.info(`[Seeder] Step 10/${TOTAL_STEPS}: Creating coupons`);
const couponsSeeder = new CouponsSeeder();
await couponsSeeder.run(em, shopsMap);
console.info(`[Seeder] Step 11/${TOTAL_STEPS}: Done`);
console.info(`[Seeder] Step 10/${TOTAL_STEPS}: Done`);
// 12. Create Admins
console.info(`[Seeder] Step 12/${TOTAL_STEPS}: Creating admins`);
// 11. Create Admins
console.info(`[Seeder] Step 11/${TOTAL_STEPS}: Creating admins`);
const adminsSeeder = new AdminsSeeder();
await adminsSeeder.run(em, rolesMap, shopsMap);
console.info(`[Seeder] Step 12/${TOTAL_STEPS}: Done`);
console.info(`[Seeder] Step 11/${TOTAL_STEPS}: Done`);
// 13. Create Users
console.info(`[Seeder] Step 13/${TOTAL_STEPS}: Creating users`);
// 12. Create Users
console.info(`[Seeder] Step 12/${TOTAL_STEPS}: Creating users`);
const usersSeeder = new UsersSeeder();
await usersSeeder.run(em);
console.info(`[Seeder] Step 13/${TOTAL_STEPS}: Done`);
console.info(`[Seeder] Step 12/${TOTAL_STEPS}: Done`);
// 14. Create User Wallets
console.info(`[Seeder] Step 14/${TOTAL_STEPS}: Creating user wallets`);
// 13. Create User Wallets
console.info(`[Seeder] Step 13/${TOTAL_STEPS}: Creating user wallets`);
const userWalletsSeeder = new UserWalletsSeeder();
await userWalletsSeeder.run(em);
console.info(`[Seeder] Step 14/${TOTAL_STEPS}: Done`);
console.info(`[Seeder] Step 13/${TOTAL_STEPS}: Done`);
// 15. Create Notifications for admins and users for all shops
console.info(`[Seeder] Step 15/${TOTAL_STEPS}: (optional) creating notifications`);
// 14. Create Notifications for admins and users for all shops
console.info(`[Seeder] Step 14/${TOTAL_STEPS}: (optional) creating notifications`);
// const notificationsSeeder = new NotificationsSeeder();
// await notificationsSeeder.run(em);
console.info(`[Seeder] Step 15/${TOTAL_STEPS}: Done`);
+1 -1
View File
@@ -2,7 +2,7 @@ import type { EntityManager } from '@mikro-orm/core';
import { Admin } from '../modules/admin/entities/admin.entity';
import { AdminRole } from '../modules/admin/entities/adminRole.entity';
import type { Role } from '../modules/roles/entities/role.entity';
import type { Shop } from ../../..shops/entities/shop.entity';
import type { Shop } from ../../../shops/entities/shop.entity';
import { adminsData } from './data/admins.data';
import { normalizePhone } from '../modules/utils/phone.util';
+2 -2
View File
@@ -1,6 +1,6 @@
import type { EntityManager } from '@mikro-orm/core';
import { Category } from ../../..products/entities/category.entity';
import type { Shop } from ../../..shops/entities/shop.entity';
import { Category } from ../../../products/entities/category.entity';
import type { Shop } from ../../../shops/entities/shop.entity';
import { categoriesData } from './data/categories.data';
import * as fs from 'fs';
import * as path from 'path';
+1 -1
View File
@@ -1,6 +1,6 @@
import type { EntityManager } from '@mikro-orm/core';
import { Coupon } from '../modules/coupons/entities/coupon.entity';
import type { Shop } from ../../..shops/entities/shop.entity';
import type { Shop } from ../../../shops/entities/shop.entity';
import { couponsData } from './data/coupons.data';
export class CouponsSeeder {
+1 -1
View File
@@ -1,6 +1,6 @@
import type { EntityManager } from '@mikro-orm/core';
import { Delivery } from '../modules/delivery/entities/delivery.entity';
import type { Shop } from ../../..shops/entities/shop.entity';
import type { Shop } from ../../../shops/entities/shop.entity';
import { deliveryMethodsData } from './data/delivery-methods.data';
import { DeliveryFeeTypeEnum } from 'src/modules/delivery/interface/delivery';
-29
View File
@@ -1,29 +0,0 @@
import type { EntityManager } from '@mikro-orm/core';
import { Product } from ../../..products/entities/product.entity';
import { Inventory } from '../modules/inventory/entities/inventory.entity';
export class InventorySeeder {
async run(em: EntityManager): Promise<void> {
const products = await em.find(Product, {});
for (const product of products) {
// Check if inventory already exists for this product
const existingInventory = await em.findOne(Inventory, {
product: { id: product.id },
});
if (!existingInventory) {
const inventory = em.create(Inventory, {
product,
totalStock: 50,
availableStock: 50,
});
em.persist(inventory);
}
}
await em.flush();
}
}
@@ -1,6 +1,6 @@
import type { EntityManager } from '@mikro-orm/core';
import { NotificationPreference } from '../modules/notifications/entities/notification-preference.entity';
import type { Shop } from ../../..shops/entities/shop.entity';
import type { Shop } from ../../../shops/entities/shop.entity';
import { notificationPreferencesData } from './data/notification-preferences.data';
export class NotificationPreferencesSeeder {
+1 -1
View File
@@ -2,7 +2,7 @@ import type { EntityManager } from '@mikro-orm/core';
import { Notification } from '../modules/notifications/entities/notification.entity';
import { Admin } from '../modules/admin/entities/admin.entity';
import { User } from '../modules/users/entities/user.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { NotifTitleEnum } from '../modules/notifications/interfaces/notification.interface';
export class NotificationsSeeder {
+1 -1
View File
@@ -1,6 +1,6 @@
import type { EntityManager } from '@mikro-orm/core';
import { PaymentMethod } from '../modules/payments/entities/payment-method.entity';
import type { Shop } from ../../..shops/entities/shop.entity';
import type { Shop } from ../../../shops/entities/shop.entity';
import { paymentMethodsData } from './data/payment-methods.data';
export class PaymentMethodsSeeder {
+2 -2
View File
@@ -1,6 +1,6 @@
import type { EntityManager } from '@mikro-orm/core';
import { Schedule } from ../../..shops/entities/schedule.entity';
import type { Shop } from ../../..shops/entities/shop.entity';
import { Schedule } from ../../../shops/entities/schedule.entity';
import type { Shop } from ../../../shops/entities/shop.entity';
import { timeSlots } from './data/schedules.data';
export class SchedulesSeeder {
+1 -1
View File
@@ -1,6 +1,6 @@
import type { EntityManager } from '@mikro-orm/core';
import { User } from '../modules/users/entities/user.entity';
import { Shop } from ../../..shops/entities/shop.entity';
import { Shop } from ../../../shops/entities/shop.entity';
import { WalletTransaction } from '../modules/users/entities/wallet-transaction.entity';
import { WalletTransactionReason, WalletTransactionType } from 'src/modules/users/interface/wallet';