########################################
# APPOINTMENTS MODULE (حجوزات التطبيق)
########################################

We now want to add a dedicated "Appointments" module for bookings coming from the mobile app.
This module is similar to salon orders but represents booking requests that may later be served
and/or converted into actual salon orders.

Key points:
- Appointments come mainly from the mobile app (source = "app").
- Only services are booked (no direct product selection here).
- Appointment type: inside salon OR home service.
- For home service, we must store:
  - Map location (latitude, longitude)
  - Address text
  - Visit/delivery fee (رسوم زيارة)

We will later:
- Link appointments to salon orders (e.g., when an appointment is served/completed).


### Table: appointments
Header for appointments.

Fields:
- id (integer, primary key, auto increment)
- appointment_number (string, unique)       // رقم الحجز (e.g. "AP-0001")
- source (string, not null)                 // "app" for now (can support "phone", "walk_in" later)
- client_id (integer, not null)             // العميل القادم من جدول العملاء
- branch_id (integer, nullable)             // الفرع المطلوب
- appointment_type (string, not null)       // "in_salon", "home_service"
- scheduled_at (datetime, not null)         // تاريخ ووقت الحجز المطلوب
- status (string, not null)                 // "pending", "confirmed", "in_progress", "completed", "canceled", "no_show", "postponed"
- visit_fee (real, not null, default 0)     // رسوم زيارة / خدمة منزلية (0 إذا كان داخل الصالون)
- location_lat (real, nullable)             // إحداثيات الموقع - للخدمة المنزلية فقط
- location_lng (real, nullable)
- address_text (string, nullable)           // عنوان نصي (الحي، الشارع، الخ)
- notes (string, nullable)
- subtotal_amount (real, not null, default 0) // مجموع الخدمات بدون ضريبة
- vat_amount (real, not null, default 0)      // مجموع الضريبة على الخدمات
- total_amount (real, not null, default 0)    // الإجمالي مع الضريبة + رسوم الزيارة
- related_order_id (integer, nullable)      // ربط اختياري مع طلب صالون (orders.id) في المستقبل
- created_at (datetime)
- updated_at (datetime)

Rules:
- If appointment_type = "home_service":
  - location_lat, location_lng, address_text, visit_fee must be provided.
- total_amount = subtotal_amount + vat_amount + visit_fee.


### Table: appointment_services
Services requested in the appointment.

Fields:
- id (integer, primary key, auto increment)
- appointment_id (integer, foreign key to appointments.id)
- service_id (integer, foreign key to services.id)
- quantity (real, not null, default 1)      // عدد مرات تنفيذ الخدمة في الحجز
- base_price (real, not null)               // من service.price في وقت إنشاء الحجز
- vat_type (string, not null)               // "inclusive", "exclusive", "exempt" (من الخدمة)
- vat_rate (real, not null)                 // مثال: 0.15
- line_subtotal (real, not null)            // بدون ضريبة
- vat_amount (real, not null)
- line_total (real, not null)               // مع الضريبة
- preferred_employee_id (integer, nullable) // موظف مفضّل لتنفيذ الخدمة (اختياري)
- created_at (datetime)
- updated_at (datetime)

VAT behavior (same as services in orders):
- If vat_type = "inclusive":
  - gross = base_price * quantity
  - line_subtotal = gross / (1 + vat_rate)
  - vat_amount = gross - line_subtotal
  - line_total = gross
- If vat_type = "exclusive":
  - line_subtotal = base_price * quantity
  - vat_amount = line_subtotal * vat_rate
  - line_total = line_subtotal + vat_amount
- If vat_type = "exempt":
  - line_subtotal = base_price * quantity
  - vat_amount = 0
  - line_total = line_subtotal


### APPOINTMENTS APIs

########################################
# POST /appointments
########################################

Used by the mobile app (or admin panel) to create a new appointment.

Request JSON example:

{
  "client_id": 10,
  "branch_id": 1,
  "appointment_type": "home_service",       // "in_salon" or "home_service"
  "scheduled_at": "2025-10-02T18:30:00",
  "visit_fee": 50,                          // 0 if in_salon
  "location_lat": 21.543333,
  "location_lng": 39.172778,
  "address_text": "حي السلامة، جدة",
  "services": [
    {
      "service_id": 10,
      "quantity": 1,
      "preferred_employee_id": null
    },
    {
      "service_id": 12,
      "quantity": 1
    }
  ],
  "notes": "أرجو الالتزام بالوقت"
}

Behavior:
- Validate:
  - client_id must exist (otherwise return error "العميل غير موجود").
  - branch_id (if provided) must exist (later when branches table exists).
  - appointment_type must be one of: "in_salon", "home_service".
  - services array must not be empty (otherwise return "يجب اختيار خدمة واحدة على الأقل").
  - Each service_id must exist and be active (else "الخدمة غير موجودة أو غير مفعّلة").
- If appointment_type = "home_service":
  - visit_fee, location_lat, location_lng, address_text must not be null.
  - If missing, return error "موقع العميل ورسوم الزيارة مطلوبة للخدمة المنزلية".
- For each service in services:
  - Fetch service from services table:
    - price, vat_type, vat_rate
  - Calculate line_subtotal, vat_amount, line_total based on vat_type and vat_rate.
- Sum totals:
  - subtotal_amount = sum(all line_subtotal)
  - vat_amount = sum(all vat_amount)
  - total_amount = subtotal_amount + vat_amount + visit_fee
- Set:
  - source = "app" (default)
  - status = "pending"
  - Generate appointment_number, e.g. "AP-0001" (you can implement a simple auto-increment string).
- Insert into appointments + appointment_services.
- Return full appointment object (header + services).


########################################
# GET /appointments
########################################

List appointments with filters.

Query params:
- ?date=YYYY-MM-DD      // filter by DATE(scheduled_at)
- ?status=pending|confirmed|in_progress|completed|canceled|no_show|postponed
- ?appointment_type=in_salon|home_service
- ?client_id=
- ?branch_id=

Response:
- List of appointments with:
  - id, appointment_number, scheduled_at, status, appointment_type,
    client basic info (name, phone if available),
    branch_id, subtotal_amount, vat_amount, total_amount, visit_fee.


########################################
# GET /appointments/:id
########################################

Return full appointment details:

- Appointment header:
  - id, appointment_number, source, client_id, branch_id, appointment_type,
    scheduled_at, status, visit_fee, location_lat, location_lng, address_text,
    notes, subtotal_amount, vat_amount, total_amount, related_order_id.
- Services:
  - For each appointment_services row:
    - id, service_id, service_name_ar, quantity, base_price, vat_type, vat_rate,
      line_subtotal, vat_amount, line_total, preferred_employee_id (and employee name if available).


########################################
# PUT /appointments/:id
########################################

Update an existing appointment.

Allowed updates:
- scheduled_at (reschedule)
- branch_id
- appointment_type (careful – only if still pending)
- visit_fee, location_lat, location_lng, address_text (for home_service and still pending)
- status (e.g., pending → confirmed, confirmed → in_progress, in_progress → completed, etc.)
- notes

Behavior:
- If status changes to "completed":
  - For now, just mark as completed.
  - Later, we will optionally:
    - Create a related salon order and set related_order_id.
- If status changes to "canceled":
  - Do not delete anything, just mark as canceled.
- If appointment_type is "home_service", keep validating the location fields when updating.

Validation messages:
- If trying to set status to "completed" while there are no services:
  - Return "لا يمكن إكمال الحجز بدون خدمات".
- If status value is not in allowed list:
  - Return "حالة الحجز غير صحيحة".


########################################
# OPTIONAL: POST /appointments/:id/confirm
########################################

Shortcut endpoint to set status to "confirmed".

Behavior:
- Only allow if current status = "pending".
- Otherwise return "لا يمكن تأكيد هذا الحجز في حالته الحالية".


########################################
# OPTIONAL: POST /appointments/:id/cancel
########################################

Shortcut endpoint to set status to "canceled".

Request JSON example (optional reason):
{
  "reason": "العميلة ألغت الحجز"
}

Behavior:
- Set status = "canceled".
- Optionally store reason in notes or an extra field if you add it later.


########################################
# VALIDATION & ARABIC ERRORS (الحجوزات)
########################################

Please add basic validation and return Arabic error messages such as:

- "العميل غير موجود"
- "الخدمة غير موجودة أو غير مفعّلة"
- "يجب اختيار خدمة واحدة على الأقل"
- "موقع العميل ورسوم الزيارة مطلوبة للخدمة المنزلية"
- "حالة الحجز غير صحيحة"
- "لا يمكن إكمال الحجز بدون خدمات"
- "لا يمكن تأكيد هذا الحجز في حالته الحالية"
