change entity names

This commit is contained in:
2026-02-08 09:18:20 +03:30
parent 6be6a66079
commit 9a7010dcea
180 changed files with 2751 additions and 2513 deletions
+4 -4
View File
@@ -9,8 +9,8 @@ import { UploaderModule } from './modules/uploader/uploader.module';
import { AdminModule } from './modules/admin/admin.module'; import { AdminModule } from './modules/admin/admin.module';
import { ThrottlerModule } from '@nestjs/throttler'; import { ThrottlerModule } from '@nestjs/throttler';
import { ScheduleModule } from '@nestjs/schedule'; import { ScheduleModule } from '@nestjs/schedule';
import { RestaurantsModule } from './modules/restaurants/restaurants.module'; import { ShopsModule } from './modules/shops/shops.module';
import { FoodModule } from './modules/foods/food.module'; import { ProductModule } from './modules/products/product.module';
import { CartModule } from './modules/cart/cart.module'; import { CartModule } from './modules/cart/cart.module';
import { RolesModule } from './modules/roles/roles.module'; import { RolesModule } from './modules/roles/roles.module';
import { PaymentsModule } from './modules/payments/payments.module'; import { PaymentsModule } from './modules/payments/payments.module';
@@ -44,8 +44,8 @@ import { cacheConfig } from './config/cache.config';
}, },
]), ]),
ScheduleModule.forRoot(), ScheduleModule.forRoot(),
RestaurantsModule, ShopsModule,
FoodModule, ProductModule,
CartModule, CartModule,
RolesModule, RolesModule,
PaymentsModule, PaymentsModule,
+1 -1
View File
@@ -6,7 +6,7 @@ import type { Request } from 'express';
* Must be used after AdminAuthGuard or AuthGuard that sets request.restId. * Must be used after AdminAuthGuard or AuthGuard that sets request.restId.
* *
* @example * @example
* @Get('/restaurants') * @Get('/shops')
* @UseGuards(AdminAuthGuard) * @UseGuards(AdminAuthGuard)
* getRestaurants(@RestId() restId: string) { * getRestaurants(@RestId() restId: string) {
* return this.restaurantService.findById(restId); * return this.restaurantService.findById(restId);
+23 -23
View File
@@ -71,8 +71,8 @@ export const enum AuthMessage {
INVALID_SLUG = 'اسلاگ نامعتبر است', INVALID_SLUG = 'اسلاگ نامعتبر است',
INVALID_OR_EXPIRED_TOKEN = 'توکن نامعتبر یا منقضی شده است', INVALID_OR_EXPIRED_TOKEN = 'توکن نامعتبر یا منقضی شده است',
} }
export const enum FoodMessage { export const enum ProductMessage {
NOT_FOUND = 'غذایی با این مشخصات یافت نشد', NOT_FOUND = 'محصولی با این مشخصات یافت نشد',
} }
export const enum CategoryMessage { export const enum CategoryMessage {
NOT_FOUND = 'دسته‌بندی مورد نظر یافت نشد', NOT_FOUND = 'دسته‌بندی مورد نظر یافت نشد',
@@ -82,7 +82,7 @@ export const enum CategoryMessage {
} }
export const enum UserMessage { export const enum UserMessage {
USER_NOT_FOUND = 'کاربری با این مشخصات یافت نشد', USER_NOT_FOUND = 'کاربری با این مشخصات یافت نشد',
Rest_NOT_FOUND = 'رستورانی با این مشخصات یافت نشد', Rest_NOT_FOUND = 'فروشگاهی با این مشخصات یافت نشد',
USER_EXISTS = 'با این شماره قبلا ثبت نام شده است', USER_EXISTS = 'با این شماره قبلا ثبت نام شده است',
USER_REGISTER_SUCCESS = 'ثبت نام موفقیت آمیز بود', USER_REGISTER_SUCCESS = 'ثبت نام موفقیت آمیز بود',
EMAIL_ADDRESS_ALREADY_EXISTS = 'ایمیل قبلا ثبت شده است', EMAIL_ADDRESS_ALREADY_EXISTS = 'ایمیل قبلا ثبت شده است',
@@ -651,7 +651,7 @@ export const enum CouponMessage {
export const enum ReviewMessage { export const enum ReviewMessage {
NOT_FOUND = 'نظر یافت نشد', NOT_FOUND = 'نظر یافت نشد',
ALREADY_COMMENTED = 'شما قبلاً برای این غذا نظر داده‌اید', ALREADY_COMMENTED = 'شما قبلاً برای این محصول نظر داده‌اید',
CAN_ONLY_UPDATE_OWN = 'شما فقط می‌توانید نظرات خود را ویرایش کنید', CAN_ONLY_UPDATE_OWN = 'شما فقط می‌توانید نظرات خود را ویرایش کنید',
CAN_ONLY_DELETE_OWN = 'شما فقط می‌توانید نظرات خود را حذف کنید', CAN_ONLY_DELETE_OWN = 'شما فقط می‌توانید نظرات خود را حذف کنید',
RATING_REQUIRED = 'امتیاز الزامی است', RATING_REQUIRED = 'امتیاز الزامی است',
@@ -683,7 +683,7 @@ export const enum OrderMessage {
DELIVERY_METHOD_NOT_FOUND = 'روش ارسال یافت نشد', DELIVERY_METHOD_NOT_FOUND = 'روش ارسال یافت نشد',
PAYMENT_METHOD_REQUIRED = 'روش پرداخت الزامی است. لطفا قبل از ایجاد سفارش، روش پرداخت را تنظیم کنید', PAYMENT_METHOD_REQUIRED = 'روش پرداخت الزامی است. لطفا قبل از ایجاد سفارش، روش پرداخت را تنظیم کنید',
USER_NOT_FOUND = 'کاربر یافت نشد', USER_NOT_FOUND = 'کاربر یافت نشد',
RESTAURANT_NOT_FOUND = 'رستوران یافت نشد', SHOP_NOT_FOUND = 'فروشگاه یافت نشد',
DELIVERY_NOT_FOUND = 'روش ارسال یافت نشد', DELIVERY_NOT_FOUND = 'روش ارسال یافت نشد',
MIN_ORDER_AMOUNT_NOT_MET = 'مبلغ سفارش کمتر از حداقل مجاز برای این روش ارسال است', MIN_ORDER_AMOUNT_NOT_MET = 'مبلغ سفارش کمتر از حداقل مجاز برای این روش ارسال است',
TABLE_NUMBER_REQUIRED = 'شماره میز برای سفارش در محل الزامی است', TABLE_NUMBER_REQUIRED = 'شماره میز برای سفارش در محل الزامی است',
@@ -691,8 +691,8 @@ export const enum OrderMessage {
CAR_ADDRESS_REQUIRED = 'آدرس خودرو الزامی است. لطفا قبل از ایجاد سفارش، آدرس خودرو را تنظیم کنید', CAR_ADDRESS_REQUIRED = 'آدرس خودرو الزامی است. لطفا قبل از ایجاد سفارش، آدرس خودرو را تنظیم کنید',
PAYMENT_METHOD_NOT_FOUND = 'روش پرداخت یافت نشد', PAYMENT_METHOD_NOT_FOUND = 'روش پرداخت یافت نشد',
PAYMENT_METHOD_NOT_ENABLED = 'روش پرداخت برای این رستوران فعال نیست', PAYMENT_METHOD_NOT_ENABLED = 'روش پرداخت برای این رستوران فعال نیست',
FOOD_NOT_FOUND = 'غذا یافت نشد', PRODUCT_NOT_FOUND = 'محصول یافت نشد',
FOOD_NOT_BELONGS_TO_RESTAURANT = 'غذا به این رستوران تعلق ندارد', PRODUCT_NOT_BELONGS_TO_SHOP = 'محصول به این فروشگاه تعلق ندارد',
} }
export const enum CartMessage { export const enum CartMessage {
@@ -701,30 +701,30 @@ export const enum CartMessage {
ADDRESS_REQUIRED = 'آدرس برای تحویل پیک الزامی است', ADDRESS_REQUIRED = 'آدرس برای تحویل پیک الزامی است',
NOT_FOUND = 'سبد خرید یافت نشد', NOT_FOUND = 'سبد خرید یافت نشد',
COUPON_NOT_FOUND = 'کوپن یافت نشد', COUPON_NOT_FOUND = 'کوپن یافت نشد',
FOOD_NOT_FOUND = 'غذا یافت نشد', PRODUCT_NOT_FOUND = 'محصول یافت نشد',
FOOD_NOT_BELONGS_TO_RESTAURANT = 'غذا به این رستوران تعلق ندارد', PRODUCT_NOT_BELONGS_TO_SHOP = 'محصول به این فروشگاه تعلق ندارد',
FOOD_NO_INVENTORY = 'غذا موجودی ندارد', PRODUCT_NO_INVENTORY = 'محصول موجودی ندارد',
INSUFFICIENT_STOCK = 'موجودی کافی نیست', INSUFFICIENT_STOCK = 'موجودی کافی نیست',
USER_NOT_FOUND = 'کاربر یافت نشد', USER_NOT_FOUND = 'کاربر یافت نشد',
ADDRESS_NOT_FOUND = 'آدرس یافت نشد', ADDRESS_NOT_FOUND = 'آدرس یافت نشد',
RESTAURANT_NOT_FOUND = 'رستوران یافت نشد', SHOP_NOT_FOUND = 'فروشگاه یافت نشد',
ADDRESS_NOT_BELONGS_TO_USER = 'آدرس به کاربر فعلی تعلق ندارد', ADDRESS_NOT_BELONGS_TO_USER = 'آدرس به کاربر فعلی تعلق ندارد',
ADDRESS_NO_COORDINATES = 'آدرس مختصات جغرافیایی ندارد', ADDRESS_NO_COORDINATES = 'آدرس مختصات جغرافیایی ندارد',
ADDRESS_OUTSIDE_SERVICE_AREA = 'آدرس خارج از محدوده سرویس رستوران است', ADDRESS_OUTSIDE_SERVICE_AREA = 'آدرس خارج از محدوده سرویس رستوران است',
DELIVERY_METHOD_NOT_FOUND = 'روش ارسال یافت نشد', DELIVERY_METHOD_NOT_FOUND = 'روش ارسال یافت نشد',
DELIVERY_METHOD_NOT_ENABLED = 'روش ارسال برای این رستوران فعال نیست', DELIVERY_METHOD_NOT_ENABLED = 'روش ارسال برای این فروشگاه فعال نیست',
PAYMENT_METHOD_NOT_FOUND = 'روش پرداخت یافت نشد', PAYMENT_METHOD_NOT_FOUND = 'روش پرداخت یافت نشد',
PAYMENT_METHOD_NOT_ENABLED = 'روش پرداخت برای این رستوران فعال نیست', PAYMENT_METHOD_NOT_ENABLED = 'روش پرداخت برای این رستوران فعال نیست',
WALLET_NOT_FOUND = 'کیف پول کاربر یافت نشد', WALLET_NOT_FOUND = 'کیف پول کاربر یافت نشد',
WALLET_INSUFFICIENT = 'موجودی کیف پول کافی نیست', WALLET_INSUFFICIENT = 'موجودی کیف پول کافی نیست',
COUPON_MAX_USES_REACHED = 'حداکثر استفاده از کوپن به پایان رسیده است', COUPON_MAX_USES_REACHED = 'حداکثر استفاده از کوپن به پایان رسیده است',
ITEM_NOT_FOUND = 'آیتم در سبد خرید یافت نشد', ITEM_NOT_FOUND = 'آیتم در سبد خرید یافت نشد',
DELIVERY_METHOD_NOT_FOUND_FOR_RESTAURANT = 'روش ارسال برای این رستوران یافت نشد', DELIVERY_METHOD_NOT_FOUND_FOR_SHOP = 'روش ارسال برای این فروشگاه یافت نشد',
COUPON_CANNOT_BE_APPLIED = 'این کوپن قابل اعمال بر روی آیتم‌های سبد خرید شما نیست. لطفا آیتم‌هایی از دسته‌بندی‌ها یا غذاهای مشخص شده اضافه کنید', COUPON_CANNOT_BE_APPLIED = 'این کوپن قابل اعمال بر روی آیتم‌های سبد خرید شما نیست. لطفا آیتم‌هایی از دسته‌بندی‌ها یا غذاهای مشخص شده اضافه کنید',
FOODS_MUST_HAVE_PICKUP_SERVE_FOR_COURIER = 'تمام غذاها باید قابلیت تحویل پیک داشته باشند. غذاهای زیر این قابلیت را ندارند: [foodTitles]', PRODUCTS_MUST_HAVE_PICKUP_SERVE_FOR_COURIER = 'تمام محصولات باید قابلیت تحویل پیک داشته باشند. محصولات زیر این قابلیت را ندارند: [productTitles]',
FOOD_ONLY_AVAILABLE_DURING_MEAL_TIMES = 'غذا [foodTitle] فقط در ساعات وعده‌های غذایی (صبحانه، ناهار، عصرانه یا شام) در دسترس است. زمان فعلی خارج از ساعات وعده‌های غذایی است', PRODUCT_ONLY_AVAILABLE_DURING_MEAL_TIMES = 'محصول [productTitle] فقط در ساعات وعده‌های غذایی (صبحانه، ناهار، عصرانه یا شام) در دسترس است. زمان فعلی خارج از ساعات وعده‌های غذایی است',
FOOD_ONLY_AVAILABLE_FOR_MEAL_TYPES = 'غذا [foodTitle] فقط برای [allowedMealTypes] در دسترس است. زمان فعلی [mealType] است که با این غذا سازگار نیست', PRODUCT_ONLY_AVAILABLE_FOR_MEAL_TYPES = 'محصول [productTitle] فقط برای [allowedMealTypes] در دسترس است. زمان فعلی [mealType] است که با این محصول سازگار نیست',
FOOD_ONLY_AVAILABLE_ON_DAYS = 'غذا [foodTitle] فقط در روزهای [allowedDays] در دسترس است. امروز [currentDay] است که این غذا در دسترس نیست', PRODUCT_ONLY_AVAILABLE_ON_DAYS = 'محصول [productTitle] فقط در روزهای [allowedDays] در دسترس است. امروز [currentDay] است که این محصول در دسترس نیست',
} }
export const enum PaymentMessage { export const enum PaymentMessage {
@@ -734,7 +734,7 @@ export const enum PaymentMessage {
PAYMENT_METHOD_NOT_FOUND = 'روش پرداخت یافت نشد', PAYMENT_METHOD_NOT_FOUND = 'روش پرداخت یافت نشد',
GATEWAY_REQUIRED = 'درگاه پرداخت برای پرداخت آنلاین الزامی است', GATEWAY_REQUIRED = 'درگاه پرداخت برای پرداخت آنلاین الزامی است',
MERCHANT_ID_REQUIRED = 'شناسه مرچنت برای پرداخت آنلاین الزامی است', MERCHANT_ID_REQUIRED = 'شناسه مرچنت برای پرداخت آنلاین الزامی است',
RESTAURANT_DOMAIN_REQUIRED = 'دامنه رستوران برای پرداخت آنلاین الزامی است', SHOP_DOMAIN_REQUIRED = 'دامنه فروشگاه برای پرداخت آنلاین الزامی است',
WALLET_NOT_FOUND = 'کیف پول کاربر یافت نشد', WALLET_NOT_FOUND = 'کیف پول کاربر یافت نشد',
INSUFFICIENT_WALLET_BALANCE = 'موجودی کیف پول کافی نیست', INSUFFICIENT_WALLET_BALANCE = 'موجودی کیف پول کافی نیست',
PAYMENT_NOT_FOUND = 'پرداخت یافت نشد', PAYMENT_NOT_FOUND = 'پرداخت یافت نشد',
@@ -749,16 +749,16 @@ export const enum PaymentMessage {
} }
export const enum DeliveryMessage { export const enum DeliveryMessage {
RESTAURANT_NOT_FOUND = 'رستوران یافت نشد', SHOP_NOT_FOUND = 'فروشگاه یافت نشد',
DELIVERY_METHOD_NOT_FOUND = 'روش ارسال یافت نشد', DELIVERY_METHOD_NOT_FOUND = 'روش ارسال یافت نشد',
} }
export const enum InventoryMessage { export const enum InventoryMessage {
AVAILABLE_STOCK_EXCEEDS_TOTAL = 'موجودی موجود نمی‌تواند از موجودی کل بیشتر باشد', AVAILABLE_STOCK_EXCEEDS_TOTAL = 'موجودی موجود نمی‌تواند از موجودی کل بیشتر باشد',
FOOD_NOT_FOUND = 'غذا یافت نشد', PRODUCT_NOT_FOUND = 'محصول یافت نشد',
FOOD_NOT_BELONGS_TO_RESTAURANT = 'غذا به این رستوران تعلق ندارد', PRODUCT_NOT_BELONGS_TO_SHOP = 'محصول به این فروشگاه تعلق ندارد',
RESTAURANT_NOT_FOUND = 'رستوران یافت نشد', SHOP_NOT_FOUND = 'فروشگاه یافت نشد',
FOODS_NOT_FOUND = 'غذاها یافت نشدند', PRODUCTS_NOT_FOUND = 'محصولات یافت نشدند',
} }
+1 -1
View File
@@ -4,7 +4,7 @@
*/ */
export enum Permission { export enum Permission {
MANAGE_PAGER = 'manage_pager', MANAGE_PAGER = 'manage_pager',
// Food Management // Product Management
MANAGE_FOODS = 'manage_foods', MANAGE_FOODS = 'manage_foods',
MANAGE_CATEGORIES = 'manage_categories', MANAGE_CATEGORIES = 'manage_categories',
@@ -2,7 +2,7 @@
// import { FastifyRequest } from 'fastify'; // import { FastifyRequest } from 'fastify';
// import { Observable, map } from 'rxjs'; // import { Observable, map } from 'rxjs';
// import { IPageFormat } from '../../../../danak_dsc_api/src/common/interfaces/IPagination'; // import { IPageFormat } from '../../../../../danak_dsc_api/src/common/interfaces/IPagination';
// @Injectable() // @Injectable()
// export class PaginationInterceptor implements NestInterceptor { // export class PaginationInterceptor implements NestInterceptor {
+1 -1
View File
@@ -9,7 +9,7 @@ import { Permission } from '../roles/entities/permission.entity';
import { RolePermission } from '../roles/entities/rolePermission.entity'; import { RolePermission } from '../roles/entities/rolePermission.entity';
import { AdminController } from './controllers/admin.controller'; import { AdminController } from './controllers/admin.controller';
import { UtilsModule } from '../utils/utils.module'; import { UtilsModule } from '../utils/utils.module';
import { RestaurantsModule } from '../restaurants/restaurants.module'; import { RestaurantsModule } from ../../..shops.module';
import { AdminRole } from './entities/adminRole.entity'; import { AdminRole } from './entities/adminRole.entity';
@Module({ @Module({
@@ -10,7 +10,7 @@ import { AdminId } from 'src/common/decorators/admin-id.decorator';
import { Admin } from '../entities/admin.entity'; import { Admin } from '../entities/admin.entity';
import { Permission } from 'src/common/enums/permission.enum'; import { Permission } from 'src/common/enums/permission.enum';
import { Permissions } from 'src/common/decorators/permissions.decorator'; import { Permissions } from 'src/common/decorators/permissions.decorator';
import { CreateMyRestaurantAdminDto } from '../dto/create-my-restaurant-admin.dto'; import { CreateMyRestaurantAdminDto } from '../dto/create-my-shop-admin.dto';
@ApiBearerAuth() @ApiBearerAuth()
@ApiTags('admin') @ApiTags('admin')
@@ -77,17 +77,17 @@ export class AdminController {
/** Super Admin Endpoints */ /** Super Admin Endpoints */
@UseGuards(SuperAdminAuthGuard) @UseGuards(SuperAdminAuthGuard)
@Get('super-admin/restaurants/:restaurantId/admins') @Get('super-admin/shops/:restaurantId/admins')
@ApiOperation({ summary: 'Get admins for a specific restaurant (Super Admin only)' }) @ApiOperation({ summary: 'Get admins for a specific shop (Super Admin only)' })
@ApiParam({ name: 'restaurantId', description: 'Restaurant ID' }) @ApiParam({ name: 'restaurantId', description: 'Shop ID' })
getAdminForRestaurant(@Param('restaurantId') restaurantId: string): Promise<Admin[]> { getAdminForRestaurant(@Param('restaurantId') restaurantId: string): Promise<Admin[]> {
return this.adminService.getAdminsForRestaurantBySuperAdmin(restaurantId); return this.adminService.getAdminsForRestaurantBySuperAdmin(restaurantId);
} }
@UseGuards(SuperAdminAuthGuard) @UseGuards(SuperAdminAuthGuard)
@Post('super-admin/restaurants/:restaurantId/admins') @Post('super-admin/shops/:restaurantId/admins')
@ApiOperation({ summary: 'Create admin for a specific restaurant (Super Admin only)' }) @ApiOperation({ summary: 'Create admin for a specific shop (Super Admin only)' })
@ApiParam({ name: 'restaurantId', description: 'Restaurant ID' }) @ApiParam({ name: 'restaurantId', description: 'Shop ID' })
@ApiBody({ type: CreateMyRestaurantAdminDto }) @ApiBody({ type: CreateMyRestaurantAdminDto })
createAdminForRestaurant( createAdminForRestaurant(
@Param('restaurantId') restaurantId: string, @Param('restaurantId') restaurantId: string,
@@ -97,9 +97,9 @@ export class AdminController {
} }
@UseGuards(SuperAdminAuthGuard) @UseGuards(SuperAdminAuthGuard)
@Delete('super-admin/restaurants/:restaurantId/admins/:adminId') @Delete('super-admin/shops/:restaurantId/admins/:adminId')
@ApiOperation({ summary: 'Revoke admin role from a restaurant (Super Admin only)' }) @ApiOperation({ summary: 'Revoke admin role from a shop (Super Admin only)' })
@ApiParam({ name: 'restaurantId', description: 'Restaurant ID' }) @ApiParam({ name: 'restaurantId', description: 'Shop ID' })
@ApiParam({ name: 'adminId', description: 'Admin ID' }) @ApiParam({ name: 'adminId', description: 'Admin ID' })
revokeAdminFromRestaurant( revokeAdminFromRestaurant(
@Param('restaurantId') restaurantId: string, @Param('restaurantId') restaurantId: string,
+2 -2
View File
@@ -1,7 +1,7 @@
import { Cascade, Collection, Entity, OneToMany, Property } from '@mikro-orm/core'; import { Cascade, Collection, Entity, OneToMany, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { AdminRole } from './adminRole.entity'; import { AdminRole } from './adminRole.entity';
import { normalizePhone } from '../../utils/phone.util'; import { normalizePhone } from '../../../utils/phone.util';
@Entity({ tableName: 'admins' }) @Entity({ tableName: 'admins' })
export class Admin extends BaseEntity { export class Admin extends BaseEntity {
@@ -1,14 +1,14 @@
import { Entity, Index, ManyToOne, Unique } from '@mikro-orm/core'; import { Entity, Index, ManyToOne, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { Role } from '../../roles/entities/role.entity'; import { Role } from '../../../roles/entities/role.entity';
import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { Admin } from './admin.entity'; import { Admin } from './admin.entity';
@Entity({ tableName: 'admin_roles' }) @Entity({ tableName: 'admin_roles' })
@Unique({ properties: ['admin', 'restaurant'] }) @Unique({ properties: ['admin', 'shop'] })
@Index({ properties: ['admin', 'restaurant'] }) @Index({ properties: ['admin', 'shop'] })
@Index({ properties: ['admin'] }) @Index({ properties: ['admin'] })
@Index({ properties: ['restaurant'] }) @Index({ properties: ['shop'] })
export class AdminRole extends BaseEntity { export class AdminRole extends BaseEntity {
@ManyToOne(() => Admin) @ManyToOne(() => Admin)
admin!: Admin; admin!: Admin;
@@ -16,6 +16,6 @@ export class AdminRole extends BaseEntity {
@ManyToOne(() => Role) @ManyToOne(() => Role)
role!: Role; role!: Role;
@ManyToOne(() => Restaurant, { nullable: true }) @ManyToOne(() => Shop, { nullable: true })
restaurant?: Restaurant; shop?: Shop;
} }
+26 -26
View File
@@ -2,14 +2,14 @@ import { ConflictException, Injectable, NotFoundException } from '@nestjs/common
import { InjectRepository } from '@mikro-orm/nestjs'; import { InjectRepository } from '@mikro-orm/nestjs';
import { EntityRepository } from '@mikro-orm/core'; import { EntityRepository } from '@mikro-orm/core';
import { Admin } from '../entities/admin.entity'; import { Admin } from '../entities/admin.entity';
import { Role } from '../../roles/entities/role.entity'; import { Role } from '../../../roles/entities/role.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { CacheService } from '../../utils/cache.service'; import { CacheService } from '../../../utils/cache.service';
import { CreateMyRestaurantAdminDto } from '../dto/create-my-restaurant-admin.dto'; import { CreateMyRestaurantAdminDto } from '../dto/create-my-shop-admin.dto';
import { UpdateAdminDto } from '../dto/update-admin.dto'; import { UpdateAdminDto } from '../dto/update-admin.dto';
import { AdminRole } from '../entities/adminRole.entity'; import { AdminRole } from '../entities/adminRole.entity';
import { normalizePhone } from '../../utils/phone.util'; import { normalizePhone } from '../../../utils/phone.util';
@Injectable() @Injectable()
export class AdminService { export class AdminService {
@@ -29,14 +29,14 @@ export class AdminService {
async findById(adminId: string, restId: string): Promise<Admin | null> { async findById(adminId: string, restId: string): Promise<Admin | null> {
const admin = await this.adminRepository.findOne( const admin = await this.adminRepository.findOne(
{ id: adminId, roles: { restaurant: { id: restId } } }, { id: adminId, roles: { shop: { id: restId } } },
{ populate: ['roles', 'roles.role'] }, { populate: ['roles', 'roles.role'] },
); );
return admin; return admin;
} }
async findAllByRestaurantId(restId: string): Promise<Admin[]> { async findAllByRestaurantId(restId: string): Promise<Admin[]> {
return this.adminRepository.find({ roles: { restaurant: { id: restId } } }, { populate: ['roles', 'roles.role'] }); return this.adminRepository.find({ roles: { shop: { id: restId } } }, { populate: ['roles', 'roles.role'] });
} }
async createAdminForMyRestaurant(restId: string, dto: CreateMyRestaurantAdminDto) { async createAdminForMyRestaurant(restId: string, dto: CreateMyRestaurantAdminDto) {
@@ -57,19 +57,19 @@ export class AdminService {
const role = await this.em.findOne(Role, { id: roleId, isSystem: false }); const role = await this.em.findOne(Role, { id: roleId, isSystem: false });
if (!role) throw new NotFoundException('Role not found'); if (!role) throw new NotFoundException('Role not found');
const restaurant = await this.em.findOne(Restaurant, { id: restId }); const shop = await this.em.findOne(Shop, { id: restId });
if (!restaurant) throw new NotFoundException('Restaurant not found'); if (!shop) throw new NotFoundException('Shop not found');
let adminRole = await this.adminRoleRepository.findOne({ let adminRole = await this.adminRoleRepository.findOne({
admin: admin, admin: admin,
restaurant: restaurant, shop: shop,
}); });
if (!adminRole) { if (!adminRole) {
adminRole = this.adminRoleRepository.create({ adminRole = this.adminRoleRepository.create({
admin: admin, admin: admin,
role, role,
restaurant, shop,
}); });
} else { } else {
adminRole.role = role; adminRole.role = role;
@@ -97,19 +97,19 @@ export class AdminService {
const role = await this.em.findOne(Role, { id: roleId }); const role = await this.em.findOne(Role, { id: roleId });
if (!role) throw new NotFoundException('Role* not found' + roleId); if (!role) throw new NotFoundException('Role* not found' + roleId);
const restaurant = await this.em.findOne(Restaurant, { id: restId }); const shop = await this.em.findOne(Shop, { id: restId });
if (!restaurant) throw new NotFoundException('Restaurant not found'); if (!shop) throw new NotFoundException('Shop not found');
let adminRole = await this.adminRoleRepository.findOne({ let adminRole = await this.adminRoleRepository.findOne({
admin: admin, admin: admin,
restaurant: restaurant, shop: shop,
}); });
if (!adminRole) { if (!adminRole) {
adminRole = this.adminRoleRepository.create({ adminRole = this.adminRoleRepository.create({
admin: admin, admin: admin,
role, role,
restaurant, shop,
}); });
} else { } else {
adminRole.role = role; adminRole.role = role;
@@ -120,16 +120,16 @@ export class AdminService {
} }
async getAdminsForRestaurantBySuperAdmin(restId: string): Promise<Admin[]> { async getAdminsForRestaurantBySuperAdmin(restId: string): Promise<Admin[]> {
const restaurant = await this.em.findOne(Restaurant, { id: restId }); const shop = await this.em.findOne(Shop, { id: restId });
if (!restaurant) throw new NotFoundException('Restaurant not found'); if (!shop) throw new NotFoundException('Shop not found');
return this.adminRepository.find({ roles: { restaurant: restaurant } }, { populate: ['roles', 'roles.role'] }); return this.adminRepository.find({ roles: { shop: shop } }, { populate: ['roles', 'roles.role'] });
} }
async update(adminId: string, restId: string, dto: UpdateAdminDto): Promise<Admin> { async update(adminId: string, restId: string, dto: UpdateAdminDto): Promise<Admin> {
const admin = await this.adminRepository.findOne( const admin = await this.adminRepository.findOne(
{ id: adminId, roles: { restaurant: { id: restId } } }, { id: adminId, roles: { shop: { id: restId } } },
{ populate: ['roles', 'roles.role', 'roles.restaurant'] }, { populate: ['roles', 'roles.role', 'roles.shop'] },
); );
if (!admin) { if (!admin) {
@@ -161,23 +161,23 @@ export class AdminService {
throw new NotFoundException('Role not found'); throw new NotFoundException('Role not found');
} }
// Find existing AdminRole for this admin and restaurant // Find existing AdminRole for this admin and shop
const existingAdminRole = await this.em.findOne(AdminRole, { const existingAdminRole = await this.em.findOne(AdminRole, {
admin: { id: adminId }, admin: { id: adminId },
restaurant: { id: restId }, shop: { id: restId },
}); });
if (existingAdminRole) { if (existingAdminRole) {
// Update existing role // Update existing role
existingAdminRole.role = role; existingAdminRole.role = role;
} else { } else {
const restaurant = await this.em.findOne(Restaurant, { id: restId }); const shop = await this.em.findOne(Shop, { id: restId });
if (!restaurant) throw new NotFoundException('Restaurant not found'); if (!shop) throw new NotFoundException('Shop not found');
// Create new AdminRole // Create new AdminRole
const newAdminRole = this.em.create(AdminRole, { const newAdminRole = this.em.create(AdminRole, {
admin, admin,
role, role,
restaurant, shop,
}); });
admin.roles.add(newAdminRole); admin.roles.add(newAdminRole);
} }
@@ -188,7 +188,7 @@ export class AdminService {
} }
async remove(adminId: string, restId: string): Promise<void> { async remove(adminId: string, restId: string): Promise<void> {
const adminRole = await this.adminRoleRepository.findOne({ admin: { id: adminId }, restaurant: { id: restId } }); const adminRole = await this.adminRoleRepository.findOne({ admin: { id: adminId }, shop: { id: restId } });
if (!adminRole) { if (!adminRole) {
throw new NotFoundException('Admin role not found'); throw new NotFoundException('Admin role not found');
} }
@@ -1,9 +1,9 @@
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
import { Admin } from '../entities/admin.entity'; import { Admin } from '../entities/admin.entity';
import { AdminRole } from '../entities/adminRole.entity'; import { AdminRole } from '../entities/adminRole.entity';
import { RestRepository } from '../../restaurants/repositories/rest.repository'; import { RestRepository } from ../../..shops/repositories/rest.repository';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { normalizePhone } from '../../utils/phone.util'; import { normalizePhone } from '../../../utils/phone.util';
@Injectable() @Injectable()
export class AdminRepository extends EntityRepository<Admin> { export class AdminRepository extends EntityRepository<Admin> {
@@ -17,20 +17,20 @@ export class AdminRepository extends EntityRepository<Admin> {
async findByPhoneAndRestaurantSlug(phone: string, slug: string): Promise<Admin | null> { async findByPhoneAndRestaurantSlug(phone: string, slug: string): Promise<Admin | null> {
console.log('phone', phone); console.log('phone', phone);
const normalizedPhone = normalizePhone(phone); const normalizedPhone = normalizePhone(phone);
// First, find the restaurant by slug using the same repository as auth service // First, find the shop by slug using the same repository as auth service
const restaurant = await this.restRepository.findOne({ slug }); const shop = await this.restRepository.findOne({ slug });
if (!restaurant) { if (!shop) {
return null; return null;
} }
// Find AdminRole that matches the admin phone and restaurant // Find AdminRole that matches the admin phone and shop
const adminRole = await this.em.findOne( const adminRole = await this.em.findOne(
AdminRole, AdminRole,
{ {
admin: { phone: normalizedPhone }, admin: { phone: normalizedPhone },
restaurant: { id: restaurant.id }, shop: { id: shop.id },
}, },
{ populate: ['admin', 'admin.roles', 'role', 'role.permissions', 'restaurant'] }, { populate: ['admin', 'admin.roles', 'role', 'role.permissions', 'shop'] },
); );
if (!adminRole || !adminRole.admin) { if (!adminRole || !adminRole.admin) {
@@ -50,7 +50,7 @@ export class AdminRepository extends EntityRepository<Admin> {
async findAdminsWithPermission(restaurantId: string, permission: string): Promise<Admin[]> { async findAdminsWithPermission(restaurantId: string, permission: string): Promise<Admin[]> {
const admins = await this.em.find( const admins = await this.em.find(
Admin, Admin,
{ roles: { restaurant: { id: restaurantId }, role: { permissions: { name: permission } } } }, { roles: { shop: { id: restaurantId }, role: { permissions: { name: permission } } } },
{ populate: ['roles', 'roles.role', 'roles.role.permissions'] }, { populate: ['roles', 'roles.role', 'roles.role.permissions'] },
); );
return admins; return admins;
@@ -10,7 +10,7 @@ export interface AdminDetailResponse {
name: string; name: string;
}; };
permissions: string[]; permissions: string[];
restaurant?: { shop?: {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
@@ -38,10 +38,10 @@ export class AdminDetailTransformer {
name: role.name, name: role.name,
}, },
permissions: role.permissions.getItems().map(p => p.name) || [], permissions: role.permissions.getItems().map(p => p.name) || [],
restaurant: { shop: {
id: role.restaurant?.id || '', id: role.shop?.id || '',
name: role.restaurant?.name || '', name: role.shop?.name || '',
slug: role.restaurant?.slug || '', slug: role.shop?.slug || '',
}, },
}; };
} }
+1 -1
View File
@@ -7,7 +7,7 @@ import { JwtModule } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { AdminModule } from '../admin/admin.module'; import { AdminModule } from '../admin/admin.module';
import { TokensService } from './services/tokens.service'; import { TokensService } from './services/tokens.service';
import { RestaurantsModule } from '../restaurants/restaurants.module'; import { RestaurantsModule } from ../../..shops.module';
import { MikroOrmModule } from '@mikro-orm/nestjs'; import { MikroOrmModule } from '@mikro-orm/nestjs';
import { User } from '../users/entities/user.entity'; import { User } from '../users/entities/user.entity';
import { AdminAuthGuard } from './guards/adminAuth.guard'; import { AdminAuthGuard } from './guards/adminAuth.guard';
@@ -63,7 +63,7 @@ export class AuthController {
@UseGuards(SuperAdminAuthGuard) @UseGuards(SuperAdminAuthGuard)
@Post('super-admin/auth/direct-login') @Post('super-admin/auth/direct-login')
@ApiOperation({ summary: 'Direct login for DSC - returns login credentials' }) @ApiOperation({ summary: 'Direct login for DSC - returns login credentials' })
@ApiBody({ type: DirectLoginDto, description: 'Phone number and restaurant slug for direct login' }) @ApiBody({ type: DirectLoginDto, description: 'Phone number and shop slug for direct login' })
directloginForDsc(@Body() dto: DirectLoginDto) { directloginForDsc(@Body() dto: DirectLoginDto) {
return this.authService.loginAdminForDsc(dto.phone, dto.slug); return this.authService.loginAdminForDsc(dto.phone, dto.slug);
} }
+1 -1
View File
@@ -10,6 +10,6 @@ export class DirectLoginDto {
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
@ApiProperty({ example: 'zhivan', description: 'Restaurant slug' }) @ApiProperty({ example: 'zhivan', description: 'Shop slug' })
slug: string; slug: string;
} }
+1 -1
View File
@@ -10,6 +10,6 @@ export class RequestOtpDto {
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
@ApiProperty({ example: 'zhivan', description: 'restaurant slug' }) @ApiProperty({ example: 'zhivan', description: 'shop slug' })
slug: string; slug: string;
} }
+1 -1
View File
@@ -16,6 +16,6 @@ export class VerifyOtpDto {
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
@ApiProperty({ example: 'zhivan', description: 'restaurant slug' }) @ApiProperty({ example: 'zhivan', description: 'shop slug' })
slug: string; slug: string;
} }
@@ -1,6 +1,6 @@
import { Entity, Enum, Index, Property } from '@mikro-orm/core'; import { Entity, Enum, Index, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
export enum RefreshTokenType { export enum RefreshTokenType {
USER = 'user', USER = 'user',
+1 -1
View File
@@ -12,7 +12,7 @@ import { Request } from 'express';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { IAdminTokenPayload } from '../interfaces/IToken-payload'; import { IAdminTokenPayload } from '../interfaces/IToken-payload';
import { PERMISSIONS_KEY } from '../../../common/decorators/permissions.decorator'; import { PERMISSIONS_KEY } from '../../../../common/decorators/permissions.decorator';
import { PermissionsService } from 'src/modules/roles/providers/permissions.service'; import { PermissionsService } from 'src/modules/roles/providers/permissions.service';
export interface AdminAuthRequest extends Request { export interface AdminAuthRequest extends Request {
+5 -5
View File
@@ -1,17 +1,17 @@
import { Injectable, BadRequestException } from '@nestjs/common'; import { Injectable, BadRequestException } from '@nestjs/common';
import { RequestOtpDto } from '../dto/request-otp.dto'; import { RequestOtpDto } from '../dto/request-otp.dto';
import { CacheService } from '../../utils/cache.service'; import { CacheService } from '../../../utils/cache.service';
import { SmsService } from '../../notifications/services/sms.service'; import { SmsService } from '../../../notifications/services/sms.service';
import { UserService } from '../../users/providers/user.service'; import { UserService } from '../../../users/providers/user.service';
import { randomInt } from 'crypto'; import { randomInt } from 'crypto';
import { TokensService } from './tokens.service'; import { TokensService } from './tokens.service';
import { RestRepository } from 'src/modules/restaurants/repositories/rest.repository'; import { RestRepository } from ../../..shops/repositories/rest.repository';
import { AuthMessage, RestMessage } from 'src/common/enums/message.enum'; import { AuthMessage, RestMessage } from 'src/common/enums/message.enum';
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository'; import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
import { AdminLoginTransformer } from '../transformers/admin-login.transformer'; import { AdminLoginTransformer } from '../transformers/admin-login.transformer';
import { UserLoginTransformer } from '../transformers/user-login.transformer'; import { UserLoginTransformer } from '../transformers/user-login.transformer';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { normalizePhone } from '../../utils/phone.util'; import { normalizePhone } from '../../../utils/phone.util';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
+6 -6
View File
@@ -6,10 +6,10 @@ import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { AuthMessage } from '../../../common/enums/message.enum'; import { AuthMessage } from '../../../../common/enums/message.enum';
import { RefreshToken, RefreshTokenType } from '../entities/refresh-token.entity'; import { RefreshToken, RefreshTokenType } from '../entities/refresh-token.entity';
import { IAdminTokenPayload, ITokenPayload } from '../interfaces/IToken-payload'; import { IAdminTokenPayload, ITokenPayload } from '../interfaces/IToken-payload';
import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
@Injectable() @Injectable()
export class TokensService { export class TokensService {
@@ -109,16 +109,16 @@ export class TokensService {
const restId = token.restId; const restId = token.restId;
const isAdmin = token.type === RefreshTokenType.ADMIN; const isAdmin = token.type === RefreshTokenType.ADMIN;
// Verify restaurant still exists // Verify shop still exists
const restaurant = await em.findOne(Restaurant, { id: restId }); const shop = await em.findOne(Shop, { id: restId });
if (!restaurant) { if (!shop) {
throw new UnauthorizedException(AuthMessage.INVALID_REFRESH_TOKEN); throw new UnauthorizedException(AuthMessage.INVALID_REFRESH_TOKEN);
} }
try { try {
// Generate new tokens first (before removing old token) // Generate new tokens first (before removing old token)
// Pass the transaction EntityManager to ensure operations are within the transaction // Pass the transaction EntityManager to ensure operations are within the transaction
const tokens = await this.generateAccessAndRefreshToken(ownerId, restaurant.id, isAdmin, restaurant.slug, em); const tokens = await this.generateAccessAndRefreshToken(ownerId, shop.id, isAdmin, shop.slug, em);
// Remove old token only after new token is created // Remove old token only after new token is created
em.remove(token); em.remove(token);
@@ -1,5 +1,5 @@
import type { Admin } from '../../admin/entities/admin.entity'; import type { Admin } from '../../../admin/entities/admin.entity';
import type { Restaurant } from '../../restaurants/entities/restaurant.entity'; import type { Shop } from ../../..shops/entities/shop.entity';
export interface AdminLoginResponse { export interface AdminLoginResponse {
id: string; id: string;
@@ -8,7 +8,7 @@ export interface AdminLoginResponse {
phone: string; phone: string;
role: string; role: string;
permissions: string[]; permissions: string[];
restaurant?: { shop?: {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
@@ -16,9 +16,9 @@ export interface AdminLoginResponse {
} }
export class AdminLoginTransformer { export class AdminLoginTransformer {
static async transform(admin: Admin, restaurant?: Restaurant): Promise<AdminLoginResponse> { static async transform(admin: Admin, shop?: Shop): Promise<AdminLoginResponse> {
// Find the AdminRole that matches the restaurant (or get the first one) // Find the AdminRole that matches the shop (or get the first one)
let adminRole = admin.roles.getItems().find(r => (restaurant ? r.restaurant?.id === restaurant.id : true)); let adminRole = admin.roles.getItems().find(r => (shop ? r.shop?.id === shop.id : true));
// If no match found, get the first one // If no match found, get the first one
if (!adminRole && admin.roles.getItems().length > 0) { if (!adminRole && admin.roles.getItems().length > 0) {
@@ -33,11 +33,11 @@ export class AdminLoginTransformer {
phone: admin.phone, phone: admin.phone,
role: '', role: '',
permissions: [], permissions: [],
restaurant: restaurant shop: shop
? { ? {
id: restaurant.id, id: shop.id,
name: restaurant.name, name: shop.name,
slug: restaurant.slug, slug: shop.slug,
} }
: undefined, : undefined,
}; };
@@ -53,11 +53,11 @@ export class AdminLoginTransformer {
phone: admin.phone, phone: admin.phone,
role: '', role: '',
permissions: [], permissions: [],
restaurant: restaurant shop: shop
? { ? {
id: restaurant.id, id: shop.id,
name: restaurant.name, name: shop.name,
slug: restaurant.slug, slug: shop.slug,
} }
: undefined, : undefined,
}; };
@@ -81,11 +81,11 @@ export class AdminLoginTransformer {
phone: admin.phone, phone: admin.phone,
role: role.name, role: role.name,
permissions, permissions,
restaurant: restaurant shop: shop
? { ? {
id: restaurant.id, id: shop.id,
name: restaurant.name, name: shop.name,
slug: restaurant.slug, slug: shop.slug,
} }
: undefined, : undefined,
}; };
@@ -1,5 +1,5 @@
import type { User } from '../../users/entities/user.entity'; import type { User } from '../../../users/entities/user.entity';
import type { Restaurant } from '../../restaurants/entities/restaurant.entity'; import type { Shop } from ../../..shops/entities/shop.entity';
export interface UserLoginResponse { export interface UserLoginResponse {
id: string; id: string;
@@ -8,7 +8,7 @@ export interface UserLoginResponse {
phone: string; phone: string;
isActive?: boolean; isActive?: boolean;
restaurant: { shop: {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
@@ -16,17 +16,17 @@ export interface UserLoginResponse {
} }
export class UserLoginTransformer { export class UserLoginTransformer {
static transform(user: User, restaurant: Restaurant): UserLoginResponse { static transform(user: User, shop: Shop): UserLoginResponse {
return { return {
id: user.id, id: user.id,
firstName: user.firstName, firstName: user.firstName,
lastName: user.lastName, lastName: user.lastName,
phone: user.phone, phone: user.phone,
isActive: user.isActive, isActive: user.isActive,
restaurant: { shop: {
id: restaurant.id, id: shop.id,
name: restaurant.name, name: shop.name,
slug: restaurant.slug, slug: shop.slug,
}, },
}; };
} }
+4 -4
View File
@@ -2,8 +2,8 @@ import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs'; import { MikroOrmModule } from '@mikro-orm/nestjs';
import { CartService } from './providers/cart.service'; import { CartService } from './providers/cart.service';
import { CartController } from './controllers/cart.controller'; import { CartController } from './controllers/cart.controller';
import { Food } from '../foods/entities/food.entity'; import { Product } from '../products/entities/product.entity';
import { Restaurant } from '../restaurants/entities/restaurant.entity'; import { Shop } from '../shops/entities/shop.entity';
import { PaymentMethod } from '../payments/entities/payment-method.entity'; import { PaymentMethod } from '../payments/entities/payment-method.entity';
import { UserAddress } from '../users/entities/user-address.entity'; import { UserAddress } from '../users/entities/user-address.entity';
import { User } from '../users/entities/user.entity'; import { User } from '../users/entities/user.entity';
@@ -24,8 +24,8 @@ import { WalletTransaction } from '../users/entities/wallet-transaction.entity';
@Module({ @Module({
imports: [ imports: [
MikroOrmModule.forFeature([ MikroOrmModule.forFeature([
Food, Product,
Restaurant, Shop,
PaymentMethod, PaymentMethod,
UserAddress, UserAddress,
User, User,
@@ -3,7 +3,7 @@ import { CartService } from '../providers/cart.service';
import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto'; import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto';
import { ApplyCouponDto } from '../dto/apply-coupon.dto'; import { ApplyCouponDto } from '../dto/apply-coupon.dto';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiBody, ApiParam, ApiHeader } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiBody, ApiParam, ApiHeader } from '@nestjs/swagger';
import { AuthGuard } from '../../auth/guards/auth.guard'; import { AuthGuard } from '../../../auth/guards/auth.guard';
import { UserId } from 'src/common/decorators/user-id.decorator'; import { UserId } from 'src/common/decorators/user-id.decorator';
import { RestId } from 'src/common/decorators/rest-id.decorator'; import { RestId } from 'src/common/decorators/rest-id.decorator';
import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto'; import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto';
@@ -17,7 +17,7 @@ export class CartController {
constructor(private readonly cartService: CartService) {} constructor(private readonly cartService: CartService) {}
@Get() @Get()
@ApiOperation({ summary: 'Get cart for current user and restaurant' }) @ApiOperation({ summary: 'Get cart for current user and shop' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
async findOne(@UserId() userId: string, @RestId() restaurantId: string) { async findOne(@UserId() userId: string, @RestId() restaurantId: string) {
return this.cartService.getOrCreateCart(userId, restaurantId); return this.cartService.getOrCreateCart(userId, restaurantId);
@@ -26,7 +26,7 @@ export class CartController {
@Post('items/:foodId/increment') @Post('items/:foodId/increment')
@ApiOperation({ summary: 'Increment item quantity in cart by 1' }) @ApiOperation({ summary: 'Increment item quantity in cart by 1' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiParam({ name: 'foodId', description: 'Food ID to increment in cart' }) @ApiParam({ name: 'foodId', description: 'Product ID to increment in cart' })
async incrementItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('foodId') foodId: string) { async incrementItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('foodId') foodId: string) {
return this.cartService.incrementItem(userId, restaurantId, foodId, 1); return this.cartService.incrementItem(userId, restaurantId, foodId, 1);
} }
@@ -34,7 +34,7 @@ export class CartController {
@Post('items/:foodId/decrement') @Post('items/:foodId/decrement')
@ApiOperation({ summary: 'Decrement item quantity in cart by 1 (removes item if quantity reaches 0)' }) @ApiOperation({ summary: 'Decrement item quantity in cart by 1 (removes item if quantity reaches 0)' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiParam({ name: 'foodId', description: 'Food ID to decrement in cart' }) @ApiParam({ name: 'foodId', description: 'Product ID to decrement in cart' })
async decrementItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('foodId') foodId: string) { async decrementItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('foodId') foodId: string) {
return this.cartService.decrementItem(userId, restaurantId, foodId); return this.cartService.decrementItem(userId, restaurantId, foodId);
} }
@@ -54,7 +54,7 @@ export class CartController {
@Delete('items/:foodId') @Delete('items/:foodId')
@ApiOperation({ summary: 'Remove item from cart' }) @ApiOperation({ summary: 'Remove item from cart' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiParam({ name: 'foodId', description: 'Food ID in the cart' }) @ApiParam({ name: 'foodId', description: 'Product ID in the cart' })
async removeItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('foodId') foodId: string) { async removeItem(@UserId() userId: string, @RestId() restaurantId: string, @Param('foodId') foodId: string) {
return this.cartService.removeItem(userId, restaurantId, foodId); return this.cartService.removeItem(userId, restaurantId, foodId);
} }
+4 -4
View File
@@ -3,14 +3,14 @@ import { IsNotEmpty, IsArray, ValidateNested, ArrayMinSize, IsInt, Min, IsString
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
class AddItemToCartDto { class AddItemToCartDto {
@ApiProperty({ description: 'Quantity of the food item', example: 1, minimum: 1 }) @ApiProperty({ description: 'Quantity of the product item', example: 1, minimum: 1 })
@IsNotEmpty() @IsNotEmpty()
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@Min(1) @Min(1)
quantity!: number; quantity!: number;
@ApiProperty({ description: 'Food ID' }) @ApiProperty({ description: 'Product ID' })
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
foodId!: string; foodId!: string;
@@ -21,8 +21,8 @@ export class BulkAddItemsToCartDto {
description: 'Array of items to add to cart', description: 'Array of items to add to cart',
type: [AddItemToCartDto], type: [AddItemToCartDto],
example: [ example: [
{ foodId: 'food-123', quantity: 2 }, { foodId: 'product-123', quantity: 2 },
{ foodId: 'food-456', quantity: 1 }, { foodId: 'product-456', quantity: 1 },
], ],
}) })
@IsNotEmpty() @IsNotEmpty()
+2 -2
View File
@@ -3,12 +3,12 @@ import { IsNotEmpty, IsString, IsArray, ValidateNested, IsInt, Min } from 'class
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
export class CartItemDto { export class CartItemDto {
@ApiProperty({ description: 'Food ID' }) @ApiProperty({ description: 'Product ID' })
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
foodId!: string; foodId!: string;
@ApiProperty({ description: 'Quantity of the food item', example: 2, minimum: 1 }) @ApiProperty({ description: 'Quantity of the product item', example: 2, minimum: 1 })
@IsNotEmpty() @IsNotEmpty()
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from '../../shops/entities/shop.entity';
import { Delivery } from '../../delivery/entities/delivery.entity'; import { Delivery } from '../../delivery/entities/delivery.entity';
import { DeliveryFeeTypeEnum } from '../../delivery/interface/delivery'; import { DeliveryFeeTypeEnum } from '../../delivery/interface/delivery';
import { CouponType } from '../../coupons/interface/coupon'; import { CouponType } from '../../coupons/interface/coupon';
import { Food } from '../../foods/entities/food.entity'; import { Product } from '../../products/entities/product.entity';
import { Cart } from '../interfaces/cart.interface'; import { Cart } from '../interfaces/cart.interface';
import { CouponService } from '../../coupons/providers/coupon.service'; import { CouponService } from '../../../coupons/providers/coupon.service';
import { GeographicUtils } from '../utils/geographic.utils'; import { GeographicUtils } from '../utils/geographic.utils';
@Injectable() @Injectable()
@@ -59,7 +59,7 @@ export class CartCalculationService {
if (!cart.coupon || !coupon) return 0; if (!cart.coupon || !coupon) return 0;
const hasCategoryRestriction = (coupon.foodCategories?.length ?? 0) > 0; const hasCategoryRestriction = (coupon.foodCategories?.length ?? 0) > 0;
const hasFoodRestriction = (coupon.foods?.length ?? 0) > 0; const hasFoodRestriction = (coupon.products?.length ?? 0) > 0;
let eligibleItemsTotal = subTotal; let eligibleItemsTotal = subTotal;
let eligibleItemsDiscount = itemsDiscount; let eligibleItemsDiscount = itemsDiscount;
@@ -71,12 +71,12 @@ export class CartCalculationService {
const foodMap = await this.getFoodsInCartWithCategories(cart); const foodMap = await this.getFoodsInCartWithCategories(cart);
for (const item of cart.items) { for (const item of cart.items) {
const food = foodMap.get(item.foodId); const product = foodMap.get(item.foodId);
if (!food) continue; if (!product) continue;
const matchesCategory = const matchesCategory =
hasCategoryRestriction && food.category && (coupon.foodCategories ?? []).includes(food.category.id); hasCategoryRestriction && product.category && (coupon.foodCategories ?? []).includes(product.category.id);
const matchesFood = hasFoodRestriction && (coupon.foods ?? []).includes(food.id); const matchesFood = hasFoodRestriction && (coupon.products ?? []).includes(product.id);
if (matchesCategory || matchesFood) { if (matchesCategory || matchesFood) {
const unitPrice = Number(item.price) || 0; const unitPrice = Number(item.price) || 0;
@@ -104,8 +104,8 @@ export class CartCalculationService {
* Calculate tax * Calculate tax
*/ */
async calculateTax(restaurantId: string, amountAfterDiscounts: number): Promise<number> { async calculateTax(restaurantId: string, amountAfterDiscounts: number): Promise<number> {
const restaurant = await this.em.findOne(Restaurant, { id: restaurantId }); const shop = await this.em.findOne(Shop, { id: restaurantId });
const vat = restaurant?.vat ? Number(restaurant.vat) : 0; const vat = shop?.vat ? Number(shop.vat) : 0;
if (!vat || vat <= 0) return 0; if (!vat || vat <= 0) return 0;
return (Math.max(0, amountAfterDiscounts) * vat) / 100; return (Math.max(0, amountAfterDiscounts) * vat) / 100;
} }
@@ -117,7 +117,7 @@ export class CartCalculationService {
const deliveryMethodId = cart.deliveryMethodId; const deliveryMethodId = cart.deliveryMethodId;
if (!deliveryMethodId) return 0; if (!deliveryMethodId) return 0;
const deliveryMethod = await this.em.findOne(Delivery, { id: deliveryMethodId }, { populate: ['restaurant'] }); const deliveryMethod = await this.em.findOne(Delivery, { id: deliveryMethodId }, { populate: ['shop'] });
if (!deliveryMethod || !deliveryMethod.enabled) return 0; if (!deliveryMethod || !deliveryMethod.enabled) return 0;
// If not distance based, return fixed delivery fee // If not distance based, return fixed delivery fee
@@ -125,11 +125,11 @@ export class CartCalculationService {
return Number(deliveryMethod.deliveryFee) || 0; return Number(deliveryMethod.deliveryFee) || 0;
} }
// For distance based calculation we need restaurant and user coordinates // For distance based calculation we need shop and user coordinates
const restaurant = (deliveryMethod as any).restaurant as Restaurant | undefined; const shop = (deliveryMethod as any).shop as Shop | undefined;
const userAddr = cart.userAddress; const userAddr = cart.userAddress;
if (!restaurant || restaurant.latitude == null || restaurant.longitude == null) { if (!shop || shop.latitude == null || shop.longitude == null) {
// fallback to configured fixed fee when coordinates are missing // fallback to configured fixed fee when coordinates are missing
return Number(deliveryMethod.deliveryFee) || 0; return Number(deliveryMethod.deliveryFee) || 0;
} }
@@ -138,8 +138,8 @@ export class CartCalculationService {
return Number(deliveryMethod.deliveryFee) || 0; return Number(deliveryMethod.deliveryFee) || 0;
} }
const restLat = Number(restaurant.latitude); const restLat = Number(shop.latitude);
const restLng = Number(restaurant.longitude); const restLng = Number(shop.longitude);
const userLat = Number(userAddr.latitude); const userLat = Number(userAddr.latitude);
const userLng = Number(userAddr.longitude); const userLng = Number(userAddr.longitude);
@@ -190,7 +190,7 @@ export class CartCalculationService {
startDate?: Date; startDate?: Date;
endDate?: Date; endDate?: Date;
foodCategories?: string[]; foodCategories?: string[];
foods?: string[]; products?: string[];
} | null> { } | null> {
if (!cart.coupon) return null; if (!cart.coupon) return null;
@@ -215,7 +215,7 @@ export class CartCalculationService {
startDate: c.startDate, startDate: c.startDate,
endDate: c.endDate, endDate: c.endDate,
foodCategories: c.foodCategories, foodCategories: c.foodCategories,
foods: c.foods, products: c.products,
}; };
} catch { } catch {
cart.coupon = undefined; cart.coupon = undefined;
@@ -224,14 +224,14 @@ export class CartCalculationService {
} }
/** /**
* Get foods in cart with categories * Get products in cart with categories
*/ */
private async getFoodsInCartWithCategories(cart: Cart): Promise<Map<string, Food>> { private async getFoodsInCartWithCategories(cart: Cart): Promise<Map<string, Product>> {
const foodIds = cart.items.map(item => item.foodId); const foodIds = cart.items.map(item => item.foodId);
if (foodIds.length === 0) return new Map(); if (foodIds.length === 0) return new Map();
const foods = await this.em.find(Food, { id: { $in: foodIds } }, { populate: ['category'] }); const products = await this.em.find(Product, { id: { $in: foodIds } }, { populate: ['category'] });
return new Map(foods.map(f => [f.id, f])); return new Map(products.map(f => [f.id, f]));
} }
/** /**
+20 -20
View File
@@ -1,5 +1,5 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { Food } from '../../foods/entities/food.entity'; import { Product } from ../../..products/entities/product.entity';
import { Cart, CartItem } from '../interfaces/cart.interface'; import { Cart, CartItem } from '../interfaces/cart.interface';
import { CartValidationService } from './cart-validation.service'; import { CartValidationService } from './cart-validation.service';
import { CartCalculationService } from './cart-calculation.service'; import { CartCalculationService } from './cart-calculation.service';
@@ -13,15 +13,15 @@ export class CartItemService {
) {} ) {}
/** /**
* Create a new cart item from food * Create a new cart item from product
*/ */
createCartItem(food: Food, quantity: number): CartItem { createCartItem(product: Product, quantity: number): CartItem {
const itemPrice = food.price || 0; const itemPrice = product.price || 0;
const itemDiscount = food.discount || 0; const itemDiscount = product.discount || 0;
return { return {
foodId: food.id, foodId: product.id,
foodTitle: food.title, foodTitle: product.title,
quantity, quantity,
price: itemPrice, price: itemPrice,
discount: itemDiscount, discount: itemDiscount,
@@ -30,15 +30,15 @@ export class CartItemService {
} }
/** /**
* Build cart item from food (updating existing item if provided) * Build cart item from product (updating existing item if provided)
*/ */
buildCartItemFromFood(food: Food, quantity: number, previous?: CartItem): CartItem { buildCartItemFromFood(product: Product, quantity: number, previous?: CartItem): CartItem {
const itemPrice = food.price || 0; const itemPrice = product.price || 0;
const itemDiscount = food.discount || 0; const itemDiscount = product.discount || 0;
return { return {
...(previous ?? ({} as CartItem)), ...(previous ?? ({} as CartItem)),
foodId: food.id, foodId: product.id,
foodTitle: food.title, foodTitle: product.title,
quantity, quantity,
price: itemPrice, price: itemPrice,
discount: itemDiscount, discount: itemDiscount,
@@ -57,19 +57,19 @@ export class CartItemService {
* Add or increment item in cart * Add or increment item in cart
*/ */
async addOrIncrementItem(cart: Cart, foodId: string, restaurantId: string, quantityToAdd: number): Promise<void> { async addOrIncrementItem(cart: Cart, foodId: string, restaurantId: string, quantityToAdd: number): Promise<void> {
const food = await this.validationService.validateAndGetFood(foodId, restaurantId, quantityToAdd); const product = await this.validationService.validateAndGetFood(foodId, restaurantId, quantityToAdd);
const index = this.getItemIndex(cart, food.id); const index = this.getItemIndex(cart, product.id);
if (index < 0) { if (index < 0) {
cart.items.push(this.createCartItem(food, quantityToAdd)); cart.items.push(this.createCartItem(product, quantityToAdd));
return; return;
} }
const existingItem = cart.items[index]; const existingItem = cart.items[index];
const newQuantity = existingItem.quantity + quantityToAdd; const newQuantity = existingItem.quantity + quantityToAdd;
this.validationService.validateStock(food, newQuantity); this.validationService.validateStock(product, newQuantity);
cart.items[index] = this.buildCartItemFromFood(food, newQuantity, existingItem); cart.items[index] = this.buildCartItemFromFood(product, newQuantity, existingItem);
} }
/** /**
@@ -100,8 +100,8 @@ export class CartItemService {
return; return;
} }
const food = await this.validationService.getFoodOrFail(foodId); const product = await this.validationService.getFoodOrFail(foodId);
cart.items[itemIndex] = this.buildCartItemFromFood(food, newQuantity, existingItem); cart.items[itemIndex] = this.buildCartItemFromFood(product, newQuantity, existingItem);
} }
} }
@@ -1,15 +1,15 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { Food } from '../../foods/entities/food.entity'; import { Product } from ../../..products/entities/product.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { UserAddress } from '../../users/entities/user-address.entity'; import { UserAddress } from '../../../users/entities/user-address.entity';
import { User } from '../../users/entities/user.entity'; import { User } from '../../../users/entities/user.entity';
import { PaymentMethod } from '../../payments/entities/payment-method.entity'; import { PaymentMethod } from '../../../payments/entities/payment-method.entity';
import { Delivery } from '../../delivery/entities/delivery.entity'; import { Delivery } from '../../../delivery/entities/delivery.entity';
import { DeliveryMethodEnum } from '../../delivery/interface/delivery'; import { DeliveryMethodEnum } from '../../../delivery/interface/delivery';
import { PointTransaction } from '../../users/entities/point-transaction.entity'; import { PointTransaction } from '../../../users/entities/point-transaction.entity';
import { Order } from '../../orders/entities/order.entity'; import { Order } from '../../../orders/entities/order.entity';
import { OrderStatus } from '../../orders/interface/order.interface'; import { OrderStatus } from '../../../orders/interface/order.interface';
import { Cart } from '../interfaces/cart.interface'; import { Cart } from '../interfaces/cart.interface';
import { GeographicUtils } from '../utils/geographic.utils'; import { GeographicUtils } from '../utils/geographic.utils';
import { CartMessage } from 'src/common/enums/message.enum'; import { CartMessage } from 'src/common/enums/message.enum';
@@ -23,33 +23,33 @@ export class CartValidationService {
) { } ) { }
/** /**
* Validate food exists, belongs to restaurant, has inventory, and check stock * Validate product exists, belongs to shop, has inventory, and check stock
*/ */
async validateAndGetFood(foodId: string, restaurantId: string, quantity: number): Promise<Food> { async validateAndGetFood(foodId: string, restaurantId: string, quantity: number): Promise<Product> {
const food = await this.em.findOne(Food, { id: foodId }, { populate: ['restaurant', 'inventory'] }); const product = await this.em.findOne(Product, { id: foodId }, { populate: ['shop', 'inventory'] });
if (!food) { if (!product) {
throw new NotFoundException(CartMessage.FOOD_NOT_FOUND); throw new NotFoundException(CartMessage.FOOD_NOT_FOUND);
} }
if (food.restaurant.id !== restaurantId) { if (product.shop.id !== restaurantId) {
throw new BadRequestException(CartMessage.FOOD_NOT_BELONGS_TO_RESTAURANT); throw new BadRequestException(CartMessage.FOOD_NOT_BELONGS_TO_RESTAURANT);
} }
if (!food.inventory) { if (!product.inventory) {
throw new BadRequestException(CartMessage.FOOD_NO_INVENTORY); throw new BadRequestException(CartMessage.FOOD_NO_INVENTORY);
} }
this.validateStock(food, quantity); this.validateStock(product, quantity);
return food; return product;
} }
/** /**
* Validate stock availability for food * Validate stock availability for product
*/ */
validateStock(food: Food, quantity: number): void { validateStock(product: Product, quantity: number): void {
const availableStock = food.inventory!.availableStock; const availableStock = product.inventory!.availableStock;
if (availableStock < quantity) { if (availableStock < quantity) {
throw new BadRequestException(CartMessage.INSUFFICIENT_STOCK + food.title); throw new BadRequestException(CartMessage.INSUFFICIENT_STOCK + product.title);
} }
} }
@@ -76,25 +76,25 @@ export class CartValidationService {
} }
/** /**
* Get food or throw if not found * Get product or throw if not found
*/ */
async getFoodOrFail(foodId: string): Promise<Food> { async getFoodOrFail(foodId: string): Promise<Product> {
const food = await this.em.findOne(Food, { id: foodId }); const product = await this.em.findOne(Product, { id: foodId });
if (!food) { if (!product) {
throw new NotFoundException(CartMessage.FOOD_NOT_FOUND); throw new NotFoundException(CartMessage.FOOD_NOT_FOUND);
} }
return food; return product;
} }
/** /**
* Get restaurant or throw if not found * Get shop or throw if not found
*/ */
async getRestaurantOrFail(restaurantId: string): Promise<Restaurant> { async getRestaurantOrFail(restaurantId: string): Promise<Shop> {
const restaurant = await this.em.findOne(Restaurant, { id: restaurantId }); const shop = await this.em.findOne(Shop, { id: restaurantId });
if (!restaurant) { if (!shop) {
throw new NotFoundException(CartMessage.RESTAURANT_NOT_FOUND); throw new NotFoundException(CartMessage.RESTAURANT_NOT_FOUND);
} }
return restaurant; return shop;
} }
/** /**
@@ -107,7 +107,7 @@ export class CartValidationService {
} }
/** /**
* Assert address is inside restaurant service area * Assert address is inside shop service area
*/ */
async assertAddressInsideServiceArea( async assertAddressInsideServiceArea(
restaurantId: string, restaurantId: string,
@@ -118,9 +118,9 @@ export class CartValidationService {
throw new BadRequestException(CartMessage.ADDRESS_NO_COORDINATES); throw new BadRequestException(CartMessage.ADDRESS_NO_COORDINATES);
} }
const restaurant = await this.getRestaurantOrFail(restaurantId); const shop = await this.getRestaurantOrFail(restaurantId);
const serviceArea = restaurant.serviceArea; const serviceArea = shop.serviceArea;
// If no service area is defined, assume service is available everywhere // If no service area is defined, assume service is available everywhere
if (!serviceArea || !serviceArea.coordinates || !Array.isArray(serviceArea.coordinates)) return; if (!serviceArea || !serviceArea.coordinates || !Array.isArray(serviceArea.coordinates)) return;
@@ -142,7 +142,7 @@ export class CartValidationService {
} }
/** /**
* Get delivery method for restaurant or throw if not found * Get delivery method for shop or throw if not found
*/ */
async getDeliveryMethodForRestaurantOrFail( async getDeliveryMethodForRestaurantOrFail(
restaurantId: string, restaurantId: string,
@@ -150,7 +150,7 @@ export class CartValidationService {
): Promise<Delivery> { ): Promise<Delivery> {
const deliveryMethod = await this.em.findOne(Delivery, { const deliveryMethod = await this.em.findOne(Delivery, {
id: deliveryMethodId, id: deliveryMethodId,
restaurant: { id: restaurantId }, shop: { id: restaurantId },
}); });
if (!deliveryMethod) { if (!deliveryMethod) {
@@ -166,8 +166,8 @@ export class CartValidationService {
async getEnabledDeliveryMethodOrFail(restaurantId: string, deliveryMethodId: string): Promise<Delivery> { async getEnabledDeliveryMethodOrFail(restaurantId: string, deliveryMethodId: string): Promise<Delivery> {
const deliveryMethod = await this.em.findOne( const deliveryMethod = await this.em.findOne(
Delivery, Delivery,
{ id: deliveryMethodId, restaurant: { id: restaurantId } }, { id: deliveryMethodId, shop: { id: restaurantId } },
{ populate: ['restaurant'] }, { populate: ['shop'] },
); );
if (!deliveryMethod) { if (!deliveryMethod) {
throw new NotFoundException(CartMessage.DELIVERY_METHOD_NOT_FOUND); throw new NotFoundException(CartMessage.DELIVERY_METHOD_NOT_FOUND);
@@ -203,8 +203,8 @@ export class CartValidationService {
async getEnabledPaymentMethodOrFail(restaurantId: string, paymentMethodId: string): Promise<PaymentMethod> { async getEnabledPaymentMethodOrFail(restaurantId: string, paymentMethodId: string): Promise<PaymentMethod> {
const paymentMethod = await this.em.findOne( const paymentMethod = await this.em.findOne(
PaymentMethod, PaymentMethod,
{ id: paymentMethodId, restaurant: { id: restaurantId } }, { id: paymentMethodId, shop: { id: restaurantId } },
{ populate: ['restaurant'] }, { populate: ['shop'] },
); );
if (!paymentMethod) { if (!paymentMethod) {
throw new NotFoundException(CartMessage.PAYMENT_METHOD_NOT_FOUND); throw new NotFoundException(CartMessage.PAYMENT_METHOD_NOT_FOUND);
@@ -250,10 +250,10 @@ export class CartValidationService {
async assertCouponMatchesCartIfRestricted( async assertCouponMatchesCartIfRestricted(
cart: Cart, cart: Cart,
foodCategories?: string[] | null, foodCategories?: string[] | null,
foods?: string[] | null, products?: string[] | null,
): Promise<void> { ): Promise<void> {
const categoryRestriction = foodCategories?.filter(Boolean) ?? []; const categoryRestriction = foodCategories?.filter(Boolean) ?? [];
const foodRestriction = foods?.filter(Boolean) ?? []; const foodRestriction = products?.filter(Boolean) ?? [];
const hasCategoryRestriction = categoryRestriction.length > 0; const hasCategoryRestriction = categoryRestriction.length > 0;
const hasFoodRestriction = foodRestriction.length > 0; const hasFoodRestriction = foodRestriction.length > 0;
if (!hasCategoryRestriction && !hasFoodRestriction) return; if (!hasCategoryRestriction && !hasFoodRestriction) return;
@@ -261,11 +261,11 @@ export class CartValidationService {
const foodMap = await this.getFoodsInCartWithCategories(cart); const foodMap = await this.getFoodsInCartWithCategories(cart);
for (const item of cart.items) { for (const item of cart.items) {
const food = foodMap.get(item.foodId); const product = foodMap.get(item.foodId);
if (!food) continue; if (!product) continue;
const matchesCategory = hasCategoryRestriction && food.category && categoryRestriction.includes(food.category.id); const matchesCategory = hasCategoryRestriction && product.category && categoryRestriction.includes(product.category.id);
const matchesFood = hasFoodRestriction && foodRestriction.includes(food.id); const matchesFood = hasFoodRestriction && foodRestriction.includes(product.id);
if (matchesCategory || matchesFood) return; if (matchesCategory || matchesFood) return;
} }
@@ -273,14 +273,14 @@ export class CartValidationService {
} }
/** /**
* Get foods in cart with categories * Get products in cart with categories
*/ */
async getFoodsInCartWithCategories(cart: Cart): Promise<Map<string, Food>> { async getFoodsInCartWithCategories(cart: Cart): Promise<Map<string, Product>> {
const foodIds = cart.items.map(item => item.foodId); const foodIds = cart.items.map(item => item.foodId);
if (foodIds.length === 0) return new Map(); if (foodIds.length === 0) return new Map();
const foods = await this.em.find(Food, { id: { $in: foodIds } }, { populate: ['category'] }); const products = await this.em.find(Product, { id: { $in: foodIds } }, { populate: ['category'] });
return new Map(foods.map(f => [f.id, f])); return new Map(products.map(f => [f.id, f]));
} }
} }
+13 -13
View File
@@ -1,12 +1,12 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { DeliveryMethodEnum } from '../../delivery/interface/delivery'; import { DeliveryMethodEnum } from '../../../delivery/interface/delivery';
import { PaymentMethodEnum } from '../../payments/interface/payment'; import { PaymentMethodEnum } from '../../../payments/interface/payment';
import { OrderCouponDetail } from '../../orders/interface/order.interface'; import { OrderCouponDetail } from '../../../orders/interface/order.interface';
import { Cart } from '../interfaces/cart.interface'; import { Cart } from '../interfaces/cart.interface';
import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto'; import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto';
import { ApplyCouponDto } from '../dto/apply-coupon.dto'; import { ApplyCouponDto } from '../dto/apply-coupon.dto';
import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto'; import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto';
import { CouponService } from '../../coupons/providers/coupon.service'; import { CouponService } from '../../../coupons/providers/coupon.service';
import { CartRepository } from '../repositories/cart.repository'; import { CartRepository } from '../repositories/cart.repository';
import { CartValidationService } from './cart-validation.service'; import { CartValidationService } from './cart-validation.service';
import { CartCalculationService } from './cart-calculation.service'; import { CartCalculationService } from './cart-calculation.service';
@@ -80,7 +80,7 @@ export class CartService {
const address = await this.validationService.getUserAddressOrFail(addressId); const address = await this.validationService.getUserAddressOrFail(addressId);
this.validationService.validateAddressOwnership(address, userId); this.validationService.validateAddressOwnership(address, userId);
// ensure address is within restaurant service area // ensure address is within shop service area
await this.validationService.assertAddressInsideServiceArea( await this.validationService.assertAddressInsideServiceArea(
restaurantId, restaurantId,
address.latitude, address.latitude,
@@ -125,7 +125,7 @@ export class CartService {
} }
/** /**
* Get or create cart for user and restaurant * Get or create cart for user and shop
*/ */
async getOrCreateCart(userId: string, restaurantId: string): Promise<Cart> { async getOrCreateCart(userId: string, restaurantId: string): Promise<Cart> {
const existingCart = await this.cartRepository.findByUserAndRestaurant(userId, restaurantId); const existingCart = await this.cartRepository.findByUserAndRestaurant(userId, restaurantId);
@@ -133,15 +133,15 @@ export class CartService {
return existingCart; return existingCart;
} }
// Find restaurant // Find shop
const restaurant = await this.validationService.getRestaurantOrFail(restaurantId); const shop = await this.validationService.getRestaurantOrFail(restaurantId);
// Create new cart // Create new cart
const now = this.nowIso(); const now = this.nowIso();
const cart: Cart = { const cart: Cart = {
userId, userId,
restaurantId, restaurantId,
restaurantName: restaurant.name, restaurantName: shop.name,
items: [], items: [],
deliveryFee: 0, deliveryFee: 0,
subTotal: 0, subTotal: 0,
@@ -160,7 +160,7 @@ export class CartService {
} }
/** /**
* Find cart by user and restaurant * Find cart by user and shop
*/ */
async findOneOrFail(userId: string, restaurantId: string): Promise<Cart> { async findOneOrFail(userId: string, restaurantId: string): Promise<Cart> {
const cart = await this.cartRepository.findByUserAndRestaurant(userId, restaurantId); const cart = await this.cartRepository.findByUserAndRestaurant(userId, restaurantId);
@@ -218,7 +218,7 @@ export class CartService {
const orderAmount = cart.subTotal - cart.itemsDiscount; const orderAmount = cart.subTotal - cart.itemsDiscount;
const user = await this.validationService.getUserOrFail(userId); const user = await this.validationService.getUserOrFail(userId);
// check if coupon is valid and belong to the restaurant // check if coupon is valid and belong to the shop
const { valid, coupon, message } = await this.couponService.validateCoupon( const { valid, coupon, message } = await this.couponService.validateCoupon(
applyCouponDto.code, applyCouponDto.code,
restaurantId, restaurantId,
@@ -239,7 +239,7 @@ export class CartService {
await this.validationService.assertCouponMatchesCartIfRestricted( await this.validationService.assertCouponMatchesCartIfRestricted(
cart, cart,
coupon.foodCategories, coupon.foodCategories,
coupon.foods, coupon.products,
); );
const couponDetail: OrderCouponDetail = { const couponDetail: OrderCouponDetail = {
@@ -291,7 +291,7 @@ export class CartService {
// Verify address belongs to the user // Verify address belongs to the user
this.validationService.validateAddressOwnership(address, userId); this.validationService.validateAddressOwnership(address, userId);
// ensure address is within restaurant service area // ensure address is within shop service area
await this.validationService.assertAddressInsideServiceArea( await this.validationService.assertAddressInsideServiceArea(
restaurantId, restaurantId,
address.latitude, address.latitude,
@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { CacheService } from '../../utils/cache.service'; import { CacheService } from '../../../utils/cache.service';
import { Cart } from '../interfaces/cart.interface'; import { Cart } from '../interfaces/cart.interface';
@Injectable() @Injectable()
@@ -10,7 +10,7 @@ export class CartRepository {
constructor(private readonly cacheService: CacheService) {} constructor(private readonly cacheService: CacheService) {}
/** /**
* Get cart by user and restaurant * Get cart by user and shop
*/ */
async findByUserAndRestaurant(userId: string, restaurantId: string): Promise<Cart | null> { async findByUserAndRestaurant(userId: string, restaurantId: string): Promise<Cart | null> {
const cacheKey = this.getCacheKey(userId, restaurantId); const cacheKey = this.getCacheKey(userId, restaurantId);
@@ -14,7 +14,7 @@ export class CreateContactDto {
content!: string; content!: string;
@IsNotEmpty() @IsNotEmpty()
@IsEnum(['application', 'restaurant']) @IsEnum(['application', 'shop'])
@ApiProperty({ description: 'Contact Scope' }) @ApiProperty({ description: 'Contact Scope' })
scope!: ContactScope; scope!: ContactScope;
@@ -1,7 +1,7 @@
import { Entity, Enum, ManyToOne, Property } from '@mikro-orm/core'; import { Entity, Enum, ManyToOne, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { ContactScope, ContactStatusEnum } from '../interface/interface'; import { ContactScope, ContactStatusEnum } from '../interface/interface';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
@Entity({ tableName: 'contacts' }) @Entity({ tableName: 'contacts' })
export class Contact extends BaseEntity { export class Contact extends BaseEntity {
@@ -17,8 +17,8 @@ export class Contact extends BaseEntity {
@Enum(() => ContactScope) @Enum(() => ContactScope)
scope: ContactScope scope: ContactScope
@ManyToOne(() => Restaurant, { nullable: true }) @ManyToOne(() => Shop, { nullable: true })
restaurant?: Restaurant | null = null; shop?: Shop | null = null;
@Property({ default: ContactStatusEnum.New }) @Property({ default: ContactStatusEnum.New })
+1 -1
View File
@@ -5,5 +5,5 @@ export enum ContactStatusEnum {
export enum ContactScope { export enum ContactScope {
APPLICATION = 'application', APPLICATION = 'application',
RESTAURANT = 'restaurant', RESTAURANT = 'shop',
} }
@@ -8,7 +8,7 @@ import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { ContactStatusEnum } from '../interface/interface'; import { ContactStatusEnum } from '../interface/interface';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { ContactScope } from '../interface/interface'; import { ContactScope } from '../interface/interface';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
@Injectable() @Injectable()
export class ContactService { export class ContactService {
@@ -20,22 +20,22 @@ export class ContactService {
async create(dto: CreateContactDto, restId?: string, slug?: string): Promise<Contact> { async create(dto: CreateContactDto, restId?: string, slug?: string): Promise<Contact> {
let restaurant: Restaurant | null = null; let shop: Shop | null = null;
if ((restId || slug) && dto.scope === ContactScope.RESTAURANT) { if ((restId || slug) && dto.scope === ContactScope.RESTAURANT) {
restaurant = await this.em.findOne(Restaurant, { shop = await this.em.findOne(Shop, {
...(restId ? { id: restId } : {}) ...(restId ? { id: restId } : {})
, ...(slug ? { slug: slug } : {}) , ...(slug ? { slug: slug } : {})
}); });
if (!restaurant) { if (!shop) {
throw new NotFoundException(`Restaurant with ID ${restId} not found`); throw new NotFoundException(`Shop with ID ${restId} not found`);
} }
} }
const contact = this.contactRepository.create({ const contact = this.contactRepository.create({
...dto, ...dto,
status: ContactStatusEnum.New, status: ContactStatusEnum.New,
restaurant, shop,
}); });
await this.em.persistAndFlush(contact); await this.em.persistAndFlush(contact);
return contact; return contact;
@@ -68,7 +68,7 @@ export class ContactService {
async findOne(id: string, restaurantId?: string): Promise<Contact> { async findOne(id: string, restaurantId?: string): Promise<Contact> {
const where: any = { id }; const where: any = { id };
if (restaurantId) { if (restaurantId) {
where.restaurant = restaurantId; where.shop = restaurantId;
} }
const contact = await this.contactRepository.findOne(where); const contact = await this.contactRepository.findOne(where);
@@ -81,7 +81,7 @@ export class ContactService {
async updateStatus(id: string, dto: UpdateContactStatusDto, restaurantId?: string): Promise<Contact> { async updateStatus(id: string, dto: UpdateContactStatusDto, restaurantId?: string): Promise<Contact> {
const where: any = { id }; const where: any = { id };
if (restaurantId) { if (restaurantId) {
where.restaurant = restaurantId; where.shop = restaurantId;
} }
const contact = await this.contactRepository.findOne(where); const contact = await this.contactRepository.findOne(where);
@@ -96,7 +96,7 @@ export class ContactService {
async remove(id: string, restaurantId?: string): Promise<void> { async remove(id: string, restaurantId?: string): Promise<void> {
const where: any = { id }; const where: any = { id };
if (restaurantId) { if (restaurantId) {
where.restaurant = restaurantId; where.shop = restaurantId;
} }
const contact = await this.contactRepository.findOne(where); const contact = await this.contactRepository.findOne(where);
@@ -40,7 +40,7 @@ export class ContactRepository extends EntityRepository<Contact> {
where.scope = scope; where.scope = scope;
} }
if (restaurantId) { if (restaurantId) {
where.restaurant = restaurantId; where.shop = restaurantId;
} }
if (search) { if (search) {
@@ -30,7 +30,7 @@ export class CouponController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('public/coupons/me') @Get('public/coupons/me')
@ApiOperation({ summary: 'Get paginated list of restaurant coupons' }) @ApiOperation({ summary: 'Get paginated list of shop coupons' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiQuery({ name: 'page', required: false, type: Number }) @ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'limit', required: false, type: Number }) @ApiQuery({ name: 'limit', required: false, type: Number })
+1 -1
View File
@@ -4,7 +4,7 @@ import { CouponController } from './controllers/coupon.controller';
import { CouponRepository } from './repositories/coupon.repository'; import { CouponRepository } from './repositories/coupon.repository';
import { MikroOrmModule } from '@mikro-orm/nestjs'; import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Coupon } from './entities/coupon.entity'; import { Coupon } from './entities/coupon.entity';
import { RestaurantsModule } from '../restaurants/restaurants.module'; import { RestaurantsModule } from ../../..shops.module';
import { AuthModule } from '../auth/auth.module'; import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
+4 -4
View File
@@ -90,7 +90,7 @@ export class CreateCouponDto {
@IsString({ each: true }) @IsString({ each: true })
@ApiPropertyOptional({ @ApiPropertyOptional({
example: ['category-id-1', 'category-id-2'], example: ['category-id-1', 'category-id-2'],
description: 'Array of food category IDs. If empty, coupon applies to all categories.', description: 'Array of product category IDs. If empty, coupon applies to all categories.',
type: [String], type: [String],
}) })
foodCategories?: string[]; foodCategories?: string[];
@@ -99,11 +99,11 @@ export class CreateCouponDto {
@IsArray() @IsArray()
@IsString({ each: true }) @IsString({ each: true })
@ApiPropertyOptional({ @ApiPropertyOptional({
example: ['food-id-1', 'food-id-2'], example: ['product-id-1', 'product-id-2'],
description: 'Array of food IDs. If empty, coupon applies to all foods.', description: 'Array of product IDs. If empty, coupon applies to all products.',
type: [String], type: [String],
}) })
foods?: string[]; products?: string[];
@IsOptional() @IsOptional()
@IsString() @IsString()
@@ -1,17 +1,17 @@
import { Entity, Index, ManyToOne, Property, Enum, Unique } from '@mikro-orm/core'; import { Entity, Index, ManyToOne, Property, Enum, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { normalizePhone } from '../../utils/phone.util'; import { normalizePhone } from '../../../utils/phone.util';
import { CouponType } from '../interface/coupon'; import { CouponType } from '../interface/coupon';
@Entity({ tableName: 'coupons' }) @Entity({ tableName: 'coupons' })
@Unique({ properties: ['code', 'restaurant'] }) @Unique({ properties: ['code', 'shop'] })
@Index({ properties: ['restaurant', 'code', 'isActive'] }) @Index({ properties: ['shop', 'code', 'isActive'] })
@Index({ properties: ['restaurant', 'isActive'] }) @Index({ properties: ['shop', 'isActive'] })
@Index({ properties: ['code'] }) @Index({ properties: ['code'] })
export class Coupon extends BaseEntity { export class Coupon extends BaseEntity {
@ManyToOne(() => Restaurant) @ManyToOne(() => Shop)
restaurant!: Restaurant; shop!: Shop;
@Property() @Property()
code!: string; code!: string;
@@ -56,7 +56,7 @@ export class Coupon extends BaseEntity {
foodCategories?: string[]; // Array of category IDs foodCategories?: string[]; // Array of category IDs
@Property({ type: 'json', nullable: true }) @Property({ type: 'json', nullable: true })
foods?: string[]; // Array of food IDs products?: string[]; // Array of product IDs
private _userPhone?: string; private _userPhone?: string;
+14 -14
View File
@@ -3,7 +3,7 @@ import { CreateCouponDto } from '../dto/create-coupon.dto';
import { UpdateCouponDto } from '../dto/update-coupon.dto'; import { UpdateCouponDto } from '../dto/update-coupon.dto';
import { FindCouponsDto } from '../dto/find-coupons.dto'; import { FindCouponsDto } from '../dto/find-coupons.dto';
import { CouponRepository } from '../repositories/coupon.repository'; import { CouponRepository } from '../repositories/coupon.repository';
import { RestRepository } from '../../restaurants/repositories/rest.repository'; import { RestRepository } from ../../..shops/repositories/rest.repository';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core'; import { RequiredEntityData } from '@mikro-orm/core';
import { Coupon } from '../entities/coupon.entity'; import { Coupon } from '../entities/coupon.entity';
@@ -20,8 +20,8 @@ export class CouponService {
) {} ) {}
async create(restId: string, createCouponDto: CreateCouponDto): Promise<Coupon> { async create(restId: string, createCouponDto: CreateCouponDto): Promise<Coupon> {
const restaurant = await this.restRepository.findOne({ id: restId }); const shop = await this.restRepository.findOne({ id: restId });
if (!restaurant) { if (!shop) {
throw new NotFoundException(RestMessage.NOT_FOUND); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
@@ -48,7 +48,7 @@ export class CouponService {
} }
const data: RequiredEntityData<Coupon> = { const data: RequiredEntityData<Coupon> = {
restaurant, shop,
code: createCouponDto.code, code: createCouponDto.code,
name: createCouponDto.name, name: createCouponDto.name,
description: createCouponDto.description, description: createCouponDto.description,
@@ -63,7 +63,7 @@ export class CouponService {
isActive: createCouponDto.isActive ?? true, isActive: createCouponDto.isActive ?? true,
usedCount: 0, usedCount: 0,
foodCategories: createCouponDto.foodCategories, foodCategories: createCouponDto.foodCategories,
foods: createCouponDto.foods, products: createCouponDto.products,
userPhone: createCouponDto.userPhone, userPhone: createCouponDto.userPhone,
}; };
@@ -81,7 +81,7 @@ export class CouponService {
} }
async findById(id: string): Promise<Coupon> { async findById(id: string): Promise<Coupon> {
const coupon = await this.couponRepository.findOne({ id }, { populate: ['restaurant'] }); const coupon = await this.couponRepository.findOne({ id }, { populate: ['shop'] });
if (!coupon) { if (!coupon) {
throw new NotFoundException(CouponMessage.NOT_FOUND); throw new NotFoundException(CouponMessage.NOT_FOUND);
} }
@@ -89,7 +89,7 @@ export class CouponService {
} }
async findByCode(code: string, restId: string): Promise<Coupon> { async findByCode(code: string, restId: string): Promise<Coupon> {
const coupon = await this.couponRepository.findOne({ code, restaurant: { id: restId } }); const coupon = await this.couponRepository.findOne({ code, shop: { id: restId } });
if (!coupon) { if (!coupon) {
throw new NotFoundException(CouponMessage.NOT_FOUND); throw new NotFoundException(CouponMessage.NOT_FOUND);
} }
@@ -139,7 +139,7 @@ export class CouponService {
if (dto.endDate !== undefined) coupon.endDate = dto.endDate ? new Date(dto.endDate) : undefined; if (dto.endDate !== undefined) coupon.endDate = dto.endDate ? new Date(dto.endDate) : undefined;
if (dto.isActive !== undefined) coupon.isActive = dto.isActive; if (dto.isActive !== undefined) coupon.isActive = dto.isActive;
if (dto.foodCategories !== undefined) coupon.foodCategories = dto.foodCategories; if (dto.foodCategories !== undefined) coupon.foodCategories = dto.foodCategories;
if (dto.foods !== undefined) coupon.foods = dto.foods; if (dto.products !== undefined) coupon.products = dto.products;
if (dto.userPhone !== undefined) coupon.userPhone = dto.userPhone; if (dto.userPhone !== undefined) coupon.userPhone = dto.userPhone;
await this.em.persistAndFlush(coupon); await this.em.persistAndFlush(coupon);
@@ -169,8 +169,8 @@ export class CouponService {
message?: string; message?: string;
}> { }> {
const coupon = await this.couponRepository.findOne( const coupon = await this.couponRepository.findOne(
{ code, restaurant: { id: restId } }, { code, shop: { id: restId } },
{ populate: ['restaurant'] }, { populate: ['shop'] },
); );
if (!coupon) { if (!coupon) {
@@ -258,12 +258,12 @@ export class CouponService {
} }
async generateCouponCode(restId: string): Promise<string> { async generateCouponCode(restId: string): Promise<string> {
const restaurant = await this.restRepository.findOne({ id: restId }); const shop = await this.restRepository.findOne({ id: restId });
if (!restaurant) { if (!shop) {
throw new NotFoundException(RestMessage.NOT_FOUND); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
const code = this.generateShortCode(restaurant.slug); const code = this.generateShortCode(shop.slug);
const existing = await this.couponRepository.findOne({ code, restaurant: { id: restId } }); const existing = await this.couponRepository.findOne({ code, shop: { id: restId } });
if (existing) return this.generateCouponCode(restId); if (existing) return this.generateCouponCode(restId);
return code; return code;
@@ -30,7 +30,7 @@ export class CouponRepository extends EntityRepository<Coupon> {
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
const where: FilterQuery<Coupon> = { restaurant: { id: restId } }; const where: FilterQuery<Coupon> = { shop: { id: restId } };
if (typeof isActive === 'boolean') { if (typeof isActive === 'boolean') {
where.isActive = isActive; where.isActive = isActive;
@@ -28,8 +28,8 @@ export class DeliveryController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('public/delivery-methods/restaurant') @Get('public/delivery-methods/shop')
@ApiOperation({ summary: 'Get restaurant delivery methods' }) @ApiOperation({ summary: 'Get shop delivery methods' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
findAllDeliveryMethods(@RestId() restId: string) { findAllDeliveryMethods(@RestId() restId: string) {
return this.deliveryService.findAllForRestaurantId(restId); return this.deliveryService.findAllForRestaurantId(restId);
@@ -39,8 +39,8 @@ export class DeliveryController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_DELIVERY) @Permissions(Permission.MANAGE_DELIVERY)
@Post('admin/delivery-methods/restaurant') @Post('admin/delivery-methods/shop')
@ApiOperation({ summary: 'Create a delivery method for a restaurant' }) @ApiOperation({ summary: 'Create a delivery method for a shop' })
@ApiBody({ type: CreateDeliveryDto }) @ApiBody({ type: CreateDeliveryDto })
create(@Body() createDto: CreateDeliveryDto, @RestId() restId: string) { create(@Body() createDto: CreateDeliveryDto, @RestId() restId: string) {
return this.deliveryService.create(restId, createDto); return this.deliveryService.create(restId, createDto);
@@ -49,8 +49,8 @@ export class DeliveryController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_DELIVERY) @Permissions(Permission.MANAGE_DELIVERY)
@Get('admin/delivery-methods/restaurant') @Get('admin/delivery-methods/shop')
@ApiOperation({ summary: 'Get the restaurant delivery methods' }) @ApiOperation({ summary: 'Get the shop delivery methods' })
findRestaurantDeliveryMethods(@RestId() restId: string) { findRestaurantDeliveryMethods(@RestId() restId: string) {
return this.deliveryService.findAllForRestaurantId(restId); return this.deliveryService.findAllForRestaurantId(restId);
} }
@@ -58,8 +58,8 @@ export class DeliveryController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_DELIVERY) @Permissions(Permission.MANAGE_DELIVERY)
@Get('admin/delivery-methods/restaurant/:deliveryId') @Get('admin/delivery-methods/shop/:deliveryId')
@ApiOperation({ summary: 'Get a restaurant delivery method by delivery ID' }) @ApiOperation({ summary: 'Get a shop delivery method by delivery ID' })
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' }) @ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
findOne(@Param('deliveryId') deliveryId: string, @RestId() restId: string) { findOne(@Param('deliveryId') deliveryId: string, @RestId() restId: string) {
return this.deliveryService.findOne(restId, deliveryId); return this.deliveryService.findOne(restId, deliveryId);
@@ -68,8 +68,8 @@ export class DeliveryController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_DELIVERY) @Permissions(Permission.MANAGE_DELIVERY)
@Patch('admin/delivery-methods/restaurant/:deliveryId') @Patch('admin/delivery-methods/shop/:deliveryId')
@ApiOperation({ summary: 'Update a restaurant delivery method' }) @ApiOperation({ summary: 'Update a shop delivery method' })
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' }) @ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
@ApiBody({ type: UpdateDeliveryDto }) @ApiBody({ type: UpdateDeliveryDto })
update(@Param('deliveryId') deliveryId: string, @Body() updateDto: UpdateDeliveryDto, @RestId() restId: string) { update(@Param('deliveryId') deliveryId: string, @Body() updateDto: UpdateDeliveryDto, @RestId() restId: string) {
@@ -79,8 +79,8 @@ export class DeliveryController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_DELIVERY) @Permissions(Permission.MANAGE_DELIVERY)
@Delete('admin/delivery-methods/restaurant/:deliveryId') @Delete('admin/delivery-methods/shop/:deliveryId')
@ApiOperation({ summary: 'Delete a restaurant delivery method' }) @ApiOperation({ summary: 'Delete a shop delivery method' })
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' }) @ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
remove(@Param('deliveryId') deliveryId: string, @RestId() restId: string) { remove(@Param('deliveryId') deliveryId: string, @RestId() restId: string) {
return this.deliveryService.remove(restId, deliveryId); return this.deliveryService.remove(restId, deliveryId);
+1 -1
View File
@@ -3,7 +3,7 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { DeliveryController } from './controllers/delivery.controller'; import { DeliveryController } from './controllers/delivery.controller';
import { DeliveryService } from './providers/delivery.service'; import { DeliveryService } from './providers/delivery.service';
import { RestaurantsModule } from '../restaurants/restaurants.module'; import { RestaurantsModule } from ../../..shops.module';
import { Delivery } from './entities/delivery.entity'; import { Delivery } from './entities/delivery.entity';
import { DeliveryRepository } from './repositories/delivery.repository'; import { DeliveryRepository } from './repositories/delivery.repository';
@@ -1,6 +1,6 @@
import { Entity, Property, Enum, ManyToOne } from '@mikro-orm/core'; import { Entity, Property, Enum, ManyToOne } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from '../../../shops/entities/shop.entity';
import { DeliveryFeeTypeEnum, DeliveryMethodEnum } from '../interface/delivery'; import { DeliveryFeeTypeEnum, DeliveryMethodEnum } from '../interface/delivery';
@Entity({ tableName: 'deliveries' }) @Entity({ tableName: 'deliveries' })
@@ -8,8 +8,8 @@ export class Delivery extends BaseEntity {
@Enum(() => DeliveryMethodEnum) @Enum(() => DeliveryMethodEnum)
method!: DeliveryMethodEnum; method!: DeliveryMethodEnum;
@ManyToOne(() => Restaurant) @ManyToOne(() => Shop)
restaurant!: Restaurant; shop!: Shop;
@Property({ type: 'decimal', precision: 10, scale: 0, default: 0 }) @Property({ type: 'decimal', precision: 10, scale: 0, default: 0 })
deliveryFee: number = 0; deliveryFee: number = 0;
@@ -4,7 +4,7 @@ import { Delivery } from '../entities/delivery.entity';
import { DeliveryRepository } from '../repositories/delivery.repository'; import { DeliveryRepository } from '../repositories/delivery.repository';
import { CreateDeliveryDto } from '../dto/create-delivery.dto'; import { CreateDeliveryDto } from '../dto/create-delivery.dto';
import { UpdateDeliveryDto } from '../dto/update-delivery.dto'; import { UpdateDeliveryDto } from '../dto/update-delivery.dto';
import { RestRepository } from '../../restaurants/repositories/rest.repository'; import { RestRepository } from ../../..shops/repositories/rest.repository';
import { DeliveryMethodEnum } from '../interface/delivery'; import { DeliveryMethodEnum } from '../interface/delivery';
import { DeliveryMessage } from 'src/common/enums/message.enum'; import { DeliveryMessage } from 'src/common/enums/message.enum';
@@ -17,14 +17,14 @@ export class DeliveryService {
) {} ) {}
async create(restId: string, createDto: CreateDeliveryDto): Promise<Delivery> { async create(restId: string, createDto: CreateDeliveryDto): Promise<Delivery> {
const restaurant = await this.restRepository.findOne({ id: restId }); const shop = await this.restRepository.findOne({ id: restId });
if (!restaurant) { if (!shop) {
throw new NotFoundException(DeliveryMessage.RESTAURANT_NOT_FOUND); throw new NotFoundException(DeliveryMessage.RESTAURANT_NOT_FOUND);
} }
// Check if the delivery method with the same name already exists for this restaurant // Check if the delivery method with the same name already exists for this shop
const existing = await this.deliveryRepository.findOne({ const existing = await this.deliveryRepository.findOne({
restaurant: { id: restId }, shop: { id: restId },
method: createDto.method, method: createDto.method,
}); });
@@ -33,7 +33,7 @@ export class DeliveryService {
} }
const data: RequiredEntityData<Delivery> = { const data: RequiredEntityData<Delivery> = {
restaurant, shop,
method: createDto.method, method: createDto.method,
deliveryFee: createDto.deliveryFee, deliveryFee: createDto.deliveryFee,
minOrderPrice: createDto.minOrderPrice, minOrderPrice: createDto.minOrderPrice,
@@ -51,13 +51,13 @@ export class DeliveryService {
} }
async findAllForRestaurantId(restId: string): Promise<Delivery[]> { async findAllForRestaurantId(restId: string): Promise<Delivery[]> {
return this.deliveryRepository.find({ restaurant: { id: restId } }, { orderBy: { order: 'asc' } }); return this.deliveryRepository.find({ shop: { id: restId } }, { orderBy: { order: 'asc' } });
} }
async findOne(restId: string, deliveryId: string): Promise<Delivery> { async findOne(restId: string, deliveryId: string): Promise<Delivery> {
const delivery = await this.deliveryRepository.findOne({ const delivery = await this.deliveryRepository.findOne({
id: deliveryId, id: deliveryId,
restaurant: { id: restId }, shop: { id: restId },
}); });
if (!delivery) { if (!delivery) {
@@ -69,7 +69,7 @@ export class DeliveryService {
async update(restId: string, deliveryId: string, updateDto: UpdateDeliveryDto): Promise<Delivery> { async update(restId: string, deliveryId: string, updateDto: UpdateDeliveryDto): Promise<Delivery> {
const delivery = await this.deliveryRepository.findOne({ const delivery = await this.deliveryRepository.findOne({
restaurant: { id: restId }, shop: { id: restId },
id: deliveryId, id: deliveryId,
}); });
@@ -80,7 +80,7 @@ export class DeliveryService {
// If method is being updated, check for conflicts // If method is being updated, check for conflicts
if (updateDto.method !== undefined && updateDto.method !== delivery.method) { if (updateDto.method !== undefined && updateDto.method !== delivery.method) {
const existing = await this.deliveryRepository.findOne({ const existing = await this.deliveryRepository.findOne({
restaurant: { id: restId }, shop: { id: restId },
method: updateDto.method, method: updateDto.method,
id: { $ne: deliveryId }, id: { $ne: deliveryId },
}); });
@@ -98,7 +98,7 @@ export class DeliveryService {
async remove(restId: string, deliveryId: string): Promise<void> { async remove(restId: string, deliveryId: string): Promise<void> {
const delivery = await this.deliveryRepository.findOne({ const delivery = await this.deliveryRepository.findOne({
restaurant: { id: restId }, shop: { id: restId },
id: deliveryId, id: deliveryId,
}); });
@@ -1,14 +1,14 @@
import { Entity, ManyToOne, Unique } from '@mikro-orm/core'; import { Entity, ManyToOne, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../common/entities/base.entity';
import { User } from '../../users/entities/user.entity'; import { User } from '../../users/entities/user.entity';
import { Food } from '../../foods/entities/food.entity'; import { Product } from '../../products/entities/product.entity';
@Entity({ tableName: 'favorites' }) @Entity({ tableName: 'favorites' })
@Unique({ properties: ['user', 'food'] }) @Unique({ properties: ['user', 'product'] })
export class Favorite extends BaseEntity { export class Favorite extends BaseEntity {
@ManyToOne(() => User) @ManyToOne(() => User)
user: User; user: User;
@ManyToOne(() => Food) @ManyToOne(() => Product)
food: Food; product: Product;
} }
+9 -9
View File
@@ -1,32 +1,32 @@
import { Cascade, Collection, Entity, Index, ManyToOne, OneToMany, Property, OneToOne } from '@mikro-orm/core'; import { Cascade, Collection, Entity, Index, ManyToOne, OneToMany, Property, OneToOne } from '@mikro-orm/core';
import { Category } from './category.entity'; import { Category } from './category.entity';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../common/entities/base.entity';
import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity'; import { Shop } from '../../../modules/shops/entities/shop.entity';
import { Review } from 'src/modules/review/entities/review.entity'; import { Review } from 'src/modules/review/entities/review.entity';
import { Inventory } from 'src/modules/inventory/entities/inventory.entity'; import { Inventory } from 'src/modules/inventory/entities/inventory.entity';
import { Favorite } from './favorite.entity'; import { Favorite } from './favorite.entity';
@Entity({ tableName: 'foods' }) @Entity({ tableName: 'products' })
@Index({ properties: ['restaurant', 'isActive'] }) @Index({ properties: ['shop', 'isActive'] })
@Index({ properties: ['category', 'isActive'] }) @Index({ properties: ['category', 'isActive'] })
@Index({ properties: ['isActive'] }) @Index({ properties: ['isActive'] })
export class Food extends BaseEntity { export class Product extends BaseEntity {
@ManyToOne(() => Restaurant) @ManyToOne(() => Shop)
restaurant: Restaurant; shop: Shop;
@ManyToOne(() => Category) @ManyToOne(() => Category)
category: 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); reviews = new Collection<Review>(this);
@OneToOne(() => Inventory, { @OneToOne(() => Inventory, {
mappedBy: 'food', mappedBy: 'product',
nullable: true, nullable: true,
}) })
inventory?: Inventory; inventory?: Inventory;
@OneToMany(() => Favorite, favorite => favorite.food) @OneToMany(() => Favorite, favorite => favorite.product)
favorites = new Collection<Favorite>(this); favorites = new Collection<Favorite>(this);
@Property({ nullable: true }) @Property({ nullable: true })
+12 -12
View File
@@ -1,15 +1,15 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { FoodService } from './providers/food.service'; import { ProductService } from './providers/product.service';
import { FoodStockCrone } from './crone/food.crone'; import { ProductStockCrone } from './crone/product.crone';
import { FoodController } from './controllers/food.controller'; import { ProductController } from './controllers/product.controller';
import { CategoryController } from './controllers/category.controller'; import { CategoryController } from './controllers/category.controller';
import { CategoryService } from './providers/category.service'; 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 { CategoryRepository } from './repositories/category.repository';
import { MikroOrmModule } from '@mikro-orm/nestjs'; import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Category } from './entities/category.entity'; 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 { ShopsModule } from '../shops/shops.module';
import { AuthModule } from '../auth/auth.module'; import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { UtilsModule } from '../utils/utils.module'; import { UtilsModule } from '../utils/utils.module';
@@ -17,14 +17,14 @@ import { Favorite } from './entities/favorite.entity';
@Module({ @Module({
imports: [ imports: [
MikroOrmModule.forFeature([Food, Category, Favorite]), MikroOrmModule.forFeature([Product, Category, Favorite]),
RestaurantsModule, ShopsModule,
AuthModule, AuthModule,
JwtModule, JwtModule,
UtilsModule, UtilsModule,
], ],
controllers: [FoodController, CategoryController], controllers: [ProductController, CategoryController],
providers: [FoodService, CategoryService, FoodRepository, CategoryRepository, FoodStockCrone], providers: [ProductService, CategoryService, ProductRepository, CategoryRepository, ProductStockCrone],
exports: [FoodRepository, CategoryRepository], exports: [ProductRepository, CategoryRepository],
}) })
export class FoodModule {} export class ProductModule {}
+1 -1
View File
@@ -1,5 +1,5 @@
import { Entity, Index, Property, Collection, OneToMany, Cascade } from '@mikro-orm/core'; import { Entity, Index, Property, Collection, OneToMany, Cascade } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { Icon } from './icon.entity'; import { Icon } from './icon.entity';
@Entity({ tableName: 'icon_groups' }) @Entity({ tableName: 'icon_groups' })
+1 -1
View File
@@ -1,5 +1,5 @@
import { Entity, Index, Property, ManyToOne } from '@mikro-orm/core'; import { Entity, Index, Property, ManyToOne } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { Group } from './group.entity'; import { Group } from './group.entity';
@Entity({ tableName: 'icons' }) @Entity({ tableName: 'icons' })
@@ -3,7 +3,7 @@ import { IsNotEmpty, IsArray, ValidateNested, ArrayMinSize, IsString, IsNumber,
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
export class BulkReserveFoodItemDto { export class BulkReserveFoodItemDto {
@ApiProperty({ example: 'food-123', description: 'Food ID' }) @ApiProperty({ example: 'product-123', description: 'Product ID' })
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
foodId!: string; foodId!: string;
@@ -18,11 +18,11 @@ export class BulkReserveFoodItemDto {
export class BulkReserveFoodDto { export class BulkReserveFoodDto {
@ApiProperty({ @ApiProperty({
description: 'Array of food reservations to create', description: 'Array of product reservations to create',
type: [BulkReserveFoodItemDto], type: [BulkReserveFoodItemDto],
example: [ example: [
{ foodId: 'food-123', quantity: 5 }, { foodId: 'product-123', quantity: 5 },
{ foodId: 'food-789', quantity: 3 }, { foodId: 'product-789', quantity: 3 },
], ],
}) })
@IsNotEmpty() @IsNotEmpty()
@@ -4,7 +4,7 @@ import { Type } from 'class-transformer';
import { SetStockDto } from './set-stock.dto'; import { SetStockDto } from './set-stock.dto';
export class BulkSetStockItemDto extends SetStockDto { export class BulkSetStockItemDto extends SetStockDto {
@ApiProperty({ example: 'food-123', description: 'Food ID' }) @ApiProperty({ example: 'product-123', description: 'Product ID' })
@IsNotEmpty() @IsNotEmpty()
foodId!: string; foodId!: string;
} }
@@ -14,8 +14,8 @@ export class BulkSetStockDto {
description: 'Array of stock items to set', description: 'Array of stock items to set',
type: [BulkSetStockItemDto], type: [BulkSetStockItemDto],
example: [ example: [
{ foodId: 'food-123', totalStock: 100, availableStock: 80 }, { foodId: 'product-123', totalStock: 100, availableStock: 80 },
{ foodId: 'food-456', totalStock: 50, availableStock: 45 }, { foodId: 'product-456', totalStock: 50, availableStock: 45 },
], ],
}) })
@IsNotEmpty() @IsNotEmpty()
@@ -1,14 +1,14 @@
import { Cascade, Entity, Property, OneToOne } from '@mikro-orm/core'; import { Cascade, Entity, Property, OneToOne } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { Food } from '../../foods/entities/food.entity'; import { Product } from '../../../products/entities/product.entity';
@Entity({ tableName: 'inventory' }) @Entity({ tableName: 'inventory' })
export class Inventory extends BaseEntity { export class Inventory extends BaseEntity {
@OneToOne(() => Food, { @OneToOne(() => Product, {
cascade: [Cascade.ALL], cascade: [Cascade.ALL],
orphanRemoval: true, orphanRemoval: true,
}) })
food!: Food; product!: Product;
@Property({ type: 'int' }) @Property({ type: 'int' })
totalStock!: number; totalStock!: number;
@@ -17,9 +17,9 @@ import { Permission } from 'src/common/enums/permission.enum';
export class InventoryController { export class InventoryController {
constructor(private readonly inventoryService: InventoryService) { } constructor(private readonly inventoryService: InventoryService) { }
@Patch('admin/inventory/food/:foodId/stock') @Patch('admin/inventory/product/:foodId/stock')
@ApiOperation({ summary: 'Set available and total stock for a food item' }) @ApiOperation({ summary: 'Set available and total stock for a product item' })
@ApiParam({ name: 'foodId', description: 'Food ID' }) @ApiParam({ name: 'foodId', description: 'Product ID' })
@ApiBody({ type: SetStockDto }) @ApiBody({ type: SetStockDto })
setStockForFood( setStockForFood(
@Param('foodId') foodId: string, @Param('foodId') foodId: string,
@@ -29,8 +29,8 @@ export class InventoryController {
return this.inventoryService.setStockForFood(foodId, restaurantId, setStockDto); return this.inventoryService.setStockForFood(foodId, restaurantId, setStockDto);
} }
@Post('admin/inventory/foods/stock/bulk') @Post('admin/inventory/products/stock/bulk')
@ApiOperation({ summary: 'Bulk set available and total stock for multiple food items' }) @ApiOperation({ summary: 'Bulk set available and total stock for multiple product items' })
@ApiBody({ type: BulkSetStockDto }) @ApiBody({ type: BulkSetStockDto })
bulkSetStockForFoods(@RestId() restaurantId: string, @Body() bulkSetStockDto: BulkSetStockDto): Promise<Inventory[]> { bulkSetStockForFoods(@RestId() restaurantId: string, @Body() bulkSetStockDto: BulkSetStockDto): Promise<Inventory[]> {
return this.inventoryService.bulkSetStockForFoods(restaurantId, bulkSetStockDto); return this.inventoryService.bulkSetStockForFoods(restaurantId, bulkSetStockDto);
+55 -55
View File
@@ -2,10 +2,10 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/comm
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { SetStockDto } from './dto/set-stock.dto'; import { SetStockDto } from './dto/set-stock.dto';
import { BulkSetStockDto } from './dto/bulk-set-stock.dto'; import { BulkSetStockDto } from './dto/bulk-set-stock.dto';
import { BulkReserveFoodDto } from './dto/bulk-reserve-food.dto'; import { BulkReserveFoodDto } from './dto/bulk-reserve-product.dto';
import { Inventory } from './entities/inventory.entity'; import { Inventory } from './entities/inventory.entity';
import { Food } from '../foods/entities/food.entity'; import { Product } from ../../..products/entities/product.entity';
import { Restaurant } from '../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
@Injectable() @Injectable()
export class InventoryService { export class InventoryService {
@@ -17,30 +17,30 @@ export class InventoryService {
throw new BadRequestException('Available stock cannot exceed total stock'); throw new BadRequestException('Available stock cannot exceed total stock');
} }
// Find food and verify it belongs to the restaurant // Find product and verify it belongs to the shop
const food = await this.em.findOne(Food, { id: foodId }, { populate: ['restaurant'] }); const product = await this.em.findOne(Product, { id: foodId }, { populate: ['shop'] });
if (!food) { if (!product) {
throw new NotFoundException(`Food with ID ${foodId} not found`); throw new NotFoundException(`Product with ID ${foodId} not found`);
} }
if (food.restaurant.id !== restaurantId) { if (product.shop.id !== restaurantId) {
throw new BadRequestException(`Food does not belong to restaurant ${restaurantId}`); throw new BadRequestException(`Product does not belong to shop ${restaurantId}`);
} }
// Find or create inventory record // Find or create inventory record
let inventory = await this.em.findOne(Inventory, { let inventory = await this.em.findOne(Inventory, {
food: { id: foodId, restaurant: { id: restaurantId } }, product: { id: foodId, shop: { id: restaurantId } },
}); });
if (!inventory) { if (!inventory) {
// Create new inventory record // Create new inventory record
const restaurant = await this.em.findOne(Restaurant, { id: restaurantId }); const shop = await this.em.findOne(Shop, { id: restaurantId });
if (!restaurant) { if (!shop) {
throw new NotFoundException(`Restaurant with ID ${restaurantId} not found`); throw new NotFoundException(`Shop with ID ${restaurantId} not found`);
} }
inventory = this.em.create(Inventory, { inventory = this.em.create(Inventory, {
food, product,
totalStock: setStockDto.totalStock, totalStock: setStockDto.totalStock,
availableStock: setStockDto.availableStock, availableStock: setStockDto.availableStock,
}); });
@@ -60,57 +60,57 @@ export class InventoryService {
// Validate all items first // Validate all items first
for (const item of items) { for (const item of items) {
if (item.availableStock > item.totalStock) { if (item.availableStock > item.totalStock) {
throw new BadRequestException(`Available stock cannot exceed total stock for food ${item.foodId}`); throw new BadRequestException(`Available stock cannot exceed total stock for product ${item.foodId}`);
} }
} }
// Get all food IDs // Get all product IDs
const foodIds = items.map(item => item.foodId); const foodIds = items.map(item => item.foodId);
// Load all foods in one query // Load all products in one query
const foods = await this.em.find(Food, { id: { $in: foodIds } }, { populate: ['restaurant'] }); const products = await this.em.find(Product, { id: { $in: foodIds } }, { populate: ['shop'] });
// Verify all foods exist and belong to the restaurant // Verify all products exist and belong to the shop
const foodMap = new Map<string, Food>(); const foodMap = new Map<string, Product>();
for (const food of foods) { for (const product of products) {
if (food.restaurant.id !== restaurantId) { if (product.shop.id !== restaurantId) {
throw new BadRequestException(`Food ${food.id} does not belong to restaurant ${restaurantId}`); throw new BadRequestException(`Product ${product.id} does not belong to shop ${restaurantId}`);
} }
foodMap.set(food.id, food); foodMap.set(product.id, product);
} }
// Check for missing foods // Check for missing products
const missingFoodIds = foodIds.filter(id => !foodMap.has(id)); const missingFoodIds = foodIds.filter(id => !foodMap.has(id));
if (missingFoodIds.length > 0) { if (missingFoodIds.length > 0) {
throw new NotFoundException(`Foods not found: ${missingFoodIds.join(', ')}`); throw new NotFoundException(`Products not found: ${missingFoodIds.join(', ')}`);
} }
// Load all existing inventories in one query // Load all existing inventories in one query
const existingInventories = await this.em.find(Inventory, { const existingInventories = await this.em.find(Inventory, {
food: { id: { $in: foodIds }, restaurant: { id: restaurantId } }, product: { id: { $in: foodIds }, shop: { id: restaurantId } },
}); });
const inventoryMap = new Map<string, Inventory>(); const inventoryMap = new Map<string, Inventory>();
for (const inventory of existingInventories) { for (const inventory of existingInventories) {
inventoryMap.set(inventory.food.id, inventory); inventoryMap.set(inventory.product.id, inventory);
} }
// Get restaurant once // Get shop once
const restaurant = await this.em.findOne(Restaurant, { id: restaurantId }); const shop = await this.em.findOne(Shop, { id: restaurantId });
if (!restaurant) { if (!shop) {
throw new NotFoundException(`Restaurant with ID ${restaurantId} not found`); throw new NotFoundException(`Shop with ID ${restaurantId} not found`);
} }
// Process all items // Process all items
const results: Inventory[] = []; const results: Inventory[] = [];
for (const item of items) { for (const item of items) {
const food = foodMap.get(item.foodId)!; const product = foodMap.get(item.foodId)!;
let inventory = inventoryMap.get(item.foodId); let inventory = inventoryMap.get(item.foodId);
if (!inventory) { if (!inventory) {
// Create new inventory record // Create new inventory record
inventory = this.em.create(Inventory, { inventory = this.em.create(Inventory, {
food, product,
totalStock: item.totalStock, totalStock: item.totalStock,
availableStock: item.availableStock, availableStock: item.availableStock,
}); });
@@ -132,32 +132,32 @@ export class InventoryService {
async deductFromInventory(em: EntityManager, bulkReserveFoodDto: BulkReserveFoodDto): Promise<Inventory[]> { async deductFromInventory(em: EntityManager, bulkReserveFoodDto: BulkReserveFoodDto): Promise<Inventory[]> {
return em.transactional(async em => { return em.transactional(async em => {
const { items } = bulkReserveFoodDto; const { items } = bulkReserveFoodDto;
// Get all unique food IDs // Get all unique product IDs
const foodIds = [...new Set(items.map(item => item.foodId))]; const foodIds = [...new Set(items.map(item => item.foodId))];
// Load all foods in one query // Load all products in one query
const foods = await em.find(Food, { id: { $in: foodIds } }); const products = await em.find(Product, { id: { $in: foodIds } });
// Verify all foods exist and belong to the restaurant // Verify all products exist and belong to the shop
const foodMap = new Map<string, Food>(); const foodMap = new Map<string, Product>();
for (const food of foods) { for (const product of products) {
foodMap.set(food.id, food); foodMap.set(product.id, product);
} }
// Check for missing foods // Check for missing products
const missingFoodIds = foodIds.filter(id => !foodMap.has(id)); const missingFoodIds = foodIds.filter(id => !foodMap.has(id));
if (missingFoodIds.length > 0) { if (missingFoodIds.length > 0) {
throw new NotFoundException(`Foods not found: ${missingFoodIds.join(', ')}`); throw new NotFoundException(`Products not found: ${missingFoodIds.join(', ')}`);
} }
// Load all existing inventories in one query // Load all existing inventories in one query
const existingInventories = await em.find(Inventory, { const existingInventories = await em.find(Inventory, {
food: { id: { $in: foodIds } }, product: { id: { $in: foodIds } },
}); });
const inventoryMap = new Map<string, Inventory>(); const inventoryMap = new Map<string, Inventory>();
for (const inventory of existingInventories) { for (const inventory of existingInventories) {
inventoryMap.set(inventory.food.id, inventory); inventoryMap.set(inventory.product.id, inventory);
} }
// Validate stock availability and create reservations // Validate stock availability and create reservations
@@ -167,13 +167,13 @@ export class InventoryService {
// Check if inventory exists // Check if inventory exists
if (!inventory) { if (!inventory) {
throw new NotFoundException(`Inventory not found for food ${item.foodId}`); throw new NotFoundException(`Inventory not found for product ${item.foodId}`);
} }
// Check if available stock is sufficient // Check if available stock is sufficient
if (inventory.availableStock < item.quantity) { if (inventory.availableStock < item.quantity) {
throw new BadRequestException( throw new BadRequestException(
`Insufficient stock for food ${item.foodId}. Available: ${inventory.availableStock}, Requested: ${item.quantity}`, `Insufficient stock for product ${item.foodId}. Available: ${inventory.availableStock}, Requested: ${item.quantity}`,
); );
} }
inventory.availableStock -= item.quantity; inventory.availableStock -= item.quantity;
@@ -190,31 +190,31 @@ export class InventoryService {
const foodIds = [...new Set(items.map(item => item.foodId))]; const foodIds = [...new Set(items.map(item => item.foodId))];
const foods = await em.find(Food, { id: { $in: foodIds } }); const products = await em.find(Product, { id: { $in: foodIds } });
const foodMap = new Map<string, Food>(); const foodMap = new Map<string, Product>();
for (const food of foods) { for (const product of products) {
foodMap.set(food.id, food); foodMap.set(product.id, product);
} }
const missingFoodIds = foodIds.filter(id => !foodMap.has(id)); const missingFoodIds = foodIds.filter(id => !foodMap.has(id));
if (missingFoodIds.length > 0) { if (missingFoodIds.length > 0) {
throw new NotFoundException(`Foods not found: ${missingFoodIds.join(', ')}`); throw new NotFoundException(`Products not found: ${missingFoodIds.join(', ')}`);
} }
const existingInventories = await em.find(Inventory, { const existingInventories = await em.find(Inventory, {
food: { id: { $in: foodIds } }, product: { id: { $in: foodIds } },
}); });
const inventoryMap = new Map<string, Inventory>(); const inventoryMap = new Map<string, Inventory>();
for (const inventory of existingInventories) { for (const inventory of existingInventories) {
inventoryMap.set(inventory.food.id, inventory); inventoryMap.set(inventory.product.id, inventory);
} }
const inventories: Inventory[] = []; const inventories: Inventory[] = [];
for (const item of items) { for (const item of items) {
const inventory = inventoryMap.get(item.foodId); const inventory = inventoryMap.get(item.foodId);
if (!inventory) { if (!inventory) {
throw new NotFoundException(`Inventory not found for food ${item.foodId}`); throw new NotFoundException(`Inventory not found for product ${item.foodId}`);
} }
inventory.availableStock += item.quantity; inventory.availableStock += item.quantity;
inventories.push(inventory); inventories.push(inventory);
@@ -2,10 +2,10 @@ import { Controller, Get, Body, Param, UseGuards, Query, Patch, Put, Post } from
import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiQuery, ApiHeader } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiQuery, ApiHeader } from '@nestjs/swagger';
import { NotificationService } from '../services/notification.service'; import { NotificationService } from '../services/notification.service';
import { NotificationPreferenceService } from '../services/notification-preference.service'; import { NotificationPreferenceService } from '../services/notification-preference.service';
import { AuthGuard } from '../../auth/guards/auth.guard'; import { AuthGuard } from '../../../auth/guards/auth.guard';
import { UserId } from '../../../common/decorators/user-id.decorator'; import { UserId } from '../../../../common/decorators/user-id.decorator';
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard'; import { AdminAuthGuard } from '../../../auth/guards/adminAuth.guard';
import { RestId } from '../../../common/decorators/rest-id.decorator'; import { RestId } from '../../../../common/decorators/rest-id.decorator';
import { UpdatePreferenceDto } from '../dto/update-preference.dto'; import { UpdatePreferenceDto } from '../dto/update-preference.dto';
import { CreatePreferenceDto } from '../dto/create-preference.dto'; import { CreatePreferenceDto } from '../dto/create-preference.dto';
import { AdminId } from 'src/common/decorators/admin-id.decorator'; import { AdminId } from 'src/common/decorators/admin-id.decorator';
@@ -26,7 +26,7 @@ export class NotificationsController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('public/notifications') @Get('public/notifications')
@ApiOperation({ summary: 'Get user restaurant notifications' }) @ApiOperation({ summary: 'Get user shop notifications' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiQuery({ name: 'limit', required: false, type: Number, description: 'Number of items to return (default: 50)' }) @ApiQuery({ name: 'limit', required: false, type: Number, description: 'Number of items to return (default: 50)' })
@ApiQuery({ @ApiQuery({
@@ -137,7 +137,7 @@ export class NotificationsController {
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_SETTINGS) @Permissions(Permission.MANAGE_SETTINGS)
@Get('admin/notification-preferences') @Get('admin/notification-preferences')
@ApiOperation({ summary: 'Get all notification preferences for a restaurant' }) @ApiOperation({ summary: 'Get all notification preferences for a shop' })
async getPreferences(@RestId() restaurantId: string) { async getPreferences(@RestId() restaurantId: string) {
return this.preferenceService.findByRestaurant(restaurantId); return this.preferenceService.findByRestaurant(restaurantId);
} }
@@ -171,7 +171,7 @@ export class NotificationsController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('admin/notifications/sms-usage') @Get('admin/notifications/sms-usage')
@ApiOperation({ summary: 'Get SMS usage for my restaurant' }) @ApiOperation({ summary: 'Get SMS usage for my shop' })
async getRestaurantSmsUsage(@RestId() restaurantId: string) { async getRestaurantSmsUsage(@RestId() restaurantId: string) {
const smsCount = await this.notificationService.getSmsCountByRestaurantId(restaurantId); const smsCount = await this.notificationService.getSmsCountByRestaurantId(restaurantId);
return { smsCount }; return { smsCount };
@@ -182,7 +182,7 @@ export class NotificationsController {
@UseGuards(SuperAdminAuthGuard) @UseGuards(SuperAdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('super-admin/notifications/sms-count') @Get('super-admin/notifications/sms-count')
@ApiOperation({ summary: 'Get SMS count for each restaurant with pagination' }) @ApiOperation({ summary: 'Get SMS count for each shop with pagination' })
@ApiQuery({ name: 'page', required: false, type: Number, description: 'Page number (default: 1)', minimum: 1 }) @ApiQuery({ name: 'page', required: false, type: Number, description: 'Page number (default: 1)', minimum: 1 })
@ApiQuery({ name: 'limit', required: false, type: Number, description: 'Items per page (default: 10)', minimum: 1 }) @ApiQuery({ name: 'limit', required: false, type: Number, description: 'Items per page (default: 10)', minimum: 1 })
async getSmsCount( async getSmsCount(
@@ -3,7 +3,7 @@ import { createParamDecorator } from '@nestjs/common';
import type { AuthenticatedSocket } from '../guards/ws-admin-auth.guard'; import type { AuthenticatedSocket } from '../guards/ws-admin-auth.guard';
/** /**
* Extract restaurant ID from authenticated WebSocket client * Extract shop ID from authenticated WebSocket client
* Use this decorator in WebSocket handlers to get the restId from the authenticated admin * Use this decorator in WebSocket handlers to get the restId from the authenticated admin
* *
* @example * @example
@@ -19,7 +19,7 @@ export const WsRestId = createParamDecorator((data: unknown, ctx: ExecutionConte
const restId = client.restId; const restId = client.restId;
if (!restId) { if (!restId) {
throw new Error('Restaurant ID not found. Ensure WsAdminAuthGuard is applied.'); throw new Error('Shop ID not found. Ensure WsAdminAuthGuard is applied.');
} }
return restId; return restId;
@@ -2,7 +2,7 @@ import { IsString, IsNotEmpty, IsOptional, IsObject } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class SendNotificationDto { export class SendNotificationDto {
@ApiProperty({ description: 'Restaurant ID (ULID)' }) @ApiProperty({ description: 'Shop ID (ULID)' })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
restaurantId: string; restaurantId: string;
@@ -1,14 +1,14 @@
import { Entity, Property, ManyToOne, Unique } from '@mikro-orm/core'; import { Entity, Property, ManyToOne, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { NotifTitleEnum } from '../interfaces/notification.interface'; import { NotifTitleEnum } from '../interfaces/notification.interface';
import { NotifChannelEnum } from '../interfaces/notification.interface'; import { NotifChannelEnum } from '../interfaces/notification.interface';
@Entity({ tableName: 'notification_preferences' }) @Entity({ tableName: 'notification_preferences' })
@Unique({ properties: ['restaurant', 'title'] }) @Unique({ properties: ['shop', 'title'] })
export class NotificationPreference extends BaseEntity { export class NotificationPreference extends BaseEntity {
@ManyToOne(() => Restaurant) @ManyToOne(() => Shop)
restaurant!: Restaurant; shop!: Shop;
@Property() @Property()
title!: NotifTitleEnum; title!: NotifTitleEnum;
@@ -1,14 +1,14 @@
import { Entity, Property, ManyToOne, Enum } from '@mikro-orm/core'; import { Entity, Property, ManyToOne, Enum } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { User } from '../../users/entities/user.entity'; import { User } from '../../../users/entities/user.entity';
import { NotifTitleEnum } from '../interfaces/notification.interface'; import { NotifTitleEnum } from '../interfaces/notification.interface';
import { Admin } from 'src/modules/admin/entities/admin.entity'; import { Admin } from 'src/modules/admin/entities/admin.entity';
@Entity({ tableName: 'notifications' }) @Entity({ tableName: 'notifications' })
export class Notification extends BaseEntity { export class Notification extends BaseEntity {
@ManyToOne(() => Restaurant) @ManyToOne(() => Shop)
restaurant!: Restaurant; shop!: Shop;
@ManyToOne(() => User, { nullable: true }) @ManyToOne(() => User, { nullable: true })
user?: User; user?: User;
@@ -1,13 +1,13 @@
import { Entity, Property, ManyToOne, PrimaryKey } from '@mikro-orm/core'; import { Entity, Property, ManyToOne, PrimaryKey } from '@mikro-orm/core';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
@Entity({ tableName: 'sms_logs' }) @Entity({ tableName: 'sms_logs' })
export class SmsLog { export class SmsLog {
@PrimaryKey() @PrimaryKey()
id!: number; id!: number;
@ManyToOne(() => Restaurant) @ManyToOne(() => Shop)
restaurant!: Restaurant; shop!: Shop;
@Property() @Property()
phone!: string; phone!: string;
@@ -3,7 +3,7 @@ import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io'; import { Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { IAdminTokenPayload } from '../../auth/interfaces/IToken-payload'; import { IAdminTokenPayload } from '../../../auth/interfaces/IToken-payload';
export interface AuthenticatedSocket extends Socket { export interface AuthenticatedSocket extends Socket {
adminId?: string; adminId?: string;
@@ -1,7 +1,7 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
import { SmsSentEvent } from '../events/sms.events'; import { SmsSentEvent } from '../events/sms.events';
import { RestRepository } from '../../restaurants/repositories/rest.repository'; import { RestRepository } from ../../..shops/repositories/rest.repository';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { SmsLog } from '../entities/smsLogs.entity'; import { SmsLog } from '../entities/smsLogs.entity';
@@ -19,22 +19,22 @@ export class SmsListeners {
async handleSmsSent(event: SmsSentEvent) { async handleSmsSent(event: SmsSentEvent) {
try { try {
this.logger.log( this.logger.log(
`SMS sent event received: phone ${event.phoneNumber} for restaurant: ${event.restaurantId}`, `SMS sent event received: phone ${event.phoneNumber} for shop: ${event.restaurantId}`,
); );
// Get the restaurant entity // Get the shop entity
const restaurant = await this.restRepository.findOne({ id: event.restaurantId }); const shop = await this.restRepository.findOne({ id: event.restaurantId });
if (!restaurant) { if (!shop) {
this.logger.warn( this.logger.warn(
`Restaurant not found for SMS log: ${event.restaurantId}`, `Shop not found for SMS log: ${event.restaurantId}`,
); );
return; return;
} }
// Create SMS log record // Create SMS log record
const smsLog = this.em.create(SmsLog, { const smsLog = this.em.create(SmsLog, {
restaurant: restaurant, shop: shop,
phone: event.phoneNumber, phone: event.phoneNumber,
createdAt: new Date(), createdAt: new Date(),
}); });
@@ -82,7 +82,7 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco
} }
private getRoom(adminId: string, restaurantId: string) { private getRoom(adminId: string, restaurantId: string) {
return `restaurant:${restaurantId}-admin:${adminId}`; return `shop:${restaurantId}-admin:${adminId}`;
} }
handleLeaveRoom(client: AuthenticatedSocket) { handleLeaveRoom(client: AuthenticatedSocket) {
@@ -12,7 +12,7 @@ import { SmsProcessor } from './processors/sms.processor';
import { PushProcessor } from './processors/push.processor'; import { PushProcessor } from './processors/push.processor';
import { NotificationsController } from './controllers/notifications.controller'; import { NotificationsController } from './controllers/notifications.controller';
import { User } from '../users/entities/user.entity'; import { User } from '../users/entities/user.entity';
import { Restaurant } from '../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { AuthModule } from '../auth/auth.module'; import { AuthModule } from '../auth/auth.module';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { NotificationQueueNameEnum } from './constants/queue'; import { NotificationQueueNameEnum } from './constants/queue';
@@ -26,14 +26,14 @@ import { NotificationCrone } from './crone/notification.crone';
import { SmsService } from './services/sms.service'; import { SmsService } from './services/sms.service';
import { SmsLog } from './entities/smsLogs.entity'; import { SmsLog } from './entities/smsLogs.entity';
import { SmsLogRepository } from './repositories/sms-log.repository'; import { SmsLogRepository } from './repositories/sms-log.repository';
import { RestaurantsModule } from '../restaurants/restaurants.module'; import { RestaurantsModule } from ../../..shops.module';
import { SmsListeners } from './listeners/sms.listeners'; import { SmsListeners } from './listeners/sms.listeners';
@Module({ @Module({
imports: [ imports: [
forwardRef(() => AuthModule), forwardRef(() => AuthModule),
JwtModule, JwtModule,
MikroOrmModule.forFeature([Notification, NotificationPreference, User, Restaurant, SmsLog]), MikroOrmModule.forFeature([Notification, NotificationPreference, User, Shop, SmsLog]),
BullModule.registerQueue( BullModule.registerQueue(
{ name: NotificationQueueNameEnum.SMS }, { name: NotificationQueueNameEnum.SMS },
{ name: NotificationQueueNameEnum.PUSH }, { name: NotificationQueueNameEnum.PUSH },
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
import { SmsLog } from '../entities/smsLogs.entity'; import { SmsLog } from '../entities/smsLogs.entity';
import { PaginatedResult } from '../../../common/interfaces/pagination.interface'; import { PaginatedResult } from '../../../../common/interfaces/pagination.interface';
@Injectable() @Injectable()
export class SmsLogRepository extends EntityRepository<SmsLog> { export class SmsLogRepository extends EntityRepository<SmsLog> {
@@ -15,7 +15,7 @@ export class SmsLogRepository extends EntityRepository<SmsLog> {
): Promise<PaginatedResult<{ restaurantId: string; restaurantName: string; smsCount: number }>> { ): Promise<PaginatedResult<{ restaurantId: string; restaurantName: string; smsCount: number }>> {
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
// Get total count of unique restaurants with SMS logs // Get total count of unique shops with SMS logs
const totalResult = await this.em.execute( const totalResult = await this.em.execute(
` `
SELECT COUNT(DISTINCT sl.restaurant_id) as total SELECT COUNT(DISTINCT sl.restaurant_id) as total
@@ -25,7 +25,7 @@ export class SmsLogRepository extends EntityRepository<SmsLog> {
); );
const total = parseInt(totalResult[0]?.total || '0', 10); const total = parseInt(totalResult[0]?.total || '0', 10);
// Get paginated results with SMS counts grouped by restaurant // Get paginated results with SMS counts grouped by shop
const results = await this.em.execute( const results = await this.em.execute(
` `
SELECT SELECT
@@ -33,7 +33,7 @@ export class SmsLogRepository extends EntityRepository<SmsLog> {
r.name as "restaurantName", r.name as "restaurantName",
COUNT(sl.id)::int as "smsCount" COUNT(sl.id)::int as "smsCount"
FROM sms_logs sl FROM sms_logs sl
INNER JOIN restaurants r ON sl.restaurant_id = r.id INNER JOIN shops r ON sl.restaurant_id = r.id
GROUP BY r.id, r.name GROUP BY r.id, r.name
ORDER BY "smsCount" DESC, r.name ASC ORDER BY "smsCount" DESC, r.name ASC
LIMIT ? OFFSET ? LIMIT ? OFFSET ?
@@ -1,7 +1,7 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { NotificationPreference } from '../entities/notification-preference.entity'; import { NotificationPreference } from '../entities/notification-preference.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { CreatePreferenceDto } from '../dto/create-preference.dto'; import { CreatePreferenceDto } from '../dto/create-preference.dto';
import { UpdatePreferenceDto } from '../dto/update-preference.dto'; import { UpdatePreferenceDto } from '../dto/update-preference.dto';
import { NotifTitleEnum } from '../interfaces/notification.interface'; import { NotifTitleEnum } from '../interfaces/notification.interface';
@@ -11,13 +11,13 @@ export class NotificationPreferenceService {
constructor(private readonly em: EntityManager) { } constructor(private readonly em: EntityManager) { }
async create(restaurantId: string, dto: CreatePreferenceDto): Promise<NotificationPreference> { async create(restaurantId: string, dto: CreatePreferenceDto): Promise<NotificationPreference> {
const restaurant = await this.em.findOne(Restaurant, { id: restaurantId }); const shop = await this.em.findOne(Shop, { id: restaurantId });
if (!restaurant) { if (!shop) {
throw new NotFoundException('Restaurant not found'); throw new NotFoundException('Shop not found');
} }
const preference = this.em.create(NotificationPreference, { const preference = this.em.create(NotificationPreference, {
restaurant, shop,
channels: dto.channels, channels: dto.channels,
title: dto.title, title: dto.title,
}); });
@@ -28,13 +28,13 @@ export class NotificationPreferenceService {
async findByRestaurant(restaurantId: string): Promise<NotificationPreference[]> { async findByRestaurant(restaurantId: string): Promise<NotificationPreference[]> {
return this.em.find(NotificationPreference, { return this.em.find(NotificationPreference, {
restaurant: { id: restaurantId }, shop: { id: restaurantId },
}); });
} }
async findByRestaurantAndType(restaurantId: string, title: NotifTitleEnum): Promise<NotificationPreference | null> { async findByRestaurantAndType(restaurantId: string, title: NotifTitleEnum): Promise<NotificationPreference | null> {
return this.em.findOne(NotificationPreference, { return this.em.findOne(NotificationPreference, {
restaurant: { id: restaurantId }, shop: { id: restaurantId },
title, title,
}); });
} }
@@ -61,7 +61,7 @@ export class NotificationPreferenceService {
): Promise<NotificationPreference> { ): Promise<NotificationPreference> {
const preference = await this.em.findOne(NotificationPreference, { const preference = await this.em.findOne(NotificationPreference, {
id: preferenceId, id: preferenceId,
restaurant: { id: restaurantId }, shop: { id: restaurantId },
}); });
if (!preference) { if (!preference) {
throw new NotFoundException('Notification preference not found'); throw new NotFoundException('Notification preference not found');
@@ -74,7 +74,7 @@ export class NotificationPreferenceService {
async delete(restaurantId: string, preferenceId: string): Promise<void> { async delete(restaurantId: string, preferenceId: string): Promise<void> {
const preference = await this.em.findOne(NotificationPreference, { const preference = await this.em.findOne(NotificationPreference, {
restaurant: { id: restaurantId }, shop: { id: restaurantId },
id: preferenceId, id: preferenceId,
}); });
if (!preference) { if (!preference) {
@@ -7,7 +7,7 @@ import { NotificationsGateway } from '../notifications.gateway';
import { NotifChannelEnum, NotifRequest, NotifTitleEnum } from '../interfaces/notification.interface'; import { NotifChannelEnum, NotifRequest, NotifTitleEnum } from '../interfaces/notification.interface';
import { SmsLog } from '../entities/smsLogs.entity'; import { SmsLog } from '../entities/smsLogs.entity';
import { SmsLogRepository } from '../repositories/sms-log.repository'; import { SmsLogRepository } from '../repositories/sms-log.repository';
import { PaginatedResult } from '../../../common/interfaces/pagination.interface'; import { PaginatedResult } from '../../../../common/interfaces/pagination.interface';
@Injectable() @Injectable()
export class NotificationService { export class NotificationService {
@@ -39,7 +39,7 @@ export class NotificationService {
const preference = await this.preferenceService.findByRestaurantAndType(restaurantId, message.title); const preference = await this.preferenceService.findByRestaurantAndType(restaurantId, message.title);
if (preference?.channels?.length === 0) { if (preference?.channels?.length === 0) {
this.logger.warn(`Notification type is NONE for restaurant ${restaurantId}, title ${message.title}`); this.logger.warn(`Notification type is NONE for shop ${restaurantId}, title ${message.title}`);
return notifications; return notifications;
} }
@@ -67,7 +67,7 @@ export class NotificationService {
); );
} }
this.logger.log(`Queued notification for restaurant ${restaurantId}, title ${message.title}`); this.logger.log(`Queued notification for shop ${restaurantId}, title ${message.title}`);
return notifications; return notifications;
} }
@@ -83,7 +83,7 @@ export class NotificationService {
): Promise<Notification[]> { ): Promise<Notification[]> {
const notifications = params.map(param => { const notifications = params.map(param => {
return this.em.create(Notification, { return this.em.create(Notification, {
restaurant: param.restaurantId, shop: param.restaurantId,
admin: param.adminId, admin: param.adminId,
user: param.userId && param.userId.trim() !== '' ? param.userId : null, user: param.userId && param.userId.trim() !== '' ? param.userId : null,
title: param.title, title: param.title,
@@ -95,7 +95,7 @@ export class NotificationService {
} }
async findOne(id: string): Promise<Notification> { async findOne(id: string): Promise<Notification> {
const notification = await this.em.findOne(Notification, { id }, { populate: ['restaurant', 'user'] }); const notification = await this.em.findOne(Notification, { id }, { populate: ['shop', 'user'] });
if (!notification) { if (!notification) {
throw new NotFoundException('Notification not found'); throw new NotFoundException('Notification not found');
} }
@@ -110,7 +110,7 @@ export class NotificationService {
status?: 'seen' | 'unseen', status?: 'seen' | 'unseen',
): Promise<{ data: Notification[]; nextCursor: string | null }> { ): Promise<{ data: Notification[]; nextCursor: string | null }> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
restaurant: { id: restaurantId }, shop: { id: restaurantId },
admin: { id: adminId }, admin: { id: adminId },
}; };
@@ -151,7 +151,7 @@ export class NotificationService {
): Promise<{ data: Notification[]; nextCursor: string | null }> { ): Promise<{ data: Notification[]; nextCursor: string | null }> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
user: { id: userId }, user: { id: userId },
restaurant: { id: restaurantId }, shop: { id: restaurantId },
}; };
// Filter by status (seen/unseen) // Filter by status (seen/unseen)
@@ -186,7 +186,7 @@ export class NotificationService {
const notification = await this.em.findOne(Notification, { const notification = await this.em.findOne(Notification, {
id, id,
admin: { id: adminId }, admin: { id: adminId },
restaurant: { id: restaurantId }, shop: { id: restaurantId },
}); });
if (!notification) { if (!notification) {
throw new NotFoundException('Notification not found'); throw new NotFoundException('Notification not found');
@@ -198,7 +198,7 @@ export class NotificationService {
const notification = await this.em.findOne(Notification, { const notification = await this.em.findOne(Notification, {
id, id,
user: { id: userId }, user: { id: userId },
restaurant: { id: restaurantId }, shop: { id: restaurantId },
}); });
if (!notification) { if (!notification) {
throw new NotFoundException('Notification not found'); throw new NotFoundException('Notification not found');
@@ -211,7 +211,7 @@ export class NotificationService {
return this.em.find( return this.em.find(
Notification, Notification,
{ {
restaurant: { id: restaurantId }, shop: { id: restaurantId },
title, title,
}, },
{ {
@@ -225,7 +225,7 @@ export class NotificationService {
async findByAdminAndRestaurant(adminId: string, restaurantId: string, limit = 50): Promise<Notification[]> { async findByAdminAndRestaurant(adminId: string, restaurantId: string, limit = 50): Promise<Notification[]> {
return this.em.find( return this.em.find(
Notification, Notification,
{ admin: { id: adminId }, restaurant: { id: restaurantId } }, { admin: { id: adminId }, shop: { id: restaurantId } },
{ {
orderBy: { createdAt: 'DESC' }, orderBy: { createdAt: 'DESC' },
limit, limit,
@@ -236,7 +236,7 @@ export class NotificationService {
async countUnseenByUserAndRestaurant(userId: string, restaurantId: string): Promise<number> { async countUnseenByUserAndRestaurant(userId: string, restaurantId: string): Promise<number> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
user: { id: userId }, user: { id: userId },
restaurant: { id: restaurantId }, shop: { id: restaurantId },
seenAt: null, seenAt: null,
}; };
return this.em.count(Notification, where); return this.em.count(Notification, where);
@@ -245,7 +245,7 @@ export class NotificationService {
async countUnseenByRestaurant(adminId: string, restaurantId: string): Promise<number> { async countUnseenByRestaurant(adminId: string, restaurantId: string): Promise<number> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
admin: { id: adminId }, admin: { id: adminId },
restaurant: { id: restaurantId }, shop: { id: restaurantId },
seenAt: null, seenAt: null,
}; };
return this.em.count(Notification, where); return this.em.count(Notification, where);
@@ -254,7 +254,7 @@ export class NotificationService {
readAllNotifsAsUser(userId: string, restaurantId: string): Promise<number> { readAllNotifsAsUser(userId: string, restaurantId: string): Promise<number> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
user: { id: userId }, user: { id: userId },
restaurant: { id: restaurantId }, shop: { id: restaurantId },
seenAt: null, seenAt: null,
}; };
return this.em.nativeUpdate(Notification, where, { seenAt: new Date() }); return this.em.nativeUpdate(Notification, where, { seenAt: new Date() });
@@ -263,7 +263,7 @@ export class NotificationService {
readAllNotifsAsAdmin(adminId: string, restaurantId: string): Promise<number> { readAllNotifsAsAdmin(adminId: string, restaurantId: string): Promise<number> {
const where: FilterQuery<Notification> = { const where: FilterQuery<Notification> = {
admin: { id: adminId }, admin: { id: adminId },
restaurant: { id: restaurantId }, shop: { id: restaurantId },
seenAt: null, seenAt: null,
}; };
return this.em.nativeUpdate(Notification, where, { seenAt: new Date() }); return this.em.nativeUpdate(Notification, where, { seenAt: new Date() });
@@ -1,10 +1,10 @@
import { Controller, Get, Post, Param, UseGuards, Patch, Query, Body } from '@nestjs/common'; import { Controller, Get, Post, Param, UseGuards, Patch, Query, Body } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiHeader, ApiBody } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiHeader, ApiBody } from '@nestjs/swagger';
import { OrdersService } from '../providers/orders.service'; import { OrdersService } from '../providers/orders.service';
import { AuthGuard } from '../../auth/guards/auth.guard'; import { AuthGuard } from '../../../auth/guards/auth.guard';
import { UserId } from '../../../common/decorators/user-id.decorator'; import { UserId } from '../../../../common/decorators/user-id.decorator';
import { RestId } from 'src/common/decorators/rest-id.decorator'; import { RestId } from 'src/common/decorators/rest-id.decorator';
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard'; import { AdminAuthGuard } from '../../../auth/guards/adminAuth.guard';
import { FindOrdersDto } from '../dto/find-orders.dto'; import { FindOrdersDto } from '../dto/find-orders.dto';
import { OrderStatus } from '../interface/order.interface'; import { OrderStatus } from '../interface/order.interface';
import { API_HEADER_SLUG } from 'src/common/constants/index'; import { API_HEADER_SLUG } from 'src/common/constants/index';
@@ -111,9 +111,9 @@ export class OrdersController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@Permissions(Permission.VIEW_REPORTS) @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) @ApiHeader(API_HEADER_SLUG)
@Get('admin/orders/food-sales-pie-chart') @Get('admin/orders/product-sales-pie-chart')
getFoodSalesPieChart(@RestId() restId: string) { getFoodSalesPieChart(@RestId() restId: string) {
return this.ordersService.getFoodSalesPieChart(restId); return this.ordersService.getFoodSalesPieChart(restId);
} }
+11 -11
View File
@@ -2,9 +2,9 @@ import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule'; import { Cron } from '@nestjs/schedule';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { Payment } from '../../payments/entities/payment.entity'; import { Payment } from '../../../payments/entities/payment.entity';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment'; import { PaymentMethodEnum, PaymentStatusEnum } from '../../../payments/interface/payment';
import { InventoryService } from '../../inventory/inventory.service'; import { InventoryService } from '../../../inventory/inventory.service';
import { OrderStatus } from '../interface/order.interface'; import { OrderStatus } from '../interface/order.interface';
import { Order } from '../entities/order.entity'; import { Order } from '../entities/order.entity';
import { OrderStatusChangedEvent } from '../events/order.events'; import { OrderStatusChangedEvent } from '../events/order.events';
@@ -37,7 +37,7 @@ export class OrdersCrone {
status: PaymentStatusEnum.Pending, status: PaymentStatusEnum.Pending,
createdAt: { $lte: cutoff }, createdAt: { $lte: cutoff },
}, },
{ populate: ['order', 'order.items', 'order.items.food'] }, { populate: ['order', 'order.items', 'order.items.product'] },
); );
if (!payments || payments.length === 0) { if (!payments || payments.length === 0) {
@@ -53,7 +53,7 @@ export class OrdersCrone {
const payment = await em.findOne( const payment = await em.findOne(
Payment, Payment,
{ id: p.id }, { id: p.id },
{ populate: ['order', 'order.items', 'order.items.food'] }, { populate: ['order', 'order.items', 'order.items.product'] },
); );
if (!payment) return; if (!payment) return;
if (payment.status !== PaymentStatusEnum.Pending) return; if (payment.status !== PaymentStatusEnum.Pending) return;
@@ -67,7 +67,7 @@ export class OrdersCrone {
// prepare restore payload // prepare restore payload
const items = (payment.order as any).items || []; const items = (payment.order as any).items || [];
const restorePayload = { const restorePayload = {
items: items.map((it: any) => ({ foodId: it.food.id, quantity: it.quantity })), items: items.map((it: any) => ({ foodId: it.product.id, quantity: it.quantity })),
}; };
if (restorePayload.items.length > 0) { if (restorePayload.items.length > 0) {
@@ -114,7 +114,7 @@ export class OrdersCrone {
}, },
updatedAt: { $lte: cutoff }, updatedAt: { $lte: cutoff },
}, },
{ populate: ['restaurant'] }, { populate: ['shop'] },
); );
if (!orders || orders.length === 0) { if (!orders || orders.length === 0) {
@@ -130,7 +130,7 @@ export class OrdersCrone {
const reloadedOrder = await em.findOne( const reloadedOrder = await em.findOne(
Order, Order,
{ id: order.id }, { id: order.id },
{ populate: ['restaurant', 'user'] }, { populate: ['shop', 'user'] },
); );
if (!reloadedOrder) return; if (!reloadedOrder) return;
if ( if (
@@ -145,9 +145,9 @@ export class OrdersCrone {
const previousStatus = reloadedOrder.status; const previousStatus = reloadedOrder.status;
const restaurantId = const restaurantId =
typeof reloadedOrder.restaurant === 'string' typeof reloadedOrder.shop === 'string'
? reloadedOrder.restaurant ? reloadedOrder.shop
: reloadedOrder.restaurant.id; : reloadedOrder.shop.id;
// Update order status and history // Update order status and history
reloadedOrder.status = OrderStatus.COMPLETED; reloadedOrder.status = OrderStatus.COMPLETED;
+1 -1
View File
@@ -11,7 +11,7 @@ import {
import { Type, Transform } from 'class-transformer'; import { Type, Transform } from 'class-transformer';
import { ApiPropertyOptional } from '@nestjs/swagger'; import { ApiPropertyOptional } from '@nestjs/swagger';
import { OrderStatus } from '../interface/order.interface'; import { OrderStatus } from '../interface/order.interface';
import { PaymentStatusEnum } from '../../payments/interface/payment'; import { PaymentStatusEnum } from '../../../payments/interface/payment';
const sortOrderOptions = ['asc', 'desc'] as const; const sortOrderOptions = ['asc', 'desc'] as const;
type SortOrder = (typeof sortOrderOptions)[number]; type SortOrder = (typeof sortOrderOptions)[number];
@@ -1,17 +1,17 @@
import { Entity, Index, ManyToOne, Property } from '@mikro-orm/core'; import { Entity, Index, ManyToOne, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { Order } from './order.entity'; import { Order } from './order.entity';
import { Food } from '../../foods/entities/food.entity'; import { Product } from ../../..products/entities/product.entity';
@Entity({ tableName: 'order_items' }) @Entity({ tableName: 'order_items' })
@Index({ properties: ['order'] }) @Index({ properties: ['order'] })
@Index({ properties: ['food'] }) @Index({ properties: ['product'] })
export class OrderItem extends BaseEntity { export class OrderItem extends BaseEntity {
@ManyToOne(() => Order) @ManyToOne(() => Order)
order!: Order; order!: Order;
@ManyToOne(() => Food) @ManyToOne(() => Product)
food!: Food; product!: Product;
@Property({ type: 'int' }) @Property({ type: 'int' })
quantity!: number; quantity!: number;
+17 -17
View File
@@ -11,28 +11,28 @@ import {
Unique, Unique,
type EventArgs, type EventArgs,
} from '@mikro-orm/core'; } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { OrderCouponDetail, OrderStatus } from '../interface/order.interface'; import { OrderCouponDetail, OrderStatus } from '../interface/order.interface';
import { User } from '../../users/entities/user.entity'; import { User } from '../../../users/entities/user.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { PaymentMethod } from '../../payments/entities/payment-method.entity'; import { PaymentMethod } from '../../../payments/entities/payment-method.entity';
import { OrderItem } from './order-item.entity'; import { OrderItem } from './order-item.entity';
import { Delivery } from '../../delivery/entities/delivery.entity'; import { Delivery } from '../../../delivery/entities/delivery.entity';
import { OrderUserAddress, OrderCarAddress } from '../interface/order.interface'; import { OrderUserAddress, OrderCarAddress } from '../interface/order.interface';
import { Payment } from 'src/modules/payments/entities/payment.entity'; import { Payment } from 'src/modules/payments/entities/payment.entity';
@Entity({ tableName: 'orders' }) @Entity({ tableName: 'orders' })
@Unique({ properties: ['restaurant', 'orderNumber'] }) @Unique({ properties: ['shop', 'orderNumber'] })
@Index({ properties: ['restaurant', 'status'] }) @Index({ properties: ['shop', 'status'] })
@Index({ properties: ['user', 'status'] }) @Index({ properties: ['user', 'status'] })
@Index({ properties: ['restaurant', 'orderNumber'] }) @Index({ properties: ['shop', 'orderNumber'] })
@Index({ properties: ['status'] }) @Index({ properties: ['status'] })
export class Order extends BaseEntity { export class Order extends BaseEntity {
@ManyToOne(() => User) @ManyToOne(() => User)
user!: User; user!: User;
@ManyToOne(() => Restaurant) @ManyToOne(() => Shop)
restaurant!: Restaurant; shop!: Shop;
@ManyToOne(() => Delivery) @ManyToOne(() => Delivery)
deliveryMethod!: Delivery; deliveryMethod!: Delivery;
@@ -105,19 +105,19 @@ export class Order extends BaseEntity {
const em = args.em; const em = args.em;
const order = args.entity; const order = args.entity;
// Ensure restaurant is loaded // Ensure shop is loaded
if (!order.restaurant) { if (!order.shop) {
throw new Error('Restaurant must be set before creating order'); throw new Error('Shop must be set before creating order');
} }
// Get the restaurant ID (handle both entity and ID cases) // Get the shop ID (handle both entity and ID cases)
const restaurantId = typeof order.restaurant === 'string' ? order.restaurant : order.restaurant.id; const restaurantId = typeof order.shop === 'string' ? order.shop : order.shop.id;
// Query for max orderNumber for this restaurant // Query for max orderNumber for this shop
// Using findOne with orderBy to get the highest order number // Using findOne with orderBy to get the highest order number
const maxOrder = await em.findOne( const maxOrder = await em.findOne(
Order, Order,
{ restaurant: restaurantId }, { shop: restaurantId },
{ {
orderBy: { orderNumber: 'DESC' }, orderBy: { orderNumber: 'DESC' },
fields: ['orderNumber'], fields: ['orderNumber'],
@@ -48,7 +48,7 @@ export class OrderListeners {
async handleOrderCreated(event: OrderCreatedEvent) { async handleOrderCreated(event: OrderCreatedEvent) {
try { try {
this.logger.log( this.logger.log(
`Order created event received: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, `Order created event received: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`,
); );
const order = await this.OrderRepository.findOne(event.orderId); const order = await this.OrderRepository.findOne(event.orderId);
@@ -102,7 +102,7 @@ export class OrderListeners {
async handleOrderStatusChanged(event: OrderStatusChangedEvent) { async handleOrderStatusChanged(event: OrderStatusChangedEvent) {
try { try {
this.logger.log( this.logger.log(
`Order status changed event received: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, `Order status changed event received: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`,
); );
//TODO : REFACTOR to use queue or other way to handle this //TODO : REFACTOR to use queue or other way to handle this
const recipients = [ const recipients = [
@@ -114,21 +114,21 @@ export class OrderListeners {
if (!event?.userId) { if (!event?.userId) {
this.logger.log( this.logger.log(
`User not found for order: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, `User not found for order: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`,
); );
} }
// const restaurant = await this.RestaurantRepository.findOne(event.restaurantId); // const shop = await this.RestaurantRepository.findOne(event.restaurantId);
// if (!restaurant) { // if (!shop) {
// this.logger.log( // this.logger.log(
// `Restaurant not found for order: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, // `Shop not found for order: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`,
// ); // );
// return; // return;
// } // }
// const score = restaurant.score; // const score = shop.score;
// if (!score) { // if (!score) {
// this.logger.log( // this.logger.log(
// `Score not found for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, // `Score not found for shop: ${event.restaurantId} and order number: ${event.orderNumber}`,
// ); // );
// return; // return;
// } // }
@@ -137,7 +137,7 @@ export class OrderListeners {
// const order = await this.OrderRepository.findOne(event.orderId); // const order = await this.OrderRepository.findOne(event.orderId);
// if (!order) { // if (!order) {
// this.logger.log( // this.logger.log(
// `Order not found for order: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, // `Order not found for order: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`,
// ); // );
// return; // return;
// } // }
+3 -3
View File
@@ -5,8 +5,8 @@ import { OrdersController } from './controllers/orders.controller';
import { Order } from './entities/order.entity'; import { Order } from './entities/order.entity';
import { OrderItem } from './entities/order-item.entity'; import { OrderItem } from './entities/order-item.entity';
import { User } from '../users/entities/user.entity'; import { User } from '../users/entities/user.entity';
import { Restaurant } from '../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { Food } from '../foods/entities/food.entity'; import { Product } from ../../..products/entities/product.entity';
import { UserAddress } from '../users/entities/user-address.entity'; import { UserAddress } from '../users/entities/user-address.entity';
import { PaymentMethod } from '../payments/entities/payment-method.entity'; import { PaymentMethod } from '../payments/entities/payment-method.entity';
import { CartModule } from '../cart/cart.module'; import { CartModule } from '../cart/cart.module';
@@ -24,7 +24,7 @@ import { UserModule } from '../users/user.module';
@Module({ @Module({
imports: [ imports: [
MikroOrmModule.forFeature([Order, OrderItem, User, Restaurant, Food, UserAddress, PaymentMethod]), MikroOrmModule.forFeature([Order, OrderItem, User, Shop, Product, UserAddress, PaymentMethod]),
CartModule, CartModule,
UtilsModule, UtilsModule,
AuthModule, AuthModule,
+51 -51
View File
@@ -2,32 +2,32 @@ import { Injectable, NotFoundException, BadRequestException, Logger } from '@nes
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { Order } from '../entities/order.entity'; import { Order } from '../entities/order.entity';
import { OrderItem } from '../entities/order-item.entity'; import { OrderItem } from '../entities/order-item.entity';
import { User } from '../../users/entities/user.entity'; import { User } from '../../../users/entities/user.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { Food } from '../../foods/entities/food.entity'; import { Product } from ../../..products/entities/product.entity';
import { CartService } from '../../cart/providers/cart.service'; import { CartService } from '../../../cart/providers/cart.service';
import { OrderStatus, OrderUserAddress, OrderCarAddress } from '../interface/order.interface'; import { OrderStatus, OrderUserAddress, OrderCarAddress } from '../interface/order.interface';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment'; import { PaymentMethodEnum, PaymentStatusEnum } from '../../../payments/interface/payment';
import { Cart } from '../../cart/interfaces/cart.interface'; import { Cart } from '../../../cart/interfaces/cart.interface';
import { PaymentMethod } from '../../payments/entities/payment-method.entity'; import { PaymentMethod } from '../../../payments/entities/payment-method.entity';
import { PaymentsService } from '../../payments/services/payments.service'; import { PaymentsService } from '../../../payments/services/payments.service';
import { DeliveryMethodEnum } from '../../delivery/interface/delivery'; import { DeliveryMethodEnum } from '../../../delivery/interface/delivery';
import { Delivery } from '../../delivery/entities/delivery.entity'; import { Delivery } from '../../../delivery/entities/delivery.entity';
import { OrderRepository } from '../repositories/order.repository'; import { OrderRepository } from '../repositories/order.repository';
import { FindOrdersDto } from '../dto/find-orders.dto'; import { FindOrdersDto } from '../dto/find-orders.dto';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { Payment } from 'src/modules/payments/entities/payment.entity'; import { Payment } from 'src/modules/payments/entities/payment.entity';
import { InventoryService } from 'src/modules/inventory/inventory.service'; import { InventoryService } from 'src/modules/inventory/inventory.service';
import { BulkReserveFoodDto } from 'src/modules/inventory/dto/bulk-reserve-food.dto'; import { BulkReserveFoodDto } from 'src/modules/inventory/dto/bulk-reserve-product.dto';
import { StatusTransitionRef } from '../interface/order.interface'; import { StatusTransitionRef } from '../interface/order.interface';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { OrderCreatedEvent, OrderStatusChangedEvent } from '../events/order.events'; import { OrderCreatedEvent, OrderStatusChangedEvent } from '../events/order.events';
import { OrderMessage } from 'src/common/enums/message.enum'; 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 = { type ValidatedCartForOrder = {
user: User; user: User;
restaurant: Restaurant; shop: Shop;
delivery: Delivery; delivery: Delivery;
userAddress: OrderUserAddress | null; userAddress: OrderUserAddress | null;
carAddress: OrderCarAddress | null; carAddress: OrderCarAddress | null;
@@ -66,7 +66,7 @@ export class OrdersService {
const order = await this.em.transactional(async em => { const order = await this.em.transactional(async em => {
const order = em.create(Order, { const order = em.create(Order, {
user: validated.user, user: validated.user,
restaurant: validated.restaurant, shop: validated.shop,
deliveryMethod: validated.delivery, deliveryMethod: validated.delivery,
userAddress: validated.userAddress, userAddress: validated.userAddress,
carAddress: validated.carAddress, carAddress: validated.carAddress,
@@ -89,15 +89,15 @@ export class OrdersService {
em.persist(order); em.persist(order);
for (const itemData of validated.orderItemsData) { for (const itemData of validated.orderItemsData) {
const { food, quantity, unitPrice, discount } = itemData; const { product, quantity, unitPrice, discount } = itemData;
this.assertFoodHasSufficientStock(food, quantity); this.assertFoodHasSufficientStock(product, quantity);
const totalPrice = (unitPrice - discount) * quantity; const totalPrice = (unitPrice - discount) * quantity;
const orderItem = em.create(OrderItem, { const orderItem = em.create(OrderItem, {
order, order,
food, product,
quantity, quantity,
unitPrice, unitPrice,
discount, discount,
@@ -119,13 +119,13 @@ export class OrdersService {
// reserve stock based on payment method. // reserve stock based on payment method.
const bulkReserveFoodDto: BulkReserveFoodDto = { const bulkReserveFoodDto: BulkReserveFoodDto = {
items: validated.orderItemsData.map(item => ({ items: validated.orderItemsData.map(item => ({
foodId: item.food.id, foodId: item.product.id,
quantity: item.quantity, quantity: item.quantity,
})), })),
}; };
await this.inventoryService.deductFromInventory(em, bulkReserveFoodDto); await this.inventoryService.deductFromInventory(em, bulkReserveFoodDto);
await em.flush(); await em.flush();
this.logger.debug(`Order ${order.id} created for user ${userId} (restaurant ${restaurantId})`); this.logger.debug(`Order ${order.id} created for user ${userId} (shop ${restaurantId})`);
return order; return order;
}); });
@@ -148,7 +148,7 @@ export class OrdersService {
this.assertCartHasDeliveryMethod(cart); this.assertCartHasDeliveryMethod(cart);
this.assertCartHasPaymentMethod(cart); this.assertCartHasPaymentMethod(cart);
const [user, restaurant, delivery] = await Promise.all([ const [user, shop, delivery] = await Promise.all([
this.getUserOrFail(userId), this.getUserOrFail(userId),
this.getRestaurantOrFail(restaurantId), this.getRestaurantOrFail(restaurantId),
this.getDeliveryOrFail(cart.deliveryMethodId!), this.getDeliveryOrFail(cart.deliveryMethodId!),
@@ -164,7 +164,7 @@ export class OrdersService {
return { return {
user, user,
restaurant, shop,
delivery, delivery,
paymentMethod, paymentMethod,
userAddress: delivery.method === DeliveryMethodEnum.DeliveryCourier ? (cart?.userAddress ?? null) : null, userAddress: delivery.method === DeliveryMethodEnum.DeliveryCourier ? (cart?.userAddress ?? null) : null,
@@ -209,18 +209,18 @@ export class OrdersService {
async findOne(id: string, restId: string): Promise<Order> { async findOne(id: string, restId: string): Promise<Order> {
const order = await this.orderRepository.findOne( const order = await this.orderRepository.findOne(
{ id, restaurant: { id: restId } }, { id, shop: { id: restId } },
{ {
populate: [ populate: [
'user', 'user',
'restaurant', 'shop',
'deliveryMethod', 'deliveryMethod',
'userAddress', 'userAddress',
'carAddress', 'carAddress',
'paymentMethod', 'paymentMethod',
'payments', 'payments',
'items', 'items',
'items.food', 'items.product',
], ],
}, },
); );
@@ -333,7 +333,7 @@ export class OrdersService {
private async getOrderOrFail(orderId: string, restId: string): Promise<Order> { private async getOrderOrFail(orderId: string, restId: string): Promise<Order> {
const order = await this.em.findOne( const order = await this.em.findOne(
Order, Order,
{ id: orderId, restaurant: { id: restId } }, { id: orderId, shop: { id: restId } },
{ populate: ['paymentMethod', 'deliveryMethod', 'user'] }, { populate: ['paymentMethod', 'deliveryMethod', 'user'] },
); );
if (!order) throw new NotFoundException(OrderMessage.NOT_FOUND); if (!order) throw new NotFoundException(OrderMessage.NOT_FOUND);
@@ -364,10 +364,10 @@ export class OrdersService {
return user; return user;
} }
private async getRestaurantOrFail(restaurantId: string): Promise<Restaurant> { private async getRestaurantOrFail(restaurantId: string): Promise<Shop> {
const restaurant = await this.em.findOne(Restaurant, { id: restaurantId }); const shop = await this.em.findOne(Shop, { id: restaurantId });
if (!restaurant) throw new NotFoundException(OrderMessage.RESTAURANT_NOT_FOUND); if (!shop) throw new NotFoundException(OrderMessage.RESTAURANT_NOT_FOUND);
return restaurant; return shop;
} }
private async getDeliveryOrFail(deliveryId: string): Promise<Delivery> { private async getDeliveryOrFail(deliveryId: string): Promise<Delivery> {
@@ -404,13 +404,13 @@ export class OrdersService {
PaymentMethod, PaymentMethod,
{ {
id: paymentMethodId, id: paymentMethodId,
restaurant: { id: restaurantId }, shop: { id: restaurantId },
}, },
{ populate: ['restaurant'] }, { populate: ['shop'] },
); );
if (!paymentMethod) { if (!paymentMethod) {
throw new NotFoundException(`Payment method with ID ${paymentMethodId} not found for restaurant ${restaurantId}`); throw new NotFoundException(`Payment method with ID ${paymentMethodId} not found for shop ${restaurantId}`);
} }
return paymentMethod; return paymentMethod;
@@ -426,28 +426,28 @@ export class OrdersService {
const orderItemsData: OrderItemData[] = []; const orderItemsData: OrderItemData[] = [];
for (const cartItem of cart.items) { for (const cartItem of cart.items) {
const food = await this.em.findOne(Food, { id: cartItem.foodId }, { populate: ['restaurant', 'inventory'] }); const product = await this.em.findOne(Product, { id: cartItem.foodId }, { populate: ['shop', 'inventory'] });
if (!food) throw new NotFoundException(OrderMessage.FOOD_NOT_FOUND); if (!product) throw new NotFoundException(OrderMessage.FOOD_NOT_FOUND);
if (food.restaurant.id !== restaurantId) { if (product.shop.id !== restaurantId) {
throw new BadRequestException(OrderMessage.FOOD_NOT_BELONGS_TO_RESTAURANT); throw new BadRequestException(OrderMessage.FOOD_NOT_BELONGS_TO_RESTAURANT);
} }
this.assertFoodHasSufficientStock(food, cartItem.quantity); this.assertFoodHasSufficientStock(product, cartItem.quantity);
orderItemsData.push({ orderItemsData.push({
food, product,
quantity: cartItem.quantity, quantity: cartItem.quantity,
unitPrice: food.price || 0, unitPrice: product.price || 0,
discount: food.discount || 0, discount: product.discount || 0,
}); });
} }
return orderItemsData; return orderItemsData;
} }
private assertFoodHasSufficientStock(food: Food, quantity: number) { private assertFoodHasSufficientStock(product: Product, quantity: number) {
const availableStock = food.inventory?.availableStock ?? 0; const availableStock = product.inventory?.availableStock ?? 0;
if (availableStock < quantity) { if (availableStock < quantity) {
throw new BadRequestException(OrderMessage.FOOD_NOT_FOUND); throw new BadRequestException(OrderMessage.FOOD_NOT_FOUND);
} }
@@ -458,7 +458,7 @@ export class OrdersService {
return this.em.transactional(async em => { return this.em.transactional(async em => {
// 1. Total orders count (excluding pending and canceled) // 1. Total orders count (excluding pending and canceled)
const totalOrders = await em.count(Order, { const totalOrders = await em.count(Order, {
restaurant: { id: restId }, shop: { id: restId },
status: { status: {
$in: [ $in: [
OrderStatus.PAID, OrderStatus.PAID,
@@ -471,13 +471,13 @@ export class OrdersService {
}, },
}); });
// 2. Active foods count // 2. Active products count
const activeFoods = await em.count(Food, { const activeFoods = await em.count(Product, {
restaurant: { id: restId }, shop: { id: restId },
isActive: true, isActive: true,
}); });
// 3. Total clients count (distinct users with orders for this restaurant) // 3. Total clients count (distinct users with orders for this shop)
const clientsResult = await em.execute( const clientsResult = await em.execute(
` `
SELECT COUNT(DISTINCT o.user_id) as count SELECT COUNT(DISTINCT o.user_id) as count
@@ -488,7 +488,7 @@ export class OrdersService {
); );
const totalClients = Number(clientsResult[0]?.count || 0); const totalClients = Number(clientsResult[0]?.count || 0);
// 4. Total revenue (sum of paid payments for orders of this restaurant) // 4. Total revenue (sum of paid payments for orders of this shop)
const revenueResult = await em.execute( const revenueResult = await em.execute(
` `
SELECT COALESCE(SUM(p.amount), 0)::numeric as total SELECT COALESCE(SUM(p.amount), 0)::numeric as total
@@ -525,7 +525,7 @@ export class OrdersService {
const lastDayOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999); const lastDayOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
this.logger.debug( 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 // Diagnostic query to check what orders exist
@@ -545,7 +545,7 @@ export class OrdersService {
`, `,
[restId], [restId],
); );
this.logger.debug(`Diagnostic: Found ${diagnosticResult.length} recent orders for restaurant ${restId}`); this.logger.debug(`Diagnostic: Found ${diagnosticResult.length} recent orders for shop ${restId}`);
if (diagnosticResult.length > 0) { if (diagnosticResult.length > 0) {
this.logger.debug(`Sample order: status=${diagnosticResult[0].status}, created_at=${diagnosticResult[0].created_at}`); this.logger.debug(`Sample order: status=${diagnosticResult[0].status}, created_at=${diagnosticResult[0].created_at}`);
} }
@@ -561,7 +561,7 @@ export class OrdersService {
COALESCE(SUM(oi.total_price), 0)::numeric as total_revenue COALESCE(SUM(oi.total_price), 0)::numeric as total_revenue
FROM order_items oi FROM order_items oi
INNER JOIN orders o ON oi.order_id = o.id 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.food_id = f.id
WHERE o.restaurant_id = ? WHERE o.restaurant_id = ?
AND o.created_at >= ? AND o.created_at >= ?
AND o.created_at <= ? AND o.created_at <= ?
@@ -572,7 +572,7 @@ export class OrdersService {
[restId, startDate, endDate], [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 // Calculate total revenue for percentage calculation
const totalRevenue = result.reduce((sum: number, item: any) => { const totalRevenue = result.reduce((sum: number, item: any) => {
@@ -4,8 +4,8 @@ import { FilterQuery } from '@mikro-orm/core';
import { Order } from '../entities/order.entity'; import { Order } from '../entities/order.entity';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { OrderStatus } from '../interface/order.interface'; import { OrderStatus } from '../interface/order.interface';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment'; import { PaymentMethodEnum, PaymentStatusEnum } from '../../../payments/interface/payment';
import { Review } from '../../review/entities/review.entity'; import { Review } from '../../../review/entities/review.entity';
type FindOrdersOpts = { type FindOrdersOpts = {
page?: number; page?: number;
@@ -47,7 +47,7 @@ export class OrderRepository extends EntityRepository<Order> {
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
const where: FilterQuery<Order> = { restaurant: { id: restId } }; const where: FilterQuery<Order> = { shop: { id: restId } };
// Filter by statuses // Filter by statuses
if (statuses) { if (statuses) {
@@ -118,15 +118,15 @@ export class OrderRepository extends EntityRepository<Order> {
limit, limit,
offset, offset,
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' }, orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
populate: ['user', 'restaurant', 'deliveryMethod', 'paymentMethod', 'items', 'items.food'] as never, populate: ['user', 'shop', 'deliveryMethod', 'paymentMethod', 'items', 'items.product'] as never,
}); });
// Collect all (orderId, foodId) pairs for efficient review lookup // Collect all (orderId, foodId) pairs for efficient review lookup
const orderFoodPairs: Array<{ orderId: string; foodId: string }> = []; const orderFoodPairs: Array<{ orderId: string; foodId: string }> = [];
for (const order of data) { for (const order of data) {
for (const item of order.items.getItems()) { for (const item of order.items.getItems()) {
if (item.food?.id) { if (item.product?.id) {
orderFoodPairs.push({ orderId: order.id, foodId: item.food.id }); orderFoodPairs.push({ orderId: order.id, foodId: item.product.id });
} }
} }
} }
@@ -141,30 +141,30 @@ export class OrderRepository extends EntityRepository<Order> {
Review, Review,
{ {
order: { id: { $in: orderIds } }, order: { id: { $in: orderIds } },
food: { id: { $in: foodIds } }, product: { id: { $in: foodIds } },
}, },
{ {
fields: ['id', 'order', 'food'], fields: ['id', 'order', 'product'],
populate: ['order', 'food'], populate: ['order', 'product'],
}, },
); );
// Create a map: key = `${orderId}-${foodId}`, value = reviewId // Create a map: key = `${orderId}-${foodId}`, value = reviewId
for (const review of reviews) { 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); reviewsMap.set(key, review.id);
} }
} }
// Map reviewIds to foods // Map reviewIds to products
for (const order of data) { for (const order of data) {
for (const item of order.items.getItems()) { for (const item of order.items.getItems()) {
if (item.food) { if (item.product) {
const key = `${order.id}-${item.food.id}`; const key = `${order.id}-${item.product.id}`;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment // 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 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
food.reviewId = reviewsMap.get(key) || null; product.reviewId = reviewsMap.get(key) || null;
} }
} }
} }
@@ -1,13 +1,13 @@
import { Controller, Get, Post, Body, UseGuards, Res, Req, Patch, Param } from '@nestjs/common'; import { Controller, Get, Post, Body, UseGuards, Res, Req, Patch, Param } from '@nestjs/common';
import { PagerService } from '../providers/pager.service'; import { PagerService } from '../providers/pager.service';
import { CreatePagerDto } from '../dto/create-pager.dto'; import { CreatePagerDto } from '../dto/create-pager.dto';
import { OptionalAuthGuard } from '../../auth/guards/optinalAuth.guard'; import { OptionalAuthGuard } from '../../../auth/guards/optinalAuth.guard';
import { RestId } from 'src/common/decorators/rest-id.decorator'; import { RestId } from 'src/common/decorators/rest-id.decorator';
import { UserId } from 'src/common/decorators/user-id.decorator'; import { UserId } from 'src/common/decorators/user-id.decorator';
import { RestSlug } from 'src/common/decorators/rest-slug.decorator'; import { RestSlug } from 'src/common/decorators/rest-slug.decorator';
import { ApiTags, ApiOperation, ApiBody, ApiHeader, ApiBearerAuth, ApiParam } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiBody, ApiHeader, ApiBearerAuth, ApiParam } from '@nestjs/swagger';
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from 'fastify';
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard'; import { AdminAuthGuard } from '../../../auth/guards/adminAuth.guard';
import { AdminId } from 'src/common/decorators/admin-id.decorator'; import { AdminId } from 'src/common/decorators/admin-id.decorator';
import { UpdatePagerStatusDto } from '../dto/update-pager.dto'; import { UpdatePagerStatusDto } from '../dto/update-pager.dto';
import { PagerMessage } from 'src/common/enums/message.enum'; import { PagerMessage } from 'src/common/enums/message.enum';
@@ -23,7 +23,7 @@ export class PagerController {
@UseGuards(OptionalAuthGuard) @UseGuards(OptionalAuthGuard)
@Post('public/pager') @Post('public/pager')
@ApiOperation({ summary: 'Create a new pager request' }) @ApiOperation({ summary: 'Create a new pager request' })
@ApiHeader({ name: 'X-Slug', required: true, description: 'Restaurant slug identifier' }) @ApiHeader({ name: 'X-Slug', required: true, description: 'Shop slug identifier' })
@ApiHeader({ name: 'Authorization', required: false, description: 'Bearer token (optional authentication)' }) @ApiHeader({ name: 'Authorization', required: false, description: 'Bearer token (optional authentication)' })
@ApiBody({ type: CreatePagerDto }) @ApiBody({ type: CreatePagerDto })
async create( async create(
@@ -78,7 +78,7 @@ export class PagerController {
@Permissions(Permission.MANAGE_PAGER) @Permissions(Permission.MANAGE_PAGER)
@ApiBearerAuth() @ApiBearerAuth()
@Get('admin/pager') @Get('admin/pager')
@ApiOperation({ summary: 'Get all pager requests for the restaurant' }) @ApiOperation({ summary: 'Get all pager requests for the shop' })
async findAllAdmin(@RestId() restId: string) { async findAllAdmin(@RestId() restId: string) {
return this.pagerService.findAllByRestaurantId(restId); return this.pagerService.findAllByRestaurantId(restId);
} }
@@ -3,12 +3,12 @@ import { createParamDecorator } from '@nestjs/common';
import type { AuthenticatedSocket } from '../guards/ws-admin-auth.guard'; import type { AuthenticatedSocket } from '../guards/ws-admin-auth.guard';
/** /**
* Extract restaurant ID from authenticated WebSocket client * Extract shop ID from authenticated WebSocket client
* Use this decorator in WebSocket handlers to get the restId from the authenticated admin * Use this decorator in WebSocket handlers to get the restId from the authenticated admin
* *
* @example * @example
* ```typescript * ```typescript
* @SubscribeMessage('join:restaurant') * @SubscribeMessage('join:shop')
* handleJoinRestaurant(@WsRestId() restId: string) { * handleJoinRestaurant(@WsRestId() restId: string) {
* // restId is automatically extracted from authenticated admin * // restId is automatically extracted from authenticated admin
* } * }
@@ -19,7 +19,7 @@ export const WsRestId = createParamDecorator((data: unknown, ctx: ExecutionConte
const restId = client.restId; const restId = client.restId;
if (!restId) { if (!restId) {
throw new Error('Restaurant ID not found. Ensure WsAdminAuthGuard is applied.'); throw new Error('Shop ID not found. Ensure WsAdminAuthGuard is applied.');
} }
return restId; return restId;
@@ -54,7 +54,7 @@ export interface PagerSocketConnectionConfig {
*/ */
export enum PagerSocketEvents { export enum PagerSocketEvents {
// Client to Server Events // Client to Server Events
JOIN_RESTAURANT = 'join:restaurant', JOIN_RESTAURANT = 'join:shop',
LEAVE_ROOM = 'leave:room', LEAVE_ROOM = 'leave:room',
GET_PAGERS = 'get:pagers', GET_PAGERS = 'get:pagers',
UPDATE_PAGER_STATUS = 'update:pager:status', UPDATE_PAGER_STATUS = 'update:pager:status',
@@ -72,7 +72,7 @@ export enum PagerSocketEvents {
} }
/** /**
* Payload for joining restaurant room * Payload for joining shop room
* Note: restId is automatically extracted from JWT token, no need to send it * Note: restId is automatically extracted from JWT token, no need to send it
*/ */
export type JoinRestaurantPayload = Record<string, never>; export type JoinRestaurantPayload = Record<string, never>;
+5 -5
View File
@@ -1,7 +1,7 @@
import { Entity, ManyToOne, Property, Enum } from '@mikro-orm/core'; import { Entity, ManyToOne, Property, Enum } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { User } from '../../users/entities/user.entity'; import { User } from '../../../users/entities/user.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { PagerStatus } from '../interface/pager'; import { PagerStatus } from '../interface/pager';
import { Admin } from 'src/modules/admin/entities/admin.entity'; import { Admin } from 'src/modules/admin/entities/admin.entity';
@@ -10,8 +10,8 @@ export class Pager extends BaseEntity {
@Property({ type: 'string' }) @Property({ type: 'string' })
cookieId!: string; cookieId!: string;
@ManyToOne(() => Restaurant) @ManyToOne(() => Shop)
restaurant!: Restaurant; shop!: Shop;
@Property({ type: 'string' }) @Property({ type: 'string' })
tableNumber!: string; tableNumber!: string;
@@ -3,7 +3,7 @@ import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io'; import { Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { IAdminTokenPayload } from '../../auth/interfaces/IToken-payload'; import { IAdminTokenPayload } from '../../../auth/interfaces/IToken-payload';
export interface AuthenticatedSocket extends Socket { export interface AuthenticatedSocket extends Socket {
adminId?: string; adminId?: string;
@@ -45,7 +45,7 @@ export class PagerListeners {
icon: `/assets/images/logo.png`, icon: `/assets/images/logo.png`,
action: { action: {
type: NotifTitleEnum.PAGER_CREATED, type: NotifTitleEnum.PAGER_CREATED,
url: `/restaurants/${event.restaurantId}/pagers`, url: `/shops/${event.restaurantId}/pagers`,
}, },
}, },
}, },
+19 -19
View File
@@ -1,12 +1,12 @@
import { BadRequestException, Injectable, NotFoundException, Inject, forwardRef } from '@nestjs/common'; import { BadRequestException, Injectable, NotFoundException, Inject, forwardRef } from '@nestjs/common';
import { CreatePagerDto } from '../dto/create-pager.dto'; import { CreatePagerDto } from '../dto/create-pager.dto';
import { User } from '../../users/entities/user.entity'; import { User } from '../../../users/entities/user.entity';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { Pager } from '../entities/pager.entity'; import { Pager } from '../entities/pager.entity';
import { PagerStatus } from '../interface/pager'; import { PagerStatus } from '../interface/pager';
import { ulid } from 'ulid'; import { ulid } from 'ulid';
import { Admin } from '../../admin/entities/admin.entity'; import { Admin } from '../../../admin/entities/admin.entity';
import { PagerCreatedEvent } from '../events/pager.events'; import { PagerCreatedEvent } from '../events/pager.events';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
@@ -24,15 +24,15 @@ export class PagerService {
params: { restId?: string; userId?: string; slug: string; userCookieId?: string }, params: { restId?: string; userId?: string; slug: string; userCookieId?: string },
) { ) {
const { restId, userId, slug, userCookieId } = params; const { restId, userId, slug, userCookieId } = params;
let restaurant: Restaurant | null = null; let shop: Shop | null = null;
let user: User | null = null; let user: User | null = null;
if (!restId && !userId && !slug) { if (!restId && !userId && !slug) {
throw new BadRequestException('Invalid parameters'); throw new BadRequestException('Invalid parameters');
} }
if (restId) { if (restId) {
restaurant = await this.em.findOne(Restaurant, { id: restId }); shop = await this.em.findOne(Shop, { id: restId });
if (!restaurant) { if (!shop) {
throw new NotFoundException('Restaurant not found'); throw new NotFoundException('Shop not found');
} }
} }
if (userId) { if (userId) {
@@ -41,15 +41,15 @@ export class PagerService {
throw new NotFoundException('User not found'); throw new NotFoundException('User not found');
} }
} }
if (!restaurant && slug) { if (!shop && slug) {
restaurant = await this.em.findOne(Restaurant, { slug }); shop = await this.em.findOne(Shop, { slug });
if (!restaurant) { if (!shop) {
throw new NotFoundException('Restaurant not found'); throw new NotFoundException('Shop not found');
} }
} }
const cookieId = userCookieId || ulid().toString(); const cookieId = userCookieId || ulid().toString();
// const existingPager = await this.em.findOne(Pager, { // const existingPager = await this.em.findOne(Pager, {
// restaurant: { id: restaurant!.id }, // shop: { id: shop!.id },
// status: PagerStatus.Pending, // status: PagerStatus.Pending,
// cookieId, // cookieId,
// }); // });
@@ -59,7 +59,7 @@ export class PagerService {
const pager = this.em.create(Pager, { const pager = this.em.create(Pager, {
...createPagerDto, ...createPagerDto,
restaurant: restaurant!, shop: shop!,
user, user,
cookieId: ulid().toString(), cookieId: ulid().toString(),
status: PagerStatus.Pending, status: PagerStatus.Pending,
@@ -68,9 +68,9 @@ export class PagerService {
await this.em.persistAndFlush(pager); await this.em.persistAndFlush(pager);
// Populate relations before emitting // Populate relations before emitting
await this.em.populate(pager, ['restaurant']); await this.em.populate(pager, ['shop']);
// Emit socket event for new pager // Emit socket event for new pager
this.eventEmitter.emit(PagerCreatedEvent.name, new PagerCreatedEvent(pager.restaurant.id, pager.tableNumber)); this.eventEmitter.emit(PagerCreatedEvent.name, new PagerCreatedEvent(pager.shop.id, pager.tableNumber));
return pager; return pager;
} }
@@ -81,15 +81,15 @@ export class PagerService {
} }
async findAllByRestaurantId(restId: string) { async findAllByRestaurantId(restId: string) {
const pagers = await this.em.find(Pager, { restaurant: { id: restId } }, { populate: ['user'] }); const pagers = await this.em.find(Pager, { shop: { id: restId } }, { populate: ['user'] });
return pagers; return pagers;
} }
async updateStatus(pagerId: string, restId: string, staffId: string, status: PagerStatus) { async updateStatus(pagerId: string, restId: string, staffId: string, status: PagerStatus) {
const pager = await this.em.findOne( const pager = await this.em.findOne(
Pager, Pager,
{ id: pagerId, restaurant: { id: restId } }, { id: pagerId, shop: { id: restId } },
{ populate: ['restaurant'] }, { populate: ['shop'] },
); );
if (!pager) { if (!pager) {
throw new NotFoundException('Pager not found *'); throw new NotFoundException('Pager not found *');
@@ -105,7 +105,7 @@ export class PagerService {
await this.em.persistAndFlush(pager); await this.em.persistAndFlush(pager);
// Populate relations before emitting // Populate relations before emitting
await this.em.populate(pager, ['restaurant', 'staff', 'user']); await this.em.populate(pager, ['shop', 'staff', 'user']);
// Emit socket event for status update // Emit socket event for status update
// this.pagerGateway.emitPagerStatusUpdated(pager); // this.pagerGateway.emitPagerStatusUpdated(pager);
@@ -31,10 +31,10 @@ export class PaymentsController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('public/payments/methods/restaurant') @Get('public/payments/methods/shop')
@ApiOperation({ summary: 'Get the restaurant payment methods' }) @ApiOperation({ summary: 'Get the shop payment methods' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiNotFoundResponse({ description: 'Restaurant payment methods not found' }) @ApiNotFoundResponse({ description: 'Shop payment methods not found' })
getTheRestaurantPaymentMethods(@RestId() restId: string) { getTheRestaurantPaymentMethods(@RestId() restId: string) {
return this.paymentMethodService.findByRestaurant(restId); return this.paymentMethodService.findByRestaurant(restId);
} }
@@ -42,7 +42,7 @@ export class PaymentsController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('public/payments') @Get('public/payments')
@ApiOperation({ summary: 'Get all the restaurant payments for user' }) @ApiOperation({ summary: 'Get all the shop payments for user' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
findAllByRestaurant(@UserId() userId: string, @RestId() restId: string) { findAllByRestaurant(@UserId() userId: string, @RestId() restId: string) {
return this.paymentsService.findAllPaymentsByRestaurantId(restId, userId); return this.paymentsService.findAllPaymentsByRestaurantId(restId, userId);
@@ -72,7 +72,7 @@ export class PaymentsController {
@Permissions(Permission.MANAGE_PAYMENTS) @Permissions(Permission.MANAGE_PAYMENTS)
@ApiBearerAuth() @ApiBearerAuth()
@Get('admin/payments/methods') @Get('admin/payments/methods')
@ApiOperation({ summary: 'Get restaurant all payment methods' }) @ApiOperation({ summary: 'Get shop all payment methods' })
findByRestaurant(@RestId() restId: string) { findByRestaurant(@RestId() restId: string) {
return this.paymentMethodService.findByRestaurant(restId); return this.paymentMethodService.findByRestaurant(restId);
} }
@@ -81,7 +81,7 @@ export class PaymentsController {
@Permissions(Permission.MANAGE_PAYMENTS) @Permissions(Permission.MANAGE_PAYMENTS)
@ApiBearerAuth() @ApiBearerAuth()
@Post('admin/payments/methods') @Post('admin/payments/methods')
@ApiOperation({ summary: 'Create a new restaurant payment method' }) @ApiOperation({ summary: 'Create a new shop payment method' })
@ApiBody({ type: CreatePaymentMethodDto }) @ApiBody({ type: CreatePaymentMethodDto })
createRestaurantPaymentMethod(@RestId() restId: string, @Body() createPaymentMethodDto: CreatePaymentMethodDto) { createRestaurantPaymentMethod(@RestId() restId: string, @Body() createPaymentMethodDto: CreatePaymentMethodDto) {
return this.paymentMethodService.create(restId, createPaymentMethodDto); return this.paymentMethodService.create(restId, createPaymentMethodDto);
@@ -91,7 +91,7 @@ export class PaymentsController {
@Permissions(Permission.MANAGE_PAYMENTS) @Permissions(Permission.MANAGE_PAYMENTS)
@ApiBearerAuth() @ApiBearerAuth()
@Get('admin/payments/methods/:paymentMethodId') @Get('admin/payments/methods/:paymentMethodId')
@ApiOperation({ summary: 'Get restaurant payment method by ID' }) @ApiOperation({ summary: 'Get shop payment method by ID' })
@ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' }) @ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' })
findOneRestaurantPaymentMethod(@Param('paymentMethodId') paymentMethodId: string) { findOneRestaurantPaymentMethod(@Param('paymentMethodId') paymentMethodId: string) {
return this.paymentMethodService.findOne(paymentMethodId); return this.paymentMethodService.findOne(paymentMethodId);
@@ -101,7 +101,7 @@ export class PaymentsController {
@Permissions(Permission.MANAGE_PAYMENTS) @Permissions(Permission.MANAGE_PAYMENTS)
@ApiBearerAuth() @ApiBearerAuth()
@Patch('admin/payments/methods/:paymentMethodId') @Patch('admin/payments/methods/:paymentMethodId')
@ApiOperation({ summary: 'Update a restaurant payment method' }) @ApiOperation({ summary: 'Update a shop payment method' })
@ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' }) @ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' })
@ApiBody({ type: UpdatePaymentMethodDto }) @ApiBody({ type: UpdatePaymentMethodDto })
updateRestaurantPaymentMethod( updateRestaurantPaymentMethod(
@@ -115,7 +115,7 @@ export class PaymentsController {
@Permissions(Permission.MANAGE_PAYMENTS) @Permissions(Permission.MANAGE_PAYMENTS)
@ApiBearerAuth() @ApiBearerAuth()
@Delete('admin/payments/methods/:paymentMethodId') @Delete('admin/payments/methods/:paymentMethodId')
@ApiOperation({ summary: 'Delete a restaurant payment method' }) @ApiOperation({ summary: 'Delete a shop payment method' })
@ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' }) @ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' })
removeRestaurantPaymentMethod(@Param('paymentMethodId') paymentMethodId: string) { removeRestaurantPaymentMethod(@Param('paymentMethodId') paymentMethodId: string) {
return this.paymentMethodService.remove(paymentMethodId); return this.paymentMethodService.remove(paymentMethodId);
@@ -1,14 +1,14 @@
import { Entity, Enum, Index, ManyToOne, Property, Unique } from '@mikro-orm/core'; import { Entity, Enum, Index, ManyToOne, Property, Unique } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { PaymentGatewayEnum, PaymentMethodEnum } from '../interface/payment'; import { PaymentGatewayEnum, PaymentMethodEnum } from '../interface/payment';
@Entity({ tableName: 'payment_methods' }) @Entity({ tableName: 'payment_methods' })
@Unique({ properties: ['restaurant', 'method'] }) @Unique({ properties: ['shop', 'method'] })
@Index({ properties: ['restaurant', 'enabled'] }) @Index({ properties: ['shop', 'enabled'] })
export class PaymentMethod extends BaseEntity { export class PaymentMethod extends BaseEntity {
@ManyToOne(() => Restaurant) @ManyToOne(() => Shop)
restaurant!: Restaurant; shop!: Shop;
@Enum(() => PaymentMethodEnum) @Enum(() => PaymentMethodEnum)
method!: PaymentMethodEnum; method!: PaymentMethodEnum;
@@ -1,6 +1,6 @@
import { Entity, ManyToOne, Property, Enum, Index } from '@mikro-orm/core'; import { Entity, ManyToOne, Property, Enum, Index } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity'; import { BaseEntity } from '../../../../common/entities/base.entity';
import { Order } from '../../orders/entities/order.entity'; import { Order } from '../../../orders/entities/order.entity';
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment'; import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
@Entity({ tableName: 'payments' }) @Entity({ tableName: 'payments' })
@@ -36,7 +36,7 @@ export class PaymentListeners {
async handlePaymentSucceed(event: paymentSucceedEvent) { async handlePaymentSucceed(event: paymentSucceedEvent) {
try { try {
this.logger.log( this.logger.log(
`Payment paid event received: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, `Payment paid event received: ${event.orderId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`,
); );
// get admnin os restuaraant that have order permissuins // get admnin os restuaraant that have order permissuins
const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_ORDERS); const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_ORDERS);
@@ -84,7 +84,7 @@ export class PaymentListeners {
try { try {
this.logger.log( this.logger.log(
`Online payment succeed event received: ${event.paymentId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, `Online payment succeed event received: ${event.paymentId} for shop: ${event.restaurantId} and order number: ${event.orderNumber}`,
); );
const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_ORDERS); const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_ORDERS);
const recipients = admins.map(admin => ({ const recipients = admins.map(admin => ({
@@ -123,7 +123,7 @@ export class PaymentListeners {
const order = await this.orderService.findOne(event.orderId, event.restaurantId); const order = await this.orderService.findOne(event.orderId, event.restaurantId);
if (!order) { if (!order) {
this.logger.error( this.logger.error(
`Order not found: ${event.orderId} for restaurant: ${event.restaurantId}`, `Order not found: ${event.orderId} for shop: ${event.restaurantId}`,
); );
return; return;
} }
+2 -2
View File
@@ -4,7 +4,7 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
import { PaymentMethod } from './entities/payment-method.entity'; import { PaymentMethod } from './entities/payment-method.entity';
import { PaymentMethodRepository } from './repositories/payment-method.repository'; import { PaymentMethodRepository } from './repositories/payment-method.repository';
import { PaymentMethodService } from './services/payment-method.service'; import { PaymentMethodService } from './services/payment-method.service';
import { Restaurant } from '../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { PaymentsController } from './controllers/payments.controller'; import { PaymentsController } from './controllers/payments.controller';
import { AuthModule } from '../auth/auth.module'; import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
@@ -19,7 +19,7 @@ import { NotificationsModule } from '../notifications/notifications.module';
import { OrdersModule } from '../orders/orders.module'; import { OrdersModule } from '../orders/orders.module';
@Module({ @Module({
imports: [MikroOrmModule.forFeature([PaymentMethod, Payment, Restaurant]), imports: [MikroOrmModule.forFeature([PaymentMethod, Payment, Shop]),
AuthModule, JwtModule, InventoryModule, AdminModule, NotificationsModule, AuthModule, JwtModule, InventoryModule, AdminModule, NotificationsModule,
forwardRef(() => OrdersModule)], forwardRef(() => OrdersModule)],
controllers: [PaymentsController], controllers: [PaymentsController],
@@ -5,7 +5,7 @@ import { PaymentMethodRepository } from '../repositories/payment-method.reposito
import { CreatePaymentMethodDto } from '../dto/create-payment-method.dto'; import { CreatePaymentMethodDto } from '../dto/create-payment-method.dto';
import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto'; import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto';
import { RequiredEntityData } from '@mikro-orm/core'; import { RequiredEntityData } from '@mikro-orm/core';
import { Restaurant } from '../../restaurants/entities/restaurant.entity'; import { Shop } from ../../..shops/entities/shop.entity';
import { RestMessage, PaymentMessage } from 'src/common/enums/message.enum'; import { RestMessage, PaymentMessage } from 'src/common/enums/message.enum';
@Injectable() @Injectable()
@@ -16,14 +16,14 @@ export class PaymentMethodService {
) {} ) {}
async create(restId: string, createPaymentMethodDto: CreatePaymentMethodDto): Promise<PaymentMethod> { async create(restId: string, createPaymentMethodDto: CreatePaymentMethodDto): Promise<PaymentMethod> {
// Check if restaurant exists // Check if shop exists
const restaurant = await this.em.findOne(Restaurant, { id: restId }); const shop = await this.em.findOne(Shop, { id: restId });
if (!restaurant) { if (!shop) {
throw new NotFoundException(RestMessage.NOT_FOUND); throw new NotFoundException(RestMessage.NOT_FOUND);
} }
const paymentMethod = this.paymentMethodRepository.create({ const paymentMethod = this.paymentMethodRepository.create({
restaurant, shop,
...createPaymentMethodDto, ...createPaymentMethodDto,
} as unknown as RequiredEntityData<PaymentMethod>); } as unknown as RequiredEntityData<PaymentMethod>);
await this.em.persistAndFlush(paymentMethod); await this.em.persistAndFlush(paymentMethod);
@@ -31,11 +31,11 @@ export class PaymentMethodService {
} }
async findAll(): Promise<PaymentMethod[]> { async findAll(): Promise<PaymentMethod[]> {
return this.paymentMethodRepository.findAll({ populate: ['restaurant'] }); return this.paymentMethodRepository.findAll({ populate: ['shop'] });
} }
async findOne(id: string): Promise<PaymentMethod> { async findOne(id: string): Promise<PaymentMethod> {
const paymentMethod = await this.paymentMethodRepository.findOne({ id }, { populate: ['restaurant'] }); const paymentMethod = await this.paymentMethodRepository.findOne({ id }, { populate: ['shop'] });
if (!paymentMethod) { if (!paymentMethod) {
throw new NotFoundException(PaymentMessage.PAYMENT_METHOD_NOT_FOUND); throw new NotFoundException(PaymentMessage.PAYMENT_METHOD_NOT_FOUND);
} }
@@ -43,7 +43,7 @@ export class PaymentMethodService {
} }
async findByRestaurant(restaurantId: string): Promise<PaymentMethod[]> { async findByRestaurant(restaurantId: string): Promise<PaymentMethod[]> {
return this.paymentMethodRepository.find({ restaurant: { id: restaurantId } }, { populate: ['restaurant'] }); return this.paymentMethodRepository.find({ shop: { id: restaurantId } }, { populate: ['shop'] });
} }
async update(id: string, updatePaymentMethodDto: UpdatePaymentMethodDto): Promise<PaymentMethod> { async update(id: string, updatePaymentMethodDto: UpdatePaymentMethodDto): Promise<PaymentMethod> {
@@ -2,7 +2,7 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/comm
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment'; import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
import { Payment } from '../entities/payment.entity'; import { Payment } from '../entities/payment.entity';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { Order } from '../../orders/entities/order.entity'; import { Order } from '../../../orders/entities/order.entity';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { GatewayManager } from '../gateways/gateway.manager'; import { GatewayManager } from '../gateways/gateway.manager';
import { WalletTransaction } from 'src/modules/users/entities/wallet-transaction.entity'; import { WalletTransaction } from 'src/modules/users/entities/wallet-transaction.entity';
@@ -53,7 +53,7 @@ export class PaymentsService {
const order = await this.em.findOne( const order = await this.em.findOne(
Order, Order,
{ id: orderId }, { id: orderId },
{ populate: ['user', 'restaurant', 'paymentMethod', 'paymentMethod.restaurant'] }, { populate: ['user', 'shop', 'paymentMethod', 'paymentMethod.shop'] },
); );
if (!order) { if (!order) {
@@ -72,7 +72,7 @@ export class PaymentsService {
if (pm.method === PaymentMethodEnum.Online) { if (pm.method === PaymentMethodEnum.Online) {
if (!pm.gateway) throw new BadRequestException(PaymentMessage.GATEWAY_REQUIRED); if (!pm.gateway) throw new BadRequestException(PaymentMessage.GATEWAY_REQUIRED);
if (!pm.merchantId) throw new BadRequestException(PaymentMessage.MERCHANT_ID_REQUIRED); if (!pm.merchantId) throw new BadRequestException(PaymentMessage.MERCHANT_ID_REQUIRED);
if (!pm.restaurant?.domain) throw new BadRequestException(PaymentMessage.RESTAURANT_DOMAIN_REQUIRED); if (!pm.shop?.domain) throw new BadRequestException(PaymentMessage.RESTAURANT_DOMAIN_REQUIRED);
} }
return { return {
@@ -81,7 +81,7 @@ export class PaymentsService {
method: pm.method, method: pm.method,
gateway: pm.gateway ?? null, gateway: pm.gateway ?? null,
merchantId: pm.merchantId ?? null, merchantId: pm.merchantId ?? null,
restaurantDomain: pm.restaurant.domain ?? null, restaurantDomain: pm.shop.domain ?? null,
}; };
} }
@@ -96,7 +96,7 @@ export class PaymentsService {
// TODO clean this code and refactor it // TODO clean this code and refactor it
private async handleWalletPayment(ctx: OrderPaymentContext): Promise<void> { private async handleWalletPayment(ctx: OrderPaymentContext): Promise<void> {
await this.em.transactional(async em => { await this.em.transactional(async em => {
const order = await em.findOne(Order, { id: ctx.order.id }, { populate: ['user', 'restaurant'] }); const order = await em.findOne(Order, { id: ctx.order.id }, { populate: ['user', 'shop'] });
if (!order) throw new NotFoundException(OrderMessage.NOT_FOUND); if (!order) throw new NotFoundException(OrderMessage.NOT_FOUND);
if (order.status === OrderStatus.PAID) { if (order.status === OrderStatus.PAID) {
@@ -105,7 +105,7 @@ export class PaymentsService {
const walletTransaction = await em.findOne(WalletTransaction, { const walletTransaction = await em.findOne(WalletTransaction, {
user: { id: order.user.id }, user: { id: order.user.id },
restaurant: { id: order.restaurant.id }, shop: { id: order.shop.id },
}, { }, {
orderBy: { createdAt: 'DESC' } orderBy: { createdAt: 'DESC' }
}); });
@@ -130,7 +130,7 @@ export class PaymentsService {
const newWalletTransaction = em.create(WalletTransaction, { const newWalletTransaction = em.create(WalletTransaction, {
user: order.user, user: order.user,
restaurant: order.restaurant, shop: order.shop,
amount: ctx.amount, amount: ctx.amount,
type: WalletTransactionType.DEBIT, type: WalletTransactionType.DEBIT,
reason: WalletTransactionReason.ORDER_PAYMENT, reason: WalletTransactionReason.ORDER_PAYMENT,
@@ -146,7 +146,7 @@ export class PaymentsService {
}); });
this.eventEmitter.emit( this.eventEmitter.emit(
paymentSucceedEvent.name, paymentSucceedEvent.name,
new paymentSucceedEvent(ctx.order.id, ctx.order.restaurant.id, String(ctx.order?.orderNumber) || '', ctx.method, ctx.amount), new paymentSucceedEvent(ctx.order.id, ctx.order.shop.id, String(ctx.order?.orderNumber) || '', ctx.method, ctx.amount),
); );
} }
@@ -228,7 +228,7 @@ export class PaymentsService {
}); });
this.eventEmitter.emit( this.eventEmitter.emit(
onlinePaymentSucceedEvent.name, onlinePaymentSucceedEvent.name,
new onlinePaymentSucceedEvent(payment.id, payment.order.id, payment.order.restaurant.id, String(payment.order?.orderNumber) || '', payment.amount), new onlinePaymentSucceedEvent(payment.id, payment.order.id, payment.order.shop.id, String(payment.order?.orderNumber) || '', payment.amount),
); );
return payment; return payment;
} }
@@ -236,7 +236,7 @@ export class PaymentsService {
findAllPaymentsByRestaurantId(restId: string, userId: string) { findAllPaymentsByRestaurantId(restId: string, userId: string) {
return this.em.find( return this.em.find(
Payment, Payment,
{ order: { restaurant: { id: restId }, user: { id: userId } } }, { order: { shop: { id: restId }, user: { id: userId } } },
{ populate: ['order', 'order.paymentMethod'] }, { populate: ['order', 'order.paymentMethod'] },
); );
} }
@@ -25,11 +25,11 @@ import { API_HEADER_SLUG } from 'src/common/constants';
export class CategoryController { export class CategoryController {
constructor(private readonly categoryService: CategoryService) { } constructor(private readonly categoryService: CategoryService) { }
@Get('public/categories/restaurant/:slug') @Get('public/categories/shop/:slug')
@ApiOperation({ summary: 'Get all categories by restaurant slug' }) @ApiOperation({ summary: 'Get all categories by shop slug' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiParam({ name: 'slug', required: true, description: 'Restaurant Slug' }) @ApiParam({ name: 'slug', required: true, description: 'Shop Slug' })
@ApiOkResponse({ description: 'List of all categories for the restaurant' }) @ApiOkResponse({ description: 'List of all categories for the shop' })
findAllByRestaurant(@Param('slug') slug: string) { findAllByRestaurant(@Param('slug') slug: string) {
return this.categoryService.findAllByRestaurant(slug); return this.categoryService.findAllByRestaurant(slug);
} }
@@ -46,7 +46,7 @@ export class CategoryController {
return this.categoryService.create(restId, dto); return this.categoryService.create(restId, dto);
} }
@Permissions(Permission.MANAGE_CATEGORIES) @Permissions(Permission.MANAGE_CATEGORIES)
@ApiOperation({ summary: 'my restaurant categories' }) @ApiOperation({ summary: 'my shop categories' })
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Get('admin/categories') @Get('admin/categories')
@@ -1,8 +1,8 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } from '@nestjs/common'; import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } from '@nestjs/common';
import { FoodService } from '../providers/food.service'; import { FoodService } from '../providers/product.service';
import { CreateFoodDto } from '../dto/create-food.dto'; import { CreateFoodDto } from '../dto/create-product.dto';
import { UpdateFoodDto } from '../dto/update-food.dto'; import { UpdateFoodDto } from '../dto/update-product.dto';
import { FindFoodsDto } from '../dto/find-foods.dto'; import { FindFoodsDto } from ../../..products.dto';
import { import {
ApiTags, ApiTags,
ApiOperation, ApiOperation,
@@ -20,41 +20,41 @@ import { API_HEADER_SLUG } from 'src/common/constants';
import { Permission } from 'src/common/enums/permission.enum'; import { Permission } from 'src/common/enums/permission.enum';
import { Permissions } from 'src/common/decorators/permissions.decorator'; import { Permissions } from 'src/common/decorators/permissions.decorator';
@ApiTags('foods') @ApiTags('products')
@Controller() @Controller()
export class FoodController { export class FoodController {
constructor(private readonly foodsService: FoodService) { } constructor(private readonly foodsService: FoodService) { }
@Get('public/foods/restaurant/:slug') @Get('public/products/shop/:slug')
@ApiOperation({ summary: 'Get all foods by restaurant slug' }) @ApiOperation({ summary: 'Get all products by shop slug' })
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiParam({ name: 'slug', required: true, description: 'Restaurant Slug' }) @ApiParam({ name: 'slug', required: true, description: 'Shop Slug' })
findAllByRestaurant(@Param('slug') slug: string) { findAllByRestaurant(@Param('slug') slug: string) {
return this.foodsService.findByWeekDateAndMealType(slug); return this.foodsService.findByWeekDateAndMealType(slug);
} }
@Get('public/foods/:foodId') @Get('public/products/:foodId')
@UseGuards(OptionalAuthGuard) @UseGuards(OptionalAuthGuard)
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'Get a food by id' }) @ApiOperation({ summary: 'Get a product by id' })
@ApiParam({ name: 'foodId', required: true }) @ApiParam({ name: 'foodId', required: true })
findPublicFoodById(@Param('foodId') foodId: string, @UserId() userId?: string): Promise<any> { findPublicFoodById(@Param('foodId') foodId: string, @UserId() userId?: string): Promise<any> {
const a = this.foodsService.findPublicById(foodId, userId); const a = this.foodsService.findPublicById(foodId, userId);
return a; return a;
} }
@Post('public/foods/favorite/:foodId') @Post('public/products/favorite/:foodId')
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'toggle a food as favorite' }) @ApiOperation({ summary: 'toggle a product as favorite' })
@ApiParam({ name: 'foodId', required: true }) @ApiParam({ name: 'foodId', required: true })
setAsFavorute(@Param('foodId') foodId: string, @UserId() userId: string) { setAsFavorute(@Param('foodId') foodId: string, @UserId() userId: string) {
return this.foodsService.toggleFavorite(userId, foodId); return this.foodsService.toggleFavorite(userId, foodId);
} }
@Get('public/foods/favorite') @Get('public/products/favorite')
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@ApiHeader(API_HEADER_SLUG) @ApiHeader(API_HEADER_SLUG)
@ApiBearerAuth() @ApiBearerAuth()
@@ -67,8 +67,8 @@ export class FoodController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_FOODS) @Permissions(Permission.MANAGE_FOODS)
@Post('admin/foods') @Post('admin/products')
@ApiOperation({ summary: 'Create a new food' }) @ApiOperation({ summary: 'Create a new product' })
@ApiBody({ type: CreateFoodDto }) @ApiBody({ type: CreateFoodDto })
create(@Body() createFoodDto: CreateFoodDto, @RestId() restId: string) { create(@Body() createFoodDto: CreateFoodDto, @RestId() restId: string) {
return this.foodsService.create(restId, createFoodDto); return this.foodsService.create(restId, createFoodDto);
@@ -77,8 +77,8 @@ export class FoodController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_FOODS) @Permissions(Permission.MANAGE_FOODS)
@Get('admin/foods') @Get('admin/products')
@ApiOperation({ summary: 'Get a paginated list of foods' }) @ApiOperation({ summary: 'Get a paginated list of products' })
@ApiQuery({ name: 'page', required: false, type: Number }) @ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'limit', required: false, type: Number }) @ApiQuery({ name: 'limit', required: false, type: Number })
@ApiQuery({ name: 'search', required: false, type: String }) @ApiQuery({ name: 'search', required: false, type: String })
@@ -94,8 +94,8 @@ export class FoodController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_FOODS) @Permissions(Permission.MANAGE_FOODS)
@Get('admin/foods/:id') @Get('admin/products/:id')
@ApiOperation({ summary: 'Get a food by id' }) @ApiOperation({ summary: 'Get a product by id' })
@ApiParam({ name: 'id', required: true }) @ApiParam({ name: 'id', required: true })
findById(@Param('id') id: string, @RestId() restId: string) { findById(@Param('id') id: string, @RestId() restId: string) {
return this.foodsService.findAdminById(restId, id); return this.foodsService.findAdminById(restId, id);
@@ -104,8 +104,8 @@ export class FoodController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_FOODS) @Permissions(Permission.MANAGE_FOODS)
@Patch('admin/foods/:id') @Patch('admin/products/:id')
@ApiOperation({ summary: 'Update a food' }) @ApiOperation({ summary: 'Update a product' })
@ApiParam({ name: 'id', required: true }) @ApiParam({ name: 'id', required: true })
@ApiBody({ type: UpdateFoodDto }) @ApiBody({ type: UpdateFoodDto })
update(@Param('id') id: string, @Body() updateFoodDto: UpdateFoodDto, @RestId() restId: string) { update(@Param('id') id: string, @Body() updateFoodDto: UpdateFoodDto, @RestId() restId: string) {
@@ -114,8 +114,8 @@ export class FoodController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@Permissions(Permission.MANAGE_FOODS) @Permissions(Permission.MANAGE_FOODS)
@Delete('admin/foods/:id') @Delete('admin/products/:id')
@ApiOperation({ summary: 'Delete (soft) a food' }) @ApiOperation({ summary: 'Delete (soft) a product' })
@ApiParam({ name: 'id', required: true }) @ApiParam({ name: 'id', required: true })
remove(@Param('id') id: string, @RestId() restId: string) { remove(@Param('id') id: string, @RestId() restId: string) {
return this.foodsService.remove(restId, id); return this.foodsService.remove(restId, id);
@@ -1,7 +1,7 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule'; import { Cron } from '@nestjs/schedule';
import { EntityManager } from '@mikro-orm/postgresql'; import { EntityManager } from '@mikro-orm/postgresql';
import { Inventory } from '../../inventory/entities/inventory.entity'; import { Inventory } from '../../../inventory/entities/inventory.entity';
@Injectable() @Injectable()
export class FoodStockCrone { export class FoodStockCrone {

Some files were not shown because too many files have changed in this diff Show More