# Halı Takip Sistemi API

**Sürüm:** v1.0  
**Base URL:** `http://localhost/halitakip/api/v1`  
**Auth:** Bearer JWT  
**Rate Limit:** 100/dakika, 1000/saat

Halı yıkama firmaları için çok kiracılı (multi-tenant) sipariş, müşteri, halı ve ödeme yönetim API'si. JWT (Bearer) tabanlı kimlik doğrulama, firma bazlı veri izolasyonu, rol tabanlı yetkilendirme.

---

## Kimlik Doğrulama

1. POST /auth/login ile e-posta + şifre gönder, access + refresh token al
2. Tüm korumalı endpoint'lere "Authorization: Bearer <access_token>" header'ı ile istek gönder
3. Access token süresi dolduğunda POST /auth/refresh ile yenisini al

### Login Örneği (cURL)

```bash
curl -X POST 'http://localhost/halitakip/api/v1/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@halitakip.com","password":"admin123"}'
```

### Yanıt Örneği

```json
{
    "success": true,
    "message": "Giriş başarılı",
    "data": {
        "access_token": "eyJ0eXAiOiJKV1Q...",
        "refresh_token": "eyJ0eXAiOiJKV1Q...",
        "token_type": "Bearer",
        "expires_in": 3600,
        "user": {
            "id": 1,
            "name": "Sistem Yöneticisi",
            "email": "admin@halitakip.com",
            "role": "super_admin",
            "firm_id": null,
            "firm": null
        }
    }
}
```

Sonraki istekler için header:
```
Authorization: Bearer <access_token>
```

---

## Roller

| Kod | Ad | Açıklama |
|-----|-----|----------|
| `super_admin` | Sistem Yöneticisi | Tüm sistem yetkileri (firmalar, planlar, sistem ayarları). firm_id IS NULL. |
| `moderator` | Moderatör | Salt-okunur sistem kullanıcısı. Tüm sayfaları görür, yazma yapamaz. firm_id IS NULL. |
| `firm_admin` | Firma Yöneticisi | Kendi firmasının tüm verileri ve kullanıcıları üzerinde yetkili. |
| `employee` | Çalışan | Müşteri/halı/sipariş/ödeme operasyonları. |
| `driver` | Kurye/Şoför | Operasyonel işlemler (sipariş durumu, teslim). |

---

## Yetkiler (Permissions)

Rol bazlı varsayılan yetkiler + kullanıcı bazlı override sistemi. Her kullanıcının role’ünden gelen varsayılan yetkileri vardır; firma yöneticisi (firm_admin) veya süper admin custom_permissions JSON array’i ile bu yetkileri override edebilir.

**Override:** users.custom_permissions sütunu JSON array. NULL ise rol varsayılanı, dolu ise sadece liste içindeki yetkiler etkin.

### Tüm Yetki Anahtarları

**customers:**
- `customers.view` — Müşterileri görüntüle
- `customers.create` — Yeni müşteri ekle
- `customers.update` — Müşteri düzenle
- `customers.delete` — Müşteri sil

**orders:**
- `orders.view` — Siparişleri görüntüle
- `orders.create` — Yeni sipariş oluştur
- `orders.update` — Sipariş düzenle
- `orders.status` — Sipariş durumu güncelle
- `orders.delete` — Sipariş sil/iptal

**payments:**
- `payments.view` — Ödemeleri görüntüle
- `payments.create` — Ödeme kaydet
- `payments.delete` — Ödeme sil

**operations:**
- `carpets.types.manage` — Halı türü/fiyat yönetimi
- `vehicles.manage` — Araç/plaka yönetimi
- `service_areas.manage` — Hizmet bölgeleri yönetimi

**management:**
- `reports.view` — Raporları görüntüle
- `users.manage` — Kullanıcı yönetimi
- `settings.manage` — Firma ayarları yönetimi
- `firm.edit` — Firma bilgilerini düzenle

### Rol Varsayılan Yetkileri

- `super_admin` — * (tüm yetkiler)
- `moderator` — Sadece view yetkileri (customers.view, orders.view, payments.view, reports.view) — write işlemleri middleware seviyesinde engellenir
- `firm_admin` — Tüm firma yetkileri
- `employee` — customers.view/create/update, orders.view/create/update/status, payments.view/create, reports.view
- `driver` — customers.view, orders.view, orders.status (sadece görüntüleme + durum güncelleme)

### Kullanım Örneği

Bir çalışana sadece müşteri görüntüleme yetkisi verme:

```json
{
    "name": "Sınırlı Çalışan",
    "email": "sinirli@firma.com",
    "password": "******",
    "role": "employee",
    "firm_id": 1,
    "custom_permissions": [
        "customers.view",
        "orders.view"
    ]
}
```

---

## Yanıt Formatı

### Başarılı
```json
{
    "success": true,
    "message": "Başarılı",
    "data": "..."
}
```

### Sayfalı Liste
```json
{
    "success": true,
    "data": "[...]",
    "meta": {
        "current_page": 1,
        "per_page": 20,
        "total": 145,
        "last_page": 8,
        "has_more": true
    }
}
```

### Hata
```json
{
    "success": false,
    "message": "Bir hata oluştu"
}
```

### Doğrulama Hatası
```json
{
    "success": false,
    "message": "Doğrulama hatası",
    "errors": {
        "email": "E-posta zorunludur",
        "password": "Şifre en az 6 karakter olmalıdır"
    }
}
```

### HTTP Status Kodları

- `200` — Başarılı
- `201` — Oluşturuldu
- `400` — Hatalı istek
- `401` — Yetkisiz (token yok / geçersiz / süresi dolmuş)
- `403` — Yasaklı (yetersiz rol)
- `404` — Bulunamadı
- `422` — Doğrulama hatası (errors objesi içerir)
- `500` — Sunucu hatası

---

## Endpoint'ler

### Kimlik Doğrulama

#### `POST /auth/login`

E-posta + şifre ile giriş, JWT token al

- **Yetki:** public
- **Auth:** none
- **Body:**
  - `email` — string (required)
  - `password` — string (required, min 6)
- **Yanıt:** access_token, refresh_token, user object

#### `POST /auth/refresh`

Refresh token ile yeni access token al

- **Yetki:** public
- **Auth:** none
- **Body:**
  - `refresh_token` — string (required)

#### `GET /auth/me`

Giriş yapan kullanıcı bilgisi

- **Yetki:** authenticated
- **Auth:** bearer

#### `PUT /auth/profile`

Kendi profilini güncelle

- **Yetki:** authenticated
- **Auth:** bearer
- **Body:**
  - `name` — string
  - `phone` — string
  - `password` — string (opsiyonel, min 6)

---

### Firmalar

#### `GET /firms`

Firmaları listele (sayfalı)

- **Yetki:** super_admin, moderator
- **Auth:** bearer
- **Query:** `page` (int), `per_page` (int), `search` (string)

#### `POST /firms`

Yeni firma + yöneticisi oluştur

- **Yetki:** super_admin
- **Auth:** bearer
- **Body:**
  - `name` — string (required)
  - `plan_id` — int
  - `license_starts_at` — date
  - `license_expires_at` — date
  - `phone` — string
  - `email` — string
  - `tax_no` — string
  - `tax_office` — string
  - `city` — string
  - `address` — string
  - `admin_name` — string (required)
  - `admin_email` — string (required)
  - `admin_password` — string (required, min 6)
  - `admin_phone` — string

#### `GET /firms/{id}`

Firma detayı (effective_limits, license_status, active_overrides dahil)

- **Yetki:** super_admin, moderator, firm_admin
- **Auth:** bearer

#### `PUT /firms/{id}`

Firma güncelle (super_admin tüm alanları, firm_admin sadece bilgileri)

- **Yetki:** super_admin, firm_admin
- **Auth:** bearer

#### `DELETE /firms/{id}`

Firmayı devre dışı bırak

- **Yetki:** super_admin
- **Auth:** bearer

#### `GET /firms/{id}/usage`

Firma kullanım/limit raporu (% dahil)

- **Yetki:** super_admin, moderator, firm_admin
- **Auth:** bearer

#### `POST /firms/{id}/extend-license`

Lisans süresini uzat

- **Yetki:** super_admin
- **Auth:** bearer
- **Body:**
  - `days` — int (required, > 0)

---

### Planlar

#### `GET /plans`

Tüm planları listele (firm_count dahil)

- **Yetki:** authenticated
- **Auth:** bearer

#### `POST /plans`

Yeni plan oluştur

- **Yetki:** super_admin
- **Auth:** bearer
- **Body:**
  - `code` — string (required, unique)
  - `name` — string (required)
  - `price_monthly` — float
  - `duration_days` — int
  - `max_users` — int (required)
  - `max_customers` — int (required)
  - `max_orders_per_month` — int (required)
  - `can_use_sms` — bool
  - `can_use_webhook` — bool
  - `can_use_reports` — bool
  - `can_use_custom_carpet_types` — bool
  - `can_export_data` — bool

#### `GET /plans/{id}`

Plan detayı

- **Yetki:** authenticated
- **Auth:** bearer

#### `PUT /plans/{id}`

Planı güncelle

- **Yetki:** super_admin
- **Auth:** bearer

#### `DELETE /plans/{id}`

Planı sil (kullanımdaysa engellenir)

- **Yetki:** super_admin
- **Auth:** bearer

---

### Müşteriler

#### `GET /customers`

Müşteri listesi (firma scoped, sayfalı)

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer
- **Query:** `page`, `per_page`, `search`, `city`

#### `GET /customers/search?q=`

Autocomplete arama (max 10 sonuç)

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `POST /customers`

Yeni müşteri oluştur (limit kontrolü vardır)

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer
- **Body:**
  - `first_name` — string (required)
  - `last_name` — string (required)
  - `phone` — string (required)
  - `phone2` — string
  - `email` — string
  - `address` — string
  - `city` — string
  - `district` — string
  - `latitude` — float (-90..90)
  - `longitude` — float (-180..180)
  - `location_note` — string
  - `notes` — string

#### `GET /customers/{id}`

Müşteri detayı

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `PUT /customers/{id}`

Müşteri güncelle

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `DELETE /customers/{id}`

Müşteriyi devre dışı bırak

- **Yetki:** super_admin, firm_admin
- **Auth:** bearer

#### `GET /customers/{id}/orders`

Müşterinin siparişleri

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

---

### Halılar

#### `GET /carpets/types`

Halı türleri ve fiyatları (firma bazlı + yerleşik)

- **Yetki:** authenticated
- **Auth:** bearer

#### `POST /carpets/types`

Özel halı türü ekle

- **Yetki:** firm_admin
- **Auth:** bearer
- **Body:**
  - `label` — string (required)
  - `price` — float (required)
  - `group_name` — string
  - `description` — string

#### `PUT /carpets/types/{key}`

Özel halı türünü güncelle

- **Yetki:** firm_admin
- **Auth:** bearer

#### `DELETE /carpets/types/{key}`

Özel halı türünü sil (kullanımdaysa engellenir)

- **Yetki:** firm_admin
- **Auth:** bearer

#### `GET /carpets`

Halı kayıtları listesi

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `GET /carpets/{id}`

Halı detayı

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `GET /carpets/{id}/barcode`

Barkod SVG (Content-Type: image/svg+xml)

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `GET /customers/{id}/carpets`

Müşterinin halıları

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

---

### Siparişler

#### `GET /orders`

Sipariş listesi (filtre + arama, sayfalı)

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer
- **Query:** `page`, `per_page`, `search`, `status`, `service_type`, `payment_status`, `priority`, `date_from`, `date_to`, `customer_id`

#### `POST /orders`

Yeni sipariş — çoklu halı kalemi (items) destekli. Tek bir sipariş kaydı oluşur, her kalem için quantity kadar halı kaydı (barkod) açılır.

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer
- **Body:**
  - `customer_id` — int (required)
  - `items` — array (required, en az 1 eleman) — her eleman: {carpet_type, width, length, quantity, unit_price?}
  - `items[].carpet_type` — string (required) - halı türü kodu
  - `items[].width` — float (required, m)
  - `items[].length` — float (required, m)
  - `items[].quantity` — int (default: 1) - aynı türden kaç halı
  - `items[].unit_price` — float (opsiyonel; boşsa firma plan fiyatı)
  - `service_type` — string (required) - tüm sipariş için tek servis
  - `pickup_date` — datetime (required)
  - `estimated_delivery` — datetime
  - `priority` — string (normal|urgent|express)
  - `pickup_address` — string
  - `delivery_address` — string
  - `courier_name` — string
  - `courier_phone` — string
  - `notes` — string
- **Yanıt:** Tek order objesi + items[] array (her item için id, carpet_type, carpet_type_label, width, length, quantity, unit_price, line_total, carpet_ids[], barcodes[])

#### `GET /orders/dashboard`

Dashboard istatistikleri (today/week/month)

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `GET /orders/{id}`

Sipariş detayı (status_history + payments dahil)

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `PUT /orders/{id}`

Sipariş güncelle (priority, dates, courier, unit_price)

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `PATCH /orders/{id}/status`

Sipariş durumunu güncelle (webhook tetikler)

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer
- **Body:**
  - `status` — string (required)
  - `note` — string

#### `GET /orders/{id}/history`

Sipariş durum geçmişi

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

---

### Ödemeler

#### `GET /payments`

Ödeme listesi

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `POST /payments`

Ödeme kaydet (kısmi ödeme destekli, fazla ödeme engellenir)

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer
- **Body:**
  - `order_id` — int (required)
  - `amount` — float (required)
  - `payment_method` — string (cash|credit_card|debit_card|bank_transfer|other)
  - `reference_no` — string
  - `note` — string
  - `payment_date` — datetime

#### `GET /payments/{id}`

Ödeme detayı

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `DELETE /payments/{id}`

Ödeme sil (sipariş tutarı yeniden hesaplanır)

- **Yetki:** super_admin, firm_admin
- **Auth:** bearer

#### `GET /orders/{id}/payments`

Siparişin ödemeleri

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

---

### Araçlar (Plakalar)

#### `GET /vehicles`

Firma araçları (plakalar) listesi

- **Yetki:** firm_admin
- **Auth:** bearer
- **Query:** `page`, `per_page`, `search`, `driver_id`
- **Yanıt:** driver_name + driver_phone JOIN dahil

#### `POST /vehicles`

Yeni araç ekle (plaka firma içinde benzersiz, otomatik upper-case)

- **Yetki:** firm_admin
- **Auth:** bearer
- **Body:**
  - `plate` — string (required, max 20) - örn: 34 ABC 123
  - `brand` — string
  - `model` — string
  - `color` — string
  - `year` — int (1990-2030)
  - `capacity_kg` — int - yük kapasitesi kg
  - `driver_id` — int (opsiyonel) - sadece role=driver olan firma kullanıcıları
  - `notes` — string

#### `GET /vehicles/drivers`

Firma şoförleri listesi (vehicle eşleştirme için)

- **Yetki:** authenticated
- **Auth:** bearer

#### `GET /vehicles/{id}`

Araç detayı

- **Yetki:** firm_admin
- **Auth:** bearer

#### `PUT /vehicles/{id}`

Araç güncelle

- **Yetki:** firm_admin
- **Auth:** bearer

#### `DELETE /vehicles/{id}`

Aracı devre dışı bırak (soft delete, is_active=0)

- **Yetki:** firm_admin
- **Auth:** bearer

---

### Hizmet Bölgeleri

#### `GET /service-areas`

Hizmet bölgeleri listesi (firma scoped)

- **Yetki:** authenticated
- **Auth:** bearer
- **Query:** `grouped` (bool (1 ise şehre göre gruplandırılmış object döner))
- **Yanıt:** Düz liste veya {city: [{district, delivery_fee, extra_delivery_days}]}

#### `POST /service-areas`

Tek hizmet bölgesi ekle

- **Yetki:** firm_admin
- **Auth:** bearer
- **Body:**
  - `city` — string (required, max 50)
  - `district` — string (required, max 50)
  - `delivery_fee` — float - bölgeye özel teslim ücreti TL
  - `extra_delivery_days` — int - standart süreye eklenecek gün
  - `notes` — string

#### `POST /service-areas/bulk`

Toplu bölge ekleme (mevcut olanlar atlanır)

- **Yetki:** firm_admin
- **Auth:** bearer
- **Body:**
  - `areas` — array - her eleman: {city, district, delivery_fee?, extra_delivery_days?}
- **Yanıt:** {added: count}

#### `GET /service-areas/{id}`

Bölge detayı

- **Yetki:** firm_admin
- **Auth:** bearer

#### `PUT /service-areas/{id}`

Bölge güncelle

- **Yetki:** firm_admin
- **Auth:** bearer

#### `DELETE /service-areas/{id}`

Bölge sil (hard delete)

- **Yetki:** firm_admin
- **Auth:** bearer

---

### Kullanıcılar

#### `GET /users?firm_id=X`

Firma kullanıcıları

- **Yetki:** super_admin, moderator, firm_admin
- **Auth:** bearer
- **Query:** `firm_id`, `page`, `per_page`, `search`, `role`

#### `GET /users?system_only=1`

Sistem kullanıcıları (super_admin + moderator)

- **Yetki:** super_admin, moderator
- **Auth:** bearer

#### `POST /users`

Yeni kullanıcı oluştur

- **Yetki:** super_admin, firm_admin
- **Auth:** bearer
- **Body:**
  - `name` — string (required)
  - `email` — string (required, unique)
  - `password` — string (required, min 6)
  - `phone` — string
  - `role` — string (super_admin/moderator/firm_admin/employee/driver)
  - `firm_id` — int (sistem rolleri için NULL)
  - `custom_permissions` — array (opsiyonel) - rol varsayılan yetkilerini override eder. Örn: ["customers.view","orders.view"]

#### `GET /users/{id}`

Kullanıcı detayı (custom_permissions JSON dahil)

- **Yetki:** super_admin, moderator, firm_admin
- **Auth:** bearer

#### `PUT /users/{id}`

Kullanıcı güncelle (şifre + custom_permissions dahil)

- **Yetki:** super_admin, firm_admin
- **Auth:** bearer
- **Body:**
  - `name` — string
  - `email` — string
  - `phone` — string
  - `password` — string (opsiyonel, boşsa değişmez)
  - `role` — string
  - `is_active` — bool
  - `custom_permissions` — array | null - null = rol varsayılanına döndür

#### `DELETE /users/{id}`

Kullanıcıyı devre dışı bırak

- **Yetki:** super_admin, firm_admin
- **Auth:** bearer

---

### Ayarlar

#### `GET /settings`

Firma ayarları (gruplu) — company, pricing, notification, system, operation, carpet_prices

- **Yetki:** firm_admin
- **Auth:** bearer
- **Yanıt:** Gruplar: company (firma bilgileri), pricing (KDV/para birimi), notification (SMS şablonları, webhook), system (barkod prefix, varsayılan teslim süresi), operation (yıkama/kurutma süreleri, çalışma saatleri, otomatik durum akışı), carpet_prices (halı türü m² fiyatları)

#### `PUT /settings`

Firma ayarlarını topluca güncelle. Anahtarlar (örnek): default_delivery_days, wash_default_days, drying_default_days, quality_check_required, auto_calculate_delivery_date, working_hours_start, working_hours_end, working_days, order_min_area_m2, sms_template_pickup, sms_template_ready, sms_template_delivered, vat_rate, currency, webhook_url, webhook_secret, barcode_prefix, carpet_price_<type_key>

- **Yetki:** firm_admin
- **Auth:** bearer
- **Body:**
  - `settings` — object {key: value}

#### `GET /settings/{group}`

Belirli grupta ayarları al (operation, notification, vb.)

- **Yetki:** firm_admin, employee, driver
- **Auth:** bearer

#### `GET /admin/settings`

Sistem ayarları (firm_id IS NULL)

- **Yetki:** super_admin, moderator
- **Auth:** bearer

#### `PUT /admin/settings`

Sistem ayarlarını güncelle

- **Yetki:** super_admin
- **Auth:** bearer

---

### Raporlar

#### `GET /reports/revenue`

Gelir raporu (firma scoped)

- **Yetki:** firm_admin
- **Auth:** bearer
- **Query:** `date_from`, `date_to`
- **Yanıt:** summary, daily, by_service, by_customer, payment_methods

---

### Sistem Yönetim

#### `GET /admin/dashboard`

Sistem dashboard (tüm firmalar geneli)

- **Yetki:** super_admin, moderator
- **Auth:** bearer

#### `GET /admin/health`

Sistem sağlık (PHP, MySQL, disk)

- **Yetki:** super_admin, moderator
- **Auth:** bearer

#### `GET /admin/api-endpoints`

API endpoint listesi (admin paneli için)

- **Yetki:** super_admin, moderator
- **Auth:** bearer

---

### Public Endpoints

#### `GET /track?q=`

Sipariş takip (barkod, telefon veya sipariş no ile)

- **Yetki:** public
- **Auth:** none
- **Query:** `q` (string (required, min 3 char))

#### `GET /docs`

Bu API dokümantasyonu (JSON)

- **Yetki:** public
- **Auth:** none

#### `GET /docs.md`

API dokümantasyonu (Markdown)

- **Yetki:** public
- **Auth:** none

---

## Webhook

Sipariş durumu değiştiğinde firma ayarlarındaki webhook_url'ye POST isteği gönderilir.

**Yapılandırma:** Firma Ayarları → Bildirim → webhook_url ve webhook_secret

### İstek Body Örneği
```json
{
    "event": "order.status_changed",
    "order_id": 123,
    "order_no": "SP25042500001",
    "new_status": "pending_delivery",
    "status_label": "Teslim Edilecek",
    "customer": {
        "name": "Ahmet Yılmaz",
        "phone": "05001234567"
    },
    "note": "Yıkama tamamlandı",
    "timestamp": "2026-04-29T18:53:10+00:00"
}
```

### İmza Doğrulama (PHP)
```php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $payload, $webhookSecret);
if (hash_equals($expected, $signature)) {
    $data = json_decode($payload, true);
    // Güvenilir payload
}
```

### Sipariş Durum Kodları

- `pending_pickup` — Teslim Alınacak
- `pending_delivery` — Teslim Edilecek
- `delivered` — Teslim Edildi
- `cancelled` — İptal Edildi

