From 6009b1ecbebfb1ae13b2150cb2af72a21ef1b869 Mon Sep 17 00:00:00 2001 From: Md Afiq Iskandar Date: Fri, 25 Jul 2025 10:05:37 +0800 Subject: [PATCH] Enhance Documentation for Gateway Decision Logic in Process Builder - Added detailed documentation for the new Gateway Decision Logic, including UI components for gateway condition configuration and evaluation processes. - Introduced examples for condition evaluation and decision path selection, improving clarity for users on how to implement and utilize gateway conditions. - Updated the process definition and variables to support the new gateway logic, ensuring seamless integration within the existing workflow. - Enhanced the user interface to display decision paths and condition evaluation results, providing better feedback during workflow execution. --- docs/json/form/customScript.js | 309 ------- docs/json/form/formComponents.json | 758 +----------------- .../process-builder/processDefinition.json | 121 ++- .../process-builder/processVariables.json | 32 +- docs/vue-flow-process-builder-system-guide.md | 262 +++++- pages/workflow/[id].vue | 291 ++++++- 6 files changed, 649 insertions(+), 1124 deletions(-) diff --git a/docs/json/form/customScript.js b/docs/json/form/customScript.js index 66a3c6e..e69de29 100644 --- a/docs/json/form/customScript.js +++ b/docs/json/form/customScript.js @@ -1,309 +0,0 @@ -this.hideField("form_jeniskp_1"); -this.hideField("form_jeniskp_2"); -this.hideField("form_jeniskp_3"); - -this.onFieldChange("select_1", (value) => { - this.hideField("form_jeniskp_1"); - this.hideField("form_jeniskp_2"); - this.hideField("form_jeniskp_3"); - if (value && value.trim()) { - if (value == "jeniskp_1") this.showField("form_jeniskp_1"); - if (value == "jeniskp_2") this.showField("form_jeniskp_2"); - if (value == "jeniskp_3") this.showField("form_jeniskp_3"); - } -}); -// Conditional Logic Script - -// Conditional Logic Script - -// Conditional Logic Script - -// Conditional Logic Script - -// Conditional Logic Script - -// Conditional Logic Script - -// Conditional logic for field: nyatakan_lain2 -onFieldChange("radio_bangsa", function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -}); - -// Initial evaluation for field: nyatakan_lain2 -(function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -})(); -// Conditional Logic Script - -// Conditional logic for field: nyatakan_lain2 -onFieldChange("radio_bangsa", function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -}); - -// Initial evaluation for field: nyatakan_lain2 -(function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -})(); -// Conditional Logic Script - -// Conditional logic for field: nyatakan_lain2 -onFieldChange("radio_bangsa", function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -}); - -// Initial evaluation for field: nyatakan_lain2 -(function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -})(); - -// Conditional logic for field: text_14 -onFieldChange("radio_pendidikan", function () { - if (getField("radio_pendidikan") !== "lain") { - hideField("text_14"); - } else { - showField("text_14"); - } -}); - -// Initial evaluation for field: text_14 -(function () { - if (getField("radio_pendidikan") !== "lain") { - hideField("text_14"); - } else { - showField("text_14"); - } -})(); -// Conditional Logic Script - -// Conditional logic for field: nyatakan_lain2 -onFieldChange("radio_bangsa", function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -}); - -// Initial evaluation for field: nyatakan_lain2 -(function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -})(); - -// Conditional logic for field: text_14 -onFieldChange("radio_pendidikan", function () { - if (getField("radio_pendidikan") !== "lain") { - hideField("text_14"); - } else { - showField("text_14"); - } -}); - -// Initial evaluation for field: text_14 -(function () { - if (getField("radio_pendidikan") !== "lain") { - hideField("text_14"); - } else { - showField("text_14"); - } -})(); -// Conditional Logic Script - -// Conditional logic for field: nyatakan_lain2 -onFieldChange("radio_bangsa", function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -}); - -// Initial evaluation for field: nyatakan_lain2 -(function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -})(); - -// Conditional logic for field: text_14 -onFieldChange("radio_pendidikan", function () { - if (getField("radio_pendidikan") !== "lain") { - hideField("text_14"); - } else { - showField("text_14"); - } -}); - -// Initial evaluation for field: text_14 -(function () { - if (getField("radio_pendidikan") !== "lain") { - hideField("text_14"); - } else { - showField("text_14"); - } -})(); -// Conditional Logic Script - -// Conditional logic for field: nyatakan_lain2 -onFieldChange("radio_bangsa", function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -}); - -// Initial evaluation for field: nyatakan_lain2 -(function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -})(); - -// Conditional logic for field: text_14 -onFieldChange("radio_pendidikan", function () { - if (getField("radio_pendidikan") !== "lain") { - hideField("text_14"); - } else { - showField("text_14"); - } -}); - -// Initial evaluation for field: text_14 -(function () { - if (getField("radio_pendidikan") !== "lain") { - hideField("text_14"); - } else { - showField("text_14"); - } -})(); -// Conditional Logic Script - -// Conditional logic for field: nyatakan_lain2 -onFieldChange("radio_bangsa", function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -}); - -// Initial evaluation for field: nyatakan_lain2 -(function () { - if (getField("radio_bangsa") !== "lain") { - hideField("nyatakan_lain2"); - } else { - showField("nyatakan_lain2"); - } -})(); - -// Conditional logic for field: text_14 -onFieldChange("radio_pendidikan", function () { - if (getField("radio_pendidikan") !== "lain") { - hideField("text_14"); - } else { - showField("text_14"); - } -}); - -// Initial evaluation for field: text_14 -(function () { - if (getField("radio_pendidikan") !== "lain") { - hideField("text_14"); - } else { - showField("text_14"); - } -})(); - -// Hide "Nyatakan Hubungan Lain-lain" initially -this.hideField("hubungan_lain_nyatakan"); - -// Show/hide relationship specification field -this.onFieldChange("hubungan_keluarga", (value) => { - if (value && value.includes("lain_lain")) { - this.showField("hubungan_lain_nyatakan"); - } else { - this.hideField("hubungan_lain_nyatakan"); - } -}); - -// Hide "Sebab Pembayaran Tunai" initially -this.hideField("sebab_tunai"); - -// Show/hide cash payment reason field -this.onFieldChange("cara_pembayaran", (value) => { - if (value && value.includes("tunai")) { - this.showField("sebab_tunai"); - } else { - this.hideField("sebab_tunai"); - } -}); - -// Hide education specification field initially -this.hideField("pendidikan_lain_tanggungan"); - -// Show/hide education specification field -this.onFieldChange("pendidikan_tertinggi_tanggungan", (value) => { - if (value && value.includes("lain_lain")) { - this.showField("pendidikan_lain_tanggungan"); - } else { - this.hideField("pendidikan_lain_tanggungan"); - } -}); - -// Hide school information initially -this.hideField("maklumat_sekolah"); - -// Show/hide school information based on schooling status -this.onFieldChange("bersekolah_tanggungan", (value) => { - if (value === "ya") { - this.showField("maklumat_sekolah"); - } else { - this.hideField("maklumat_sekolah"); - } -}); - -// Handle repeating group conditional logic for each dependent -this.onFieldChange("tanggungan_maklumat", (value) => { - if (value && Array.isArray(value)) { - value.forEach((item, index) => { - // Handle race specification for each dependent - if (item.bangsa_tanggungan !== "lain_lain") { - // Hide the specification field for this item - const fieldName = `tanggungan_maklumat[${index}].bangsa_lain_tanggungan`; - // Note: Repeating group field hiding requires specific handling - } - }); - } -}); diff --git a/docs/json/form/formComponents.json b/docs/json/form/formComponents.json index a70ab43..e74f378 100644 --- a/docs/json/form/formComponents.json +++ b/docs/json/form/formComponents.json @@ -2,9 +2,9 @@ { "type": "heading", "props": { - "name": "heading_2", + "name": "heading_kategori_asnaf", "level": 2, - "value": "SEKSYEN A", + "value": "PILIHAN KATEGORI ASNAF", "width": "100%", "gridColumn": "span 12", "conditionalLogic": { @@ -16,11 +16,10 @@ } }, { - "type": "heading", + "type": "paragraph", "props": { - "name": "heading_1", - "level": 3, - "value": "1. MAKLUMAT PENDAFTARAN ASNAF (KETUA KELUARGA)", + "name": "paragraph_kategori_asnaf", + "value": "Sila pilih kategori asnaf yang bersesuaian.", "width": "100%", "gridColumn": "span 12", "conditionalLogic": { @@ -31,139 +30,19 @@ } } }, - { - "type": "text", - "props": { - "help": "Untuk Mualaf, nama mengikut kad pengenalan", - "name": "text_3", - "type": "text", - "label": "Nama", - "width": "100%", - "gridColumn": "span 12", - "validation": "required", - "placeholder": "", - "conditionalLogic": { - "action": "show", - "enabled": false, - "operator": "and", - "conditions": [] - } - } - }, - { - "type": "select", - "props": { - "help": "", - "name": "select_1", - "type": "select", - "label": "Jenis Kad Pengenalan", - "width": "100%", - "options": [ - { "label": "Sila Pilih Jenis Kad Pengenalan", "value": "jeniskp" }, - { "label": "MyKad/MyKid", "value": "jeniskp_1" }, - { "label": "No. K/P/Polis/Tentera/No. Pasport", "value": "jeniskp_2" }, - { "label": "No. Sijil Beranak", "value": "jeniskp_3" } - ], - "gridColumn": "span 12", - "validation": "required", - "placeholder": "", - "conditionalLogic": { - "action": "show", - "enabled": false, - "operator": "and", - "conditions": [] - } - } - }, - { - "type": "text", - "props": { - "help": "", - "name": "form_jeniskp_1", - "type": "text", - "label": "MyKad/MyKid", - "width": "100%", - "gridColumn": "span 12", - "validation": "required", - "placeholder": "", - "conditionalLogic": { - "action": "show", - "enabled": false, - "operator": "and", - "conditions": [] - } - } - }, - { - "type": "text", - "props": { - "help": "", - "name": "form_jeniskp_2", - "type": "text", - "label": "No. K/P/Polis/Tentera/No. Pasport", - "width": "100%", - "gridColumn": "span 12", - "validation": "required", - "placeholder": "", - "conditionalLogic": { - "action": "show", - "enabled": false, - "operator": "and", - "conditions": [] - } - } - }, - { - "type": "text", - "props": { - "help": "", - "name": "form_jeniskp_3", - "type": "text", - "label": "No. Sijil Beranak", - "width": "100%", - "gridColumn": "span 12", - "validation": "required", - "placeholder": "", - "conditionalLogic": { - "action": "show", - "enabled": false, - "operator": "and", - "conditions": [] - } - } - }, - { - "type": "text", - "props": { - "help": "Isi Jika Berkenaan", - "name": "text_warganegara", - "type": "text", - "label": "Warganegara", - "width": "100%", - "gridColumn": "span 12", - "validation": "", - "placeholder": "", - "conditionalLogic": { - "action": "show", - "enabled": false, - "operator": "and", - "conditions": [] - } - } - }, { "type": "radio", "props": { - "help": "", - "name": "radio_jantina", + "help": "Pilih kategori asnaf", + "name": "kategori_asnaf", "type": "radio", - "label": "Jantina", - "width": "50%", + "label": "Kategori Asnaf", + "width": "100%", "options": [ - { "label": "Lelaki", "value": "lelaki" }, - { "label": "Perempuan", "value": "perempuan" } + { "label": "Fakir Miskin", "value": "fakir_miskin" }, + { "label": "Bukan Fakir Miskin", "value": "bukan_fakir_miskin" } ], - "gridColumn": "span 6", + "gridColumn": "span 12", "validation": "required", "conditionalLogic": { "action": "show", @@ -173,60 +52,17 @@ } } }, - { - "type": "select", - "props": { - "help": "", - "name": "radio_bangsa", - "type": "select", - "label": "Bangsa", - "width": "50%", - "options": [ - { "label": "Melayu", "value": "melayu" }, - { "label": "Cina", "value": "cina" }, - { "label": "India", "value": "india" }, - { "label": "Lain-lain", "value": "lain" } - ], - "gridColumn": "span 6", - "validation": "required", - "placeholder": "Select an option" - } - }, { "type": "text", "props": { "help": "", - "name": "nyatakan_lain2", + "name": "nama_asnaf", "type": "text", - "label": "Nyatakan (Bangsa Lain-Lain)", + "label": "Nama Asnaf", "width": "100%", "gridColumn": "span 12", - "validation": "", + "validation": "required", "placeholder": "Enter text...", - "conditionalLogic": { - "action": "hide", - "enabled": true, - "operator": "and", - "conditions": [ - { "field": "radio_bangsa", "value": "lain", "operator": "not_equals" } - ] - } - } - }, - { - "type": "radio", - "props": { - "help": "", - "name": "radio_9_copy", - "type": "radio", - "label": "Bersekolah", - "width": "50%", - "options": [ - { "label": "Ya", "value": "ya" }, - { "label": "Tidak", "value": "tidak" } - ], - "gridColumn": "span 6", - "validation": "", "conditionalLogic": { "action": "show", "enabled": false, @@ -234,569 +70,5 @@ "conditions": [] } } - }, - { - "type": "select", - "props": { - "help": "", - "name": "radio_pendidikan", - "type": "select", - "label": "Pendidikan Tertinggi", - "width": "50%", - "options": [ - { "label": "Peringkat Rendah", "value": "rendah" }, - { "label": "SRP/PMR", "value": "srp" }, - { "label": "SPM", "value": "spm" }, - { "label": "Sijil", "value": "sijil" }, - { "label": "Diploma", "value": "diploma" }, - { "label": "STPM", "value": "stpm" }, - { "label": "Ijazah", "value": "ijazah" }, - { "label": "Lain-Lain", "value": "lain" } - ], - "gridColumn": "span 6", - "validation": "required", - "placeholder": "Select an option" - } - }, - { - "type": "text", - "props": { - "help": "", - "name": "text_14", - "type": "text", - "label": "Nyatakan (Pendidikan Tertinggi Lain-Lain)", - "width": "100%", - "gridColumn": "span 12", - "validation": "", - "placeholder": "Enter text...", - "conditionalLogic": { - "action": "hide", - "enabled": true, - "operator": "and", - "conditions": [ - { - "field": "radio_pendidikan", - "value": "lain", - "operator": "not_equals" - } - ] - } - } - }, - { - "type": "date", - "props": { - "help": "Isi Jika Berkenaan", - "name": "date_masukislam", - "type": "date", - "label": "Tarikh Masuk Islam", - "width": "50%", - "gridColumn": "span 6", - "validation": "", - "conditionalLogic": { - "action": "show", - "enabled": false, - "operator": "and", - "conditions": [] - } - } - }, - { - "type": "date", - "props": { - "help": "Isi Jika Berkenaan", - "name": "date_masukislam_copy", - "type": "date", - "label": "Tarikh Mula Kelas fardu Ain Muallaf (KFAM)", - "width": "50%", - "gridColumn": "span 6", - "validation": "", - "conditionalLogic": { - "action": "show", - "enabled": false, - "operator": "and", - "conditions": [] - } - } - }, - { - "type": "select", - "props": { - "help": "", - "name": "select_statusperkahwinan", - "type": "select", - "label": "Status Perkahwinan", - "width": "100%", - "options": [ - { "label": "Berkahwin", "value": "berkahwin" }, - { "label": "Ibu Tinggal/Bapa Tinggal", "value": "ibubapatunggal" }, - { "label": "Bujang", "value": "bujang" }, - { "label": "Duda", "value": "duda" }, - { "label": "Janda", "value": "janda" }, - { "label": "Balu", "value": "balu" } - ], - "gridColumn": "span 12", - "validation": "required", - "placeholder": "Select an option", - "conditionalLogic": { - "action": "show", - "enabled": false, - "operator": "and", - "conditions": [] - } - } - }, - { - "type": "heading", - "props": { - "name": "heading_seksyen_b", - "level": 2, - "value": "SEKSYEN B", - "width": "100%", - "gridColumn": "span 12", - "conditionalLogic": { - "action": "show", - "enabled": false, - "operator": "and", - "conditions": [] - } - } - }, - { - "type": "heading", - "props": { - "name": "heading_tanggungan", - "level": 3, - "value": "1. MAKLUMAT PERIBADI TANGGUNGAN (Ruangan WAJIB diisi)", - "width": "100%", - "gridColumn": "span 12", - "conditionalLogic": { - "action": "show", - "enabled": false, - "operator": "and", - "conditions": [] - } - } - }, - { - "type": "checkbox", - "props": { - "help": "Pilih semua yang berkenaan", - "name": "hubungan_keluarga", - "type": "checkbox", - "label": "Hubungan dengan Pemohon/Asnaf", - "width": "100%", - "options": [ - { "label": "Pasangan Pemohon", "value": "pasangan" }, - { "label": "Isteri Kedua", "value": "isteri_kedua" }, - { "label": "Isteri Ketiga", "value": "isteri_ketiga" }, - { "label": "Isteri Keempat", "value": "isteri_keempat" }, - { "label": "Ipar", "value": "ipar" }, - { "label": "Abang", "value": "abang" }, - { "label": "Bapa", "value": "bapa" }, - { "label": "Ibu", "value": "ibu" }, - { "label": "Kakak", "value": "kakak" }, - { "label": "Adik", "value": "adik" }, - { "label": "Anak", "value": "anak" }, - { "label": "Cucu", "value": "cucu" }, - { "label": "Bapa Mertua", "value": "bapa_mertua" }, - { "label": "Ibu Mertua", "value": "ibu_mertua" }, - { "label": "Lain-lain (Sila Nyatakan)", "value": "lain_lain" } - ], - "gridColumn": "span 12", - "validation": "required" - } - }, - { - "type": "text", - "props": { - "name": "hubungan_lain_nyatakan", - "type": "text", - "label": "Nyatakan Hubungan Lain-lain", - "width": "100%", - "gridColumn": "span 12", - "validation": "", - "placeholder": "Sila nyatakan hubungan lain-lain", - "conditionalLogic": { - "action": "hide", - "enabled": true, - "operator": "and", - "conditions": [ - { - "field": "hubungan_keluarga", - "value": "lain_lain", - "operator": "not_contains" - } - ] - } - } - }, - { - "type": "repeating-group", - "props": { - "help": "Tambah maklumat untuk setiap tanggungan", - "name": "tanggungan_maklumat", - "label": "Maklumat Tanggungan", - "width": "100%", - "fields": [ - { - "name": "nama_tanggungan", - "type": "text", - "label": "Nama (Untuk Mualaf, nama mengikut kad pengenalan)", - "validation": "required", - "placeholder": "Masukkan nama lengkap" - }, - { - "name": "jenis_kad_tanggungan", - "type": "select", - "label": "Jenis Pengenalan", - "options": [ - { "label": "MyKad/MyKid", "value": "mykad" }, - { - "label": "No. K/P/Polis/Tentera/No. Pasport", - "value": "kp_polis" - }, - { "label": "No. Sijil Beranak", "value": "sijil_beranak" } - ], - "validation": "required" - }, - { - "name": "no_pengenalan_tanggungan", - "type": "text", - "label": "No. Pengenalan", - "validation": "required", - "placeholder": "Masukkan nombor pengenalan" - }, - { - "name": "jantina_tanggungan", - "type": "radio", - "label": "Jantina", - "options": [ - { "label": "Lelaki", "value": "lelaki" }, - { "label": "Perempuan", "value": "perempuan" } - ], - "validation": "required" - }, - { - "name": "tarikh_lahir_tanggungan", - "type": "date", - "label": "Tarikh Lahir", - "validation": "required" - }, - { - "name": "tempat_lahir_tanggungan", - "type": "text", - "label": "Tempat Lahir", - "placeholder": "Masukkan tempat lahir" - }, - { - "name": "bangsa_tanggungan", - "type": "select", - "label": "Bangsa", - "options": [ - { "label": "Melayu", "value": "melayu" }, - { "label": "Cina", "value": "cina" }, - { "label": "India", "value": "india" }, - { "label": "Lain-lain (Sila Nyatakan)", "value": "lain_lain" } - ], - "validation": "required" - }, - { - "name": "bangsa_lain_tanggungan", - "type": "text", - "label": "Nyatakan Bangsa Lain-lain", - "placeholder": "Sila nyatakan" - }, - { - "name": "status_kahwin_tanggungan", - "type": "select", - "label": "Status Perkahwinan", - "options": [ - { "label": "Berkahwin", "value": "berkahwin" }, - { - "label": "Ibu Tinggal/Bapa Tinggal", - "value": "ibu_bapa_tinggal" - }, - { "label": "Bujang", "value": "bujang" }, - { "label": "Duda", "value": "duda" }, - { "label": "Janda", "value": "janda" }, - { "label": "Balu", "value": "balu" } - ] - }, - { - "help": "Jika Berkenaan", - "name": "tarikh_masuk_islam_tanggungan", - "type": "date", - "label": "Tarikh Masuk Islam" - }, - { - "help": "Jika Berkenaan", - "name": "tarikh_mula_kfam_tanggungan", - "type": "date", - "label": "Tarikh Mula KFAM" - }, - { - "help": "Jika Berkenaan", - "name": "warganegara_tanggungan", - "type": "text", - "label": "Warganegara", - "placeholder": "Masukkan warganegara" - }, - { - "name": "tempat_menetap_tanggungan", - "type": "text", - "label": "Tempoh Menetap Di Selangor", - "placeholder": "Contoh: 5 Tahun" - }, - { - "name": "no_telefon_tanggungan", - "type": "text", - "label": "No. Telefon/Telefon Bimbit", - "placeholder": "Contoh: 03-12345678" - } - ], - "maxItems": 10, - "minItems": 1, - "buttonText": "Tambah Tanggungan", - "gridColumn": "span 12", - "removeText": "Buang" - } - }, - { - "type": "heading", - "props": { - "name": "heading_maklumat_perbankan", - "level": 3, - "value": "MAKLUMAT PERBANKAN (Jika Berkenaan)", - "width": "100%", - "gridColumn": "span 12" - } - }, - { - "type": "text", - "props": { - "name": "nama_pemegang_akaun", - "type": "text", - "label": "Nama Pemegang Akaun", - "width": "100%", - "gridColumn": "span 12", - "placeholder": "Masukkan nama pemegang akaun" - } - }, - { - "type": "text", - "props": { - "name": "nama_bank", - "type": "text", - "label": "Bank", - "width": "50%", - "gridColumn": "span 6", - "placeholder": "Masukkan nama bank" - } - }, - { - "type": "text", - "props": { - "name": "no_akaun_bank", - "type": "text", - "label": "No. Akaun Bank", - "width": "50%", - "gridColumn": "span 6", - "placeholder": "Masukkan nombor akaun" - } - }, - { - "type": "checkbox", - "props": { - "name": "cara_pembayaran", - "type": "checkbox", - "label": "Cara Pembayaran", - "width": "100%", - "options": [ - { "label": "Akaun", "value": "akaun" }, - { "label": "Cek", "value": "cek" }, - { "label": "Tunai, Nyatakan Sebab", "value": "tunai" }, - { "label": "Uzur/Sakit", "value": "uzur_sakit" }, - { "label": "Muflis", "value": "muflis" }, - { "label": "Disenarai Hitam", "value": "disenarai_hitam" } - ], - "gridColumn": "span 12" - } - }, - { - "type": "text", - "props": { - "name": "sebab_tunai", - "type": "text", - "label": "Sebab Pembayaran Tunai", - "width": "100%", - "gridColumn": "span 12", - "placeholder": "Sila nyatakan sebab", - "conditionalLogic": { - "action": "hide", - "enabled": true, - "operator": "and", - "conditions": [ - { - "field": "cara_pembayaran", - "value": "tunai", - "operator": "not_contains" - } - ] - } - } - }, - { - "type": "heading", - "props": { - "name": "heading_pendidikan", - "level": 3, - "value": "2. PENDIDIKAN", - "width": "100%", - "gridColumn": "span 12" - } - }, - { - "type": "radio", - "props": { - "name": "bersekolah_tanggungan", - "type": "radio", - "label": "Bersekolah", - "width": "100%", - "options": [ - { "label": "Ya", "value": "ya" }, - { "label": "Tidak", "value": "tidak" } - ], - "gridColumn": "span 12", - "validation": "required" - } - }, - { - "type": "checkbox", - "props": { - "name": "pendidikan_tertinggi_tanggungan", - "type": "checkbox", - "label": "Pendidikan Tertinggi", - "width": "100%", - "options": [ - { "label": "Peringkat Rendah", "value": "peringkat_rendah" }, - { "label": "SRP/PMR", "value": "srp_pmr" }, - { "label": "SPM", "value": "spm" }, - { "label": "STPM", "value": "stpm" }, - { "label": "Pra Sekolah", "value": "pra_sekolah" }, - { "label": "Sekolah Rendah Kebangsaan", "value": "srk" }, - { "label": "Sekolah Menengah Kebangsaan", "value": "smk" }, - { "label": "Sekolah Menengah Agama", "value": "sma" }, - { "label": "Sijil", "value": "sijil" }, - { "label": "Diploma", "value": "diploma" }, - { "label": "Ijazah", "value": "ijazah" }, - { "label": "Lain-Lain (Sila Nyatakan)", "value": "lain_lain" }, - { - "label": "Sekolah Rendah Kebangsaan dan Agama", - "value": "srk_agama" - }, - { "label": "IPTA/IPTS", "value": "ipta_ipts" }, - { "label": "Maahad Tahfiz", "value": "maahad_tahfiz" } - ], - "gridColumn": "span 12" - } - }, - { - "type": "text", - "props": { - "name": "pendidikan_lain_tanggungan", - "type": "text", - "label": "Nyatakan Pendidikan Lain-lain", - "width": "100%", - "gridColumn": "span 12", - "placeholder": "Sila nyatakan", - "conditionalLogic": { - "action": "hide", - "enabled": true, - "operator": "and", - "conditions": [ - { - "field": "pendidikan_tertinggi_tanggungan", - "value": "lain_lain", - "operator": "not_contains" - } - ] - } - } - }, - { - "type": "repeating-group", - "props": { - "help": "Tambah maklumat sekolah untuk setiap tanggungan yang bersekolah", - "name": "maklumat_sekolah", - "label": "Nama dan Alamat Sekolah/Institusi", - "width": "100%", - "fields": [ - { - "name": "nama_sekolah", - "type": "text", - "label": "Nama Sekolah", - "validation": "required", - "placeholder": "Masukkan nama sekolah/institusi" - }, - { - "name": "alamat_sekolah", - "type": "textarea", - "label": "Alamat Sekolah", - "validation": "required", - "placeholder": "Masukkan alamat lengkap sekolah/institusi" - }, - { - "name": "daerah_sekolah", - "type": "text", - "label": "Daerah", - "placeholder": "Masukkan daerah" - }, - { - "name": "negeri_sekolah", - "type": "text", - "label": "Negeri", - "placeholder": "Masukkan negeri" - }, - { - "name": "poskod_sekolah", - "type": "text", - "label": "Poskod", - "placeholder": "Masukkan poskod" - } - ], - "maxItems": 10, - "minItems": 0, - "buttonText": "Tambah Sekolah", - "gridColumn": "span 12", - "removeText": "Buang", - "conditionalLogic": { - "action": "hide", - "enabled": true, - "operator": "and", - "conditions": [ - { - "field": "bersekolah_tanggungan", - "value": "ya", - "operator": "not_equals" - } - ] - } - } - }, - { - "type": "radio", - "props": { - "name": "tinggal_bersama_keluarga", - "type": "radio", - "label": "Tinggal Bersama Keluarga", - "width": "100%", - "options": [ - { "label": "Ya", "value": "ya" }, - { "label": "Tidak", "value": "tidak" }, - { "label": "Asrama", "value": "asrama" } - ], - "gridColumn": "span 12" - } } ] diff --git a/docs/json/process-builder/processDefinition.json b/docs/json/process-builder/processDefinition.json index 7c199c0..14816bb 100644 --- a/docs/json/process-builder/processDefinition.json +++ b/docs/json/process-builder/processDefinition.json @@ -33,17 +33,6 @@ "sourceHandle": "api-1752550319410-right", "targetHandle": "script-1752550430989-left" }, - { - "id": "script-1752550430989-html-1752550500000", - "data": {}, - "type": "custom", - "label": "", - "source": "script-1752550430989", - "target": "html-1752550500000", - "animated": true, - "sourceHandle": "script-1752550430989-right", - "targetHandle": "html-1752550500000-left" - }, { "id": "html-1752550500000-end-1752546716111-1752550899936", "data": {}, @@ -76,6 +65,39 @@ "animated": true, "sourceHandle": "notification-1752621850786-right", "targetHandle": "end-1752546716111-left" + }, + { + "id": "script-1752550430989-gateway-1752550505000", + "data": {}, + "type": "custom", + "label": "", + "source": "script-1752550430989", + "target": "gateway-1752550505000", + "animated": true, + "sourceHandle": "script-1752550430989-right", + "targetHandle": "gateway-1752550505000-left" + }, + { + "id": "gateway-1752550505000-html-1752550500000", + "data": { "condition": "todoStatus === true" }, + "type": "custom", + "label": "Completed", + "source": "gateway-1752550505000", + "target": "html-1752550500000", + "animated": true, + "sourceHandle": "gateway-1752550505000-right", + "targetHandle": "html-1752550500000-left" + }, + { + "id": "gateway-1752550505000-notification-1752621850786", + "data": { "condition": "todoStatus === false" }, + "type": "custom", + "label": "Not Completed", + "source": "gateway-1752550505000", + "target": "notification-1752621850786", + "animated": true, + "sourceHandle": "gateway-1752550505000-bottom", + "targetHandle": "notification-1752621850786-left" } ], "nodes": [ @@ -84,15 +106,18 @@ "data": { "label": "Start", "description": "Process start point" }, "type": "start", "label": "Start", - "position": { "x": 270, "y": 345 } + "position": { "x": 300, "y": 135 } }, { "id": "form-1752546702226", "data": { "label": "Pilihan Kategori Asnaf", + "shape": "rectangle", "formId": 7, "formName": "Pilihan Kategori Asnaf", "formUuid": "d3612e05-b31a-46dc-b5e5-67e6c5bd3e78", + "textColor": "#6b21a8", + "borderColor": "#9333ea", "description": "Form: Pilihan Kategori Asnaf", "assignedRoles": [ { "label": "Pemohon", "value": "2", "description": "" } @@ -104,20 +129,21 @@ { "formField": "kategori_asnaf", "processVariable": "kategoriAsnaf" }, { "formField": "nama_asnaf", "processVariable": "namaAsnaf" } ], + "backgroundColor": "#faf5ff", "fieldConditions": [], "assignmentVariable": "", "assignmentVariableType": "user_id" }, "type": "form", "label": "Pilihan Kategori Asnaf", - "position": { "x": 540, "y": 330 } + "position": { "x": 510, "y": 105 } }, { "id": "end-1752546716111", "data": { "label": "End", "description": "Process end point" }, "type": "end", "label": "End", - "position": { "x": 1890, "y": 135 } + "position": { "x": 2070, "y": 300 } }, { "id": "api-1752550319410", @@ -144,7 +170,7 @@ }, "type": "api", "label": "API Call", - "position": { "x": 855, "y": 345 } + "position": { "x": 795, "y": 105 } }, { "id": "script-1752550430989", @@ -189,7 +215,62 @@ }, "type": "script", "label": "Script Task", - "position": { "x": 1185, "y": 330 } + "position": { "x": 1050, "y": 120 } + }, + { + "id": "gateway-1752550505000", + "data": { + "label": "Decision: Todo Status", + "shape": "diamond", + "textColor": "#c2410c", + "conditions": [ + { + "id": "condition-group-1", + "output": "Completed", + "conditions": [ + { + "id": "condition-1", + "value": true, + "operator": "eq", + "variable": "todoStatus", + "valueType": "boolean", + "logicalOperator": "and" + }, + { + "id": "condition-1753408402567", + "value": "afiq", + "maxValue": "", + "minValue": "", + "operator": "contains", + "variable": "namaAsnaf", + "valueType": "string", + "logicalOperator": "and" + } + ] + }, + { + "id": "condition-group-2", + "output": "Not Completed", + "conditions": [ + { + "id": "condition-2", + "value": false, + "operator": "eq", + "variable": "todoStatus", + "valueType": "boolean", + "logicalOperator": "and" + } + ] + } + ], + "borderColor": "#f97316", + "defaultPath": "Default", + "description": "Branch based on todoStatus", + "backgroundColor": "#fff7ed" + }, + "type": "gateway", + "label": "Decision: Todo Status", + "position": { "x": 1365, "y": 120 } }, { "id": "html-1752550500000", @@ -210,7 +291,7 @@ }, "type": "html", "label": "Show Result", - "position": { "x": 1425, "y": 75 } + "position": { "x": 1590, "y": 150 } }, { "id": "notification-1752621850786", @@ -239,8 +320,8 @@ } ], "viewport": { - "x": -193.044397463002, - "y": 197.8681289640592, - "zoom": 1.049154334038055 + "x": -271.3433493533669, + "y": 163.8456958483416, + "zoom": 0.6965142034495484 } } diff --git a/docs/json/process-builder/processVariables.json b/docs/json/process-builder/processVariables.json index ad70996..b930fb5 100644 --- a/docs/json/process-builder/processVariables.json +++ b/docs/json/process-builder/processVariables.json @@ -18,6 +18,18 @@ "scope": "global", "description": "Title from API response" }, + "asnafScore": { + "name": "asnafScore", + "type": "number", + "scope": "global", + "description": "Calculated score based on input lengths" + }, + "todoStatus": { + "name": "todoStatus", + "type": "boolean", + "scope": "global", + "description": "Completion status (true if id > 100)" + }, "apiResponse": { "name": "apiResponse", "type": "object", @@ -31,28 +43,16 @@ "scope": "global", "description": "" }, - "todoStatus": { - "name": "todoStatus", - "type": "boolean", + "resultSummary": { + "name": "resultSummary", + "type": "string", "scope": "global", - "description": "Completion status (true if id > 100)" - }, - "asnafScore": { - "name": "asnafScore", - "type": "number", - "scope": "global", - "description": "Calculated score based on input lengths" + "description": "Summary string for result" }, "resultTimestamp": { "name": "resultTimestamp", "type": "string", "scope": "global", "description": "Timestamp when script ran" - }, - "resultSummary": { - "name": "resultSummary", - "type": "string", - "scope": "global", - "description": "Summary string for result" } } diff --git a/docs/vue-flow-process-builder-system-guide.md b/docs/vue-flow-process-builder-system-guide.md index 2c00b79..d09a1c3 100644 --- a/docs/vue-flow-process-builder-system-guide.md +++ b/docs/vue-flow-process-builder-system-guide.md @@ -45,6 +45,8 @@ components/process-flow/ │ ├── NotificationManager.vue │ ├── NotificationQueue.vue │ └── NotificationLogs.vue +├── GatewayConditionManager.vue # Gateway condition configuration UI +├── GatewayConditionManagerModal.vue # Modal for gateway setup └── [25+ other process flow files] ``` @@ -454,6 +456,153 @@ const customProperties = computed(() => props.data?.customProperties || {}) ``` +### **4. Gateway Decision Logic** + +#### **Gateway Configuration UI Components** +```vue + + +``` + +#### **Condition Evaluation Process** +```javascript +// Workflow execution evaluates gateway conditions +function evaluateConditionGroup(conditionGroup, variables) { + if (!conditionGroup.conditions || conditionGroup.conditions.length === 0) { + return false; + } + + // If only one condition, evaluate it directly + if (conditionGroup.conditions.length === 1) { + return evaluateCondition(conditionGroup.conditions[0], variables); + } + + // For multiple conditions, evaluate based on logical operators + let result = evaluateCondition(conditionGroup.conditions[0], variables); + + for (let i = 1; i < conditionGroup.conditions.length; i++) { + const condition = conditionGroup.conditions[i]; + const conditionResult = evaluateCondition(condition, variables); + const operator = condition.logicalOperator || 'and'; + + if (operator === 'and') { + result = result && conditionResult; + } else if (operator === 'or') { + result = result || conditionResult; + } + } + + return result; +} + +// Single condition evaluation +function evaluateCondition(condition, variables) { + const { variable, operator, value, valueType } = condition; + const variableValue = variables[variable]; + + // Handle boolean type conversions + let compareValue = value; + if (valueType === 'boolean') { + if (typeof value === 'string') { + compareValue = value.toLowerCase() === 'true'; + } else { + compareValue = Boolean(value); + } + } + + // Evaluate based on operator + switch (operator) { + case 'eq': + return variableValue == compareValue; + case 'gt': + return Number(variableValue) > Number(compareValue); + case 'is_true': + return Boolean(variableValue) === true; + // ... other operators + } +} +``` + +#### **Decision Path Selection** +```javascript +// Determine which path to follow based on conditions +function getNextNodeIdForDecision(currentNodeId) { + const currentNodeObj = workflowData.nodes.find(n => n.id === currentNodeId); + const outgoingEdges = getOutgoingEdges(currentNodeId); + + if (!currentNodeObj || !outgoingEdges.length) return null; + + const { conditions = [] } = currentNodeObj.data || {}; + + // Evaluate condition groups (each group represents a path) + for (const conditionGroup of conditions) { + if (evaluateConditionGroup(conditionGroup, variables)) { + // Find the edge that matches this condition group's output label + const edge = outgoingEdges.find(e => e.label === conditionGroup.output); + if (edge) return edge.target; + } + } + + // If no conditions match, use default path + const defaultEdge = outgoingEdges.find(e => e.data?.isDefault); + if (defaultEdge) return defaultEdge.target; + + // Fallback to first edge + return outgoingEdges[0]?.target || null; +} +``` + --- ## 🎨 Styling System @@ -577,7 +726,7 @@ export default defineNuxtPlugin(() => { formId: 'form-uuid', // For form nodes apiEndpoint: 'url', // For API nodes scriptCode: 'javascript', // For script nodes - conditions: [], // For gateway nodes + conditions: [], // For gateway nodes (see Gateway Conditions section) // Custom properties customProperties: {} @@ -585,6 +734,73 @@ export default defineNuxtPlugin(() => { } ``` +### **Gateway Conditions Structure** +```javascript +// Gateway node conditions format (group-based) +{ + id: 'gateway-node-id', + type: 'gateway', + data: { + label: 'Decision Point', + conditions: [ + { + id: 'condition-group-1', + output: 'Path Label', // Label for this decision path + conditions: [ // Array of conditions for this path + { + id: 'condition-1', + variable: 'processVariable', // Process variable to evaluate + operator: 'eq', // Comparison operator + value: 'expectedValue', // Value to compare against + valueType: 'string', // Data type (string, boolean, number, etc.) + logicalOperator: 'and' // How to combine with next condition (and/or) + } + // ... more conditions for this path + ] + } + // ... more condition groups (paths) + ], + defaultPath: 'Default' // Default path if no conditions match + } +} +``` + +### **Supported Condition Operators** +```javascript +// String operators +'eq', 'equals', '==' // Equal to +'neq', 'not_equals', '!=' // Not equal to +'contains' // Contains substring +'not_contains' // Does not contain +'starts_with' // Starts with +'ends_with' // Ends with +'empty' // Is empty/null +'not_empty' // Is not empty/null +'regex' // Matches regex pattern + +// Numeric operators +'gt', 'greater_than', '>' // Greater than +'lt', 'less_than', '<' // Less than +'gte', 'greater_than_or_equal', '>=' // Greater than or equal +'lte', 'less_than_or_equal', '<=' // Less than or equal +'between' // Between two values +'not_between' // Not between two values + +// Boolean operators +'is_true' // Is true +'is_false' // Is false + +// Date operators +'today' // Is today +'this_week' // Is this week +'this_month' // Is this month +'this_year' // Is this year + +// Object operators +'has_property' // Has specific property +'property_equals' // Property equals value +``` + --- ## 🐛 Common Issues & Solutions @@ -700,4 +916,46 @@ const nodeTypes = { **🔄 Version**: 1.0 - Comprehensive system documentation -**👥 For New Team Members**: This document contains everything needed to understand and work with the Vue Flow Process Builder system. Start with the "File Structure" section and follow the implementation patterns for any new features. \ No newline at end of file +**👥 For New Team Members**: This document contains everything needed to understand and work with the Vue Flow Process Builder system. Start with the "File Structure" section and follow the implementation patterns for any new features. + +--- + +## 🧪 Testing Gateway Decisions + +### **Form Field Integration** +To test gateway decisions, add form fields that map to process variables: + +```json +// Form component with gateway test field +{ + "type": "checkbox", + "props": { + "name": "todoStatus", + "label": "Test Gateway Decision", + "help": "Toggle to test gateway paths" + } +} +``` + +### **Process Variable Mapping** +Map form fields to process variables for gateway testing: + +```json +// Form node output mappings +{ + "outputMappings": [ + { "formField": "todoStatus", "processVariable": "todoStatus" } + ] +} +``` + +### **Gateway Testing Scenarios** +1. **"Completed" Path**: Check the test field → `todoStatus = true` → Follow "Completed" path +2. **"Not Completed" Path**: Leave test field unchecked → `todoStatus = false` → Follow "Not Completed" path + +### **Workflow Execution** +The workflow execution engine (`pages/workflow/[id].vue`) automatically: +- Evaluates gateway conditions based on current process variables +- Shows visual feedback of which conditions are true/false +- Follows the first matching path +- Falls back to default path if no conditions match \ No newline at end of file diff --git a/pages/workflow/[id].vue b/pages/workflow/[id].vue index 07fda72..717ab66 100644 --- a/pages/workflow/[id].vue +++ b/pages/workflow/[id].vue @@ -155,63 +155,204 @@ function getNextNodeIdForDecision(currentNodeId) { if (!currentNodeObj || !outgoingEdges.length) return null; - const { conditions = [], defaultPath } = currentNodeObj.data || {}; + const { conditions = [] } = currentNodeObj.data || {}; - // Evaluate conditions - for (const condition of conditions) { - if (evaluateCondition(condition, processVariables.value)) { - // Find the edge that matches this condition's target - const edge = outgoingEdges.find(e => e.data?.conditionId === condition.id || e.label === condition.label); + // Evaluate condition groups (each group represents a path) + for (const conditionGroup of conditions) { + if (evaluateConditionGroup(conditionGroup, processVariables.value)) { + // Find the edge that matches this condition group's output label + const edge = outgoingEdges.find(e => e.label === conditionGroup.output || e.data?.condition === conditionGroup.output); if (edge) return edge.target; } } - // If no conditions match, use default path - if (defaultPath) { - const defaultEdge = outgoingEdges.find(e => e.data?.isDefault || e.label?.toLowerCase().includes('default')); - if (defaultEdge) return defaultEdge.target; - } + // If no conditions match, look for default path + const defaultEdge = outgoingEdges.find(e => e.data?.isDefault || e.label?.toLowerCase().includes('default')); + if (defaultEdge) return defaultEdge.target; // Fallback to first edge return outgoingEdges[0]?.target || null; } +// Helper: Evaluate a condition group (multiple conditions with AND/OR logic) +function evaluateConditionGroup(conditionGroup, variables) { + if (!conditionGroup.conditions || conditionGroup.conditions.length === 0) { + return false; + } + + // If only one condition, evaluate it directly + if (conditionGroup.conditions.length === 1) { + const singleResult = evaluateCondition(conditionGroup.conditions[0], variables); + console.log(`[Gateway Debug] Group '${conditionGroup.output}': single condition result=`, singleResult); + return singleResult; + } + + // For multiple conditions, evaluate based on logical operators + let result = evaluateCondition(conditionGroup.conditions[0], variables); + console.log(`[Gateway Debug] Group '${conditionGroup.output}': condition 0 result=`, result); + + for (let i = 1; i < conditionGroup.conditions.length; i++) { + const condition = conditionGroup.conditions[i]; + const conditionResult = evaluateCondition(condition, variables); + const operator = condition.logicalOperator || 'and'; + if (operator === 'and') { + result = result && conditionResult; + } else if (operator === 'or') { + result = result || conditionResult; + } + console.log(`[Gateway Debug] Group '${conditionGroup.output}': after condition ${i}, operator='${operator}', conditionResult=`, conditionResult, ', groupResult=', result); + } + + return result; +} + // Helper: Evaluate a single condition function evaluateCondition(condition, variables) { - const { variable, operator, value } = condition; + const { variable, operator, value, valueType, minValue, maxValue } = condition; const variableValue = variables[variable]; - + + // Handle boolean type conversions + let compareValue = value; + if (valueType === 'boolean') { + if (typeof value === 'string') { + compareValue = value.toLowerCase() === 'true'; + } else { + compareValue = Boolean(value); + } + } + + let result; switch (operator) { + case 'eq': case 'equals': case '==': - return variableValue == value; + result = variableValue == compareValue; + break; + case 'neq': case 'not_equals': case '!=': - return variableValue != value; + result = variableValue != compareValue; + break; + case 'gt': case 'greater_than': case '>': - return Number(variableValue) > Number(value); + result = Number(variableValue) > Number(compareValue); + break; + case 'lt': case 'less_than': case '<': - return Number(variableValue) < Number(value); + result = Number(variableValue) < Number(compareValue); + break; + case 'gte': case 'greater_than_or_equal': case '>=': - return Number(variableValue) >= Number(value); + result = Number(variableValue) >= Number(compareValue); + break; + case 'lte': case 'less_than_or_equal': case '<=': - return Number(variableValue) <= Number(value); + result = Number(variableValue) <= Number(compareValue); + break; + case 'between': + result = ( + Number(variableValue) >= Number(minValue) && + Number(variableValue) <= Number(maxValue) + ); + break; + case 'not_between': + result = ( + Number(variableValue) < Number(minValue) || + Number(variableValue) > Number(maxValue) + ); + break; case 'contains': - return String(variableValue).includes(String(value)); + result = String(variableValue).includes(String(compareValue)); + break; case 'not_contains': - return !String(variableValue).includes(String(value)); + result = !String(variableValue).includes(String(compareValue)); + break; + case 'starts_with': + result = String(variableValue).startsWith(String(compareValue)); + break; + case 'ends_with': + result = String(variableValue).endsWith(String(compareValue)); + break; + case 'regex': + try { + result = new RegExp(compareValue).test(variableValue); + } catch (e) { + result = false; + } + break; + case 'has_property': + result = variableValue && typeof variableValue === 'object' && value in variableValue; + break; + case 'property_equals': { + if (!variableValue || typeof variableValue !== 'object' || typeof compareValue !== 'string') { result = false; break; } + const [prop, val] = compareValue.split(':'); + result = variableValue[prop] == val; + break; + } + case 'empty': case 'is_empty': - return !variableValue || variableValue === '' || variableValue === null || variableValue === undefined; + result = !variableValue || variableValue === '' || variableValue === null || variableValue === undefined; + break; + case 'not_empty': case 'is_not_empty': - return variableValue && variableValue !== '' && variableValue !== null && variableValue !== undefined; + result = variableValue && variableValue !== '' && variableValue !== null && variableValue !== undefined; + break; + case 'is_true': + result = Boolean(variableValue) === true; + break; + case 'is_false': + result = Boolean(variableValue) === false; + break; + // Date operators + case 'today': { + if (!variableValue) { result = false; break; } + const date = new Date(variableValue); + const now = new Date(); + result = ( + date.getFullYear() === now.getFullYear() && + date.getMonth() === now.getMonth() && + date.getDate() === now.getDate() + ); + break; + } + case 'this_week': { + if (!variableValue) { result = false; break; } + const date = new Date(variableValue); + const now = new Date(); + const startOfWeek = new Date(now); + startOfWeek.setDate(now.getDate() - now.getDay()); + const endOfWeek = new Date(startOfWeek); + endOfWeek.setDate(startOfWeek.getDate() + 6); + result = date >= startOfWeek && date <= endOfWeek; + break; + } + case 'this_month': { + if (!variableValue) { result = false; break; } + const date = new Date(variableValue); + const now = new Date(); + result = ( + date.getFullYear() === now.getFullYear() && + date.getMonth() === now.getMonth() + ); + break; + } + case 'this_year': { + if (!variableValue) { result = false; break; } + const date = new Date(variableValue); + const now = new Date(); + result = date.getFullYear() === now.getFullYear(); + break; + } default: console.warn('[Workflow] Unknown condition operator:', operator); - return false; + result = false; } + console.log(`[Gateway Debug] Condition: variable='${variable}', operator='${operator}', value=`, value, ', minValue=', minValue, ', maxValue=', maxValue, '| variableValue=', variableValue, '| result=', result); + return result; } // Move to a specific node by its ID @@ -744,7 +885,10 @@ const executeDecisionNode = async () => { stepLoading.value = true; const currentNodeObj = workflowData.value.nodes[currentStep.value]; const { executionType = 'automatic' } = currentNodeObj.data || {}; - + + // Debug: Log process variables before evaluating gateway + console.log('[Gateway Debug] Current processVariables:', JSON.stringify(processVariables.value, null, 2)); + if (executionType === 'automatic') { // Automatic decision based on conditions const nextNodeId = getNextNodeIdForDecision(currentNodeObj.id); @@ -837,6 +981,16 @@ const htmlNodeStyles = computed(() => { return currentNode.value?.data?.cssCode || ''; }); +// Computed: Condition evaluation results for gateway nodes +const conditionEvaluationResults = computed(() => { + if (!['decision', 'gateway'].includes(currentNode.value?.type)) return []; + if (!currentNode.value?.data?.conditions) return []; + + return currentNode.value.data.conditions.map(conditionGroup => + getConditionGroupResult(conditionGroup, processVariables.value) + ); +}); + // CSS injection for HTML nodes let currentStyleElement = null; // JS injection for HTML nodes @@ -923,6 +1077,51 @@ function getNodeLabel(nodeId) { const node = workflowData.value.nodes.find(n => n.id === nodeId); return node?.data?.label || node?.label || 'Next Step'; } + +// Helper: Get human-readable condition description +function getConditionDescription(condition, variables) { + const { variable, operator, value, valueType } = condition; + const variableValue = variables[variable]; + + let operatorText = operator; + switch (operator) { + case 'eq': operatorText = '='; break; + case 'neq': operatorText = '≠'; break; + case 'gt': operatorText = '>'; break; + case 'lt': operatorText = '<'; break; + case 'gte': operatorText = '≥'; break; + case 'lte': operatorText = '≤'; break; + case 'contains': operatorText = 'contains'; break; + case 'not_contains': operatorText = 'does not contain'; break; + case 'starts_with': operatorText = 'starts with'; break; + case 'ends_with': operatorText = 'ends with'; break; + case 'empty': operatorText = 'is empty'; break; + case 'not_empty': operatorText = 'is not empty'; break; + case 'is_true': operatorText = 'is true'; break; + case 'is_false': operatorText = 'is false'; break; + } + + if (['empty', 'not_empty', 'is_true', 'is_false'].includes(operator)) { + return `${variable} ${operatorText}`; + } + + return `${variable} ${operatorText} ${value}`; +} + +// Helper: Get condition group evaluation result +function getConditionGroupResult(conditionGroup, variables) { + const results = conditionGroup.conditions.map(condition => { + const result = evaluateCondition(condition, variables); + const description = getConditionDescription(condition, variables); + return { result, description }; + }); + + return { + group: conditionGroup, + results, + finalResult: evaluateConditionGroup(conditionGroup, variables) + }; +}