@rglabs/butterfly 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,255 @@
1
+ # Butterfly Dosya Yükleme Dokümantasyonu
2
+
3
+ ## Genel Bakış
4
+
5
+ Butterfly DXP'de dosya yükleme iki yöntemle yapılabilir:
6
+
7
+ 1. **Admin Panel Endpoint (Önerilen)** — Session-based auth, custom report sayfalarında ve admin panelinde kullanılır
8
+ 2. **REST API Endpoint** — Token-based auth, harici sistemler ve mobil uygulamalar için
9
+
10
+ ---
11
+
12
+ ## Yöntem 1: Admin Panel File Helper (Önerilen)
13
+
14
+ Custom report sayfaları veya admin panelindeki JavaScript kodlarından dosya yüklemek için bu yöntem kullanılır. Kullanıcı zaten admin paneline giriş yaptığı için ek authentication gerekmez.
15
+
16
+ ### Endpoint
17
+
18
+ ```
19
+ POST /admin/ajax/file_helper/upload_file?alias=ALIAS
20
+ ```
21
+
22
+ ### Parametreler
23
+
24
+ | Parametre | Tip | Zorunlu | Açıklama |
25
+ |-----------|-----|---------|----------|
26
+ | `file_upload` | File | Evet | Yüklenecek dosya (input name **mutlaka** `file_upload` olmalı) |
27
+ | `uuid` | String | Evet | Benzersiz tanımlayıcı (örn: `upload-` + timestamp) |
28
+ | `original_filename` | String | Evet | Orijinal dosya adı |
29
+ | `total_file_size` | Number | Evet | Dosya boyutu (bytes) |
30
+ | `alias` | String | Evet | Storage alias (URL query parameter olarak gönderilir) |
31
+
32
+ ### Storage Alias Yapılandırması
33
+
34
+ Alias'lar `cms_file_uploads` tablosunda tanımlıdır. Her alias şunları kontrol eder:
35
+ - İzin verilen dosya uzantıları (`extensions` alanı, virgülle ayrılmış)
36
+ - Maksimum dosya boyutu
37
+ - Depolama konumu
38
+
39
+ **Alias yönetimi:** Admin panel → File Storage menüsü veya `cms_file_uploads` tablosu üzerinden.
40
+
41
+ **Yaygın alias'lar:**
42
+ - `content` — Genel içerik (görsel, medya)
43
+ - `import` — İçe aktarma dosyaları (varsayılan: json, xlsx, xls; ihtiyaca göre genişletilebilir)
44
+
45
+ **Yeni uzantı eklemek için:**
46
+ ```bash
47
+ butterfly-cli record edit cms_file_uploads --id <alias_id> --data '{"extensions": "mevcut,yeni,uzantilar"}'
48
+ ```
49
+
50
+ ### JavaScript Örneği (Custom Report Sayfası)
51
+
52
+ ```javascript
53
+ function uploadFile(file) {
54
+ return new Promise(function(resolve, reject) {
55
+ var fd = new FormData();
56
+ fd.append('file_upload', file);
57
+ fd.append('uuid', 'upload-' + Date.now());
58
+ fd.append('original_filename', file.name);
59
+ fd.append('total_file_size', file.size);
60
+
61
+ $.ajax({
62
+ url: '/admin/ajax/file_helper/upload_file?alias=import',
63
+ type: 'POST',
64
+ data: fd,
65
+ processData: false,
66
+ contentType: false,
67
+ headers: { 'X-Requested-With': 'XMLHttpRequest' },
68
+ success: function(response) {
69
+ if (response.success && response.filename) {
70
+ resolve(response.filename);
71
+ } else {
72
+ rg_alert(response.message || 'Dosya yüklenemedi.');
73
+ reject('Upload failed');
74
+ }
75
+ },
76
+ error: function() {
77
+ rg_alert('Dosya yükleme sırasında hata oluştu.');
78
+ reject('Upload error');
79
+ }
80
+ });
81
+ });
82
+ }
83
+ ```
84
+
85
+ ### Yanıt Formatı
86
+
87
+ **Başarılı:**
88
+ ```json
89
+ {
90
+ "success": true,
91
+ "filename": "dosya_adi_islenmis.jpg",
92
+ "full_path": "/storage/path/dosya_adi_islenmis.jpg"
93
+ }
94
+ ```
95
+
96
+ **Hata:**
97
+ ```json
98
+ {
99
+ "success": false,
100
+ "message": "Dosya tipi izin verilmiyor."
101
+ }
102
+ ```
103
+
104
+ ### Dosya Kaydı ile Entegrasyon (İki Aşamalı)
105
+
106
+ Dosya yükleme ve kayıt oluşturma iki ayrı adımda yapılır:
107
+
108
+ ```javascript
109
+ // 1. Dosyayı yükle → filename al
110
+ var filename = await uploadFile(file);
111
+
112
+ // 2. Kaydı oluştur, filename'i veri olarak gönder
113
+ $.ajax({
114
+ url: '/admin/ajax/cms_object/operation?do=tablo_adi__add',
115
+ type: 'POST',
116
+ data: {
117
+ csrf_token: csrfToken,
118
+ parent_id: parentId,
119
+ dosya: filename, // file_upload field'ın column adı
120
+ aciklama: 'Açıklama'
121
+ }
122
+ });
123
+ ```
124
+
125
+ ### Object Spec Yapılandırması
126
+
127
+ `file_upload` tipindeki field'ın `val_1` parametresi alias adını belirtir:
128
+
129
+ ```json
130
+ {
131
+ "type": "file_upload",
132
+ "val_1": "import"
133
+ }
134
+ ```
135
+
136
+ ### Dosya Önizleme Linki Oluşturma
137
+
138
+ Upload edilen dosyalar veritabanında sadece **relative path** olarak saklanır (örn: `26-03/08/dosya.png`). Tam URL oluşturmak için alias'ın `path` değeri gerekir.
139
+
140
+ **Twig'de base path'i al:**
141
+ ```twig
142
+ {% set importAlias = db().table('cms_file_uploads').where('alias', 'import').first() %}
143
+ {% set importBasePath = importAlias ? importAlias.path : '/static/import/' %}
144
+ ```
145
+
146
+ **JS'e aktar:**
147
+ ```html
148
+ <script>
149
+ window._data = {
150
+ importBasePath: '{{ importBasePath }}'
151
+ };
152
+ </script>
153
+ ```
154
+
155
+ **JS'de tam URL oluştur:**
156
+ ```javascript
157
+ var basePath = data.importBasePath || '/static/import/';
158
+ var fullUrl = basePath + record.dosya;
159
+ // Örnek: /static/import/26-03/08/dosya.png
160
+ ```
161
+
162
+ **Path yapısı:**
163
+ - `cms_file_storages.base_url` → Storage base URL (örn: `/static`)
164
+ - `cms_file_uploads.path` → Alias path (örn: `/static/import/`)
165
+ - Veritabanındaki `dosya` değeri → Relative path (örn: `26-03/08/dosya.png`)
166
+ - **Tam URL** = `cms_file_uploads.path` + `dosya` değeri
167
+
168
+ ### Önemli Notlar
169
+
170
+ - **Input name:** Mutlaka `file_upload` olmalı, field'ın column adı (örn: `dosya`) DEĞİL
171
+ - **Authentication:** Admin session cookie'leri otomatik gönderilir, ek header gerekmez
172
+ - **Alias zorunlu:** URL'de `?alias=ALIAS` belirtilmeli, yoksa hata verir
173
+ - **Tek dosya:** Bu endpoint tek seferde bir dosya yükler, birden fazla dosya için döngü kullanın
174
+ - **Dosya uzantısı kontrol:** Alias'ta tanımlı uzantılarla eşleşmeyen dosyalar reddedilir
175
+ - **Dosya linki:** Veritabanında sadece relative path saklanır, görüntülemek için alias path'i ile birleştirin
176
+
177
+ ---
178
+
179
+ ## Yöntem 2: REST API (Harici Sistemler İçin)
180
+
181
+ Harici uygulamalar, mobil uygulamalar veya otomatik süreçler için REST API kullanılır.
182
+
183
+ ### Ön Gereksinimler
184
+
185
+ 1. **API Erişimi:** Admin panelinde kullanıcı profilinde "API Access" aktif edilmeli
186
+ 2. **API Key:** `/admin/user_auth/list` sayfasından API key oluşturulmalı
187
+
188
+ ### Authentication
189
+
190
+ ```bash
191
+ # Token oluştur
192
+ curl -X POST /rest/auth/generateToken \
193
+ -H "Content-Type: application/x-www-form-urlencoded" \
194
+ -d "api_key=YOUR_API_KEY&password=YOUR_PASSWORD"
195
+ ```
196
+
197
+ **Yanıt:**
198
+ ```json
199
+ {
200
+ "success": true,
201
+ "token": "session_token",
202
+ "expires_at": "2024-12-31 23:59:59"
203
+ }
204
+ ```
205
+
206
+ ### Dosya Yükleme
207
+
208
+ ```bash
209
+ curl -X POST /rest/upload/image \
210
+ -H "BUTTERFLY_API_KEY: api-key-here" \
211
+ -H "BUTTERFLY_ACCESS_TOKEN: access-token-here" \
212
+ -F "files[]=@/path/to/image.jpg" \
213
+ -F "alias=content" \
214
+ -F "sub_folder=gallery"
215
+ ```
216
+
217
+ **Parametreler:**
218
+ - `files[]` (file array, zorunlu): Yüklenecek dosyalar
219
+ - `alias` (string, opsiyonel): Storage alias (varsayılan: 'content')
220
+ - `sub_folder` (string, opsiyonel): Alt klasör
221
+
222
+ **Yanıt:**
223
+ ```json
224
+ {
225
+ "success": true,
226
+ "result": [
227
+ {
228
+ "full_path": "https://cdn.example.com/content/gallery/image.jpg",
229
+ "filename": "image.jpg",
230
+ "webp": "https://cdn.example.com/content/gallery/image.webp"
231
+ }
232
+ ]
233
+ }
234
+ ```
235
+
236
+ ### REST API Hata Durumları
237
+
238
+ | Hata | Açıklama |
239
+ |------|----------|
240
+ | `"invalid request"` | api_key veya password eksik |
241
+ | `"invalid parameters"` | API key bulunamadı, API erişimi kapalı, yanlış şifre |
242
+ | 401 Unauthorized | Geçersiz veya süresi dolmuş token |
243
+ | 422 Unprocessable | Dosya tipi veya boyut sınırı aşıldı |
244
+
245
+ ---
246
+
247
+ ## Hangi Yöntemi Kullanmalı?
248
+
249
+ | Senaryo | Yöntem |
250
+ |---------|--------|
251
+ | Custom report sayfasında dosya yükleme | Admin Panel (Yöntem 1) |
252
+ | Object edit sayfasında file_upload field | Otomatik (Butterfly yönetir) |
253
+ | Harici uygulama / mobil uygulama | REST API (Yöntem 2) |
254
+ | Cron job / workflow / otomasyon | REST API (Yöntem 2) |
255
+ | Admin paneli içi JavaScript | Admin Panel (Yöntem 1) |
@@ -0,0 +1,78 @@
1
+ # Object Listing Query
2
+
3
+ Each object can have a `listing_query.bfy` file that customizes the listing page behavior and appearance.
4
+
5
+ ## File Location
6
+
7
+ ```
8
+ butterfly-resources/objects/[butterfly|app]/[table_name]/listing_query.bfy
9
+ ```
10
+
11
+ ## Header Block
12
+
13
+ Use the `{% block header %}` to display custom content at the top of the listing page, above the data table.
14
+
15
+ ### Basic Usage
16
+
17
+ ```twig
18
+ {% block header %}
19
+ <div class="alert alert-info">
20
+ Custom content displayed at the top of the listing page
21
+ </div>
22
+ {% endblock %}
23
+ ```
24
+
25
+ ### Example with Statistics
26
+
27
+ ```twig
28
+ {% block header %}
29
+ {% set stats = db().table('orders').selectRaw('COUNT(*) as total, SUM(amount) as sum').first() %}
30
+ <div class="row mb-3">
31
+ <div class="col-md-4">
32
+ <div class="card">
33
+ <div class="card-body text-center">
34
+ <h5 class="card-title">Total Orders</h5>
35
+ <p class="display-6">{{ stats.total }}</p>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ <div class="col-md-4">
40
+ <div class="card">
41
+ <div class="card-body text-center">
42
+ <h5 class="card-title">Total Amount</h5>
43
+ <p class="display-6">{{ stats.sum|number_format(2) }} TL</p>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ {% endblock %}
49
+ ```
50
+
51
+ ### Example with Filters
52
+
53
+ ```twig
54
+ {% block header %}
55
+ <div class="card mb-3">
56
+ <div class="card-body">
57
+ <form method="get" class="row g-3">
58
+ <div class="col-md-3">
59
+ <label class="form-label">Status</label>
60
+ <select name="status" class="form-select">
61
+ <option value="">All</option>
62
+ <option value="active">Active</option>
63
+ <option value="inactive">Inactive</option>
64
+ </select>
65
+ </div>
66
+ <div class="col-md-3 d-flex align-items-end">
67
+ <button type="submit" class="btn btn-primary">Filter</button>
68
+ </div>
69
+ </form>
70
+ </div>
71
+ </div>
72
+ {% endblock %}
73
+ ```
74
+
75
+ ## Related
76
+
77
+ - [Objects Overview](./README.md)
78
+ - [Creating Objects](./creating.md)
@@ -0,0 +1,376 @@
1
+ # Butterfly PDF Oluşturma Dokümantasyonu
2
+
3
+ ## Genel Bakış
4
+
5
+ Butterfly DXP, `pdf_templates` tablosundaki Twig şablonlarından PDF dosyaları oluşturmak için yerleşik bir PDF sistemi sunar. Sistem arka planda `https://pdf.butterfly.dev/get` adresindeki PDF sunucusunu kullanır.
6
+
7
+ ## Mimari
8
+
9
+ ```
10
+ [İstek] → /pdf/create veya /pdf/view
11
+ → pdf_templates tablosundan şablon çek (system_name ile)
12
+ → Twig render (Input parametreleri değişken olarak geçer)
13
+ → HTML'i PDF sunucusuna gönder
14
+ → /create: PDF'i storage'a kaydet, dosya bilgisi dön
15
+ → /view: PDF'i tarayıcıda inline göster
16
+ ```
17
+
18
+ ## Endpoint'ler
19
+
20
+ ### 1. PDF Oluştur ve Kaydet (`/pdf/create`)
21
+
22
+ PDF oluşturur, storage alias'a kaydeder, dosya bilgisi döner.
23
+
24
+ **URL:** `POST /pdf/create`
25
+
26
+ **Parametreler:**
27
+
28
+ | Parametre | Tip | Zorunlu | Açıklama |
29
+ |-----------|-----|---------|----------|
30
+ | `template_name` | string | Evet | `pdf_templates.system_name` değeri |
31
+ | `filename` | string | Hayır | Çıktı dosya adı (varsayılan: `document.pdf`) |
32
+ | `alias` | string | Hayır | Storage alias (varsayılan: `pdf`) |
33
+ | *diğer parametreler* | any | Hayır | Template'e değişken olarak geçer |
34
+
35
+ **Başarılı Yanıt:**
36
+ ```json
37
+ {
38
+ "success": true,
39
+ "full_path": "https://butterfly.rg/static/pdf/26-03/08/harcama-3.pdf",
40
+ "filename": "26-03/08/harcama-3.pdf"
41
+ }
42
+ ```
43
+
44
+ **Hata Yanıtları:**
45
+ ```json
46
+ {"success": false, "message": "PDF tasarımı seçiniz."}
47
+ {"success": false, "message": "PDF tasarımı bulunamadı."}
48
+ {"success": false, "message": "Hata: ...twig render hatası..."}
49
+ {"success": false, "message": "PDF oluşturulurken bir hata oluştu..."}
50
+ ```
51
+
52
+ ### 2. PDF Önizleme (`/pdf/view`)
53
+
54
+ PDF oluşturur ve tarayıcıda inline gösterir (kaydetmez). Yazdır/önizleme için idealdir.
55
+
56
+ **URL:** `GET /pdf/view`
57
+
58
+ **Parametreler:** `/pdf/create` ile aynı. Ek olarak template'e `preview: true` değişkeni geçer.
59
+
60
+ **Yanıt:** `Content-Type: application/pdf` ile doğrudan PDF binary.
61
+
62
+ ## pdf_templates Tablosu
63
+
64
+ | Kolon | Açıklama |
65
+ |-------|----------|
66
+ | `system_name` | Şablonun benzersiz tanımlayıcısı (endpoint'ten `template_name` ile eşleşir) |
67
+ | `name` | Şablon görünen adı |
68
+ | `template` | Twig + HTML şablon içeriği |
69
+
70
+ ## Storage Alias Yapılandırması
71
+
72
+ PDF dosyaları `cms_file_uploads` tablosundaki alias ayarına göre kaydedilir.
73
+
74
+ **`pdf` alias yoksa oluştur:**
75
+ ```bash
76
+ butterfly-cli record add cms_file_uploads --data '{
77
+ "alias": "pdf",
78
+ "path": "/static/pdf/",
79
+ "extensions": "pdf",
80
+ "cms_file_storage_id": 1
81
+ }'
82
+ ```
83
+
84
+ Kayıtlı PDF'lerin tam URL'i: `cms_file_uploads.path` + `response.filename`
85
+ Örnek: `/static/pdf/` + `26-03/08/harcama-3.pdf` = `/static/pdf/26-03/08/harcama-3.pdf`
86
+
87
+ ## Şablon Yazma Kuralları
88
+
89
+ ### Permission Bypass (KRİTİK)
90
+
91
+ `/pdf/create` ve `/pdf/view` endpoint'leri admin context dışında çalışır. Standart `db().table()` çağrıları permission hatası verir:
92
+
93
+ ```
94
+ "You don't have permission to view TABLE_NAME object"
95
+ ```
96
+
97
+ **Çözüm:** `db('default', false)` kullan — ikinci parametre `false` permission kontrolünü devre dışı bırakır.
98
+
99
+ ```twig
100
+ {# ❌ YANLIŞ — permission hatası verir #}
101
+ {% set r = db().table('tablo').where('id', id).first() %}
102
+
103
+ {# ✅ DOĞRU — permission bypass #}
104
+ {% set r = db('default', false).table('tablo').where('id', id).first() %}
105
+ ```
106
+
107
+ > **Not:** Bu sadece PDF template'lerinde kullanılmalıdır. Normal report/object kodlarında standart `db()` kullanmaya devam edin.
108
+
109
+ ### Değişken Erişimi
110
+
111
+ Template'e gönderilen tüm parametreler doğrudan Twig değişkeni olarak erişilebilir:
112
+
113
+ ```
114
+ POST /pdf/create?template_name=sablonAdi&id=5&extra_param=deger
115
+ ```
116
+
117
+ Template içinde:
118
+ ```twig
119
+ {{ id }} {# 5 #}
120
+ {{ extra_param }} {# deger #}
121
+ ```
122
+
123
+ ### Inline CSS Zorunluluğu
124
+
125
+ PDF sunucusu harici CSS dosyalarını yüklemez. **Tüm stiller `<style>` etiketi içinde veya inline olmalıdır.**
126
+
127
+ ```html
128
+ {# ❌ YANLIŞ — PDF'te çalışmaz #}
129
+ <link href="https://cdn.jsdelivr.net/npm/tailwindcss..." rel="stylesheet">
130
+
131
+ {# ✅ DOĞRU #}
132
+ <style>
133
+ body { font-family: Arial, sans-serif; font-size: 12px; }
134
+ .section { background: #f3f4f6; padding: 6px 12px; font-weight: 700; }
135
+ table { width: 100%; border-collapse: collapse; }
136
+ </style>
137
+ ```
138
+
139
+ ### Temel Şablon Yapısı
140
+
141
+ ```html
142
+ <html>
143
+ <head>
144
+ <meta charset="utf-8">
145
+ <style>
146
+ body { font-family: Arial, sans-serif; font-size: 12px; color: #333; margin: 20px 40px; }
147
+ h1 { font-size: 18px; text-align: center; }
148
+ .section { background: #f3f4f6; padding: 6px 12px; font-weight: 700; font-size: 13px;
149
+ border-left: 4px solid #ef4444; margin: 14px 0 8px; }
150
+ table { width: 100%; border-collapse: collapse; margin-bottom: 10px; }
151
+ .info-table td { padding: 4px 8px; font-size: 12px; }
152
+ .info-table .lbl { font-weight: 600; width: 140px; color: #555; }
153
+ .data-table th { background: #f3f4f6; padding: 6px 8px; text-align: left; font-size: 11px;
154
+ font-weight: 700; border: 1px solid #d1d5db; }
155
+ .data-table td { padding: 5px 8px; border: 1px solid #d1d5db; font-size: 11px; }
156
+ .footer { margin-top: 20px; border-top: 1px solid #d1d5db; padding-top: 8px;
157
+ font-size: 10px; color: #999; text-align: center; }
158
+ </style>
159
+ </head>
160
+ <body>
161
+ {% set r = db('default', false).table('ana_tablo').where('id', id).first() %}
162
+ {% if r is empty %}
163
+ <p>Kayıt bulunamadı.</p>
164
+ {% else %}
165
+ {% set children = db('default', false).table('child_tablo').where('parent_id', id).get() %}
166
+
167
+ <h1>BAŞLIK</h1>
168
+
169
+ <div class="section">Bilgiler</div>
170
+ <table class="info-table">
171
+ <tr><td class="lbl">Alan Adı</td><td>{{ r.alan_adi }}</td></tr>
172
+ </table>
173
+
174
+ {% if children|length > 0 %}
175
+ <div class="section">Alt Kayıtlar</div>
176
+ <table class="data-table">
177
+ <tr><th>Kolon 1</th><th>Kolon 2</th></tr>
178
+ {% for c in children %}
179
+ <tr><td>{{ c.kolon1 }}</td><td>{{ c.kolon2 }}</td></tr>
180
+ {% endfor %}
181
+ </table>
182
+ {% endif %}
183
+
184
+ <div class="footer">Bu belge sistem tarafından otomatik oluşturulmuştur.</div>
185
+ {% endif %}
186
+ </body>
187
+ </html>
188
+ ```
189
+
190
+ ## Entegrasyon Örnekleri
191
+
192
+ ### Custom Report Sayfasından Yazdır Butonu
193
+
194
+ **HTML (query_code.bfy):**
195
+ ```html
196
+ {% if isEdit %}
197
+ <button onclick="printPdf()">
198
+ <i class="fas fa-print mr-1"></i> Yazdır
199
+ </button>
200
+ {% endif %}
201
+ ```
202
+
203
+ **JS (script.js):**
204
+ ```javascript
205
+ function printPdf() {
206
+ if (!data.isEdit || !data.recordId) {
207
+ rg_alert('Lütfen önce formu kaydediniz.');
208
+ return;
209
+ }
210
+ window.open('/pdf/view?template_name=SABLON_ADI&id=' + data.recordId
211
+ + '&filename=dosya-' + data.recordId + '.pdf', '_blank');
212
+ }
213
+ ```
214
+
215
+ ### PDF Oluştur ve Kayda Yaz
216
+
217
+ Formu göndermeden önce PDF oluşturup dosya yolunu ana kayda yazmak için:
218
+
219
+ ```javascript
220
+ function createPdf(recordId) {
221
+ return new Promise(function(resolve, reject) {
222
+ $.ajax({
223
+ url: '/pdf/create',
224
+ type: 'POST',
225
+ data: {
226
+ template_name: 'SABLON_ADI',
227
+ id: recordId,
228
+ filename: 'dosya-' + recordId + '.pdf',
229
+ alias: 'pdf'
230
+ },
231
+ headers: { 'X-Requested-With': 'XMLHttpRequest' },
232
+ success: function(response) {
233
+ if (response.success && response.filename) {
234
+ var pdfPath = response.full_path || '/static/pdf/' + response.filename;
235
+ // PDF yolunu ana kayda yaz
236
+ $.ajax({
237
+ url: '/admin/ajax/cms_object/operation?do=TABLO__edit',
238
+ type: 'POST',
239
+ data: { csrf_token: csrfToken, id: recordId, pdf_dosya: pdfPath },
240
+ headers: { 'X-Requested-With': 'XMLHttpRequest' },
241
+ success: function() { resolve(pdfPath); },
242
+ error: function() { resolve(pdfPath); }
243
+ });
244
+ } else {
245
+ rg_alert(response.message || 'PDF oluşturulamadı.');
246
+ reject('PDF failed');
247
+ }
248
+ },
249
+ error: function() {
250
+ rg_alert('PDF oluşturma sırasında hata oluştu.');
251
+ reject('PDF error');
252
+ }
253
+ });
254
+ });
255
+ }
256
+ ```
257
+
258
+ ### State Machine Transition ile PDF Link'ini E-postaya Ekle
259
+
260
+ PDF dosyası JS tarafında oluşturulup `pdf_dosya` field'ına yazıldıktan sonra, transition action_code'unda bu değer okunabilir:
261
+
262
+ ```twig
263
+ {# action_code.bfy #}
264
+ {% set tpl = db().table('cms_email_templates').where('alias', 'template-alias').first() %}
265
+ {% set pdfDosya = getValue('pdf_dosya') %}
266
+ {% set vars = {
267
+ 'talep_no': getValue('talep_no'),
268
+ 'ad_soyad': getValue('ad_soyad'),
269
+ 'pdf_link': pdfDosya
270
+ ? '<p><strong>PDF Doküman:</strong> <a href="' ~ pdfDosya ~ '" style="color:#3b82f6;">Formu İndir</a></p>'
271
+ : ''
272
+ } %}
273
+ {% set replacements = {} %}
274
+ {% for key, val in vars %}
275
+ {% set replacements = replacements|merge({('{{ ' ~ key ~ ' }}'): val}) %}
276
+ {% endfor %}
277
+ {{ sendEmail(recipient, tpl.subject|replace(replacements), tpl.content|replace(replacements)) }}
278
+ ```
279
+
280
+ E-posta template'ine `{{ pdf_link }}` placeholder'ı eklemeyi unutmayın.
281
+
282
+ ### Tipik Akış (Kaydet → PDF → Gönder)
283
+
284
+ ```javascript
285
+ function submitForm() {
286
+ rg_confirm('Göndermek istediğinize emin misiniz?', function() {
287
+ saveForm().then(function(recordId) { // 1. Formu kaydet
288
+ return createPdf(recordId).then(function() { // 2. PDF oluştur + kayda yaz
289
+ return triggerTransition(recordId, transitionId); // 3. Transition tetikle
290
+ });
291
+ }).then(function() {
292
+ window.location.href = '/admin/cms_report/view/DASHBOARD_ID';
293
+ });
294
+ });
295
+ }
296
+ ```
297
+
298
+ ## Sıfırdan PDF Entegrasyonu Adımları
299
+
300
+ ### 1. Storage Alias Oluştur (bir kez)
301
+
302
+ ```bash
303
+ butterfly-cli record add cms_file_uploads --data '{
304
+ "alias": "pdf",
305
+ "path": "/static/pdf/",
306
+ "extensions": "pdf",
307
+ "cms_file_storage_id": 1
308
+ }'
309
+ ```
310
+
311
+ ### 2. pdf_templates Objesi Oluştur (bir kez)
312
+
313
+ Eğer `pdf_templates` objesi yoksa:
314
+
315
+ ```bash
316
+ # Object
317
+ butterfly-cli record add objects --data '{
318
+ "table_name": "pdf_templates",
319
+ "name": "PDF Şablonları",
320
+ "has_item": 0,
321
+ "auto_increment_column_name": "id"
322
+ }'
323
+ # Object ID'yi not al, aşağıda OBJECT_ID olarak kullan
324
+
325
+ # Field'lar
326
+ butterfly-cli record add object_specs --data '{"object_id":OBJECT_ID,"name":"System Name","column_name":"system_name","type":"string","required":1,"list_column":1,"edit_order_no":1}'
327
+ butterfly-cli record add object_specs --data '{"object_id":OBJECT_ID,"name":"Şablon Adı","column_name":"name","type":"string","required":1,"list_column":1,"edit_order_no":2}'
328
+ butterfly-cli record add object_specs --data '{"object_id":OBJECT_ID,"name":"HTML Şablon","column_name":"template","type":"code","edit_order_no":3}'
329
+ ```
330
+
331
+ ### 3. Şablon Kaydı Ekle
332
+
333
+ ```bash
334
+ butterfly-cli record add pdf_templates --data '{
335
+ "system_name": "sablonun-adi",
336
+ "name": "Şablonun Görünen Adı",
337
+ "template": "<html>...şablon HTML...</html>"
338
+ }'
339
+ ```
340
+
341
+ ### 4. Ana Tabloya `pdf_dosya` Field Ekle (opsiyonel)
342
+
343
+ PDF dosya yolunu kayıtta saklamak istiyorsan:
344
+
345
+ ```bash
346
+ butterfly-cli record add object_specs --data '{
347
+ "object_id": ANA_OBJECT_ID,
348
+ "name": "PDF Dosya",
349
+ "column_name": "pdf_dosya",
350
+ "type": "string",
351
+ "edit_order_no": 99
352
+ }'
353
+ ```
354
+
355
+ ### 5. JS Fonksiyonlarını Ekle
356
+
357
+ `printPdf()` ve `createPdf()` fonksiyonlarını script.js'e ekle (yukarıdaki örneklere bakın).
358
+
359
+ ### 6. Yazdır Butonunu Forma Ekle
360
+
361
+ ```html
362
+ {% if isEdit %}
363
+ <button onclick="printPdf()"><i class="fas fa-print mr-1"></i> Yazdır</button>
364
+ {% endif %}
365
+ ```
366
+
367
+ ## Sık Karşılaşılan Hatalar
368
+
369
+ | Hata | Neden | Çözüm |
370
+ |------|-------|-------|
371
+ | `"PDF tasarımı bulunamadı."` | `template_name` ile eşleşen `system_name` yok | `pdf_templates` tablosunu kontrol et |
372
+ | `"You don't have permission to view X object"` | Template'te `db()` kullanılmış | `db('default', false)` kullan |
373
+ | PDF oluştu ama değerler boş | Template'e parametre geçilmemiş veya `db()` sorgusu hatalı | `id` parametresinin gönderildiğini ve sorgunun doğru olduğunu kontrol et |
374
+ | PDF sunucu hatası (HTTP != 200) | HTML geçersiz veya PDF sunucusu erişilemiyor | HTML'i `/pdf/view` ile test et, hata mesajını incele |
375
+ | Stiller PDF'te görünmüyor | Harici CSS kullanılmış | Tüm stilleri `<style>` etiketi içine al, inline CSS kullan |
376
+ | `"Sayfa Bulunamadı"` | `/admin/pdf/create` veya `/admin/ajax/pdf/create` kullanılmış | Doğru URL: `/pdf/create` (admin prefix'siz) |