remove unused modules
This commit is contained in:
@@ -8,7 +8,7 @@ import { Role } from '../roles/entities/role.entity';
|
||||
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 { UtilsModule } from '../util/utils.module';
|
||||
import { RestaurantsModule } from '../restaurants/restaurants.module';
|
||||
import { AdminRole } from './entities/adminRole.entity';
|
||||
|
||||
@@ -23,4 +23,4 @@ import { AdminRole } from './entities/adminRole.entity';
|
||||
],
|
||||
exports: [AdminService, AdminRepository],
|
||||
})
|
||||
export class AdminModule {}
|
||||
export class AdminModule { }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Cascade, Collection, Entity, OneToMany, Property } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { AdminRole } from './adminRole.entity';
|
||||
import { normalizePhone } from '../../utils/phone.util';
|
||||
import { normalizePhone } from '../../util/phone.util';
|
||||
|
||||
@Entity({ tableName: 'admins' })
|
||||
export class Admin extends BaseEntity {
|
||||
|
||||
@@ -5,11 +5,11 @@ import { Admin } from '../entities/admin.entity';
|
||||
import { Role } from '../../roles/entities/role.entity';
|
||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||
import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import { CacheService } from '../../utils/cache.service';
|
||||
import { CacheService } from '../../util/cache.service';
|
||||
import { CreateMyRestaurantAdminDto } from '../dto/create-my-restaurant-admin.dto';
|
||||
import { UpdateAdminDto } from '../dto/update-admin.dto';
|
||||
import { AdminRole } from '../entities/adminRole.entity';
|
||||
import { normalizePhone } from '../../utils/phone.util';
|
||||
import { normalizePhone } from '../../util/phone.util';
|
||||
|
||||
@Injectable()
|
||||
export class AdminService {
|
||||
@@ -20,7 +20,7 @@ export class AdminService {
|
||||
private readonly adminRoleRepository: EntityRepository<AdminRole>,
|
||||
private readonly em: EntityManager,
|
||||
private readonly cacheService: CacheService,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
async findByPhone(phone: string): Promise<Admin | null> {
|
||||
const normalizedPhone = normalizePhone(phone);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Admin } from '../entities/admin.entity';
|
||||
import { AdminRole } from '../entities/adminRole.entity';
|
||||
import { RestRepository } from '../../restaurants/repositories/rest.repository';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { normalizePhone } from '../../utils/phone.util';
|
||||
import { normalizePhone } from '../../util/phone.util';
|
||||
|
||||
@Injectable()
|
||||
export class AdminRepository extends EntityRepository<Admin> {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { AuthService } from './services/auth.service';
|
||||
import { AuthController } from './controllers/auth.controller';
|
||||
import { UtilsModule } from '../utils/utils.module';
|
||||
import { UserModule } from '../users/user.module';
|
||||
import { UtilsModule } from '../util/utils.module';
|
||||
import { UserModule } from '../user/user.module';
|
||||
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 '../restaurants/restaurants.module';
|
||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||
import { User } from '../users/entities/user.entity';
|
||||
import { User } from '../user/entities/user.entity';
|
||||
import { AdminAuthGuard } from './guards/adminAuth.guard';
|
||||
import { RefreshToken } from './entities/refresh-token.entity';
|
||||
import { NotificationsModule } from '../notifications/notifications.module';
|
||||
import { NotificationsModule } from '../notification/notifications.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -36,10 +36,10 @@ import { NotificationsModule } from '../notifications/notifications.module';
|
||||
forwardRef(() => RestaurantsModule),
|
||||
MikroOrmModule.forFeature([User, RefreshToken]),
|
||||
forwardRef(() => NotificationsModule),
|
||||
|
||||
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, TokensService, AdminAuthGuard],
|
||||
exports: [AdminAuthGuard],
|
||||
})
|
||||
export class AuthModule {}
|
||||
export class AuthModule { }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Injectable, BadRequestException } from '@nestjs/common';
|
||||
import { RequestOtpDto } from '../dto/request-otp.dto';
|
||||
import { CacheService } from '../../utils/cache.service';
|
||||
import { SmsService } from '../../notifications/services/sms.service';
|
||||
import { UserService } from '../../users/providers/user.service';
|
||||
import { CacheService } from '../../util/cache.service';
|
||||
import { SmsService } from '../../notification/services/sms.service';
|
||||
import { UserService } from '../../user/providers/user.service';
|
||||
import { randomInt } from 'crypto';
|
||||
import { TokensService } from './tokens.service';
|
||||
import { RestRepository } from 'src/modules/restaurants/repositories/rest.repository';
|
||||
@@ -11,7 +11,7 @@ import { AdminRepository } from 'src/modules/admin/repositories/admin.repository
|
||||
import { AdminLoginTransformer } from '../transformers/admin-login.transformer';
|
||||
import { UserLoginTransformer } from '../transformers/user-login.transformer';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { normalizePhone } from '../../utils/phone.util';
|
||||
import { normalizePhone } from '../../util/phone.util';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { User } from '../../users/entities/user.entity';
|
||||
import type { User } from '../../user/entities/user.entity';
|
||||
import type { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||
|
||||
export interface UserLoginResponse {
|
||||
@@ -6,7 +6,7 @@ export interface UserLoginResponse {
|
||||
firstName: string;
|
||||
lastName?: string;
|
||||
phone: string;
|
||||
|
||||
|
||||
isActive?: boolean;
|
||||
restaurant: {
|
||||
id: string;
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiCreatedResponse,
|
||||
ApiOkResponse,
|
||||
ApiNotFoundResponse,
|
||||
ApiParam,
|
||||
ApiBody,
|
||||
ApiBearerAuth,
|
||||
ApiHeader,
|
||||
} from '@nestjs/swagger';
|
||||
import { DeliveryService } from '../providers/delivery.service';
|
||||
import { CreateDeliveryDto } from '../dto/create-delivery.dto';
|
||||
import { UpdateDeliveryDto } from '../dto/update-delivery.dto';
|
||||
import { RestId } from 'src/common/decorators/rest-id.decorator';
|
||||
import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
|
||||
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
||||
import { Delivery } from '../entities/delivery.entity';
|
||||
import { API_HEADER_SLUG } from 'src/common/constants';
|
||||
import { Permission } from 'src/common/enums/permission.enum';
|
||||
import { Permissions } from 'src/common/decorators/permissions.decorator';
|
||||
|
||||
@ApiTags('Delivery')
|
||||
@Controller()
|
||||
export class DeliveryController {
|
||||
constructor(private readonly deliveryService: DeliveryService) {}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Get('public/delivery-methods/restaurant')
|
||||
@ApiOperation({ summary: 'Get restaurant delivery methods' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
findAllDeliveryMethods(@RestId() restId: string) {
|
||||
return this.deliveryService.findAllForRestaurantId(restId);
|
||||
}
|
||||
|
||||
/*** Admin ***/
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_DELIVERY)
|
||||
@Post('admin/delivery-methods/restaurant')
|
||||
@ApiOperation({ summary: 'Create a delivery method for a restaurant' })
|
||||
@ApiBody({ type: CreateDeliveryDto })
|
||||
create(@Body() createDto: CreateDeliveryDto, @RestId() restId: string) {
|
||||
return this.deliveryService.create(restId, createDto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_DELIVERY)
|
||||
@Get('admin/delivery-methods/restaurant')
|
||||
@ApiOperation({ summary: 'Get the restaurant delivery methods' })
|
||||
findRestaurantDeliveryMethods(@RestId() restId: string) {
|
||||
return this.deliveryService.findAllForRestaurantId(restId);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_DELIVERY)
|
||||
@Get('admin/delivery-methods/restaurant/:deliveryId')
|
||||
@ApiOperation({ summary: 'Get a restaurant delivery method by delivery ID' })
|
||||
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
|
||||
findOne(@Param('deliveryId') deliveryId: string, @RestId() restId: string) {
|
||||
return this.deliveryService.findOne(restId, deliveryId);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_DELIVERY)
|
||||
@Patch('admin/delivery-methods/restaurant/:deliveryId')
|
||||
@ApiOperation({ summary: 'Update a restaurant delivery method' })
|
||||
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
|
||||
@ApiBody({ type: UpdateDeliveryDto })
|
||||
update(@Param('deliveryId') deliveryId: string, @Body() updateDto: UpdateDeliveryDto, @RestId() restId: string) {
|
||||
return this.deliveryService.update(restId, deliveryId, updateDto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_DELIVERY)
|
||||
@Delete('admin/delivery-methods/restaurant/:deliveryId')
|
||||
@ApiOperation({ summary: 'Delete a restaurant delivery method' })
|
||||
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
|
||||
remove(@Param('deliveryId') deliveryId: string, @RestId() restId: string) {
|
||||
return this.deliveryService.remove(restId, deliveryId);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
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 '../restaurants/restaurants.module';
|
||||
import { Delivery } from './entities/delivery.entity';
|
||||
import { DeliveryRepository } from './repositories/delivery.repository';
|
||||
|
||||
@Module({
|
||||
controllers: [DeliveryController],
|
||||
providers: [DeliveryService, DeliveryRepository],
|
||||
exports: [DeliveryService, DeliveryRepository],
|
||||
imports: [MikroOrmModule.forFeature([Delivery]), JwtModule, RestaurantsModule],
|
||||
})
|
||||
export class DeliveryModule {}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { IsNumber, IsBoolean, IsOptional, IsString, Min, IsEnum } from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { DeliveryFeeTypeEnum, DeliveryMethodEnum } from '../interface/delivery';
|
||||
|
||||
export class CreateDeliveryDto {
|
||||
@ApiProperty({ example: 'dineIn', description: 'Delivery method name', enum: DeliveryMethodEnum })
|
||||
@IsEnum(DeliveryMethodEnum)
|
||||
method!: DeliveryMethodEnum;
|
||||
|
||||
@ApiProperty({ example: 5000, description: 'Delivery fee' })
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Type(() => Number)
|
||||
deliveryFee!: number;
|
||||
|
||||
@ApiProperty({ example: 100000, description: 'Minimum order price for free delivery' })
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Type(() => Number)
|
||||
minOrderPrice!: number;
|
||||
|
||||
@ApiPropertyOptional({ example: 'توضیحات روش ارسال', description: 'Delivery method description' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: true, description: 'Is this delivery method enabled?' })
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
enabled?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ example: 0, description: 'Display order for sorting delivery methods' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
order?: number;
|
||||
|
||||
@ApiPropertyOptional({ example: 0, description: 'Kilometer number' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
distanceBasedMinCost?: number;
|
||||
|
||||
@ApiPropertyOptional({ example: 0, description: 'Delivery fee per kilometer' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
perKilometerFee?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: DeliveryFeeTypeEnum.FIXED,
|
||||
description: 'Delivery fee type',
|
||||
enum: DeliveryFeeTypeEnum,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsEnum(DeliveryFeeTypeEnum)
|
||||
deliveryFeeType!: DeliveryFeeTypeEnum;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateDeliveryDto } from './create-delivery.dto';
|
||||
|
||||
export class UpdateDeliveryDto extends PartialType(CreateDeliveryDto) {}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Entity, Property, Enum, ManyToOne } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||
import { DeliveryFeeTypeEnum, DeliveryMethodEnum } from '../interface/delivery';
|
||||
|
||||
@Entity({ tableName: 'deliveries' })
|
||||
export class Delivery extends BaseEntity {
|
||||
@Enum(() => DeliveryMethodEnum)
|
||||
method!: DeliveryMethodEnum;
|
||||
|
||||
@ManyToOne(() => Restaurant)
|
||||
restaurant!: Restaurant;
|
||||
|
||||
@Property({ type: 'decimal', precision: 10, scale: 0, default: 0 })
|
||||
deliveryFee: number = 0;
|
||||
|
||||
@Enum(() => DeliveryFeeTypeEnum)
|
||||
deliveryFeeType: DeliveryFeeTypeEnum = DeliveryFeeTypeEnum.FIXED;
|
||||
|
||||
@Property({ type: 'decimal', precision: 10, scale: 0, default: 0 })
|
||||
perKilometerFee: number | null = null;
|
||||
|
||||
@Property({ type: 'decimal', precision: 10, scale: 0, default: 0 })
|
||||
distanceBasedMinCost: number | null = null;
|
||||
|
||||
@Property({ type: 'decimal', precision: 10, scale: 0, default: 0 })
|
||||
minOrderPrice: number = 0;
|
||||
|
||||
@Property({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Property({ default: true })
|
||||
enabled: boolean = true;
|
||||
|
||||
@Property({ type: 'integer', default: 0 })
|
||||
order: number = 0;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
export enum DeliveryMethodEnum {
|
||||
DineIn = 'dineIn',
|
||||
CustomerPickup = 'customerPickup',
|
||||
DeliveryCar = 'deliveryCar',
|
||||
DeliveryCourier = 'deliveryCourier',
|
||||
}
|
||||
|
||||
export enum DeliveryFeeTypeEnum {
|
||||
FIXED = 'fixed',
|
||||
DISTANCE_BASED = 'distanceBased',
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
|
||||
import { EntityManager, RequiredEntityData } from '@mikro-orm/postgresql';
|
||||
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 '../../restaurants/repositories/rest.repository';
|
||||
import { DeliveryMethodEnum } from '../interface/delivery';
|
||||
import { DeliveryMessage } from 'src/common/enums/message.enum';
|
||||
|
||||
@Injectable()
|
||||
export class DeliveryService {
|
||||
constructor(
|
||||
private readonly deliveryRepository: DeliveryRepository,
|
||||
private readonly restRepository: RestRepository,
|
||||
private readonly em: EntityManager,
|
||||
) {}
|
||||
|
||||
async create(restId: string, createDto: CreateDeliveryDto): Promise<Delivery> {
|
||||
const restaurant = await this.restRepository.findOne({ id: restId });
|
||||
if (!restaurant) {
|
||||
throw new NotFoundException(DeliveryMessage.RESTAURANT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check if the delivery method with the same name already exists for this restaurant
|
||||
const existing = await this.deliveryRepository.findOne({
|
||||
restaurant: { id: restId },
|
||||
method: createDto.method,
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new ConflictException('این روش ارسال قبلاً برای این رستوران ثبت شده است.');
|
||||
}
|
||||
|
||||
const data: RequiredEntityData<Delivery> = {
|
||||
restaurant,
|
||||
method: createDto.method,
|
||||
deliveryFee: createDto.deliveryFee,
|
||||
minOrderPrice: createDto.minOrderPrice,
|
||||
description: createDto.description,
|
||||
enabled: createDto.enabled ?? true,
|
||||
order: createDto.order ?? 0,
|
||||
deliveryFeeType: createDto.deliveryFeeType,
|
||||
perKilometerFee: createDto.perKilometerFee ?? null,
|
||||
distanceBasedMinCost: createDto.distanceBasedMinCost ?? null,
|
||||
};
|
||||
|
||||
const delivery = this.deliveryRepository.create(data);
|
||||
await this.em.persistAndFlush(delivery);
|
||||
return delivery;
|
||||
}
|
||||
|
||||
async findAllForRestaurantId(restId: string): Promise<Delivery[]> {
|
||||
return this.deliveryRepository.find({ restaurant: { id: restId } }, { orderBy: { order: 'asc' } });
|
||||
}
|
||||
|
||||
async findOne(restId: string, deliveryId: string): Promise<Delivery> {
|
||||
const delivery = await this.deliveryRepository.findOne({
|
||||
id: deliveryId,
|
||||
restaurant: { id: restId },
|
||||
});
|
||||
|
||||
if (!delivery) {
|
||||
throw new NotFoundException(DeliveryMessage.DELIVERY_METHOD_NOT_FOUND);
|
||||
}
|
||||
|
||||
return delivery;
|
||||
}
|
||||
|
||||
async update(restId: string, deliveryId: string, updateDto: UpdateDeliveryDto): Promise<Delivery> {
|
||||
const delivery = await this.deliveryRepository.findOne({
|
||||
restaurant: { id: restId },
|
||||
id: deliveryId,
|
||||
});
|
||||
|
||||
if (!delivery) {
|
||||
throw new NotFoundException(DeliveryMessage.DELIVERY_METHOD_NOT_FOUND);
|
||||
}
|
||||
|
||||
// If method is being updated, check for conflicts
|
||||
if (updateDto.method !== undefined && updateDto.method !== delivery.method) {
|
||||
const existing = await this.deliveryRepository.findOne({
|
||||
restaurant: { id: restId },
|
||||
method: updateDto.method,
|
||||
id: { $ne: deliveryId },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new ConflictException('این روش ارسال قبلاً برای این رستوران ثبت شده است.');
|
||||
}
|
||||
}
|
||||
|
||||
this.em.assign(delivery, updateDto);
|
||||
await this.em.persistAndFlush(delivery);
|
||||
|
||||
return delivery;
|
||||
}
|
||||
|
||||
async remove(restId: string, deliveryId: string): Promise<void> {
|
||||
const delivery = await this.deliveryRepository.findOne({
|
||||
restaurant: { id: restId },
|
||||
id: deliveryId,
|
||||
});
|
||||
|
||||
if (!delivery) {
|
||||
throw new NotFoundException(DeliveryMessage.DELIVERY_METHOD_NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.em.removeAndFlush(delivery);
|
||||
}
|
||||
|
||||
findAll(): DeliveryMethodEnum[] {
|
||||
return Object.values(DeliveryMethodEnum);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
||||
import { Delivery } from '../entities/delivery.entity';
|
||||
|
||||
@Injectable()
|
||||
export class DeliveryRepository extends EntityRepository<Delivery> {
|
||||
constructor(readonly em: EntityManager) {
|
||||
super(em, Delivery);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } from '@nestjs/common';
|
||||
import { FoodService } from '../providers/food.service';
|
||||
import { CreateFoodDto } from '../dto/create-food.dto';
|
||||
import { UpdateFoodDto } from '../dto/update-food.dto';
|
||||
import { FindFoodsDto } from '../dto/find-foods.dto';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiCreatedResponse,
|
||||
ApiOkResponse,
|
||||
ApiNotFoundResponse,
|
||||
ApiQuery,
|
||||
ApiBody,
|
||||
ApiParam,
|
||||
ApiBearerAuth,
|
||||
ApiHeader,
|
||||
} from '@nestjs/swagger';
|
||||
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
||||
import { RestId, UserId } from 'src/common/decorators';
|
||||
import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
|
||||
import { OptionalAuthGuard } from 'src/modules/auth/guards/optinalAuth.guard';
|
||||
import { API_HEADER_SLUG } from 'src/common/constants';
|
||||
import { Permission } from 'src/common/enums/permission.enum';
|
||||
import { Permissions } from 'src/common/decorators/permissions.decorator';
|
||||
|
||||
@ApiTags('foods')
|
||||
@Controller()
|
||||
export class FoodController {
|
||||
constructor(private readonly foodsService: FoodService) { }
|
||||
|
||||
@Get('public/foods/restaurant/:slug')
|
||||
@ApiOperation({ summary: 'Get all foods by restaurant slug' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiParam({ name: 'slug', required: true, description: 'Restaurant Slug' })
|
||||
findAllByRestaurant(@Param('slug') slug: string) {
|
||||
return this.foodsService.findByWeekDateAndMealType(slug);
|
||||
}
|
||||
|
||||
@Get('public/foods/:foodId')
|
||||
@UseGuards(OptionalAuthGuard)
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Get a food by id' })
|
||||
@ApiParam({ name: 'foodId', required: true })
|
||||
findPublicFoodById(@Param('foodId') foodId: string, @UserId() userId?: string): Promise<any> {
|
||||
const a = this.foodsService.findPublicById(foodId, userId);
|
||||
return a;
|
||||
}
|
||||
|
||||
@Post('public/foods/favorite/:foodId')
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'toggle a food as favorite' })
|
||||
@ApiParam({ name: 'foodId', required: true })
|
||||
setAsFavorute(@Param('foodId') foodId: string, @UserId() userId: string) {
|
||||
return this.foodsService.toggleFavorite(userId, foodId);
|
||||
}
|
||||
|
||||
@Get('public/foods/favorite')
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'get my favorites' })
|
||||
getMyFavorites(@UserId() userId: string, @RestId() restId: string) {
|
||||
return this.foodsService.getMyFavorites(userId, restId);
|
||||
}
|
||||
|
||||
/* ---------------------------------- Admin ---------------------------------- */
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_FOODS)
|
||||
@Post('admin/foods')
|
||||
@ApiOperation({ summary: 'Create a new food' })
|
||||
@ApiBody({ type: CreateFoodDto })
|
||||
create(@Body() createFoodDto: CreateFoodDto, @RestId() restId: string) {
|
||||
return this.foodsService.create(restId, createFoodDto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_FOODS)
|
||||
@Get('admin/foods')
|
||||
@ApiOperation({ summary: 'Get a paginated list of foods' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'limit', required: false, type: Number })
|
||||
@ApiQuery({ name: 'search', required: false, type: String })
|
||||
@ApiQuery({ name: 'orderBy', required: false, type: String })
|
||||
@ApiQuery({ name: 'order', required: false, enum: ['asc', 'desc'] })
|
||||
@ApiQuery({ name: 'categoryId', required: false, type: String })
|
||||
@ApiQuery({ name: 'isActive', required: false, type: Boolean })
|
||||
async findAll(@Query() dto: FindFoodsDto, @RestId() restId: string) {
|
||||
const result = await this.foodsService.findAll(restId, dto);
|
||||
return result;
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_FOODS)
|
||||
@Get('admin/foods/:id')
|
||||
@ApiOperation({ summary: 'Get a food by id' })
|
||||
@ApiParam({ name: 'id', required: true })
|
||||
findById(@Param('id') id: string, @RestId() restId: string) {
|
||||
return this.foodsService.findAdminById(restId, id);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_FOODS)
|
||||
@Patch('admin/foods/:id')
|
||||
@ApiOperation({ summary: 'Update a food' })
|
||||
@ApiParam({ name: 'id', required: true })
|
||||
@ApiBody({ type: UpdateFoodDto })
|
||||
update(@Param('id') id: string, @Body() updateFoodDto: UpdateFoodDto, @RestId() restId: string) {
|
||||
return this.foodsService.update(restId, id, updateFoodDto);
|
||||
}
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_FOODS)
|
||||
@Delete('admin/foods/:id')
|
||||
@ApiOperation({ summary: 'Delete (soft) a food' })
|
||||
@ApiParam({ name: 'id', required: true })
|
||||
remove(@Param('id') id: string, @RestId() restId: string) {
|
||||
return this.foodsService.remove(restId, id);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateFoodDto } from './create-food.dto';
|
||||
|
||||
export class UpdateFoodDto extends PartialType(CreateFoodDto) {}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { Entity, Property, ManyToOne, Enum } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { NotifTitleEnum } from '../interfaces/notification.interface';
|
||||
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
||||
|
||||
+3
-3
@@ -11,17 +11,17 @@ import { PushNotificationService } from './services/push-notification.service';
|
||||
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 { User } from '../user/entities/user.entity';
|
||||
import { Restaurant } from '../restaurants/entities/restaurant.entity';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { NotificationQueueNameEnum } from './constants/queue';
|
||||
import { UtilsModule } from '../utils/utils.module';
|
||||
import { UtilsModule } from '../util/utils.module';
|
||||
import { NotificationsGateway } from './notifications.gateway';
|
||||
import { WsAdminAuthGuard } from './guards/ws-admin-auth.guard';
|
||||
import { InAppProcessor } from './processors/in-app.processor';
|
||||
import { AdminModule } from '../admin/admin.module';
|
||||
import { UserModule } from '../users/user.module';
|
||||
import { UserModule } from '../user/user.module';
|
||||
import { NotificationCrone } from './crone/notification.crone';
|
||||
import { SmsService } from './services/sms.service';
|
||||
import { SmsLog } from './entities/smsLogs.entity';
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Job } from 'bullmq';
|
||||
import { UserRepository } from 'src/modules/users/repositories/user.repository';
|
||||
import { UserRepository } from 'src/modules/user/repositories/user.repository';
|
||||
import { SmsNotificationQueueJob } from '../interfaces/jobs-queue.interface';
|
||||
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
|
||||
import { NotificationQueueNameEnum } from '../constants/queue';
|
||||
@@ -59,7 +59,7 @@ export class SmsProcessor extends WorkerHost {
|
||||
this.logger.log(`SMS notification sent successfully to ${phone}`);
|
||||
|
||||
this.eventEmitter.emit(SmsSentEvent.name, new SmsSentEvent(phone, restaurantId));
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
+4
-4
@@ -111,10 +111,10 @@ export class OrdersController {
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@Permissions(Permission.VIEW_REPORTS)
|
||||
@ApiOperation({ summary: 'Get food sales pie chart data for last month' })
|
||||
@ApiOperation({ summary: 'Get product sales pie chart data for last month' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@Get('admin/orders/food-sales-pie-chart')
|
||||
getFoodSalesPieChart(@RestId() restId: string) {
|
||||
return this.ordersService.getFoodSalesPieChart(restId);
|
||||
@Get('admin/orders/product-sales-pie-chart')
|
||||
getproductSalesPieChart(@RestId() restId: string) {
|
||||
return this.ordersService.getproductSalesPieChart(restId);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron } from '@nestjs/schedule';
|
||||
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 { Payment } from '../../payment/entities/payment.entity';
|
||||
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payment/interface/payment';
|
||||
import { InventoryService } from '../../inventory/inventory.service';
|
||||
import { OrderStatus } from '../interface/order.interface';
|
||||
import { Order } from '../entities/order.entity';
|
||||
@@ -37,7 +37,7 @@ export class OrdersCrone {
|
||||
status: PaymentStatusEnum.Pending,
|
||||
createdAt: { $lte: cutoff },
|
||||
},
|
||||
{ populate: ['order', 'order.items', 'order.items.food'] },
|
||||
{ populate: ['order', 'order.items', 'order.items.product'] },
|
||||
);
|
||||
|
||||
if (!payments || payments.length === 0) {
|
||||
@@ -53,7 +53,7 @@ export class OrdersCrone {
|
||||
const payment = await em.findOne(
|
||||
Payment,
|
||||
{ id: p.id },
|
||||
{ populate: ['order', 'order.items', 'order.items.food'] },
|
||||
{ populate: ['order', 'order.items', 'order.items.product'] },
|
||||
);
|
||||
if (!payment) return;
|
||||
if (payment.status !== PaymentStatusEnum.Pending) return;
|
||||
@@ -67,7 +67,7 @@ export class OrdersCrone {
|
||||
// prepare restore payload
|
||||
const items = (payment.order as any).items || [];
|
||||
const restorePayload = {
|
||||
items: items.map((it: any) => ({ foodId: it.food.id, quantity: it.quantity })),
|
||||
items: items.map((it: any) => ({ productId: it.product.id, quantity: it.quantity })),
|
||||
};
|
||||
|
||||
if (restorePayload.items.length > 0) {
|
||||
+1
-1
@@ -11,7 +11,7 @@ import {
|
||||
import { Type, Transform } from 'class-transformer';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { OrderStatus } from '../interface/order.interface';
|
||||
import { PaymentStatusEnum } from '../../payments/interface/payment';
|
||||
import { PaymentStatusEnum } from '../../payment/interface/payment';
|
||||
|
||||
const sortOrderOptions = ['asc', 'desc'] as const;
|
||||
type SortOrder = (typeof sortOrderOptions)[number];
|
||||
+4
-4
@@ -1,17 +1,17 @@
|
||||
import { Entity, Index, ManyToOne, Property } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Order } from './order.entity';
|
||||
import { Food } from '../../foods/entities/food.entity';
|
||||
import { product } from '../../products/entities/product.entity';
|
||||
|
||||
@Entity({ tableName: 'order_items' })
|
||||
@Index({ properties: ['order'] })
|
||||
@Index({ properties: ['food'] })
|
||||
@Index({ properties: ['product'] })
|
||||
export class OrderItem extends BaseEntity {
|
||||
@ManyToOne(() => Order)
|
||||
order!: Order;
|
||||
|
||||
@ManyToOne(() => Food)
|
||||
food!: Food;
|
||||
@ManyToOne(() => product)
|
||||
product!: product;
|
||||
|
||||
@Property({ type: 'int' })
|
||||
quantity!: number;
|
||||
+3
-3
@@ -13,13 +13,13 @@ import {
|
||||
} from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { OrderCouponDetail, OrderStatus } from '../interface/order.interface';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
||||
import { PaymentMethod } from '../../payment/entities/payment-method.entity';
|
||||
import { OrderItem } from './order-item.entity';
|
||||
import { Delivery } from '../../delivery/entities/delivery.entity';
|
||||
import { OrderUserAddress, OrderCarAddress } from '../interface/order.interface';
|
||||
import { Payment } from 'src/modules/payments/entities/payment.entity';
|
||||
import { Payment } from 'src/modules/payment/entities/payment.entity';
|
||||
|
||||
@Entity({ tableName: 'orders' })
|
||||
@Unique({ properties: ['restaurant', 'orderNumber'] })
|
||||
+5
-5
@@ -3,14 +3,14 @@ import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
|
||||
import { OrderCreatedEvent, OrderStatusChangedEvent } from '../events/order.events';
|
||||
import { Permission } from 'src/common/enums/permission.enum';
|
||||
import { NotifTitleEnum } from 'src/modules/notifications/interfaces/notification.interface';
|
||||
import { NotificationService } from 'src/modules/notifications/services/notification.service';
|
||||
import { NotifTitleEnum } from 'src/modules/notification/interfaces/notification.interface';
|
||||
import { NotificationService } from 'src/modules/notification/services/notification.service';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { OrderRepository } from '../repositories/order.repository';
|
||||
import { OrderStatus } from '../interface/order.interface';
|
||||
import { PaymentMethodEnum } from 'src/modules/payments/interface/payment';
|
||||
import { UserService } from 'src/modules/users/providers/user.service';
|
||||
import { WalletTransactionReason, WalletTransactionType } from 'src/modules/users/interface/wallet';
|
||||
import { PaymentMethodEnum } from 'src/modules/payment/interface/payment';
|
||||
import { UserService } from 'src/modules/user/providers/user.service';
|
||||
import { WalletTransactionReason, WalletTransactionType } from 'src/modules/user/interface/wallet';
|
||||
|
||||
@Injectable()
|
||||
export class OrderListeners {
|
||||
@@ -4,27 +4,27 @@ import { OrdersService } from './providers/orders.service';
|
||||
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 { User } from '../user/entities/user.entity';
|
||||
import { Restaurant } from '../restaurants/entities/restaurant.entity';
|
||||
import { Food } from '../foods/entities/food.entity';
|
||||
import { UserAddress } from '../users/entities/user-address.entity';
|
||||
import { PaymentMethod } from '../payments/entities/payment-method.entity';
|
||||
import { product } from '../products/entities/product.entity';
|
||||
import { UserAddress } from '../user/entities/user-address.entity';
|
||||
import { PaymentMethod } from '../payment/entities/payment-method.entity';
|
||||
import { CartModule } from '../cart/cart.module';
|
||||
import { UtilsModule } from '../utils/utils.module';
|
||||
import { UtilsModule } from '../util/utils.module';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { PaymentsModule } from '../payments/payments.module';
|
||||
import { PaymentsModule } from '../payment/payments.module';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { OrderRepository } from './repositories/order.repository';
|
||||
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 { NotificationsModule } from '../notification/notifications.module';
|
||||
import { InventoryModule } from '../inventory/inventory.module';
|
||||
import { UserModule } from '../users/user.module';
|
||||
|
||||
import { UserModule } from '../user/user.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MikroOrmModule.forFeature([Order, OrderItem, User, Restaurant, Food, UserAddress, PaymentMethod]),
|
||||
MikroOrmModule.forFeature([Order, OrderItem, User, Restaurant, product, UserAddress, PaymentMethod]),
|
||||
CartModule,
|
||||
UtilsModule,
|
||||
AuthModule,
|
||||
+37
-37
@@ -2,28 +2,28 @@ import { Injectable, NotFoundException, BadRequestException, Logger } from '@nes
|
||||
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 { User } from '../../user/entities/user.entity';
|
||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||
import { Food } from '../../foods/entities/food.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';
|
||||
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payment/interface/payment';
|
||||
import { Cart } from '../../cart/interfaces/cart.interface';
|
||||
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
||||
import { PaymentsService } from '../../payments/services/payments.service';
|
||||
import { PaymentMethod } from '../../payment/entities/payment-method.entity';
|
||||
import { PaymentsService } from '../../payment/services/payments.service';
|
||||
import { DeliveryMethodEnum } from '../../delivery/interface/delivery';
|
||||
import { Delivery } from '../../delivery/entities/delivery.entity';
|
||||
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 { Payment } from 'src/modules/payment/entities/payment.entity';
|
||||
import { InventoryService } from 'src/modules/inventory/inventory.service';
|
||||
import { BulkReserveFoodDto } from 'src/modules/inventory/dto/bulk-reserve-food.dto';
|
||||
import { BulkReserveproductDto } 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';
|
||||
import { OrderMessage } from 'src/common/enums/message.enum';
|
||||
type OrderItemData = { food: Food; quantity: number; unitPrice: number; discount: number };
|
||||
type OrderItemData = { product: product; quantity: number; unitPrice: number; discount: number };
|
||||
|
||||
type ValidatedCartForOrder = {
|
||||
user: User;
|
||||
@@ -89,15 +89,15 @@ export class OrdersService {
|
||||
em.persist(order);
|
||||
|
||||
for (const itemData of validated.orderItemsData) {
|
||||
const { food, quantity, unitPrice, discount } = itemData;
|
||||
const { product, quantity, unitPrice, discount } = itemData;
|
||||
|
||||
this.assertFoodHasSufficientStock(food, quantity);
|
||||
this.assertproductHasSufficientStock(product, quantity);
|
||||
|
||||
const totalPrice = (unitPrice - discount) * quantity;
|
||||
|
||||
const orderItem = em.create(OrderItem, {
|
||||
order,
|
||||
food,
|
||||
product,
|
||||
quantity,
|
||||
unitPrice,
|
||||
discount,
|
||||
@@ -117,13 +117,13 @@ export class OrdersService {
|
||||
|
||||
em.persist(payment);
|
||||
// reserve stock based on payment method.
|
||||
const bulkReserveFoodDto: BulkReserveFoodDto = {
|
||||
const bulkReserveproductDto: BulkReserveproductDto = {
|
||||
items: validated.orderItemsData.map(item => ({
|
||||
foodId: item.food.id,
|
||||
productId: item.product.id,
|
||||
quantity: item.quantity,
|
||||
})),
|
||||
};
|
||||
await this.inventoryService.deductFromInventory(em, bulkReserveFoodDto);
|
||||
await this.inventoryService.deductFromInventory(em, bulkReserveproductDto);
|
||||
await em.flush();
|
||||
this.logger.debug(`Order ${order.id} created for user ${userId} (restaurant ${restaurantId})`);
|
||||
return order;
|
||||
@@ -220,7 +220,7 @@ export class OrdersService {
|
||||
'paymentMethod',
|
||||
'payments',
|
||||
'items',
|
||||
'items.food',
|
||||
'items.product',
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -426,30 +426,30 @@ export class OrdersService {
|
||||
const orderItemsData: OrderItemData[] = [];
|
||||
|
||||
for (const cartItem of cart.items) {
|
||||
const food = await this.em.findOne(Food, { id: cartItem.foodId }, { populate: ['restaurant', 'inventory'] });
|
||||
if (!food) throw new NotFoundException(OrderMessage.FOOD_NOT_FOUND);
|
||||
const product = await this.em.findOne(product, { id: cartItem.productId }, { populate: ['restaurant', 'inventory'] });
|
||||
if (!product) throw new NotFoundException(OrderMessage.product_NOT_FOUND);
|
||||
|
||||
if (food.restaurant.id !== restaurantId) {
|
||||
throw new BadRequestException(OrderMessage.FOOD_NOT_BELONGS_TO_RESTAURANT);
|
||||
if (product.restaurant.id !== restaurantId) {
|
||||
throw new BadRequestException(OrderMessage.product_NOT_BELONGS_TO_RESTAURANT);
|
||||
}
|
||||
|
||||
this.assertFoodHasSufficientStock(food, cartItem.quantity);
|
||||
this.assertproductHasSufficientStock(product, cartItem.quantity);
|
||||
|
||||
orderItemsData.push({
|
||||
food,
|
||||
product,
|
||||
quantity: cartItem.quantity,
|
||||
unitPrice: food.price || 0,
|
||||
discount: food.discount || 0,
|
||||
unitPrice: product.price || 0,
|
||||
discount: product.discount || 0,
|
||||
});
|
||||
}
|
||||
|
||||
return orderItemsData;
|
||||
}
|
||||
|
||||
private assertFoodHasSufficientStock(food: Food, quantity: number) {
|
||||
const availableStock = food.inventory?.availableStock ?? 0;
|
||||
private assertproductHasSufficientStock(product: product, quantity: number) {
|
||||
const availableStock = product.inventory?.availableStock ?? 0;
|
||||
if (availableStock < quantity) {
|
||||
throw new BadRequestException(OrderMessage.FOOD_NOT_FOUND);
|
||||
throw new BadRequestException(OrderMessage.product_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,8 +471,8 @@ export class OrdersService {
|
||||
},
|
||||
});
|
||||
|
||||
// 2. Active foods count
|
||||
const activeFoods = await em.count(Food, {
|
||||
// 2. Active products count
|
||||
const activeproducts = await em.count(product, {
|
||||
restaurant: { id: restId },
|
||||
isActive: true,
|
||||
});
|
||||
@@ -502,14 +502,14 @@ export class OrdersService {
|
||||
|
||||
return {
|
||||
totalOrders,
|
||||
activeFoods,
|
||||
activeproducts,
|
||||
totalClients,
|
||||
totalRevenue,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async getFoodSalesPieChart(restId: string) {
|
||||
async getproductSalesPieChart(restId: string) {
|
||||
return this.em.transactional(async em => {
|
||||
// Use last 30 days instead of just last month to be more inclusive
|
||||
const now = new Date();
|
||||
@@ -525,7 +525,7 @@ export class OrdersService {
|
||||
const lastDayOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
|
||||
|
||||
this.logger.debug(
|
||||
`Food sales pie chart query params: restId=${restId}, startDate=${startDate.toISOString()}, endDate=${endDate.toISOString()}`,
|
||||
`product sales pie chart query params: restId=${restId}, startDate=${startDate.toISOString()}, endDate=${endDate.toISOString()}`,
|
||||
);
|
||||
|
||||
// Diagnostic query to check what orders exist
|
||||
@@ -555,13 +555,13 @@ export class OrdersService {
|
||||
const result = await em.execute(
|
||||
`
|
||||
SELECT
|
||||
f.id as food_id,
|
||||
f.title as food_title,
|
||||
f.id as product_id,
|
||||
f.title as product_title,
|
||||
COALESCE(SUM(oi.quantity), 0)::int as total_quantity,
|
||||
COALESCE(SUM(oi.total_price), 0)::numeric as total_revenue
|
||||
FROM order_items oi
|
||||
INNER JOIN orders o ON oi.order_id = o.id
|
||||
INNER JOIN foods f ON oi.food_id = f.id
|
||||
INNER JOIN products f ON oi.product_id = f.id
|
||||
WHERE o.restaurant_id = ?
|
||||
AND o.created_at >= ?
|
||||
AND o.created_at <= ?
|
||||
@@ -572,7 +572,7 @@ export class OrdersService {
|
||||
[restId, startDate, endDate],
|
||||
);
|
||||
|
||||
this.logger.debug(`Food sales pie chart query returned ${result.length} rows`);
|
||||
this.logger.debug(`product sales pie chart query returned ${result.length} rows`);
|
||||
|
||||
// Calculate total revenue for percentage calculation
|
||||
const totalRevenue = result.reduce((sum: number, item: any) => {
|
||||
@@ -581,8 +581,8 @@ export class OrdersService {
|
||||
|
||||
// Format data for pie chart and calculate percentages
|
||||
const chartData = result.map((item: any) => ({
|
||||
foodId: item.food_id,
|
||||
foodTitle: item.food_title || 'Unknown',
|
||||
productId: item.product_id,
|
||||
productTitle: item.product_title || 'Unknown',
|
||||
quantity: Number(item.total_quantity || 0),
|
||||
revenue: Number(item.total_revenue || 0),
|
||||
percentage: totalRevenue > 0 ? Number(((Number(item.total_revenue || 0) / totalRevenue) * 100).toFixed(2)) : 0,
|
||||
+19
-19
@@ -4,7 +4,7 @@ import { FilterQuery } from '@mikro-orm/core';
|
||||
import { Order } from '../entities/order.entity';
|
||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||
import { OrderStatus } from '../interface/order.interface';
|
||||
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment';
|
||||
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payment/interface/payment';
|
||||
import { Review } from '../../review/entities/review.entity';
|
||||
|
||||
type FindOrdersOpts = {
|
||||
@@ -118,53 +118,53 @@ export class OrderRepository extends EntityRepository<Order> {
|
||||
limit,
|
||||
offset,
|
||||
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
||||
populate: ['user', 'restaurant', 'deliveryMethod', 'paymentMethod', 'items', 'items.food'] as never,
|
||||
populate: ['user', 'restaurant', 'deliveryMethod', 'paymentMethod', 'items', 'items.product'] as never,
|
||||
});
|
||||
|
||||
// Collect all (orderId, foodId) pairs for efficient review lookup
|
||||
const orderFoodPairs: Array<{ orderId: string; foodId: string }> = [];
|
||||
// Collect all (orderId, productId) pairs for efficient review lookup
|
||||
const orderproductPairs: Array<{ orderId: string; productId: string }> = [];
|
||||
for (const order of data) {
|
||||
for (const item of order.items.getItems()) {
|
||||
if (item.food?.id) {
|
||||
orderFoodPairs.push({ orderId: order.id, foodId: item.food.id });
|
||||
if (item.product?.id) {
|
||||
orderproductPairs.push({ orderId: order.id, productId: item.product.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all relevant reviews in a single query
|
||||
const reviewsMap: Map<string, string> = new Map();
|
||||
if (orderFoodPairs.length > 0) {
|
||||
const orderIds = [...new Set(orderFoodPairs.map(p => p.orderId))];
|
||||
const foodIds = [...new Set(orderFoodPairs.map(p => p.foodId))];
|
||||
if (orderproductPairs.length > 0) {
|
||||
const orderIds = [...new Set(orderproductPairs.map(p => p.orderId))];
|
||||
const productIds = [...new Set(orderproductPairs.map(p => p.productId))];
|
||||
|
||||
const reviews = await this.em.find(
|
||||
Review,
|
||||
{
|
||||
order: { id: { $in: orderIds } },
|
||||
food: { id: { $in: foodIds } },
|
||||
product: { id: { $in: productIds } },
|
||||
},
|
||||
{
|
||||
fields: ['id', 'order', 'food'],
|
||||
populate: ['order', 'food'],
|
||||
fields: ['id', 'order', 'product'],
|
||||
populate: ['order', 'product'],
|
||||
},
|
||||
);
|
||||
|
||||
// Create a map: key = `${orderId}-${foodId}`, value = reviewId
|
||||
// Create a map: key = `${orderId}-${productId}`, value = reviewId
|
||||
for (const review of reviews) {
|
||||
const key = `${review.order.id}-${review.food.id}`;
|
||||
const key = `${review.order.id}-${review.product.id}`;
|
||||
reviewsMap.set(key, review.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Map reviewIds to foods
|
||||
// Map reviewIds to products
|
||||
for (const order of data) {
|
||||
for (const item of order.items.getItems()) {
|
||||
if (item.food) {
|
||||
const key = `${order.id}-${item.food.id}`;
|
||||
if (item.product) {
|
||||
const key = `${order.id}-${item.product.id}`;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const food = item.food as any;
|
||||
const product = item.product as any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
food.reviewId = reviewsMap.get(key) || null;
|
||||
product.reviewId = reviewsMap.get(key) || null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { Entity, ManyToOne, Property, Enum, Index } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Order } from '../../orders/entities/order.entity';
|
||||
import { Order } from '../../order/entities/order.entity';
|
||||
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
|
||||
|
||||
@Entity({ tableName: 'payments' })
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { Order } from 'src/modules/orders/entities/order.entity';
|
||||
import type { Order } from 'src/modules/order/entities/order.entity';
|
||||
|
||||
export enum PaymentMethodEnum {
|
||||
Online = 'Online',
|
||||
+3
-3
@@ -3,10 +3,10 @@ import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
|
||||
import { onlinePaymentSucceedEvent, paymentSucceedEvent } from '../events/payment.events';
|
||||
import { Permission } from 'src/common/enums/permission.enum';
|
||||
import { NotifTitleEnum } from 'src/modules/notifications/interfaces/notification.interface';
|
||||
import { NotificationService } from 'src/modules/notifications/services/notification.service';
|
||||
import { NotifTitleEnum } from 'src/modules/notification/interfaces/notification.interface';
|
||||
import { NotificationService } from 'src/modules/notification/services/notification.service';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { OrdersService } from 'src/modules/orders/providers/orders.service';
|
||||
import { OrdersService } from 'src/modules/order/providers/orders.service';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentListeners {
|
||||
@@ -15,8 +15,8 @@ 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';
|
||||
import { OrdersModule } from '../orders/orders.module';
|
||||
import { NotificationsModule } from '../notification/notifications.module';
|
||||
import { OrdersModule } from '../order/orders.module';
|
||||
|
||||
@Module({
|
||||
imports: [MikroOrmModule.forFeature([PaymentMethod, Payment, Restaurant]),
|
||||
+5
-5
@@ -2,17 +2,17 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/comm
|
||||
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
|
||||
import { Payment } from '../entities/payment.entity';
|
||||
import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import { Order } from '../../orders/entities/order.entity';
|
||||
import { Order } from '../../order/entities/order.entity';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { GatewayManager } from '../gateways/gateway.manager';
|
||||
import { WalletTransaction } from 'src/modules/users/entities/wallet-transaction.entity';
|
||||
import { WalletTransaction } from 'src/modules/user/entities/wallet-transaction.entity';
|
||||
import { OrderPaymentContext } from '../interface/payment';
|
||||
import { OrderStatus } from 'src/modules/orders/interface/order.interface';
|
||||
import { OrderStatus } from 'src/modules/order/interface/order.interface';
|
||||
import { ChartPeriodEnum, PaymentChartDto } from '../dto/payment-chart.dto';
|
||||
import { PaymentMessage, OrderMessage } from 'src/common/enums/message.enum';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { onlinePaymentSucceedEvent, paymentSucceedEvent } from '../events/payment.events';
|
||||
import { WalletTransactionReason, WalletTransactionType } from 'src/modules/users/interface/wallet';
|
||||
import { WalletTransactionReason, WalletTransactionType } from 'src/modules/user/interface/wallet';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentsService {
|
||||
@@ -141,7 +141,7 @@ export class PaymentsService {
|
||||
payment.paidAt = new Date();
|
||||
order.status = OrderStatus.PAID;
|
||||
|
||||
em.persist([ payment, order, newWalletTransaction]);
|
||||
em.persist([payment, order, newWalletTransaction]);
|
||||
await em.flush();
|
||||
});
|
||||
this.eventEmitter.emit(
|
||||
@@ -0,0 +1,126 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } from '@nestjs/common';
|
||||
import { productService } from '../providers/product.service';
|
||||
import { CreateproductDto } from '../dto/create-product.dto';
|
||||
import { UpdateproductDto } from '../dto/update-product.dto';
|
||||
import { FindproductsDto } from '../dto/find-products.dto';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiCreatedResponse,
|
||||
ApiOkResponse,
|
||||
ApiNotFoundResponse,
|
||||
ApiQuery,
|
||||
ApiBody,
|
||||
ApiParam,
|
||||
ApiBearerAuth,
|
||||
ApiHeader,
|
||||
} from '@nestjs/swagger';
|
||||
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
||||
import { RestId, UserId } from 'src/common/decorators';
|
||||
import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
|
||||
import { OptionalAuthGuard } from 'src/modules/auth/guards/optinalAuth.guard';
|
||||
import { API_HEADER_SLUG } from 'src/common/constants';
|
||||
import { Permission } from 'src/common/enums/permission.enum';
|
||||
import { Permissions } from 'src/common/decorators/permissions.decorator';
|
||||
|
||||
@ApiTags('products')
|
||||
@Controller()
|
||||
export class productController {
|
||||
constructor(private readonly productsService: productService) { }
|
||||
|
||||
@Get('public/products/restaurant/:slug')
|
||||
@ApiOperation({ summary: 'Get all products by restaurant slug' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiParam({ name: 'slug', required: true, description: 'Restaurant Slug' })
|
||||
findAllByRestaurant(@Param('slug') slug: string) {
|
||||
return this.productsService.findByWeekDateAndMealType(slug);
|
||||
}
|
||||
|
||||
@Get('public/products/:productId')
|
||||
@UseGuards(OptionalAuthGuard)
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Get a product by id' })
|
||||
@ApiParam({ name: 'productId', required: true })
|
||||
findPublicproductById(@Param('productId') productId: string, @UserId() userId?: string): Promise<any> {
|
||||
const a = this.productsService.findPublicById(productId, userId);
|
||||
return a;
|
||||
}
|
||||
|
||||
@Post('public/products/favorite/:productId')
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'toggle a product as favorite' })
|
||||
@ApiParam({ name: 'productId', required: true })
|
||||
setAsFavorute(@Param('productId') productId: string, @UserId() userId: string) {
|
||||
return this.productsService.toggleFavorite(userId, productId);
|
||||
}
|
||||
|
||||
@Get('public/products/favorite')
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'get my favorites' })
|
||||
getMyFavorites(@UserId() userId: string, @RestId() restId: string) {
|
||||
return this.productsService.getMyFavorites(userId, restId);
|
||||
}
|
||||
|
||||
/* ---------------------------------- Admin ---------------------------------- */
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_productS)
|
||||
@Post('admin/products')
|
||||
@ApiOperation({ summary: 'Create a new product' })
|
||||
@ApiBody({ type: CreateproductDto })
|
||||
create(@Body() createproductDto: CreateproductDto, @RestId() restId: string) {
|
||||
return this.productsService.create(restId, createproductDto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_productS)
|
||||
@Get('admin/products')
|
||||
@ApiOperation({ summary: 'Get a paginated list of products' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'limit', required: false, type: Number })
|
||||
@ApiQuery({ name: 'search', required: false, type: String })
|
||||
@ApiQuery({ name: 'orderBy', required: false, type: String })
|
||||
@ApiQuery({ name: 'order', required: false, enum: ['asc', 'desc'] })
|
||||
@ApiQuery({ name: 'categoryId', required: false, type: String })
|
||||
@ApiQuery({ name: 'isActive', required: false, type: Boolean })
|
||||
async findAll(@Query() dto: FindproductsDto, @RestId() restId: string) {
|
||||
const result = await this.productsService.findAll(restId, dto);
|
||||
return result;
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_productS)
|
||||
@Get('admin/products/:id')
|
||||
@ApiOperation({ summary: 'Get a product by id' })
|
||||
@ApiParam({ name: 'id', required: true })
|
||||
findById(@Param('id') id: string, @RestId() restId: string) {
|
||||
return this.productsService.findAdminById(restId, id);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_productS)
|
||||
@Patch('admin/products/:id')
|
||||
@ApiOperation({ summary: 'Update a product' })
|
||||
@ApiParam({ name: 'id', required: true })
|
||||
@ApiBody({ type: UpdateproductDto })
|
||||
update(@Param('id') id: string, @Body() updateproductDto: UpdateproductDto, @RestId() restId: string) {
|
||||
return this.productsService.update(restId, id, updateproductDto);
|
||||
}
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_productS)
|
||||
@Delete('admin/products/:id')
|
||||
@ApiOperation({ summary: 'Delete (soft) a product' })
|
||||
@ApiParam({ name: 'id', required: true })
|
||||
remove(@Param('id') id: string, @RestId() restId: string) {
|
||||
return this.productsService.remove(restId, id);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import { Inventory } from '../../inventory/entities/inventory.entity';
|
||||
|
||||
@Injectable()
|
||||
export class FoodStockCrone {
|
||||
private readonly logger = new Logger(FoodStockCrone.name);
|
||||
export class productStockCrone {
|
||||
private readonly logger = new Logger(productStockCrone.name);
|
||||
|
||||
constructor(private readonly em: EntityManager) {}
|
||||
constructor(private readonly em: EntityManager) { }
|
||||
|
||||
// run every day at 00:03
|
||||
@Cron('3 0 * * *', {
|
||||
@@ -39,7 +39,7 @@ export class FoodStockCrone {
|
||||
|
||||
this.logger.log('Daily inventory reset completed');
|
||||
} catch (err) {
|
||||
this.logger.error(`FoodStockCrone failed: ${err?.message}`, err);
|
||||
this.logger.error(`productStockCrone failed: ${err?.message}`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -13,9 +13,9 @@ import {
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { MealType } from '../interface/food.interface';
|
||||
import { MealType } from '../interface/product.interface';
|
||||
|
||||
export class CreateFoodDto {
|
||||
export class CreateproductDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty()
|
||||
@@ -2,7 +2,7 @@ import { IsOptional, IsNumber, IsString, IsIn, IsBoolean } from 'class-validator
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class FindFoodsDto {
|
||||
export class FindproductsDto {
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateproductDto } from './create-product.dto';
|
||||
|
||||
export class UpdateproductDto extends PartialType(CreateproductDto) { }
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
import { Entity, Index, Property, Collection, OneToMany, ManyToOne } from '@mikro-orm/core';
|
||||
import { Food } from './food.entity';
|
||||
import { product } from './product.entity';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity';
|
||||
|
||||
@@ -10,8 +10,8 @@ export class Category extends BaseEntity {
|
||||
@Property()
|
||||
title!: string;
|
||||
|
||||
@OneToMany(() => Food, food => food.category)
|
||||
foods = new Collection<Food>(this);
|
||||
@OneToMany(() => product, product => product.category)
|
||||
products = new Collection<product>(this);
|
||||
|
||||
@Property({ default: true })
|
||||
isActive: boolean = true;
|
||||
+5
-5
@@ -1,14 +1,14 @@
|
||||
import { Entity, ManyToOne, Unique } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { User } from '../../users/entities/user.entity';
|
||||
import { Food } from '../../foods/entities/food.entity';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { product } from '../../products/entities/product.entity';
|
||||
|
||||
@Entity({ tableName: 'favorites' })
|
||||
@Unique({ properties: ['user', 'food'] })
|
||||
@Unique({ properties: ['user', 'product'] })
|
||||
export class Favorite extends BaseEntity {
|
||||
@ManyToOne(() => User)
|
||||
user: User;
|
||||
|
||||
@ManyToOne(() => Food)
|
||||
food: Food;
|
||||
@ManyToOne(() => product)
|
||||
product: product;
|
||||
}
|
||||
+6
-6
@@ -4,30 +4,30 @@ import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity';
|
||||
import { Review } from 'src/modules/review/entities/review.entity';
|
||||
import { Inventory } from 'src/modules/inventory/entities/inventory.entity';
|
||||
import { MealType } from '../interface/food.interface';
|
||||
import { MealType } from '../interface/product.interface';
|
||||
import { Favorite } from './favorite.entity';
|
||||
|
||||
@Entity({ tableName: 'foods' })
|
||||
@Entity({ tableName: 'products' })
|
||||
@Index({ properties: ['restaurant', 'isActive'] })
|
||||
@Index({ properties: ['category', 'isActive'] })
|
||||
@Index({ properties: ['isActive'] })
|
||||
export class Food extends BaseEntity {
|
||||
export class product extends BaseEntity {
|
||||
@ManyToOne(() => Restaurant)
|
||||
restaurant: Restaurant;
|
||||
|
||||
@ManyToOne(() => Category)
|
||||
category: Category;
|
||||
|
||||
@OneToMany(() => Review, review => review.food, { cascade: [Cascade.ALL], orphanRemoval: true })
|
||||
@OneToMany(() => Review, review => review.product, { cascade: [Cascade.ALL], orphanRemoval: true })
|
||||
reviews = new Collection<Review>(this);
|
||||
|
||||
@OneToOne(() => Inventory, {
|
||||
mappedBy: 'food',
|
||||
mappedBy: 'product',
|
||||
nullable: true,
|
||||
})
|
||||
inventory?: Inventory;
|
||||
|
||||
@OneToMany(() => Favorite, favorite => favorite.food)
|
||||
@OneToMany(() => Favorite, favorite => favorite.product)
|
||||
favorites = new Collection<Favorite>(this);
|
||||
|
||||
@Property({ nullable: true })
|
||||
@@ -1,30 +1,30 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { FoodService } from './providers/food.service';
|
||||
import { FoodStockCrone } from './crone/food.crone';
|
||||
import { FoodController } from './controllers/food.controller';
|
||||
import { productService } from './providers/product.service';
|
||||
import { productStockCrone } from './crone/product.crone';
|
||||
import { productController } from './controllers/product.controller';
|
||||
import { CategoryController } from './controllers/category.controller';
|
||||
import { CategoryService } from './providers/category.service';
|
||||
import { FoodRepository } from './repositories/food.repository';
|
||||
import { productRepository } from './repositories/product.repository';
|
||||
import { CategoryRepository } from './repositories/category.repository';
|
||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||
import { Category } from './entities/category.entity';
|
||||
import { Food } from './entities/food.entity';
|
||||
import { product } from './entities/product.entity';
|
||||
import { RestaurantsModule } from '../restaurants/restaurants.module';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { UtilsModule } from '../utils/utils.module';
|
||||
import { UtilsModule } from '../util/utils.module';
|
||||
import { Favorite } from './entities/favorite.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MikroOrmModule.forFeature([Food, Category, Favorite]),
|
||||
MikroOrmModule.forFeature([product, Category, Favorite]),
|
||||
RestaurantsModule,
|
||||
AuthModule,
|
||||
JwtModule,
|
||||
UtilsModule,
|
||||
],
|
||||
controllers: [FoodController, CategoryController],
|
||||
providers: [FoodService, CategoryService, FoodRepository, CategoryRepository, FoodStockCrone],
|
||||
exports: [FoodRepository, CategoryRepository],
|
||||
controllers: [productController, CategoryController],
|
||||
providers: [productService, CategoryService, productRepository, CategoryRepository, productStockCrone],
|
||||
exports: [productRepository, CategoryRepository],
|
||||
})
|
||||
export class FoodModule {}
|
||||
export class productModule { }
|
||||
+5
-5
@@ -48,7 +48,7 @@ export class CategoryService {
|
||||
// const where: FilterQuery<Category> = {};
|
||||
// if (opts?.restId) where.restId = opts.restId;
|
||||
// if (opts && typeof opts.isActive === 'boolean') where.isActive = opts.isActive;
|
||||
// const cats = await this.categoryRepository.find(where, { populate: ['foods'] });
|
||||
// const cats = await this.categoryRepository.find(where, { populate: ['products'] });
|
||||
|
||||
// // Return plain objects to avoid circular references during JSON serialization
|
||||
// return cats.map(cat => ({
|
||||
@@ -59,12 +59,12 @@ export class CategoryService {
|
||||
// avatarUrl: cat.avatarUrl,
|
||||
// createdAt: cat.createdAt,
|
||||
// updatedAt: cat.updatedAt,
|
||||
// foods: cat.foods.getItems().map(f => ({ id: f.id, title: f.title })),
|
||||
// products: cat.products.getItems().map(f => ({ id: f.id, title: f.title })),
|
||||
// })) as unknown as Category[];
|
||||
// }
|
||||
|
||||
async findOne(restId: string, id: string): Promise<Category> {
|
||||
const cat = await this.categoryRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['foods'] });
|
||||
const cat = await this.categoryRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['products'] });
|
||||
if (!cat) throw new NotFoundException(CategoryMessage.NOT_FOUND);
|
||||
|
||||
return {
|
||||
@@ -75,12 +75,12 @@ export class CategoryService {
|
||||
avatarUrl: cat.avatarUrl,
|
||||
createdAt: cat.createdAt,
|
||||
updatedAt: cat.updatedAt,
|
||||
foods: cat.foods.getItems().map(f => ({ id: f.id, title: f.title })),
|
||||
products: cat.products.getItems().map(f => ({ id: f.id, title: f.title })),
|
||||
} as unknown as Category;
|
||||
}
|
||||
|
||||
async findRestaurantCategories(restId: string): Promise<Category> {
|
||||
const cat = await this.categoryRepository.findOne({ restaurant: { id: restId } }, { populate: ['foods'] });
|
||||
const cat = await this.categoryRepository.findOne({ restaurant: { id: restId } }, { populate: ['products'] });
|
||||
if (!cat) throw new NotFoundException(CategoryMessage.NOT_FOUND);
|
||||
|
||||
return {
|
||||
+69
-69
@@ -1,33 +1,33 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { CreateFoodDto } from '../dto/create-food.dto';
|
||||
import { FindFoodsDto } from '../dto/find-foods.dto';
|
||||
import { FoodRepository } from '../repositories/food.repository';
|
||||
import { CreateproductDto } from '../dto/create-product.dto';
|
||||
import { FindproductsDto } from '../dto/find-products.dto';
|
||||
import { productRepository } from '../repositories/product.repository';
|
||||
import { CategoryRepository } from '../repositories/category.repository';
|
||||
import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import { RequiredEntityData, FilterQuery } from '@mikro-orm/core';
|
||||
import { Food } from '../entities/food.entity';
|
||||
import { CategoryMessage, FoodMessage, RestMessage } from 'src/common/enums/message.enum';
|
||||
import { product } from '../entities/product.entity';
|
||||
import { CategoryMessage, productMessage, RestMessage } from 'src/common/enums/message.enum';
|
||||
import { RestRepository } from 'src/modules/restaurants/repositories/rest.repository';
|
||||
import { CacheService } from '../../utils/cache.service';
|
||||
import { CacheService } from '../../util/cache.service';
|
||||
import { Favorite } from '../entities/favorite.entity';
|
||||
import { MealType } from '../interface/food.interface';
|
||||
import { MealType } from '../interface/product.interface';
|
||||
import { Inventory } from 'src/modules/inventory/entities/inventory.entity';
|
||||
|
||||
@Injectable()
|
||||
export class FoodService {
|
||||
private readonly FOODS_BY_RESTAURANT_CACHE_KEY_PREFIX = 'foods:restaurant:';
|
||||
private readonly FOODS_CACHE_TTL = 600; // 10 minutes in seconds
|
||||
export class productService {
|
||||
private readonly productS_BY_RESTAURANT_CACHE_KEY_PREFIX = 'products:restaurant:';
|
||||
private readonly productS_CACHE_TTL = 600; // 10 minutes in seconds
|
||||
|
||||
constructor(
|
||||
private readonly foodRepository: FoodRepository,
|
||||
private readonly productRepository: productRepository,
|
||||
private readonly categoryRepository: CategoryRepository,
|
||||
private readonly restRepository: RestRepository,
|
||||
private readonly em: EntityManager,
|
||||
private readonly cacheService: CacheService,
|
||||
) { }
|
||||
|
||||
async create(restId: string, createFoodDto: CreateFoodDto) {
|
||||
const { categoryId, dailyStock = 0, ...rest } = createFoodDto;
|
||||
async create(restId: string, createproductDto: CreateproductDto) {
|
||||
const { categoryId, dailyStock = 0, ...rest } = createproductDto;
|
||||
const restaurant = await this.restRepository.findOne({ id: restId });
|
||||
if (!restaurant) {
|
||||
throw new NotFoundException(RestMessage.NOT_FOUND);
|
||||
@@ -37,9 +37,9 @@ export class FoodService {
|
||||
throw new NotFoundException(CategoryMessage.NOT_FOUND);
|
||||
}
|
||||
|
||||
const { food, inventory } = await this.em.transactional(async em => {
|
||||
const { product, inventory } = await this.em.transactional(async em => {
|
||||
// prepare data with defaults for required fields so repository.create typing is satisfied
|
||||
const data: RequiredEntityData<Food> = {
|
||||
const data: RequiredEntityData<product> = {
|
||||
desc: rest.desc,
|
||||
isActive: rest.isActive ?? true,
|
||||
inPlaceServe: rest.inPlaceServe ?? false,
|
||||
@@ -61,62 +61,62 @@ export class FoodService {
|
||||
category: category,
|
||||
};
|
||||
|
||||
const food = em.create(Food, data);
|
||||
const product = em.create(product, data);
|
||||
const newInventoryRecord = em.create(Inventory, {
|
||||
food: food,
|
||||
product: product,
|
||||
availableStock: dailyStock,
|
||||
totalStock: dailyStock,
|
||||
});
|
||||
|
||||
await em.flush();
|
||||
return { food, inventory: newInventoryRecord };
|
||||
return { product, inventory: newInventoryRecord };
|
||||
});
|
||||
|
||||
// Re-load created entities with the primary EM to ensure they're attached
|
||||
const savedFood = food?.id
|
||||
? await this.foodRepository.findOne({ id: food.id }, { populate: ['category', 'restaurant'] })
|
||||
const savedproduct = product?.id
|
||||
? await this.productRepository.findOne({ id: product.id }, { populate: ['category', 'restaurant'] })
|
||||
: null;
|
||||
const savedInventory = inventory?.id ? await this.em.findOne(Inventory, { id: inventory.id }) : inventory;
|
||||
|
||||
return { food: savedFood ?? food, inventory: savedInventory ?? inventory };
|
||||
return { product: savedproduct ?? product, inventory: savedInventory ?? inventory };
|
||||
}
|
||||
|
||||
findAll(restId: string, dto: FindFoodsDto) {
|
||||
return this.foodRepository.findAllPaginated(restId, dto);
|
||||
findAll(restId: string, dto: FindproductsDto) {
|
||||
return this.productRepository.findAllPaginated(restId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public food detail (only active foods are visible).
|
||||
* Public product detail (only active products are visible).
|
||||
*/
|
||||
async findPublicById(foodId: string, userId?: string): Promise<any> {
|
||||
const food = await this.foodRepository.findOne({ id: foodId, isActive: true }, { populate: ['category', 'inventory'] });
|
||||
if (!food) throw new NotFoundException(FoodMessage.NOT_FOUND);
|
||||
async findPublicById(productId: string, userId?: string): Promise<any> {
|
||||
const product = await this.productRepository.findOne({ id: productId, isActive: true }, { populate: ['category', 'inventory'] });
|
||||
if (!product) throw new NotFoundException(productMessage.NOT_FOUND);
|
||||
let isFavorite = false;
|
||||
if (userId) {
|
||||
isFavorite = (await this.em.count(Favorite, { user: { id: userId }, food: { id: foodId } })) > 0;
|
||||
isFavorite = (await this.em.count(Favorite, { user: { id: userId }, product: { id: productId } })) > 0;
|
||||
}
|
||||
return {
|
||||
...food,
|
||||
...product,
|
||||
isFavorite,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin food detail (scoped to the authenticated restaurant).
|
||||
* Admin product detail (scoped to the authenticated restaurant).
|
||||
*/
|
||||
async findAdminById(restId: string, id: string): Promise<Food> {
|
||||
const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['category','inventory'] });
|
||||
async findAdminById(restId: string, id: string): Promise<product> {
|
||||
const product = await this.productRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['category', 'inventory'] });
|
||||
|
||||
if (!food) throw new NotFoundException(FoodMessage.NOT_FOUND);
|
||||
return food;
|
||||
if (!product) throw new NotFoundException(productMessage.NOT_FOUND);
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find active foods for a restaurant based on current week day and meal type in Iran timezone.
|
||||
* Find active products for a restaurant based on current week day and meal type in Iran timezone.
|
||||
* @param slug - Restaurant slug identifier
|
||||
* @returns Array of active foods matching current day and meal time
|
||||
* @returns Array of active products matching current day and meal time
|
||||
*/
|
||||
async findByWeekDateAndMealType(slug: string): Promise<Food[]> {
|
||||
async findByWeekDateAndMealType(slug: string): Promise<product[]> {
|
||||
const restaurant = await this.restRepository.findOne({ slug });
|
||||
if (!restaurant) {
|
||||
throw new NotFoundException(RestMessage.NOT_FOUND);
|
||||
@@ -124,14 +124,14 @@ export class FoodService {
|
||||
|
||||
const { iranWeekDay, mealType } = this.getCurrentIranTimeContext();
|
||||
|
||||
const queryFilter: FilterQuery<Food> = {
|
||||
const queryFilter: FilterQuery<product> = {
|
||||
restaurant: { slug },
|
||||
isActive: true,
|
||||
// weekDays: { $contains: iranWeekDay },
|
||||
// ...(mealType ? { mealTypes: { $contains: mealType } } : {}),
|
||||
} as unknown as FilterQuery<Food>;
|
||||
} as unknown as FilterQuery<product>;
|
||||
|
||||
return this.foodRepository.find(queryFilter, { populate: ['category'] });
|
||||
return this.productRepository.find(queryFilter, { populate: ['category'] });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,11 +182,11 @@ export class FoodService {
|
||||
return null;
|
||||
}
|
||||
|
||||
async update(restId: string, id: string, dto: Partial<CreateFoodDto>): Promise<Food> {
|
||||
async update(restId: string, id: string, dto: Partial<CreateproductDto>): Promise<product> {
|
||||
const { categoryId, dailyStock, ...rest } = dto;
|
||||
const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['restaurant'] });
|
||||
if (!food) {
|
||||
throw new NotFoundException(FoodMessage.NOT_FOUND);
|
||||
const product = await this.productRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['restaurant'] });
|
||||
if (!product) {
|
||||
throw new NotFoundException(productMessage.NOT_FOUND);
|
||||
}
|
||||
|
||||
// attach new categories if provided (adds, does not attempt to preserve/remove existing ones)
|
||||
@@ -197,23 +197,23 @@ export class FoodService {
|
||||
throw new NotFoundException(CategoryMessage.NOT_FOUND);
|
||||
}
|
||||
|
||||
this.em.assign(food, { category: category });
|
||||
this.em.assign(product, { category: category });
|
||||
}
|
||||
|
||||
// assign other fields from DTO (excluding dailyStock handled below)
|
||||
this.em.assign(food, rest);
|
||||
this.em.assign(product, rest);
|
||||
|
||||
// Persist food and update/create related inventory atomically
|
||||
// Persist product and update/create related inventory atomically
|
||||
await this.em.transactional(async em => {
|
||||
await em.persistAndFlush(food);
|
||||
await em.persistAndFlush(product);
|
||||
|
||||
if (typeof dailyStock !== 'undefined') {
|
||||
const inventoryRecord = await em.findOne(Inventory, { food: food });
|
||||
const inventoryRecord = await em.findOne(Inventory, { product: product });
|
||||
if (inventoryRecord) {
|
||||
inventoryRecord.totalStock = dailyStock;
|
||||
} else {
|
||||
em.create(Inventory, {
|
||||
food: food,
|
||||
product: product,
|
||||
availableStock: dailyStock,
|
||||
totalStock: dailyStock,
|
||||
});
|
||||
@@ -223,52 +223,52 @@ export class FoodService {
|
||||
await em.flush();
|
||||
});
|
||||
|
||||
// Re-load the food to ensure populated relations and stable instance
|
||||
const savedFood = await this.foodRepository.findOne({ id: food.id }, { populate: ['category', 'restaurant'] });
|
||||
// Re-load the product to ensure populated relations and stable instance
|
||||
const savedproduct = await this.productRepository.findOne({ id: product.id }, { populate: ['category', 'restaurant'] });
|
||||
|
||||
// Invalidate cache for the restaurant if present (optional)
|
||||
// await this.invalidateRestaurantFoodsCache(savedFood?.restaurant?.slug);
|
||||
// await this.invalidateRestaurantproductsCache(savedproduct?.restaurant?.slug);
|
||||
|
||||
return savedFood ?? food;
|
||||
return savedproduct ?? product;
|
||||
}
|
||||
|
||||
async remove(restId: string, id: string): Promise<void> {
|
||||
const food = await this.foodRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['restaurant'] });
|
||||
if (!food) {
|
||||
throw new NotFoundException(FoodMessage.NOT_FOUND);
|
||||
const product = await this.productRepository.findOne({ id, restaurant: { id: restId } }, { populate: ['restaurant'] });
|
||||
if (!product) {
|
||||
throw new NotFoundException(productMessage.NOT_FOUND);
|
||||
}
|
||||
|
||||
// const restaurantSlug = food.restaurant.slug;
|
||||
food.deletedAt = new Date();
|
||||
await this.em.persistAndFlush(food);
|
||||
// const restaurantSlug = product.restaurant.slug;
|
||||
product.deletedAt = new Date();
|
||||
await this.em.persistAndFlush(product);
|
||||
|
||||
// Invalidate cache for the restaurant
|
||||
// await this.invalidateRestaurantFoodsCache(restaurantSlug);
|
||||
// await this.invalidateRestaurantproductsCache(restaurantSlug);
|
||||
}
|
||||
|
||||
|
||||
async toggleFavorite(userId: string, foodId: string) {
|
||||
async toggleFavorite(userId: string, productId: string) {
|
||||
|
||||
const favorite = await this.em.findOne(Favorite, { user: userId, food: foodId });
|
||||
const favorite = await this.em.findOne(Favorite, { user: userId, product: productId });
|
||||
if (favorite) {
|
||||
return this.em.removeAndFlush(favorite);
|
||||
}
|
||||
const newFavorite = this.em.create(Favorite, {
|
||||
user: userId,
|
||||
food: foodId,
|
||||
product: productId,
|
||||
});
|
||||
return this.em.persistAndFlush(newFavorite);
|
||||
}
|
||||
|
||||
|
||||
async getMyFavorites(userId: string,restId: string) {
|
||||
return this.em.find(Favorite, { user: userId, food: { restaurant: { id: restId } } }, { populate: ['food'] });
|
||||
async getMyFavorites(userId: string, restId: string) {
|
||||
return this.em.find(Favorite, { user: userId, product: { restaurant: { id: restId } } }, { populate: ['product'] });
|
||||
}
|
||||
/**
|
||||
* Invalidate cache for restaurant foods
|
||||
* Invalidate cache for restaurant products
|
||||
*/
|
||||
// private async invalidateRestaurantFoodsCache(slug: string): Promise<void> {
|
||||
// const cacheKey = `${this.FOODS_BY_RESTAURANT_CACHE_KEY_PREFIX}${slug}`;
|
||||
// private async invalidateRestaurantproductsCache(slug: string): Promise<void> {
|
||||
// const cacheKey = `${this.productS_BY_RESTAURANT_CACHE_KEY_PREFIX}${slug}`;
|
||||
// await this.cacheService.del(cacheKey);
|
||||
// }
|
||||
}
|
||||
+10
-10
@@ -1,10 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
||||
import { FilterQuery } from '@mikro-orm/core';
|
||||
import { Food } from '../entities/food.entity';
|
||||
import { product } from '../entities/product.entity';
|
||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||
|
||||
type FindFoodsOpts = {
|
||||
type FindproductsOpts = {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
@@ -15,20 +15,20 @@ type FindFoodsOpts = {
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class FoodRepository extends EntityRepository<Food> {
|
||||
export class productRepository extends EntityRepository<product> {
|
||||
constructor(readonly em: EntityManager) {
|
||||
super(em, Food);
|
||||
super(em, product);
|
||||
}
|
||||
/**
|
||||
* Find foods with pagination and optional filters.
|
||||
* Find products with pagination and optional filters.
|
||||
* Supports: search (title/content), categoryId, isActive, ordering.
|
||||
*/
|
||||
async findAllPaginated(restId: string, opts: FindFoodsOpts = {}): Promise<PaginatedResult<Food>> {
|
||||
async findAllPaginated(restId: string, opts: FindproductsOpts = {}): Promise<PaginatedResult<product>> {
|
||||
const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc', categoryId, isActive } = opts;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const where: FilterQuery<Food> = { restaurant: { id: restId } };
|
||||
const where: FilterQuery<product> = { restaurant: { id: restId } };
|
||||
|
||||
if (typeof isActive === 'boolean') {
|
||||
where.isActive = isActive;
|
||||
@@ -40,15 +40,15 @@ export class FoodRepository extends EntityRepository<Food> {
|
||||
}
|
||||
|
||||
if (categoryId) {
|
||||
// filter by related category (Food has a single `category` relation)
|
||||
Object.assign(where, { category: { id: categoryId } } as unknown as FilterQuery<Food>);
|
||||
// filter by related category (product has a single `category` relation)
|
||||
Object.assign(where, { category: { id: categoryId } } as unknown as FilterQuery<product>);
|
||||
}
|
||||
|
||||
const [data, total] = await this.findAndCount(where, {
|
||||
limit,
|
||||
offset,
|
||||
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
||||
populate: ['category','inventory'],
|
||||
populate: ['category', 'inventory'],
|
||||
});
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { EntityManager, EntityRepository } from '@mikro-orm/core';
|
||||
import { Permission } from '../entities/permission.entity';
|
||||
import { CacheService } from 'src/modules/utils/cache.service';
|
||||
import { CacheService } from 'src/modules/util/cache.service';
|
||||
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
|
||||
import { AdminRole } from 'src/modules/admin/entities/adminRole.entity';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Role } from './entities/role.entity';
|
||||
import { Permission } from './entities/permission.entity';
|
||||
import { RolePermission } from './entities/rolePermission.entity';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { UtilsModule } from '../utils/utils.module';
|
||||
import { UtilsModule } from '../util/utils.module';
|
||||
import { AdminModule } from '../admin/admin.module';
|
||||
|
||||
@Global()
|
||||
@@ -17,4 +17,4 @@ import { AdminModule } from '../admin/admin.module';
|
||||
providers: [RolesService, PermissionsService],
|
||||
exports: [RolesService, PermissionsService],
|
||||
})
|
||||
export class RolesModule {}
|
||||
export class RolesModule { }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user