Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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,115 @@
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

**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:**
- Reason: Motivo detalhado da correção necessária
- RequestedBy: Identificador do verificador/administrador

**Validações aplicadas:**
- Prestador em status PendingDocumentVerification
- Motivo não pode ser vazio
Comment thread
frigini marked this conversation as resolved.
Outdated
- Prestador existente e ativo
- Autorização administrativa
""")
.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="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
/// - 404 Not Found: Prestador não encontrado
/// </returns>
/// <remarks>
/// Fluxo de execução:
/// 1. Valida ID do prestador e autorização
/// 2. Converte request em comando CQRS
/// 3. Envia comando através do dispatcher
/// 4. Processa resultado e retorna confirmação
/// 5. Emite evento de domínio para notificação
/// </remarks>
private static async Task<IResult> RequireBasicInfoCorrectionAsync(
Guid id,
[FromBody] RequireBasicInfoCorrectionRequest request,
ICommandDispatcher commandDispatcher,
CancellationToken cancellationToken)
{
if (request is null)
return Results.BadRequest("Request body is required");

var command = request.ToCommand(id);
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>();
}
}
15 changes: 15 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,21 @@ 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>
/// <returns>RequireBasicInfoCorrectionCommand com propriedades mapeadas</returns>
public static RequireBasicInfoCorrectionCommand ToCommand(this RequireBasicInfoCorrectionRequest request, Guid providerId)
{
return new RequireBasicInfoCorrectionCommand(
providerId,
request.Reason,
request.RequestedBy
);
}
Comment thread
frigini marked this conversation as resolved.

/// <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>;
Comment thread
frigini marked this conversation as resolved.
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,23 @@
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;

/// <summary>
/// Identificador de quem está solicitando a correção (verificador/administrador).
/// </summary>
public string RequestedBy { get; init; } = string.Empty;
Comment thread
frigini marked this conversation as resolved.
Outdated
}
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");
}
Comment thread
frigini marked this conversation as resolved.
}
}
Loading
Loading