گشت‌آفرین (GashtAfarin) یک پلتفرم پیشرفته و یکپارچه در حوزه صنعت توریسم و گردشگری است که با هدف پاسخگویی به تمامی نیازهای مسافران و مدیران آژانس‌های مسافرتی طراحی شده است. توسعه سیستم‌های رزرواسیون با ابعاد گسترده همواره یکی از پیچیده‌ترین پروژه‌های مهندسی نرم‌افزار به شمار می‌آید؛ زیرا نیازمند مدیریت صدها جریان کاری هم‌زمان نظیر رزرو پرواز، هتل، تور، سفرهای دریایی و خودروی اجاره‌ای، همراه با تضمین عدم تداخل ظرفیت‌ها و محاسبات مالی بی‌نقص است.

💡 چشم‌انداز محصول و ارزش‌آفرینی

در سامانه‌های سنتی گردشگری، خرید هر خدمت از یک بستر مجزا صورت می‌گیرد؛ کاربر هتل را در یک سایت، پرواز را در سایتی دیگر و تور را از سامانه‌ای متفرقه رزرو می‌کند. گشت‌آفرین با تجمیع هوشمندانه این خدمات در یک سبد خرید سراسری (Unified Cart) و پشتیبانی کامل از چینش راست‌به‌چپ (RTL)، تقویم جلالی و سیستم دوزبانه رسمی، تجربه خریدی مدرن، روان و بدون دغدغه را رقم زده است.


بخش اول: قابلیت‌ها و امکانات سامانه

پیش از ورود به عمق معماری و تصمیمات فنی، در این بخش مروری کاربرمحور بر آنچه گشت‌آفرین واقعاً برای مسافر، کاربر ثبت‌نام‌کرده و مدیر آژانس فراهم می‌کند، خواهیم داشت.

۱.۱ موتور جستجو و پنج سرویس اصلی سفر

صفحه اصلی با یک فرم جستجوی تب‌دار شروع می‌شود که مقاصد محبوب، پیشنهادهای برگزیده و ۵ دیدگاه برتر مسافران (Top Featured Reviews) را به‌صورت داینامیک نمایش می‌دهد. هر یک از پنج سرویس زیر صفحه فهرست، فیلترهای اختصاصی و صفحه جزئیات مستقل دارد:

✈️ پرواز /Flights

فیلتر ایرلاین، قیمت، تاریخ و کلاس پروازی؛ جزئیات هواپیما، قوانین بار و کنسلی.

🏨 هتل /Hotels

فیلتر استان/شهر، ستاره و امکانات؛ اتاق‌های در دسترس، گالری، ساعت ورود/خروج.

🗺️ تور /Tours

فیلتر مدت و مقصد؛ برنامه روزانه سفر (Itinerary)، لیدر تور و سقف ظرفیت گروه.

🚗 اجاره خودرو /Cars

فیلتر نوع دنده، ظرفیت و سوخت؛ محاسبه قیمت بر اساس تعداد روز اجاره.

🚢 کروز /Cruises

مشخصات کشتی تفریحی، نوع کابین و برنامه توقف در بنادر.

۱.۲ فرآیند رزرو، سبد خرید و پرداخت

ویجت رزرو هوشمند با تفکیک سنی

تعیین تعداد مسافران به تفکیک بزرگسال، کودک (۲۵٪ تخفیف) و نوزاد (۹۰٪ تخفیف) و جنسیت، همراه با محاسبه لحظه‌ای و داینامیک مبلغ کل بر اساس ضرایب سنی و مدت اقامت/اجاره.

سبد خرید یکپارچه و چندمنظوره /Cart

امکان افزودن هم‌زمان چندین خدمت ناهمگون (مثلاً یک پرواز، یک هتل و یک تور) در یک سبد مشترک، با پیش‌فاکتور تفکیک‌شده به قیمت پایه، مالیات و تخفیف و قابلیت حذف تک‌تک آیتم‌ها.

تسویه‌حساب و ثبت مسافران /Checkout

فرم دریافت اطلاعات تک‌تک مسافران با اعتبارسنجی الگوریتمی کدملی ایرانی (باقی‌مانده بر ۱۱)، و کلید هوشمند «من یکی از مسافران هستم» برای پرکردن خودکار مسافر اول از حساب کاربری.

فاکتور و صدور بلیت /Invoice/{id}

فاکتور رسمی با کد پیگیری اختصاصی، وضعیت «در انتظار پرداخت»، جدول کامل مسافران با شماره بلیت یکتا و صندلی تخصیص‌یافته، و مهلت پرداخت ۲۴ ساعته.

۱.۳ تعامل کاربر: نظرات، علاقه‌مندی‌ها و حساب کاربری

  • نظرات و امتیازدهی: ثبت دیدگاه و امتیاز ۵ ستاره برای هر خدمت، واکنش لایک/دیس‌لایک، و نمایش دیدگاه‌های برگزیده در صفحه اول پس از تأیید ادمین.
  • علاقه‌مندی‌ها: بوک‌مارک سریع تور، هتل و پرواز با آیکون قلب در تمام صفحات.
  • احراز هویت: ورود، ثبت‌نام و بازیابی رمز عبور بر پایه OpenIddict و ASP.NET Core Identity.

۱.۴ داشبورد اختصاصی مسافر /Dashboard

هر کاربر پس از ورود به یک پنل شخصی دسترسی دارد که شامل نمای کلی آمار رزروها، تاریخچه کامل فاکتورها با امکان مشاهده/ابطال، مدیریت علاقه‌مندی‌ها، پیگیری وضعیت نظرات ارسالی، ویرایش پروفایل و کدملی، رهگیری تراکنش‌های مالی و بخش پشتیبانی می‌شود.

۱.۵ پنل مدیریت و آژانس /Admin

پنل مدیریت با تم مترونیک، کنترل کامل زیرساخت داده و عملیات آژانس را در اختیار می‌گذارد:

  • اطلاعات جغرافیایی: مدیریت آبشاری کشور → استان → شهر.
  • پرواز و ناوگان هوایی: ایرلاین‌ها، هواپیماها و ظرفیت صندلی، تعریف و زمان‌بندی پروازها.
  • هتل‌ها: ثبت هتل و رتبه‌بندی، امکانات رفاهی، قوانین کنسلی/کودکان/حیوانات خانگی.
  • تور و کروز: پکیج تور و لیدرها، سفرهای دریایی، کلاس‌های سفر و پرواز.
  • خودروهای اجاره‌ای: مشخصات فنی، قیمت روزانه و ودیعه.
  • فاکتورها و سفارشات: جستجوی پیشرفته، تغییر وضعیت و لغو سفارش.
  • نظارت بر دیدگاه‌ها: تأیید/رد نظرات و کنترل نمایش دیدگاه برگزیده.
  • دسترسی‌ها (RBAC): مدیریت کاربران، نقش‌ها و ماتریس مجوزهای ABP.

۱.۶ چندزبانگی، RTL و تقویم جلالی

  • چندزبانگی واقعی: پشتیبانی کامل فارسی/انگلیسی با منابع ترجمه مستقل در لایه Domain.Shared و سوئیچ زبان جدا برای سایت و پنل ادمین.
  • RTL کامل: استایل اختصاصی راست‌چین و تایپوگرافی هماهنگ با فونت‌های IRANSans و Vazir.
  • تاریخ‌گر جلالی هوشمند: نمایش و دریافت تاریخ به شمسی در فرانت، و تبدیل خودکار به UTC میلادی هنگام ارسال به سرور تا دیتابیس همیشه زمان یکپارچه ذخیره کند.

۱.۷ وضعیت فعلی پروژه — شفاف و بدون اغراق

بخشی از صداقت مهندسی، مشخص کردن مرز میان چیزی است که کار می‌کند و چیزی که هنوز در مسیر توسعه است:

✅ تکمیل و آماده استفاده
  • معماری کامل DDD و مایگریشن‌های پایدار
  • ثبت/ویرایش/فیلتر تمام سرویس‌های سفر
  • سبد خرید، چک‌اوت و صدور فاکتور
  • نظرات، امتیازدهی و علاقه‌مندی‌ها
  • پنل ادمین کامل با RBAC
  • داشبورد مسافر و بومی‌سازی دوزبانه
🚧 در حال توسعه
  • اتصال درگاه پرداخت بانکی واقعی
  • اتصال زنده به وب‌سرویس‌های تأمین‌کننده (GDS/Amadeus)
  • سیستم اعلان پیامکی (SMS Gateway)
  • کش توزیع‌شده Redis برای مقیاس بالا
  • نمایش لحظه‌ای وضعیت صندلی با SignalR

بخش دوم: عمق فنی و معماری

برای علاقه‌مندان به مهندسی نرم‌افزار و بازبینی رزومه فنی، در ادامه به تصمیمات معماری، ساختار دامنه و چالش‌های حل‌شده در گشت‌آفرین می‌پردازیم.

۲.۱ چرا معماری مبتنی بر دامنه (DDD) و فریم‌ورک ABP؟

از روز نخست از رویکرد Domain-Driven Design (DDD) و متدولوژی Onion Architecture بر بستر ABP Framework استفاده شد. این انتخاب تضمین می‌کند که:

  • ایزولاسیون کامل هسته منطق (Pure Domain): لایه دامنه کوچک‌ترین وابستگی به ابزارهای وب یا جزئیات پایگاه داده ندارد.
  • حفظ جامعیت داده‌ها (Data Consistency): تغییرات از طریق ریشه‌های کلنی (Aggregate Roots) هدایت می‌شوند.
  • ردگیری و ممیزی کامل رویدادها: با بهره‌گیری از FullAuditedAggregateRoot، هر تراکنش با هویت کاربر و تایم‌استمپ ثبت می‌شود.

۲.۲ ساختار Solution و لایه‌بندی پروژه

  • GashtAfarin.Domain — هسته خالص منطق تجاری، موجودیت‌ها و سرویس‌های دامنه.
  • GashtAfarin.Domain.Shared — Enumها، اعتبارسنج کدملی و فایل‌های چندزبانگی.
  • GashtAfarin.Application و Application.Contracts — سرویس‌های اپلیکیشن، DTOها و مجوزها.
  • GashtAfarin.EntityFrameworkCore — دسترسی داده با EF Core 10 و مپینگ‌های Fluent API.
  • GashtAfarin.DbMigrator — کنسول مستقل برای مایگریشن و Seed Data.
  • GashtAfarin.HttpApi / .Client — کنترلرهای REST خودکار و کلاینت مصرف‌کننده.
  • GashtAfarin.Web — Razor Pages شامل سایت عمومی، ناحیه ادمین و داشبورد مسافر.

۲.۳ کالبدشکافی موجودیت‌ها و خوشه‌های لایه Domain

AirLine & Travel سفرهای هوایی و نقشه صندلی

کلاس‌های Trip، AirLine و AirPlane در کنار موجودیت‌های پیشرفته TripSeatMap و TripSeat امکان مدل‌سازی دقیق چینش صندلی و وضعیت رزرو تک‌تک صندلی‌ها را فراهم می‌آورند.

Hotel صنعت هتلداری و مدیریت اتاق‌ها

مدل‌های Hotel و HotelRoom امکانات رفاهی، ساعت ورود/خروج، سقف نفرات و شمارنده ظرفیت لحظه‌ای اتاق‌ها (AvailableCount) را کنترل می‌کنند.

Tour & Cruise تورهای گروهی و سفرهای دریایی

کلاس‌های TourTrip و CruiseTrip برنامه دقیق سفر (Itinerary)، لیدرهای تور، کابین‌های کشتی و سقف نفرات گروه (MaxGroupSize) را مدیریت می‌نمایند.

RentalCar & Locations اجاره خودرو و تقسیمات کشوری

پوشش ناوگان خودرو با RentalCar و تقسیمات جغرافیایی سلسله‌مراتبی Country → State → City برای فیلترینگ و موقعیت‌یابی هوشمند.

Commerce & Identity سایر ریشه‌های کلنی و موجودیت‌های کلیدی

در کنار موجودیت‌های بالا، اگرگیت‌های Cart/CartItem، Invoice/InvoiceItem/InvoicePassenger، Comment/CommentReaction، Favorite و موجودیت‌های Policy، Amenity و TripClass چرخه کامل سبد خرید، فاکتور، تعامل کاربر و سیاست‌های خدماتی را پوشش می‌دهند.

۲.۴ سرویس‌های دامنه (Domain Services): هوش و منطق بیزینس

BookingManager

متد ValidateCapacityAsync ظرفیت پرواز، کشتی، هتل، تور یا خودرو را به‌شکل اتمیک پیش از رزرو بررسی و از Overbooking جلوگیری می‌کند.

AgeGroupPricingCalculator

محاسبه تعرفه رده‌های سنی (بزرگسال ۱.۰، کودک ۲۵٪ و نوزاد ۹۰٪ تخفیف) به همراه فاکتور شب‌های اقامت یا روزهای اجاره.

NationalCodeValidator

اعتبارسنجی الگوریتمی کدملی ۱۰ رقمی مطابق استاندارد رسمی، پیش از تکمیل رزرو مسافر.

۲.۵ کنترل هم‌زمانی و پیشگیری از Overbooking

  • صحت‌سنجی دو مرحله‌ای: بار اول در CartAppService.AddToCartAsync هنگام افزودن به سبد، و بار دوم درست پیش از درج فاکتور در InvoiceAppService.CreateInvoiceFromCartAsync — تا عارضه رقابتی TOCTOU (Time-Of-Check to Time-Of-Use) خنثی شود.
  • قفل صندلی: پرچم IsBooked از رزرو تکراری صندلی در یک کوئری اتمیک جلوگیری می‌کند.
  • انقضای خودکار فاکتور: مهلت پرداخت ۲۴ ساعته (ExpiryDate)؛ با انقضا، ظرفیت معلق آزاد می‌شود.

۲.۶ چرخه حیات سبد خرید، صدور فاکتور و فرآیند مالی

  1. سبد خرید یکپارچه (Cart & CartItem): کاربر می‌تواند یک پرواز رفت‌وبرگشت، اتاق هتل و یک تور شهری را هم‌زمان در سبد خود قرار دهد.
  2. صدور فاکتور رسمی (Invoice, InvoiceItem, InvoicePassenger): هنگام ثبت سفارش، فاکتور معتبر با شماره یکتا تولید شده، مالیات، عوارض و تخفیف‌ها محاسبه و مهلت پرداخت ۲۴ ساعته اعمال می‌شود.

۲.۷ تصمیمات معماری غیرپیش‌فرض

  • وراثت موجودیت‌های سفر: موجودیت انتزاعی Trip فیلدهای مشترک را متمرکز می‌کند تا AirTrip، TourTrip و CruiseTrip از آن ارث‌بری کنند — افزودن سرویس‌های آینده (قطار، اتوبوس) را بدون دست‌کاری در سبد و فاکتور ممکن می‌سازد.
  • سبد خرید ناهمگن: با الگوی ReservationTypeEnum، کاربر می‌تواند چند خدمت متفاوت را در یک سبد و یک فاکتور سفارش دهد — برخلاف اغلب سامانه‌های داخلی که تنها یک نوع خدمت را در هر فرآیند می‌پذیرند.
  • تفکیک مسافران در سطح فاکتور: ثبت InvoicePassenger با شماره بلیت یکتا (TicketNumber) زمینه صدور کارت پرواز یا واچر اختصاصی هر فرد را فراهم می‌کند.
  • تاریخ‌گر دومنظوره: نمایش شمسی در فرانت، ذخیره UTC در دیتابیس — بدون نیاز به تبدیل پیچیده در سمت کلاینت.

۲.۸ چالش‌های فنی حل‌شده

رزرو هم‌زمان یک صندلی یا اتاق توسط چند کاربر

مشکل: اگر دو مسافر هم‌زمان پرداخت را بزنند، ممکن است یک صندلی به دو نفر فروخته شود.
راه‌حل: اعتبارسنجی دو‌فازه در BookingManager و انطباق لحظه‌ای ظرفیت پیش از ذخیره نهایی فاکتور.

فرمول‌های پیچیده قیمت‌گذاری سنی

مشکل: هزینه متفاوت نوزاد/کودک/بزرگسال ترکیب‌شده با روزهای اقامت، کدنویسی اسپاگتی در فرانت و بک‌اند ایجاد می‌کرد.
راه‌حل: ماشین محاسبه‌گر متمرکز AgeGroupPricingCalculator در لایه دامنه برای یکپارچگی ارقام در همه‌جای سیستم.

ورود کدهای ملی نامعتبر در رزرو پرواز

مشکل: کدملی نادرست می‌تواند در فرودگاه منجر به جریمه یا ابطال بلیت شود.
راه‌حل: ماژول NationalCodeValidator که پیش از ادامه رزرو، صحت الگوریتمی کدملی را در سرویس اپلیکیشن تأیید می‌کند.

۲.۹ پشته فناوری کامل

C# 13 / .NET 10 ABP Framework 10.5 Onion Architecture / DDD OpenIddict ASP.NET Core Identity SQL Server EF Core 10 Razor Pages Trizen Template Metronic Admin Bootstrap 5 RTL jQuery / Select2 / DataTables / SweetAlert2 Serilog AspNetCore HealthChecks

۲.۱۰ تست‌پذیری و کیفیت کد

سرویس‌های اپلیکیشن حیاتی (CartAppService، InvoiceAppService، CommentAppService، FavoriteAppService) دارای تست‌های واحد و یکپارچه هستند و لایه دیتابیس با SQLite In-Memory تست می‌شود. فرآیند بیلد و دیپلوی در حال حاضر محلی/Docker است و پایپ‌لاین CI/CD مستقلی (مانند GitHub Actions) هنوز تعریف نشده.

۲.۱۱ روش توسعه: مهندسی به کمک ابزارهای هوش مصنوعی

گشت‌آفرین با یک گردش‌کار توسعه‌ی مبتنی بر هوش مصنوعی و در محیط Antigravity IDE ساخته و مدیریت شده است؛ رویکردی که در آن نویسنده به‌عنوان معمار و تصمیم‌گیرنده اصلی، طراحی دامنه، معماری لایه‌ها و بازبینی نهایی کد را برعهده داشته و از چند مدل هوش مصنوعی برای تسریع اجرا استفاده کرده است:

  • برنامه‌ریزی و معماری: استفاده از مدل Claude Opus برای تدوین پلن‌های پیاده‌سازی گام‌به‌گام (به سبک TDD) پیش از هرگونه کدنویسی.
  • ارکستریشن و اجرا: هماهنگی وظایف با Gemini Pro و اجرای بخش‌های تکراری‌تر کد با Gemini Flash، تحت نظارت و تأیید نهایی توسعه‌دهنده.
  • یکپارچگی ابزارها: استفاده از ابزارهای کمکی برای ناوبری و درک ساختار کدبیس توسط عامل‌های هوش مصنوعی، به‌همراه قوانین حاکمیتی مدون در پروژه برای هدایت رفتار آن‌ها.

این روش، تصمیمات معماری، مدل‌سازی دامنه و کیفیت نهایی کد را همچنان کاملاً در اختیار توسعه‌دهنده نگه می‌دارد؛ اما توانایی بهره‌گیری مؤثر از ابزارهای مدرن هوش مصنوعی برای افزایش سرعت و کیفیت تحویل را نیز به‌عنوان یکی از مهارت‌های فنی این پروژه نشان می‌دهد.

۲.۱۲ ساختار پروژه و نمونه کد لایه‌ها

از آن‌جا که کد گشت‌آفرین متن‌باز نیست، در این بخش برای شفافیت بیشتر، ساختار درختی اصلی سالوشن و یک نمونه کد کوتاه از هر لایه — همگی حول یکی از مهم‌ترین موجودیت‌های سیستم یعنی AirTrip (پرواز) — ارائه شده است.

GashtAfarin/
├── src/
│   ├── GashtAfarin.Domain/                      # لایه هسته تجاری و منطق دامنه (DDD)
│   │   ├── Travel/                             # موجودیت‌های انواع سفر و وراثت TPT
│   │   │   ├── Trip.cs                         # موجودیت ریشه و پایه (شامل Trip و AirTrip)
│   │   │   ├── CruiseTrip.cs                   # سفرهای دریایی
│   │   │   ├── TourTrip.cs                     # تورهای گردشگری
│   │   │   ├── TripSeatMap.cs                  # نقشه و چیدمان صندلی‌ها
│   │   │   └── ... و ۴ فایل دیگر
│   │   ├── Booking/                            # سرویس‌های دامنه و محاسبات قیمت
│   │   │   ├── BookingManager.cs               # اعتبارسنجی ظرفیت، صندلی‌ها و اتاق‌ها
│   │   │   └── AgeGroupPricingCalculator.cs    # محاسبات تخفیف رده‌های سنی
│   │   ├── Cart/                               # موجودیت‌های سبد خرید (Cart, CartItem)
│   │   ├── Invoices/                           # اسناد مالی، فاکتور و اطلاعات مسافران
│   │   ├── Hotel/                              # موجودیت‌های هتل، اتاق‌ها و قوانین کنسلی
│   │   ├── AirlLine/                           # شرکت‌های هواپیمایی و ناوگان هوایی
│   │   ├── RentalCar/                          # خودروهای اجاره‌ای و امکانات
│   │   ├── Locations/                          # موقعیت‌های جغرافیایی (Country, State, City)
│   │   ├── Comments/ & Favorites/              # نظرات سلسله‌مراتبی و علاقه‌مندی‌ها
│   │   └── GashtAfarinConsts.cs                # ثوابت سراسری سیستم
│   │
│   ├── GashtAfarin.Domain.Shared/               # قراردادها و تعاریف مشترک کلاینت و سرور
│   │   ├── Enums/                              # کلیه شمارنده‌ها (FlightType, ReservationType, ...)
│   │   ├── Localization/GashtAfarin/           # منابع چندزبانه سیستم (fa.json, en.json)
│   │   ├── ValueObjects/                       # اشیای مقداری (ContactInfo, SocialLinks)
│   │   └── GashtAfarinDomainErrorCodes.cs      # کدهای خطای دامنه
│   │
│   ├── GashtAfarin.Application.Contracts/      # واسط‌ها (Interfaces) و DTOهای لایه کاربرد
│   │   ├── Travel/AirTrip/
│   │   │   ├── AirTripDtos.cs                  # کلاس‌های Create/Update DTO با Data Annotations
│   │   │   ├── AirTripListDto.cs
│   │   │   └── IAirTripAppService.cs
│   │   ├── Cart/                               # DTOها و اینترفیس مدیریت سبد خرید
│   │   ├── Invoices/                           # قراردادهای صدور فاکتور و مسافران
│   │   ├── Permissions/                        # تعاریف سطوح دسترسی و پالیسی‌های امنیتی
│   │   └── ... و ۶ پوشه دیگر (Hotel, RentalCar, Tours, ...)
│   │
│   ├── GashtAfarin.Application/                # پیاده‌سازی Use Caseها و منطق هماهنگی سیستم
│   │   ├── Travel/AirTripAppService.cs         # سرویس کاربردی مدیریت پروازها
│   │   ├── Cart/CartAppService.cs              # جریان اضافه/حذف از سبد و اعمال تخفیف‌ها
│   │   ├── Invoices/InvoiceAppService.cs       # نهایی‌سازی خرید و تولید فاکتور
│   │   ├── Mapping/                            # پروفایل‌های نگاشت داده (AutoMapper)
│   │   │   ├── AirTripAutoMapper.cs
│   │   │   ├── CartAutoMapper.cs
│   │   │   └── ... و ۱۲ پروفایل دیگر
│   │   └── ... و ۶ سرویس دیگر (Hotel, RentalCar, Locations, ...)
│   │
│   ├── GashtAfarin.EntityFrameworkCore/         # پیاده‌سازی ORM و ارتباط با دیتابیس
│   │   ├── EntityFrameworkCore/
│   │   │   ├── GashtAfarinDbContext.cs         # تنظیمات Fluent API، نگاشت TPT و روابط
│   │   │   ├── GashtAfarinDbContextFactory.cs  # فکتوری زمان طراحی برای ابزارهای CLI
│   │   │   └── GashtAfarinEntityFrameworkCoreModule.cs
│   │   └── Migrations/                         # اسکریپت‌های مایگریشن دیتابیس
│   │
│   ├── GashtAfarin.HttpApi/                    # کنترلرهای وب‌سرویس RESTful
│   │   └── Controllers/
│   │       ├── CartController.cs
│   │       ├── InvoiceController.cs
│   │       └── GashtAfarinController.cs
│   │
│   ├── GashtAfarin.Web/                        # لایه نمایش و رابط کاربری (Razor Pages + AJAX)
│   │   ├── Pages/                              # صفحات تعاملی وب‌سایت
│   │   │   ├── Flights/                        # لیست و جزئیات پرواز (Detail.cshtml/.cs)
│   │   │   ├── Cart/                           # نمایش و عملیات سبد خرید (Index.cshtml/.cs)
│   │   │   ├── Checkout/                       # ثبت مشخصات مسافران و درگاه پرداخت
│   │   │   ├── Hotels/, Tours/, Cruises/, Cars/
│   │   │   └── Index.cshtml                    # صفحه اصلی پرتال
│   │   ├── Areas/
│   │   │   ├── Admin/Pages/                    # پنل مدیریت سامانه (AirTrips, Users, Roles, ...)
│   │   │   └── Dashboard/                      # پنل مدیریت حساب مسافران
│   │   ├── Components/ & TagHelpers/           # کامپوننت‌های رندر مجدد و تگ‌هلپرهای اختصاصی
│   │   └── Program.cs
│   │
│   └── GashtAfarin.DbMigrator/                 # کنسول مستقل اجرای مایگریشن‌ها و Seed Data
│       ├── DbMigratorHostedService.cs
│       └── Program.cs
۱. لایه دامنه — موجودیت ریشه و وراثت دامنه

src/GashtAfarin.Domain/Travel/Trip.cs — الگوی ارث‌بری در طراحی دامنه (DDD) با موجودیت پایه ممیزی‌شده (FullAuditedEntity) و فیلدهای تخصصی پرواز.

public class Trip : FullAuditedEntity<long>
{
    public string Title { get; set; }
    public DateTime StartDateTime { get; set; }
    public long BasePrice { get; set; }
    public int ChildDiscountPercent { get; set; } = 25;
    public int InfantDiscountPercent { get; set; } = 90;
    public TripSeatMap TripSeatMap { get; set; }
}

public class AirTrip : Trip
{
    public int AirLineId { get; set; }
    public AirLine AirLine { get; set; }
    public int? AirPlaneId { get; set; }
    public AirPlane AirPlane { get; set; }
    public DateTime LandingDateTime { get; set; }
    public FlightTypeEnum FlightTypeEnum { get; set; }
    public int TripClassId { get; set; }
    public TripClass TripClass { get; set; }
    public int StopsCount { get; set; }
    public string? SeatAndBaggageInfo { get; set; }
    public List<Amenity> FlightAmenities { get; set; } = new();
    public List<Policy> FareRules { get; set; } = new();
}
۲. لایه دامنه — سرویس دامنه

src/GashtAfarin.Domain/Booking/BookingManager.cs — محافظت از اینورینت‌های تجاری، اعتبارسنجی صندلی‌های رزروشده و جلوگیری از Overbooking پیش از نهایی‌سازی سفارش.

public async Task ValidateCapacityAsync(ReservationTypeEnum type, long targetId,
    int requestedCount, List<string>? requestedSeats = null)
{
    if (type == ReservationTypeEnum.Flight || type == ReservationTypeEnum.Cruise)
    {
        var trip = await _tripRepository.FindAsync(targetId);
        if (trip == null) throw new UserFriendlyException("سفر مورد نظر یافت نشد.");

        var seatMap = trip.TripSeatMapId.HasValue
            ? await _tripSeatMapRepository.FindAsync((int)trip.TripSeatMapId.Value)
            : await _tripSeatMapRepository.FirstOrDefaultAsync(m => m.TripId == targetId);

        if (seatMap != null)
        {
            var seatQuery = await _tripSeatRepository.GetQueryableAsync();
            var totalAvailable = await AsyncExecuter.CountAsync(
                seatQuery.Where(s => s.TripSeatMapId == seatMap.Id && !s.IsBooked));

            if (totalAvailable < requestedCount)
                throw new UserFriendlyException(
                    $"ظرفیت باقیمانده این سفر ({totalAvailable} صندلی) کمتر از تعداد مسافران است.");

            if (requestedSeats != null && requestedSeats.Any())
            {
                var bookedSeats = await AsyncExecuter.ToListAsync(seatQuery.Where(s =>
                    s.TripSeatMapId == seatMap.Id && requestedSeats.Contains(s.SeatNumber) && s.IsBooked));
                if (bookedSeats.Any())
                    throw new UserFriendlyException(
                        $"صندلی‌های ({string.Join(", ", bookedSeats.Select(s => s.SeatNumber))}) قبلاً رزرو شده‌اند.");
            }
        }
    }
}
۳. لایه کاربرد — سرویس برنامه

src/GashtAfarin.Application/Cart/CartAppService.cs — ارکستراسیون میان امنیت (CurrentUser)، سرویس دامنه برای اعتبارسنجی و موتور محاسباتی رده‌های سنی، با ثبت اتمیک آیتم سبد.

[Authorize]
public async Task<CartDto> AddToCartAsync(AddToCartDto input)
{
    var totalPassengers = input.AdultsCount + input.ChildrenCount + input.InfantsCount;
    if (totalPassengers <= 0) totalPassengers = Math.Max(1, input.Quantity);

    // اعتبارسنجی ظرفیت و صندلی‌ها از طریق سرویس دامنه
    await _bookingManager.ValidateCapacityAsync(input.ReservationType, input.TargetId,
        totalPassengers, input.SelectedSeatNumbers);

    var userId = CurrentUser.GetId();
    var query = await _cartRepository.WithDetailsAsync(c => c.Items);
    var cart = await AsyncExecuter.FirstOrDefaultAsync(query.Where(c => c.UserId == userId))
               ?? await _cartRepository.InsertAsync(new Cart(GuidGenerator.Create(), userId), autoSave: true);

    var cartItem = new CartItem(GuidGenerator.Create(), cart.Id, input.ReservationType,
        input.TargetId, input.TargetTitle, input.UnitPrice, input.Quantity,
        input.StartDateTime, input.EndDateTime, input.AdultsCount, input.ChildrenCount,
        input.InfantsCount, input.ImageUrl);

    var calculatedPrice = AgeGroupPricingCalculator.CalculateItemTotal(
        input.UnitPrice, input.AdultsCount, input.ChildrenCount, input.InfantsCount);
    cartItem.TotalPrice = calculatedPrice > 0 ? calculatedPrice : input.UnitPrice * Math.Max(1, input.Quantity);

    await _cartItemRepository.InsertAsync(cartItem, autoSave: true);
    return await GetCartAsync();
}
۴. قراردادها — DTO و اعتبارسنجی ورودی

src/GashtAfarin.Application.Contracts/Travel/AirTrip/AirTripDtos.cs — اعتبارسنجی اظهاری با پیام‌های چندزبانه، محدوده‌های مجاز چمدان و جداسازی کامل مدل ورودی از مدل پایگاه داده.

public class CreateUpdateAirTripDto
{
    [Required(ErrorMessage = "Validation:FlightTitleRequired")]
    [StringLength(128)]
    [Display(Name = "عنوان پرواز")]
    public string Title { get; set; } = string.Empty;

    [Display(Name = "توضیحات")]
    [StringLength(4000)]
    public string? Description { get; set; }

    [Required]
    public DateTime StartDateTime { get; set; }

    [Range(0, long.MaxValue)]
    public long BasePrice { get; set; }

    [Range(0, 50)]
    public int MaxBaggageCount { get; set; }

    [Range(0.0, 1000.0)]
    public decimal MaxBaggageWeightKg { get; set; }

    [Range(0, 100)]
    public int ChildDiscountPercent { get; set; } = 25;

    [Range(0, 100)]
    public int StopsCount { get; set; }

    public int AirLineId { get; set; }
    public int TripClassId { get; set; }
    public FlightTypeEnum FlightTypeEnum { get; set; }
}
۵. پایگاه داده — نگاشت وراثت با Fluent API

src/GashtAfarin.EntityFrameworkCore/EntityFrameworkCore/GashtAfarinDbContext.cs — نگاشت Table-per-Type (TPT) با جدول مجزا برای AirTrip از طریق HasBaseType<Trip>()، به‌همراه Value Object مختصات جغرافیایی.

// پیکربندی موجودیت ریشه Trip در جدول پایه
builder.Entity<Trip>(b =>
{
    b.ToTable(GashtAfarinConsts.DbTablePrefix + "Trips", GashtAfarinConsts.DbSchema);
    b.ConfigureByConvention();
    b.Property(x => x.Title).IsRequired().HasMaxLength(128);
    b.Property(x => x.MaxBaggageWeightKg).HasPrecision(18, 2);
    b.OwnsOne(x => x.OriginCoordinates, l => {
        l.Property(p => p.Latitude).HasPrecision(18, 6);
        l.Property(p => p.Longitude).HasPrecision(18, 6);
    });
    b.HasOne(x => x.TripSeatMap).WithOne(x => x.Trip)
     .HasForeignKey<TripSeatMap>(x => x.TripId).OnDelete(DeleteBehavior.Cascade);
});

// مدل‌سازی وراثت Table-per-Type (TPT) برای AirTrip با جدول تفکیک‌شده
builder.Entity<AirTrip>(b =>
{
    b.ToTable(GashtAfarinConsts.DbTablePrefix + "AirTrips", GashtAfarinConsts.DbSchema);
    b.HasBaseType<Trip>(); // تعیین صریح والد در نگاشت وراثت EF Core
    b.HasOne(x => x.AirPlane).WithMany().HasForeignKey(x => x.AirPlaneId).OnDelete(DeleteBehavior.NoAction);
    b.HasMany(x => x.FareRules).WithMany();
});
۶. لایه وب — هندلر ناهمگام در Razor Page

src/GashtAfarin.Web/Pages/Cart/Index.cshtml.cs — هندلر AJAX برای حذف آیتم از سبد، محاسبه مالیات و جمع کل در بک‌اند و پاسخ JSON ساخت‌یافته برای به‌روزرسانی زنده صفحه.

public async Task<IActionResult> OnPostRemoveItemAsync(Guid itemId)
{
    if (!CurrentUser.IsAuthenticated)
        return new JsonResult(new { success = false, message = "لطفاً ابتدا وارد حساب کاربری خود شوید." });

    try
    {
        var updatedCart = await _cartAppService.RemoveCartItemAsync(itemId);
        var subtotal = updatedCart.TotalAmount;
        var tax = (long)(subtotal * GashtAfarinConsts.TaxRate);
        var grandTotal = subtotal + tax;

        return new JsonResult(new
        {
            success = true,
            message = "خدمت مورد نظر با موفقیت از سبد خرید حذف شد.",
            subtotalFormatted = subtotal.ToString("N0") + " تومان",
            taxFormatted = tax.ToString("N0") + " تومان",
            grandTotalFormatted = grandTotal.ToString("N0") + " تومان",
            itemsCount = updatedCart.TotalItemsCount,
            isEmpty = updatedCart.Items.Count == 0
        });
    }
    catch (Exception ex)
    {
        return new JsonResult(new { success = false, message = ex.Message });
    }
}

جمع‌بندی دستاوردهای مهندسی

  • معماری سازمانی استاندارد: اصول Clean Architecture و DDD در چارچوب ABP Framework.
  • مدیریت هم‌زمانی و تراکنش‌ها: جلوگیری از فروش مجدد بلیت یا اتاق در شرایط پربار.
  • ماژولار بودن و تست‌پذیری: تفکیک کامل DTOها، سرویس‌های اپلیکیشن، مجوزها و لایه دامین.
  • سبد خرید ناهمگن: پذیرش هم‌زمان چند نوع خدمت متفاوت در یک فاکتور واحد — قابلیتی کمیاب در سامانه‌های مشابه داخلی.
  • رابط کاربری حرفه‌ای: پنل مدیریت با تم مترونیک، بومی‌سازی کامل فارسی و تجربه کاربری روان.
  • بهره‌گیری هدفمند از هوش مصنوعی: گردش‌کار توسعه‌ی چند-مدلی (Claude برای برنامه‌ریزی، Gemini برای اجرا) با حفظ مالکیت کامل تصمیمات معماری توسط توسعه‌دهنده.