16. Script Görevleri (Dinamik Hesaplamalar)
Script Görevleri (Script Tasks), süreç yürütülürken satır içi (inline) JavaScript kodunu çalıştırır. İş akışı tasarımcılarının Java kod değişikliğine gerek duymadan dinamik hesaplamalar, veri dönüşümleri ve tarih işlemleri yapmalarını sağlar.
Nasıl Çalışır
Script Görevleri, özel bir GraalJS yapılandırması kullanarak sunucu üzerinde (Flowable motoru) çalışır. Süreç değişkenlerine execution nesnesi aracılığıyla erişir ve bunları değiştirirler.
| Özellik | Detaylar |
|---|---|
| Motor | GraalJS (super-js tanımlayıcısı aracılığıyla) |
| Çalıştığı Yer | Sunucu (Flowable motoru) |
| Erişim | execution.getVariable(), execution.setVariable() |
| Platform yardımcıları | wf.* fonksiyonları (aşağıya bakın) |
| Format | scriptFormat="super-js" (Zorunlu) |
[!IMPORTANT] Script Görevleriniz için
scriptFormat="super-js"kullanmanız ZORUNLUDUR. Standartjavascriptveyajsformatları, katı güvenlik ayarları nedeniyleexecutionnesnesine erişemeyebilir.
[!CAUTION] Güvenlik Uyarısı: Script Görevleri, sunucuda Java çalışma zamanına (runtime) tam erişimle yürütülür. Bu, Scriptlerin sınıf yolundaki (classpath) dosya sistemi, ağ ve sistem işlemleri dahil herhangi bir Java sınıfına erişebileceği anlamına gelir. Platform, BPMN yazarının güvenilir bir yönetici olduğunu varsayar. Sadece süreç dağıtım (deployment) izinlerine sahip kullanıcıların (ROLE_ADMIN) Script Görevleri oluşturmasına veya değiştirmesine izin verilmelidir. Platform, özel Scriptlerin neden olduğu hasarlar için sorumluluk kabul etmez. Üretime (production) dağıtım yapmadan önce tüm Script Görevi kodlarını gözden geçirin.
Platform Yardımcıları (wf.*)
Script Görevleri, salt JavaScript'ten ulaşılamayan Spring tarafından yönetilen verilere (kullanıcılar, roller, süreç geçmişi) ve biçimlendirme araçlarına (tarihler, sayılar, HTML oluşturma) erişim sağlayan kapsamlı bir platform fonksiyonları seti olan wf nesnesine erişime sahiptir.
[!IMPORTANT] Süreç farkındalığı olan fonksiyonlar (Aşağıdaki 5. Bölüm) ilk parametre olarak
executionnesnesini gerektirir. Bu, tasarımcıların halihazırdaexecution.getVariable()için kullandıklarıexecutionnesnesinin aynısıdır. Belirtilmezse, bu yöntemler boş/null döndürür (null-güvenli/null-safe). Diğer tüm fonksiyonlarexecutionnesnesi GEREKTİRMEZ.
1. Kullanıcı ve Organizasyon
| Fonksiyon | Döndürdüğü Değer | Açıklama |
|---|---|---|
wf.getUserFullName(email) |
String |
E-postayı tam adla çözer. Bulunamazsa e-postayı döndürür. |
wf.getUsersByRole(roleName) |
List<String> |
Bir role ait tüm e-postalar (ör. "ROLE_MANAGER"). |
wf.getUserCountByRole(roleName) |
int |
Bir roldeki kullanıcı sayısı. Koşullu mantık için faydalıdır. |
wf.getRoleDisplayName(roleName) |
String |
"ROLE_MANAGER" → "Manager". Öneki (prefix) atar, baş harfini büyütür. |
wf.getAllRoles() |
List<String> |
Sistemdeki tüm rol adları. |
2. Tarih ve Saat
| Fonksiyon | Döndürdüğü Değer | Açıklama |
|---|---|---|
wf.now() |
Date |
Mevcut sunucu tarihi/saati. |
wf.today() |
String |
dd/MM/yyyy formatında bugünün tarihi. |
wf.formatDate(date, pattern) |
String |
Bir tarihi biçimlendirir (ör. "dd MMMM yyyy" → "19 April 2026"). |
wf.formatDateTime(date, pattern) |
String |
formatDate ile aynıdır — tasarımcının anlaşılabilirliği için mevcuttur. |
wf.addDays(date, n) |
Date |
Bir tarihe takvim günleri ekler (veya negatif değerle çıkarır). |
wf.addBusinessDays(date, n) |
Date |
İş günleri ekler (hafta sonlarını atlar). |
wf.daysBetween(start, end) |
int |
İki tarih arasındaki takvim günleri. |
wf.businessDaysBetween(start, end) |
int |
İki tarih arasındaki iş günleri (hafta sonlarını hariç tutar). |
wf.isWeekend(date) |
boolean |
Bu tarih Cumartesi veya Pazar mı? |
wf.isOverdue(date) |
boolean |
Bu tarih geçmişte mi kaldı? |
wf.getYear(date) |
int |
Bir tarihten yılı çıkarır. Belge numaralandırması için faydalıdır. |
Tarih Deseni Hızlı Başvuru
formatDate ve formatDateTime, standart Java SimpleDateFormat desenlerini kabul eder. İhtiyacınız olan biçim dizesini (format string) oluşturmak için aşağıdaki belirteçleri (tokens) kullanın:
| Belirteç | Anlamı | Örnek |
|---|---|---|
d |
Ayın günü (başında sıfır yok) | 2 |
dd |
Ayın günü (başında sıfır var) | 02 |
M |
Ay numarası (başında sıfır yok) | 5 |
MM |
Ay numarası (başında sıfır var) | 05 |
MMM |
Ay kısaltması | May |
MMMM |
Ay tam adı | May |
yy |
Yıl 2 haneli | 26 |
yyyy |
Yıl 4 haneli | 2026 |
HH |
Saat 24s (başında sıfır var) | 21 |
hh |
Saat 12s (başında sıfır var) | 09 |
mm |
Dakika (başında sıfır var) | 55 |
ss |
Saniye (başında sıfır var) | 07 |
a |
AM/PM işareti | PM |
Kullanıma hazır yaygın desenler:
| Desen | Çıktı |
|---|---|
dd/MM/yyyy |
02/05/2026 |
dd.MM.yyyy |
02.05.2026 |
yyyy-MM-dd |
2026-05-02 |
dd MMMM yyyy |
02 May 2026 |
dd MMM yyyy |
02 May 2026 |
dd/MM/yyyy HH:mm |
02/05/2026 21:55 |
dd MMMM yyyy HH:mm |
02 May 2026 21:55 |
yyyy-MM-dd HH:mm:ss |
2026-05-02 21:55:07 |
hh:mm a |
09:55 PM |
İpucu:
wf.addDays()veyawf.addBusinessDays()işlevlerini doğrudanwf.formatDate()içine yerleştirebilirsiniz:wf.formatDate(wf.addBusinessDays(wf.now(), 5), 'dd MMMM yyyy') // → "09 May 2026" (bugünden itibaren 5 iş günü)
3. Sayı ve Para Birimi Biçimlendirme
| Fonksiyon | Döndürdüğü Değer | Açıklama |
|---|---|---|
wf.formatNumber(number, decimals) |
String |
Sabit ondalıklarla biçimlendirir: 1234.5 → "1,234.50". |
wf.formatCurrency(number, symbol) |
String |
formatCurrency(1500.00, "₺") → "₺1,500.00". |
wf.formatPercent(number) |
String |
0.85 → "85%". |
wf.numberToWordsEN(number) |
String |
1500 → "One Thousand Five Hundred". |
wf.numberToWordsTR(number) |
String |
1500 → "Bin Beş Yüz". Türkçe kuralları uygulanır. |
wf.round(number, decimals) |
double |
N ondalık basamağa yuvarlar. |
wf.sum(jsonArray, field) |
double |
Bir JSON dizisindeki sayısal bir alanı toplar. |
wf.average(jsonArray, field) |
double |
Bir JSON dizisindeki sayısal bir alanın ortalamasını alır. |
4. Dize ve Metin
| Fonksiyon | Döndürdüğü Değer | Açıklama |
|---|---|---|
wf.capitalize(str) |
String |
"hello world" → "Hello world". |
wf.titleCase(str) |
String |
"john doe" → "John Doe". |
wf.truncate(str, maxLen) |
String |
Üç nokta ile keser: "Long text...". |
wf.padLeft(str, len, char) |
String |
padLeft("42", 6, "0") → "000042". |
wf.nl2br(str) |
String |
Satır sonlarını <br/> etiketlerine dönüştürür. PDF'lerdeki textarea içeriği için çok önemlidir. |
wf.escapeHtml(str) |
String |
Güvenli HTML yerleştirme için <>&"' karakterlerinden kaçış (escape) sağlar. |
wf.stripHtml(str) |
String |
Bir dizedeki tüm HTML etiketlerini kaldırır. |
⚠️ Güvenlik: Kullanıcı girişini
${var}yer tutucuları aracılığıyla PDF şablonlarına yerleştirirken daimawf.escapeHtml()kullanın. Bu olmazsa, bir kullanıcı form alanı aracılığıyla PDF'e HTML/CSS enjekte edebilir.
5. Süreç ve İş Akışı (execution gerektirir)
| Fonksiyon | Döndürdüğü Değer | Açıklama |
|---|---|---|
wf.getProcessName(execution) |
String |
Mevcut süreç tanımının insan tarafından okunabilir adı. |
wf.getProcessStartDate(execution) |
Date |
Mevcut süreç örneğinin başlatıldığı tarih. |
wf.getCompletedTaskUser(execution, taskDefinitionKey) |
String |
Belirli bir geçmiş görevi kimin tamamladığının e-postası. |
wf.getCompletedTaskDate(execution, taskDefinitionKey) |
Date |
Belirli bir geçmiş görevin tamamlandığı tarih. |
wf.getProcessAuditTrail(execution) |
List<Map> |
Tam yapılandırılmış geçmiş: tamamlanan her görev için taskName, completedBy, completedDate. |
6. HTML Oluşturucuları (PDF Güç Araçları)
Bu işlevler, ham JSON verilerini generatePdf şablonlarındaki ${var} yer tutucularına doğrudan düşen biçimlendirilmiş HTML'ye dönüştürür. Tümü isteğe bağlı bir style parametresini (kök öğeye uygulanan CSS dizesi) kabul eder. Özel bir stil sağlanmadığında varsayılan profesyonel stiller kullanılır.
| Fonksiyon | Döndürdüğü Değer | Açıklama |
|---|---|---|
wf.toHtmlTable(jsonArray, columns [, style]) |
String |
JSON dizisi → stillendirilmiş <table>. Anahtar adlarından otomatik başlık (header) oluşturur. |
wf.toHtmlTableWithHeaders(jsonArray, columns, headers [, style]) |
String |
Aynı işlemi özel başlık adlarıyla yapar. |
wf.toHtmlGroupedTable(jsonArray, groupByFields, columns [, style]) |
String |
Alt başlık satırları ve otomatik alt toplamları (subtotals) olan gruplanmış tablo. Çoklu düzey gruplama desteklenir. |
wf.toHtmlList(jsonArray, field) |
String |
Tek bir alanı çıkarır → <ul><li> listesi. |
wf.toHtmlOrderedList(jsonArray, field) |
String |
Aynısı ancak <ol> (numaralı) olarak. |
wf.toHtmlKeyValue(jsonObject) |
String |
Düz JSON → anahtar-değer (key-value) çiftleri. |
wf.toHtmlBadge(text, color) |
String |
Renkli rozet/etiket (badge/tag). |
wf.toHtmlSignatureLine(name, title) |
String |
İmza bloğu: ad, unvan, imza çizgisi, tarih. |
wf.toHtmlQrCode(data, size) |
String |
Base64 ile kodlanmış karekod (QR code) <img> etiketi. |
wf.toHtmlBarcode(data, type) |
String |
Base64 ile kodlanmış barkod <img> etiketi. CODE128 ve EAN13 destekler. |
wf.* Örnekleri
Örnek 1: İsimleri çözümleme ve tarihleri biçimlendirme
<scriptTask id="prepareLabels" name="Prepare Labels" scriptFormat="super-js">
<script>
var email = execution.getVariable('assignedUser');
execution.setVariable('assignedUserName', wf.getUserFullName(email));
execution.setVariable('formattedDate', wf.formatDate(wf.now(), 'dd MMMM yyyy'));
execution.setVariable('dueDate', wf.formatDate(wf.addBusinessDays(wf.now(), 5), 'dd/MM/yyyy'));
</script>
</scriptTask>
Örnek 2: Yardımcılar + generatePdf ile profesyonel bir fatura oluşturma
<!-- Step 1: Prepare invoice data -->
<scriptTask id="prepInvoice" name="Prepare Invoice" scriptFormat="super-js">
<script>
var items = execution.getVariable('lineItems');
var total = wf.sum(items, 'amount');
execution.setVariable('invoiceTable', wf.toHtmlTable(items, 'name,quantity,amount'));
execution.setVariable('totalFormatted', wf.formatCurrency(total, '₺'));
execution.setVariable('totalInWords', wf.numberToWordsTR(Math.floor(total)).toString() + ' TL');
var ref = 'INV-' + wf.getYear(wf.now()) + '-' + wf.padLeft(
execution.getProcessInstanceId().substring(0, 4).toUpperCase(), 4, '0');
execution.setVariable('invoiceNumber', ref);
execution.setVariable('signatureBlock',
wf.toHtmlSignatureLine(wf.getUserFullName(execution.getVariable('initiator')), 'Requester'));
</script>
</scriptTask>
<!-- Step 2: Generate the PDF -->
<serviceTask id="genPdf" flowable:delegateExpression="${generatePdf}">
<extensionElements>
<flowable:field name="outputVariable" stringValue="invoicePdf"/>
<flowable:field name="outputFileName" stringValue="invoice"/>
<flowable:field name="template">
<flowable:string><![CDATA[
<html><body style="font-family:Arial,sans-serif;">
<h1>Invoice ${invoiceNumber}</h1>
<p>Date: ${formattedDate}</p>
${invoiceTable}
<p><strong>Total: ${totalFormatted}</strong></p>
<p>Amount in words: ${totalInWords}</p>
${signatureBlock}
</body></html>
]]></flowable:string>
</flowable:field>
</extensionElements>
</serviceTask>
Örnek 3: Uyumluluk (compliance) PDF'inde süreç denetim izi (audit trail)
<scriptTask id="buildAudit" name="Build Audit Trail" scriptFormat="super-js">
<script>
var trail = wf.getProcessAuditTrail(execution);
var jsonStr = JSON.stringify(trail);
execution.setVariable('auditTable',
wf.toHtmlTableWithHeaders(jsonStr, 'taskName,completedBy,completedDate',
'Step,Completed By,Date'));
execution.setVariable('processName', wf.getProcessName(execution));
</script>
</scriptTask>
Örnek 4: Belge Referans Numaraları
// Kural: ÖNEK + YIL + kısa süreç ID'si
var ref = 'INV-' + wf.getYear(wf.now()) + '-' + wf.padLeft(
execution.getProcessInstanceId().substring(0, 4).toUpperCase(), 4, '0');
// → "INV-2026-70BE"
execution.setVariable('invoiceNumber', ref);
İpucu: Bu, kullanım senaryolarının %90'ını kapsar. Yasal uyumlu ardışık numaralandırmaya (legal-compliant sequential numbering) (
INV-2026-0001,0002, ...) ihtiyaç duyulursa, bu özel bir veritabanı sıralama (sequence) tablosu gerektirir — bu gelecekteki bir özelliktir, birwf.*yardımcısı değildir.
Null-güvenli: Tüm
wf.*işlevleri null/boş girdileri zarif bir şekilde işler ve asla istisna (exception) fırlatmaz.
Temel Sözdizimi
<scriptTask id="myScript" name="Calculate Values" scriptFormat="super-js">
<script>
// Read variables
var inputValue = execution.getVariable('inputField');
// Perform calculations
var result = inputValue * 2;
// Store result for next task
execution.setVariable('calculatedResult', result);
</script>
</scriptTask>
⚠️ Kritik: Tip Güvenliği ve Dönüştürme (Type Safety & Casting)
Kullanıcı Görevi Formlarında (User Task Forms) kullanılacak değişkenleri ayarlarken, tipin form özelliği tipiyle eşleştiğinden emin olmalısınız.
- Dize Form Özellikleri (
type="string"): Bir sayı hesaplarsanız (örneğinvar sum = 10 + 20), hedef form özelliği bir dize (string) ise, bunu kaydetmeden önce ZORUNLU OLARAK bir dizeye dönüştürmelisiniz.// YANLIŞ: Bir Tamsayı (Integer) ayarlar, Kullanıcı Görevinde ClassCastException hatasına neden olur execution.setVariable('average', 100); // DOĞRU: Dizeye (String) dönüştürür execution.setVariable('average', (100).toString()); - Long/Double Form Özellikleri: Sayıları doğrudan saklayabilirsiniz.
- Mantıksal (Boolean): Kesin boolean değerleri saklayın (
true/false).
Örnek 1: Birden Fazla Sayıdan Toplamı Hesaplama
Kullanıcı 3 sayı girer, Script Görevi (Script Task) toplamı hesaplar:
<!-- User enters numbers -->
<userTask id="enterNumbers" name="Enter Numbers" flowable:assignee="${initiator}">
<extensionElements>
<activiti:formProperty id="num1" name="First Number" type="long" required="true"/>
<activiti:formProperty id="num2" name="Second Number" type="long" required="true"/>
<activiti:formProperty id="num3" name="Third Number" type="long" required="true"/>
</extensionElements>
</userTask>
<sequenceFlow sourceRef="enterNumbers" targetRef="calculateTotal"/>
<!-- Calculate total -->
<scriptTask id="calculateTotal" name="Calculate Total" scriptFormat="super-js">
<script>
var n1 = execution.getVariable('num1');
var n2 = execution.getVariable('num2');
var n3 = execution.getVariable('num3');
var total = n1 + n2 + n3;
var average = total / 3;
execution.setVariable('total', total);
// Convert to String because the form property 'average' is type="string"
var avgStr = (Math.round(average * 100) / 100).toString();
execution.setVariable('average', avgStr);
</script>
</scriptTask>
<sequenceFlow sourceRef="calculateTotal" targetRef="showResults"/>
<!-- Results are available as read-only fields -->
<userTask id="showResults" name="View Results" flowable:assignee="${initiator}">
<extensionElements>
<activiti:formProperty id="total" name="Total" type="long" writable="false"/>
<!-- Note type="string" here requires the .toString() conversion above -->
<activiti:formProperty id="average" name="Average" type="string" writable="false"/>
</extensionElements>
</userTask>
Örnek 2: Bir Tarihten İtibaren Geçen Günleri Hesaplama
Kullanıcı bir tarih girer, Script Görevi (Script Task) kaç gün geçtiğini hesaplar:
<!-- User enters a date -->
<userTask id="enterDate" name="Enter Date" flowable:assignee="${initiator}">
<extensionElements>
<activiti:formProperty id="targetDate" name="Select a Date" type="date" required="true"/>
</extensionElements>
</userTask>
<sequenceFlow sourceRef="enterDate" targetRef="calculateDays"/>
<!-- Calculate days passed -->
<scriptTask id="calculateDays" name="Calculate Days" scriptFormat="super-js">
<script>
var targetDate = execution.getVariable('targetDate');
var today = new Date();
// targetDate is a java.util.Date, get millis directly
var targetMillis = targetDate.getTime();
var todayMillis = today.getTime();
var diffMillis = todayMillis - targetMillis;
var daysPassed = Math.floor(diffMillis / (1000 * 60 * 60 * 24));
execution.setVariable('daysPassed', daysPassed);
execution.setVariable('isInPast', daysPassed > 0);
execution.setVariable('isInFuture', daysPassed < 0);
</script>
</scriptTask>
Örnek 3: Dize İşlemleri ve Koşullu Mantık
<scriptTask id="processText" name="Process Text" scriptFormat="super-js">
<script>
var description = execution.getVariable('description') || '';
// Check for keywords
var isUrgent = description.toLowerCase().includes('urgent');
var isPriority = description.toLowerCase().includes('priority');
// Count words
var wordCount = description.trim().split(/\s+/).length;
// Set priority level
var priorityLevel = 'normal';
if (isUrgent) priorityLevel = 'high';
if (isPriority && isUrgent) priorityLevel = 'critical';
execution.setVariable('isUrgent', isUrgent);
execution.setVariable('wordCount', wordCount);
execution.setVariable('priorityLevel', priorityLevel);
</script>
</scriptTask>
Örnek 4: Doğum Yılından Yaş Hesaplama
<scriptTask id="calculateAge" name="Calculate Age" scriptFormat="super-js">
<script>
var birthYear = execution.getVariable('birthYear');
var currentYear = new Date().getFullYear();
var age = currentYear - birthYear;
execution.setVariable('age', age);
execution.setVariable('isAdult', age >= 18);
execution.setVariable('ageGroup', age < 18 ? 'Minor' : (age < 65 ? 'Adult' : 'Senior'));
</script>
</scriptTask>
Script Görevi (Script Task) vs Hizmet Görevi (Service Task) vs JUEL
| Özellik | Script Görevi (Script Task) | Hizmet Görevi (Service Task) | JUEL İfadesi |
|---|---|---|---|
| Kod Konumu | BPMN XML içinde | Java sınıfı | Niteliklerde/koşullarda |
| Kimin yazabileceği | İş akışı tasarımcısı | Java geliştiricisi | İş akışı tasarımcısı |
| Karmaşıklık | Orta düzey mantık | Karmaşık mantık | Basit ifadeler |
| Döngüler/koşullar | ✅ Evet | ✅ Evet | ❌ Hayır |
| Tarih işlemleri | ✅ Evet | ✅ Evet | Sınırlı |
| Test Edilebilir | Zor | Kolay | Uygulanamaz |
| Dağıtım (Deployment) | BPMN yükle | Uygulamayı yeniden dağıt | BPMN yükle |
İpucu: Dış entegrasyonlar gerektirmeyen orta düzey karmaşıklıktaki mantık için Script Görevlerini (Script Tasks) kullanın. Karmaşık iş mantığı, veritabanı çağrıları veya API entegrasyonları için Java temsilcileri (delegates) ile Hizmet Görevlerini (Service Tasks) kullanın.