diff --git a/PPMTool.Data/Entities/Project.cs b/PPMTool.Data/Entities/Project.cs index ab1a1e77..88589a96 100644 --- a/PPMTool.Data/Entities/Project.cs +++ b/PPMTool.Data/Entities/Project.cs @@ -333,6 +333,7 @@ public bool HasResourcesWithNoFundingSourceOnRunningTask() /// Whether to update the subtask costs and save to database /// If necessary a set of financial references /// The percentage of top slice to apply from the settings + /// When no financial references are available in the DB with which to update the costs public void UpdateProjectMetaData(bool recomputeSubTaskCosts, IEnumerable financialReferences, float indirectsPercentage) { // Check conditions for cost update diff --git a/PPMTool.Data/Helpers/AssignmentHelper.cs b/PPMTool.Data/Helpers/AssignmentHelper.cs index dc9a2628..fda63fc1 100644 --- a/PPMTool.Data/Helpers/AssignmentHelper.cs +++ b/PPMTool.Data/Helpers/AssignmentHelper.cs @@ -134,8 +134,8 @@ public static IEnumerable GetAssignmentChunks( ProjectId = project.RTP, ProjectName = project.Name, LeadRSE = project.ProjectManager?.Name ?? "Unknown", - UpperOrgUnit = project.School.Faculty.Name, - LowerOrgUnit = project.School.Name, + UpperOrgUnit = project.School?.Faculty?.Name ?? "Unknown", + LowerOrgUnit = project.School?.Name ?? "Unknown", PI = project.PI, TaskName = task.Name, StartDate = adjustedTaskStart, diff --git a/PPMTool/Program.cs b/PPMTool/Program.cs index 67c0b6ee..815167a1 100644 --- a/PPMTool/Program.cs +++ b/PPMTool/Program.cs @@ -345,6 +345,11 @@ } } +// Set default culture +var cultureInfo = new CultureInfo("en-GB"); +CultureInfo.DefaultThreadCurrentCulture = cultureInfo; +CultureInfo.DefaultThreadCurrentUICulture = cultureInfo; + // Set dummy data seed flag // This is intended for development and testing purposes only and should be used with caution as it will delete existing data. var shouldSeed = builder.Configuration.GetValue("DeveloperSettings:SeedDummyData"); @@ -364,53 +369,51 @@ // execution (throws InvalidCastException: PoolingDataSource -> MultiplexingDataSource), // so the synchronous overload silently fails on PostgreSQL, leaving the schema out of date. await context.Database.MigrateAsync(); -} - -// Seed the default superuser from the settings if it doesn't already exist -SeedHelper.SeedSuperUserIfNotExist(scope.ServiceProvider); -// Seed features -SeedHelper.SeedFeatures(scope.ServiceProvider); + // Seed the default superuser from the settings if it doesn't already exist + SeedHelper.SeedSuperUserIfNotExist(scope.ServiceProvider); -// If seeding run the dummy data seeding methods -if (shouldSeed) -{ - // Seed tables with suitable values -- Note that competencies are already seeded by migrations - SeedHelper.SeedPeople(scope.ServiceProvider); - SeedHelper.SeedAbsences(scope.ServiceProvider); - SeedHelper.SeedUsers(scope.ServiceProvider); - SeedHelper.SeedWorkloadModelChanges(scope.ServiceProvider); - SeedHelper.SeedSkillTags(scope.ServiceProvider); - SeedHelper.SeedOwnedSkillsForPeople(scope.ServiceProvider); - SeedHelper.SeedCompetencyAssessments(scope.ServiceProvider); - SeedHelper.SeedInnateCodesAndTasks(scope.ServiceProvider); - SeedHelper.SeedFinancialReferences(scope.ServiceProvider); - SeedHelper.SeedOrganisationalUnits(scope.ServiceProvider); - SeedHelper.SeedProjects(scope.ServiceProvider); - SeedHelper.SeedFundingSources(scope.ServiceProvider); - SeedHelper.SeedSubTasks(scope.ServiceProvider); - SeedHelper.SeedResources(scope.ServiceProvider); - SeedHelper.SeedNotes(scope.ServiceProvider); - SeedHelper.SeedInvoicesAndPayments(scope.ServiceProvider); - SeedHelper.SeedTimesheets(scope.ServiceProvider); -} - -// Set default culture -var cultureInfo = new CultureInfo("en-GB"); -CultureInfo.DefaultThreadCurrentCulture = cultureInfo; -CultureInfo.DefaultThreadCurrentUICulture = cultureInfo; + // Seed features + SeedHelper.SeedFeatures(scope.ServiceProvider); -// Clean local application file path -FileHelper.CleanLocalApplicationFilePath(logger); + // If seeding run the dummy data seeding methods + if (shouldSeed) + { + // Seed tables with suitable values -- Note that competencies are already seeded by migrations + SeedHelper.SeedPeople(scope.ServiceProvider); + SeedHelper.SeedAbsences(scope.ServiceProvider); + SeedHelper.SeedUsers(scope.ServiceProvider); + SeedHelper.SeedWorkloadModelChanges(scope.ServiceProvider); + SeedHelper.SeedSkillTags(scope.ServiceProvider); + SeedHelper.SeedOwnedSkillsForPeople(scope.ServiceProvider); + SeedHelper.SeedCompetencyAssessments(scope.ServiceProvider); + SeedHelper.SeedInnateCodesAndTasks(scope.ServiceProvider); + SeedHelper.SeedFinancialReferences(scope.ServiceProvider); + SeedHelper.SeedOrganisationalUnits(scope.ServiceProvider); + SeedHelper.SeedProjects(scope.ServiceProvider); + SeedHelper.SeedFundingSources(scope.ServiceProvider); + SeedHelper.SeedSubTasks(scope.ServiceProvider); + SeedHelper.SeedResources(scope.ServiceProvider); + SeedHelper.SeedNotes(scope.ServiceProvider); + SeedHelper.SeedInvoicesAndPayments(scope.ServiceProvider); + SeedHelper.SeedTimesheets(scope.ServiceProvider); + } -// Initialise feature service cache -using (var context = dbContextFactory.CreateDbContext()) -{ + // Initialise the settings cache var settingsService = app.Services.GetRequiredService(); await settingsService.IntialiseServiceCacheAsync(context); + + // Initialise the feature cache var featureService = app.Services.GetRequiredService(); await featureService.IntialiseServiceCacheAsync(context); + + // Update the project meta data for all projects in the database + var projectService = scope.ServiceProvider.GetRequiredService(); + projectService.UpdateAllProjectMetaData(context); } +// Clean local application file path +FileHelper.CleanLocalApplicationFilePath(logger); + // Run the app app.Run(); \ No newline at end of file diff --git a/PPMTool/Services/ProjectService.cs b/PPMTool/Services/ProjectService.cs index 6fcbee2e..27bbcec9 100644 --- a/PPMTool/Services/ProjectService.cs +++ b/PPMTool/Services/ProjectService.cs @@ -12,10 +12,16 @@ namespace PPMTool.Services public class ProjectService : BaseEntityService { private readonly SettingsService settingsService; + private readonly FinancialReferenceService financialReferenceService; - public ProjectService(ILogger logger, SettingsService settingsService) : base(logger) + public ProjectService( + SettingsService settingsService, + FinancialReferenceService financialReferenceService, + ILogger logger + ) : base(logger) { this.settingsService = settingsService; + this.financialReferenceService = financialReferenceService; } /// @@ -214,5 +220,42 @@ internal School GetSchoolAndFaculty(PPMToolContext context, int projectId) .ThenInclude(x => x.Faculty) .FirstOrDefault(x => x.ProjectId == projectId)?.School; } + + /// + /// Updates the metadata for all projects in the database context. + /// This method iterates through all projects, invoking the UpdateProjectMetaData method on each project to refresh its metadata. + /// The changes are then persisted to the database. + /// + /// The database context used to access and update projects. + internal void UpdateAllProjectMetaData(PPMToolContext context) + { + var prefix = settingsService.GetSetting(SettingType.ProjectAbbreviation, string.Empty); + var indirects = settingsService.GetSetting(SettingType.BAUTopSliceFractionDefault, 0f); + var finrefs = financialReferenceService.GetAll(context).ToList(); + var projects = context.Projects + .Include(p => p.SubTasks) + .ThenInclude(s => s.AssignedResources) + .ThenInclude(r => r.Person) + .ThenInclude(r => r.WorkloadModelChanges) + .Include(p => p.FundingSources) + .ToList(); + foreach (var project in projects) + { + try + { + project.UpdateProjectMetaData( + true, + finrefs, + indirects + ); + CommitChanges(context); + logger.LogInformation($"Updated project metadata for project {project.ProjectId} ({prefix}-{project.RTP})"); + } + catch (Exception ex) + { + logger.LogWarning(ex, $"Error occurred while updating project metadata for project {project.ProjectId} ({prefix}-{project.RTP}): {ex.Message}"); + } + } + } } }