Sms & Email

This commit is contained in:
2026-10-20 10:45:03 +05:30
parent a40e463e8b
commit 0e423b806c
29 changed files with 13845 additions and 26 deletions
@@ -0,0 +1,77 @@
using ERPCore.Dtos.Common;
using ERPCore.Dtos.Config;
using ERPCore.Infra.Auth;
using ERPCore.Services.OwnerReport;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ERPCore.Controllers;
/// <summary>
/// Day-end owner report settings (docs/DAY_END_OWNER_REPORT_PLAN.md §3.5) — recipients, schedule,
/// preview, "send test now" and the send log. Gated on the Settings nav policy like the other
/// configuration screens (BranchConfigController, GlAccountConfigController).
/// </summary>
[Route("api/v1/settings/owner-report")]
[Authorize(Policy = NavPolicies.Prefix + "settings")]
public sealed class OwnerReportController : ApiControllerBase
{
// One test send per minute (whole deployment) so the button can't be used to spam the owner.
private static readonly SemaphoreSlim TestGate = new(1, 1);
private static DateTime _lastTestUtc = DateTime.MinValue;
private readonly IOwnerReportService _service;
public OwnerReportController(IOwnerReportService service) => _service = service;
[HttpGet]
[ProducesResponseType(typeof(OwnerReportConfigDto), StatusCodes.Status200OK)]
public async Task<ActionResult<OwnerReportConfigDto>> Get(CancellationToken ct)
{
var result = await _service.GetAsync(ct);
SetETag(result.RowVersion);
return Ok(result.Value);
}
[HttpPut]
[ProducesResponseType(typeof(OwnerReportConfigDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
public async Task<ActionResult<OwnerReportConfigDto>> Update([FromBody] UpdateOwnerReportConfigRequest request, CancellationToken ct)
{
var result = await _service.UpdateAsync(request, RequireIfMatch(), ct);
SetETag(result.RowVersion);
return Ok(result.Value);
}
[HttpGet("preview")]
[ProducesResponseType(typeof(OwnerReportPreviewDto), StatusCodes.Status200OK)]
public async Task<ActionResult<OwnerReportPreviewDto>> Preview([FromQuery] DateOnly? date, CancellationToken ct)
=> Ok(await _service.PreviewAsync(date, ct));
[HttpPost("test")]
[ProducesResponseType(typeof(IReadOnlyList<OwnerReportSendResultDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status429TooManyRequests)]
public async Task<ActionResult<IReadOnlyList<OwnerReportSendResultDto>>> SendTest([FromBody] OwnerReportTestRequest? request, CancellationToken ct)
{
await TestGate.WaitAsync(ct);
try
{
if (DateTime.UtcNow - _lastTestUtc < TimeSpan.FromMinutes(1))
return StatusCode(StatusCodes.Status429TooManyRequests,
new { title = "A test was sent less than a minute ago — wait a moment before sending another." });
_lastTestUtc = DateTime.UtcNow;
}
finally
{
TestGate.Release();
}
return Ok(await _service.SendTestAsync(request?.Date, ct));
}
[HttpGet("dispatches")]
[ProducesResponseType(typeof(PagedResponse<OwnerReportDispatchDto>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedResponse<OwnerReportDispatchDto>>> Dispatches([FromQuery] PageQuery query, CancellationToken ct)
=> Ok(await _service.ListDispatchesAsync(query, ct));
}