267 lines
12 KiB
C#
267 lines
12 KiB
C#
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.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, AcceptOrderRequest request, ISender sender) =>
|
||
{
|
||
var command = new AcceptOrderCommand(id, request.PerformerId);
|
||
await sender.Send(command);
|
||
return Results.Ok();
|
||
})
|
||
.WithName("AcceptOrder")
|
||
.WithOpenApi(op => new(op)
|
||
{
|
||
Summary = "Принять заказ",
|
||
Description = "Исполнитель принимает заказ. Переводит заказ в статус «В работе»."
|
||
});
|
||
|
||
// ─── Завершить заказ ─────────────────────────────────────────────────────────
|
||
group.MapPost("/{id:guid}/complete", async (Guid id, CompleteOrderRequest request, ISender sender) =>
|
||
{
|
||
var command = new CompleteOrderCommand(id, request.RequesterId);
|
||
await sender.Send(command);
|
||
return Results.Ok();
|
||
})
|
||
.WithName("CompleteOrder")
|
||
.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 ────────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// Запрос на создание заказа.
|
||
/// </summary>
|
||
public record CreateOrderRequest
|
||
{
|
||
/// <summary>Идентификатор заказчика.</summary>
|
||
public Guid CustomerId { get; init; }
|
||
|
||
/// <summary>ФИО заказчика.</summary>
|
||
public string CustomerName { get; init; } = default!;
|
||
|
||
/// <summary>Идентификатор услуги.</summary>
|
||
public Guid ServiceId { get; init; }
|
||
|
||
/// <summary>Название услуги.</summary>
|
||
public string ServiceTitle { get; init; } = default!;
|
||
|
||
/// <summary>Сумма услуги.</summary>
|
||
public decimal PriceAmount { get; init; }
|
||
|
||
/// <summary>Тип цены (0=Фикс, 1=Почасовая, 2=Договорная).</summary>
|
||
public PriceType PriceType { get; init; }
|
||
|
||
/// <summary>Тип заказа: Direct (прямой) или PublicJob (публичный).</summary>
|
||
public OrderType Type { get; init; }
|
||
|
||
/// <summary>Адрес выполнения заказа (текстовый).</summary>
|
||
public string Address { get; init; } = default!;
|
||
|
||
/// <summary>Широта места выполнения.</summary>
|
||
public double Latitude { get; init; }
|
||
|
||
/// <summary>Долгота места выполнения.</summary>
|
||
public double Longitude { get; init; }
|
||
|
||
/// <summary>Крайний срок выполнения (необязательно).</summary>
|
||
public DateTime? Deadline { get; init; }
|
||
|
||
/// <summary>Идентификатор исполнителя (обязателен для прямого заказа).</summary>
|
||
public Guid? PerformerId { get; init; }
|
||
|
||
/// <summary>Имя исполнителя (обязателен для прямого заказа).</summary>
|
||
public string? PerformerName { get; init; }
|
||
|
||
public CreateOrderRequest() { }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Запрос принятия заказа исполнителем.
|
||
/// </summary>
|
||
public record AcceptOrderRequest
|
||
{
|
||
/// <summary>Идентификатор исполнителя, принимающего заказ.</summary>
|
||
public Guid PerformerId { get; init; }
|
||
|
||
public AcceptOrderRequest() { }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Запрос завершения заказа.
|
||
/// </summary>
|
||
public record CompleteOrderRequest
|
||
{
|
||
/// <summary>Идентификатор пользователя (заказчик или исполнитель).</summary>
|
||
public Guid RequesterId { get; init; }
|
||
|
||
public CompleteOrderRequest() { }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Запрос отмены заказа.
|
||
/// </summary>
|
||
public record CancelOrderRequest
|
||
{
|
||
/// <summary>Идентификатор пользователя, инициирующего отмену.</summary>
|
||
public Guid RequesterId { get; init; }
|
||
|
||
/// <summary>Причина отмены (необязательно).</summary>
|
||
public string? Reason { get; init; }
|
||
|
||
public CancelOrderRequest() { }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Запрос добавления отклика на заказ.
|
||
/// </summary>
|
||
public record AddApplicationRequest
|
||
{
|
||
/// <summary>Идентификатор исполнителя.</summary>
|
||
public Guid PerformerId { get; init; }
|
||
|
||
/// <summary>Предлагаемая сумма.</summary>
|
||
public decimal Amount { get; init; }
|
||
|
||
/// <summary>Валюта (например, RUB).</summary>
|
||
public string Currency { get; init; } = "RUB";
|
||
|
||
/// <summary>Комментарий к отклику.</summary>
|
||
public string Comment { get; init; } = default!;
|
||
|
||
public AddApplicationRequest() { }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Запрос выбора исполнителя.
|
||
/// </summary>
|
||
public record SelectPerformerRequest
|
||
{
|
||
/// <summary>Идентификатор выбранного исполнителя.</summary>
|
||
public Guid PerformerId { get; init; }
|
||
|
||
public SelectPerformerRequest() { }
|
||
}
|