2 Commits
Author SHA1 Message Date
hermes f4024d563a Implemented centralised audit logging 2026-09-21 16:32:56 +02:00
hermes 78b03b6996 Colocated entity type configurations with entities
Added tenant entities and models
Modified db context
2026-09-21 09:34:49 +02:00
90 changed files with 6920 additions and 1061 deletions
+3
View File
@@ -1,8 +1,10 @@
using Microsoft.FeatureManagement; using Microsoft.FeatureManagement;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Filters; using PostFundManagement.Application.Filters;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Health; using PostFundManagement.Domain.Health;
using PostFundManagement.Domain.Mediator; using PostFundManagement.Domain.Mediator;
using PostFundManagement.Infrastructure.Database.Services;
using static PostFundManagement.Domain.Extensions.Constants; using static PostFundManagement.Domain.Extensions.Constants;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -27,6 +29,7 @@ builder.Services.AddApiVersioning(options =>
}); });
builder.Services.AddScoped<ActiveUserFilter>(); builder.Services.AddScoped<ActiveUserFilter>();
builder.Services.AddTransient<ITenantContextService, TenantContextService>();
builder.Services.AddEndpoints(Assembly.GetExecutingAssembly()); builder.Services.AddEndpoints(Assembly.GetExecutingAssembly());
builder.Services.AddApiServices(builder.Configuration); builder.Services.AddApiServices(builder.Configuration);
@@ -1,11 +1,13 @@
using PostFundManagement.Application.Services.Communications; using PostFundManagement.Application.Services.Communications;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
public interface ICommunicationsService public interface ICommunicationsService
{ {
[Audit]
ValueTask<Result<Invite>> SendInviteAsync(InviteRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result<Invite>> SendInviteAsync(InviteRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default); ValueTask<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default);
@@ -1,21 +1,26 @@
using PostFundManagement.Application.Services.Evidence; using PostFundManagement.Application.Services.Evidence;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
public interface IEvidenceService public interface IEvidenceService
{ {
[Audit]
ValueTask<Result> CreateEvidenceAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default); ValueTask<Result> CreateEvidenceAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Indicator[]>> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default); ValueTask<Result<Indicator[]>> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result<Evidence[]>> GetEvidenceFilesAsync(Pagination pagination, CancellationToken cancellationToken = default); ValueTask<Result<Evidence[]>> GetEvidenceFilesAsync(Pagination pagination, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreateMilestoneAsync(CreateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreateMilestoneAsync(CreateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreateIndicatorAsync(CreateIndicatorRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreateIndicatorAsync(CreateIndicatorRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Indicator>> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken); ValueTask<Result<Indicator>> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken);
@@ -26,6 +31,7 @@ public interface IEvidenceService
ValueTask<Result<Evidence[]>> GetProjectEvidencesAsync(string projectIdHash, CancellationToken cancellationToken = default); ValueTask<Result<Evidence[]>> GetProjectEvidencesAsync(string projectIdHash, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> UpdateEvidenceAsync(UpdateEvidenceRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> UpdateEvidenceAsync(UpdateEvidenceRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<string>> GetEvidenceUrlAsync(string evidenceIdHash, CancellationToken cancellationToken = default); ValueTask<Result<string>> GetEvidenceUrlAsync(string evidenceIdHash, CancellationToken cancellationToken = default);
@@ -1,5 +1,6 @@
using PostFundManagement.Application.Services.Execution; using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
@@ -10,17 +11,22 @@ public interface IExecutionService
ValueTask<Result<Disbursement[]>> GetDisbursementsAsync(Pagination pagination, CancellationToken cancellationToken = default); ValueTask<Result<Disbursement[]>> GetDisbursementsAsync(Pagination pagination, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreateRiskAsync(CreateRiskRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreateRiskAsync(CreateRiskRequest request, Guid userId, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Disbursement>> GetDisbursementAsync(string disbursementIdHash, CancellationToken cancellationToken = default); ValueTask<Result<Disbursement>> GetDisbursementAsync(string disbursementIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Risk>> GetRiskAsync(string riskIdHash, CancellationToken cancellationToken = default); ValueTask<Result<Risk>> GetRiskAsync(string riskIdHash, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> UpdateRiskAsync(UpdateRiskRequest request, CancellationToken cancellationToken = default); ValueTask<Result> UpdateRiskAsync(UpdateRiskRequest request, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> UpdateDisbursementAsync(UpdateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> UpdateDisbursementAsync(UpdateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default);
} }
@@ -1,5 +1,6 @@
using PostFundManagement.Application.Services.Governance; using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
@@ -18,12 +19,16 @@ public interface IGovernanceService
ValueTask<Result<Dashboard>> GetDashboardAsync(CancellationToken cancellationToken = default); ValueTask<Result<Dashboard>> GetDashboardAsync(CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreateOrganisationAsync(CreateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreateOrganisationAsync(CreateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result<Organisation>> UpdateOrganisationAsync(UpdateOrganisationRequest request, CancellationToken cancellationToken = default); ValueTask<Result<Organisation>> UpdateOrganisationAsync(UpdateOrganisationRequest request, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreatePortfolioAsync(CreatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreatePortfolioAsync(CreatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> UpdatePortfolioAsync(UpdatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> UpdatePortfolioAsync(UpdatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Programme[]>> GetPortfolioProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default); ValueTask<Result<Programme[]>> GetPortfolioProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default);
@@ -1,5 +1,6 @@
using PostFundManagement.Application.Services.Grants; using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
@@ -18,17 +19,23 @@ public interface IGrantsService
ValueTask<Result<Award>> GetAwardAsync(string awardIdHash, CancellationToken cancellationToken = default); ValueTask<Result<Award>> GetAwardAsync(string awardIdHash, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<string>> GetContractUrlAsync(string contractIdHash, CancellationToken cancellationToken = default); ValueTask<Result<string>> GetContractUrlAsync(string contractIdHash, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> UploadContractAsync(string contractIdHash, Guid userId, IFormFile file, CancellationToken cancellationToken = default); ValueTask<Result> UploadContractAsync(string contractIdHash, Guid userId, IFormFile file, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> UpdateContractAsync(UpdateContractRequest request, CancellationToken cancellationToken = default); ValueTask<Result> UpdateContractAsync(UpdateContractRequest request, CancellationToken cancellationToken = default);
} }
@@ -1,6 +1,9 @@
namespace PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Domain.Attributes;
namespace PostFundManagement.Application.Abstractions.Services;
public interface IIdentityService public interface IIdentityService
{ {
[Audit]
ValueTask<Result> UserIsAuthorisedAsync(Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> UserIsAuthorisedAsync(Guid userId, CancellationToken cancellationToken = default);
} }
@@ -1,11 +1,13 @@
using PostFundManagement.Application.Services.Projects; using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
public interface IProjectService public interface IProjectService
{ {
[Audit]
ValueTask<Result> UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Project[]>> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default); ValueTask<Result<Project[]>> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default);
@@ -14,8 +16,10 @@ public interface IProjectService
ValueTask<Result<Domain.Models.Activity[]>> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default); ValueTask<Result<Domain.Models.Activity[]>> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Milestone[]>> GetProjectMilestonesAsync(string projectIdHash, CancellationToken cancellationToken = default); ValueTask<Result<Milestone[]>> GetProjectMilestonesAsync(string projectIdHash, CancellationToken cancellationToken = default);
@@ -26,16 +30,20 @@ public interface IProjectService
ValueTask<Result<Milestone[]>> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default); ValueTask<Result<Milestone[]>> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Domain.Models.Activity>> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken = default); ValueTask<Result<Domain.Models.Activity>> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Risk[]>> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default); ValueTask<Result<Risk[]>> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default); ValueTask<Result> UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Programme>> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default); ValueTask<Result<Programme>> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default);
@@ -1,15 +1,18 @@
using PostFundManagement.Application.Services.Users; using PostFundManagement.Application.Services.Users;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
public interface IUserService public interface IUserService
{ {
[Audit]
ValueTask<Result> SyncUserAsync(Guid userId, string email, CancellationToken cancellationToken = default); ValueTask<Result> SyncUserAsync(Guid userId, string email, CancellationToken cancellationToken = default);
ValueTask<Result<User[]>> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default); ValueTask<Result<User[]>> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default);
[Audit]
ValueTask<Result> UpdateUserStatusAsync(Guid userId, ActivityStatus status, CancellationToken cancellationToken = default); ValueTask<Result> UpdateUserStatusAsync(Guid userId, ActivityStatus status, CancellationToken cancellationToken = default);
ValueTask<Result<User>> GetUserAsync(Guid userId, CancellationToken cancellationToken); ValueTask<Result<User>> GetUserAsync(Guid userId, CancellationToken cancellationToken);
@@ -1,5 +1,6 @@
using PostFundManagement.Application.Abstractions; using PostFundManagement.Application.Abstractions;
using PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Interceptors;
using PostFundManagement.Application.Services.Communications; using PostFundManagement.Application.Services.Communications;
using PostFundManagement.Application.Services.Evidence; using PostFundManagement.Application.Services.Evidence;
using PostFundManagement.Application.Services.Execution; using PostFundManagement.Application.Services.Execution;
@@ -15,32 +16,60 @@ namespace PostFundManagement.Application.Extensions;
public static class Services public static class Services
{ {
public static IServiceCollection AddAuditedCachedScoped<TInterface, TImplementation, TCacheDecorator>(this IServiceCollection services)
where TInterface : class
where TImplementation : class, TInterface
where TCacheDecorator : class, TInterface
{
services.AddScoped<TImplementation>();
services.AddScoped<TCacheDecorator>();
services.AddScoped(sp =>
{
var proxyGenerator = sp.GetRequiredService<ProxyGenerator>();
var interceptor = sp.GetRequiredService<AuditInterceptor>();
var actual = sp.GetRequiredService<TImplementation>();
var cached = ActivatorUtilities.CreateInstance<TCacheDecorator>(sp, actual);
return proxyGenerator.CreateInterfaceProxyWithTarget<TInterface>(cached, interceptor.ToInterceptor());
});
return services;
}
public static IServiceCollection AddAuditedScoped<TInterface, TImplementation>(this IServiceCollection services)
where TInterface : class
where TImplementation : class, TInterface
{
services.AddScoped<TImplementation>();
services.AddScoped(sp =>
{
var proxyGenerator = sp.GetRequiredService<ProxyGenerator>();
var actual = sp.GetRequiredService<TImplementation>();
var interceptor = sp.GetRequiredService<AuditInterceptor>();
return proxyGenerator.CreateInterfaceProxyWithTarget<TInterface>(actual, interceptor.ToInterceptor());
});
return services;
}
public static IServiceCollection AddRegisterServicesWithCaching(this IServiceCollection services, IConfiguration configuration) public static IServiceCollection AddRegisterServicesWithCaching(this IServiceCollection services, IConfiguration configuration)
{ {
services.AddScoped<ICommunicationsService, CommunicationsService>(); services.AddScoped<AuditInterceptor>();
services.Decorate<ICommunicationsService, CommunicationsCacheService>(); services.AddSingleton<ProxyGenerator>();
services.AddScoped<IEvidenceService, EvidenceService>(); services.AddAuditedCachedScoped<ICommunicationsService, CommunicationsService, CommunicationsCacheService>();
services.Decorate<IEvidenceService, EvidenceCacheService>(); services.AddAuditedCachedScoped<IEvidenceService, EvidenceService, EvidenceCacheService>();
services.AddAuditedCachedScoped<IUserService, UserService, UserCacheService>();
services.AddAuditedCachedScoped<IIdentityService, IdentityService, IdentityCacheService>();
services.AddAuditedCachedScoped<IExecutionService, ExecutionService, ExecutionCacheService>();
services.AddAuditedCachedScoped<IGovernanceService, GovernanceService, GovernanceCacheService>();
services.AddAuditedCachedScoped<IGrantsService, GrantsService, GrantsCacheService>();
services.AddAuditedCachedScoped<IProjectService, ProjectService, ProjectCacheService>();
services.AddScoped<IUserService, UserService>();
services.Decorate<IUserService, UserCacheService>();
services.AddScoped<IIdentityService, IdentityService>();
services.Decorate<IIdentityService, IdentityCacheService>();
services.AddScoped<IExecutionService, ExecutionService>();
services.Decorate<IExecutionService, ExecutionCacheService>();
services.AddScoped<IGovernanceService, GovernanceService>();
services.Decorate<IGovernanceService, GovernanceCacheService>();
services.AddScoped<IGrantsService, GrantsService>();
services.Decorate<IGrantsService, GrantsCacheService>();
services.AddScoped<IProjectService, ProjectService>();
services.Decorate<IProjectService, ProjectCacheService>();
services.AddScoped<IToolService, ToolService>(); services.AddScoped<IToolService, ToolService>();
services.Decorate<IToolService, ToolCacheService>(); services.Decorate<IToolService, ToolCacheService>();
@@ -26,7 +26,7 @@ public sealed class ActiveUserFilter(IIdentityService identityService) : IEndpoi
if (!string.IsNullOrWhiteSpace(path) && !path!.EndsWith("integration/sync", StringComparison.InvariantCultureIgnoreCase)) if (!string.IsNullOrWhiteSpace(path) && !path!.EndsWith("integration/sync", StringComparison.InvariantCultureIgnoreCase))
if ((await identityService.UserIsAuthorisedAsync(userId, context.HttpContext.RequestAborted)).IsFailed) 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[UserIdItemKey] = userId;
context.HttpContext.Items[EmailtemKey] = email; context.HttpContext.Items[EmailtemKey] = email;
@@ -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<TResult>(IInvocation invocation) =>
invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation);
private static async Task InternalInterceptAsynchronous(IInvocation invocation)
{
invocation.Proceed();
var task = (Task)invocation.ReturnValue;
await task.ConfigureAwait(false);
}
private async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation)
{
invocation.Proceed();
var task = (Task<TResult>)invocation.ReturnValue;
var resultObj = await task.ConfigureAwait(false);
var method = invocation.MethodInvocationTarget ?? invocation.Method;
var auditAttr = method.GetCustomAttribute<AuditAttribute>()
?? invocation.Method.GetCustomAttribute<AuditAttribute>();
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<string>();
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<IFormFile> 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;
}
}
@@ -24,6 +24,7 @@
<!-- API Documentation --> <!-- API Documentation -->
<ItemGroup> <ItemGroup>
<PackageReference Include="Castle.Core.AsyncInterceptor" Version="2.1.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.12" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.Validation" Version="10.0.12" /> <PackageReference Include="Microsoft.Extensions.Validation" Version="10.0.12" />
<PackageReference Include="Scalar.AspNetCore" Version="2.17.4" /> <PackageReference Include="Scalar.AspNetCore" Version="2.17.4" />
@@ -36,6 +37,7 @@
<!-- Shared Usings --> <!-- Shared Usings -->
<ItemGroup> <ItemGroup>
<Using Include="Humanizer" /> <Using Include="Humanizer" />
<Using Include="Castle.DynamicProxy" />
<Using Include="Microsoft.FeatureManagement" /> <Using Include="Microsoft.FeatureManagement" />
<Using Include="System.ComponentModel.DataAnnotations" /> <Using Include="System.ComponentModel.DataAnnotations" />
<Using Include="Microsoft.Extensions.Caching.Distributed" /> <Using Include="Microsoft.Extensions.Caching.Distributed" />
@@ -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<ApplicationDbContext> contextFactory, ITenantContextService tenantContextService) : IService
{
public async ValueTask<Result> LogActionAsync(string entityId, string entityName, string action, string[] changes, Guid performedBy, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result>>)(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);
}));
}
@@ -0,0 +1,9 @@
namespace PostFundManagement.Domain.Abstractions;
public interface ITenantContextService
{
string? TenantName { get; }
long TenantId { get; }
bool IsAdmin { get; }
bool HasTenantFilter { get; }
}
@@ -0,0 +1,4 @@
namespace PostFundManagement.Domain.Attributes;
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class AuditAttribute : Attribute;
@@ -1,57 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Activity : IEntityTypeConfiguration<Domain.Entities.Activity>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Activity> builder)
{
builder.ToTable(nameof(Domain.Entities.Activity).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.MilestoneId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.ApprovedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.PerformedAt).IsRequired(false);
builder.Property(f => f.Category).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Status).IsRequired().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Milestone)
.WithMany()
.HasForeignKey(fk => fk.MilestoneId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Approver)
.WithMany()
.HasForeignKey(fk => fk.ApprovedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,25 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class AuditLog : IEntityTypeConfiguration<Domain.Entities.AuditLog>
{
public void Configure(EntityTypeBuilder<Domain.Entities.AuditLog> builder)
{
builder.ToTable(nameof(Domain.Entities.AuditLog).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.EntityName).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.EntityId).IsRequired();
builder.Property(f => f.Action).IsRequired().HasMaxLength(ShortLabelLength);
builder.Property(f => f.Changes).IsRequired(false).HasColumnType("jsonb");
builder.Property(f => f.PerformedBy).IsRequired();
builder.Property(f => f.Timestamp).IsRequired().HasDefaultValueSql("now()");
builder.HasOne(f => f.Performer)
.WithMany()
.HasForeignKey(fk => fk.PerformedBy)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
}
}
@@ -1,58 +0,0 @@
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Award : IEntityTypeConfiguration<Domain.Entities.Award>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Award> builder)
{
builder.ToTable(nameof(Domain.Entities.Award).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.OrganisationId).IsRequired();
builder.Property(f => f.ProgrammeId).IsRequired();
builder.Property(f => f.ProjectId).IsRequired(false);
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.ApprovedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
builder.HasOne(f => f.Organisation)
.WithMany()
.HasForeignKey(fk => fk.OrganisationId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Programme)
.WithMany(f => f.Awards)
.HasForeignKey(fk => fk.ProgrammeId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Project)
.WithMany(f => f.Awards)
.HasForeignKey(fk => fk.ProjectId)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Approver)
.WithMany()
.HasForeignKey(fk => fk.ApprovedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,40 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Beneficiary : IEntityTypeConfiguration<Domain.Entities.Beneficiary>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Beneficiary> builder)
{
builder.ToTable(nameof(Domain.Entities.Beneficiary).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UserId).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Category).IsRequired().HasMaxLength(ShortLabelLength);
builder.Property(f => f.TargetCount).IsRequired();
builder.Property(f => f.ReachedCount).IsRequired();
builder.Property(f => f.Location).IsRequired();
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.User)
.WithMany()
.HasForeignKey(fk => fk.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,38 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Budget : IEntityTypeConfiguration<Domain.Entities.Budget>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Budget> builder)
{
builder.ToTable(nameof(Domain.Entities.Budget).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.ApprovedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Approver)
.WithMany()
.HasForeignKey(fk => fk.ApprovedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,42 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class ChangeRequest : IEntityTypeConfiguration<Domain.Entities.ChangeRequest>
{
public void Configure(EntityTypeBuilder<Domain.Entities.ChangeRequest> builder)
{
builder.ToTable(nameof(Domain.Entities.ChangeRequest).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.ApprovedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Justification).IsRequired().HasMaxLength(TextLength);
builder.Property(f => f.RevisedBudget).IsRequired(false).HasPrecision(18, 2);
builder.Property(f => f.RevisedEndedAt).IsRequired(false);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Approver)
.WithMany()
.HasForeignKey(fk => fk.ApprovedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,40 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class ComplianceItem : IEntityTypeConfiguration<Domain.Entities.ComplianceItem>
{
public void Configure(EntityTypeBuilder<Domain.Entities.ComplianceItem> builder)
{
builder.ToTable(nameof(Domain.Entities.ComplianceItem).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.VerifiedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Requirements).IsRequired().HasMaxLength(MediumTextLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Verifier)
.WithMany()
.HasForeignKey(fk => fk.VerifiedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,35 +0,0 @@
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Contract : IEntityTypeConfiguration<Domain.Entities.Contract>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Contract> builder)
{
builder.ToTable(nameof(Domain.Entities.Contract).Pluralize());
builder.HasKey(pk => pk.Id);
builder.HasIndex(f => f.AwardId).IsUnique();
builder.Property(f => f.AwardId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.ExternalSignatureId).IsRequired(false);
builder.Property(f => f.SignedDocumentUrl).IsRequired(false);
builder.Property(f => f.EffectiveAt).IsRequired(false);
builder.Property(f => f.ExpiresAt).IsRequired(false);
builder.Property(f => f.TotalValue).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ContractStatus.Draft);
builder.HasOne(f => f.Award)
.WithOne(f => f.Contract)
.HasForeignKey<Domain.Entities.Contract>(fk => fk.AwardId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,44 +0,0 @@
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Disbursement : IEntityTypeConfiguration<Domain.Entities.Disbursement>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Disbursement> builder)
{
builder.ToTable(nameof(Domain.Entities.Disbursement).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.MilestoneId).IsRequired(false);
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Milestone)
.WithMany()
.HasForeignKey(fk => fk.MilestoneId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,61 +0,0 @@
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Evidence : IEntityTypeConfiguration<Domain.Entities.Evidence>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Evidence> builder)
{
builder.ToTable(nameof(Domain.Entities.Evidence).Pluralize());
builder.HasKey(f => f.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.MilestoneId).IsRequired();
builder.Property(f => f.IndicatorId).IsRequired();
builder.Property(f => f.ActivityId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Version).IsRequired().HasDefaultValue(1);
builder.Property(f => f.DocumentUrl).IsRequired();
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.Property(f => f.Type).IsRequired().HasConversion<int>().HasDefaultValueSql("9")
.HasSentinel(EvidenceType.ProcurementDocument);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Milestone)
.WithMany()
.HasForeignKey(fk => fk.MilestoneId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Indicator)
.WithMany()
.HasForeignKey(fk => fk.IndicatorId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Activity)
.WithMany(f => f.Evidences)
.HasForeignKey(fk => fk.ActivityId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,33 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Indicator : IEntityTypeConfiguration<Domain.Entities.Indicator>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Indicator> builder)
{
builder.ToTable(nameof(Domain.Entities.Indicator).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.UnitOfMeasure).IsRequired().HasConversion<int>().HasDefaultValueSql("2");
builder.Property(f => f.BaselineAmount).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.TargetAmount).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.ActualAmount).IsRequired().HasPrecision(18, 2);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,33 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Instrument : IEntityTypeConfiguration<Domain.Entities.Instrument>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Instrument> builder)
{
builder.ToTable(nameof(Domain.Entities.Instrument).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProgrammeId).IsRequired();
builder.Property(f => f.Code).IsRequired().HasMaxLength(ShortLabelLength);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired(false).HasMaxLength(TextLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("2")
.HasSentinel(ActivityStatus.Inactive);
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.HasOne(f => f.Programme)
.WithMany(f => f.Instruments)
.HasForeignKey(fk => fk.ProgrammeId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,51 +0,0 @@
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Invite : IEntityTypeConfiguration<Domain.Entities.Invite>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Invite> builder)
{
builder.ToTable(nameof(Domain.Entities.Invite).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.OrganisationId).IsRequired();
builder.Property(f => f.InvitedOrganisationId).IsRequired();
builder.Property(f => f.AwardId).IsRequired();
builder.Property(f => f.ContractId).IsRequired();
builder.Property(f => f.Recipient).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.HasOne(f => f.Organisation)
.WithMany()
.HasForeignKey(fk => fk.OrganisationId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.InvitedOrganisation)
.WithMany()
.HasForeignKey(fk => fk.InvitedOrganisationId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Award)
.WithMany()
.HasForeignKey(fk => fk.AwardId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Contract)
.WithMany()
.HasForeignKey(fk => fk.ContractId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.RecipientUser)
.WithMany()
.HasForeignKey(fk => fk.Recipient)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
}
}
@@ -1,43 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Issue : IEntityTypeConfiguration<Domain.Entities.Issue>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Issue> builder)
{
builder.ToTable(nameof(Domain.Entities.Issue).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.ResolvedAt).IsRequired(false);
builder.Property(f => f.AssignedTo).IsRequired(false);
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
builder.Property(f => f.Priority).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(Priority.Low);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ActivityStatus.Active);
builder.Property(f => f.Resolution).IsRequired();
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Assignee)
.WithMany()
.HasForeignKey(fk => fk.AssignedTo)
.IsRequired()
.OnDelete(DeleteBehavior.SetNull);
}
}
@@ -1,42 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Milestone : IEntityTypeConfiguration<Domain.Entities.Milestone>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Milestone> builder)
{
builder.ToTable(nameof(Domain.Entities.Milestone).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.DueAt).IsRequired();
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.Property(f => f.Priority).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(Priority.Low);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,38 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Notification : IEntityTypeConfiguration<Domain.Entities.Notification>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Notification> builder)
{
builder.ToTable(nameof(Domain.Entities.Notification).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.SentAt).IsRequired(false);
builder.Property(f => f.Recipient).IsRequired();
builder.Property(f => f.OrganisationId).IsRequired();
builder.Property(f => f.Platform).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(NotificationPlatform.Email);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(NotificationStatus.Pending);
builder.Property(f => f.Subject).IsRequired(false).HasMaxLength(LabelLength);
builder.Property(f => f.Message).IsRequired().HasMaxLength(LargeTextLength);
builder.Property(f => f.Destination).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Error).IsRequired(false);
builder.HasOne(f => f.Organisation)
.WithMany()
.HasForeignKey(fk => fk.OrganisationId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.RecipientUser)
.WithMany()
.HasForeignKey(fk => fk.Recipient)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
}
}
@@ -1,28 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Organisation : IEntityTypeConfiguration<Domain.Entities.Organisation>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Organisation> builder)
{
builder.ToTable(nameof(Domain.Entities.Organisation).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.RegistrationNo).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Email).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Type).IsRequired().HasConversion<int>().HasDefaultValueSql("5")
.HasSentinel(OrganisationType.PrivateLimitedCompany);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ActivityStatus.Active);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,30 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Outcome : IEntityTypeConfiguration<Domain.Entities.Outcome>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Outcome> builder)
{
builder.ToTable(nameof(Domain.Entities.Outcome).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.BeneficiaryId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
builder.HasOne(f => f.Beneficiary)
.WithMany()
.HasForeignKey(fk => fk.BeneficiaryId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,37 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Portfolio : IEntityTypeConfiguration<Domain.Entities.Portfolio>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Portfolio> builder)
{
builder.ToTable(nameof(Domain.Entities.Portfolio).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.OrganisationId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.OwnedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired(false).HasMaxLength(TextLength);
builder.HasOne(f => f.Organisation)
.WithMany()
.HasForeignKey(fk => fk.OrganisationId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Owner)
.WithMany()
.HasForeignKey(fk => fk.OwnedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,40 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Programme : IEntityTypeConfiguration<Domain.Entities.Programme>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Programme> builder)
{
builder.ToTable(nameof(Domain.Entities.Programme).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.PortfolioId).IsRequired();
builder.Property(f => f.OwnedBy).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Portfolio)
.WithMany(f => f.Programmes)
.HasForeignKey(fk => fk.PortfolioId)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Owner)
.WithMany()
.IsRequired()
.HasForeignKey(fk => fk.OwnedBy)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,46 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Project : IEntityTypeConfiguration<Domain.Entities.Project>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Project> builder)
{
builder.ToTable(nameof(Domain.Entities.Project).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProgrammeId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.StartedAt).IsRequired(false);
builder.Property(f => f.EndedAt).IsRequired(false);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
builder.Property(f => f.Sector).IsRequired().HasConversion<int>().HasDefaultValueSql("11")
.HasSentinel(IndustrySector.InformationCommunicationTechnology);
builder.Property(f => f.ProjectType).IsRequired().HasConversion<int>().HasDefaultValueSql("10")
.HasSentinel(ProjectType.Innovation);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Programme)
.WithMany(f => f.Projects)
.HasForeignKey(fk => fk.ProgrammeId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,34 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Risk : IEntityTypeConfiguration<Domain.Entities.Risk>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Risk> builder)
{
builder.ToTable(nameof(Domain.Entities.Risk).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.Likelihood).IsRequired().HasConversion<int>().HasDefaultValueSql("3")
.HasSentinel(RiskLikelihood.Possible);
builder.Property(f => f.Impact).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(RiskImpact.Negligible);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,40 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class Rule : IEntityTypeConfiguration<Domain.Entities.Rule>
{
public void Configure(EntityTypeBuilder<Domain.Entities.Rule> builder)
{
builder.ToTable(nameof(Domain.Entities.Rule).Pluralize());
builder.HasKey(fk => fk.Id);
builder.Property(f => f.ProgrammeId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Version).IsRequired().HasDefaultValue(1);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("2")
.HasSentinel(ActivityStatus.Inactive);
builder.HasOne(f => f.Programme)
.WithMany(f => f.Rules)
.HasForeignKey(fk => fk.ProgrammeId)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,33 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class SiteVisit : IEntityTypeConfiguration<Domain.Entities.SiteVisit>
{
public void Configure(EntityTypeBuilder<Domain.Entities.SiteVisit> builder)
{
builder.ToTable(nameof(Domain.Entities.SiteVisit).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.VisitedAt).IsRequired();
builder.Property(f => f.InspectorName).IsRequired();
builder.Property(f => f.Findings).IsRequired(false).HasMaxLength(LargeTextLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,17 +0,0 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Configuration.Entities;
public sealed class User : IEntityTypeConfiguration<Domain.Entities.User>
{
public void Configure(EntityTypeBuilder<Domain.Entities.User> builder)
{
builder.ToTable(nameof(Domain.Entities.User).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.Email).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("2")
.HasSentinel(ActivityStatus.Inactive);
builder.Property(f => f.LastLoginAt).IsRequired(false);
}
}
+57 -1
View File
@@ -1,6 +1,8 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Activity, Activity>] [EntityTypeConfiguration<ActivityConfiguration, Activity>]
public class Activity : Models.Activity public class Activity : Models.Activity
{ {
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
@@ -14,4 +16,58 @@ public class Activity : Models.Activity
public virtual Milestone? Milestone { get; set; } public virtual Milestone? Milestone { get; set; }
public virtual ICollection<Evidence> Evidences { get; set; } = []; public virtual ICollection<Evidence> Evidences { get; set; } = [];
}
public sealed class ActivityConfiguration : IEntityTypeConfiguration<Activity>
{
public void Configure(EntityTypeBuilder<Activity> builder)
{
builder.ToTable(nameof(Activity).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.MilestoneId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.ApprovedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.PerformedAt).IsRequired(false);
builder.Property(f => f.Category).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Status).IsRequired().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Milestone)
.WithMany()
.HasForeignKey(fk => fk.MilestoneId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Approver)
.WithMany()
.HasForeignKey(fk => fk.ApprovedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
} }
+35 -2
View File
@@ -1,7 +1,40 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.AuditLog, AuditLog>] [EntityTypeConfiguration<AuditLogConfiguration, AuditLog>]
public class AuditLog : Models.AuditLog 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<AuditLog>
{
public void Configure(EntityTypeBuilder<AuditLog> builder)
{
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);
builder.Property(f => f.Changes).IsRequired(false).HasColumnType("jsonb");
builder.Property(f => 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)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
}
} }
+67 -1
View File
@@ -1,6 +1,6 @@
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Award, Award>] [EntityTypeConfiguration<AwardConfiguration, Award>]
public class Award : Models.Award public class Award : Models.Award
{ {
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
@@ -9,6 +9,8 @@ public class Award : Models.Award
public virtual User? Approver { get; set; } public virtual User? Approver { get; set; }
public virtual Tenant? Tenant { get; set; }
public virtual Organisation? Organisation { get; set; } public virtual Organisation? Organisation { get; set; }
public virtual Programme? Programme { get; set; } public virtual Programme? Programme { get; set; }
@@ -16,4 +18,68 @@ public class Award : Models.Award
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
public virtual Contract? Contract { get; set; } public virtual Contract? Contract { get; set; }
}
public sealed class AwardConfiguration : IEntityTypeConfiguration<Award>
{
public void Configure(EntityTypeBuilder<Award> builder)
{
builder.ToTable(nameof(Award).Pluralize());
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);
builder.Property(f => f.ApprovedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.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)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Programme)
.WithMany(f => f.Awards)
.HasForeignKey(fk => fk.ProgrammeId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Project)
.WithMany(f => f.Awards)
.HasForeignKey(fk => fk.ProjectId)
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Approver)
.WithMany()
.HasForeignKey(fk => fk.ApprovedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
} }
@@ -1,6 +1,8 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Beneficiary, Beneficiary>] [EntityTypeConfiguration<BeneficiaryConfiguration, Beneficiary>]
public class Beneficiary : Models.Beneficiary public class Beneficiary : Models.Beneficiary
{ {
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
@@ -8,4 +10,41 @@ public class Beneficiary : Models.Beneficiary
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual User? User { get; set; } public virtual User? User { get; set; }
}
public sealed class BeneficiaryConfiguration : IEntityTypeConfiguration<Beneficiary>
{
public void Configure(EntityTypeBuilder<Beneficiary> builder)
{
builder.ToTable(nameof(Beneficiary).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UserId).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Category).IsRequired().HasMaxLength(ShortLabelLength);
builder.Property(f => f.TargetCount).IsRequired();
builder.Property(f => f.ReachedCount).IsRequired();
builder.Property(f => f.Location).IsRequired();
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.User)
.WithMany()
.HasForeignKey(fk => fk.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
} }
+38 -1
View File
@@ -1,6 +1,8 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Budget, Budget>] [EntityTypeConfiguration<BudgetConfiguration, Budget>]
public class Budget : Models.Budget public class Budget : Models.Budget
{ {
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
@@ -8,4 +10,39 @@ public class Budget : Models.Budget
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual User? Approver { get; set; } public virtual User? Approver { get; set; }
}
public sealed class BudgetConfiguration : IEntityTypeConfiguration<Budget>
{
public void Configure(EntityTypeBuilder<Budget> builder)
{
builder.ToTable(nameof(Budget).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.ApprovedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Approver)
.WithMany()
.HasForeignKey(fk => fk.ApprovedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
} }
@@ -1,6 +1,8 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.ChangeRequest, ChangeRequest>] [EntityTypeConfiguration<ChangeRequestConfiguration, ChangeRequest>]
public class ChangeRequest : Models.ChangeRequest public class ChangeRequest : Models.ChangeRequest
{ {
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
@@ -8,4 +10,43 @@ public class ChangeRequest : Models.ChangeRequest
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual User? Approver { get; set; } public virtual User? Approver { get; set; }
}
public sealed class ChangeRequestConfiguration : IEntityTypeConfiguration<ChangeRequest>
{
public void Configure(EntityTypeBuilder<ChangeRequest> builder)
{
builder.ToTable(nameof(ChangeRequest).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.ApprovedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Justification).IsRequired().HasMaxLength(TextLength);
builder.Property(f => f.RevisedBudget).IsRequired(false).HasPrecision(18, 2);
builder.Property(f => f.RevisedEndedAt).IsRequired(false);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Approver)
.WithMany()
.HasForeignKey(fk => fk.ApprovedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
} }
@@ -1,6 +1,8 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.ComplianceItem, ComplianceItem>] [EntityTypeConfiguration<ComplianceItemConfiguration, ComplianceItem>]
public class ComplianceItem : Models.ComplianceItem public class ComplianceItem : Models.ComplianceItem
{ {
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
@@ -8,4 +10,41 @@ public class ComplianceItem : Models.ComplianceItem
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual User? Verifier { get; set; } public virtual User? Verifier { get; set; }
}
public sealed class ComplianceItemConfiguration : IEntityTypeConfiguration<ComplianceItem>
{
public void Configure(EntityTypeBuilder<ComplianceItem> builder)
{
builder.ToTable(nameof(ComplianceItem).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.VerifiedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Requirements).IsRequired().HasMaxLength(MediumTextLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Verifier)
.WithMany()
.HasForeignKey(fk => fk.VerifiedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
} }
+35 -1
View File
@@ -1,9 +1,43 @@
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Contract, Contract>] [EntityTypeConfiguration<ContractConfiguration, Contract>]
public class Contract : Models.Contract public class Contract : Models.Contract
{ {
public virtual Award? Award { get; set; } public virtual Award? Award { get; set; }
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
}
public sealed class ContractConfiguration : IEntityTypeConfiguration<Contract>
{
public void Configure(EntityTypeBuilder<Contract> builder)
{
builder.ToTable(nameof(Contract).Pluralize());
builder.HasKey(pk => pk.Id);
builder.HasIndex(f => f.AwardId).IsUnique();
builder.Property(f => f.AwardId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.ExternalSignatureId).IsRequired(false);
builder.Property(f => f.SignedDocumentUrl).IsRequired(false);
builder.Property(f => f.EffectiveAt).IsRequired(false);
builder.Property(f => f.ExpiresAt).IsRequired(false);
builder.Property(f => f.TotalValue).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ContractStatus.Draft);
builder.HasOne(f => f.Award)
.WithOne(f => f.Contract)
.HasForeignKey<Domain.Entities.Contract>(fk => fk.AwardId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
} }
@@ -1,6 +1,6 @@
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Disbursement, Disbursement>] [EntityTypeConfiguration<DisbursementConfiguration, Disbursement>]
public class Disbursement : Models.Disbursement public class Disbursement : Models.Disbursement
{ {
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
@@ -10,4 +10,47 @@ public class Disbursement : Models.Disbursement
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual User? Updator { get; set; } public virtual User? Updator { get; set; }
}
public sealed class DisbursementConfiguration : IEntityTypeConfiguration<Disbursement>
{
public void Configure(EntityTypeBuilder<Disbursement> builder)
{
builder.ToTable(nameof(Disbursement).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.MilestoneId).IsRequired(false);
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Milestone)
.WithMany()
.HasForeignKey(fk => fk.MilestoneId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
} }
+61 -1
View File
@@ -1,6 +1,6 @@
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Evidence, Evidence>] [EntityTypeConfiguration<EvidenceConfiguration, Evidence>]
public class Evidence : Models.Evidence public class Evidence : Models.Evidence
{ {
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
@@ -14,4 +14,64 @@ public class Evidence : Models.Evidence
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual User? Updator { get; set; } public virtual User? Updator { get; set; }
}
public sealed class EvidenceConfiguration : IEntityTypeConfiguration<Evidence>
{
public void Configure(EntityTypeBuilder<Evidence> builder)
{
builder.ToTable(nameof(Evidence).Pluralize());
builder.HasKey(f => f.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.MilestoneId).IsRequired();
builder.Property(f => f.IndicatorId).IsRequired();
builder.Property(f => f.ActivityId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Version).IsRequired().HasDefaultValue(1);
builder.Property(f => f.DocumentUrl).IsRequired();
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.Property(f => f.Type).IsRequired().HasConversion<int>().HasDefaultValueSql("9")
.HasSentinel(EvidenceType.ProcurementDocument);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Milestone)
.WithMany()
.HasForeignKey(fk => fk.MilestoneId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Indicator)
.WithMany()
.HasForeignKey(fk => fk.IndicatorId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Activity)
.WithMany(f => f.Evidences)
.HasForeignKey(fk => fk.ActivityId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
} }
@@ -1,9 +1,41 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Indicator, Indicator>] [EntityTypeConfiguration<IndicatorConfiguration, Indicator>]
public class Indicator : Models.Indicator public class Indicator : Models.Indicator
{ {
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
}
public sealed class IndicatorConfiguration : IEntityTypeConfiguration<Indicator>
{
public void Configure(EntityTypeBuilder<Indicator> builder)
{
builder.ToTable(nameof(Indicator).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.UnitOfMeasure).IsRequired().HasConversion<int>().HasDefaultValueSql("2");
builder.Property(f => f.BaselineAmount).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.TargetAmount).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.ActualAmount).IsRequired().HasPrecision(18, 2);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
} }
@@ -1,9 +1,41 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Instrument, Instrument>] [EntityTypeConfiguration<InstrumentConfiguration, Instrument>]
public class Instrument : Models.Instrument public class Instrument : Models.Instrument
{ {
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual Programme? Programme { get; set; } public virtual Programme? Programme { get; set; }
} }
public sealed class InstrumentConfiguration : IEntityTypeConfiguration<Instrument>
{
public void Configure(EntityTypeBuilder<Instrument> builder)
{
builder.ToTable(nameof(Instrument).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProgrammeId).IsRequired();
builder.Property(f => f.Code).IsRequired().HasMaxLength(ShortLabelLength);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired(false).HasMaxLength(TextLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("2")
.HasSentinel(ActivityStatus.Inactive);
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.HasOne(f => f.Programme)
.WithMany(f => f.Instruments)
.HasForeignKey(fk => fk.ProgrammeId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
}
+50 -1
View File
@@ -1,6 +1,6 @@
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Invite, Invite>] [EntityTypeConfiguration<InviteConfiguration, Invite>]
public class Invite : Models.Invite public class Invite : Models.Invite
{ {
public virtual Organisation? Organisation { get; set; } public virtual Organisation? Organisation { get; set; }
@@ -12,4 +12,53 @@ public class Invite : Models.Invite
public virtual Contract? Contract { get; set; } public virtual Contract? Contract { get; set; }
public virtual User? RecipientUser { get; set; } public virtual User? RecipientUser { get; set; }
}
public sealed class InviteConfiguration : IEntityTypeConfiguration<Invite>
{
public void Configure(EntityTypeBuilder<Invite> builder)
{
builder.ToTable(nameof(Invite).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.OrganisationId).IsRequired();
builder.Property(f => f.InvitedOrganisationId).IsRequired();
builder.Property(f => f.AwardId).IsRequired();
builder.Property(f => f.ContractId).IsRequired();
builder.Property(f => f.Recipient).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.HasOne(f => f.Organisation)
.WithMany()
.HasForeignKey(fk => fk.OrganisationId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.InvitedOrganisation)
.WithMany()
.HasForeignKey(fk => fk.InvitedOrganisationId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Award)
.WithMany()
.HasForeignKey(fk => fk.AwardId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Contract)
.WithMany()
.HasForeignKey(fk => fk.ContractId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.RecipientUser)
.WithMany()
.HasForeignKey(fk => fk.Recipient)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
}
} }
+43 -1
View File
@@ -1,6 +1,8 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Issue, Issue>] [EntityTypeConfiguration<IssueConfiguration, Issue>]
public class Issue : Models.Issue public class Issue : Models.Issue
{ {
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
@@ -8,4 +10,44 @@ public class Issue : Models.Issue
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual User? Assignee { get; set; } public virtual User? Assignee { get; set; }
}
public sealed class IssueConfiguration : IEntityTypeConfiguration<Issue>
{
public void Configure(EntityTypeBuilder<Issue> builder)
{
builder.ToTable(nameof(Issue).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.ResolvedAt).IsRequired(false);
builder.Property(f => f.AssignedTo).IsRequired(false);
builder.Property(f => f.Title).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
builder.Property(f => f.Priority).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(Priority.Low);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ActivityStatus.Active);
builder.Property(f => f.Resolution).IsRequired();
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Assignee)
.WithMany()
.HasForeignKey(fk => fk.AssignedTo)
.IsRequired()
.OnDelete(DeleteBehavior.SetNull);
}
} }
@@ -1,6 +1,8 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Milestone, Milestone>] [EntityTypeConfiguration<MilestoneConfiguration, Milestone>]
public class Milestone : Models.Milestone public class Milestone : Models.Milestone
{ {
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
@@ -8,4 +10,43 @@ public class Milestone : Models.Milestone
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual User? Updator { get; set; } public virtual User? Updator { get; set; }
}
public sealed class MilestoneConfiguration : IEntityTypeConfiguration<Milestone>
{
public void Configure(EntityTypeBuilder<Milestone> builder)
{
builder.ToTable(nameof(Milestone).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.DueAt).IsRequired();
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.Property(f => f.Priority).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(Priority.Low);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
} }
@@ -1,9 +1,55 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Notification, Notification>] [EntityTypeConfiguration<NotificationConfiguration, Notification>]
public class Notification : Models.Notification public class Notification : Models.Notification
{ {
public virtual Tenant? Tenant { get; set; }
public virtual Organisation? Organisation { get; set; } public virtual Organisation? Organisation { get; set; }
public virtual User? RecipientUser { get; set; } public virtual User? RecipientUser { get; set; }
}
public sealed class NotificationConfiguration : IEntityTypeConfiguration<Notification>
{
public void Configure(EntityTypeBuilder<Notification> builder)
{
builder.ToTable(nameof(Notification).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => 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<int>().HasDefaultValueSql("1")
.HasSentinel(NotificationPlatform.Email);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(NotificationStatus.Pending);
builder.Property(f => f.Subject).IsRequired(false).HasMaxLength(LabelLength);
builder.Property(f => f.Message).IsRequired().HasMaxLength(LargeTextLength);
builder.Property(f => 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)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.RecipientUser)
.WithMany()
.HasForeignKey(fk => fk.Recipient)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
}
} }
@@ -1,7 +1,34 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Organisation, Organisation>] [EntityTypeConfiguration<OrganisationConfiguration, Organisation>]
public class Organisation : Models.Organisation public class Organisation : Models.Organisation
{ {
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
}
public sealed class OrganisationConfiguration : IEntityTypeConfiguration<Organisation>
{
public void Configure(EntityTypeBuilder<Organisation> builder)
{
builder.ToTable(nameof(Organisation).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.RegistrationNo).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Email).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Type).IsRequired().HasConversion<int>().HasDefaultValueSql("5")
.HasSentinel(OrganisationType.PrivateLimitedCompany);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ActivityStatus.Active);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
} }
+30 -1
View File
@@ -1,9 +1,38 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Outcome, Outcome>] [EntityTypeConfiguration<OutcomeConfiguration, Outcome>]
public class Outcome : Models.Outcome public class Outcome : Models.Outcome
{ {
public virtual Beneficiary? Beneficiary { get; set; } public virtual Beneficiary? Beneficiary { get; set; }
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
}
public sealed class OutcomeConfiguration : IEntityTypeConfiguration<Outcome>
{
public void Configure(EntityTypeBuilder<Outcome> builder)
{
builder.ToTable(nameof(Outcome).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.BeneficiaryId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
builder.HasOne(f => f.Beneficiary)
.WithMany()
.HasForeignKey(fk => fk.BeneficiaryId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
} }
@@ -1,13 +1,58 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Portfolio, Portfolio>] [EntityTypeConfiguration<PortfolioConfiguration, Portfolio>]
public class Portfolio : Models.Portfolio public class Portfolio : Models.Portfolio
{ {
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual User? Owner { get; set; } public virtual User? Owner { get; set; }
public virtual Tenant? Tenant { get; set; }
public virtual Organisation? Organisation { get; set; } public virtual Organisation? Organisation { get; set; }
public virtual ICollection<Programme> Programmes { get; set; } = []; public virtual ICollection<Programme> Programmes { get; set; } = [];
} }
public sealed class PortfolioConfiguration : IEntityTypeConfiguration<Portfolio>
{
public void Configure(EntityTypeBuilder<Portfolio> builder)
{
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();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
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)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Owner)
.WithMany()
.HasForeignKey(fk => fk.OwnedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,11 +1,15 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Programme, Programme>] [EntityTypeConfiguration<ProgrammeConfiguration, Programme>]
public class Programme : Models.Programme public class Programme : Models.Programme
{ {
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual User? Owner { get; set; } public virtual User? Owner { get; set; }
public virtual Tenant? Tenant { get; set; }
public virtual Portfolio? Portfolio { get; set; } public virtual Portfolio? Portfolio { get; set; }
@@ -16,4 +20,48 @@ public class Programme : Models.Programme
public virtual ICollection<Project> Projects { get; set; } = []; public virtual ICollection<Project> Projects { get; set; } = [];
public virtual ICollection<Award> Awards { get; set; } = []; public virtual ICollection<Award> Awards { get; set; } = [];
}
public sealed class ProgrammeConfiguration : IEntityTypeConfiguration<Programme>
{
public void Configure(EntityTypeBuilder<Programme> builder)
{
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();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().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)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Owner)
.WithMany()
.IsRequired()
.HasForeignKey(fk => fk.OwnedBy)
.OnDelete(DeleteBehavior.NoAction);
}
} }
+55 -1
View File
@@ -1,13 +1,67 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Project, Project>] [EntityTypeConfiguration<ProjectConfiguration, Project>]
public class Project : Models.Project public class Project : Models.Project
{ {
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
public virtual User? Updator { get; set; } public virtual User? Updator { get; set; }
public virtual Tenant? Tenant { get; set; }
public virtual Programme? Programme { get; set; } public virtual Programme? Programme { get; set; }
public virtual ICollection<Award> Awards { get; set; } = []; public virtual ICollection<Award> Awards { get; set; } = [];
}
public sealed class ProjectConfiguration : IEntityTypeConfiguration<Project>
{
public void Configure(EntityTypeBuilder<Project> builder)
{
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);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.StartedAt).IsRequired(false);
builder.Property(f => f.EndedAt).IsRequired(false);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
builder.Property(f => f.Sector).IsRequired().HasConversion<int>().HasDefaultValueSql("11")
.HasSentinel(IndustrySector.InformationCommunicationTechnology);
builder.Property(f => f.ProjectType).IsRequired().HasConversion<int>().HasDefaultValueSql("10")
.HasSentinel(ProjectType.Innovation);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().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)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
} }
+34 -1
View File
@@ -1,9 +1,42 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Risk, Risk>] [EntityTypeConfiguration<RiskConfiguration, Risk>]
public class Risk : Models.Risk public class Risk : Models.Risk
{ {
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
}
public sealed class RiskConfiguration : IEntityTypeConfiguration<Risk>
{
public void Configure(EntityTypeBuilder<Risk> builder)
{
builder.ToTable(nameof(Risk).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.Likelihood).IsRequired().HasConversion<int>().HasDefaultValueSql("3")
.HasSentinel(RiskLikelihood.Possible);
builder.Property(f => f.Impact).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(RiskImpact.Negligible);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired().HasMaxLength(TextLength);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
} }
+40 -1
View File
@@ -1,6 +1,8 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.Rule, Rule>] [EntityTypeConfiguration<RuleConfiguration, Rule>]
public class Rule : Models.Rule public class Rule : Models.Rule
{ {
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
@@ -8,4 +10,41 @@ public class Rule : Models.Rule
public virtual User? Updator { get; set; } public virtual User? Updator { get; set; }
public virtual Programme? Programme { get; set; } public virtual Programme? Programme { get; set; }
}
public sealed class RuleConfiguration : IEntityTypeConfiguration<Rule>
{
public void Configure(EntityTypeBuilder<Rule> builder)
{
builder.ToTable(nameof(Rule).Pluralize());
builder.HasKey(fk => fk.Id);
builder.Property(f => f.ProgrammeId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Version).IsRequired().HasDefaultValue(1);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("2")
.HasSentinel(ActivityStatus.Inactive);
builder.HasOne(f => f.Programme)
.WithMany(f => f.Rules)
.HasForeignKey(fk => fk.ProgrammeId)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
}
} }
@@ -1,9 +1,41 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.SiteVisit, SiteVisit>] [EntityTypeConfiguration<SiteVisitConfiguration, SiteVisit>]
public class SiteVisit : Models.SiteVisit public class SiteVisit : Models.SiteVisit
{ {
public virtual Project? Project { get; set; } public virtual Project? Project { get; set; }
public virtual User? Creator { get; set; } public virtual User? Creator { get; set; }
}
public sealed class SiteVisitConfiguration : IEntityTypeConfiguration<SiteVisit>
{
public void Configure(EntityTypeBuilder<SiteVisit> builder)
{
builder.ToTable(nameof(SiteVisit).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.ProjectId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.VisitedAt).IsRequired();
builder.Property(f => f.InspectorName).IsRequired();
builder.Property(f => f.Findings).IsRequired(false).HasMaxLength(LargeTextLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Project)
.WithMany()
.HasForeignKey(fk => fk.ProjectId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
}
} }
@@ -0,0 +1,40 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<TenantConfiguration, Tenant>]
public class Tenant : Models.Tenant
{
public virtual User? Creator { get; set; }
public virtual User? Updator { get; set; }
}
public sealed class TenantConfiguration : IEntityTypeConfiguration<Tenant>
{
public void Configure(EntityTypeBuilder<Tenant> builder)
{
builder.ToTable(nameof(Tenant).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
builder.Property(f => f.UpdatedAt).IsRequired(false);
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ActivityStatus.Active);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Updator)
.WithMany()
.HasForeignKey(fk => fk.UpdatedBy)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
}
}
@@ -0,0 +1,45 @@
namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<TenantOrganisationConfiguration, TenantOrganisation>]
public class TenantOrganisation : Models.TenantOrganisation
{
public virtual Tenant? Tenant { get; set; }
public virtual Organisation? Organisation { get; set; }
public virtual User? Creator { get; set; }
}
public sealed class TenantOrganisationConfiguration : IEntityTypeConfiguration<TenantOrganisation>
{
public void Configure(EntityTypeBuilder<TenantOrganisation> builder)
{
builder.ToTable(nameof(TenantOrganisation).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.TenantId).IsRequired();
builder.Property(f => f.OrganisationId).IsRequired();
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ActivityStatus.Active);
builder.HasOne(f => f.Tenant)
.WithMany()
.HasForeignKey(fk => fk.TenantId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Organisation)
.WithMany()
.HasForeignKey(fk => fk.OrganisationId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
}
}
@@ -0,0 +1,45 @@
namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<TenantUserConfiguration, TenantUser>]
public class TenantUser : Models.TenantUser
{
public virtual Tenant? Tenant { get; set; }
public virtual User? Creator { get; set; }
public virtual User? User { get; set; }
}
public sealed class TenantUserConfiguration : IEntityTypeConfiguration<TenantUser>
{
public void Configure(EntityTypeBuilder<TenantUser> builder)
{
builder.ToTable(nameof(TenantUser).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.CreatedAt).IsRequired().HasDefaultValueSql("now()");
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UserId).IsRequired();
builder.Property(f => f.TenantId).IsRequired();
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ActivityStatus.Active);
builder.HasOne(f => f.Tenant)
.WithMany()
.HasForeignKey(fk => fk.TenantId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.User)
.WithMany()
.HasForeignKey(fk => fk.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
}
}
+18 -2
View File
@@ -1,4 +1,20 @@
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Domain.Entities; namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<Configuration.Entities.User, User>] [EntityTypeConfiguration<UserConfiguration, User>]
public sealed class User : Models.User; public sealed class User : Models.User;
public sealed class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable(nameof(User).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.Email).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("2")
.HasSentinel(ActivityStatus.Inactive);
builder.Property(f => f.LastLoginAt).IsRequired(false);
}
}
@@ -4,6 +4,37 @@ namespace PostFundManagement.Domain.Extensions;
public static class Mappers 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() public static Activity Map(this Entities.Activity entity) => new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -40,6 +71,7 @@ public static class Mappers
ApprovedBy = entity.ApprovedBy, ApprovedBy = entity.ApprovedBy,
CreatedAt = entity.CreatedAt, CreatedAt = entity.CreatedAt,
CreatedBy = entity.CreatedBy, CreatedBy = entity.CreatedBy,
TenantId = entity.TenantId,
OrganisationId = entity.OrganisationId, OrganisationId = entity.OrganisationId,
ProgrammeId = entity.ProgrammeId, ProgrammeId = entity.ProgrammeId,
ProjectId = entity.ProjectId, ProjectId = entity.ProjectId,
@@ -162,7 +194,7 @@ public static class Mappers
AwardId = entity.AwardId, AwardId = entity.AwardId,
ContractId = entity.ContractId, ContractId = entity.ContractId,
CreatedAt = entity.CreatedAt, CreatedAt = entity.CreatedAt,
CreatedBy = entity.CreatedBy, CreatedBy = entity.CreatedBy,
InvitedOrganisationId = entity.InvitedOrganisationId, InvitedOrganisationId = entity.InvitedOrganisationId,
OrganisationId = entity.OrganisationId, OrganisationId = entity.OrganisationId,
Recipient = entity.Recipient, Recipient = entity.Recipient,
@@ -202,7 +234,7 @@ public static class Mappers
public static Organisation Map(this Entities.Organisation entity) => new() public static Organisation Map(this Entities.Organisation entity) => new()
{ {
Id = entity.Id, Id = entity.Id,
CreatedAt = entity.CreatedAt, CreatedAt = entity.CreatedAt,
CreatedBy = entity.CreatedBy, CreatedBy = entity.CreatedBy,
Email = entity.Email, Email = entity.Email,
Name = entity.Name, Name = entity.Name,
@@ -225,6 +257,7 @@ public static class Mappers
{ {
Id = entity.Id, Id = entity.Id,
CreatedAt = entity.CreatedAt, CreatedAt = entity.CreatedAt,
TenantId = entity.TenantId,
CreatedBy = entity.CreatedBy, CreatedBy = entity.CreatedBy,
Description = entity.Description, Description = entity.Description,
Name = entity.Name, Name = entity.Name,
@@ -239,6 +272,7 @@ public static class Mappers
Destination = entity.Destination, Destination = entity.Destination,
Error = entity.Error, Error = entity.Error,
Message = entity.Message, Message = entity.Message,
TenantId = entity.TenantId,
OrganisationId = entity.OrganisationId, OrganisationId = entity.OrganisationId,
Platform = entity.Platform, Platform = entity.Platform,
Recipient = entity.Recipient, Recipient = entity.Recipient,
@@ -255,6 +289,7 @@ public static class Mappers
CreatedBy = entity.CreatedBy, CreatedBy = entity.CreatedBy,
Name = entity.Name, Name = entity.Name,
OwnedBy = entity.OwnedBy, OwnedBy = entity.OwnedBy,
TenantId = entity.TenantId,
PortfolioId = entity.PortfolioId, PortfolioId = entity.PortfolioId,
Status = entity.Status, Status = entity.Status,
UpdatedAt = entity.UpdatedAt, UpdatedAt = entity.UpdatedAt,
@@ -269,6 +304,7 @@ public static class Mappers
Description = entity.Description, Description = entity.Description,
EndedAt = entity.EndedAt, EndedAt = entity.EndedAt,
Name = entity.Name, Name = entity.Name,
TenantId = entity.TenantId,
ProgrammeId = entity.ProgrammeId, ProgrammeId = entity.ProgrammeId,
StartedAt = entity.StartedAt, StartedAt = entity.StartedAt,
Status = entity.Status, Status = entity.Status,
+3 -1
View File
@@ -4,9 +4,11 @@ public class AuditLog
{ {
public long Id { get; set; } public long Id { get; set; }
public long? TenantId { get; set; }
public string? EntityName { get; set; } public string? EntityName { get; set; }
public long EntityId { get; set; } public string? EntityId { get; set; }
public string? Action { get; set; } public string? Action { get; set; }
@@ -6,6 +6,8 @@ public class Award
public long OrganisationId { get; set; } public long OrganisationId { get; set; }
public long TenantId { get; set; }
public long ProgrammeId { get; set; } public long ProgrammeId { get; set; }
public long? ProjectId { get; set; } public long? ProjectId { get; set; }
@@ -4,6 +4,8 @@ public class Notification
{ {
public long Id { get; set; } public long Id { get; set; }
public long TenantId { get; set; }
public long OrganisationId { get; set; } public long OrganisationId { get; set; }
public Guid Recipient { get; set; } public Guid Recipient { get; set; }
@@ -4,6 +4,8 @@ public class Portfolio
{ {
public long Id { get; set; } public long Id { get; set; }
public long TenantId { get; set; }
public long OrganisationId { get; set; } public long OrganisationId { get; set; }
public Guid OwnedBy { get; set; } public Guid OwnedBy { get; set; }
@@ -6,6 +6,8 @@ public class Programme
public long PortfolioId { get; set; } public long PortfolioId { get; set; }
public long TenantId { get; set; }
public Guid OwnedBy { get; set; } public Guid OwnedBy { get; set; }
public Guid CreatedBy { get; set; } public Guid CreatedBy { get; set; }
@@ -6,6 +6,8 @@ public class Project
public long ProgrammeId { get; set; } public long ProgrammeId { get; set; }
public long TenantId { get; set; }
public Guid CreatedBy { get; set; } public Guid CreatedBy { get; set; }
public Guid? UpdatedBy { get; set; } public Guid? UpdatedBy { get; set; }
@@ -0,0 +1,18 @@
namespace PostFundManagement.Domain.Models;
public class Tenant
{
public long Id { get; set; }
public DateTime CreatedAt { get; set; }
public Guid CreatedBy { get; set; }
public DateTime? UpdatedAt { get; set; }
public Guid? UpdatedBy { get; set; }
public string? Name { get; set; }
public ActivityStatus Status { get; set; }
}
@@ -0,0 +1,16 @@
namespace PostFundManagement.Domain.Models;
public class TenantOrganisation
{
public long Id { get; set; }
public DateTime CreatedAt { get; set; }
public Guid CreatedBy { get; set; }
public long TenantId { get; set; }
public long OrganisationId { get; set; }
public ActivityStatus Status { get; set; }
}
@@ -0,0 +1,16 @@
namespace PostFundManagement.Domain.Models;
public class TenantUser
{
public long Id { get; set; }
public DateTime CreatedAt { get; set; }
public Guid CreatedBy { get; set; }
public long TenantId { get; set; }
public Guid UserId { get; set; }
public ActivityStatus Status { get; set; }
}
@@ -9,6 +9,14 @@
<PackageLicenseFile>..\LICENSE</PackageLicenseFile> <PackageLicenseFile>..\LICENSE</PackageLicenseFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;MA0048</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1701;1702;MA0048</NoWarn>
</PropertyGroup>
<!-- Transformation --> <!-- Transformation -->
<ItemGroup> <ItemGroup>
<PackageReference Include="Humanizer" Version="3.0.10" /> <PackageReference Include="Humanizer" Version="3.0.10" />
@@ -1,9 +1,20 @@
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Entities; using PostFundManagement.Domain.Entities;
namespace PostFundManagement.Infrastructure.Database; namespace PostFundManagement.Infrastructure.Database;
public sealed class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : DbContext(options) public sealed class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
ITenantContextService tenantService) : DbContext(options)
{ {
private bool BypassFilter => tenantService.IsAdmin;
private long CurrentTenantId => tenantService.TenantId;
public DbSet<TenantUser> TenantUsers => Set<TenantUser>();
public DbSet<TenantOrganisation> TenantOrganisations => Set<TenantOrganisation>();
public DbSet<Tenant> Tenants => Set<Tenant>();
public DbSet<Notification> Notifications => Set<Notification>(); public DbSet<Notification> Notifications => Set<Notification>();
public DbSet<Invite> Invites => Set<Invite>(); public DbSet<Invite> Invites => Set<Invite>();
@@ -11,13 +22,13 @@ public sealed class ApplicationDbContext(DbContextOptions<ApplicationDbContext>
public DbSet<Activity> Activities => Set<Activity>(); public DbSet<Activity> Activities => Set<Activity>();
public DbSet<AuditLog> AuditLogs => Set<AuditLog>(); public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
public DbSet<SiteVisit> SiteVisits => Set<SiteVisit>(); public DbSet<SiteVisit> SiteVisits => Set<SiteVisit>();
public DbSet<ChangeRequest> ChangeRequests => Set<ChangeRequest>(); public DbSet<ChangeRequest> ChangeRequests => Set<ChangeRequest>();
public DbSet<ComplianceItem> ComplianceItems => Set<ComplianceItem>(); public DbSet<ComplianceItem> ComplianceItems => Set<ComplianceItem>();
public DbSet<Issue> Issues => Set<Issue>(); public DbSet<Issue> Issues => Set<Issue>();
public DbSet<Outcome> Outcomes => Set<Outcome>(); public DbSet<Outcome> Outcomes => Set<Outcome>();
@@ -25,13 +36,13 @@ public sealed class ApplicationDbContext(DbContextOptions<ApplicationDbContext>
public DbSet<Beneficiary> Beneficiaries => Set<Beneficiary>(); public DbSet<Beneficiary> Beneficiaries => Set<Beneficiary>();
public DbSet<Evidence> Evidences => Set<Evidence>(); public DbSet<Evidence> Evidences => Set<Evidence>();
public DbSet<Risk> Risks => Set<Risk>(); public DbSet<Risk> Risks => Set<Risk>();
public DbSet<Indicator> Indicators => Set<Indicator>(); public DbSet<Indicator> Indicators => Set<Indicator>();
public DbSet<Disbursement> Disbursements => Set<Disbursement>(); public DbSet<Disbursement> Disbursements => Set<Disbursement>();
public DbSet<Milestone> Milestones => Set<Milestone>(); public DbSet<Milestone> Milestones => Set<Milestone>();
public DbSet<Budget> Budgets => Set<Budget>(); public DbSet<Budget> Budgets => Set<Budget>();
@@ -51,6 +62,38 @@ public sealed class ApplicationDbContext(DbContextOptions<ApplicationDbContext>
public DbSet<Portfolio> Portfolios => Set<Portfolio>(); public DbSet<Portfolio> Portfolios => Set<Portfolio>();
public DbSet<Instrument> Instruments => Set<Instrument>(); public DbSet<Instrument> Instruments => Set<Instrument>();
public DbSet<User> Users => Set<User>(); public DbSet<User> Users => Set<User>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Portfolio>()
.HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId);
modelBuilder.Entity<Programme>()
.HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId);
modelBuilder.Entity<Project>()
.HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId);
modelBuilder.Entity<Award>()
.HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId);
modelBuilder.Entity<Notification>()
.HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId);
modelBuilder.Entity<TenantOrganisation>()
.HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId);
modelBuilder.Entity<TenantUser>()
.HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId);
modelBuilder.Entity<AuditLog>()
.HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId);
modelBuilder.Entity<Organisation>().HasQueryFilter(o => BypassFilter ||
Set<TenantOrganisation>().Any(to => to.TenantId == CurrentTenantId && to.OrganisationId == o.Id));
}
} }
@@ -2,7 +2,7 @@ using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Infrastructure.Database.Factories; namespace PostFundManagement.Infrastructure.Database.Factories;
public sealed class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext> public sealed partial class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{ {
public ApplicationDbContext CreateDbContext(string[] args) public ApplicationDbContext CreateDbContext(string[] args)
{ {
@@ -14,11 +14,14 @@ public sealed class ApplicationDbContextFactory : IDesignTimeDbContextFactory<Ap
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>(); var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
var connectionString = configuration.GetConnectionString(DatabaseConfigName) var connectionString = configuration.GetConnectionString(DatabaseConfigName)
?? throw new InvalidOperationException($"Connection string '{DatabaseConfigName}' was not found in configuration."); ?? 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);
} }
} }
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,370 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace PostFundManagement.Infrastructure.Database.Migrations
{
/// <inheritdoc />
public partial class AddedTenant : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "TenantId",
table: "Projects",
type: "bigint",
nullable: false,
defaultValue: 0L);
migrationBuilder.AddColumn<long>(
name: "TenantId",
table: "Programmes",
type: "bigint",
nullable: false,
defaultValue: 0L);
migrationBuilder.AddColumn<long>(
name: "TenantId",
table: "Portfolios",
type: "bigint",
nullable: false,
defaultValue: 0L);
migrationBuilder.AddColumn<long>(
name: "TenantId",
table: "Notifications",
type: "bigint",
nullable: false,
defaultValue: 0L);
migrationBuilder.AddColumn<long>(
name: "TenantId",
table: "Awards",
type: "bigint",
nullable: false,
defaultValue: 0L);
migrationBuilder.CreateTable(
name: "Tenants",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
Status = table.Column<int>(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<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<long>(type: "bigint", nullable: false),
OrganisationId = table.Column<long>(type: "bigint", nullable: false),
Status = table.Column<int>(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<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<long>(type: "bigint", nullable: false),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
Status = table.Column<int>(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);
}
/// <inheritdoc />
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");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,64 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace PostFundManagement.Infrastructure.Database.Migrations
{
/// <inheritdoc />
public partial class UpdateAuditLog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "EntityId",
table: "AuditLogs",
type: "text",
nullable: false,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AddColumn<long>(
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");
}
/// <inheritdoc />
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<long>(
name: "EntityId",
table: "AuditLogs",
type: "bigint",
nullable: false,
oldClrType: typeof(string),
oldType: "text");
}
}
}
@@ -17,7 +17,7 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "10.0.11") .HasAnnotation("ProductVersion", "10.0.12")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@@ -111,8 +111,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.PrimitiveCollection<string>("Changes") b.PrimitiveCollection<string>("Changes")
.HasColumnType("jsonb"); .HasColumnType("jsonb");
b.Property<long>("EntityId") b.Property<string>("EntityId")
.HasColumnType("bigint"); .IsRequired()
.HasColumnType("text");
b.Property<string>("EntityName") b.Property<string>("EntityName")
.IsRequired() .IsRequired()
@@ -122,6 +123,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.Property<Guid>("PerformedBy") b.Property<Guid>("PerformedBy")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<long?>("TenantId")
.HasColumnType("bigint");
b.Property<DateTime>("Timestamp") b.Property<DateTime>("Timestamp")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
@@ -131,6 +135,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.HasIndex("PerformedBy"); b.HasIndex("PerformedBy");
b.HasIndex("TenantId");
b.ToTable("AuditLogs", (string)null); b.ToTable("AuditLogs", (string)null);
}); });
@@ -171,6 +177,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
.HasColumnType("integer") .HasColumnType("integer")
.HasDefaultValueSql("1"); .HasDefaultValueSql("1");
b.Property<long>("TenantId")
.HasColumnType("bigint");
b.Property<DateTime?>("UpdatedAt") b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
@@ -189,6 +198,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.HasIndex("ProjectId"); b.HasIndex("ProjectId");
b.HasIndex("TenantId");
b.HasIndex("UpdatedBy"); b.HasIndex("UpdatedBy");
b.ToTable("Awards", (string)null); b.ToTable("Awards", (string)null);
@@ -883,6 +894,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
.HasMaxLength(256) .HasMaxLength(256)
.HasColumnType("character varying(256)"); .HasColumnType("character varying(256)");
b.Property<long>("TenantId")
.HasColumnType("bigint");
b.Property<DateTime?>("UpdatedAt") b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
@@ -892,6 +906,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.HasIndex("Recipient"); b.HasIndex("Recipient");
b.HasIndex("TenantId");
b.ToTable("Notifications", (string)null); b.ToTable("Notifications", (string)null);
}); });
@@ -1012,6 +1028,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.Property<Guid>("OwnedBy") b.Property<Guid>("OwnedBy")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<long>("TenantId")
.HasColumnType("bigint");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("CreatedBy"); b.HasIndex("CreatedBy");
@@ -1020,6 +1039,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.HasIndex("OwnedBy"); b.HasIndex("OwnedBy");
b.HasIndex("TenantId");
b.ToTable("Portfolios", (string)null); b.ToTable("Portfolios", (string)null);
}); });
@@ -1055,6 +1076,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
.HasColumnType("integer") .HasColumnType("integer")
.HasDefaultValueSql("1"); .HasDefaultValueSql("1");
b.Property<long>("TenantId")
.HasColumnType("bigint");
b.Property<DateTime?>("UpdatedAt") b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
@@ -1069,6 +1093,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.HasIndex("PortfolioId"); b.HasIndex("PortfolioId");
b.HasIndex("TenantId");
b.ToTable("Programmes", (string)null); b.ToTable("Programmes", (string)null);
}); });
@@ -1122,6 +1148,9 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
.HasColumnType("integer") .HasColumnType("integer")
.HasDefaultValueSql("1"); .HasDefaultValueSql("1");
b.Property<long>("TenantId")
.HasColumnType("bigint");
b.Property<DateTime?>("UpdatedAt") b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
@@ -1134,6 +1163,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.HasIndex("ProgrammeId"); b.HasIndex("ProgrammeId");
b.HasIndex("TenantId");
b.HasIndex("UpdatedBy"); b.HasIndex("UpdatedBy");
b.ToTable("Projects", (string)null); b.ToTable("Projects", (string)null);
@@ -1282,6 +1313,123 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.ToTable("SiteVisits", (string)null); b.ToTable("SiteVisits", (string)null);
}); });
modelBuilder.Entity("PostFundManagement.Domain.Entities.Tenant", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasDefaultValueSql("now()");
b.Property<Guid>("CreatedBy")
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int>("Status")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValueSql("1");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("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<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasDefaultValueSql("now()");
b.Property<Guid>("CreatedBy")
.HasColumnType("uuid");
b.Property<long>("OrganisationId")
.HasColumnType("bigint");
b.Property<int>("Status")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValueSql("1");
b.Property<long>("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<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasDefaultValueSql("now()");
b.Property<Guid>("CreatedBy")
.HasColumnType("uuid");
b.Property<int>("Status")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValueSql("1");
b.Property<long>("TenantId")
.HasColumnType("bigint");
b.Property<Guid>("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 => modelBuilder.Entity("PostFundManagement.Domain.Entities.User", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@@ -1355,7 +1503,14 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.NoAction);
b.Navigation("Performer"); b.Navigation("Performer");
b.Navigation("Tenant");
}); });
modelBuilder.Entity("PostFundManagement.Domain.Entities.Award", b => modelBuilder.Entity("PostFundManagement.Domain.Entities.Award", b =>
@@ -1388,6 +1543,12 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
.HasForeignKey("ProjectId") .HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") b.HasOne("PostFundManagement.Domain.Entities.User", "Updator")
.WithMany() .WithMany()
.HasForeignKey("UpdatedBy") .HasForeignKey("UpdatedBy")
@@ -1403,6 +1564,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.Navigation("Project"); b.Navigation("Project");
b.Navigation("Tenant");
b.Navigation("Updator"); b.Navigation("Updator");
}); });
@@ -1761,9 +1924,17 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Organisation"); b.Navigation("Organisation");
b.Navigation("RecipientUser"); b.Navigation("RecipientUser");
b.Navigation("Tenant");
}); });
modelBuilder.Entity("PostFundManagement.Domain.Entities.Organisation", b => modelBuilder.Entity("PostFundManagement.Domain.Entities.Organisation", b =>
@@ -1816,11 +1987,19 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
.OnDelete(DeleteBehavior.NoAction) .OnDelete(DeleteBehavior.NoAction)
.IsRequired(); .IsRequired();
b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Creator"); b.Navigation("Creator");
b.Navigation("Organisation"); b.Navigation("Organisation");
b.Navigation("Owner"); b.Navigation("Owner");
b.Navigation("Tenant");
}); });
modelBuilder.Entity("PostFundManagement.Domain.Entities.Programme", b => modelBuilder.Entity("PostFundManagement.Domain.Entities.Programme", b =>
@@ -1843,11 +2022,19 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
.OnDelete(DeleteBehavior.NoAction) .OnDelete(DeleteBehavior.NoAction)
.IsRequired(); .IsRequired();
b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("Creator"); b.Navigation("Creator");
b.Navigation("Owner"); b.Navigation("Owner");
b.Navigation("Portfolio"); b.Navigation("Portfolio");
b.Navigation("Tenant");
}); });
modelBuilder.Entity("PostFundManagement.Domain.Entities.Project", b => modelBuilder.Entity("PostFundManagement.Domain.Entities.Project", b =>
@@ -1864,6 +2051,12 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.HasOne("PostFundManagement.Domain.Entities.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("PostFundManagement.Domain.Entities.User", "Updator") b.HasOne("PostFundManagement.Domain.Entities.User", "Updator")
.WithMany() .WithMany()
.HasForeignKey("UpdatedBy") .HasForeignKey("UpdatedBy")
@@ -1873,6 +2066,8 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.Navigation("Programme"); b.Navigation("Programme");
b.Navigation("Tenant");
b.Navigation("Updator"); b.Navigation("Updator");
}); });
@@ -1940,6 +2135,78 @@ namespace PostFundManagement.Infrastructure.Database.Migrations
b.Navigation("Project"); 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 => modelBuilder.Entity("PostFundManagement.Domain.Entities.Activity", b =>
{ {
b.Navigation("Evidences"); b.Navigation("Evidences");
@@ -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;
}
}
@@ -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;
}
}
@@ -36,6 +36,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.12" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.12" /> <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.12" />
<Using Include="Microsoft.AspNetCore.Http" />
<Using Include="Microsoft.Extensions.Configuration" /> <Using Include="Microsoft.Extensions.Configuration" />
<Using Include="Microsoft.Extensions.DependencyInjection" /> <Using Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup> </ItemGroup>