21. Desteklenen İş Akışı Desenleri

1. Ardışık Onay (Sequential Approval)

Görevler bir kullanıcıdan diğerine sırayla akar.

  • Örnek: Çalışan gönderir → Yönetici inceler → Sonuç bildirimi

2. İnceleme/Revizyon Döngüsü (Review/Revision Loop)

Bir ağ geçidi (gateway), bir karara bağlı olarak süreci önceki bir göreve geri yönlendirir.

  • Örnek: Eğer ${reviewDecision == 'revise'} ise, hazırlık görevine geri dön.

3. Dinamik Rol Ataması

Süreç başlatıcısı bir rol seçer; görev o gruba atanır.

  • Örnek desen: Bir rolü yakalamak için role_* alanı kullanın, ardından flowable:candidateGroups="${role_dept}" ile atama yapın.

4. Dinamik Kullanıcı Ataması

Süreç başlatıcısı belirli bir kullanıcıyı seçer; sonraki görev onlara atanır.

  • Şu örnekte gösterilmiştir: candidate_interview.bpmn (mülakatçıları atamak için users_panel kullanır)

5. Dosya Alışverişi İş Akışı

Kullanıcılar bir görev sırasında dosya yükleyebilir ve sonraki kullanıcılar bunları görüntüleyebilir/indirebilir.

  • Şu örneklerde gösterilmiştir: expense_reimbursement.bpmn (makbuz yükleme), policy_acknowledgment.bpmn (politika belgesi)

6. Tarih İşleme Desenleri (Date Handling Patterns)

Workingflow'daki tarihler, Flowable'ın dahili DateFormType'ı ile uyumluluk için java.util.Date olarak saklanır. Bu bölüm tüm yaygın tarih senaryolarını kapsar.

Yetenekler Özeti

# Senaryo Yöntem Nasıl
1 Tarihi sonraki görevde salt okunur (read-only) göster Yerel BPMN type="date" writable="false"
2 Tarihi zamanlayıcı yükseltme süresi (escalation deadline) olarak kullan Yerel BPMN <timeDate>${dueDate}</timeDate>
3a Bir ağ geçidinde iki tarihi karşılaştır Yerel UEL ${endDate.after(startDate)}
3b Bir ağ geçidinde tarihi "şu an" (now) ile karşılaştır GraalJS + Ağ Geçidi Script (script) boolean ayarlar → ağ geçidi dallanır
4 Daha sonra kullanmak için iş akışı başlangıç zamanını yakala GraalJS Script new java.util.Date() saklar
5 Bir döngüde veya sonraki bir görevde tarihi düzenle Yerel BPMN type="date" (yazılabilir - writable) — önceden doldurulmuş

Kullanılabilir Yöntemler (java.util.Date)

Yöntem Döndürdüğü Kullanım
.after(otherDate) boolean Bu tarih diğerinden sonra mı?
.before(otherDate) boolean Bu tarih diğerinden önce mi?
.getTime() long Dönemden (epoch) bu yana geçen milisaniye (aritmetik işlemler için)
.equals(otherDate) boolean Kesin eşitlik kontrolü
.toInstant() Instant Daha fazla işlem için java.time.Instant nesnesine dönüştürür

Senaryo 1: Salt Okunur Tarih Gösterimi (Read-Only Date Display)

Bir görevde girilen tarih, daha sonraki bir görevde devre dışı bırakılmış (disabled) bir alan olarak görüntülenir.

<!-- Task 1: User enters a date -->
<activiti:formProperty id="dueDate" name="Due Date" type="date" required="true"/>

<!-- Task 2: Same date shown read-only -->
<activiti:formProperty id="dueDate" name="Due Date" type="date" writable="false"/>

Senaryo 2: Zamanlayıcı Son Süresi (Timer Deadline) Olarak Tarih

Kullanıcı tarafından girilen bir tarih, sonraki görevde bir yükseltme zamanlayıcısını (escalation timer) tetikler.

<!-- User enters a deadline in a previous task -->
<activiti:formProperty id="deadline" name="Deadline" type="date" required="true"/>

<!-- Boundary timer fires at the user-entered date -->
<boundaryEvent id="deadlineTimer" attachedToRef="reviewTask" cancelActivity="false">
    <timerEventDefinition>
        <timeDate>${deadline}</timeDate>
    </timerEventDefinition>
</boundaryEvent>

Senaryo 3a: Bir Ağ Geçidinde İki Tarihi Karşılaştırmak (Yerel - Native)

java.util.Date, UEL ifadelerinde doğrudan kullanılabilen .after() ve .before() yöntemlerine sahiptir.

<exclusiveGateway id="dateCheck"/>

<sequenceFlow sourceRef="dateCheck" targetRef="validPath">
    <conditionExpression xsi:type="tFormalExpression">${endDate.after(startDate)}</conditionExpression>
</sequenceFlow>

<sequenceFlow sourceRef="dateCheck" targetRef="invalidPath">
    <conditionExpression xsi:type="tFormalExpression">${!endDate.after(startDate)}</conditionExpression>
</sequenceFlow>

Senaryo 3b: Bir Ağ Geçidinde Tarihi "Şu An" ile Karşılaştırmak (GraalJS)

Bir boolean değer hesaplamak için ağ geçidinden önce bir Script Görevi (Script Task) kullanın.

<scriptTask id="checkOverdue" name="Check If Overdue" scriptFormat="super-js">
  <script>
    var dueDate = execution.getVariable('dueDate');
    execution.setVariable('isOverdue', dueDate.before(new java.util.Date()));
  </script>
</scriptTask>

<sequenceFlow sourceRef="checkOverdue" targetRef="overdueGateway"/>

<exclusiveGateway id="overdueGateway"/>

<sequenceFlow sourceRef="overdueGateway" targetRef="escalate">
    <conditionExpression xsi:type="tFormalExpression">${isOverdue}</conditionExpression>
</sequenceFlow>

<sequenceFlow sourceRef="overdueGateway" targetRef="continueNormally">
    <conditionExpression xsi:type="tFormalExpression">${!isOverdue}</conditionExpression>
</sequenceFlow>

Senaryo 4: İş Akışı Başlangıç Zamanını Yakalama

Geçerli zaman damgasını (timestamp) kaydetmek için Başlangıç Olayından (Start Event) hemen sonra bir Script Görevi (Script Task) kullanın.

<startEvent id="start" flowable:initiator="initiator"/>
<sequenceFlow sourceRef="start" targetRef="captureStart"/>

<scriptTask id="captureStart" name="Capture Start Time" scriptFormat="super-js">
  <script>execution.setVariable('processStartDate', new java.util.Date());</script>
</scriptTask>

processStartDate değişkeni daha sonra yukarıdaki desenlerin herhangi birinde (görüntüleme, zamanlayıcı, ağ geçidi karşılaştırması) kullanılabilir.

Senaryo 5: Bir Döngüde Düzenlenebilir Tarih

Bir ağ geçidi bir göreve geri döndüğünde (loop back), tarih alanı süreç değişkeninden (process variable) önceden doldurulur. Kullanıcı bunu değiştirebilir.

<!-- Revision task with editable date -->
<userTask id="reviseRequest" name="Revise Request" flowable:assignee="${initiator}">
    <extensionElements>
        <activiti:formProperty id="startDate" name="Start Date" type="date" required="true"/>
        <activiti:formProperty id="endDate" name="End Date" type="date" required="true"/>
    </extensionElements>
</userTask>

<!-- Gateway loops back -->
<sequenceFlow sourceRef="decisionGateway" targetRef="reviseRequest">
    <conditionExpression xsi:type="tFormalExpression">${decision == 'revise'}</conditionExpression>
</sequenceFlow>

Scriptlerde (Scripts) Tarih Aritmetiği

Gelişmiş tarih hesaplamaları için (örneğin "bu 3 günden eski mi?"), GraalJS kullanın:

<scriptTask id="dateCalc" name="Date Arithmetic" scriptFormat="super-js">
  <script>
    var startDate = execution.getVariable('startDate');
    var now = new java.util.Date();

    // Days between two dates
    var diffMs = now.getTime() - startDate.getTime();
    var daysPassed = java.lang.Math.floor(diffMs / (1000 * 60 * 60 * 24));
    execution.setVariable('daysPassed', daysPassed);

    // Is it more than 3 days old?
    var threeDaysMs = 3 * 24 * 60 * 60 * 1000;
    execution.setVariable('isExpired', diffMs > threeDaysMs);
  </script>
</scriptTask>

Neden java.util.Date? Flowable'ın dahili DateFormType sınıfı, yalnızca java.util.Date veya Calendar kabul eden Apache Commons FastDateFormat sınıfını kullanır. Tarihleri java.time.LocalDateTime olarak saklamak, Flowable görev formlarındaki tarih alanlarını oluşturduğunda (render) bir IllegalArgumentException hatasına neden olur. java.util.Date türü, zamanlayıcılar, form oluşturma ve UEL ifadeleri dahil olmak üzere tüm Flowable API'leri ile tam uyumludur.

7. Görev Süresi / SLA Kontrolü

Bir iş akışını bir görevin ne kadar sürdüğüne bağlı olarak yönlendirmek için (örneğin "Kullanıcı 2 saat içinde bitirdi mi?"), görevden önce başlangıç zamanını yakalayın ve bir Script Görevi (Script Task) kullanarak ağ geçidinde (gateway) karşılaştırın.

Desen:

  1. Script Görevi (Script Task): Geçerli zamanı bir değişkene kaydet (Capture).
  2. Kullanıcı Görevi (User Task): Kullanıcı işi gerçekleştirir.
  3. Script Görevi: Geçerli zamanı kaydedilen başlangıç zamanıyla karşılaştır, bir boolean (true/false) ayarla.
  4. Ağ Geçidi (Gateway): Boolean değere göre dallan.
<!-- 1. Mark Start Time -->
<scriptTask id="markStart" name="Mark Start" scriptFormat="super-js">
    <script>execution.setVariable('taskStartTime', new java.util.Date());</script>
</scriptTask>

<sequenceFlow sourceRef="markStart" targetRef="userTask"/>

<!-- 2. The Task -->
<userTask id="userTask" name="Perform Work" flowable:assignee="${initiator}"/>

<sequenceFlow sourceRef="userTask" targetRef="checkDuration"/>

<!-- 3. Check Duration -->
<scriptTask id="checkDuration" name="Check Duration" scriptFormat="super-js">
  <script>
    var startTime = execution.getVariable('taskStartTime');
    var elapsed = new java.util.Date().getTime() - startTime.getTime();
    // 2 hours in milliseconds
    var twoHoursMs = 2 * 60 * 60 * 1000;
    execution.setVariable('tookTooLong', elapsed > twoHoursMs);
  </script>
</scriptTask>

<sequenceFlow sourceRef="checkDuration" targetRef="checkSLA"/>

<!-- 4. Branch -->
<exclusiveGateway id="checkSLA"/>

<sequenceFlow sourceRef="checkSLA" targetRef="escalate">
    <conditionExpression xsi:type="tFormalExpression">${tookTooLong}</conditionExpression>
</sequenceFlow>

<sequenceFlow sourceRef="checkSLA" targetRef="continue">
    <conditionExpression xsi:type="tFormalExpression">${!tookTooLong}</conditionExpression>
</sequenceFlow>

Not: Bu Toplam Geri Dönüş Süresini (Total Turnaround Time) (görevin oluşturulmasından tamamlanmasına kadar) ölçer. Süreyi kesin olarak "Üzerine Alma Tarihinden (Claim Date)" itibaren ölçmek, özel Java Görev Dinleyicileri (Task Listeners) gerektirir.