مبادئ التصميم
معرفات نصية قصيرة
جميع المفاتيح الأساسية تستخدم معرّفات نصية قصيرة مخصصة (أقصى 15 حرفًا) عبر BaseEntity. لا أعداد صحيحة متزايدة تلقائيًا مكشوفة للعملاء.
DATETIMEOFFSET
جميع الطوابع الزمنية تستخدم DATETIMEOFFSET للتوعية بالمنطقة الزمنية. التطبيق يعمل بتوقيت Asia/Riyadh (UTC+3)، بدون توقيت صيفي.
فرض على مستوى قاعدة البيانات
الثوابت الحرجة تُفرض عبر قيود SQL Server والمشغّلات (triggers) والفهارس الفريدة — وليس فقط في كود التطبيق.
سجل تدقيق كامل
جدول audit_logs غير قابل للتغيير وملحق فقط. كل تعديل يُسجّل.
القيود الحرجة
منع الحجز المزدوج يجب أن يُفرض على مستوى قاعدة البيانات. في SQL Server، استخدم مزيجًا من UNIQUE INDEX على (field_id, date, start_time) مع INSTEAD OF INSERT trigger (أو serializable transaction) للتحقق من النطاقات الزمنية المتداخلة. حتى لو كان هناك خطأ في كود التطبيق، يجب على قاعدة البيانات رفض الإدراجات المتعارضة.
| القيد |
الجدول |
الغرض |
UNIQUE INDEX + Trigger |
bookings |
يمنع الحجوزات المتداخلة على نفس الملعب + النطاق الزمني. غير قابل للتفاوض. |
NOT NULL + CHECK(price > 0) |
bookings |
كل حجز يجب أن يكون له سعر. الإصدار السابق كان يفتقد 58.3%. |
معرفات نصية قصيرة (BaseEntity) |
جميع الجداول |
معرّفات قصيرة مخصصة (أقصى 15 حرفًا). لا أعداد صحيحة متزايدة تلقائيًا مكشوفة. |
DATETIMEOFFSET |
جميع أعمدة الطوابع الزمنية |
طوابع زمنية واعية بالمنطقة الزمنية (UTC+3). |
| التعدادات كنصوص |
أعمدة الحالة |
EF Core HasConversion<string>() + قيود CHECK للقيم الصالحة. |
UNIQUE(field_id, date, start_time) |
booking_holds |
حجز مؤقت واحد فقط لكل فترة في كل مرة. |
INSERT ONLY (بدون UPDATE/DELETE) |
audit_logs |
سجل تدقيق غير قابل للتغيير. يُفرض عبر trigger أو سياسة التطبيق. |
أنواع التعدادات تُخزّن كنصوص
جميع التعدادات تُخزّن كأعمدة NVARCHAR باستخدام HasConversion<string>() في EF Core. يُعرّف التطبيق 18 نوع تعداد.
| التعداد |
القيم |
booking_status |
booked
confirmed
completed
cancelled
no_show
|
payment_status |
unpaid
partial
paid
refunded
|
booking_source |
admin
fm
customer
contract
guest
|
user_role |
admin
executive
field_manager
customer
|
notification_status |
pending
sent
delivered
failed
|
sport_type |
football
volleyball
padel
|
schedule_rule_type |
add_slots
remove_slots
modify_price
modify_time
|
الجداول حسب النطاق
التسلسل الجغرافي
cities → locations → fields
— مدينة واحدة تحتوي على عدة مواقع، وموقع واحد يحتوي على عدة ملاعب.
| الجدول |
الأعمدة الرئيسية |
الغرض |
cities |
id, name_ar, name_en, is_active |
التجميع الجغرافي الأعلى (مثلًا: بريدة، عنيزة) |
locations |
id, city_id, name_ar, name_en, lat, lng, address, operating_hours |
موقع فعلي مع إحداثيات GPS. موقع واحد يمكن أن يحتوي على عدة ملاعب. |
fields |
id, location_id, name_ar, name_en, sport_type, is_active, base_price, schedule_profile_id |
ملعب فردي. له نوع رياضة، سعر أساسي، وملف جدولة مُسند. |
الجدولة
نظام قائم على القوالب يحل محل التسلسل الرباعي غير المستدام في الإصدار السابق. الملف يحتوي على قواعد مرتبة تحدد الفترات المتاحة.
| الجدول |
الأعمدة الرئيسية |
الغرض |
schedule_profiles |
id, name, description, is_default |
قالب مسمى يُسند للملاعب. يحتوي على مجموعة من القواعد. |
schedule_rules |
id, profile_id, rule_type, priority, days_of_week, date_from, date_to, start_time, end_time, slot_duration, price_override |
قاعدة فردية بأولوية رقمية. الأولوية الأعلى تفوز في التعارضات. الأنواع: add_slots, remove_slots, modify_price, modify_time. |
prayer_times |
id, city_id, date, fajr, zuhr, asr, maghrib, isha |
أوقات الصلاة اليومية لكل مدينة. قواعد الجدولة يمكنها الإشارة إليها مع إزاحات. |
دورة حياة الحجز
| الجدول |
الأعمدة الرئيسية |
الغرض |
bookings |
id, field_id, customer_id, creator_id, date, start_time, end_time, status, source, price, payment_status, cancellation_reason, notes |
الجدول المركزي. آلة حالة (5 حالات). UNIQUE INDEX + Trigger يمنع التداخلات. السعر إلزامي. |
booking_holds |
id, field_id, date, start_time, end_time, held_by, expires_at |
حجز مؤقت للفترة لمدة 5 دقائق أثناء ملء النموذج. تنتهي صلاحيته عبر مهمة خلفية. |
tickets |
id, booking_id, slot_date, slot_start, slot_end |
فترات زمنية فردية ضمن الحجز (للحجوزات متعددة الفترات). |
العقود (الحجوزات المتكررة)
| الجدول |
الأعمدة الرئيسية |
الغرض |
contracts |
id, customer_id, creator_id, start_date, end_date, is_active, total_price, notes |
اتفاقية حجز متكرر. تُنشئ الحجوزات تلقائيًا. |
contract_days |
id, contract_id, field_id, day_of_week, start_time, end_time, price, is_enabled |
إعداد كل يوم. تفعيل/تعطيل الأيام بدون إعادة إنشاء العقد. |
المستخدمون والمصادقة
| الجدول |
الأعمدة الرئيسية |
الغرض |
users |
id, phone, email, name, role, pin_hash, is_active, assigned_field_ids |
جميع الأدوار في جدول واحد. رقم الهاتف هو المعرّف الأساسي للعملاء. نطاق مدير الملعب عبر assigned_field_ids. |
otp_codes |
id, phone, code, expires_at, is_used, attempts |
رموز OTP من 6 أرقام. صلاحية 5 دقائق. بحد أقصى 3 كل 15 دقيقة لكل رقم هاتف. |
sessions |
id, user_id, token, ip_address, user_agent, expires_at |
الجلسات النشطة للويب (cookie) والموبايل (JWT). |
login_attempts |
id, phone_or_email, attempt_type, ip_address, success, created_at |
تتبع محاولات تسجيل الدخول لتحديد المعدل والأمان. |
المالية
| الجدول |
الأعمدة الرئيسية |
الغرض |
payments |
id, booking_id, amount, method, recorded_by, vat_amount, notes |
مدفوعات نقدية عند الإطلاق. جاهز لبوابة الدفع مستقبلًا. أول دفعة تؤكد الحجز تلقائيًا. |
refunds |
id, payment_id, amount, reason, processed_by |
مرتبط بالدفعة الأصلية. يتتبع من قام بالمعالجة ولماذا. |
الاتصالات
| الجدول |
الأعمدة الرئيسية |
الغرض |
notifications |
id, user_id, booking_id, template_id, channel, status, sent_at, delivered_at, provider, external_id |
الإشعارات الصادرة (WhatsApp). تتبع حالة التسليم والمزوّد المستخدم. |
notification_templates |
id, key, name_ar, body_ar, variables |
4 قوالب أساسية: booking_created, booking_confirmed, 24h_reminder, booking_cancelled. |
incoming_messages |
id, from_phone, body, received_at, is_processed |
رسائل WhatsApp الواردة عبر WAHA webhook. للمراسلة ثنائية الاتجاه مستقبلًا. |
التدقيق والعملاء
| الجدول |
الأعمدة الرئيسية |
الغرض |
audit_logs |
id, user_id, entity_type, entity_id, action, field_name, old_value, new_value, reason, created_at |
غير قابل للتغيير وملحق فقط. لا يُسمح بـ UPDATE أو DELETE. يسجل كل تعديل على البيانات. |
customer_contacts |
id, phone, name, notes, created_by |
بحث العملاء بالهاتف للإكمال التلقائي أثناء إنشاء الحجز. |
الجداول المؤجلة المخطط فقط
هذه الجداول موجودة في المخطط لكنها معطّلة عند الإطلاق بعلامات الميزات. أنشئ الترحيلات لكن لا تبنِ منطق التطبيق.
| الجدول |
الغرض |
captains |
كباتن الفرق الذين ينظمون مجموعات اللاعبين. |
teams |
الفرق المرتبطة بالكباتن. |
team_members |
الأعضاء المنتمون للفرق. |
booking_attendance |
تتبع الحضور (check-in) للحجوزات الفردية. |
العلاقات الرئيسية
City 1 ──> * Location 1 ──> * Field
Field * ──> 1 ScheduleProfile 1 ──> * ScheduleRule
Field 1 ──> * Booking
Customer 1 ──> * Booking
User 1 ──> * Booking
Booking 1 ──> * Ticket
Booking 1 ──> * Payment
Payment 1 ──> * Refund
Customer 1 ──> * Contract
Contract 1 ──> * ContractDay
Contract 1 ──> * Booking
Booking 1 ──> * Notification
User 1 ──> * Notification
Template 1 ──> * Notification
User 1 ──> * AuditLog
المواصفات الكاملة للمخطط: راجع design/SCHEMA.md في المستودع لتعريفات الأعمدة والفهارس الكاملة. ترحيل EF Core في OneTwoApi/Migrations/ يُظهر المخطط المُنفّذ الحالي.