refinement phase 7

This commit is contained in:
hamid
2026-07-13 17:48:50 +03:30
parent 70268ecc06
commit 7edadadea1
30 changed files with 6971 additions and 154 deletions
@@ -33,7 +33,14 @@ internal sealed class GeneratePayoutBatchCommandHandler(
public async ValueTask<OperationResult<GeneratePayoutBatchResult>> Handle(
GeneratePayoutBatchCommand request, CancellationToken cancellationToken)
{
if (currentUser.UserId is not { } adminId)
// A scheduled (system-initiated) run has no human initiator; the HTTP path still requires an authenticated
// admin and records their id. SystemInitiated can only be set in-process by the scheduler.
int? initiatedByAdminId;
if (request.SystemInitiated)
initiatedByAdminId = null;
else if (currentUser.UserId is { } adminId)
initiatedByAdminId = adminId;
else
return OperationResult<GeneratePayoutBatchResult>.UnauthorizedResult("Not authenticated.");
var now = dateTimeProvider.UtcNow.UtcDateTime;
@@ -57,7 +64,7 @@ internal sealed class GeneratePayoutBatchCommandHandler(
PeriodStart = request.PeriodStart,
PeriodEnd = periodEnd,
ProcessingDate = processingDate,
InitiatedByAdminId = adminId
InitiatedByAdminId = initiatedByAdminId
};
var nurseIds = eligible.Select(e => e.NurseId).Distinct().ToList();
@@ -9,4 +9,13 @@ namespace Baya.Application.Features.Payouts.Commands.GeneratePayoutBatch;
/// clawbacks, snapshotting the verified primary IBAN, linking each booking under the UNIQUE guard). Returns the
/// draft batch + payouts for admin preview; no money moves until <c>process</c>.</summary>
public record GeneratePayoutBatchCommand(DateOnly PeriodStart, DateOnly PeriodEnd)
: IRequest<OperationResult<GeneratePayoutBatchResult>>;
: IRequest<OperationResult<GeneratePayoutBatchResult>>
{
/// <summary>
/// True only when the in-process scheduler runs the weekly generation unattended (refinement-phase-7): the
/// batch is recorded with a null initiator instead of requiring an authenticated admin. The HTTP path always
/// leaves this <c>false</c> — <c>AdminPayoutsController</c> neutralizes any request-supplied value — so it can
/// never be set by an API caller.
/// </summary>
public bool SystemInitiated { get; init; }
}
@@ -27,7 +27,7 @@ public record PayoutBatchDto(
string TotalAmount,
int PayoutCount,
string Status,
int InitiatedByAdminId,
int? InitiatedByAdminId,
DateTime? ProcessedAt,
string? FailureNotes,
DateTimeOffset CreatedAt);
@@ -35,8 +35,9 @@ public class NursePayoutBatch : BaseEntity<long>, IAuditable
/// <summary>Guarded — mutated only through <see cref="TransitionTo"/> so every write goes through the machine.</summary>
public string Status { get; private set; } = PayoutBatchStatus.Draft;
/// <summary>The admin who initiated the run (FK <c>users</c>). A future cron sets its own service id.</summary>
public int InitiatedByAdminId { get; set; }
/// <summary>The admin who initiated the run (FK <c>users</c>), or <c>null</c> for a system-initiated
/// (scheduled/unattended) batch — the weekly cron has no human initiator (refinement-phase-7).</summary>
public int? InitiatedByAdminId { get; set; }
public DateTime? ProcessedAt { get; private set; }