Enforced relationships between activities and indicators, fixed actuals recording

This commit is contained in:
2026-10-22 13:47:54 +02:00
parent 4dd0899209
commit 9835121fd9
23 changed files with 3496 additions and 270 deletions
@@ -1,4 +1,4 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Shared;
using PostFundManagement.Domain;
@@ -189,6 +189,9 @@ public sealed class EvidenceCacheService(EvidenceService service, IDistributedCa
public async Task<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default) =>
await service.RecordIndicatorActualAmountAsync(indicatorIdHash, amount, userId, cancellationToken);
public async Task<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, decimal? actualCost, Guid userId, CancellationToken cancellationToken = default) =>
await service.RecordIndicatorActualAmountAsync(indicatorIdHash, amount, actualCost, userId, cancellationToken);
public async Task<Result> UpdateEvidenceAsync(UpdateEvidenceRequest request, Guid userId, CancellationToken cancellationToken = default) =>
await service.UpdateEvidenceAsync(request, userId, cancellationToken);
}
@@ -1,4 +1,4 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Services.Shared;
using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions;
@@ -50,6 +50,9 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
}));
public async Task<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default) =>
await RecordIndicatorActualAmountAsync(indicatorIdHash, amount, null, userId, cancellationToken);
public async Task<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, decimal? actualCost, Guid userId, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result>>)(async () =>
{
var decodeResult = hashService.DecodeLongIdHash(indicatorIdHash);
@@ -63,6 +66,7 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
var changedTotal = await context.Indicators.Where(i => i.Id == decodeResult.Value)
.ExecuteUpdateAsync(f => f
.SetProperty(f => f.ActualAmount, amount)
.SetProperty(f => f.ActualCost, f => actualCost.HasValue ? actualCost.Value : f.ActualCost)
.SetProperty(f => f.CreatedBy, userId), cancellationToken);
return Result.OkIf(changedTotal > 0, UpdateError.Create("Failed to update indicator."));
@@ -130,14 +134,21 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken))
return Result.Fail(NotFoundError.New);
if (request.ActivityId.HasValue && !await context.Activities.AnyAsync(a => a.Id == request.ActivityId.Value && a.ProjectId == request.ProjectId, cancellationToken))
return Result.Fail(NotFoundError.Create("Activity not found"));
context.Indicators.Add(new Domain.Entities.Indicator
{
ProjectId = request.ProjectId,
ActivityId = request.ActivityId,
Name = request.Name,
UnitOfMeasure = request.UnitOfMeasure,
BaselineAmount = request.BaselineAmount,
TargetAmount = request.TargetAmount,
ActualAmount = request.BaselineAmount,
TargetCost = request.TargetCost,
ActualCost = 0,
CreatedBy = userId,
CreatedAt = DateTime.UtcNow
});
@@ -1,4 +1,4 @@
using PostFundManagement.Domain;
using PostFundManagement.Domain;
namespace PostFundManagement.Application.Services.Evidence;
@@ -7,7 +7,7 @@ public sealed record UpdateEvidenceRequest([Required] long EvidenceId,
[Required] EvidenceType EvidenceType,
[Required] ApprovalStatus Status);
public sealed record RecordActualRequest([Required] decimal ActualAmount);
public sealed record RecordActualRequest([Required] decimal ActualAmount, decimal? ActualCost = null);
public sealed record CreateMilestoneRequest([Required] long ProjectId,
[Required] DateTime DueAt,
@@ -18,4 +18,6 @@ public sealed record CreateIndicatorRequest([Required] long ProjectId,
[Required][MaxLength(LabelLength)] string Name,
[Required] UnitOfMeasure UnitOfMeasure,
[Required] decimal BaselineAmount,
[Required] decimal TargetAmount);
[Required] decimal TargetAmount,
long? ActivityId = null,
decimal TargetCost = 0);