import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { CreateRestaurantDto } from '../dto/create-shop.dto'; import { UpdateRestaurantDto } from '../dto/update-shop.dto'; import { UpgradeSubscriptionDto } from '../dto/upgrade-subscription.dto'; import { Shop } from '../entities/shop.entity'; import { EntityManager } from '@mikro-orm/postgresql'; import { RestRepository } from '../repositories/rest.repository'; import { RestMessage } from 'src/common/enums/message.enum'; import { FindRestaurantsDto } from ../../..shops.dto'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { PlanEnum } from '../interface/plan.interface'; import { Admin } from '../../../admin/entities/admin.entity'; import { AdminRole } from '../../../admin/entities/adminRole.entity'; import { Role } from '../../../roles/entities/role.entity'; import { normalizePhone } from 'src/modules/utils/phone.util'; import slugify from 'slugify'; import { NotificationPreference } from '../../../notifications/entities/notification-preference.entity'; import { notificationPreferencesData } from '../../../../seeders/data/notification-preferences.data'; import { Order } from '../../../orders/entities/order.entity'; import { Coupon } from '../../../coupons/entities/coupon.entity'; import { Category } from ../../..products/entities/category.entity'; import { Product } from ../../..products/entities/product.entity'; import { Delivery } from '../../../delivery/entities/delivery.entity'; import { PaymentMethod } from '../../../payments/entities/payment-method.entity'; import { Pager } from '../../../pager/entities/pager.entity'; import { Schedule } from '../entities/schedule.entity'; import { WalletTransaction } from '../../../users/entities/wallet-transaction.entity'; import { SmsLog } from '../../../notifications/entities/smsLogs.entity'; import { Notification } from '../../../notifications/entities/notification.entity'; @Injectable() export class RestaurantsService { constructor( private readonly em: EntityManager, private readonly restRepository: RestRepository, ) { } async setupRestuarant(dto: CreateRestaurantDto): Promise { return await this.em.transactional(async (em) => { // Generate or validate slug const slug = slugify(dto.slug ?? dto.name, { lower: true, strict: true, locale: 'fa' }); const validateSubscriptionId = await em.findOne(Shop, { subscriptionId: dto.subscriptionId }) if (validateSubscriptionId) { throw new BadRequestException("Duplicate subscriptionId: " + dto.subscriptionId) } // Create shop const shop = em.create(Shop, { name: dto.name, slug, establishedYear: dto.establishedYear, phone: dto.phone, isActive: true, plan: dto.plan, domain: `https://dmenu.danakcorp.com/${slug}`, subscriptionId: dto.subscriptionId, subscriptionEndDate: dto.subscriptionEndDate, subscriptionStartDate: dto.subscriptionStartDate, }); // Find the appropriate role based on plan const roleName = dto.plan === PlanEnum.Base ? 'مدیر (پلن پایه)' : 'مدیر ( پلن ویژه)'; const role = await em.findOne(Role, { name: roleName }); if (!role) { throw new BadRequestException(`Role not found for plan: ${dto.plan}`); } const normalizedPhone = normalizePhone(dto.phone); let admin = await em.findOne(Admin, { phone: normalizedPhone }); if (!admin) { admin = em.create(Admin, { phone: normalizedPhone, firstName: '[نام]', lastName: '[نام خانوادگی]', }); } // Create admin role relationship const adminRole = em.create(AdminRole, { admin, role, shop, }); // Create notification preferences for the shop const notificationPreferences = notificationPreferencesData.map(preferenceData => em.create(NotificationPreference, { shop, title: preferenceData.title, channels: preferenceData.channels, }) ); // Persist all entities em.persist([shop, admin, adminRole, ...notificationPreferences]); await em.flush(); return shop; }); } async findAll(dto: FindRestaurantsDto): Promise> { return this.restRepository.findAllPaginated({ page: dto.page, limit: dto.limit, search: dto.search, isActive: dto.isActive, plan: dto.plan, orderBy: dto.orderBy, order: dto.order, }); } async findBySlug(slug: string): Promise { const shop = await this.em.findOne(Shop, { slug }); if (!shop) { throw new NotFoundException(`Shop with slug "${slug}" not found`); } return shop; } async findOne(id: string): Promise { const shop = await this.restRepository.findOne({ id }); if (!shop) { throw new NotFoundException(RestMessage.NOT_FOUND); } return shop; } async findOneBySubscriptionId(subscriptionId: string): Promise { console.log('subscriptionId', subscriptionId) const shop = await this.restRepository.findOne({ subscriptionId }); console.log('shop', shop) if (!shop) { throw new NotFoundException(RestMessage.NOT_FOUND); } return shop; } async getRestaurantSpecification(slug: string) { const shop = await this.findBySlug(slug); return shop; } // TODO : it must be done inside transaction async update(id: string, dto: UpdateRestaurantDto): Promise { const shop = await this.restRepository.findOne({ id: id }); if (!shop) { throw new NotFoundException(RestMessage.NOT_FOUND); } if (dto.plan && dto.plan !== shop.plan) { const isPremium = dto.plan === PlanEnum.Premium // find admins and make them premium or base const adminRoles = await this.em.find(AdminRole, { shop: { id }, role: { name: isPremium ? 'مدیر (پلن پایه)' : 'مدیر ( پلن ویژه)' } }) const newRole = await this.em.findOne(Role, { name: isPremium ? 'مدیر ( پلن ویژه)' : 'مدیر (پلن پایه)' }) if (!newRole) { throw new BadRequestException("Role Not Found") } adminRoles.forEach(a => { a.role = newRole }) this.em.persistAndFlush(adminRoles) } this.restRepository.assign(shop, dto); await this.em.persistAndFlush(shop); return shop; } async remove(id: string) { // Soft delete the shop by setting isActive to false const shop = await this.restRepository.findOne({ id }); if (!shop) { throw new NotFoundException(RestMessage.NOT_FOUND); } shop.isActive = false; await this.em.persistAndFlush(shop); return shop; } async upgradeSubscription(subscriptionId: string, dto: UpgradeSubscriptionDto): Promise { const shop = await this.restRepository.findOne({ subscriptionId }); if (!shop) { throw new NotFoundException(RestMessage.NOT_FOUND); } const isActive = new Date(dto.subscriptionEndDate) > new Date() await this.update(shop.id, { plan: dto.newPlan, subscriptionEndDate: dto.subscriptionEndDate, isActive }) return shop; } async hardDeleteRestaurant(id: string): Promise { return await this.em.transactional(async (em) => { // Find the shop first const shop = await em.findOne(Shop, { id }); if (!shop) { throw new NotFoundException(RestMessage.NOT_FOUND); } // Delete orders first (will cascade to order items and payments due to cascade settings) await em.nativeDelete(Order, { shop: id }); // Delete coupons await em.nativeDelete(Coupon, { shop: id }); // Delete products (will cascade to reviews and favorites due to cascade settings) await em.nativeDelete(Product, { shop: id }); // Delete categories (products should be deleted by cascade, but delete explicitly to be safe) await em.nativeDelete(Category, { shop: id }); // Delete deliveries await em.nativeDelete(Delivery, { shop: id }); // Delete schedules await em.nativeDelete(Schedule, { restId: id }); // Delete notification preferences await em.nativeDelete(NotificationPreference, { shop: id }); // Delete notifications await em.nativeDelete(Notification, { shop: id }); // Delete SMS logs await em.nativeDelete(SmsLog, { shop: id }); // Delete admin roles for this shop await em.nativeDelete(AdminRole, { shop: id }); // Delete payment methods await em.nativeDelete(PaymentMethod, { shop: id }); // Delete pagers await em.nativeDelete(Pager, { shop: id }); // Delete wallet transactions await em.nativeDelete(WalletTransaction, { shop: id }); // Finally, delete the shop itself await em.nativeDelete(Shop, { id }); }); } }