This commit is contained in:
2025-11-17 23:44:45 +03:30
parent 7bd2b59cb1
commit e699a961be
7 changed files with 517 additions and 4 deletions
+33
View File
@@ -43,6 +43,7 @@
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@mikro-orm/cli": "^6.5.8",
"@mikro-orm/seeder": "^6.5.8",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
@@ -3529,6 +3530,38 @@
"@mikro-orm/core": "^6.0.0"
}
},
"node_modules/@mikro-orm/seeder": {
"version": "6.5.8",
"resolved": "https://registry.npmjs.org/@mikro-orm/seeder/-/seeder-6.5.8.tgz",
"integrity": "sha512-EVfXNJ+Q7I1jKLlYwWDdQ/67DxhuBSfS2U/XKKo/+uEaxGBl5vDe8kV3RNRwx8I9x6DHsQpt1NzJL+JS3fSt3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"fs-extra": "11.3.2",
"globby": "11.1.0"
},
"engines": {
"node": ">= 18.12.0"
},
"peerDependencies": {
"@mikro-orm/core": "^6.0.0"
}
},
"node_modules/@mikro-orm/seeder/node_modules/fs-extra": {
"version": "11.3.2",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz",
"integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+2
View File
@@ -9,6 +9,7 @@
"db:create": "npx mikro-orm schema:create --run --config ./src/config/mikro-orm.config.dev.ts",
"db:update": "npx mikro-orm schema:update --run --config ./src/config/mikro-orm.config.dev.ts",
"db:drop": "npx mikro-orm schema:drop --run --config ./src/config/mikro-orm.config.dev.ts",
"db:seed": "npx mikro-orm seeder:run --config ./src/config/mikro-orm.config.dev.ts",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start dist/main",
@@ -54,6 +55,7 @@
"ulid": "^3.0.1"
},
"devDependencies": {
"@mikro-orm/seeder": "^6.5.8",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@mikro-orm/cli": "^6.5.8",
+5
View File
@@ -62,6 +62,11 @@ export default defineConfig({
},
connect: true,
allowGlobalContext: true,
seeder: {
path: './dist/seeders',
pathTs: './src/seeders',
defaultSeeder: 'DatabaseSeeder',
},
driverOptions: {
connection: {
keepAlive: true,
+2 -2
View File
@@ -1,11 +1,11 @@
import { Collection, Entity, ManyToMany, OneToOne, Property } from '@mikro-orm/core';
import { Collection, Entity, ManyToMany, ManyToOne, Property } from '@mikro-orm/core';
import { Category } from './category.entity';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Restaurant } from '../../../modules/restaurants/entities/restaurant.entity';
@Entity({ tableName: 'foods' })
export class Food extends BaseEntity {
@OneToOne(() => Restaurant)
@ManyToOne(() => Restaurant)
restaurant: Restaurant;
@ManyToMany(() => Category)
+466
View File
@@ -0,0 +1,466 @@
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 { Permission } from '../modules/admin/entities/permission.entity';
import { Role } from '../modules/admin/entities/role.entity';
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
import { User } from '../modules/users/entities/user.entity';
export class DatabaseSeeder extends Seeder {
async run(em: EntityManager) {
// 1. Create Permissions (English)
const permissions = [
{ name: 'manage_restaurants' },
{ name: 'manage_foods' },
{ name: 'manage_categories' },
{ name: 'manage_orders' },
{ name: 'manage_admins' },
{ name: 'manage_users' },
{ name: 'view_reports' },
{ name: 'manage_finances' },
];
const createdPermissions: Permission[] = [];
for (const permData of permissions) {
let permission = await em.findOne(Permission, { name: permData.name });
if (!permission) {
permission = em.create(Permission, permData);
em.persist(permission);
}
createdPermissions.push(permission);
}
// 2. Create Roles (English)
const ownerRole = await em.findOne(Role, { name: 'owner' });
if (!ownerRole) {
const role = em.create(Role, { name: 'owner' });
// Add all permissions to owner
createdPermissions.forEach(p => role.permissions.add(p));
em.persist(role);
}
const accountantRole = await em.findOne(Role, { name: 'accountant' });
if (!accountantRole) {
const role = em.create(Role, { name: 'accountant' });
// Add financial permissions to accountant
const financialPerms = createdPermissions.filter(
p => p.name === 'view_reports' || p.name === 'manage_finances' || p.name === 'manage_orders',
);
financialPerms.forEach(p => role.permissions.add(p));
em.persist(role);
}
await em.flush();
// Get roles after flush
const owner = await em.findOne(Role, { name: 'owner' });
const accountant = await em.findOne(Role, { name: 'accountant' });
// 3. Create Restaurants (Farsi)
const zhivanRestaurant = await em.findOne(Restaurant, { slug: 'zhivan' });
if (!zhivanRestaurant) {
const restaurant = em.create(Restaurant, {
name: 'ژیوان',
slug: 'zhivan',
isActive: true,
});
em.persist(restaurant);
}
const booteRestaurant = await em.findOne(Restaurant, { slug: 'boote' });
if (!booteRestaurant) {
const restaurant = em.create(Restaurant, {
name: 'بوته',
slug: 'boote',
isActive: true,
});
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' });
// 4. Create Categories (Farsi)
const categories = [
{ title: 'غذای اصلی', restId: zhivan?.id },
{ title: 'پیش غذا', restId: zhivan?.id },
{ title: 'دسر', restId: zhivan?.id },
{ title: 'نوشیدنی', restId: zhivan?.id },
{ title: 'غذای اصلی', restId: boote?.id },
{ title: 'پیش غذا', restId: boote?.id },
{ title: 'دسر', restId: boote?.id },
{ title: 'نوشیدنی', restId: boote?.id },
];
const createdCategories: Category[] = [];
for (const catData of categories) {
const existing = await em.findOne(Category, { title: catData.title, restId: catData.restId });
if (!existing) {
const category = em.create(Category, {
title: catData.title,
restId: catData.restId,
isActive: true,
});
em.persist(category);
createdCategories.push(category);
} else {
createdCategories.push(existing);
}
}
await em.flush();
// 5. Create Foods (Farsi)
const foods = [
// Zhivan Restaurant Foods
{
title: 'کباب کوبیده',
desc: 'کباب کوبیده خوشمزه با برنج و کره',
price: 150000,
restaurant: zhivan,
categories: [createdCategories[0]], // غذای اصلی
isActive: true,
stock: 50,
stockDefault: 50,
},
{
title: 'جوجه کباب',
desc: 'جوجه کباب تازه با برنج',
price: 120000,
restaurant: zhivan,
categories: [createdCategories[0]], // غذای اصلی
isActive: true,
stock: 40,
stockDefault: 40,
},
{
title: 'کباب بختیاری',
desc: 'کباب بختیاری با برنج و کره',
price: 180000,
restaurant: zhivan,
categories: [createdCategories[0]], // غذای اصلی
isActive: true,
stock: 35,
stockDefault: 35,
},
{
title: 'کباب برگ',
desc: 'کباب برگ مرغوب با برنج',
price: 160000,
restaurant: zhivan,
categories: [createdCategories[0]], // غذای اصلی
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'سالاد فصل',
desc: 'سالاد تازه با سس مخصوص',
price: 35000,
restaurant: zhivan,
categories: [createdCategories[1]], // پیش غذا
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'ماست و خیار',
desc: 'ماست و خیار تازه',
price: 25000,
restaurant: zhivan,
categories: [createdCategories[1]], // پیش غذا
isActive: true,
stock: 25,
stockDefault: 25,
},
{
title: 'ترشی',
desc: 'ترشی مخلوط خانگی',
price: 20000,
restaurant: zhivan,
categories: [createdCategories[1]], // پیش غذا
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'بستنی',
desc: 'بستنی وانیلی خوشمزه',
price: 25000,
restaurant: zhivan,
categories: [createdCategories[2]], // دسر
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'شیرینی',
desc: 'شیرینی محلی',
price: 30000,
restaurant: zhivan,
categories: [createdCategories[2]], // دسر
isActive: true,
stock: 15,
stockDefault: 15,
},
{
title: 'نوشابه',
desc: 'نوشابه گازدار',
price: 15000,
restaurant: zhivan,
categories: [createdCategories[3]], // نوشیدنی
isActive: true,
stock: 100,
stockDefault: 100,
},
{
title: 'دوغ',
desc: 'دوغ محلی',
price: 20000,
restaurant: zhivan,
categories: [createdCategories[3]], // نوشیدنی
isActive: true,
stock: 50,
stockDefault: 50,
},
{
title: 'آب معدنی',
desc: 'آب معدنی',
price: 10000,
restaurant: zhivan,
categories: [createdCategories[3]], // نوشیدنی
isActive: true,
stock: 80,
stockDefault: 80,
},
// Boote Restaurant Foods
{
title: 'پیتزا مخصوص',
desc: 'پیتزا با پنیر و قارچ',
price: 180000,
restaurant: boote,
categories: [createdCategories[4]], // غذای اصلی
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'پیتزا پپرونی',
desc: 'پیتزا پپرونی تند',
price: 200000,
restaurant: boote,
categories: [createdCategories[4]], // غذای اصلی
isActive: true,
stock: 25,
stockDefault: 25,
},
{
title: 'برگر',
desc: 'برگر با سیب زمینی سرخ کرده',
price: 140000,
restaurant: boote,
categories: [createdCategories[4]], // غذای اصلی
isActive: true,
stock: 25,
stockDefault: 25,
},
{
title: 'برگر مخصوص',
desc: 'برگر دوبل با پنیر',
price: 180000,
restaurant: boote,
categories: [createdCategories[4]], // غذای اصلی
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'سوپ',
desc: 'سوپ گرم و خوشمزه',
price: 45000,
restaurant: boote,
categories: [createdCategories[5]], // پیش غذا
isActive: true,
stock: 15,
stockDefault: 15,
},
{
title: 'سالاد سزار',
desc: 'سالاد سزار با سس مخصوص',
price: 55000,
restaurant: boote,
categories: [createdCategories[5]], // پیش غذا
isActive: true,
stock: 20,
stockDefault: 20,
},
{
title: 'سیب زمینی سرخ کرده',
desc: 'سیب زمینی سرخ کرده ترد',
price: 40000,
restaurant: boote,
categories: [createdCategories[5]], // پیش غذا
isActive: true,
stock: 30,
stockDefault: 30,
},
{
title: 'کیک شکلاتی',
desc: 'کیک شکلاتی خامه‌ای',
price: 60000,
restaurant: boote,
categories: [createdCategories[6]], // دسر
isActive: true,
stock: 10,
stockDefault: 10,
},
{
title: 'چیزکیک',
desc: 'چیزکیک وانیلی',
price: 65000,
restaurant: boote,
categories: [createdCategories[6]], // دسر
isActive: true,
stock: 12,
stockDefault: 12,
},
{
title: 'نوشابه',
desc: 'نوشابه گازدار',
price: 15000,
restaurant: boote,
categories: [createdCategories[7]], // نوشیدنی
isActive: true,
stock: 100,
stockDefault: 100,
},
{
title: 'آبمیوه طبیعی',
desc: 'آبمیوه تازه',
price: 35000,
restaurant: boote,
categories: [createdCategories[7]], // نوشیدنی
isActive: true,
stock: 40,
stockDefault: 40,
},
{
title: 'قهوه',
desc: 'قهوه اسپرسو',
price: 45000,
restaurant: boote,
categories: [createdCategories[7]], // نوشیدنی
isActive: true,
stock: 50,
stockDefault: 50,
},
];
for (const foodData of foods) {
if (foodData.restaurant) {
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,
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,
});
// Add categories
if (foodData.categories) {
foodData.categories.forEach(cat => food.categories.add(cat));
}
em.persist(food);
}
}
}
await em.flush();
// 6. Create Admins (Farsi)
const adminMorteza = await em.findOne(Admin, { phone: '09362532122' });
if (!adminMorteza && owner && zhivan) {
const admin = em.create(Admin, {
phone: '09362532122',
firstName: 'مرتضی',
lastName: 'مرتضایی',
role: owner,
restaurant: zhivan,
});
em.persist(admin);
}
const adminHamid = await em.findOne(Admin, { phone: '09185290775' });
if (!adminHamid && accountant && boote) {
const admin = em.create(Admin, {
phone: '09185290775',
firstName: 'حمید',
lastName: 'زرگامی',
role: accountant,
restaurant: boote,
});
em.persist(admin);
}
await em.flush();
// 7. 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();
}
}
+4
View File
@@ -0,0 +1,4 @@
import { DatabaseSeeder } from './DatabaseSeeder';
export { DatabaseSeeder };
export default DatabaseSeeder;
+5 -2
View File
@@ -13,13 +13,16 @@
"target": "ES2023",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false
"noFallthroughCasesInSwitch": false,
"baseUrl": "./",
"paths": {
"src/*": ["./src/*"]
}
}
}