Files
dkala-api/src/modules/shops/providers/shops.service.ts
T
morteza f8f3fa528e
deploy to danak / build_and_deploy (push) Has been cancelled
background pattern
2026-07-04 15:43:13 +03:30

276 lines
8.7 KiB
TypeScript

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<Shop> {
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<PaginatedResult<Shop>> {
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<Shop> {
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<Shop> {
const shop = await this.shopRepository.findOne({ id });
if (!shop) {
throw new NotFoundException(ShopMessage.NOT_FOUND);
}
return shop;
}
async findOneBySubscriptionId(subscriptionId: string): Promise<Shop> {
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<Background[]> {
return this.em.find(Background, {}, { orderBy: { createdAt: 'asc' } });
}
async updateBackground(id: string, dto: UpdateShopBackgroundDto): Promise<Shop> {
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<Shop> {
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<Shop> {
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<void> {
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
}
}