Hello @harishk07
I tried your solution but it still not setting values, and field is assigned to (task.assigned_to).
Please check below script. I have added client script , server script and html script.
This is Client controller script:
function($scope, TimeCardPortalService, spUtil, $sanitize, $rootScope, $window, $timeout, spModal, $q, TooltipFactory) {
var c = this;
// var $timeout = $injector.get('$timeout');
_highlightField();
var skipNextRecordWatchUpdate = false;
$scope.pageSize = $scope.data.pageSize;
var minWidthFields = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", "total"];
$scope.getWidth = function(field) {
if(field && $scope.data.column_meta[field]
&& $scope.data.column_meta[field]['width_in_percent']) {
return {
'min-width': $scope.data.column_meta[field]['width_in_percent'] + '%'
};
}
// Days and Total will have Minimum width of 5%.
// Smaller the resolution, smaller the scroll.
if (field && $scope.data.column_meta[field] && (minWidthFields.indexOf(field) > -1)) {
return {
'min-width': '5%'
}
}
// Default width of 10% which has not configured width_in_percent.
return {
'min-width': '10%'
}
}
function updatePagination(from, to) {
$scope.total = c.data.totalRows;
$scope.current = {
to: to > c.data.totalRows? c.data.totalRows: to,
from: from,
pageSize: $scope.pageSize
};
$scope.paginatedList = c.data.list.slice(from-1, to);
}
updatePagination(1, $scope.pageSize);
$scope.$on('sp_load_new_page', function(event, page) {
$scope.paginatedList = c.data.list.slice(page.from-1, page.to);
});
$scope.cancelEdit = function(evt, index){
if(_isEmptyObject($scope.data.editTimecardObj))
return;
_preventDefault(evt);
$scope.data.list[index] = $scope.data.editTimecardObj;
$scope.data.editTimecardObj = {};
$scope.data.timecardInEditMode = '';
$scope.data.highlightedField = $scope.data.previousHighlightedField || $scope.data.defaultField;
$scope.highlightDuplicates();
};
$scope.saveRecord = function(evt, record, promiseResolver){
_preventDefault(evt);
if($scope.data.recordSetForDelete == record.sys_id) return;
var data = {};
var editabelFields = $scope.data.editable_fields;
for (var i in editabelFields){
var field = editabelFields[i];
if(field === "rate_type" || field === "project_time_category" || field === "resource_plan" || field === "resource_assignment" || field === "task.assigned_to") {
// we don't need sanitization here as this is not directly edited by user
// this value is only selected from pre existing values and typed by user
data[field] = record[field].value;
continue;
}
var val = $sanitize(record[field].display_value);
if(!isValidNumber(val))
data[field] = 0;
else
data[field] = val;
}$scope.saveRecord = function(evt, record, promiseResolver){
_preventDefault(evt);
var data = {};
var editableFields = $scope.data.editable_fields;
for (var i in editableFields){
var field = editableFields[i];
// Treat these as selector/reference fields and send sys_id
if (field === "rate_type" || field === "project_time_category" ||
field === "resource_plan" || field === "resource_assignment" ||
field === "task.assigned_to") {
data[field] = record[field] && record[field].value ? record[field].value : "";
continue;
}
// Numeric fields (monday..sunday, etc.)
var val = $sanitize(record[field].display_value);
data[field] = isValidNumber(val) ? val : 0;
}
TimeCardPortalService.updateTimeCard(record.sys_id, data, editableFields.join(',') + ',total');
};
$scope.data.timecardInEditMode = '';
$scope.data.editTimecardObj = {};
TimeCardPortalService.updateTimeCard(record.sys_id, data, editabelFields.join(',')+',total').then(function(response){
var result = response.data.result;
for(var field in result){
record[field].display_value = result[field].display_value;
record[field].value = result[field].value;
if(isValidNumber(result[field].value))
record[field].invalid = false;
$scope.data.highlightedField = $scope.data.previousHighlightedField;
}
var status = response.data.status;
if(status == 'error')
refreshWidget();
else {
skipNextRecordWatchUpdate = true;
$scope.highlightDuplicates();
}
if(promiseResolver) {
promiseResolver();
}
});
};
var $timeout = $injector.get('$timeout');
$scope.deleteRecord = function(evt){
var _target = $(evt.target);
var _id = _target.closest('.flex-item').data('id') || evt.target.id;
var record = $scope.data.list.filter(function(listItem) {
return (listItem.sys_id == _id);
})[0];
var confirmModalCtrl;
var index = $(".container .table-body .flex.tc-row").index(_target.closest('.flex.tc-row'));
$scope.data['tcPopover' + record.sys_id] = false;
var numRows = _target.closest('.table-body').children().length;
var currRow = _target.closest('.table-body').find('.tc-row').get(index);
var nextRow;
if(numRows > 1){
if(index == numRows-1)
nextRow = _target.closest('.table-body').find('.tc-row').get(index-1);
else
nextRow = _target.closest('.table-body').find('.tc-row').get(index+1);
}
$scope.data.confirmDialogOpts = {
title: $scope.data.messages.confirmDlgTitle,
text: $scope.data.messages.confirmMssg,
okTxt: $scope.data.messages['delete'],
cancelTxt: $scope.data.messages.cancel,
okBtnClass: 'btn-danger',
ok: function(){
$scope.data.recordSetForDelete = record.sys_id;
TimeCardPortalService.deleteTimeCard(record.sys_id).then(function(response){
$scope.data.recordSetForDelete = '';
$scope.data.list.splice(index, 1);
skipNextRecordWatchUpdate = true;
$scope.highlightDuplicates();
c.data.totalRows -= 1;
updatePagination($scope.current.from, $scope.current.to);
$rootScope.$broadcast("record-delete");
});
confirmModalCtrl.close();
},
afterOpen: function(ctrl){
confirmModalCtrl = ctrl;
},
afterClose : function(){
$scope.showConfirm = false;
confirmModalCtrl = null;
$scope.data.confirmDialogOpts = null;
if(currRow && $('#' + _id).length)
currRow.focus();
else if(numRows==1 && $('#generate-timecard'))
$('#generate-timecard').focus();
else if(nextRow)
nextRow.focus();
}
};
$scope.showConfirm = true;
$scope.safeDigest();
_preventDefault(evt);
};
function updateRecordState(evt, newState) {
var _target = $(evt.target);
var _id = _target.closest('.flex-item').data('id') || evt.target.id;
var record = $scope.data.list.filter(function(listItem) {
return (listItem.sys_id == _id);
})[0];
TimeCardPortalService.updateTimeCardState(record.sys_id, newState).then(function(response) {
var status = response.data.status;
var message = response.data.data.message;
if (status != 'success')
spUtil.addErrorMessage(message);
else if(newState == 'Submitted' && parseInt(record.total.value) > 0)
spUtil.addInfoMessage(message);
refreshWidget();
})
}
$scope.updateRecordState = function(evt, newState){
$scope.hideTcPopover();
if($scope.data.timecardInEditMode) {
cancelGridEditModePromiseWrapper(event, true).then(function () {
updateRecordState(evt, newState);
})
} else {
updateRecordState(evt, newState);
}
};
$scope.recallRecord = function(evt){
var _target = $(evt.target);
var _id = _target.closest('.flex-item').data('id') || evt.target.id;
var record = $scope.data.list.filter(function(listItem) {
return (listItem.sys_id == _id);
})[0];
var confirmModalCtrl;
var index = $(".container .table-body .flex.tc-row").index(_target.closest('.flex.tc-row'));
$scope.data['tcPopover' + record.sys_id] = false;
$scope.data.confirmDialogOpts = {
title: $scope.data.messages.recallDlgTitle,
text: $scope.data.messages.recallConfirmMssg,
okTxt: $scope.data.messages.recall,
cancelTxt: $scope.data.messages.cancel,
okBtnClass: 'btn-danger',
ok: function(){
TimeCardPortalService.updateTimeCardState(record.sys_id, 'Recalled').then(function(response) {
var status = response.data.status;
var message = response.data.data.message;
if (status != 'success')
spUtil.addErrorMessage(message);
else
spUtil.addInfoMessage(message);
})
confirmModalCtrl.close();
},
afterOpen: function(ctrl){
confirmModalCtrl = ctrl;
},
afterClose : function(){
$scope.showConfirm = false;
confirmModalCtrl = null;
$scope.data.confirmDialogOpts = null;
}
};
$scope.showConfirm = true;
$scope.safeDigest();
_preventDefault(evt);
};
$scope.generateTimecards = function(){
TimeCardPortalService.generateTimecards({
timesheet_id: $scope.data.timesheetId,
tables: $scope.data.tables
}).then(function(response){
spUtil.addInfoMessage(response.data.message);
});
};
$scope.copyFromPreviousTimesheet = function(){
$rootScope.$broadcast('tcp.copy.timesheet');
};
$scope.filterNumericInput = function(item) {
if (!item.display_value) {
item.display_value = '0';
return;
}
var decimalSeparator = g_user_decimal_separator;
var inputValue = item.display_value.trim();
switch(inputValue) {
case '-':
case '0-':
item.display_value = '-';
return;
case '00':
item.display_value = '0';
return;
case '-00':
item.display_value = '-0';
return;
}
if (/^0[1-9]/.test(inputValue)) {
item.display_value = inputValue.substring(1);
return;
}
if (/^-0[1-9]/.test(inputValue)) {
item.display_value = '-' + inputValue.substring(2);
return;
}
var hasNegativeSign = inputValue.charAt(0) === '-';
var numericValue = inputValue.replace(new RegExp('[^0-9' + decimalSeparator + '-]', 'g'), '');
if (numericValue) {
var parts = numericValue.split(decimalSeparator);
if (parts.length > 2) {
numericValue = parts[0] + decimalSeparator + parts.slice(1).join('');
}
numericValue = numericValue.replace(/-/g, '');
if (hasNegativeSign) {
numericValue = '-' + numericValue;
}
}
if (numericValue === '-') {
item.display_value = '-';
} else {
item.display_value = numericValue || '0';
}
};
$scope.focusOutEndDecimalCheck = function(item) {
if (!item.display_value) {
item.display_value = '0';
return;
}
var decimalSeparator = g_user_decimal_separator;
var inputValue = item.display_value.trim();
switch(inputValue) {
case '-':
case '-0':
item.display_value = '-0';
return;
case decimalSeparator:
item.display_value = '0';
return;
}
var hasNegativeSign = inputValue.charAt(0) === '-';
var numericValue = inputValue.replace(new RegExp('[^0-9' + decimalSeparator + '-]', 'g'), '');
numericValue = numericValue.replace(/-/g, '');
if (hasNegativeSign) {
numericValue = '-' + numericValue;
}
if (numericValue.endsWith(decimalSeparator)) {
numericValue = numericValue.slice(0, -1);
}
if (numericValue === '-') {
numericValue = '-0';
}
item.display_value = numericValue || '0';
};
function isValidNumber(number){
if (number === '-') {
return true;
}
var decimalSeperator = g_user_decimal_separator;
var regx = new RegExp('^-?((\\d+(\\'+ decimalSeperator + '\\d+)?)|(\\' + decimalSeperator + '\\d+))$');
if(regx.test(number) && !isNaN(parseFloat(number))){
return true;
}
return false;
}
function refreshWidget(start, end){
/* Carry forward options for grid customizaions */
start = start ? start:1;
end = end ? end: $scope.pageSize
$scope.data.options = $scope.options.tm_grid_options;
spUtil.update($scope).then(function(){
var timecardToEdit = $scope.data.timecardToEdit;
if(timecardToEdit){
$scope.data.timecardToEdit = '';
$scope.showTimecardInEditMode(timecardToEdit);
}
updatePagination(start, end);
$scope.highlightDuplicates();
$scope.addSyncScrollEventListener();
});
}
var filterQuery = 'time_sheet='+ $scope.data.timesheetId;
var recordWatcherTimer;
spUtil.recordWatch($scope, 'time_card', filterQuery, function(){
$timeout.cancel(recordWatcherTimer);
recordWatcherTimer = $timeout(function(){
if (skipNextRecordWatchUpdate) {
skipNextRecordWatchUpdate = false;
$scope.highlightDuplicates();
} else
refreshWidget();
}, 250);
});
$scope.mergeDuplicateTimeCards = function(record) {
TimeCardPortalService.mergeDuplicateTc({
timesheet_id: $scope.data.timesheetId,
record : record
}).then(function() {
refreshWidget();
});
};
$scope.$on('tcp.refresh.widgets', function(){
skipNextRecordWatchUpdate = true;
refreshWidget();
});
$scope.$on('tcp.highlight.field',function(scope, field){
_highlightField(field);
});
$scope.$on('tcp.edit.timecard', function(scope, timecardId){
$scope.showTimecardInEditMode(timecardId);
});
var foundRecord = null;
$scope.$on('pagination.send.window.for.index', function() {
$scope.enableEditMode(foundRecord[0]);
foundRecord = null;
});
$scope.$on('ng.repeat.completed.tc-grid', function() {
if(foundRecord) {
$scope.enableEditMode(foundRecord[0]);
foundRecord = null;
}
});
function cancelGridEditModePromiseWrapper(event, buttonClicked) {
return $q(function(resolve) {
$scope.cancelGridEditMode(event, buttonClicked, resolve);
});
}
function submitTimesheet (timesheetId, oldState) {
TimeCardPortalService.submitTimesheet(timesheetId).then(function(response){
$scope.data.canSubmitTimesheet = response.data.canSubmitTimesheet;
var newState = response.data.data.value;
$scope.data.state = response.data.data;
var mssg = response.data.message;
if(response.data.status == 'error'){
refreshWidget();
if(mssg)
spUtil.addErrorMessage(mssg);
}
else if(oldState == newState){
refreshWidget();
if(mssg)
spUtil.addInfoMessage(mssg);
}
else if(mssg && response.data.status == 'success'){
spUtil.addInfoMessage(mssg);
}
}
);
}
function submitTimesheetButtonClicked(event, timesheetId, oldState) {
if($scope.data.timecardInEditMode) {
cancelGridEditModePromiseWrapper(event, false).then(function () {
submitTimesheet(timesheetId, oldState);
})
} else {
submitTimesheet(timesheetId, oldState);
}
}
$scope.$on('submitTimesheetButtonClicked', function(event, data) {
submitTimesheetButtonClicked(data.event, data.timesheetId, data.oldState);
});
$scope.showTimecardInEditMode = function(timecardId){
var index = null
var record = $scope.data.list.filter(function(record, idx){
if(record.sys_id == timecardId) {
index = index ? index: idx;
return true;
}
return false;
});
if(record.length > 0) {
foundRecord = record;
// if index is on current page. directly call enableEditmode.
// else figure out on which page it is.
var pageIndex = index + 1;
if (pageIndex <= $scope.current.to && pageIndex >= $scope.current.from) {
$scope.enableEditMode(foundRecord[0]);
} else {
// figure out which page it will be.
var targetPage = parseInt(pageIndex / $scope.current.pageSize);
targetPage += pageIndex % $scope.current.pageSize ? 1 : 0;
$rootScope.$broadcast('pagination.get.window.for.index', {
targetPage: targetPage
});
}
} else {
$scope.data.timecardToEdit = timecardId;
skipNextRecordWatchUpdate = true;
refreshWidget();
}
};
function _highlightField(field){
if(field)
$scope.data.highlightedField = field;
else
$scope.data.highlightedField = $scope.data.defaultField;
}
function _preventDefault(evt){
if(evt){
evt.stopPropagation();
evt.preventDefault();
}
}
function _isEmptyObject(obj){
return !obj || Object.keys(obj).length === 0;
}
$scope.openTimecard = function(ev){
_preventDefault(ev);
var _target = $(ev.target);
var sysId = _target.closest('.flex-item').data('id');
$scope.data['tcPopover' + sysId] = false;
spUtil.get('widget-modal', {embeddedWidgetId: 'widget-form', embeddedWidgetOptions: {table:'time_card', sys_id: sysId, view: 'worker_portal'}}).then(function(widget){
var modalCtrl;
var unregister = $scope.$on('sp.form.record.updated', function(){
skipNextRecordWatchUpdate = true;
refreshWidget();
$rootScope.$broadcast("record-add");
modalCtrl.close();
});
widget.options.afterClose = function(){
unregister();
$scope.data.modalInstance = null;
modalCtrl = null;
$rootScope.$broadcast("timesheet_portal_form_closed");
if($('#' + sysId)){
var moreActionBtn = $('#' + sysId).find('.more-action-btn');
if(moreActionBtn)
moreActionBtn.focus();
}
};
widget.options.afterOpen = function(ctrl){
modalCtrl = ctrl;
};
$scope.data.modalInstance = widget;
});
};
$scope.keydownOnMoreActionCntr = function(e) {
var menuLinks = document.getElementById("moreActionCntr").querySelectorAll("a");
// Added e.preventDefault to prevent page to scroll
switch (e.key) {
case 'ArrowUp':
e.preventDefault();
scope.moveUpInMenu(menuLinks);
break;
case 'ArrowDown':
e.preventDefault();
scope.moveDownInMenu(menuLinks);
break;
case 'Home':
e.preventDefault();
menuLinks && menuLinks.length > 0 ? menuLinks[0].focus() : '';
break;
case 'End':
e.preventDefault();
menuLinks && menuLinks.length > 0 ? menuLinks[menuLinks.length - 1].focus() : '';
break;
}
//reset the attributes on all the tabs
menuLinks.forEach(function(link){
link.setAttribute("tabindex", "-1");
});
//set the attribute on selected tab
document.activeElement.removeAttribute("tabindex");
};
$scope.moveUpInMenu = function(menuLinks) {
var currentLink = document.activeElement;
if (!currentLink.parentElement.previousElementSibling) {
(menuLinks && menuLinks.length > 0) ? menuLinks[menuLinks.length - 1].focus() : '';
} else {
if(currentLink.parentElement.previousElementSibling.querySelector("a")){
currentLink.parentElement.previousElementSibling.querySelector("a").focus();
}
}
};
$scope.moveDownInMenu = function(menuLinks) {
var currentLink = document.activeElement;
if (!currentLink.parentElement.nextElementSibling) {
(menuLinks && menuLinks.length > 0) ? menuLinks[0].focus() : '';
} else {
if(currentLink.parentElement.nextElementSibling.querySelector("a")){
currentLink.parentElement.nextElementSibling.querySelector("a").focus();
}
}
};
$scope.highlightDuplicates = function() {
$scope.duplicateList = {};
if($scope.data.list && $scope.data.list.length) {
for(var i=0; i< $scope.data.list.length; i++) {
list_item = $scope.data.list[i];
var _sd = list_item['task.short_description'].value.replace(/\s/g, "");
var _state = list_item.state.value;
if($scope.data.restrictedStatesForDuplicateMerge.indexOf(_state.toLowerCase()) !== -1)
continue;
var _ptc = list_item.project_time_category.value || '';
var _taskSysId = list_item.taskSysId;
//Since task can be associated with any categories
var _category = list_item.category.value;
var _rateType = (list_item.rate_type && list_item.rate_type.value) || '';
var _rpln = (list_item.resource_plan && list_item.resource_plan.value) || '';
var uniqueItemKey = _sd + _state + _ptc + _rateType + _taskSysId + _category + _rpln;
var duplicateList = $scope.duplicateList;
if(duplicateList[uniqueItemKey] && duplicateList[uniqueItemKey].length && _taskSysId) {
duplicateList[uniqueItemKey][0].isDuplicate = true;
duplicateList[uniqueItemKey].push(list_item);
list_item.isDuplicate = true;
} else {
duplicateList[uniqueItemKey] = [];
duplicateList[uniqueItemKey].push(list_item);
}
}
}
for(var prop in $scope.duplicateList) {
if($scope.duplicateList[prop] && $scope.duplicateList[prop].length === 1) {
$scope.duplicateList[prop][0].isDuplicate = false;
}
}
$scope.$$phase || $scope.$root && $scope.$root.$$phase || $scope.$digest();
};
$scope.highlightDuplicates();
$scope.showTooltip = TooltipFactory.showTooltip;
}
This is server script:
(function($sp) {
data.userId = $sp.getParameter('sysparm_user_id') || gs.getUserID();
var timesheetId = $sp.getParameter('sysparm_timesheet_id') || input.timesheetId;
if(!timesheetId)
timesheetId = new TimeSheetFactory().getTimeSheet(data.userId);
data.timesheetId = timesheetId;
var timesheet = new GlideRecord('time_sheet');
timesheet.get(timesheetId);
var timecardGr = new GlideRecord('time_card');
timecardGr.newRecord();
data.canCreateTimecards = timecardGr.canCreate();
data.isTooltipEnable = true;
var tcps = new TimeCardPortalService();
data.week_range = tcps.getWeekRange(timesheet.week_starts_on.getGlideObject());
var state = timesheet.getValue('state');
var dayFields = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
var timesheetPolicy = TimeSheetPolicy.getFromUserId(data.userId);
data.isAllowMultipleRateTypes = timesheetPolicy.allowMultipleRatetypes();
data.defaultRateType = timesheetPolicy.defaultRateType();
if(data.isAllowMultipleRateTypes)
data.tcDuplicateMsg = gs.getMessage("A timecard for the task, project category and rate type already exists. Do you want to merge duplicate timecards?");
else
data.tcDuplicateMsg = gs.getMessage("A timecard for the task and project category already exists. Do you want to merge duplicate timecards?");
var defaultHeaderFields = ['task.short_description','task.assigned_to', 'project_time_category'];
data.fields = defaultHeaderFields;
if(data.isAllowMultipleRateTypes)
data.fields.push('rate_type');
// Add Assigned to column (reference field on time_card)
if (data.fields.indexOf('task.assigned_to') === -1)
data.fields.push('task.assigned_to');
/*
Integrating Resource Plan in TimeCard Portal.
*/
var grRpln = new GlideRecordSecure('time_card');
var rplnColumnName = 'resource_plan';
data.allowChangeOfResourcePlan = grRpln.isValidField(rplnColumnName) && timesheetPolicy.updateResourcePlan();
// Adds resource plan column by default.
if(data.allowChangeOfResourcePlan)
data.fields.push(rplnColumnName);
/* Get grid configuration */
var customFields = options.header_fields || input.options.header_fields;
data.pageSize = options.pageSize;
data.restrictedStatesForDuplicateMerge = options.restrictedStatesForDuplicateMerge || input.options.restrictedStatesForDuplicateMerge || [];
//Consider the tables provided in the input or options
var defaultTables = 'rm_epic,sn_safe_epic,incident,problem,change_request,rm_story,rm_scrum_task,pm_project,pm_project_task,rm_defect,rm_enhancement,dmn_demand_task,sn_audit_task,sn_audit_engagement,sn_audit_advanced_milestone,sn_audit_advanced_engagement_project,sn_grc_issue';
data.tables = input && input.tables ? input.tables : options.tables || defaultTables;
var nameMapping = [];
var gr = new GlideRecord('sys_db_object');
gr.addQuery('name', 'IN', data.tables);
gr.query();
while (gr.next()) {
nameMapping.push({
id: gr.name.toString(),
text: gr.label.toString() || ''
});
}
data.tableOption = nameMapping;
/* Inlcude additional configured fields */
if(customFields) {
customFields.forEach(function(fieldConf) {
if(data.fields.indexOf(fieldConf.name)==-1) {
if(fieldConf.name === 'resource_assignment') {
var grRAss = new GlideRecordSecure('time_card');
var rassColumnName = 'resource_assignment';
data.allowChangeOfResourceAssign = grRAss.isValidField(rassColumnName) && timesheetPolicy.updateResourcePlan();
// Adds resource assignment column by default.
if(data.allowChangeOfResourceAssign && data.fields.indexOf(fieldConf.name)==-1)
data.fields.push(rassColumnName);
} else
data.fields.push(fieldConf.name);
}
});
}
var dayObj = timesheet.week_starts_on.getGlideObject();
var day = dayObj.getDayOfWeekUTC();
for(var i = 1; i<=7;i++){
data.fields.push(dayFields[day-1]);
day = (day + 1 <= 7) ? day + 1 : 1;
}
data.fields.push('total');
var field_labels = {
//'task.number' : gs.getMessage('Task Number'),
'task.short_description' : gs.getMessage('Task'),
'task.assigned_to' : gs.getMessage('Assigned To'),
'monday' : sn_i18n.Message.getMessage('app_ppm', 'Mon{?three char representing day of week Monday}'),
'tuesday': sn_i18n.Message.getMessage('app_ppm', 'Tue{?three char representing day of week Tuesday}'),
'wednesday': sn_i18n.Message.getMessage('app_ppm', 'Wed{?three char representing day of week Wednesday}'),
'thursday': sn_i18n.Message.getMessage('app_ppm', 'Thu{?three char representing day of week Thursday}'),
'friday': sn_i18n.Message.getMessage('app_ppm', 'Fri{?three char representing day of week Friday}'),
'saturday': sn_i18n.Message.getMessage('app_ppm', 'Sat{?three char representing day of week Saturday}'),
'sunday': sn_i18n.Message.getMessage('app_ppm', 'Sun{?three char representing day of week Sunday}')
};
/* Add custom labels */
if(customFields) {
customFields.forEach(function(fieldConf) {
field_labels[fieldConf.name] = fieldConf.label;
});
}
data.breakdowns = tcps.getDayBreakdowns(data.timesheetId);
var dateOfDay = {};
for(var j in data.breakdowns) {
if(!dateOfDay[data.breakdowns[j].field]) {
dateOfDay[data.breakdowns[j].field] = data.breakdowns[j].date;
}
}
data.column_meta = {};
data.editable_fields = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'project_time_category', 'task.assigned_to'];
var grForLabels = new GlideRecord('time_card');
for (var i in data.fields) {
var field = data.fields[i];
var ge = grForLabels.getElement(field);
if(field_labels[field]) {
data.column_meta[field] = {};
data.column_meta[field]['label'] = field_labels[field];
}
if (ge == null)
continue;
data.column_meta[field] = {};
data.column_meta[field]['label'] = field_labels[field] || ge.getLabel();
if(dateOfDay[field]) {
data.column_meta[field]['label'] = data.column_meta[field]['label'] + " " + dateOfDay[field];
}
var userConfig = customFields.filter(function(conf){return conf.name == field && conf.width_in_percent;});
if(userConfig && userConfig.length>0)
data.column_meta[field]['width_in_percent'] = userConfig[0]['width_in_percent'];
}
for (i in data.editable_fields) {
if(data.column_meta[data.editable_fields[i]])
data.column_meta[data.editable_fields[i]]['editable'] = true;
}
if(data.isAllowMultipleRateTypes) {
data.editable_fields.push('rate_type');
data.column_meta['rate_type']['editable'] = true;
}
if(data.allowChangeOfResourcePlan && data.column_meta.hasOwnProperty('resource_plan')) {
data.editable_fields.push('resource_plan');
data.column_meta['resource_plan']['editable'] = true;
}
if(data.allowChangeOfResourceAssign && data.column_meta.hasOwnProperty('resource_assignment')) {
data.editable_fields.push('resource_assignment');
data.column_meta['resource_assignment']['editable'] = true;
}
// Fetch all active users for Assigned To dropdown
var userGR = new GlideRecord('sys_user');
userGR.addActiveQuery();
userGR.query();
data.usersList = [];
while (userGR.next()) {
data.usersList.push({
sys_id: userGR.getValue('sys_id'),
name: userGR.getValue('name')
});
}
data.isProjectActive = data.column_meta.hasOwnProperty("project_time_category");
data.list = [];
var isDataSeparationSkippedFromNowOnwards = false;
try {
isDataSeparationSkippedFromNowOnwards = typeof DataSeparatorGlobalUtil !== "undefined" ? DataSeparatorGlobalUtil.skipDataSeparation() : false;
var gr = new GlideRecordSecure('time_card');
gr.addQuery('time_sheet', data.timesheetId);
gr.orderByDesc('sys_created_on');
gr.query();
data.fields_array = data.fields.concat(['category', 'state', 'task.sys_id', 'task.sys_class_name', 'project_time_category', 'category']);
data.actionableRows = 0;
data.totalRows = gr.getRowCount();
while (gr.next()) {
var record = {};
record.messages = {};
var taskRec = gr.task.getRefRecord();
$sp.getRecordElements(record, gr, data.fields_array);
record.sys_id = gr.getValue('sys_id');
record.comments = gr.getDisplayValue('comments');
record.canEdit = gr.canWrite();
record.canDelete = gr.canDelete();
var timeCardUiActionHelper = new TimeCardUIActionHelper(gr);
record.canSubmit = timeCardUiActionHelper.canSubmit();
record.canApprove = timeCardUiActionHelper.canApprove();
record.canReject = timeCardUiActionHelper.canReject();
record.canRecall = timeCardUiActionHelper.canRecall();
record.isProjectWork = false;
record.taskSysId = false;
record.messages.stateDisplayMessage = gs.getMessage('State: {0}', record['state'].display_value);
record.messages.rowDisplayMessage = gs.getMessage('Total hours for task {0} is {1} and the state is {2}', [record['task.short_description'].value, record['total'].value, record['state'].display_value]);
if(taskRec){
record.isProjectWork = taskRec.instanceOf("pm_project") || taskRec.instanceOf("pm_project_task");
record.taskSysId = taskRec.getValue("sys_id");
}
if(!record.taskSysId && gr.getValue("category") != "project_work" && gr.getValue("category") != "task_work" )
record.taskSysId = "no_task";
if(record.canEdit || record.canDelete)
data.actionableRows++;
if(JSUtil.nil(record['task.short_description']['value']))
if(record['task.sys_class_name']['value'] == 'sn_audit_engagement'){
record['task.short_description']['display_value'] = String(gr.task.name);
record['task.short_description']['number'] = String(gr.task.number);
} else {
record['task.short_description'] = record['category'];
}
else
record['task.short_description']['number'] = String(gr.task.number);
// var projectTimeCategory = record['project_time_category']['display_value'];
// if(JSUtil.notNil(projectTimeCategory)){
// record['task.short_description'].display_value += ' (' + projectTimeCategory +')';
// }
data.list.push(record);
}
} finally {
if (isDataSeparationSkippedFromNowOnwards)
DataSeparatorGlobalUtil.honourDataSeparation();
}
data.defaultField = 'total';
data.canGenerateTimeCards = (new TimeSheetUIActionHelper(timesheet)).canGenerateTimeCards(data.userId);
data.messages = {};
data.messages.selectATask = gs.getMessage('Select a Task');
data.messages.enterSearchBy = gs.getMessage('Select a Table');
data.messages.allTablesOption = gs.getMessage('All');
data.messages.noMatchesFound = gs.getMessage('No matches found');
data.messages.loadingFailed = gs.getMessage('Loading failed');
data.messages.loadingMoreResults = gs.getMessage('Loading more results…');
data.messages.searching = gs.getMessage('Searching…');
data.messages.inputTooShort = gs.getMessage('Enter task number or short description');
data.messages.confirmDlgTitle = gs.getMessage('Delete Time Card');
data.messages.confirmMssg = gs.getMessage('Are you sure, you want to delete this time card?');
data.messages.recallDlgTitle = gs.getMessage('Recall Time Card');
data.messages.recallConfirmMssg = gs.getMessage('Recalling the Time Card will revert actual effort and associated expense lines.');
data.messages['delete'] = gs.getMessage('Delete');
data.messages.recall = gs.getMessage('Recall');
data.messages.cancel = gs.getMessage('Cancel');
data.messages.selectRateType = gs.getMessage('Select rate type');
data.messages.selectProjectTimeCategoryType = gs.getMessage('Select category');
data.messages.selectResourcePlan = gs.getMessage('Select Resource Plan');
data.messages.selectResourceAssignment = gs.getMessage('Select Resource Assignment');
data.messages.none = gs.getMessage('--None--');
})($sp);
This is HTML script-
<div id="tc-grid">
<div class="grid-header clearfix">
<span class="tc-grid-header">${Logged Time Cards}</span>
<span class="pull-right clearfix" ng-if="::data.canCreateTimecards">
<span class="fa fa-question-circle pull-right" uib-tooltip="${Search any task that you have worked during the week and add to the Time Sheet.}"
tooltip-placement="left" aria-label="${Help}" tooltip-trigger="focus mouseover" ng-mouseenter="showTooltip($event)" ng-mouseleave="showTooltip($event)"></span>
<button type="button" class="btn btn-link pull-right" ng-click="addTask()" id="add-task" aria-haspopup="true">${Add unassigned tasks to Time Sheet}</button>
</span>
</div>
<div class="tc-grid-cntr" ng-if="data.list.length">
<div class="table-responsive">
<div class="tc-table-layout">
<div class="container" id="tc-grid-table" role="table" aria-label="Timecards">
<div role="rowgroup">
<div class="flex table-headers" role="row">
<div scope="col" ng-repeat="field in ::data.fields track by $index" class="flex-item {{field}}" ng-class="{'selected':data.highlightedField == field}"
role="columnheader" ng-style="{{::getWidth(field)}}">
<div class="th-title {{field}}">
<strong class="wrap-text">{{::data.column_meta[field].label}}</strong>
</div>
</div>
<div class="flex-item placeholder" role="columnheader">
<a href="Javascript:void(0);" class="icon-ellipsis-vertical more-action-btn"></a>
</div>
</div>
</div>
<div class="table-body" role="rowgroup" ng-style="::tableBodyStyles">
<div ng-repeat="item in paginatedList track by item.sys_id" collection="paginatedList" repeat-complete="tc-grid" class="flex tc-row" id="{{::item.sys_id}}"
ng-class="{'active': data.timecardInEditMode == item.sys_id, 'disabled': !item.canEdit, 'rejected-desc' : item.state.value === 'Rejected'}"
tabindex="{{item.canEdit ? 0 : -1}}" ng-dblclick="editRow($event, item);" ng-keydown="keyHandler($event, item, $index)"
aria-disabled="{{!item.canEdit}}" aria-selected="{{data.timecardInEditMode === item.sys_id}}" aria-label="{{item.messages.rowDisplayMessage}}"
role="row">
<div ng-repeat="field in ::data.fields track by field" class="{{::field}} flex-item" data-field="{{::field}}" data-th="{{::data.column_meta[field].label}}"
ng-class="{'selected':data.highlightedField == field}" role="cell" ng-style="{{::getWidth(field)}}">
<div class="{{field}} text-elipsis" ng-if="data.timecardInEditMode != item.sys_id || !data.column_meta[field].editable">
<div class="tc-value" ng-class="{'disp-flex align-center' : $first}">
<span ng-if="$first" tabindex="0" class="state-icon" uib-tooltip="{{item.messages.stateDisplayMessage}}" tooltip-placement="right" tooltip-trigger="focus mouseover" ng-mouseenter="showTooltip($event)" ng-mouseleave="showTooltip($event)"
tooltip-append-to-body="true" ng-class="{'icon-workflow-pending' : (item['state'].value === 'Pending'), 'icon-workflow-progress' : (item['state'].value === 'Submitted'), 'icon-workflow-check' : (item['state'].value === 'Processed' || item['state'].value === 'Approved'), 'icon-workflow-rejected' : (item['state'].value === 'Rejected'), 'icon-workflow-skip icon-flipped': (item['state'].value === 'Recalled')}" aria-label="{{item.messages.stateDisplayMessage}}" role="img"></span>
<p class="text-elipsis" uib-tooltip="{{item[field].display_value}}" tooltip-placement="auto top-left" tooltip-append-to-body="true" tooltip-trigger="focus mouseover" ng-mouseenter="showTooltip($event)" ng-mouseleave="showTooltip($event)">
{{item[field].display_value || (item[field].value ? item[field].value : '${None}')}}
</p>
<div ng-if="field === 'task.short_description' && item[field].number">
<a target="_blank" class="anchor-tag" ng-href="/{{::item['task.sys_class_name'].value}}.do?sys_id={{::item['task.sys_id'].value}}">{{item[field].number}}</a>
</div>
</div>
</div>
<div ng-if="data.timecardInEditMode == item.sys_id && data.column_meta[field].editable && field === 'task.assigned_to'" class="select2_field">
<label class="sr-only" for="{{field}}">{{field}}</label>
<select class="form-control tc-row-input"
id="{{field}}"
ng-model="item[field].value"
ng-change="item[field].display_value = (data.usersList | filter:{sys_id: item[field].value})[0].name"
ng-options="user.sys_id as user.name for user in data.usersList">
<option value="">-- None --</option>
</select>
</div>
<div ng-if="data.timecardInEditMode == item.sys_id && data.column_meta[field].editable && field === 'project_time_category' && data.isProjectActive" ng-class="{select2_field:(item['isProjectWork']) }">
<label class="sr-only" for="{{field}}">{{field}}</label>
<span ng-if="!(item['isProjectWork'])">${None}</span>
<input ng-if="(item['isProjectWork'])" type="text" id="{{field}}" name="{{field}}" ng-model="item[field].display_value" class="tc-row-input"
ng-class="{'error': item[field].invalid}" aria-invalid="{{item[field].invalid}}" />
</div>
<div ng-if="data.timecardInEditMode == item.sys_id && data.column_meta[field].editable && field !== 'rate_type' && field !== 'project_time_category' && field !== 'resource_plan' && field !== 'resource_assignment' && field !== 'task.assigned_to'" class="edit_mode">
<label class="sr-only" for="{{field}}">{{field}}</label>
<input type="text" id="{{field}}" name="{{field}}" ng-model="item[field].display_value" class="tc-row-input day-input"
ng-change="filterNumericInput(item[field])" ng-blur="focusOutEndDecimalCheck(item[field])" required/>
</div>
<div ng-if="data.timecardInEditMode == item.sys_id && data.column_meta[field].editable && (field === 'rate_type' || field === 'resource_plan' || field === 'resource_assignment') && (item.state.value === 'Pending' || item.state.value === 'Rejected' || item.state.value === 'Recalled')" class="select2_field">
<label class="sr-only" for="{{field}}">{{field}}</label>
<input type="text" id="{{field}}" name="{{field}}" ng-model="item[field].display_value" class="tc-row-input" ng-class="{'error': item[field].invalid}" aria-invalid="{{item[field].invalid}}" />
</div>
</div>
<div class="flex-item">
<div data-id="{{::item.sys_id}}">
<a href="Javascript:void(0);" class="icon-ellipsis-vertical more-action-btn" popover-animation="true" uib-popover-template="'tcMoreActions'"
popover-placement="auto left" popover-trigger="outsideClick" popover-is-open="data.tcPopover{{item.sys_id}}"
popover-class="more-action-popover" ng-click="adjustPopover($event)" role="button" aria-label="${More actions for {{::item['task.short_description'].display_value}}}"
uib-tooltip="${More actions for {{::item['task.short_description'].display_value}}}" tooltip-placement="auto left" tooltip-append-to-body="true"
tooltip-is-open="data.moreActionTp{{item.sys_id}}" ng-mouseover="tooltipEnable($event,false)" tooltip-trigger="focus mouseover" ng-mouseenter="showTooltip($event)" ng-mouseleave="showTooltip($event)"></a>
</div>
</div>
</div>
<sp-paginator ng-if="pageSize <= total" total="total" page-size="pageSize" current="current"></sp-paginator>
</div>
</div>
</div>
</div>
</div>
<div ng-if="!data.list.length">
<div id="empty-state">
<div style="font-size:12px;text-align:center;margin-top: 50px;">${No Time Cards logged yet.}</div>
<div style="margin-top:30px;text-align:center;" ng-if="::data.canCreateTimecards">
<button type="button" class="btn btn-default" id="generate-timecard" ng-if="data.canGenerateTimeCards" ng-click="generateTimecards()">${Generate Time Cards}</button>
<button type="button" class="btn btn-default" id="copy-timesheet" ng-click="copyFromPreviousTimesheet()" aria-haspopup="true">${Copy from previous Time Sheet}</button>
</div>
</div>
</div>
<sp-widget widget="data.modalInstance" ng-if="data.modalInstance"></sp-widget>
<pw-confirm-dialog data="data.confirmDialogOpts" ng-if="showConfirm"></pw-confirm-dialog>
</div>