########################################
# 7) SALON ORDERS (طلبات الصالون)
########################################

We now want to build the salon "sales orders" module.
This module will be used inside the salon (POS / front desk), not from the mobile app.
It should support:
- Today's orders
- All orders
- App orders (if we later decide to unify sources)
- Canceled orders
- Scheduled orders
- Postponed orders
- Ratings

The order represents one sales ticket (طلب مبيعات) that may contain:
- One or more services
- Optional additional products sold (from inventory)
- Optional coupon discount
- Optional future execution date/time for each service
- The employee who created the order (front desk / receptionist)
- The employee who will execute each service


### Table: orders
Main header for salon sales orders.

Fields:
- id (integer, primary key, auto increment)
- order_number (string, unique)             // رقم الطلب (e.g. "SO-0001")
- order_date (datetime, not null)           // تاريخ ووقت إنشاء الطلب
- source (string, not null)                 // "pos" (inside salon), "app" (if later used)
- order_type (string, not null)             // "normal" (طلب طبيعي), "gift" (هدية من زبونة لأخرى)
- client_id (integer, nullable)             // العميل (from clients/customers table – can be null for walk-in)
- created_by_employee_id (integer, not null) // الموظف الذي أنشأ الطلب (from employees/users table)
- branch_id (integer, nullable)             // الفرع (if multi-branch)
- coupon_code (string, nullable)            // كود الكوبون المستخدم (if any)
- coupon_discount_amount (real, not null, default 0) // قيمة الخصم من الكوبون
- status (string, not null)                 // "new", "in_progress", "completed", "canceled", "scheduled", "postponed"
- notes (string, nullable)
- subtotal_amount (real, not null, default 0) // مجموع الخدمات/المنتجات بدون ضريبة وقبل الخصومات
- vat_amount (real, not null, default 0)      // مجموع الضريبة
- total_amount (real, not null, default 0)    // الإجمالي بعد الضريبة وقبل/بعد الخصم (please document clearly in code comments)
- created_at (datetime)
- updated_at (datetime)

Notes:
- "طلبات اليوم" = filter orders by DATE(order_date) = today's date.
- "الطلبات الملغية" = status = "canceled".
- "الطلبات المجدولة" = status = "scheduled".
- "الطلبات المؤجلة" = status = "postponed".
- "طلبات التطبيق" = source = "app" (if later used in unified list).


### Table: order_services
Line items for services within an order.

Fields:
- id (integer, primary key, auto increment)
- order_id (integer, foreign key to orders.id)
- service_id (integer, foreign key to services.id)      // from services module
- quantity (real, not null, default 1)                  // عدد مرات تنفيذ الخدمة
- base_price (real, not null)                           // سعر الخدمة المفترض (من service.price)
- vat_type (string, not null)                           // "inclusive", "exclusive", "exempt" (from service.vat_type by default)
- vat_rate (real, not null)                             // نسبة الضريبة (from service.vat_rate by default)
- line_subtotal (real, not null)                        // المبلغ بدون ضريبة
- vat_amount (real, not null)                           // قيمة الضريبة
- line_total (real, not null)                           // المبلغ مع الضريبة
- scheduled_at (datetime, nullable)                     // تاريخ ووقت تنفيذ الخدمة (اختياري)
- executing_employee_id (integer, nullable)             // الموظف الذي سينفذ الخدمة
- created_at (datetime)
- updated_at (datetime)

Behavior:
- By default, base_price, vat_type, vat_rate should be copied from the service definition.
- Calculations per line:
  - 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


### Table: order_products
Optional additional products sold in the order (non-service items).

Fields:
- id (integer, primary key, auto increment)
- order_id (integer, foreign key to orders.id)
- product_id (integer, foreign key to products.id)
- quantity (real, not null)                        // بوحدة البيع للمنتج
- unit_price (real, not null)                      // سعر بيع الوحدة (قبل الضريبة أو بعد حسب vat_type)
- vat_type (string, not null)                      // "inclusive", "exclusive", "exempt"
- vat_rate (real, not null)
- line_subtotal (real, not null)                   // بدون الضريبة
- vat_amount (real, not null)
- line_total (real, not null)
- created_at (datetime)
- updated_at (datetime)

Behavior:
- Similar VAT calculation rules as order_services.
- Future enhancement: we may separate "retail sales" vs "consumption-only", but for now treat this as sales lines.


### Table: order_ratings
Customer ratings for completed orders.

Fields:
- id (integer, primary key, auto increment)
- order_id (integer, foreign key to orders.id)
- client_id (integer, nullable)              // نفس العميل في الطلب إن وجد
- rating (integer, not null)                 // e.g., 1–5
- comment (string, nullable)
- created_at (datetime)

Rules:
- Allow rating only if order.status = "completed".
- An order can have at most one rating (you can enforce a unique constraint on order_id if you want).


### Automatic inventory consumption (to be implemented or prepared now):

- When an order is marked as "completed":
  - For each service in order_services:
    - Find related service_products (from services module).
    - For each related product:
      - Calculate total consumption quantity = quantity_per_service * service_line.quantity.
      - Create stock_movements record with:
        - movement_type = "consume"
        - quantity_out = total consumption quantity
        - reference_type = "OrderService"
        - reference_id = order_services.id
      - Decrease stock_batches.quantity_on_hand according to a simple FIFO method (can be basic for now).
  - For each order_products line (actual product sale):
    - Create stock_movements record with:
      - movement_type = "sale"
      - quantity_out = quantity
      - reference_type = "OrderProduct"
      - reference_id = order_products.id


### Orders APIs

1) GET /orders
   - List orders with optional filters:
     - ?date=YYYY-MM-DD (for "today")
     - ?status=new|in_progress|completed|canceled|scheduled|postponed
     - ?source=pos|app
     - ?client_id=
     - ?branch_id=
   - Return:
     - id, order_number, order_date, client info, status, source, subtotal_amount, vat_amount, total_amount.

2) POST /orders
   - Create a new salon order with services and optional products.

   Request JSON example:
   {
     "order_type": "normal",              // "normal" or "gift"
     "source": "pos",
     "client_id": 1,
     "created_by_employee_id": 5,
     "branch_id": 1,
     "coupon_code": "WELCOME10",
     "services": [
       {
         "service_id": 10,
         "quantity": 1,
         "scheduled_at": "2025-10-01T16:00:00",
         "executing_employee_id": 7
       },
       {
         "service_id": 12,
         "quantity": 2,
         "scheduled_at": null,
         "executing_employee_id": 8
       }
     ],
     "products": [
       {
         "product_id": 20,
         "quantity": 1
       }
     ],
     "notes": "طلب داخل الصالون"
   }

   Behavior:
   - Validate client_id (if provided), created_by_employee_id, branch_id.
   - Validate service_id for each service and that the service is active.
   - Validate product_id for each product and that the product is active.
   - For each service line:
     - Fetch service.price, vat_type, vat_rate as defaults (unless we allow override).
     - Calculate line_subtotal, vat_amount, line_total.
   - For each product line:
     - Use product.default_sell_price as default unit_price (unless we allow override).
     - Calculate line_subtotal, vat_amount, line_total.
   - Apply coupon_code logic later; for now, just accept coupon_code and coupon_discount_amount as inputs or keep at 0.
   - Sum totals:
     - subtotal_amount = sum(service line_subtotal + product line_subtotal)
     - vat_amount = sum(service vat_amount + product vat_amount)
     - total_amount = subtotal_amount + vat_amount - coupon_discount_amount
   - Set status:
     - If all services have scheduled_at in the future => status = "scheduled"
     - Else => status = "new"

   Return:
   - Order header + services + products.


3) GET /orders/:id
   - Return:
     - order header
     - services (with service name, category name, executing employee)
     - products (with product name)
     - rating (if exists)
     - basic stock movement references if needed.


4) PUT /orders/:id
   - Allow updating:
     - status
     - notes
     - maybe scheduled_at or executing_employee for services (optional, you can define a safe subset).
   - If status changes to "completed":
     - Trigger inventory consumption logic as described above.


5) POST /orders/:id/rate
   - Create or update a rating for the order.

   Request JSON:
   {
     "client_id": 1,
     "rating": 5,
     "comment": "خدمة ممتازة"
   }

   Behavior:
   - Only allow if order.status = "completed".
   - If a rating already exists, update it; otherwise create new.
   - Return updated rating.


########################################
# 8) MOBILE APP APPOINTMENTS (الحجوزات من التطبيق)
########################################

This module handles bookings coming from the mobile app.
It is similar to orders, but it represents a booking request that may later be converted into an actual order when the customer arrives or when the service is executed.

Key differences:
- Only services are booked (no direct product selection here).
- Appointment type: inside salon or home service.
- For home service:
  - We need to store map location (latitude, longitude) and address.
  - We need to store a visit/delivery fee.


### Table: appointments
Header for appointments from the mobile app.

Fields:
- id (integer, primary key, auto increment)
- appointment_number (string, unique)       // e.g. "AP-0001"
- source (string, not null)                 // "app"
- 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)     // رسوم زيارة / توصيل للخدمة المنزلية
- 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)    // الإجمالي مع الضريبة + رسوم الزيارة
- created_at (datetime)
- updated_at (datetime)

Rules:
- If appointment_type = "home_service":
  - location_lat, location_lng, address_text should be required (validation).
- 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)                 // from service.price
- vat_type (string, not null)                 // from service.vat_type
- vat_rate (real, not null)                   // from service.vat_rate
- line_subtotal (real, not null)              // without VAT
- vat_amount (real, not null)
- line_total (real, not null)
- preferred_employee_id (integer, nullable)   // موظف مفضل لتنفيذ الخدمة (اختياري)
- created_at (datetime)
- updated_at (datetime)

Behavior:
- Same VAT calculation logic as in order_services.
- subtotal_amount, vat_amount, total_amount on appointments table should be the sum of these lines, plus visit_fee.


### Appointments APIs

1) POST /appointments
   - Used by the mobile app 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,
     "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, branch_id, services.
   - If appointment_type = "home_service":
     - location_lat, location_lng, address_text, visit_fee are required.
   - For each service, fetch service price, vat_type, vat_rate.
   - Calculate line_subtotal, vat_amount, line_total.
   - Sum totals:
     - subtotal_amount = sum(line_subtotal)
     - vat_amount = sum(vat_amount)
     - total_amount = subtotal_amount + vat_amount + visit_fee
   - Set status = "pending" by default.
   - Return appointment with services.


2) GET /appointments
   - List appointments with filters:
     - ?date=YYYY-MM-DD (on scheduled_at)
     - ?status=pending|confirmed|completed|canceled|no_show|postponed
     - ?appointment_type=in_salon|home_service
     - ?client_id=
     - ?branch_id=
   - Used by admin/staff to see today's bookings, upcoming bookings, etc.


3) GET /appointments/:id
   - Return:
     - appointment header
     - services (with service name and optional preferred employee)


4) PUT /appointments/:id
   - Update:
     - status
     - scheduled_at
     - branch_id
     - notes
     - visit_fee, location fields (if still pending and type = home_service)
   - If status becomes "completed":
     - In a later phase, we may:
       - Automatically create a corresponding order in the orders module
       - Or at least link this appointment to an order_id.


5) Optional: POST /appointments/:id/confirm
   - To mark appointment as confirmed (status = "confirmed").

6) Optional: POST /appointments/:id/cancel
   - To mark as canceled and record cancellation reason if needed.


### Validation & Arabic messages

- If client_id not found: return error "العميل غير موجود".
- If service_id not found or inactive: return "الخدمة غير موجودة أو غير مفعّلة".
- If appointment_type = "home_service" and location is missing: return "موقع العميل مطلوب للخدمة المنزلية".
- If trying to complete an appointment with no services: return "لا يمكن إكمال الحجز بدون خدمات".
