Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
368 changes: 368 additions & 0 deletions PLAN.md

Large diffs are not rendered by default.

548 changes: 548 additions & 0 deletions WARP.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using MeAjudaAi.Modules.Providers.API.Mappers;
using MeAjudaAi.Modules.Providers.Application.Commands;
using MeAjudaAi.Modules.Providers.Application.DTOs.Requests;
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Endpoints;
using MeAjudaAi.Shared.Functional;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;

namespace MeAjudaAi.Modules.Providers.API.Endpoints.ProviderAdmin;

/// <summary>
/// Endpoint responsável por solicitar correção de informações básicas de prestadores.
/// </summary>
/// <remarks>
/// Implementa padrão de endpoint mínimo para retornar prestadores da etapa de verificação
/// de documentos para correção de informações básicas utilizando arquitetura CQRS.
/// Restrito a administradores e verificadores devido à criticidade da operação.
/// </remarks>
public class RequireBasicInfoCorrectionEndpoint : BaseEndpoint, IEndpoint
{
/// <summary>
/// Configura o mapeamento do endpoint de solicitação de correção.
/// </summary>
/// <param name="app">Builder de rotas do endpoint</param>
/// <remarks>
/// Configura endpoint POST em "/{id:guid}/require-basic-info-correction" com:
/// - Autorização AdminOnly (apenas administradores/verificadores podem solicitar correções)
/// - Validação automática de GUID para o parâmetro ID
/// - Documentação OpenAPI automática
/// - Códigos de resposta apropriados
/// - Nome único para referência
/// </remarks>
public static void Map(IEndpointRouteBuilder app)
=> app.MapPost("/{id:guid}/require-basic-info-correction", RequireBasicInfoCorrectionAsync)
.WithName("RequireBasicInfoCorrection")
.WithSummary("Solicitar correção de informações básicas")
.WithDescription("""
Retorna um prestador de serviços para correção de informações básicas
durante o processo de verificação de documentos.

**🔒 Acesso Restrito: Apenas Administradores/Verificadores**

**Quando usar:**
- Informações básicas incorretas ou incompletas
- Inconsistências identificadas durante verificação de documentos
- Dados empresariais que precisam ser atualizados
- Informações de contato inválidas

**Características:**
- 🔄 Retorna prestador para status PendingBasicInfo
- 📧 Notificação automática ao prestador (futuro)
- 📋 Auditoria completa da solicitação
- ⚖️ Motivo obrigatório para rastreabilidade
- 🔐 Identificação do solicitante extraída da autenticação

**Fluxo após correção:**
1. Prestador recebe notificação com motivo da correção
2. Prestador atualiza informações básicas
3. Prestador conclui informações básicas novamente
4. Sistema retorna para verificação de documentos

**Campos obrigatórios no request body:**
- Reason: Motivo detalhado da correção necessária

**Campos derivados do servidor:**
- RequestedBy: Extraído automaticamente do contexto de autenticação (claims: name, sub ou email)

**Validações aplicadas:**
- Prestador em status PendingDocumentVerification
- Motivo não pode ser vazio
- Prestador existente e ativo
- Autorização administrativa verificada
- Identidade do solicitante autenticada
""")
.RequireAuthorization("AdminOnly")
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status404NotFound);

/// <summary>
/// Processa requisição de solicitação de correção de forma assíncrona.
/// </summary>
/// <param name="id">ID único do prestador</param>
/// <param name="request">Dados da solicitação de correção</param>
/// <param name="commandDispatcher">Dispatcher para envio de comandos CQRS</param>
/// <param name="httpContext">Contexto HTTP para obter o usuário autenticado</param>
/// <param name="cancellationToken">Token de cancelamento da operação</param>
/// <returns>
/// Resultado HTTP contendo:
/// - 200 OK: Correção solicitada com sucesso
/// - 400 Bad Request: Erro de validação ou solicitação
/// - 401 Unauthorized: Usuário não autenticado
/// - 404 Not Found: Prestador não encontrado
/// </returns>
/// <remarks>
/// Fluxo de execução:
/// 1. Valida ID do prestador e autorização
/// 2. Extrai identidade do usuário autenticado do contexto HTTP
/// 3. Converte request em comando CQRS com identidade verificada
/// 4. Envia comando através do dispatcher
/// 5. Processa resultado e retorna confirmação
/// 6. Emite evento de domínio para notificação
/// </remarks>
private static async Task<IResult> RequireBasicInfoCorrectionAsync(
Guid id,
[FromBody] RequireBasicInfoCorrectionRequest request,
ICommandDispatcher commandDispatcher,
HttpContext httpContext,
CancellationToken cancellationToken)
{
if (request is null)
return Results.BadRequest("Request body is required");

// Extrai a identidade do usuário autenticado do contexto HTTP
var requestedBy = httpContext.User.Identity?.Name
?? httpContext.User.FindFirst("sub")?.Value
?? httpContext.User.FindFirst("email")?.Value
?? "system";

var command = request.ToCommand(id, requestedBy);
var result = await commandDispatcher.SendAsync<RequireBasicInfoCorrectionCommand, Result>(
command, cancellationToken);

return Handle(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public static void MapProvidersEndpoints(this WebApplication app)
.MapEndpoint<AddDocumentEndpoint>()
.MapEndpoint<RemoveDocumentEndpoint>()
.MapEndpoint<UpdateVerificationStatusEndpoint>()
.MapEndpoint<RequireBasicInfoCorrectionEndpoint>()
.MapEndpoint<DeleteProviderEndpoint>();
}
}
16 changes: 16 additions & 0 deletions src/Modules/Providers/API/Mappers/RequestMapperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ public static UpdateVerificationStatusCommand ToCommand(this UpdateVerificationS
);
}

/// <summary>
/// Mapeia RequireBasicInfoCorrectionRequest para RequireBasicInfoCorrectionCommand.
/// </summary>
/// <param name="request">Requisição de correção de informações básicas</param>
/// <param name="providerId">ID do prestador</param>
/// <param name="requestedBy">Identificador autenticado do administrador/verificador solicitando a correção</param>
/// <returns>RequireBasicInfoCorrectionCommand com propriedades mapeadas</returns>
public static RequireBasicInfoCorrectionCommand ToCommand(this RequireBasicInfoCorrectionRequest request, Guid providerId, string requestedBy)
{
return new RequireBasicInfoCorrectionCommand(
providerId,
request.Reason,
requestedBy
);
}

/// <summary>
/// Mapeia o ID do prestador para GetProviderByIdQuery.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Functional;

namespace MeAjudaAi.Modules.Providers.Application.Commands;

/// <summary>
/// Comando para ativar um prestador de serviços após verificação bem-sucedida de documentos.
/// </summary>
/// <param name="ProviderId">Identificador do prestador de serviços</param>
/// <param name="ActivatedBy">Quem está executando a ativação</param>
public sealed record ActivateProviderCommand(
Guid ProviderId,
string? ActivatedBy = null
) : Command<Result>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Functional;

namespace MeAjudaAi.Modules.Providers.Application.Commands;

/// <summary>
/// Comando para completar o preenchimento de informações básicas e avançar
/// para a etapa de verificação de documentos.
/// </summary>
/// <param name="ProviderId">Identificador do prestador de serviços</param>
/// <param name="UpdatedBy">Quem está executando a atualização</param>
public sealed record CompleteBasicInfoCommand(
Guid ProviderId,
string? UpdatedBy = null
) : Command<Result>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Functional;

namespace MeAjudaAi.Modules.Providers.Application.Commands;

/// <summary>
/// Comando para rejeitar o registro de um prestador de serviços.
/// </summary>
/// <param name="ProviderId">Identificador do prestador de serviços</param>
/// <param name="RejectedBy">Quem está executando a rejeição</param>
/// <param name="Reason">Motivo da rejeição (obrigatório para auditoria)</param>
public sealed record RejectProviderCommand(
Guid ProviderId,
string RejectedBy,
string Reason
) : Command<Result>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Functional;

namespace MeAjudaAi.Modules.Providers.Application.Commands;

/// <summary>
/// Comando para retornar um prestador de serviços para correção de informações básicas
/// durante o processo de verificação de documentos.
/// </summary>
/// <param name="ProviderId">Identificador do prestador de serviços</param>
/// <param name="Reason">Motivo da correção necessária (obrigatório para auditoria e notificação)</param>
/// <param name="RequestedBy">Quem está solicitando a correção (verificador/administrador)</param>
public sealed record RequireBasicInfoCorrectionCommand(
Guid ProviderId,
string Reason,
string RequestedBy
) : Command<Result>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Functional;

namespace MeAjudaAi.Modules.Providers.Application.Commands;

/// <summary>
/// Comando para suspender um prestador de serviços.
/// </summary>
/// <param name="ProviderId">Identificador do prestador de serviços</param>
/// <param name="SuspendedBy">Quem está executando a suspensão</param>
/// <param name="Reason">Motivo da suspensão (obrigatório para auditoria)</param>
public sealed record SuspendProviderCommand(
Guid ProviderId,
string SuspendedBy,
string Reason
) : Command<Result>;
5 changes: 4 additions & 1 deletion src/Modules/Providers/Application/DTOs/ProviderDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ public sealed record ProviderDto(
string Name,
EProviderType Type,
BusinessProfileDto BusinessProfile,
EProviderStatus Status,
EVerificationStatus VerificationStatus,
IReadOnlyList<DocumentDto> Documents,
IReadOnlyList<QualificationDto> Qualifications,
DateTime CreatedAt,
DateTime? UpdatedAt,
bool IsDeleted,
DateTime? DeletedAt
DateTime? DeletedAt,
string? SuspensionReason = null,
string? RejectionReason = null
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using MeAjudaAi.Shared.Contracts;

namespace MeAjudaAi.Modules.Providers.Application.DTOs.Requests;

/// <summary>
/// Request para solicitar correção de informações básicas de um prestador de serviços.
/// </summary>
public record RequireBasicInfoCorrectionRequest : Request
{
/// <summary>
/// Motivo detalhado da correção necessária (obrigatório).
/// </summary>
/// <remarks>
/// Este campo será enviado ao prestador para que ele saiba quais informações
/// precisam ser corrigidas ou complementadas.
/// </remarks>
public string Reason { get; init; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using MeAjudaAi.Modules.Providers.Application.Commands;
using MeAjudaAi.Modules.Providers.Domain.Repositories;
using MeAjudaAi.Modules.Providers.Domain.ValueObjects;
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Functional;
using Microsoft.Extensions.Logging;

namespace MeAjudaAi.Modules.Providers.Application.Handlers.Commands;

/// <summary>
/// Handler responsável por processar comandos de ativação de prestadores.
/// </summary>
/// <remarks>
/// Este handler ativa um prestador após a verificação bem-sucedida dos documentos,
/// permitindo que ele comece a oferecer serviços na plataforma.
/// </remarks>
/// <param name="providerRepository">Repositório para persistência de prestadores de serviços</param>
/// <param name="logger">Logger estruturado para auditoria e debugging</param>
public sealed class ActivateProviderCommandHandler(
IProviderRepository providerRepository,
ILogger<ActivateProviderCommandHandler> logger
) : ICommandHandler<ActivateProviderCommand, Result>
{
/// <summary>
/// Processa o comando de ativação de prestador.
/// </summary>
/// <param name="command">Comando de ativação</param>
/// <param name="cancellationToken">Token de cancelamento</param>
/// <returns>Resultado da operação</returns>
public async Task<Result> HandleAsync(ActivateProviderCommand command, CancellationToken cancellationToken)
{
try
{
logger.LogInformation("Activating provider {ProviderId}", command.ProviderId);

var provider = await providerRepository.GetByIdAsync(new ProviderId(command.ProviderId), cancellationToken);
if (provider == null)
{
logger.LogWarning("Provider {ProviderId} not found", command.ProviderId);
return Result.Failure("Provider not found");
}

provider.Activate(command.ActivatedBy);

await providerRepository.UpdateAsync(provider, cancellationToken);

logger.LogInformation("Provider {ProviderId} activated successfully", command.ProviderId);
return Result.Success();
}
catch (Exception ex)
{
logger.LogError(ex, "Error activating provider {ProviderId}", command.ProviderId);
return Result.Failure("Failed to activate provider");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using MeAjudaAi.Modules.Providers.Application.Commands;
using MeAjudaAi.Modules.Providers.Domain.Repositories;
using MeAjudaAi.Modules.Providers.Domain.ValueObjects;
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Functional;
using Microsoft.Extensions.Logging;

namespace MeAjudaAi.Modules.Providers.Application.Handlers.Commands;

/// <summary>
/// Handler responsável por processar comandos de conclusão de informações básicas.
/// </summary>
/// <remarks>
/// Este handler move o prestador da etapa de PendingBasicInfo para PendingDocumentVerification,
/// indicando que as informações básicas foram preenchidas e o próximo passo é o envio de documentos.
/// </remarks>
/// <param name="providerRepository">Repositório para persistência de prestadores de serviços</param>
/// <param name="logger">Logger estruturado para auditoria e debugging</param>
public sealed class CompleteBasicInfoCommandHandler(
IProviderRepository providerRepository,
ILogger<CompleteBasicInfoCommandHandler> logger
) : ICommandHandler<CompleteBasicInfoCommand, Result>
{
/// <summary>
/// Processa o comando de conclusão de informações básicas.
/// </summary>
/// <param name="command">Comando de conclusão</param>
/// <param name="cancellationToken">Token de cancelamento</param>
/// <returns>Resultado da operação</returns>
public async Task<Result> HandleAsync(CompleteBasicInfoCommand command, CancellationToken cancellationToken)
{
try
{
logger.LogInformation("Completing basic info for provider {ProviderId}", command.ProviderId);

var provider = await providerRepository.GetByIdAsync(new ProviderId(command.ProviderId), cancellationToken);
if (provider == null)
{
logger.LogWarning("Provider {ProviderId} not found", command.ProviderId);
return Result.Failure("Provider not found");
}

provider.CompleteBasicInfo(command.UpdatedBy);

await providerRepository.UpdateAsync(provider, cancellationToken);

logger.LogInformation("Basic info completed for provider {ProviderId}", command.ProviderId);
return Result.Success();
}
catch (Exception ex)
{
logger.LogError(ex, "Error completing basic info for provider {ProviderId}", command.ProviderId);
return Result.Failure("Failed to complete provider basic info");
}
}
}
Loading
Loading