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; } } /// /// GET: /Project/ /// /// Empty view [HttpGet] [AreaSecurityAttribute(area = Areas.Projects, level = AccessLevel.Read)] public ActionResult Index() { if (!SecurityManager.CheckSecurityObjectPermission(Areas.Projects, AccessLevel.Read)) return Redirect("/"); return View(); } /// /// Returns JSON project list with filters and sort for jQuery DataTables /// [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 GetProjects(int startIndex, int pageSize, ReadOnlyCollection 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>(_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> projectScenarios = new Dictionary>(); Dictionary> newProjectTeamList = new Dictionary>(); //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()); if ((part.AssignedTeams != null) && part.AssignedTeams.Count > 0) newProjectTeamList[partId].AddRange(part.AssignedTeams); List partScenarios = scenarioManager.GetProjectNonActualsScenarios(partId); projectScenarios.Add(partId, partScenarios); /* Dictionary> 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(); 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(); 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 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 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 newAttachments = partModel.Attachments.Where(x => x.IsNew).ToList(); List existingAttachments = partModel.Attachments.Where(x => !x.IsNew).ToList(); IList 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 attachmentsToRemove = fileMngr.GetPermanentFilesByHolder(partsIds) as List; List 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 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(); 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 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 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(); 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 LoadContacts(Guid? parentId) { if (parentId == null || parentId == Guid.Empty) return new List(); 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; } } }