Инициализировать репозиторий «Дейл»

Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы
ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ,
инструкция пользователя, техдокументация, код-стайл), бэклог,
скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
Rustam Khalimov
2026-09-11 02:50:17 +03:00
commit 9e07568ddd
1402 changed files with 177470 additions and 0 deletions
@@ -0,0 +1,80 @@
using Deal.Modules.Kanban.Application.Models;
using Deal.Modules.Settings.Application;
namespace Deal.Modules.Kanban.Application.ColumnRules;
/// <summary>
/// Проверка попадания распознанных сумм в бюджетный диапазон правил колонки (rules.py _amount_in_range L147–173).
/// </summary>
/// <remarks>
/// «Интерфейс курсов» — чистый: сравнение в одной валюте не требует курсов, при разных валютах суммы
/// конвертируются по словарю «код → курс к рублю» через чистую <see cref="RatesService.ConvertAmount"/>
/// (Settings, rates.py L86–103): USDT приравнивается к USD, отсутствующая валюта → сумма пропускается.
/// Курсы — параметр функции: чтение кэша ratesCache (Ruling 7) остаётся за вызывающим (CardsService и
/// др.), модуль и функция не ходят в БД. Границы из null → диапазон без ограничений (True), как прототип.
/// </remarks>
public static class BudgetInRange
{
/// <summary>
/// Попадает ли хотя бы одна распознанная сумма в диапазон правил (rules.py L147–173).
/// </summary>
/// <param name="amounts">Суммы из текста (<see cref="AmountParser.Parse"/>).</param>
/// <param name="budget">Бюджет правил колонки (валюта пуста → USD, как в прототипе L157).</param>
/// <param name="rates">Курсы к рублю «код → курс» либо null (конвертация невозможна).</param>
/// <returns>
/// True — есть сумма, которая после конвертации (при необходимости) ≥ from и ≤ to; обе границы null → True;
/// пустые amounts или непереводимая валюта → False.
/// </returns>
public static bool IsInRange(IReadOnlyList<AmountRange> amounts, BudgetRangeDto budget, IReadOnlyDictionary<string, double>? rates)
{
double? lo = budget.From;
double? hi = budget.To;
if (lo is null && hi is null)
{
return true;
}
string targetCurrency = string.IsNullOrWhiteSpace(budget.Cur) ? "USD" : budget.Cur.ToUpperInvariant();
foreach (AmountRange amount in amounts)
{
double? rawValue = amount.From ?? amount.To;
if (rawValue is null)
{
continue;
}
double? value;
if (string.Equals(amount.Cur, targetCurrency, StringComparison.OrdinalIgnoreCase))
{
value = rawValue;
}
else if (rates is null)
{
continue;
}
else
{
value = RatesService.ConvertAmount(rawValue, amount.Cur, targetCurrency, rates);
}
if (value is null)
{
continue;
}
if (lo is not null && value < lo)
{
continue;
}
if (hi is not null && value > hi)
{
continue;
}
return true;
}
return false;
}
}