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}");
+ }
+ }
+ }
}
}