public ActionResult Edit(int id, int surveyId, int? categoryId, Question question, ResponsesParameter[] response) { //To check if there are related answers, we need to check two things: // 1) Does this question have related answers // 2) Does the related Category have related answers (cauz we might be moving the question to a different category) //If there are related answers we may have to version one or both categories //And we need to check if we need to version // 1) Moving question to a different category // 2) Active changed // 3) Open Ended Changed // 4) Response added // 5) Response Hidden/Unhidden (Response.IsActive) // 6) quesion (name) is changed var originalCategoryId = 0; var newCategoryId = 0; var originalCategoryHasAnswers = false; var newCategoryHasAnswers = false; var originalHasChanges = false; var newHasChanges = false; var survey = Repository.OfType<Survey>().GetNullableById(surveyId); if (survey == null) { Message = "Survey Not Found"; return this.RedirectToAction<ErrorController>(a => a.Index()); } var questionToEdit = _questionRepository.GetNullableById(id); if (questionToEdit == null) { Message = "Question Not Found."; return this.RedirectToAction<SurveyController>(a => a.Edit(survey.Id)); } //Save the Id of the original Category in case we need to version it. originalCategoryId = questionToEdit.Category.Id; originalCategoryHasAnswers = Repository.OfType<Answer>().Queryable.Where(a => a.Category.Id == originalCategoryId).Any(); if (question.Category != null && question.Category.Id != originalCategoryId) { newCategoryId = question.Category.Id; newCategoryHasAnswers = Repository.OfType<Answer>().Queryable.Where(a => a.Category.Id == newCategoryId).Any(); } var viewModel = QuestionViewModel.Create(Repository, survey); if (categoryId != null) { var category = Repository.OfType<Category>().GetNullableById(categoryId.Value); viewModel.Category = category; } viewModel.Question = question; // never removed saved responses, only make them inactive. var cleanedResponse = new List<ResponsesParameter>(); foreach (var responsesParameter in response.OrderBy(a => a.Order)) { if (responsesParameter.ResponseId != 0) { cleanedResponse.Add(responsesParameter); } else if (!string.IsNullOrWhiteSpace(responsesParameter.Value) && !responsesParameter.Remove) { cleanedResponse.Add(responsesParameter); } } viewModel.Responses = cleanedResponse; //Version Checks Part1 if (originalCategoryHasAnswers) //original category and has answers { if (newCategoryId != 0) //Changed to a different Category { //if we are changing the category, but the question was not active then there would not be any changes to the original category if (questionToEdit.IsActive) { originalHasChanges = true; } } else if (questionToEdit.IsActive != question.IsActive) //Active state changed { originalHasChanges = true; } else if (questionToEdit.IsOpenEnded != question.IsOpenEnded) //OpenEnded Question Changed { originalHasChanges = true; } else if (questionToEdit.OpenEndedQuestionType != question.OpenEndedQuestionType) //type of open ended question changed { originalHasChanges = true; } else if (questionToEdit.Name.ToLower() != question.Name.ToLower()) //Question (name) has changed { originalHasChanges = true; } else if (viewModel.Responses.Count != questionToEdit.Responses.Count) //Number of possible responses has changed { //Added originalHasChanges = true; } else { foreach (var responsesParameter in viewModel.Responses) { var foundResp = questionToEdit.Responses.Where(a => a.Id == responsesParameter.ResponseId).Single(); if (foundResp.Value.ToLower() != responsesParameter.Value.ToLower()) //Response Value (choice) has changed { originalHasChanges = true; break; } else if (foundResp.Score != responsesParameter.Score.GetValueOrDefault(0)) //Score has Changed { originalHasChanges = true; break; } else if (foundResp.IsActive == responsesParameter.Remove) //Hide response has changed { originalHasChanges = true; break; } } } } if (newCategoryId != 0 && newCategoryHasAnswers) { var newCategory = Repository.OfType<Category>().GetNullableById(newCategoryId); if (newCategory != null) { if (newCategory.IsActive && question.IsActive) //If the new Category isn't active it shouldn't have answers { newHasChanges = true; } } } //Mapper.Map(question, questionToEdit); question.Responses.Clear(); foreach (var responsesParameter in viewModel.Responses) { if (responsesParameter.ResponseId != 0) { var foundResp = questionToEdit.Responses.Where(a => a.Id == responsesParameter.ResponseId).Single(); foundResp.Value = responsesParameter.Value; foundResp.Score = responsesParameter.Score.GetValueOrDefault(0); foundResp.IsActive = !responsesParameter.Remove; foundResp.Order = responsesParameter.Order; question.AddResponse(foundResp); } else { var responseToAdd = new Response { Order = responsesParameter.Order, IsActive = true, Score = responsesParameter.Score.GetValueOrDefault(0), Value = responsesParameter.Value }; question.AddResponse(responseToAdd); } } question.Survey = questionToEdit.Survey; ModelState.Clear(); question.TransferValidationMessagesTo(ModelState); foreach (var responsesParameter in cleanedResponse) { if (!responsesParameter.Score.HasValue) { ModelState.AddModelError("Question", "All responses need a score"); } if (question.IsOpenEnded && !responsesParameter.Remove) { switch ((QuestionType)question.OpenEndedQuestionType) { case QuestionType.WholeNumber: int number; if (!int.TryParse(responsesParameter.Value, out number)) { ModelState.AddModelError("Question", "Choices must be whole numbers"); } break; case QuestionType.Decimal: float floatNumber; if (!float.TryParse(responsesParameter.Value, out floatNumber)) { ModelState.AddModelError("Question", "Choices must be numbers (decimal ok)"); } break; case QuestionType.Time: float floatTime; if (!responsesParameter.Value.TimeTryParse(out floatTime)) { ModelState.AddModelError("Question", "Choices must be Time (hh:mm)"); } break; case QuestionType.TimeAmPm: float floatTimeAmPm; if (!responsesParameter.Value.TimeTryParseAmPm(out floatTimeAmPm)) { ModelState.AddModelError("Question", "Choices must be Time (hh:mm AM/PM)"); } break; case QuestionType.TimeRange: float timeRangeNumber; if (!float.TryParse(responsesParameter.Value, out timeRangeNumber)) { ModelState.AddModelError("Question", "Choices must be numbers (decimal ok)"); } break; default: ModelState.AddModelError("Question", "time and time range not supported yet"); break; } } } foreach (var responseCheck in question.Responses) { if (string.IsNullOrWhiteSpace(responseCheck.Value)) { ModelState.AddModelError("Question", string.Format("Response {0} must have a choice.", responseCheck.Order + 1)); } } if (question.Responses.Where(a => a.IsActive).Count() == 0) { ModelState.AddModelError("Question", "Active Responses are required."); } if (question.Category != null && !question.Category.IsCurrentVersion) { ModelState.AddModelError("Question.Category", "Selected Category is not current."); } if (ModelState.IsValid) { string extraMessage1; string extraMessage2; #region Original Has Changes and needs to be versioned if (originalHasChanges) { var newOriginalCategory = _archiveService.ArchiveCategory( Repository, originalCategoryId, questionToEdit); if (newCategoryId == 0) { var newQuestion = new Question(question.Survey); newQuestion.Category = newOriginalCategory; newQuestion.IsActive = question.IsActive; newQuestion.IsOpenEnded = question.IsOpenEnded; newQuestion.OpenEndedQuestionType = question.OpenEndedQuestionType; newQuestion.Name = question.Name; newQuestion.PrimaryPhoto = question.PrimaryPhoto; newQuestion.Order = questionToEdit.Order; foreach (var responsesParameter in viewModel.Responses) { var responseToAdd = new Response { Order = responsesParameter.Order, IsActive = !responsesParameter.Remove, Score = responsesParameter.Score.GetValueOrDefault(0), Value = responsesParameter.Value }; newQuestion.AddResponse(responseToAdd); } foreach (var photo in questionToEdit.Photos) //This should work, but it needs to be tested. { var newPhoto = Repository.OfType<Photo>().Queryable.Single(a => a.Id == photo.Id); newQuestion.Photos.Add(newPhoto); } question = newQuestion; extraMessage1 = "Related Category Versioned"; extraMessage2 = string.Empty; _questionRepository.EnsurePersistent(question); Message = string.Format("Question Edited Successfully {0} {1}", extraMessage1, extraMessage2); if (viewModel.Category != null) { return this.RedirectToAction<CategoryController>(a => a.Edit(newOriginalCategory.Id)); } return this.RedirectToAction<SurveyController>(a => a.Edit(survey.Id)); } else { if (newHasChanges) { var newNewCategory = _archiveService.ArchiveCategory(Repository, newCategoryId, questionToEdit); var newQuestion = new Question(question.Survey); newQuestion.Category = newNewCategory; newQuestion.IsActive = question.IsActive; newQuestion.IsOpenEnded = question.IsOpenEnded; newQuestion.OpenEndedQuestionType = question.OpenEndedQuestionType; newQuestion.Name = question.Name; newQuestion.PrimaryPhoto = question.PrimaryPhoto; newQuestion.Order = questionToEdit.Order; foreach (var responsesParameter in viewModel.Responses) { var responseToAdd = new Response { Order = responsesParameter.Order, IsActive = !responsesParameter.Remove, Score = responsesParameter.Score.GetValueOrDefault(0), Value = responsesParameter.Value }; newQuestion.AddResponse(responseToAdd); } foreach (var photo in questionToEdit.Photos) //This should work, but it needs to be tested. { var newPhoto = Repository.OfType<Photo>().Queryable.Single(a => a.Id == photo.Id); newQuestion.Photos.Add(newPhoto); } question = newQuestion; extraMessage1 = "Previously Related Category Versioned"; extraMessage2 = "Newly Related Category Versioned"; _questionRepository.EnsurePersistent(question); Message = string.Format("Question Edited Successfully {0} {1}", extraMessage1, extraMessage2); if (viewModel.Category != null) { return this.RedirectToAction<CategoryController>(a => a.Edit(newOriginalCategory.Id)); } return this.RedirectToAction<SurveyController>(a => a.Edit(survey.Id)); } else { var newQuestion = new Question(question.Survey); newQuestion.Category = question.Category; newQuestion.IsActive = question.IsActive; newQuestion.IsOpenEnded = question.IsOpenEnded; newQuestion.OpenEndedQuestionType = question.OpenEndedQuestionType; newQuestion.Name = question.Name; newQuestion.PrimaryPhoto = question.PrimaryPhoto; newQuestion.Order = questionToEdit.Order; foreach (var responsesParameter in viewModel.Responses) { var responseToAdd = new Response { Order = responsesParameter.Order, IsActive = !responsesParameter.Remove, Score = responsesParameter.Score.GetValueOrDefault(0), Value = responsesParameter.Value }; newQuestion.AddResponse(responseToAdd); } foreach (var photo in questionToEdit.Photos) //This should work, but it needs to be tested. { var newPhoto = Repository.OfType<Photo>().Queryable.Single(a => a.Id == photo.Id); newQuestion.Photos.Add(newPhoto); } question = newQuestion; extraMessage1 = "Previously Related Category Versioned"; extraMessage2 = "Newly Related Category Not Versioned"; _questionRepository.EnsurePersistent(question); Message = string.Format("Question Edited Successfully {0} {1}", extraMessage1, extraMessage2); if (viewModel.Category != null) { return this.RedirectToAction<CategoryController>(a => a.Edit(newOriginalCategory.Id)); } return this.RedirectToAction<SurveyController>(a => a.Edit(survey.Id)); } } } #endregion Original Has Changes and needs to be versioned #region Original Is not Versioned, but new Category Is if (newHasChanges) { var newNewCategory = _archiveService.ArchiveCategory(Repository, newCategoryId, questionToEdit); var newQuestion = new Question(question.Survey); newQuestion.Category = newNewCategory; newQuestion.IsActive = question.IsActive; newQuestion.IsOpenEnded = question.IsOpenEnded; newQuestion.OpenEndedQuestionType = question.OpenEndedQuestionType; newQuestion.Name = question.Name; newQuestion.PrimaryPhoto = question.PrimaryPhoto; newQuestion.Order = questionToEdit.Order; foreach (var responsesParameter in viewModel.Responses) { var responseToAdd = new Response { Order = responsesParameter.Order, IsActive = !responsesParameter.Remove, Score = responsesParameter.Score.GetValueOrDefault(0), Value = responsesParameter.Value }; newQuestion.AddResponse(responseToAdd); } foreach (var photo in questionToEdit.Photos) //This should work, but it needs to be tested. { var newPhoto = Repository.OfType<Photo>().Queryable.Single(a => a.Id == photo.Id); newQuestion.Photos.Add(newPhoto); } question = newQuestion; extraMessage1 = "Previously Related Category Not Versioned"; extraMessage2 = "Newly Related category Versioned"; _questionRepository.EnsurePersistent(question); Message = string.Format("Question Edited Successfully {0} {1}", extraMessage1, extraMessage2); if (viewModel.Category != null) { return this.RedirectToAction<CategoryController>(a => a.Edit(viewModel.Category.Id)); } return this.RedirectToAction<SurveyController>(a => a.Edit(survey.Id)); } #endregion Original Is not Versioned, but new Category Is #region No Versioning, editing as normal Mapper.Map(question, questionToEdit); questionToEdit.Responses.Clear(); foreach (var responsesParameter in viewModel.Responses) { if (responsesParameter.ResponseId != 0) { var foundResp = question.Responses.Where(a => a.Id == responsesParameter.ResponseId).Single(); foundResp.Value = responsesParameter.Value; foundResp.Score = responsesParameter.Score.GetValueOrDefault(0); foundResp.IsActive = !responsesParameter.Remove; foundResp.Order = responsesParameter.Order; questionToEdit.AddResponse(foundResp); } else { var responseToAdd = new Response { Order = responsesParameter.Order, IsActive = true, Score = responsesParameter.Score.GetValueOrDefault(0), Value = responsesParameter.Value }; questionToEdit.AddResponse(responseToAdd); } } _questionRepository.EnsurePersistent(questionToEdit); Message = "Question Edited Successfully"; if (viewModel.Category != null) { return this.RedirectToAction<CategoryController>(a => a.Edit(questionToEdit.Category.Id)); } return this.RedirectToAction<SurveyController>(a => a.Edit(survey.Id)); #endregion No Versioning, editing as normal } else { viewModel.UniqueTags = Repository.OfType<PhotoTag>().Queryable.OrderBy(b => b.Name).Select(a => a.Name).Distinct().ToList(); return View(viewModel); } }
public ActionResult Create(int id, int? categoryId, Question question, ResponsesParameter[] response) { var isNewVersion = false; var survey = Repository.OfType<Survey>().GetNullableById(id); if (survey == null) { Message = "Survey Not Found"; return this.RedirectToAction<ErrorController>(a => a.Index()); } var viewModel = QuestionViewModel.Create(Repository, survey); if (categoryId != null) //This category Id is just used for defaults and navigation { var category = Repository.OfType<Category>().GetNullableById(categoryId.Value); viewModel.Category = category; } viewModel.Question = question; if (question.Category != null && Repository.OfType<Answer>().Queryable.Where(a => a.Category.Id == question.Category.Id).Any()) { //The category picked already has answers, so if it is valid and we create a new question, we want to create a new version of the category first. isNewVersion = true; } // Remove responses that do not have a Choice or that have the remove checked. This is the create, so they will never be added var cleanedResponse = new List<ResponsesParameter>(); foreach (var responsesParameter in response.OrderBy(a => a.Order)) { if (!string.IsNullOrWhiteSpace(responsesParameter.Value) && !responsesParameter.Remove) { cleanedResponse.Add(responsesParameter); } } viewModel.Responses = cleanedResponse; var questionToCreate = new Question(survey); Mapper.Map(question, questionToCreate); foreach (var responsesParameter in viewModel.Responses) { var responseToAdd = new Response { Order = responsesParameter.Order, IsActive = true, Score = responsesParameter.Score.GetValueOrDefault(0), Value = responsesParameter.Value }; questionToCreate.AddResponse(responseToAdd); } ModelState.Clear(); questionToCreate.TransferValidationMessagesTo(ModelState); foreach (var responsesParameter in cleanedResponse) { if (!responsesParameter.Score.HasValue) { ModelState.AddModelError("Question", "All responses need a score"); } else { if(questionToCreate.IsOpenEnded) { switch ((QuestionType)questionToCreate.OpenEndedQuestionType) { case QuestionType.WholeNumber: int number; if (!int.TryParse(responsesParameter.Value, out number)) { ModelState.AddModelError("Question", "Choices must be whole numbers"); } break; case QuestionType.Decimal: float floatNumber; if (!float.TryParse(responsesParameter.Value, out floatNumber)) { ModelState.AddModelError("Question", "Choices must be numbers (decimal ok)"); } break; case QuestionType.Time: float floatTime; if (!responsesParameter.Value.TimeTryParse(out floatTime)) { ModelState.AddModelError("Question", "Choices must be Time (hh:mm)"); } break; case QuestionType.TimeAmPm: float floatTimeAmPm; if (!responsesParameter.Value.TimeTryParseAmPm(out floatTimeAmPm)) { ModelState.AddModelError("Question", "Choices must be Time (hh:mm AM/PM)"); } break; case QuestionType.TimeRange: float timeRangeNumber; if (!float.TryParse(responsesParameter.Value, out timeRangeNumber)) { ModelState.AddModelError("Question", "Choices must be numbers (decimal ok)"); } break; default: ModelState.AddModelError("Question", "time and time range not supported yet"); break; } } } } if (questionToCreate.Responses.Where(a => a.IsActive).Count() == 0) { ModelState.AddModelError("Question", "Responses are required."); } if (questionToCreate.Category != null && !questionToCreate.Category.IsCurrentVersion) { ModelState.AddModelError("Question.Category", "Selected Category is not current."); } if (ModelState.IsValid) { var extraMessage = string.Empty; if (isNewVersion && questionToCreate.IsActive) { questionToCreate.Category = _archiveService.ArchiveCategory(Repository, questionToCreate.Category.Id, questionToCreate.Category); viewModel.Category = questionToCreate.Category; extraMessage = ", related Category versioned"; } _questionRepository.EnsurePersistent(questionToCreate); Message = string.Format("Question Created Successfully{0}", extraMessage); if (viewModel.Category != null) { return this.RedirectToAction<CategoryController>(a => a.Edit(viewModel.Category.Id)); } return this.RedirectToAction<SurveyController>(a => a.Edit(survey.Id)); } return View(viewModel); }
public Category ArchiveCategory(IRepository repository, int id, Question questionToEdit) { var oldVersion = repository.OfType<Category>().Queryable.Where(a => a.Id == id).Single(); var newVersion = new Category(oldVersion.Survey); newVersion.Rank = oldVersion.Rank; newVersion.LastUpdate = DateTime.Now; newVersion.CreateDate = newVersion.LastUpdate; foreach (var categoryGoal in oldVersion.CategoryGoals) { var categoryGoalToDuplicate = new CategoryGoal(); Mapper.Map(categoryGoal, categoryGoalToDuplicate); newVersion.AddCategoryGoal(categoryGoalToDuplicate); } foreach (var question in oldVersion.Questions) { if (question.Id == questionToEdit.Id) { //Ignore } else { var questionToDuplicate = new Question(oldVersion.Survey); questionToDuplicate.Order = question.Order; questionToDuplicate.PrimaryPhoto = question.PrimaryPhoto; foreach (var response in question.Responses) { var newResponse = new Response(); //If I don't do this, the old responses are *moved* here, not copied Mapper.Map(response, newResponse); questionToDuplicate.AddResponse(newResponse); } foreach (var photo in question.Photos) //This should work, but it needs to be tested. { var newPhoto = repository.OfType<Photo>().Queryable.Single(a => a.Id == photo.Id); questionToDuplicate.Photos.Add(newPhoto); } Mapper.Map(question, questionToDuplicate); newVersion.AddQuestions(questionToDuplicate); } } newVersion.IsActive = oldVersion.IsActive; newVersion.Name = oldVersion.Name; newVersion.Affirmation = oldVersion.Affirmation; newVersion.Encouragement = oldVersion.Encouragement; newVersion.DoNotUseForCalculations = oldVersion.DoNotUseForCalculations; newVersion.PreviousVersion = oldVersion; //******************* SAVE repository.OfType<Category>().EnsurePersistent(newVersion); //******************* SAVE oldVersion = repository.OfType<Category>().GetNullableById(id); oldVersion.IsCurrentVersion = false; //******************* SAVE repository.OfType<Category>().EnsurePersistent(oldVersion); //******************* SAVE return newVersion; }