Hide "Rate type " column on time sheet portal

Anish9515
Tera Contributor

Hi All ,

I want to hide "Rate type" Column on time sheet portal it's related out of box functionality.

Anish9515_0-1742302723191.png

Regards,
Anish
@Ankur Bawiskar 

6 REPLIES 6

@Anish9515 

share the complete HTML, client, server widget code here

If my response helped please mark it correct and close the thread so that it benefits future readers.

Regards,
Ankur
Certified Technical Architect  ||  10x ServiceNow MVP  ||  ServiceNow Community Leader

HTML  -

<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}"></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&colon;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" 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" class="state-icon" uib-tooltip="{{item.messages.stateDisplayMessage}}" tooltip-placement="right"
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')}"></span>
<span class="tc-desc" uib-tooltip="{{item[field].display_value}}" tooltip-enable="$first" tooltip-placement="auto top-left"
tooltip-append-to-body="true" ng-if="(( (field === 'rate_type' || field === 'resource_plan') || (data.isProjectActive && field === 'project_time_category')) && !item[field].value)">${None}</span>
<span class="tc-desc text-elipsis" ng-if="!(( (field === 'rate_type' || field === 'resource_plan') || (data.isProjectActive && field === 'project_time_category')) && !item[field].value)">
<span>
<p class="text-elipsis" uib-tooltip="{{item[field].display_value}}" tooltip-placement="auto top-left" tooltip-append-to-body="true">{{item[field].display_value}}</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>
</span>
</span>
<div data-id="{{::item.sys_id}}">
<a href="Javascript&colon;void(0);" ng-if="$first && item.isDuplicate && (data.timecardInEditMode != item.sys_id)" uib-popover-template="'mergeTc'"
popover-class="duplicate-popover" popover-animation="true" popover-placement="auto right"
popover-trigger="outsideClick" popover-is-open="data.mergePopover{{item.sys_id}}"
class="duplicate-tc fa fa-exclamation-triangle" ng-click="adjustPopover($event)"
uib-tooltip="${Merge Duplicate Time cards}" tooltip-placement="auto right" tooltip-append-to-body="true"
tooltip-is-open="data.mergeTp{{item.sys_id}}" ng-mouseover="tooltipEnable($event,true)"
aria-label="show or hide merge timecards popover" role="button"></a>
</div>
</div>
</div>
<div ng-if="data.timecardInEditMode == item.sys_id && data.column_meta[field].editable && (field !== 'rate_type' && field !== 'project_time_category' && field !== 'resource_plan')"
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-class="{'error': item[field].invalid}"
ng-change="validateInput(item[field])" aria-invalid="{{item[field].invalid}}" aria-required="true"
required/>
</div>
<div ng-if="data.timecardInEditMode == item.sys_id && data.column_meta[field].editable && (field === 'rate_type' || field === 'resource_plan') && (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 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>

<div data-id="{{::item.sys_id}}" class="flex-item">
<a href="Javascript&colon;void(0);" class="icon-ellipsis-vertical more-action-btn" popover-animation="true" uib-popover-template="'tcMoreActions'"
popover-animation="true" 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}"
uib-tooltip="${More Actions}" tooltip-placement="auto left" tooltip-append-to-body="true"
tooltip-is-open="data.moreActionTp{{item.sys_id}}" ng-mouseover="tooltipEnable($event,false)"></a>
</div>
</div>
</div>
<sp-paginator ng-if="pageSize <= total" total="total" page-size="pageSize" current="current"></sp-paginator>
</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" ng-if="data.canGenerateTimeCards" ng-click="generateTimecards()">${Generate Time Cards}</button>
<button type="button" class="btn btn-default" 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>

Server -

            (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 timecardportfolio = new GlideRecord('time_card');
    timecardportfolio.newRecord();
    data.canCreateTimecards = timecardportfolio.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'];
    //var defaultHeaderFields = ['task.short_description', 'project_time_category'];
    data.fields = defaultHeaderFields;
   
    if(data.isAllowMultipleRateTypes)
        data.fields.push('rate_type');

    /*
        Inteportfolioating Resource Plan in TimeCard Portal.
    */
    var portfolioRpln = new GlideRecordSecure('time_card');
    var rplnColumnName = 'resource_plan';
    data.allowChangeOfResourcePlan = portfolioRpln.isValidField(rplnColumnName) && timesheetPolicy.updateResourcePlan();
    // Adds resource plan column by default.
    if(data.allowChangeOfResourcePlan)
        data.fields.push(rplnColumnName);


    /* Get portfolioid 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_portfolioc_issue';
    data.tables = input && input.tables ? input.tables : options.tables || defaultTables;

    /* Inlcude additional configured fields */
    if(customFields) {
        customFields.forEach(function(fieldConf) {
            if(data.fields.indexOf(fieldConf.name)==-1) {
                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'),
        'monday' : gs.getMessage('Mon'),
        'tuesday': gs.getMessage('Tue'),
        'wednesday': gs.getMessage('Wed'),
        'thursday': gs.getMessage('Thu'),
        'friday': gs.getMessage('Fri'),
        'saturday': gs.getMessage('Sat'),
        'sunday': gs.getMessage('Sun')
    };

    /* 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'];
    var portfolioForLabels = new GlideRecord('time_card');
    for (var i in data.fields) {
        var field = data.fields[i];
        var ge = portfolioForLabels.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;
    }
   
    data.isProjectActive = data.column_meta.hasOwnProperty("project_time_category");
    data.list = [];
    var isDataSeparationSkippedFromNowOnwards = false;
    try {
    isDataSeparationSkippedFromNowOnwards = typeof DataSeparatorGlobalUtil !== "undefined" ? DataSeparatorGlobalUtil.skipDataSeparation() : false;
        var portfolio = new GlideRecordSecure('time_card');
        portfolio.addQuery('time_sheet', data.timesheetId);
        portfolio.orderByDesc('sys_created_on');
        portfolio.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 = portfolio.getRowCount();
        while (portfolio.next()) {
            var record = {};
            record.messages = {};
            var taskRec = portfolio.task.getRefRecord();
            $sp.getRecordElements(record, portfolio, data.fields_array);
            record.sys_id = portfolio.getValue('sys_id');
            record.comments = portfolio.getDisplayValue('comments');
            record.canEdit = portfolio.canWrite();
            record.canDelete = portfolio.canDelete();
            var timeCardUiActionHelper = new TimeCardUIActionHelper(portfolio);
            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 && portfolio.getValue("category") != "project_work" && portfolio.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(portfolio.task.name);
                    record['task.short_description']['number'] = String(portfolio.task.number);
                } else {
                    record['task.short_description'] = record['category'];
                }
            else
                record['task.short_description']['number'] = String(portfolio.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.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.none = gs.getMessage('--None--');

})($sp);
Client -
            function($scope, TimeCardPortalService, spUtil, $sanitize, $rootScope, $window, $timeout, spModal, $q) {

    var c = this;
    _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" ) {
                // 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.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();
            }
        });
    };

    $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;

        $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;
            }
        };
        $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');
            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.validateInput = function(field){
        var val = field.display_value;
        if(isValidNumber(val))
            field.invalid = false;
        else
            field.invalid = true;
    };

    function isValidNumber(number){
        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");
            };
            widget.options.afterOpen = function(ctrl){
                modalCtrl = ctrl;
            };
            $scope.data.modalInstance = widget;
        });
    };

    $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();
}