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 Team → tenants koleksiyonu
Appwrite Team membership → tenant_members koleksiyonu
Appwrite Auth → directus_users (Directus built-in)
Appwrite Storage buckets → directus_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):
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) |
|
invite_links
| 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)
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
Sayfalama — sonsuz liste / cursor
Field projection — sadece gerekeni çek
Relation join — N+1 önleme
Finance listesi — tarih aralığı ile kes
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
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: