1153 lines
52 KiB
C#
1153 lines
52 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Data.Entity;
|
|
using System.Data.Entity.Infrastructure;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Web;
|
|
using System.Web.Mvc;
|
|
using EnVisage;
|
|
using EnVisage.Code;
|
|
using EnVisage.Code.BLL;
|
|
using EnVisage.Code.HtmlHelpers;
|
|
using EnVisage.Models;
|
|
using jQuery.DataTables.Mvc;
|
|
using EnVisage.App_Start;
|
|
using Microsoft.AspNet.Identity;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using FileHelpers;
|
|
using System.Web.Script.Serialization;
|
|
using EnVisage.Code.Cache;
|
|
|
|
namespace EnVisage.Controllers
|
|
{
|
|
[Authorize]
|
|
public class ProjectController : BaseController
|
|
{
|
|
internal class PreferenceItem
|
|
{
|
|
public string Key { get; set; }
|
|
public object Value { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// GET: /Project/
|
|
/// </summary>
|
|
/// <returns>Empty view</returns>
|
|
[HttpGet]
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Read)]
|
|
public ActionResult Index()
|
|
{
|
|
if (!SecurityManager.CheckSecurityObjectPermission(Areas.Projects, AccessLevel.Read))
|
|
return Redirect("/");
|
|
return View();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns JSON project list with filters and sort for jQuery DataTables
|
|
/// </summary>
|
|
[HttpPost]
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Read)]
|
|
public JsonResult Index(JQueryDataTablesModel jQueryDataTablesModel)
|
|
{
|
|
int totalRecordCount;
|
|
int searchRecordCount;
|
|
|
|
var clients = GetProjects(startIndex: jQueryDataTablesModel.iDisplayStart,
|
|
pageSize: jQueryDataTablesModel.iDisplayLength, sortedColumns: jQueryDataTablesModel.GetSortedColumns(),
|
|
totalRecordCount: out totalRecordCount, searchRecordCount: out searchRecordCount, searchString: jQueryDataTablesModel.sSearch);
|
|
|
|
return this.DataTablesJson(items: clients,
|
|
totalRecords: totalRecordCount,
|
|
totalDisplayRecords: searchRecordCount,
|
|
sEcho: jQueryDataTablesModel.sEcho);
|
|
|
|
}
|
|
private string GetJson(Scenario scenario)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
private IList<ProjectListModel> GetProjects(int startIndex,
|
|
int pageSize,
|
|
ReadOnlyCollection<SortedColumn> sortedColumns,
|
|
out int totalRecordCount,
|
|
out int searchRecordCount,
|
|
string searchString)
|
|
{
|
|
var userId = SecurityManager.GetUserPrincipal();
|
|
var user = new UsersCache().Value.FirstOrDefault(x => x.Id == userId);
|
|
string _ref = user.GetPreferences(this.Url.Action("Index", "Project"), "projectsTable");
|
|
var groupByTeam = false;
|
|
if (!string.IsNullOrEmpty( _ref))
|
|
{
|
|
var oprions = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PreferenceItem>>(_ref);
|
|
var option = oprions.FirstOrDefault(x => x.Key.Equals("groupByTeam"));
|
|
if (option != null)
|
|
{
|
|
groupByTeam = (bool)option.Value;
|
|
}
|
|
}
|
|
|
|
var projectsToDisplay = (new ProjectManager(DbContext)).GetProjects4User(userId, groupByTeam, sortedColumns[0], startIndex, pageSize, searchString, out totalRecordCount, out searchRecordCount);
|
|
|
|
var projectsIds = projectsToDisplay.Select(x => x.Id);
|
|
var partsIds = projectsToDisplay.Where(x => x.ProjectParts != null).SelectMany(x => x.ProjectParts).Where(x => x != null).Select(x => x.Id);
|
|
var projectsWithPartsIds = projectsIds.Union(partsIds).ToList();
|
|
|
|
var inactiveScenarios =
|
|
DbContext.Scenarios.Where(s => s.Type == (int)ScenarioType.Portfolio &&
|
|
s.Status == (int)ScenarioStatus.Inactive &&
|
|
s.ParentId.HasValue &&
|
|
projectsWithPartsIds.Contains(s.ParentId.Value))
|
|
.Select(x => new
|
|
{
|
|
ProjectId = x.ParentId.Value,
|
|
Scenario = new ScenarioInProjectModel()
|
|
{
|
|
Id = x.Id,
|
|
Name = x.Name
|
|
}
|
|
}).ToList().GroupBy(x => x.ProjectId).ToDictionary(x => x.Key, g => g.Select(x => x.Scenario).ToList());
|
|
|
|
foreach (var projItem in projectsToDisplay)
|
|
{
|
|
if (inactiveScenarios.ContainsKey(projItem.Id))
|
|
projItem.InactiveScenarios = inactiveScenarios[projItem.Id];
|
|
|
|
if ((projItem.ProjectParts != null) && projItem.ProjectParts.Count > 0)
|
|
{
|
|
foreach (var projPartItem in projItem.ProjectParts)
|
|
{
|
|
if (inactiveScenarios.ContainsKey(projPartItem.Id))
|
|
projPartItem.InactiveScenarios = inactiveScenarios[projPartItem.Id];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return projectsToDisplay;
|
|
}
|
|
|
|
|
|
// GET: /Project/Details/5
|
|
[HttpGet]
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Read)]
|
|
public ActionResult Details(Guid? id)
|
|
{
|
|
if (id == null || id == Guid.Empty)
|
|
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
|
|
if (!SecurityManager.CheckProjectPermission(id.Value, AccessLevel.Read))
|
|
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
|
|
|
|
var model = new ProjectModel();
|
|
try
|
|
{
|
|
var manager = new ProjectManager(DbContext);
|
|
model = (ProjectModel)manager.Load(id) ?? new ProjectModel();
|
|
if (model.Id == Guid.Empty)
|
|
return HttpNotFound();
|
|
}
|
|
catch (BLLException blEx)
|
|
{
|
|
if (blEx.DisplayError)
|
|
SetErrorScript(message: blEx.Message);
|
|
else
|
|
{
|
|
LogException(blEx);
|
|
SetErrorScript();
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
LogException(exception);
|
|
SetErrorScript();
|
|
}
|
|
return PartialView("_details", model);
|
|
}
|
|
|
|
[HttpGet]
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Write)]
|
|
public ActionResult Copy(Guid? id)
|
|
{
|
|
if (id != null && id != Guid.Empty)
|
|
if (!SecurityManager.CheckProjectPermission(id.Value, AccessLevel.Write))
|
|
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
|
|
|
|
var model = new ProjectModel();
|
|
try
|
|
{
|
|
var manager = new ProjectManager(DbContext);
|
|
model = (ProjectModel)manager.Load(id) ?? new ProjectModel();
|
|
|
|
var projectStatus = DbContext.Status.FirstOrDefault(x => x.Id == model.StatusId);
|
|
if (projectStatus != null)
|
|
{
|
|
if (projectStatus.Probability100)
|
|
model.Probability = 100;
|
|
ViewBag.IsProbability100 = projectStatus.Probability100;
|
|
}
|
|
else
|
|
{
|
|
ViewBag.IsProbability100 = false;
|
|
}
|
|
|
|
var statuses = DbContext.Status.Select(x => new { Id = x.Id, Probability100 = x.Probability100 }).ToList();
|
|
ViewBag.Statuses = new JavaScriptSerializer().Serialize(statuses);
|
|
}
|
|
catch (BLLException blEx)
|
|
{
|
|
if (blEx.DisplayError)
|
|
SetErrorScript(message: blEx.Message);
|
|
else
|
|
{
|
|
LogException(blEx);
|
|
SetErrorScript();
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
LogException(exception);
|
|
SetErrorScript();
|
|
}
|
|
model.SaveAsCopy = true;
|
|
return Edit(model);
|
|
}
|
|
|
|
// GET: /Project/Edit/5
|
|
[HttpGet]
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Write)]
|
|
public ActionResult Edit(Guid? id, Guid? partId, string backUrl, string backName)
|
|
{
|
|
if (id != null && id != Guid.Empty)
|
|
if (!SecurityManager.CheckProjectPermission(id.Value, AccessLevel.Write))
|
|
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
|
|
|
|
var model = new ProjectModel();
|
|
try
|
|
{
|
|
var manager = new ProjectManager(DbContext);
|
|
model = (ProjectModel)manager.Load(id) ?? new ProjectModel();
|
|
if (model.ParentProjectId != null && model.ParentProjectId != Guid.Empty)
|
|
{
|
|
model = (ProjectModel)manager.Load(model.ParentProjectId);
|
|
model.PartForScenarioId = id;
|
|
}
|
|
if (id == null || Guid.Empty.Equals(id))
|
|
{
|
|
model.ClientId = Guid.Empty;
|
|
model.Priority = 1;
|
|
}
|
|
else
|
|
model.ExpandedPartId = partId;
|
|
model.BackUrl = !string.IsNullOrEmpty(backUrl) ? backUrl : Url.Action("Index", "Project");
|
|
model.BackName = !string.IsNullOrEmpty(backName) ? backName : "projects";
|
|
|
|
var statuses = DbContext.Status.Select(x => new { Id = x.Id, Probability100 = x.Probability100 }).ToList();
|
|
ViewBag.Statuses = new JavaScriptSerializer().Serialize(statuses);
|
|
}
|
|
catch (BLLException blEx)
|
|
{
|
|
if (blEx.DisplayError)
|
|
SetErrorScript(message: blEx.Message);
|
|
else
|
|
{
|
|
LogException(blEx);
|
|
SetErrorScript();
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
LogException(exception);
|
|
SetErrorScript();
|
|
}
|
|
return View(model);
|
|
}
|
|
|
|
// POST: /Project/Edit/5
|
|
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
|
|
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Write)]
|
|
public ActionResult Edit(ProjectModel model)
|
|
{
|
|
if (model == null || ContentLocker.IsLock("Project", model.Id.ToString(), User.Identity.Name))
|
|
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
|
|
if (model.Id != Guid.Empty)
|
|
if (!SecurityManager.CheckProjectPermission(model.Id, AccessLevel.Write))
|
|
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
|
|
|
|
#region trim model and remove incorrect values
|
|
model.TrimStringProperties();
|
|
|
|
//if user converted ordinary project to project with parts we need to convert old project to a "part" and create a parent project for it
|
|
//so basically we need to set project model's first part a successor of project that now became project with parts so all scenarios, permissions, etc. will be "reassigned" to the first part
|
|
//technically we just need to assign project ID to the first part and drop the ID of project + remove CompanyId, Projectnumber and Color from newly "added" part (done by ProjectPartModel itself)
|
|
if (model.Id != Guid.Empty && model.InitialHasChildrenState == false && model.HasChildren == true)
|
|
{
|
|
if (model.Parts.Count == 0)
|
|
throw new InvalidOperationException();
|
|
|
|
model.Parts[0].Id = model.Id;
|
|
model.Parts[0].OldId = model.Id;
|
|
model.Id = Guid.Empty;
|
|
model.InitialHasChildrenState = model.HasChildren;
|
|
|
|
//also need to drop ParentProjectId from all parts
|
|
foreach (ProjectPartModel pp in model.Parts)
|
|
pp.ParentProjectId = Guid.Empty;
|
|
}
|
|
|
|
// if user clicked "Generate Template" then save a project as new and mark it as IsTemplate=true
|
|
var isCreateCopy = model.SaveAsCopy;
|
|
var oldId = Guid.Empty;
|
|
var model2Save = (ProjectModel)model.Clone();
|
|
if (isCreateCopy)
|
|
{
|
|
oldId = model.Id;
|
|
model2Save.Parts = model.Parts.Select(p => p.Clone()).ToList();
|
|
model2Save.Id = Guid.Empty;
|
|
model2Save.Name += " - Copy";
|
|
model2Save.Parts.ToList().ForEach(p => p.Id = Guid.Empty);
|
|
model2Save.Parts.ToList().ForEach(p => p.ParentProjectId = Guid.Empty);
|
|
}
|
|
model2Save.ClientId = null;
|
|
var revalidationRequired = false;
|
|
for (var i = 0; i < model2Save.Parts.Count; i++)
|
|
{
|
|
if (model2Save.Parts[i].DeletedPart)
|
|
{
|
|
model.Parts.RemoveAt(i);
|
|
revalidationRequired = true;
|
|
}
|
|
}
|
|
foreach (var part in model2Save.Parts)
|
|
{
|
|
if (part.InternalContacts != null)
|
|
part.InternalContacts.RemoveAll(t => t == Guid.Empty);
|
|
if (part.ExternalContacts != null)
|
|
part.ExternalContacts.RemoveAll(t => t == Guid.Empty);
|
|
}
|
|
//TODO: temporary solution, we need to check for Guid.Empty.Equals(model2Save.Id). Should be replaced when fix the bug in BaseManager.Save method.
|
|
var isNew = Guid.Empty.Equals(model2Save.Id);
|
|
if (revalidationRequired)
|
|
{
|
|
ModelState.Clear();
|
|
TryValidateModel(model);
|
|
}
|
|
#endregion
|
|
|
|
#region Check all deleted teams can be removed from scenarios
|
|
// SA. ENV-754. Begin
|
|
ScenarioManager scenarioManager = new ScenarioManager(DbContext);
|
|
Dictionary<Guid, List<Guid>> projectScenarios = new Dictionary<Guid, List<Guid>>();
|
|
Dictionary<Guid, List<Guid>> newProjectTeamList = new Dictionary<Guid, List<Guid>>();
|
|
|
|
//TODO: check if we need to bypass this when project is being copied (!isCreateCopy) as we may get a copied project with incorrect permission set
|
|
if (!isNew && (model2Save.Parts.Count > 0) && !isCreateCopy)
|
|
{
|
|
for (int partIndex = 0; partIndex < model.Parts.Count; partIndex++)
|
|
{
|
|
ProjectPartModel part = model.Parts[partIndex];
|
|
Guid partId = part.Id;
|
|
|
|
if (!model.HasChildren && part.Id.Equals(Guid.Empty))
|
|
// If project has no parts, its virtual 0 part has no id. We must use model.id instead
|
|
partId = model.Id;
|
|
|
|
if (Guid.Empty.Equals(partId))
|
|
continue;
|
|
|
|
newProjectTeamList.Add(partId, new List<Guid>());
|
|
|
|
if ((part.AssignedTeams != null) && part.AssignedTeams.Count > 0)
|
|
newProjectTeamList[partId].AddRange(part.AssignedTeams);
|
|
|
|
List<Guid> partScenarios = scenarioManager.GetProjectNonActualsScenarios(partId);
|
|
projectScenarios.Add(partId, partScenarios);
|
|
|
|
/*
|
|
Dictionary<Guid, List<Guid>> violdatedRecords =
|
|
scenarioManager.GetViolatedUpdateScenarioTeams(projectScenarios[partId],
|
|
newProjectTeamList[partId]);
|
|
|
|
foreach (Guid teamId in violdatedRecords.Keys)
|
|
{
|
|
// Some teams in scenarios can't be removed, because are allocated
|
|
string teamName = DbContext.Teams.AsNoTracking().SingleOrDefault(x => x.Id == teamId).Name;
|
|
string scenariosNames = "";
|
|
|
|
foreach (Guid scenarioId in violdatedRecords[teamId])
|
|
{
|
|
string scenarioName =
|
|
DbContext.Scenarios.AsNoTracking().SingleOrDefault(x => x.Id == scenarioId).Name;
|
|
scenariosNames += (", '" + scenarioName + "'");
|
|
}
|
|
|
|
if (scenariosNames.Length > 2)
|
|
scenariosNames = scenariosNames.Substring(2);
|
|
|
|
string errorText =
|
|
String.Format("The team '{0}' can't be removed from this project or part, because it is used in scenarios: {1}",
|
|
teamName, scenariosNames);
|
|
|
|
ModelState.AddModelError("", errorText);
|
|
ModelState.Remove(String.Format("Parts[{0}].AssignedTeams", partIndex));
|
|
|
|
// Restore original project teams
|
|
var originalProjectTeams = DbContext.Team2Project.Where(x => x.ProjectId.Equals(partId)).AsNoTracking()
|
|
.Select(t => t.TeamId);
|
|
|
|
model.Parts[partIndex].AssignedTeams = new List<Guid>();
|
|
model.Parts[partIndex].AssignedTeams.AddRange(originalProjectTeams);
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
// SA. ENV-754. End
|
|
#endregion
|
|
|
|
if (ModelState.IsValid)
|
|
{
|
|
using (DbContextTransaction trans = DbContext.Database.BeginTransaction())
|
|
try
|
|
{
|
|
var manager = new ProjectManager(DbContext);
|
|
var newProject = manager.Save(model2Save);
|
|
DbContext.SaveChanges();
|
|
|
|
var partIds = DbContext.Projects.Where(t => t.ParentProjectId.HasValue && t.ParentProjectId.Value == newProject.Id).Select(t => t.Id).ToArray();
|
|
//Give user Full Access permissions to the new project
|
|
var teamIds = new List<Guid>();
|
|
|
|
foreach (var part in model2Save.Parts)
|
|
{
|
|
if (part.AssignedTeams != null)
|
|
teamIds.AddRange(part.AssignedTeams);
|
|
}
|
|
|
|
var existingUsersPermissions =
|
|
DbContext.ProjectAccesses.Where(t => partIds.Contains(t.ProjectId) || t.ProjectId == newProject.Id)
|
|
.Select(t => new { t.ProjectId, t.PrincipalId }).ToList();
|
|
|
|
var users = (from c in DbContext.User2Team where teamIds.Contains(c.TeamId) select c.UserId).Distinct().ToList();
|
|
|
|
if (!users.Contains(User.Identity.GetID(), StringComparer.OrdinalIgnoreCase))
|
|
users.Add(User.Identity.GetID());
|
|
|
|
foreach (var contributor in users)
|
|
{
|
|
if (!existingUsersPermissions.Any(t => t.PrincipalId == new Guid(contributor) && t.ProjectId == newProject.Id))
|
|
{
|
|
DbContext.ProjectAccesses.Add(new ProjectAccess()
|
|
{
|
|
PrincipalId = new Guid(contributor),
|
|
ProjectId = newProject.Id,
|
|
Read = 1,
|
|
Write = 1
|
|
});
|
|
}
|
|
foreach (var partId in partIds)
|
|
{
|
|
if (!existingUsersPermissions.Any(t => t.PrincipalId == new Guid(contributor) && t.ProjectId == partId))
|
|
{
|
|
DbContext.ProjectAccesses.Add(new ProjectAccess()
|
|
{
|
|
PrincipalId = new Guid(contributor),
|
|
ProjectId = partId,
|
|
Read = 1,
|
|
Write = 1
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isNew)
|
|
{
|
|
#region Create Actuals scenario
|
|
if (!isCreateCopy)
|
|
{
|
|
var scenario = new Scenario
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Name = "ACTUALS",
|
|
ParentId = newProject.Id,
|
|
Type = ScenarioType.Actuals.GetHashCode(),
|
|
StartDate = DateTime.Now,
|
|
Color = "",
|
|
ProjectedRevenue = 0
|
|
};
|
|
DbContext.Scenarios.Add(scenario);
|
|
}
|
|
#endregion
|
|
#region Copy scenarios and referenced scenario details
|
|
|
|
//if (model2Save.TemplateId.HasValue && !Guid.Empty.Equals(model2Save.TemplateId))
|
|
//{
|
|
// oldId = model2Save.TemplateId.Value;
|
|
// var scenarios = DbContext.Scenarios.Where(t => t.ParentId == oldId).ToList();
|
|
|
|
// CopyScenarios(scenarios, newProject.Id);
|
|
//}
|
|
|
|
if (isCreateCopy)
|
|
{
|
|
FileManager fileMngr = new FileManager(DbContext);
|
|
|
|
if (!model2Save.HasChildren)
|
|
{
|
|
var scenarios = DbContext.Scenarios.Where(t => t.ParentId.HasValue && t.ParentId.Value == oldId).ToList();
|
|
CopyScenarios(scenarios, newProject.Id);
|
|
|
|
if ((model2Save.Parts.Count == 1) && (model2Save.Parts.First().Attachments != null) &&
|
|
(model2Save.Parts.First().Attachments.Count > 0))
|
|
{
|
|
IList<AttachmentModel> copiedAttachments =
|
|
fileMngr.CopyPermanentFiles(model2Save.Parts.First().Attachments, newProject.Id, model2Save.GetType());
|
|
model2Save.Parts.First().Attachments = copiedAttachments;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
foreach (var part in model2Save.Parts)
|
|
{
|
|
var scenarios = DbContext.Scenarios.Where(t => t.ParentId.HasValue && t.ParentId.Value == part.OldId).ToList();
|
|
CopyScenarios(scenarios, part.Id);
|
|
|
|
if ((part.Attachments != null) && (part.Attachments.Count > 0))
|
|
{
|
|
IList<AttachmentModel> copiedAttachments =
|
|
fileMngr.CopyPermanentFiles(part.Attachments, part.Id, part.GetType());
|
|
part.Attachments = copiedAttachments;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
#region Move attachments to permanent storage (SA. ENV-502)
|
|
|
|
if (isNew && (model.Parts.Count > 0))
|
|
{
|
|
FileManager fileMngr = new FileManager(DbContext);
|
|
|
|
foreach (ProjectPartModel partModel in model.Parts)
|
|
{
|
|
Guid parentId = partModel.Id;
|
|
System.Type parentType = partModel.GetType();
|
|
|
|
if ((model.Parts.Count == 1) && partModel.Id.Equals(Guid.Empty))
|
|
{
|
|
parentId = newProject.Id;
|
|
parentType = model.GetType();
|
|
}
|
|
|
|
if ((partModel.Attachments != null) && (partModel.Attachments.Count > 0))
|
|
{
|
|
List<AttachmentModel> newAttachments = partModel.Attachments.Where(x => x.IsNew).ToList();
|
|
List<AttachmentModel> existingAttachments = partModel.Attachments.Where(x => !x.IsNew).ToList();
|
|
|
|
IList<Guid> undeletedFiles = fileMngr.MoveToPermanentStorage(newAttachments, parentId, parentType);
|
|
|
|
if (undeletedFiles.Count > 0)
|
|
{
|
|
string errorMessage = "";
|
|
foreach (Guid fileId in undeletedFiles)
|
|
errorMessage += (", " + fileId.ToString());
|
|
|
|
errorMessage = errorMessage.Substring(2);
|
|
LogError(String.Format("Temp attachments copied to permanent storage, but some temp files were not deleted: {0}", errorMessage));
|
|
}
|
|
|
|
if (!isCreateCopy && (existingAttachments.Count > 0))
|
|
fileMngr.ChangeFilesParent(existingAttachments, parentId, parentType);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
// SA. Update project scenarios teams. ENV-754. Begin
|
|
if (!isNew && (model.Parts.Count > 0) && !isCreateCopy)
|
|
{
|
|
foreach (Guid partId in projectScenarios.Keys)
|
|
scenarioManager.UpdateScenarioTeams(projectScenarios[partId], newProjectTeamList[partId]);
|
|
|
|
#region Update project attachments (SA. ENV-502)
|
|
|
|
FileManager fileMngr = new FileManager(DbContext);
|
|
var partsIds = model.Parts.Select(x => x.Id).ToList();
|
|
|
|
if ((partsIds.Count() == 1) && partsIds.First().Equals(Guid.Empty))
|
|
{
|
|
partsIds.Clear();
|
|
partsIds.Add(model.Id);
|
|
}
|
|
|
|
List<Guid> attachmentsToRemove = fileMngr.GetPermanentFilesByHolder(partsIds) as List<Guid>;
|
|
List<AttachmentModel> attachmentsToAdd;
|
|
|
|
foreach (ProjectPartModel partModel in model.Parts)
|
|
{
|
|
if ((partModel.Attachments != null) && (partModel.Attachments.Count > 0))
|
|
{
|
|
var foundAttachments = partModel.Attachments.Where(x => !x.IsNew).Select(y => y.Id);
|
|
attachmentsToRemove.RemoveAll(x => foundAttachments.Contains(x));
|
|
attachmentsToAdd = partModel.Attachments.Where(x => x.IsNew).ToList();
|
|
|
|
Guid holderId = partModel.Id;
|
|
System.Type holderType = partModel.GetType();
|
|
|
|
if (holderId.Equals(Guid.Empty) && (model.Parts.Count == 1) && !model.Id.Equals(Guid.Empty))
|
|
{
|
|
holderId = model.Id;
|
|
holderType = model.GetType();
|
|
}
|
|
|
|
if (attachmentsToAdd.Count > 0)
|
|
{
|
|
IList<Guid> undeletedFiles = fileMngr.MoveToPermanentStorage(attachmentsToAdd, holderId, holderType);
|
|
|
|
if (undeletedFiles.Count > 0)
|
|
{
|
|
string errorMessage = "";
|
|
foreach (Guid fileId in undeletedFiles)
|
|
errorMessage += (", " + fileId.ToString());
|
|
|
|
errorMessage = errorMessage.Substring(2);
|
|
LogError(String.Format("Temp attachments copied to permanent storage, but some temp files were not deleted: {0}", errorMessage));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (attachmentsToRemove.Count > 0)
|
|
fileMngr.DeletePermanentFiles(attachmentsToRemove, true);
|
|
|
|
#endregion
|
|
}
|
|
// SA. ENV-754. End
|
|
|
|
DbContext.SaveChanges();
|
|
trans.Commit();
|
|
|
|
new ProjectAccessCache().Invalidate();
|
|
ContentLocker.RemoveLock("Project", model.Id.ToString(), User.Identity.Name);
|
|
if (!isCreateCopy)
|
|
{
|
|
if (model2Save.ContinueToScenarios)
|
|
{
|
|
if (model.HasChildren)
|
|
return RedirectToAction("Edit", new { @id = newProject.Id, @ptab = "scenarios" });
|
|
else
|
|
return RedirectToAction("Edit", new { @id = newProject.Id, @ptab = "newscenario" });
|
|
}
|
|
else
|
|
{
|
|
return RedirectToAction("Edit", "Project", new { id = newProject.Id });//RedirectToAction("Index");
|
|
}
|
|
}
|
|
else
|
|
return RedirectToAction("Edit", "Project", new { id = newProject.Id });
|
|
}
|
|
catch (BLLException blEx) // handle any system specific error
|
|
{
|
|
trans.Rollback();
|
|
|
|
// display error message if required
|
|
if (blEx.DisplayError)
|
|
ModelState.AddModelError(string.Empty, blEx.Message);
|
|
else // if display not requried then display modal form with general error message
|
|
{
|
|
LogException(blEx);
|
|
SetErrorScript();
|
|
}
|
|
}
|
|
catch (Exception exception) // handle any unexpected error
|
|
{
|
|
trans.Rollback();
|
|
LogException(exception);
|
|
SetErrorScript();
|
|
}
|
|
}
|
|
|
|
if (model.Id != null && model.Id != Guid.Empty)
|
|
{
|
|
model.Scenarios = new List<ScenarioTabModel>();
|
|
var proj_db = DbContext.Projects.Where(x => x.Id == model.Id).FirstOrDefault();
|
|
|
|
foreach (var childProject in proj_db.ChildProjects.OrderBy(pp => pp.PartNum))
|
|
{
|
|
foreach (var scenario in childProject.Scenarios)
|
|
{
|
|
if (scenario.Type != ScenarioType.Actuals.GetHashCode())
|
|
model.ScenariosCount++;
|
|
model.Scenarios.Add((ScenarioTabModel)scenario);
|
|
}
|
|
}
|
|
}
|
|
|
|
// return empty model with validation messages (if any)
|
|
var statuses = DbContext.Status.Select(x => new { Id = x.Id, Probability100 = x.Probability100 }).ToList();
|
|
ViewBag.Statuses = new JavaScriptSerializer().Serialize(statuses);
|
|
return View(model);
|
|
}
|
|
|
|
private void CopyScenarios(IEnumerable<Scenario> scenarios, Guid newProjectId)
|
|
{
|
|
foreach (var currScenario in scenarios)
|
|
{
|
|
var currDetails = DbContext.ScenarioDetail.Where(
|
|
t => t.ParentID == currScenario.Id).AsNoTracking().ToList().OrderBy(t => t.ExpenditureCategoryId).ThenBy(t => t.WeekOrdinal);
|
|
|
|
var scenarioId = Guid.NewGuid();
|
|
var newScenario = new Scenario
|
|
{
|
|
Id = scenarioId,
|
|
ParentId = newProjectId,
|
|
Type = currScenario.Type,
|
|
Name = currScenario.Name,
|
|
ProjectedRevenue = currScenario.ProjectedRevenue,
|
|
ExpectedGrossMargin = currScenario.ExpectedGrossMargin,
|
|
CalculatedGrossMargin = currScenario.CalculatedGrossMargin,
|
|
CGSplit = currScenario.CGSplit,
|
|
EFXSplit = currScenario.EFXSplit,
|
|
Duration = currScenario.Duration,
|
|
TDDirectCosts = currScenario.TDDirectCosts,
|
|
BUDirectCosts = currScenario.BUDirectCosts,
|
|
Shots = currScenario.Shots,
|
|
TDRevenueShot = currScenario.TDRevenueShot,
|
|
BURevenueShot = currScenario.BURevenueShot,
|
|
LastUpdate = DateTime.Now,
|
|
Status = currScenario.Status,
|
|
UseLMMargin = currScenario.UseLMMargin,
|
|
ExpectedGrossMargin_LM = currScenario.ExpectedGrossMargin_LM,
|
|
CalculatedGrossMargin_LM = currScenario.CalculatedGrossMargin_LM,
|
|
TDDirectCosts_LM = currScenario.TDDirectCosts_LM,
|
|
BUDirectCosts_LM = currScenario.BUDirectCosts_LM,
|
|
BURevenueShot_LM = currScenario.BURevenueShot_LM,
|
|
EntryTimeStamp = DateTime.Now,
|
|
Actuals_BUDirectCosts = currScenario.Actuals_BUDirectCosts,
|
|
Actuals_BUDirectCosts_LM = currScenario.Actuals_BUDirectCosts_LM,
|
|
FreezeRevenue = currScenario.FreezeRevenue,
|
|
GrowthScenario = currScenario.GrowthScenario,
|
|
TemplateId = currScenario.TemplateId,
|
|
Color = currScenario.Color,
|
|
ProjectedExpense = currScenario.ProjectedExpense,
|
|
StartDate = currScenario.StartDate,
|
|
EndDate = currScenario.EndDate,
|
|
ShotStartDate = currScenario.ShotStartDate,
|
|
SystemAttributeObjectID = currScenario.SystemAttributeObjectID,
|
|
Team2Scenario = currScenario.Team2Scenario.Select(s => new Team2Scenario() { Id = Guid.NewGuid(), TeamId = s.TeamId, Allocation = s.Allocation, ScenarioId = scenarioId }).ToList()
|
|
};
|
|
DbContext.Scenarios.Add(newScenario);
|
|
|
|
foreach (var detail in currDetails)
|
|
{
|
|
var newDetailItem = new ScenarioDetail
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ExpenditureCategoryId = detail.ExpenditureCategoryId,
|
|
ParentID = newScenario.Id,
|
|
Quantity = detail.Quantity,
|
|
Cost = detail.Cost,
|
|
WeekOrdinal = detail.WeekOrdinal,
|
|
WeekEndingDate = detail.WeekEndingDate,
|
|
LastUpdate = DateTime.Now
|
|
};
|
|
|
|
DbContext.ScenarioDetail.Add(newDetailItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Write)]
|
|
public ActionResult AddPart(ProjectPartModel model, int count)
|
|
{
|
|
try
|
|
{
|
|
if (ContentLocker.IsLock("Project", (model.ParentProjectId ?? Guid.Empty).ToString(), User.Identity.Name))
|
|
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
|
|
if (model.ParentProjectId.HasValue && model.ParentProjectId != Guid.Empty)
|
|
if (!SecurityManager.CheckProjectPermission(model.ParentProjectId.Value, AccessLevel.Write))
|
|
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
|
|
|
|
//ENV-680. As we pass something to the action method, ModelState object is being generated and passed items being automatically validated
|
|
//The issue is - we pass ProjectPartModel instance with empty values and it is being validated and partial view being rendered with validation errors
|
|
//(for now ClientId and Priority are invalid). This it a standard MVC behavior and we cannot change that, so we have two options:
|
|
//1. reset the validation on the client side manipulating jQuery unobtrusive validation, but this will drop all the validation errors from the entire form
|
|
// and not just the part we've added
|
|
//2. clear errors in the ModelState manually - ModelState is not preserved during roundtrips so we're just dropping it now and validation
|
|
// still performs on the project save
|
|
//Option 2 seems to be much better choice here so here is the magic:
|
|
foreach (var key in ModelState.Keys)
|
|
{
|
|
ModelState[key].Errors.Clear();
|
|
}
|
|
|
|
return PartialView("~/Views/Shared/EditorTemplates/ProjectPartModel.cshtml", model);
|
|
}
|
|
catch (BLLException blEx) // handle any system specific error
|
|
{
|
|
// display error message if required
|
|
if (blEx.DisplayError)
|
|
SetErrorScript(message: blEx.Message);
|
|
else // if display not requried then display modal form with general error message
|
|
{
|
|
LogException(blEx);
|
|
SetErrorScript();
|
|
}
|
|
}
|
|
catch (Exception exception) // handle any unexpected error
|
|
{
|
|
LogException(exception);
|
|
SetErrorScript();
|
|
}
|
|
return new EmptyResult();
|
|
}
|
|
|
|
// GET: /Project/Delete/5
|
|
[HttpGet]
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Write)]
|
|
public ActionResult Delete(Guid? id)
|
|
{
|
|
if (id == null || id == Guid.Empty)
|
|
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
|
|
if (!SecurityManager.CheckProjectPermission(id.Value, AccessLevel.Write))
|
|
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
|
|
|
|
var model = new ProjectModel();
|
|
try
|
|
{
|
|
var manager = new ProjectManager(DbContext);
|
|
model = (ProjectModel)manager.Load(id);
|
|
if (model == null)
|
|
return HttpNotFound();
|
|
}
|
|
catch (BLLException blEx)
|
|
{
|
|
if (blEx.DisplayError)
|
|
SetErrorScript(message: blEx.Message);
|
|
else
|
|
{
|
|
LogException(blEx);
|
|
SetErrorScript();
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
LogException(exception);
|
|
SetErrorScript();
|
|
}
|
|
return View(model);
|
|
}
|
|
|
|
// POST: /Project/Delete/5
|
|
[HttpPost, ActionName("Delete")]
|
|
[ValidateAntiForgeryToken]
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Write)]
|
|
public ActionResult Delete(ProjectModel model)
|
|
{
|
|
if (ContentLocker.IsLock("Project", model.Id.ToString(), User.Identity.Name))
|
|
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
|
|
if (!SecurityManager.CheckProjectPermission(model.Id, AccessLevel.Write))
|
|
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
|
|
|
|
var manager = new ProjectManager(DbContext);
|
|
var dbObj = manager.Load(model.Id, false);
|
|
if (dbObj == null)
|
|
return HttpNotFound();
|
|
|
|
// SA. ENV-502. Attachments. Get project files to delete
|
|
IList<Guid> holders = manager.GetSubprojectsAndParts(dbObj);
|
|
FileManager fileMngr = new FileManager(DbContext);
|
|
fileMngr.QueuePermanentFilesToDelete(holders);
|
|
|
|
// SA. ENV-914. Project parts now are deleted inside SQL procedure
|
|
(DbContext as IObjectContextAdapter).ObjectContext.ExecuteStoreCommand(string.Format("exec sp_DeleteProject '{0}'", dbObj.Id));
|
|
DbContext.SaveChanges();
|
|
|
|
// SA. ENV-502. Delete attachments
|
|
fileMngr.DeleteQueuedFiles(true);
|
|
|
|
ContentLocker.RemoveLock("Project", dbObj.Id.ToString(), User.Identity.Name);
|
|
return RedirectToAction("Index");
|
|
}
|
|
|
|
[HttpGet]
|
|
[AreaSecurityAttribute(area = Areas.ImportActuals, level = AccessLevel.Read)]
|
|
public ActionResult ImportActuals(Guid? id)
|
|
{
|
|
ImportActualsModel model = null;
|
|
return View(model);
|
|
}
|
|
|
|
// POST: /Project/Import/5
|
|
[HttpPost, ActionName("ImportActuals")]
|
|
[AreaSecurityAttribute(area = Areas.ImportActuals, level = AccessLevel.Write)]
|
|
public ActionResult ImportActuals(ImportActualsModel model, HttpPostedFileBase fileUpload)
|
|
{
|
|
if (model == null || model.Id == Guid.Empty)
|
|
{
|
|
var file = fileUpload;
|
|
|
|
if (file == null || file.ContentLength < 1)
|
|
{
|
|
ModelState.AddModelError("", "File was not loaded");
|
|
return View();
|
|
}
|
|
|
|
using (var reader = new StreamReader(file.InputStream))
|
|
{
|
|
try
|
|
{
|
|
var engine = new FileHelperEngine<EnVisage.Code.ActualsImportRow>();
|
|
ActualsImportRow[] dataRead = engine.ReadStream(reader);
|
|
var importer = new ImportActuals();
|
|
string log = string.Empty;
|
|
model = importer.ProcessImport(dataRead, Request["firstRowHeaders"] == "on", Request["zeroOut"] == "on", Request["uomHours"] == "on", User.Identity.Name, out log);
|
|
ViewBag.ImportResult = model.ImportSuccessful;
|
|
ViewBag.ImportLog = log;
|
|
return View(model);
|
|
}
|
|
catch (Exception c)
|
|
{
|
|
LogException(c);
|
|
ModelState.AddModelError("", c.Message);
|
|
return View(model);
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string log = string.Empty;
|
|
var importer = new ImportActuals();
|
|
var result = importer.CommitImport(model, User.Identity.Name, out log);
|
|
//ViewBag.ImportResult = result;
|
|
ViewBag.ComletedImport = result;
|
|
ViewBag.ImportLog = log;
|
|
return View(model);
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Write)]
|
|
public ActionResult AddNote(NoteModel model)
|
|
{
|
|
//if (model.ScenarioId != Guid.Empty && ContentLocker.IsLock("Scenarios", model.ScenarioId.ToString(), User.Identity.Name))
|
|
// return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
|
|
model.TrimStringProperties();
|
|
if (ModelState.IsValid)
|
|
{
|
|
try
|
|
{
|
|
model.Id = Guid.NewGuid();
|
|
var newnote = new Note();
|
|
model.CopyTo(newnote);
|
|
newnote.UserId = new Guid(User.Identity.GetID());
|
|
DbContext.Notes.Add(newnote);
|
|
DbContext.SaveChanges();
|
|
}
|
|
catch (BLLException blEx) // handle any system specific error
|
|
{
|
|
// display error message if required
|
|
if (blEx.DisplayError)
|
|
ModelState.AddModelError(string.Empty, blEx.Message);
|
|
else // if display not requried then display modal form with general error message
|
|
{
|
|
LogException(blEx);
|
|
SetErrorScript();
|
|
}
|
|
}
|
|
catch (Exception exception) // handle any unexpected error
|
|
{
|
|
LogException(exception);
|
|
SetErrorScript();
|
|
}
|
|
}
|
|
UriBuilder builder = new UriBuilder(HttpContext.Request.UrlReferrer);
|
|
var query = HttpUtility.ParseQueryString(builder.Query);
|
|
query["ptab"] = "notes";
|
|
builder.Query = query.ToString();
|
|
return Redirect(builder.Uri.AbsoluteUri);
|
|
}
|
|
|
|
[HttpPost]
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Write)]
|
|
public ActionResult EditNote(NoteModel model)
|
|
{
|
|
//if (model.ScenarioId != Guid.Empty && ContentLocker.IsLock("Scenarios", model.ScenarioId.ToString(), User.Identity.Name))
|
|
// return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
|
|
model.TrimStringProperties();
|
|
if (ModelState.IsValid)
|
|
{
|
|
try
|
|
{
|
|
var note = (from c in DbContext.Notes where c.Id == model.Id select c).FirstOrDefault();
|
|
if (note == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
note.Title = model.Title;
|
|
note.NoteDetail = model.Details;
|
|
DbContext.SaveChanges();
|
|
}
|
|
catch (BLLException blEx) // handle any system specific error
|
|
{
|
|
// display error message if required
|
|
if (blEx.DisplayError)
|
|
ModelState.AddModelError(string.Empty, blEx.Message);
|
|
else // if display not requried then display modal form with general error message
|
|
{
|
|
LogException(blEx);
|
|
SetErrorScript();
|
|
}
|
|
}
|
|
catch (Exception exception) // handle any unexpected error
|
|
{
|
|
LogException(exception);
|
|
SetErrorScript();
|
|
}
|
|
}
|
|
UriBuilder builder = new UriBuilder(HttpContext.Request.UrlReferrer);
|
|
var query = HttpUtility.ParseQueryString(builder.Query);
|
|
query["ptab"] = "notes";
|
|
builder.Query = query.ToString();
|
|
return Redirect(builder.Uri.AbsoluteUri);
|
|
}
|
|
|
|
// GET: /User/Edit/5
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Write)]
|
|
public ActionResult DeleteNote(string Id)
|
|
{
|
|
if (string.IsNullOrEmpty(Id) || Id == "JSVar")
|
|
{
|
|
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
}
|
|
var NoteId = new Guid(Id);
|
|
var note = (from c in DbContext.Notes where c.Id == NoteId select c).FirstOrDefault();
|
|
if (note == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
else
|
|
{
|
|
DbContext.Notes.Remove(note);
|
|
DbContext.SaveChanges();
|
|
}
|
|
return Redirect(HttpContext.Request.UrlReferrer.AbsoluteUri);
|
|
}
|
|
|
|
// GET: /User/Edit/5
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Write)]
|
|
public ActionResult EditNote(string Id)
|
|
{
|
|
if (string.IsNullOrEmpty(Id) || Id == "JSVar")
|
|
{
|
|
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
}
|
|
var NoteId = new Guid(Id);
|
|
var note = (from c in DbContext.Notes where c.Id == NoteId select c).FirstOrDefault();
|
|
if (note == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
else
|
|
{
|
|
return PartialView("_addNote", (NoteModel)note);
|
|
}
|
|
}
|
|
// GET: /User/Edit/5
|
|
[AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Write)]
|
|
public ActionResult AddNote(string Id)
|
|
{
|
|
if (string.IsNullOrEmpty(Id))
|
|
{
|
|
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
}
|
|
var ParentId = new Guid(Id);
|
|
return PartialView("_addNote", new NoteModel(ParentId));
|
|
}
|
|
|
|
[HttpGet]
|
|
public JsonResult LoadExternalContacts(Guid? clientId, string selectControlId)
|
|
{
|
|
return Json(new
|
|
{
|
|
contacts = LoadContacts(clientId),
|
|
selectControlId = selectControlId
|
|
}
|
|
, JsonRequestBehavior.AllowGet);
|
|
}
|
|
|
|
[HttpGet]
|
|
public JsonResult LoadInternalContacts(Guid? companyId, string selectControlId)
|
|
{
|
|
return Json(new
|
|
{
|
|
contacts = LoadContacts(companyId),
|
|
selectControlId = selectControlId
|
|
}
|
|
, JsonRequestBehavior.AllowGet);
|
|
}
|
|
|
|
[HttpGet]
|
|
public JsonResult LoadCompanyGoals(Guid? companyId, string selectControlId)
|
|
{
|
|
return Json(new
|
|
{
|
|
goals = Utils.GetStrategicGoals(companyId, false),
|
|
selectControlId = selectControlId
|
|
}
|
|
, JsonRequestBehavior.AllowGet);
|
|
}
|
|
|
|
private List<ContactModel> LoadContacts(Guid? parentId)
|
|
{
|
|
if (parentId == null || parentId == Guid.Empty)
|
|
return new List<ContactModel>();
|
|
|
|
return DbContext.Contacts.Where(c => c.ParentId == parentId).OrderBy(c => c.LastName).Select(c => new ContactModel
|
|
{
|
|
Id = c.Id,
|
|
ParentId = c.ParentId ?? Guid.Empty,
|
|
FirstName = c.FirstName,
|
|
LastName = c.LastName,
|
|
Type = (ContactType)c.Type,
|
|
Email = c.Email,
|
|
}).ToList();
|
|
}
|
|
|
|
//function will do autocomplete for project number on the
|
|
//Edit Project page. It returns a list of unused project numbers from the supt_tbl_ProjectIds
|
|
[HttpGet]
|
|
public ActionResult ProjectNumberSearch(string term)
|
|
{
|
|
using (var dbContext = new EnVisageEntities())
|
|
{
|
|
var projectIDs = dbContext.supt_tbl_ProjectIds.AsNoTracking().Where(x => x.ProjectID.StartsWith(term)).OrderBy(p => p.ProjectID);
|
|
|
|
var projects = dbContext.Projects.AsNoTracking().OrderBy(p => p.ProjectNumber);
|
|
var list = (from pid in projectIDs
|
|
join d in projects
|
|
on pid.ProjectID equals d.ProjectNumber into output
|
|
from d in output.DefaultIfEmpty()
|
|
where d == null
|
|
select new { pid.ProjectID });
|
|
|
|
return this.Json(list.Select(x => x.ProjectID).ToList(),
|
|
JsonRequestBehavior.AllowGet);
|
|
}
|
|
return null;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
}
|