Refactor Travel Workflow Components and Update Form Logic

- Updated VariableManager.vue to change variable type labels for clarity, replacing 'Int' and 'Decimal' with 'Number'.
- Modified manager-approval-customScript.js to correct field references and enhance conditional logic for displaying the custom approved amount.
- Enhanced manager-approval-form.json by adding 'readonly' attributes to several fields, ensuring they are non-editable during the approval process.
- Revised travel-workflow-process.json to improve node connections and labels for better workflow clarity.
- Updated travel-workflow-variables.json to refine variable descriptions and ensure consistency across the workflow.
- Adjusted [id].vue to improve form data handling and loading states, enhancing user experience during workflow execution.
This commit is contained in:
Md Afiq Iskandar 2025-07-25 14:29:04 +08:00
parent 84e32e4dc7
commit 597a443453
6 changed files with 410 additions and 344 deletions

View File

@ -518,8 +518,7 @@ const variableForm = ref({
// Variable type options with descriptions // Variable type options with descriptions
const variableTypes = [ const variableTypes = [
{ label: 'String - Text values', value: 'string' }, { label: 'String - Text values', value: 'string' },
{ label: 'Int - Whole numbers', value: 'int' }, { label: 'Number - Decimal numbers', value: 'number' },
{ label: 'Decimal - Decimal numbers', value: 'decimal' },
{ label: 'Object - Complex data structure', value: 'object' }, { label: 'Object - Complex data structure', value: 'object' },
{ label: 'DateTime - Date and time values', value: 'datetime' }, { label: 'DateTime - Date and time values', value: 'datetime' },
{ label: 'Date - Date values only', value: 'date' }, { label: 'Date - Date values only', value: 'date' },

View File

@ -14,7 +14,7 @@ const setApprovalDate = () => {
const calculateRecommendedAmounts = () => { const calculateRecommendedAmounts = () => {
const totalClaimed = parseFloat(getField("total_cost_display")) || 0; const totalClaimed = parseFloat(getField("total_cost_display")) || 0;
const policyLimit = parseFloat(getField("policy_limit_display")) || 0; const policyLimit = parseFloat(getField("policy_limit_display")) || 0;
const overBudget = parseFloat(getField("over_budget_amount_display")) || 0; const overBudget = parseFloat(getField("over_budget_amount")) || 0;
if (totalClaimed > 0 && policyLimit > 0) { if (totalClaimed > 0 && policyLimit > 0) {
const percentageOver = ((overBudget / policyLimit) * 100).toFixed(1); const percentageOver = ((overBudget / policyLimit) * 100).toFixed(1);
@ -222,7 +222,7 @@ onFieldChange("manager_name", (newValue) => {
function waitForFields(fields, callback, maxAttempts = 20) { function waitForFields(fields, callback, maxAttempts = 20) {
let attempts = 0; let attempts = 0;
function check() { function check() {
const allPresent = fields.every(f => getField(f) !== undefined); const allPresent = fields.every((f) => getField(f) !== undefined);
if (allPresent) { if (allPresent) {
callback(); callback();
} else if (attempts < maxAttempts) { } else if (attempts < maxAttempts) {
@ -239,13 +239,15 @@ waitForFields(
[ [
"total_cost_display", "total_cost_display",
"policy_limit_display", "policy_limit_display",
"over_budget_amount_display", "over_budget_amount",
// add any other required fields here // add any other required fields here
], ],
() => { () => {
setApprovalDate(); setApprovalDate();
calculateRecommendedAmounts(); calculateRecommendedAmounts();
showInfo("Review the claim details above and make your approval decision below."); showInfo(
"Review the claim details above and make your approval decision below."
);
} }
); );
@ -271,3 +273,23 @@ onFieldChange("manager_decision", function () {
hideField("custom_approved_amount"); hideField("custom_approved_amount");
} }
})(); })();
// Conditional Logic Script
// Conditional logic for field: custom_approved_amount
onFieldChange("manager_decision", function () {
if (getField("manager_decision") === "approve_full") {
showField("custom_approved_amount");
} else {
hideField("custom_approved_amount");
}
});
// Initial evaluation for field: custom_approved_amount
(function () {
if (getField("manager_decision") === "approve_full") {
showField("custom_approved_amount");
} else {
hideField("custom_approved_amount");
}
})();

View File

@ -54,6 +54,7 @@
"type": "text", "type": "text",
"label": "Claim Summary", "label": "Claim Summary",
"width": "100%", "width": "100%",
"readonly": true,
"gridColumn": "span 12", "gridColumn": "span 12",
"validation": "", "validation": "",
"placeholder": "Claim summary will be populated from process", "placeholder": "Claim summary will be populated from process",
@ -73,6 +74,7 @@
"type": "text", "type": "text",
"label": "Employee Name", "label": "Employee Name",
"width": "50%", "width": "50%",
"readonly": true,
"gridColumn": "span 6", "gridColumn": "span 6",
"validation": "", "validation": "",
"placeholder": "Employee name", "placeholder": "Employee name",
@ -92,6 +94,7 @@
"type": "text", "type": "text",
"label": "Department", "label": "Department",
"width": "50%", "width": "50%",
"readonly": true,
"gridColumn": "span 6", "gridColumn": "span 6",
"validation": "", "validation": "",
"placeholder": "Department", "placeholder": "Department",
@ -111,6 +114,7 @@
"type": "text", "type": "text",
"label": "Trip Purpose", "label": "Trip Purpose",
"width": "50%", "width": "50%",
"readonly": true,
"gridColumn": "span 6", "gridColumn": "span 6",
"validation": "", "validation": "",
"placeholder": "Trip purpose", "placeholder": "Trip purpose",
@ -130,6 +134,7 @@
"type": "text", "type": "text",
"label": "Destination", "label": "Destination",
"width": "50%", "width": "50%",
"readonly": true,
"gridColumn": "span 6", "gridColumn": "span 6",
"validation": "", "validation": "",
"placeholder": "Destination", "placeholder": "Destination",
@ -165,9 +170,16 @@
"type": "number", "type": "number",
"label": "Total Claimed Amount (RM)", "label": "Total Claimed Amount (RM)",
"width": "33.33%", "width": "33.33%",
"readonly": true,
"gridColumn": "span 4", "gridColumn": "span 4",
"validation": "", "validation": "",
"placeholder": "0.00" "placeholder": "0.00",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
} }
}, },
{ {
@ -178,22 +190,36 @@
"type": "number", "type": "number",
"label": "Policy Limit (RM)", "label": "Policy Limit (RM)",
"width": "33.33%", "width": "33.33%",
"readonly": true,
"gridColumn": "span 4", "gridColumn": "span 4",
"validation": "", "validation": "",
"placeholder": "0.00" "placeholder": "0.00",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
} }
}, },
{ {
"type": "number", "type": "number",
"props": { "props": {
"help": "Amount exceeding policy limits", "help": "Amount exceeding policy limits",
"name": "over_budget_amount_display", "name": "over_budget_amount",
"type": "number", "type": "number",
"label": "Over Budget Amount (RM)", "label": "Over Budget Amount (RM)",
"width": "33.33%", "width": "33.33%",
"readonly": true,
"gridColumn": "span 4", "gridColumn": "span 4",
"validation": "", "validation": "",
"placeholder": "0.00" "placeholder": "0.00",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
} }
}, },
{ {

View File

@ -55,50 +55,6 @@
"sourceHandle": "script-1753000005-right", "sourceHandle": "script-1753000005-right",
"targetHandle": "gateway-1753000006-left" "targetHandle": "gateway-1753000006-left"
}, },
{
"id": "gateway-1753000006-form-1753000007-edge",
"data": { "condition": "isOverBudget === true" },
"type": "custom",
"label": "Requires Approval",
"source": "gateway-1753000006",
"target": "form-1753000007",
"animated": true,
"sourceHandle": "gateway-1753000006-top",
"targetHandle": "form-1753000007-left"
},
{
"id": "gateway-1753000006-script-1753000008-edge",
"data": { "condition": "isOverBudget === false" },
"type": "custom",
"label": "Auto Approve",
"source": "gateway-1753000006",
"target": "script-1753000008",
"animated": true,
"sourceHandle": "gateway-1753000006-bottom",
"targetHandle": "script-1753000008-left"
},
{
"id": "form-1753000007-script-1753000009-edge",
"data": {},
"type": "custom",
"label": "",
"source": "form-1753000007",
"target": "script-1753000009",
"animated": true,
"sourceHandle": "form-1753000007-right",
"targetHandle": "script-1753000009-left"
},
{
"id": "script-1753000008-notification-1753000010-edge",
"data": {},
"type": "custom",
"label": "",
"source": "script-1753000008",
"target": "notification-1753000010",
"animated": true,
"sourceHandle": "script-1753000008-right",
"targetHandle": "notification-1753000010-left"
},
{ {
"id": "script-1753000009-notification-1753000011-edge", "id": "script-1753000009-notification-1753000011-edge",
"data": {}, "data": {},
@ -131,6 +87,50 @@
"animated": true, "animated": true,
"sourceHandle": "notification-1753000011-bottom", "sourceHandle": "notification-1753000011-bottom",
"targetHandle": "end-1753000012-top" "targetHandle": "end-1753000012-top"
},
{
"id": "form-1753000007-script-set-approved-amount-1753423747167",
"data": {},
"type": "custom",
"label": "",
"source": "form-1753000007",
"target": "script-set-approved-amount",
"animated": true,
"sourceHandle": "form-1753000007-right",
"targetHandle": "script-set-approved-amount-left"
},
{
"id": "script-set-approved-amount-script-1753000009-1753423748408",
"data": {},
"type": "custom",
"label": "",
"source": "script-set-approved-amount",
"target": "script-1753000009",
"animated": true,
"sourceHandle": "script-set-approved-amount-right",
"targetHandle": "script-1753000009-left"
},
{
"id": "gateway-1753000006-form-1753000007-1753423756117",
"data": {},
"type": "custom",
"label": "Requires Approval",
"source": "gateway-1753000006",
"target": "form-1753000007",
"animated": true,
"sourceHandle": "gateway-1753000006-right",
"targetHandle": "form-1753000007-left"
},
{
"id": "gateway-1753000006-notification-1753000010-1753423759335",
"data": {},
"type": "custom",
"label": "Auto Approve",
"source": "gateway-1753000006",
"target": "notification-1753000010",
"animated": true,
"sourceHandle": "gateway-1753000006-right",
"targetHandle": "notification-1753000010-left"
} }
], ],
"nodes": [ "nodes": [
@ -138,14 +138,14 @@
"id": "start-1753000001", "id": "start-1753000001",
"data": { "data": {
"label": "Start Travel Claim", "label": "Start Travel Claim",
"description": "Employee initiates travel reimbursement claim",
"shape": "circle", "shape": "circle",
"backgroundColor": "#dcfce7", "borderColor": "#10b981",
"borderColor": "#10b981" "description": "Employee initiates travel reimbursement claim",
"backgroundColor": "#dcfce7"
}, },
"type": "start", "type": "start",
"label": "Start Travel Claim", "label": "Start Travel Claim",
"position": { "x": 100, "y": 300 } "position": { "x": 105, "y": 300 }
}, },
{ {
"id": "form-1753000002", "id": "form-1753000002",
@ -159,7 +159,11 @@
"borderColor": "#9333ea", "borderColor": "#9333ea",
"description": "Employee fills travel reimbursement claim form", "description": "Employee fills travel reimbursement claim form",
"assignedRoles": [ "assignedRoles": [
{ "label": "Employee", "value": "3", "description": "Regular employee role" } {
"label": "Employee",
"value": "3",
"description": "Regular employee role"
}
], ],
"assignedUsers": [], "assignedUsers": [],
"inputMappings": [], "inputMappings": [],
@ -174,7 +178,10 @@
{ "formField": "return_date", "processVariable": "returnDate" }, { "formField": "return_date", "processVariable": "returnDate" },
{ "formField": "travel_type", "processVariable": "travelType" }, { "formField": "travel_type", "processVariable": "travelType" },
{ "formField": "transport_cost", "processVariable": "transportCost" }, { "formField": "transport_cost", "processVariable": "transportCost" },
{ "formField": "accommodation_cost", "processVariable": "accommodationCost" }, {
"formField": "accommodation_cost",
"processVariable": "accommodationCost"
},
{ "formField": "meals_cost", "processVariable": "mealsCost" }, { "formField": "meals_cost", "processVariable": "mealsCost" },
{ "formField": "other_cost", "processVariable": "otherCost" }, { "formField": "other_cost", "processVariable": "otherCost" },
{ "formField": "has_receipts", "processVariable": "hasReceipts" } { "formField": "has_receipts", "processVariable": "hasReceipts" }
@ -186,7 +193,7 @@
}, },
"type": "form", "type": "form",
"label": "Travel Claim Form", "label": "Travel Claim Form",
"position": { "x": 400, "y": 270 } "position": { "x": 405, "y": 270 }
}, },
{ {
"id": "script-1753000003", "id": "script-1753000003",
@ -198,8 +205,15 @@
"borderColor": "#6b7280", "borderColor": "#6b7280",
"description": "Validate form data and calculate totals", "description": "Validate form data and calculate totals",
"inputVariables": [ "inputVariables": [
"employeeName", "tripPurpose", "destination", "transportCost", "employeeName",
"accommodationCost", "mealsCost", "otherCost", "departureDate", "returnDate" "tripPurpose",
"destination",
"transportCost",
"accommodationCost",
"mealsCost",
"otherCost",
"departureDate",
"returnDate"
], ],
"scriptLanguage": "javascript", "scriptLanguage": "javascript",
"backgroundColor": "#f9fafb", "backgroundColor": "#f9fafb",
@ -260,7 +274,7 @@
}, },
"type": "api", "type": "api",
"label": "Get Policy Rates", "label": "Get Policy Rates",
"position": { "x": 1100, "y": 270 } "position": { "x": 1095, "y": 270 }
}, },
{ {
"id": "script-1753000005", "id": "script-1753000005",
@ -271,7 +285,12 @@
"scriptCode": "// Process policy API response\nconst policyResponse = processVariables.policyApiResponse || {};\n\n// Mock policy rates based on travel type (in real system, would come from API)\nlet maxTransport = 0, maxAccommodation = 0, maxMeals = 0, maxOther = 0;\n\nswitch(processVariables.travelType) {\n case 'flight':\n maxTransport = 2000;\n maxAccommodation = 300 * processVariables.tripDuration;\n maxMeals = 150 * processVariables.tripDuration;\n maxOther = 100 * processVariables.tripDuration;\n break;\n case 'train':\n maxTransport = 500;\n maxAccommodation = 250 * processVariables.tripDuration;\n maxMeals = 120 * processVariables.tripDuration;\n maxOther = 80 * processVariables.tripDuration;\n break;\n case 'car':\n maxTransport = 800;\n maxAccommodation = 200 * processVariables.tripDuration;\n maxMeals = 100 * processVariables.tripDuration;\n maxOther = 60 * processVariables.tripDuration;\n break;\n default:\n maxTransport = 300;\n maxAccommodation = 150 * processVariables.tripDuration;\n maxMeals = 80 * processVariables.tripDuration;\n maxOther = 50 * processVariables.tripDuration;\n}\n\n// Calculate allowed amounts\nprocessVariables.allowedTransport = Math.min(processVariables.transportCost, maxTransport);\nprocessVariables.allowedAccommodation = Math.min(processVariables.accommodationCost, maxAccommodation);\nprocessVariables.allowedMeals = Math.min(processVariables.mealsCost, maxMeals);\nprocessVariables.allowedOther = Math.min(processVariables.otherCost, maxOther);\n\n// Calculate total allowed and reimbursement\nprocessVariables.totalAllowed = processVariables.allowedTransport + processVariables.allowedAccommodation + processVariables.allowedMeals + processVariables.allowedOther;\nprocessVariables.reimbursementAmount = processVariables.totalAllowed;\n\n// Check if over budget\nprocessVariables.isOverBudget = processVariables.totalCost > processVariables.totalAllowed;\nprocessVariables.overBudgetAmount = Math.max(0, processVariables.totalCost - processVariables.totalAllowed);\n\n// Create claim summary\nprocessVariables.claimSummary = `Travel Claim - ${processVariables.employeeName} - ${processVariables.tripPurpose} - ${processVariables.destination} - RM${processVariables.reimbursementAmount.toFixed(2)} reimbursement`;\n\n// Set initial approval status\nprocessVariables.approvalStatus = processVariables.isOverBudget ? 'pending_approval' : 'auto_approved';", "scriptCode": "// Process policy API response\nconst policyResponse = processVariables.policyApiResponse || {};\n\n// Mock policy rates based on travel type (in real system, would come from API)\nlet maxTransport = 0, maxAccommodation = 0, maxMeals = 0, maxOther = 0;\n\nswitch(processVariables.travelType) {\n case 'flight':\n maxTransport = 2000;\n maxAccommodation = 300 * processVariables.tripDuration;\n maxMeals = 150 * processVariables.tripDuration;\n maxOther = 100 * processVariables.tripDuration;\n break;\n case 'train':\n maxTransport = 500;\n maxAccommodation = 250 * processVariables.tripDuration;\n maxMeals = 120 * processVariables.tripDuration;\n maxOther = 80 * processVariables.tripDuration;\n break;\n case 'car':\n maxTransport = 800;\n maxAccommodation = 200 * processVariables.tripDuration;\n maxMeals = 100 * processVariables.tripDuration;\n maxOther = 60 * processVariables.tripDuration;\n break;\n default:\n maxTransport = 300;\n maxAccommodation = 150 * processVariables.tripDuration;\n maxMeals = 80 * processVariables.tripDuration;\n maxOther = 50 * processVariables.tripDuration;\n}\n\n// Calculate allowed amounts\nprocessVariables.allowedTransport = Math.min(processVariables.transportCost, maxTransport);\nprocessVariables.allowedAccommodation = Math.min(processVariables.accommodationCost, maxAccommodation);\nprocessVariables.allowedMeals = Math.min(processVariables.mealsCost, maxMeals);\nprocessVariables.allowedOther = Math.min(processVariables.otherCost, maxOther);\n\n// Calculate total allowed and reimbursement\nprocessVariables.totalAllowed = processVariables.allowedTransport + processVariables.allowedAccommodation + processVariables.allowedMeals + processVariables.allowedOther;\nprocessVariables.reimbursementAmount = processVariables.totalAllowed;\n\n// Check if over budget\nprocessVariables.isOverBudget = processVariables.totalCost > processVariables.totalAllowed;\nprocessVariables.overBudgetAmount = Math.max(0, processVariables.totalCost - processVariables.totalAllowed);\n\n// Create claim summary\nprocessVariables.claimSummary = `Travel Claim - ${processVariables.employeeName} - ${processVariables.tripPurpose} - ${processVariables.destination} - RM${processVariables.reimbursementAmount.toFixed(2)} reimbursement`;\n\n// Set initial approval status\nprocessVariables.approvalStatus = processVariables.isOverBudget ? 'pending_approval' : 'auto_approved';",
"borderColor": "#6b7280", "borderColor": "#6b7280",
"description": "Calculate reimbursement based on policy rates", "description": "Calculate reimbursement based on policy rates",
"inputVariables": ["policyApiResponse", "totalCost", "travelType", "tripDuration"], "inputVariables": [
"policyApiResponse",
"totalCost",
"travelType",
"tripDuration"
],
"scriptLanguage": "javascript", "scriptLanguage": "javascript",
"backgroundColor": "#f9fafb", "backgroundColor": "#f9fafb",
"outputVariables": [ "outputVariables": [
@ -309,7 +328,7 @@
}, },
"type": "script", "type": "script",
"label": "Calculate Reimbursement", "label": "Calculate Reimbursement",
"position": { "x": 1450, "y": 270 } "position": { "x": 1455, "y": 270 }
}, },
{ {
"id": "gateway-1753000006", "id": "gateway-1753000006",
@ -358,7 +377,7 @@
}, },
"type": "gateway", "type": "gateway",
"label": "Budget Check", "label": "Budget Check",
"position": { "x": 1800, "y": 270 } "position": { "x": 1755, "y": 330 }
}, },
{ {
"id": "form-1753000007", "id": "form-1753000007",
@ -368,71 +387,68 @@
"formId": 3, "formId": 3,
"formName": "Travel Claim Approval Form", "formName": "Travel Claim Approval Form",
"formUuid": "travel-approval-form-uuid", "formUuid": "travel-approval-form-uuid",
"textColor": "#dc2626", "textColor": "#6b21a8",
"borderColor": "#ef4444", "borderColor": "#9333ea",
"description": "Manager reviews and approves/rejects over-budget travel claim", "description": "Manager reviews and approves/rejects over-budget travel claim",
"assignedRoles": [ "assignedRoles": [
{ "label": "Manager", "value": "4", "description": "Department manager role" } {
"label": "Manager",
"value": "4",
"description": "Department manager role"
}
], ],
"assignedUsers": [], "assignedUsers": [],
"inputMappings": [ "inputMappings": [
{ "processVariable": "claimSummary", "formField": "claim_summary" }, { "formField": "claim_summary", "processVariable": "claimSummary" },
{ "processVariable": "totalCost", "formField": "total_cost_display" }, {
{ "processVariable": "overBudgetAmount", "formField": "over_budget_amount_display" }, "formField": "employee_name_display",
{ "processVariable": "totalAllowed", "formField": "policy_limit_display" } "processVariable": "employeeName"
},
{
"formField": "department_display",
"processVariable": "department"
},
{
"formField": "trip_purpose_display",
"processVariable": "tripPurpose"
},
{
"formField": "destination_display",
"processVariable": "destination"
},
{ "formField": "total_cost_display", "processVariable": "totalCost" },
{
"formField": "policy_limit_display",
"processVariable": "totalAllowed"
},
{
"formField": "over_budget_amount",
"processVariable": "overBudgetAmount"
}
], ],
"assignmentType": "roles", "assignmentType": "roles",
"outputMappings": [ "outputMappings": [
{ "formField": "manager_decision", "processVariable": "managerDecision" }, {
{ "formField": "manager_comments", "processVariable": "managerComments" } "formField": "manager_decision",
"processVariable": "managerDecision"
},
{
"formField": "manager_comments",
"processVariable": "managerComments"
},
{
"formField": "custom_approved_amount",
"processVariable": "customApprovedAmount"
}
], ],
"backgroundColor": "#fef2f2", "backgroundColor": "#faf5ff",
"fieldConditions": [], "fieldConditions": [],
"assignmentVariable": "", "assignmentVariable": "",
"assignmentVariableType": "user_id" "assignmentVariableType": "user_id"
}, },
"type": "form", "type": "form",
"label": "Manager Approval", "label": "Manager Approval",
"position": { "x": 2150, "y": 100 } "position": { "x": 1980, "y": 105 }
},
{
"id": "script-1753000008",
"data": {
"label": "Auto Approve & Store",
"shape": "rectangle",
"textColor": "#374151",
"scriptCode": "// Auto-approve claim within budget\nprocessVariables.finalApprovalStatus = 'approved';\nprocessVariables.approvedAmount = processVariables.reimbursementAmount;\nprocessVariables.approvalTimestamp = new Date().toISOString();\nprocessVariables.approvedBy = 'System (Auto-approved)';\nprocessVariables.approvalComments = 'Claim is within policy limits and auto-approved.';\n\n// Create final claim record for storage\nprocessVariables.finalClaimRecord = {\n employeeName: processVariables.employeeName,\n employeeEmail: processVariables.employeeEmail,\n department: processVariables.department,\n tripPurpose: processVariables.tripPurpose,\n destination: processVariables.destination,\n totalClaimed: processVariables.totalCost,\n approvedAmount: processVariables.approvedAmount,\n status: processVariables.finalApprovalStatus,\n submissionDate: processVariables.submissionTimestamp,\n approvalDate: processVariables.approvalTimestamp,\n approvedBy: processVariables.approvedBy\n};\n\n// Mock API call to store claim (in real system, would call actual storage API)\nprocessVariables.claimId = 'CLAIM-' + Date.now();\nprocessVariables.storageResult = 'success';",
"borderColor": "#10b981",
"description": "Auto-approve claim and store in system",
"inputVariables": ["reimbursementAmount", "claimSummary"],
"scriptLanguage": "javascript",
"backgroundColor": "#f0fdf4",
"outputVariables": [
{
"name": "finalApprovalStatus",
"type": "string",
"description": "Final approval status"
},
{
"name": "approvedAmount",
"type": "number",
"description": "Final approved amount"
},
{
"name": "claimId",
"type": "string",
"description": "Generated claim ID"
},
{
"name": "approvalTimestamp",
"type": "string",
"description": "When claim was approved"
}
]
},
"type": "script",
"label": "Auto Approve & Store",
"position": { "x": 2150, "y": 440 }
}, },
{ {
"id": "script-1753000009", "id": "script-1753000009",
@ -440,7 +456,7 @@
"label": "Process Manager Decision", "label": "Process Manager Decision",
"shape": "rectangle", "shape": "rectangle",
"textColor": "#374151", "textColor": "#374151",
"scriptCode": "// Process manager's decision\nprocessVariables.finalApprovalStatus = processVariables.managerDecision === 'approve' ? 'approved' : 'rejected';\nprocessVariables.approvalTimestamp = new Date().toISOString();\nprocessVariables.approvedBy = 'Manager';\nprocessVariables.approvalComments = processVariables.managerComments || '';\n\n// Set approved amount based on decision\nif (processVariables.finalApprovalStatus === 'approved') {\n processVariables.approvedAmount = processVariables.totalCost; // Manager can approve full amount\n} else {\n processVariables.approvedAmount = 0;\n}\n\n// Create final claim record for storage\nprocessVariables.finalClaimRecord = {\n employeeName: processVariables.employeeName,\n employeeEmail: processVariables.employeeEmail,\n department: processVariables.department,\n tripPurpose: processVariables.tripPurpose,\n destination: processVariables.destination,\n totalClaimed: processVariables.totalCost,\n approvedAmount: processVariables.approvedAmount,\n status: processVariables.finalApprovalStatus,\n submissionDate: processVariables.submissionTimestamp,\n approvalDate: processVariables.approvalTimestamp,\n approvedBy: processVariables.approvedBy,\n managerComments: processVariables.approvalComments\n};\n\n// Mock API call to store claim\nprocessVariables.claimId = 'CLAIM-' + Date.now();\nprocessVariables.storageResult = 'success';", "scriptCode": "// Process manager's decision\nconst decision = processVariables.managerDecision;\nconst customAmount = parseFloat(processVariables.customApprovedAmount) || 0;\nconst policyLimit = parseFloat(processVariables.totalAllowed) || 0;\nif (decision === 'approve_full' && customAmount > 0) {\n processVariables.approvedAmount = customAmount;\n} else if (decision === 'approve_policy') {\n processVariables.approvedAmount = policyLimit;\n} else if (decision === 'reject') {\n processVariables.approvedAmount = 0;\n}\nprocessVariables.finalApprovalStatus = processVariables.managerDecision === 'approve' ? 'approved' : 'rejected';\nprocessVariables.approvalTimestamp = new Date().toISOString();\nprocessVariables.approvedBy = 'Manager';\nprocessVariables.approvalComments = processVariables.managerComments || '';\n\n// Set approved amount based on decision\nif (processVariables.finalApprovalStatus === 'approved') {\n processVariables.approvedAmount = processVariables.totalCost; // Manager can approve full amount\n} else {\n processVariables.approvedAmount = 0;\n}\n\n// Create final claim record for storage\nprocessVariables.finalClaimRecord = {\n employeeName: processVariables.employeeName,\n employeeEmail: processVariables.employeeEmail,\n department: processVariables.department,\n tripPurpose: processVariables.tripPurpose,\n destination: processVariables.destination,\n totalClaimed: processVariables.totalCost,\n approvedAmount: processVariables.approvedAmount,\n status: processVariables.finalApprovalStatus,\n submissionDate: processVariables.submissionTimestamp,\n approvalDate: processVariables.approvalTimestamp,\n approvedBy: processVariables.approvedBy,\n managerComments: processVariables.approvalComments\n};\n\n// Mock API call to store claim\nprocessVariables.claimId = 'CLAIM-' + Date.now();\nprocessVariables.storageResult = 'success';",
"borderColor": "#6b7280", "borderColor": "#6b7280",
"description": "Process manager decision and store final result", "description": "Process manager decision and store final result",
"inputVariables": ["managerDecision", "managerComments", "totalCost"], "inputVariables": ["managerDecision", "managerComments", "totalCost"],
@ -471,7 +487,7 @@
}, },
"type": "script", "type": "script",
"label": "Process Manager Decision", "label": "Process Manager Decision",
"position": { "x": 2500, "y": 100 } "position": { "x": 2520, "y": 105 }
}, },
{ {
"id": "notification-1753000010", "id": "notification-1753000010",
@ -487,7 +503,7 @@
"recipientRole": "", "recipientRole": "",
"recipientType": "email", "recipientType": "email",
"recipientUser": "", "recipientUser": "",
"recipientEmail": "{employeeEmail}", "recipientEmail": "mdafiqiskandar@gmail.com",
"recipientGroup": "", "recipientGroup": "",
"deliveryOptions": { "sms": false, "email": true, "inApp": true }, "deliveryOptions": { "sms": false, "email": true, "inApp": true },
"richTextMessage": "", "richTextMessage": "",
@ -496,7 +512,7 @@
}, },
"type": "notification", "type": "notification",
"label": "Auto Approval Notification", "label": "Auto Approval Notification",
"position": { "x": 2500, "y": 440 } "position": { "x": 2265, "y": 375 }
}, },
{ {
"id": "notification-1753000011", "id": "notification-1753000011",
@ -507,12 +523,12 @@
"priority": "high", "priority": "high",
"expiration": { "unit": "hours", "value": 48, "enabled": false }, "expiration": { "unit": "hours", "value": 48, "enabled": false },
"description": "Notify employee of manager's decision", "description": "Notify employee of manager's decision",
"htmlMessage": "<div style=\"font-family: Arial, sans-serif; padding: 20px; border-left: 4px solid #3b82f6;\">\n <h2 style=\"color: #1e40af; margin-top: 0;\">📋 Travel Claim Decision</h2>\n <p>Dear {employeeName},</p>\n <p>Your manager has reviewed your travel reimbursement claim.</p>\n \n <div style=\"background: #eff6ff; padding: 15px; border-radius: 8px; margin: 20px 0;\">\n <h3 style=\"margin-top: 0; color: #1e40af;\">Claim Details:</h3>\n <ul style=\"list-style: none; padding: 0;\">\n <li><strong>Claim ID:</strong> {claimId}</li>\n <li><strong>Trip Purpose:</strong> {tripPurpose}</li>\n <li><strong>Destination:</strong> {destination}</li>\n <li><strong>Total Claimed:</strong> RM{totalCost}</li>\n <li><strong>Approved Amount:</strong> RM{approvedAmount}</li>\n <li><strong>Status:</strong> <span style=\"font-weight: bold; color: {finalApprovalStatus === 'approved' ? '#059669' : '#dc2626'};\">{finalApprovalStatus.toUpperCase()}</span></li>\n </ul>\n </div>\n \n <div style=\"background: #f8fafc; padding: 15px; border-radius: 8px; margin: 20px 0;\">\n <h4 style=\"margin-top: 0;\">Manager Comments:</h4>\n <p style=\"font-style: italic;\">{approvalComments}</p>\n </div>\n \n <p>If approved, your reimbursement will be processed within 3-5 business days.</p>\n \n <p style=\"margin-bottom: 0;\">Best regards,<br>\n <strong>Travel Management System</strong></p>\n</div>", "htmlMessage": "<div style=\"font-family: Arial, sans-serif; padding: 20px; border-left: 4px solid #3b82f6;\">\n <h2 style=\"color: #1e40af; margin-top: 0;\">📋 Travel Claim Decision</h2>\n <p>Dear {employeeName},</p>\n <p>Your manager has reviewed your travel reimbursement claim.</p>\n \n <div style=\"background: #eff6ff; padding: 15px; border-radius: 8px; margin: 20px 0;\">\n <h3 style=\"margin-top: 0; color: #1e40af;\">Claim Details:</h3>\n <ul style=\"list-style: none; padding: 0;\">\n <li><strong>Claim ID:</strong> {claimId}</li>\n <li><strong>Trip Purpose:</strong> {tripPurpose}</li>\n <li><strong>Destination:</strong> {destination}</li>\n <li><strong>Total Claimed:</strong> RM{totalCost}</li>\n <li><strong>Approved Amount:</strong> RM{approvedAmount}</li>\n <li><strong>Status:</strong> <span style=\"font-weight: bold;\">{finalApprovalStatus}</span></li>\n </ul>\n </div>\n \n <div style=\"background: #f8fafc; padding: 15px; border-radius: 8px; margin: 20px 0;\">\n <h4 style=\"margin-top: 0;\">Manager Comments:</h4>\n <p style=\"font-style: italic;\">{approvalComments}</p>\n </div>\n \n <p>If approved, your reimbursement will be processed within 3-5 business days.</p>\n \n <p style=\"margin-bottom: 0;\">Best regards,<br>\n <strong>Travel Management System</strong></p>\n</div>",
"messageFormat": "html", "messageFormat": "html",
"recipientRole": "", "recipientRole": "",
"recipientType": "email", "recipientType": "email",
"recipientUser": "", "recipientUser": "",
"recipientEmail": "{employeeEmail}", "recipientEmail": "mdafiqiskandar@gmail.com",
"recipientGroup": "", "recipientGroup": "",
"deliveryOptions": { "sms": false, "email": true, "inApp": true }, "deliveryOptions": { "sms": false, "email": true, "inApp": true },
"richTextMessage": "", "richTextMessage": "",
@ -521,25 +537,25 @@
}, },
"type": "notification", "type": "notification",
"label": "Manager Decision Notification", "label": "Manager Decision Notification",
"position": { "x": 2850, "y": 100 } "position": { "x": 2850, "y": 105 }
}, },
{ {
"id": "end-1753000012", "id": "end-1753000012",
"data": { "data": {
"label": "End", "label": "End",
"description": "Travel claim process completed",
"shape": "circle", "shape": "circle",
"backgroundColor": "#fef3c7", "borderColor": "#f59e0b",
"borderColor": "#f59e0b" "description": "Travel claim process completed",
"backgroundColor": "#fef3c7"
}, },
"type": "end", "type": "end",
"label": "End", "label": "End",
"position": { "x": 3200, "y": 300 } "position": { "x": 3195, "y": 300 }
} }
], ],
"viewport": { "viewport": {
"x": -50, "x": -1535.387843430428,
"y": 50, "y": 250.2076943977823,
"zoom": 0.6 "zoom": 0.8368461998102432
} }
} }

View File

@ -1,73 +1,10 @@
{ {
"employeeName": { "claimId": {
"name": "employeeName", "name": "claimId",
"type": "string", "type": "string",
"scope": "global", "scope": "global",
"value": "", "value": "",
"description": "Full name of the employee submitting the claim" "description": "Unique identifier generated for the claim"
},
"employeeEmail": {
"name": "employeeEmail",
"type": "string",
"scope": "global",
"value": "",
"description": "Email address of the employee for notifications"
},
"department": {
"name": "department",
"type": "string",
"scope": "global",
"value": "",
"description": "Employee's department name"
},
"tripPurpose": {
"name": "tripPurpose",
"type": "string",
"scope": "global",
"value": "",
"description": "Purpose or reason for the business trip"
},
"destination": {
"name": "destination",
"type": "string",
"scope": "global",
"value": "",
"description": "Travel destination city and country"
},
"departureDate": {
"name": "departureDate",
"type": "string",
"scope": "global",
"value": "",
"description": "Trip departure date in ISO format"
},
"returnDate": {
"name": "returnDate",
"type": "string",
"scope": "global",
"value": "",
"description": "Trip return date in ISO format"
},
"travelType": {
"name": "travelType",
"type": "string",
"scope": "global",
"value": "",
"description": "Primary mode of transportation (flight, train, car, bus)"
},
"transportCost": {
"name": "transportCost",
"type": "number",
"scope": "global",
"value": 0,
"description": "Transportation expenses claimed"
},
"accommodationCost": {
"name": "accommodationCost",
"type": "number",
"scope": "global",
"value": 0,
"description": "Accommodation expenses claimed"
}, },
"mealsCost": { "mealsCost": {
"name": "mealsCost", "name": "mealsCost",
@ -83,13 +20,6 @@
"value": 0, "value": 0,
"description": "Other miscellaneous expenses claimed" "description": "Other miscellaneous expenses claimed"
}, },
"hasReceipts": {
"name": "hasReceipts",
"type": "boolean",
"scope": "global",
"value": false,
"description": "Whether employee has all supporting receipts"
},
"totalCost": { "totalCost": {
"name": "totalCost", "name": "totalCost",
"type": "number", "type": "number",
@ -97,61 +27,54 @@
"value": 0, "value": 0,
"description": "Total amount claimed (calculated)" "description": "Total amount claimed (calculated)"
}, },
"isValidSubmission": { "approvedBy": {
"name": "isValidSubmission", "name": "approvedBy",
"type": "string",
"scope": "global",
"value": "",
"description": "Who approved the claim (System/Manager name)"
},
"department": {
"name": "department",
"type": "string",
"scope": "global",
"value": "",
"description": "Employee's department name"
},
"returnDate": {
"name": "returnDate",
"type": "string",
"scope": "global",
"value": "",
"description": "Trip return date in ISO format"
},
"travelType": {
"name": "travelType",
"type": "string",
"scope": "global",
"value": "",
"description": "Primary mode of transportation (flight, train, car, bus)"
},
"destination": {
"name": "destination",
"type": "string",
"scope": "global",
"value": "",
"description": "Travel destination city and country"
},
"hasReceipts": {
"name": "hasReceipts",
"type": "boolean", "type": "boolean",
"scope": "global", "scope": "global",
"value": false, "value": false,
"description": "Whether the form submission passed validation" "description": "Whether employee has all supporting receipts"
}, },
"validationError": { "tripPurpose": {
"name": "validationError", "name": "tripPurpose",
"type": "string", "type": "string",
"scope": "global", "scope": "global",
"value": "", "value": "",
"description": "Validation error message if submission is invalid" "description": "Purpose or reason for the business trip"
},
"tripDuration": {
"name": "tripDuration",
"type": "number",
"scope": "global",
"value": 0,
"description": "Trip duration in days (calculated)"
},
"submissionTimestamp": {
"name": "submissionTimestamp",
"type": "string",
"scope": "global",
"value": "",
"description": "ISO timestamp when claim was submitted"
},
"policyApiResponse": {
"name": "policyApiResponse",
"type": "object",
"scope": "global",
"value": null,
"description": "Response from company policy rates API"
},
"policyApiError": {
"name": "policyApiError",
"type": "object",
"scope": "global",
"value": null,
"description": "Error from policy API if request fails"
},
"allowedTransport": {
"name": "allowedTransport",
"type": "number",
"scope": "global",
"value": 0,
"description": "Maximum allowed transportation reimbursement per policy"
},
"allowedAccommodation": {
"name": "allowedAccommodation",
"type": "number",
"scope": "global",
"value": 0,
"description": "Maximum allowed accommodation reimbursement per policy"
}, },
"allowedMeals": { "allowedMeals": {
"name": "allowedMeals", "name": "allowedMeals",
@ -167,19 +90,19 @@
"value": 0, "value": 0,
"description": "Maximum allowed other expenses reimbursement per policy" "description": "Maximum allowed other expenses reimbursement per policy"
}, },
"totalAllowed": { "claimSummary": {
"name": "totalAllowed", "name": "claimSummary",
"type": "number", "type": "string",
"scope": "global", "scope": "global",
"value": 0, "value": "",
"description": "Total allowed reimbursement according to company policy" "description": "Formatted summary string of the travel claim"
}, },
"reimbursementAmount": { "employeeName": {
"name": "reimbursementAmount", "name": "employeeName",
"type": "number", "type": "string",
"scope": "global", "scope": "global",
"value": 0, "value": "",
"description": "Final calculated reimbursement amount" "description": "Full name of the employee submitting the claim"
}, },
"isOverBudget": { "isOverBudget": {
"name": "isOverBudget", "name": "isOverBudget",
@ -188,19 +111,47 @@
"value": false, "value": false,
"description": "Whether claimed amount exceeds policy limits" "description": "Whether claimed amount exceeds policy limits"
}, },
"overBudgetAmount": { "totalAllowed": {
"name": "overBudgetAmount", "name": "totalAllowed",
"type": "number", "type": "number",
"scope": "global", "scope": "global",
"value": 0, "value": 0,
"description": "Amount by which claim exceeds policy limits" "description": "Total allowed reimbursement according to company policy"
}, },
"claimSummary": { "tripDuration": {
"name": "claimSummary", "name": "tripDuration",
"type": "number",
"scope": "global",
"value": 0,
"description": "Trip duration in days (calculated)"
},
"departureDate": {
"name": "departureDate",
"type": "string", "type": "string",
"scope": "global", "scope": "global",
"value": "", "value": "",
"description": "Formatted summary string of the travel claim" "description": "Trip departure date in ISO format"
},
"employeeEmail": {
"name": "employeeEmail",
"type": "string",
"scope": "global",
"value": "",
"description": "Email address of the employee for notifications"
},
"storageResult": {
"name": "storageResult",
"type": "string",
"scope": "global",
"value": "",
"description": "Result of storing claim in database (success/error)"
},
"transportCost": {
"name": "transportCost",
"type": "number",
"scope": "global",
"value": 0,
"description": "Transportation expenses claimed"
}, },
"approvalStatus": { "approvalStatus": {
"name": "approvalStatus", "name": "approvalStatus",
@ -209,12 +160,19 @@
"value": "pending", "value": "pending",
"description": "Current approval status (pending_approval, auto_approved, etc.)" "description": "Current approval status (pending_approval, auto_approved, etc.)"
}, },
"managerDecision": { "approvedAmount": {
"name": "managerDecision", "name": "approvedAmount",
"type": "string", "type": "number",
"scope": "global", "scope": "global",
"value": "", "value": 0,
"description": "Manager's approval decision (approve/reject)" "description": "Final approved reimbursement amount"
},
"policyApiError": {
"name": "policyApiError",
"type": "object",
"scope": "global",
"value": null,
"description": "Error from policy API if request fails"
}, },
"managerComments": { "managerComments": {
"name": "managerComments", "name": "managerComments",
@ -223,33 +181,26 @@
"value": "", "value": "",
"description": "Manager's comments on the approval decision" "description": "Manager's comments on the approval decision"
}, },
"finalApprovalStatus": { "managerDecision": {
"name": "finalApprovalStatus", "name": "managerDecision",
"type": "string", "type": "string",
"scope": "global", "scope": "global",
"value": "", "value": "",
"description": "Final approval status after all reviews (approved/rejected)" "description": "Manager's approval decision (approve/reject)"
}, },
"approvedAmount": { "validationError": {
"name": "approvedAmount", "name": "validationError",
"type": "string",
"scope": "global",
"value": "",
"description": "Validation error message if submission is invalid"
},
"allowedTransport": {
"name": "allowedTransport",
"type": "number", "type": "number",
"scope": "global", "scope": "global",
"value": 0, "value": 0,
"description": "Final approved reimbursement amount" "description": "Maximum allowed transportation reimbursement per policy"
},
"approvalTimestamp": {
"name": "approvalTimestamp",
"type": "string",
"scope": "global",
"value": "",
"description": "ISO timestamp when claim was approved/rejected"
},
"approvedBy": {
"name": "approvedBy",
"type": "string",
"scope": "global",
"value": "",
"description": "Who approved the claim (System/Manager name)"
}, },
"approvalComments": { "approvalComments": {
"name": "approvalComments", "name": "approvalComments",
@ -265,18 +216,67 @@
"value": null, "value": null,
"description": "Complete claim record object for storage/archival" "description": "Complete claim record object for storage/archival"
}, },
"claimId": { "overBudgetAmount": {
"name": "claimId", "name": "overBudgetAmount",
"type": "string", "type": "number",
"scope": "global", "scope": "global",
"value": "", "value": 0,
"description": "Unique identifier generated for the claim" "description": "Amount by which claim exceeds policy limits"
}, },
"storageResult": { "accommodationCost": {
"name": "storageResult", "name": "accommodationCost",
"type": "number",
"scope": "global",
"value": 0,
"description": "Accommodation expenses claimed"
},
"approvalTimestamp": {
"name": "approvalTimestamp",
"type": "string", "type": "string",
"scope": "global", "scope": "global",
"value": "", "value": "",
"description": "Result of storing claim in database (success/error)" "description": "ISO timestamp when claim was approved/rejected"
},
"isValidSubmission": {
"name": "isValidSubmission",
"type": "boolean",
"scope": "global",
"value": false,
"description": "Whether the form submission passed validation"
},
"policyApiResponse": {
"name": "policyApiResponse",
"type": "object",
"scope": "global",
"value": null,
"description": "Response from company policy rates API"
},
"finalApprovalStatus": {
"name": "finalApprovalStatus",
"type": "string",
"scope": "global",
"value": "",
"description": "Final approval status after all reviews (approved/rejected)"
},
"reimbursementAmount": {
"name": "reimbursementAmount",
"type": "number",
"scope": "global",
"value": 0,
"description": "Final calculated reimbursement amount"
},
"submissionTimestamp": {
"name": "submissionTimestamp",
"type": "string",
"scope": "global",
"value": "",
"description": "ISO timestamp when claim was submitted"
},
"allowedAccommodation": {
"name": "allowedAccommodation",
"type": "number",
"scope": "global",
"value": 0,
"description": "Maximum allowed accommodation reimbursement per policy"
} }
} }

View File

@ -901,45 +901,48 @@ onMounted(() => {
// Watch for step changes to auto-execute non-form steps or load form data // Watch for step changes to auto-execute non-form steps or load form data
watch(currentStep, async (newStep) => { watch(currentStep, async (newStep) => {
if (currentNode.value) { if (currentNode.value) {
if (['api', 'script', 'notification'].includes(currentNode.value.type)) { stepLoading.value = true; // Start loading for any node
await executeCurrentStep(); try {
} else if (currentNode.value.type === 'form') { if (["api", "script", "notification"].includes(currentNode.value.type)) {
// Load form data for form nodes await executeCurrentStep();
const formId = currentNode.value.data?.formId; } else if (currentNode.value.type === "form") {
if (formId) { // Reset all form-related state to prevent flush errors
currentForm.value = await loadFormData(formId); formData.value = {};
fieldStates.value = {};
// Apply input mappings to pre-fill form
if (currentNode.value.data?.inputMappings) {
applyInputMappings(
currentNode.value.data,
processVariables.value,
formData.value
);
}
// Apply field conditions
if (currentNode.value.data?.fieldConditions) {
fieldStates.value = applyFieldConditions(
currentNode.value.data,
processVariables.value
);
}
// Update form store with form components and data for ComponentPreview
// Clear previous form state to prevent stale/leaked fields
formStore.formComponents = [];
formStore.updatePreviewFormData({}); formStore.updatePreviewFormData({});
if (currentForm.value?.formComponents) { formStore.formComponents = [];
formStore.formComponents = currentForm.value.formComponents; // Load form data for form nodes
formStore.updatePreviewFormData(formData.value); const formId = currentNode.value.data?.formId;
if (formId) {
currentForm.value = await loadFormData(formId);
// Apply input mappings to pre-fill form
if (currentNode.value.data?.inputMappings) {
applyInputMappings(
currentNode.value.data,
processVariables.value,
formData.value
);
}
// Apply field conditions
if (currentNode.value.data?.fieldConditions) {
fieldStates.value = applyFieldConditions(
currentNode.value.data,
processVariables.value
);
}
// Update form store with form components and data for ComponentPreview
if (currentForm.value?.formComponents) {
formStore.formComponents = currentForm.value.formComponents;
formStore.updatePreviewFormData(formData.value);
}
} }
} else if (["decision", "gateway"].includes(currentNode.value.type)) {
await executeDecisionNode();
} }
} else if (['decision', 'gateway'].includes(currentNode.value.type)) { // html nodes are handled in template - no auto-execution needed
// Handle decision nodes } finally {
await executeDecisionNode(); stepLoading.value = false; // End loading after all async work
} }
// html nodes are handled in template - no auto-execution needed
} }
}); });
@ -1369,10 +1372,10 @@ function getConditionGroupResult(conditionGroup, variables) {
:actions="false" :actions="false"
:incomplete-message="false" :incomplete-message="false"
validation-visibility="submit" validation-visibility="submit"
:key="currentNode.value?.id" :key="currentForm?.formId || currentNode.value?.id"
> >
<div class="grid grid-cols-12 gap-2"> <div class="grid grid-cols-12 gap-2">
<template v-for="(component, index) in currentForm.formComponents" :key="index"> <template v-for="component in currentForm.formComponents" :key="component.id || component.props.name">
<ComponentPreview <ComponentPreview
:component="component" :component="component"
:is-preview="false" :is-preview="false"