using System.Security.Claims; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Nashel.Modules.Order.Application.Commands; using Nashel.Modules.Order.Application.Queries; using Nashel.Modules.Order.Domain.Entities; using Nashel.Modules.Order.Domain.Enums; namespace Nashel.Modules.Order.Presentation; public static class OrderEndpoints { public static void MapOrderEndpoints(this IEndpointRouteBuilder app) { // ─── Создание заказа (временно без авторизации) ───────────────────────────── // AllowAnonymous не перекрывает RequireAuthorization на группе в Minimal API, // поэтому создание вынесено в отдельную группу. app.MapPost("/api/orders", async ([FromBody] CreateOrderRequest request, ISender sender) => { var command = new CreateOrderCommand( request.CustomerId, request.CustomerName, request.ServiceId, request.ServiceTitle, request.PriceAmount, request.PriceType, request.Type, request.Address, request.Latitude, request.Longitude, request.Deadline, request.PerformerId, request.PerformerName ); var orderId = await sender.Send(command); return Results.Ok(orderId); }) .WithTags("Заказы") .WithName("CreateOrder") .WithOpenApi(op => new(op) { Summary = "Создать заказ", Description = "Создаёт прямой заказ (конкретному мастеру) или публичную заявку." }) .AllowAnonymous(); var group = app.MapGroup("/api/orders") .WithTags("Заказы") .RequireAuthorization(); // ─── Получить мои заказы (как заказчик) ───────────────────────────────────── group.MapGet("/as-customer/{userId:guid}", async (Guid userId, ISender sender) => { var result = await sender.Send(new GetMyOrdersAsCustomerQuery(userId)); return Results.Ok(result); }) .WithName("GetMyOrdersAsCustomer") .WithOpenApi(op => new(op) { Summary = "Мои заказы (роль: Заказчик)", Description = "Возвращает список всех заказов, где текущий пользователь является заказчиком, отсортированных по дате создания." }); // ─── Получить мои заказы (как исполнитель) ─────────────────────────────────── group.MapGet("/as-performer/{userId:guid}", async (Guid userId, ISender sender) => { var result = await sender.Send(new GetMyOrdersAsPerformerQuery(userId)); return Results.Ok(result); }) .WithName("GetMyOrdersAsPerformer") .WithOpenApi(op => new(op) { Summary = "Мои заказы (роль: Исполнитель)", Description = "Возвращает список всех заказов, где текущий пользователь является назначенным исполнителем." }); // ─── Принять заказ (мастер) ────────────────────────────────────────────────── group.MapPost("/{id:guid}/accept", async (Guid id, ISender sender) => { var command = new AcceptOrderCommand(id); await sender.Send(command); return Results.Ok(); }) .WithName("AcceptOrder") .WithOpenApi(op => new(op) { Summary = "Принять заказ", Description = "Исполнитель принимает заказ. Переводит заказ в статус «В работе»." }); // ─── Пометить выполненным (мастер) ────────────────────────────────────────── group.MapPost("/{id:guid}/finish", async (Guid id, FinishOrderRequest request, ISender sender) => { var command = new FinishOrderCommand(id, request.PerformerId); await sender.Send(command); return Results.Ok(); }) .WithName("FinishOrder") .WithOpenApi(op => new(op) { Summary = "Завершить работу (мастер)", Description = "Исполнитель помечает работу как выполненную. Заказ переходит в статус подтверждения заказчиком." }); // ─── Подтвердить выполнение (заказчик) ─────────────────────────────────────── group.MapPost("/{id:guid}/confirm", async (Guid id, ConfirmOrderCompletionRequest request, ISender sender) => { var command = new ConfirmOrderCompletionCommand(id, request.CustomerId); await sender.Send(command); return Results.Ok(); }) .WithName("ConfirmOrderCompletion") .WithOpenApi(op => new(op) { Summary = "Подтвердить выполнение (заказчик)", Description = "Заказчик подтверждает, что работа выполнена успешно. Статус заказа становится «Завершён»." }); // ─── Открыть спор (заказчик) ────────────────────────────────────────────────── group.MapPost("/{id:guid}/dispute", async (Guid id, OpenDisputeRequest request, ISender sender) => { var command = new OpenOrderDisputeCommand( id, request.CustomerId, request.Reason, request.Description, request.ProposedSolution, request.Evidence); await sender.Send(command); return Results.Ok(); }) .WithName("OpenOrderDispute") .WithOpenApi(op => new(op) { Summary = "Открыть спор", Description = "Заказчик открывает спор, если работа выполнена некачественно или не в полном объеме." }); // ─── Ответить на спор (мастер) ─────────────────────────────────────────────── group.MapPost("/{id:guid}/dispute/respond", async (Guid id, RespondToDisputeRequest request, ISender sender) => { var command = new RespondToOrderDisputeCommand( id, request.PerformerId, request.Position, request.Evidence, request.CounterSolution); await sender.Send(command); return Results.Ok(); }) .WithName("RespondToDispute") .WithOpenApi(op => new(op) { Summary = "Ответить на спор", Description = "Исполнитель предоставляет свою позицию и доказательства в рамках открытого спора." }); // ─── Возразить на ответ исполнителя (заказчик) ────────────────────────────── group.MapPost("/{id:guid}/dispute/rebut", async (Guid id, RebutDisputeRequest request, ISender sender) => { var command = new RebutDisputeCommand( id, request.CustomerId, request.Comment, request.Evidence, request.CounterSolution); await sender.Send(command); return Results.Ok(); }) .WithName("RebutDispute") .WithOpenApi(op => new(op) { Summary = "Возразить на ответ исполнителя", Description = "Заказчик добавляет новые доказательства или требования в ответ на позицию исполнителя." }); // ─── Принять условия спора (заказчик) ────────────────────────────────────── group.MapPost("/{id:guid}/dispute/accept", async (Guid id, AcceptDisputeTermsRequest request, ISender sender) => { var command = new AcceptDisputeTermsCommand(id, request.CustomerId); await sender.Send(command); return Results.Ok(); }) .WithName("AcceptDisputeTerms") .WithOpenApi(op => new(op) { Summary = "Принять условия спора", Description = "Заказчик принимает предложенное исполнителем решение. Заказ завершается." }); // ─── Отменить заказ ────────────────────────────────────────────────────────── group.MapPost("/{id:guid}/cancel", async (Guid id, CancelOrderRequest request, ISender sender) => { var command = new CancelOrderCommand(id, request.RequesterId, request.Reason); await sender.Send(command); return Results.Ok(); }) .WithName("CancelOrder") .WithOpenApi(op => new(op) { Summary = "Отменить заказ", Description = "Заказчик или исполнитель отменяет заказ с указанием причины." }); // ─── Добавить отклик (мастер на публичный заказ) ───────────────────────────── group.MapPost("/{id:guid}/applications", async (Guid id, AddApplicationRequest request, ISender sender) => { var command = new AddApplicationCommand(id, request.PerformerId, request.Amount, request.Currency, request.Comment); await sender.Send(command); return Results.Ok(); }) .WithName("AddOrderApplication") .WithOpenApi(op => new(op) { Summary = "Откликнуться на заказ", Description = "Исполнитель отправляет отклик на публичный заказ с указанием цены и комментария." }); // ─── Выбрать исполнителя из откликов (заказчик) ───────────────────────────── group.MapPost("/{id:guid}/select-performer", async (Guid id, SelectPerformerRequest request, ISender sender) => { var command = new SelectPerformerCommand(id, request.PerformerId); await sender.Send(command); return Results.Ok(); }) .WithName("SelectPerformer") .WithOpenApi(op => new(op) { Summary = "Выбрать исполнителя", Description = "Заказчик выбирает конкретного исполнителя из откликнувшихся. Запускает SLA-таймер 60 минут." }); } } // ─── Request DTOs ──────────────────────────────────────────────────────────── /// /// Запрос на создание заказа. /// public record CreateOrderRequest { /// Идентификатор заказчика. public Guid CustomerId { get; init; } /// ФИО заказчика. public string CustomerName { get; init; } = default!; /// Идентификатор услуги. public Guid ServiceId { get; init; } /// Название услуги. public string ServiceTitle { get; init; } = default!; /// Сумма услуги. public decimal PriceAmount { get; init; } /// Тип цены (0=Фикс, 1=Почасовая, 2=Договорная). public PriceType PriceType { get; init; } /// Тип заказа: Direct (прямой) или PublicJob (публичный). public OrderType Type { get; init; } /// Адрес выполнения заказа (текстовый). public string Address { get; init; } = default!; /// Широта места выполнения. public double Latitude { get; init; } /// Долгота места выполнения. public double Longitude { get; init; } /// Крайний срок выполнения (необязательно). public DateTime? Deadline { get; init; } /// Идентификатор исполнителя (обязателен для прямого заказа). public Guid? PerformerId { get; init; } /// Имя исполнителя (обязателен для прямого заказа). public string? PerformerName { get; init; } public CreateOrderRequest() { } } /// /// Запрос принятия заказа исполнителем (Пустой, так как ID берется из токена). /// public record AcceptOrderRequest(); /// /// Запрос завершения заказа. /// public record CompleteOrderRequest { /// Идентификатор пользователя (заказчик или исполнитель). public Guid RequesterId { get; init; } public CompleteOrderRequest() { } } /// /// Запрос отмены заказа. /// public record CancelOrderRequest { /// Идентификатор пользователя, инициирующего отмену. public Guid RequesterId { get; init; } /// Причина отмены (необязательно). public string? Reason { get; init; } public CancelOrderRequest() { } } /// /// Запрос добавления отклика на заказ. /// public record AddApplicationRequest { /// Идентификатор исполнителя. public Guid PerformerId { get; init; } /// Предлагаемая сумма. public decimal Amount { get; init; } /// Валюта (например, RUB). public string Currency { get; init; } = "RUB"; /// Комментарий к отклику. public string Comment { get; init; } = default!; public AddApplicationRequest() { } } /// /// Запрос выбора исполнителя. /// public record SelectPerformerRequest { /// Идентификатор выбранного исполнителя. public Guid PerformerId { get; init; } public SelectPerformerRequest() { } } /// /// Запрос пометки выполнения заказа. /// public record FinishOrderRequest(Guid PerformerId); /// /// Запрос подтверждения выполнения. /// public record ConfirmOrderCompletionRequest(Guid CustomerId); /// /// Запрос открытия спора. /// public record OpenDisputeRequest( Guid CustomerId, DisputeReason Reason, string Description, string ProposedSolution, List Evidence); /// /// Запрос ответа на спор. /// public record RespondToDisputeRequest( Guid PerformerId, string Position, List Evidence, string? CounterSolution); /// /// Запрос на возражение по спору. /// public record RebutDisputeRequest( Guid CustomerId, string Comment, List Evidence, string? CounterSolution); /// /// Запрос на принятие условий спора. /// public record AcceptDisputeTermsRequest(Guid CustomerId);