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
const variableTypes = [
{ label: 'String - Text values', value: 'string' },
{ label: 'Int - Whole numbers', value: 'int' },
{ label: 'Decimal - Decimal numbers', value: 'decimal' },
{ label: 'Number - Decimal numbers', value: 'number' },
{ label: 'Object - Complex data structure', value: 'object' },
{ label: 'DateTime - Date and time values', value: 'datetime' },
{ label: 'Date - Date values only', value: 'date' },

View File

@ -14,7 +14,7 @@ const setApprovalDate = () => {
const calculateRecommendedAmounts = () => {
const totalClaimed = parseFloat(getField("total_cost_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) {
const percentageOver = ((overBudget / policyLimit) * 100).toFixed(1);
@ -222,7 +222,7 @@ onFieldChange("manager_name", (newValue) => {
function waitForFields(fields, callback, maxAttempts = 20) {
let attempts = 0;
function check() {
const allPresent = fields.every(f => getField(f) !== undefined);
const allPresent = fields.every((f) => getField(f) !== undefined);
if (allPresent) {
callback();
} else if (attempts < maxAttempts) {
@ -239,13 +239,15 @@ waitForFields(
[
"total_cost_display",
"policy_limit_display",
"over_budget_amount_display",
"over_budget_amount",
// add any other required fields here
],
() => {
setApprovalDate();
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");
}
})();
// 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",
"label": "Claim Summary",
"width": "100%",
"readonly": true,
"gridColumn": "span 12",
"validation": "",
"placeholder": "Claim summary will be populated from process",
@ -73,6 +74,7 @@
"type": "text",
"label": "Employee Name",
"width": "50%",
"readonly": true,
"gridColumn": "span 6",
"validation": "",
"placeholder": "Employee name",
@ -92,6 +94,7 @@
"type": "text",
"label": "Department",
"width": "50%",
"readonly": true,
"gridColumn": "span 6",
"validation": "",
"placeholder": "Department",
@ -111,6 +114,7 @@
"type": "text",
"label": "Trip Purpose",
"width": "50%",
"readonly": true,
"gridColumn": "span 6",
"validation": "",
"placeholder": "Trip purpose",
@ -130,6 +134,7 @@
"type": "text",
"label": "Destination",
"width": "50%",
"readonly": true,
"gridColumn": "span 6",
"validation": "",
"placeholder": "Destination",
@ -165,9 +170,16 @@
"type": "number",
"label": "Total Claimed Amount (RM)",
"width": "33.33%",
"readonly": true,
"gridColumn": "span 4",
"validation": "",
"placeholder": "0.00"
"placeholder": "0.00",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
@ -178,22 +190,36 @@
"type": "number",
"label": "Policy Limit (RM)",
"width": "33.33%",
"readonly": true,
"gridColumn": "span 4",
"validation": "",
"placeholder": "0.00"
"placeholder": "0.00",
"conditionalLogic": {
"action": "show",
"enabled": false,
"operator": "and",
"conditions": []
}
}
},
{
"type": "number",
"props": {
"help": "Amount exceeding policy limits",
"name": "over_budget_amount_display",
"name": "over_budget_amount",
"type": "number",
"label": "Over Budget Amount (RM)",
"width": "33.33%",
"readonly": true,
"gridColumn": "span 4",
"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",
"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",
"data": {},
@ -131,21 +87,65 @@
"animated": true,
"sourceHandle": "notification-1753000011-bottom",
"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": [
{
"id": "start-1753000001",
"data": {
"label": "Start Travel Claim",
"description": "Employee initiates travel reimbursement claim",
"data": {
"label": "Start Travel Claim",
"shape": "circle",
"backgroundColor": "#dcfce7",
"borderColor": "#10b981"
"borderColor": "#10b981",
"description": "Employee initiates travel reimbursement claim",
"backgroundColor": "#dcfce7"
},
"type": "start",
"label": "Start Travel Claim",
"position": { "x": 100, "y": 300 }
"position": { "x": 105, "y": 300 }
},
{
"id": "form-1753000002",
@ -159,7 +159,11 @@
"borderColor": "#9333ea",
"description": "Employee fills travel reimbursement claim form",
"assignedRoles": [
{ "label": "Employee", "value": "3", "description": "Regular employee role" }
{
"label": "Employee",
"value": "3",
"description": "Regular employee role"
}
],
"assignedUsers": [],
"inputMappings": [],
@ -174,7 +178,10 @@
{ "formField": "return_date", "processVariable": "returnDate" },
{ "formField": "travel_type", "processVariable": "travelType" },
{ "formField": "transport_cost", "processVariable": "transportCost" },
{ "formField": "accommodation_cost", "processVariable": "accommodationCost" },
{
"formField": "accommodation_cost",
"processVariable": "accommodationCost"
},
{ "formField": "meals_cost", "processVariable": "mealsCost" },
{ "formField": "other_cost", "processVariable": "otherCost" },
{ "formField": "has_receipts", "processVariable": "hasReceipts" }
@ -186,7 +193,7 @@
},
"type": "form",
"label": "Travel Claim Form",
"position": { "x": 400, "y": 270 }
"position": { "x": 405, "y": 270 }
},
{
"id": "script-1753000003",
@ -198,8 +205,15 @@
"borderColor": "#6b7280",
"description": "Validate form data and calculate totals",
"inputVariables": [
"employeeName", "tripPurpose", "destination", "transportCost",
"accommodationCost", "mealsCost", "otherCost", "departureDate", "returnDate"
"employeeName",
"tripPurpose",
"destination",
"transportCost",
"accommodationCost",
"mealsCost",
"otherCost",
"departureDate",
"returnDate"
],
"scriptLanguage": "javascript",
"backgroundColor": "#f9fafb",
@ -260,7 +274,7 @@
},
"type": "api",
"label": "Get Policy Rates",
"position": { "x": 1100, "y": 270 }
"position": { "x": 1095, "y": 270 }
},
{
"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';",
"borderColor": "#6b7280",
"description": "Calculate reimbursement based on policy rates",
"inputVariables": ["policyApiResponse", "totalCost", "travelType", "tripDuration"],
"inputVariables": [
"policyApiResponse",
"totalCost",
"travelType",
"tripDuration"
],
"scriptLanguage": "javascript",
"backgroundColor": "#f9fafb",
"outputVariables": [
@ -309,7 +328,7 @@
},
"type": "script",
"label": "Calculate Reimbursement",
"position": { "x": 1450, "y": 270 }
"position": { "x": 1455, "y": 270 }
},
{
"id": "gateway-1753000006",
@ -358,7 +377,7 @@
},
"type": "gateway",
"label": "Budget Check",
"position": { "x": 1800, "y": 270 }
"position": { "x": 1755, "y": 330 }
},
{
"id": "form-1753000007",
@ -368,71 +387,68 @@
"formId": 3,
"formName": "Travel Claim Approval Form",
"formUuid": "travel-approval-form-uuid",
"textColor": "#dc2626",
"borderColor": "#ef4444",
"textColor": "#6b21a8",
"borderColor": "#9333ea",
"description": "Manager reviews and approves/rejects over-budget travel claim",
"assignedRoles": [
{ "label": "Manager", "value": "4", "description": "Department manager role" }
{
"label": "Manager",
"value": "4",
"description": "Department manager role"
}
],
"assignedUsers": [],
"inputMappings": [
{ "processVariable": "claimSummary", "formField": "claim_summary" },
{ "processVariable": "totalCost", "formField": "total_cost_display" },
{ "processVariable": "overBudgetAmount", "formField": "over_budget_amount_display" },
{ "processVariable": "totalAllowed", "formField": "policy_limit_display" }
{ "formField": "claim_summary", "processVariable": "claimSummary" },
{
"formField": "employee_name_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",
"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": [],
"assignmentVariable": "",
"assignmentVariableType": "user_id"
},
"type": "form",
"label": "Manager Approval",
"position": { "x": 2150, "y": 100 }
},
{
"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 }
"position": { "x": 1980, "y": 105 }
},
{
"id": "script-1753000009",
@ -440,7 +456,7 @@
"label": "Process Manager Decision",
"shape": "rectangle",
"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",
"description": "Process manager decision and store final result",
"inputVariables": ["managerDecision", "managerComments", "totalCost"],
@ -471,7 +487,7 @@
},
"type": "script",
"label": "Process Manager Decision",
"position": { "x": 2500, "y": 100 }
"position": { "x": 2520, "y": 105 }
},
{
"id": "notification-1753000010",
@ -487,7 +503,7 @@
"recipientRole": "",
"recipientType": "email",
"recipientUser": "",
"recipientEmail": "{employeeEmail}",
"recipientEmail": "mdafiqiskandar@gmail.com",
"recipientGroup": "",
"deliveryOptions": { "sms": false, "email": true, "inApp": true },
"richTextMessage": "",
@ -496,7 +512,7 @@
},
"type": "notification",
"label": "Auto Approval Notification",
"position": { "x": 2500, "y": 440 }
"position": { "x": 2265, "y": 375 }
},
{
"id": "notification-1753000011",
@ -507,12 +523,12 @@
"priority": "high",
"expiration": { "unit": "hours", "value": 48, "enabled": false },
"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",
"recipientRole": "",
"recipientType": "email",
"recipientUser": "",
"recipientEmail": "{employeeEmail}",
"recipientEmail": "mdafiqiskandar@gmail.com",
"recipientGroup": "",
"deliveryOptions": { "sms": false, "email": true, "inApp": true },
"richTextMessage": "",
@ -521,25 +537,25 @@
},
"type": "notification",
"label": "Manager Decision Notification",
"position": { "x": 2850, "y": 100 }
"position": { "x": 2850, "y": 105 }
},
{
"id": "end-1753000012",
"data": {
"label": "End",
"description": "Travel claim process completed",
"data": {
"label": "End",
"shape": "circle",
"backgroundColor": "#fef3c7",
"borderColor": "#f59e0b"
"borderColor": "#f59e0b",
"description": "Travel claim process completed",
"backgroundColor": "#fef3c7"
},
"type": "end",
"label": "End",
"position": { "x": 3200, "y": 300 }
"position": { "x": 3195, "y": 300 }
}
],
"viewport": {
"x": -50,
"y": 50,
"zoom": 0.6
"x": -1535.387843430428,
"y": 250.2076943977823,
"zoom": 0.8368461998102432
}
}
}

View File

@ -1,73 +1,10 @@
{
"employeeName": {
"name": "employeeName",
"claimId": {
"name": "claimId",
"type": "string",
"scope": "global",
"value": "",
"description": "Full name of the employee submitting 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"
"description": "Unique identifier generated for the claim"
},
"mealsCost": {
"name": "mealsCost",
@ -83,13 +20,6 @@
"value": 0,
"description": "Other miscellaneous expenses claimed"
},
"hasReceipts": {
"name": "hasReceipts",
"type": "boolean",
"scope": "global",
"value": false,
"description": "Whether employee has all supporting receipts"
},
"totalCost": {
"name": "totalCost",
"type": "number",
@ -97,61 +27,54 @@
"value": 0,
"description": "Total amount claimed (calculated)"
},
"isValidSubmission": {
"name": "isValidSubmission",
"approvedBy": {
"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",
"scope": "global",
"value": false,
"description": "Whether the form submission passed validation"
"description": "Whether employee has all supporting receipts"
},
"validationError": {
"name": "validationError",
"tripPurpose": {
"name": "tripPurpose",
"type": "string",
"scope": "global",
"value": "",
"description": "Validation error message if submission is invalid"
},
"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"
"description": "Purpose or reason for the business trip"
},
"allowedMeals": {
"name": "allowedMeals",
@ -167,19 +90,19 @@
"value": 0,
"description": "Maximum allowed other expenses reimbursement per policy"
},
"totalAllowed": {
"name": "totalAllowed",
"type": "number",
"claimSummary": {
"name": "claimSummary",
"type": "string",
"scope": "global",
"value": 0,
"description": "Total allowed reimbursement according to company policy"
"value": "",
"description": "Formatted summary string of the travel claim"
},
"reimbursementAmount": {
"name": "reimbursementAmount",
"type": "number",
"employeeName": {
"name": "employeeName",
"type": "string",
"scope": "global",
"value": 0,
"description": "Final calculated reimbursement amount"
"value": "",
"description": "Full name of the employee submitting the claim"
},
"isOverBudget": {
"name": "isOverBudget",
@ -188,19 +111,47 @@
"value": false,
"description": "Whether claimed amount exceeds policy limits"
},
"overBudgetAmount": {
"name": "overBudgetAmount",
"totalAllowed": {
"name": "totalAllowed",
"type": "number",
"scope": "global",
"value": 0,
"description": "Amount by which claim exceeds policy limits"
"description": "Total allowed reimbursement according to company policy"
},
"claimSummary": {
"name": "claimSummary",
"tripDuration": {
"name": "tripDuration",
"type": "number",
"scope": "global",
"value": 0,
"description": "Trip duration in days (calculated)"
},
"departureDate": {
"name": "departureDate",
"type": "string",
"scope": "global",
"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": {
"name": "approvalStatus",
@ -209,12 +160,19 @@
"value": "pending",
"description": "Current approval status (pending_approval, auto_approved, etc.)"
},
"managerDecision": {
"name": "managerDecision",
"type": "string",
"approvedAmount": {
"name": "approvedAmount",
"type": "number",
"scope": "global",
"value": "",
"description": "Manager's approval decision (approve/reject)"
"value": 0,
"description": "Final approved reimbursement amount"
},
"policyApiError": {
"name": "policyApiError",
"type": "object",
"scope": "global",
"value": null,
"description": "Error from policy API if request fails"
},
"managerComments": {
"name": "managerComments",
@ -223,33 +181,26 @@
"value": "",
"description": "Manager's comments on the approval decision"
},
"finalApprovalStatus": {
"name": "finalApprovalStatus",
"managerDecision": {
"name": "managerDecision",
"type": "string",
"scope": "global",
"value": "",
"description": "Final approval status after all reviews (approved/rejected)"
"description": "Manager's approval decision (approve/reject)"
},
"approvedAmount": {
"name": "approvedAmount",
"validationError": {
"name": "validationError",
"type": "string",
"scope": "global",
"value": "",
"description": "Validation error message if submission is invalid"
},
"allowedTransport": {
"name": "allowedTransport",
"type": "number",
"scope": "global",
"value": 0,
"description": "Final approved reimbursement amount"
},
"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)"
"description": "Maximum allowed transportation reimbursement per policy"
},
"approvalComments": {
"name": "approvalComments",
@ -265,18 +216,67 @@
"value": null,
"description": "Complete claim record object for storage/archival"
},
"claimId": {
"name": "claimId",
"type": "string",
"overBudgetAmount": {
"name": "overBudgetAmount",
"type": "number",
"scope": "global",
"value": "",
"description": "Unique identifier generated for the claim"
"value": 0,
"description": "Amount by which claim exceeds policy limits"
},
"storageResult": {
"name": "storageResult",
"accommodationCost": {
"name": "accommodationCost",
"type": "number",
"scope": "global",
"value": 0,
"description": "Accommodation expenses claimed"
},
"approvalTimestamp": {
"name": "approvalTimestamp",
"type": "string",
"scope": "global",
"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(currentStep, async (newStep) => {
if (currentNode.value) {
if (['api', 'script', 'notification'].includes(currentNode.value.type)) {
await executeCurrentStep();
} else if (currentNode.value.type === 'form') {
// Load form data for form nodes
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
// Clear previous form state to prevent stale/leaked fields
formStore.formComponents = [];
stepLoading.value = true; // Start loading for any node
try {
if (["api", "script", "notification"].includes(currentNode.value.type)) {
await executeCurrentStep();
} else if (currentNode.value.type === "form") {
// Reset all form-related state to prevent flush errors
formData.value = {};
fieldStates.value = {};
formStore.updatePreviewFormData({});
if (currentForm.value?.formComponents) {
formStore.formComponents = currentForm.value.formComponents;
formStore.updatePreviewFormData(formData.value);
formStore.formComponents = [];
// Load form data for form nodes
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)) {
// Handle decision nodes
await executeDecisionNode();
// html nodes are handled in template - no auto-execution needed
} finally {
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"
:incomplete-message="false"
validation-visibility="submit"
:key="currentNode.value?.id"
:key="currentForm?.formId || currentNode.value?.id"
>
<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
:component="component"
:is-preview="false"