777 lines
35 KiB
Plaintext
777 lines
35 KiB
Plaintext
@using EnVisage.Code
|
|
@using EnVisage.Models
|
|
@using System.Linq
|
|
@using EnVisage.Code.Extensions
|
|
@model NonProjectTimeModel
|
|
@{
|
|
string userId = User.Identity.GetID();
|
|
|
|
var allocCats = string.Empty;
|
|
foreach (var item in Utils.GetNonProjectTimeCategories())
|
|
{
|
|
if (!string.IsNullOrEmpty(allocCats))
|
|
{
|
|
allocCats += ",";
|
|
}
|
|
allocCats += "{ id: '" + item.Value + "', text: '" + item.Text + "'}";
|
|
}
|
|
|
|
var distribution = string.Empty;
|
|
if (Model != null && Model.NonProjectTimeAllocations != null)
|
|
{
|
|
var distributionModel = Model.NonProjectTimeAllocations
|
|
.ToDictionary(x => Utils.ConvertToUnixDate(x.WeekEndingDate), x => new
|
|
{
|
|
x.HoursOff,
|
|
StartDate = Utils.ConvertToUnixDate(x.WeekStartDate),
|
|
EndDate = Utils.ConvertToUnixDate(x.WeekEndingDate)
|
|
});
|
|
distribution = Newtonsoft.Json.JsonConvert.SerializeObject(distributionModel);
|
|
}
|
|
|
|
// Get teams list for display in assignment picker
|
|
var teamOptsForSelect2 = Utils.GetTeams(userId, true);
|
|
|
|
// Get resources list for display in assignment picker
|
|
DateTime dp = Model.Id.Equals(Guid.Empty) ? DateTime.UtcNow.Date : Model.NonProjectTimeStartDate.ToUniversalTime().Date;
|
|
List<Guid> teamsToBrowse =
|
|
teamOptsForSelect2.Where(x => !String.IsNullOrEmpty(x.Value) && (x.IsAccessible || x.Value == Model.TeamId.ToString())).Select(x => new Guid(x.Value)).ToList();
|
|
|
|
// Format SelectAll option for teams list
|
|
var selectAllOption = teamOptsForSelect2.FirstOrDefault(x => String.IsNullOrEmpty(x.Value));
|
|
if (selectAllOption != null)
|
|
{
|
|
selectAllOption.Value = Guid.Empty.ToString();
|
|
selectAllOption.Text = "Select All";
|
|
selectAllOption.IsAccessible = true;
|
|
}
|
|
|
|
var resourceOptsForSelect2 = Utils.GetResourcesByTeams(teamsToBrowse, dp, Model.TeamId);
|
|
if (!SecurityManager.CheckSecurityObjectPermission(Areas.AccessToOtherResources, AccessLevel.Read))
|
|
{
|
|
resourceOptsForSelect2 =
|
|
from optGroup in resourceOptsForSelect2
|
|
where optGroup.children.Any(x => Guid.Equals(new Guid(x.id), ViewBag.CurrentResourceId))
|
|
let children = optGroup.children.Where(x => Guid.Equals(new Guid(x.id), ViewBag.CurrentResourceId)).ToList()
|
|
select new Utils.Select2DataOptionGroup
|
|
{
|
|
id = optGroup.id,
|
|
text = optGroup.text,
|
|
children = children
|
|
};
|
|
}
|
|
}
|
|
|
|
<script type="text/javascript">
|
|
var isNew = @(Model.Id.Equals(Guid.Empty) ? "true" : "false");
|
|
var jsonDistribution = '@Html.Raw(distribution)';
|
|
var currentDistribution;
|
|
var nptAllocations = jsonDistribution ? JSON.parse(jsonDistribution) : {};
|
|
var C_EMPTY_GUID_VALUE = '@(Guid.Empty.ToString())';
|
|
|
|
var _stDataChanged = false;
|
|
function onSTDataChanged() {
|
|
_stDataChanged = true;
|
|
}
|
|
function resetSTDataChanged() {
|
|
_stDataChanged = false;
|
|
}
|
|
function isSTDataChanged() {
|
|
return _stDataChanged;
|
|
}
|
|
|
|
function getResourceOptions() {
|
|
var resourcesOpts = [];
|
|
@foreach (var optGroup in resourceOptsForSelect2)
|
|
{
|
|
// Creates server resource options on client side
|
|
<text>
|
|
resourcesOpts.push({
|
|
text: "@optGroup.text",
|
|
children: [
|
|
</text>
|
|
|
|
foreach(var optItem in optGroup.children)
|
|
{
|
|
<text>
|
|
{
|
|
id: "@optItem.id",
|
|
text: '@Html.Raw(optItem.text.Replace("'", "\\'"))'
|
|
},
|
|
</text>
|
|
}
|
|
<text>
|
|
]
|
|
});
|
|
</text>
|
|
}
|
|
return resourcesOpts;
|
|
}
|
|
|
|
function getTeamOptions() {
|
|
var opts = [];
|
|
@foreach (var optItem in teamOptsForSelect2)
|
|
{
|
|
// Creates server team options on client side
|
|
if (optItem.IsAccessible || optItem.Value == Model.TeamId.ToString())
|
|
{
|
|
<text>
|
|
opts.push({
|
|
id: "@optItem.Value",
|
|
text: '@Html.Raw(optItem.Text.Replace("'", "\\'"))'
|
|
});
|
|
</text>
|
|
}
|
|
}
|
|
|
|
return opts;
|
|
}
|
|
|
|
function initNonProjectTimes(needToRecalculate, needToApplyAllocations) {
|
|
// Init Resources picker control.
|
|
// SA: Resource picker is based on Input control, because in other case (of select or listbox)
|
|
// Select2 stores selected items in order, which is different of the displayed to user one.
|
|
// As Select2 is based on input control, we should specify options for display via select2 initialization
|
|
var resourceOpts = getResourceOptions();
|
|
$("#@Html.IdFor(model => model.ResourcesAsText)").select2({
|
|
placeholder: "Select Resources",
|
|
allowClear: true,
|
|
multiple: true,
|
|
data: resourceOpts
|
|
}).on('change', function() {
|
|
fillNonProjectTimeWeeksGrid(true);
|
|
});
|
|
|
|
var teamOpts = getTeamOptions();
|
|
$("#@Html.IdFor(model => model.TeamsAsText)").select2({
|
|
placeholder: "Select Teams",
|
|
allowClear: true,
|
|
multiple: true,
|
|
data: teamOpts,
|
|
formatResult: function (item, container, query) {
|
|
var result = $("<span>" + item.text + "</span>")
|
|
|
|
if (item.id == C_EMPTY_GUID_VALUE) {
|
|
$(result).attr("class", "select2-group-option");
|
|
$(container).css("border-bottom", "2px solid #ccc");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
})
|
|
.on("change", function (e) {
|
|
if (e && e.val && e.val.indexOf(C_EMPTY_GUID_VALUE) >= 0) {
|
|
// Perform selection of all elements
|
|
var newSelection = [];
|
|
var options = $(e.target).data("select2").opts.data;
|
|
|
|
if (options) {
|
|
for (var index = 1; index < options.length; index++) {
|
|
newSelection.push(options[index].id);
|
|
}
|
|
}
|
|
|
|
$(e.currentTarget).select2('val', newSelection);
|
|
}
|
|
fillNonProjectTimeWeeksGrid(true);
|
|
});
|
|
|
|
if("@Model.IsHistory" == "True"){
|
|
$("#btnSaveNPTime").css("display", "none");
|
|
$("#@Html.IdFor(model=> model.NonProjectTimeName)").prop("disabled", "disabled");
|
|
$("#@Html.IdFor(model=> model.Resources)").prop("disabled", "disabled");
|
|
$("#@Html.IdFor(model=> model.NonProjectTimeCategoryId)").prop("disabled", "disabled");
|
|
$("#@Html.IdFor(model=> model.NonProjectTimeStartDate)").prop("disabled", "disabled");
|
|
$("#@Html.IdFor(model=> model.NonProjectTimeEndDate)").prop("disabled", "disabled");
|
|
$("#@Html.IdFor(model=> model.NonProjectTimeDuration)").prop("disabled", "disabled");
|
|
$("#@Html.IdFor(model=> model.NonProjectTimeCost)").prop("disabled", "disabled");
|
|
$("#@Html.IdFor(model=> model.Details)").prop("disabled", "disabled");
|
|
$("#@Html.IdFor(model=> model.IsTeamAssignmentMode)").prop("disabled", "disabled");
|
|
$("#@Html.IdFor(model=> model.Permanent)").prop("disabled", "disabled");
|
|
$("#@Html.IdFor(model=> model.IsPercentsMode)").prop("disabled", "disabled");
|
|
$("#@Html.IdFor(model=> model.ResourcesAsText)").prop("disabled", "disabled");
|
|
}
|
|
|
|
$('#npTimeForm input.select2-default').css('width', '125px');
|
|
$("#@Html.IdFor(model=>model.NonProjectTimeCategoryId)").select2({
|
|
@if (SecurityManager.CheckSecurityObjectPermission(Areas.RD_ResourceNPTAllocationCategory, AccessLevel.Write))
|
|
{
|
|
<text>
|
|
createSearchChoice: function(term, data) {
|
|
if ($(data).filter(function() { return this.text.localeCompare(term) === 0; }).length === 0) {
|
|
return { id: (term == '') ? null : C_EMPTY_GUID_VALUE, text: term };
|
|
}
|
|
},
|
|
</text>
|
|
}
|
|
multiple: false,
|
|
data: [@Html.Raw(allocCats)]
|
|
});
|
|
|
|
fillNonProjectTimeWeeksGrid(needToRecalculate, needToApplyAllocations);
|
|
updateSliderTitles();
|
|
|
|
$('#btnSaveNPTime').click(function () {
|
|
var data = $("#@Html.IdFor(model=>model.NonProjectTimeCategoryId)").select2('data');
|
|
$("#@Html.IdFor(model=>model.NonProjectTimeCategoryName)").val(data == null? "" : data.text);
|
|
$.validator.unobtrusive.parseDynamicContent('#npTimeForm');
|
|
});
|
|
|
|
$('#npTimeForm').find('input[type=checkbox],input[type=text],select,textarea')
|
|
.not('#@Html.IdFor(x=> x.IsPercentsMode)')
|
|
.on("change", function () {
|
|
if (typeof onSTDataChanged === 'function')
|
|
onSTDataChanged();
|
|
});
|
|
|
|
$('#@Html.IdFor(x=> x.Permanent)').switcher({
|
|
on_state_content: 'Permanent',
|
|
off_state_content: 'One Time'
|
|
}).on('change', function(){
|
|
var isPermanent = $(this).is(':checked');
|
|
$('#bs-datepicker-np-time-range').datePickerRange('setState', !isPermanent);
|
|
if (!isPermanent) {
|
|
var startDate = $('#@Html.IdFor(x=> x.NonProjectTimeStartDate)').datepicker('getDate');
|
|
if (startDate && (startDate.getTime() || 0) > 0) {
|
|
$('#@Html.IdFor(x=> x.NonProjectTimeEndDate)').datepicker('setDate', addDaysToDate(startDate, 14));
|
|
}
|
|
}
|
|
$('#effective-date-container').toggleClass('hidden');
|
|
}).parent().css("width", "100px");
|
|
|
|
if(("@Model.IsHistory" == "True"))
|
|
$("#@Html.IdFor(x => x.Permanent)").prop("disabled", "disabled");
|
|
|
|
$('#@Html.IdFor(x=> x.IsPercentsMode)').switcher({
|
|
on_state_content: 'Percents',
|
|
off_state_content: 'Hours'
|
|
}).parent().css("width", "100px");
|
|
$('#@Html.IdFor(x=> x.IsPercentsMode)').on('change', function () {
|
|
fillNonProjectTimeWeeksGrid(false);
|
|
$('#hours-mode-message').toggleClass('hidden');
|
|
});
|
|
|
|
$('#@Html.IdFor(x=> x.IsTeamAssignmentMode)').switcher({
|
|
on_state_content: 'Teams',
|
|
off_state_content: 'Resources'
|
|
}).parent().css("width", "100px");
|
|
$('#@Html.IdFor(x=> x.IsTeamAssignmentMode)').on('change', function () {
|
|
setAssignmentBlockState();
|
|
});
|
|
|
|
setAssignmentBlockState();
|
|
$.validator.unobtrusive.parseDynamicContent('form#npTimeForm');
|
|
|
|
var options2 = {
|
|
orientation: $('body').hasClass('right-to-left') ? "auto right" : 'auto auto',
|
|
startDate: '@Constants.MIN_SELECTABLE_DATE',
|
|
endDate: '@Constants.MAX_SELECTABLE_DATE'
|
|
};
|
|
|
|
var pickerRangeSettings = {
|
|
datePickerOptions: options2,
|
|
multiple: !$('#@Html.IdFor(x=> x.Permanent)').is(':checked')
|
|
};
|
|
$('#bs-datepicker-np-time-range').datePickerRange(pickerRangeSettings).on('changeDate', function (evt) {
|
|
var _sDate = new Date($('#@Html.IdFor(model => model.NonProjectTimeStartDate)').val());
|
|
if (_sDate.getTime()){
|
|
$('#@Html.IdFor(model => model.NonProjectTimeEndDate)').data('datepicker').setStartDate(_sDate);
|
|
var effectiveDateOfChange = $('#@Html.IdFor(model => model.EffectiveDateOfChange)').data('datepicker');
|
|
if (effectiveDateOfChange){
|
|
var isPermanent = $('#@Html.IdFor(x=> x.Permanent)').is(':checked');
|
|
if (isPermanent){
|
|
var effectiveDateOfChangeValue = effectiveDateOfChange.getDate();
|
|
effectiveDateOfChange.setStartDate(_sDate);
|
|
if (effectiveDateOfChangeValue && effectiveDateOfChangeValue.getTime() && effectiveDateOfChangeValue < _sDate)
|
|
effectiveDateOfChange.setDate(_sDate);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
$('#@Html.IdFor(x=> x.EffectiveDateOfChange)').datepicker($.extend(true, options2, {startDate: '@Model.NonProjectTimeStartDate.ToShortDateString()'}));
|
|
$('#@Html.IdFor(model => model.NonProjectTimeStartDate)').data('prev-value', getLocal(@Utils.ConvertToUnixDate(Model.NonProjectTimeStartDate))).change(onDateChange);
|
|
$('#@Html.IdFor(model => model.NonProjectTimeEndDate)').data('prev-value', getLocal(@(Model.NonProjectTimeEndDate.HasValue ? Utils.ConvertToUnixDate(Model.NonProjectTimeEndDate.Value) : (long?)null))).change(onDateChange);
|
|
$('#@Html.IdFor(model => model.NonProjectTimeEndDate)').data('datepicker').setStartDate((new Date('@Model.NonProjectTimeStartDate.ToString("yyyy-MM-dd")')));
|
|
|
|
resetSTDataChanged();
|
|
}
|
|
|
|
function setAssignmentBlockState() {
|
|
var isTeamMode = $('#@Html.IdFor(x=> x.IsTeamAssignmentMode)').is(':checked');
|
|
|
|
if (isTeamMode) {
|
|
$("#nptResourcesBlock").hide();
|
|
$("#nptTeamsBlock").show();
|
|
}
|
|
else {
|
|
$("#nptTeamsBlock").hide();
|
|
$("#nptResourcesBlock").show();
|
|
}
|
|
}
|
|
|
|
function onDateChange(ctrl, e) {
|
|
var startDateCtrl = $('#@Html.IdFor(model => model.NonProjectTimeStartDate)'),
|
|
endDateCtrl = $('#@Html.IdFor(model => model.NonProjectTimeEndDate)');
|
|
|
|
var startDate = new Date(startDateCtrl.val()),
|
|
prevStartDate = new Date(startDateCtrl.data('prev-value')),
|
|
endDate = new Date(endDateCtrl.val()),
|
|
prevEndDate = new Date(endDateCtrl.data('prev-value'));
|
|
|
|
if ((startDate.getTime() || 0) == (prevStartDate.getTime() || 0) &&
|
|
(endDate.getTime() || 0) == (prevEndDate.getTime() || 0)) {
|
|
return;
|
|
}
|
|
|
|
startDateCtrl.data('prev-value', startDate.getTime());
|
|
endDateCtrl.data('prev-value', endDate.getTime());
|
|
|
|
refreshDuration();
|
|
fillNonProjectTimeWeeksGrid(true);
|
|
}
|
|
function refreshDuration() {
|
|
var isPermanent = $('#@Html.IdFor(x=> x.Permanent)').is(':checked');
|
|
if (isPermanent) {
|
|
$('#@Html.IdFor(model => model.NonProjectTimeDuration)').val('Permanent');
|
|
}
|
|
else {
|
|
var startDateCtrl = $('#@Html.IdFor(model => model.NonProjectTimeStartDate)'),
|
|
endDateCtrl = $('#@Html.IdFor(model => model.NonProjectTimeEndDate)');
|
|
|
|
$('#@Html.IdFor(model => model.NonProjectTimeDuration)').val(formatDateRange(startDateCtrl.val(), endDateCtrl.val(), true));
|
|
}
|
|
};
|
|
function onDependentChange(event, ui) {
|
|
timeOffChanged(this, parseInt(ui.value));
|
|
};
|
|
function hoursChanged(control){
|
|
var hours = parseInt($(control).val()) || 0;
|
|
timeOffChanged(control, hours);
|
|
};
|
|
function timeOffChanged(control, value){
|
|
var weekEnding = new Date($(control).parent("td").children(".weekending-hidden").val()),
|
|
weekEndingUtc = weekEnding.getTime();
|
|
|
|
updateDistributionValue(weekEndingUtc, value);
|
|
$(control).children(".sliderValue").val(value);
|
|
var percentVal = currentDistribution[weekEndingUtc].PercentOff;
|
|
$(control).children(".sliderTitle").html(percentVal + "%");
|
|
onSTDataChanged();
|
|
};
|
|
function updateSliderTitles() {
|
|
$(".slider-container").each(function (i, e) {
|
|
var weekEnding = new Date($(e).parent("td").children(".weekending-hidden").val()),
|
|
weekEndingUtc = weekEnding.getTime();
|
|
var percentVal = currentDistribution[weekEndingUtc].PercentOff;
|
|
$(e).children('.sliderTitle').html(percentVal + "%");
|
|
});
|
|
}
|
|
|
|
function onFailure(xhr) {
|
|
showErrorModal();
|
|
}
|
|
|
|
function onSuccess(result) {
|
|
handleAjaxResponse(result, function() {
|
|
onSuccessCallback(result.Content);
|
|
}, null, null, $('#npTimeForm'));
|
|
}
|
|
|
|
function onSuccessCallback(data) {
|
|
var nptCompletlyBeforeResourceStartDate = @((int)NonProjectTimeDatesCheckResultModel.ViolationType.NptCompletlyBeforeResourceStartDate);
|
|
var nptCompletlyAfterResourceEndDate = @((int)NonProjectTimeDatesCheckResultModel.ViolationType.NptCompletlyAfterResourceEndDate);
|
|
var nptPartiallyBeforeResourceStartDate = @((int)NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyBeforeResourceStartDate);
|
|
var nptPartiallyAfterResourceEndDate = @((int)NonProjectTimeDatesCheckResultModel.ViolationType.NptPartiallyAfterResourceEndDate);
|
|
var nptOutBothResourceDates = @((int)NonProjectTimeDatesCheckResultModel.ViolationType.NptOutBothResourceDates);
|
|
var notMemberOfAnyTeam = @((int)NonProjectTimeDatesCheckResultModel.ViolationType.NotMemberOfAnyTeam);
|
|
var isInactive = @((int)NonProjectTimeDatesCheckResultModel.ViolationType.IsInactive);
|
|
|
|
var isTeamMode = $('#@Html.IdFor(x=> x.IsTeamAssignmentMode)').is(':checked');
|
|
|
|
if (data && data.HasViolation && data.Teams && (data.Teams.length > 0)) {
|
|
var publishTeamNames = data.Teams.length > 1;
|
|
var message = "";
|
|
|
|
if (isTeamMode) {
|
|
message = "The scheduled Non-Project Time does not occur between the Start Date and End Date for the following resource(s), and will only be applied to the active date range for each resource";
|
|
}
|
|
else {
|
|
message = "The scheduled Non-Project Time does not occur between the Start Date and End Date for this resource, and will only be applied to their active date range";
|
|
}
|
|
|
|
var resources = "";
|
|
|
|
for(var tIndex = 0; tIndex < data.Teams.length; tIndex++) {
|
|
var currentTeam = data.Teams[tIndex];
|
|
|
|
if (publishTeamNames)
|
|
resources += ("<br/></br/>Team '" + currentTeam.Name + "':");
|
|
else
|
|
resources += ("<br/>");
|
|
|
|
for(var rIndex = 0; rIndex < currentTeam.Resources.length; rIndex++) {
|
|
var currResource = currentTeam.Resources[rIndex];
|
|
var currResourceText = '<span style="margin-left: {2}px"><strong>{0}</strong> ({1})</span>';
|
|
var violationText = "";
|
|
|
|
switch (currResource.ViolationType) {
|
|
case nptCompletlyBeforeResourceStartDate:
|
|
violationText = "NPT occurs before the resource Start Date";
|
|
break;
|
|
|
|
case nptCompletlyAfterResourceEndDate:
|
|
violationText = "NPT occurs after the resource End Date";
|
|
break;
|
|
|
|
case nptPartiallyBeforeResourceStartDate:
|
|
violationText = "NPT occurs before the resource Start Date";
|
|
break;
|
|
|
|
case nptPartiallyAfterResourceEndDate:
|
|
violationText = "NPT occurs after the resource End Date";
|
|
break;
|
|
|
|
case nptOutBothResourceDates:
|
|
violationText = "NPT occurs before and finishes after the resource dates";
|
|
break;
|
|
|
|
case notMemberOfAnyTeam:
|
|
violationText = "resource is not member of any team";
|
|
break;
|
|
|
|
case isInactive:
|
|
violationText = "resource is inactive";
|
|
break;
|
|
}
|
|
|
|
currResourceText = currResourceText.replace("{0}", currResource.Name).replace("{1}", violationText)
|
|
.replace("{2}", publishTeamNames ? "18": "0");
|
|
resources += ("<br/>" + currResourceText);
|
|
}
|
|
}
|
|
|
|
message += resources;
|
|
|
|
bootbox.alert(message, function () {
|
|
finishNptSavingAction();
|
|
});
|
|
}
|
|
else{
|
|
finishNptSavingAction();
|
|
}
|
|
}
|
|
|
|
function finishNptSavingAction() {
|
|
// SA: reloadPage() performs redirect to url, stored in ContentLocker. ContentLocker gets url,
|
|
// when lock is set. But for new items no any lock is set, so ContentLocker doesn't have url
|
|
// to reload page. So, for newly created NPT we should simply reload browser window
|
|
if (!isNew)
|
|
reloadPage();
|
|
else
|
|
window.location.reload();
|
|
}
|
|
|
|
function fillNonProjectTimeWeeksGrid(needToRecalculate, needToApplyAllocations) {
|
|
if (needToRecalculate){
|
|
currentDistribution = {};
|
|
}
|
|
|
|
if (needToRecalculate || needToApplyAllocations) {
|
|
var isPermanent = $('#@Html.IdFor(x=> x.Permanent)').is(':checked'),
|
|
startDate = new Date($('#@Html.IdFor(model => model.NonProjectTimeStartDate)').val()),
|
|
startDateUTC = getUTC(startDate),
|
|
endDate = new Date($('#@Html.IdFor(model => model.NonProjectTimeEndDate)').val()),
|
|
endDateUTC = getUTC(endDate);
|
|
|
|
if (!isPermanent) {
|
|
if (!startDateUTC || !endDateUTC) {
|
|
$("#nptime-allocations").html("<tr><td colspan=\"2\">Please select Start and End dates for Non-Project Time</td></tr>");
|
|
return;
|
|
}
|
|
} else {
|
|
if (!startDateUTC) {
|
|
$("#nptime-allocations").html("<tr><td colspan=\"2\">Please select Start date for Non-Project Time</td></tr>");
|
|
return;
|
|
}
|
|
}
|
|
|
|
blockUI();
|
|
|
|
var isTeamMode = $('#@Html.IdFor(x=> x.IsTeamAssignmentMode)').is(':checked');
|
|
var resourceIds, teamIds;
|
|
if (isTeamMode)
|
|
teamIds = $("#@Html.IdFor(t=>t.TeamsAsText)").select2('val')
|
|
else
|
|
resourceIds = $("#@Html.IdFor(t=>t.ResourcesAsText)").select2('val')
|
|
$.post('/PeopleResource/RecalculateNPTimeAllocations', {
|
|
startDate: startDateUTC,
|
|
endDate: endDateUTC,
|
|
resourceIds: resourceIds,
|
|
teamIds: teamIds
|
|
}).done(function(data, textStatus, jqXHR){
|
|
currentDistribution = data || {};
|
|
if (needToApplyAllocations)
|
|
{
|
|
for(var key in currentDistribution)
|
|
{
|
|
var alloc = nptAllocations[key];
|
|
if (alloc)
|
|
{
|
|
if (alloc.HoursOff == 0)
|
|
{
|
|
currentDistribution[key].PercentOff = currentDistribution[key].HoursOff = 0;
|
|
} else {
|
|
if (currentDistribution[key].UOMValue == 0)
|
|
currentDistribution[key].PercentOff = currentDistribution[key].HoursOff = 0;
|
|
else
|
|
{
|
|
currentDistribution[key].PercentOff = Math.round(alloc.HoursOff * 100 / currentDistribution[key].UOMValue);
|
|
currentDistribution[key].HoursOff = alloc.HoursOff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
redrawNPTimeGrid();
|
|
}).always(unblockUI);
|
|
}
|
|
else{
|
|
redrawNPTimeGrid();
|
|
}
|
|
}
|
|
function redrawNPTimeGrid(){
|
|
if (!currentDistribution)
|
|
return;
|
|
|
|
var isPercentMode = $('#@Html.IdFor(x=> x.IsPercentsMode)').is(':checked');
|
|
var isPermanent = $('#@Html.IdFor(x=> x.Permanent)').is(':checked');
|
|
var keys = Object.keys(currentDistribution);
|
|
var content = "";
|
|
for (var i = 0; i<keys.length; i++) {
|
|
var weekDistribution = currentDistribution[keys[i]];
|
|
var row = drawNonProjectTimeWeeksGridRow(isPercentMode, isPermanent, i+1, weekDistribution.StartDate, weekDistribution.EndDate, weekDistribution.HoursOff || 0, weekDistribution.UOMValue);
|
|
if (row){
|
|
content+=row.prop('outerHTML');
|
|
}
|
|
}
|
|
|
|
$("#nptime-allocations").html(content);
|
|
if (isPercentMode) {
|
|
$('.slider-container').each(function (i, e) {
|
|
var weekEnding = new Date($(e).parent("td").children(".weekending-hidden").val()),
|
|
weekEndingUtc = weekEnding.getTime();
|
|
$(e).slider({
|
|
'range': 'min',
|
|
'min': 0,
|
|
'max': currentDistribution[weekEndingUtc].UOMValue || 0,
|
|
'value': $(e).children('.sliderValue').val(),
|
|
change: onDependentChange,
|
|
slide: onDependentChange
|
|
});
|
|
if(("@Model.IsHistory" == "True") || !((currentDistribution[weekEndingUtc].UOMValue || 0) > 0))
|
|
$(e).slider("disable");
|
|
});
|
|
updateSliderTitles();
|
|
}
|
|
}
|
|
function drawNonProjectTimeWeeksGridRow(isPercentMode, isPermanent, weekNo, startDate, endDate, hoursValue, uomValue) {
|
|
var localWeekStart = new Date(getLocal(startDate));
|
|
var localWeekEnding = new Date(getLocal(endDate));
|
|
var weekStartStrUTC = formatUniversalDate(localWeekStart);
|
|
var weekEndingStrUTC = formatUniversalDate(localWeekEnding);
|
|
|
|
var disabled = ("@Model.IsHistory" == "True" || !((uomValue || 0) > 0)) ? "disabled" : "";
|
|
|
|
var hoursOffTD = $("<td style='width: 235px; max-width: 235px; height: 43px; max-height: 43px;'></td>");
|
|
hoursOffTD.append("<input type=\"hidden\" name=\"NonProjectTimeAllocations.Index\" value=\"" + weekNo + "\"/>");
|
|
hoursOffTD.append("<input type=\"hidden\" name=\"NonProjectTimeAllocations[" + weekNo + "].WeekStartDate\" value=\"" + weekStartStrUTC + "\"/>");
|
|
hoursOffTD.append("<input type=\"hidden\" class=\"weekending-hidden\" name=\"NonProjectTimeAllocations[" + weekNo + "].WeekEndingDate\" value=\"" + weekEndingStrUTC + "\"/>");
|
|
|
|
if (isPercentMode) {
|
|
var sliderDiv = $("<div class=\"ui-slider-compacttablecell slider-container\" id=\"sldrcntr_" + weekNo + "\"></div>");
|
|
sliderDiv.append("<input type=\"hidden\" name=\"NonProjectTimeAllocations[" + weekNo + "].HoursOff\" class=\"sliderValue\" value=\"" + hoursValue + "\" /><span class=\"sliderTitle\"></span>");
|
|
hoursOffTD.append(sliderDiv);
|
|
}
|
|
else {
|
|
var hoursInput = '<input ' + disabled + ' class="form-control txtHoursOff" onchange="hoursChanged(this);" data-val="true" data-val-range="The field Time Off must be more than 0." data-val-range-max="2147483647" data-val-range-min="0" data-val-number="The field Time Off must be a number." data-val-required="The field Time Off is required." name="NonProjectTimeAllocations[' + weekNo + '].HoursOff" type="text" value="' + hoursValue + '">'
|
|
hoursOffTD.append(hoursInput);
|
|
}
|
|
|
|
// create row and append content to it
|
|
var startStr = (localWeekStart.getMonth() + 1) + '/' + localWeekStart.getDate() + '/' + localWeekStart.getFullYear(),
|
|
endStr = !isPermanent ? (localWeekEnding.getMonth() + 1) + '/' + localWeekEnding.getDate() + '/' + localWeekEnding.getFullYear() : "";
|
|
|
|
var row = $('<tr></tr>').append($("<td></td>").text(weekNo + ": " + startStr + ' - ' + endStr))
|
|
.append(hoursOffTD);
|
|
|
|
return row;
|
|
}
|
|
function updateDistributionValue(key, value) {
|
|
// cehck that value is number
|
|
if (!key || isNaN(value = parseInt(value)))
|
|
return;
|
|
currentDistribution[key].HoursOff = value;
|
|
currentDistribution[key].PercentOff = getPercentValue(value, currentDistribution[key].UOMValue);
|
|
};
|
|
function getUTC(date){
|
|
date = new Date(date);
|
|
if (isNaN(date.getTime()) || date.getTime() <= 0)
|
|
return null;
|
|
|
|
return date.getTime() - (date.getTimezoneOffset() * 60 * 1000);
|
|
};
|
|
function getLocal(date){
|
|
date = new Date(date);
|
|
if (isNaN(date.getTime()) || date.getTime() <= 0)
|
|
return null;
|
|
|
|
return date.getTime() + (date.getTimezoneOffset() * 60 * 1000);
|
|
};
|
|
function getPercentValue(hoursValue, maxUOM)
|
|
{
|
|
return Math.min(Math.round(hoursValue * 100.0 / maxUOM), 100);
|
|
}
|
|
</script>
|
|
@using (Ajax.BeginForm("ScheduleNonProjectTime", "PeopleResource", null, new AjaxOptions { HttpMethod = "Post", OnBegin = "blockUI", OnSuccess = "onSuccess", OnFailure = "onFailure(xhr)", OnComplete = "unblockUI" }, new { id = "npTimeForm" }))
|
|
{
|
|
@Html.AntiForgeryToken()
|
|
@Html.HiddenFor(model => model.Id)
|
|
@Html.HiddenFor(t => t.NonProjectTimeCategoryName)
|
|
@Html.HiddenFor(t => t.TeamId)
|
|
@Html.HiddenFor(x => x.NonProjectTimeStartDateOld)
|
|
@Html.HiddenFor(x => x.PermanentOld)
|
|
|
|
<div class="modal-header">
|
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
|
|
<h4 class="modal-title">Schedule Non-Project Time</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
@Html.LabelFor(model => model.NonProjectTimeName, new { @class = "col-sm-3 control-label" })
|
|
<div class="col-sm-9">
|
|
@Html.TextBoxFor(model => model.NonProjectTimeName, new { @class = "form-control" })
|
|
@Html.ValidationMessageFor(model => model.NonProjectTimeName)
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<div class="col-sm-9">
|
|
@Html.CheckBoxFor(model => model.Permanent, new { @class = "form-control" })
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="control-label">Dates</label>
|
|
<div style="overflow: hidden;">
|
|
<div class="input-daterange input-group" id="bs-datepicker-np-time-range">
|
|
@Html.EditorFor(model => model.NonProjectTimeStartDate)
|
|
<div class="input-group-addon">to</div>
|
|
@Html.EditorFor(model => model.NonProjectTimeEndDate)
|
|
</div>
|
|
@Html.ValidationMessageFor(model => model.NonProjectTimeStartDate)
|
|
@Html.ValidationMessageFor(model => model.NonProjectTimeEndDate)
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-2">
|
|
</div>
|
|
<div class="col-sm-4">
|
|
<div class="form-group">
|
|
@Html.LabelFor(model => model.NonProjectTimeDuration, new { @class = "col-sm-3 control-label" })
|
|
@Html.TextBoxFor(model => model.NonProjectTimeDuration, new { @class = "form-control", Readonly = "true" })
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<div class="col-sm-6">
|
|
<div class="form-group">
|
|
<div>
|
|
@Html.LabelFor(model => model.NonProjectTimeCategoryId, new { @class = "control-label" })
|
|
@Html.EditorFor(model => model.NonProjectTimeCategoryId)
|
|
@Html.ValidationMessageFor(model => model.NonProjectTimeCategoryId)
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-2">
|
|
</div>
|
|
<div class="form-group col-sm-4">
|
|
@Html.LabelFor(model => model.NonProjectTimeCost, new { @class = "control-label" })
|
|
<div class="input-group">
|
|
<span class="input-group-addon">$</span>
|
|
@Html.TextBox("NonProjectTimeCost", Model.NonProjectTimeCost.ToCurrencyString(), new { @class = "form-control", style = "width:125px;" })
|
|
@Html.ValidationMessageFor(model => model.NonProjectTimeCost)
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
@Html.LabelFor(x => x.IsTeamAssignmentMode, new { @class = "col-sm-3 control-label" })
|
|
<div class="col-sm-9">
|
|
@Html.CheckBoxFor(model => model.IsTeamAssignmentMode, new { @class = "form-control" })
|
|
</div>
|
|
</div>
|
|
<div class="form-group" id="nptResourcesBlock">
|
|
@Html.LabelFor(model => model.ResourcesAsText, new { @class = "col-sm-3 control-label" })
|
|
<div class="col-sm-9 select2-primary">
|
|
@Html.TextBoxFor(model => model.ResourcesAsText, new { @class = "form-control" })
|
|
@Html.ValidationMessageFor(model => model.ResourcesAsText)
|
|
</div>
|
|
</div>
|
|
<div class="form-group" id="nptTeamsBlock">
|
|
@Html.LabelFor(model => model.TeamsAsText, new { @class = "col-sm-3 control-label" })
|
|
<div class="col-sm-9 select2-primary">
|
|
@Html.TextBoxFor(model => model.TeamsAsText, new { @class = "form-control" })
|
|
@Html.ValidationMessageFor(model => model.TeamsAsText)
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
@Html.LabelFor(x => x.IsPercentsMode, new { @class = "col-sm-3 control-label" })
|
|
<div class="col-sm-9">
|
|
@Html.CheckBoxFor(model => model.IsPercentsMode, new { @class = "form-control" })
|
|
</div>
|
|
</div>
|
|
@if (Model != null && Model.Id != Guid.Empty && Model.Permanent)
|
|
{
|
|
<div class="form-group" id="effective-date-container">
|
|
@Html.LabelFor(x => x.EffectiveDateOfChange, new { @class = "col-sm-3 control-label" })
|
|
<div class="col-sm-9">
|
|
@Html.EditorFor(model => model.EffectiveDateOfChange)
|
|
@Html.ValidationMessageFor(model => model.EffectiveDateOfChange)
|
|
</div>
|
|
</div>
|
|
}
|
|
@* TODO: uncomment this section after Advanced Mode is completed *@
|
|
@*@if (Model != null && Model.Permanent && !Model.IsCurveConstant)
|
|
{
|
|
<div class="form-group">
|
|
<div class="alert alert-warning">
|
|
Changes have been made to the Non-Project time curve and it is now not evenly distributed - please use Advanced Mode for precise settings
|
|
</div>
|
|
</div>
|
|
}*@
|
|
<div id="hours-mode-message" class="form-group @(Model != null && !Model.IsPercentsMode ? "" : "hidden")">
|
|
<div class="alert alert-info">
|
|
If number of hours exceeds Resource's available hours, total available hours will be used as Non Project Time for a given Week and Resource
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<table id="npTimeWeeksGrid" class="table table-light table-striped dataTable-tight col-sm-12">
|
|
<thead>
|
|
<tr>
|
|
<th class="autowidth">Week</th>
|
|
<th class="hugeCol">Time off</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="nptime-allocations"></tbody>
|
|
</table>
|
|
</div>
|
|
<div class="form-group">
|
|
@Html.LabelFor(model => model.Details, new { @class = "col-sm-3 control-label" })
|
|
<div class="col-sm-9 select2-primary">
|
|
@Html.TextAreaFor(model => model.Details, new { @class = "form-control", rows = 4 })
|
|
@Html.ValidationMessageFor(model => model.Details)
|
|
</div>
|
|
</div>
|
|
|
|
@Html.ValidationSummary(false, "The non-project time schedule could not be saved due to the following errors:")
|
|
</div> <!-- / .modal-body -->
|
|
<div class="modal-footer">
|
|
<button id="btnSaveNPTime" type="submit" class="btn btn-success">Save</button>
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
|
</div>
|
|
} |