- Introduced comprehensive documentation for the Travel Reimbursement Workflow, detailing form components, process definitions, and business rules. - Added new custom scripts for the Manager Approval Form and Travel Reimbursement Form to enhance dynamic behavior and validation. - Updated the ComponentPreview component to include a new prop for field states, improving state management during previews. - Created JSON files for the Manager Approval Form and Travel Reimbursement Form, defining their structure and validation rules. - Implemented a new process definition for the travel workflow, outlining the steps and decision points for claim processing. - Established global variables for managing workflow data, ensuring consistency and accessibility across the process.
232 lines
7.2 KiB
JavaScript
232 lines
7.2 KiB
JavaScript
// Manager Approval Form Custom Script Engine
|
|
// This script provides dynamic behavior for the manager approval form
|
|
|
|
console.log('Manager Approval Form Script Loaded');
|
|
|
|
// Auto-set approval date to today
|
|
const setApprovalDate = () => {
|
|
const today = new Date();
|
|
const formattedDate = today.toISOString().split('T')[0]; // YYYY-MM-DD format
|
|
setField('approval_date', formattedDate);
|
|
};
|
|
|
|
// Calculate and display recommended approval amounts
|
|
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;
|
|
|
|
if (totalClaimed > 0 && policyLimit > 0) {
|
|
const percentageOver = ((overBudget / policyLimit) * 100).toFixed(1);
|
|
showInfo(`This claim is ${percentageOver}% over the policy limit. Policy limit: RM${policyLimit.toFixed(2)}, Claimed: RM${totalClaimed.toFixed(2)}`);
|
|
}
|
|
};
|
|
|
|
// Validate manager decision and provide guidance
|
|
const handleDecisionChange = (decision) => {
|
|
const totalClaimed = parseFloat(getField('total_cost_display')) || 0;
|
|
const policyLimit = parseFloat(getField('policy_limit_display')) || 0;
|
|
|
|
console.log('Manager decision changed to:', decision);
|
|
|
|
switch(decision) {
|
|
case 'approve_full':
|
|
showField('custom_approved_amount');
|
|
showInfo(`💰 Approving full amount: RM${totalClaimed.toFixed(2)}. You can enter a custom amount if needed.`);
|
|
setField('custom_approved_amount', totalClaimed.toString());
|
|
break;
|
|
|
|
case 'approve_policy':
|
|
hideField('custom_approved_amount');
|
|
showInfo(`⚖️ Approving policy limit only: RM${policyLimit.toFixed(2)}. Employee will be notified of the reduced amount.`);
|
|
break;
|
|
|
|
case 'reject':
|
|
hideField('custom_approved_amount');
|
|
showError('❌ Claim will be rejected. Please provide detailed comments explaining the rejection reason.');
|
|
break;
|
|
|
|
default:
|
|
hideField('custom_approved_amount');
|
|
}
|
|
};
|
|
|
|
// Validate custom approved amount
|
|
const validateCustomAmount = (amount) => {
|
|
const numAmount = parseFloat(amount) || 0;
|
|
const totalClaimed = parseFloat(getField('total_cost_display')) || 0;
|
|
const policyLimit = parseFloat(getField('policy_limit_display')) || 0;
|
|
|
|
if (numAmount < 0) {
|
|
showError('Approved amount cannot be negative');
|
|
return false;
|
|
}
|
|
|
|
if (numAmount > totalClaimed) {
|
|
showError(`Approved amount (RM${numAmount}) cannot exceed claimed amount (RM${totalClaimed})`);
|
|
return false;
|
|
}
|
|
|
|
if (numAmount > 0 && numAmount < policyLimit) {
|
|
showInfo(`Custom amount (RM${numAmount}) is less than policy limit (RM${policyLimit}). Consider approving policy limit instead.`);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// Validate manager comments based on decision
|
|
const validateComments = (comments, decision) => {
|
|
if (!comments || comments.trim().length < 10) {
|
|
showError('Please provide detailed comments (minimum 10 characters)');
|
|
return false;
|
|
}
|
|
|
|
if (decision === 'reject' && comments.length < 50) {
|
|
showError('Rejection requires detailed explanation (minimum 50 characters)');
|
|
return false;
|
|
}
|
|
|
|
if (decision === 'approve_full') {
|
|
const totalClaimed = parseFloat(getField('total_cost_display')) || 0;
|
|
const policyLimit = parseFloat(getField('policy_limit_display')) || 0;
|
|
const overBudget = totalClaimed - policyLimit;
|
|
|
|
if (overBudget > 500 && !comments.toLowerCase().includes('business')) {
|
|
showInfo('Consider mentioning business justification for approving over-budget amounts > RM500');
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// Format manager name (title case)
|
|
const formatManagerName = (name) => {
|
|
if (!name || typeof name !== 'string') return name;
|
|
|
|
return name
|
|
.toLowerCase()
|
|
.split(' ')
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
.join(' ');
|
|
};
|
|
|
|
// Form validation before submission
|
|
const validateApprovalForm = () => {
|
|
let isValid = true;
|
|
const errors = [];
|
|
|
|
// Check required fields
|
|
const requiredFields = {
|
|
'manager_decision': 'Approval Decision',
|
|
'manager_comments': 'Manager Comments',
|
|
'manager_name': 'Manager Name',
|
|
'approval_date': 'Approval Date'
|
|
};
|
|
|
|
Object.entries(requiredFields).forEach(([fieldName, displayName]) => {
|
|
const value = getField(fieldName);
|
|
if (!value || value.toString().trim() === '') {
|
|
errors.push(`${displayName} is required`);
|
|
isValid = false;
|
|
}
|
|
});
|
|
|
|
// Validate decision-specific requirements
|
|
const decision = getField('manager_decision');
|
|
const comments = getField('manager_comments');
|
|
const customAmount = getField('custom_approved_amount');
|
|
|
|
if (decision && comments) {
|
|
if (!validateComments(comments, decision)) {
|
|
isValid = false;
|
|
}
|
|
}
|
|
|
|
if (decision === 'approve_full' && customAmount) {
|
|
if (!validateCustomAmount(customAmount)) {
|
|
isValid = false;
|
|
}
|
|
}
|
|
|
|
// Validate approval date is not in the future
|
|
const approvalDate = getField('approval_date');
|
|
if (approvalDate) {
|
|
const selectedDate = new Date(approvalDate);
|
|
const today = new Date();
|
|
today.setHours(23, 59, 59, 999); // End of today
|
|
|
|
if (selectedDate > today) {
|
|
errors.push('Approval date cannot be in the future');
|
|
isValid = false;
|
|
}
|
|
}
|
|
|
|
// Show validation results
|
|
if (errors.length > 0) {
|
|
showError(`Please fix the following errors:\n• ${errors.join('\n• ')}`);
|
|
} else {
|
|
showSuccess('Form validation passed! Ready to submit approval decision.');
|
|
}
|
|
|
|
return isValid;
|
|
};
|
|
|
|
// Set up field change handlers
|
|
onFieldChange('manager_decision', (newValue) => {
|
|
if (newValue) {
|
|
handleDecisionChange(newValue);
|
|
}
|
|
});
|
|
|
|
onFieldChange('custom_approved_amount', (newValue) => {
|
|
if (newValue) {
|
|
validateCustomAmount(newValue);
|
|
}
|
|
});
|
|
|
|
onFieldChange('manager_comments', (newValue) => {
|
|
const decision = getField('manager_decision');
|
|
if (newValue && decision) {
|
|
validateComments(newValue, decision);
|
|
}
|
|
});
|
|
|
|
onFieldChange('manager_name', (newValue) => {
|
|
if (newValue && typeof newValue === 'string') {
|
|
const formatted = formatManagerName(newValue);
|
|
if (formatted !== newValue) {
|
|
setTimeout(() => {
|
|
setField('manager_name', formatted);
|
|
}, 100);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initialize form on load
|
|
setTimeout(() => {
|
|
setApprovalDate();
|
|
calculateRecommendedAmounts();
|
|
|
|
// Make display fields read-only by adding visual styling
|
|
const displayFields = [
|
|
'claim_summary', 'employee_name_display', 'department_display',
|
|
'trip_purpose_display', 'destination_display', 'total_cost_display',
|
|
'policy_limit_display', 'over_budget_amount_display'
|
|
];
|
|
|
|
displayFields.forEach(fieldName => {
|
|
const fieldElement = document.querySelector(`[data-name="${fieldName}"] input`);
|
|
if (fieldElement) {
|
|
fieldElement.style.backgroundColor = '#f3f4f6';
|
|
fieldElement.style.cursor = 'not-allowed';
|
|
fieldElement.readOnly = true;
|
|
}
|
|
});
|
|
|
|
showInfo('Review the claim details above and make your approval decision below.');
|
|
}, 1000);
|
|
|
|
// Expose validation function for form submission
|
|
window.validateManagerApprovalForm = validateApprovalForm;
|
|
|
|
console.log('Manager Approval Form Script initialized successfully'); |