From f4024d563aa852c98d2fbcdc1e48a54dd09fee1d Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Mon, 21 Sep 2026 16:32:56 +0200 Subject: [PATCH] Implemented centralised audit logging --- PostFundManagement.Api/Program.cs | 3 + .../Services/ICommunicationsService.cs | 2 + .../Abstractions/Services/IEvidenceService.cs | 6 + .../Services/IExecutionService.cs | 6 + .../Services/IGovernanceService.cs | 9 +- .../Abstractions/Services/IGrantsService.cs | 11 +- .../Abstractions/Services/IIdentityService.cs | 5 +- .../Abstractions/Services/IProjectService.cs | 8 + .../Abstractions/Services/IUserService.cs | 3 + .../Extensions/Services.cs | 73 +- .../Filters/ActiveUserFilter.cs | 2 +- .../Interceptors/AuditInterceptor.cs | 211 ++ .../PostFundManagement.Application.csproj | 2 + .../Services/Shared/AuditService.cs | 27 + .../Abstractions/ITenantContextService.cs | 9 + .../Attributes/AuditAttribute.cs | 4 + .../Entities/AuditLog.cs | 17 +- PostFundManagement.Domain/Entities/Award.cs | 9 + .../Entities/Notification.cs | 9 + .../Entities/Portfolio.cs | 9 + .../Entities/Programme.cs | 9 + PostFundManagement.Domain/Entities/Project.cs | 9 + .../Entities/TenantUser.cs | 6 + .../Extensions/Mappers.cs | 40 +- PostFundManagement.Domain/Models/AuditLog.cs | 4 +- PostFundManagement.Domain/Models/Award.cs | 2 + .../Models/Notification.cs | 2 + PostFundManagement.Domain/Models/Portfolio.cs | 2 + PostFundManagement.Domain/Models/Programme.cs | 2 + PostFundManagement.Domain/Models/Project.cs | 2 + PostFundManagement.Domain/Models/Tenant.cs | 4 +- .../Database/ApplicationDbContext.cs | 55 +- .../Factories/ApplicationDbContextFactory.cs | 13 +- .../20260921085528_AddedTenant.Designer.cs | 2233 ++++++++++++++++ .../Migrations/20260921085528_AddedTenant.cs | 370 +++ .../20260921125152_UpdateAuditLog.Designer.cs | 2246 +++++++++++++++++ .../20260921125152_UpdateAuditLog.cs | 64 + .../ApplicationDbContextModelSnapshot.cs | 273 +- .../DesignTimeTenantContextService.cs | 14 + .../Database/Services/TenantContextService.cs | 29 + .../PostFundManagement.Infrastructure.csproj | 1 + 41 files changed, 5751 insertions(+), 54 deletions(-) create mode 100644 PostFundManagement.Application/Interceptors/AuditInterceptor.cs create mode 100644 PostFundManagement.Application/Services/Shared/AuditService.cs create mode 100644 PostFundManagement.Domain/Abstractions/ITenantContextService.cs create mode 100644 PostFundManagement.Domain/Attributes/AuditAttribute.cs create mode 100644 PostFundManagement.Infrastructure/Database/Migrations/20260921085528_AddedTenant.Designer.cs create mode 100644 PostFundManagement.Infrastructure/Database/Migrations/20260921085528_AddedTenant.cs create mode 100644 PostFundManagement.Infrastructure/Database/Migrations/20260921125152_UpdateAuditLog.Designer.cs create mode 100644 PostFundManagement.Infrastructure/Database/Migrations/20260921125152_UpdateAuditLog.cs create mode 100644 PostFundManagement.Infrastructure/Database/Services/DesignTimeTenantContextService.cs create mode 100644 PostFundManagement.Infrastructure/Database/Services/TenantContextService.cs diff --git a/PostFundManagement.Api/Program.cs b/PostFundManagement.Api/Program.cs index 2d378b6..ee03d59 100644 --- a/PostFundManagement.Api/Program.cs +++ b/PostFundManagement.Api/Program.cs @@ -1,8 +1,10 @@ using Microsoft.FeatureManagement; using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Filters; +using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Health; using PostFundManagement.Domain.Mediator; +using PostFundManagement.Infrastructure.Database.Services; using static PostFundManagement.Domain.Extensions.Constants; var builder = WebApplication.CreateBuilder(args); @@ -27,6 +29,7 @@ builder.Services.AddApiVersioning(options => }); builder.Services.AddScoped(); +builder.Services.AddTransient(); builder.Services.AddEndpoints(Assembly.GetExecutingAssembly()); builder.Services.AddApiServices(builder.Configuration); diff --git a/PostFundManagement.Application/Abstractions/Services/ICommunicationsService.cs b/PostFundManagement.Application/Abstractions/Services/ICommunicationsService.cs index 9a3b259..10aebbb 100644 --- a/PostFundManagement.Application/Abstractions/Services/ICommunicationsService.cs +++ b/PostFundManagement.Application/Abstractions/Services/ICommunicationsService.cs @@ -1,11 +1,13 @@ using PostFundManagement.Application.Services.Communications; using PostFundManagement.Domain; +using PostFundManagement.Domain.Attributes; using PostFundManagement.Domain.Models; namespace PostFundManagement.Application.Abstractions.Services; public interface ICommunicationsService { + [Audit] ValueTask> SendInviteAsync(InviteRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default); diff --git a/PostFundManagement.Application/Abstractions/Services/IEvidenceService.cs b/PostFundManagement.Application/Abstractions/Services/IEvidenceService.cs index 57550a0..751e11b 100644 --- a/PostFundManagement.Application/Abstractions/Services/IEvidenceService.cs +++ b/PostFundManagement.Application/Abstractions/Services/IEvidenceService.cs @@ -1,21 +1,26 @@ using PostFundManagement.Application.Services.Evidence; using PostFundManagement.Domain; +using PostFundManagement.Domain.Attributes; using PostFundManagement.Domain.Models; namespace PostFundManagement.Application.Abstractions.Services; public interface IEvidenceService { + [Audit] ValueTask CreateEvidenceAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default); + [Audit] ValueTask RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default); ValueTask> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default); ValueTask> GetEvidenceFilesAsync(Pagination pagination, CancellationToken cancellationToken = default); + [Audit] ValueTask CreateMilestoneAsync(CreateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default); + [Audit] ValueTask CreateIndicatorAsync(CreateIndicatorRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken); @@ -26,6 +31,7 @@ public interface IEvidenceService ValueTask> GetProjectEvidencesAsync(string projectIdHash, CancellationToken cancellationToken = default); + [Audit] ValueTask UpdateEvidenceAsync(UpdateEvidenceRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask> GetEvidenceUrlAsync(string evidenceIdHash, CancellationToken cancellationToken = default); diff --git a/PostFundManagement.Application/Abstractions/Services/IExecutionService.cs b/PostFundManagement.Application/Abstractions/Services/IExecutionService.cs index df89145..6cd37bc 100644 --- a/PostFundManagement.Application/Abstractions/Services/IExecutionService.cs +++ b/PostFundManagement.Application/Abstractions/Services/IExecutionService.cs @@ -1,5 +1,6 @@ using PostFundManagement.Application.Services.Execution; using PostFundManagement.Domain; +using PostFundManagement.Domain.Attributes; using PostFundManagement.Domain.Models; namespace PostFundManagement.Application.Abstractions.Services; @@ -10,17 +11,22 @@ public interface IExecutionService ValueTask> GetDisbursementsAsync(Pagination pagination, CancellationToken cancellationToken = default); + [Audit] ValueTask CreateRiskAsync(CreateRiskRequest request, Guid userId, CancellationToken cancellationToken = default); + [Audit] ValueTask CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default); + [Audit] ValueTask ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default); ValueTask> GetDisbursementAsync(string disbursementIdHash, CancellationToken cancellationToken = default); ValueTask> GetRiskAsync(string riskIdHash, CancellationToken cancellationToken = default); + [Audit] ValueTask UpdateRiskAsync(UpdateRiskRequest request, CancellationToken cancellationToken = default); + [Audit] ValueTask UpdateDisbursementAsync(UpdateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default); } diff --git a/PostFundManagement.Application/Abstractions/Services/IGovernanceService.cs b/PostFundManagement.Application/Abstractions/Services/IGovernanceService.cs index c1d7b61..477125e 100644 --- a/PostFundManagement.Application/Abstractions/Services/IGovernanceService.cs +++ b/PostFundManagement.Application/Abstractions/Services/IGovernanceService.cs @@ -1,5 +1,6 @@ using PostFundManagement.Application.Services.Governance; using PostFundManagement.Domain; +using PostFundManagement.Domain.Attributes; using PostFundManagement.Domain.Models; namespace PostFundManagement.Application.Abstractions.Services; @@ -18,12 +19,16 @@ public interface IGovernanceService ValueTask> GetDashboardAsync(CancellationToken cancellationToken = default); + [Audit] ValueTask CreateOrganisationAsync(CreateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default); + [Audit] ValueTask> UpdateOrganisationAsync(UpdateOrganisationRequest request, CancellationToken cancellationToken = default); - + + [Audit] ValueTask CreatePortfolioAsync(CreatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default); - + + [Audit] ValueTask UpdatePortfolioAsync(UpdatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask> GetPortfolioProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default); diff --git a/PostFundManagement.Application/Abstractions/Services/IGrantsService.cs b/PostFundManagement.Application/Abstractions/Services/IGrantsService.cs index ca9bf30..22dce48 100644 --- a/PostFundManagement.Application/Abstractions/Services/IGrantsService.cs +++ b/PostFundManagement.Application/Abstractions/Services/IGrantsService.cs @@ -1,5 +1,6 @@ using PostFundManagement.Application.Services.Grants; using PostFundManagement.Domain; +using PostFundManagement.Domain.Attributes; using PostFundManagement.Domain.Models; namespace PostFundManagement.Application.Abstractions.Services; @@ -18,17 +19,23 @@ public interface IGrantsService ValueTask> GetAwardAsync(string awardIdHash, CancellationToken cancellationToken = default); + [Audit] ValueTask ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default); + [Audit] ValueTask CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default); + [Audit] ValueTask CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default); + [Audit] ValueTask CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask> GetContractUrlAsync(string contractIdHash, CancellationToken cancellationToken = default); - + + [Audit] ValueTask UploadContractAsync(string contractIdHash, Guid userId, IFormFile file, CancellationToken cancellationToken = default); - + + [Audit] ValueTask UpdateContractAsync(UpdateContractRequest request, CancellationToken cancellationToken = default); } diff --git a/PostFundManagement.Application/Abstractions/Services/IIdentityService.cs b/PostFundManagement.Application/Abstractions/Services/IIdentityService.cs index bb4036e..9056366 100644 --- a/PostFundManagement.Application/Abstractions/Services/IIdentityService.cs +++ b/PostFundManagement.Application/Abstractions/Services/IIdentityService.cs @@ -1,6 +1,9 @@ -namespace PostFundManagement.Application.Abstractions.Services; +using PostFundManagement.Domain.Attributes; + +namespace PostFundManagement.Application.Abstractions.Services; public interface IIdentityService { + [Audit] ValueTask UserIsAuthorisedAsync(Guid userId, CancellationToken cancellationToken = default); } diff --git a/PostFundManagement.Application/Abstractions/Services/IProjectService.cs b/PostFundManagement.Application/Abstractions/Services/IProjectService.cs index f99f26d..b5f80d5 100644 --- a/PostFundManagement.Application/Abstractions/Services/IProjectService.cs +++ b/PostFundManagement.Application/Abstractions/Services/IProjectService.cs @@ -1,11 +1,13 @@ using PostFundManagement.Application.Services.Projects; using PostFundManagement.Domain; +using PostFundManagement.Domain.Attributes; using PostFundManagement.Domain.Models; namespace PostFundManagement.Application.Abstractions.Services; public interface IProjectService { + [Audit] ValueTask UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default); @@ -14,8 +16,10 @@ public interface IProjectService ValueTask> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default); + [Audit] ValueTask CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default); + [Audit] ValueTask CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask> GetProjectMilestonesAsync(string projectIdHash, CancellationToken cancellationToken = default); @@ -26,16 +30,20 @@ public interface IProjectService ValueTask> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default); + [Audit] ValueTask UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default); + [Audit] ValueTask UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken = default); ValueTask> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default); + [Audit] ValueTask CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default); + [Audit] ValueTask UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default); diff --git a/PostFundManagement.Application/Abstractions/Services/IUserService.cs b/PostFundManagement.Application/Abstractions/Services/IUserService.cs index 3cd5bdc..9b6d5c3 100644 --- a/PostFundManagement.Application/Abstractions/Services/IUserService.cs +++ b/PostFundManagement.Application/Abstractions/Services/IUserService.cs @@ -1,15 +1,18 @@ using PostFundManagement.Application.Services.Users; using PostFundManagement.Domain; +using PostFundManagement.Domain.Attributes; using PostFundManagement.Domain.Models; namespace PostFundManagement.Application.Abstractions.Services; public interface IUserService { + [Audit] ValueTask SyncUserAsync(Guid userId, string email, CancellationToken cancellationToken = default); ValueTask> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default); + [Audit] ValueTask UpdateUserStatusAsync(Guid userId, ActivityStatus status, CancellationToken cancellationToken = default); ValueTask> GetUserAsync(Guid userId, CancellationToken cancellationToken); diff --git a/PostFundManagement.Application/Extensions/Services.cs b/PostFundManagement.Application/Extensions/Services.cs index c571328..127a317 100644 --- a/PostFundManagement.Application/Extensions/Services.cs +++ b/PostFundManagement.Application/Extensions/Services.cs @@ -1,5 +1,6 @@ using PostFundManagement.Application.Abstractions; using PostFundManagement.Application.Abstractions.Services; +using PostFundManagement.Application.Interceptors; using PostFundManagement.Application.Services.Communications; using PostFundManagement.Application.Services.Evidence; using PostFundManagement.Application.Services.Execution; @@ -15,32 +16,60 @@ namespace PostFundManagement.Application.Extensions; public static class Services { + public static IServiceCollection AddAuditedCachedScoped(this IServiceCollection services) + where TInterface : class + where TImplementation : class, TInterface + where TCacheDecorator : class, TInterface + { + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(sp => + { + var proxyGenerator = sp.GetRequiredService(); + var interceptor = sp.GetRequiredService(); + + var actual = sp.GetRequiredService(); + + var cached = ActivatorUtilities.CreateInstance(sp, actual); + + return proxyGenerator.CreateInterfaceProxyWithTarget(cached, interceptor.ToInterceptor()); + }); + + return services; + } + + public static IServiceCollection AddAuditedScoped(this IServiceCollection services) + where TInterface : class + where TImplementation : class, TInterface + { + services.AddScoped(); + services.AddScoped(sp => + { + var proxyGenerator = sp.GetRequiredService(); + var actual = sp.GetRequiredService(); + var interceptor = sp.GetRequiredService(); + + return proxyGenerator.CreateInterfaceProxyWithTarget(actual, interceptor.ToInterceptor()); + }); + + return services; + } + public static IServiceCollection AddRegisterServicesWithCaching(this IServiceCollection services, IConfiguration configuration) { - services.AddScoped(); - services.Decorate(); + services.AddScoped(); + services.AddSingleton(); - services.AddScoped(); - services.Decorate(); + services.AddAuditedCachedScoped(); + services.AddAuditedCachedScoped(); + services.AddAuditedCachedScoped(); + services.AddAuditedCachedScoped(); + services.AddAuditedCachedScoped(); + services.AddAuditedCachedScoped(); + services.AddAuditedCachedScoped(); + services.AddAuditedCachedScoped(); - services.AddScoped(); - services.Decorate(); - - services.AddScoped(); - services.Decorate(); - - services.AddScoped(); - services.Decorate(); - - services.AddScoped(); - services.Decorate(); - - services.AddScoped(); - services.Decorate(); - - services.AddScoped(); - services.Decorate(); - services.AddScoped(); services.Decorate(); diff --git a/PostFundManagement.Application/Filters/ActiveUserFilter.cs b/PostFundManagement.Application/Filters/ActiveUserFilter.cs index feba9b0..0bd8ecd 100644 --- a/PostFundManagement.Application/Filters/ActiveUserFilter.cs +++ b/PostFundManagement.Application/Filters/ActiveUserFilter.cs @@ -26,7 +26,7 @@ public sealed class ActiveUserFilter(IIdentityService identityService) : IEndpoi if (!string.IsNullOrWhiteSpace(path) && !path!.EndsWith("integration/sync", StringComparison.InvariantCultureIgnoreCase)) if ((await identityService.UserIsAuthorisedAsync(userId, context.HttpContext.RequestAborted)).IsFailed) - return (object?)Results.Json(new { message = "Account approval is pending or inactive." }, statusCode: StatusCodes.Status403Forbidden); + return Results.Json(new { message = "Account approval is pending or inactive." }, statusCode: StatusCodes.Status403Forbidden); context.HttpContext.Items[UserIdItemKey] = userId; context.HttpContext.Items[EmailtemKey] = email; diff --git a/PostFundManagement.Application/Interceptors/AuditInterceptor.cs b/PostFundManagement.Application/Interceptors/AuditInterceptor.cs new file mode 100644 index 0000000..02f2aab --- /dev/null +++ b/PostFundManagement.Application/Interceptors/AuditInterceptor.cs @@ -0,0 +1,211 @@ +using PostFundManagement.Application.Services.Shared; +using PostFundManagement.Domain.Attributes; + +namespace PostFundManagement.Application.Interceptors; + +public sealed class AuditInterceptor(AuditService auditService, IHttpContextAccessor httpContextAccessor) : IAsyncInterceptor +{ + public void InterceptSynchronous(IInvocation invocation) => invocation.Proceed(); + + public void InterceptAsynchronous(IInvocation invocation) => + invocation.ReturnValue = InternalInterceptAsynchronous(invocation); + + public void InterceptAsynchronous(IInvocation invocation) => + invocation.ReturnValue = InternalInterceptAsynchronous(invocation); + + private static async Task InternalInterceptAsynchronous(IInvocation invocation) + { + invocation.Proceed(); + + var task = (Task)invocation.ReturnValue; + + await task.ConfigureAwait(false); + } + + private async Task InternalInterceptAsynchronous(IInvocation invocation) + { + invocation.Proceed(); + + var task = (Task)invocation.ReturnValue; + var resultObj = await task.ConfigureAwait(false); + + var method = invocation.MethodInvocationTarget ?? invocation.Method; + var auditAttr = method.GetCustomAttribute() + ?? invocation.Method.GetCustomAttribute(); + + if (auditAttr == null) return resultObj; + + if (resultObj is IResultBase result && result.IsSuccess) + { + try + { + var action = ResolveActionName(method.Name); + var entityName = ResolveEntityName(method, resultObj, invocation.Arguments); + var entityId = ResolveEntityId(resultObj, invocation.Arguments); + var changes = BuildPayloadSummary(invocation.Method.GetParameters(), invocation.Arguments); + var performedBy = ResolveUserId(); + + await auditService.LogActionAsync( + entityId: entityId, + entityName: entityName, + action: action, + changes: changes, + performedBy: performedBy).ConfigureAwait(false); + } + catch { /* eat all exceptions, don't interrupt program flow */ } + } + + return resultObj; + } + + private static string ResolveActionName(string methodName) => + methodName.EndsWith("Async", StringComparison.Ordinal) + ? methodName[..^5] + : methodName; + + private static string ResolveEntityName(MethodInfo method, object? resultObj, object[] args) + { + var returnType = method.ReturnType; + + Type? innerType = null; + + if (returnType.IsGenericType) + { + var genericDef = returnType.GetGenericTypeDefinition(); + + if (genericDef == typeof(Task<>) || genericDef == typeof(ValueTask<>)) + innerType = returnType.GetGenericArguments()[0]; + } + + if (innerType != null && innerType.IsGenericType && typeof(IResultBase).IsAssignableFrom(innerType)) + { + var valueType = innerType.GetGenericArguments().FirstOrDefault(); + + if (valueType != null && valueType != typeof(bool) && valueType != typeof(int) && valueType != typeof(string)) + return valueType.Name; + } + + foreach (var arg in args) + { + if (arg == null || arg is CancellationToken or IFormFile) continue; + + var argTypeName = arg.GetType().Name; + + if (argTypeName.EndsWith("Request", StringComparison.Ordinal)) + { + var trimmed = argTypeName[..^7]; + + return trimmed.Replace("Create", "").Replace("Update", ""); + } + } + + return ResolveActionName(method.Name); + } + + private static string ResolveEntityId(object? resultObj, object[] args) + { + if (resultObj != null) + { + var valueProp = resultObj.GetType().GetProperty("Value"); + if (valueProp != null) + { + var value = valueProp.GetValue(resultObj); + if (value != null) + { + var idProp = value.GetType().GetProperty("Id") ?? value.GetType().GetProperty("id"); + if (idProp != null) + { + var idVal = idProp.GetValue(value); + if (idVal != null) return idVal.ToString()!; + } + } + } + } + + foreach (var arg in args) + { + if (arg is long or int or Guid or string) + return arg.ToString()!; + + if (arg != null && arg is not CancellationToken and not IFormFile) + { + var idProp = arg.GetType().GetProperty("Id") ?? arg.GetType().GetProperty("id"); + + if (idProp != null) + { + var idVal = idProp.GetValue(arg); + + if (idVal != null) return idVal.ToString()!; + } + } + } + + return "N/A"; + } + + private static string[] BuildPayloadSummary(ParameterInfo[] parameters, object[] args) + { + var entries = new List(); + + for (var i = 0; i < parameters.Length; i++) + { + var param = parameters[i]; + var arg = args[i]; + + if (param.ParameterType == typeof(CancellationToken)) + continue; + + if (arg == null) + { + entries.Add($"{param.Name}: null"); + + continue; + } + + if (arg is IFormFile file) + { + entries.Add($"{param.Name}: [File] Name='{file.FileName}', Size={file.Length} bytes, Type='{file.ContentType}'"); + + continue; + } + + if (arg is IEnumerable files) + { + var fileSummaries = files.Select(f => $"'{f.FileName}' ({f.Length} bytes)"); + + entries.Add($"{param.Name}: [Files] count={fileSummaries.Count()} [{string.Join(", ", fileSummaries)}]"); + + continue; + } + + if (arg is Stream stream) + { + entries.Add($"{param.Name}: [Stream] Length={(stream.CanSeek ? stream.Length : -1)} bytes"); + + continue; + } + + try + { + var json = JsonSerializer.Serialize(arg); + + entries.Add($"{param.Name}: {json}"); + } + catch + { + entries.Add($"{param.Name}: [Unserializable]"); + } + } + + return [.. entries]; + } + + private Guid ResolveUserId() + { + var user = httpContextAccessor.HttpContext?.User; + + var sub = user?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? user?.FindFirst("sub")?.Value; + + return Guid.TryParse(sub, out var guid) ? guid : Guid.Empty; + } +} \ No newline at end of file diff --git a/PostFundManagement.Application/PostFundManagement.Application.csproj b/PostFundManagement.Application/PostFundManagement.Application.csproj index b69904b..cb7e900 100644 --- a/PostFundManagement.Application/PostFundManagement.Application.csproj +++ b/PostFundManagement.Application/PostFundManagement.Application.csproj @@ -24,6 +24,7 @@ + @@ -36,6 +37,7 @@ + diff --git a/PostFundManagement.Application/Services/Shared/AuditService.cs b/PostFundManagement.Application/Services/Shared/AuditService.cs new file mode 100644 index 0000000..5104493 --- /dev/null +++ b/PostFundManagement.Application/Services/Shared/AuditService.cs @@ -0,0 +1,27 @@ +using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; +using PostFundManagement.Infrastructure.Database; + +namespace PostFundManagement.Application.Services.Shared; + +public sealed class AuditService(IDbContextFactory contextFactory, ITenantContextService tenantContextService) : IService +{ + public async ValueTask LogActionAsync(string entityId, string entityName, string action, string[] changes, Guid performedBy, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + context.Add(new Domain.Entities.AuditLog + { + EntityId = entityId, + Action = action, + Changes = changes, + EntityName = entityName, + PerformedBy = performedBy, + Timestamp = DateTime.UtcNow, + TenantId = tenantContextService.TenantId > 0 ? tenantContextService.TenantId : null, + }); + + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); +} diff --git a/PostFundManagement.Domain/Abstractions/ITenantContextService.cs b/PostFundManagement.Domain/Abstractions/ITenantContextService.cs new file mode 100644 index 0000000..21a94cc --- /dev/null +++ b/PostFundManagement.Domain/Abstractions/ITenantContextService.cs @@ -0,0 +1,9 @@ +namespace PostFundManagement.Domain.Abstractions; + +public interface ITenantContextService +{ + string? TenantName { get; } + long TenantId { get; } + bool IsAdmin { get; } + bool HasTenantFilter { get; } +} diff --git a/PostFundManagement.Domain/Attributes/AuditAttribute.cs b/PostFundManagement.Domain/Attributes/AuditAttribute.cs new file mode 100644 index 0000000..7d5d758 --- /dev/null +++ b/PostFundManagement.Domain/Attributes/AuditAttribute.cs @@ -0,0 +1,4 @@ +namespace PostFundManagement.Domain.Attributes; + +[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] +public sealed class AuditAttribute : Attribute; diff --git a/PostFundManagement.Domain/Entities/AuditLog.cs b/PostFundManagement.Domain/Entities/AuditLog.cs index 53cf177..7d8d833 100644 --- a/PostFundManagement.Domain/Entities/AuditLog.cs +++ b/PostFundManagement.Domain/Entities/AuditLog.cs @@ -5,16 +5,19 @@ namespace PostFundManagement.Domain.Entities; [EntityTypeConfiguration] public class AuditLog : Models.AuditLog { - public virtual User? Performer { get; set; } + public virtual Tenant? Tenant { get; set; } + + public virtual User? Performer { get; set; } } -public sealed class AuditLogConfiguration : IEntityTypeConfiguration +public sealed class AuditLogConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) + public void Configure(EntityTypeBuilder builder) { - builder.ToTable(nameof(Domain.Entities.AuditLog).Pluralize()); + builder.ToTable(nameof(AuditLog).Pluralize()); builder.HasKey(pk => pk.Id); + builder.Property(f => f.TenantId).IsRequired(false); builder.Property(f => f.EntityName).IsRequired().HasMaxLength(LabelLength); builder.Property(f => f.EntityId).IsRequired(); builder.Property(f => f.Action).IsRequired().HasMaxLength(ShortLabelLength); @@ -22,6 +25,12 @@ public sealed class AuditLogConfiguration : IEntityTypeConfiguration f.PerformedBy).IsRequired(); builder.Property(f => f.Timestamp).IsRequired().HasDefaultValueSql("now()"); + builder.HasOne(f => f.Tenant) + .WithMany() + .HasForeignKey(fk => fk.TenantId) + .IsRequired(false) + .OnDelete(DeleteBehavior.NoAction); + builder.HasOne(f => f.Performer) .WithMany() .HasForeignKey(fk => fk.PerformedBy) diff --git a/PostFundManagement.Domain/Entities/Award.cs b/PostFundManagement.Domain/Entities/Award.cs index 488d234..cfaf78c 100644 --- a/PostFundManagement.Domain/Entities/Award.cs +++ b/PostFundManagement.Domain/Entities/Award.cs @@ -9,6 +9,8 @@ public class Award : Models.Award public virtual User? Approver { get; set; } + public virtual Tenant? Tenant { get; set; } + public virtual Organisation? Organisation { get; set; } public virtual Programme? Programme { get; set; } @@ -27,6 +29,7 @@ public sealed class AwardConfiguration : IEntityTypeConfiguration builder.HasKey(pk => pk.Id); builder.Property(f => f.OrganisationId).IsRequired(); builder.Property(f => f.ProgrammeId).IsRequired(); + builder.Property(f => f.TenantId).IsRequired(); builder.Property(f => f.ProjectId).IsRequired(false); builder.Property(f => f.CreatedBy).IsRequired(); builder.Property(f => f.UpdatedBy).IsRequired(false); @@ -37,6 +40,12 @@ public sealed class AwardConfiguration : IEntityTypeConfiguration .HasSentinel(ApprovalStatus.Pending); builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2); + builder.HasOne(f => f.Tenant) + .WithMany() + .HasForeignKey(fk => fk.TenantId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + builder.HasOne(f => f.Organisation) .WithMany() .HasForeignKey(fk => fk.OrganisationId) diff --git a/PostFundManagement.Domain/Entities/Notification.cs b/PostFundManagement.Domain/Entities/Notification.cs index ce256f9..d6cfc05 100644 --- a/PostFundManagement.Domain/Entities/Notification.cs +++ b/PostFundManagement.Domain/Entities/Notification.cs @@ -5,6 +5,8 @@ namespace PostFundManagement.Domain.Entities; [EntityTypeConfiguration] public class Notification : Models.Notification { + public virtual Tenant? Tenant { get; set; } + public virtual Organisation? Organisation { get; set; } public virtual User? RecipientUser { get; set; } @@ -22,6 +24,7 @@ public sealed class NotificationConfiguration : IEntityTypeConfiguration f.SentAt).IsRequired(false); builder.Property(f => f.Recipient).IsRequired(); builder.Property(f => f.OrganisationId).IsRequired(); + builder.Property(f => f.TenantId).IsRequired(); builder.Property(f => f.Platform).IsRequired().HasConversion().HasDefaultValueSql("1") .HasSentinel(NotificationPlatform.Email); builder.Property(f => f.Status).IsRequired().HasConversion().HasDefaultValueSql("1") @@ -31,6 +34,12 @@ public sealed class NotificationConfiguration : IEntityTypeConfiguration f.Destination).IsRequired().HasMaxLength(LabelLength); builder.Property(f => f.Error).IsRequired(false); + builder.HasOne(f => f.Tenant) + .WithMany() + .HasForeignKey(fk => fk.TenantId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + builder.HasOne(f => f.Organisation) .WithMany() .HasForeignKey(fk => fk.OrganisationId) diff --git a/PostFundManagement.Domain/Entities/Portfolio.cs b/PostFundManagement.Domain/Entities/Portfolio.cs index 9b48583..7c2ff85 100644 --- a/PostFundManagement.Domain/Entities/Portfolio.cs +++ b/PostFundManagement.Domain/Entities/Portfolio.cs @@ -9,6 +9,8 @@ public class Portfolio : Models.Portfolio public virtual User? Owner { get; set; } + public virtual Tenant? Tenant { get; set; } + public virtual Organisation? Organisation { get; set; } public virtual ICollection Programmes { get; set; } = []; @@ -21,6 +23,7 @@ public sealed class PortfolioConfiguration : IEntityTypeConfiguration builder.ToTable(nameof(Portfolio).Pluralize()); builder.HasKey(pk => pk.Id); + builder.Property(f => f.TenantId).IsRequired(); builder.Property(f => f.OrganisationId).IsRequired(); builder.Property(f => f.CreatedBy).IsRequired(); builder.Property(f => f.OwnedBy).IsRequired(); @@ -28,6 +31,12 @@ public sealed class PortfolioConfiguration : IEntityTypeConfiguration builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength); builder.Property(f => f.Description).IsRequired(false).HasMaxLength(TextLength); + builder.HasOne(f => f.Tenant) + .WithMany() + .HasForeignKey(fk => fk.TenantId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + builder.HasOne(f => f.Organisation) .WithMany() .HasForeignKey(fk => fk.OrganisationId) diff --git a/PostFundManagement.Domain/Entities/Programme.cs b/PostFundManagement.Domain/Entities/Programme.cs index 052a261..4ce3583 100644 --- a/PostFundManagement.Domain/Entities/Programme.cs +++ b/PostFundManagement.Domain/Entities/Programme.cs @@ -8,6 +8,8 @@ public class Programme : Models.Programme public virtual User? Creator { get; set; } public virtual User? Owner { get; set; } + + public virtual Tenant? Tenant { get; set; } public virtual Portfolio? Portfolio { get; set; } @@ -27,6 +29,7 @@ public sealed class ProgrammeConfiguration : IEntityTypeConfiguration builder.ToTable(nameof(Programme).Pluralize()); builder.HasKey(pk => pk.Id); + builder.Property(f => f.TenantId).IsRequired(); builder.Property(f => f.PortfolioId).IsRequired(); builder.Property(f => f.OwnedBy).IsRequired(); builder.Property(f => f.CreatedBy).IsRequired(); @@ -37,6 +40,12 @@ public sealed class ProgrammeConfiguration : IEntityTypeConfiguration builder.Property(f => f.Status).IsRequired().HasConversion().HasDefaultValueSql("1") .HasSentinel(ApprovalStatus.Pending); + builder.HasOne(f => f.Tenant) + .WithMany() + .HasForeignKey(fk => fk.TenantId) + .IsRequired() + .OnDelete(DeleteBehavior.NoAction); + builder.HasOne(f => f.Portfolio) .WithMany(f => f.Programmes) .HasForeignKey(fk => fk.PortfolioId) diff --git a/PostFundManagement.Domain/Entities/Project.cs b/PostFundManagement.Domain/Entities/Project.cs index 3bbfa25..37be0b8 100644 --- a/PostFundManagement.Domain/Entities/Project.cs +++ b/PostFundManagement.Domain/Entities/Project.cs @@ -9,6 +9,8 @@ public class Project : Models.Project public virtual User? Updator { get; set; } + public virtual Tenant? Tenant { get; set; } + public virtual Programme? Programme { get; set; } public virtual ICollection Awards { get; set; } = []; @@ -21,6 +23,7 @@ public sealed class ProjectConfiguration : IEntityTypeConfiguration builder.ToTable(nameof(Project).Pluralize()); builder.HasKey(pk => pk.Id); + builder.Property(f => f.TenantId).IsRequired(); builder.Property(f => f.ProgrammeId).IsRequired(); builder.Property(f => f.CreatedBy).IsRequired(); builder.Property(f => f.UpdatedBy).IsRequired(false); @@ -37,6 +40,12 @@ public sealed class ProjectConfiguration : IEntityTypeConfiguration builder.Property(f => f.Status).IsRequired().HasConversion().HasDefaultValueSql("1") .HasSentinel(ApprovalStatus.Pending); + builder.HasOne(f => f.Tenant) + .WithMany() + .HasForeignKey(fk => fk.TenantId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + builder.HasOne(f => f.Programme) .WithMany(f => f.Projects) .HasForeignKey(fk => fk.ProgrammeId) diff --git a/PostFundManagement.Domain/Entities/TenantUser.cs b/PostFundManagement.Domain/Entities/TenantUser.cs index cca0418..0d9f570 100644 --- a/PostFundManagement.Domain/Entities/TenantUser.cs +++ b/PostFundManagement.Domain/Entities/TenantUser.cs @@ -30,6 +30,12 @@ public sealed class TenantUserConfiguration : IEntityTypeConfiguration f.User) + .WithMany() + .HasForeignKey(fk => fk.UserId) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + builder.HasOne(f => f.Creator) .WithMany() .HasForeignKey(fk => fk.CreatedBy) diff --git a/PostFundManagement.Domain/Extensions/Mappers.cs b/PostFundManagement.Domain/Extensions/Mappers.cs index 1e547ff..0355088 100644 --- a/PostFundManagement.Domain/Extensions/Mappers.cs +++ b/PostFundManagement.Domain/Extensions/Mappers.cs @@ -4,6 +4,37 @@ namespace PostFundManagement.Domain.Extensions; public static class Mappers { + public static TenantUser Map(this Entities.TenantUser entity) => new() + { + Id = entity.Id, + TenantId = entity.TenantId, + CreatedAt = entity.CreatedAt, + CreatedBy = entity.CreatedBy, + Status = entity.Status, + UserId = entity.UserId + }; + + public static TenantOrganisation Map(this Entities.TenantOrganisation entity) => new() + { + Id = entity.Id, + Status = entity.Status, + CreatedAt = entity.CreatedAt, + CreatedBy = entity.CreatedBy, + OrganisationId = entity.OrganisationId, + TenantId = entity.TenantId + }; + + public static Tenant Map(this Entities.Tenant entity) => new() + { + Id = entity.Id, + Name = entity.Name, + CreatedAt = entity.CreatedAt, + CreatedBy = entity.CreatedBy, + Status = entity.Status, + UpdatedAt = entity.UpdatedAt, + UpdatedBy = entity.UpdatedBy + }; + public static Activity Map(this Entities.Activity entity) => new() { Id = entity.Id, @@ -40,6 +71,7 @@ public static class Mappers ApprovedBy = entity.ApprovedBy, CreatedAt = entity.CreatedAt, CreatedBy = entity.CreatedBy, + TenantId = entity.TenantId, OrganisationId = entity.OrganisationId, ProgrammeId = entity.ProgrammeId, ProjectId = entity.ProjectId, @@ -162,7 +194,7 @@ public static class Mappers AwardId = entity.AwardId, ContractId = entity.ContractId, CreatedAt = entity.CreatedAt, - CreatedBy = entity.CreatedBy, + CreatedBy = entity.CreatedBy, InvitedOrganisationId = entity.InvitedOrganisationId, OrganisationId = entity.OrganisationId, Recipient = entity.Recipient, @@ -202,7 +234,7 @@ public static class Mappers public static Organisation Map(this Entities.Organisation entity) => new() { Id = entity.Id, - CreatedAt = entity.CreatedAt, + CreatedAt = entity.CreatedAt, CreatedBy = entity.CreatedBy, Email = entity.Email, Name = entity.Name, @@ -225,6 +257,7 @@ public static class Mappers { Id = entity.Id, CreatedAt = entity.CreatedAt, + TenantId = entity.TenantId, CreatedBy = entity.CreatedBy, Description = entity.Description, Name = entity.Name, @@ -239,6 +272,7 @@ public static class Mappers Destination = entity.Destination, Error = entity.Error, Message = entity.Message, + TenantId = entity.TenantId, OrganisationId = entity.OrganisationId, Platform = entity.Platform, Recipient = entity.Recipient, @@ -255,6 +289,7 @@ public static class Mappers CreatedBy = entity.CreatedBy, Name = entity.Name, OwnedBy = entity.OwnedBy, + TenantId = entity.TenantId, PortfolioId = entity.PortfolioId, Status = entity.Status, UpdatedAt = entity.UpdatedAt, @@ -269,6 +304,7 @@ public static class Mappers Description = entity.Description, EndedAt = entity.EndedAt, Name = entity.Name, + TenantId = entity.TenantId, ProgrammeId = entity.ProgrammeId, StartedAt = entity.StartedAt, Status = entity.Status, diff --git a/PostFundManagement.Domain/Models/AuditLog.cs b/PostFundManagement.Domain/Models/AuditLog.cs index 91ed184..1bcd1a2 100644 --- a/PostFundManagement.Domain/Models/AuditLog.cs +++ b/PostFundManagement.Domain/Models/AuditLog.cs @@ -4,9 +4,11 @@ public class AuditLog { public long Id { get; set; } + public long? TenantId { get; set; } + public string? EntityName { get; set; } - public long EntityId { get; set; } + public string? EntityId { get; set; } public string? Action { get; set; } diff --git a/PostFundManagement.Domain/Models/Award.cs b/PostFundManagement.Domain/Models/Award.cs index cbb27e0..e77c763 100644 --- a/PostFundManagement.Domain/Models/Award.cs +++ b/PostFundManagement.Domain/Models/Award.cs @@ -6,6 +6,8 @@ public class Award public long OrganisationId { get; set; } + public long TenantId { get; set; } + public long ProgrammeId { get; set; } public long? ProjectId { get; set; } diff --git a/PostFundManagement.Domain/Models/Notification.cs b/PostFundManagement.Domain/Models/Notification.cs index affa356..ce678e6 100644 --- a/PostFundManagement.Domain/Models/Notification.cs +++ b/PostFundManagement.Domain/Models/Notification.cs @@ -4,6 +4,8 @@ public class Notification { public long Id { get; set; } + public long TenantId { get; set; } + public long OrganisationId { get; set; } public Guid Recipient { get; set; } diff --git a/PostFundManagement.Domain/Models/Portfolio.cs b/PostFundManagement.Domain/Models/Portfolio.cs index 23a04bb..3bd7f53 100644 --- a/PostFundManagement.Domain/Models/Portfolio.cs +++ b/PostFundManagement.Domain/Models/Portfolio.cs @@ -4,6 +4,8 @@ public class Portfolio { public long Id { get; set; } + public long TenantId { get; set; } + public long OrganisationId { get; set; } public Guid OwnedBy { get; set; } diff --git a/PostFundManagement.Domain/Models/Programme.cs b/PostFundManagement.Domain/Models/Programme.cs index e05fd15..e34a0e1 100644 --- a/PostFundManagement.Domain/Models/Programme.cs +++ b/PostFundManagement.Domain/Models/Programme.cs @@ -6,6 +6,8 @@ public class Programme public long PortfolioId { get; set; } + public long TenantId { get; set; } + public Guid OwnedBy { get; set; } public Guid CreatedBy { get; set; } diff --git a/PostFundManagement.Domain/Models/Project.cs b/PostFundManagement.Domain/Models/Project.cs index 795f75a..abcc9f9 100644 --- a/PostFundManagement.Domain/Models/Project.cs +++ b/PostFundManagement.Domain/Models/Project.cs @@ -6,6 +6,8 @@ public class Project public long ProgrammeId { get; set; } + public long TenantId { get; set; } + public Guid CreatedBy { get; set; } public Guid? UpdatedBy { get; set; } diff --git a/PostFundManagement.Domain/Models/Tenant.cs b/PostFundManagement.Domain/Models/Tenant.cs index d46ad75..792dfb9 100644 --- a/PostFundManagement.Domain/Models/Tenant.cs +++ b/PostFundManagement.Domain/Models/Tenant.cs @@ -8,9 +8,9 @@ public class Tenant public Guid CreatedBy { get; set; } - public DateTime UpdatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } - public Guid UpdatedBy { get; set; } + public Guid? UpdatedBy { get; set; } public string? Name { get; set; } diff --git a/PostFundManagement.Infrastructure/Database/ApplicationDbContext.cs b/PostFundManagement.Infrastructure/Database/ApplicationDbContext.cs index 27b8776..df81852 100644 --- a/PostFundManagement.Infrastructure/Database/ApplicationDbContext.cs +++ b/PostFundManagement.Infrastructure/Database/ApplicationDbContext.cs @@ -1,9 +1,14 @@ +using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Entities; namespace PostFundManagement.Infrastructure.Database; -public sealed class ApplicationDbContext(DbContextOptions options) : DbContext(options) +public sealed class ApplicationDbContext(DbContextOptions options, + ITenantContextService tenantService) : DbContext(options) { + private bool BypassFilter => tenantService.IsAdmin; + private long CurrentTenantId => tenantService.TenantId; + public DbSet TenantUsers => Set(); public DbSet TenantOrganisations => Set(); @@ -17,13 +22,13 @@ public sealed class ApplicationDbContext(DbContextOptions public DbSet Activities => Set(); public DbSet AuditLogs => Set(); - + public DbSet SiteVisits => Set(); - + public DbSet ChangeRequests => Set(); public DbSet ComplianceItems => Set(); - + public DbSet Issues => Set(); public DbSet Outcomes => Set(); @@ -31,13 +36,13 @@ public sealed class ApplicationDbContext(DbContextOptions public DbSet Beneficiaries => Set(); public DbSet Evidences => Set(); - + public DbSet Risks => Set(); - + public DbSet Indicators => Set(); - + public DbSet Disbursements => Set(); - + public DbSet Milestones => Set(); public DbSet Budgets => Set(); @@ -57,6 +62,38 @@ public sealed class ApplicationDbContext(DbContextOptions public DbSet Portfolios => Set(); public DbSet Instruments => Set(); - + public DbSet Users => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId); + + modelBuilder.Entity() + .HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId); + + modelBuilder.Entity() + .HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId); + + modelBuilder.Entity() + .HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId); + + modelBuilder.Entity() + .HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId); + + modelBuilder.Entity() + .HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId); + + modelBuilder.Entity() + .HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId); + + modelBuilder.Entity() + .HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId); + + modelBuilder.Entity().HasQueryFilter(o => BypassFilter || + Set().Any(to => to.TenantId == CurrentTenantId && to.OrganisationId == o.Id)); + } } \ No newline at end of file diff --git a/PostFundManagement.Infrastructure/Database/Factories/ApplicationDbContextFactory.cs b/PostFundManagement.Infrastructure/Database/Factories/ApplicationDbContextFactory.cs index 7d06651..6046775 100644 --- a/PostFundManagement.Infrastructure/Database/Factories/ApplicationDbContextFactory.cs +++ b/PostFundManagement.Infrastructure/Database/Factories/ApplicationDbContextFactory.cs @@ -2,7 +2,7 @@ using static PostFundManagement.Domain.Extensions.Constants; namespace PostFundManagement.Infrastructure.Database.Factories; -public sealed class ApplicationDbContextFactory : IDesignTimeDbContextFactory +public sealed partial class ApplicationDbContextFactory : IDesignTimeDbContextFactory { public ApplicationDbContext CreateDbContext(string[] args) { @@ -14,11 +14,14 @@ public sealed class ApplicationDbContextFactory : IDesignTimeDbContextFactory(); - var connectionString = configuration.GetConnectionString(DatabaseConfigName) + var connectionString = configuration.GetConnectionString(DatabaseConfigName) ?? throw new InvalidOperationException($"Connection string '{DatabaseConfigName}' was not found in configuration."); - - optionsBuilder.UseNpgsql(connectionString, npgsqlOptions => npgsqlOptions.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)); - return new ApplicationDbContext(optionsBuilder.Options); + optionsBuilder.UseNpgsql(connectionString, npgsqlOptions => + npgsqlOptions.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)); + + var designTimeTenantService = new DesignTimeTenantContextService(); + + return new ApplicationDbContext(optionsBuilder.Options, designTimeTenantService); } } \ No newline at end of file diff --git a/PostFundManagement.Infrastructure/Database/Migrations/20260921085528_AddedTenant.Designer.cs b/PostFundManagement.Infrastructure/Database/Migrations/20260921085528_AddedTenant.Designer.cs new file mode 100644 index 0000000..2fac050 --- /dev/null +++ b/PostFundManagement.Infrastructure/Database/Migrations/20260921085528_AddedTenant.Designer.cs @@ -0,0 +1,2233 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using PostFundManagement.Infrastructure.Database; + +#nullable disable + +namespace PostFundManagement.Infrastructure.Database.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260921085528_AddedTenant")] + partial class AddedTenant + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Activity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ApprovedBy") + .HasColumnType("uuid"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("MilestoneId") + .HasColumnType("bigint"); + + b.Property("PerformedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("MilestoneId"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Activities", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.PrimitiveCollection("Changes") + .HasColumnType("jsonb"); + + b.Property("EntityId") + .HasColumnType("bigint"); + + b.Property("EntityName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PerformedBy") + .HasColumnType("uuid"); + + b.Property("Timestamp") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("PerformedBy"); + + b.ToTable("AuditLogs", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Award", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ApprovedBy") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("OrganisationId") + .HasColumnType("bigint"); + + b.Property("ProgrammeId") + .HasColumnType("bigint"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("ProgrammeId"); + + b.HasIndex("ProjectId"); + + b.HasIndex("TenantId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Awards", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Beneficiary", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("ReachedCount") + .HasColumnType("integer"); + + b.Property("TargetCount") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("Beneficiaries", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Budget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ApprovedBy") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("Budgets", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.ChangeRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ApprovedBy") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Justification") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("RevisedBudget") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("RevisedEndedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("ChangeRequests", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.ComplianceItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Requirements") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VerifiedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.HasIndex("VerifiedBy"); + + b.ToTable("ComplianceItems", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AwardId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("EffectiveAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalSignatureId") + .HasColumnType("text"); + + b.Property("SignedDocumentUrl") + .HasColumnType("text"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TotalValue") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("AwardId") + .IsUnique(); + + b.HasIndex("CreatedBy"); + + b.ToTable("Contracts", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Disbursement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("MilestoneId") + .HasColumnType("bigint"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("MilestoneId"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Disbursements", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Evidence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActivityId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("IndicatorId") + .HasColumnType("bigint"); + + b.Property("MilestoneId") + .HasColumnType("bigint"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("9"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.Property("Version") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.HasKey("Id"); + + b.HasIndex("ActivityId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("MilestoneId"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Evidences", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Indicator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActualAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("BaselineAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("TargetAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("UnitOfMeasure") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("2"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("Indicators", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Instrument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProgrammeId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("2"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProgrammeId"); + + b.ToTable("Instruments", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Invite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AwardId") + .HasColumnType("bigint"); + + b.Property("ContractId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("InvitedOrganisationId") + .HasColumnType("bigint"); + + b.Property("OrganisationId") + .HasColumnType("bigint"); + + b.Property("Recipient") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AwardId"); + + b.HasIndex("ContractId"); + + b.HasIndex("InvitedOrganisationId"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("Recipient"); + + b.ToTable("Invites", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Issue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssignedTo") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Priority") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Resolution") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("AssignedTo"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("Issues", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Milestone", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DueAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Priority") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Milestones", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Destination") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Error") + .HasColumnType("text"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.Property("OrganisationId") + .HasColumnType("bigint"); + + b.Property("Platform") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Recipient") + .HasColumnType("uuid"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Subject") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("Recipient"); + + b.HasIndex("TenantId"); + + b.ToTable("Notifications", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Organisation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RegistrationNo") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("5"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Organisations", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Outcome", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BeneficiaryId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("BeneficiaryId"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Outcomes", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Portfolio", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganisationId") + .HasColumnType("bigint"); + + b.Property("OwnedBy") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("OwnedBy"); + + b.HasIndex("TenantId"); + + b.ToTable("Portfolios", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Programme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnedBy") + .HasColumnType("uuid"); + + b.Property("PortfolioId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("OwnedBy"); + + b.HasIndex("PortfolioId"); + + b.HasIndex("TenantId"); + + b.ToTable("Programmes", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("EndedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProgrammeId") + .HasColumnType("bigint"); + + b.Property("ProjectType") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("10"); + + b.Property("Sector") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("11"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProgrammeId"); + + b.HasIndex("TenantId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Projects", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Risk", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Impact") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Likelihood") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("3"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("Risks", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Rule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProgrammeId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("2"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.Property("Version") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProgrammeId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Rules", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.SiteVisit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Findings") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.Property("InspectorName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("VisitedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("SiteVisits", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Tenants", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantOrganisation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("OrganisationId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantOrganisations", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("TenantId"); + + b.HasIndex("UserId"); + + b.ToTable("TenantUsers", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastLoginAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("2"); + + b.HasKey("Id"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Activity", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApprovedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Milestone", "Milestone") + .WithMany() + .HasForeignKey("MilestoneId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Approver"); + + b.Navigation("Creator"); + + b.Navigation("Milestone"); + + b.Navigation("Project"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.AuditLog", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Performer") + .WithMany() + .HasForeignKey("PerformedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Performer"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Award", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApprovedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Programme", "Programme") + .WithMany("Awards") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany("Awards") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Approver"); + + b.Navigation("Creator"); + + b.Navigation("Organisation"); + + b.Navigation("Programme"); + + b.Navigation("Project"); + + b.Navigation("Tenant"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Beneficiary", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Budget", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApprovedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.ChangeRequest", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApprovedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.ComplianceItem", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Verifier") + .WithMany() + .HasForeignKey("VerifiedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Creator"); + + b.Navigation("Project"); + + b.Navigation("Verifier"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Contract", b => + { + b.HasOne("PostFundManagement.Domain.Entities.Award", "Award") + .WithOne("Contract") + .HasForeignKey("PostFundManagement.Domain.Entities.Contract", "AwardId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Award"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Disbursement", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Milestone", "Milestone") + .WithMany() + .HasForeignKey("MilestoneId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Creator"); + + b.Navigation("Milestone"); + + b.Navigation("Project"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Evidence", b => + { + b.HasOne("PostFundManagement.Domain.Entities.Activity", "Activity") + .WithMany("Evidences") + .HasForeignKey("ActivityId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Indicator", "Indicator") + .WithMany() + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Milestone", "Milestone") + .WithMany() + .HasForeignKey("MilestoneId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Activity"); + + b.Navigation("Creator"); + + b.Navigation("Indicator"); + + b.Navigation("Milestone"); + + b.Navigation("Project"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Indicator", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Instrument", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Programme", "Programme") + .WithMany("Instruments") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Programme"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Invite", b => + { + b.HasOne("PostFundManagement.Domain.Entities.Award", "Award") + .WithMany() + .HasForeignKey("AwardId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Contract", "Contract") + .WithMany() + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "InvitedOrganisation") + .WithMany() + .HasForeignKey("InvitedOrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "RecipientUser") + .WithMany() + .HasForeignKey("Recipient") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Award"); + + b.Navigation("Contract"); + + b.Navigation("InvitedOrganisation"); + + b.Navigation("Organisation"); + + b.Navigation("RecipientUser"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Issue", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Assignee") + .WithMany() + .HasForeignKey("AssignedTo") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Assignee"); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Milestone", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Creator"); + + b.Navigation("Project"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Notification", b => + { + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "RecipientUser") + .WithMany() + .HasForeignKey("Recipient") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Organisation"); + + b.Navigation("RecipientUser"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Organisation", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Outcome", b => + { + b.HasOne("PostFundManagement.Domain.Entities.Beneficiary", "Beneficiary") + .WithMany() + .HasForeignKey("BeneficiaryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Beneficiary"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Portfolio", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Organisation"); + + b.Navigation("Owner"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Programme", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Portfolio", "Portfolio") + .WithMany("Programmes") + .HasForeignKey("PortfolioId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Owner"); + + b.Navigation("Portfolio"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Project", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Programme", "Programme") + .WithMany("Projects") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Creator"); + + b.Navigation("Programme"); + + b.Navigation("Tenant"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Risk", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Rule", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Programme", "Programme") + .WithMany("Rules") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Creator"); + + b.Navigation("Programme"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.SiteVisit", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Tenant", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Creator"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantOrganisation", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Organisation"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantUser", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Activity", b => + { + b.Navigation("Evidences"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Award", b => + { + b.Navigation("Contract"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Portfolio", b => + { + b.Navigation("Programmes"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Programme", b => + { + b.Navigation("Awards"); + + b.Navigation("Instruments"); + + b.Navigation("Projects"); + + b.Navigation("Rules"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Project", b => + { + b.Navigation("Awards"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/PostFundManagement.Infrastructure/Database/Migrations/20260921085528_AddedTenant.cs b/PostFundManagement.Infrastructure/Database/Migrations/20260921085528_AddedTenant.cs new file mode 100644 index 0000000..1f7a79e --- /dev/null +++ b/PostFundManagement.Infrastructure/Database/Migrations/20260921085528_AddedTenant.cs @@ -0,0 +1,370 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace PostFundManagement.Infrastructure.Database.Migrations +{ + /// + public partial class AddedTenant : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TenantId", + table: "Projects", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "TenantId", + table: "Programmes", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "TenantId", + table: "Portfolios", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "TenantId", + table: "Notifications", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.AddColumn( + name: "TenantId", + table: "Awards", + type: "bigint", + nullable: false, + defaultValue: 0L); + + migrationBuilder.CreateTable( + name: "Tenants", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + CreatedBy = table.Column(type: "uuid", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + UpdatedBy = table.Column(type: "uuid", nullable: true), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + Status = table.Column(type: "integer", nullable: false, defaultValueSql: "1") + }, + constraints: table => + { + table.PrimaryKey("PK_Tenants", x => x.Id); + table.ForeignKey( + name: "FK_Tenants_Users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Tenants_Users_UpdatedBy", + column: x => x.UpdatedBy, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "TenantOrganisations", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + CreatedBy = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "bigint", nullable: false), + OrganisationId = table.Column(type: "bigint", nullable: false), + Status = table.Column(type: "integer", nullable: false, defaultValueSql: "1") + }, + constraints: table => + { + table.PrimaryKey("PK_TenantOrganisations", x => x.Id); + table.ForeignKey( + name: "FK_TenantOrganisations_Organisations_OrganisationId", + column: x => x.OrganisationId, + principalTable: "Organisations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_TenantOrganisations_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_TenantOrganisations_Users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "TenantUsers", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + CreatedBy = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "bigint", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + Status = table.Column(type: "integer", nullable: false, defaultValueSql: "1") + }, + constraints: table => + { + table.PrimaryKey("PK_TenantUsers", x => x.Id); + table.ForeignKey( + name: "FK_TenantUsers_Tenants_TenantId", + column: x => x.TenantId, + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_TenantUsers_Users_CreatedBy", + column: x => x.CreatedBy, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_TenantUsers_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Projects_TenantId", + table: "Projects", + column: "TenantId"); + + migrationBuilder.CreateIndex( + name: "IX_Programmes_TenantId", + table: "Programmes", + column: "TenantId"); + + migrationBuilder.CreateIndex( + name: "IX_Portfolios_TenantId", + table: "Portfolios", + column: "TenantId"); + + migrationBuilder.CreateIndex( + name: "IX_Notifications_TenantId", + table: "Notifications", + column: "TenantId"); + + migrationBuilder.CreateIndex( + name: "IX_Awards_TenantId", + table: "Awards", + column: "TenantId"); + + migrationBuilder.CreateIndex( + name: "IX_TenantOrganisations_CreatedBy", + table: "TenantOrganisations", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_TenantOrganisations_OrganisationId", + table: "TenantOrganisations", + column: "OrganisationId"); + + migrationBuilder.CreateIndex( + name: "IX_TenantOrganisations_TenantId", + table: "TenantOrganisations", + column: "TenantId"); + + migrationBuilder.CreateIndex( + name: "IX_Tenants_CreatedBy", + table: "Tenants", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_Tenants_UpdatedBy", + table: "Tenants", + column: "UpdatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_TenantUsers_CreatedBy", + table: "TenantUsers", + column: "CreatedBy"); + + migrationBuilder.CreateIndex( + name: "IX_TenantUsers_TenantId", + table: "TenantUsers", + column: "TenantId"); + + migrationBuilder.CreateIndex( + name: "IX_TenantUsers_UserId", + table: "TenantUsers", + column: "UserId"); + + migrationBuilder.Sql(@" + DO $$ + BEGIN + IF EXISTS (SELECT 1 FROM ""Users"" WHERE ""Id"" = '18caeb45-65f6-4103-9bda-ddf073a24de2'::uuid) THEN + INSERT INTO ""Tenants"" (""Id"", ""Name"", ""Status"", ""CreatedBy"", ""CreatedAt"") + OVERRIDING SYSTEM VALUE + VALUES (1, 'Default Tenant', 1, '18caeb45-65f6-4103-9bda-ddf073a24de2'::uuid, NOW()) + ON CONFLICT (""Id"") DO NOTHING; + ELSE + RAISE EXCEPTION 'Target user 18caeb45-65f6-4103-9bda-ddf073a24de2 does not exist in Users table.'; + END IF; + END $$; + "); + + migrationBuilder.Sql(@"SELECT setval(pg_get_serial_sequence('""Tenants""', 'Id'), GREATEST(coalesce(max(""Id""), 1), 1)) FROM ""Tenants"";"); + + migrationBuilder.Sql("UPDATE \"Notifications\" SET \"TenantId\" = 1 WHERE \"TenantId\" = 0;"); + migrationBuilder.Sql("UPDATE \"Awards\" SET \"TenantId\" = 1 WHERE \"TenantId\" = 0;"); + migrationBuilder.Sql("UPDATE \"Portfolios\" SET \"TenantId\" = 1 WHERE \"TenantId\" = 0;"); + migrationBuilder.Sql("UPDATE \"Programmes\" SET \"TenantId\" = 1 WHERE \"TenantId\" = 0;"); + migrationBuilder.Sql("UPDATE \"Projects\" SET \"TenantId\" = 1 WHERE \"TenantId\" = 0;"); + + migrationBuilder.CreateIndex( + name: "IX_TenantOrganisations_TenantId_OrganisationId", + table: "TenantOrganisations", + columns: ["TenantId", "OrganisationId"], + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_TenantUsers_TenantId_UserId", + table: "TenantUsers", + columns: ["TenantId", "UserId"], + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Awards_Tenants_TenantId", + table: "Awards", + column: "TenantId", + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Notifications_Tenants_TenantId", + table: "Notifications", + column: "TenantId", + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Portfolios_Tenants_TenantId", + table: "Portfolios", + column: "TenantId", + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Programmes_Tenants_TenantId", + table: "Programmes", + column: "TenantId", + principalTable: "Tenants", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_Projects_Tenants_TenantId", + table: "Projects", + column: "TenantId", + principalTable: "Tenants", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Awards_Tenants_TenantId", + table: "Awards"); + + migrationBuilder.DropForeignKey( + name: "FK_Notifications_Tenants_TenantId", + table: "Notifications"); + + migrationBuilder.DropForeignKey( + name: "FK_Portfolios_Tenants_TenantId", + table: "Portfolios"); + + migrationBuilder.DropForeignKey( + name: "FK_Programmes_Tenants_TenantId", + table: "Programmes"); + + migrationBuilder.DropForeignKey( + name: "FK_Projects_Tenants_TenantId", + table: "Projects"); + + migrationBuilder.DropTable( + name: "TenantOrganisations"); + + migrationBuilder.DropTable( + name: "TenantUsers"); + + migrationBuilder.DropTable( + name: "Tenants"); + + migrationBuilder.DropIndex( + name: "IX_TenantOrganisations_TenantId_OrganisationId", + table: "TenantOrganisations"); + + migrationBuilder.DropIndex( + name: "IX_TenantUsers_TenantId_UserId", + table: "TenantUsers"); + + migrationBuilder.DropIndex( + name: "IX_Projects_TenantId", + table: "Projects"); + + migrationBuilder.DropIndex( + name: "IX_Programmes_TenantId", + table: "Programmes"); + + migrationBuilder.DropIndex( + name: "IX_Portfolios_TenantId", + table: "Portfolios"); + + migrationBuilder.DropIndex( + name: "IX_Notifications_TenantId", + table: "Notifications"); + + migrationBuilder.DropIndex( + name: "IX_Awards_TenantId", + table: "Awards"); + + migrationBuilder.DropColumn( + name: "TenantId", + table: "Projects"); + + migrationBuilder.DropColumn( + name: "TenantId", + table: "Programmes"); + + migrationBuilder.DropColumn( + name: "TenantId", + table: "Portfolios"); + + migrationBuilder.DropColumn( + name: "TenantId", + table: "Notifications"); + + migrationBuilder.DropColumn( + name: "TenantId", + table: "Awards"); + } + } +} diff --git a/PostFundManagement.Infrastructure/Database/Migrations/20260921125152_UpdateAuditLog.Designer.cs b/PostFundManagement.Infrastructure/Database/Migrations/20260921125152_UpdateAuditLog.Designer.cs new file mode 100644 index 0000000..0c14db6 --- /dev/null +++ b/PostFundManagement.Infrastructure/Database/Migrations/20260921125152_UpdateAuditLog.Designer.cs @@ -0,0 +1,2246 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using PostFundManagement.Infrastructure.Database; + +#nullable disable + +namespace PostFundManagement.Infrastructure.Database.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260921125152_UpdateAuditLog")] + partial class UpdateAuditLog + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Activity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ApprovedBy") + .HasColumnType("uuid"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("MilestoneId") + .HasColumnType("bigint"); + + b.Property("PerformedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("MilestoneId"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Activities", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.PrimitiveCollection("Changes") + .HasColumnType("jsonb"); + + b.Property("EntityId") + .IsRequired() + .HasColumnType("text"); + + b.Property("EntityName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PerformedBy") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("Timestamp") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("PerformedBy"); + + b.HasIndex("TenantId"); + + b.ToTable("AuditLogs", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Award", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ApprovedBy") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("OrganisationId") + .HasColumnType("bigint"); + + b.Property("ProgrammeId") + .HasColumnType("bigint"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("ProgrammeId"); + + b.HasIndex("ProjectId"); + + b.HasIndex("TenantId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Awards", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Beneficiary", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Category") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("ReachedCount") + .HasColumnType("integer"); + + b.Property("TargetCount") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("Beneficiaries", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Budget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("ApprovedBy") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("Budgets", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.ChangeRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ApprovedBy") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Justification") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("RevisedBudget") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("RevisedEndedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("ChangeRequests", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.ComplianceItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Requirements") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VerifiedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.HasIndex("VerifiedBy"); + + b.ToTable("ComplianceItems", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AwardId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("EffectiveAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalSignatureId") + .HasColumnType("text"); + + b.Property("SignedDocumentUrl") + .HasColumnType("text"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TotalValue") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("AwardId") + .IsUnique(); + + b.HasIndex("CreatedBy"); + + b.ToTable("Contracts", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Disbursement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("MilestoneId") + .HasColumnType("bigint"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("MilestoneId"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Disbursements", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Evidence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActivityId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("IndicatorId") + .HasColumnType("bigint"); + + b.Property("MilestoneId") + .HasColumnType("bigint"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("9"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.Property("Version") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.HasKey("Id"); + + b.HasIndex("ActivityId"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("MilestoneId"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Evidences", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Indicator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActualAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("BaselineAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("TargetAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("UnitOfMeasure") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("2"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("Indicators", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Instrument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProgrammeId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("2"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProgrammeId"); + + b.ToTable("Instruments", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Invite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AwardId") + .HasColumnType("bigint"); + + b.Property("ContractId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("InvitedOrganisationId") + .HasColumnType("bigint"); + + b.Property("OrganisationId") + .HasColumnType("bigint"); + + b.Property("Recipient") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AwardId"); + + b.HasIndex("ContractId"); + + b.HasIndex("InvitedOrganisationId"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("Recipient"); + + b.ToTable("Invites", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Issue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssignedTo") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Priority") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Resolution") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("AssignedTo"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("Issues", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Milestone", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DueAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Priority") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Milestones", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Destination") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Error") + .HasColumnType("text"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.Property("OrganisationId") + .HasColumnType("bigint"); + + b.Property("Platform") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Recipient") + .HasColumnType("uuid"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Subject") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("Recipient"); + + b.HasIndex("TenantId"); + + b.ToTable("Notifications", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Organisation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RegistrationNo") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Type") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("5"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Organisations", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Outcome", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BeneficiaryId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("BeneficiaryId"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Outcomes", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Portfolio", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganisationId") + .HasColumnType("bigint"); + + b.Property("OwnedBy") + .HasColumnType("uuid"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("OwnedBy"); + + b.HasIndex("TenantId"); + + b.ToTable("Portfolios", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Programme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnedBy") + .HasColumnType("uuid"); + + b.Property("PortfolioId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("OwnedBy"); + + b.HasIndex("PortfolioId"); + + b.HasIndex("TenantId"); + + b.ToTable("Programmes", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("EndedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProgrammeId") + .HasColumnType("bigint"); + + b.Property("ProjectType") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("10"); + + b.Property("Sector") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("11"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProgrammeId"); + + b.HasIndex("TenantId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Projects", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Risk", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Impact") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("Likelihood") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("3"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("Risks", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Rule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProgrammeId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("2"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.Property("Version") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProgrammeId"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Rules", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.SiteVisit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Findings") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.Property("InspectorName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("VisitedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("ProjectId"); + + b.ToTable("SiteVisits", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Tenants", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantOrganisation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("OrganisationId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantOrganisations", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("TenantId"); + + b.HasIndex("UserId"); + + b.ToTable("TenantUsers", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastLoginAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("2"); + + b.HasKey("Id"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Activity", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApprovedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Milestone", "Milestone") + .WithMany() + .HasForeignKey("MilestoneId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Approver"); + + b.Navigation("Creator"); + + b.Navigation("Milestone"); + + b.Navigation("Project"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.AuditLog", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Performer") + .WithMany() + .HasForeignKey("PerformedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Performer"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Award", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApprovedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Programme", "Programme") + .WithMany("Awards") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany("Awards") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Approver"); + + b.Navigation("Creator"); + + b.Navigation("Organisation"); + + b.Navigation("Programme"); + + b.Navigation("Project"); + + b.Navigation("Tenant"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Beneficiary", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Budget", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApprovedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.ChangeRequest", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Approver") + .WithMany() + .HasForeignKey("ApprovedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Approver"); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.ComplianceItem", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Verifier") + .WithMany() + .HasForeignKey("VerifiedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Creator"); + + b.Navigation("Project"); + + b.Navigation("Verifier"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Contract", b => + { + b.HasOne("PostFundManagement.Domain.Entities.Award", "Award") + .WithOne("Contract") + .HasForeignKey("PostFundManagement.Domain.Entities.Contract", "AwardId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Award"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Disbursement", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Milestone", "Milestone") + .WithMany() + .HasForeignKey("MilestoneId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Creator"); + + b.Navigation("Milestone"); + + b.Navigation("Project"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Evidence", b => + { + b.HasOne("PostFundManagement.Domain.Entities.Activity", "Activity") + .WithMany("Evidences") + .HasForeignKey("ActivityId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Indicator", "Indicator") + .WithMany() + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Milestone", "Milestone") + .WithMany() + .HasForeignKey("MilestoneId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Activity"); + + b.Navigation("Creator"); + + b.Navigation("Indicator"); + + b.Navigation("Milestone"); + + b.Navigation("Project"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Indicator", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Instrument", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Programme", "Programme") + .WithMany("Instruments") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Programme"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Invite", b => + { + b.HasOne("PostFundManagement.Domain.Entities.Award", "Award") + .WithMany() + .HasForeignKey("AwardId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Contract", "Contract") + .WithMany() + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "InvitedOrganisation") + .WithMany() + .HasForeignKey("InvitedOrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "RecipientUser") + .WithMany() + .HasForeignKey("Recipient") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Award"); + + b.Navigation("Contract"); + + b.Navigation("InvitedOrganisation"); + + b.Navigation("Organisation"); + + b.Navigation("RecipientUser"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Issue", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Assignee") + .WithMany() + .HasForeignKey("AssignedTo") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Assignee"); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Milestone", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Creator"); + + b.Navigation("Project"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Notification", b => + { + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "RecipientUser") + .WithMany() + .HasForeignKey("Recipient") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Organisation"); + + b.Navigation("RecipientUser"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Organisation", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Outcome", b => + { + b.HasOne("PostFundManagement.Domain.Entities.Beneficiary", "Beneficiary") + .WithMany() + .HasForeignKey("BeneficiaryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Beneficiary"); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Portfolio", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Organisation"); + + b.Navigation("Owner"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Programme", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Portfolio", "Portfolio") + .WithMany("Programmes") + .HasForeignKey("PortfolioId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Owner"); + + b.Navigation("Portfolio"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Project", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Programme", "Programme") + .WithMany("Projects") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Creator"); + + b.Navigation("Programme"); + + b.Navigation("Tenant"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Risk", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Rule", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Programme", "Programme") + .WithMany("Rules") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("Creator"); + + b.Navigation("Programme"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.SiteVisit", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Tenant", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Creator"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantOrganisation", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Organisation"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantUser", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Activity", b => + { + b.Navigation("Evidences"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Award", b => + { + b.Navigation("Contract"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Portfolio", b => + { + b.Navigation("Programmes"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Programme", b => + { + b.Navigation("Awards"); + + b.Navigation("Instruments"); + + b.Navigation("Projects"); + + b.Navigation("Rules"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.Project", b => + { + b.Navigation("Awards"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/PostFundManagement.Infrastructure/Database/Migrations/20260921125152_UpdateAuditLog.cs b/PostFundManagement.Infrastructure/Database/Migrations/20260921125152_UpdateAuditLog.cs new file mode 100644 index 0000000..8deba19 --- /dev/null +++ b/PostFundManagement.Infrastructure/Database/Migrations/20260921125152_UpdateAuditLog.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PostFundManagement.Infrastructure.Database.Migrations +{ + /// + public partial class UpdateAuditLog : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "EntityId", + table: "AuditLogs", + type: "text", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint"); + + migrationBuilder.AddColumn( + name: "TenantId", + table: "AuditLogs", + type: "bigint", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_AuditLogs_TenantId", + table: "AuditLogs", + column: "TenantId"); + + migrationBuilder.AddForeignKey( + name: "FK_AuditLogs_Tenants_TenantId", + table: "AuditLogs", + column: "TenantId", + principalTable: "Tenants", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_AuditLogs_Tenants_TenantId", + table: "AuditLogs"); + + migrationBuilder.DropIndex( + name: "IX_AuditLogs_TenantId", + table: "AuditLogs"); + + migrationBuilder.DropColumn( + name: "TenantId", + table: "AuditLogs"); + + migrationBuilder.AlterColumn( + name: "EntityId", + table: "AuditLogs", + type: "bigint", + nullable: false, + oldClrType: typeof(string), + oldType: "text"); + } + } +} diff --git a/PostFundManagement.Infrastructure/Database/Migrations/ApplicationDbContextModelSnapshot.cs b/PostFundManagement.Infrastructure/Database/Migrations/ApplicationDbContextModelSnapshot.cs index 02f2c1f..36cd197 100644 --- a/PostFundManagement.Infrastructure/Database/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/PostFundManagement.Infrastructure/Database/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace PostFundManagement.Infrastructure.Database.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.11") + .HasAnnotation("ProductVersion", "10.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -111,8 +111,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.PrimitiveCollection("Changes") .HasColumnType("jsonb"); - b.Property("EntityId") - .HasColumnType("bigint"); + b.Property("EntityId") + .IsRequired() + .HasColumnType("text"); b.Property("EntityName") .IsRequired() @@ -122,6 +123,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.Property("PerformedBy") .HasColumnType("uuid"); + b.Property("TenantId") + .HasColumnType("bigint"); + b.Property("Timestamp") .ValueGeneratedOnAdd() .HasColumnType("timestamp with time zone") @@ -131,6 +135,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.HasIndex("PerformedBy"); + b.HasIndex("TenantId"); + b.ToTable("AuditLogs", (string)null); }); @@ -171,6 +177,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations .HasColumnType("integer") .HasDefaultValueSql("1"); + b.Property("TenantId") + .HasColumnType("bigint"); + b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); @@ -189,6 +198,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.HasIndex("ProjectId"); + b.HasIndex("TenantId"); + b.HasIndex("UpdatedBy"); b.ToTable("Awards", (string)null); @@ -883,6 +894,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations .HasMaxLength(256) .HasColumnType("character varying(256)"); + b.Property("TenantId") + .HasColumnType("bigint"); + b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); @@ -892,6 +906,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.HasIndex("Recipient"); + b.HasIndex("TenantId"); + b.ToTable("Notifications", (string)null); }); @@ -1012,6 +1028,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.Property("OwnedBy") .HasColumnType("uuid"); + b.Property("TenantId") + .HasColumnType("bigint"); + b.HasKey("Id"); b.HasIndex("CreatedBy"); @@ -1020,6 +1039,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.HasIndex("OwnedBy"); + b.HasIndex("TenantId"); + b.ToTable("Portfolios", (string)null); }); @@ -1055,6 +1076,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations .HasColumnType("integer") .HasDefaultValueSql("1"); + b.Property("TenantId") + .HasColumnType("bigint"); + b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); @@ -1069,6 +1093,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.HasIndex("PortfolioId"); + b.HasIndex("TenantId"); + b.ToTable("Programmes", (string)null); }); @@ -1122,6 +1148,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations .HasColumnType("integer") .HasDefaultValueSql("1"); + b.Property("TenantId") + .HasColumnType("bigint"); + b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); @@ -1134,6 +1163,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.HasIndex("ProgrammeId"); + b.HasIndex("TenantId"); + b.HasIndex("UpdatedBy"); b.ToTable("Projects", (string)null); @@ -1282,6 +1313,123 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.ToTable("SiteVisits", (string)null); }); + modelBuilder.Entity("PostFundManagement.Domain.Entities.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("UpdatedBy"); + + b.ToTable("Tenants", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantOrganisation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("OrganisationId") + .HasColumnType("bigint"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("OrganisationId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantOrganisations", (string)null); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValueSql("1"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.HasIndex("TenantId"); + + b.HasIndex("UserId"); + + b.ToTable("TenantUsers", (string)null); + }); + modelBuilder.Entity("PostFundManagement.Domain.Entities.User", b => { b.Property("Id") @@ -1355,7 +1503,14 @@ namespace PostFundManagement.Infrastructure.Database.Migrations .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.NoAction); + b.Navigation("Performer"); + + b.Navigation("Tenant"); }); modelBuilder.Entity("PostFundManagement.Domain.Entities.Award", b => @@ -1388,6 +1543,12 @@ namespace PostFundManagement.Infrastructure.Database.Migrations .HasForeignKey("ProjectId") .OnDelete(DeleteBehavior.SetNull); + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") .WithMany() .HasForeignKey("UpdatedBy") @@ -1403,6 +1564,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.Navigation("Project"); + b.Navigation("Tenant"); + b.Navigation("Updator"); }); @@ -1761,9 +1924,17 @@ namespace PostFundManagement.Infrastructure.Database.Migrations .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + b.Navigation("Organisation"); b.Navigation("RecipientUser"); + + b.Navigation("Tenant"); }); modelBuilder.Entity("PostFundManagement.Domain.Entities.Organisation", b => @@ -1816,11 +1987,19 @@ namespace PostFundManagement.Infrastructure.Database.Migrations .OnDelete(DeleteBehavior.NoAction) .IsRequired(); + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + b.Navigation("Creator"); b.Navigation("Organisation"); b.Navigation("Owner"); + + b.Navigation("Tenant"); }); modelBuilder.Entity("PostFundManagement.Domain.Entities.Programme", b => @@ -1843,11 +2022,19 @@ namespace PostFundManagement.Infrastructure.Database.Migrations .OnDelete(DeleteBehavior.NoAction) .IsRequired(); + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + b.Navigation("Creator"); b.Navigation("Owner"); b.Navigation("Portfolio"); + + b.Navigation("Tenant"); }); modelBuilder.Entity("PostFundManagement.Domain.Entities.Project", b => @@ -1864,6 +2051,12 @@ namespace PostFundManagement.Infrastructure.Database.Migrations .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") .WithMany() .HasForeignKey("UpdatedBy") @@ -1873,6 +2066,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.Navigation("Programme"); + b.Navigation("Tenant"); + b.Navigation("Updator"); }); @@ -1940,6 +2135,78 @@ namespace PostFundManagement.Infrastructure.Database.Migrations b.Navigation("Project"); }); + modelBuilder.Entity("PostFundManagement.Domain.Entities.Tenant", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") + .WithMany() + .HasForeignKey("UpdatedBy") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Creator"); + + b.Navigation("Updator"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantOrganisation", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Organisation", "Organisation") + .WithMany() + .HasForeignKey("OrganisationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Organisation"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("PostFundManagement.Domain.Entities.TenantUser", b => + { + b.HasOne("PostFundManagement.Domain.Entities.User", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("PostFundManagement.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + modelBuilder.Entity("PostFundManagement.Domain.Entities.Activity", b => { b.Navigation("Evidences"); diff --git a/PostFundManagement.Infrastructure/Database/Services/DesignTimeTenantContextService.cs b/PostFundManagement.Infrastructure/Database/Services/DesignTimeTenantContextService.cs new file mode 100644 index 0000000..66c1707 --- /dev/null +++ b/PostFundManagement.Infrastructure/Database/Services/DesignTimeTenantContextService.cs @@ -0,0 +1,14 @@ +using PostFundManagement.Domain.Abstractions; + +namespace PostFundManagement.Infrastructure.Database.Factories; + +public sealed partial class ApplicationDbContextFactory +{ + private sealed class DesignTimeTenantContextService : ITenantContextService + { + public string? TenantName => "Design Time"; + public long TenantId => 0L; + public bool IsAdmin => true; + public bool HasTenantFilter => false; + } +} \ No newline at end of file diff --git a/PostFundManagement.Infrastructure/Database/Services/TenantContextService.cs b/PostFundManagement.Infrastructure/Database/Services/TenantContextService.cs new file mode 100644 index 0000000..9eae36f --- /dev/null +++ b/PostFundManagement.Infrastructure/Database/Services/TenantContextService.cs @@ -0,0 +1,29 @@ +using PostFundManagement.Domain.Abstractions; + +namespace PostFundManagement.Infrastructure.Database.Services; + +public sealed class TenantContextService : ITenantContextService +{ + public string? TenantName { get; } + + public long TenantId { get; } + + public bool IsAdmin { get; } + + public bool HasTenantFilter { get; } + + public TenantContextService(IHttpContextAccessor httpContextAccessor) + { + var user = httpContextAccessor.HttpContext?.User; + + IsAdmin = user?.IsInRole("Admin") == true || user?.HasClaim("role", "Admin") == true; + + HasTenantFilter = !IsAdmin; + + var tenantIdClaimVal = user?.FindFirst("tenant_id")?.Value ?? user?.FindFirst("TenantId")?.Value; + TenantId = long.TryParse(tenantIdClaimVal, out var id) ? id : 0L; + + var tenantNameClaimVal = user?.FindFirst("tenant")?.Value ?? user?.FindFirst("Tenant")?.Value; + TenantName = !string.IsNullOrWhiteSpace(tenantNameClaimVal) ? tenantNameClaimVal : null; + } +} \ No newline at end of file diff --git a/PostFundManagement.Infrastructure/PostFundManagement.Infrastructure.csproj b/PostFundManagement.Infrastructure/PostFundManagement.Infrastructure.csproj index 485285a..de366dd 100644 --- a/PostFundManagement.Infrastructure/PostFundManagement.Infrastructure.csproj +++ b/PostFundManagement.Infrastructure/PostFundManagement.Infrastructure.csproj @@ -36,6 +36,7 @@ +