From 87e935acfd0b6e07d263a96285d542885c3b78ef Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Wed, 3 Dec 2025 23:41:24 +0330 Subject: [PATCH] seeder --- src/seeders/DatabaseSeeder.ts | 795 ++-------------------- src/seeders/admins.seeder.ts | 48 ++ src/seeders/categories.seeder.ts | 39 ++ src/seeders/data/admins.data.ts | 32 + src/seeders/data/categories.data.ts | 16 + src/seeders/data/delivery-methods.data.ts | 51 ++ src/seeders/data/foods.data.ts | 256 +++++++ src/seeders/data/payment-methods.data.ts | 40 ++ src/seeders/data/permissions.data.ts | 16 + src/seeders/data/restaurants.data.ts | 53 ++ src/seeders/data/roles.data.ts | 32 + src/seeders/data/schedules.data.ts | 11 + src/seeders/data/users.data.ts | 40 ++ src/seeders/delivery-methods.seeder.ts | 52 ++ src/seeders/foods.seeder.ts | 53 ++ src/seeders/payment-methods.seeder.ts | 56 ++ src/seeders/permissions.seeder.ts | 29 + src/seeders/restaurants.seeder.ts | 22 + src/seeders/roles.seeder.ts | 44 ++ src/seeders/schedules.seeder.ts | 39 ++ src/seeders/users.seeder.ts | 33 + 21 files changed, 1002 insertions(+), 755 deletions(-) create mode 100644 src/seeders/admins.seeder.ts create mode 100644 src/seeders/categories.seeder.ts create mode 100644 src/seeders/data/admins.data.ts create mode 100644 src/seeders/data/categories.data.ts create mode 100644 src/seeders/data/delivery-methods.data.ts create mode 100644 src/seeders/data/foods.data.ts create mode 100644 src/seeders/data/payment-methods.data.ts create mode 100644 src/seeders/data/permissions.data.ts create mode 100644 src/seeders/data/restaurants.data.ts create mode 100644 src/seeders/data/roles.data.ts create mode 100644 src/seeders/data/schedules.data.ts create mode 100644 src/seeders/data/users.data.ts create mode 100644 src/seeders/delivery-methods.seeder.ts create mode 100644 src/seeders/foods.seeder.ts create mode 100644 src/seeders/payment-methods.seeder.ts create mode 100644 src/seeders/permissions.seeder.ts create mode 100644 src/seeders/restaurants.seeder.ts create mode 100644 src/seeders/roles.seeder.ts create mode 100644 src/seeders/schedules.seeder.ts create mode 100644 src/seeders/users.seeder.ts diff --git a/src/seeders/DatabaseSeeder.ts b/src/seeders/DatabaseSeeder.ts index fa9bc43..da1302b 100644 --- a/src/seeders/DatabaseSeeder.ts +++ b/src/seeders/DatabaseSeeder.ts @@ -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); } } diff --git a/src/seeders/admins.seeder.ts b/src/seeders/admins.seeder.ts new file mode 100644 index 0000000..fcf2463 --- /dev/null +++ b/src/seeders/admins.seeder.ts @@ -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, + restaurantsMap: Map, + ): Promise { + 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(); + } +} + diff --git a/src/seeders/categories.seeder.ts b/src/seeders/categories.seeder.ts new file mode 100644 index 0000000..456a41e --- /dev/null +++ b/src/seeders/categories.seeder.ts @@ -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, + ): Promise> { + const categoriesMap = new Map(); + + 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; + } +} + diff --git a/src/seeders/data/admins.data.ts b/src/seeders/data/admins.data.ts new file mode 100644 index 0000000..4cdca77 --- /dev/null +++ b/src/seeders/data/admins.data.ts @@ -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, + }, +]; + diff --git a/src/seeders/data/categories.data.ts b/src/seeders/data/categories.data.ts new file mode 100644 index 0000000..337edc1 --- /dev/null +++ b/src/seeders/data/categories.data.ts @@ -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' }, +]; + diff --git a/src/seeders/data/delivery-methods.data.ts b/src/seeders/data/delivery-methods.data.ts new file mode 100644 index 0000000..77a20a8 --- /dev/null +++ b/src/seeders/data/delivery-methods.data.ts @@ -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, + }, +]; + diff --git a/src/seeders/data/foods.data.ts b/src/seeders/data/foods.data.ts new file mode 100644 index 0000000..0ec1192 --- /dev/null +++ b/src/seeders/data/foods.data.ts @@ -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, + }, +]; + diff --git a/src/seeders/data/payment-methods.data.ts b/src/seeders/data/payment-methods.data.ts new file mode 100644 index 0000000..e8ac316 --- /dev/null +++ b/src/seeders/data/payment-methods.data.ts @@ -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', + }, +]; + diff --git a/src/seeders/data/permissions.data.ts b/src/seeders/data/permissions.data.ts new file mode 100644 index 0000000..4a74a07 --- /dev/null +++ b/src/seeders/data/permissions.data.ts @@ -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', + })); +}; + diff --git a/src/seeders/data/restaurants.data.ts b/src/seeders/data/restaurants.data.ts new file mode 100644 index 0000000..fe88fd5 --- /dev/null +++ b/src/seeders/data/restaurants.data.ts @@ -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], + ], + ], + }, + }, +]; + diff --git a/src/seeders/data/roles.data.ts b/src/seeders/data/roles.data.ts new file mode 100644 index 0000000..a8d07a2 --- /dev/null +++ b/src/seeders/data/roles.data.ts @@ -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, + }, +]; + diff --git a/src/seeders/data/schedules.data.ts b/src/seeders/data/schedules.data.ts new file mode 100644 index 0000000..dcd9350 --- /dev/null +++ b/src/seeders/data/schedules.data.ts @@ -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 +]; + diff --git a/src/seeders/data/users.data.ts b/src/seeders/data/users.data.ts new file mode 100644 index 0000000..e24394a --- /dev/null +++ b/src/seeders/data/users.data.ts @@ -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, + }, +]; + diff --git a/src/seeders/delivery-methods.seeder.ts b/src/seeders/delivery-methods.seeder.ts new file mode 100644 index 0000000..085c74e --- /dev/null +++ b/src/seeders/delivery-methods.seeder.ts @@ -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 { + 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(); + } +} + diff --git a/src/seeders/foods.seeder.ts b/src/seeders/foods.seeder.ts new file mode 100644 index 0000000..354bbff --- /dev/null +++ b/src/seeders/foods.seeder.ts @@ -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, + categoriesMap: Map, + ): Promise { + 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(); + } +} + diff --git a/src/seeders/payment-methods.seeder.ts b/src/seeders/payment-methods.seeder.ts new file mode 100644 index 0000000..d83586b --- /dev/null +++ b/src/seeders/payment-methods.seeder.ts @@ -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 { + 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(); + } +} + diff --git a/src/seeders/permissions.seeder.ts b/src/seeders/permissions.seeder.ts new file mode 100644 index 0000000..31ce8bc --- /dev/null +++ b/src/seeders/permissions.seeder.ts @@ -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 { + 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; + } +} + diff --git a/src/seeders/restaurants.seeder.ts b/src/seeders/restaurants.seeder.ts new file mode 100644 index 0000000..4eef90e --- /dev/null +++ b/src/seeders/restaurants.seeder.ts @@ -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> { + const restaurantsMap = new Map(); + + 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; + } +} + diff --git a/src/seeders/roles.seeder.ts b/src/seeders/roles.seeder.ts new file mode 100644 index 0000000..d6946fd --- /dev/null +++ b/src/seeders/roles.seeder.ts @@ -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> { + const rolesMap = new Map(); + + 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; + } +} + diff --git a/src/seeders/schedules.seeder.ts b/src/seeders/schedules.seeder.ts new file mode 100644 index 0000000..8a42179 --- /dev/null +++ b/src/seeders/schedules.seeder.ts @@ -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 { + 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(); + } +} + diff --git a/src/seeders/users.seeder.ts b/src/seeders/users.seeder.ts new file mode 100644 index 0000000..1e6c4b8 --- /dev/null +++ b/src/seeders/users.seeder.ts @@ -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): Promise { + 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(); + } +} +