Разбить модули Deal.Modules.* по назначению

Application проектов Discovery, Kanban, Pipeline, Settings, Tenants
разделён на Abstractions/Exceptions/Extensions/Models/Registrars/Services;
namespace приведён к путям, using потребителей мигрированы и
дедуплицированы (169 файлов), cref/FQN обновлены.
This commit is contained in:
Rustam Khalimov
2026-09-11 13:18:14 +03:00
parent 31c434ed93
commit cd0b3b606b
356 changed files with 14436 additions and 12906 deletions
@@ -0,0 +1,89 @@
using Deal.Modules.Settings.Application.Abstractions;
using Deal.Modules.Settings.Application.Models;
using Deal.Modules.Settings.Application.Registrars;
using Deal.Modules.Settings.Application.Services;
using Deal.Modules.Discovery.Application.Abstractions;
using Deal.Modules.Discovery.Application.Exceptions;
using Deal.Modules.Discovery.Application.Extensions;
using Deal.Modules.Discovery.Application.Models;
using Deal.Modules.Discovery.Application.Registrars;
namespace Deal.Modules.Discovery.Application.Services;
/// <summary>
/// План-бюджет авто-вступлений Discovery: лимит и правило суммы (python L80111, Ruling 9).
/// </summary>
/// <remarks>
/// Правило 1:1 с discovery.py: суточный лимит discJoinLimit (настройка, дефолт 50 — SettingsDefaults) — верхняя
/// граница plan_joins одной задачи (<see cref="AssertPlanAsync"/>); сумма plan_joins активных задач (status NOT IN
/// done/failed) плюс новая/увеличиваемая задача не должна превышать лимит (<see cref="AssertBudgetAsync"/>).
/// Итог семантики: задача на 50 занимает весь бюджет (другую создать нельзя); задача на 25 оставляет остаток
/// 25 (следующая может быть не больше 25). Тексты 400 — 1:1 с python (L97110).
/// </remarks>
public sealed class DiscoveryPlanGuard(IDiscoveryStore store, ISettingsStore settings)
{
/// <summary>
/// 400: план меньше 1 (python L97 «plan_joins должен быть не меньше 1»).
/// </summary>
public const string PlanTooSmallDetail = "plan_joins должен быть не меньше 1";
/// <summary>
/// 400: план больше суточного лимита (python L100). Формат: {plan}, {limit}.
/// </summary>
public const string PlanTooBigFormat = "plan_joins {0} больше суточного лимита авто-вступлений ({1})";
/// <summary>
/// 400: бюджет исчерпан (python L108110). Формат: {used}, {limit}, {plan}.
/// </summary>
public const string BudgetExceededFormat =
"Бюджет авто-вступлений исчерпан: задачи уже занимают {0} из {1} в сутки, ещё {2} не влезает";
/// <summary>
/// Верхняя граница plan_joins: суточный лимит авто-вступлений (python _plan_limit L8082).
/// </summary>
/// <param name="ct">Токен отмены.</param>
/// <returns>discJoinLimit настройки (дефолт 50) с нижней границей 1.</returns>
public async Task<int> PlanLimitAsync(CancellationToken ct)
{
int limit = (await TenantSettingsSnapshot.LoadAsync(settings, ct))
.GetInt(SettingsKeys.DiscJoinLimit, SettingsDefaults.DiscJoinLimit);
return Math.Max(1, limit);
}
/// <summary>
/// Проверяет план новой задачи: 1..discJoinLimit (python _assert_plan L94100).
/// </summary>
/// <param name="planJoins">Запрашиваемый план авто-вступлений.</param>
/// <param name="ct">Токен отмены.</param>
/// <exception cref="DiscoveryValidationException">План меньше 1 или больше суточного лимита (тексты python).</exception>
public async Task AssertPlanAsync(int planJoins, CancellationToken ct)
{
if (planJoins < 1)
{
throw new DiscoveryValidationException(PlanTooSmallDetail);
}
int limit = await PlanLimitAsync(ct).ConfigureAwait(false);
if (planJoins > limit)
{
throw new DiscoveryValidationException(string.Format(PlanTooBigFormat, planJoins, limit));
}
}
/// <summary>
/// Проверяет бюджет: занято активными задачами + новая ≤ discJoinLimit (python _assert_budget L103111).
/// </summary>
/// <param name="planJoins">План создаваемой/итоговый план увеличиваемой задачи.</param>
/// <param name="excludeTaskId">Id задачи, исключаемой из занятого бюджета (patch-рост плана); null — создание.</param>
/// <param name="ct">Токен отмены.</param>
/// <exception cref="DiscoveryValidationException">Бюджет исчерпан (текст python L108110).</exception>
public async Task AssertBudgetAsync(int planJoins, string? excludeTaskId, CancellationToken ct)
{
int limit = await PlanLimitAsync(ct).ConfigureAwait(false);
int used = await store.SumActivePlanAsync(excludeTaskId, ct).ConfigureAwait(false);
if (used + planJoins > limit)
{
throw new DiscoveryValidationException(string.Format(BudgetExceededFormat, used, limit, planJoins));
}
}
}