change entity names
This commit is contained in:
+4
-4
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 = 'محصولات یافت نشدند',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 || '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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]));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
|||||||
@@ -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,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,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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'] },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -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')
|
||||||
+23
-23
@@ -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
Reference in New Issue
Block a user