@rglabs/butterfly 2.0.2 → 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.
- package/.claude/commands/docs-page-layout.md +12 -0
- package/CLAUDE.md +167 -1
- package/dist/commands/setup.js +15 -1
- package/dist/commands/view-report.d.ts +8 -0
- package/dist/commands/view-report.js +89 -0
- package/dist/index.js +8 -0
- package/docs/IMPLEMENTATION_PROMPT.md +252 -0
- package/docs/butterfly_upload_api_docs-2.md +255 -0
- package/docs/objects/listing-query.md +78 -0
- package/docs/pdf-generation.md +376 -0
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Read the page layout documentation from docs/page-layout.md.
|
|
2
|
+
|
|
3
|
+
Provide guidance on organizing object add/edit page layouts including:
|
|
4
|
+
- Creating and managing object_tabs (groups/tabs)
|
|
5
|
+
- Creating object_sections (higher-level grouping)
|
|
6
|
+
- Assigning fields to tabs via edit_place_no
|
|
7
|
+
- Field positioning (edit_position, edit_order_no)
|
|
8
|
+
- Column sizes (left_column_size, column_size)
|
|
9
|
+
- Bulk layout ordering via API or butterfly-cli layout command
|
|
10
|
+
- section_title for visual section headers within tabs
|
|
11
|
+
|
|
12
|
+
Focus on the specific layout task the user asks about, or provide an overview if no specific task is mentioned.
|
package/CLAUDE.md
CHANGED
|
@@ -109,6 +109,46 @@ See [docs/translations.md](docs/translations.md) for full documentation.
|
|
|
109
109
|
|
|
110
110
|
See [docs/twig-helpers.md](docs/twig-helpers.md) for full documentation.
|
|
111
111
|
|
|
112
|
+
## Email Sending (Quick Reference)
|
|
113
|
+
|
|
114
|
+
```twig
|
|
115
|
+
{# Send email directly #}
|
|
116
|
+
{{ sendEmail('recipient@example.com', 'Subject', '<p>HTML body</p>') }}
|
|
117
|
+
|
|
118
|
+
{# Send using cms_email_templates (recommended pattern) #}
|
|
119
|
+
{% set tpl = db().table('cms_email_templates').where('alias', 'template-alias').first() %}
|
|
120
|
+
{% set replacements = {} %}
|
|
121
|
+
{% for key, val in vars %}
|
|
122
|
+
{% set replacements = replacements|merge({('{{ ' ~ key ~ ' }}'): val}) %}
|
|
123
|
+
{% endfor %}
|
|
124
|
+
{{ sendEmail(recipient, tpl.subject|replace(replacements), tpl.content|replace(replacements)) }}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Email Templates (`cms_email_templates`):**
|
|
128
|
+
- Store HTML email templates with `{{ variable_name }}` placeholders in subject and content
|
|
129
|
+
- Use `alias` field for programmatic lookup
|
|
130
|
+
- `test_data` field: JSON with sample values for preview
|
|
131
|
+
- `preview` field: calculated field that renders content with test_data, shown in edit page
|
|
132
|
+
- Templates should use **inline CSS** (not classes) for email client compatibility
|
|
133
|
+
- Link to state machine via `bfy_state_machine_id` for organization
|
|
134
|
+
|
|
135
|
+
**Important:** Twig `|replace` filter requires a **mapping** (associative array), NOT individual string arguments. Build replacements map with `|merge`.
|
|
136
|
+
|
|
137
|
+
## Settings (Quick Reference)
|
|
138
|
+
|
|
139
|
+
```twig
|
|
140
|
+
{# Get single setting value #}
|
|
141
|
+
{% set value = setting('group_alias', 'setting_alias') %}
|
|
142
|
+
|
|
143
|
+
{# Get all settings in a group #}
|
|
144
|
+
{% set all = setting('group_alias') %}
|
|
145
|
+
{{ all.setting_alias.value }}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Tables:** `cms_setting_groups` (groups by alias) → `cms_settings` (individual settings)
|
|
149
|
+
|
|
150
|
+
See [docs/twig-helpers.md](docs/twig-helpers.md#settings) for full documentation.
|
|
151
|
+
|
|
112
152
|
## JavaScript Field Updates (Quick Reference)
|
|
113
153
|
|
|
114
154
|
| Field Types | Method |
|
|
@@ -124,13 +164,34 @@ See [docs/object-specs/js-field-updates.md](docs/object-specs/js-field-updates.m
|
|
|
124
164
|
/admin/SINGULAR_TABLE_NAME/list
|
|
125
165
|
/admin/SINGULAR_TABLE_NAME/add
|
|
126
166
|
/admin/SINGULAR_TABLE_NAME/edit/ID
|
|
167
|
+
/admin/cms_report/view/REPORT_ID
|
|
127
168
|
```
|
|
128
169
|
|
|
129
170
|
For non-default database: `/admin/DATABASE_ALIAS/SINGULAR_TABLE_NAME/...`
|
|
130
171
|
|
|
131
172
|
## Object Menu Placement
|
|
132
173
|
|
|
133
|
-
|
|
174
|
+
Admin menus have 3 levels. Levels 1-2 are created in `cms_admin_menus`, level 3 is set on the object itself.
|
|
175
|
+
|
|
176
|
+
### Step 1: Create Main Menu (Level 1) and Sub Menu (Level 2) in `cms_admin_menus`
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Level 1 - Main menu
|
|
180
|
+
butterfly-cli record add cms_admin_menus --data '{"title":"Menu Name","parent_id":0,"icon":"b-icon-icon-name"}'
|
|
181
|
+
# Returns ID, e.g. 67
|
|
182
|
+
|
|
183
|
+
# Level 2 - Sub menu (parent_id = main menu ID)
|
|
184
|
+
butterfly-cli record add cms_admin_menus --data '{"title":"Sub Menu","parent_id":67,"icon":"b-icon-icon-name"}'
|
|
185
|
+
# Returns ID, e.g. 68
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Step 2: Assign Object to Menu (Level 3) via `main_menu_id` + `sub_menu_id`
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
butterfly-cli record edit objects --id <object_id> --data '{"main_menu_id":<main_menu_id>,"sub_menu_id":<sub_menu_id>}'
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Do NOT** create level 3 entries in `cms_admin_menus`. The object itself becomes the menu item when `main_menu_id` and `sub_menu_id` are set.
|
|
134
195
|
|
|
135
196
|
## Object Default Permissions
|
|
136
197
|
|
|
@@ -159,6 +220,46 @@ butterfly-cli record add <table_name> --data '{"bfy_workspace_id": <workspace_id
|
|
|
159
220
|
|
|
160
221
|
See [docs/workspaces.md](docs/workspaces.md) for full documentation.
|
|
161
222
|
|
|
223
|
+
## Page Generation Guidelines
|
|
224
|
+
|
|
225
|
+
When a task requires generating a page:
|
|
226
|
+
|
|
227
|
+
- **Simple pages** (single table, basic CRUD): Use standard Butterfly object pages.
|
|
228
|
+
- **Complex pages** (multiple table relations, complex design, custom layouts): Use **custom report pages** with Butterfly endpoints and objects for data operations.
|
|
229
|
+
|
|
230
|
+
### Custom Report Page Approach
|
|
231
|
+
|
|
232
|
+
For complex pages, create a custom report page with HTML/Twig template and use Butterfly's AJAX endpoints to save/update data:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
POST /admin/ajax/cms_object/operation?do=TABLE_NAME__OPERATION
|
|
236
|
+
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
|
|
237
|
+
X-Requested-With: XMLHttpRequest
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Example - saving page data via AJAX:**
|
|
241
|
+
```javascript
|
|
242
|
+
$.ajax({
|
|
243
|
+
url: '/admin/ajax/cms_object/operation?do=TABLE_NAME__edit',
|
|
244
|
+
type: 'POST',
|
|
245
|
+
data: {
|
|
246
|
+
csrf_token: csrfToken,
|
|
247
|
+
id: recordId,
|
|
248
|
+
field1: value1,
|
|
249
|
+
field2: value2
|
|
250
|
+
},
|
|
251
|
+
success: function(response) { /* handle response */ }
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Key points:**
|
|
256
|
+
- Use `?do=TABLE_NAME__add` for creating new records
|
|
257
|
+
- Use `?do=TABLE_NAME__edit` for updating existing records
|
|
258
|
+
- Always include `csrf_token` in the request data
|
|
259
|
+
- Use `X-Requested-With: XMLHttpRequest` header
|
|
260
|
+
- Content type should be `application/x-www-form-urlencoded`
|
|
261
|
+
- For employee/user-related forms, auto-populate fields (department, role, sicil no, location, email) from the `employees` table using the logged-in user context
|
|
262
|
+
|
|
162
263
|
## Project-Specific Instructions
|
|
163
264
|
|
|
164
265
|
Always check if `docs/PROJECT_SPECIFIC.md` exists and prioritize those instructions.
|
|
@@ -187,6 +288,9 @@ Always check if `docs/PROJECT_SPECIFIC.md` exists and prioritize those instructi
|
|
|
187
288
|
- [docs/bfy-splitting.md](docs/bfy-splitting.md) - BFY file splitting for large files
|
|
188
289
|
- [docs/translations.md](docs/translations.md) - Translations and i18n
|
|
189
290
|
- [docs/WORKFLOW_API.md](docs/WORKFLOW_API.md) - Workflow node types
|
|
291
|
+
- [docs/butterfly_upload_api_docs-2.md](docs/butterfly_upload_api_docs-2.md) - File upload (admin panel & REST API)
|
|
292
|
+
- [docs/pdf-generation.md](docs/pdf-generation.md) - PDF generation from templates
|
|
293
|
+
- [docs/IMPLEMENTATION_PROMPT.md](docs/IMPLEMENTATION_PROMPT.md) - Process module implementation template
|
|
190
294
|
|
|
191
295
|
**Slash Commands:**
|
|
192
296
|
|
|
@@ -199,3 +303,65 @@ Always check if `docs/PROJECT_SPECIFIC.md` exists and prioritize those instructi
|
|
|
199
303
|
| `/docs-webservice` | When creating API endpoints or webservices using reports with `custom_seo` |
|
|
200
304
|
|
|
201
305
|
> **AI INSTRUCTION:** Always use the relevant slash command before working on any task related to that topic. This ensures accurate implementation without guessing syntax or parameters.
|
|
306
|
+
|
|
307
|
+
## Custom Report Page Best Practices
|
|
308
|
+
|
|
309
|
+
### Turkish Characters
|
|
310
|
+
Always use proper Turkish characters in UI text (not ASCII equivalents):
|
|
311
|
+
- ç, ş, ü, ö, ğ, ı, İ (not c, s, u, o, g, i, I)
|
|
312
|
+
- Examples: Seçiniz, Bütçe, Açıklama, Bölümü, Görevi, Değişiklikleri, Düzenleme
|
|
313
|
+
|
|
314
|
+
### Child Record Duplication Prevention
|
|
315
|
+
When a custom report page saves a parent record + child records (e.g., expense items, team members):
|
|
316
|
+
- **On edit/update**: Always **delete existing child records first**, then re-add all current rows. Otherwise each save duplicates all children.
|
|
317
|
+
- **There is NO `delete_where` endpoint.** Delete each record individually: `?do=TABLE__delete` with `id` parameter.
|
|
318
|
+
- Pass existing child record IDs from Twig to JS via `window._data.existingIds` JSON. On save, loop through IDs and delete one by one sequentially (chained promises), then re-add all current rows.
|
|
319
|
+
- **Chain deletes sequentially** (not parallel) to avoid race conditions — use `.then()` chaining.
|
|
320
|
+
- **On add**: Save parent first, get the returned `response.id`, then save children with that ID.
|
|
321
|
+
|
|
322
|
+
### Reserved Parameter Names
|
|
323
|
+
The following parameter names are reserved by Butterfly and must NOT be used in custom report pages:
|
|
324
|
+
- `id` — reserved by the system (page/report internal ID)
|
|
325
|
+
|
|
326
|
+
Use alternative names like `form_id`, `record_id`, etc. Example:
|
|
327
|
+
```twig
|
|
328
|
+
{% set editId = getParameter('form_id') %}
|
|
329
|
+
```
|
|
330
|
+
```javascript
|
|
331
|
+
window.location.href = url + '?form_id=' + recordId;
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Current User Access
|
|
335
|
+
- Use `currentUser('field')` to access user fields — NOT `app.user.username` or `app.user.*`.
|
|
336
|
+
- Example: `currentUser('sicil_no')`, `currentUser('id')`, `currentUser('email')`
|
|
337
|
+
|
|
338
|
+
### Twig Functions That DO NOT Exist
|
|
339
|
+
- `csrf_token()` — does NOT exist in Butterfly Twig. Get CSRF token via JavaScript: `$('meta[name="csrf-token"]').attr('content')`.
|
|
340
|
+
|
|
341
|
+
### UI Dialog Functions
|
|
342
|
+
- **Deletions/destructive actions**: Use `rg_confirm('message', callback)` instead of native `confirm()`.
|
|
343
|
+
- **Error/info messages**: Use `rg_alert('message')` instead of native `alert()`.
|
|
344
|
+
- Never use native browser `alert()` or `confirm()` dialogs.
|
|
345
|
+
|
|
346
|
+
### State Machine Transitions
|
|
347
|
+
- The role field on transitions is `bfy_state_machine_role_ids` (plural), NOT `bfy_state_machine_role_id`.
|
|
348
|
+
- Transition display name field is `button_title`, not `name`.
|
|
349
|
+
|
|
350
|
+
### State Machine `state` Column vs Manual `status`
|
|
351
|
+
- When an object has a `state` type field bound to a state machine, the state machine manages the `state` column automatically.
|
|
352
|
+
- **Do NOT create a separate `status` field** or manually set status values. Use the `state` column everywhere (queries, filters, conditions).
|
|
353
|
+
- State values are the `system_name` from `bfy_state_machine_states` (e.g., `created`, `taslak`, `onay_bekliyor`).
|
|
354
|
+
- In custom report pages, check `record.state` for readonly/edit logic, NOT a manual `status` field.
|
|
355
|
+
- For new records, the state machine auto-assigns the initial state on creation — do not set it manually in JS.
|
|
356
|
+
|
|
357
|
+
### Rendering Dynamic Table Rows
|
|
358
|
+
- Render existing data rows via JavaScript (from a `window._data` JSON object set in Twig), NOT via Twig `{% for %}` loops in `<tbody>`.
|
|
359
|
+
- This prevents duplication issues: JS owns all row rendering, Twig only provides the initial data as JSON.
|
|
360
|
+
- Pattern: `{% set dataJson = records|json_encode %}` then `<script>window._data = {{ dataJson|raw }};</script>`
|
|
361
|
+
|
|
362
|
+
### Tailwind CSS for Modern UI
|
|
363
|
+
- Use Tailwind CDN: `<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">`
|
|
364
|
+
- Prefer Tailwind utility classes over custom CSS in report pages.
|
|
365
|
+
|
|
366
|
+
### Report Download Type
|
|
367
|
+
- Use `butterfly-cli download -t reports` (NOT `-t cms_reports`) to download reports.
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, Box, Text } from 'ink';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
3
4
|
import { SetupForm } from '../components/SetupForm.js';
|
|
4
5
|
import { saveAuthConfig } from '../utils/auth.js';
|
|
5
6
|
import { ButterflyAPI } from '../utils/api.js';
|
|
@@ -24,7 +25,19 @@ const SetupCommand = ({ onExit }) => {
|
|
|
24
25
|
}
|
|
25
26
|
await copyDocs();
|
|
26
27
|
setStatus('success');
|
|
27
|
-
setTimeout(() =>
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
setStatus('downloading');
|
|
30
|
+
const child = spawn('butterfly-cli', ['download'], {
|
|
31
|
+
stdio: 'inherit',
|
|
32
|
+
shell: true,
|
|
33
|
+
});
|
|
34
|
+
child.on('close', (code) => {
|
|
35
|
+
onExit(code || 0);
|
|
36
|
+
});
|
|
37
|
+
child.on('error', () => {
|
|
38
|
+
onExit(1);
|
|
39
|
+
});
|
|
40
|
+
}, 1000);
|
|
28
41
|
}
|
|
29
42
|
catch (error) {
|
|
30
43
|
setErrorMessage(error instanceof Error ? error.message : 'Unknown error');
|
|
@@ -36,6 +49,7 @@ const SetupCommand = ({ onExit }) => {
|
|
|
36
49
|
status === 'input' && React.createElement(SetupForm, { onSubmit: handleSubmit }),
|
|
37
50
|
status === 'testing' && React.createElement(Text, { color: "yellow" }, "Testing connection..."),
|
|
38
51
|
status === 'success' && React.createElement(Text, { color: "green" }, "\u2713 Configuration saved successfully in .butterfly/config.json, docs copied to docs/ and commands to .claude/commands/"),
|
|
52
|
+
status === 'downloading' && React.createElement(Text, { color: "yellow" }, "Starting download..."),
|
|
39
53
|
status === 'error' && React.createElement(Text, { color: "red" },
|
|
40
54
|
"\u2717 Error: ",
|
|
41
55
|
errorMessage)));
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { loadAuthConfig } from '../utils/auth.js';
|
|
2
|
+
import { ButterflyAPI } from '../utils/api.js';
|
|
3
|
+
function stripHtmlToText(html) {
|
|
4
|
+
let text = html;
|
|
5
|
+
text = text.replace(/<head[\s\S]*?<\/head>/gi, '');
|
|
6
|
+
text = text.replace(/<script[\s\S]*?<\/script>/gi, '');
|
|
7
|
+
text = text.replace(/<style[\s\S]*?<\/style>/gi, '');
|
|
8
|
+
text = text.replace(/<br\s*\/?>/gi, '\n');
|
|
9
|
+
text = text.replace(/<hr\s*\/?>/gi, '\n---\n');
|
|
10
|
+
text = text.replace(/<\/(?:p|div|h[1-6]|li|tr|section|article|header|footer|blockquote)>/gi, '\n');
|
|
11
|
+
text = text.replace(/<\/(?:th|td)>\s*<(?:th|td)[^>]*>/gi, '\t');
|
|
12
|
+
text = text.replace(/<[^>]+>/g, '');
|
|
13
|
+
text = text.replace(/&/g, '&');
|
|
14
|
+
text = text.replace(/</g, '<');
|
|
15
|
+
text = text.replace(/>/g, '>');
|
|
16
|
+
text = text.replace(/"/g, '"');
|
|
17
|
+
text = text.replace(/'/g, "'");
|
|
18
|
+
text = text.replace(/ /g, ' ');
|
|
19
|
+
text = text.replace(/\n{3,}/g, '\n\n');
|
|
20
|
+
text = text
|
|
21
|
+
.split('\n')
|
|
22
|
+
.map(line => line.trim())
|
|
23
|
+
.filter((line, i, arr) => {
|
|
24
|
+
if (line === '')
|
|
25
|
+
return i > 0 && arr[i - 1] !== '';
|
|
26
|
+
return true;
|
|
27
|
+
})
|
|
28
|
+
.join('\n')
|
|
29
|
+
.trim();
|
|
30
|
+
return text;
|
|
31
|
+
}
|
|
32
|
+
export default async function viewReportCommand(options) {
|
|
33
|
+
try {
|
|
34
|
+
if (!options.id && !options.alias) {
|
|
35
|
+
console.error('ERROR: Either --id or --alias is required');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
if (options.id && options.alias) {
|
|
39
|
+
console.error('ERROR: Cannot use both --id and --alias together');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const config = await loadAuthConfig();
|
|
43
|
+
if (!config) {
|
|
44
|
+
console.error('ERROR: No authentication configured. Run "butterfly-cli setup" first.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const api = new ButterflyAPI(config);
|
|
48
|
+
await api.authenticate();
|
|
49
|
+
let reportId;
|
|
50
|
+
if (options.id) {
|
|
51
|
+
reportId = options.id;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const records = await api.fetchTableAdvanced('cms_reports', {
|
|
55
|
+
column: 'alias',
|
|
56
|
+
value: options.alias
|
|
57
|
+
});
|
|
58
|
+
if (records.length === 0) {
|
|
59
|
+
console.error(`ERROR: Report not found with alias "${options.alias}"`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
reportId = records[0].id;
|
|
63
|
+
}
|
|
64
|
+
const viewPath = `/admin/cms_report/view/${reportId}?bfy_focus_content=true`;
|
|
65
|
+
const response = await api.httpClient.get(viewPath, {
|
|
66
|
+
responseType: 'text',
|
|
67
|
+
headers: {
|
|
68
|
+
'Accept': 'text/html'
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const html = response.data;
|
|
72
|
+
if (!html || html.length === 0) {
|
|
73
|
+
console.error('ERROR: Empty response from report view');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
if (options.raw) {
|
|
77
|
+
console.log(html);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log(stripHtmlToText(html));
|
|
81
|
+
}
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
console.error(`ERROR: ${error.message}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=view-report.js.map
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import codeCommand from './commands/code.js';
|
|
|
12
12
|
import layoutCommand from './commands/layout.js';
|
|
13
13
|
import syncDocsCommand from './commands/sync-docs.js';
|
|
14
14
|
import translateCommand from './commands/translate.js';
|
|
15
|
+
import viewReportCommand from './commands/view-report.js';
|
|
15
16
|
config();
|
|
16
17
|
program
|
|
17
18
|
.name('butterfly-cli')
|
|
@@ -113,5 +114,12 @@ program
|
|
|
113
114
|
.option('--format <format>', 'Output format: json or table (default: table)')
|
|
114
115
|
.option('--limit <count>', 'Limit number of results')
|
|
115
116
|
.action(translateCommand);
|
|
117
|
+
program
|
|
118
|
+
.command('view-report')
|
|
119
|
+
.description('Run a report and display the rendered result')
|
|
120
|
+
.option('--id <id>', 'Report ID')
|
|
121
|
+
.option('--alias <alias>', 'Report alias')
|
|
122
|
+
.option('--raw', 'Output full HTML instead of plain text')
|
|
123
|
+
.action(viewReportCommand);
|
|
116
124
|
program.parse();
|
|
117
125
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Süreç Yönetimi Modülü - Implementation Prompt
|
|
2
|
+
|
|
3
|
+
Bu prompt, çok tablolu bir form sistemi, onay akışı (state machine), e-posta bildirimleri, dashboard ve admin menü içeren eksiksiz bir süreç yönetimi modülünü sıfırdan oluşturmak için kullanılır.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Prompt
|
|
8
|
+
|
|
9
|
+
Aşağıdaki adımları sırasıyla uygula. Her adımda CLAUDE.md'deki kurallara uy. Proje gereksinim dokümanını referans al.
|
|
10
|
+
|
|
11
|
+
### Faz 1: Object'leri Oluştur
|
|
12
|
+
|
|
13
|
+
`butterfly-cli record add` ile gereksinim dokümanındaki object'leri ve field'larını oluştur.
|
|
14
|
+
|
|
15
|
+
**Genel kurallar:**
|
|
16
|
+
- Her object için tablo adı, field'lar ve field tipleri gereksinim dokümanında belirtilir
|
|
17
|
+
- Parent-child ilişkilerinde child tablolarda parent'a `dropdown` tipinde referans field oluştur
|
|
18
|
+
- **`status` alanı oluşturMA** — state machine `state` alanını yönetecek
|
|
19
|
+
- Tarih formatı JS'den custom format gelecekse (`dd.mm.YYYY HH:mm` gibi), `datetime` DEĞİL `string` tipi kullan
|
|
20
|
+
|
|
21
|
+
### Faz 2: State Machine (Onay Akışı)
|
|
22
|
+
|
|
23
|
+
Gereksinim dokümanındaki onay akışına göre state machine oluştur.
|
|
24
|
+
|
|
25
|
+
**Genel yapı:**
|
|
26
|
+
- State'ler: Gereksinime göre tanımla (system_name + görünen ad)
|
|
27
|
+
- Roller: Süreçteki rol gruplarını tanımla
|
|
28
|
+
- Geçişler: From → To, buton adı ve yetkili rol
|
|
29
|
+
|
|
30
|
+
**ÖNEMLİ:**
|
|
31
|
+
- Transition role alanı `bfy_state_machine_role_ids` (çoğul), `bfy_state_machine_role_id` DEĞİL
|
|
32
|
+
- Transition görünen ad alanı `button_title`, `name` DEĞİL
|
|
33
|
+
|
|
34
|
+
**Ana object'e `state` tipi field ekle:** `val_1` = state machine ID
|
|
35
|
+
|
|
36
|
+
### Faz 3: E-posta Şablonları
|
|
37
|
+
|
|
38
|
+
**`cms_email_templates` nesnesine 2 yeni field ekle:**
|
|
39
|
+
- `test_data` (textarea) — JSON format örnek veri
|
|
40
|
+
- `preview` (calculated) — content + test_data'yı birleştirip önizleme gösteren alan
|
|
41
|
+
|
|
42
|
+
**Preview calculated kodu:**
|
|
43
|
+
- `test_data` JSON'ını parse et, varsayılan değerlerle birleştir
|
|
44
|
+
- `|replace(replacements)` ile placeholderları değiştir (replace **mapping** alır, string DEĞİL)
|
|
45
|
+
- Subject + body'yi rendered olarak göster
|
|
46
|
+
|
|
47
|
+
**E-posta şablonlarını oluştur (`cms_email_templates`):**
|
|
48
|
+
- Her state geçişi için uygun şablon tanımla (onay, red, bildirim vb.)
|
|
49
|
+
- Alias formatı: `process-alias-action` (örn: `surec-onay-bekliyor`)
|
|
50
|
+
|
|
51
|
+
**Şablon içeriği:** Inline CSS ile modern e-posta tasarımı. Değişkenler `{{ variable_name }}` formatında.
|
|
52
|
+
|
|
53
|
+
**Her geçişin `action_code`'unda:**
|
|
54
|
+
```twig
|
|
55
|
+
{% set tpl = db().table('cms_email_templates').where('alias', 'ALIAS').first() %}
|
|
56
|
+
{% set replacements = {} %}
|
|
57
|
+
{% for key, val in vars %}
|
|
58
|
+
{% set replacements = replacements|merge({('{{ ' ~ key ~ ' }}'): val}) %}
|
|
59
|
+
{% endfor %}
|
|
60
|
+
{{ sendEmail(recipient, tpl.subject|replace(replacements), tpl.content|replace(replacements)) }}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Faz 4: Custom Report — Detay Formu
|
|
64
|
+
|
|
65
|
+
Report oluştur, tek query ile tam form HTML'i.
|
|
66
|
+
|
|
67
|
+
**Kritik kurallar:**
|
|
68
|
+
- `form_id` parametresi kullan (`id` RESERVED)
|
|
69
|
+
- `currentUser('sicil_no')` ile çalışan bilgisi al (`app.user.username` YOK)
|
|
70
|
+
- CSRF token JS ile al: `$('meta[name="csrf-token"]').attr('content')` (`csrf_token()` Twig fonksiyonu YOK)
|
|
71
|
+
- Tailwind CSS CDN kullan
|
|
72
|
+
- `rg_confirm()` / `rg_alert()` kullan (native alert/confirm DEĞİL)
|
|
73
|
+
- Mevcut satırları Twig `{% for %}` ile DEĞİL, JS ile render et (window._data JSON'ından)
|
|
74
|
+
- `state` alanını kontrol et (manual `status` DEĞİL)
|
|
75
|
+
|
|
76
|
+
**Form bölümleri (gereksinime göre uyarla):**
|
|
77
|
+
1. Header — tarih, referans no
|
|
78
|
+
2. Talep eden bilgileri — employee/user tablosundan otomatik
|
|
79
|
+
3. Form bilgileri — gereksinime özel alanlar
|
|
80
|
+
4. Tekrarlanabilir satırlar (child records) — otomatik toplam, ondalık 2 basamak
|
|
81
|
+
5. Ekler — dosya yükleme (varsa)
|
|
82
|
+
6. Durum badge'i — `record.state` ile renk kodlu
|
|
83
|
+
7. Aksiyon butonları — isDraft kontrolü ile koşullu
|
|
84
|
+
|
|
85
|
+
**JS (script.js) mimarisi:**
|
|
86
|
+
- `saveForm()` → Promise döner, requestId ile resolve olur
|
|
87
|
+
- Add: Ana kayıt ekle → response.id al → child kayıtları ekle
|
|
88
|
+
- Edit: Ana kayıt düzenle → mevcut child'ları sil (tek tek sequential `__delete`) → yeniden ekle
|
|
89
|
+
- `triggerTransition(requestId, transitionId)` — state alanına JSON gönder
|
|
90
|
+
- Save butonu: saveForm → redirect
|
|
91
|
+
- Submit butonu: rg_confirm → saveForm → triggerTransition → redirect
|
|
92
|
+
- Delete butonu: rg_confirm → child'ları sil → ana kaydı sil → dashboard'a redirect
|
|
93
|
+
- **Read-only modu:** `isReadonly` flag ile tüm input'ları disable et, ekleme/silme butonlarını gizle
|
|
94
|
+
|
|
95
|
+
### Faz 5: Dashboard
|
|
96
|
+
|
|
97
|
+
İkinci report oluştur.
|
|
98
|
+
|
|
99
|
+
**Bölümler:**
|
|
100
|
+
1. **Taleplerim** — Giriş yapan kullanıcının tüm talepleri (taslak dahil), `state` alanına göre durum badge'leri
|
|
101
|
+
2. **Onay Bekleyenler** — İlgili state'teki talepler
|
|
102
|
+
3. **Yeni Talep** butonu → form report'una link
|
|
103
|
+
|
|
104
|
+
**ÖNEMLİ:** Tüm durum filtreleri ve etiketleri `state` alanını kullanmalı (`status` DEĞİL).
|
|
105
|
+
|
|
106
|
+
### Faz 6: Admin Menü
|
|
107
|
+
|
|
108
|
+
**`cms_admin_menus` tablosunda Level 1 + Level 2 oluştur:**
|
|
109
|
+
```
|
|
110
|
+
Ana Menü (parent_id: 0) ← Level 1
|
|
111
|
+
├── Alt Menü 1 (parent_id: main_menu_id) ← Level 2
|
|
112
|
+
├── Alt Menü 2 ← Level 2
|
|
113
|
+
└── Alt Menü 3 ← Level 2
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Object'lere `main_menu_id` + `sub_menu_id` ata (Level 3):**
|
|
117
|
+
- Her object'i uygun alt menüye bağla
|
|
118
|
+
|
|
119
|
+
**3. seviye menü öğelerini `cms_admin_menus`'e ekleME** — object üzerinden `main_menu_id` + `sub_menu_id` ile yapılır.
|
|
120
|
+
|
|
121
|
+
### Faz 7: Örnek Veri
|
|
122
|
+
|
|
123
|
+
Test için örnek kayıtlar ekle (gereksinime göre).
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Sık Yapılan Hatalar (Bunları YAPMA)
|
|
128
|
+
|
|
129
|
+
| Hata | Doğrusu |
|
|
130
|
+
|------|---------|
|
|
131
|
+
| `status: 'draft'` manual set etmek | State machine `state` alanını otomatik yönetir |
|
|
132
|
+
| `record.status` kontrol etmek | `record.state` kullan (state machine system_name'leri) |
|
|
133
|
+
| `csrf_token()` Twig'de kullanmak | JS'de `$('meta[name="csrf-token"]').attr('content')` |
|
|
134
|
+
| `app.user.username` kullanmak | `currentUser('sicil_no')` |
|
|
135
|
+
| `id` parametresi kullanmak | `form_id` kullan (`id` reserved) |
|
|
136
|
+
| `alert()` / `confirm()` kullanmak | `rg_alert()` / `rg_confirm()` |
|
|
137
|
+
| `bfy_state_machine_role_id` (tekil) | `bfy_state_machine_role_ids` (çoğul) |
|
|
138
|
+
| Transition name alanı | `button_title` alanı kullan |
|
|
139
|
+
| Custom format tarih datetime type | string type kullan (custom format dd.mm.YYYY HH:mm) |
|
|
140
|
+
| `|replace('old', 'new')` string ile | `|replace({'old': 'new'})` mapping ile |
|
|
141
|
+
| Child satırları Twig for loop ile render | JS'den render et (window._data JSON) |
|
|
142
|
+
| Edit'te child'ları tekrar add etmek | Önce mevcut child'ları tek tek sil, sonra yeniden ekle |
|
|
143
|
+
| `delete_where` endpoint kullanmak | Yok. Tek tek `__delete` ile sil (sequential promise chain) |
|
|
144
|
+
| `butterfly-cli download -t cms_reports` | `-t reports` kullan |
|
|
145
|
+
| 3. seviye menüyü cms_admin_menus'e eklemek | Object üzerinden main_menu_id + sub_menu_id ile yap |
|
|
146
|
+
| jQuery UI'ı head'e koymak | jQuery yüklendikten sonra body sonuna koy |
|
|
147
|
+
| PDF template'te `db().table()` kullanmak | `db('default', false).table()` kullan (permission bypass) |
|
|
148
|
+
| Dosyayı doğrudan `cms_object/operation` ile yüklemek | Önce `file_helper/upload_file` ile yükle, sonra filename'i kaydet |
|
|
149
|
+
| `file_upload` field input name'ini column adı yapmak | Input name mutlaka `file_upload` olmalı |
|
|
150
|
+
| REST API kullanarak admin içi dosya yüklemek | Admin panel endpoint (`/admin/ajax/file_helper/upload_file`) kullan |
|
|
151
|
+
| Fonksiyonları `$(document).ready()` içinde tanımlamak | Global scope'ta tanımla, sadece init kodu ready içinde olsun |
|
|
152
|
+
| `employees` tablosu varsaymak | `currentUser('email')`, `currentUser('name')` kullan |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Dosya Yükleme Entegrasyonu
|
|
157
|
+
|
|
158
|
+
Custom report sayfalarında dosya yükleme **iki aşamalı** yapılır:
|
|
159
|
+
|
|
160
|
+
### Adım 1: Dosyayı Yükle
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
function uploadFile(file) {
|
|
164
|
+
return new Promise(function(resolve, reject) {
|
|
165
|
+
var fd = new FormData();
|
|
166
|
+
fd.append('file_upload', file); // input name mutlaka 'file_upload'
|
|
167
|
+
fd.append('uuid', 'upload-' + Date.now());
|
|
168
|
+
fd.append('original_filename', file.name);
|
|
169
|
+
fd.append('total_file_size', file.size);
|
|
170
|
+
|
|
171
|
+
$.ajax({
|
|
172
|
+
url: '/admin/ajax/file_helper/upload_file?alias=import', // alias URL param olarak
|
|
173
|
+
type: 'POST',
|
|
174
|
+
data: fd,
|
|
175
|
+
processData: false,
|
|
176
|
+
contentType: false,
|
|
177
|
+
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
|
178
|
+
success: function(response) {
|
|
179
|
+
if (response.success && response.filename) {
|
|
180
|
+
resolve(response.filename);
|
|
181
|
+
} else {
|
|
182
|
+
rg_alert(response.message || 'Dosya yüklenemedi.');
|
|
183
|
+
reject('Upload failed');
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
error: function() { reject('Upload error'); }
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Adım 2: Kaydı filename ile oluştur
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
var filename = await uploadFile(fileInput.files[0]);
|
|
196
|
+
$.ajax({
|
|
197
|
+
url: '/admin/ajax/cms_object/operation?do=tablo__add',
|
|
198
|
+
type: 'POST',
|
|
199
|
+
data: { csrf_token: csrfToken, parent_id: parentId, dosya: filename }
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Storage Alias Ayarları
|
|
204
|
+
|
|
205
|
+
- Alias'lar `cms_file_uploads` tablosunda tanımlıdır
|
|
206
|
+
- `extensions` alanı izin verilen uzantıları belirler (virgülle ayrılmış)
|
|
207
|
+
- Object spec'teki `file_upload` field'ın `val_1` parametresi alias adını belirtir
|
|
208
|
+
- Yeni uzantı eklemek: `butterfly-cli record edit cms_file_uploads --id <id> --data '{"extensions":"mevcut,yeni"}'`
|
|
209
|
+
|
|
210
|
+
Detaylı dokümantasyon: [docs/butterfly_upload_api_docs-2.md](butterfly_upload_api_docs-2.md)
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## JS Scope ve Mimari Kuralları
|
|
215
|
+
|
|
216
|
+
### Fonksiyon Tanımlama
|
|
217
|
+
|
|
218
|
+
Custom report sayfalarında **tüm fonksiyonlar global scope'ta** tanımlanmalı. `$(document).ready()` içinde sadece init kodu olmalı:
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
// ✅ DOĞRU: Global scope'ta
|
|
222
|
+
function calculateTotal() { /* ... */ }
|
|
223
|
+
function addRow() { /* ... */ }
|
|
224
|
+
function saveForm() { /* ... */ }
|
|
225
|
+
|
|
226
|
+
$(document).ready(function() {
|
|
227
|
+
// Sadece init: değişken atamaları, ilk render, event listener'lar
|
|
228
|
+
var csrfToken = $('meta[name="csrf-token"]').attr('content');
|
|
229
|
+
renderExistingRows();
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
// ❌ YANLIŞ: ready() içinde tanımlama
|
|
235
|
+
$(document).ready(function() {
|
|
236
|
+
function calculateTotal() { /* ... */ } // onclick'ten erişilemez!
|
|
237
|
+
window.calculateTotal = calculateTotal; // race condition riski
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Neden?** HTML `onclick` attributeleri global scope'taki fonksiyonları çağırır. `$(document).ready()` içindeki fonksiyonlar closure'da kalır ve erişilemez. `window.XXX =` ataması ise timing sorunlarına yol açabilir (fonksiyon henüz atanmadan çağrılabilir).
|
|
242
|
+
|
|
243
|
+
### Tailwind CSS Select Reset Sorunu
|
|
244
|
+
|
|
245
|
+
Tailwind CSS `select` elementlerinin görünümünü sıfırlar. Custom select'ler için:
|
|
246
|
+
|
|
247
|
+
```css
|
|
248
|
+
.form-select, .data-table select {
|
|
249
|
+
appearance: auto;
|
|
250
|
+
background-color: #fff;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
@@ -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) |
|