Files
dkala-api/src/modules/shops/providers/shops.service.ts
T
2026-02-08 10:49:16 +03:30

282 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<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("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<PaginatedResult<Shop>> {
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<Shop> {
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<Shop> {
const shop = await this.restRepository.findOne({ id });
if (!shop) {
throw new NotFoundException(RestMessage.NOT_FOUND);
}
return shop;
}
async findOneBySubscriptionId(subscriptionId: string): Promise<Shop> {
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<Shop> {
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<Shop> {
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<void> {
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 });
});
}
}