Files
nashel-backend/src/Modules/Order/Presentation/OrderEndpoints.cs

390 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 ────────────────────────────────────────────────────────────
/// <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>
/// Запрос принятия заказа исполнителем (Пустой, так как ID берется из токена).
/// </summary>
public record 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() { }
}
/// <summary>
/// Запрос пометки выполнения заказа.
/// </summary>
public record FinishOrderRequest(Guid PerformerId);
/// <summary>
/// Запрос подтверждения выполнения.
/// </summary>
public record ConfirmOrderCompletionRequest(Guid CustomerId);
/// <summary>
/// Запрос открытия спора.
/// </summary>
public record OpenDisputeRequest(
Guid CustomerId,
DisputeReason Reason,
string Description,
string ProposedSolution,
List<string> Evidence);
/// <summary>
/// Запрос ответа на спор.
/// </summary>
public record RespondToDisputeRequest(
Guid PerformerId,
string Position,
List<string> Evidence,
string? CounterSolution);
/// <summary>
/// Запрос на возражение по спору.
/// </summary>
public record RebutDisputeRequest(
Guid CustomerId,
string Comment,
List<string> Evidence,
string? CounterSolution);
/// <summary>
/// Запрос на принятие условий спора.
/// </summary>
public record AcceptDisputeTermsRequest(Guid CustomerId);