Files
Emre Emir 8bbc9dbff2 Initial commit: DLS - Dental Lab System
- Flutter + PocketBase dental lab management system
- Clinic & lab dashboards, job tracking, patient management
- Product catalog, finance tracking, multi-language support
- AI assistant integration, realtime notifications
- Windows installer (Inno Setup) included
- Developed by kovakyazilim.com
2026-06-11 15:57:31 +03:00

14 KiB
Raw Permalink Blame History

DLS — Veritabanı Referansı (Directus)

Backend: Appwrite → Directus migrasyonu. Kaynak gerçek bu dokümandır. Oluşturulma: 2026-06-05 (Directus MCP ile canlıdan inşa edildi).

1. Bağlantı bilgileri

Değer
Directus URL (Coolify'da set edilecek)
Auth Directus built-in JWT (email + şifre)
Storage Directus Files (directus_files)
Tenant izolasyonu tenants + tenant_members tabloları

Appwrite → Directus eşlemesi:

  • Appwrite Teamtenants koleksiyonu
  • Appwrite Team membershiptenant_members koleksiyonu
  • Appwrite Authdirectus_users (Directus built-in)
  • Appwrite Storage bucketsdirectus_files (tek bucket, kind alanıyla ayrım)
  • Row-level security → Directus policies + $CURRENT_USER + Flutter tarafında tenant_id filtresi

2. Uygulama özeti (DLS — Dental Lab System)

Diş klinikleri ↔ diş laboratuvarları arasındaki protez iş alışverişini dijitalleştirir.

  • Klinik bir hasta için protez işi açar → bağlı laboratuvara yollar.
  • Lab gelen kutusundan görür, durum adımlarını işler: Ölçü → Alt Yapı Prova → Üst Yapı Prova → Cila/Bitim.
  • Tamamlanınca iş sent → klinik teslim alınca delivered.
  • Her iki taraf finansal akışı kendi defterinde izler.

3. Multi-tenancy & yetki modeli

  • Tenant = tenants satırı. Her tenant'ın bir kind'ı var: clinic veya lab.
  • Kullanıcıtenant_members üzerinden bir veya birden fazla tenant'a bağlanır.
  • member_number (12 hane, unique): tenant'ın bağlantı kodu. Login'de kullanılmaz; sadece iki tenant'ı eşlemek için.
  • Flutter tarafında her sorguda tenant_id (veya clinic_tenant_id/lab_tenant_id) filtresi zorunlu.
  • Cross-tenant tablolar (connections, jobs, job_files, job_status_history): hem clinic_tenant_id hem lab_tenant_id taşır — iki taraf da erişir.

4. Koleksiyonlar (17)

Notasyon: IDX=index, UNQ=unique, FK=foreign key, DEF=default. Tüm tablolarda sistem alanları: id (uuid PK), date_created, date_updated (varsa).

tenants — tenant profili (Appwrite Team karşılığı)

Alan Tip Not
id uuid PK
kind enum[lab|clinic] tenant türü · IDX
member_number string(12) bağlantı kodu · UNQ
company_name string(255)
company_tax_id string(50)
company_address text
company_email string(255)
company_phone string(30)
logo uuid directus_files
default_currency string(8) DEF: TRY
status enum[active|suspended] DEF: active

tenant_members — kullanıcı ↔ tenant üyeliği

Alan Tip Not
id uuid PK
tenant_id uuid tenants CASCADE · IDX
user_id uuid directus_users CASCADE
role enum[owner|admin|member] DEF: member

profiles — kullanıcı başına ek bilgi

Alan Tip Not
id uuid PK
tenant_id uuid tenants
user_id uuid directus_users
display_name string(255)
phone string(30)
title string(100)

connections — iki tenant arası bağlantı

Alan Tip Not
id uuid PK
clinic_tenant_id uuid tenants · IDX
lab_tenant_id uuid tenants · IDX
status enum[pending|approved|rejected] DEF: pending · IDX
requested_by uuid directus_users
requested_at timestamp
approved_at timestamp
rejected_at timestamp

Unique constraint: (clinic_tenant_id, lab_tenant_id) — aynı çift iki kez bağlanamaz. Flutter tarafında kontrol edilmeli; DB constraint Directus MCP ile eklenemez, migration SQL ile ekle.

patients — klinik hasta kayıtları

Alan Tip Not
id uuid PK
clinic_tenant_id uuid tenants · IDX
created_by uuid directus_users
patient_code string(50) klinik içinde unique (soft)
first_name string(100)
last_name string(100)
phone string(30)
date_of_birth date
notes text
archived boolean DEF: false

jobs — protez işi (çekirdek tablo — en ağır)

Alan Tip Not
id uuid PK
clinic_tenant_id uuid tenants · IDX
lab_tenant_id uuid tenants · IDX
created_by uuid directus_users
patient_id uuid patients SET NULL · IDX
patient_code string(50)
prosthetic_id uuid prosthetics SET NULL
prosthetic_type enum[metal_porselen|zirkonyum|implant_ustu_zirkonyum|gecici|e_max|diger]
member_count integer üye (diş) sayısı
teeth json diş numaraları dizisi List<String>
color string(20) Vita renk kodu
description text
price decimal(10,2)
currency string(8)
status enum[pending|in_progress|sent|delivered|cancelled] DEF: pending · IDX
current_step enum[olcu|alt_yapi_prova|ust_yapi_prova|cila_bitim]
location enum[at_clinic|at_lab] DEF: at_clinic
due_date timestamp

Query pattern (Flutter):

// Lab gelen kutusu
GET /items/jobs?filter[lab_tenant_id][_eq]=$tenantId&filter[status][_eq]=pending
  &sort=-date_created&limit=50&page=1

// Klinik giden işler
GET /items/jobs?filter[clinic_tenant_id][_eq]=$tenantId
  &sort=-date_created&limit=50&page=1

// Filtre + arama
&filter[status][_in]=pending,in_progress
&search=hasta_kodu

job_files — işe bağlı dosyalar

Alan Tip Not
id uuid PK
job_id uuid jobs CASCADE · IDX
clinic_tenant_id uuid tenants CASCADE · IDX
lab_tenant_id uuid tenants CASCADE · IDX
uploaded_by uuid directus_users
file_id uuid directus_files SET NULL
kind enum[scan|image|document]
name string(255)
size integer bayt
mime_type string(100)
archived_at timestamp soft-delete; set → download disabled

job_status_history — stepper denetim izi

Alan Tip Not
id uuid PK
job_id uuid jobs CASCADE · IDX
clinic_tenant_id uuid tenants
lab_tenant_id uuid tenants
step enum[olcu|alt_yapi_prova|ust_yapi_prova|cila_bitim]
completed_by uuid directus_users
completed_at timestamp
note text

prosthetics — lab ürün kataloğu

Alan Tip Not
id uuid PK
tenant_id uuid tenants · IDX
created_by uuid directus_users
name string(255)
type enum[...prosthetic_types...]
unit_price decimal(10,2)
currency string(8) DEF: TRY
archived boolean DEF: false · IDX

finance_entries — tek-taraflı defter

Alan Tip Not
id uuid PK
tenant_id uuid tenants · IDX
created_by uuid directus_users
job_id uuid jobs SET NULL
counterpart_tenant_id uuid tenants SET NULL
type enum[income|expense|receivable|payable]
amount decimal(12,2)
currency string(8)
status enum[pending|paid|cancelled] DEF: pending · IDX
date timestamp IDX
description text

Cross-tenant sync (Directus Flow): İş sent/delivered olunca:

  • Lab tarafıreceivable/pending satır
  • Klinik tarafıpayable/pending satır Idempotent: (job_id, tenant_id, type) kombinasyonu varsa atla.

payments — ödeme kayıtları

Alan Tip Not
id uuid PK
tenant_id uuid tenants
counterpart_tenant_id uuid tenants
direction enum[inflow|outflow]
amount decimal(12,2)
currency string(8)
payment_date timestamp
method string(30)
notes text
recorded_by uuid directus_users
status enum[pending|confirmed|rejected] DEF: confirmed

clinic_pricing — kliniğe özel lab fiyatı

Alan Tip Not
id uuid PK
lab_tenant_id uuid tenants CASCADE
clinic_tenant_id uuid tenants CASCADE
prosthetic_type enum[...]
custom_unit_price decimal(10,2)
discount_percent decimal(5,2)
currency string(8)
created_by uuid directus_users

Unique: (lab_tenant_id, clinic_tenant_id, prosthetic_type) — Flutter tarafında kontrol et.

notifications

Alan Tip Not
id uuid PK
tenant_id uuid tenants · IDX
user_id uuid directus_users CASCADE
job_id uuid jobs SET NULL
connection_id uuid connections SET NULL
message string(500)
read boolean DEF: false · IDX
severity enum[info|warning] DEF: info

audit_logs

Alan Tip Not
id uuid PK
tenant_id uuid tenants
user_id uuid directus_users
action enum[create|update|delete]
entity_type string(50)
entity_id string(36)
changes json
ip_address string(50)
user_agent string(500)
Alan Tip Not
id uuid PK
tenant_id uuid tenants CASCADE
code string(32) UNQ
email string(255)
role enum[admin|member]
status enum[pending|accepted|cancelled|expired] DEF: pending
invited_by uuid directus_users
expires_at timestamp
accepted_at timestamp
accepted_by uuid directus_users

user_preferences

Alan Tip Not
id uuid PK
user_id uuid directus_users CASCADE
theme enum[light|dark|system] DEF: system
color_theme string(50)

5. İş akışı (jobs lifecycle)

Klinik açar (pending)
  → Lab "İşleme Al" (in_progress, currentStep: alt_yapi_prova, location: at_lab)
    → Lab "Kliniğe Gönder" (location: at_clinic) — prova için
      → Klinik "Provayı Onayla" (currentStep++, location: at_lab)
      → Klinik "Düzeltme İste" (location: at_lab, step aynı)
    → [Cila/Bitim sonrası] Lab "Kliniğe Gönder" (status: sent, location: at_clinic)
      → Klinik "Teslim Al" (status: delivered)
        → finance-sync tetiklenir
        → job_files arşivlenir (archived_at set)

Adım sırası: olcu → alt_yapi_prova → ust_yapi_prova → cila_bitim

Not: acceptJob = olcu tamamlandı anlamına gelir; currentStep direkt alt_yapi_prova'ya atlar.

6. Query Optimizasyon Kuralları (Flutter)

Zorunlu filtreler — hiç ihmal etme

// Her jobs sorgusunda tenant filtresi ZORUNLU
filter: {
  'lab_tenant_id': {'_eq': tenantId},   // lab tarafı
  // veya
  'clinic_tenant_id': {'_eq': tenantId}, // klinik tarafı
}

Sayfalama — sonsuz liste / cursor

// İlk yükleme
limit: 30, page: 1

// Sonraki sayfa (offset bazlı)
limit: 30, offset: 30

// Toplam sayı için ayrı istek (pahalı — sadece gerektiğinde)
meta: 'filter_count'

Field projection — sadece gerekeni çek

// Jobs listesi — detay alanlarını çekme
fields: ['id', 'patient_code', 'prosthetic_type', 'status',
         'current_step', 'location', 'date_created', 'due_date',
         'clinic_tenant_id.company_name', 'lab_tenant_id.company_name']

// Jobs detay — tam veri
fields: ['*', 'patient_id.*', 'job_files.*']

Relation join — N+1 önleme

// Kötü: jobs çek → her iş için ayrı tenant sorgusu
// İyi: nested fields ile tek sorguda
fields: ['*', 'clinic_tenant_id.company_name', 'lab_tenant_id.company_name']

Finance listesi — tarih aralığı ile kes

filter: {
  'tenant_id': {'_eq': tenantId},
  'date': {'_gte': '2026-01-01'},        // son 6 ay gibi
  'status': {'_neq': 'cancelled'},
}
sort: ['-date']
limit: 50

7. Klinik vs Lab — Ayrı Akışlar

Ekran Klinik Lab
Ana sayfa Gönderilen işler özeti, bekleyen ödemeler Gelen işler özeti, işlemdeki işler
İşler ana liste Giden işler (clinic_tenant_id) Gelen işler (lab_tenant_id)
İş aksiyon butonu Provayı Onayla / Düzeltme İste / Teslim Al İşleme Al / Kliniğe Gönder
Ürünler Katalog CRUD
Hastalar Hasta kayıtları
Bağlantı kur Lab'ı member_number ile ara Gelen talepleri onayla/reddet
Finans Borçlar (payable) Alacaklar (receivable)

8. Flutter → Directus API pattern

// Auth
POST /auth/login
  body: { email, password }
  returns: { access_token, refresh_token, expires }

// Token yenileme
POST /auth/refresh
  body: { refresh_token }

// Koleksiyon okuma
GET /items/{collection}?filter[field][_eq]=value&limit=30&sort=-date_created

// Tekil kayıt
GET /items/{collection}/{id}?fields=*,relation.*

// Oluşturma
POST /items/{collection}
  body: { field: value, ... }

// Güncelleme
PATCH /items/{collection}/{id}
  body: { field: value }

// Dosya yükleme
POST /files
  Content-Type: multipart/form-data
  field: file (binary)
  returns: { id, filename_download, ... }

9. Directus Flows (server-side otomasyonlar)

Aşağıdaki iş mantıkları Flutter client'tan değil Directus Flow'dan tetiklenmeli:

Tetikleyici Flow Açıklama
jobs.status → sent|delivered finance-sync Lab receivable + klinik payable oluştur (idempotent)
jobs.status → delivered archive-job-files job_files.archived_at set et
jobs UPDATE audit-log audit_logs satır yaz
connections.status → approved|rejected notify-connection İlgili tarafa bildirim gönder

Flows henüz oluşturulmadı — bir sonraki adım.

10. Eksik kısıtlamalar (SQL ile eklenecek)

Directus MCP composite unique constraint desteklemiyor; veritabanına direkt SQL ile eklenecek:

-- connections çifti unique
ALTER TABLE connections
  ADD CONSTRAINT connections_pair_unique UNIQUE (clinic_tenant_id, lab_tenant_id);

-- clinic_pricing üçlüsü unique
ALTER TABLE clinic_pricing
  ADD CONSTRAINT clinic_pricing_triple_unique
  UNIQUE (lab_tenant_id, clinic_tenant_id, prosthetic_type);