feat: add CategoryImporter service and SeedCategoriesCommand for seeding category data

- Implemented CategoryImporter service to handle bulk export/import logic for categories.
- Created SeedCategoriesCommand to seed category tables from JSON files in data/seed/.
- Added validation and normalization for category data during import.
- Ensured proper error handling and user feedback during the seeding process.
This commit is contained in:
hamed
2026-06-30 21:58:44 +03:30
parent 22937dfa56
commit 9eb5a03258
22 changed files with 5236 additions and 1032 deletions
+4
View File
@@ -20,6 +20,10 @@ services:
App\:
resource: '../src/'
App\Category\Command\SeedCategoriesCommand:
arguments:
$projectDir: '%kernel.project_dir%'
App\Doctor\Controller\DoctorController:
arguments:
$projectDir: '%kernel.project_dir%'
+761
View File
@@ -0,0 +1,761 @@
[
{
"id": 129,
"uuid": "7bfba4b5-646b-11f1-84d7-f26ca1845824",
"name": "اراک",
"status": 1,
"weight": 0,
"province_id": 28,
"province_name": "مرکزی",
"representation_id": null,
"contact_phone": "+98 086 33333333",
"email": "nobat724@gmail.com",
"description": "اراک نوبت، سامانه هوشمند نوبت‌دهی پزشکی استان مرکزی. دسترسی به پزشکان و بیمارستان‌ها. اراک نوبت امکان فیلتر براساس تخصص، منطقه و بیمه پذیرفته شده را فراهم می‌کند.",
"slogan": "رزرو آنلاین نوبت پزشکان اراک، ۲۴ ساعته",
"domain": "arak-nobat.ir",
"keywords": "پزشکان اراک, مراکز درمانی مرکزی, متخصصین اراک, کلینیک‌های اراک, بیمارستان‌های استان مرکزی",
"footer_description": "اراک نوبت، سامانه جامع نوبت‌دهی پزشکی در استان مرکزی. رزرو نوبت از پزشکان و مراکز درمانی اراک بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "arak_nobat",
"telegram": "arak_nobat",
"whatsapp": "989120000027"
},
"logo_url": null
},
{
"id": 103,
"uuid": "7bfb6d65-646b-11f1-84d7-f26ca1845824",
"name": "اردبیل",
"status": 1,
"weight": 0,
"province_id": 3,
"province_name": "اردبیل",
"representation_id": null,
"contact_phone": "+98 045 33333333",
"email": "nobat724@gmail.com",
"description": "اردبیل نوبت، پیشرفته‌ترین سیستم نوبت‌دهی آنلاین پزشکی در اردبیل. انتخاب پزشک براساس تخصص و منطقه. با اردبیل نوبت، خداحافظی با صف‌های طولانی و اتلاف وقت.",
"slogan": "نوبت‌دهی آنلاین پزشکی در اردبیل، بدون اتلاف وقت",
"domain": "ardabil-nobat.ir",
"keywords": "نوبت‌دهی پزشکی اردبیل, پزشکان متخصص اردبیل, کلینیک‌های اردبیل, خدمات پزشکی استان اردبیل, مراکز درمانی اردبیل",
"footer_description": "اردبیل نوبت، سامانه پیشرفته نوبت‌دهی آنلاین پزشکی در استان اردبیل. رزرو نوبت از پزشکان و مراکز درمانی اردبیل بدون اتلاف وقت.",
"social_media": {
"instagram": "ardabil_nobat",
"telegram": "ardabil_nobat",
"whatsapp": "989120000002"
},
"logo_url": null
},
{
"id": 102,
"uuid": "7bfb6ac3-646b-11f1-84d7-f26ca1845824",
"name": "ارومیه",
"status": 1,
"weight": 0,
"province_id": 2,
"province_name": "آذربایجان غربی",
"representation_id": null,
"contact_phone": "+98 044 33333333",
"email": "nobat724@gmail.com",
"description": "ارومیه نوبت، امکان رزرو آنلاین پزشکان متخصص و عمومی در ارومیه. بدون نیاز به مراجعه حضوری، نوبت خود را رزرو کنید. ارومیه نوبت همراه شما در مسیر سلامت است.",
"slogan": "نوبت پزشک در ارومیه، در سریع‌ترین زمان ممکن",
"domain": "urmia-nobat.ir",
"keywords": "پزشکان ارومیه, درمانگاه‌های ارومیه, متخصصین ارومیه, نوبت دهی آنلاین ارومیه, کلینیک‌های آذربایجان غربی",
"footer_description": "ارومیه نوبت، سامانه جامع نوبت‌دهی پزشکی در آذربایجان غربی. رزرو نوبت از پزشکان و مراکز درمانی ارومیه بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "urmia_nobat",
"telegram": "urmia_nobat",
"whatsapp": "989120000001"
},
"logo_url": null
},
{
"id": 104,
"uuid": "7bfb6f78-646b-11f1-84d7-f26ca1845824",
"name": "اصفهان",
"status": 1,
"weight": 0,
"province_id": 4,
"province_name": "اصفهان",
"representation_id": null,
"contact_phone": "+98 031 33333333",
"email": "nobat724@gmail.com",
"description": "اصفهان نوبت، دسترسی به شبکه پزشکان و بیمارستان‌های اصفهان. امکان جستجو و رزرو از بهترین متخصصان. با اصفهان نوبت، رزرو آنلاین در کمتر از یک دقیقه ممکن است.",
"slogan": "دریافت نوبت از بهترین پزشکان اصفهان، تنها با چند کلیک",
"domain": "esf-nobat.ir",
"keywords": "رزرو نوبت اصفهان, پزشکان نصف جهان, مراکز تخصصی اصفهان, بیمارستان‌های اصفهان, مشاوره پزشکی اصفهان",
"footer_description": "اصفهان نوبت، سامانه جامع نوبت‌دهی پزشکی در استان اصفهان. رزرو نوبت از پزشکان و مراکز درمانی اصفهان در کمترین زمان ممکن.",
"social_media": {
"instagram": "esfahan_nobat",
"telegram": "esfahan_nobat",
"whatsapp": "989120000003"
},
"logo_url": null
},
{
"id": 113,
"uuid": "7bfb8171-646b-11f1-84d7-f26ca1845824",
"name": "اهواز",
"status": 1,
"weight": 0,
"province_id": 13,
"province_name": "خوزستان",
"representation_id": null,
"contact_phone": "+98 061 33333333",
"email": "nobat724@gmail.com",
"description": "اهواز نوبت، سامانه نوبت‌دهی آنلاین در اهواز و استان خوزستان. امکان جستجو و رزرو نوبت در مراکز درمانی. اهواز نوبت بهترین راه دسترسی به پزشکان متخصص است.",
"slogan": "سامانه هوشمند نوبت‌دهی پزشکان اهواز",
"domain": "ahvaz-nobat.ir",
"keywords": "رزرو نوبت اهواز, پزشکان خوزستان, کلینیک‌های اهواز, بیمارستان‌های اهواز, متخصصین پزشکی اهواز",
"footer_description": "اهواز نوبت، سامانه جامع نوبت‌دهی پزشکی در استان خوزستان. رزرو نوبت از پزشکان و مراکز درمانی اهواز بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "ahvaz_nobat",
"telegram": "ahvaz_nobat",
"whatsapp": "989120000012"
},
"logo_url": null
},
{
"id": 106,
"uuid": "7bfb738a-646b-11f1-84d7-f26ca1845824",
"name": "ایلام",
"status": 1,
"weight": 0,
"province_id": 6,
"province_name": "ایلام",
"representation_id": null,
"contact_phone": "+98 084 33333333",
"email": "nobat724@gmail.com",
"description": "ایلام نوبت، اولین سامانه تخصصی نوبت‌دهی پزشکی در استان ایلام. پوشش گسترده پزشکان با امکان فیلتر تخصص. با ایلام نوبت، نوبت‌دهی ۲۴ ساعته، آسان و سریع.",
"slogan": "رزرو آنلاین نوبت پزشکان ایلام، بدون مراجعه حضوری",
"domain": "ilam-nobat.ir",
"keywords": "دکترهای ایلام, مراکز درمانی ایلام, نوبت آنلاین ایلام, پزشکان متخصص ایلام, کلینیک‌های ایلام",
"footer_description": "ایلام نوبت، سامانه جامع نوبت‌دهی پزشکی در استان ایلام. رزرو نوبت از پزشکان و مراکز درمانی ایلام بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "ilam_nobat",
"telegram": "ilam_nobat",
"whatsapp": "989120000005"
},
"logo_url": null
},
{
"id": 112,
"uuid": "7bfb7f73-646b-11f1-84d7-f26ca1845824",
"name": "بجنورد",
"status": 1,
"weight": 0,
"province_id": 12,
"province_name": "خراسان شمالی",
"representation_id": null,
"contact_phone": "+98 058 33333333",
"email": "nobat724@gmail.com",
"description": "بجنورد نوبت، ارائه‌دهنده خدمات نوبت‌دهی آنلاین به پزشکان و مراکز درمانی خراسان شمالی. بجنورد نوبت به شما در صرفه‌جویی وقت و هزینه با سیستم رزرو هوشمند کمک می‌کند.",
"slogan": "نوبت پزشک در بجنورد، تنها با چند کلیک",
"domain": "bojnord-nobat.ir",
"keywords": "دکترهای بجنورد, مطب‌های خراسان شمالی, کلینیک‌های بجنورد, خدمات درمانی بجنورد, پزشکان متخصص خراسان شمالی",
"footer_description": "بجنورد نوبت، سامانه جامع نوبت‌دهی پزشکی در استان خراسان شمالی. رزرو نوبت از پزشکان و مراکز درمانی بجنورد بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "bojnord_nobat",
"telegram": "bojnord_nobat",
"whatsapp": "989120000011"
},
"logo_url": null
},
{
"id": 130,
"uuid": "7bfba6b9-646b-11f1-84d7-f26ca1845824",
"name": "بندرعباس",
"status": 1,
"weight": 0,
"province_id": 29,
"province_name": "هرمزگان",
"representation_id": null,
"contact_phone": "+98 076 33333333",
"email": "nobat724@gmail.com",
"description": "بندرعباس نوبت، اولین سامانه نوبت‌دهی آنلاین پزشکی هرمزگان. دسترسی به پزشکان و مراکز تشخیصی. با بندرعباس نوبت رزرو نوبت با کمترین زمان انتظار امکان‌پذیر است.",
"slogan": "سیستم جامع نوبت‌گیری پزشکی بندرعباس",
"domain": "bandar-nobat.ir",
"keywords": "نوبت پزشک بندرعباس, مراکز درمانی هرمزگان, متخصصین بندرعباس, کلینیک‌های جنوب ایران, پزشکان هرمزگان",
"footer_description": "بندرعباس نوبت، سامانه جامع نوبت‌دهی پزشکی در استان هرمزگان. رزرو نوبت از پزشکان و مراکز درمانی بندرعباس بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "bandarabbas_nobat",
"telegram": "bandarabbas_nobat",
"whatsapp": "989120000028"
},
"logo_url": null
},
{
"id": 107,
"uuid": "7bfb7583-646b-11f1-84d7-f26ca1845824",
"name": "بوشهر",
"status": 1,
"weight": 0,
"province_id": 7,
"province_name": "بوشهر",
"representation_id": null,
"contact_phone": "+98 077 33333333",
"email": "nobat724@gmail.com",
"description": "بوشهر نوبت، پلتفرم جامع نوبت‌دهی پزشکی استان بوشهر. دسترسی به پزشکان متخصص و مراکز پاراکلینیکی. با بوشهر نوبت، مدیریت زمان و کاهش هزینه‌های درمان آسان است.",
"slogan": "نوبت‌دهی هوشمند پزشکی بوشهر، در هر ساعت از شبانه‌روز",
"domain": "bushehr-nobat.ir",
"keywords": "نوبت پزشک بوشهر, کلینیک‌های استان بوشهر, پزشکان جنوب کشور, مراکز تشخیصی بوشهر, خدمات پزشکی بوشهر",
"footer_description": "بوشهر نوبت، سامانه تخصصی نوبت‌دهی پزشکی در استان بوشهر. رزرو نوبت از پزشکان و مراکز درمانی بوشهر بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "bushehr_nobat",
"telegram": "bushehr_nobat",
"whatsapp": "989120000006"
},
"logo_url": null
},
{
"id": 110,
"uuid": "7bfb7b9b-646b-11f1-84d7-f26ca1845824",
"name": "بیرجند",
"status": 1,
"weight": 0,
"province_id": 10,
"province_name": "خراسان جنوبی",
"representation_id": null,
"contact_phone": "+98 056 33333333",
"email": "nobat724@gmail.com",
"description": "بیرجند نوبت، سامانه هوشمند نوبت‌دهی پزشکی در خراسان جنوبی. پوشش مراکز درمانی و پزشکان متخصص. با بیرجند نوبت، با چند کلیک ساده در هر ساعت نوبت خود را رزرو کنید.",
"slogan": "نوبت‌دهی پزشکی بیرجند، در کمترین زمان ممکن",
"domain": "birjand-nobat.ir",
"keywords": "پزشکان بیرجند, مراکز درمانی خراسان جنوبی, کلینیک‌های بیرجند, متخصصین بیرجند, نوبت‌دهی آنلاین خراسان جنوبی",
"footer_description": "بیرجند نوبت، سامانه جامع نوبت‌دهی پزشکی در استان خراسان جنوبی. رزرو نوبت از پزشکان و مراکز درمانی بیرجند بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "birjand_nobat",
"telegram": "birjand_nobat",
"whatsapp": "989120000009"
},
"logo_url": null
},
{
"id": 101,
"uuid": "7bfb684b-646b-11f1-84d7-f26ca1845824",
"name": "تبریز",
"status": 1,
"weight": 0,
"province_id": 1,
"province_name": "آذربایجان شرقی",
"representation_id": null,
"contact_phone": "+98 041 33333333",
"email": "nobat724@gmail.com",
"description": "تبریز نوبت، سامانه تخصصی نوبت‌دهی پزشکی با سریع‌ترین روش ممکن. دسترسی به پزشکان متخصص و مراکز درمانی معتبر تبریز. با تبریز نوبت، رزرو آنلاین در هر ساعت از شبانه‌روز امکان‌پذیر است.",
"slogan": "سریع‌ترین سیستم نوبت‌دهی پزشکی تبریز",
"domain": "tabriz-nobat.ir",
"keywords": "نوبت پزشک تبریز, متخصصان تبریز, رزرو آنلاین پزشک تبریز, مراکز درمانی تبریز, بیمارستان‌های تبریز",
"footer_description": "تبریز نوبت، اولین سامانه تخصصی نوبت‌دهی آنلاین پزشکی در شمال غرب کشور. رزرو نوبت از پزشکان و مراکز درمانی معتبر تبریز در کمترین زمان ممکن.",
"social_media": {
"instagram": "tabriz_nobat",
"telegram": "tabriz_nobat",
"whatsapp": "989120000000"
},
"logo_url": null
},
{
"id": 108,
"uuid": "7bfb777e-646b-11f1-84d7-f26ca1845824",
"name": "تهران",
"status": 1,
"weight": 0,
"province_id": 8,
"province_name": "تهران",
"representation_id": null,
"contact_phone": "+98 021 33333333",
"email": "nobat724@gmail.com",
"description": "تهران نوبت، بزرگترین سامانه نوبت‌دهی پزشکی پایتخت. پوشش هزاران پزشک و بیمارستان با جستجوی پیشرفته. تهران نوبت امکان مشاهده نظرات بیماران و رزرو آنلاین را فراهم می‌کند.",
"slogan": "دسترسی به هزاران پزشک تهران، با یک سامانه هوشمند",
"domain": "tehran-nobat.ir",
"keywords": "پزشکان متخصص تهران, کلینیک‌های پایتخت, بیمارستان‌های تهران, نوبت‌گیری آنلاین پزشکی تهران, فوق تخصص‌های تهران",
"footer_description": "تهران نوبت، سامانه جامع نوبت‌دهی پزشکی در پایتخت. رزرو نوبت از پزشکان و مراکز درمانی تهران بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "tehran_nobat",
"telegram": "tehran_nobat",
"whatsapp": "989120000007"
},
"logo_url": null
},
{
"id": 127,
"uuid": "7bfba0b0-646b-11f1-84d7-f26ca1845824",
"name": "خرم‌آباد",
"status": 1,
"weight": 0,
"province_id": 26,
"province_name": "لرستان",
"representation_id": null,
"contact_phone": "+98 066 33333333",
"email": "nobat724@gmail.com",
"description": "خرم‌آباد نوبت، سامانه نوبت‌دهی آنلاین پزشکی در لرستان. ارائه خدمات به مراکز درمانی و پزشکان استان. خرم‌آباد نوبت امکان جستجو براساس تخصص و بیمه را فراهم می‌کند.",
"slogan": "نوبت‌دهی آنلاین پزشکان خرم‌آباد، با چند کلیک ساده",
"domain": "lorestan-nobat.ir",
"keywords": "پزشکان خرم‌آباد, مراکز درمانی لرستان, کلینیک‌های خرم‌آباد, متخصصین لرستان, خدمات پزشکی خرم‌آباد",
"footer_description": "خرم‌آباد نوبت، سامانه جامع نوبت‌دهی پزشکی در استان لرستان. رزرو نوبت از پزشکان و مراکز درمانی خرم‌آباد بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "khorramabad_nobat",
"telegram": "khorramabad_nobat",
"whatsapp": "989120000025"
},
"logo_url": null
},
{
"id": 126,
"uuid": "7bfb9ea9-646b-11f1-84d7-f26ca1845824",
"name": "رشت",
"status": 1,
"weight": 0,
"province_id": 25,
"province_name": "گیلان",
"representation_id": null,
"contact_phone": "+98 013 33333333",
"email": "nobat724@gmail.com",
"description": "رشت نوبت، سامانه تخصصی نوبت‌دهی پزشکی استان گیلان. دسترسی به پزشکان و مراکز تشخیصی استان. رشت نوبت امکان رزرو از پزشکان معتبر با مرور سوابق را فراهم می‌کند.",
"slogan": "سامانه هوشمند نوبت پزشکی در رشت",
"domain": "rasht-nobat.ir",
"keywords": "نوبت پزشک رشت, مراکز درمانی گیلان, متخصصین رشت, کلینیک‌های رشت, بیمارستان‌های گیلان",
"footer_description": "رشت نوبت، سامانه جامع نوبت‌دهی پزشکی در استان گیلان. رزرو نوبت از پزشکان و مراکز درمانی رشت بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "rasht_nobat",
"telegram": "rasht_nobat",
"whatsapp": "989120000024"
},
"logo_url": null
},
{
"id": 116,
"uuid": "7bfb88eb-646b-11f1-84d7-f26ca1845824",
"name": "زاهدان",
"status": 1,
"weight": 0,
"province_id": 16,
"province_name": "سیستان و بلوچستان",
"representation_id": null,
"contact_phone": "+98 054 33333333",
"email": "nobat724@gmail.com",
"description": "زاهدان نوبت، سامانه ویژه نوبت‌دهی پزشکی در سیستان و بلوچستان. پوشش پزشکان عمومی، متخصص و فوق‌تخصص. با زاهدان نوبت، رزرو آنلاین از مراکز معتبر درمانی استان.",
"slogan": "نوبت‌دهی پزشکی زاهدان، سریع و مطمئن",
"domain": "zahedan-nobat.ir",
"keywords": "نوبت پزشکان زاهدان, مراکز درمانی سیستان و بلوچستان, متخصصین زاهدان, کلینیک‌های زاهدان, خدمات پزشکی زاهدان",
"footer_description": "زاهدان نوبت، سامانه جامع نوبت‌دهی پزشکی در استان سیستان و بلوچستان. رزرو نوبت از پزشکان و مراکز درمانی زاهدان بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "zahedan_nobat",
"telegram": "zahedan_nobat",
"whatsapp": "989120000015"
},
"logo_url": null
},
{
"id": 114,
"uuid": "7bfb8454-646b-11f1-84d7-f26ca1845824",
"name": "زنجان",
"status": 1,
"weight": 0,
"province_id": 14,
"province_name": "زنجان",
"representation_id": null,
"contact_phone": "+98 024 33333333",
"email": "nobat724@gmail.com",
"description": "زنجان نوبت، اولین سامانه تخصصی رزرو آنلاین نوبت پزشکی در استان زنجان. دسترسی سریع به پزشکان. با زنجان نوبت، انتخاب آگاهانه با مشاهده سوابق و نظرات بیماران.",
"slogan": "نوبت‌گیری آنلاین از پزشکان زنجان، در هر ساعتی از روز",
"domain": "zanjan-nobat.ir",
"keywords": "نوبت پزشک زنجان, درمانگاه‌های زنجان, متخصصین استان زنجان, کلینیک‌های زنجان, مراکز پزشکی زنجان",
"footer_description": "زنجان نوبت، سامانه جامع نوبت‌دهی پزشکی در استان زنجان. رزرو نوبت از پزشکان و مراکز درمانی زنجان بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "zanjan_nobat",
"telegram": "zanjan_nobat",
"whatsapp": "989120000013"
},
"logo_url": null
},
{
"id": 128,
"uuid": "7bfba2b0-646b-11f1-84d7-f26ca1845824",
"name": "ساری",
"status": 1,
"weight": 0,
"province_id": 27,
"province_name": "مازندران",
"representation_id": null,
"contact_phone": "+98 011 33333333",
"email": "nobat724@gmail.com",
"description": "ساری نوبت، سامانه جامع نوبت‌دهی پزشکی استان مازندران. پوشش مراکز درمانی و پزشکان متخصص. ساری نوبت امکان رزرو در هر ساعت بدون نیاز به تماس تلفنی را فراهم می‌کند.",
"slogan": "نوبت پزشکی در ساری، بدون اتلاف وقت",
"domain": "sari-nobat.ir",
"keywords": "نوبت پزشک ساری, مراکز درمانی مازندران, متخصصین ساری, کلینیک‌های شمال ایران, پزشکان استان مازندران",
"footer_description": "ساری نوبت، سامانه جامع نوبت‌دهی پزشکی در استان مازندران. رزرو نوبت از پزشکان و مراکز درمانی ساری بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "sari_nobat",
"telegram": "sari_nobat",
"whatsapp": "989120000026"
},
"logo_url": null
},
{
"id": 115,
"uuid": "7bfb8666-646b-11f1-84d7-f26ca1845824",
"name": "سمنان",
"status": 1,
"weight": 0,
"province_id": 15,
"province_name": "سمنان",
"representation_id": null,
"contact_phone": "+98 023 33333333",
"email": "nobat724@gmail.com",
"description": "سمنان نوبت، جامع‌ترین مرجع نوبت‌دهی آنلاین پزشکی در استان سمنان. با سمنان نوبت خدمات رزرو آنلاین برای مراکز درمانی و پزشکان متخصص را در کمترین زمان دریافت کنید.",
"slogan": "رزرو آنلاین پزشکان سمنان، بدون محدودیت زمانی",
"domain": "semnan-nobat.ir",
"keywords": "پزشکان سمنان, مراکز درمانی سمنان, رزرو نوبت آنلاین سمنان, متخصصین استان سمنان, کلینیک‌های سمنان",
"footer_description": "سمنان نوبت، سامانه جامع نوبت‌دهی پزشکی در استان سمنان. رزرو نوبت از پزشکان و مراکز درمانی سمنان بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "semnan_nobat",
"telegram": "semnan_nobat",
"whatsapp": "989120000014"
},
"logo_url": null
},
{
"id": 120,
"uuid": "7bfb90fb-646b-11f1-84d7-f26ca1845824",
"name": "سنندج",
"status": 1,
"weight": 0,
"province_id": 20,
"province_name": "کردستان",
"representation_id": null,
"contact_phone": "+98 087 33333333",
"email": "nobat724@gmail.com",
"description": "سنندج نوبت، سامانه تخصصی نوبت‌دهی پزشکی استان کردستان. رزرو آنلاین نوبت از مراکز بهداشتی و پزشکان. سنندج نوبت به کاهش هزینه‌های درمان با مدیریت زمان کمک می‌کند.",
"slogan": "نوبت‌دهی آنلاین پزشکان سنندج، ساده و کاربردی",
"domain": "sanandaj-nobat.ir",
"keywords": "نوبت پزشک سنندج, مراکز درمانی کردستان, متخصصان سنندج, کلینیک‌های سنندج, پزشکان عمومی سنندج",
"footer_description": "سنندج نوبت، سامانه جامع نوبت‌دهی پزشکی در استان کردستان. رزرو نوبت از پزشکان و مراکز درمانی سنندج بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "sanandaj_nobat",
"telegram": "sanandaj_nobat",
"whatsapp": "989120000019"
},
"logo_url": null
},
{
"id": 109,
"uuid": "7bfb797f-646b-11f1-84d7-f26ca1845824",
"name": "شهرکرد",
"status": 1,
"weight": 0,
"province_id": 9,
"province_name": "چهارمحال و بختیاری",
"representation_id": null,
"contact_phone": "+98 038 33333333",
"email": "nobat724@gmail.com",
"description": "شهرکرد نوبت، سامانه جامع نوبت‌دهی پزشکی در چهارمحال و بختیاری. حذف صف‌های طولانی با رزرو آنلاین. با شهرکرد نوبت، دسترسی به پزشکان در کوتاه‌ترین زمان ممکن.",
"slogan": "صف‌های طولانی نوبت در شهرکرد را فراموش کنید",
"domain": "shkord-nobat.ir",
"keywords": "پزشکان شهرکرد, درمانگاه‌های چهارمحال و بختیاری, متخصصین شهرکرد, کلینیک‌های شهرکرد, خدمات درمانی بام ایران",
"footer_description": "شهرکرد نوبت، سامانه تخصصی نوبت‌دهی پزشکی در استان چهارمحال و بختیاری. رزرو نوبت از پزشکان و مراکز درمانی شهرکرد بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "shahrekord_nobat",
"telegram": "shahrekord_nobat",
"whatsapp": "989120000008"
},
"logo_url": null
},
{
"id": 117,
"uuid": "7bfb8b02-646b-11f1-84d7-f26ca1845824",
"name": "شیراز",
"status": 1,
"weight": 0,
"province_id": 17,
"province_name": "فارس",
"representation_id": null,
"contact_phone": "+98 071 33333333",
"email": "nobat724@gmail.com",
"description": "شیراز نوبت، سامانه پیشرفته رزرو آنلاین نوبت پزشکی در شیراز. دسترسی به پزشکان برتر و مراکز درمانی فارس. شیراز نوبت امکان جستجو براساس تخصص و منطقه را فراهم می‌کند.",
"slogan": "نوبت‌دهی آنلاین پزشکان شیراز، در کمترین زمان",
"domain": "shiraz-nobat.ir",
"keywords": "پزشکان شیراز, بیمارستان‌های شیراز, مراکز درمانی فارس, مطب‌های شیراز, متخصصین شهر شیراز",
"footer_description": "شیراز نوبت، سامانه جامع نوبت‌دهی پزشکی در استان فارس. رزرو نوبت از پزشکان و مراکز درمانی شیراز بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "shiraz_nobat",
"telegram": "shiraz_nobat",
"whatsapp": "989120000016"
},
"logo_url": null
},
{
"id": 118,
"uuid": "7bfb8d02-646b-11f1-84d7-f26ca1845824",
"name": "قزوین",
"status": 1,
"weight": 0,
"province_id": 18,
"province_name": "قزوین",
"representation_id": null,
"contact_phone": "+98 028 33333333",
"email": "nobat724@gmail.com",
"description": "قزوین نوبت، اولین سامانه هوشمند نوبت‌دهی پزشکی در استان قزوین. رزرو آنلاین نوبت از پزشکان و مراکز پاراکلینیکی. با قزوین نوبت، مدیریت نوبت‌ها از طریق پنل کاربری.",
"slogan": "نوبت‌گیری از پزشکان قزوین، بدون نیاز به حضور فیزیکی",
"domain": "qazvin-nobat.ir",
"keywords": "نوبت پزشک قزوین, مراکز درمانی استان قزوین, متخصصین قزوین, کلینیک‌های قزوین, خدمات پزشکی قزوین",
"footer_description": "قزوین نوبت، سامانه جامع نوبت‌دهی پزشکی در استان قزوین. رزرو نوبت از پزشکان و مراکز درمانی قزوین بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "qazvin_nobat",
"telegram": "qazvin_nobat",
"whatsapp": "989120000017"
},
"logo_url": null
},
{
"id": 119,
"uuid": "7bfb8efd-646b-11f1-84d7-f26ca1845824",
"name": "قم",
"status": 1,
"weight": 0,
"province_id": 19,
"province_name": "قم",
"representation_id": null,
"contact_phone": "+98 025 33333333",
"email": "nobat724@gmail.com",
"description": "قم نوبت، سامانه اختصاصی نوبت‌دهی پزشکی قم با پشتیبانی ۲۴ ساعته. دسترسی به پزشکان و مراکز درمانی شهر قم. قم نوبت امکان مشاهده تخصص‌ها و هزینه‌ها را فراهم می‌کند.",
"slogan": "سامانه جامع نوبت‌دهی پزشکی قم، در خدمت سلامت شما",
"domain": "qom-nobat.ir",
"keywords": "پزشکان قم, بیمارستان‌های قم, مراکز تشخیصی قم, متخصصین شهر قم, کلینیک‌های درمانی قم",
"footer_description": "قم نوبت، سامانه جامع نوبت‌دهی پزشکی در استان قم. رزرو نوبت از پزشکان و مراکز درمانی قم بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "qom_nobat",
"telegram": "qom_nobat",
"whatsapp": "989120000018"
},
"logo_url": null
},
{
"id": 105,
"uuid": "7bfb718c-646b-11f1-84d7-f26ca1845824",
"name": "کرج",
"status": 1,
"weight": 0,
"province_id": 5,
"province_name": "البرز",
"representation_id": null,
"contact_phone": "+98 026 33333333",
"email": "nobat724@gmail.com",
"description": "کرج نوبت، راهکار آسان برای نوبت‌دهی پزشکی در کرج و حومه. پوشش مراکز درمانی البرز با رزرو آنلاین. کرج نوبت بدون نیاز به تلفن و انتظار در صف خدمت می‌کند.",
"slogan": "نوبت‌دهی پزشکی کرج، سریع و آسان",
"domain": "karaj-nobat.ir",
"keywords": "نوبت‌دهی آنلاین کرج, پزشکان البرز, مطب‌های کرج, درمانگاه‌های کرج, مراکز تخصصی البرز",
"footer_description": "کرج نوبت، سامانه تخصصی نوبت‌دهی پزشکی در استان البرز. رزرو نوبت از پزشکان و مراکز درمانی کرج بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "karaj_nobat",
"telegram": "karaj_nobat",
"whatsapp": "989120000004"
},
"logo_url": null
},
{
"id": 121,
"uuid": "7bfb93e4-646b-11f1-84d7-f26ca1845824",
"name": "کرمان",
"status": 1,
"weight": 0,
"province_id": 21,
"province_name": "کرمان",
"representation_id": null,
"contact_phone": "+98 034 33333333",
"email": "nobat724@gmail.com",
"description": "کرمان نوبت، سیستم جامع نوبت‌دهی آنلاین پزشکی در استان کرمان. پوشش گسترده پزشکان و مراکز تخصصی. کرمان نوبت امکان رزرو براساس نزدیکترین زمان و مکان را فراهم می‌کند.",
"slogan": "نوبت‌دهی پزشکی کرمان، در خدمت سلامت شما",
"domain": "kerman-nobat.ir",
"keywords": "رزرو نوبت کرمان, پزشکان استان کرمان, کلینیک‌های کرمان, متخصصین کرمان, مراکز درمانی کرمان",
"footer_description": "کرمان نوبت، سامانه جامع نوبت‌دهی پزشکی در استان کرمان. رزرو نوبت از پزشکان و مراکز درمانی کرمان بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "kerman_nobat",
"telegram": "kerman_nobat",
"whatsapp": "989120000020"
},
"logo_url": null
},
{
"id": 122,
"uuid": "7bfb9685-646b-11f1-84d7-f26ca1845824",
"name": "کرمانشاه",
"status": 1,
"weight": 0,
"province_id": 22,
"province_name": "کرمانشاه",
"representation_id": null,
"contact_phone": "+98 083 33333333",
"email": "nobat724@gmail.com",
"description": "کرمانشاه نوبت، سامانه هوشمند نوبت‌دهی پزشکی با پوشش مراکز درمانی کرمانشاه. کرمانشاه نوبت امکان رزرو آنلاین از پزشکان متخصص و بیمارستان‌ها در هر ساعت را فراهم می‌کند.",
"slogan": "رزرو آنلاین نوبت پزشکان کرمانشاه، ۲۴ ساعته",
"domain": "kermanshah-nobat.ir",
"keywords": "پزشکان کرمانشاه, نوبت‌دهی درمانی کرمانشاه, مطب‌های کرمانشاه, کلینیک‌های غرب کشور, بیمارستان‌های کرمانشاه",
"footer_description": "کرمانشاه نوبت، سامانه جامع نوبت‌دهی پزشکی در استان کرمانشاه. رزرو نوبت از پزشکان و مراکز درمانی کرمانشاه بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "kermanshah_nobat",
"telegram": "kermanshah_nobat",
"whatsapp": "989120000021"
},
"logo_url": null
},
{
"id": 124,
"uuid": "7bfb9ab1-646b-11f1-84d7-f26ca1845824",
"name": "گچساران",
"status": 1,
"weight": 0,
"province_id": 23,
"province_name": "کهگیلویه و بویراحمد",
"representation_id": null,
"contact_phone": "+98 074 33333333",
"email": "nobat724@gmail.com",
"description": "یاسوج نوبت، سامانه نوبت‌دهی آنلاین پزشکی در کهگیلویه و بویراحمد. دسترسی به پزشکان متخصص و مراکز درمانی. یاسوج نوبت باعث کاهش اتلاف وقت و هزینه می‌شود.",
"slogan": "نوبت‌دهی پزشکان یاسوج، بدون اتلاف وقت و هزینه",
"domain": "yasuj-nobat.ir",
"keywords": "نوبت پزشک یاسوج, مراکز درمانی کهگیلویه و بویراحمد, متخصصین یاسوج, کلینیک‌های یاسوج, درمانگاه‌های یاسوج",
"footer_description": "یاسوج نوبت، سامانه جامع نوبت‌دهی پزشکی در استان کهگیلویه و بویراحمد. رزرو نوبت از پزشکان و مراکز درمانی یاسوج بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "yasuj_nobat",
"telegram": "yasuj_nobat",
"whatsapp": "989120000022"
},
"logo_url": null
},
{
"id": 125,
"uuid": "7bfb9cb5-646b-11f1-84d7-f26ca1845824",
"name": "گرگان",
"status": 1,
"weight": 0,
"province_id": 24,
"province_name": "گلستان",
"representation_id": null,
"contact_phone": "+98 017 33333333",
"email": "nobat724@gmail.com",
"description": "گرگان نوبت، پیشرفته‌ترین سامانه رزرو آنلاین نوبت پزشکی گلستان. خدمات نوبت‌دهی به تمامی مراکز درمانی و پزشکان. با گرگان نوبت، رزرو سریع با چند کلیک ساده.",
"slogan": "نوبت‌دهی آنلاین پزشکی گرگان، ساده و سریع",
"domain": "golestan-nobat.ir",
"keywords": "پزشکان گرگان, خدمات درمانی گلستان, مطب‌های گرگان, کلینیک‌های گرگان, متخصصین استان گلستان",
"footer_description": "گرگان نوبت، سامانه جامع نوبت‌دهی پزشکی در استان گلستان. رزرو نوبت از پزشکان و مراکز درمانی گرگان بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "gorgan_nobat",
"telegram": "gorgan_nobat",
"whatsapp": "989120000023"
},
"logo_url": null
},
{
"id": 111,
"uuid": "7bfb7d86-646b-11f1-84d7-f26ca1845824",
"name": "مشهد",
"status": 1,
"weight": 0,
"province_id": 11,
"province_name": "خراسان رضوی",
"representation_id": null,
"contact_phone": "+98 051 33333333",
"email": "nobat724@gmail.com",
"description": "مشهد نوبت، پیشرفته‌ترین سامانه رزرو نوبت پزشکی در مشهد مقدس. امکان مشاهده سوابق پزشکان و نظرات بیماران. مشهد نوبت تمامی مراکز درمانی شهر را پوشش می‌دهد.",
"slogan": "خداحافظی با صف‌های نوبت پزشکی در مشهد",
"domain": "mashhad-nobat.ir",
"keywords": "پزشکان مشهد مقدس, بیمارستان‌های مشهد, کلینیک‌های خراسان رضوی, متخصصین مشهد, فوق تخصص‌های مشهد",
"footer_description": "مشهد نوبت، سامانه جامع نوبت‌دهی پزشکی در استان خراسان رضوی. رزرو نوبت از پزشکان و مراکز درمانی مشهد بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "mashhad_nobat",
"telegram": "mashhad_nobat",
"whatsapp": "989120000010"
},
"logo_url": null
},
{
"id": 600,
"uuid": "7bfbad3c-646b-11f1-84d7-f26ca1845824",
"name": "نوبت 724",
"status": 1,
"weight": 0,
"province_id": null,
"province_name": null,
"representation_id": null,
"contact_phone": "+98 021 724724724",
"email": "nobat724@gmail.com",
"description": "نوبت 724 - سیستم آنلاین نوبت‌دهی برای پزشکان و کلینیک‌ها. با استفاده از نوبت 724، به راحتی نوبت پزشکی خود را به صورت آنلاین رزرو کنید و از خدمات سریع و کارآمد ما بهره‌مند شوید",
"slogan": "دیگه نوبت گرفتن کار سختی نیست !",
"domain": "nobat724.com",
"keywords": "نوبت 724, نوبت دهی, رزرو نوبت, پزشک, پزشکی, کلینیک, بیمارستان, نوبت آنلاین, سیستم نوبت دهی, سیستم نوبت دهی آنلاین, سیستم نوبت دهی پزشکی, سیستم نوبت دهی کلینیک, سیستم نوبت دهی بیمارستان, سیستم نوبت دهی آنلاین پزشکی, سیستم نوبت دهی آنلاین کلینیک, سیستم نوبت دهی آنلاین بیمارستان",
"footer_description": "نوبت 724، سامانه جامع نوبت‌دهی پزشکی برای تمامی شهرها. رزرو نوبت از پزشکان و مراکز درمانی معتبر بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "nobat724",
"telegram": "nobat724",
"whatsapp": "989120000031"
},
"logo_url": null
},
{
"id": 131,
"uuid": "7bfba8b7-646b-11f1-84d7-f26ca1845824",
"name": "همدان",
"status": 1,
"weight": 0,
"province_id": 30,
"province_name": "همدان",
"representation_id": null,
"contact_phone": "+98 081 33333333",
"email": "nobat724@gmail.com",
"description": "همدان نوبت، سامانه پیشرفته نوبت‌دهی پزشکی در استان همدان. پوشش پزشکان و مراکز درمانی استان. همدان نوبت امکان جستجو براساس تخصص و محله را فراهم می‌کند.",
"slogan": "نوبت‌دهی پزشکی همدان، آسان و سریع",
"domain": "hamadan-nobat.ir",
"keywords": "پزشکان همدان, نوبت‌دهی متخصصین همدان, کلینیک‌های استان همدان, مراکز درمانی همدان, بیمارستان‌های همدان",
"footer_description": "همدان نوبت، سامانه جامع نوبت‌دهی پزشکی در استان همدان. رزرو نوبت از پزشکان و مراکز درمانی همدان بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "hamedan_nobat",
"telegram": "hamedan_nobat",
"whatsapp": "989120000029"
},
"logo_url": null
},
{
"id": 123,
"uuid": "7bfb989e-646b-11f1-84d7-f26ca1845824",
"name": "یاسوج",
"status": 1,
"weight": 0,
"province_id": 23,
"province_name": "کهگیلویه و بویراحمد",
"representation_id": null,
"contact_phone": "+98 074 33333333",
"email": "nobat724@gmail.com",
"description": "یاسوج نوبت، سامانه نوبت‌دهی آنلاین پزشکی در کهگیلویه و بویراحمد. دسترسی به پزشکان متخصص و مراکز درمانی. یاسوج نوبت باعث کاهش اتلاف وقت و هزینه می‌شود.",
"slogan": "نوبت‌دهی پزشکان یاسوج، بدون اتلاف وقت و هزینه",
"domain": "yasuj-nobat.ir",
"keywords": "نوبت پزشک یاسوج, مراکز درمانی کهگیلویه و بویراحمد, متخصصین یاسوج, کلینیک‌های یاسوج, درمانگاه‌های یاسوج",
"footer_description": "یاسوج نوبت، سامانه جامع نوبت‌دهی پزشکی در استان کهگیلویه و بویراحمد. رزرو نوبت از پزشکان و مراکز درمانی یاسوج بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "yasuj_nobat",
"telegram": "yasuj_nobat",
"whatsapp": "989120000022"
},
"logo_url": null
},
{
"id": 132,
"uuid": "7bfbaacd-646b-11f1-84d7-f26ca1845824",
"name": "یزد",
"status": 1,
"weight": 0,
"province_id": 100,
"province_name": "یزد",
"representation_id": null,
"contact_phone": "+98 035 33333333",
"email": "nobat724@gmail.com",
"description": "یزد نوبت، کامل‌ترین سامانه نوبت‌دهی آنلاین پزشکی در استان یزد. دسترسی به پزشکان و مراکز پاراکلینیکی. یزد نوبت امکان رزرو با کمترین زمان انتظار را فراهم می‌کند.",
"slogan": "نوبت‌گیری آنلاین پزشکان یزد، در هر ساعت از روز",
"domain": "yazd-nobat.ir",
"keywords": "نوبت پزشک یزد, مراکز درمانی استان یزد, متخصصین یزد, کلینیک‌های یزد, خدمات پزشکی شهر جهانی یزد",
"footer_description": "یزد نوبت، سامانه جامع نوبت‌دهی پزشکی در استان یزد. رزرو نوبت از پزشکان و مراکز درمانی یزد بدون نیاز به مراجعه حضوری.",
"social_media": {
"instagram": "yazd_nobat",
"telegram": "yazd_nobat",
"whatsapp": "989120000030"
},
"logo_url": null
}
]
File diff suppressed because it is too large Load Diff
+219
View File
@@ -0,0 +1,219 @@
[
{
"id": 1,
"uuid": "7bfb3151-646b-11f1-84d7-f26ca1845824",
"name": "آذربایجان شرقی",
"status": 1,
"weight": 10
},
{
"id": 2,
"uuid": "7bfb33c9-646b-11f1-84d7-f26ca1845824",
"name": "آذربایجان غربی",
"status": 1,
"weight": 20
},
{
"id": 3,
"uuid": "7bfb35cc-646b-11f1-84d7-f26ca1845824",
"name": "اردبیل",
"status": 1,
"weight": 30
},
{
"id": 4,
"uuid": "7bfb37c2-646b-11f1-84d7-f26ca1845824",
"name": "اصفهان",
"status": 1,
"weight": 40
},
{
"id": 5,
"uuid": "7bfb3912-646b-11f1-84d7-f26ca1845824",
"name": "البرز",
"status": 1,
"weight": 50
},
{
"id": 6,
"uuid": "7bfb3b56-646b-11f1-84d7-f26ca1845824",
"name": "ایلام",
"status": 1,
"weight": 60
},
{
"id": 7,
"uuid": "7bfb3d5d-646b-11f1-84d7-f26ca1845824",
"name": "بوشهر",
"status": 1,
"weight": 70
},
{
"id": 8,
"uuid": "7bfb3f37-646b-11f1-84d7-f26ca1845824",
"name": "تهران",
"status": 1,
"weight": 80
},
{
"id": 9,
"uuid": "7bfb410e-646b-11f1-84d7-f26ca1845824",
"name": "چهارمحال و بختیاری",
"status": 1,
"weight": 90
},
{
"id": 10,
"uuid": "7bfb42e9-646b-11f1-84d7-f26ca1845824",
"name": "خراسان جنوبی",
"status": 1,
"weight": 100
},
{
"id": 11,
"uuid": "7bfb44b7-646b-11f1-84d7-f26ca1845824",
"name": "خراسان رضوی",
"status": 1,
"weight": 110
},
{
"id": 12,
"uuid": "7bfb46c7-646b-11f1-84d7-f26ca1845824",
"name": "خراسان شمالی",
"status": 1,
"weight": 120
},
{
"id": 13,
"uuid": "7bfb4897-646b-11f1-84d7-f26ca1845824",
"name": "خوزستان",
"status": 1,
"weight": 130
},
{
"id": 14,
"uuid": "7bfb4a78-646b-11f1-84d7-f26ca1845824",
"name": "زنجان",
"status": 1,
"weight": 140
},
{
"id": 15,
"uuid": "7bfb4c40-646b-11f1-84d7-f26ca1845824",
"name": "سمنان",
"status": 1,
"weight": 150
},
{
"id": 16,
"uuid": "7bfb4db6-646b-11f1-84d7-f26ca1845824",
"name": "سیستان و بلوچستان",
"status": 1,
"weight": 160
},
{
"id": 17,
"uuid": "7bfb4f39-646b-11f1-84d7-f26ca1845824",
"name": "فارس",
"status": 1,
"weight": 170
},
{
"id": 18,
"uuid": "7bfb50da-646b-11f1-84d7-f26ca1845824",
"name": "قزوین",
"status": 1,
"weight": 180
},
{
"id": 19,
"uuid": "7bfb5214-646b-11f1-84d7-f26ca1845824",
"name": "قم",
"status": 1,
"weight": 190
},
{
"id": 20,
"uuid": "7bfb5347-646b-11f1-84d7-f26ca1845824",
"name": "کردستان",
"status": 1,
"weight": 200
},
{
"id": 21,
"uuid": "7bfb54a5-646b-11f1-84d7-f26ca1845824",
"name": "کرمان",
"status": 1,
"weight": 210
},
{
"id": 22,
"uuid": "7bfb55d3-646b-11f1-84d7-f26ca1845824",
"name": "کرمانشاه",
"status": 1,
"weight": 220
},
{
"id": 23,
"uuid": "7bfb5705-646b-11f1-84d7-f26ca1845824",
"name": "کهگیلویه و بویراحمد",
"status": 1,
"weight": 230
},
{
"id": 24,
"uuid": "7bfb583a-646b-11f1-84d7-f26ca1845824",
"name": "گلستان",
"status": 1,
"weight": 240
},
{
"id": 25,
"uuid": "7bfb5963-646b-11f1-84d7-f26ca1845824",
"name": "گیلان",
"status": 1,
"weight": 250
},
{
"id": 26,
"uuid": "7bfb5a8e-646b-11f1-84d7-f26ca1845824",
"name": "لرستان",
"status": 1,
"weight": 260
},
{
"id": 27,
"uuid": "7bfb5bca-646b-11f1-84d7-f26ca1845824",
"name": "مازندران",
"status": 1,
"weight": 270
},
{
"id": 28,
"uuid": "7bfb5cf2-646b-11f1-84d7-f26ca1845824",
"name": "مرکزی",
"status": 1,
"weight": 280
},
{
"id": 29,
"uuid": "7bfb5e34-646b-11f1-84d7-f26ca1845824",
"name": "هرمزگان",
"status": 1,
"weight": 290
},
{
"id": 30,
"uuid": "7bfb5fff-646b-11f1-84d7-f26ca1845824",
"name": "همدان",
"status": 1,
"weight": 300
},
{
"id": 100,
"uuid": "7bfb61b0-646b-11f1-84d7-f26ca1845824",
"name": "یزد",
"status": 1,
"weight": 310
}
]
+839
View File
@@ -0,0 +1,839 @@
[
{
"id": 1,
"uuid": "c60b2c3c-de3c-4c2b-a4c9-75c3444e69cb",
"name": "پزشک عمومی",
"slug": "general-practitioner",
"status": 1,
"weight": 10,
"parent_id": null
},
{
"id": 2,
"uuid": "0774979b-99d0-4918-aeb4-60967bce7add",
"name": "داخلی",
"slug": "internal-medicine",
"status": 1,
"weight": 20,
"parent_id": null
},
{
"id": 3,
"uuid": "b5ce97a1-0cab-4997-9889-7d30a60091ff",
"name": "گوارش و کبد",
"slug": "gastroenterology",
"status": 1,
"weight": 1,
"parent_id": 2
},
{
"id": 4,
"uuid": "cf6c5c00-ca83-4b2f-aed8-8cb5d5ed88be",
"name": "کلیه و نفرولوژی",
"slug": "nephrology",
"status": 1,
"weight": 2,
"parent_id": 2
},
{
"id": 5,
"uuid": "827ca657-2666-4ce0-988d-72ce5118a836",
"name": "غدد و متابولیسم",
"slug": "endocrinology",
"status": 1,
"weight": 3,
"parent_id": 2
},
{
"id": 6,
"uuid": "1a15587d-be31-433b-8ece-aec32a22b344",
"name": "روماتولوژی",
"slug": "rheumatology",
"status": 1,
"weight": 4,
"parent_id": 2
},
{
"id": 7,
"uuid": "092f916c-e156-433a-bf3c-e5016e6ecc74",
"name": "خون و آنکولوژی",
"slug": "hematology-oncology",
"status": 1,
"weight": 5,
"parent_id": 2
},
{
"id": 8,
"uuid": "f47465e1-793c-403a-b9d7-73f0dbd6e6ff",
"name": "ریه",
"slug": "pulmonology",
"status": 1,
"weight": 6,
"parent_id": 2
},
{
"id": 9,
"uuid": "9e76f731-5e16-41f1-98f6-99df6c8e0972",
"name": "عفونی",
"slug": "infectious-disease",
"status": 1,
"weight": 7,
"parent_id": 2
},
{
"id": 10,
"uuid": "a6916e7b-2633-4fa0-9998-6d197712cae6",
"name": "قلب و عروق",
"slug": "cardiology",
"status": 1,
"weight": 30,
"parent_id": null
},
{
"id": 11,
"uuid": "af3f35aa-5355-4fe2-9ad2-9ca4c233d2e9",
"name": "اینترونشنال کاردیولوژی",
"slug": "interventional-cardiology",
"status": 1,
"weight": 1,
"parent_id": 10
},
{
"id": 12,
"uuid": "fa9ccaf1-b150-4cd7-9f5c-751567282e8b",
"name": "الکتروفیزیولوژی قلب",
"slug": "cardiac-electrophysiology",
"status": 1,
"weight": 2,
"parent_id": 10
},
{
"id": 13,
"uuid": "b11b599b-e864-4e5e-8635-90c1fab2c480",
"name": "جراحی عمومی",
"slug": "general-surgery",
"status": 1,
"weight": 40,
"parent_id": null
},
{
"id": 14,
"uuid": "c5dba7a5-336a-4e86-aeea-8c3e841c5046",
"name": "جراحی پلاستیک و زیبایی",
"slug": "plastic-surgery",
"status": 1,
"weight": 1,
"parent_id": 13
},
{
"id": 15,
"uuid": "cc229600-3622-44e9-be8f-d919d57e0b99",
"name": "جراحی عروق",
"slug": "vascular-surgery",
"status": 1,
"weight": 2,
"parent_id": 13
},
{
"id": 16,
"uuid": "724448b9-3863-489e-a08a-120acc22ca98",
"name": "جراحی توراکس",
"slug": "thoracic-surgery",
"status": 1,
"weight": 3,
"parent_id": 13
},
{
"id": 17,
"uuid": "8361c86f-8797-42c7-af68-c5331dfb4db3",
"name": "جراحی کولورکتال",
"slug": "colorectal-surgery",
"status": 1,
"weight": 4,
"parent_id": 13
},
{
"id": 18,
"uuid": "75995975-caa9-4d54-a550-0cdb8f4cd123",
"name": "جراحی پستان",
"slug": "breast-surgery",
"status": 1,
"weight": 5,
"parent_id": 13
},
{
"id": 19,
"uuid": "abae50f0-dd5d-4be9-b93d-7dbf5db44b4a",
"name": "ارتوپدی",
"slug": "orthopedics",
"status": 1,
"weight": 50,
"parent_id": null
},
{
"id": 20,
"uuid": "6243a40a-14c9-4f3d-afd0-115189905eb4",
"name": "جراحی زانو",
"slug": "knee-surgery",
"status": 1,
"weight": 1,
"parent_id": 19
},
{
"id": 21,
"uuid": "acbae32d-91db-4bd6-9d9c-290c2fbbf6e2",
"name": "جراحی شانه و آرنج",
"slug": "shoulder-elbow-surgery",
"status": 1,
"weight": 2,
"parent_id": 19
},
{
"id": 22,
"uuid": "a1d72081-5bbc-4455-b534-c9f5aa363c12",
"name": "جراحی ستون فقرات",
"slug": "spine-surgery",
"status": 1,
"weight": 3,
"parent_id": 19
},
{
"id": 23,
"uuid": "3fc84d3e-7d4e-4924-aae1-08ed92deec6f",
"name": "جراحی دست",
"slug": "hand-surgery",
"status": 1,
"weight": 4,
"parent_id": 19
},
{
"id": 24,
"uuid": "440638f8-07c8-42a9-8d1b-261baf05f080",
"name": "جراحی پا و مچ پا",
"slug": "foot-ankle-surgery",
"status": 1,
"weight": 5,
"parent_id": 19
},
{
"id": 25,
"uuid": "32745d28-1f03-43e9-92f1-7d106df05842",
"name": "زنان و زایمان",
"slug": "obstetrics-gynecology",
"status": 1,
"weight": 60,
"parent_id": null
},
{
"id": 26,
"uuid": "8a6bcd23-178d-43ba-a646-ac0b519cada6",
"name": "نازایی و IVF",
"slug": "infertility-ivf",
"status": 1,
"weight": 1,
"parent_id": 25
},
{
"id": 27,
"uuid": "9a1d6f51-9959-4b6a-8fbb-861e354e1998",
"name": "انکولوژی زنان",
"slug": "gynecologic-oncology",
"status": 1,
"weight": 2,
"parent_id": 25
},
{
"id": 28,
"uuid": "6f2fe9f5-4f2c-4c36-a28c-a44f38e9d2d9",
"name": "پریناتولوژی",
"slug": "perinatology",
"status": 1,
"weight": 3,
"parent_id": 25
},
{
"id": 29,
"uuid": "e2b27ef8-5eb6-4324-856d-2c841db47178",
"name": "کودکان و اطفال",
"slug": "pediatrics",
"status": 1,
"weight": 70,
"parent_id": null
},
{
"id": 30,
"uuid": "5dd804cc-f159-41d2-9fce-cd0fa9da2d52",
"name": "طب نوزادی",
"slug": "neonatology",
"status": 1,
"weight": 1,
"parent_id": 29
},
{
"id": 31,
"uuid": "b9661fbb-77dd-4378-a048-4b38adaed35a",
"name": "قلب کودکان",
"slug": "pediatric-cardiology",
"status": 1,
"weight": 2,
"parent_id": 29
},
{
"id": 32,
"uuid": "4dff6aa0-2d15-4a77-98a3-6d03675378b5",
"name": "گوارش کودکان",
"slug": "pediatric-gastroenterology",
"status": 1,
"weight": 3,
"parent_id": 29
},
{
"id": 33,
"uuid": "ca1ebfbd-6abd-4b17-84b8-9a44d34ca93d",
"name": "عفونی کودکان",
"slug": "pediatric-infectious-disease",
"status": 1,
"weight": 4,
"parent_id": 29
},
{
"id": 34,
"uuid": "b3469e17-f66c-422a-be8e-d2732727a42d",
"name": "غدد کودکان",
"slug": "pediatric-endocrinology",
"status": 1,
"weight": 5,
"parent_id": 29
},
{
"id": 35,
"uuid": "2af734ef-3109-40fc-a720-8fe48c02197c",
"name": "مغز و اعصاب کودکان",
"slug": "pediatric-neurology",
"status": 1,
"weight": 6,
"parent_id": 29
},
{
"id": 36,
"uuid": "ae0d7fc2-d418-4b16-94c9-5b04867f58a6",
"name": "کلیه کودکان",
"slug": "pediatric-nephrology",
"status": 1,
"weight": 7,
"parent_id": 29
},
{
"id": 37,
"uuid": "09b8af69-0c70-466e-bb6a-3f954444b229",
"name": "خون و سرطان کودکان",
"slug": "pediatric-hematology-oncology",
"status": 1,
"weight": 8,
"parent_id": 29
},
{
"id": 38,
"uuid": "f80bbdc9-ee39-492c-b22e-8b6962805ea7",
"name": "مغز و اعصاب (نورولوژی)",
"slug": "neurology",
"status": 1,
"weight": 80,
"parent_id": null
},
{
"id": 39,
"uuid": "0f5d9ba4-e732-4857-858d-233885a62386",
"name": "جراحی مغز و اعصاب",
"slug": "neurosurgery",
"status": 1,
"weight": 90,
"parent_id": null
},
{
"id": 40,
"uuid": "9745fefe-8404-4697-a34e-98784960cab5",
"name": "روانپزشکی",
"slug": "psychiatry",
"status": 1,
"weight": 100,
"parent_id": null
},
{
"id": 41,
"uuid": "46164060-8ee9-442a-9b69-4d92256a347a",
"name": "روانپزشکی کودک و نوجوان",
"slug": "child-adolescent-psychiatry",
"status": 1,
"weight": 1,
"parent_id": 40
},
{
"id": 42,
"uuid": "052621ab-b3bd-4072-9c48-6707ff92eded",
"name": "چشم پزشکی",
"slug": "ophthalmology",
"status": 1,
"weight": 110,
"parent_id": null
},
{
"id": 43,
"uuid": "b9c1dc6d-136f-4629-a708-6e2d964b1ac1",
"name": "قرنیه",
"slug": "cornea",
"status": 1,
"weight": 1,
"parent_id": 42
},
{
"id": 44,
"uuid": "921b2830-6cfe-4a92-a811-051de577b7e7",
"name": "شبکیه (رتین)",
"slug": "retina",
"status": 1,
"weight": 2,
"parent_id": 42
},
{
"id": 45,
"uuid": "c2e00ff9-eb27-48c4-9c03-3f2b5ac8ea44",
"name": "گلوکوم (آب سیاه)",
"slug": "glaucoma",
"status": 1,
"weight": 3,
"parent_id": 42
},
{
"id": 46,
"uuid": "0a0a2942-afc2-4728-8dd9-015b58494d1b",
"name": "انحراف چشم (استرابیسم)",
"slug": "strabismus",
"status": 1,
"weight": 4,
"parent_id": 42
},
{
"id": 47,
"uuid": "be28431b-9421-4574-92f9-a13348aa15b9",
"name": "چشم پزشکی کودکان",
"slug": "pediatric-ophthalmology",
"status": 1,
"weight": 5,
"parent_id": 42
},
{
"id": 48,
"uuid": "cae460c2-81c4-4e5c-bcc1-8a2f0cf9b7a4",
"name": "گوش و حلق و بینی",
"slug": "ent",
"status": 1,
"weight": 120,
"parent_id": null
},
{
"id": 49,
"uuid": "ca92fbbc-1e10-48ae-9230-4f176536a0f7",
"name": "جراحی بینی (رینوپلاستی)",
"slug": "rhinoplasty",
"status": 1,
"weight": 1,
"parent_id": 48
},
{
"id": 50,
"uuid": "c2eb20ce-158f-4e4b-9a57-d5bd0fd22f0c",
"name": "اتولوژی و شنوایی",
"slug": "otology",
"status": 1,
"weight": 2,
"parent_id": 48
},
{
"id": 51,
"uuid": "1ae24990-f48e-4f02-8c69-bcd86076100e",
"name": "پوست و مو",
"slug": "dermatology",
"status": 1,
"weight": 130,
"parent_id": null
},
{
"id": 52,
"uuid": "cf06d647-ebea-471a-8c02-f50cadf54c01",
"name": "اورولوژی",
"slug": "urology",
"status": 1,
"weight": 140,
"parent_id": null
},
{
"id": 53,
"uuid": "750b12d1-a6ad-48fc-a069-a7fe93157d34",
"name": "اندویورولوژی",
"slug": "endourology",
"status": 1,
"weight": 1,
"parent_id": 52
},
{
"id": 54,
"uuid": "4faa9cf1-239b-4776-8a36-0f2a98605781",
"name": "اوروانکولوژی",
"slug": "urologic-oncology",
"status": 1,
"weight": 2,
"parent_id": 52
},
{
"id": 55,
"uuid": "234ce8ec-e831-4654-917d-a91afc76fa0c",
"name": "اورولوژی کودکان",
"slug": "pediatric-urology",
"status": 1,
"weight": 3,
"parent_id": 52
},
{
"id": 56,
"uuid": "3b2fcb1f-f27d-4d25-a5b0-bedf36cc8440",
"name": "رادیولوژی و تصویربرداری",
"slug": "radiology",
"status": 1,
"weight": 150,
"parent_id": null
},
{
"id": 57,
"uuid": "5162b9a2-b644-48e1-b048-7a3ecf5c7c53",
"name": "سونوگرافی",
"slug": "ultrasound",
"status": 1,
"weight": 1,
"parent_id": 56
},
{
"id": 58,
"uuid": "7700a1e6-e28d-43c1-ba23-a73dde339b52",
"name": "ام آر آی (MRI)",
"slug": "mri",
"status": 1,
"weight": 2,
"parent_id": 56
},
{
"id": 59,
"uuid": "9ed62c2e-9793-47cc-b917-f60ff49e50bb",
"name": "سی تی اسکن",
"slug": "ct-scan",
"status": 1,
"weight": 3,
"parent_id": 56
},
{
"id": 60,
"uuid": "17eaf65f-5e49-448a-ab44-bd4c5990c1ce",
"name": "پاتولوژی و آسیب‌شناسی",
"slug": "pathology",
"status": 1,
"weight": 160,
"parent_id": null
},
{
"id": 61,
"uuid": "ba14b33b-8d2a-4827-9860-28f414059ead",
"name": "بیهوشی",
"slug": "anesthesiology",
"status": 1,
"weight": 170,
"parent_id": null
},
{
"id": 62,
"uuid": "da600631-75e6-40ac-8d23-07055eec5b39",
"name": "طب درد",
"slug": "pain-medicine",
"status": 1,
"weight": 180,
"parent_id": null
},
{
"id": 63,
"uuid": "0c0e2925-81b4-4a7b-8295-2b806b47391a",
"name": "طب اورژانس",
"slug": "emergency-medicine",
"status": 1,
"weight": 190,
"parent_id": null
},
{
"id": 64,
"uuid": "c75bd083-64d6-48ff-88cc-bb94872a400d",
"name": "طب فیزیکی و توانبخشی",
"slug": "physical-medicine-rehab",
"status": 1,
"weight": 200,
"parent_id": null
},
{
"id": 65,
"uuid": "c098edd8-2625-45a7-b218-7351c4719015",
"name": "طب کار",
"slug": "occupational-medicine",
"status": 1,
"weight": 210,
"parent_id": null
},
{
"id": 66,
"uuid": "89379b60-04e5-44da-9730-8e259aea2041",
"name": "پزشکی هسته‌ای",
"slug": "nuclear-medicine",
"status": 1,
"weight": 220,
"parent_id": null
},
{
"id": 67,
"uuid": "b73eb94f-16d9-41e9-b2bb-8d60ce3074c5",
"name": "رادیوتراپی و انکولوژی",
"slug": "radiation-oncology",
"status": 1,
"weight": 230,
"parent_id": null
},
{
"id": 68,
"uuid": "cd25dde8-0df7-4449-82d5-bd0d60c44006",
"name": "طب سالمندان",
"slug": "geriatrics",
"status": 1,
"weight": 240,
"parent_id": null
},
{
"id": 69,
"uuid": "7853fe8b-2f55-44e4-803a-dd75e47d965b",
"name": "طب خواب",
"slug": "sleep-medicine",
"status": 1,
"weight": 250,
"parent_id": null
},
{
"id": 70,
"uuid": "99c185b1-63c9-487e-83bf-2c4a579e8774",
"name": "طب ورزشی",
"slug": "sports-medicine",
"status": 1,
"weight": 260,
"parent_id": null
},
{
"id": 71,
"uuid": "89a75512-e621-4c4e-94a6-4506e3e7a7a5",
"name": "ژنتیک پزشکی",
"slug": "medical-genetics",
"status": 1,
"weight": 270,
"parent_id": null
},
{
"id": 72,
"uuid": "e316dee3-bf28-41db-959d-8b491bcf0a35",
"name": "آلرژی و ایمونولوژی",
"slug": "allergy-immunology",
"status": 1,
"weight": 280,
"parent_id": null
},
{
"id": 73,
"uuid": "4714aca3-8618-4b2e-9980-a0287ca34ca0",
"name": "طب سنتی",
"slug": "traditional-medicine",
"status": 1,
"weight": 290,
"parent_id": null
},
{
"id": 74,
"uuid": "1a9f12af-095f-42ac-b6c3-724e01871f70",
"name": "دندانپزشک",
"slug": "dentistry",
"status": 1,
"weight": 300,
"parent_id": null
},
{
"id": 75,
"uuid": "5e95efb3-9db1-4fcc-9e84-f50916589e4b",
"name": "جراحی فک و صورت",
"slug": "oral-maxillofacial-surgery",
"status": 1,
"weight": 1,
"parent_id": 74
},
{
"id": 76,
"uuid": "0802c01f-17a2-4e68-9bfb-3994d4edf1bd",
"name": "ارتودنسی",
"slug": "orthodontics",
"status": 1,
"weight": 2,
"parent_id": 74
},
{
"id": 77,
"uuid": "0a2dc4fa-834f-4a15-a517-c3fbf81c6db7",
"name": "درمان ریشه (اندودانتیکس)",
"slug": "endodontics",
"status": 1,
"weight": 3,
"parent_id": 74
},
{
"id": 78,
"uuid": "9c53ef7d-bd01-41f8-840d-673c7467790d",
"name": "دندانپزشکی کودکان",
"slug": "pediatric-dentistry",
"status": 1,
"weight": 4,
"parent_id": 74
},
{
"id": 79,
"uuid": "ebba72d7-da6a-43ee-8751-cd9e85dbf8ce",
"name": "پروتزهای دندانی",
"slug": "prosthodontics",
"status": 1,
"weight": 5,
"parent_id": 74
},
{
"id": 80,
"uuid": "ae4174bf-546c-42ac-8741-f390ebe8ab13",
"name": "لثه (پریودانتیکس)",
"slug": "periodontics",
"status": 1,
"weight": 6,
"parent_id": 74
},
{
"id": 81,
"uuid": "fdceafab-8861-46dc-9231-0f79cc00d9e8",
"name": "دندانپزشکی ترمیمی و زیبایی",
"slug": "restorative-dentistry",
"status": 1,
"weight": 7,
"parent_id": 74
},
{
"id": 82,
"uuid": "111154ae-731c-407f-b324-5a47f4686cf7",
"name": "ایمپلنت دندان",
"slug": "dental-implant",
"status": 1,
"weight": 8,
"parent_id": 74
},
{
"id": 83,
"uuid": "f241f1fe-baf5-4b0e-a2cc-1600960b3b33",
"name": "تغذیه و رژیم درمانی",
"slug": "nutrition",
"status": 1,
"weight": 310,
"parent_id": null
},
{
"id": 84,
"uuid": "55513c19-c0fa-422c-9718-0b5a98bbc17d",
"name": "روانشناسی و مشاوره",
"slug": "psychology",
"status": 1,
"weight": 320,
"parent_id": null
},
{
"id": 85,
"uuid": "0b54a5e1-9c85-4de1-a90f-d528afd487dc",
"name": "روانشناسی کودک",
"slug": "child-psychology",
"status": 1,
"weight": 1,
"parent_id": 84
},
{
"id": 86,
"uuid": "009ca13f-dbb9-434d-aea1-306d652988e1",
"name": "فیزیوتراپی",
"slug": "physiotherapy",
"status": 1,
"weight": 330,
"parent_id": null
},
{
"id": 87,
"uuid": "0639904d-0730-497d-8680-7f57761adabe",
"name": "کاردرمانی",
"slug": "occupational-therapy",
"status": 1,
"weight": 340,
"parent_id": null
},
{
"id": 88,
"uuid": "b0408896-7919-4b85-b48c-20f2465653b1",
"name": "گفتاردرمانی",
"slug": "speech-therapy",
"status": 1,
"weight": 350,
"parent_id": null
},
{
"id": 89,
"uuid": "d51ad157-eb69-40a6-ba79-0e4aa8a5ce3a",
"name": "شنوایی‌سنجی",
"slug": "audiology",
"status": 1,
"weight": 360,
"parent_id": null
},
{
"id": 90,
"uuid": "63974c3c-232d-4416-b38f-c230eb4ec2b3",
"name": "بینایی‌سنجی",
"slug": "optometry",
"status": 1,
"weight": 370,
"parent_id": null
},
{
"id": 91,
"uuid": "38422839-d512-487e-b010-bf580eab8e9e",
"name": "مامایی",
"slug": "midwifery",
"status": 1,
"weight": 380,
"parent_id": null
},
{
"id": 92,
"uuid": "9bbe83ab-e8d9-44ee-bdc2-94d55dcc3bca",
"name": "علوم آزمایشگاهی",
"slug": "lab-sciences",
"status": 1,
"weight": 390,
"parent_id": null
},
{
"id": 93,
"uuid": "249928fd-5d81-48f1-8ee5-cdec1ea086cd",
"name": "داروسازی",
"slug": "pharmacy",
"status": 1,
"weight": 400,
"parent_id": null
}
]
+26
View File
@@ -111,3 +111,29 @@ A non-empty array of records, or `{ "items": [ … ] }`. Max **5000** rows.
| `ERR_IMPORT_TOO_LARGE` | 422 | More than 5000 rows |
| (field errors) | 422 | One entry per failed row/field; no rows written |
| `ERR_IMPORT_FAILED` | 500 | DB error during replace (transaction rolled back) |
---
## Seeding a from-scratch database
Canonical category data ships in the repo at `data/seed/*.json`
(`provinces.json`, `cities.json`, `specialties.json`, `doctor_services.json`).
After creating an empty schema, populate it with:
```bash
ddev exec php bin/console app:seed-categories
```
The command (`App\Category\Command\SeedCategoriesCommand`) reads those files and
runs the same strict validation + wipe-and-replace as the HTTP import, in
dependency order (provinces → cities, specialties → doctor_services). The shared
logic lives in `App\Category\Service\CategoryImporter`.
Full rebuild:
```bash
ddev exec php bin/console doctrine:database:drop --force
ddev exec php bin/console doctrine:database:create
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
ddev exec php bin/console app:seed-categories
ddev exec php bin/console app:create-admin 09100000001 'Test@1234'
```
+1 -5
View File
@@ -653,13 +653,9 @@
"651": "Community 651",
"652": "Community 652",
"653": "Community 653",
"659": "Community 659",
"660": "Community 660",
"661": "Community 661",
"654": "Community 654",
"662": "Community 662",
"663": "Community 663",
"666": "Community 666",
"667": "Community 667",
"668": "Community 668",
"669": "Community 669",
"670": "Community 670",
+112 -148
View File
@@ -1,16 +1,16 @@
# Graph Report - clinicpro (2026-06-30)
## Corpus Check
- 657 files · ~453,738 words
- 663 files · ~463,560 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 8314 nodes · 11496 edges · 665 communities (540 shown, 125 thin omitted)
- 8328 nodes · 11514 edges · 661 communities (532 shown, 129 thin omitted)
- Extraction: 98% EXTRACTED · 2% INFERRED · 0% AMBIGUOUS · INFERRED: 264 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `80319610`
- Built from commit: `22937dfa`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@@ -655,13 +655,9 @@
- [[_COMMUNITY_Community 651|Community 651]]
- [[_COMMUNITY_Community 652|Community 652]]
- [[_COMMUNITY_Community 653|Community 653]]
- [[_COMMUNITY_Community 659|Community 659]]
- [[_COMMUNITY_Community 660|Community 660]]
- [[_COMMUNITY_Community 661|Community 661]]
- [[_COMMUNITY_Community 654|Community 654]]
- [[_COMMUNITY_Community 662|Community 662]]
- [[_COMMUNITY_Community 663|Community 663]]
- [[_COMMUNITY_Community 666|Community 666]]
- [[_COMMUNITY_Community 667|Community 667]]
- [[_COMMUNITY_Community 668|Community 668]]
- [[_COMMUNITY_Community 669|Community 669]]
- [[_COMMUNITY_Community 672|Community 672]]
@@ -683,45 +679,45 @@
assets/admin/components/ServiceTariffModal.tsx → assets/admin/lib/utils.ts
- `PersianDatePicker()` --calls--> `formatDate()` [EXTRACTED]
assets/admin/components/ui/PersianDatePicker.tsx → assets/admin/lib/utils.ts
- `MyFinancialPage()` --calls--> `formatRial()` [EXTRACTED]
assets/admin/pages/MyFinancialPage.tsx → assets/admin/lib/utils.ts
- `RepresentationSettlementPage()` --calls--> `formatRial()` [EXTRACTED]
assets/admin/pages/RepresentationSettlementPage.tsx → assets/admin/lib/utils.ts
- `SmsPage()` --calls--> `formatDateTime()` [EXTRACTED]
assets/admin/pages/SmsPage.tsx → assets/admin/lib/utils.ts
- `PrivateRoute()` --calls--> `useAuthStore` [EXTRACTED]
assets/admin/App.tsx → assets/admin/stores/authStore.ts
- `PublicRoute()` --calls--> `useAuthStore` [EXTRACTED]
assets/admin/App.tsx → assets/admin/stores/authStore.ts
## Import Cycles
- None detected.
## Communities (665 total, 125 thin omitted)
## Communities (661 total, 129 thin omitted)
### Community 0 - "Community 0"
Cohesion: 0.04
Nodes (46): ALLOWED_ROLES, PrivateRoute(), PublicRoute(), RoleRoute(), queryClient, toastStyle, NewAppointmentModal(), LogoUploadField() (+38 more)
Cohesion: 0.05
Nodes (43): ALLOWED_ROLES, PrivateRoute(), PublicRoute(), RoleRoute(), queryClient, toastStyle, AddForm, addSchema (+35 more)
### Community 1 - "Community 1"
Cohesion: 0.03
Nodes (41): AddressData, AddrForm, addrSchema, AVATAR_COLORS, BookingMeta, CityOpt, DateOverrideData, DEFAULT_BOOKING_META (+33 more)
### Community 2 - "Community 2"
Cohesion: 0.10
Nodes (19): get, BeforeInstallPromptEvent, usePwaInstall(), api, ApiError, getToken(), refreshOnce(), request() (+11 more)
Cohesion: 0.07
Nodes (27): get, BeforeInstallPromptEvent, usePwaInstall(), api, ApiError, getToken(), refreshOnce(), request() (+19 more)
### Community 3 - "Community 3"
Cohesion: 0.05
Nodes (43): PaginatedResponse, formatDate(), STATUS_FILTERS, FILTERS, Breakdown, SOURCE_LABEL, Summary, STATUS_FILTERS (+35 more)
Cohesion: 0.04
Nodes (71): PaginatedResponse, formatDate(), ALL_STATUSES, AppointmentDetailPage(), STATUS_FILTERS, FILTERS, Breakdown, SOURCE_LABEL (+63 more)
### Community 4 - "Community 4"
Cohesion: 0.05
Nodes (11): DoctorServiceController, DoctorService, Doctor, DoctorServiceRepository, Collection, self, User, WeeklySchedule (+3 more)
Cohesion: 0.06
Nodes (8): DoctorService, Doctor, DoctorServiceRepository, Collection, self, User, WeeklySchedule, ManagerRegistry
### Community 5 - "Community 5"
Cohesion: 0.07
Nodes (3): UserProfile, self, User
### Community 6 - "Community 6"
Cohesion: 0.17
Nodes (8): SettlementController, SettlementRepository, Settlement, JsonResponse, Request, User, ManagerRegistry, User
Cohesion: 0.12
Nodes (12): SettlementController, SettlementRepository, WalletTransactionRepository, Settlement, JsonResponse, Request, User, ManagerRegistry (+4 more)
### Community 7 - "Community 7"
Cohesion: 0.07
@@ -729,7 +725,7 @@ Nodes (5): Clinic, Collection, Doctor, self, User
### Community 8 - "Community 8"
Cohesion: 0.08
Nodes (30): useSubscription(), AdminLayout(), avatarBg(), buildSections(), HUES, Props, ROLE_LABELS, Section (+22 more)
Nodes (28): useSubscription(), AdminLayout(), avatarBg(), buildSections(), HUES, Props, ROLE_LABELS, Section (+20 more)
### Community 9 - "Community 9"
Cohesion: 0.04
@@ -740,8 +736,8 @@ Cohesion: 0.04
Nodes (46): Clinic Address Management, Clinic API, DELETE `/api/v1/admin/clinic/{clinicUuid}/doctor/{doctorUuid}`, `DELETE /api/v1/clinic/{clinicUuid}/address/{addressUuid}`, Errors, Errors, Errors, Errors (+38 more)
### Community 11 - "Community 11"
Cohesion: 0.06
Nodes (35): FreeVisitPrice(), Pricing, usePaymentConfig(), ApiResponse, formatRial(), InsurancePricingPage(), FinancialSummary, MonthlyEntry (+27 more)
Cohesion: 0.05
Nodes (33): usePaymentConfig(), ApiResponse, emptyFeatures(), FEATURE_KEYS, FEATURE_LABELS, PeriodForm, periodSchema, PLAN_DISPLAY (+25 more)
### Community 12 - "Community 12"
Cohesion: 0.05
@@ -752,16 +748,16 @@ Cohesion: 0.08
Nodes (28): cn(), formatDateTime(), iranMobileOptionalSchema, iranMobileSchema, isValidIranMobile(), maskMobile(), sanitizeMobileInput(), toDate() (+20 more)
### Community 14 - "Community 14"
Cohesion: 0.10
Nodes (12): AppointmentSettingsController, DateOverride, Holiday, DateOverrideRepository, HolidayRepository, JsonResponse, Request, User (+4 more)
Cohesion: 0.15
Nodes (8): AppointmentSettingsController, Holiday, HolidayRepository, JsonResponse, Request, User, Doctor, ManagerRegistry
### Community 15 - "Community 15"
Cohesion: 0.10
Nodes (13): PaymentController, MockGateway, SepGateway, JsonResponse, Payment, PaymentGatewayInterface, Request, Response (+5 more)
### Community 16 - "Community 16"
Cohesion: 0.09
Nodes (6): PatientSession, Appointment, Collection, PatientRecord, self, SessionService
Cohesion: 0.07
Nodes (7): PatientSession, SmsWallet, Appointment, Collection, PatientRecord, self, SessionService
### Community 17 - "Community 17"
Cohesion: 0.05
@@ -772,20 +768,20 @@ Cohesion: 0.05
Nodes (38): API, API, API, API, API, Route, Route, Route (+30 more)
### Community 19 - "Community 19"
Cohesion: 0.07
Nodes (21): Contract, InsuranceOption, KIND_LABEL, ALL_STATUSES, AppointmentDetailPage(), FormData, schema, AdminUser (+13 more)
Cohesion: 0.09
Nodes (17): Contract, InsuranceOption, KIND_LABEL, FormData, schema, AdminUser, ChangeRoleModal(), getPrimaryRole() (+9 more)
### Community 20 - "Community 20"
Cohesion: 0.15
Nodes (3): AdminApiController, JsonResponse, Request
Cohesion: 0.12
Nodes (4): AdminApiController, InputValidator, JsonResponse, Request
### Community 21 - "Community 21"
Cohesion: 0.05
Nodes (34): formatNumber(), ClinicDetailPage(), AdminCharts, AdminDashboard(), AdminRecent, AdminStats, APPT_CLS, APPT_COLOR (+26 more)
Nodes (42): FreeVisitPrice(), Pricing, formatNumber(), formatRial(), ClinicDetailPage(), AdminCharts, AdminDashboard(), AdminRecent (+34 more)
### Community 22 - "Community 22"
Cohesion: 0.15
Nodes (9): RatingController, Like, LikeRepository, JsonResponse, Request, User, Comment, ManagerRegistry (+1 more)
Cohesion: 0.11
Nodes (14): RatingController, Like, Rate, LikeRepository, RateRepository, JsonResponse, Request, User (+6 more)
### Community 23 - "Community 23"
Cohesion: 0.05
@@ -800,8 +796,8 @@ Cohesion: 0.05
Nodes (39): Appointment API, Error Responses, Errors, Errors, Errors, Errors, Errors, Errors (+31 more)
### Community 26 - "Community 26"
Cohesion: 0.24
Nodes (4): InsuranceController, JsonResponse, Request, User
Cohesion: 0.15
Nodes (7): InsuranceController, EntityInsurancePricing, EntityInsurancePricingRepository, JsonResponse, Request, User, ManagerRegistry
### Community 27 - "Community 27"
Cohesion: 0.05
@@ -860,8 +856,8 @@ Cohesion: 0.09
Nodes (4): User, PasswordAuthenticatedUserInterface, self, UserInterface
### Community 41 - "Community 41"
Cohesion: 0.04
Nodes (41): CityForm, citySchema, ImportError, InsuranceForm, insuranceSchema, ProvinceForm, provinceSchema, ServiceForm (+33 more)
Cohesion: 0.06
Nodes (23): CityForm, citySchema, ImportError, InsuranceForm, insuranceSchema, LogoUploadField(), ProvinceForm, provinceSchema (+15 more)
### Community 42 - "Community 42"
Cohesion: 0.07
@@ -893,7 +889,7 @@ Nodes (4): Payment, Appointment, self, User
### Community 49 - "Community 49"
Cohesion: 0.07
Nodes (20): AppointmentsPage(), BookingSlot, CancelledBadge(), DateNavigator(), EMPTY_ARR, getPersianWeekDay(), navBtnSx, SessionGroup (+12 more)
Nodes (21): AppointmentsPage(), BookingSlot, CancelledBadge(), DateNavigator(), EMPTY_ARR, getPersianWeekDay(), navBtnSx, NewAppointmentModal() (+13 more)
### Community 50 - "Community 50"
Cohesion: 0.07
@@ -908,8 +904,8 @@ Cohesion: 0.07
Nodes (26): الزامات UI, باگ‌فیکس صفحه نوبت‌ها, باگ ۱ — کرش تقویم, باگ ۲ — روز هفته در DateNavigator, باگ ۳ — پیام «slot نیست», باگ ۴ — نوبت جدید: نام اجباری + find-or-create patient, باگ ۵ — patient_mobile نشان می‌دهد موبایل پزشک, باگ ۶ — نوبت‌های رزرو شده در نمایش زمانبندی (+18 more)
### Community 53 - "Community 53"
Cohesion: 0.11
Nodes (13): AppLogRepository, ClaimItemRepository, DoctorInsuranceRepository, PreRegistrationRepository, SmsSettingsRepository, ServiceEntityRepository, SmsSettings, ManagerRegistry (+5 more)
Cohesion: 0.08
Nodes (15): AppLogRepository, CityRepository, ClaimItemRepository, InvoiceItemRepository, PreRegistrationRepository, SiteConfigRepository, ServiceEntityRepository, ManagerRegistry (+7 more)
### Community 54 - "Community 54"
Cohesion: 0.10
@@ -945,24 +941,20 @@ Nodes (22): Billing API — صورتحساب (فاز ۴ سیستم صورتحس
### Community 62 - "Community 62"
Cohesion: 0.08
Nodes (20): Authentication, ClinicPro — API Documentation Index, Error Code Reference, Modules, Standard Response Envelope, Bulk import / export, DELETE `/api/v1/admin/specialty/{id}`, Errors (+12 more)
Nodes (24): Bulk import / export, DELETE `/api/v1/admin/specialty/{id}`, Errors, Errors, Errors, Errors, GET `/api/v1/admin/specialties`, GET `/api/v1/specialties` (+16 more)
### Community 63 - "Community 63"
Cohesion: 0.22
Nodes (5): AuthController, RateLimiterFactory, JsonResponse, Request, User
### Community 64 - "Community 64"
Cohesion: 0.32
Nodes (4): SmsWalletController, JsonResponse, Request, User
Cohesion: 0.20
Nodes (7): SmsWalletController, SmsSettingsRepository, SmsSettings, JsonResponse, Request, User, ManagerRegistry
### Community 65 - "Community 65"
Cohesion: 0.08
Nodes (24): 2. کاربر (User), 4. 🔵 `POST` verify code, 5. 🔵 `POST` send code, 6. 🔵 `POST` register, 7. 🔴 `DELETE` delete user, 8. 🟡 `PATCH` patch, 9. 🟢 `GET` list secretary, Request Body (+16 more)
### Community 66 - "Community 66"
Cohesion: 0.07
Nodes (4): ClinicStaff, SmsSettings, self, self
### Community 67 - "Community 67"
Cohesion: 0.11
Nodes (4): Invoice, Collection, InvoiceItem, self
@@ -1015,10 +1007,6 @@ Nodes (3): SubscriptionPlan, Collection, self
Cohesion: 0.09
Nodes (22): dependencies, @ckeditor/ckeditor5-build-classic, @ckeditor/ckeditor5-react, @fontsource/vazirmatn, @heroicons/react, @hookform/resolvers, jalaali-js, leaflet (+14 more)
### Community 82 - "Community 82"
Cohesion: 0.05
Nodes (14): CategoryImportTest, Connection, RepositoryClassMappingTest, KernelTestCase, LoggerInterface, DbLogger, KavehNegarProvider, RanginehProvider (+6 more)
### Community 83 - "Community 83"
Cohesion: 0.09
Nodes (21): Backend, CSS / UI, Frontend, روند اجرای هر قابلیت, قبل از شروع — تحلیل پرامپت و ساخت Todo, قوانین اجرا (اجباری — هیچ استثنایی ندارد), قوانین خاص این پروژه, مثال اجرا (+13 more)
@@ -1032,8 +1020,8 @@ Cohesion: 0.07
Nodes (29): devDependencies, @babel/core, @babel/preset-env, @babel/preset-react, @babel/preset-typescript, core-js, @hotwired/stimulus, jsdom (+21 more)
### Community 86 - "Community 86"
Cohesion: 0.07
Nodes (13): AppointmentExpiryServiceTest, LowTierFixesTest, SendCodeMobileRateLimitTest, ClaimsListPaginationTest, ServiceItemStaffOwnershipTest, EntityManagerInterface, KernelBrowser, CommentPaginationTest (+5 more)
Cohesion: 0.06
Nodes (15): AppointmentExpiryServiceTest, LowTierFixesTest, SendCodeMobileRateLimitTest, ClaimsListPaginationTest, ServiceItemDeleteCleanupTest, ServiceItemStaffOwnershipTest, EntityManagerInterface, KernelBrowser (+7 more)
### Community 87 - "Community 87"
Cohesion: 0.10
@@ -1068,8 +1056,8 @@ Cohesion: 0.10
Nodes (20): 12. تنظیمات نوبت — تعطیلات, 76. 🔴 `DELETE` delete, 77. 🔵 `POST` post, 78. 🟡 `PATCH` patch, 79. 🔴 `DELETE` delete, 80. 🟢 `GET` get, Request Body, Request Body (+12 more)
### Community 95 - "Community 95"
Cohesion: 0.09
Nodes (7): ClinicSubscription, TenantInsuranceService, CoverageRule, TenantInsurance, Payment, SubscriptionPeriod, SubscriptionPlan
Cohesion: 0.15
Nodes (4): ClinicSubscription, Payment, SubscriptionPeriod, SubscriptionPlan
### Community 96 - "Community 96"
Cohesion: 0.18
@@ -1096,7 +1084,7 @@ Cohesion: 0.10
Nodes (19): Backend — endpoint اشتراک, Frontend — App.tsx, Frontend — authStore, Frontend — Sidebar, اعمال محدودیت‌های اشتراک در پنل ادمین (Frontend Gate), ترتیب اجرا, زمینه, فایل‌های مرتبط (+11 more)
### Community 102 - "Community 102"
Cohesion: 0.10
Cohesion: 0.11
Nodes (19): Bulk import / export, DELETE `/api/v1/admin/tag/{id}`, Errors, Errors, Errors, Errors, GET `/api/v1/admin/tags`, GET `/api/v1/tags` (+11 more)
### Community 103 - "Community 103"
@@ -1121,7 +1109,7 @@ Nodes (6): AppointmentController, Appointment, Doctor, JsonResponse, Request, Us
### Community 108 - "Community 108"
Cohesion: 0.18
Nodes (7): BaseController, CategoryController, SmsMessageController, JsonResponse, JsonResponse, JsonResponse, Request
Nodes (7): BaseController, CategoryController, CategoryImportController, JsonResponse, JsonResponse, Request, JsonResponse
### Community 109 - "Community 109"
Cohesion: 0.29
@@ -1328,8 +1316,8 @@ Cohesion: 0.13
Nodes (13): Architecture, Auth, Backend (PHP/Symfony), Backend — `src/`, Category / Bundle system, Commands, Database, First-time setup (+5 more)
### Community 163 - "Community 163"
Cohesion: 0.11
Nodes (18): autoload, autoload-dev, psr-4, psr-4, conflict, symfony/symfony, description, extra (+10 more)
Cohesion: 0.13
Nodes (14): autoload, autoload-dev, psr-4, psr-4, conflict, symfony/symfony, description, license (+6 more)
### Community 164 - "Community 164"
Cohesion: 0.29
@@ -1399,10 +1387,6 @@ Nodes (3): PatientRecord, Collection, User
Cohesion: 0.23
Nodes (3): WeeklySchedule, Doctor, self
### Community 182 - "Community 182"
Cohesion: 0.13
Nodes (13): emptyFeatures(), FEATURE_KEYS, FEATURE_LABELS, PeriodForm, periodSchema, PLAN_DISPLAY, PlanForm, planSchema (+5 more)
### Community 183 - "Community 183"
Cohesion: 0.14
Nodes (13): `UserDetailPage.tsx` — فقط user query, زمینه, فایل‌های مرتبط, قرارداد پروفایل (`UserProfile.toArray`), مشکل / هدف, نمایش پروفایل کاربر در صفحه‌ی جزئیات کاربرِ پنل ادمین, نکات مهم, وضعیت فعلی (کد واقعی) (+5 more)
@@ -1473,7 +1457,7 @@ Nodes (13): Endpoint ها, PATCH /api/v1/secretary/{uuid}, POST /api/v1/secretar
### Community 201 - "Community 201"
Cohesion: 0.15
Nodes (13): Admin API, Clinic Invitation Management, GET `/api/v1/admin/representations`, GET `/api/v1/admin/secretaries`, GET /api/v1/admin/settings, PATCH /api/v1/admin/settings, Query Parameters, Query Parameters (+5 more)
Nodes (13): Admin API, Clinic Invitation Management, GET `/api/v1/admin/payments`, GET `/api/v1/admin/representations`, GET /api/v1/admin/settings, PATCH /api/v1/admin/settings, Payment Management, Query Parameters (+5 more)
### Community 202 - "Community 202"
Cohesion: 0.15
@@ -1488,16 +1472,16 @@ Cohesion: 0.21
Nodes (6): AuthenticationException, ExceptionSubscriber, SecurityHeadersSubscriber, EventSubscriberInterface, ExceptionEvent, ResponseEvent
### Community 205 - "Community 205"
Cohesion: 0.10
Nodes (14): Command, CancelExpiredAppointmentsCommand, CreateAdminCommand, SeedSmsMessageTemplatesCommand, SmsMessageTemplateRepository, SmsTextResolver, SmsMessageTemplate, InputInterface (+6 more)
Cohesion: 0.13
Nodes (13): Command, CancelExpiredAppointmentsCommand, CreateAdminCommand, SeedCategoriesCommand, SeedSmsMessageTemplatesCommand, InputInterface, OutputInterface, InputInterface (+5 more)
### Community 206 - "Community 206"
Cohesion: 0.35
Nodes (6): DashboardController, Clinic, DoctorSecretary, JsonResponse, Request, User
### Community 207 - "Community 207"
Cohesion: 0.15
Nodes (13): ۲. مدل داده و موجودیت‌ها, ۲.۱ کاربر (User), ۲.۱۰ پرداخت (Payment Types), ۲.۱۰.۱ پرداخت نوبت, ۲.۱۰.۲ پرداخت اشتراک, ۲.۱۱ پروفایل بیمار (Profile), ۲.۱۲ وبلاگ (Blog), ۲.۳ دکتر (Doctor) (+5 more)
Cohesion: 0.07
Nodes (30): ۲. مدل داده و موجودیت‌ها, ۲.۱ کاربر (User), ۲.۱۰ پرداخت (Payment Types), ۲.۱۰.۱ پرداخت نوبت, ۲.۱۰.۲ پرداخت اشتراک, ۲.۱۱ پروفایل بیمار (Profile), ۲.۱۲ وبلاگ (Blog), ۲.۱۳ نظرات، لایک و امتیازدهی (+22 more)
### Community 208 - "Community 208"
Cohesion: 0.23
@@ -1664,8 +1648,8 @@ Cohesion: 0.18
Nodes (11): 1. 🔵 `POST` refresh token, 1. احراز هویت (Authentication), 2. 🟢 `GET` X-CSRF-Token, 3. 🟢 `GET` user info 🆕, Request Body, بخش دوم — مستند کامل API, خطاهای عمومی, فهرست مطالب (+3 more)
### Community 255 - "Community 255"
Cohesion: 0.18
Nodes (11): GET /api/v1/doctors/{id}, GET /api/v1/doctors/{id}/insurances, GET /api/v1/representations/{id}, GET /oauth/userinfo — اطلاعات کاربر (سازگار با دروپال), POST /api/v1/representations/{id}/bank-accounts, POST /oauth/token — تجدید توکن (Refresh), POST /oauth/token — ورود به سیستم, Task-02: احراز هویت (Authentication) (+3 more)
Cohesion: 0.11
Nodes (19): GET /api/v1/appointment-settings/slots — دریافت اسلات‌های خالی, GET /api/v1/categories/cities — شهرها, GET /api/v1/categories/states — استان‌ها, GET /api/v1/doctors/{id}, GET /api/v1/doctors/{id}/insurances, GET /api/v1/representations/{id}, GET /oauth/userinfo — اطلاعات کاربر (سازگار با دروپال), POST /api/v1/appointment-settings/holidays — ثبت تعطیلی (فقط ادمین) (+11 more)
### Community 256 - "Community 256"
Cohesion: 0.29
@@ -1736,8 +1720,8 @@ Cohesion: 0.18
Nodes (10): Endpoint های موجود که تغییر می‌کنند, GET /api/v1/admin/dashboard/charts?from=UNIX&to=UNIX, GET /api/v1/dashboard/clinic, GET /api/v1/dashboard/doctor, تسک ۱۶: داشبورد هوشمند — چارت + فیلتر زمانی, توضیح, زمان تخمینی, فیلتر بازه زمانی (+2 more)
### Community 274 - "Community 274"
Cohesion: 0.22
Nodes (3): CategoryImportController, JsonResponse, Request
Cohesion: 0.19
Nodes (5): Authentication, ClinicPro — API Documentation Index, Error Code Reference, Modules, Standard Response Envelope
### Community 275 - "Community 275"
Cohesion: 0.38
@@ -1748,8 +1732,8 @@ Cohesion: 0.20
Nodes (9): Architecture Audit — ClinicPro Symfony 7 Migration, Architecture Score (بعد از اصلاحات), Architecture Violations, Executive Summary, Final Verdict (بعد از اصلاحات), Missing Requirements (کامل), اصلاحات اعمال‌شده (بعد از Audit), دلیل تصمیم (+1 more)
### Community 277 - "Community 277"
Cohesion: 0.20
Nodes (9): بخش اول — مستند محصول (PRD), فهرست کلی, کلینیک پرو — مستند جامع فنی و API, ۵. فهرست مشکلات شناسایی‌شده و اصلاحات لازم, ۶. پیوست, ۶.۱ دیاگرام وضعیت نوبت, ۶.۲ جریان کمیسیون, ۶.۳ بررسی محدودیت منشی (+1 more)
Cohesion: 0.08
Nodes (23): GET /api/v1/sms/balance — موجودی حساب پیامک, POST /api/v1/sms/queue — افزودن به صف, بخش اول — مستند محصول (PRD), ثبت‌نام دکتر — از طریق نماینده, ثبت‌نام دکتر — از طریق کلینیک, ثبت‌نام دکتر — مستقل, فهرست کلی, کلینیک پرو — مستند جامع فنی و API (+15 more)
### Community 279 - "Community 279"
Cohesion: 0.20
@@ -1853,7 +1837,7 @@ Nodes (16): آماده‌سازی پروژه ClinicPro برای دیپلوی ر
### Community 304 - "Community 304"
Cohesion: 0.04
Nodes (56): 29. 🟢 `GET` clinic list 🆕, 30. 🟢 `GET` get 🆕, 31. 🟡 `PATCH` patch, 33. 🔵 `POST` image_clinic, 34. 🔵 `POST` image logo, 36. 🟢 `GET` get my rate, 37. 🔵 `POST` post, 39. 🟡 `PATCH` Comment confirmation (+48 more)
Nodes (52): 29. 🟢 `GET` clinic list 🆕, 30. 🟢 `GET` get 🆕, 31. 🟡 `PATCH` patch, 32. 🔵 `POST` post, 33. 🔵 `POST` image_clinic, 34. 🔵 `POST` image logo, 36. 🟢 `GET` get my rate, 37. 🔵 `POST` post (+44 more)
### Community 306 - "Community 306"
Cohesion: 0.07
@@ -1903,10 +1887,6 @@ Nodes (3): SubscriptionPlanRepository, ManagerRegistry, SubscriptionPlan
Cohesion: 0.36
Nodes (3): TariffRepository, ManagerRegistry, Tariff
### Community 318 - "Community 318"
Cohesion: 0.15
Nodes (5): EntityInsurancePricing, TenantInsuranceCleanupTest, EntityInsurancePricingRepository, TenantInsuranceCleanupService, ManagerRegistry
### Community 319 - "Community 319"
Cohesion: 0.39
Nodes (3): UserRepository, ManagerRegistry, User
@@ -1979,10 +1959,6 @@ Nodes (15): ClaimsPage.tsx — بدون صفحه‌بندی، فقط `data.data.
Cohesion: 0.39
Nodes (5): JsonContains, FunctionNode, Node, Parser, SqlWalker
### Community 340 - "Community 340"
Cohesion: 0.24
Nodes (5): WalletTransactionRepository, WalletTransactionsPaginationTest, ManagerRegistry, User, WalletTransaction
### Community 341 - "Community 341"
Cohesion: 0.12
Nodes (15): بازنویسی متن استارت‌اپی و رفع نمایش موبایل صفحه فرود (Landing), تست نهایی, راهنمای بازنویسی (نمونه لحن استارت‌اپی — می‌توانی بهترش کنی), زمینه, فایل‌های مرتبط, مشکل / هدف, مشکل ۲٫۱ — سرریز هدر روی گوشی, مشکل ۲٫۲ — منوی موبایل با inline-style شکننده باز می‌شود و بسته نمی‌شود (+7 more)
@@ -2004,20 +1980,20 @@ Cohesion: 0.25
Nodes (8): بک‌اند — تغییرات `Appointment` entity:, بک‌اند — تغییرات `AppointmentController::book()`:, بک‌اند — موجودیت `SiteConfig` (جدید):, بک‌اند — کنترلر تنظیمات ادمین (جدید):, فرانت‌اند — تغییرات `AppointmentsPage.tsx`:, فرانت‌اند — صفحه تنظیمات ادمین (جدید):, مرحله ۹ — سیستم کمیسیون نوبت‌دهی (بک‌اند + فرانت‌اند) — جدید, منطق کسب‌وکار:
### Community 346 - "Community 346"
Cohesion: 0.39
Nodes (3): CityRepository, City, ManagerRegistry
Cohesion: 0.26
Nodes (3): TenantInsuranceService, CoverageRule, TenantInsurance
### Community 347 - "Community 347"
Cohesion: 0.25
Nodes (8): ثبت‌نام دکتر — از طریق نماینده, ثبت‌نام دکتر — از طریق کلینیک, ثبت‌نام دکتر — مستقل, ۱. معرفی محصول, ۱.۱ نقش‌های سیستم, ۱.۲ فلوهای عملیاتی اصلی, ۱.۳ پلن‌های اشتراک, ۱.۴ سیستم پیامک
Cohesion: 0.36
Nodes (3): DoctorServiceController, JsonResponse, Request
### Community 348 - "Community 348"
Cohesion: 0.39
Nodes (3): ProvinceRepository, ManagerRegistry, Province
### Community 349 - "Community 349"
Cohesion: 0.20
Nodes (10): Category Import / Export API, Errors, Errors, GET `/api/v1/admin/categories/{bundle}/export`, Per-row validation rules, POST `/api/v1/admin/categories/{bundle}/import`, Request Body (`application/json`), Response `200` (+2 more)
Cohesion: 0.18
Nodes (11): Category Import / Export API, Errors, Errors, GET `/api/v1/admin/categories/{bundle}/export`, Per-row validation rules, POST `/api/v1/admin/categories/{bundle}/import`, Request Body (`application/json`), Response `200` (+3 more)
### Community 350 - "Community 350"
Cohesion: 0.43
@@ -2028,8 +2004,8 @@ Cohesion: 0.50
Nodes (4): UserActiveContextRepository, ManagerRegistry, User, UserActiveContext
### Community 352 - "Community 352"
Cohesion: 0.38
Nodes (5): Rate, RateRepository, Doctor, ManagerRegistry, User
Cohesion: 0.25
Nodes (4): SmsMessageTemplateRepository, SmsTextResolver, SmsMessageTemplate, ManagerRegistry
### Community 354 - "Community 354"
Cohesion: 0.36
@@ -2087,6 +2063,10 @@ Nodes (7): ادمین, دکتر نمونه کامل — تبریز, دکتران
Cohesion: 0.29
Nodes (7): Dashboard, GET `/api/v1/admin/dashboard/charts`, GET `/api/v1/admin/dashboard/recent`, GET `/api/v1/admin/dashboard/stats`, Response `200`, Response `200`, Response `200`
### Community 368 - "Community 368"
Cohesion: 0.21
Nodes (5): DateOverrideOwnershipTest, DateOverride, DateOverrideRepository, Doctor, ManagerRegistry
### Community 369 - "Community 369"
Cohesion: 0.29
Nodes (7): php-http/discovery, symfony/flex, symfony/runtime, config, allow-plugins, bump-after-update, sort-packages
@@ -2099,10 +2079,6 @@ Nodes (7): Authentication, Authorization, Input Validation, Logging Security, Ra
Cohesion: 0.15
Nodes (6): UniqueConstraintsTest, FinancialBreakdown, FinancialBreakdownRepository, FinancialBreakdownIntegrityTest, ManagerRegistry, Payment
### Community 372 - "Community 372"
Cohesion: 0.25
Nodes (8): ۲.۲ انواع دسته‌بندی (Category Types), ۲.۲.۱ تگ (Tag), ۲.۲.۲ استان (State), ۲.۲.۳ شهر (City), ۲.۲.۴ بیمه پایه (Basic Insurance), ۲.۲.۵ بیمه مکمل (Supplementary Insurance), ۲.۲.۶ تخصص دکتر (Doctor Specialty), ۲.۲.۷ خدمات دکتر (Doctor Services)
### Community 374 - "Community 374"
Cohesion: 0.29
Nodes (7): API, Entity جدید, Frontend, اپیک ۱ — پرسنل (Staff), توضیح, فایل‌های جدید, نیازمندی‌های کارکردی
@@ -2199,14 +2175,26 @@ Nodes (6): ClaimAmountBoundsTest, ClaimsListNPlusOneTest, ClaimItem, Claim, Doct
Cohesion: 0.33
Nodes (4): initiate(), verify(), PaymentInitResult, PaymentVerifyResult
### Community 400 - "Community 400"
Cohesion: 0.23
Nodes (5): AbstractMigration, Schema, Version20260609131553, Schema, Version20260609134741
### Community 405 - "Community 405"
Cohesion: 0.47
Nodes (3): CommissionService, Payment, Representation
### Community 407 - "Community 407"
Cohesion: 0.36
Nodes (3): SmsService, SendSmsMessage, SmsProviderInterface
### Community 418 - "Community 418"
Cohesion: 0.17
Nodes (11): دیپلوی ClinicPro روی Coolify (Docker Compose), رفع اشکال, مراحل دیپلوی, معماری دیپلوی, نکات عملیاتی, چند دامنه فرانت‌اند (مهم), ۱. ساخت Resource در Coolify, ۲. اختصاص دامنه (+3 more)
### Community 440 - "Community 440"
Cohesion: 0.43
Nodes (3): SmsMessageController, JsonResponse, Request
### Community 452 - "Community 452"
Cohesion: 0.10
Nodes (12): ClinicAddress, ClinicDoctorItem, ClinicInvitation, EditForm, editSchema, HUES_LIST, INV_STATUS_MAP, IRAN_CENTER (+4 more)
@@ -2300,8 +2288,8 @@ Cohesion: 0.40
Nodes (5): Errors, Notes, POST `/api/v1/notification-mobile/request-otp`, Request Body, Response `200`
### Community 482 - "Community 482"
Cohesion: 0.33
Nodes (6): GET /api/v1/sms/balance — موجودی حساب پیامک, POST /api/v1/sms/queue — افزودن به صف, ۴. سیستم حساب و صف پیامک, ۴.۱ حساب پیامک (SMS Account), ۴.۲ صف پیامک (SMS Queue), ۴.۳ API پیامک
Cohesion: 0.38
Nodes (3): RepositoryClassMappingTest, KernelTestCase, DbLoggerTest
### Community 483 - "Community 483"
Cohesion: 0.40
@@ -2323,10 +2311,6 @@ Nodes (5): Errors, GET `/api/v1/representation/dashboard/summary`, GET `/api/v1/
Cohesion: 0.40
Nodes (5): Admin Endpoints, GET /api/v1/admin/sms/settings/review, GET /api/v1/admin/sms/wallet-report, POST /api/v1/admin/sms/settings/{id}/approve, POST /api/v1/admin/sms/settings/{id}/reject
### Community 492 - "Community 492"
Cohesion: 0.40
Nodes (5): 32. 🔵 `POST` post, Request Body, مثال Request, هدرهای اضافی, پاسخ‌ها
### Community 493 - "Community 493"
Cohesion: 0.20
Nodes (9): Backend Audit Backlog — ClinicPro, ☐ CRITICAL, ✅ DONE (committed on backend-audit), ☐ EPICS (design debt — cross-repo, defer; do NOT quick-fix), ☐ HIGH, ☐ LOW, ☐ MEDIUM, Progress (this audit session) (+1 more)
@@ -2611,10 +2595,6 @@ Nodes (15): دیپلوی ClinicPro (Symfony) روی لیارا با Docker, زم
Cohesion: 0.50
Nodes (4): API های جدید که باید ساخته شوند, API های موجود (استفاده کن، تغییر نده), تغییر روی API موجود, وضعیت فعلی API (مهم — قبل از پیاده‌سازی بخوان)
### Community 583 - "Community 583"
Cohesion: 0.23
Nodes (5): AbstractMigration, Schema, Version20260609134112, Schema, Version20260628165710
### Community 584 - "Community 584"
Cohesion: 0.50
Nodes (3): Entity: UserProfile, ساختار فایل‌ها, معماری — تسک ۰۳: ماژول پروفایل کاربر
@@ -2672,8 +2652,8 @@ Cohesion: 0.67
Nodes (3): POST `/api/v1/admin/sms/template/{uuid}/approve`, Request Body (`application/json`), Response `200`
### Community 602 - "Community 602"
Cohesion: 0.40
Nodes (5): Errors, PATCH `/api/v1/admin/specialty/{id}`, Path Parameters, Request Body (all optional), Response `200`
Cohesion: 0.48
Nodes (3): DoctorInsuranceRepository, DoctorInsurance, ManagerRegistry
### Community 603 - "Community 603"
Cohesion: 0.67
@@ -2695,17 +2675,13 @@ Nodes (5): DELETE `/api/v1/billing/tenant-insurances/{uuid}`, GET `/api/v1/billi
Cohesion: 0.39
Nodes (3): ClinicStaffRepository, ClinicStaff, ManagerRegistry
### Community 608 - "Community 608"
Cohesion: 0.50
Nodes (4): Errors, POST `/api/v1/admin/specialty`, Request Body (`application/json`), Response `201`
### Community 612 - "Community 612"
Cohesion: 0.67
Nodes (3): بک‌اند, فرانت‌اند, وضعیت فعلی کد (مهم — قبل از تغییر بخوان)
### Community 618 - "Community 618"
Cohesion: 0.16
Nodes (5): ServiceItemDeleteCleanupTest, ServiceCoverageNPlusOneTest, TenantServiceCoverageRepository, ManagerRegistry, TenantServiceCoverage
Cohesion: 0.12
Nodes (6): ServiceCoverageNPlusOneTest, TenantInsuranceCleanupTest, TenantServiceCoverageRepository, TenantInsuranceCleanupService, ManagerRegistry, TenantServiceCoverage
### Community 631 - "Community 631"
Cohesion: 0.50
@@ -2747,17 +2723,17 @@ Nodes (3): POST `/api/v1/admin/insurance/{id}/upload-logo`, Request, Response `2
Cohesion: 0.40
Nodes (5): 35. 🟡 `PATCH` patch, Request Body, مثال Request, هدرهای اضافی, پاسخ‌ها
### Community 647 - "Community 647"
Cohesion: 0.43
Nodes (3): InvoiceItemRepository, InvoiceItem, ManagerRegistry
### Community 648 - "Community 648"
Cohesion: 0.40
Nodes (5): ۲.۸ تنظیمات نوبت (Appointment Settings), ۲.۸.۱ برنامه هفتگی (Weekly Schedule), ۲.۸.۲ تعطیلات (Holidays), ۲.۸.۳ لغو تعطیل (Date Override), ۲.۸.۴ الگوریتم محاسبه اسلات‌های خالی
Nodes (5): 41. 🟡 `PATCH` patch, Request Body, مثال Request, هدرهای اضافی, پاسخ‌ها
### Community 649 - "Community 649"
Cohesion: 0.50
Nodes (4): GET `/api/v1/admin/secretaries`, Query Parameters, Response `200`, Secretary Management
### Community 650 - "Community 650"
Cohesion: 0.40
Nodes (3): Props, StatTone, TONE
Cohesion: 0.50
Nodes (4): extra, symfony, allow-contrib, require
### Community 651 - "Community 651"
Cohesion: 0.53
@@ -2771,49 +2747,37 @@ Nodes (4): GET `/api/v1/admin/pre-registrations`, POST `/api/v1/admin/pre-regist
Cohesion: 0.50
Nodes (4): 38. 🟢 `GET` Unapproved comments, هدرهای اضافی, پارامترهای Query, پاسخ‌ها
### Community 659 - "Community 659"
### Community 654 - "Community 654"
Cohesion: 0.50
Nodes (4): GET /api/v1/appointment-settings/slots — دریافت اسلات‌های خالی, POST /api/v1/appointment-settings/holidays — ثبت تعطیلی (فقط ادمین), POST /api/v1/appointment-settings/overrides — ثبت Override توسط دکتر, Task-09: API تنظیمات نوبت
### Community 660 - "Community 660"
Cohesion: 0.50
Nodes (4): GET /api/v1/categories/cities — شهرها, GET /api/v1/categories/states — استان‌ها, Task-08: API دسته‌بندی‌ها, سایر Endpoint های دسته‌بندی — الزامی
### Community 661 - "Community 661"
Cohesion: 0.50
Nodes (4): ۲.۱۳ نظرات، لایک و امتیازدهی, ۲.۱۳.۱ نظر (Comment), ۲.۱۳.۲ لایک (Like), ۲.۱۳.۳ امتیاز (Rate)
Nodes (4): 44. 🟢 `GET` list comment, هدرهای اضافی, پارامترهای Query, پاسخ‌ها
### Community 662 - "Community 662"
Cohesion: 0.50
Nodes (4): Application Logs, GET `/api/v1/admin/logs`, Query Parameters, Response `200`
### Community 667 - "Community 667"
Cohesion: 0.50
Nodes (4): GET `/api/v1/admin/payments`, Payment Management, Query Parameters, Response `200`
### Community 672 - "Community 672"
Cohesion: 0.50
Nodes (4): GET `/api/v1/admin/settlements`, Query Parameters, Response `200`, Settlement Management
## Knowledge Gaps
- **3563 isolated node(s):** `ALLOWED_ROLES`, `Pricing`, `TenantInsurance`, `CoverageRow`, `Draft` (+3558 more)
- **3564 isolated node(s):** `ALLOWED_ROLES`, `Pricing`, `TenantInsurance`, `CoverageRow`, `Draft` (+3559 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **125 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **129 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `BaseController` connect `Community 108` to `Community 4`, `Community 6`, `Community 138`, `Community 139`, `Community 14`, `Community 15`, `Community 274`, `Community 275`, `Community 20`, `Community 22`, `Community 26`, `Community 164`, `Community 295`, `Community 300`, `Community 301`, `Community 175`, `Community 176`, `Community 177`, `Community 58`, `Community 59`, `Community 63`, `Community 64`, `Community 70`, `Community 71`, `Community 75`, `Community 206`, `Community 230`, `Community 103`, `Community 104`, `Community 107`, `Community 109`, `Community 121`, `Community 122`, `Community 252`?**
_High betweenness centrality (0.037) - this node is a cross-community bridge._
- **Why does `Version20260614181657` connect `Community 431` to `Community 583`?**
_High betweenness centrality (0.017) - this node is a cross-community bridge._
- **Why does `Version20260625150304` connect `Community 457` to `Community 583`?**
- **Why does `BaseController` connect `Community 108` to `Community 6`, `Community 138`, `Community 139`, `Community 14`, `Community 15`, `Community 275`, `Community 20`, `Community 22`, `Community 26`, `Community 164`, `Community 295`, `Community 300`, `Community 301`, `Community 175`, `Community 176`, `Community 177`, `Community 440`, `Community 58`, `Community 59`, `Community 63`, `Community 64`, `Community 70`, `Community 71`, `Community 75`, `Community 206`, `Community 347`, `Community 230`, `Community 103`, `Community 104`, `Community 107`, `Community 109`, `Community 121`, `Community 122`, `Community 252`?**
_High betweenness centrality (0.028) - this node is a cross-community bridge._
- **Why does `ApiTestCase` connect `Community 86` to `Community 609`, `Community 541`, `Community 484`, `Community 574`, `Community 618`, `Community 397`, `Community 495`, `Community 368`, `Community 497`, `Community 594`, `Community 562`, `Community 371`, `Community 565`, `Community 535`, `Community 636`, `Community 573`, `Community 318`, `Community 575`?**
_High betweenness centrality (0.019) - this node is a cross-community bridge._
- **Why does `Version20260614181657` connect `Community 431` to `Community 400`?**
_High betweenness centrality (0.017) - this node is a cross-community bridge._
- **What connects `ALLOWED_ROLES`, `Pricing`, `TenantInsurance` to the rest of the system?**
_3563 weakly-connected nodes found - possible documentation gaps or missing edges._
_3564 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.04471153846153846 - nodes in this community are weakly interconnected._
_Cohesion score 0.047107014848950336 - nodes in this community are weakly interconnected._
- **Should `Community 1` be split into smaller, more focused modules?**
_Cohesion score 0.028985507246376812 - nodes in this community are weakly interconnected._
- **Should `Community 2` be split into smaller, more focused modules?**
_Cohesion score 0.10037878787878787 - nodes in this community are weakly interconnected._
_Cohesion score 0.07317073170731707 - nodes in this community are weakly interconnected._
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (non-object root)"}
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (non-object root)"}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (non-object root)"}
@@ -0,0 +1 @@
{"nodes": [], "edges": [], "skipped": "data json (non-object root)"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+935 -609
View File
File diff suppressed because it is too large Load Diff
+36 -6
View File
@@ -2300,8 +2300,8 @@
"semantic_hash": ""
},
"config/services.yaml": {
"mtime": 1782749831.6264865,
"ast_hash": "e2fbe3625e44dc10f140933838278ac1",
"mtime": 1782843927.2499301,
"ast_hash": "6ddd0cb49e2be3c0d29348e843166ed1",
"semantic_hash": ""
},
"docs/Architecture_Audit.md": {
@@ -3415,8 +3415,8 @@
"semantic_hash": ""
},
"src/Category/Controller/CategoryImportController.php": {
"mtime": 1782839057.0287304,
"ast_hash": "f4d8651689e73ca956bc2a9570bba695",
"mtime": 1782843840.4128816,
"ast_hash": "c7d911f98ab9a98e3f8394370ebc2021",
"semantic_hash": ""
},
"tests/Category/CategoryImportTest.php": {
@@ -3425,8 +3425,38 @@
"semantic_hash": ""
},
"docs/api/category-import.md": {
"mtime": 1782839220.8153825,
"ast_hash": "da0c24b873a9f46c97cd8760ac4eeba5",
"mtime": 1782844030.4935105,
"ast_hash": "d7eaf7bd2002ca82b3a95e16b0eb34dd",
"semantic_hash": ""
},
"data/seed/cities.json": {
"mtime": 1782843868.7730389,
"ast_hash": "2baadcc7287ed58538b074906b531efc",
"semantic_hash": ""
},
"data/seed/doctor_services.json": {
"mtime": 1782843868.7779899,
"ast_hash": "4001a6181d3279c2f850e20748a33566",
"semantic_hash": ""
},
"data/seed/provinces.json": {
"mtime": 1782843868.7706103,
"ast_hash": "24b4c89c8deb44101cedaa3006b74751",
"semantic_hash": ""
},
"data/seed/specialties.json": {
"mtime": 1782843868.7755058,
"ast_hash": "37dc79f859896696d1f7dd74afecfabe",
"semantic_hash": ""
},
"src/Category/Command/SeedCategoriesCommand.php": {
"mtime": 1782843891.8818347,
"ast_hash": "2aa038b0454d52de87cc30f83d8c9da0",
"semantic_hash": ""
},
"src/Category/Service/CategoryImporter.php": {
"mtime": 1782843813.8434396,
"ast_hash": "47a348a1a7cf97da5de81caad5f026d9",
"semantic_hash": ""
}
}
@@ -0,0 +1,69 @@
<?php
namespace App\Category\Command;
use App\Category\Service\CategoryImporter;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Seeds the category tables from the JSON files in data/seed/.
*
* The data ships inside this repo (data/seed/) so a from-scratch database can be
* populated with the canonical, valid categories without any external source.
* Bundles are seeded in dependency order (provinces cities, specialties
* doctor_services) and each is a strict wipe+replace via {@see CategoryImporter}.
*
* ddev exec php bin/console app:seed-categories
*/
#[AsCommand(name: 'app:seed-categories', description: 'Seed category tables (provinces, cities, specialties, doctor services) from data/seed/*.json')]
class SeedCategoriesCommand extends Command
{
/** seed order matters: referenced tables first */
private const ORDER = ['provinces', 'cities', 'specialties', 'doctor_services'];
public function __construct(
private readonly CategoryImporter $importer,
private readonly string $projectDir,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$seedDir = $this->projectDir . '/data/seed';
foreach (self::ORDER as $bundle) {
$file = $seedDir . '/' . $bundle . '.json';
if (!is_file($file)) {
$io->error("فایل seed یافت نشد: {$file}");
return Command::FAILURE;
}
$payload = json_decode((string) file_get_contents($file), true);
if (!is_array($payload) || $payload === [] || array_keys($payload) !== range(0, count($payload) - 1)) {
$io->error("{$bundle}: فایل باید یک آرایه‌ی غیرخالی باشد");
return Command::FAILURE;
}
$normalized = [];
$errors = $this->importer->validate($bundle, $payload, $normalized);
if ($errors !== []) {
$io->error("{$bundle}: اعتبارسنجی ناموفق — " . count($errors) . ' خطا');
foreach (array_slice($errors, 0, 10) as $e) {
$io->writeln("{$e['message']}");
}
return Command::FAILURE;
}
$this->importer->replace($bundle, $normalized);
$io->success(sprintf('%s: %d رکورد seed شد', $bundle, count($normalized)));
}
return Command::SUCCESS;
}
}
@@ -2,83 +2,54 @@
namespace App\Category\Controller;
use App\Category\Service\CategoryImporter;
use App\Shared\Controller\BaseController;
use Doctrine\DBAL\Connection;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Uid\Uuid;
use OpenApi\Attributes as OA;
/**
* Bulk JSON import for the admin "دسته‌بندی‌ها" page (all six tabs).
* Bulk JSON export/import for the admin "دسته‌بندی‌ها" page (all six tabs).
*
* Consumes the exact JSON produced by each tab's "خروجی JSON" export
* (an array of objects shaped like the entity's toArray()).
* Thin HTTP layer over {@see CategoryImporter}, which holds the strict
* validation + wipe-and-replace logic shared with the `app:seed-categories`
* console command.
*
* Behaviour:
* - Strict two-phase validation: the WHOLE file is validated first; if any
* row is invalid, nothing is written and every error is returned (HTTP 422).
* - Wipe + full replace: on success the target table is emptied and rebuilt
* from the file, preserving the raw `id` of each row so relations
* (parent_id / province_id / specialty_id) stay intact.
* - The whole replace runs inside one transaction with FK checks disabled.
* - Wipe + full replace, preserving raw `id`s so relations stay intact, inside
* one transaction with FK checks disabled.
*/
#[OA\Tag(name: 'Categories')]
#[IsGranted('ROLE_ADMIN')]
class CategoryImportController extends BaseController
{
/** bundle => table name */
private const TABLES = [
'provinces' => 'provinces',
'cities' => 'cities',
'specialties' => 'specialties',
'doctor_services' => 'doctor_services',
'insurances' => 'insurances',
'tags' => 'tags',
];
public function __construct(
private readonly Connection $db,
private readonly CategoryImporter $importer,
) {}
/**
* Complete export of a bundle EVERY row, no pagination cap.
*
* The page's old "خروجی JSON" button pointed at the admin list endpoint,
* which silently caps at 100 rows. Feeding that truncated file back into the
* wipe+replace import deletes the tail. This endpoint returns the full table
* so the export/import round-trip is lossless.
* Complete export of a bundle EVERY row, no pagination cap. The page's old
* "خروجی JSON" button pointed at the admin list endpoint (caps at 100 rows);
* feeding that truncated file into the wipe+replace import below deletes the
* tail. This endpoint returns the full table so the round-trip is lossless.
*/
#[Route('/api/v1/admin/categories/{bundle}/export', methods: ['GET'])]
public function export(string $bundle): JsonResponse
{
$table = self::TABLES[$bundle] ?? null;
if ($table === null) {
if (!$this->importer->isValidBundle($bundle)) {
return $this->error('ERR_BUNDLE_UNKNOWN', 'نوع دسته‌بندی نامعتبر است', 404, 'bundle');
}
$rows = $this->db->fetchAllAssociative('SELECT * FROM ' . $table . ' ORDER BY id ASC');
// normalize numeric columns so the JSON matches the entity shape
foreach ($rows as &$r) {
foreach (['id', 'status', 'weight', 'parent_id', 'specialty_id', 'province_id', 'representation_id'] as $intCol) {
if (array_key_exists($intCol, $r) && $r[$intCol] !== null) {
$r[$intCol] = (int) $r[$intCol];
}
}
}
unset($r);
return $this->success($rows);
return $this->success($this->importer->exportAll($bundle));
}
#[Route('/api/v1/admin/categories/{bundle}/import', methods: ['POST'])]
public function import(string $bundle, Request $request): JsonResponse
{
$table = self::TABLES[$bundle] ?? null;
if ($table === null) {
if (!$this->importer->isValidBundle($bundle)) {
return $this->error('ERR_BUNDLE_UNKNOWN', 'نوع دسته‌بندی نامعتبر است', 404, 'bundle');
}
@@ -94,233 +65,18 @@ class CategoryImportController extends BaseController
return $this->error('ERR_IMPORT_TOO_LARGE', 'حداکثر ۵۰۰۰ رکورد در هر import مجاز است', 422, 'file');
}
// ── Phase 1: strict validation ───────────────────────────────────────
$errors = [];
$rows = []; // normalized, ready-to-insert column maps
$seenId = []; // id => row index (uniqueness within file)
$seenSlug = []; // slug => row index
foreach ($payload as $i => $raw) {
$rowNo = $i + 1;
$add = function (string $field, string $message) use (&$errors, $rowNo): void {
$errors[] = ['row' => $rowNo, 'field' => $field, 'message' => "ردیف {$rowNo}: {$message}"];
};
if (!is_array($raw) || array_keys($raw) === range(0, count($raw) - 1)) {
$add('-', 'ساختار رکورد نامعتبر است');
continue;
}
// id — required, positive int, unique in file (relations rely on it)
$id = $raw['id'] ?? null;
if (!self::isPositiveInt($id)) {
$add('id', 'شناسه (id) باید عدد صحیح مثبت باشد');
} elseif (isset($seenId[(int) $id])) {
$add('id', "شناسه {$id} تکراری است (ردیف {$seenId[(int) $id]})");
} else {
$seenId[(int) $id] = $rowNo;
}
// name — required non-empty string ≤255
$name = $raw['name'] ?? null;
if (!is_string($name) || trim($name) === '') {
$add('name', 'نام الزامی است');
} elseif (mb_strlen($name) > 255) {
$add('name', 'نام نباید بیش از ۲۵۵ نویسه باشد');
}
// status — 0 or 1
$status = self::normInt($raw['status'] ?? 1);
if ($status === null || !in_array($status, [0, 1], true)) {
$add('status', 'وضعیت باید ۰ یا ۱ باشد');
}
$row = [
'id' => self::isPositiveInt($id) ? (int) $id : null,
'uuid' => (is_string($raw['uuid'] ?? null) && $raw['uuid'] !== '') ? mb_substr($raw['uuid'], 0, 36) : Uuid::v4()->toRfc4122(),
'name' => is_string($name) ? trim($name) : '',
'status' => $status ?? 1,
];
// weight — int ≥0 (only bundles whose table has a weight column)
if (in_array($bundle, ['provinces', 'cities', 'specialties', 'doctor_services'], true)) {
$weight = self::normInt($raw['weight'] ?? 0);
if ($weight === null || $weight < 0) {
$add('weight', 'ترتیب نمایش باید عدد صحیح نامنفی باشد');
}
$row['weight'] = $weight ?? 0;
}
// slug — required for bundles that have it, unique in file
if (in_array($bundle, ['specialties', 'doctor_services', 'tags'], true)) {
$slug = $raw['slug'] ?? null;
if (!is_string($slug) || trim($slug) === '') {
$add('slug', 'slug الزامی است');
} elseif (mb_strlen($slug) > 255) {
$add('slug', 'slug نباید بیش از ۲۵۵ نویسه باشد');
} else {
$slug = trim($slug);
if (isset($seenSlug[$slug])) {
$add('slug', "slug «{$slug}» تکراری است (ردیف {$seenSlug[$slug]})");
} else {
$seenSlug[$slug] = $rowNo;
}
$row['slug'] = $slug;
}
}
// bundle-specific columns
switch ($bundle) {
case 'specialties':
$row['parent_id'] = self::nullableId($raw['parent_id'] ?? null, $add, 'parent_id');
break;
case 'doctor_services':
$row['specialty_id'] = self::nullableId($raw['specialty_id'] ?? null, $add, 'specialty_id');
break;
case 'insurances':
$type = $raw['type'] ?? null;
if (!in_array($type, ['basic', 'supplementary'], true)) {
$add('type', 'نوع بیمه باید basic یا supplementary باشد');
}
$row['type'] = is_string($type) ? $type : 'basic';
$row['logo_url'] = self::optStr($raw['logo_url'] ?? null);
break;
case 'cities':
$row['province_id'] = self::nullableId($raw['province_id'] ?? null, $add, 'province_id');
$row['representation_id'] = self::nullableId($raw['representation_id'] ?? null, $add, 'representation_id');
$row['contact_phone'] = self::optStr($raw['contact_phone'] ?? null);
$row['email'] = self::optStr($raw['email'] ?? null);
$row['description'] = self::optStr($raw['description'] ?? null);
$row['slogan'] = self::optStr($raw['slogan'] ?? null);
$row['domain'] = self::optStr($raw['domain'] ?? null);
$row['keywords'] = self::optStr($raw['keywords'] ?? null);
$row['footer_description'] = self::optStr($raw['footer_description'] ?? null);
$row['logo_url'] = self::optStr($raw['logo_url'] ?? null);
$sm = $raw['social_media'] ?? null;
$row['social_media'] = ($sm === null || $sm === '') ? null : (is_string($sm) ? $sm : json_encode($sm, JSON_UNESCAPED_UNICODE));
break;
}
$rows[] = $row;
}
// referential integrity (only meaningful once ids are collected)
$this->validateReferences($bundle, $rows, $seenId, $errors);
$normalized = [];
$errors = $this->importer->validate($bundle, $payload, $normalized);
if ($errors !== []) {
return new JsonResponse(['success' => false, 'data' => null, 'errors' => $errors], 422);
}
// ── Phase 2: wipe + replace inside a transaction ─────────────────────
$this->db->beginTransaction();
try {
$this->db->executeStatement('SET FOREIGN_KEY_CHECKS = 0');
$this->db->executeStatement('DELETE FROM ' . $table);
foreach ($rows as $row) {
$this->db->insert($table, $row);
}
$this->db->executeStatement('SET FOREIGN_KEY_CHECKS = 1');
$this->db->commit();
$this->importer->replace($bundle, $normalized);
} catch (\Throwable $e) {
$this->db->rollBack();
try { $this->db->executeStatement('SET FOREIGN_KEY_CHECKS = 1'); } catch (\Throwable) {}
return $this->error('ERR_IMPORT_FAILED', 'خطا در ذخیره‌سازی: ' . $e->getMessage(), 500);
}
return $this->success(['imported' => count($rows)]);
}
/**
* Cross-row / cross-table reference checks.
* - specialties.parent_id must point to an id present in the same file.
* - cities.province_id / cities.representation_id / doctor_services.specialty_id
* must exist in their (un-wiped) reference tables.
*/
private function validateReferences(string $bundle, array $rows, array $seenId, array &$errors): void
{
$err = function (int $rowNo, string $field, string $message) use (&$errors): void {
$errors[] = ['row' => $rowNo, 'field' => $field, 'message' => "ردیف {$rowNo}: {$message}"];
};
if ($bundle === 'specialties') {
foreach ($rows as $idx => $r) {
$p = $r['parent_id'] ?? null;
if ($p !== null && !isset($seenId[$p])) {
$err($idx + 1, 'parent_id', "والد با شناسه {$p} در همین فایل وجود ندارد");
}
}
return;
}
if ($bundle === 'doctor_services') {
$valid = $this->existingIds('specialties');
foreach ($rows as $idx => $r) {
$s = $r['specialty_id'] ?? null;
if ($s !== null && !isset($valid[$s])) {
$err($idx + 1, 'specialty_id', "تخصص با شناسه {$s} در سیستم وجود ندارد");
}
}
return;
}
if ($bundle === 'cities') {
$validProv = $this->existingIds('provinces');
$validRep = $this->existingIds('representations');
foreach ($rows as $idx => $r) {
$pv = $r['province_id'] ?? null;
if ($pv !== null && !isset($validProv[$pv])) {
$err($idx + 1, 'province_id', "استان با شناسه {$pv} وجود ندارد");
}
$rp = $r['representation_id'] ?? null;
if ($rp !== null && !isset($validRep[$rp])) {
$err($idx + 1, 'representation_id', "نماینده با شناسه {$rp} وجود ندارد");
}
}
}
}
/** @return array<int,true> set of existing ids in a table */
private function existingIds(string $table): array
{
$ids = $this->db->fetchFirstColumn('SELECT id FROM ' . $table);
$set = [];
foreach ($ids as $id) {
$set[(int) $id] = true;
}
return $set;
}
private static function isPositiveInt(mixed $v): bool
{
return (is_int($v) || (is_string($v) && ctype_digit($v))) && (int) $v > 0;
}
private static function normInt(mixed $v): ?int
{
if (is_int($v)) return $v;
if (is_string($v) && preg_match('/^-?\d+$/', $v)) return (int) $v;
return null;
}
/** Validate an optional FK-style id: null stays null, otherwise must be positive int. */
private static function nullableId(mixed $v, callable $add, string $field): ?int
{
if ($v === null || $v === '' || $v === 0 || $v === '0') return null;
if (!self::isPositiveInt($v)) {
$add($field, "{$field} باید عدد صحیح مثبت یا خالی باشد");
return null;
}
return (int) $v;
}
private static function optStr(mixed $v): ?string
{
if ($v === null) return null;
if (!is_string($v)) return null;
$v = trim($v);
return $v === '' ? null : $v;
return $this->success(['imported' => count($normalized)]);
}
}
+269
View File
@@ -0,0 +1,269 @@
<?php
namespace App\Category\Service;
use Doctrine\DBAL\Connection;
use Symfony\Component\Uid\Uuid;
/**
* Shared bulk export/import logic for the six category bundles
* (provinces, cities, specialties, doctor_services, insurances, tags).
*
* Used by both the admin HTTP endpoint (CategoryImportController) and the
* `app:seed-categories` console command, so validation + the wipe+replace
* behaviour live in exactly one place.
*/
class CategoryImporter
{
/** bundle => table name */
public const TABLES = [
'provinces' => 'provinces',
'cities' => 'cities',
'specialties' => 'specialties',
'doctor_services' => 'doctor_services',
'insurances' => 'insurances',
'tags' => 'tags',
];
public function __construct(
private readonly Connection $db,
) {}
public function isValidBundle(string $bundle): bool
{
return isset(self::TABLES[$bundle]);
}
/** Complete export of a bundle — every row, normalized numeric columns. */
public function exportAll(string $bundle): array
{
$table = self::TABLES[$bundle];
$rows = $this->db->fetchAllAssociative('SELECT * FROM ' . $table . ' ORDER BY id ASC');
foreach ($rows as &$r) {
foreach (['id', 'status', 'weight', 'parent_id', 'specialty_id', 'province_id', 'representation_id'] as $intCol) {
if (array_key_exists($intCol, $r) && $r[$intCol] !== null) {
$r[$intCol] = (int) $r[$intCol];
}
}
}
unset($r);
return $rows;
}
/**
* Strict, two-phase validation. Returns a list of
* ['row' => int, 'field' => string, 'message' => string]; empty == valid.
* On success $normalized is filled with ready-to-insert column maps.
*/
public function validate(string $bundle, array $payload, array &$normalized): array
{
$errors = [];
$normalized = [];
$seenId = [];
$seenSlug = [];
foreach ($payload as $i => $raw) {
$rowNo = $i + 1;
$add = function (string $field, string $message) use (&$errors, $rowNo): void {
$errors[] = ['row' => $rowNo, 'field' => $field, 'message' => "ردیف {$rowNo}: {$message}"];
};
if (!is_array($raw) || array_keys($raw) === range(0, count($raw) - 1)) {
$add('-', 'ساختار رکورد نامعتبر است');
continue;
}
$id = $raw['id'] ?? null;
if (!self::isPositiveInt($id)) {
$add('id', 'شناسه (id) باید عدد صحیح مثبت باشد');
} elseif (isset($seenId[(int) $id])) {
$add('id', "شناسه {$id} تکراری است (ردیف {$seenId[(int) $id]})");
} else {
$seenId[(int) $id] = $rowNo;
}
$name = $raw['name'] ?? null;
if (!is_string($name) || trim($name) === '') {
$add('name', 'نام الزامی است');
} elseif (mb_strlen($name) > 255) {
$add('name', 'نام نباید بیش از ۲۵۵ نویسه باشد');
}
$status = self::normInt($raw['status'] ?? 1);
if ($status === null || !in_array($status, [0, 1], true)) {
$add('status', 'وضعیت باید ۰ یا ۱ باشد');
}
$row = [
'id' => self::isPositiveInt($id) ? (int) $id : null,
'uuid' => (is_string($raw['uuid'] ?? null) && $raw['uuid'] !== '') ? mb_substr($raw['uuid'], 0, 36) : Uuid::v4()->toRfc4122(),
'name' => is_string($name) ? trim($name) : '',
'status' => $status ?? 1,
];
if (in_array($bundle, ['provinces', 'cities', 'specialties', 'doctor_services'], true)) {
$weight = self::normInt($raw['weight'] ?? 0);
if ($weight === null || $weight < 0) {
$add('weight', 'ترتیب نمایش باید عدد صحیح نامنفی باشد');
}
$row['weight'] = $weight ?? 0;
}
if (in_array($bundle, ['specialties', 'doctor_services', 'tags'], true)) {
$slug = $raw['slug'] ?? null;
if (!is_string($slug) || trim($slug) === '') {
$add('slug', 'slug الزامی است');
} elseif (mb_strlen($slug) > 255) {
$add('slug', 'slug نباید بیش از ۲۵۵ نویسه باشد');
} else {
$slug = trim($slug);
if (isset($seenSlug[$slug])) {
$add('slug', "slug «{$slug}» تکراری است (ردیف {$seenSlug[$slug]})");
} else {
$seenSlug[$slug] = $rowNo;
}
$row['slug'] = $slug;
}
}
switch ($bundle) {
case 'specialties':
$row['parent_id'] = self::nullableId($raw['parent_id'] ?? null, $add, 'parent_id');
break;
case 'doctor_services':
$row['specialty_id'] = self::nullableId($raw['specialty_id'] ?? null, $add, 'specialty_id');
break;
case 'insurances':
$type = $raw['type'] ?? null;
if (!in_array($type, ['basic', 'supplementary'], true)) {
$add('type', 'نوع بیمه باید basic یا supplementary باشد');
}
$row['type'] = is_string($type) ? $type : 'basic';
$row['logo_url'] = self::optStr($raw['logo_url'] ?? null);
break;
case 'cities':
$row['province_id'] = self::nullableId($raw['province_id'] ?? null, $add, 'province_id');
$row['representation_id'] = self::nullableId($raw['representation_id'] ?? null, $add, 'representation_id');
$row['contact_phone'] = self::optStr($raw['contact_phone'] ?? null);
$row['email'] = self::optStr($raw['email'] ?? null);
$row['description'] = self::optStr($raw['description'] ?? null);
$row['slogan'] = self::optStr($raw['slogan'] ?? null);
$row['domain'] = self::optStr($raw['domain'] ?? null);
$row['keywords'] = self::optStr($raw['keywords'] ?? null);
$row['footer_description'] = self::optStr($raw['footer_description'] ?? null);
$row['logo_url'] = self::optStr($raw['logo_url'] ?? null);
$sm = $raw['social_media'] ?? null;
$row['social_media'] = ($sm === null || $sm === '') ? null : (is_string($sm) ? $sm : json_encode($sm, JSON_UNESCAPED_UNICODE));
break;
}
$normalized[] = $row;
}
$this->validateReferences($bundle, $normalized, $seenId, $errors);
return $errors;
}
/** Wipe the bundle's table and insert the normalized rows in one transaction. */
public function replace(string $bundle, array $normalized): void
{
$table = self::TABLES[$bundle];
$this->db->beginTransaction();
try {
$this->db->executeStatement('SET FOREIGN_KEY_CHECKS = 0');
$this->db->executeStatement('DELETE FROM ' . $table);
foreach ($normalized as $row) {
$this->db->insert($table, $row);
}
$this->db->executeStatement('SET FOREIGN_KEY_CHECKS = 1');
$this->db->commit();
} catch (\Throwable $e) {
$this->db->rollBack();
try { $this->db->executeStatement('SET FOREIGN_KEY_CHECKS = 1'); } catch (\Throwable) {}
throw $e;
}
}
private function validateReferences(string $bundle, array $rows, array $seenId, array &$errors): void
{
$err = function (int $rowNo, string $field, string $message) use (&$errors): void {
$errors[] = ['row' => $rowNo, 'field' => $field, 'message' => "ردیف {$rowNo}: {$message}"];
};
if ($bundle === 'specialties') {
foreach ($rows as $idx => $r) {
$p = $r['parent_id'] ?? null;
if ($p !== null && !isset($seenId[$p])) {
$err($idx + 1, 'parent_id', "والد با شناسه {$p} در همین فایل وجود ندارد");
}
}
return;
}
if ($bundle === 'doctor_services') {
$valid = $this->existingIds('specialties');
foreach ($rows as $idx => $r) {
$s = $r['specialty_id'] ?? null;
if ($s !== null && !isset($valid[$s])) {
$err($idx + 1, 'specialty_id', "تخصص با شناسه {$s} در سیستم وجود ندارد");
}
}
return;
}
if ($bundle === 'cities') {
$validProv = $this->existingIds('provinces');
$validRep = $this->existingIds('representations');
foreach ($rows as $idx => $r) {
$pv = $r['province_id'] ?? null;
if ($pv !== null && !isset($validProv[$pv])) {
$err($idx + 1, 'province_id', "استان با شناسه {$pv} وجود ندارد");
}
$rp = $r['representation_id'] ?? null;
if ($rp !== null && !isset($validRep[$rp])) {
$err($idx + 1, 'representation_id', "نماینده با شناسه {$rp} وجود ندارد");
}
}
}
}
/** @return array<int,true> */
private function existingIds(string $table): array
{
$set = [];
foreach ($this->db->fetchFirstColumn('SELECT id FROM ' . $table) as $id) {
$set[(int) $id] = true;
}
return $set;
}
private static function isPositiveInt(mixed $v): bool
{
return (is_int($v) || (is_string($v) && ctype_digit($v))) && (int) $v > 0;
}
private static function normInt(mixed $v): ?int
{
if (is_int($v)) return $v;
if (is_string($v) && preg_match('/^-?\d+$/', $v)) return (int) $v;
return null;
}
private static function nullableId(mixed $v, callable $add, string $field): ?int
{
if ($v === null || $v === '' || $v === 0 || $v === '0') return null;
if (!self::isPositiveInt($v)) {
$add($field, "{$field} باید عدد صحیح مثبت یا خالی باشد");
return null;
}
return (int) $v;
}
private static function optStr(mixed $v): ?string
{
if (!is_string($v)) return null;
$v = trim($v);
return $v === '' ? null : $v;
}
}