# Operasyon Yönetimi Modülü — Devir / İlerleme Dosyası

> Bu dosya, "Operasyon Yönetimi" modülünün fazlı geliştirme planını, tamamlanan işleri
> ve kalan fazları (Faz 4–5) başka bir bilgisayarda/başka bir sohbette kaldığı yerden
> sürdürebilmek için özetler. Tarih: 2026-07 itibarıyla.

## Genel Amaç

Acente panelindeki modüller (turlar, satış/rezervasyon, müşteri, tedarikçi, kaynak
yönetimi) birbirine canlı bağlı değildi. Operasyon Yönetimi modülü bu boşluğu doldurur:
rezervasyon almış tur **kalkışlarını** tarih bazında yönetir, kaynak (rehber/araç/kaptan)
atayıp maliyet gösterir, kesin kalkış/iptal işlemlerini yaptırır, rehberle kontrat +
mesajlaşma yürütür ve tüm turları + kaynak planlarını tek bir **Acente Takvimi**'nde birleştirir.
Arayüzler ultra-modern; operasyon modülünün quick-view kartı **slate/grafit (koyu gri)** tema.

## Mimari Kararlar (özet)

1. **Kalkış = polimorfik tur tarihi.** Üç tarih tablosu var: `cultural_tour_dates`,
   `international_tour_dates`, `day_tour_dates` (+ legacy `day_trip_tour_dates`).
   Operasyona özel üst-durum tek polimorfik `tour_operations` tablosunda.
2. **Panoya girme kuralı = ≥1 rezervasyon.** 0 rezervasyonlu kalkışlar ayrı ekranda.
3. **Kaynak ataması mevcut `ResourceSchedule` yapısı üzerinde** (yeni paralel tablo YOK).
   `schedule_type='tour_assignment'` + polimorfik `reference_model` ile tur tarihine bağlı.
4. **Envanter tek serviste** (`DepartureInventoryService`) — idempotent yeniden sayım.
5. **Kontrat = şablon + üretilen örnek** (Faz 4): mevcut `agency_contracts` (Quill HTML)
   genişletilir; atama başına render edilmiş örnek `resource_contracts`'ta.
6. **Ev-stiline sadık**: `byAgency` scope, `authorizeAgency`/manual agency guard, soft delete,
   `created_by/updated_by` boot hook, quick-view drawer + inline `updateField` whitelist,
   Quill+CodeMirror editör, FullCalendar takvim.
7. **URL'ler İngilizce** (`agency/operations`, `agency/calendar`). Migration'ları KULLANICI çalıştırır.

---

## TAMAMLANAN FAZLAR

### ✅ Faz 0 — Envanter + Satış↔Kalkış bağı
- **Migration:** `database/migrations/2026_07_09_000001_add_tour_date_link_to_sales_table.php`
  → `sales.tour_date_type` + `sales.tour_date_id` (polimorfik kalkış bağı).
- **`app/Models/Sale/Sale.php`**: fillable'a `tour_date_type/id`, `tourDate()` morphTo.
- **`app/Services/Operation/DepartureInventoryService.php`**: satışlardan idempotent
  yeniden sayım; tip başına kolon adaptörü (cultural/international: sold_count/reserved_count/
  remaining_capacity; day_tour: booked_seats/reserved_seats/available_seats; day_trip: sold_count).
  Ayrıca `PRODUCTABLE_DATE_MAP`, `FRONTEND_TYPE_MAP`, `resolve()`, `recount()`, `recountForSale()`.
- **`app/Http/Controllers/Agency/SalesController.php`**: `syncDeparture()`, `refreshDeparture()`
  helper'ları; store/update/confirm/cancel/destroy/bulkDelete entegrasyonu.
  **Bug fix:** `getProductDates()` içine eksik **InternationalTour** branch'i eklendi
  (yanlış tablodan tarih çekiyordu). **Bug fix:** `TourController::bulkDelete` `exists:tours,id`
  → `exists:cultural_tours,id`.

### ✅ Faz 1 — Operasyon Panosu
- **Migration:** `2026_07_09_000002_create_tour_operations_table.php`
  (`operatable_type/id` unique, `departure_status`: collecting/guaranteed/departed/completed/cancelled,
  `guaranteed_mode` auto|manual, `guaranteed_at/by`, iptal alanları, `operation_notes`).
- **`app/Models/Operation/TourOperation.php`** (polimorfik operatable, statü label'ları).
- **`app/Services/Operation/OperationService.php`**: kalkış normalizasyonu (capacityOf,
  startDateOf/endDateOf, tourOf, meta), `TYPE_META` (cultural/international/day_tour/day_trip
  renk+etiket+ilişki), lazy `forDate`, `evaluate()` (otomatik kesin kalkış eşiği), `markGuaranteed`,
  `cancelDeparture`, `completeDeparture`, `badgeState()`, `classForKey/resolveByKey`.
- **`app/Http/Controllers/Agency/Operation/OperationController.php`**: index (pano),
  pending (0 rezervasyon), show, quickView, updateField, setGuaranteed, cancel, complete,
  confirmService; `collectDepartures`/`hydrateDepartures`/`buildVM`; filtre/sıralama/istatistik;
  aktif filtre listesi + kart/tablo görünüm tercihi (cookie `ops_view`).
- **Rotalar** (`routes/agency.php`): `operations` prefix, `{type}/{id}` alt grubu
  (`type` ∈ cultural|international|day_tour|day_trip).
- **Görünümler** (`resources/views/agency/operations/`): `index`, `pending`, `show`,
  `quick-view`, `_card`, `_table`, `_styles`, `_filterbar-styles`.
- **`resources/views/components/quick-view-drawer.blade.php`**: `theme-operations` (slate) eklendi.
- **Sidebar**: "Operasyon Yönetimi" menüsü.
- **Kesin kalkış rozetleri:** Otomatik (robot) / Manuel (el) / "Rezervasyon Var·Kesin Değil" (amber) / İptal.
- **Gelişmiş filtre:** custom aranabilir select, tanımlı tur filtresi, doluluk, tarih aralığı,
  hızlı tarih çipleri (bu hafta/ay/30 gün), yüzen aktif-filtre çubuğu, canlı arama (Türkçe/harf duyarsız).
- **Ciro ayrımı:** özet şeritte "Kesin Ciro" (confirmed/in_progress/completed) + "Potansiyel Ciro"
  (potential/offer_sent/revised) ayrı gösterilir.
- **Tur adı tıklanınca** turun ürün quick-view kartı açılır (kart/tablo/quick-view/show).
- **Tablo görünümü** zorunlu `x-data-table` component'i ile.

### ✅ Faz 2 — Kaynak Ataması + Maliyet
- **Migration:** `2026_07_09_000003_add_assignment_fields_to_resource_schedules_table.php`
  → `resource_schedules`'e `role`, `assignment_status`, `agreed_cost`, `cost_type`, `currency`,
  `contract_id` (mevcut yapı genişletildi; yeni tablo yok).
- **`app/Models/ResourceSchedule.php`**: yeni alanlar fillable/cast; `tourAssignments()`,
  `forReference()` scope; `cost_type_label`/`assignment_status_label`; **`effective_role`**
  (rol boşsa kaynağın tipinden) ve **`effective_cost`** (ücret boşsa kaynağın cost_daily'sinden) accessor'ları.
- **`app/Services/Operation/ResourceAssignmentService.php`**: `assignmentsFor`, `totalCost`,
  `assign` (ResourceSchedule tour_assignment satırı + tarih modelindeki assigned_guide_id/
  assigned_vehicle_id/vehicle_id senkronu), `remove`, `assignableResources` (**gün + saat bazlı
  çakışma kontrolü**: taraflardan biri çok günlükse veya saat yoksa tam gün; ikisi de tek günlük+saatliyse
  saat kesişimi), `schedulesConflict`.
- **`app/Http/Controllers/Agency/Operation/OperationResourceController.php`**: assign/remove/available.
- **Rotalar:** `operations/{type}/{id}/resources` (available GET, assign POST, remove DELETE).
- **OperationController**: show/quickView'e atama listesi + atanabilir kaynaklar + maliyet;
  pano bulk-load'ta `COALESCE(agreed_cost, resources.cost_daily, 0)` ile kaynak sayısı/maliyet.
- **Görünümler:** show.blade kaynak paneli (atanmışlar + maliyet özeti [Toplam Maliyet/Kesin Ciro/Net]
  + "Kaynak Ata" paneli: rol sekmeleri + arama + müsaitlik rozeti + düzenlenebilir ücret);
  _card ve _table'da kaynak/maliyet çipi; quick-view'de atanan kaynaklar bölümü.
- **Seeder:** `CalendarPopulatorSeeder` atamalarına role/agreed_cost/cost_type eklendi.
- **Not:** Atamalar kaynağın takviminde görünür ve müsaitliği bloke eder (ResourceSchedule
  `isAvailableOn`/`inDateRange` + `ResourceController@events` tour_assignment case).

### ✅ Faz 3 — Acente Takvimi
- **`app/Http/Controllers/Agency/AgencyCalendarController.php`**: index + events (FullCalendar JSON):
  tüm tur kalkışları (cultural/international/day_tour) + tüm ResourceSchedule kayıtları birleşik;
  filtreler (source: all/tours/resources, tour_type, resource_type, schedule_type, resource_id).
- **Rotalar:** `agency/calendar` (index) + `agency/calendar/events`.
- **Sidebar**: "Panel" altına "Acente Takvimi".
- **Görünüm:** `resources/views/agency/calendar/index.blade.php` — FullCalendar 6.1.15 (CDN),
  ay/hafta/gün/liste, slate tema, **yatay açılır filtre bar** (tercih localStorage), custom select'ler.
  Etkinlik tıklama → operasyon/kaynak quick-view.
- **Reusable partial:** `resources/views/agency/partials/custom-select.blade.php` — operasyondaki
  görünümle aynı, `select.js-cselect`'i aranabilir custom select'e çevirir; `change` event dispatch eder.
- **Kaynak takvimleri yatay/açılır yapıldı:** `resources/views/agency/resources/calendar.blade.php`
  (yatay "Filtreler" + ayrı açılır "Bloke/İzin Ekle" + custom select), `resources/views/agency/resources/my-schedule.blade.php`
  (yatay üst şerit, tablo tam genişlik).

### Ek Seeder Güncellemeleri
- **`SaleSeeder`**: satışları gerçek tur tarihlerine (`tour_date_id/type`) bağlar; envanteri
  yeniden sayar + operasyon durumu değerlendirir; vitrin için bir manuel kesin kalkış + bir iptal.
- **`TourSeeder` / `InternationalTourSeeder`**: kalkış tarihlerine `min_participants`=10 + `auto_confirm`=true.

### ⚠️ Çalıştırılacak Migration'lar (KULLANICI)
```
php artisan migrate
```
Üç yeni migration: sales kalkış alanları, tour_operations, resource_schedules atama alanları.
Temiz demo verisi için: `php artisan migrate:fresh --seed`.

---

## KALAN FAZLAR

> **GÜNCELLEME (2026-07-09):** Faz 4 tamamlandı. Faz 5, kullanıcı isteğiyle
> "atama-bazlı rehber mesajlaşması" yerine **sistem geneli global mesajlaşma
> widget'ı** olarak uygulandı (tüm kullanıcılar birbirine metin + dosya/resim
> gönderebilir; sağ-alt avatar rayı, hover'da genişleyen liste, geçmiş paneli,
> ~2.5–4 sn polling ile canlı — soket altyapısı olmadığından). Detaylar aşağıda
> "TAMAMLANAN — Faz 4 & Global Chat" bölümünde.

### ✅ Faz 4 — Rehber Kontrat Şablonu Motoru (merge fields + rich editor)

**Amaç:** Kullanıcı bir kontrat **şablonu** hazırlar; içine `{{token}}` birleştirme alanları
gömer (bazıları otomatik, bazıları manuel doldurulur). Bir rehber atamasından bu şablon
**otomatik doldurulup** kontrat örneği üretilir, Quill ile düzenlenip gönderilir.

**Veri modeli:**
- `agency_contracts` genişlet: `context` enum (`customer`|`guide`|`general`, default general),
  `merge_fields` json (`[{token,label,mode:'auto'|'manual',bind}]`). (Migration + `AgencyContract` model.)
- Yeni tablo **`resource_contracts`**: `id, agency_id, assignment_id (FK — tour_resource_assignments
  yerine resource_schedules.id kullan çünkü Faz 2 ResourceSchedule üzerinden ilerledi; `schedule_id`
  olarak adlandır), template_id (FK agency_contracts nullable), resource_id, title, content (longText
  render edilmiş HTML), merge_data (json snapshot), status (draft/sent/signed/declined), sent_at,
  signed_at, pdf_path, audit + softDeletes`.
- `resource_schedules.contract_id` (Faz 2'de eklendi) → üretilen kontrata bağla.

**Şablon tarafı:** mevcut `ContractController` + `agency.settings.contracts.*` genişlet (guide context).
Editöre (`resources/views/agency/settings/contracts/partials/multilang-editor.blade.php` deseni)
**"Alan Ekle" (merge field) menüsü**: `{{rehber.ad_soyad}}`, `{{tur.ad}}`, `{{gorev.baslangic}}`,
`{{gorev.bitis}}`, `{{ucret.tutar}}`, `{{ucret.para_birimi}}`, `{{acente.ad}}` vb. tokenlar.
Her token için mod (auto/manual) → `agency_contracts.merge_fields`.

**Üretim tarafı:** `app/Services/Operation/ContractRenderService.php`:
`render(AgencyContract $template, ResourceSchedule $assignment, array $manualValues)` → auto tokenları
atama/kaynak/tur/tarih/ücret verisinden doldurur, manuel tokenları `$manualValues`'tan;
sonucu `resource_contracts.content` + `merge_data` snapshot. Quill ile son düzenleme, önizleme, (ops.) PDF.

**Rotalar (operasyon altında):**
- `POST operations/{type}/{id}/resources/{schedule}/contract` → şablondan üret
- `GET/PUT resource-contracts/{contract}` → görüntüle/düzenle
- `POST resource-contracts/{contract}/send` → durum `sent`

**UI:** show.blade kaynak satırındaki her atamaya "Kontrat" butonu → şablon seç + manuel alanları
doldur → üret → Quill'de düzenle → gönder. Reusable Quill editör (projede zaten var, `#quillEditor`).

### ✅ Faz 5 — (Yeniden kapsamlandı) Global Mesajlaşma Widget'ı

**Ön koşul:** kaynağın bağlı `user_id`'si (`Resource.user_id`).

**Veri modeli:** `resource_messages`: `id, agency_id, schedule_id (nullable, atama), resource_id,
sender_type (agency_user|resource_user), sender_id, body, attachment_path, contract_id (nullable),
read_at, audit`.

**Controller:** `ResourceMessageController` — atama/kaynak bazlı thread; mesaj gönder, kontrat iliştir,
okundu. Basit polling (projede broadcast yok).

**UI:** operasyon show/quick-view'de atama başına mesaj paneli; rehber tarafı görünümü (bağlı user
kendi panelinde atandığı görevler + kontratlar + mesajlaşma). Kontrat üzerinde anlaşma →
`resource_contracts.status = signed`.

**Uygulama sırası:** Faz 4 → Faz 5. Faz 5 en fazla belirsizliği taşır; Faz 4 bitince kullanıcı ile netleştir.

---

---

## TAMAMLANAN — Faz 4 & Global Chat (2026-07-09 oturumu)

### Faz 4 — Rehber Kontrat Şablonu Motoru
- **Migration:** `2026_07_10_000001_add_context_and_merge_fields_to_agency_contracts.php`
  (`context` enum-benzeri string default `general`; `merge_fields` json) +
  `2026_07_10_000002_create_resource_contracts_table.php`.
- **Model:** `app/Models/Operation/ResourceContract.php` (softDeletes, status draft/sent/signed/declined,
  status label/color accessor, audit boot). `AgencyContract` genişletildi (context/merge_fields fillable+cast,
  `CONTEXTS`, `scopeForGuide`, `resourceContracts()`). `ResourceSchedule`'a `contract()`/`contracts()` ilişkisi.
- **Servis:** `app/Services/Operation/ContractRenderService.php` — `TOKENS` kataloğu (auto/manual),
  `catalog()`, `defaultGuideMergeFields()`, `extractMergeFields()`, `render($template,$assignment,$manual)`
  (rehber/tur/görev/ücret/acente tokenlarını doldurur, manuel alanlar için serbest giriş).
- **Şablon tarafı:** `ContractController` store/update'e `context` + otomatik `merge_fields` türetme.
  `multilang-editor.blade.php`'ye **Kontrat Bağlamı** pill seçici + editör başlığına **"Alan Ekle"**
  (birleştirme alanı) menüsü (Quill/CodeMirror imlecine `{{token}}` ekler).
- **Üretim tarafı:** `ResourceContractController` (options/generate/show/update/send/status/destroy).
  Rotalar: `operations/{type}/{id}/resources/{schedule}/contract(+/options)` ve `resource-contracts/{id}` grubu.
- **UI:** `operations/show.blade.php` her atama satırına **Kontrat** butonu (+ durum rozeti) ve
  `partials/contract-modal.blade.php` (şablon seç → manuel alan doldur → üret → Quill'de düzenle → Kaydet/Gönder).
- **Seeder:** her acenteye varsayılan **"Rehber Hizmet Sözleşmesi"** (context=guide, tokenlı) eklenir.

### Global Mesajlaşma (Faz 5 yerine)
- **Migration'lar:** `2026_07_10_000003..000006` — `conversations` (dm/group, `dm_key` unique),
  `conversation_participants` (last_read_at/cleared_at/is_muted), `messages` (body, has_attachments),
  `message_attachments` (path/original_name/mime/size/kind).
- **Modeller:** `app/Models/Chat/{Conversation,ConversationParticipant,Message,MessageAttachment}.php`.
  `Conversation::findOrCreateDm()` idempotent 1-1 sohbet.
- **Controller:** `app/Http/Controllers/Agency/ChatController.php` — contacts (tüm aktif kullanıcı arama),
  conversations, poll, start, messages (after_id artan), store (metin+ekler, public disk `chat/{id}`),
  markRead. Rotalar: `agency/chat/*`.
- **UI:** `resources/views/agency/layouts/partials/chat-widget.blade.php` — app.blade'e `@auth` ile dahil.
  Sağ-alt cam efektli avatar rayı, `:hover` genişleyen sohbet listesi, sohbet penceresi (optimistic gönderim,
  görsel/dosya önizleme, sürükle-bırak), **geçmiş/kişiler** overlay'i (rayın altındaki saat butonu).
  Canlılık: açık sohbet 2.5 sn, global liste 4 sn polling. **Not:** Reverb/Echo eklenirse polling
  yerine broadcast'e geçilebilir (transport JS'te ayrık).
- **recent-sidebar çakışması:** Chat rayı bilinçli olarak **sağ-alt** köşeye kondu (recent-sidebar'ın
  sağ-orta tetik sekmesiyle çakışmaz).

### ⚠️ Çalıştırılacak (KULLANICI)
```
php artisan migrate          # 6 yeni tablo/alan (kontrat + chat)
php artisan db:seed --class=ContractSeeder   # (ops.) mevcut acentelere rehber şablonu ekler
php artisan storage:link     # ek/logolar için (zaten varsa atla)
```
Temiz demo verisi için: `php artisan migrate:fresh --seed`.

---

## Güncelleme: Başlıklı Maliyet Yapısı (17.07.2026)
Maliyetler sabit kolonlar yerine serbest başlıklı, çok satırlı bir yapıya taşındı.
Yukarıdaki Faz 2 notlarında geçen `cost_type` (Günlük/KM/Sabit) ve `resources.cost_daily` /
`cost_per_km` açıklamaları **artık geçerli değil**:
- `resources.cost_daily` / `cost_per_km` **düşürüldü** → `resource_cost_items` (resource_id, title,
  amount, currency, sort_order). Bir kaynak birden fazla başlık taşır (ör. "Sigorta Maliyeti").
- Atama başına maliyet → `resource_schedule_cost_items`. Kaynağın varsayılan başlıkları atama
  anında kopyalanır, kalkışa özel düzenlenebilir (`ResourceAssignmentService::resolveCostItems`).
- `resource_schedules.agreed_cost` **korundu**: başlıkların toplamı olarak senkron tutulur
  (`ResourceSchedule::syncAgreedCost`), böylece pano SQL'i ve kontrat şablonları tek alandan okur.
  `cost_type` kolonu kullanımdan kalktı (kolon şemada duruyor, artık yazılmıyor).
- Kontrat token'ı `{{ucret.tur}}` artık başlık listesini basar; tutarlı döküm için
  `{{ucret.kalemler}}` eklendi.

## Kapsam Dışı / Not
- Legacy `App\Models\Product\DayTripTour*` ile `App\Models\DayTour\*` ikiliği birleştirilmedi
  (operasyon her ikisini de okuyabilir).
- Eksik InternationalTour alt-policy'leri / DayTour policy'leri — operasyon `authorize()` yerine
  manuel agency guard kullandığı için gerekmedi.
- Ödeme ledger'ı bu modül kapsamı dışında.

## Anahtar Dosya Konumları
- Servisler: `app/Services/Operation/` (DepartureInventoryService, OperationService, ResourceAssignmentService, [Faz4] ContractRenderService)
- Modeller: `app/Models/Operation/TourOperation.php`, `app/Models/ResourceSchedule.php`, `app/Models/Sale/Sale.php`
- Controller'lar: `app/Http/Controllers/Agency/Operation/{OperationController,OperationResourceController}.php`, `app/Http/Controllers/Agency/AgencyCalendarController.php`
- Görünümler: `resources/views/agency/operations/*`, `resources/views/agency/calendar/index.blade.php`, `resources/views/agency/partials/custom-select.blade.php`
- Rotalar: `routes/agency.php` (operations + calendar grupları)
- Sidebar: `resources/views/agency/layouts/partials/sidebar.blade.php`
