# USER POS ACCESS — Erişim ve POS Seçim Mekanizması

Bu doküman, `ViewServiceProvider.php` (satır 34) üzerinden okunan **`user_pos_access`** yapısının
nasıl çalıştığını; kullanıcı yetkilendirmesini, POS (işletme) seçimini ve seçilen POS'un tüm
uygulama genelinde veri filtrelemesini nasıl etkilediğini açıklar.

---

## 1. Genel Bakış

`posmanager` uygulaması çok kiracılı (multi-tenant) bir yapıdadır. Her kullanıcı, takımı
(`current_team_id`) altında birden fazla **erişim kaydına** (`user_accesses`) sahip olabilir.
Bu kayıtlar iki temel amaç için kullanılır:

1. **Yetkilendirme (Authorization):** Kullanıcının `posmanager` aracını kullanıp kullanamayacağı.
2. **POS Seçimi (Data Scoping):** Kullanıcının hangi POS işletmesinin (`service_id`) verilerini
   göreceği. Uzak (`mysql-remote`) POS veritabanındaki tüm sorgular bu seçime göre filtrelenir.

`user_pos_access` yapısı, `type = 'pos'` olan erişim kayıtlarını ifade eder ve kullanıcının
seçebileceği POS işletmelerinin listesini oluşturur.

---

## 2. Veri Modeli

### 2.1 `UserAccess` Modeli

`app/Models/UserAccess.php`

```php
class UserAccess extends Model
{
    protected $table = 'user_accesses';

    protected $fillable = [
        'is_active', 'is_archived', 'type', 'service_id',
        'access_level', 'note', 'current_team_id', 'user_id',
    ];

    protected static function booted()
    {
        static::addGlobalScope(new CurrentTeamScope);
    }
}
```

| Alan              | Açıklama                                                            |
| ----------------- | ------------------------------------------------------------------- |
| `user_id`         | Erişimin sahibi kullanıcı.                                          |
| `current_team_id` | Takım (tenant) kimliği. Global scope ile otomatik filtrelenir.      |
| `type`            | Erişim tipi: `pos`, `posmanager` veya diğer araç tipleri.           |
| `service_id`      | `type = 'pos'` için uzak POS veritabanındaki `companies.id` değeri. |
| `access_level`    | Yetki seviyesi (örn. `admin`).                                      |
| `note`            | Kullanıcıya gösterilen insan-okunur işletme adı/etiketi.            |
| `is_active`       | Kaydın aktif olup olmadığı.                                         |
| `is_archived`     | Kaydın arşivlenip arşivlenmediği.                                   |

### 2.2 `CurrentTeamScope` (Global Scope)

`app/Models/Scopes/currentTeamScope.php`

```php
public function apply(Builder $builder, Model $model): void
{
    if (auth()->check()) {
        $builder->where('current_team_id', auth()->user()->currentTeam->id);
    }
}
```

Bu scope sayesinde tüm `UserAccess` sorguları **yalnızca aktif takımın** kayıtlarını döndürür.
Kullanıcı takım değiştirdiğinde erişim listesi de otomatik olarak değişir.

### 2.3 `User` İlişkisi

`app/Models/User.php`

```php
public function userAccesses()
{
    return $this->hasMany(UserAccess::class);
}
```

Bir kullanıcının birden çok erişim kaydı olabilir (örneğin birkaç farklı POS işletmesi + bir
`posmanager` yetkisi).

---

## 3. Erişim Tipleri

| `type`       | Amaç                                                                                         |
| ------------ | -------------------------------------------------------------------------------------------- |
| `posmanager` | Uygulamaya giriş yetkisi. `CheckUserAccess` middleware'i bu kaydın varlığını zorunlu kılar.  |
| `pos`        | Seçilebilir bir POS işletmesi. `service_id` uzak POS `companies.id` değerine karşılık gelir. |
| diğer        | `pos` harici araç erişimleri (`$user_accesses` içinde toplanır).                             |

> **Önemli:** Ne `posmanager` ne de `pos` erişimi takımdan **kalıtılmaz**. Her takım üyesinin
> kendi `user_accesses` satırlarına açıkça sahip olması gerekir. Aksi halde kullanıcı ya 403
> hatası alır ya da POS listesi boş görünür.

---

## 4. ViewServiceProvider ile Veri Hazırlama

`app/Providers/ViewServiceProvider.php` — `view()->composer('*', ...)` her view render'ında
çalışır ve tüm görünümlere erişim verilerini enjekte eder.

```php
if (Auth::check()) {
    $is_admin          = auth()->user()->userAccesses()->where('type', 'posmanager')->where('access_level', 'admin')->count();
    $user_accesses     = auth()->user()->userAccesses()->where('type', '!=', 'pos')->get();
    $user_pos_accesses = auth()->user()->userAccesses()->where('type', 'pos')->get();   // <-- satır 34
    ...
}
```

- **`$user_pos_accesses`** → `type = 'pos'` olan tüm kayıtlar. POS seçim menüsünü oluşturur.
- **`$user_accesses`** → `pos` harici tüm kayıtlar.
- **`$is_admin`** → `posmanager` + `access_level = admin` kayıt sayısı (yönetici kontrolü).

### 4.1 Varsayılan POS Seçimi

```php
// ID'si 6 olmayan ilk pos access'i bul
$default_pos_access = $user_pos_accesses->where('service_id', '!=', 6)->first();
if (!$default_pos_access) {
    $default_pos_access = $user_pos_accesses->first();
}
```

`service_id = 6` özel/demo bir işletme olarak kabul edilir ve varsayılan seçimde **atlanır**.
Eğer kullanıcının `6` dışında hiçbir POS'u yoksa, ilk kayıt (yani `6`) kullanılır.

### 4.2 Session Başlatma (İlk Yükleme)

```php
$selected_pos_access = session('selected_pos_access')
    ? session('selected_pos_access')
    : ($default_pos_access ? $default_pos_access->service_id : null);

if (!session('selected_pos_note') && $default_pos_access) {
    session(['selected_pos_note' => $default_pos_access->note]);
}

if (!session('selected_pos_service_id') && $default_pos_access) {
    session(['selected_pos_service_id' => $default_pos_access->service_id]);
    session(['selected_pos_access'     => $default_pos_access->service_id]);
}
```

Kullanıcı authenticate olmuşsa ancak session'da POS seçimi yoksa, varsayılan POS session'a yazılır.

---

## 5. Session Anahtarları

| Session Anahtarı          | İçerik                                             | Kullanım Alanı                 |
| ------------------------- | -------------------------------------------------- | ------------------------------ |
| `selected_pos_service_id` | Seçili POS'un `service_id` (= uzak `companies.id`) | Tüm veri filtreleme scope'ları |
| `selected_pos_access`     | Seçili POS'un `service_id`                         | View'da aktif POS vurgusu      |
| `selected_pos_note`       | Seçili POS'un `note` (işletme adı)                 | Navigasyon çubuğunda gösterim  |

---

## 6. POS Değiştirme

### 6.1 Route

`routes/web.php`

```php
Route::get('/set-pos-access/{service_id}', function ($service_id) {
    $pos_access = auth()->user()->userAccesses()
        ->where('type', 'pos')
        ->where('service_id', $service_id)
        ->first();

    if ($pos_access) {
        session(['selected_pos_service_id' => $service_id]);
        session(['selected_pos_access'     => $service_id]);
        session(['selected_pos_note'       => $pos_access->note]);
    }

    return redirect()->back();
})->name('set-pos-access');
```

Kullanıcının **yalnızca kendi** `pos` erişimine sahip olduğu bir `service_id` seçmesine izin verilir
(sorgu `userAccesses()` üzerinden geçtiği için başka bir kullanıcının POS'u seçilemez).

### 6.2 Kullanıcı Arayüzü

`resources/views/partials/navigation-bar.blade.php`

- POS seçim menüsü **yalnızca `dashboard` route'unda** etkileşimlidir.
- Diğer sayfalarda seçili POS **salt-okunur** olarak gösterilir
  (`title="POS değişikliği sadece Dashboard'da yapılabilir"`).
- Menü, `$user_pos_accesses` üzerinde dönerek her kayıt için `set-pos-access` linki üretir ve
  etiket olarak `note` alanını gösterir.

---

## 7. Yetkilendirme (CheckUserAccess Middleware)

`app/Http/Middleware/CheckUserAccess.php` — `Kernel.php` içinde `check.user.access` alias'ı ile
kayıtlıdır ve korunan tüm route grubuna uygulanır.

```php
$hasAccess = UserAccess::where('current_team_id', $teamId)
    ->where('user_id', $user->id)
    ->where('type', 'posmanager')
    ->exists();

if (! $hasAccess) {
    return response()->json(['error' => 'Bu aracı kullanma yetkiniz yok.'], 403);
}
```

- Uygulamaya erişim için `type = 'posmanager'` kaydı **zorunludur**.
- Bu middleware `pos` erişimini kontrol **etmez**; `pos` yalnızca veri kapsamı (scoping) içindir.

---

## 8. Seçili POS'un Veri Filtrelemesi

`selected_pos_service_id`, uzak (`mysql-remote`) POS modellerinde global scope veya
`scopeForSelectedPos()` ile otomatik filtreleme için kullanılır.

Örnek — `PosCompanyCustomer`:

```php
protected static function booted()
{
    static::addGlobalScope('selected_pos', function (Builder $builder) {
        $selectedPosServiceId = session('selected_pos_service_id');
        if ($selectedPosServiceId) {
            $builder->where('company_id', $selectedPosServiceId);
        }
    });
}
```

Örnek — `StockItem` / `PosCompany` / `PosUser` (`scopeForSelectedPos`):

```php
public function scopeForSelectedPos(Builder $query)
{
    $selectedPosServiceId = session('selected_pos_service_id');
    if ($selectedPosServiceId) {
        return $query->where('company_id', $selectedPosServiceId); // PosCompany'de 'id'
    }
    return $query;
}
```

Böylece seçilen POS işletmesi, stoklardan satışlara kadar tüm uzak verinin görünürlüğünü belirler.

---

## 9. Akış Diyagramı

```mermaid
graph TD
    A[Kullanici istegi] --> B{Auth::check}
    B -->|Hayir| C[ Bos erisim listeleri ]
    B -->|Evet| D[ CheckUserAccess: type=posmanager var mi ]
    D -->|Yok| E[ 403 Yetkisiz ]
    D -->|Var| F[ ViewServiceProvider composer ]
    F --> G[ user_pos_accesses = type=pos kayitlari ]
    G --> H{ Session selected_pos_service_id var mi }
    H -->|Yok| I[ Varsayilan POS: service_id != 6 ilk kayit ]
    I --> J[ Session'a yaz ]
    H -->|Var| K[ Mevcut secimi kullan ]
    J --> L[ View render + navigasyon menusu ]
    K --> L
    L --> M[ Uzak POS modelleri selected_pos_service_id ile filtrelenir ]
    M --> N[ Kullanici set-pos-access ile POS degistirebilir ]
    N --> J
```

---

## 10. Bilinen Noktalar ve Dikkat Edilmesi Gerekenler

1. **Takımdan kalıtım yok:** Her kullanıcı için `posmanager` ve `pos` erişim satırları ayrı ayrı
   oluşturulmalıdır. Yeni takım üyeleri aksi halde 403 alır veya boş POS listesi görür.
2. **Session başlatma tutarsızlığı:** `selected_pos_service_id` temelde `/dashboard` route'unda ve
   `ViewServiceProvider` composer'ında başlatılır. `/dashboard`'a uğramadan doğrudan başka bir
   sayfaya (örn. `/integrations/eadisyon`) gidilirse session boş kalabilir ve veriler
   filtrelendiği için **boş liste** görünebilir.
3. **`service_id = 6` istisnası:** Varsayılan POS seçiminde atlanır; yalnızca başka seçenek
   yoksa kullanılır. Bu değerin ne anlama geldiği (demo/özel işletme) iş kuralı olarak sabittir.
4. **POS değişimi sadece Dashboard'da:** Diğer sayfalarda seçim salt-okunurdur.
5. **Global scope zinciri:** `UserAccess` → `CurrentTeamScope` (takım filtresi),
   uzak POS modelleri → `selected_pos` scope (işletme filtresi). İkisi birlikte veri izolasyonunu sağlar.

---

## 11. İlgili Dosyalar

| Dosya                                               | Rol                                              |
| --------------------------------------------------- | ------------------------------------------------ |
| `app/Models/UserAccess.php`                         | Erişim kaydı modeli                              |
| `app/Models/User.php`                               | `userAccesses()` ilişkisi                        |
| `app/Models/Scopes/currentTeamScope.php`            | Takım bazlı global scope                         |
| `app/Providers/ViewServiceProvider.php`             | View composer, `$user_pos_accesses` üretimi      |
| `app/Http/Middleware/CheckUserAccess.php`           | `posmanager` yetki kontrolü                      |
| `app/Http/Kernel.php`                               | `check.user.access` alias kaydı                  |
| `routes/web.php`                                    | `set-pos-access` route'u, dashboard session init |
| `resources/views/partials/navigation-bar.blade.php` | POS seçim menüsü                                 |
| `app/Models/PosCompany.php`, `StockItem.php`, vb.   | `selected_pos_service_id` ile veri filtreleme    |
