backend phase 13 & frontend phase 6

This commit is contained in:
hamid
2026-07-09 04:09:35 +03:30
parent dc64472631
commit de53f9d8a6
97 changed files with 11969 additions and 77 deletions
@@ -0,0 +1,74 @@
using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Baya.Application.Features.Payouts.Commands.ExecutePayoutBatch;
using Baya.Application.Features.Payouts.Commands.GeneratePayoutBatch;
using Baya.Application.Features.Payouts.Commands.MarkPayoutFailed;
using Baya.Application.Features.Payouts.Commands.RetryFailedPayout;
using Baya.Application.Features.Payouts.Queries.ComputeEligibleEarnings;
using Baya.Application.Features.Payouts.Queries.GetBatchDetail;
using Baya.Application.Features.Payouts.Queries.ListPayoutBatches;
using Baya.Application.Models.Common;
using Baya.Application.Models.Payouts;
using Baya.Infrastructure.Identity.Identity.PermissionManager;
using Baya.WebFramework.Attributes;
using Baya.WebFramework.BaseController;
using Baya.WebFramework.ServiceConfiguration;
using Mediator;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
namespace Baya.Web.Api.Controllers.V1;
/// <summary>
/// Admin payout console: preview eligible earnings, open a draft batch, submit it to the (mocked) PAYA/SATNA
/// rail, retry a failed payout, mark a reconciled bank rejection, and read batches. Generating and processing a
/// batch move real (mocked) money and are rate-limited as money endpoints. One payout per booking is guaranteed by
/// the <c>nurse_payout_booking_links.booking_id</c> UNIQUE; processing is the one irreversible step.
/// </summary>
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/admin_payouts")]
[Authorize(ConstantPolicies.DynamicPermission)]
[EnableRateLimiting(RateLimitingServiceExtension.SensitivePolicy)]
[Display(Description = "Admin payout batches: eligible preview, generate, process, retry, mark-failed, read")]
public sealed class AdminPayoutsController(ISender sender) : BaseController
{
[HttpGet("eligible")]
[ProducesOkApiResponseType<PagedResult<EligibleNurseEarningsDto>>]
public async Task<IActionResult> Eligible([FromQuery] ComputeEligibleEarningsQuery query, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(query, cancellationToken));
[HttpPost("batches")]
[ProducesOkApiResponseType<GeneratePayoutBatchResult>]
public async Task<IActionResult> Generate(GeneratePayoutBatchCommand command, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(command, cancellationToken));
[HttpPost("batches/{id}/process")]
[ProducesOkApiResponseType<ExecutePayoutBatchResult>]
public async Task<IActionResult> Process(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new ExecutePayoutBatchCommand(id), cancellationToken));
[HttpGet("batches/{id}")]
[ProducesOkApiResponseType<PayoutBatchDetailDto>]
public async Task<IActionResult> Get(long id, [FromQuery] int page = 1, [FromQuery] int pageSize = 50, CancellationToken cancellationToken = default)
=> OperationResult(await sender.Send(new GetBatchDetailQuery(id, page, pageSize), cancellationToken));
[HttpGet("batches")]
[ProducesOkApiResponseType<PagedResult<PayoutBatchDto>>]
public async Task<IActionResult> List([FromQuery] ListPayoutBatchesQuery query, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(query, cancellationToken));
[HttpPost("{payoutId}/retry")]
[ProducesOkApiResponseType<bool>]
public async Task<IActionResult> Retry(long payoutId, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new RetryFailedPayoutCommand(payoutId), cancellationToken));
[HttpPost("{payoutId}/mark_failed")]
[ProducesOkApiResponseType<bool>]
public async Task<IActionResult> MarkFailed(long payoutId, MarkPayoutFailedBody body, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new MarkPayoutFailedCommand(payoutId, body.FailureReason), cancellationToken));
/// <summary>The mark-failed body (the payout id comes from the route).</summary>
public record MarkPayoutFailedBody(string FailureReason);
}
@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Baya.Application.Features.Payouts.Queries.GetNursePayoutHistory;
using Baya.Application.Models.Common;
using Baya.Application.Models.Payouts;
using Baya.WebFramework.Attributes;
using Baya.WebFramework.BaseController;
using Mediator;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Baya.Web.Api.Controllers.V1;
/// <summary>The signed-in nurse's own payout history — tenancy-scoped to <c>ICurrentUser</c> (a nurse can never
/// read another nurse's payouts). Feeds f12's earnings screen: status, net, masked IBAN + transfer reference, and
/// any clawback applied.</summary>
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/nurse_payouts")]
[Authorize]
[Display(Description = "The signed-in nurse's payout history")]
public sealed class NursePayoutsController(ISender sender) : BaseController
{
[HttpGet("history")]
[ProducesOkApiResponseType<PagedResult<NursePayoutHistoryDto>>]
public async Task<IActionResult> History([FromQuery] GetNursePayoutHistoryQuery query, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(query, cancellationToken));
}