import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { CreateRestaurantDto } from '../dto/create-shop.dto'; import { UpdateRestaurantDto } from '../dto/update-shop.dto'; import { UpdateSubscriptionDto } from '../dto/update-subscription.dto'; import { UpdateShopBackgroundDto } from '../dto/update-shop-background.dto'; import { Shop } from '../entities/shop.entity'; import { Background } from '../entities/background.entity'; import { EntityManager } from '@mikro-orm/postgresql'; import { ShopRepository } from '../repositories/rest.repository'; import { ShopMessage } from 'src/common/enums/message.enum'; import { FindRestaurantsDto } from '../dto/find-shops.dto'; import { PaginatedResult } from 'src/common/interfaces/pagination.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 { 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 ShopService { constructor( private readonly em: EntityManager, private readonly shopRepository: ShopRepository, ) { } 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(ShopMessage.DUPLICATE_SUBSCRIPTION_ID); } // Create shop const shop = em.create(Shop, { name: dto.name, slug, establishedYear: dto.establishedYear, phone: dto.phone, isActive: true, domain: `https://dmenu.danakcorp.com/${slug}`, subscriptionId: dto.subscriptionId, subscriptionEndDate: dto.subscriptionEndDate, subscriptionStartDate: dto.subscriptionStartDate, }); const role = await em.findOne(Role, { isSystem: true }); if (!role) { throw new BadRequestException(ShopMessage.SYSTEM_ROLE_NOT_FOUND); } 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.shopRepository.findAllPaginated({ page: dto.page, limit: dto.limit, search: dto.search, isActive: dto.isActive, orderBy: dto.orderBy, order: dto.order, }); } async findBySlug(slug: string): Promise { const shop = await this.em.findOne(Shop, { slug }, { populate: ['configs'] }); if (!shop) { throw new NotFoundException(ShopMessage.NOT_FOUND); } return shop; } async findOne(id: string): Promise { const shop = await this.shopRepository.findOne({ id }); if (!shop) { throw new NotFoundException(ShopMessage.NOT_FOUND); } return shop; } async findOneBySubscriptionId(subscriptionId: string): Promise { console.log('subscriptionId', subscriptionId) const shop = await this.shopRepository.findOne({ subscriptionId }); console.log('shop', shop) if (!shop) { throw new NotFoundException(ShopMessage.NOT_FOUND); } return shop; } async getRestaurantSpecification(slug: string) { const shop = await this.findBySlug(slug); return shop; } async findAllBackgrounds(): Promise { return this.em.find(Background, {}, { orderBy: { createdAt: 'asc' } }); } async updateBackground(id: string, dto: UpdateShopBackgroundDto): Promise { const shop = await this.shopRepository.findOne({ id }); if (!shop) { throw new NotFoundException(ShopMessage.NOT_FOUND); } this.shopRepository.assign(shop, dto); await this.em.flush(); return shop; } async update(id: string, dto: UpdateRestaurantDto): Promise { const shop = await this.shopRepository.findOne({ id: id }); if (!shop) { throw new NotFoundException(ShopMessage.NOT_FOUND); } if (dto.slug) { const shopWithThisSlug = await this.shopRepository.findOne({ slug: dto.slug, $not: { id } }); if (shopWithThisSlug) { throw new NotFoundException(ShopMessage.SHOP_EXIST_WITH_THIS_SLUG); } } this.shopRepository.assign(shop, dto); await this.em.flush(); return shop; } async updateSubscription(subscriptionId: string, dto: UpdateSubscriptionDto): Promise { const shop = await this.shopRepository.findOne({ subscriptionId }); if (!shop) { throw new NotFoundException(ShopMessage.NOT_FOUND); } const isActive = new Date(dto.subscriptionEndDate) > new Date() await this.update(shop.id, { 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(ShopMessage.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, { shopId: 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 wallet transactions await em.nativeDelete(WalletTransaction, { shop: id }); // Finally, delete the shop itself await em.nativeDelete(Shop, { id }); }); } async findOrFail(shopId: string) { const shop = await this.shopRepository.findOne({ id: shopId }, { populate: ['deliveries'] }) if (!shop) { throw new BadRequestException(ShopMessage.NOT_FOUND); } return shop } async findOrFailBySlug(shopSlug: string) { const shop = await this.shopRepository.findOne({ slug: shopSlug }, { populate: ['deliveries'] }) if (!shop) { throw new BadRequestException(ShopMessage.NOT_FOUND); } return shop } }