Closing Work Orders Tasks in the Past
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday
During a recent Field Service Management (FSM) implementation, there was a requirement for the ability to close Work Order Tasks with back-dated Actual Start and End times. By default, FSM tracks work order task closing time as the current time the agent clicked on "Close Complete" on Work Order Task. But in some industries (e.g., telecom, utilities, or onsite repair), technicians may complete jobs offline and then later want to log them “in the past” when syncing back into the system.
To fulfil the use case, I created a UI Button "Closed Complete (Backdate)" that allows agent to input any past "Actual work start" and "Actual work end" which in turns closes the Work Order Tasks in the Past.
What are the key benefits of this method
- Supports technicians who complete work offline and update records later.
- Ensures accurate reporting on SLA adherence and labor hours.
- Provides flexibility without bypassing closure validations.
To achieve this functionality I created a UI Action called Close Complete (Backdate)
The UI was created for both CSM/FSM Workspace and Native UI , there the scripts may differ accordingly
To achieve the pop-up feature in Native UI I created a separate UI Page
To achieve the calculation of Actual time worked based on the time input was done through a Work Task Flows
Codes for all the configurations
UI Action
Name:-Close Complete (Backdate)
Table:-Work Order Task [wm_task]
Order:-500
Action Name:-close_complete_back_date
UI Action (Script):-
UI Action (Workspace Client Script):-
UI Page:-
Name:-KAP_service_management_task_back_date_closed_complete
Category:-HTML Editor
UI Page HTML:-
<g:ui_form>
<j:set var="task_id" value="${RP.getWindowProperties().get('sys_id')}" />
<input type="hidden" name="cancelled" id="cancelled" value="false" />
<input type="hidden" name="task_id" id="task_id" value="${RP.getWindowProperties().get('sys_id')}" />
<input type="hidden" name="state_flow" id="state_flow" value="${RP.getWindowProperties().get('state_flow_id')}" />
<j:set var="cancelled" value="false" />
<j:set var="jvar_work_notes" value="${RP.getWindowProperties().get('work_notes')}" />
<j:set var="jvar_work_start" value="${RP.getWindowProperties().get('start_work')}" />
<j:set var="jvar_work_end" value="${RP.getWindowProperties().get('end_work')}" />
<style>
#task_close_complete .left {
max-width: 60px;
}
#task_close_complete #yesno {
width: 98%;
}
HTML[data-doctype=true] #task_close_complete #yesno {
width: 100%;
}
#task_close_complete tr {
margin-bottom: 12px;
display: table-row;
}
#task_close_complete td {
padding-bottom: 12px;
}
</style>
<table id="task_close_complete" width="100%">
<colgroup>
<col />
<col />
</colgroup>
<!-- Actual Start Date Section-->
<tr>
<td class="left" valign="top">
<label class="control-label">
<span class="icon icon-required required-marker" mandatory="true" style="display:inline !important;"></span>
<span for="work_start_update">${gs.getMessage("Actual Start Date")}</span>
</label>
</td>
<td align="right">
<g:ui_date_time wrap="soft" rows="5" query="" id="work_start_update" style="width:98%; overflow:auto;" name="work_start_update" />
</td>
</tr>
<tr id="work_start_error" style="display: none;">
<td colspan="4">
<div class="notification-danger">
<div>
<img src='images/outputmsg_error.gifx' alt='' />${gs.getMessage("Please provide Actual work start")}
</div>
</div>
</td>
</tr>
<!-- Actual End Date Section-->
<tr>
<td class="left" valign="top">
<label class="control-label">
<span class="icon icon-required required-marker" mandatory="true" style="display:inline !important;"></span>
<span for="work_end_update">${gs.getMessage("Actual End Date")}</span>
</label>
</td>
<td align="right">
<g:ui_date_time wrap="soft" rows="5" query="" id="work_end_update" style="width:98%; overflow:auto;" name="work_end_update" />
</td>
</tr>
<tr id="work_end_error" style="display: none;">
<td colspan="4">
<div class="notification-danger">
<div>
<img src='images/outputmsg_error.gifx' alt='' />${gs.getMessage("Please provide Actual work end")}
</div>
</div>
</td>
</tr>
<!-- Actual Start Date Validation Section-->
<tr id="work_startdatevalid_error" style="display: none;">
<td colspan="4">
<div class="notification-danger">
<div>
<img src='images/outputmsg_error.gifx' alt='' />${gs.getMessage("Actual Start date should be before Actual work end")}
</div>
</div>
</td>
</tr>
<!-- Work Notes Section -->
<tr>
<td class="left" valign="top">
<label class="control-label">
<span class="icon icon-required required-marker" mandatory="true" style="display:inline !important;"></span>
<span for="work_notes_update">${gs.getMessage("Reason for the complete closure")}</span>
</label>
</td>
<td align="right">
<textarea wrap="soft" autocomplete="off" rows="5" id="work_notes_update" data-length="4000" style="width:98%; overflow:auto;" name="work_notes_update">${jvar_work_notes}</textarea>
</td>
</tr>
<tr>
<td colspan="2" id="work_notes_error" style="display: none">
<div class="notification-danger">
<div>
<img src='images/outputmsg_error.gifx' alt='' />${gs.getMessage("Please provide the reason for the complete closure")}
</div>
</div>
</td>
</tr>
<!-- Buttons -->
<tr>
<td colspan="2" align="right">
<g:dialog_buttons_ok_cancel ok="return submitChanges()" cancel="return submitCancel()" />
</td>
</tr>
</table>
<style>
.notification-danger {
background-color: #f8d7da;
padding: 8px 12px;
border: 1px solid #f5c2c7;
border-radius: 4px;
color: #842029;
margin: 6px 0;
}
.notification-danger img {
margin-right: 6px;
vertical-align: middle;
}
</style>
</g:ui_form>
UI Page Client Script
$('work_start_update').value = '';
$('work_end_update').value = '';
/* Handle cancel action: flag as cancelled and close the modal */
function submitCancel() {
var c = gel('cancelled');
c.value = "true";
GlideDialogWindow.get().destroy();
return false;
}
function submitChanges() {
var taskId = gel('task_id').value;
var workStart = gel('work_start_update').value;
var workEnd = gel('work_end_update').value;
/* Validate Start Date is not empty */
if (!workStart) {
document.getElementById('work_start_error').style.display = "";
return false;
} else {
document.getElementById('work_start_error').style.display = "none";
}
/* Validate End Date is not empty */
if (!workEnd) {
document.getElementById('work_end_error').style.display = "";
return false;
} else {
document.getElementById('work_end_error').style.display = "none";
}
/* Date validity check: Start < End */
var startDate = new Date(workStart);
var endDate = new Date(workEnd);
if (startDate > endDate) {
document.getElementById('work_startdatevalid_error').style.display = "";
return false;
} else {
document.getElementById('work_startdatevalid_error').style.display = "none";
}
/* Validate work notes field */
var notes = gel('work_notes_update').value.replace(/^\s+|\s+$/g, '');
if (notes.length < 1) {
document.getElementById('work_notes_error').style.display = "";
return false;
} else {
document.getElementById('work_notes_error').style.display = "none";
}
g_form.clearValue("work_notes");
/* Check if the user is being tracked and store geolocation for audit */
GlideAjax.disableSessionMessages();
var ajax = new GlideAjax('GeolocationAJAX');
ajax.addParam('sysparm_name', 'checkTracking');
ajax.addParam('sysparm_usersysid', g_user.userID);
ajax.getXML(function(response) {
var isUserTracked = response.responseXML.documentElement.getAttribute("answer");
var result = isUserTracked.split(',');
if (result[0] == 'true') {
navigator.geolocation.getCurrentPosition(
function(position) {
var ajax = new GlideAjax('GeolocationAJAX');
ajax.addParam('sysparm_name', 'storeGeolocationHistory');
ajax.addParam('sysparm_usersysid', g_user.userID);
ajax.addParam('sysparm_latitude', position.coords.latitude);
ajax.addParam('sysparm_longitude', position.coords.longitude);
ajax.addParam('sysparm_action', 'Close Complete');
ajax.addParam('sysparm_task', g_form.getUniqueValue());
ajax.getXML();
});
}
});
var ok_button = document.getElementById("ok_button");
/* hide submit button to prevent multi clicks
ok_button.style.display = "none"; */
return true;
}
UI Page Processing Script
if ('false' == cancelled) {
var wot_task = new GlideRecord("wm_task");
if (wot_task.get(task_id)) {
wot_task.work_notes = work_notes_update;
wot_task.work_start = work_start_update;
wot_task.work_end = work_end_update;
new global.StateFlow().processFlow(wot_task, state_flow, 'manual');
response.sendRedirect("wm_task.do?sys_id=" + wot_task.sys_id);
}
}
Work Task Flow:-
Number:-SF0010006
Table:-Work Order Task [wm_task]
Ending state:-Closed Complete
Name:-Close Complete (Backdate)
Manual:-
Condition-Assigned to is Dynamic me and Active is true
Manual Script:-
CSM Workspace:-
- 87 Views