This commit is contained in:
2025-12-03 23:41:24 +03:30
parent 2248f18fe8
commit 87e935acfd
21 changed files with 1002 additions and 755 deletions
+40 -755
View File
@@ -1,773 +1,58 @@
import type { EntityManager } from '@mikro-orm/core';
import { Seeder } from '@mikro-orm/seeder';
import { Category } from '../modules/foods/entities/category.entity';
import { Food } from '../modules/foods/entities/food.entity';
import { Admin } from '../modules/admin/entities/admin.entity';
import { AdminRole } from '../modules/admin/entities/adminRole.entity';
import { Permission as PermissionEntity } from '../modules/roles/entities/permission.entity';
import { Role } from '../modules/roles/entities/role.entity';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
import { User } from '../modules/users/entities/user.entity';
import { Permission, PermissionTitles } from '../common/enums/permission.enum';
import { PaymentMethod } from '../modules/payments/entities/payment-method.entity';
import { PaymentMethodEnum, PaymentGatewayEnum } from '../modules/payments/interface/payment';
import { Delivery } from '../modules/delivery/entities/delivery.entity';
import { DeliveryMethodEnum } from '../modules/delivery/interface/delivery';
import { Schedule } from '../modules/restaurants/entities/schedule.entity';
import { PermissionsSeeder } from './permissions.seeder';
import { RolesSeeder } from './roles.seeder';
import { RestaurantsSeeder } from './restaurants.seeder';
import { PaymentMethodsSeeder } from './payment-methods.seeder';
import { DeliveryMethodsSeeder } from './delivery-methods.seeder';
import { SchedulesSeeder } from './schedules.seeder';
import { CategoriesSeeder } from './categories.seeder';
import { FoodsSeeder } from './foods.seeder';
import { AdminsSeeder } from './admins.seeder';
import { UsersSeeder } from './users.seeder';
export class DatabaseSeeder extends Seeder {
async run(em: EntityManager) {
// 1. Create Permissions (English)
const specialPermissions = [
Permission.CREATE_RESTAURANT,
Permission.DELETE_RESTAURANT,
Permission.UPDATE_RESTAURANT,
];
// 1. Create Permissions
const permissionsSeeder = new PermissionsSeeder();
const permissions = await permissionsSeeder.run(em);
const permissions = Object.values(Permission).map(name => ({
name,
title: PermissionTitles[name],
group: specialPermissions.includes(name) ? 'special' : 'general',
}));
// 2. Create Roles
const rolesSeeder = new RolesSeeder();
const rolesMap = await rolesSeeder.run(em, permissions);
const createdPermissions: PermissionEntity[] = [];
for (const permData of permissions) {
let permission = await em.findOne(PermissionEntity, { name: permData.name });
if (!permission) {
permission = em.create(PermissionEntity, permData);
em.persist(permission);
} else {
// Update existing permission's group if it changed
if (permission.group !== permData.group) {
permission.group = permData.group;
em.persist(permission);
}
}
createdPermissions.push(permission);
}
// 3. Create Restaurants
const restaurantsSeeder = new RestaurantsSeeder();
const restaurantsMap = await restaurantsSeeder.run(em);
// 2. Create Roles (English)
let restaurantRole = await em.findOne(Role, { name: 'restaurant' });
if (!restaurantRole) {
const role = em.create(Role, { name: 'restaurant', isSystem: false });
// Add all general permissions to restaurant role (excluding special permissions)
const generalPermissions = createdPermissions.filter(p => p.group === 'general');
generalPermissions.forEach(p => role.permissions.add(p));
em.persist(role);
restaurantRole = role;
}
const accountantRole = await em.findOne(Role, { name: 'accountant' });
if (!accountantRole) {
const role = em.create(Role, { name: 'accountant', isSystem: false });
// Add financial permissions to accountant
const financialPerms = createdPermissions.filter(
p =>
(p.name as Permission) === Permission.VIEW_REPORTS ||
(p.name as Permission) === Permission.MANAGE_FINANCES ||
(p.name as Permission) === Permission.MANAGE_ORDERS,
);
financialPerms.forEach(p => role.permissions.add(p));
em.persist(role);
}
const superAdminRole = await em.findOne(Role, { name: 'superAdmin' });
if (!superAdminRole) {
const role = em.create(Role, { name: 'superAdmin', isSystem: true });
// Add restaurant management permissions to superAdmin
const restaurantPerms = createdPermissions.filter(
p =>
(p.name as Permission) === Permission.CREATE_RESTAURANT ||
(p.name as Permission) === Permission.DELETE_RESTAURANT ||
(p.name as Permission) === Permission.UPDATE_RESTAURANT,
);
restaurantPerms.forEach(p => role.permissions.add(p));
em.persist(role);
}
await em.flush();
// Get roles after flush (refresh to ensure they're loaded)
if (!restaurantRole) {
restaurantRole = await em.findOne(Role, { name: 'restaurant' });
}
await em.findOne(Role, { name: 'accountant' });
const superAdmin = await em.findOne(Role, { name: 'superAdmin' });
// 3. Payment Methods will be created per restaurant (see section 5.5)
// 4. Delivery methods will be created per restaurant (see section 5.5.5)
// 5. Create Restaurants (Farsi)
const zhivanRestaurant = await em.findOne(Restaurant, { slug: 'zhivan' });
if (!zhivanRestaurant) {
const restaurant = em.create(Restaurant, {
name: 'ژیوان',
slug: 'zhivan',
domain: 'zhivan.example.com',
isActive: true,
phone: '09123456789',
serviceArea: {
type: 'Polygon',
coordinates: [
[
[51.389, 35.6892],
[51.39, 35.6892],
[51.39, 35.6902],
[51.389, 35.6902],
[51.389, 35.6892],
],
],
},
});
em.persist(restaurant);
}
const booteRestaurant = await em.findOne(Restaurant, { slug: 'boote' });
if (!booteRestaurant) {
const restaurant = em.create(Restaurant, {
name: 'بوته',
slug: 'boote',
domain: 'boote.example.com',
isActive: true,
phone: '09123456790',
serviceArea: {
type: 'Polygon',
coordinates: [
[
[51.389, 35.6892],
[51.39, 35.6892],
[51.39, 35.6902],
[51.389, 35.6902],
[51.389, 35.6892],
],
],
},
});
em.persist(restaurant);
}
await em.flush();
// Get restaurants after flush
const zhivan = await em.findOne(Restaurant, { slug: 'zhivan' });
const boote = await em.findOne(Restaurant, { slug: 'boote' });
// 5.5. Create Payment Methods for each Restaurant
// 4. Create Payment Methods for all Restaurants
const allRestaurants = await em.find(Restaurant, {});
const paymentMethodsSeeder = new PaymentMethodsSeeder();
await paymentMethodsSeeder.run(em, allRestaurants);
const paymentMethodsData = [
{
title: 'پرداخت در محل',
method: PaymentMethodEnum.CardOnDelivery,
description: 'پرداخت در محل تحویل',
enabled: true,
order: 1,
gateway: null,
},
{
title: 'نقدی',
method: PaymentMethodEnum.Cash,
description: 'پرداخت نقدی',
enabled: true,
order: 2,
gateway: null,
},
{
title: 'زرین‌پال',
method: PaymentMethodEnum.Online,
description: 'درگاه پرداخت زرین‌پال',
enabled: true,
order: 3,
gateway: PaymentGatewayEnum.ZarinPal,
merchantId: 'test-merchant-id',
},
];
// 5. Create Delivery Methods for all Restaurants
const deliveryMethodsSeeder = new DeliveryMethodsSeeder();
await deliveryMethodsSeeder.run(em, allRestaurants);
for (const restaurant of allRestaurants) {
if (!restaurant) continue;
// 6. Create Schedules for all Restaurants
const schedulesSeeder = new SchedulesSeeder();
await schedulesSeeder.run(em, allRestaurants);
for (const methodData of paymentMethodsData) {
const existing = await em.findOne(PaymentMethod, {
restaurant: { id: restaurant.id },
method: methodData.method,
});
// 7. Create Categories
const categoriesSeeder = new CategoriesSeeder();
const categoriesMap = await categoriesSeeder.run(em, restaurantsMap);
if (!existing) {
const paymentMethod = em.create(PaymentMethod, {
restaurant,
title: methodData.title,
method: methodData.method,
description: methodData.description,
enabled: methodData.enabled,
order: methodData.order,
gateway: methodData.gateway,
merchantId: methodData.merchantId,
});
em.persist(paymentMethod);
} else {
// Update existing payment method if needed
if (
existing.title !== methodData.title ||
existing.description !== methodData.description ||
existing.enabled !== methodData.enabled ||
existing.order !== methodData.order
) {
existing.title = methodData.title;
existing.description = methodData.description;
existing.enabled = methodData.enabled;
existing.order = methodData.order;
if (methodData.gateway !== undefined) {
existing.gateway = methodData.gateway;
}
if (methodData.merchantId !== undefined) {
existing.merchantId = methodData.merchantId;
}
em.persist(existing);
}
}
}
}
// 8. Create Foods
const foodsSeeder = new FoodsSeeder();
await foodsSeeder.run(em, restaurantsMap, categoriesMap);
await em.flush();
// 9. Create Admins
const adminsSeeder = new AdminsSeeder();
await adminsSeeder.run(em, rolesMap, restaurantsMap);
// 5.5.5. Create Delivery Methods for Restaurants
const allRestaurantsForDeliveries = await em.find(Restaurant, {});
const deliveryMethodsData = [
{
method: DeliveryMethodEnum.DineIn,
title: 'سرو در رستوران',
description: 'سرو غذا در رستوران',
deliveryFee: 0,
minOrderPrice: 0,
enabled: true,
order: 1,
},
{
method: DeliveryMethodEnum.Pickup,
title: 'تحویل در رستوران',
description: 'تحویل غذا در محل',
deliveryFee: 0,
minOrderPrice: 0,
enabled: true,
order: 2,
},
{
method: DeliveryMethodEnum.DeliveryCar,
title: 'تحویل با ماشین',
description: 'تحویل غذا با ماشین',
deliveryFee: 5000,
minOrderPrice: 50000,
enabled: true,
order: 3,
},
{
method: DeliveryMethodEnum.DeliveryCourier,
title: 'تحویل با پیک',
description: 'تحویل غذا با پیک',
deliveryFee: 10000,
minOrderPrice: 100000,
enabled: true,
order: 4,
},
];
for (const restaurant of allRestaurantsForDeliveries) {
if (!restaurant) continue;
for (const methodData of deliveryMethodsData) {
const existing = await em.findOne(Delivery, {
restaurant: { id: restaurant.id },
method: methodData.method,
});
if (!existing) {
const delivery = em.create(Delivery, {
restaurant,
method: methodData.method,
title: methodData.title,
description: methodData.description,
deliveryFee: methodData.deliveryFee,
minOrderPrice: methodData.minOrderPrice,
enabled: methodData.enabled,
order: methodData.order,
});
em.persist(delivery);
} else {
// Update existing delivery method if needed
if (
existing.title !== methodData.title ||
existing.description !== methodData.description ||
existing.deliveryFee !== methodData.deliveryFee ||
existing.minOrderPrice !== methodData.minOrderPrice ||
existing.order !== methodData.order
) {
existing.title = methodData.title;
existing.description = methodData.description;
existing.deliveryFee = methodData.deliveryFee;
existing.minOrderPrice = methodData.minOrderPrice;
existing.order = methodData.order;
em.persist(existing);
}
}
}
}
await em.flush();
// 5.6. Create Schedules for all Restaurants
// For each restaurant, create 3 schedules per day (breakfast, lunch, dinner) for all 7 days
const allRestaurantsForSchedules = await em.find(Restaurant, {});
// Define three time slots per day
const timeSlots = [
{ openTime: '08:00', closeTime: '11:00' }, // Breakfast
{ openTime: '12:00', closeTime: '15:00' }, // Lunch
{ openTime: '18:00', closeTime: '23:00' }, // Dinner
];
for (const restaurant of allRestaurantsForSchedules) {
if (!restaurant) continue;
// Create schedules for each day (0 = Sunday, 6 = Saturday)
for (let weekDay = 0; weekDay <= 6; weekDay++) {
// Create 3 schedules per day
for (const timeSlot of timeSlots) {
const existing = await em.findOne(Schedule, {
restId: restaurant.id,
weekDay,
openTime: timeSlot.openTime,
closeTime: timeSlot.closeTime,
});
if (!existing) {
const schedule = em.create(Schedule, {
restId: restaurant.id,
weekDay,
openTime: timeSlot.openTime,
closeTime: timeSlot.closeTime,
isActive: true,
});
em.persist(schedule);
}
}
}
}
await em.flush();
// 6. Create Categories (Farsi)
const categories = [
{ title: 'غذای اصلی', restaurant: zhivan },
{ title: 'پیش غذا', restaurant: zhivan },
{ title: 'دسر', restaurant: zhivan },
{ title: 'نوشیدنی', restaurant: zhivan },
{ title: 'غذای اصلی', restaurant: boote },
{ title: 'پیش غذا', restaurant: boote },
{ title: 'دسر', restaurant: boote },
{ title: 'نوشیدنی', restaurant: boote },
];
const createdCategories: Category[] = [];
for (const catData of categories) {
if (!catData.restaurant) continue;
const existing = await em.findOne(Category, {
title: catData.title,
restaurant: catData.restaurant,
});
if (!existing) {
const category = em.create(Category, {
title: catData.title,
restaurant: catData.restaurant,
isActive: true,
});
em.persist(category);
createdCategories.push(category);
} else {
createdCategories.push(existing);
}
}
await em.flush();
// 7. Create Foods (Farsi)
const foods = [
// Zhivan Restaurant Foods
{
title: 'کباب کوبیده',
desc: 'کباب کوبیده خوشمزه با برنج و کره',
price: 150000,
restaurant: zhivan,
category: createdCategories[0], // غذای اصلی
isActive: true,
stock: 50,
stockDefault: 50,
},
{
title: 'جوجه کباب',
desc: 'جوجه کباب تازه با برنج',
price: 120000,
restaurant: zhivan,
category: createdCategories[0], // غذای اصلی
isActive: true,
stock: 40,
stockDefault: 40,
},
{
title: 'کباب بختیاری',
desc: 'کباب بختیاری با برنج و کره',
price: 180000,
restaurant: zhivan,
category: createdCategories[0], // غذای اصلی
isActive: true,
stock: 35,
stockDefault: 35,
},
{
title: 'کباب برگ',
desc: 'کباب برگ مرغوب با برنج',
price: 160000,
restaurant: zhivan,
category: createdCategories[0], // غذای اصلی
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'سالاد فصل',
desc: 'سالاد تازه با سس مخصوص',
price: 35000,
restaurant: zhivan,
category: createdCategories[1], // پیش غذا
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'ماست و خیار',
desc: 'ماست و خیار تازه',
price: 25000,
restaurant: zhivan,
category: createdCategories[1], // پیش غذا
isActive: true,
stock: 25,
stockDefault: 25,
},
{
title: 'ترشی',
desc: 'ترشی مخلوط خانگی',
price: 20000,
restaurant: zhivan,
category: createdCategories[1], // پیش غذا
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'بستنی',
desc: 'بستنی وانیلی خوشمزه',
price: 25000,
restaurant: zhivan,
category: createdCategories[2], // دسر
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'شیرینی',
desc: 'شیرینی محلی',
price: 30000,
restaurant: zhivan,
category: createdCategories[2], // دسر
isActive: true,
stock: 15,
stockDefault: 15,
},
{
title: 'نوشابه',
desc: 'نوشابه گازدار',
price: 15000,
restaurant: zhivan,
category: createdCategories[3], // نوشیدنی
isActive: true,
stock: 100,
stockDefault: 100,
},
{
title: 'دوغ',
desc: 'دوغ محلی',
price: 20000,
restaurant: zhivan,
category: createdCategories[3], // نوشیدنی
isActive: true,
stock: 50,
stockDefault: 50,
},
{
title: 'آب معدنی',
desc: 'آب معدنی',
price: 10000,
restaurant: zhivan,
category: createdCategories[3], // نوشیدنی
isActive: true,
stock: 80,
stockDefault: 80,
},
// Boote Restaurant Foods
{
title: 'پیتزا مخصوص',
desc: 'پیتزا با پنیر و قارچ',
price: 180000,
restaurant: boote,
category: createdCategories[4], // غذای اصلی
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'پیتزا پپرونی',
desc: 'پیتزا پپرونی تند',
price: 200000,
restaurant: boote,
category: createdCategories[4], // غذای اصلی
isActive: true,
stock: 25,
stockDefault: 25,
},
{
title: 'برگر',
desc: 'برگر با سیب زمینی سرخ کرده',
price: 140000,
restaurant: boote,
category: createdCategories[4], // غذای اصلی
isActive: true,
stock: 25,
stockDefault: 25,
},
{
title: 'برگر مخصوص',
desc: 'برگر دوبل با پنیر',
price: 180000,
restaurant: boote,
category: createdCategories[4], // غذای اصلی
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'سوپ',
desc: 'سوپ گرم و خوشمزه',
price: 45000,
restaurant: boote,
category: createdCategories[5], // پیش غذا
isActive: true,
stock: 15,
stockDefault: 15,
},
{
title: 'سالاد سزار',
desc: 'سالاد سزار با سس مخصوص',
price: 55000,
restaurant: boote,
category: createdCategories[5], // پیش غذا
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'سیب زمینی سرخ کرده',
desc: 'سیب زمینی سرخ کرده ترد',
price: 40000,
restaurant: boote,
category: createdCategories[5], // پیش غذا
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'کیک شکلاتی',
desc: 'کیک شکلاتی خامه‌ای',
price: 60000,
restaurant: boote,
category: createdCategories[6], // دسر
isActive: true,
stock: 10,
stockDefault: 10,
},
{
title: 'چیزکیک',
desc: 'چیزکیک وانیلی',
price: 65000,
restaurant: boote,
category: createdCategories[6], // دسر
isActive: true,
stock: 12,
stockDefault: 12,
},
{
title: 'نوشابه',
desc: 'نوشابه گازدار',
price: 15000,
restaurant: boote,
category: createdCategories[7], // نوشیدنی
isActive: true,
stock: 100,
stockDefault: 100,
},
{
title: 'آبمیوه طبیعی',
desc: 'آبمیوه تازه',
price: 35000,
restaurant: boote,
category: createdCategories[7], // نوشیدنی
isActive: true,
stock: 40,
stockDefault: 40,
},
{
title: 'قهوه',
desc: 'قهوه اسپرسو',
price: 45000,
restaurant: boote,
category: createdCategories[7], // نوشیدنی
isActive: true,
stock: 50,
stockDefault: 50,
},
];
for (const foodData of foods) {
if (foodData.restaurant && foodData.category) {
const existing = await em.findOne(Food, {
title: foodData.title,
restaurant: foodData.restaurant,
});
if (!existing) {
const food = em.create(Food, {
title: foodData.title,
desc: foodData.desc,
price: foodData.price,
restaurant: foodData.restaurant,
category: foodData.category,
isActive: foodData.isActive,
stock: foodData.stock,
stockDefault: foodData.stockDefault,
sat: false,
sun: false,
mon: false,
breakfast: false,
noon: false,
dinner: false,
discount: 0,
rate: 0,
inPlaceServe: false,
pickupServe: false,
});
em.persist(food);
}
}
}
await em.flush();
// 8. Create Admins (Farsi)
const adminMorteza = await em.findOne(Admin, { phone: '09362532122' });
if (!adminMorteza && restaurantRole && zhivan) {
const admin = em.create(Admin, {
phone: '09362532122',
firstName: 'مرتضی',
lastName: 'مرتضایی',
});
em.persist(admin);
// Create AdminRole linking admin to role and restaurant
const adminRole = em.create(AdminRole, {
admin,
role: restaurantRole,
restaurant: zhivan,
});
em.persist(adminRole);
admin.roles.add(adminRole);
}
const adminHamid = await em.findOne(Admin, { phone: '09185290775' });
if (!adminHamid && restaurantRole && boote) {
const admin = em.create(Admin, {
phone: '09185290775',
firstName: 'حمید',
lastName: 'ضرقامی',
});
em.persist(admin);
// Create AdminRole linking admin to role and restaurant
const adminRole = em.create(AdminRole, {
admin,
role: restaurantRole,
restaurant: boote,
});
em.persist(adminRole);
admin.roles.add(adminRole);
}
const adminMehrdad = await em.findOne(Admin, { phone: '0912928395' });
if (!adminMehrdad && superAdmin) {
const admin = em.create(Admin, {
phone: '0912928395',
firstName: 'مهرداد',
lastName: 'مظفری',
});
em.persist(admin);
// Create AdminRole linking admin to role (no restaurant for superAdmin)
const adminRole = em.create(AdminRole, {
admin,
role: superAdmin,
restaurant: null, // SuperAdmin doesn't belong to a specific restaurant
});
em.persist(adminRole);
admin.roles.add(adminRole);
}
await em.flush();
// 9. Create Users (Farsi)
const userMorteza = await em.findOne(User, { phone: '09362532122' });
if (!userMorteza && zhivan) {
const user = em.create(User, {
phone: '09362532122',
firstName: 'مرتضی',
lastName: 'مرتضایی',
restaurant: zhivan,
birthDate: new Date('1990-01-01'),
marriageDate: new Date('2015-01-01'),
isActive: true,
gender: true,
wallet: 0,
points: 0,
});
em.persist(user);
}
const userHamid = await em.findOne(User, { phone: '09185290775' });
if (!userHamid && boote) {
const user = em.create(User, {
phone: '09185290775',
firstName: 'حمید',
lastName: 'زرگامی',
restaurant: boote,
birthDate: new Date('1992-01-01'),
marriageDate: new Date('2017-01-01'),
isActive: true,
gender: true,
wallet: 0,
points: 0,
});
em.persist(user);
}
// Final flush
await em.flush();
// 10. Create Users
const usersSeeder = new UsersSeeder();
await usersSeeder.run(em, restaurantsMap);
}
}
+48
View File
@@ -0,0 +1,48 @@
import type { EntityManager } from '@mikro-orm/core';
import { Admin } from '../modules/admin/entities/admin.entity';
import { AdminRole } from '../modules/admin/entities/adminRole.entity';
import { Role } from '../modules/roles/entities/role.entity';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
import { adminsData } from './data/admins.data';
export class AdminsSeeder {
async run(
em: EntityManager,
rolesMap: Map<string, Role>,
restaurantsMap: Map<string, Restaurant>,
): Promise<void> {
for (const adminData of adminsData) {
const existingAdmin = await em.findOne(Admin, { phone: adminData.phone });
if (existingAdmin) continue;
const role = rolesMap.get(adminData.roleName);
if (!role) continue;
const restaurant = adminData.restaurantSlug
? restaurantsMap.get(adminData.restaurantSlug)
: null;
// For restaurant role, restaurant is required
if (adminData.roleName === 'restaurant' && !restaurant) continue;
const admin = em.create(Admin, {
phone: adminData.phone,
firstName: adminData.firstName,
lastName: adminData.lastName,
});
em.persist(admin);
// Create AdminRole linking admin to role and restaurant
const adminRole = em.create(AdminRole, {
admin,
role,
restaurant: adminData.restaurantSlug ? restaurant : null,
});
em.persist(adminRole);
admin.roles.add(adminRole);
}
await em.flush();
}
}
+39
View File
@@ -0,0 +1,39 @@
import type { EntityManager } from '@mikro-orm/core';
import { Category } from '../modules/foods/entities/category.entity';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
import { categoriesData } from './data/categories.data';
export class CategoriesSeeder {
async run(
em: EntityManager,
restaurantsMap: Map<string, Restaurant>,
): Promise<Map<string, Category>> {
const categoriesMap = new Map<string, Category>();
for (const catData of categoriesData) {
const restaurant = restaurantsMap.get(catData.restaurantSlug);
if (!restaurant) continue;
const existing = await em.findOne(Category, {
title: catData.title,
restaurant,
});
if (!existing) {
const category = em.create(Category, {
title: catData.title,
restaurant,
isActive: true,
});
em.persist(category);
categoriesMap.set(`${catData.restaurantSlug}-${catData.title}`, category);
} else {
categoriesMap.set(`${catData.restaurantSlug}-${catData.title}`, existing);
}
}
await em.flush();
return categoriesMap;
}
}
+32
View File
@@ -0,0 +1,32 @@
export interface AdminData {
phone: string;
firstName: string;
lastName: string;
roleName: string;
restaurantSlug: string | null;
}
export const adminsData: AdminData[] = [
{
phone: '09362532122',
firstName: 'مرتضی',
lastName: 'مرتضایی',
roleName: 'restaurant',
restaurantSlug: 'zhivan',
},
{
phone: '09185290775',
firstName: 'حمید',
lastName: 'ضرقامی',
roleName: 'restaurant',
restaurantSlug: 'boote',
},
{
phone: '0912928395',
firstName: 'مهرداد',
lastName: 'مظفری',
roleName: 'superAdmin',
restaurantSlug: null,
},
];
+16
View File
@@ -0,0 +1,16 @@
export interface CategoryData {
title: string;
restaurantSlug: string;
}
export const categoriesData: CategoryData[] = [
{ title: 'غذای اصلی', restaurantSlug: 'zhivan' },
{ title: 'پیش غذا', restaurantSlug: 'zhivan' },
{ title: 'دسر', restaurantSlug: 'zhivan' },
{ title: 'نوشیدنی', restaurantSlug: 'zhivan' },
{ title: 'غذای اصلی', restaurantSlug: 'boote' },
{ title: 'پیش غذا', restaurantSlug: 'boote' },
{ title: 'دسر', restaurantSlug: 'boote' },
{ title: 'نوشیدنی', restaurantSlug: 'boote' },
];
+51
View File
@@ -0,0 +1,51 @@
import { DeliveryMethodEnum } from '../../modules/delivery/interface/delivery';
export interface DeliveryMethodData {
method: DeliveryMethodEnum;
title: string;
description: string;
deliveryFee: number;
minOrderPrice: number;
enabled: boolean;
order: number;
}
export const deliveryMethodsData: DeliveryMethodData[] = [
{
method: DeliveryMethodEnum.DineIn,
title: 'سرو در رستوران',
description: 'سرو غذا در رستوران',
deliveryFee: 0,
minOrderPrice: 0,
enabled: true,
order: 1,
},
{
method: DeliveryMethodEnum.Pickup,
title: 'تحویل در رستوران',
description: 'تحویل غذا در محل',
deliveryFee: 0,
minOrderPrice: 0,
enabled: true,
order: 2,
},
{
method: DeliveryMethodEnum.DeliveryCar,
title: 'تحویل با ماشین',
description: 'تحویل غذا با ماشین',
deliveryFee: 5000,
minOrderPrice: 50000,
enabled: true,
order: 3,
},
{
method: DeliveryMethodEnum.DeliveryCourier,
title: 'تحویل با پیک',
description: 'تحویل غذا با پیک',
deliveryFee: 10000,
minOrderPrice: 100000,
enabled: true,
order: 4,
},
];
+256
View File
@@ -0,0 +1,256 @@
export interface FoodData {
title: string;
desc: string;
price: number;
restaurantSlug: string;
categoryTitle: string;
isActive: boolean;
stock: number;
stockDefault: number;
}
export const foodsData: FoodData[] = [
// Zhivan Restaurant Foods
{
title: 'کباب کوبیده',
desc: 'کباب کوبیده خوشمزه با برنج و کره',
price: 150000,
restaurantSlug: 'zhivan',
categoryTitle: 'غذای اصلی',
isActive: true,
stock: 50,
stockDefault: 50,
},
{
title: 'جوجه کباب',
desc: 'جوجه کباب تازه با برنج',
price: 120000,
restaurantSlug: 'zhivan',
categoryTitle: 'غذای اصلی',
isActive: true,
stock: 40,
stockDefault: 40,
},
{
title: 'کباب بختیاری',
desc: 'کباب بختیاری با برنج و کره',
price: 180000,
restaurantSlug: 'zhivan',
categoryTitle: 'غذای اصلی',
isActive: true,
stock: 35,
stockDefault: 35,
},
{
title: 'کباب برگ',
desc: 'کباب برگ مرغوب با برنج',
price: 160000,
restaurantSlug: 'zhivan',
categoryTitle: 'غذای اصلی',
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'سالاد فصل',
desc: 'سالاد تازه با سس مخصوص',
price: 35000,
restaurantSlug: 'zhivan',
categoryTitle: 'پیش غذا',
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'ماست و خیار',
desc: 'ماست و خیار تازه',
price: 25000,
restaurantSlug: 'zhivan',
categoryTitle: 'پیش غذا',
isActive: true,
stock: 25,
stockDefault: 25,
},
{
title: 'ترشی',
desc: 'ترشی مخلوط خانگی',
price: 20000,
restaurantSlug: 'zhivan',
categoryTitle: 'پیش غذا',
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'بستنی',
desc: 'بستنی وانیلی خوشمزه',
price: 25000,
restaurantSlug: 'zhivan',
categoryTitle: 'دسر',
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'شیرینی',
desc: 'شیرینی محلی',
price: 30000,
restaurantSlug: 'zhivan',
categoryTitle: 'دسر',
isActive: true,
stock: 15,
stockDefault: 15,
},
{
title: 'نوشابه',
desc: 'نوشابه گازدار',
price: 15000,
restaurantSlug: 'zhivan',
categoryTitle: 'نوشیدنی',
isActive: true,
stock: 100,
stockDefault: 100,
},
{
title: 'دوغ',
desc: 'دوغ محلی',
price: 20000,
restaurantSlug: 'zhivan',
categoryTitle: 'نوشیدنی',
isActive: true,
stock: 50,
stockDefault: 50,
},
{
title: 'آب معدنی',
desc: 'آب معدنی',
price: 10000,
restaurantSlug: 'zhivan',
categoryTitle: 'نوشیدنی',
isActive: true,
stock: 80,
stockDefault: 80,
},
// Boote Restaurant Foods
{
title: 'پیتزا مخصوص',
desc: 'پیتزا با پنیر و قارچ',
price: 180000,
restaurantSlug: 'boote',
categoryTitle: 'غذای اصلی',
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'پیتزا پپرونی',
desc: 'پیتزا پپرونی تند',
price: 200000,
restaurantSlug: 'boote',
categoryTitle: 'غذای اصلی',
isActive: true,
stock: 25,
stockDefault: 25,
},
{
title: 'برگر',
desc: 'برگر با سیب زمینی سرخ کرده',
price: 140000,
restaurantSlug: 'boote',
categoryTitle: 'غذای اصلی',
isActive: true,
stock: 25,
stockDefault: 25,
},
{
title: 'برگر مخصوص',
desc: 'برگر دوبل با پنیر',
price: 180000,
restaurantSlug: 'boote',
categoryTitle: 'غذای اصلی',
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'سوپ',
desc: 'سوپ گرم و خوشمزه',
price: 45000,
restaurantSlug: 'boote',
categoryTitle: 'پیش غذا',
isActive: true,
stock: 15,
stockDefault: 15,
},
{
title: 'سالاد سزار',
desc: 'سالاد سزار با سس مخصوص',
price: 55000,
restaurantSlug: 'boote',
categoryTitle: 'پیش غذا',
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'سیب زمینی سرخ کرده',
desc: 'سیب زمینی سرخ کرده ترد',
price: 40000,
restaurantSlug: 'boote',
categoryTitle: 'پیش غذا',
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'کیک شکلاتی',
desc: 'کیک شکلاتی خامه‌ای',
price: 60000,
restaurantSlug: 'boote',
categoryTitle: 'دسر',
isActive: true,
stock: 10,
stockDefault: 10,
},
{
title: 'چیزکیک',
desc: 'چیزکیک وانیلی',
price: 65000,
restaurantSlug: 'boote',
categoryTitle: 'دسر',
isActive: true,
stock: 12,
stockDefault: 12,
},
{
title: 'نوشابه',
desc: 'نوشابه گازدار',
price: 15000,
restaurantSlug: 'boote',
categoryTitle: 'نوشیدنی',
isActive: true,
stock: 100,
stockDefault: 100,
},
{
title: 'آبمیوه طبیعی',
desc: 'آبمیوه تازه',
price: 35000,
restaurantSlug: 'boote',
categoryTitle: 'نوشیدنی',
isActive: true,
stock: 40,
stockDefault: 40,
},
{
title: 'قهوه',
desc: 'قهوه اسپرسو',
price: 45000,
restaurantSlug: 'boote',
categoryTitle: 'نوشیدنی',
isActive: true,
stock: 50,
stockDefault: 50,
},
];
+40
View File
@@ -0,0 +1,40 @@
import { PaymentMethodEnum, PaymentGatewayEnum } from '../../modules/payments/interface/payment';
export interface PaymentMethodData {
title: string;
method: PaymentMethodEnum;
description: string;
enabled: boolean;
order: number;
gateway: PaymentGatewayEnum | null;
merchantId?: string;
}
export const paymentMethodsData: PaymentMethodData[] = [
{
title: 'پرداخت در محل',
method: PaymentMethodEnum.CardOnDelivery,
description: 'پرداخت در محل تحویل',
enabled: true,
order: 1,
gateway: null,
},
{
title: 'نقدی',
method: PaymentMethodEnum.Cash,
description: 'پرداخت نقدی',
enabled: true,
order: 2,
gateway: null,
},
{
title: 'زرین‌پال',
method: PaymentMethodEnum.Online,
description: 'درگاه پرداخت زرین‌پال',
enabled: true,
order: 3,
gateway: PaymentGatewayEnum.ZarinPal,
merchantId: 'test-merchant-id',
},
];
+16
View File
@@ -0,0 +1,16 @@
import { Permission, PermissionTitles } from '../../common/enums/permission.enum';
export const specialPermissions = [
Permission.CREATE_RESTAURANT,
Permission.DELETE_RESTAURANT,
Permission.UPDATE_RESTAURANT,
];
export const getPermissionsData = () => {
return Object.values(Permission).map(name => ({
name,
title: PermissionTitles[name],
group: specialPermissions.includes(name) ? 'special' : 'general',
}));
};
+53
View File
@@ -0,0 +1,53 @@
export interface RestaurantData {
name: string;
slug: string;
domain: string;
isActive: boolean;
phone: string;
serviceArea: {
type: 'Polygon';
coordinates: number[][][];
};
}
export const restaurantsData: RestaurantData[] = [
{
name: 'ژیوان',
slug: 'zhivan',
domain: 'zhivan.example.com',
isActive: true,
phone: '09123456789',
serviceArea: {
type: 'Polygon',
coordinates: [
[
[51.389, 35.6892],
[51.39, 35.6892],
[51.39, 35.6902],
[51.389, 35.6902],
[51.389, 35.6892],
],
],
},
},
{
name: 'بوته',
slug: 'boote',
domain: 'boote.example.com',
isActive: true,
phone: '09123456790',
serviceArea: {
type: 'Polygon',
coordinates: [
[
[51.389, 35.6892],
[51.39, 35.6892],
[51.39, 35.6902],
[51.389, 35.6902],
[51.389, 35.6892],
],
],
},
},
];
+32
View File
@@ -0,0 +1,32 @@
import { Permission } from '../../common/enums/permission.enum';
export interface RoleConfig {
name: string;
isSystem: boolean;
permissionFilter: (permissionName: Permission) => boolean;
}
export const rolesData: RoleConfig[] = [
{
name: 'restaurant',
isSystem: false,
permissionFilter: () => true, // Will be filtered by group in seeder
},
{
name: 'accountant',
isSystem: false,
permissionFilter: (name: Permission) =>
name === Permission.VIEW_REPORTS ||
name === Permission.MANAGE_FINANCES ||
name === Permission.MANAGE_ORDERS,
},
{
name: 'superAdmin',
isSystem: true,
permissionFilter: (name: Permission) =>
name === Permission.CREATE_RESTAURANT ||
name === Permission.DELETE_RESTAURANT ||
name === Permission.UPDATE_RESTAURANT,
},
];
+11
View File
@@ -0,0 +1,11 @@
export interface TimeSlot {
openTime: string;
closeTime: string;
}
export const timeSlots: TimeSlot[] = [
{ openTime: '08:00', closeTime: '11:00' }, // Breakfast
{ openTime: '12:00', closeTime: '15:00' }, // Lunch
{ openTime: '18:00', closeTime: '23:00' }, // Dinner
];
+40
View File
@@ -0,0 +1,40 @@
export interface UserData {
phone: string;
firstName: string;
lastName: string;
restaurantSlug: string;
birthDate: string;
marriageDate: string;
isActive: boolean;
gender: boolean;
wallet: number;
points: number;
}
export const usersData: UserData[] = [
{
phone: '09362532122',
firstName: 'مرتضی',
lastName: 'مرتضایی',
restaurantSlug: 'zhivan',
birthDate: '1990-01-01',
marriageDate: '2015-01-01',
isActive: true,
gender: true,
wallet: 0,
points: 0,
},
{
phone: '09185290775',
firstName: 'حمید',
lastName: 'زرگامی',
restaurantSlug: 'boote',
birthDate: '1992-01-01',
marriageDate: '2017-01-01',
isActive: true,
gender: true,
wallet: 0,
points: 0,
},
];
+52
View File
@@ -0,0 +1,52 @@
import type { EntityManager } from '@mikro-orm/core';
import { Delivery } from '../modules/delivery/entities/delivery.entity';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
import { deliveryMethodsData } from './data/delivery-methods.data';
export class DeliveryMethodsSeeder {
async run(em: EntityManager, restaurants: Restaurant[]): Promise<void> {
for (const restaurant of restaurants) {
if (!restaurant) continue;
for (const methodData of deliveryMethodsData) {
const existing = await em.findOne(Delivery, {
restaurant: { id: restaurant.id },
method: methodData.method,
});
if (!existing) {
const delivery = em.create(Delivery, {
restaurant,
method: methodData.method,
title: methodData.title,
description: methodData.description,
deliveryFee: methodData.deliveryFee,
minOrderPrice: methodData.minOrderPrice,
enabled: methodData.enabled,
order: methodData.order,
});
em.persist(delivery);
} else {
// Update existing delivery method if needed
if (
existing.title !== methodData.title ||
existing.description !== methodData.description ||
existing.deliveryFee !== methodData.deliveryFee ||
existing.minOrderPrice !== methodData.minOrderPrice ||
existing.order !== methodData.order
) {
existing.title = methodData.title;
existing.description = methodData.description;
existing.deliveryFee = methodData.deliveryFee;
existing.minOrderPrice = methodData.minOrderPrice;
existing.order = methodData.order;
em.persist(existing);
}
}
}
}
await em.flush();
}
}
+53
View File
@@ -0,0 +1,53 @@
import type { EntityManager } from '@mikro-orm/core';
import { Food } from '../modules/foods/entities/food.entity';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
import { Category } from '../modules/foods/entities/category.entity';
import { foodsData } from './data/foods.data';
export class FoodsSeeder {
async run(
em: EntityManager,
restaurantsMap: Map<string, Restaurant>,
categoriesMap: Map<string, Category>,
): Promise<void> {
for (const foodData of foodsData) {
const restaurant = restaurantsMap.get(foodData.restaurantSlug);
const category = categoriesMap.get(`${foodData.restaurantSlug}-${foodData.categoryTitle}`);
if (!restaurant || !category) continue;
const existing = await em.findOne(Food, {
title: foodData.title,
restaurant,
});
if (!existing) {
const food = em.create(Food, {
title: foodData.title,
desc: foodData.desc,
price: foodData.price,
restaurant,
category,
isActive: foodData.isActive,
stock: foodData.stock,
stockDefault: foodData.stockDefault,
sat: false,
sun: false,
mon: false,
breakfast: false,
noon: false,
dinner: false,
discount: 0,
rate: 0,
inPlaceServe: false,
pickupServe: false,
});
em.persist(food);
}
}
await em.flush();
}
}
+56
View File
@@ -0,0 +1,56 @@
import type { EntityManager } from '@mikro-orm/core';
import { PaymentMethod } from '../modules/payments/entities/payment-method.entity';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
import { paymentMethodsData } from './data/payment-methods.data';
export class PaymentMethodsSeeder {
async run(em: EntityManager, restaurants: Restaurant[]): Promise<void> {
for (const restaurant of restaurants) {
if (!restaurant) continue;
for (const methodData of paymentMethodsData) {
const existing = await em.findOne(PaymentMethod, {
restaurant: { id: restaurant.id },
method: methodData.method,
});
if (!existing) {
const paymentMethod = em.create(PaymentMethod, {
restaurant,
title: methodData.title,
method: methodData.method,
description: methodData.description,
enabled: methodData.enabled,
order: methodData.order,
gateway: methodData.gateway,
merchantId: methodData.merchantId,
});
em.persist(paymentMethod);
} else {
// Update existing payment method if needed
if (
existing.title !== methodData.title ||
existing.description !== methodData.description ||
existing.enabled !== methodData.enabled ||
existing.order !== methodData.order
) {
existing.title = methodData.title;
existing.description = methodData.description;
existing.enabled = methodData.enabled;
existing.order = methodData.order;
if (methodData.gateway !== undefined) {
existing.gateway = methodData.gateway;
}
if (methodData.merchantId !== undefined) {
existing.merchantId = methodData.merchantId;
}
em.persist(existing);
}
}
}
}
await em.flush();
}
}
+29
View File
@@ -0,0 +1,29 @@
import type { EntityManager } from '@mikro-orm/core';
import { Permission as PermissionEntity } from '../modules/roles/entities/permission.entity';
import { getPermissionsData } from './data/permissions.data';
export class PermissionsSeeder {
async run(em: EntityManager): Promise<PermissionEntity[]> {
const permissions = getPermissionsData();
const createdPermissions: PermissionEntity[] = [];
for (const permData of permissions) {
let permission = await em.findOne(PermissionEntity, { name: permData.name });
if (!permission) {
permission = em.create(PermissionEntity, permData);
em.persist(permission);
} else {
// Update existing permission's group if it changed
if (permission.group !== permData.group) {
permission.group = permData.group;
em.persist(permission);
}
}
createdPermissions.push(permission);
}
await em.flush();
return createdPermissions;
}
}
+22
View File
@@ -0,0 +1,22 @@
import type { EntityManager } from '@mikro-orm/core';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
import { restaurantsData } from './data/restaurants.data';
export class RestaurantsSeeder {
async run(em: EntityManager): Promise<Map<string, Restaurant>> {
const restaurantsMap = new Map<string, Restaurant>();
for (const restaurantData of restaurantsData) {
let restaurant = await em.findOne(Restaurant, { slug: restaurantData.slug });
if (!restaurant) {
restaurant = em.create(Restaurant, restaurantData);
em.persist(restaurant);
}
restaurantsMap.set(restaurantData.slug, restaurant);
}
await em.flush();
return restaurantsMap;
}
}
+44
View File
@@ -0,0 +1,44 @@
import type { EntityManager } from '@mikro-orm/core';
import { Role } from '../modules/roles/entities/role.entity';
import { Permission } from '../common/enums/permission.enum';
import { Permission as PermissionEntity } from '../modules/roles/entities/permission.entity';
import { rolesData } from './data/roles.data';
export class RolesSeeder {
async run(em: EntityManager, permissions: PermissionEntity[]): Promise<Map<string, Role>> {
const rolesMap = new Map<string, Role>();
for (const roleConfig of rolesData) {
let role = await em.findOne(Role, { name: roleConfig.name });
if (!role) {
role = em.create(Role, {
name: roleConfig.name,
isSystem: roleConfig.isSystem,
});
// Add permissions based on role configuration
// TypeScript knows role is not null here because we just created it
const newRole = role;
if (roleConfig.name === 'restaurant') {
// Add all general permissions to restaurant role
const generalPermissions = permissions.filter(p => p.group === 'general');
generalPermissions.forEach(p => newRole.permissions.add(p));
} else {
// For other roles, use the permission filter
const filteredPermissions = permissions.filter(p =>
roleConfig.permissionFilter(p.name as Permission),
);
filteredPermissions.forEach(p => newRole.permissions.add(p));
}
em.persist(newRole);
}
// Always add to map, whether existing or newly created
rolesMap.set(roleConfig.name, role);
}
await em.flush();
return rolesMap;
}
}
+39
View File
@@ -0,0 +1,39 @@
import type { EntityManager } from '@mikro-orm/core';
import { Schedule } from '../modules/restaurants/entities/schedule.entity';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
import { timeSlots } from './data/schedules.data';
export class SchedulesSeeder {
async run(em: EntityManager, restaurants: Restaurant[]): Promise<void> {
for (const restaurant of restaurants) {
if (!restaurant) continue;
// Create schedules for each day (0 = Sunday, 6 = Saturday)
for (let weekDay = 0; weekDay <= 6; weekDay++) {
// Create 3 schedules per day
for (const timeSlot of timeSlots) {
const existing = await em.findOne(Schedule, {
restId: restaurant.id,
weekDay,
openTime: timeSlot.openTime,
closeTime: timeSlot.closeTime,
});
if (!existing) {
const schedule = em.create(Schedule, {
restId: restaurant.id,
weekDay,
openTime: timeSlot.openTime,
closeTime: timeSlot.closeTime,
isActive: true,
});
em.persist(schedule);
}
}
}
}
await em.flush();
}
}
+33
View File
@@ -0,0 +1,33 @@
import type { EntityManager } from '@mikro-orm/core';
import { User } from '../modules/users/entities/user.entity';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
import { usersData } from './data/users.data';
export class UsersSeeder {
async run(em: EntityManager, restaurantsMap: Map<string, Restaurant>): Promise<void> {
for (const userData of usersData) {
const existingUser = await em.findOne(User, { phone: userData.phone });
if (existingUser) continue;
const restaurant = restaurantsMap.get(userData.restaurantSlug);
if (!restaurant) continue;
const user = em.create(User, {
phone: userData.phone,
firstName: userData.firstName,
lastName: userData.lastName,
restaurant,
birthDate: new Date(userData.birthDate),
marriageDate: new Date(userData.marriageDate),
isActive: userData.isActive,
gender: userData.gender,
wallet: userData.wallet,
points: userData.points,
});
em.persist(user);
}
await em.flush();
}
}