using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.IO; using System.Text; using NLog; using FileHelpers; using System.Data.Entity; using EnVisage.Code.BLL; using EnVisage.App_Start; namespace EnVisage.Code { [DelimitedRecord(",")] public class ActualsImportRow { public String ProjectNumber; public String WeekEndingDate; public String ExpenditureCategoryName; public String Quantity; public String Cost; } public class ImportActuals { EnVisageEntities _dbContext = new EnVisageEntities(); private Dictionary moDateColl; private Guid msScenarioOID; private Dictionary maScenarioDetails;//key = ExpenditureCategoryId + WeekEndingDate private long mlRecsProcessed; private long mlNumRecs; private long mlRecsBypassed; private byte mbPercentComplete; public bool ProcessImport(ActualsImportRow[] ludtInFile, bool firstRowContainsHeaders, bool resetActuals, string userName, out string log) { StringBuilder sbLog = new StringBuilder(); Dictionary projects = new Dictionary(); Dictionary expenditureCategories = new Dictionary(); ; Dictionary actualsScenarioIds = new Dictionary(); string lvHoldMissingPrjID = string.Empty; Guid lvHoldShowNoActuals = Guid.Empty; mlNumRecs = ludtInFile.LongLength; if (ludtInFile.Length < 1 || (firstRowContainsHeaders && ludtInFile.Length < 2)) { sbLog.AppendLine("- Import file does not seem to have enough records for import -- ending data import process."); log = sbLog.ToString(); return false; } //sort input array for better performance with current algorithm: ProjectNumber ASC, ExpCategory ASC, WKEndingDate ASC sbLog.AppendLine("- Sorting input data for better performance"); try { if (firstRowContainsHeaders) { List tmpFirstRow = ludtInFile.Take(1).ToList(); IOrderedEnumerable sortedColl = ludtInFile.Skip(1).OrderBy(r => r.ProjectNumber).ThenBy(r => r.ExpenditureCategoryName).ThenBy(r => DateTime.Parse(r.WeekEndingDate)); tmpFirstRow.AddRange(sortedColl); ludtInFile = tmpFirstRow.ToArray(); } else { ludtInFile = ludtInFile.OrderBy(r => r.ProjectNumber).ThenBy(r => r.ExpenditureCategoryName).ThenBy(r => DateTime.Parse(r.WeekEndingDate)).ToArray(); } } catch (Exception ex) { sbLog.AppendLine("- An error occurred while sorting data: " + ex.Message + " -- ending data import process."); log = sbLog.ToString(); return false; } sbLog.AppendLine("- Retrieving all AWARDED Projects with non-empty unique Project Numbers from the database."); var projectQuery = _dbContext.Projects.Where(p => p.Status.Name == "AWARDED" && p.ProjectNumber != ""); foreach (Project p in projectQuery) { if (projects.ContainsKey(p.ProjectNumber)) { sbLog.AppendLine("- There is more than one project with Project Number " + p.ProjectNumber + " in the system -- ending data import process."); log = sbLog.ToString(); return false; } projects.Add(p.ProjectNumber, p); } if (projects.Count == 0) { sbLog.AppendLine("- 0 Projects retrieved -- ending data import process."); log = sbLog.ToString(); return false; } else { sbLog.AppendLine("- " + projects.Count() + " Projects retrieved from the database."); } sbLog.AppendLine("- Retrieving all Expenditure Category data from the database."); var _expenditureCategories = _dbContext.ExpenditureCategory.ToList(); foreach (var exCat in _expenditureCategories) if (!expenditureCategories.Keys.Contains(exCat.Expenditure.Name)) expenditureCategories.Add(exCat.Expenditure.Name, exCat); if (expenditureCategories.Count() == 0) { sbLog.AppendLine("- 0 Expenditure Categories retrieved -- ending data import process."); log = sbLog.ToString(); return false; } else { sbLog.AppendLine("- " + expenditureCategories.Count() + " Expenditure Categories retrieved from the database."); } for (int i = 0; i < ludtInFile.Length; i++ ) { if (i == 0 && firstRowContainsHeaders) continue; if (!expenditureCategories.ContainsKey(ludtInFile[i].ExpenditureCategoryName)) { sbLog.AppendLine("- Checking expenditure category existence failed - " + ludtInFile[i].ExpenditureCategoryName + " does not exist in the database -- ending data import process."); log = sbLog.ToString(); return false; } } moDateColl = new Dictionary(); bool firstWrite = true; Guid bucketScenarioId = Guid.Empty; Guid bucketExpenditureCatId = Guid.Empty; DateTime bucketWeekEndingDate = DateTime.MinValue; decimal bucketQuantity = 0.0M; decimal bucketCost = 0.0M; long arrayPointer = 0; if (firstRowContainsHeaders) { mlRecsProcessed++; mbPercentComplete = Convert.ToByte(((decimal)mlRecsProcessed / (decimal)mlNumRecs) * (decimal)100); arrayPointer++; } sbLog.AppendLine("- Reading records from input data..."); msScenarioOID = Guid.Empty; for (long i = arrayPointer; i < mlNumRecs; i++) { ActualsImportRow row = ludtInFile[i]; DateTime rowWeekEndingDate = DateTime.MinValue; Guid rowProjectId = Guid.Empty; Guid rowScenarioId = Guid.Empty; Guid rowExpenditureCatId = Guid.Empty; if (!DateTime.TryParse(row.WeekEndingDate, out rowWeekEndingDate)) { sbLog.AppendLine(" - ERROR: Line " + i + " has incorrectly formatted Week Ending Date. The import will be aborted"); log = sbLog.ToString(); return false; } if (!CheckWeekForWorkStatus(rowWeekEndingDate)) { sbLog.AppendLine(" - ERROR: Line " + i + " contains data for a non-work week period. The import will be aborted"); log = sbLog.ToString(); return false; } mlRecsProcessed++; mbPercentComplete = Convert.ToByte(((decimal)mlRecsProcessed / (decimal)mlNumRecs) * (decimal)100); if (projects.ContainsKey(row.ProjectNumber)) { rowProjectId = projects[row.ProjectNumber].Id; } else { if (lvHoldMissingPrjID != row.ProjectNumber) { lvHoldMissingPrjID = row.ProjectNumber; sbLog.AppendLine("- WARNING: Project with Project Number '" + lvHoldMissingPrjID + "' does not have a match in the database."); sbLog.AppendLine(" All subsequent import records with this Project Number will be bypassed."); sbLog.AppendLine(" This Project should be defined in PlanIT. If it is defined, ensure the Project Number is correct."); } mlRecsBypassed++; continue; } if (actualsScenarioIds.ContainsKey(rowProjectId)) { rowScenarioId = actualsScenarioIds[rowProjectId]; } else { var scenario = _dbContext.Scenarios.Where(s => s.ParentId == rowProjectId && s.Type == (int)ScenarioType.Actuals).FirstOrDefault(); if (scenario == null || scenario.Id == Guid.Empty) { if (lvHoldShowNoActuals != rowProjectId) { lvHoldShowNoActuals = rowProjectId; sbLog.AppendLine("- No ACTUALS Scenario data located for Project with Project Number '" + row.ProjectNumber + "' ...bypassing Project."); } mlRecsBypassed++; continue; } rowScenarioId = scenario.Id; actualsScenarioIds.Add(rowProjectId, scenario.Id); ContentLocker.AddLock("Scenario", scenario.Id.ToString(), userName); } rowExpenditureCatId = expenditureCategories[row.ExpenditureCategoryName].Id; if (rowScenarioId == bucketScenarioId && rowExpenditureCatId == bucketExpenditureCatId && rowWeekEndingDate == bucketWeekEndingDate) { bucketQuantity += Decimal.Parse(row.Quantity); bucketCost += Decimal.Parse(row.Cost); } else { if (firstWrite) firstWrite = false; else { DoScDetailFlushToDB(bucketExpenditureCatId, bucketWeekEndingDate, bucketScenarioId, bucketQuantity, bucketCost, resetActuals); } bucketScenarioId = rowScenarioId; bucketExpenditureCatId = rowExpenditureCatId; bucketWeekEndingDate = rowWeekEndingDate; bucketQuantity = Decimal.Parse(row.Quantity); bucketCost = Decimal.Parse(row.Cost); } } if ((mlRecsProcessed - mlRecsBypassed > 0) && bucketScenarioId != Guid.Empty && bucketExpenditureCatId != Guid.Empty) { DoScDetailFlushToDB(bucketExpenditureCatId, bucketWeekEndingDate, bucketScenarioId, bucketQuantity, bucketCost, resetActuals); } sbLog.AppendLine(); sbLog.AppendLine("- Total import records - " + mlRecsProcessed); sbLog.AppendLine("- Total import records bypassed - " + mlRecsBypassed); sbLog.AppendLine("- Total import records processed - " + (mlRecsProcessed - mlRecsBypassed)); sbLog.AppendLine(); _dbContext.SaveChanges(); sbLog.AppendLine("Updating scenario headers and actual buttom up direct costs."); foreach (Guid projectId in actualsScenarioIds.Keys) { Guid scenarioId = actualsScenarioIds[projectId]; Scenario scenario = _dbContext.Scenarios.Where(s => s.Id == scenarioId).FirstOrDefault(); scenario.StartDate = _dbContext.ScenarioDetail.Where(sd => sd.ParentID == scenario.Id).Min(sd => sd.WeekEndingDate).Value; scenario.EndDate = _dbContext.ScenarioDetail.Where(sd => sd.ParentID == scenario.Id).Max(sd => sd.WeekEndingDate).Value; ScenarioManager sm = new ScenarioManager(_dbContext); sm.SetBottomUpCosts(scenario, true); scenario.LastUpdate = DateTime.Now; _dbContext.SaveChanges(); ContentLocker.RemoveLock("Scenario", actualsScenarioIds[projectId].ToString(), userName); sbLog.AppendLine("- Updated Scenario with Id " + actualsScenarioIds[projectId] + " to reflect accurate Start and End Dates."); } sbLog.AppendLine(); sbLog.AppendLine("Data import is complete."); log = sbLog.ToString(); return true; } private void DoScDetailFlushToDB(Guid expenditureCategoryId, DateTime weekEndingDate, Guid scenarioId, decimal quantity, decimal cost, bool resetActuals) { ScenarioDetail detail = DetailUpdate(expenditureCategoryId, weekEndingDate, scenarioId); if (detail == null) { detail = new ScenarioDetail() { Id = Guid.NewGuid(), ParentID = scenarioId, ExpenditureCategoryId = expenditureCategoryId, WeekEndingDate = weekEndingDate, WeekOrdinal = 0, LastUpdate = DateTime.Now, Cost = cost, Quantity = quantity }; _dbContext.ScenarioDetail.Add(detail); } else { if (!detail.Quantity.HasValue) detail.Quantity = 0; if (!detail.Cost.HasValue) detail.Cost = 0; if (resetActuals) { detail.Quantity = quantity; detail.Cost = cost; } else { detail.Quantity += quantity; detail.Cost += cost; } _dbContext.Entry(detail).State = EntityState.Modified; } } private ScenarioDetail DetailUpdate(Guid expenditureCategoryId, DateTime weekEndingDate, Guid scenarioId) { if(scenarioId == Guid.Empty) throw new ArgumentException("scenarioId"); if(scenarioId != msScenarioOID) { msScenarioOID = scenarioId; maScenarioDetails = _dbContext.ScenarioDetail.Where(sd => sd.ParentID == msScenarioOID).OrderBy(sd => sd.ExpenditureCategoryId).ThenBy(sd => sd.WeekEndingDate). ToDictionary(sd => sd.ExpenditureCategoryId.ToString() + sd.WeekEndingDate.ToString()); } return maScenarioDetails.ContainsKey(expenditureCategoryId.ToString() + weekEndingDate.ToString()) ? maScenarioDetails[expenditureCategoryId.ToString() + weekEndingDate.ToString()] : null; } private bool CheckWeekForWorkStatus(DateTime psWeekEndDt) { if (moDateColl.Count == 0) { moDateColl = (from c in _dbContext.FiscalCalendars where c.Type == 0 && c.NonWorking == 1 select c).ToDictionary(c => c.EndDate); } return !moDateColl.ContainsKey(psWeekEndDt); } } }