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