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. Standart javascript veya js formatları, katı güvenlik ayarları nedeniyle execution nesnesine 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 execution nesnesini gerektirir. Bu, tasarımcıların halihazırda execution.getVariable() için kullandıkları execution nesnesinin aynısıdır. Belirtilmezse, bu yöntemler boş/null döndürür (null-güvenli/null-safe). Diğer tüm fonksiyonlar execution nesnesi 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() veya wf.addBusinessDays() işlevlerini doğrudan wf.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 daima wf.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, bir wf.* 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ğin var 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.